2
votes

I have a custom set and I want to use it in typed racket. I require it using require/typed with the #:opaque custom-set? instruction. It works, except that the code fails at run time when I call custom-set? with a syntax object.

I have something like the following:

#lang typed/racket/base

(module UNTYPED racket/base
  (require racket/set)
  (provide custom-set?
           make-immutable-custom-set)

  (define-custom-set-types custom-set
    #:elem? identifier?
    (λ (id1 id2) (eq? (syntax-e id1) (syntax-e id2)))))

(require/typed 'UNTYPED
  [#:opaque MySet custom-set?]
  [make-immutable-custom-set ((Listof Identifier) -> MySet)])

(custom-set? (make-immutable-custom-set (list #'foo #'bar)))  ;; #t
(custom-set? '())  ;; #f

Which type checks and returns #t or #f as expected.

Now, if I try to call the same custom-set? predicate with a syntax object:

(custom-set? #'(foo bar))

Then, I get the following contract violation instead of #f:

custom-set?: broke its own contract
  Attempted to use a higher-order value passed as `Any` in untyped code: #<syntax:stdin:: a>
  in: the 1st argument of
      a part of the or/c of
      (or/c
       struct-predicate-procedure?/c
       (-> Any boolean?))
  contract from: (interface for custom-set?)
  blaming: (interface for custom-set?)

The same call into the UNTYPED module works and returns #f as expected. Could you tell me why a syntax object breaks the contract here? And can I fix this?

1
Note: the same problem can happen with non-transparent structs (struct x ()) (custom-set? (x))Ben Greenman

1 Answers

2
votes

This is a bug caused by syntax-object contracts not being good enough. Specifically, the syntax/c contract works on flat contracts, while Typed Racket wants to work with chaperone contracts when the Any type is involved.

The Any that's involved here is the implicit Any introduced by #:opaque requiring the predicate custom-set?. It appears in an input, protecting values that start in typed code and end up in untyped code. Since untyped code could try to mess with a typed higher-order value, it must be wrapped with a chaperone contract any-wrap/c (internal to Typed Racket).

The any-wrap/c is there to protect potentially higher-order values, and container values that could contain higher-order values. If some part was mutable, or if some part had a function in it, untyped code cannot be allowed to mutate that data or call that function.

Syntax objects are containers. They can contain arbitrary values in the "syntax-e" and the syntax properties. Ideally, Typed Racket's any-wrap/c contract should wrap syntax objects in chaperone contracts that protect these places. Unfortunately, the syntax/c contract in the contract system isn't good enough for that. Because of this syntax objects are considered "unsafe" containers, and if any-wrap/c can't wrap them safely it must raise a contract error.

The only way I can see this potentially being fixed in the future is by improving syntax/c to work with chaperone contracts. After that any-wrap/c could consider syntax objects safe containers just like lists.