[racket] Typed Racket macros in untyped code

From: Sam Tobin-Hochstadt (samth at ccs.neu.edu)
Date: Thu Sep 15 22:53:48 EDT 2011

On Thu, Sep 15, 2011 at 10:29 PM, Eric Dobson <eric.n.dobson at gmail.com> wrote:
> Currently macros written from Typed Racket can not be used in the
> untyped world.
> Example:
>
> #lang racket/load
>
> (module t typed/racket
>  (provide foo)
>  (define-syntax (foo stx)
>    #'(+ 2 1)))
>
> (module u racket
>  (require 't)
>  (displayln (foo)))
>
> (require 'u)
>
> This was brought up on the list a while ago, and the answer was that
> when the certificate system needed work before this was feasible. My
> question is: are taints the feature that was needed? And has any
> progress been made towards this?

Yes, taints are the right thing here.  I was just thinking this week
about what the right strategy is here, and it shouldn't be too hard to
implement.

To explain a little more about what the problem was, and why taints
fix it, consider the following modules:

#lang racket/load
(module m1 typed/racket
   (: f : Number -> Number)
  (define (f x) (add1 x))
  (define-syntax-rule (g e) (f e))
  (provide g))

(module n racket
  (require 'm1)
  (define-syntax (cheat stx)
    (syntax-case (local-expand #'(g 0) 'expression null) ()
       [(app f _)  #'f])
  ((cheat) 'bad)))

This is obviously not ok, since we're passing 'bad to `f'.  The
solution is to rewrite `m1' to this:

(module m1 typed/racket
   (: f : Number -> Number)
  (define (f* x) (add1 x))
  (define/contract contract-f (number? . -> . number?) (lambda (x) (add1 x)))
  (define-syntax (f stx) (if typed-context? (taint #'f*) (taint #'contract-f)))
  (define-syntax-rule (g e) (f e))
  (provide g))

Now our bad module can't get the bad identifier, because `g' doesn't
have a reference to it, and the taints ensure that even a conspiracy
between a typed and and untyped module can't expose the internal
version of `f'.

> Also as a work around the following program works:
>
>
> #lang racket/load
>
>
> (module t2 racket
>  (require typed/racket)
>  (provide foo)
>  (define-syntax (foo stx)
>    #'(let ()
>        (: x Number)
>        (define x (sqrt 2))
>        x)))
>
>
> (module u2 racket
>  (require 't2)
>  (displayln (foo)))
>
> (module tu2 typed/racket
>  (require 't2)
>  (: y Number)
>  (define y (foo))
>  (displayln y))
>
> (require 'u2)
> (require 'tu2)
>
> Which seems to achieve what TR is currently preventing. Is this
> unsafe/buggy, or is there another reason why it works when the
> language is not typed?

This works because the `foo' macro hasn't made any promises about
being typed, and can't refer to any typed identifier without going
through a contract.  It's just like a regular untyped macro, except
that it includes uses of the `:' form.  The only way to get Typed
Racket to let you call yourself typed, or use any typed values without
a contract, is to be checked by the typed version of `#%module-begin',
which `foo' is not.  So there's no danger here.
-- 
sam th
samth at ccs.neu.edu



Posted on the users mailing list.