diff --git a/pkgs/racket-doc/syntax/scribblings/free-vars.scrbl b/pkgs/racket-doc/syntax/scribblings/free-vars.scrbl index 0077a7b4e3..f5ba88658f 100644 --- a/pkgs/racket-doc/syntax/scribblings/free-vars.scrbl +++ b/pkgs/racket-doc/syntax/scribblings/free-vars.scrbl @@ -5,7 +5,9 @@ @defmodule[syntax/free-vars] -@defproc[(free-vars [expr-stx syntax?] [insp inspector? _mod-decl-insp]) +@defproc[(free-vars [expr-stx syntax?] + [insp inspector? _mod-decl-insp] + [#:module-bound? module-bound? any/c #f]) (listof identifier?)]{ Returns a list of free @racket[lambda]- and @racket[let]-bound @@ -18,3 +20,6 @@ The inspector @racket[insp] is used to disarm @racket[expr-stx] and sub-expressions before extracting identifiers. The default @racket[insp] is the declaration-time inspector of the @racketmodname[syntax/free-vars] module.} + +If @racket[module-bound?] is non-false, the list of free variables also +includes free module-bound identifiers. diff --git a/pkgs/racket-test/tests/syntax/free-vars.rkt b/pkgs/racket-test/tests/syntax/free-vars.rkt index 233f4a5643..50850c10ca 100644 --- a/pkgs/racket-test/tests/syntax/free-vars.rkt +++ b/pkgs/racket-test/tests/syntax/free-vars.rkt @@ -1,5 +1,6 @@ #lang racket -(require syntax/free-vars) +(require syntax/free-vars + rackunit) (parameterize ([current-namespace (make-base-namespace)]) (define (check stx) @@ -40,3 +41,9 @@ '(x) (let ([y 3]) (list x y))))) + +(check-equal? (free-vars (expand #'(+ 1 2))) + '()) +(check-pred (lambda (x) (free-identifier=? x #'+)) + (first (free-vars (expand #'(+ 1 2)) + #:module-bound? #t))) diff --git a/racket/collects/syntax/free-vars.rkt b/racket/collects/syntax/free-vars.rkt index 1daa430e3d..4a29674ac2 100644 --- a/racket/collects/syntax/free-vars.rkt +++ b/racket/collects/syntax/free-vars.rkt @@ -43,12 +43,15 @@ [(null? f) null] [(syntax? f) (loop (syntax-e f))]))) -;; free-vars : expr-stx -> (listof id) +;; free-vars : expr-stx [inspector?] [#:module-bound? any/c] -> (listof id) ;; Returns a list of free lambda- and let-bound identifiers in a ;; given epression. The expression must be fully expanded. -(define (free-vars e [code-insp - (variable-reference->module-declaration-inspector - (#%variable-reference))]) +;; If `module-bound?` is true, also return module-bound variables. +(define (free-vars e + [code-insp + (variable-reference->module-declaration-inspector + (#%variable-reference))] + #:module-bound? [module-bound? #f]) (define (submodule-error e) (error 'free-vars "submodules not supported: ~a" e)) ;; It would be nicer to have a functional mapping: @@ -56,12 +59,19 @@ (merge (let free-vars ([e e]) (kernel-syntax-case (syntax-disarm e code-insp) #f - [id - (identifier? #'id) - (if (and (eq? 'lexical (identifier-binding #'id)) - (not (bound-identifier-mapping-get bindings #'id (lambda () #f)))) - (list #'id) - null)] + [id + (identifier? #'id) + (let ([b (identifier-binding #'id)]) + (cond [(and (eq? 'lexical b) + (not (bound-identifier-mapping-get bindings #'id (lambda () #f)))) + (list #'id)] + [(and module-bound? ; do we count module-bound vars too? + ;; we're in an expression context, so any module-bound + ;; variable is free + (list? b)) + (list #'id)] + [else + null]))] [(#%top . id) null] [(quote q) null] [(quote-syntax . _) null]