debug-scopes.scrbl (8104B)
1 #lang scribble/manual 2 @require[scribble/example 3 scribble-enhanced/doc 4 @for-label[debug-scopes 5 racket/base 6 racket/contract]] 7 8 @title{Debuging scope-related issues} 9 @author[@author+email["Suzanne Soy" "racket@suzanne.soy"]] 10 11 @defmodule[debug-scopes] 12 13 @defproc[(+scopes [stx syntax?]) string?]{The identifiers are adorned with 14 superscripts indicating the scopes present on them. Each scope is represented 15 as an ascending integer which is unique within the current expansion. At the 16 end of the expansion, a table showing the equivalence between the small 17 integers and the scopes as represented by Racket is printed. Ranges of 18 consecutive scopes are represented as @racket[identifier³˙˙⁹] (which would 19 indicate that the scopes 3, 4, 5, 6, 7, 8 and 9 are present on the 20 identifier). When only a few scopes are missing from the range, they are 21 printed as @racket[identifier³˙˙⁹⁻⁵⁻⁷] (which would indicate that the scopes 22 3, 4, 6, 8 and 9 are present on the identifier). When there are too many 23 missing identifiers within the range, the scopes are instead displayed 24 alternatively as superscripts and subscripts, e.g. 25 @racket[identifier²₃⁵₇¹¹₁₃¹⁷₁₉] (which would indicate that only the scopes 2, 26 3, 5, 7, 11, 17 and 19 are present on the identifier, and would also indicate 27 that a developer is playing a trick on you). Finally the current macro scope 28 (which can be removed using @racket[syntax-local-value]) and the current 29 use-site scope, if any (which can be removed using 30 @racket[syntax-local-identifier-as-binding]) is printed for the whole 31 expression, using the notation @racket[(expression …)ˢˡⁱ⁼⁴⁺ᵘˢᵉ⁼¹²] (which 32 would indicate that the macro scope is 4 and the use-site scope is 12). 33 34 @examples[#:lang racket 35 (require (for-syntax racket/base 36 debug-scopes)) 37 (define-syntax (foo stx) 38 (displayln (+scopes stx)) 39 (displayln (+scopes (datum->syntax #f 'no-scopes))) 40 (displayln (+scopes (syntax-local-introduce #'here))) 41 (print-full-scopes) 42 #'(void)) 43 44 (foo (list 123))] 45 46 When using named scopes---for example, via 47 @racketmodname[debug-scopes/named-scopes/override]---a named scope is often 48 used instead of the macro scope flipped by @racket[syntax-local-introduce]. If 49 @racket[+scopes] is called within that context, it also annotates the whole 50 expression with the named scope which acts as a replacement for the macro 51 scope, using the notation @racket[(expression …)ˢˡⁱ⁼⁴⁺ᵘˢᵉ⁼¹²⁽ⁿᵃᵐᵉᵈ⁼⁵⁾] (which 52 would indicate that the original macro scope was 4, the use-site-scope is 12, 53 and the named macro scope is 5). 54 55 @examples[#:lang racket 56 (require (for-syntax (except-in racket/base syntax-local-introduce) 57 debug-scopes 58 debug-scopes/named-scopes)) 59 (define-syntax (foo stx) 60 (displayln (+scopes stx)) 61 (displayln (+scopes (datum->syntax #f 'no-scopes))) 62 (displayln (+scopes (syntax-local-introduce #'here))) 63 (print-full-scopes) 64 #'(void)) 65 66 (foo (list 123))]} 67 68 You can combine @racket[+scopes] with @racketmodname[racket/trace] to trace 69 scopes through expansion. 70 @examples[ 71 (require racket/trace (for-syntax racket/base racket/trace debug-scopes)) 72 (begin-for-syntax 73 (define (maybe-syntax->scoped syn?) 74 (if (syntax? syn?) 75 (+scopes syn?) 76 syn?)) 77 (current-trace-print-args 78 (let ([ctpa (current-trace-print-args)]) 79 (lambda (s l kw l2 n) 80 (ctpa s (map maybe-syntax->scoped l) kw l2 n)))) 81 (current-trace-print-results 82 (let ([ctpr (current-trace-print-results)]) 83 (lambda (s l n) 84 (ctpr s (map maybe-syntax->scoped l) n))))) 85 (trace-define-syntax let 86 (syntax-rules () 87 [(_ ([x v]) e) ((lambda (x) e) v)])) 88 (let ([x 5]) (let ([y 120]) y))] 89 90 @defproc[(print-full-scopes [reset? any/c #t]) void?]{ Prints the long scope id 91 and annotation for all scopes displayed as part of preceeding calls to 92 @racket[+scopes], as would be shown by 93 @racket[(hash-ref (syntax-debug-info stx) 'context)]. 94 95 This allows to get some extended information about the scopes in a summary 96 table by calling @racket[print-full-scopes], while still getting short and 97 readable display of syntax objects with @racket[+scopes]. 98 99 After running @racket[(print-full-scopes)], if @racket[reset?] is true, then 100 the scope counter is reset (and @racket[+scopes] therefore starts numbering 101 scopes starting from @racket[0] again).} 102 103 @section{Hack for named scopes} 104 105 @defmodule[debug-scopes/named-scopes/exptime] 106 107 Module scopes bear are annotated by Racket with the name of the module. As of 108 December 2016, other scopes like macro scopes@note{Both the ones implicitly 109 created when a macro is called, and the ones explicitly created via 110 @racket[make-syntax-introducer] are concerned by this} or use-site scopes lack 111 any form of annotation or naming. 112 113 @defproc[(make-named-scope [name (or/c string? symbol?)]) 114 (->* (syntax?) ([or/c 'add 'remove 'flip]) syntax?)]{ This function 115 uses a hack to create named scopes on demand: it creates a dummy mododule with 116 the desired name, expands it and extracts the module's scope. The exact 117 implementation mechanism may vary in future versions, for example if later 118 versions of Racket directly support the creation of named scopes, 119 @racket[make-named-scope] would simply become an alias for the official 120 mechanism. Later versions of this function may therefore produce named scopes 121 other than module-like scopes.} 122 123 @defproc[(make-module-like-named-scope [name (or/c string? symbol?)]) 124 (->* (syntax?) ([or/c 'add 'remove 'flip]) syntax?)]{ 125 Produces a named module-like scope. The @racket[make-named-scope] function 126 currently also produces a module-like scope, so the two are equivalent for now. 127 In later versions, @racket[make-named-scope] may produce other sorts of named 128 scopes if they can be created more efficiently, but 129 @racket[make-module-like-named-scope] will always produce module-like scopes.} 130 131 @define[orig:define-syntax @racket[define-syntax]] 132 @define[orig:syntax-local-introduce @racket[syntax-local-introduce]] 133 134 @subsection{Automatic use of named scopes} 135 136 @defmodule[debug-scopes/named-scopes/override] 137 138 This module overrides @orig:define-syntax and @orig:syntax-local-introduce to 139 automatically use a named macro scope. The use-site scope is not affected for 140 now, as the original unnamed use-site scope from Racket benefits from special 141 cooperation from definition contexts, which would be hard to achieve with the 142 hack currently used to implement named scopes. 143 144 @defform*[((define-syntax (name stx-arg) . body) 145 (define-syntax name value))]{ 146 147 Like @orig:define-syntax, but the first form changes the macro scope 148 introduced by @racket[syntax-local-introduce] to use a named scope, bearing 149 the @racket[name] of the macro. 150 151 Note that this change only affects the scopes introduced by the overriden 152 version of @racket[syntax-local-introduce], not the original 153 @|orig:syntax-local-introduce|. 154 155 This means that if the macro calls a function defined in another file which 156 uses the non-overidden version of @orig:syntax-local-introduce, both the 157 original unnamed scope and the named scope may accidentally appear in the 158 result. Macros defined using the overridden @racket[syntax-local-introduce] 159 should therefore take special care to always use the overridden version of 160 @racket[syntax-local-introduce]. 161 162 The use-site scope is not affected for now, as the original unnamed use-site 163 scope from Racket benefits from special cooperation from definition contexts, 164 which would be hard to achieve with the hack currently used to implement named 165 scopes.} 166 167 @defproc[(syntax-local-introduce [stx syntax?]) syntax?]{ Like 168 @orig:syntax-local-introduce, but uses the named scope set up by 169 @racket[define-syntax] if called within the dynamic extent of a call to a 170 macro defined by the overridden @racket[define-syntax] (and otherwise behaves 171 like the original @orig:syntax-local-introduce).}