Scope and status
LinkTopaz 5.21 language mode. Rules cite the SELF source at af88afbc1d99. Examples target the public Topaz 5.21.x CLI.
Use this page to declare bindings and understand name lookup, assignment, and lexical capture. Types covers compatibility and shared inference. Functions and generics covers calls and generic specialization. Pattern forms, module lookup, and protocol resolution belong to their respective chapters.
Declaration syntax
LinkNamedLet = "let" "mut"? Identifier (":" Type)? "=" Expression ;Const = "const" Identifier (":" Type)? "=" Expression ;Assignment = AssignmentTarget "=" Expression ;AssignmentTarget = Identifier | MemberAccess | IndexAccess ;
This transcribed excerpt shows named declarations and plain assignment. Pattern forms, compound operators, and statement boundaries have separate grammar productions.
Source evidence
- parseLet / parseConst / parseExpressionStatement at
af88afbc1d99· 603-654 - parseLet / parseConst / parseExpressionStatement at
af88afbc1d99· 855-906
Declaration source observations
LinkBinding declaration forms
Each declaration requires an initializer after = and permits a type annotation. A named binding accepts at most one annotation, including an exported binding. Repeating the annotation produces TPZ2001 during parsing.
| Declaration | Binding form |
|---|---|
let | A pattern |
let mut | A single name |
const | A single name |
Why
An initializer gives every binding a value. One type annotation supplies one expectation without a second spelling or a competing annotation for the same name.
Source evidence
- parseLet; parseConst at
af88afbc1d99· 603-654 - parseLet and parseConst at
9b1bbdeceba9· 603-654 - parseTypedPattern at
9b1bbdeceba9· 509-527 - fail and expect at
9b1bbdeceba9· 188-211 - issues/31 at
af88afbc1d99
Binding mutability
| Declaration | Binding mutability |
|---|---|
let | Immutable |
let mut | Mutable |
const | Immutable |
Why
Reassignment is an explicit choice in the declaration. Immutable bindings let readers rely on one assigned value.
Source evidence
- parseLet; parseConst at
af88afbc1d99· 603-654 - bindNamedPatternType at
af88afbc1d99· 542-583 - bindCheckedDeclaration at
af88afbc1d99· 214-290
Literal const initializers
Integer literals and untagged string literals without interpolation satisfy the constant-expression requirement for const initializers.
Why
A literal supplies its value without consulting runtime state, so it can be used wherever a constant expression is required.
Source evidence
- checkBindingStatement at
af88afbc1d99· 476-504 - constantExpressionInvalid at
af88afbc1d99· 1144-1193 - constantExpressionInvalid at
af88afbc1d99· 1200-1251
Const references to let bindings
A const initializer must be a constant expression. Referencing an ordinary let or let mut binding is rejected with TPZ5001.
Why
An immutable runtime binding can still depend on runtime computation. Rejecting such references keeps constant-expression admission independent of runtime state.
Source evidence
- checkBindingStatement at
af88afbc1d99· 476-504 - constantExpressionInvalid at
af88afbc1d99· 1144-1193
Reserved None binding name
None cannot be a binding name. This includes let mut, const, typed binding patterns, and bare untyped patterns. The parser reports TPZ2012. Write None() for the Option constructor in patterns and values.
Why
Reserving None keeps absence recognizable in expressions and patterns. A local name cannot silently replace the Option constructor.
Source evidence
- rejectReservedBinding at
af88afbc1d99· 175-185 - parseLet; parseConst at
af88afbc1d99· 603-644 - parseTypedPattern at
af88afbc1d99· 509-527 - parseIdentifierPattern at
af88afbc1d99· 574-586
Constant expressions and retained values
A const initializer must pass ordinary name and type checking and the constant-expression check. Admitted forms include scalar and Length literals, None(), untagged strings with constant interpolations, available constant bindings, function values, parentheses, unary expressions, binary expressions other than in, arrays without spread, and record literals whose fields are constant expressions. A reference to a let binding, an ordinary function call, or a later constant in the same module is rejected. Const makes the binding immutable. Its data follows collection value semantics. Grouping a callee preserves its operation and receiver. The grouping does not change whether the call is a constant expression.
The numeric call forms are int.and, int.or, int.xor, and int.shift with one constant integer operand, and float.trunc, float.floor, float.ceil, and float.round with no arguments and a constant float receiver. These calls take no explicit type arguments or spreads. Integer operands are supplied positionally. Integer and float arithmetic expressions may supply their receivers.
Compiler-recognized std.typeset.fromScaledPoints and fromMilliPoints calls, Length conversion methods toPt, toMm, toCm, toIn and toScaledPoints, Pt.toMilliPoints, and Em.resolve admit constant operands. The std.typeset constructor recognition requires the generated standard module. Normal type and argument checks still apply. The implementation issue below preserves the permitted function-value expectation. Initialization-pass ordering is not a new language timing guarantee.
Why
A constant must not depend on runtime state. Grouping a callee preserves its operation and receiver, so it must preserve the same constant-expression decision.
Source evidence
- constantExpressionInvalid at
151141d7bb44· 1144-1251 - constantFloatShape; constantIntegerBitShape; integerBitConstant; floatIntegerConstant; lengthConstructorName; lengthConstantReceiver at
151141d7bb44· 1032-1143 - checkBindingStatement at
151141d7bb44· 476-506 - emitConstant at
151141d7bb44· 2360-2381 - emitModuleInit at
151141d7bb44· 393-437 - readBind at
151141d7bb44· 161-188 - Value at
151141d7bb44· 48-67 - integerBitConstant, floatIntegerConstant, lengthConstructorName, lengthConstantReceiver and constantExpressionInvalid at
9b1bbdeceba9· 1032-1193 - calleeReference at
9b1bbdeceba9· 86-110 - checkBindingStatement at
9b1bbdeceba9· 476-506 - issues/31 at
af88afbc1d99 - issues/31 at
1162031127a0
Assignment source observations
LinkCollection values
Since 6.0.0, Array, Map, Set, and ByteBuffer are values. Assignment, argument passing, return, and immutable capture each produce an independent value. Storing a value in a record field, collection element, or Map key or value does the same. Changing one value leaves the others unchanged.
The rule applies recursively inside records, enum payloads, Option, and Result. ByteBuffer contains data without host state and follows the same rule.
A write requires one let mut root binding, including writes through nested fields, indices, and collection operations. An immutable binding's data cannot change through an alias. The immutable assignment rule governs the write path. A function cannot change a caller's collection through a parameter. Bind a mutable local and return the changed value when the caller needs it.
An implementation may share storage and copy when a write occurs. A program cannot observe this storage sharing. The rule makes no promise about allocation, time, or space costs.
Shared handles represent host resources. File retains its shared cursor and closed state. A mutable capture shares one variable. A closure's write to that variable is visible outside the closure. A value assigned to another variable before the write remains unchanged.
See immutable assignment, capture observation, parameter bindings, and collection keys. The existing File cursor example and closed handle example show the resource exception.
Why
Each intent has one representation. Data uses values and host resources use handles. Independent values prevent hidden changes, so results are reproducible and easy to verify. Concurrent work on separate collection values has no data race through shared collection storage. Collections map directly to value representations such as Rust Vec and HashMap.
Source evidence
- issues/31 at
1162031127a0
Assignment target syntax
An assignment target must be an identifier, member access, or index access. Other expression shapes produce TPZ2003 during parsing. The target also undergoes receiver and mutability checks.
Why
Assignment must identify a location that can receive a value. A temporary result has no binding or element to update.
Source evidence
- parseExpressionStatement at
af88afbc1d99· 855-906
Assignment requires a mutable root
Direct replacement of an imported binding is rejected during resolution under the import assignment rule. After resolution succeeds, assignment to an immutable binding, or to a member or index rooted in that binding, is rejected with TPZ5003.
Why
Mutability follows the root binding through field and index access. Adding an access step must not bypass the declaration's mutation rule.
Source evidence
- assignmentRoot; checkAssignmentRootMutable at
af88afbc1d99· 2178-2220
Expected type of assignment
For =, the target type is the expected type of the right-hand expression.
Why
The destination already determines which values an assignment can store. Reusing that type gives empty or generic values the context they need.
Source evidence
- checkAssignmentStatement at
af88afbc1d99· 710-721
Assignment through optional access
A member or index assignment target whose receiver chain contains optional access ?. is rejected during checking with TPZ5001.
Why
An optional receiver can be absent, leaving no location to update. An explicit branch makes the skipped update and value evaluation visible.
Source evidence
- assignmentTargetHasOptional; reportMapAssignmentTarget at
af88afbc1d99· 607-654 - checkAssignmentStatement at
af88afbc1d99· 696-713
Assignment through Map indexing
Assignment through a Map index is rejected with TPZ5001. Use the Map mutation API to update that binding's value under collection value semantics.
Why
Map insertion can add a key as well as replace a value. A named mutation operation makes that behavior explicit without another update spelling.
Source evidence
- assignmentTargetHasOptional; reportMapAssignmentTarget at
af88afbc1d99· 607-654 - checkAssignmentStatement at
af88afbc1d99· 696-713
Scope and capture source observations
LinkLexical name lookup
Local name lookup searches active scopes from the innermost outward and selects the first matching binding. Value and type namespaces are searched separately.
Why
Lexical nesting determines what a name means without depending on the caller. Separate namespaces keep value lookup from selecting a type declaration.
Source evidence
- localBinding at
af88afbc1d99· 37-49 - reference at
af88afbc1d99· 231-264
Nested block scope
A nested block introduces a scope. After an inner binding is declared, later references inside the block resolve to it instead of an outer binding of the same name. References outside that block continue to resolve to the outer binding.
Why
A block bounds the visibility of local names. Temporary names inside it cannot replace the bindings used by surrounding code.
Source evidence
- localBinding at
af88afbc1d99· 37-49 - openNodeScope at
af88afbc1d99· 928-975 - walkNode at
af88afbc1d99· 1163-1168
Local initializer scope
Local let, const names become visible after their initializers have been resolved. An existing outer binding of the same name remains visible in the initializer.
Why
The initializer can use an existing outer value while computing its replacement. The new name becomes usable after that value exists.
Source evidence
- prebindChildDeclaration; bindStatementAfterWalk at
af88afbc1d99· 978-1071 - walkNodeChildren at
af88afbc1d99· 1080-1114
Unbound value names
An ordinary value identifier with no binding or built-in definition is rejected with TPZ5002. Namespace heads follow the module lookup rules.
Why
Every ordinary reference needs a declaration that determines its value. Static rejection prevents execution with an unspecified binding.
Source evidence
- inferIdentifierExpression at
af88afbc1d99· 578-647
Direct top-level forward reads
A top-level let initializer that directly reads a later let binding in the same module is rejected with TPZ3018 during resolution, for both entry and imported files. Declaration order is the statement order in that module.
Why
A declaration cannot read a runtime value that has not been initialized. Resolving direct reads before execution catches this mistake with the same TPZ3018 identity in entry and imported files. Local unbound-name errors and delayed runtime reads retain their separate TPZ5002 contract.
Source evidence
- topLevelRuntimeBindingIndexes; initializationReferenceViolation at
af88afbc1d99· 417-462 - initializationViolation; reportInitializationViolation; checkTopLevelInitializationOrder at
af88afbc1d99· 519-595 - validateImportedInitializers at
271329b482bc· 784-827 - initObserveReference at
271329b482bc· 360-389 - reportInitializationViolation/checkTopLevelInitializationOrder at
271329b482bc· 554-595 - pull/41 at
271329b482bc
Lexical capture identity
A function or lambda reference to an outer lexical binding in the same module resolves to that binding’s original declaration.
Why
A closure's references keep the meaning they had where it was defined. Calling it from a different scope must not retarget those names.
Source evidence
- captureTargetForReference at
af88afbc1d99· 779-841 - capturesForReference at
af88afbc1d99· 842-884
Values observed by existing and escaping closures
Closures that capture the same mutable binding observe its later assignments, including assignments made by another closure. An immutable capture retains its assigned value and cannot be rebound inside the closure. Collection values follow value semantics. Captured values and shared bindings remain available after the creating function returns while a closure retains them.
Why
A mutable captured binding represents one shared variable. Retaining captured storage while closures exist prevents references from outliving their values.
Source evidence
- emitClosure; emitLambda at
151141d7bb44· 1660-1738 - emitLet at
151141d7bb44· 2319-2358 - sharedInit; sharedRead; sharedWrite at
151141d7bb44· 167-184 - Value at
151141d7bb44· 48-74 - checkAssignmentRootMutable at
151141d7bb44· 2178-2220 - issues/31 at
1162031127a0
Initialization through branches, calls, and closures
Module-level let bindings become ready after their initializers complete, and function bindings become ready at their declaration position. A direct initializer read of the same or a later runtime binding is rejected with TPZ3018 during resolution in entry and imported files alike. The direct check follows an if or while condition, a match scrutinee, a for iterator, and the first comprehension source. It does not enter function, lambda, branch, arm, or loop bodies. Short-circuit right operands and optional-call arguments are delayed as well. A delayed reference reads the binding when execution reaches it. Reading before initialization faults with TPZ5002; reading after initialization returns the current value. Merely storing a closure does not evaluate its body.
Why
Module initialization can perform effects, so source order determines when runtime values become available. Delayed code must check the value when it actually executes.
Source evidence
- topLevelRuntimeBindingIndexes; initializationChildIsImmediate; initializationViolation at
151141d7bb44· 417-549 - emitModuleInit at
151141d7bb44· 393-456 - readBind; writeBind at
151141d7bb44· 161-208 - emitDirectCall at
151141d7bb44· 1258-1266 - CELL_HELPER at
151141d7bb44· 124-130 - top_cell; top_cell_set; top_cell_get at
151141d7bb44· 20-47 - validateImportedInitializers at
271329b482bc· 784-827 - initObserveReference at
271329b482bc· 360-389 - reportInitializationViolation/checkTopLevelInitializationOrder at
271329b482bc· 554-595 - pull/41 at
271329b482bc
Redeclaration and nested shadowing
A module-level name may be declared once. Let, const, function, type, and imported declarations participate in the same module name table, and a second declaration is rejected with TPZ3008. Reusing a name in a nested scope is shadowing. A repeated local const is rejected with TPZ5008. The same local scope must also reject a second let or let mut name; the current let pattern path bypasses the checker guard, which is recorded as an implementation limitation. The local let regression retains TPZ5008 as its intended rejection contract.
Why
A name should identify one declaration within a scope. Rejecting duplicates prevents silent replacement, while nested scopes make shadowing explicit.
Source evidence
- bindingForStatement; buildTable at
151141d7bb44· 296-366 - addLocalBinding at
151141d7bb44· 80-101 - prebindChildDeclaration; bindStatementAfterWalk; walkNodeChildren at
151141d7bb44· 978-1114 - Find which name a block uses at
151141d7bb44· 68-72 - bindCheckedDeclaration: let returns before the non-let TPZ5008 guard at
151141d7bb44· 215-290 - declarationIndexes preserves the first lexical name entry at
151141d7bb44· 1027-1042 - bindNamedPatternType calls bindSymbol without a duplicate-name check at
151141d7bb44· 542-585 - bindSymbol indexes by source span without a lexical name check at
151141d7bb44· 266-306
Examples and expected results
LinkEach example is a complete example. file. The expected success or rejection is independent of future observed output. All examples remain Pending. A source-backed expectation is not an executed result. Negative examples specify the intended cause and phase; their exact diagnostic locations remain unmeasured.
Immutable, mutable, and constant bindings
Linkimport std.iolet start: int = 1let mut count: int = startcount = 2const label = "Topaz"io.writeLine("{label}:{start}:{count}")
Topaz:1:2
Expected result
Expected result
Execution completes. Exit code 0.
Topaz:1:2
Observed assertions · passed · 5.21.3
Exit code 0.
Topaz:1:2
No stderr.
- Passed · expected 0; observed 0
- Passed · stdout matches the authored expectation
- Passed · stderr matches the authored expectation
- Passed · stdout matches the authored expectation
- Passed · stdout matches the authored expectation
- Passed · stdout matches the authored expectation
- Passed · stdout matches the authored expectation
Source evidence
- parseLet; parseConst at
af88afbc1d99· 603-654 - assignmentRoot; checkAssignmentRootMutable at
af88afbc1d99· 2178-2220 - bindNamedPatternType at
af88afbc1d99· 542-583 - bindCheckedDeclaration at
af88afbc1d99· 214-290 - checkBindingStatement at
af88afbc1d99· 476-504 - constantExpressionInvalid at
af88afbc1d99· 1144-1193 - constantExpressionInvalid at
af88afbc1d99· 1200-1251
An immutable binding cannot be reassigned
Linklet count: int = 1count = 2
No stdout.
error[TPZ5003]: `count` is immutable; declare it with `let mut` --> example.tpz:2:1 | 2 | count = 2 | ^^^^^ example.tpz: 1 type diagnostic
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5003bindings-assignment-to-immutable atexample.tpz:2:1
Observed assertions · passed · 5.21.3
Exit code 1.
No stdout.
error[TPZ5003]: `count` is immutable; declare it with `let mut`--> example.tpz:2:1|2 | count = 2| ^^^^^example.tpz: 1 type diagnostic
- Passed · expected nonzero; observed 1
- Passed · stdout matches the authored expectation
- Passed · checking TPZ5003; cause bindings-assignment-to-immutable; primary location asserted; additional diagnostics allow
- Passed · checking TPZ5003; cause bindings-assignment-to-immutable; primary location asserted; additional diagnostics allow
Source evidence
- assignmentRoot; checkAssignmentRootMutable at
af88afbc1d99· 2178-2220 - bindNamedPatternType at
af88afbc1d99· 542-583 - bindCheckedDeclaration at
af88afbc1d99· 214-290
An ordinary let reference is not a const initializer
Linklet source: int = 1const frozen: int = source
No stdout.
error[TPZ5001]: `const` initializers must be constant expressions (§4) --> example.tpz:2:21 | 2 | const frozen: int = source | ^^^^^^ example.tpz: 1 type diagnostic
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5001bindings-const-runtime-reference atexample.tpz:2:21
Observed assertions · passed · 5.21.3
Exit code 1.
No stdout.
error[TPZ5001]: `const` initializers must be constant expressions (§4)--> example.tpz:2:21|2 | const frozen: int = source| ^^^^^^example.tpz: 1 type diagnostic
- Passed · expected nonzero; observed 1
- Passed · stdout matches the authored expectation
- Passed · checking TPZ5001; cause bindings-const-runtime-reference; primary location asserted; additional diagnostics allow
Source evidence
- checkBindingStatement at
af88afbc1d99· 476-504 - constantExpressionInvalid at
af88afbc1d99· 1144-1193 - constantExpressionInvalid at
af88afbc1d99· 1200-1251
None cannot name a mutable binding
Linklet mut None = 1
No stdout.
error[TPZ2012]: `None` is the Option constructor (§22.1) and cannot be a binding name --> example.tpz:1:9 | 1 | let mut None = 1 | ^^^^ example.tpz: 1 diagnostic
Expected result
Observed assertions · passed · 5.21.3
Exit code 1.
No stdout.
error[TPZ2012]: `None` is the Option constructor (§22.1) and cannot be a binding name--> example.tpz:1:9|1 | let mut None = 1| ^^^^example.tpz: 1 diagnostic
- Passed · expected nonzero; observed 1
- Passed · stdout matches the authored expectation
- Passed · checking TPZ2012; cause bindings-none-name; primary location asserted; additional diagnostics allow
Source evidence
- rejectReservedBinding at
af88afbc1d99· 175-185 - parseLet; parseConst at
af88afbc1d99· 603-644 - parseTypedPattern at
af88afbc1d99· 509-527 - parseIdentifierPattern at
af88afbc1d99· 574-586
A nested binding shadows the outer one
Linkimport std.iolet value: int = 1let nested: string = {let value: string = "inner"value}let original: int = valueio.writeLine("{nested}:{original}")
inner:1
Expected result
Expected result
Execution completes. Exit code 0.
inner:1
Observed assertions · passed · 5.21.3
Exit code 0.
inner:1
No stderr.
- Passed · expected 0; observed 0
- Passed · stdout matches the authored expectation
- Passed · stderr matches the authored expectation
- Passed · stdout matches the authored expectation
Source evidence
- localBinding at
af88afbc1d99· 37-49 - reference at
af88afbc1d99· 231-264 - openNodeScope at
af88afbc1d99· 928-975 - walkNode at
af88afbc1d99· 1163-1168
An ordinary unbound name is rejected
Linklet copied: int = missing
No stdout.
error[TPZ5002]: `missing` is not bound --> example.tpz:1:19 | 1 | let copied: int = missing | ^^^^^^^ example.tpz: 1 type diagnostic
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5002bindings-unbound-read atexample.tpz:1:19
Observed assertions · passed · 5.21.3
Exit code 1.
No stdout.
error[TPZ5002]: `missing` is not bound--> example.tpz:1:19|1 | let copied: int = missing| ^^^^^^^example.tpz: 1 type diagnostic
- Passed · expected nonzero; observed 1
- Passed · stdout matches the authored expectation
- Passed · checking TPZ5002; cause bindings-unbound-read; primary location asserted; additional diagnostics allow
Source evidence
- inferIdentifierExpression at
af88afbc1d99· 578-647
A later top-level let is unavailable to a direct initializer
Linklet before: int = afterlet after: int = 1
No stdout.
Expected result
Expected result
Rejection during resolve. Nonzero exit.
TPZ3018module-initializer-not-ready atexample.tpz:1:19
The direct module initializer uses the same TPZ3018 resolve contract in entry and imported files. Current SELF rejects this entry source with TPZ5002; the existing receipt remains historical evidence for that earlier identity. The expected rejection and location are retained, and only the diagnostic assertion is limited.
Source evidence
- topLevelRuntimeBindingIndexes; initializationReferenceViolation at
af88afbc1d99· 417-462 - initializationViolation; reportInitializationViolation; checkTopLevelInitializationOrder at
af88afbc1d99· 519-595 - validateImportedInitializers at
271329b482bc· 784-827 - initObserveReference at
271329b482bc· 360-389 - reportInitializationViolation/checkTopLevelInitializationOrder at
271329b482bc· 554-595 - pull/41 at
271329b482bc
A lambda refers to an outer binding
Linkimport std.iolet anchor: int = 7let reader: (int) -> int = (value) => anchorlet observed: int = {let anchor: int = 99reader(0)}io.writeLine("{observed}")
7
Expected result
Expected result
Execution completes. Exit code 0.
7
Observed assertions · passed · 5.21.3
Exit code 0.
7
No stderr.
- Passed · expected 0; observed 0
- Passed · stdout matches the authored expectation
- Passed · stderr matches the authored expectation
Source evidence
- capturesForReference at
af88afbc1d99· 842-884
Two escaping closures share one mutable binding
Linkimport std.iofunction makeCounters() -> { advance: () -> int, read: () -> int } {let mut count: int = 0let advance: () -> int = () => {count += 1count}let read: () -> int = () => countcount = 4{ advance: advance, read: read }}let counters = makeCounters()io.writeLine("{counters.read()}:{counters.advance()}:{counters.read()}")
4:5:5
Expected result
Expected result
Execution completes. Exit code 0.
4:5:5
Source evidence
- emitClosure; emitLambda at
151141d7bb44· 1660-1738 - emitLet at
151141d7bb44· 2319-2358 - sharedInit; sharedRead; sharedWrite at
151141d7bb44· 167-184 - Value at
151141d7bb44· 48-74 - checkAssignmentRootMutable at
151141d7bb44· 2178-2220
Immutable captures retain independent collection values
Linkimport std.iofunction makeReader() -> () -> string {let mut current: int = 1let snapshot: int = currentlet items: Array<int> = [10]let mut alias: Array<int> = itemslet read: () -> string = () => "{current}:{snapshot}:{items[0]}"current = 2alias[0] = 30read}let read = makeReader()io.writeLine(read())
2:1:10
Expected result
Expected result
Execution completes. Exit code 0.
2:1:10
Source evidence
- emitClosure; emitLambda at
151141d7bb44· 1660-1738 - emitLet at
151141d7bb44· 2319-2358 - sharedInit; sharedRead; sharedWrite at
151141d7bb44· 167-184 - Value at
151141d7bb44· 48-74 - checkAssignmentRootMutable at
151141d7bb44· 2178-2220 - issues/31 at
1162031127a0
A closure cannot reassign an immutable capture
Linkfunction makeReader() -> () -> int {let count: int = 1() => {count = 2count}}
No stdout.
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5003bindings-assignment-to-immutable atexample.tpz:4:9
Source evidence
- emitClosure; emitLambda at
151141d7bb44· 1660-1738 - emitLet at
151141d7bb44· 2319-2358 - sharedInit; sharedRead; sharedWrite at
151141d7bb44· 167-184 - Value at
151141d7bb44· 48-74 - checkAssignmentRootMutable at
151141d7bb44· 2178-2220
Call a stored closure after its later binding initializes
Linkimport std.iolet read: () -> int = () => laterlet later: int = 7io.writeLine("{read()}")
7
Expected result
Expected result
Execution completes. Exit code 0.
7
Source evidence
- topLevelRuntimeBindingIndexes; initializationChildIsImmediate; initializationViolation at
151141d7bb44· 417-549 - emitModuleInit at
151141d7bb44· 393-456 - readBind; writeBind at
151141d7bb44· 161-208 - emitDirectCall at
151141d7bb44· 1258-1266 - CELL_HELPER at
151141d7bb44· 124-130 - top_cell; top_cell_set; top_cell_get at
151141d7bb44· 20-47
A skipped branch does not read a later binding
Linkimport std.iolet chosen: int = if (false) { after } else { 2 }let after: int = 7io.writeLine("{chosen}:{after}")
2:7
Expected result
Expected result
Execution completes. Exit code 0.
2:7
Source evidence
- topLevelRuntimeBindingIndexes; initializationChildIsImmediate; initializationViolation at
151141d7bb44· 417-549 - emitModuleInit at
151141d7bb44· 393-456 - readBind; writeBind at
151141d7bb44· 161-208 - emitDirectCall at
151141d7bb44· 1258-1266 - CELL_HELPER at
151141d7bb44· 124-130 - top_cell; top_cell_set; top_cell_get at
151141d7bb44· 20-47
The condition is an immediate initializer read
Linklet chosen: int = if (after) { 1 } else { 2 }let after: bool = true
No stdout.
Expected result
Expected result
Rejection during resolve. Nonzero exit.
TPZ3018module-initializer-not-ready atexample.tpz:1:23
The direct module initializer uses the same TPZ3018 resolve contract in entry and imported files. Current SELF rejects this entry source with TPZ5002; the existing receipt remains historical evidence for that earlier identity. The expected rejection and location are retained, and only the diagnostic assertion is limited.
Source evidence
- topLevelRuntimeBindingIndexes; initializationChildIsImmediate; initializationViolation at
151141d7bb44· 417-549 - emitModuleInit at
151141d7bb44· 393-456 - readBind; writeBind at
151141d7bb44· 161-208 - emitDirectCall at
151141d7bb44· 1258-1266 - CELL_HELPER at
151141d7bb44· 124-130 - top_cell; top_cell_set; top_cell_get at
151141d7bb44· 20-47 - validateImportedInitializers at
271329b482bc· 784-827 - initObserveReference at
271329b482bc· 360-389 - reportInitializationViolation/checkTopLevelInitializationOrder at
271329b482bc· 554-595 - pull/41 at
271329b482bc
An executed branch faults before a later binding initializes
Linklet chosen: int = if (true) { after } else { 0 }let after: int = 7
No stdout.
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5002bindings-direct-forward-read atexample.tpz:1:31
Source evidence
- topLevelRuntimeBindingIndexes; initializationChildIsImmediate; initializationViolation at
151141d7bb44· 417-549 - emitModuleInit at
151141d7bb44· 393-456 - readBind; writeBind at
151141d7bb44· 161-208 - emitDirectCall at
151141d7bb44· 1258-1266 - CELL_HELPER at
151141d7bb44· 124-130 - top_cell; top_cell_set; top_cell_get at
151141d7bb44· 20-47
An inner scope may reuse an outer name
Linkimport std.iolet value: int = 1let inner: int = {let value: int = 2value}io.writeLine("{value}:{inner}")
1:2
Expected result
Expected result
Execution completes. Exit code 0.
1:2
Source evidence
- bindingForStatement; buildTable at
151141d7bb44· 296-366 - addLocalBinding at
151141d7bb44· 80-101 - prebindChildDeclaration; bindStatementAfterWalk; walkNodeChildren at
151141d7bb44· 978-1114 - Find which name a block uses at
151141d7bb44· 68-72
Module declarations cannot reuse an existing name
Linklet value: int = 1const value: int = 2
No stdout.
Expected result
Expected result
Rejection during resolve. Nonzero exit.
TPZ3008bindings-same-scope-redeclaration atexample.tpz:2:7
Source evidence
- bindingForStatement; buildTable at
151141d7bb44· 296-366 - addLocalBinding at
151141d7bb44· 80-101 - prebindChildDeclaration; bindStatementAfterWalk; walkNodeChildren at
151141d7bb44· 978-1114 - Find which name a block uses at
151141d7bb44· 68-72
A local let duplicate retains the intended static rejection
Linkfunction invalid() -> int {let value: int = 1let value: int = 2value}
No stdout.
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5008bindings-local-same-scope-redeclaration atexample.tpz:3:9
Source evidence
- bindingForStatement; buildTable at
151141d7bb44· 296-366 - addLocalBinding at
151141d7bb44· 80-101 - prebindChildDeclaration; bindStatementAfterWalk; walkNodeChildren at
151141d7bb44· 978-1114 - Find which name a block uses at
151141d7bb44· 68-72 - bindCheckedDeclaration: let returns before the non-let TPZ5008 guard at
151141d7bb44· 215-290 - declarationIndexes preserves the first lexical name entry at
151141d7bb44· 1027-1042 - bindNamedPatternType calls bindSymbol without a duplicate-name check at
151141d7bb44· 542-585 - bindSymbol indexes by source span without a lexical name check at
151141d7bb44· 266-306
Constant expressions retain independent collection values
Linkimport std.ioconst base: int = 2const bits: int = base.shift(1).or(1)const rounded: int = (2.2).ceil()const data = { label: "value-{base}", values: [base, bits] }let mut alias: Array<int> = data.valuesalias[0] = 9io.writeLine("{data.label}:{bits}:{rounded}:{data.values[0]}")
value-2:5:3:2
Expected result
Expected result
Execution completes. Exit code 0.
value-2:5:3:2
Source evidence
- constantExpressionInvalid at
151141d7bb44· 1144-1251 - constantFloatShape; constantIntegerBitShape; integerBitConstant; floatIntegerConstant; lengthConstructorName; lengthConstantReceiver at
151141d7bb44· 1032-1143 - checkBindingStatement at
151141d7bb44· 476-506 - emitConstant at
151141d7bb44· 2360-2381 - emitModuleInit at
151141d7bb44· 393-437 - readBind at
151141d7bb44· 161-188 - Value at
151141d7bb44· 48-67 - issues/31 at
1162031127a0
A constant cannot read a later same-module constant
Linkconst before: int = afterconst after: int = 7
No stdout.
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5002bindings-direct-forward-read atexample.tpz:1:21
Source evidence
- constantExpressionInvalid at
151141d7bb44· 1144-1251 - constantFloatShape; constantIntegerBitShape; integerBitConstant; floatIntegerConstant; lengthConstructorName; lengthConstantReceiver at
151141d7bb44· 1032-1143 - checkBindingStatement at
151141d7bb44· 476-506 - emitConstant at
151141d7bb44· 2360-2381 - emitModuleInit at
151141d7bb44· 393-437 - readBind at
151141d7bb44· 161-188 - Value at
151141d7bb44· 48-67
A permitted named function value remains the expected constant value
Linkimport std.iofunction answer() -> int { 7 }const saved: () -> int = answerio.writeLine("{saved()}")
7
Expected result
Expected result
Execution completes. Exit code 0.
7
Source evidence
- constantExpressionInvalid at
151141d7bb44· 1144-1251 - constantFloatShape; constantIntegerBitShape; integerBitConstant; floatIntegerConstant; lengthConstructorName; lengthConstantReceiver at
151141d7bb44· 1032-1143 - checkBindingStatement at
151141d7bb44· 476-506 - emitConstant at
151141d7bb44· 2360-2381 - emitModuleInit at
151141d7bb44· 393-437 - readBind at
151141d7bb44· 161-188 - Value at
151141d7bb44· 48-67
Store the function value after its declaration executes
Linkimport std.iofunction answer() -> int { 7 }let saved: () -> int = answerio.writeLine("{saved()}")
7
Expected result
Expected result
Execution completes. Exit code 0.
7
Source evidence
- constantExpressionInvalid at
151141d7bb44· 1144-1251 - constantFloatShape; constantIntegerBitShape; integerBitConstant; floatIntegerConstant; lengthConstructorName; lengthConstantReceiver at
151141d7bb44· 1032-1143 - checkBindingStatement at
151141d7bb44· 476-506 - emitConstant at
151141d7bb44· 2360-2381 - emitModuleInit at
151141d7bb44· 393-437 - readBind at
151141d7bb44· 161-188 - Value at
151141d7bb44· 48-67
The local const path checks duplicate names
Linkfunction invalid() -> int {const value: int = 1const value: int = 2value}
No stdout.
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5008bindings-local-same-scope-redeclaration atexample.tpz:3:11
Source evidence
- bindingForStatement; buildTable at
151141d7bb44· 296-366 - addLocalBinding at
151141d7bb44· 80-101 - prebindChildDeclaration; bindStatementAfterWalk; walkNodeChildren at
151141d7bb44· 978-1114 - Find which name a block uses at
151141d7bb44· 68-72 - bindCheckedDeclaration: let returns before the non-let TPZ5008 guard at
151141d7bb44· 215-290 - declarationIndexes preserves the first lexical name entry at
151141d7bb44· 1027-1042 - bindNamedPatternType calls bindSymbol without a duplicate-name check at
151141d7bb44· 542-585 - bindSymbol indexes by source span without a lexical name check at
151141d7bb44· 266-306
Repeat a let annotation
Linklet answer: int: int = 1
No stdout.
Expected result
Expected result
Rejection during parse. Nonzero exit.
TPZ2001bindings-duplicate-type-annotation atexample.tpz:1:16
Source evidence
- parseLet and parseConst at
9b1bbdeceba9· 603-654 - parseTypedPattern at
9b1bbdeceba9· 509-527 - fail and expect at
9b1bbdeceba9· 188-211
Repeat an exported let annotation
Linkexport let answer: int: int = 1
No stdout.
Expected result
Expected result
Rejection during parse. Nonzero exit.
TPZ2001bindings-duplicate-type-annotation atexample.tpz:1:23
Source evidence
- parseLet and parseConst at
9b1bbdeceba9· 603-654 - parseTypedPattern at
9b1bbdeceba9· 509-527 - fail and expect at
9b1bbdeceba9· 188-211
Repeat a mutable binding annotation
Linklet mut answer: int: int = 1
No stdout.
Expected result
Expected result
Rejection during parse. Nonzero exit.
TPZ2001bindings-duplicate-type-annotation atexample.tpz:1:20
Source evidence
- parseLet and parseConst at
9b1bbdeceba9· 603-654 - parseTypedPattern at
9b1bbdeceba9· 509-527 - fail and expect at
9b1bbdeceba9· 188-211
Repeat a constant annotation
Linkconst answer: int: int = 1
No stdout.
Expected result
Expected result
Rejection during parse. Nonzero exit.
TPZ2001bindings-duplicate-type-annotation atexample.tpz:1:18
Source evidence
- parseLet and parseConst at
9b1bbdeceba9· 603-654 - parseTypedPattern at
9b1bbdeceba9· 509-527 - fail and expect at
9b1bbdeceba9· 188-211
Annotate each binding once
Linklet local: int = 1export let published: int = locallet mut count: int = localconst label: string = "Topaz"export const publishedLabel: string = label
No stdout.
Expected result
Expected result
Checking accepts the program. Exit code 0.
Source evidence
- parseLet and parseConst at
9b1bbdeceba9· 603-654 - parseTypedPattern at
9b1bbdeceba9· 509-527 - fail and expect at
9b1bbdeceba9· 188-211
Group admitted numeric callees
Linkconst bits: int = ((5).and)(3)const rounded: int = ((2.2).ceil)()
No stdout.
Expected result
Expected result
Checking accepts the program. Exit code 0.
Source evidence
- integerBitConstant, floatIntegerConstant, lengthConstructorName, lengthConstantReceiver and constantExpressionInvalid at
9b1bbdeceba9· 1032-1193 - calleeReference at
9b1bbdeceba9· 86-110
Group admitted length callees
Linkimport std.typesetconst physical: Length<Pt> = ((1in).toPt)()const relative: Length<Pt> = ((2em).resolve)(12pt)
No stdout.
Expected result
Expected result
Checking accepts the program. Exit code 0.
Source evidence
- integerBitConstant, floatIntegerConstant, lengthConstructorName, lengthConstantReceiver and constantExpressionInvalid at
9b1bbdeceba9· 1032-1193 - calleeReference at
9b1bbdeceba9· 86-110
Use admitted calls and a grouped length constructor
Linkimport std.typesetconst bits: int = (5).and(3)const rounded: int = (2.2).ceil()const physical: Length<Pt> = (1in).toPt()const relative: Length<Pt> = (2em).resolve(12pt)const constructed: Length<Pt> = (typeset.fromMilliPoints)(1000)
No stdout.
Expected result
Expected result
Checking accepts the program. Exit code 0.
Source evidence
- integerBitConstant, floatIntegerConstant, lengthConstructorName, lengthConstantReceiver and constantExpressionInvalid at
9b1bbdeceba9· 1032-1193 - calleeReference at
9b1bbdeceba9· 86-110
An ordinary call remains a runtime computation
Linkfunction answer() -> int { 3 }const direct: int = answer()
No stdout.
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5001bindings-const-ordinary-call atexample.tpz:2:21
Source evidence
- integerBitConstant, floatIntegerConstant, lengthConstructorName, lengthConstantReceiver and constantExpressionInvalid at
9b1bbdeceba9· 1032-1193 - checkBindingStatement at
9b1bbdeceba9· 476-506
Grouping does not make an ordinary call constant
Linkfunction answer() -> int { 3 }const grouped: int = (answer)()
No stdout.
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5001bindings-const-ordinary-call atexample.tpz:2:22
Source evidence
- integerBitConstant, floatIntegerConstant, lengthConstructorName, lengthConstantReceiver and constantExpressionInvalid at
9b1bbdeceba9· 1032-1193 - checkBindingStatement at
9b1bbdeceba9· 476-506
Assignment keeps array values independent
Linkimport std.iolet a = [1, 2]let mut b = ab.push(3)io.writeLine("{a.length}:{b.length}")
2:3
Expected result
Expected result
Execution completes. Exit code 0.
2:3
Source evidence
- issues/31 at
1162031127a0
Return a changed local value from a function
Linkimport std.iofunction appended(values: Array<int>) -> Array<int> {let mut local = valueslocal.push(3)local}let items = [1, 2]let changed = appended(items)io.writeLine("{items.length}:{changed.length}")
2:3
Expected result
Expected result
Execution completes. Exit code 0.
2:3
Source evidence
- issues/31 at
1162031127a0
A saved record keeps its collection field
Linkimport std.iolet mut r = { items: [1, 2] }let saved = rr.items[0] = 5io.writeLine("{saved.items[0]}:{r.items[0]}")
1:5
Expected result
Expected result
Execution completes. Exit code 0.
1:5
Source evidence
- issues/31 at
1162031127a0
Insert and remove entries in an independent Map value
Linkimport std.iolet entries = map { "one": 1 }let mut changed = entrieschanged.insert("two", 2)io.writeLine("{entries.length}:{changed.length}")changed.remove("one")io.writeLine("{entries.keys[0]}:{changed.keys[0]}")
1:2 one:two
Expected result
Expected result
Execution completes. Exit code 0.
1:2one:two
Source evidence
- issues/31 at
1162031127a0
Add and remove members in an independent Set value
Linkimport std.iolet values = set { 1 }let mut changed = valueschanged.add(2)io.writeLine("{values.length}:{changed.length}")changed.remove(1)io.writeLine("{1 in values}:{1 in changed}")
1:2 true:false
Expected result
Expected result
Execution completes. Exit code 0.
1:2true:false
Source evidence
- issues/31 at
1162031127a0
A mutable capture changes one variable and preserves earlier values
Linkimport std.iolet mut items = [1]let before = itemslet append: () -> () = () => { items.push(2) }append()io.writeLine("{items.length}:{before.length}")
2:1
Expected result
Expected result
Execution completes. Exit code 0.
2:1
Source evidence
- issues/31 at
1162031127a0