What happened (real downstream hit, Tidepool)
While hardening Tidepool's audio_game_init (Tidepool #23), this catch handler silently printed the wrong message:
define f(e) as:
local why is "init failed"
if e == 1:
why is "no builtins" # <- silent no-op
return why
print of (f of 1) # prints "init failed"
why is a question word, so the bare statement why is "no builtins" is the interrogative form (what/who/when/where/why/how is x) — an expression evaluated and discarded, not an assignment. The function returns the stale value. This is documented semantics, but it produced silently-wrong output in real consumer code and took a debugging round (module-scope suspicion, catch-scope suspicion) before the question-word rule surfaced.
The gap
--lint reports no issues found on the program above:
$ eigenscript --lint repro.eigs
repro.eigs: no issues found
An interrogative used as a bare expression statement has no effect at all — its result is always discarded. There is no legitimate reason to write one (an interrogative is only useful inside an expression, e.g. print of (why is x)). And when a same-named binding already exists in scope (here the local why two lines up), the statement is almost certainly a mistaken assignment.
Ask
A lint W-rule: warn on an interrogative expression used as a bare statement (result discarded). Suggested message shape:
W0xx: why is ... is an interrogative (question words cannot be assigned with is); as a statement its result is discarded. Rename the variable or use local.
The warning is strongest (arguably error-grade) when an identifier binding for that question word exists in scope — that's the "meant it as an assignment" signature. Flagging all statement-level interrogatives is probably fine too, since the discarded form is dead code either way.
Environment
EigenScript v0.29.0, found from Tidepool (runs --lint over every source in CI).
What happened (real downstream hit, Tidepool)
While hardening Tidepool's
audio_game_init(Tidepool #23), this catch handler silently printed the wrong message:whyis a question word, so the bare statementwhy is "no builtins"is the interrogative form (what/who/when/where/why/how is x) — an expression evaluated and discarded, not an assignment. The function returns the stale value. This is documented semantics, but it produced silently-wrong output in real consumer code and took a debugging round (module-scope suspicion, catch-scope suspicion) before the question-word rule surfaced.The gap
--lintreports no issues found on the program above:An interrogative used as a bare expression statement has no effect at all — its result is always discarded. There is no legitimate reason to write one (an interrogative is only useful inside an expression, e.g.
print of (why is x)). And when a same-named binding already exists in scope (here thelocal whytwo lines up), the statement is almost certainly a mistaken assignment.Ask
A lint W-rule: warn on an interrogative expression used as a bare statement (result discarded). Suggested message shape:
The warning is strongest (arguably error-grade) when an identifier binding for that question word exists in scope — that's the "meant it as an assignment" signature. Flagging all statement-level interrogatives is probably fine too, since the discarded form is dead code either way.
Environment
EigenScript v0.29.0, found from Tidepool (runs
--lintover every source in CI).