expander: improve some errors at phase >= 1

At phase 1 and higher, the expander tentatively allows an unbound
identifier so that, for example, `define-for-syntax` can define a
helper function syntactically after a compile-time expression that
uses the helper. While unbound references eventually trigger an error,
the reordering can be consuing, as in the example

  #lang racket

  (define-syntax (f stx)
    (syntax-parse stx
      [(_ oops) #'ok]))

which complains about `_` when the real problem is that `syntax-parse`
isn't imported.

To provide better errors, `raise-syntax-error` now implicitly extends
an error message to include a list of previously encountered unbound
identifiersin the current compilation unit. That list will be
non-empty only at phase >= 1. With that change, the error message for the
above example is

 bad.rkt:5:5: _: wildcard not allowed as an expression
  after encountering unbound identifier (which is possibly the real problem):
   syntax-parse
  in: (_ oops)
  ....

Closes #2167
This commit is contained in:
Matthew Flatt 2018-07-27 12:24:39 -06:00
parent 031564b28c
commit a447b5bf6b
3 changed files with 127 additions and 33 deletions

View File

@ -4,6 +4,7 @@
"../syntax/syntax.rkt" "../syntax/syntax.rkt"
"../syntax/scope.rkt" "../syntax/scope.rkt"
"../syntax/binding.rkt" "../syntax/binding.rkt"
"../syntax/error.rkt"
"env.rkt" "env.rkt"
"free-id-set.rkt" "free-id-set.rkt"
"../namespace/namespace.rkt" "../namespace/namespace.rkt"
@ -202,3 +203,13 @@
[to-parsed? #t] [to-parsed? #t]
[observer #f] [observer #f]
[should-not-encounter-macros? #t])) [should-not-encounter-macros? #t]))
;; ----------------------------------------
;; Register a callback for `raise-syntax-error`
(set-current-previously-unbound!
(lambda ()
(define ctx (current-expand-context))
(define phase-to-ids (and ctx (expand-context-need-eventually-defined ctx)))
(and phase-to-ids
(hash-ref phase-to-ids (expand-context-phase ctx) null))))

View File

@ -10,7 +10,9 @@
make-exn:fail:syntax:unbound make-exn:fail:syntax:unbound
raise-syntax-error raise-syntax-error
raise-unbound-syntax-error) raise-unbound-syntax-error
set-current-previously-unbound!)
(struct exn:fail:syntax exn:fail (exprs) (struct exn:fail:syntax exn:fail (exprs)
#:extra-constructor-name make-exn:fail:syntax #:extra-constructor-name make-exn:fail:syntax
@ -57,6 +59,19 @@
(format "~a" (or given-name (format "~a" (or given-name
(extract-form-name expr) (extract-form-name expr)
'?))) '?)))
(define unbound-message
;; If we have unbound identifiers to check, then it's possible that the
;; true error is that the identifier should have been bound to a syntactic
;; form. So, list those identifiers. The list will be non-empty only for
;; phase >= 1.
(let ([ids (current-previously-unbound)])
(or (and (pair? ids)
(format "\n after encountering unbound identifier~a (which is possibly the real problem):~a"
(if (null? (cdr ids)) "" "s")
(apply string-append
(for/list ([id (in-list ids)])
(format "\n ~s" (syntax-e id))))))
"")))
(define at-message (define at-message
(or (and sub-expr (or (and sub-expr
(error-print-source-location) (error-print-source-location)
@ -76,6 +91,7 @@
(string-append src-loc-str (string-append src-loc-str
name ": " name ": "
message message
unbound-message
at-message at-message
in-message in-message
message-suffix) message-suffix)
@ -105,3 +121,7 @@
(and str (and str
(string-append str ": "))))) (string-append str ": ")))))
;; Hook for the expander:
(define current-previously-unbound (lambda () #f))
(define (set-current-previously-unbound! proc)
(set! current-previously-unbound proc))

View File

@ -682,6 +682,7 @@ static const char *startup_source =
"(if a_1669(procedure-keywords(a_1669 p_0))(values null null)))))" "(if a_1669(procedure-keywords(a_1669 p_0))(values null null)))))"
"(values null null)))" "(values null null)))"
" (let-values () (raise-argument-error 'procedure-keywords \"procedure?\" p_0)))))))" " (let-values () (raise-argument-error 'procedure-keywords \"procedure?\" p_0)))))))"
"(define-values(print-values)(lambda vs_0(begin(begin(for-each(current-print) vs_0)(apply values vs_0)))))"
"(define-values" "(define-values"
"(reverse$1)" "(reverse$1)"
"(lambda(l_0)" "(lambda(l_0)"
@ -12825,7 +12826,7 @@ static const char *startup_source =
"(let-values((()" "(let-values((()"
"(begin" "(begin"
"(if((lambda(x_0)" "(if((lambda(x_0)"
"(let-values(((or-part_124)(not x_0)))(if or-part_124 or-part_124(symbol? x_0))))" "(let-values(((or-part_125)(not x_0)))(if or-part_125 or-part_125(symbol? x_0))))"
" given-name_0)" " given-name_0)"
"(void)" "(void)"
" (let-values () (raise-argument-error who_0 \"(or/c symbol? #f)\" given-name_0)))" " (let-values () (raise-argument-error who_0 \"(or/c symbol? #f)\" given-name_0)))"
@ -12849,65 +12850,117 @@ static const char *startup_source =
"(let-values(((name_119)" "(let-values(((name_119)"
"(format" "(format"
" \"~a\"" " \"~a\""
"(let-values(((or-part_125) given-name_0))" "(let-values(((or-part_126) given-name_0))"
"(if or-part_125" "(if or-part_126"
" or-part_125" " or-part_126"
"(let-values(((or-part_126)(extract-form-name expr_0)))" "(let-values(((or-part_127)(extract-form-name expr_0)))"
"(if or-part_126 or-part_126 '?)))))))" "(if or-part_127 or-part_127 '?)))))))"
"(let-values(((at-message_120)" "(let-values(((unbound-message_120)"
"(let-values(((or-part_127)" "(let-values(((ids_128)(current-previously-unbound)))"
"(let-values(((or-part_129)"
"(if(pair? ids_128)"
"(format"
" \"\\n after encountering unbound identifier~a (which is possibly the real problem):~a\""
" (if (null? (cdr ids_128)) \"\" \"s\")"
"(apply"
" string-append"
"(reverse$1"
"(let-values(((lst_130) ids_128))"
"(begin"
"(if(variable-reference-from-unsafe?(#%variable-reference))"
"(void)"
"(let-values()(check-list lst_130)))"
"((letrec-values(((for-loop_131)"
"(lambda(fold-var_0 lst_0)"
"(begin"
" 'for-loop"
"(if(pair? lst_0)"
"(let-values(((id_134)"
"(unsafe-car lst_0))"
"((rest_135)"
"(unsafe-cdr lst_0)))"
"(let-values(((fold-var_136)"
"(let-values(((fold-var_137)"
" fold-var_0))"
"(let-values(((fold-var_138)"
"(let-values()"
"(cons"
"(let-values()"
"(format"
" \"\\n ~s\""
"(syntax-e$1"
" id_134)))"
" fold-var_137))))"
"(values"
" fold-var_138)))))"
"(if(not #f)"
"(for-loop_131"
" fold-var_136"
" rest_135)"
" fold-var_136)))"
" fold-var_0)))))"
" for-loop_131)"
" null"
" lst_130))))))"
" #f)))"
" (if or-part_129 or-part_129 \"\")))))"
"(let-values(((at-message_121)"
"(let-values(((or-part_139)"
"(if sub-expr_0" "(if sub-expr_0"
"(if(error-print-source-location)" "(if(error-print-source-location)"
" (format \"\\n at: ~.s\" (syntax->datum$1 (datum->syntax$1 #f sub-expr_0)))" "(format"
" \"\\n at: ~.s\""
"(syntax->datum$1(datum->syntax$1 #f sub-expr_0)))"
" #f)" " #f)"
" #f)))" " #f)))"
" (if or-part_127 or-part_127 \"\"))))" " (if or-part_139 or-part_139 \"\"))))"
"(let-values(((in-message_121)" "(let-values(((in-message_122)"
"(let-values(((or-part_128)" "(let-values(((or-part_140)"
"(if expr_0" "(if expr_0"
"(if(error-print-source-location)" "(if(error-print-source-location)"
" (format \"\\n in: ~.s\" (syntax->datum$1 (datum->syntax$1 #f expr_0)))" " (format \"\\n in: ~.s\" (syntax->datum$1 (datum->syntax$1 #f expr_0)))"
" #f)" " #f)"
" #f)))" " #f)))"
" (if or-part_128 or-part_128 \"\"))))" " (if or-part_140 or-part_140 \"\"))))"
"(let-values(((src-loc-str_122)" "(let-values(((src-loc-str_123)"
"(let-values(((or-part_129)" "(let-values(((or-part_141)"
"(if(error-print-source-location)" "(if(error-print-source-location)"
"(let-values(((or-part_130)(extract-source-location sub-expr_0)))" "(let-values(((or-part_142)(extract-source-location sub-expr_0)))"
"(if or-part_130 or-part_130(extract-source-location expr_0)))" "(if or-part_142 or-part_142(extract-source-location expr_0)))"
" #f)))" " #f)))"
" (if or-part_129 or-part_129 \"\"))))" " (if or-part_141 or-part_141 \"\"))))"
"(raise" "(raise"
"(exn:fail:syntax_0" "(exn:fail:syntax_0"
"(string-append" "(string-append"
" src-loc-str_122" " src-loc-str_123"
" name_119" " name_119"
" \": \"" " \": \""
" message_0" " message_0"
" at-message_120" " unbound-message_120"
" in-message_121" " at-message_121"
" in-message_122"
" message-suffix_0)" " message-suffix_0)"
"(current-continuation-marks)" "(current-continuation-marks)"
"(map2" "(map2"
" syntax-taint$1" " syntax-taint$1"
"(if(let-values(((or-part_131) sub-expr_0))(if or-part_131 or-part_131 expr_0))" "(if(let-values(((or-part_143) sub-expr_0))(if or-part_143 or-part_143 expr_0))"
"(cons" "(cons"
"(datum->syntax$1" "(datum->syntax$1"
" #f" " #f"
"(let-values(((or-part_132) sub-expr_0))(if or-part_132 or-part_132 expr_0)))" "(let-values(((or-part_144) sub-expr_0))(if or-part_144 or-part_144 expr_0)))"
" extra-sources_0)" " extra-sources_0)"
" extra-sources_0)))))))))))))))" " extra-sources_0))))))))))))))))"
"(define-values" "(define-values"
"(extract-form-name)" "(extract-form-name)"
"(lambda(s_0)" "(lambda(s_0)"
"(begin" "(begin"
"(if(syntax?$1 s_0)" "(if(syntax?$1 s_0)"
"(let-values()" "(let-values()"
"(let-values(((e_134)(syntax-e$1 s_0)))" "(let-values(((e_146)(syntax-e$1 s_0)))"
"(if(symbol? e_134)" "(if(symbol? e_146)"
"(let-values() e_134)" "(let-values() e_146)"
"(if(if(pair? e_134)(identifier?(car e_134)) #f)" "(if(if(pair? e_146)(identifier?(car e_146)) #f)"
"(let-values()(syntax-e$1(car e_134)))" "(let-values()(syntax-e$1(car e_146)))"
"(let-values() #f)))))" "(let-values() #f)))))"
"(let-values() #f)))))" "(let-values() #f)))))"
"(define-values" "(define-values"
@ -12916,9 +12969,11 @@ static const char *startup_source =
"(begin" "(begin"
"(if(syntax?$1 s_0)" "(if(syntax?$1 s_0)"
"(if(syntax-srcloc s_0)" "(if(syntax-srcloc s_0)"
" (let-values (((str_136) (srcloc->string (syntax-srcloc s_0)))) (if str_136 (string-append str_136 \": \") #f))" " (let-values (((str_148) (srcloc->string (syntax-srcloc s_0)))) (if str_148 (string-append str_148 \": \") #f))"
" #f)" " #f)"
" #f))))" " #f))))"
"(define-values(current-previously-unbound)(lambda()(begin #f)))"
"(define-values(set-current-previously-unbound!)(lambda(proc_0)(begin(set! current-previously-unbound proc_0))))"
"(define-values" "(define-values"
"(struct:module-use module-use1.1 module-use? module-use-module module-use-phase)" "(struct:module-use module-use1.1 module-use? module-use-module module-use-phase)"
"(let-values(((struct:_1 make-_2 ?_3 -ref_4 -set!_5)" "(let-values(((struct:_1 make-_2 ?_3 -ref_4 -set!_5)"
@ -15929,6 +15984,14 @@ static const char *startup_source =
"(expand-context/outer-current-use-scopes the-struct_374)" "(expand-context/outer-current-use-scopes the-struct_374)"
"(expand-context/outer-name the-struct_374)))" "(expand-context/outer-name the-struct_374)))"
" (raise-argument-error 'struct-copy \"expand-context/outer?\" the-struct_374)))))))" " (raise-argument-error 'struct-copy \"expand-context/outer?\" the-struct_374)))))))"
"(call-with-values"
"(lambda()"
"(set-current-previously-unbound!"
"(lambda()"
"(let-values(((ctx_380)(current-expand-context)))"
"(let-values(((phase-to-ids_381)(if ctx_380(expand-context-need-eventually-defined ctx_380) #f)))"
"(if phase-to-ids_381(hash-ref phase-to-ids_381(expand-context-phase ctx_380) null) #f))))))"
" print-values)"
"(define-values" "(define-values"
"(taint-dispatch)" "(taint-dispatch)"
"(lambda(s_0 proc_0 phase_0)" "(lambda(s_0 proc_0 phase_0)"