1.2.6 Contracts on Macro Sub-expressions
Just as procedures often expect certain kinds of values as arguments, macros often have expectations about the expressions they are given. And just as procedures express those expectations via contracts, so can macros, using the expr/c syntax class.
For example, here is a macro myparameterize that behaves like parameterize but enforces the parameter? contract on the parameter expressions.
> (define-syntax (myparameterize stx) (syntax-parse stx [(_ ((p v:expr) ...) body:expr) #:declare p (expr/c #'parameter? #:name "parameter argument") #'(parameterize ([p.c v] ...) body)]))
> (myparameterize ([current-input-port (open-input-string "(1 2 3)")]) (read)) '(1 2 3)
> (myparameterize (['whoops 'something]) 'whatever) myparameterize: contract violation
expected: parameter?
given: 'whoops
in: parameter?
macro argument contract on parameter argument
contract from: 'program
blaming: (quote program)
(assuming the contract is correct)
at: eval:3.0
Important: Make sure when using expr/c to use the c attribute. If the macro above had used p in the template, the expansion would have used the raw, unchecked expressions. The expr/c syntax class does not change how pattern variables are bound; it only computes an attribute that represents the checked expression.
The previous example shows a macro applying a contract on an argument, but a macro can also apply a contract to an expression that it produces. In that case, it should use #:arg? #f to indicate that the macro, not the calling context, is responsible for expression produced.
; BUG: rationals not closed under inversion
> (define-syntax (invert stx) (syntax-parse stx [(_ e) #:declare e (expr/c #'rational?) #:with result #'(/ 1 e.c) #:declare result (expr/c #'rational? #:arg? #f) #'result.c])) > (invert 4) 1/4
> (invert 'abc) invert: contract violation
expected: rational?
given: 'abc
in: rational?
macro argument contract
contract from: 'program
blaming: (quote program)
(assuming the contract is correct)
at: eval:6.0
> (invert 0.0) invert: contract violation
expected: rational?
given: +inf.0
in: rational?
macro result contract
contract from: 'program
blaming: (quote program)
(assuming the contract is correct)
at: eval:4.0
The following example shows a macro that uses a contracted expression
at a different phase level. The macro’s ref argument is used
as a “compile-time expression”—
> (define-syntax (define-alias stx) (syntax-parse stx [(_ name:id ref) #:declare ref (expr/c #'identifier? #:phase (add1 (syntax-local-phase-level))) #'(define-syntax name (make-rename-transformer ref.c))])) > (define-alias plus #'+) > (define-alias zero 0) define-alias: contract violation
expected: identifier?
given: 0
in: identifier?
macro argument contract
contract from: 'program
blaming: (quote program)
(assuming the contract is correct)
at: eval:10.0