Skip to content
Topaz6.0 · Before release
Download

Search documentation

Specification 6.0 · Not released · Tested product 5.21.3

Use arrow keys to choose a result. Press Enter to open.

Type an API name, diagnostic number, rule, or project topic.

    Language · chapter 04 · 21 rules

    Bindings and scope

    Defines how names are introduced, resolved, and reassigned.

    Scope and status

    Link

    Topaz 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

    Link
    EBNF
    1. NamedLet = "let" "mut"? Identifier (":" Type)? "=" Expression ;
    2. Const = "const" Identifier (":" Type)? "=" Expression ;
    3. Assignment = AssignmentTarget "=" Expression ;
    4. 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

    Declaration source observations

    Link
    Ruletopaz.rule.bindings-and-scope.declaration-forms

    Binding 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.

    DeclarationBinding form
    letA pattern
    let mutA single name
    constA 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

    Binding mutability

    DeclarationBinding mutability
    letImmutable
    let mutMutable
    constImmutable

    Why

    Reassignment is an explicit choice in the declaration. Immutable bindings let readers rely on one assigned value.

    Source evidence
    Ruletopaz.rule.bindings-and-scope.const-literals

    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

    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
    Ruletopaz.rule.bindings-and-scope.none-binding-name

    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
    Ruletopaz.rule.bindings-pending.constant-evaluation

    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

    Assignment source observations

    Link

    Collection 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
    Ruletopaz.rule.bindings-and-scope.assignment-forms

    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

    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
    Ruletopaz.rule.bindings-and-scope.assignment-type

    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

    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
    Ruletopaz.rule.bindings-and-scope.map-assignment

    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

    Scope and capture source observations

    Link
    Ruletopaz.rule.bindings-and-scope.lexical-lookup

    Lexical 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
    Ruletopaz.rule.bindings-and-scope.block-scope

    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
    Ruletopaz.rule.bindings-and-scope.unbound-name

    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

    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
    Ruletopaz.rule.bindings-and-scope.lexical-capture

    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
    Ruletopaz.rule.bindings-pending.capture-observation

    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
    Ruletopaz.rule.bindings-pending.initialization-order

    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

    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

    Examples and expected results

    Link

    Each example is a complete example.tpz 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

    Link
    example.tpzImmutable, mutable, and constant bindingsOpen in Playground
    1. import std.io
    2. let start: int = 1
    3. let mut count: int = start
    4. count = 2
    5. const label = "Topaz"
    6. io.writeLine("{label}:{start}:{count}")
    Recorded outputexit 0
    Topaz:1:2
    
    $ topaz run example.tpz --compiler self · recorded with topaz 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. Topaz:1:2
    Observed assertions · passed · 5.21.3

    Exit code 0.

    Observed stdout
    1. 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

    An immutable binding cannot be reassigned

    Link
    example.tpzAn immutable binding cannot be reassignedOpen in Playground
    1. let count: int = 1
    2. count = 2
    Recorded outputexit 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
    
    $ topaz check example.tpz --compiler self · recorded with topaz 5.21.3
    Expected result

    Expected result

    Rejection during check. Nonzero exit.

    • TPZ5003 bindings-assignment-to-immutable at example.tpz:2:1
    Observed assertions · passed · 5.21.3

    Exit code 1.

    No stdout.

    Observed stderr
    1. error[TPZ5003]: `count` is immutable; declare it with `let mut`
    2. --> example.tpz:2:1
    3. |
    4. 2 | count = 2
    5. | ^^^^^
    6. 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

    An ordinary let reference is not a const initializer

    Link
    example.tpzAn ordinary let reference is not a const initializerOpen in Playground
    1. let source: int = 1
    2. const frozen: int = source
    Recorded outputexit 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
    
    $ topaz check example.tpz --compiler self · recorded with topaz 5.21.3
    Expected result

    Expected result

    Rejection during check. Nonzero exit.

    • TPZ5001 bindings-const-runtime-reference at example.tpz:2:21
    Observed assertions · passed · 5.21.3

    Exit code 1.

    No stdout.

    Observed stderr
    1. error[TPZ5001]: `const` initializers must be constant expressions (§4)
    2. --> example.tpz:2:21
    3. |
    4. 2 | const frozen: int = source
    5. | ^^^^^^
    6. 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

    None cannot name a mutable binding

    Link
    example.tpzNone cannot name a mutable bindingOpen in Playground
    1. let mut None = 1
    Recorded outputexit 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
    
    $ topaz check example.tpz --compiler self · recorded with topaz 5.21.3
    Expected result

    Expected result

    Rejection during parse. Nonzero exit.

    • TPZ2012 bindings-none-name at example.tpz:1:9
    Observed assertions · passed · 5.21.3

    Exit code 1.

    No stdout.

    Observed stderr
    1. error[TPZ2012]: `None` is the Option constructor (§22.1) and cannot be a binding name
    2. --> example.tpz:1:9
    3. |
    4. 1 | let mut None = 1
    5. | ^^^^
    6. 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

    A nested binding shadows the outer one

    Link
    example.tpzA nested binding shadows the outer oneOpen in Playground
    1. import std.io
    2. let value: int = 1
    3. let nested: string = {
    4. let value: string = "inner"
    5. value
    6. }
    7. let original: int = value
    8. io.writeLine("{nested}:{original}")
    Recorded outputexit 0
    inner:1
    
    $ topaz run example.tpz --compiler self · recorded with topaz 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. inner:1
    Observed assertions · passed · 5.21.3

    Exit code 0.

    Observed stdout
    1. 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

    Link
    example.tpzAn ordinary unbound name is rejectedOpen in Playground
    1. let copied: int = missing
    Recorded outputexit 1
    No stdout.
    error[TPZ5002]: `missing` is not bound
     --> example.tpz:1:19
      |
    1 | let copied: int = missing
      |                   ^^^^^^^
    
    example.tpz: 1 type diagnostic
    
    $ topaz check example.tpz --compiler self · recorded with topaz 5.21.3
    Expected result

    Expected result

    Rejection during check. Nonzero exit.

    • TPZ5002 bindings-unbound-read at example.tpz:1:19
    Observed assertions · passed · 5.21.3

    Exit code 1.

    No stdout.

    Observed stderr
    1. error[TPZ5002]: `missing` is not bound
    2. --> example.tpz:1:19
    3. |
    4. 1 | let copied: int = missing
    5. | ^^^^^^^
    6. 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

    A later top-level let is unavailable to a direct initializer

    Link
    example.tpzA later top-level let is unavailable to a direct initializerOpen in Playground
    1. let before: int = after
    2. let after: int = 1
    Expected outputNonzero exit
    No stdout.
    $ topaz check example.tpz --compiler self · target product 5.21.3
    Expected result

    Expected result

    Rejection during resolve. Nonzero exit.

    • TPZ3018 module-initializer-not-ready at example.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

    A lambda refers to an outer binding

    Link
    example.tpzA lambda refers to an outer bindingOpen in Playground
    1. import std.io
    2. let anchor: int = 7
    3. let reader: (int) -> int = (value) => anchor
    4. let observed: int = {
    5. let anchor: int = 99
    6. reader(0)
    7. }
    8. io.writeLine("{observed}")
    Recorded outputexit 0
    7
    
    $ topaz run example.tpz --compiler self · recorded with topaz 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. 7
    Observed assertions · passed · 5.21.3

    Exit code 0.

    Observed stdout
    1. 7

    No stderr.

    • Passed · expected 0; observed 0
    • Passed · stdout matches the authored expectation
    • Passed · stderr matches the authored expectation
    Source evidence

    Two escaping closures share one mutable binding

    Link
    example.tpzTwo escaping closures share one mutable binding
    1. import std.io
    2. function makeCounters() -> { advance: () -> int, read: () -> int } {
    3. let mut count: int = 0
    4. let advance: () -> int = () => {
    5. count += 1
    6. count
    7. }
    8. let read: () -> int = () => count
    9. count = 4
    10. { advance: advance, read: read }
    11. }
    12. let counters = makeCounters()
    13. io.writeLine("{counters.read()}:{counters.advance()}:{counters.read()}")
    Expected outputexit 0
    4:5:5
    
    $ topaz run example.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. 4:5:5
    Source evidence

    Immutable captures retain independent collection values

    Link
    example.tpzImmutable captures retain independent collection values
    1. import std.io
    2. function makeReader() -> () -> string {
    3. let mut current: int = 1
    4. let snapshot: int = current
    5. let items: Array<int> = [10]
    6. let mut alias: Array<int> = items
    7. let read: () -> string = () => "{current}:{snapshot}:{items[0]}"
    8. current = 2
    9. alias[0] = 30
    10. read
    11. }
    12. let read = makeReader()
    13. io.writeLine(read())
    Expected outputexit 0
    2:1:10
    
    $ topaz run example.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. 2:1:10
    Source evidence

    A closure cannot reassign an immutable capture

    Link
    example.tpzA closure cannot reassign an immutable captureOpen in Playground
    1. function makeReader() -> () -> int {
    2. let count: int = 1
    3. () => {
    4. count = 2
    5. count
    6. }
    7. }
    Expected outputNonzero exit
    No stdout.
    $ topaz check example.tpz · target product 5.21.3
    Expected result

    Expected result

    Rejection during check. Nonzero exit.

    • TPZ5003 bindings-assignment-to-immutable at example.tpz:4:9
    Source evidence

    Call a stored closure after its later binding initializes

    Link
    example.tpzCall a stored closure after its later binding initializes
    1. import std.io
    2. let read: () -> int = () => later
    3. let later: int = 7
    4. io.writeLine("{read()}")
    Expected outputexit 0
    7
    
    $ topaz run example.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. 7
    Source evidence

    A skipped branch does not read a later binding

    Link
    example.tpzA skipped branch does not read a later binding
    1. import std.io
    2. let chosen: int = if (false) { after } else { 2 }
    3. let after: int = 7
    4. io.writeLine("{chosen}:{after}")
    Expected outputexit 0
    2:7
    
    $ topaz run example.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. 2:7
    Source evidence

    The condition is an immediate initializer read

    Link
    example.tpzThe condition is an immediate initializer readOpen in Playground
    1. let chosen: int = if (after) { 1 } else { 2 }
    2. let after: bool = true
    Expected outputNonzero exit
    No stdout.
    $ topaz check example.tpz · target product 5.21.3
    Expected result

    Expected result

    Rejection during resolve. Nonzero exit.

    • TPZ3018 module-initializer-not-ready at example.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

    An executed branch faults before a later binding initializes

    Link
    example.tpzAn executed branch faults before a later binding initializes
    1. let chosen: int = if (true) { after } else { 0 }
    2. let after: int = 7
    Expected outputNonzero exit
    No stdout.
    $ topaz run example.tpz · target product 5.21.3
    Expected result

    Expected result

    Rejection during check. Nonzero exit.

    • TPZ5002 bindings-direct-forward-read at example.tpz:1:31
    Source evidence

    An inner scope may reuse an outer name

    Link
    example.tpzAn inner scope may reuse an outer name
    1. import std.io
    2. let value: int = 1
    3. let inner: int = {
    4. let value: int = 2
    5. value
    6. }
    7. io.writeLine("{value}:{inner}")
    Expected outputexit 0
    1:2
    
    $ topaz run example.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. 1:2
    Source evidence

    Module declarations cannot reuse an existing name

    Link
    example.tpzModule declarations cannot reuse an existing nameOpen in Playground
    1. let value: int = 1
    2. const value: int = 2
    Expected outputNonzero exit
    No stdout.
    $ topaz check example.tpz · target product 5.21.3
    Expected result

    Expected result

    Rejection during resolve. Nonzero exit.

    • TPZ3008 bindings-same-scope-redeclaration at example.tpz:2:7
    Source evidence

    A local let duplicate retains the intended static rejection

    Link
    example.tpzA local let duplicate retains the intended static rejectionOpen in Playground
    1. function invalid() -> int {
    2. let value: int = 1
    3. let value: int = 2
    4. value
    5. }
    Expected outputNonzero exit
    No stdout.
    $ topaz check example.tpz · target product 5.21.3
    Expected result

    Expected result

    Rejection during check. Nonzero exit.

    • TPZ5008 bindings-local-same-scope-redeclaration at example.tpz:3:9
    Source evidence

    Constant expressions retain independent collection values

    Link
    example.tpzConstant expressions retain independent collection values
    1. import std.io
    2. const base: int = 2
    3. const bits: int = base.shift(1).or(1)
    4. const rounded: int = (2.2).ceil()
    5. const data = { label: "value-{base}", values: [base, bits] }
    6. let mut alias: Array<int> = data.values
    7. alias[0] = 9
    8. io.writeLine("{data.label}:{bits}:{rounded}:{data.values[0]}")
    Expected outputexit 0
    value-2:5:3:2
    
    $ topaz run example.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. value-2:5:3:2
    Source evidence

    A constant cannot read a later same-module constant

    Link
    example.tpzA constant cannot read a later same-module constantOpen in Playground
    1. const before: int = after
    2. const after: int = 7
    Expected outputNonzero exit
    No stdout.
    $ topaz check example.tpz · target product 5.21.3
    Expected result

    Expected result

    Rejection during check. Nonzero exit.

    • TPZ5002 bindings-direct-forward-read at example.tpz:1:21
    Source evidence

    A permitted named function value remains the expected constant value

    Link
    example.tpzA permitted named function value remains the expected constant value
    1. import std.io
    2. function answer() -> int { 7 }
    3. const saved: () -> int = answer
    4. io.writeLine("{saved()}")
    Expected outputexit 0
    7
    
    $ topaz run example.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. 7
    Source evidence

    Store the function value after its declaration executes

    Link
    example.tpzStore the function value after its declaration executes
    1. import std.io
    2. function answer() -> int { 7 }
    3. let saved: () -> int = answer
    4. io.writeLine("{saved()}")
    Expected outputexit 0
    7
    
    $ topaz run example.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. 7
    Source evidence

    The local const path checks duplicate names

    Link
    example.tpzThe local const path checks duplicate namesOpen in Playground
    1. function invalid() -> int {
    2. const value: int = 1
    3. const value: int = 2
    4. value
    5. }
    Expected outputNonzero exit
    No stdout.
    $ topaz check example.tpz · target product 5.21.3
    Expected result

    Expected result

    Rejection during check. Nonzero exit.

    • TPZ5008 bindings-local-same-scope-redeclaration at example.tpz:3:11
    Source evidence

    Repeat a let annotation

    Link
    example.tpzRepeat a let annotationOpen in Playground
    1. let answer: int: int = 1
    Expected outputNonzero exit
    No stdout.
    $ topaz check example.tpz --format json · target product 5.21.3
    Expected result

    Expected result

    Rejection during parse. Nonzero exit.

    • TPZ2001 bindings-duplicate-type-annotation at example.tpz:1:16
    Source evidence

    Repeat an exported let annotation

    Link
    example.tpzRepeat an exported let annotationOpen in Playground
    1. export let answer: int: int = 1
    Expected outputNonzero exit
    No stdout.
    $ topaz check example.tpz --format json · target product 5.21.3
    Expected result

    Expected result

    Rejection during parse. Nonzero exit.

    • TPZ2001 bindings-duplicate-type-annotation at example.tpz:1:23
    Source evidence

    Repeat a mutable binding annotation

    Link
    example.tpzRepeat a mutable binding annotationOpen in Playground
    1. let mut answer: int: int = 1
    Expected outputNonzero exit
    No stdout.
    $ topaz check example.tpz --format json · target product 5.21.3
    Expected result

    Expected result

    Rejection during parse. Nonzero exit.

    • TPZ2001 bindings-duplicate-type-annotation at example.tpz:1:20
    Source evidence

    Repeat a constant annotation

    Link
    example.tpzRepeat a constant annotationOpen in Playground
    1. const answer: int: int = 1
    Expected outputNonzero exit
    No stdout.
    $ topaz check example.tpz --format json · target product 5.21.3
    Expected result

    Expected result

    Rejection during parse. Nonzero exit.

    • TPZ2001 bindings-duplicate-type-annotation at example.tpz:1:18
    Source evidence

    Annotate each binding once

    Link
    example.tpzAnnotate each binding onceOpen in Playground
    1. let local: int = 1
    2. export let published: int = local
    3. let mut count: int = local
    4. const label: string = "Topaz"
    5. export const publishedLabel: string = label
    Expected outputexit 0
    No stdout.
    $ topaz check example.tpz --format json · target product 5.21.3
    Expected result

    Expected result

    Checking accepts the program. Exit code 0.

    Source evidence

    Group admitted numeric callees

    Link
    example.tpzGroup admitted numeric calleesOpen in Playground
    1. const bits: int = ((5).and)(3)
    2. const rounded: int = ((2.2).ceil)()
    Expected outputexit 0
    No stdout.
    $ topaz check example.tpz --format json · target product 5.21.3
    Expected result

    Expected result

    Checking accepts the program. Exit code 0.

    Source evidence

    Group admitted length callees

    Link
    example.tpzGroup admitted length calleesOpen in Playground
    1. import std.typeset
    2. const physical: Length<Pt> = ((1in).toPt)()
    3. const relative: Length<Pt> = ((2em).resolve)(12pt)
    Expected outputexit 0
    No stdout.
    $ topaz check example.tpz --format json · target product 5.21.3
    Expected result

    Expected result

    Checking accepts the program. Exit code 0.

    Source evidence

    Use admitted calls and a grouped length constructor

    Link
    example.tpzUse admitted calls and a grouped length constructorOpen in Playground
    1. import std.typeset
    2. const bits: int = (5).and(3)
    3. const rounded: int = (2.2).ceil()
    4. const physical: Length<Pt> = (1in).toPt()
    5. const relative: Length<Pt> = (2em).resolve(12pt)
    6. const constructed: Length<Pt> = (typeset.fromMilliPoints)(1000)
    Expected outputexit 0
    No stdout.
    $ topaz check example.tpz --format json · target product 5.21.3
    Expected result

    Expected result

    Checking accepts the program. Exit code 0.

    Source evidence

    An ordinary call remains a runtime computation

    Link
    example.tpzAn ordinary call remains a runtime computationOpen in Playground
    1. function answer() -> int { 3 }
    2. const direct: int = answer()
    Expected outputNonzero exit
    No stdout.
    $ topaz check example.tpz --format json · target product 5.21.3
    Expected result

    Expected result

    Rejection during check. Nonzero exit.

    • TPZ5001 bindings-const-ordinary-call at example.tpz:2:21
    Source evidence

    Grouping does not make an ordinary call constant

    Link
    example.tpzGrouping does not make an ordinary call constantOpen in Playground
    1. function answer() -> int { 3 }
    2. const grouped: int = (answer)()
    Expected outputNonzero exit
    No stdout.
    $ topaz check example.tpz --format json · target product 5.21.3
    Expected result

    Expected result

    Rejection during check. Nonzero exit.

    • TPZ5001 bindings-const-ordinary-call at example.tpz:2:22
    Source evidence

    Assignment keeps array values independent

    Link
    example.tpzAssignment keeps array values independent
    1. import std.io
    2. let a = [1, 2]
    3. let mut b = a
    4. b.push(3)
    5. io.writeLine("{a.length}:{b.length}")
    Expected outputexit 0
    2:3
    
    $ topaz run example.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. 2:3
    Source evidence

    Return a changed local value from a function

    Link
    example.tpzReturn a changed local value from a function
    1. import std.io
    2. function appended(values: Array<int>) -> Array<int> {
    3. let mut local = values
    4. local.push(3)
    5. local
    6. }
    7. let items = [1, 2]
    8. let changed = appended(items)
    9. io.writeLine("{items.length}:{changed.length}")
    Expected outputexit 0
    2:3
    
    $ topaz run example.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. 2:3
    Source evidence

    A saved record keeps its collection field

    Link
    example.tpzA saved record keeps its collection field
    1. import std.io
    2. let mut r = { items: [1, 2] }
    3. let saved = r
    4. r.items[0] = 5
    5. io.writeLine("{saved.items[0]}:{r.items[0]}")
    Expected outputexit 0
    1:5
    
    $ topaz run example.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. 1:5
    Source evidence

    Insert and remove entries in an independent Map value

    Link
    example.tpzInsert and remove entries in an independent Map value
    1. import std.io
    2. let entries = map { "one": 1 }
    3. let mut changed = entries
    4. changed.insert("two", 2)
    5. io.writeLine("{entries.length}:{changed.length}")
    6. changed.remove("one")
    7. io.writeLine("{entries.keys[0]}:{changed.keys[0]}")
    Expected outputexit 0
    1:2
    one:two
    
    $ topaz run example.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. 1:2
    2. one:two
    Source evidence

    Add and remove members in an independent Set value

    Link
    example.tpzAdd and remove members in an independent Set value
    1. import std.io
    2. let values = set { 1 }
    3. let mut changed = values
    4. changed.add(2)
    5. io.writeLine("{values.length}:{changed.length}")
    6. changed.remove(1)
    7. io.writeLine("{1 in values}:{1 in changed}")
    Expected outputexit 0
    1:2
    true:false
    
    $ topaz run example.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. 1:2
    2. true:false
    Source evidence

    A mutable capture changes one variable and preserves earlier values

    Link
    example.tpzA mutable capture changes one variable and preserves earlier values
    1. import std.io
    2. let mut items = [1]
    3. let before = items
    4. let append: () -> () = () => { items.push(2) }
    5. append()
    6. io.writeLine("{items.length}:{before.length}")
    Expected outputexit 0
    2:1
    
    $ topaz run example.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. 2:1
    Source evidence

    Pending

    Link

    Related references

    Link

    Types

    Functions and generics

    TPZ5001

    Reserved binding name

    Invalid assignment target

    Immutable binding mutation

    Unbound name

    Module namespace collision

    Redeclaration

    Unexpected token

    Initializer reads a later binding

    Menu

    Download