1.2.6 Contracts on Macro Sub-expressions

► |  | Syntax: Meta-Programming Helpers

1 | Parsing and Specifying Syntax
2 | Syntax Object Helpers
3 | Datum Pattern Matching
4 | Module-Processing Helpers
5 | Macro Transformer Helpers
6 | Reader Helpers
7 | Parsing for Bodies
8 | Unsafe for Clause Transforms
9 | Source Locations
10 | Preserving Source Locations
11 | Non-Module Compilation And Expansion
12 | Trusting Standard Recertifying Transformers
13 | Attaching Documentation to Exports
14 | Contracts for Macro Subexpressions
15 | Macro Testing
16 | Internal-Definition Context Helpers
| Index

► | 1 | Parsing and Specifying Syntax

1.1 | Introduction
1.2 | Examples
1.3 | Parsing Syntax
1.4 | Specifying Syntax with Syntax Classes
1.5 | Syntax Patterns
1.6 | Defining Simple Macros
1.7 | Literal Sets and Conventions
1.8 | Library Syntax Classes and Literal Sets
1.9 | Unwindable State
1.10 | Configuring Error Reporting
1.11 | Debugging and Inspection Tools
1.12 | Experimental
1.13 | Minimal Library

▼ | 1.2 | Examples

1.2.1 | Phases and Reusable Syntax Classes
1.2.2 | Optional Keyword Arguments
1.2.3 | Variants with Uniform Meanings
1.2.4 | Variants with Varied Meanings
1.2.5 | More Keyword Arguments
1.2.6 | Contracts on Macro Sub-expressions

top contents ← prev up next →

##### 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”—more precisely, it is used as an expression at a phase level one higher than the use of the macro itself. That is because the macro places the expression in the right-hand side of a define-syntax form. The macro uses expr/c with a #:phase argument to ensure that ref produces an identifier when 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

top contents ← prev up next →