Skip to content

fix: reject undeclared variables at semantic analysis - #23

Merged
esrrhs merged 1 commit into
masterfrom
fix/reject-undeclared-globals
Oct 3, 2026
Merged

esrrhs merged 1 commit into
masterfrom
fix/reject-undeclared-globals

Conversation

@esrrhs

@esrrhs esrrhs commented Oct 3, 2026

Copy link
Copy Markdown
Owner

Problem

Inside a function body, referencing a simple name that has no local declaration and does not exist at file level was lowered to the static const kNil singleton:

  • reads happened to evaluate to nil;
  • writes became kNil = rhs;, which fails to compile on the GCC backend (and silently mutates the shared nil singleton under TCC).

Since CompileFile precompiles the GCC variant, such scripts were unusable on every backend (TCC / GCC / interpreter), with an error that did not point at the script at all (cannot assign to variable 'kNil' with const-qualified type).

fakelua intentionally does not support Lua implicit globals (all state is expected to be declared explicitly), so this PR rejects them at semantic analysis with a locatable message instead of generating invalid C.

Change

  • New SemanticAnalysis::CheckUndeclaredVars (runs as part of Analyze): lexical-scope walk over the preprocessed tree; every kSimple name must resolve to a local/parameter/upvalue, a file-level symbol, or a host-registered native function.
    • read: unknown variable 'X'; fakelua has no implicit globals, declare it with 'local' first at file:line:col
    • write lvalue: undeclared variable 'X' on the left side of assignment; fakelua has no implicit globals, declare it with 'local' before assigning at file:line:col
  • New Vm::HasNativeFunctionWithPrefix to identify dotted native-library module roots.

Names that are NOT script variables (exemptions)

  1. Direct call chains f(...) and a.b.f(...) (non-colon): the whole dot chain is exempt, so late-registered host natives and package runtime names (e.g. Player.AddItem()) keep their runtime "function not found" behavior. The chain stops at square indexing — a[1].f() still requires a declared a — and colon receivers (a:m()) remain checked as ordinary variables.
  2. Dotted native-module roots (math/string/utf8/...): recognized via registered math.*-style names, covering both calls (math.floor(x)) and non-call constants (math.pi, string.charpattern). Bare natives (print/type/...) resolve via FindNativeFunction; _VERSION is accepted as before.
  3. Package declaration as the first statement in both forms: package "X" and package = "X" (preprocessing moves it into __fakelua_init).

Scope semantics mirror codegen: an until condition resolves inside the repeat body scope and can see locals declared there.

File-level non-local assignments remain rejected by the existing CheckFileLevelStmts.

Tests

  • New compile-fail regressions: undeclared read / conditional write-then-read / undeclared dotted base, with an exception test asserting message text, variable name, and file:line.
  • Positive test compiling a function using math.floor, math.pi, math.maxinteger, string.charpattern, table.insert, tostring.
  • Updated test_string.test_string_sub_undeclared_var to the new contract (compile-time rejection of an undeclared name, previously expected a runtime nil error).

Verification (macOS, all three engines)

  • Full suite on this change: 1568/1572; the only 4 failures are test_redis integration tests failing with Connection refused (no local redis server).
  • Rebuilt and re-ran on top of current origin/master: targeted 109 tests plus jitter/test_basic/closure/algo/test_table/test_math (503 tests) all pass.
  • Original repro (b = 1 style) now reports precisely:
    undeclared variable 'flag' ... at bug5_implicit_global.lua:3:20.

Undeclared simple names inside function bodies were emitted as the
static const kNil singleton: reads happened to yield nil, while writes
became 'kNil = rhs', which fails compilation on the GCC backend (and
silently mutated the shared nil singleton under TCC). Since CompileFile
precompiles the GCC variant, such scripts were unusable on every
backend.

fakelua intentionally does not support Lua implicit globals, so reject
them at semantic analysis with a locatable message:
- read  -> "unknown variable 'X'; fakelua has no implicit globals,
  declare it with 'local' first at file:line:col"
- write -> "undeclared variable 'X' on the left side of assignment..."

Exemptions (names that are not script variables):
- the whole dot call chain at direct non-colon call sites f(...) and
  a.b.f(...), so late-registered natives and package runtime names
  (Player.AddItem()) keep runtime "not found" errors; chains stop at
  square indexing (a[1].f() still requires a declared 'a') and colon
  receivers a:m() are still checked as variables;
- dotted native-module roots (math/string/utf8/...) recognized via the
  new Vm::HasNativeFunctionWithPrefix, covering both dotted calls and
  non-call module constants (math.pi, string.charpattern); bare natives
  (print/type/...) and _VERSION are also accepted;
- the first-statement package declaration in both forms
  (package "X" and package = "X", which preprocessing moves into
  __fakelua_init).

Scope resolution follows CGen: until conditions resolve inside the
repeat body scope and can see locals declared in that body.

Tests: add compile-fail regression Lua files (undeclared read /
write-then-read / undeclared dotted base) with exception tests that
check message and location, a positive module-name test, and update
test_string_sub_undeclared_var to expect the compile-time rejection
contract instead of a runtime nil error.
@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

❌ Patch coverage is 99.12281% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 87.93%. Comparing base (598632f) to head (9f34f35).

Files with missing lines Patch % Lines
src/compile/semantic_analysis.cpp 99.10% 2 Missing ⚠️
❗ Your organization needs to install the Codecov GitHub app to enable full functionality.
Additional details and impacted files
@@            Coverage Diff             @@
##           master      #23      +/-   ##
==========================================
+ Coverage   87.86%   87.93%   +0.06%     
==========================================
  Files         122      122              
  Lines       24657    24885     +228     
==========================================
+ Hits        21666    21882     +216     
- Misses       2991     3003      +12     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@esrrhs
esrrhs merged commit b83f574 into master Oct 3, 2026
9 checks passed
@esrrhs
esrrhs deleted the fix/reject-undeclared-globals branch October 3, 2026 08:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants