Repository navigation
fix: reject undeclared variables at semantic analysis - #23
Merged
Merged
Conversation
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 Report❌ Patch coverage is
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. 🚀 New features to boost your workflow:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Inside a function body, referencing a simple name that has no
localdeclaration and does not exist at file level was lowered to the static constkNilsingleton:kNil = rhs;, which fails to compile on the GCC backend (and silently mutates the shared nil singleton under TCC).Since
CompileFileprecompiles 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
SemanticAnalysis::CheckUndeclaredVars(runs as part ofAnalyze): lexical-scope walk over the preprocessed tree; everykSimplename must resolve to a local/parameter/upvalue, a file-level symbol, or a host-registered native function.unknown variable 'X'; fakelua has no implicit globals, declare it with 'local' first at file:line:colundeclared variable 'X' on the left side of assignment; fakelua has no implicit globals, declare it with 'local' before assigning at file:line:colVm::HasNativeFunctionWithPrefixto identify dotted native-library module roots.Names that are NOT script variables (exemptions)
f(...)anda.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 declareda— and colon receivers (a:m()) remain checked as ordinary variables.math/string/utf8/...): recognized via registeredmath.*-style names, covering both calls (math.floor(x)) and non-call constants (math.pi,string.charpattern). Bare natives (print/type/...) resolve viaFindNativeFunction;_VERSIONis accepted as before.package "X"andpackage = "X"(preprocessing moves it into__fakelua_init).Scope semantics mirror codegen: an
untilcondition resolves inside therepeatbody scope and can see locals declared there.File-level non-local assignments remain rejected by the existing
CheckFileLevelStmts.Tests
file:line.math.floor,math.pi,math.maxinteger,string.charpattern,table.insert,tostring.test_string.test_string_sub_undeclared_varto the new contract (compile-time rejection of an undeclared name, previously expected a runtime nil error).Verification (macOS, all three engines)
test_redisintegration tests failing withConnection refused(no local redis server).origin/master: targeted 109 tests plusjitter/test_basic/closure/algo/test_table/test_math(503 tests) all pass.b = 1style) now reports precisely:undeclared variable 'flag' ... at bug5_implicit_global.lua:3:20.