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 03 · 32 rules

    Types

    Defines type forms, compatibility, and shared inference rules.

    Scope and status

    Link

    Topaz 5.21 language mode. The rules cite SELF source at af88afbc1d99. Examples target the public Topaz 5.21.x CLI.

    Use this page to choose type annotations and understand compatibility and inference. Bindings and scope covers declarations and assignment. Functions and generics covers argument binding, specialization, and return inference.

    Nominal value construction, protocol conformance, module lookup, literal spelling, and API operations belong to their respective references.

    Type forms

    Link
    EBNF
    1. Type = PrimaryType ("|" PrimaryType)* ;
    2. PrimaryType = NamedType | LiteralType | UnitType | GroupedType | RecordType | FunctionType ;
    3. NamedType = Identifier ("." Identifier)? TypeArguments? ;
    4. TypeArguments = "<" Type ("," Type)* ">" ;
    5. UnitType = "(" ")" ;
    6. GroupedType = "(" Type ")" ;
    7. RecordType = "{" (FieldName ":" Type ("," FieldName ":" Type)* ","?)? "}" ;
    8. FunctionType = "(" FunctionTypeParameters? ")" "->" Type ;

    Hand-transcribed source grammar excerpt. Literal spellings, function-type parameter sequence restrictions, and alias declaration syntax are omitted. Checking conditions follow below.

    Source evidence
    Ruletopaz.rule.types.literal-type-forms

    Literal type forms

    Literal type syntax admits int, float, bool, null, string literals. A string literal type must contain no interpolation; interpolation is rejected during parsing with TPZ2001.

    Why

    Literal types describe exact values, which supports finite choices and discriminants. Excluding interpolation keeps their identity independent of expression evaluation.

    Source evidence
    Ruletopaz.rule.types.unit-grouping

    Unit and grouped types

    () denotes the unit type. (T) denotes the same type as T.

    Why

    The unit type describes computations with no result value. Grouping changes how a type expression is parsed without creating another type.

    Source evidence
    Ruletopaz.rule.types.empty-structural-record

    The zero-field structural record type

    A structural record type may have zero fields. In a type position, {} denotes the zero-field structural record and may appear in an annotation, alias, parameter, or function result. Structural compatibility still requires the same field-name set. This type is distinct from the unit type ().

    The ordinary expression {} remains an empty block returning unit. No empty structural record literal is introduced. An empty concurrent {} produces the zero-field structural record value.

    Why

    A structural record type describes its field set, including the empty set. Naming that type explicitly keeps annotations, parameters, and function results consistent with the type inferred for an empty concurrent group. Unit has its existing distinct type and value. The empty expression block keeps its existing unit meaning; this rule introduces no alternative record literal.

    Source evidence

    Type identity source observations

    Link
    Ruletopaz.rule.types.alias-expansion

    Alias expansion

    A resolved type alias denotes its body with the supplied type arguments substituted for the corresponding declared type parameters. The alias creates no new nominal identity.

    Why

    An alias gives an existing type a reusable name. Keeping its identity transparent avoids conversions whose sole purpose would be changing that name.

    Source evidence
    Ruletopaz.rule.types.nominal-identity

    Nominal type identity

    Named records, enums, and newtypes retain their declaration kind, name, originating module, and type arguments. Matching fields or a matching name from another module do not establish the same type identity.

    Why

    Named declarations represent distinct domain concepts even when their fields match. Including the defining module prevents unrelated declarations from becoming interchangeable.

    Source evidence
    Ruletopaz.rule.types.builtin-arity

    Compiler-owned names and type arguments

    Type namesType arguments
    int, float, string, bool0
    template, JSONValue, File, Bytes, ByteBuffer, Path, Regex, Match, TOMLValue, URL, Date, BigInt, Decimal, RoundingMode, Duration0
    Array, Set, Option1
    Map, Result2

    Each terminal name denotes a distinct type. An incorrect type argument count for a listed name produces TPZ5022.

    Why

    A type constructor needs a fixed set of arguments to determine the values it describes. Arity checks catch missing or unrelated parameters before values are used.

    Source evidence
    Ruletopaz.rule.types.length-unit

    Length unit markers

    Length takes one compiler-owned marker from Pt, Mm, Cm, In and Em.

    The marker is part of type identity and differs from the unit type (). Invalid markers and argument counts produce TPZ5001.

    Physical lengths and relative lengths define their stored values and conversions.

    Why

    A unit belongs to a length's meaning. Keeping it in the type prevents document geometry from mixing physical and font-relative lengths without conversion.

    Source evidence

    Finite alias normalization and declaration dependencies

    A transparent type alias substitutes its arguments into its body and denotes that resulting type. Alias expansion must terminate; a direct or indirect cycle is a static error identified by TPZ5023. A reference to another alias must retain the body's type constraints. An internal unresolved placeholder does not turn an alias into a type that accepts every value. The current source-order normalization defects are recorded below, including a later int alias whose constraint is lost.

    Why

    A transparent alias needs a finite meaning. Rejecting cycles and preserving forward constraints prevents declaration order or unresolved placeholders from changing that meaning.

    Source evidence

    Compatibility source observations

    Link
    Ruletopaz.rule.types.literal-to-primitive

    Literal values and primitive targets

    An inferred literal type in int, float, string, bool is compatible with its corresponding primitive type.

    Why

    An exact literal value is also a member of its primitive family. This lets precise values flow into ordinary APIs without a separate conversion.

    Source evidence
    • assignable at af88afbc1d99 · 358-376
    • widen at af88afbc1d99 · 543-554
    Ruletopaz.rule.types.primitive-identity

    Primitive compatibility

    Resolved primitive types are compatible when their names match. An int value does not implicitly convert to float, and a float value does not implicitly convert to int.

    Why

    Implicit numeric promotion can change precision and the operations a value supports. Explicit conversion keeps those changes visible at the use site.

    Source evidence
    • assignable at af88afbc1d99 · 358-376
    • assignable at af88afbc1d99 · 358-416
    Ruletopaz.rule.types.literal-target

    Literal type targets

    A literal type target requires the same inferred literal kind and value from a resolved non-union source type. A broad primitive type does not narrow to a literal type during this comparison.

    Why

    A literal target promises one exact value. A broader primitive type does not establish that promise without further checking.

    Source evidence
    • typeEqual at af88afbc1d99 · 246-259
    • assignable at af88afbc1d99 · 358-416
    Ruletopaz.rule.types.null-type

    The null literal type

    null has its own literal type. Scalar widening preserves that type.

    Why

    Absence carries information that a primitive type cannot express. Preserving null keeps that information available to union checks and narrowing.

    Source evidence
    • widen at af88afbc1d99 · 543-554
    • formLiteralType at af88afbc1d99 · 251-272
    Ruletopaz.rule.types.union-compatibility

    Union compatibility

    Every member of an actual union must be compatible with the expected type. A non-union actual type is compatible with an expected union when at least one expected member accepts it. Different actual union members can match different expected members.

    Why

    A value of a union can belong to any member. Every possible actual member must therefore be accepted by the destination.

    Source evidence
    • assignable at af88afbc1d99 · 358-416
    Ruletopaz.rule.types.union-normalization

    Union normalization

    Union construction flattens nested unions and removes exactly equal members.

    Why

    Nesting and repeated members do not change the values a union admits. Normalization keeps those spellings from creating different type identities.

    Source evidence
    • unionType at af88afbc1d99 · 784-844
    Ruletopaz.rule.types.payload-constructor

    Option and Result payload compatibility

    For the same Option, Result constructor, each actual payload type must be compatible with the corresponding expected payload type.

    Why

    A payload accepted by the destination remains safe when wrapped in the same constructor. Checking each position preserves that relationship.

    Source evidence
    Ruletopaz.rule.types.collection-constructor

    Typed collection compatibility

    An already typed Array, Map, Set value requires exactly equal corresponding type arguments when compared with the same expected collection constructor.

    Why

    An already typed collection retains its element, key, and value types. Exact corresponding type arguments preserve that contract without an implicit conversion between collection types.

    Source evidence
    Ruletopaz.rule.types.record-compatibility

    Structural record compatibility

    Structural record types require the same set of field names and a compatible actual type for every expected field. Field order does not affect compatibility. An extra or missing field fails this comparison.

    Why

    Field names identify the parts of a structural record. Comparing them as a set avoids making declaration order part of its meaning.

    Source evidence
    Ruletopaz.rule.types.function-compatibility

    Function type compatibility

    Function types require the same fixed parameter count and variadic shape. Each actual parameter type must accept the corresponding expected parameter type, including a variadic element type when present. The actual result type must be compatible with the expected result type.

    Why

    A substitute function must accept every argument its caller may pass and return a value the caller expects.

    Source evidence
    Ruletopaz.rule.bindings-pending.type-boundary-matrix

    Type boundaries across functions, payloads, and aggregates

    Function parameter compatibility runs from the expected parameter type to the actual function parameter type. The result runs in the opposite direction. The same parameter direction applies to a variadic element, and fixed arity and variadic shape must match. Both Result payload positions follow the result direction.

    BoundaryAccepted formRejected form
    Function parametersA function accepting int or string used where callers pass intAn int function used where callers may pass string
    Variadic parametersWider accepted elements used through a narrower variadic typeNarrower accepted elements promised as a wider variadic type
    Result payloadsWidening either payload argumentNarrowing a declared payload union
    Nominal identityThe same declaration from the same moduleSame-spelled declarations from different modules
    Structural fieldsDistinct names in any source orderRepeating a name in a structural type or literal
    Map and Set inferenceWidened element unions, with Map keys and values inferred separatelyEmpty bare initializers with no matching expected type
    Aggregate joinsMatching components completed by another branch or an expected typeUnresolved Result components hidden inside different record shapes

    Joining aggregate branches retains their structure while solving corresponding inference variables. A missing type argument remains unresolved until another branch or the surrounding expectation supplies it. An unannotated binding whose non-function result still contains an inference variable is rejected with TPZ5020.

    Why

    Type compatibility must remain safe through callbacks, payloads, and aggregates. Solving every missing component prevents unresolved types from admitting incompatible values.

    Source evidence

    Shared inference source observations

    Link
    Ruletopaz.rule.types.binding-annotation

    Binding annotations supply context

    An explicit binding type annotation supplies the expected type to its initializer.

    Why

    The declaration already states the required type. Passing it into inference resolves values whose types cannot be chosen from their syntax alone.

    Source evidence
    Ruletopaz.rule.types.unannotated-binding

    Unannotated binding types

    Without an annotation, const retains the inferred initializer type. A let binding widens a top-level int, float, string, bool literal type to its primitive type. Aggregate types retain the result of aggregate inference.

    Why

    Widening a let literal allows later operations to use its primitive family. Retaining a constant's literal type lets it name an exact permitted value.

    Source evidence
    Ruletopaz.rule.types.expected-type-check

    Checking an expected type

    An expected type guides expression inference and refinement before compatibility is checked. Incompatible fully resolved types produce TPZ5001.

    Why

    The surrounding use can supply information an expression lacks. Inference uses that information before checking whether the resulting value fits.

    Source evidence
    Ruletopaz.rule.types.sequence-inference

    Array and Set element inference

    For a nonempty Array, Set literal without element context, ordinary elements contribute their inferred types after scalar literal widening. Their union becomes the element type.

    Why

    Every element contributes to the collection's possible contents. Widening scalar literals avoids tying ordinary collections to the exact values used to create them.

    Source evidence
    Ruletopaz.rule.types.map-inference

    Map key and value inference

    For a nonempty Map literal without collection context, keys and values are inferred separately. Scalar literal types widen, then key types form one union and value types form another.

    Why

    Map keys and values have different roles. Inferring each domain separately retains which types may be looked up and which may be returned.

    Source evidence
    Ruletopaz.rule.types.record-inference

    Structural record field inference

    During ordinary expression checking, each distinct structural record field uses its expected field type when one is available. A field without that expectation uses its widened inferred type.

    Why

    Each field has its own meaning and can need a different expected type. Fieldwise inference keeps that information when the surrounding record type is known.

    Source evidence
    Ruletopaz.rule.types.contextual-array

    Expected Array element types

    A nonempty Array literal checked against a fully resolved Array<T> expectation uses T to check its ordinary elements and retains T as the resulting element type.

    Why

    An expected element type describes what the array may hold. Checking each element against it constructs the intended collection without an extra conversion.

    Source evidence
    Ruletopaz.rule.types.empty-collection-context

    Empty collections in a binding

    An empty Array, Map, Set literal adopts a matching expected collection type, including a generic parameter inside that constructor. A bare binding initializer without matching context is rejected with TPZ5020. Nested operands do not independently count as bare bindings.

    Why

    An empty literal contains no element from which to infer a type. Requiring context prevents a guessed type from affecting later reads or mutations.

    Source evidence
    Ruletopaz.rule.types.branch-values

    Types contributed by if branches

    For an if expression with an else branch, value-producing branches contribute their types to the join. A terminating branch contributes no value type.

    Why

    The result type must account for every branch that can produce a value. A branch that leaves the expression cannot contribute a result.

    Source evidence
    Ruletopaz.rule.types.if-without-else-unit

    The unit type of if without else

    An if expression without an else arm has the unit type in both value and statement positions. On normal completion it produces (). The selected then branch runs for its effects and its value is discarded.

    Why

    Without an else branch there is no result on the false path. Giving both paths unit keeps the result independent of whether the condition succeeds.

    Source evidence
    Ruletopaz.rule.types.branch-literal-widening

    Fresh branch literal types

    Fresh scalar literal results in an if-with-else join widen before joining when there is no fully resolved expected type or literal-preserving context.

    Why

    A conditional chooses between values at runtime. Widening fresh literals avoids assigning an ordinary result a separate exact-value type for each branch.

    Source evidence
    Ruletopaz.rule.types.branch-union-preservation

    Existing union domains in a join

    At the widening step of a branch join, an existing fully resolved union retains its member types.

    Why

    An existing union may encode a deliberate set of allowed values. Widening its members would discard that constraint when control flow joins.

    Source evidence
    Ruletopaz.rule.types.map-lookup-fallback

    Map lookup and optional fallback

    For Map.get and a fallback supplied by ??, apply the Map lookup contract.

    The Map lookup contract defines migration from getOr. The eager-effects example preserves receiver, key and default evaluation through explicit bindings.

    Why

    Map lookup represents absence with Option. The same coalescing operator handles that absence for every Option value. Keeping fallback evaluation in the operator gives lookup one spelling and makes its effects explicit.

    Source evidence
    • mapMemberType at 271329b482bc · 871-890
    • emitBinary at 271329b482bc · 483-503
    • issues/31 at a545e8811f8f

    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.

    Aliases, literals, unit, and a branch join

    Link
    example.tpzAliases, literals, unit, and a branch joinOpen in Playground
    1. type Count = int
    2. let count: Count = 3
    3. let chosen = if (true) { 1 } else { "one" }
    4. let accepted: int | string = chosen
    5. const status = "ready"
    6. let exact: "ready" = status
    7. let absent: null = null
    8. let finished: () = ()
    Recorded outputexit 0
    No stdout.
    $ topaz check example.tpz --compiler self --format json · recorded with topaz 5.21.3
    Expected result

    Expected result

    Checking accepts the program. Exit code 0.

    Observed assertions · passed · 5.21.3

    Exit code 0.

    No stdout.

    No stderr.

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

    An integer is not implicitly a float

    Link
    example.tpzAn integer is not implicitly a floatOpen in Playground
    1. let value: float = 1
    Recorded outputexit 1
    No stdout.
    error[TPZ5001]: expected `float`, found `int`
     --> example.tpz:1:20
      |
    1 | let value: float = 1
      |                    ^
    
    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 types-int-not-float at example.tpz:1:20
    Observed assertions · passed · 5.21.3

    Exit code 1.

    No stdout.

    Observed stderr
    1. error[TPZ5001]: expected `float`, found `int`
    2. --> example.tpz:1:20
    3. |
    4. 1 | let value: float = 1
    5. | ^
    6. example.tpz: 1 type diagnostic
    • Passed · expected nonzero; observed 1
    • Passed · stdout matches the authored expectation
    • Passed · checking TPZ5001; cause types-int-not-float; primary location asserted; additional diagnostics allow
    • Passed · checking TPZ5001; cause types-int-not-float; primary location asserted; additional diagnostics allow
    Source evidence

    Structural fields can be written in another order

    Link
    example.tpzStructural fields can be written in another orderOpen in Playground
    1. type Point = { x: int, y: int }
    2. let point: Point = { y: 2, x: 1 }
    Recorded outputexit 0
    No stdout.
    $ topaz check example.tpz --compiler self --format json · recorded with topaz 5.21.3
    Expected result

    Expected result

    Checking accepts the program. Exit code 0.

    Observed assertions · passed · 5.21.3

    Exit code 0.

    No stdout.

    No stderr.

    • Passed · expected 0; observed 0
    • Passed · stdout matches the authored expectation
    • Passed · stderr matches the authored expectation
    • Passed · expected 0; observed 0
    • Passed · expected 0; observed 0
    Source evidence
    • assignable at af88afbc1d99 · 388-407
    • structural at af88afbc1d99 · 632-675
    • formRecordType at af88afbc1d99 · 298-341
    • formNamedType at af88afbc1d99 · 585-609
    • fillAliasNominal at af88afbc1d99 · 936-962
    • formNamedType at af88afbc1d99 · 563-581
    • typeEqual at af88afbc1d99 · 246-259
    • assignable at af88afbc1d99 · 358-416

    An extra structural field is a different shape

    Link
    example.tpzAn extra structural field is a different shapeOpen in Playground
    1. let point: { x: int } = { x: 1, y: 2 }
    Recorded outputexit 1
    No stdout.
    error[TPZ5001]: expected `{ x: int }`, found `{ x: int, y: int }`
     --> example.tpz:1:25
      |
    1 | let point: { x: int } = { x: 1, y: 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.

    • TPZ5001 types-record-extra-field at example.tpz:1:25
    Observed assertions · passed · 5.21.3

    Exit code 1.

    No stdout.

    Observed stderr
    1. error[TPZ5001]: expected `{ x: int }`, found `{ x: int, y: int }`
    2. --> example.tpz:1:25
    3. |
    4. 1 | let point: { x: int } = { x: 1, y: 2 }
    5. | ^^^^^^^^^^^^^^
    6. example.tpz: 1 type diagnostic
    • Passed · expected nonzero; observed 1
    • Passed · stdout matches the authored expectation
    • Passed · checking TPZ5001; cause types-record-extra-field; primary location asserted; additional diagnostics allow
    • Passed · checking TPZ5001; cause types-record-extra-field; primary location asserted; additional diagnostics allow
    Source evidence

    Annotations provide empty collection types

    Link
    example.tpzAnnotations provide empty collection typesOpen in Playground
    1. let numbers: Array<int> = []
    2. let lookup: Map<string, int> = map {}
    3. let names: Set<string> = set {}
    Recorded outputexit 0
    No stdout.
    $ topaz check example.tpz --compiler self --format json · recorded with topaz 5.21.3
    Expected result

    Expected result

    Checking accepts the program. Exit code 0.

    Observed assertions · passed · 5.21.3

    Exit code 0.

    No stdout.

    No stderr.

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

    A bare empty array lacks an element type

    Link
    example.tpzA bare empty array lacks an element typeOpen in Playground
    1. let values = []
    Recorded outputexit 1
    No stdout.
    error[TPZ5020]: `[]` needs a contextual type (§22.1): annotate the binding or the expected element type
     --> example.tpz:1:14
      |
    1 | let values = []
      |              ^^
    
    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.

    • TPZ5020 types-empty-array-needs-context at example.tpz:1:14
    Observed assertions · passed · 5.21.3

    Exit code 1.

    No stdout.

    Observed stderr
    1. error[TPZ5020]: `[]` needs a contextual type (§22.1): annotate the binding or the expected element type
    2. --> example.tpz:1:14
    3. |
    4. 1 | let values = []
    5. | ^^
    6. example.tpz: 1 type diagnostic
    • Passed · expected nonzero; observed 1
    • Passed · stdout matches the authored expectation
    • Passed · checking TPZ5020; cause types-empty-array-needs-context; primary location asserted; additional diagnostics allow
    Source evidence

    An existing Array keeps its element type

    Link
    example.tpzAn existing Array keeps its element typeOpen in Playground
    1. let integers: Array<int> = [1]
    2. let values: Array<int | string> = integers
    Recorded outputexit 1
    No stdout.
    error[TPZ5001]: expected `Array<int | string>`, found `Array<int>`
     --> example.tpz:2:35
      |
    2 | let values: Array<int | string> = integers
      |                                   ^^^^^^^^
    
    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 types-invariant-array-argument at example.tpz:2:35
    Observed assertions · passed · 5.21.3

    Exit code 1.

    No stdout.

    Observed stderr
    1. error[TPZ5001]: expected `Array<int | string>`, found `Array<int>`
    2. --> example.tpz:2:35
    3. |
    4. 2 | let values: Array<int | string> = integers
    5. | ^^^^^^^^
    6. example.tpz: 1 type diagnostic
    • Passed · expected nonzero; observed 1
    • Passed · stdout matches the authored expectation
    • Passed · checking TPZ5001; cause types-invariant-array-argument; primary location asserted; additional diagnostics allow
    • Passed · checking TPZ5001; cause types-invariant-array-argument; primary location asserted; additional diagnostics allow
    Source evidence

    Payload and function types use their own compatibility rules

    Link
    example.tpzPayload and function types use their own compatibility rulesOpen in Playground
    1. let narrow: Option<int> = Some(1)
    2. let wide: Option<int | string> = narrow
    3. function acceptsEither(value: int | string) -> int {
    4. 1
    5. }
    6. let callback: (int) -> (int | string) = acceptsEither
    Recorded outputexit 0
    No stdout.
    $ topaz check example.tpz --compiler self --format json · recorded with topaz 5.21.3
    Expected result

    Expected result

    Checking accepts the program. Exit code 0.

    Observed assertions · passed · 5.21.3

    Exit code 0.

    No stdout.

    No stderr.

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

    Finite aliases substitute their type arguments

    Link
    example.tpzFinite aliases substitute their type arguments
    1. import std.io
    2. type Element = int
    3. type Bag<T> = Array<T>
    4. type Counts = Bag<Element>
    5. let counts: Counts = [2, 5]
    6. io.writeLine("{counts[0]}:{counts[1]}")
    Expected outputexit 0
    2:5
    
    $ topaz run example.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. 2:5
    Source evidence

    Recursive alias expansion retains its intended rejection

    Link
    example.tpzRecursive alias expansion retains its intended rejectionOpen in Playground
    1. type First = Second
    2. type Second = First
    3. let value: First = 1
    Expected outputNonzero exit
    No stdout.
    $ topaz check example.tpz · target product 5.21.3
    Expected result

    Expected result

    Rejection during check. Nonzero exit.

    The required primary-location contract is Pending.

    Source evidence

    A later alias body must not erase an int constraint

    Link
    example.tpzA later alias body must not erase an int constraintOpen in Playground
    1. type First = Second
    2. type Second = int
    3. let value: First = "bad"
    Expected outputNonzero exit
    No stdout.
    $ topaz check example.tpz · target product 5.21.3
    Expected result

    Expected result

    Rejection during check. Nonzero exit.

    • TPZ5001 types-alias-body-mismatch at example.tpz:3:20
    Source evidence

    A function with wider inputs fits a narrower input contract

    Link
    example.tpzA function with wider inputs fits a narrower input contract
    1. import std.io
    2. function acceptsEither(value: int | string) -> int { 7 }
    3. let callback: (int) -> (int | string) = acceptsEither
    4. io.writeLine("{callback(1)}")
    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 function accepting int cannot promise to accept string

    Link
    example.tpzA function accepting int cannot promise to accept stringOpen in Playground
    1. function acceptsInt(value: int) -> int { value }
    2. let callback: (int | string) -> int = acceptsInt
    Expected outputNonzero exit
    No stdout.
    $ topaz check example.tpz · target product 5.21.3
    Expected result

    Expected result

    Rejection during check. Nonzero exit.

    • TPZ5001 types-function-parameter-variance at example.tpz:2:39
    Source evidence

    Variadic element types follow the parameter direction

    Link
    example.tpzVariadic element types follow the parameter direction
    1. import std.io
    2. function count(...values: int | string) -> int { values.length }
    3. let callback: (...int) -> int = count
    4. io.writeLine("{callback(1, 2)}")
    Expected outputexit 0
    2
    
    $ topaz run example.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. 2
    Source evidence

    A variadic int function cannot accept string elements

    Link
    example.tpzA variadic int function cannot accept string elementsOpen in Playground
    1. function count(...values: int) -> int { values.length }
    2. let callback: (...int | string) -> int = count
    Expected outputNonzero exit
    No stdout.
    $ topaz check example.tpz · target product 5.21.3
    Expected result

    Expected result

    Rejection during check. Nonzero exit.

    • TPZ5001 types-variadic-parameter-variance at example.tpz:2:42
    Source evidence

    Both Result payload positions may widen

    Link
    example.tpzBoth Result payload positions may widenOpen in Playground
    1. let narrow: Result<int, string> = Ok(3)
    2. let wider: Result<int | string, string | bool> = narrow
    3. let error: Result<int, string> = Err("missing")
    4. let widerError: Result<int | string, string | bool> = error
    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

    A wider Result payload cannot be narrowed by assignment

    Link
    example.tpzA wider Result payload cannot be narrowed by assignmentOpen in Playground
    1. let broad: Result<int | string, string> = Ok("bad")
    2. let narrow: Result<int, string> = broad
    Expected outputNonzero exit
    No stdout.
    $ topaz check example.tpz · target product 5.21.3
    Expected result

    Expected result

    Rejection during check. Nonzero exit.

    • TPZ5001 types-result-payload-narrowing at example.tpz:2:35
    Source evidence

    A nominal value keeps its declaring module

    Link
    example.tpzA nominal value keeps its declaring moduleOpen in Playground
    1. import a
    2. import b
    3. function same(value: b.Book) -> b.Book { value }
    Expected outputexit 0
    No stdout.
    $ topaz check example.tpz --format json · target product 5.21.3
    a.tpzA nominal value keeps its declaring moduleOpen in Playground
    1. export record Book { title: string }
    b.tpzA nominal value keeps its declaring moduleOpen in Playground
    1. export record Book { title: string }
    Expected result

    Expected result

    Checking accepts the program. Exit code 0.

    Source evidence

    Same-named records from different modules remain distinct

    Link
    example.tpzSame-named records from different modules remain distinctOpen in Playground
    1. import a
    2. import b
    3. function wrong(value: b.Book) -> a.Book { value }
    Expected outputNonzero exit
    No stdout.
    $ topaz check example.tpz · target product 5.21.3
    a.tpzSame-named records from different modules remain distinctOpen in Playground
    1. export record Book { title: string }
    b.tpzSame-named records from different modules remain distinctOpen in Playground
    1. export record Book { title: string }
    Expected result

    Expected result

    Rejection during check. Nonzero exit.

    • TPZ5001 types-cross-module-nominal at example.tpz:3:43
    Source evidence

    Distinct structural fields may be written in either order

    Link
    example.tpzDistinct structural fields may be written in either order
    1. import std.io
    2. let item: { value: int, label: string } = { label: "x", value: 1 }
    3. io.writeLine("{item.value}:{item.label}")
    Expected outputexit 0
    1:x
    
    $ topaz run example.tpz · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. 1:x
    Source evidence

    A structural type cannot repeat a field name

    Link
    example.tpzA structural type cannot repeat a field nameOpen in Playground
    1. let item: { value: int, value: string } = { value: 1 }
    Expected outputNonzero exit
    No stdout.
    $ topaz check example.tpz · target product 5.21.3
    Expected result

    Expected result

    Rejection during check. Nonzero exit.

    • TPZ5022 types-repeated-record-type-field at example.tpz:1:25
    Source evidence

    A structural literal cannot repeat a field name

    Link
    example.tpzA structural literal cannot repeat a field nameOpen in Playground
    1. let item = { value: 1, value: 2 }
    Expected outputNonzero exit
    No stdout.
    $ topaz check example.tpz · target product 5.21.3
    Expected result

    Expected result

    Rejection during check. Nonzero exit.

    • TPZ5022 types-repeated-record-literal-field at example.tpz:1:24
    Source evidence

    Map and Set infer unions and accept empty typed contexts

    Link
    example.tpzMap and Set infer unions and accept empty typed contextsOpen in Playground
    1. let mapping = map { 1: "one", "two": 2 }
    2. let checkedMap: Map<int | string, int | string> = mapping
    3. let members = set { 1, "two" }
    4. let checkedSet: Set<int | string> = members
    5. let emptyMap: Map<string, int> = map {}
    6. let emptySet: Set<int> = set {}
    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 unannotated empty Map leaves its arguments unresolved

    Link
    example.tpzAn unannotated empty Map leaves its arguments unresolvedOpen in Playground
    1. let values = map {}
    Expected outputNonzero exit
    No stdout.
    $ topaz check example.tpz · target product 5.21.3
    Expected result

    Expected result

    Rejection during check. Nonzero exit.

    • TPZ5020 types-empty-map-needs-context at example.tpz:1:14
    Source evidence

    An unannotated empty Set leaves its element unresolved

    Link
    example.tpzAn unannotated empty Set leaves its element unresolvedOpen in Playground
    1. let values = set {}
    Expected outputNonzero exit
    No stdout.
    $ topaz check example.tpz · target product 5.21.3
    Expected result

    Expected result

    Rejection during check. Nonzero exit.

    • TPZ5020 types-empty-set-needs-context at example.tpz:1:14
    Source evidence

    Matching record branches complete Result payload arguments

    Link
    example.tpzMatching record branches complete Result payload argumentsOpen in Playground
    1. let joined = if (true) { { result: Ok(1) } } else { { result: Err("missing") } }
    2. let checked: { result: Result<int, string> } = joined
    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

    A callback expectation supplies both Result payloads

    Link
    example.tpzA callback expectation supplies both Result payloadsOpen in Playground
    1. function take(f: () -> { a: Result<int, string> } | { b: Result<int, string> }) -> int { 0 }
    2. let count = take(() => if (true) { { a: Ok(1) } } else { { b: Err("e") } })
    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

    Different record shapes do not erase unsolved Result arguments

    Link
    example.tpzDifferent record shapes do not erase unsolved Result argumentsOpen in Playground
    1. let got = if (true) { { a: Ok(1) } } else { { b: Err("e") } }
    Expected outputNonzero exit
    No stdout.
    $ topaz check example.tpz · target product 5.21.3
    Expected result

    Expected result

    Rejection during check. Nonzero exit.

    • TPZ5020 types-unresolved-record-join at example.tpz:1:11
    Source evidence

    An omitted else discards values and preserves selected effects

    Link
    example.tpzAn omitted else discards values and preserves selected effectsOpen in Playground
    1. import std.io
    2. let taken = if (true) {
    3. io.writeLine("then")
    4. 7
    5. }
    6. let skipped = if (false) {
    7. io.writeLine("unexpected")
    8. 9
    9. }
    10. io.writeLine("{taken}")
    11. io.writeLine("{skipped}")
    Expected outputexit 0
    then
    ()
    ()
    
    $ topaz run example.tpz --compiler self · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. then
    2. ()
    3. ()
    Source evidence

    A unit context accepts discarded non-unit branch values

    Link
    example.tpzA unit context accepts discarded non-unit branch valuesOpen in Playground
    1. let taken: () = if (true) { 7 }
    2. let skipped: () = if (false) { "discarded" }
    Expected outputexit 0
    No stdout.
    $ topaz check example.tpz --compiler self --format json · target product 5.21.3
    Expected result

    Expected result

    Checking accepts the program. Exit code 0.

    Source evidence

    The inferred result can be assigned to unit

    Link
    example.tpzThe inferred result can be assigned to unitOpen in Playground
    1. let inferred = if (true) { 7 }
    2. let checked: () = inferred
    Expected outputexit 0
    No stdout.
    $ topaz check example.tpz --compiler self --format json · target product 5.21.3
    Expected result

    Expected result

    Checking accepts the program. Exit code 0.

    Source evidence

    An omitted else cannot produce an int result

    Link
    example.tpzAn omitted else cannot produce an int resultOpen in Playground
    1. let value: int = if (true) { 7 }
    Expected outputNonzero exit
    No stdout.
    $ topaz check example.tpz --compiler self · target product 5.21.3
    Expected result

    Expected result

    Rejection during check. Nonzero exit.

    • TPZ5001 if-without-else-result-type-mismatch at example.tpz:1:18
    Source evidence

    Evaluate a Map fallback when its key is absent

    Link
    example.tpzEvaluate a Map fallback when its key is absentOpen in Playground
    1. import std.io
    2. function makeMap() -> Map<string, int> {
    3. io.writeLine("map")
    4. map { "present": 7 }
    5. }
    6. function makeKey(name: string) -> string {
    7. io.writeLine("key")
    8. name
    9. }
    10. function makeDefault() -> int {
    11. io.writeLine("fallback")
    12. 99
    13. }
    14. let present = makeMap().get(makeKey("present")) ?? makeDefault()
    15. io.writeLine("present={present}")
    16. let absent = makeMap().get(makeKey("missing")) ?? makeDefault()
    17. io.writeLine("absent={absent}")
    18. let inner: Option<int> = None()
    19. let nested = map { "present": inner }
    20. let payload: Option<int> = nested.get("present") ?? Some(99)
    21. io.writeLine("inner={payload ?? 77}")
    Expected outputexit 0
    map
    key
    present=7
    map
    key
    fallback
    absent=99
    inner=77
    
    $ topaz run example.tpz --compiler self · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. map
    2. key
    3. present=7
    4. map
    5. key
    6. fallback
    7. absent=99
    8. inner=77
    Source evidence
    • mapMemberType at 271329b482bc · 871-890
    • emitBinary at 271329b482bc · 483-503

    Preserve eager fallback effects with ordered bindings

    Link
    example.tpzPreserve eager fallback effects with ordered bindingsOpen in Playground
    1. import std.io
    2. function makeMap() -> Map<string, int> {
    3. io.writeLine("map")
    4. map { "present": 7 }
    5. }
    6. function makeKey(name: string) -> string {
    7. io.writeLine("key")
    8. name
    9. }
    10. function makeDefault() -> int {
    11. io.writeLine("fallback")
    12. 99
    13. }
    14. let receiver = makeMap()
    15. let key = makeKey("present")
    16. let default = makeDefault()
    17. let present = receiver.get(key) ?? default
    18. io.writeLine("present={present}")
    19. let absent = receiver.get("missing") ?? default
    20. io.writeLine("absent={absent}")
    Expected outputexit 0
    map
    key
    fallback
    present=7
    absent=99
    
    $ topaz run example.tpz --compiler self · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. map
    2. key
    3. fallback
    4. present=7
    5. absent=99
    Source evidence
    • mapMemberType at 271329b482bc · 871-890
    • emitBinary at 271329b482bc · 483-503

    Use the empty structural type in aliases, parameters, and results

    Link
    example.tpzUse the empty structural type in aliases, parameters, and resultsOpen in Playground
    1. type Empty = {}
    2. function keep(value: Empty) -> {} { value }
    Expected outputexit 0
    No stdout.
    $ topaz check example.tpz --compiler self --format json · target product 5.21.3
    Expected result

    Expected result

    Checking accepts the program. Exit code 0.

    Source evidence

    An empty concurrent record passes through an explicit record signature

    Link
    example.tpzAn empty concurrent record passes through an explicit record signatureOpen in Playground
    1. import std.io
    2. function keep(value: {}) -> {} { value }
    3. let joined: {} = keep(concurrent {})
    4. io.writeLine("completed")
    Expected outputexit 0
    completed
    
    $ topaz run example.tpz --compiler self · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. completed
    Source evidence

    The zero-field record is distinct from unit

    Link
    example.tpzThe zero-field record is distinct from unitOpen in Playground
    1. let joined: () = concurrent {}
    Expected outputNonzero exit
    No stdout.
    $ topaz check example.tpz --compiler self --format json · target product 5.21.3
    Expected result

    Expected result

    Rejection during check. Nonzero exit.

    • TPZ5001 concurrent-empty-result-is-not-unit at example.tpz:1:18
    Source evidence

    Pending

    Link

    Related references

    Link

    Bindings and scope

    Functions and generics

    Unexpected token

    TPZ5022

    TPZ5001

    Type could not be inferred

    Type alias cycle

    Menu

    Download