#lang scribble/doc @(require "mz.rkt") @title[#:tag "stxprops"]{Syntax Object Properties} Every syntax object has an associated @deftech{syntax property} list, which can be queried or extended with @racket[syntax-property]. A property is set as @deftech[#:key "stx-prop-preserved"]{preserved} or not; a preserved property is maintained for a syntax object in a compiled form that is marshaled to a byte string or @filepath{.zo} file, and other properties are discarded when marshaling. In @racket[read-syntax], the reader attaches a preserved @racket['paren-shape] property to any pair or vector syntax object generated from parsing a pair @litchar{[} and @litchar{]} or @litchar["{"] and @litchar["}"]; the property value is @racket[#\[] in the former case, and @racket[#\{] in the latter case. The @racket[syntax] form copies any @racket['paren-shape] property from the source of a template to corresponding generated syntax. Both the syntax input to a transformer and the syntax result of a transformer may have associated properties. The two sets of properties are merged by the syntax expander: each property in the original and not present in the result is copied to the result, and the values of properties present in both are combined with @racket[cons] (result value first, original value second) and the @racket[cons]ed value is @tech[#:key "stx-prop-preserved"]{preserved} if either of the values were preserved. Before performing the merge, however, the syntax expander automatically adds a property to the original syntax object using the key @indexed-racket['origin]. If the source syntax has no @racket['origin] property, it is set to the empty list. Then, still before the merge, the identifier that triggered the macro expansion (as syntax) is @racket[cons]ed onto the @racket['origin] property so far. The @racket['origin] property thus records (in reverse order) the sequence of macro expansions that produced an expanded expression. Usually, the @racket['origin] value is a list of identifiers. However, a transformer might return syntax that has already been expanded, in which case an @racket['origin] list can contain other lists after a merge. The @racket[syntax-track-origin] procedure implements this tracking. The @racket['origin] property is added as non-@tech[#:key "stx-prop-preserved"]{preserved}. Besides @racket['origin] tracking for general macro expansion, Racket adds properties to expanded syntax (often using @racket[syntax-track-origin]) to record additional expansion details: @itemize[ @item{When a @racket[begin] form is spliced into a sequence with internal definitions (see @secref["intdef-body"]), @racket[syntax-track-origin] is applied to every spliced element from the @racket[begin] body. The second argument to @racket[syntax-track-origin] is the @racket[begin] form, and the third argument is the @racket[begin] keyword (extracted from the spliced form).} @item{When an internal @racket[define-values] or @racket[define-syntaxes] form is converted into a @racket[letrec-syntaxes+values] form (see @secref["intdef-body"]), @racket[syntax-track-origin] is applied to each generated binding clause. The second argument to @racket[syntax-track-origin] is the converted form, and the third argument is the @racket[define-values] or @racket[define-syntaxes] keyword form the converted form.} @item{When a @racket[letrec-syntaxes+values] expression is fully expanded, syntax bindings disappear, and the result is either a @racket[letrec-values] form (if the unexpanded form contained non-syntax bindings), or only the body of the @racket[letrec-syntaxes+values] form (wrapped with @racket[begin] if the body contained multiple expressions). To record the disappeared syntax bindings, a property is added to the expansion result: an immutable list of identifiers from the disappeared bindings, as a @indexed-racket['disappeared-binding] property.} @item{When a subtyping @racket[struct] form is expanded, the identifier used to reference the base type does not appear in the expansion. Therefore, the @racket[struct] transformer adds the identifier to the expansion result as a @indexed-racket['disappeared-use] property.} @item{When a reference to an unexported or protected identifier from a module is discovered, the @indexed-racket['protected] property is added to the identifier with a @racket[#t] value.} @item{When @racket[read-syntax] generates a syntax object, it attaches a property to the object (using a private key) to mark the object as originating from a read. The @racket[syntax-original?] predicate looks for the property to recognize such syntax objects. (See @secref["stxops"] for more information.)} ] See also @seclink["Syntax_Properties_that_Check_Syntax_Looks_For" #:doc '(lib "scribblings/tools/tools.scrbl") #:indirect? #t]{Check Syntax} for one client of the @racket['disappeared-use] and @racket['disappeared-binding] properties. See @secref["modinfo"] for information about properties generated by the expansion of a module declaration. See @racket[lambda] and @secref["infernames"] for information about properties recognized when compiling a procedure. See @racket[current-compile] for information on properties and byte codes. @;------------------------------------------------------------------------ @defproc*[([(syntax-property [stx syntax?] [key (if preserved? (and/c symbol? symbol-interned?) any/c)] [v any/c] [preserved? any/c (eq? key 'paren-shape)]) syntax?] [(syntax-property [stx syntax?] [key any/c]) any])]{ The three- or four-argument form extends @racket[stx] by associating an arbitrary property value @racket[v] with the key @racket[key]; the result is a new syntax object with the association (while @racket[stx] itself is unchanged). The property is added as @tech[#:key "stx-prop-preserved"]{preserved} if @racket[preserved?] is true, in which case @racket[key] must be an @tech{interned} symbol, and @racket[v] should be a value as described below that can be saved in marshaled bytecode. The two-argument form returns an arbitrary property value associated to @racket[stx] with the key @racket[key], or @racket[#f] if no value is associated to @racket[stx] for @racket[key]. To support marshaling to bytecode, a value for a preserved syntax property must be a non-cyclic value that is either @itemlist[ @item{a @tech{pair} containing allowed preserved-property values;} @item{a @tech{vector} (unmarshaled as immutable) containing allowed preserved-property values;} @item{a @tech{box} (unmarshaled as immutable) containing allowed preserved-property values;} @item{an immutable @tech{prefab} structure containing allowed preserved-property values;} @item{an immutable @tech{hash table} whose keys and values are allowed preserved-property values;} @item{a @tech{syntax object}; or} @item{an empty list, @tech{symbol}, @tech{number}, @tech{character}, @tech{string}, @tech{byte string}, or @tech{regexp value}.} ] Any other value for a preserved property triggers an exception at an attempt to marshal the owning syntax object to bytecode form. @history[#:changed "6.4.0.14" @elem{Added the @racket[preserved?] argument.}]} @defproc[(syntax-property-preserved? [stx syntax?] [key (and/c symbol? symbol-interned?)]) boolean?]{ Returns @racket[#t] if @racket[stx] has a @tech[#:key "stx-prop-preserved"]{preserved} property value for @racket[key], @racket[#f] otherwise. @history[#:added "6.4.0.14"]} @defproc[(syntax-property-symbol-keys [stx syntax?]) list?]{ Returns a list of all symbols that as keys have associated properties in @racket[stx]. @tech{Uninterned} symbols (see @secref["symbols"]) are not included in the result list.} @defproc[(syntax-track-origin [new-stx syntax?] [orig-stx syntax?] [id-stx identifier?]) any]{ Adds properties to @racket[new-stx] in the same way that macro expansion adds properties to a transformer result. In particular, it merges the properties of @racket[orig-stx] into @racket[new-stx], first adding @racket[id-stx] as an @racket['origin] property, and it returns the property-extended syntax object. Use the @racket[syntax-track-origin] procedure in a macro transformer that discards syntax (corresponding to @racket[orig-stx] with a keyword @racket[id-stx]) leaving some other syntax in its place (corresponding to @racket[new-stx]). For example, the expression @racketblock[ (or x y) ] expands to @racketblock[ (let ([or-part x]) (if or-part or-part (or y))) ] which, in turn, expands to @racketblock[ (let-values ([(or-part) x]) (if or-part or-part y)) ] The syntax object for the final expression will have an @racket['origin] property whose value is @racket[(list (quote-syntax let) (quote-syntax or))].}