Implicitly-declared locals should have function scope
Affects | Status | Importance | Assigned to | Milestone | |
---|---|---|---|---|---|
Mars |
Fix Released
|
Critical
|
Matt Giuca |
Bug Description
Mars allows implicitly-declared locals in a limited way (through case statement pattern bindings). It will soon have full support for implicitly-declared locals (see bug #483082, bug #513633).
Currently, such things are either a) scoped for the rest of the function, from their declaration point (for assignments) or b) scoped for just the scope of their "case" statement (for case patterns). They need to be scoped for the entirety of the function, including before the point of assignment. This means they are treated as if there was an explicit declaration. There are a few subtle differences. Firstly, consider:
def foo() :: Int:
var y :: Int
y = x # "Use of uninitialised variable: x"
# Now bind x = 4
switch 4:
case x:
pass
return y
The third line will currently give the error "Undefined variable: x". But x *is* defined, it's just defined later, so it should have the error message "Use of uninitialised variable: x" instead.
Less subtly, say x was already a global variable in the above example. Now the current behaviour would be to successfully compile and run -- y would be bound to the value of the global x, because the local x hasn't been declared yet. The fact that x is global for part of the function and local for another part is confusing, so we want that to be an error. If the variable is implicitly declared anywhere in the function, then it should be treated as local for the whole function. Therefore, the above should *still* generate the error "Use of uninitialised variable: x", ignoring the presence of the global variable entirely. Note that this is exactly how Python behaves as well.
This can also (critically) be used to break type safety. The following program will currently type-check successfully, and then throw an internal error at runtime, on the call to add.
def foo(x :: Int) :: Int:
switch [1,2,3]:
case x: # Should be a type error
pass
return add(x, 1)
On the other hand, this program will not compile, when it should:
def bar(x :: Int) :: Int:
switch x:
case y:
y = add(y, 1)
return y # Undefined variable: y
The variable y should be bound to Int in the case statement, and remain bound throughout.
Fixes required:
- Need to know in advance of type checking which variable names are local. It is an error to read from a local which hasn't been declared.
- If a variable is bound in a pattern which is already declared, it should be unified with the existing binding, with a potential type error.
- Static variable bindings (type bindings) should be scoped to the function, not the case statement.
Related branches
- Matt Giuca: Approve
-
Diff: 2876 lines (+1121/-410)53 files modifieddoc/intro.rst (+3/-3)
doc/ref/procedures.rst (+23/-6)
doc/ref/types.rst (+152/-5)
lib/prelude.mar (+2/-2)
src/ast_cfg.m (+269/-157)
src/builtins.m (+24/-24)
src/interactive.m (+3/-3)
src/ir.m (+6/-2)
src/mars.m (+2/-2)
src/marsc.m (+2/-2)
src/parsem.m (+7/-2)
src/pretty.m (+2/-1)
src/tables.m (+13/-1)
src/typecheck.m (+243/-177)
src/types.m (+7/-1)
src/util.m (+16/-0)
test/cases/compiler/implicitvar.mar (+47/-0)
test/cases/compiler/patterntypeerror.mar (+27/-0)
test/cases/compiler/patterntypeerror.mtc (+8/-0)
test/cases/compiler/phipred.mtc (+0/-1)
test/cases/compiler/switch.mar (+33/-0)
test/cases/compiler/switch.mtc (+0/-5)
test/cases/semantic/assignglobal.mar (+3/-1)
test/cases/semantic/assignglobal.mtc (+1/-3)
test/cases/semantic/casetypecheck.mtc (+0/-1)
test/cases/semantic/dupfield.mar (+3/-2)
test/cases/semantic/dupfield.mtc (+4/-2)
test/cases/semantic/dupfield2.mar (+5/-0)
test/cases/semantic/dupfield2.mtc (+5/-0)
test/cases/semantic/dupfield3.mtc (+1/-1)
test/cases/semantic/dupvarcase.mar (+16/-0)
test/cases/semantic/dupvarcase.mtc (+8/-0)
test/cases/semantic/dupvarcase2.mar (+17/-0)
test/cases/semantic/dupvarcase2.mtc (+8/-0)
test/cases/semantic/localreadwrite.mtc (+0/-1)
test/cases/semantic/localreadwrite2.mar (+12/-0)
test/cases/semantic/localreadwrite2.mtc (+5/-0)
test/cases/semantic/localtypevar.mar (+14/-0)
test/cases/semantic/localtypevar.mtc (+7/-0)
test/cases/semantic/localtypevar2.mar (+10/-0)
test/cases/semantic/localtypevar2.mtc (+6/-0)
test/cases/semantic/monomorphism.mar (+16/-0)
test/cases/semantic/monomorphism.mtc (+9/-0)
test/cases/semantic/monomorphism2.mar (+23/-0)
test/cases/semantic/monomorphism2.mtc (+9/-0)
test/cases/semantic/typeerror2.mar (+6/-0)
test/cases/semantic/typeerror2.mtc (+9/-0)
test/cases/semantic/typeerror3.mar (+6/-0)
test/cases/semantic/typeerror3.mtc (+9/-0)
test/cases/semantic/typeerror4.mar (+6/-0)
test/cases/semantic/typeerror4.mtc (+9/-0)
test/cases/semantic/undefvar2.mar (+4/-2)
test/cases/semantic/undefvar2.mtc (+1/-3)
description: | updated |
Changed in mars: | |
status: | Fix Committed → Fix Released |
Once this is fixed, uncomment shadowed_ implicitvar_ return2 in cases/compiler/ inaccessible. mar. This tests other functionality, but couldn't be enabled until this bug is fixed.