Add a bunch of tests for compiler recovery

Ideally, any time you write:

(local x (<some form with compiler error>))
(print x)

I don't want it to say "unknown variable x". To make sure that's the
case, I need to do as much error recovery as possible. I don't have full
error recovery, but I've got the common cases, and I'm working down the
fennel.friends list
This commit is contained in:
XeroOl 2024-03-28 23:51:22 -05:00
parent 3bc530e11b
commit 59b966a25f
3 changed files with 88 additions and 13 deletions

View File

@ -232,25 +232,41 @@ identifiers are declared / referenced in which places."
(where [sym] (multisym? sym) (: (tostring sym) :find ":"))
(reference sym scope :read)))
(local defer [])
(λ attempt-to-recover! [msg ?ast]
(or (= 1 (msg:find "unknown identifier"))
(= 1 (msg:find "local %S+ was overshadowed by a special form or macro"))
(= 1 (msg:find "expected var "))
(= 1 (msg:find "cannot call literal value"))
(= 1 (msg:find "unexpected vararg"))
(= 1 (msg:find "expected closing delimiter"))
(= 1 (msg:find "expected body expression"))
(= 1 (msg:find ".*fennel/macros.fnl:%d+: expected body"))
(= 1 (msg:find "expected condition and body"))
(= 1 (msg:find "expected whitespace before opening delimiter"))
(= 1 (msg:find "malformed multisym"))
(= 1 (msg:find "expected at least one pattern/body pair"))
(= 1 (msg:find "module not found"))
(= 1 (msg:find "expected even number of values in table literal"))
(= 1 (msg:find "use $%.%.%. in hashfn"))
(when (and (= 1 (msg:find "expected even number of name/value bindings"))
(sequence? ?ast)
(= 1 (% (length ?ast) 2)))
(table.insert ?ast (sym :nil))
(table.insert defer #(table.remove ?ast))
true)
(when (and (= 1 (msg:find "expected a function, macro, or special to call"))
(list? ?ast)
(= (length ?ast) 0))
(table.insert ?ast (sym :do))
true)))
(table.insert defer #(table.remove ?ast))
true)
(when (= 1 (msg:find "unexpected multi symbol"))
(let [old (tostring ?ast)]
(tset ?ast 1 "!!invalid-multi-symbol!!")
(table.insert defer #(tset ?ast 1 old))
true))))
(λ on-compile-error [_ msg ast call-me-to-reset-the-compiler]
@ -353,6 +369,9 @@ identifiers are declared / referenced in which places."
(set fennel.macro-path old-macro-path))
(each [_ cmd (ipairs defer)]
(cmd))
(set file.ast ast)
(set file.calls calls)
(set file.lexical lexical)

View File

@ -143,11 +143,7 @@ Every time the client sends a message, it gets handled by a function in the corr
?parent (. parents 1)
result []
in-call-position? (and (fennel.list? ?parent)
(or (= ?symbol (. ?parent 1))
;; so, this is unfortunate
(and (= ?symbol nil)
(fennel.sym? (. ?parent 1) :do)
(= (. ?parent 1 :bytestart) nil))))]
(= ?symbol (. ?parent 1)))]
(collect-scope scope :manglings #(doto (make-completion-item self file $ scope) (tset :kind kinds.Variable)) result)
(when in-call-position?

View File

@ -57,23 +57,83 @@
nil)
(fn test-multiple-errors []
(check "(unknown-global-1 unknown-global-2)"
[{:message "unknown identifier: unknown-global-1"}
{:message "unknown identifier: unknown-global-2"}] [])
(check "(let [x unknown-global"
;; compiler recovery for every fennel.friend error
(check "(let [x.y.z 10]
(print +))"
[{:message "unexpected multi symbol x.y.z"}
{:message "tried to reference a special form without calling it"}] [])
;; use of global .* is aliased by a local
(check "(let [+ 10]
(print +))"
[{:message "local + was overshadowed by a special form or macro"}
{:message "tried to reference a special form without calling it"}] [])
(check "(let [x 10]
(set x 20)
(print x +))"
[{:message "expected var x"}
{:message "tried to reference a special form without calling it"}] [])
;; expected macros to be table
;; expected each macro to be a function
;; macro tried to bind .* without gensym
(check "(do unknown-global
(print +))"
[{:message "unknown identifier: unknown-global"}
{:message "expected body expression"}
{:message "expected closing delimiters )]"}] [])
;; recovers from ()
{:message "tried to reference a special form without calling it"}] [])
(check "(let [x ()]
(print x +))"
[{:message "expected a function, macro, or special to call"}
{:message "tried to reference a special form without calling it"}] [])
(check "(let [x (3)]
(print x +))"
[{:message "cannot call literal value 3"}
{:message "tried to reference a special form without calling it"}] [])
(check "(let [x (fn [] ...)]
(print x +))"
[{:message "unexpected vararg"}
{:message "tried to reference a special form without calling it"}] [])
(check "(let [x #...]
(print x +))"
[{:message "use $... in hashfn"}
{:message "tried to reference a special form without calling it"}] [])
(check "(let [x unknown-global"
[{:message "unknown identifier: unknown-global"}
{:message "expected body expression"}
{:message "expected closing delimiters )]"}] [])
;; recovers from mismatched let
(check "(let [x]
(print x +))"
[{:message "expected even number of name/value bindings"}
{:message "tried to reference a special form without calling it"}] [])
;; recovers from missing condition (if)
(check "(let [x (if)]
(print x +))"
[{:message "expected condition and body"}
{:message "tried to reference a special form without calling it"}] [])
; recovers from missing condition (when)
(check "(let [x (when)]
(print x +))"
[{:message #($:find ".*macros.fnl:%d+: expected body")}
{:message "tried to reference a special form without calling it"}] [])
;; recovers from missing body (when)
(check "(let [x (when (< (+ 9 10) 21))]
(print x +))"
[{:message #($:find ".*macros.fnl:%d+: expected body")}
{:message "tried to reference a special form without calling it"}] [])
(check "(let [x {:mismatched :curly :braces}]
(print x +))"
[{:message "expected even number of values in table literal"}
{:message "tried to reference a special form without calling it"}] [])
nil)
{: test-compile-error