Difference between Racket and Typed Racket with macro literals

With this untyped module:

#lang racket/base

(require (for-syntax racket/base syntax/parse))
(provide choose one two)

(define-syntax (one stx)
  (raise-syntax-error stx "use of one outside of choose"))

(define-syntax (two stx)
  (raise-syntax-error stx "use of two outside of choose"))

(define-syntax (choose stx)
  (syntax-parse stx
    #:literals (one two)
    [(_ one) #'"You chose one"]
    [(_ two) #'"You chose two"]))

everything works as expected:

$ racket
Welcome to Racket v8.13 [cs].
> (require "demo-untyped.rkt")
> (choose one)
"You chose one"
>

but with this Typed Racket version:

#lang typed/racket/base

(require (for-syntax typed/racket/base syntax/parse))
(provide choose one two)

(define-syntax (one stx)
  (raise-syntax-error stx "use of one outside of choose"))

(define-syntax (two stx)
  (raise-syntax-error stx "use of two outside of choose"))

(define-syntax (choose stx)
  (syntax-parse stx
    #:literals (one two)
    [(_ one) #'"You chose one"]
    [(_ two) #'"You chose two"]))

trying to use it gives an error:

$ racket -I typed/racket
Welcome to Racket v8.13 [cs].
> (require "demo-typed.rkt")
> (choose one)
string:1:0: choose: expected one of these identifiers: `one' or `two'
  at: one
  in: (choose one)
 [,bt for context]

I can't figure out why the exported literal identifiers aren't being used in the TR version but are in the untyped version. Any ideas? Or is this a bug?

Using #:datum-literals instead of #:literals makes it work, but I'm still wondering why the first way isn't working. Does Typed Racket wrap exported syntax in something that makes them not free-identifier=? to ones in the module they're defined in?

What if you require the typed/racket/base module, not in a REPL presumably using racket/base, but in a module explicitly also using typed/racket/base? (Therefore no typed => non-typed boundary involved?)

I ask because I wonder if this is related to Typed Racket exporting things wrapped in contracts for non-Typed Racket modules (in which case they are not the original things, but rather chaperones for the things).

I'm using a typed racket repl, but it doesn't make a difference if it's another typed module requiring it instead (That's how I originally discovered it).

Typed Racket does something funky to bindings to make them work in different contexts (deep, shallow, optional, untyped), so free-identifier=? is confused. A way to work around this is to define the keywords in a separate module, then import the keywords in both the macro module and client modules (or re-export the keywords from the macro module, for that matter).

#lang typed/racket/base
(module keyword typed/racket/base
  (require (for-syntax typed/racket/base))

  (provide one
           two)

  (define-syntax (one stx)
    (raise-syntax-error #f "use of one outside of choose"))

  (define-syntax (two stx)
    (raise-syntax-error #f "use of two outside of choose")))

(module macro typed/racket/base
  (require (submod ".." keyword)
           (for-syntax typed/racket/base
                       syntax/parse))

  (provide one                  ; re-export
           two                  ; re-export
           choose)

  (define-syntax (choose stx)
    (syntax-parse stx
      #:literals (one two)
      [(_ one) #'"You chose one"]
      [(_ two) #'"You chose two"])))

(require (submod "." macro))

(choose one)

(choose two)
1 Like

Typed Racket wraps provided macros to protect them, just like it wraps provided values. Unfortunately, that means they don't compare equal to the ones inside the module, breaking this program.