Scope and status
링크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
링크Type = PrimaryType ("|" PrimaryType)* ;PrimaryType = NamedType | LiteralType | UnitType | GroupedType | RecordType | FunctionType ;NamedType = Identifier ("." Identifier)? TypeArguments? ;TypeArguments = "<" Type ("," Type)* ">" ;UnitType = "(" ")" ;GroupedType = "(" Type ")" ;RecordType = "{" (FieldName ":" Type ("," FieldName ":" Type)* ","?)? "}" ;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.
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.
이유
Literal types describe exact values, which supports finite choices and discriminants. Excluding interpolation keeps their identity independent of expression evaluation.
Unit and grouped types
() denotes the unit type. (T) denotes the same type as T.
이유
The unit type describes computations with no result value. Grouping changes how a type expression is parsed without creating another type.
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.
이유
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.
Type identity source observations
링크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.
이유
An alias gives an existing type a reusable name. Keeping its identity transparent avoids conversions whose sole purpose would be changing that name.
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.
이유
Named declarations represent distinct domain concepts even when their fields match. Including the defining module prevents unrelated declarations from becoming interchangeable.
Compiler-owned names and type arguments
| Type names | Type arguments |
|---|---|
int, float, string, bool | 0 |
template, JSON, File, Bytes, Byte, Path, Regex, Match, TOML, URL, Date, Big, Decimal, Rounding, Duration | 0 |
Array, Set, Option | 1 |
Map, Result | 2 |
Each terminal name denotes a distinct type. An incorrect type argument count for a listed name produces TPZ5022.
이유
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.
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.
이유
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.
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.
이유
A transparent alias needs a finite meaning. Rejecting cycles and preserving forward constraints prevents declaration order or unresolved placeholders from changing that meaning.
Compatibility source observations
링크Literal values and primitive targets
An inferred literal type in int, float, string, bool is compatible with its corresponding primitive type.
이유
An exact literal value is also a member of its primitive family. This lets precise values flow into ordinary APIs without a separate conversion.
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.
이유
Implicit numeric promotion can change precision and the operations a value supports. Explicit conversion keeps those changes visible at the use site.
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.
이유
A literal target promises one exact value. A broader primitive type does not establish that promise without further checking.
The null literal type
null has its own literal type. Scalar widening preserves that type.
이유
Absence carries information that a primitive type cannot express. Preserving null keeps that information available to union checks and narrowing.
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.
이유
A value of a union can belong to any member. Every possible actual member must therefore be accepted by the destination.
Union normalization
Union construction flattens nested unions and removes exactly equal members.
이유
Nesting and repeated members do not change the values a union admits. Normalization keeps those spellings from creating different type identities.
Option and Result payload compatibility
For the same Option, Result constructor, each actual payload type must be compatible with the corresponding expected payload type.
이유
A payload accepted by the destination remains safe when wrapped in the same constructor. Checking each position preserves that relationship.
Typed collection compatibility
An already typed Array, Map, Set value requires exactly equal corresponding type arguments when compared with the same expected collection constructor.
이유
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.
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.
이유
Field names identify the parts of a structural record. Comparing them as a set avoids making declaration order part of its meaning.
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.
이유
A substitute function must accept every argument its caller may pass and return a value the caller expects.
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.
| Boundary | Accepted form | Rejected form |
|---|---|---|
| Function parameters | A function accepting int or string used where callers pass int | An int function used where callers may pass string |
| Variadic parameters | Wider accepted elements used through a narrower variadic type | Narrower accepted elements promised as a wider variadic type |
| Result payloads | Widening either payload argument | Narrowing a declared payload union |
| Nominal identity | The same declaration from the same module | Same-spelled declarations from different modules |
| Structural fields | Distinct names in any source order | Repeating a name in a structural type or literal |
| Map and Set inference | Widened element unions, with Map keys and values inferred separately | Empty bare initializers with no matching expected type |
| Aggregate joins | Matching components completed by another branch or an expected type | Unresolved 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.
이유
Type compatibility must remain safe through callbacks, payloads, and aggregates. Solving every missing component prevents unresolved types from admitting incompatible values.
Shared inference source observations
링크Binding annotations supply context
An explicit binding type annotation supplies the expected type to its initializer.
이유
The declaration already states the required type. Passing it into inference resolves values whose types cannot be chosen from their syntax alone.
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.
이유
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.
Checking an expected type
An expected type guides expression inference and refinement before compatibility is checked. Incompatible fully resolved types produce TPZ5001.
이유
The surrounding use can supply information an expression lacks. Inference uses that information before checking whether the resulting value fits.
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.
이유
Every element contributes to the collection's possible contents. Widening scalar literals avoids tying ordinary collections to the exact values used to create them.
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.
이유
Map keys and values have different roles. Inferring each domain separately retains which types may be looked up and which may be returned.
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.
이유
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.
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.
이유
An expected element type describes what the array may hold. Checking each element against it constructs the intended collection without an extra conversion.
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.
이유
An empty literal contains no element from which to infer a type. Requiring context prevents a guessed type from affecting later reads or mutations.
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.
이유
The result type must account for every branch that can produce a value. A branch that leaves the expression cannot contribute a result.
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.
이유
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.
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.
이유
A conditional chooses between values at runtime. Widening fresh literals avoids assigning an ordinary result a separate exact-value type for each branch.
Existing union domains in a join
At the widening step of a branch join, an existing fully resolved union retains its member types.
이유
An existing union may encode a deliberate set of allowed values. Widening its members would discard that constraint when control flow joins.
Map lookup and optional fallback
For Map. and a fallback supplied by ??, apply the Map lookup contract.
The Map lookup contract defines migration from get. The eager-effects example preserves receiver, key and default evaluation through explicit bindings.
이유
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.
Examples and expected results
링크Each 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.
Aliases, literals, unit, and a branch join
링크type Count = intlet count: Count = 3let chosen = if (true) { 1 } else { "one" }let accepted: int | string = chosenconst status = "ready"let exact: "ready" = statuslet absent: null = nulllet finished: () = ()
표준 출력 없음.기대 결과
기대 결과
검사가 프로그램을 받아들입니다. 종료 코드 0.
관측한 단언 · 통과 · 5.21.3
종료 코드 0.
표준 출력 없음.
표준 오류 출력 없음.
- 통과 · expected 0; observed 0
- 통과 · stdout matches the authored expectation
- 통과 · stderr matches the authored expectation
- 통과 · expected 0; observed 0
- 통과 · expected 0; observed 0
- 통과 · expected 0; observed 0
- 통과 · expected 0; observed 0
- 통과 · expected 0; observed 0
- 통과 · expected 0; observed 0
An integer is not implicitly a float
링크let value: float = 1
표준 출력 없음.error[TPZ5001]: expected `float`, found `int`
--> example.tpz:1:20
|
1 | let value: float = 1
| ^
example.tpz: 1 type diagnostic
기대 결과
관측한 단언 · 통과 · 5.21.3
종료 코드 1.
표준 출력 없음.
error[TPZ5001]: expected `float`, found `int`--> example.tpz:1:20|1 | let value: float = 1| ^example.tpz: 1 type diagnostic
- 통과 · expected nonzero; observed 1
- 통과 · stdout matches the authored expectation
- 통과 · checking TPZ5001; cause types-int-not-float; primary location asserted; additional diagnostics allow
- 통과 · checking TPZ5001; cause types-int-not-float; primary location asserted; additional diagnostics allow
Structural fields can be written in another order
링크type Point = { x: int, y: int }let point: Point = { y: 2, x: 1 }
표준 출력 없음.기대 결과
기대 결과
검사가 프로그램을 받아들입니다. 종료 코드 0.
관측한 단언 · 통과 · 5.21.3
종료 코드 0.
표준 출력 없음.
표준 오류 출력 없음.
- 통과 · expected 0; observed 0
- 통과 · stdout matches the authored expectation
- 통과 · stderr matches the authored expectation
- 통과 · expected 0; observed 0
- 통과 · expected 0; observed 0
An extra structural field is a different shape
링크let point: { x: int } = { x: 1, y: 2 }
표준 출력 없음.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
기대 결과
관측한 단언 · 통과 · 5.21.3
종료 코드 1.
표준 출력 없음.
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
- 통과 · expected nonzero; observed 1
- 통과 · stdout matches the authored expectation
- 통과 · checking TPZ5001; cause types-record-extra-field; primary location asserted; additional diagnostics allow
- 통과 · checking TPZ5001; cause types-record-extra-field; primary location asserted; additional diagnostics allow
Annotations provide empty collection types
링크let numbers: Array<int> = []let lookup: Map<string, int> = map {}let names: Set<string> = set {}
표준 출력 없음.기대 결과
기대 결과
검사가 프로그램을 받아들입니다. 종료 코드 0.
관측한 단언 · 통과 · 5.21.3
종료 코드 0.
표준 출력 없음.
표준 오류 출력 없음.
- 통과 · expected 0; observed 0
- 통과 · stdout matches the authored expectation
- 통과 · stderr matches the authored expectation
- 통과 · expected 0; observed 0
A bare empty array lacks an element type
링크let values = []
표준 출력 없음.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
기대 결과
관측한 단언 · 통과 · 5.21.3
종료 코드 1.
표준 출력 없음.
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
- 통과 · expected nonzero; observed 1
- 통과 · stdout matches the authored expectation
- 통과 · checking TPZ5020; cause types-empty-array-needs-context; primary location asserted; additional diagnostics allow
An existing Array keeps its element type
링크let integers: Array<int> = [1]let values: Array<int | string> = integers
표준 출력 없음.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
기대 결과
관측한 단언 · 통과 · 5.21.3
종료 코드 1.
표준 출력 없음.
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
- 통과 · expected nonzero; observed 1
- 통과 · stdout matches the authored expectation
- 통과 · checking TPZ5001; cause types-invariant-array-argument; primary location asserted; additional diagnostics allow
- 통과 · checking TPZ5001; cause types-invariant-array-argument; primary location asserted; additional diagnostics allow
Payload and function types use their own compatibility rules
링크let narrow: Option<int> = Some(1)let wide: Option<int | string> = narrowfunction acceptsEither(value: int | string) -> int {1}let callback: (int) -> (int | string) = acceptsEither
표준 출력 없음.기대 결과
기대 결과
검사가 프로그램을 받아들입니다. 종료 코드 0.
관측한 단언 · 통과 · 5.21.3
종료 코드 0.
표준 출력 없음.
표준 오류 출력 없음.
- 통과 · expected 0; observed 0
- 통과 · stdout matches the authored expectation
- 통과 · stderr matches the authored expectation
- 통과 · expected 0; observed 0
Finite aliases substitute their type arguments
링크import std.iotype Element = inttype Bag<T> = Array<T>type Counts = Bag<Element>let counts: Counts = [2, 5]io.writeLine("{counts[0]}:{counts[1]}")
2:5
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
2:5
Recursive alias expansion retains its intended rejection
링크type First = Secondtype Second = Firstlet value: First = 1
표준 출력 없음.기대 결과
A later alias body must not erase an int constraint
링크type First = Secondtype Second = intlet value: First = "bad"
표준 출력 없음.기대 결과
A function with wider inputs fits a narrower input contract
링크import std.iofunction acceptsEither(value: int | string) -> int { 7 }let callback: (int) -> (int | string) = acceptsEitherio.writeLine("{callback(1)}")
7
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
7
A function accepting int cannot promise to accept string
링크function acceptsInt(value: int) -> int { value }let callback: (int | string) -> int = acceptsInt
표준 출력 없음.기대 결과
Variadic element types follow the parameter direction
링크import std.iofunction count(...values: int | string) -> int { values.length }let callback: (...int) -> int = countio.writeLine("{callback(1, 2)}")
2
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
2
A variadic int function cannot accept string elements
링크function count(...values: int) -> int { values.length }let callback: (...int | string) -> int = count
표준 출력 없음.기대 결과
Both Result payload positions may widen
링크let narrow: Result<int, string> = Ok(3)let wider: Result<int | string, string | bool> = narrowlet error: Result<int, string> = Err("missing")let widerError: Result<int | string, string | bool> = error
표준 출력 없음.기대 결과
기대 결과
검사가 프로그램을 받아들입니다. 종료 코드 0.
A wider Result payload cannot be narrowed by assignment
링크let broad: Result<int | string, string> = Ok("bad")let narrow: Result<int, string> = broad
표준 출력 없음.기대 결과
A nominal value keeps its declaring module
링크import aimport bfunction same(value: b.Book) -> b.Book { value }
표준 출력 없음.export record Book { title: string }
export record Book { title: string }
기대 결과
기대 결과
검사가 프로그램을 받아들입니다. 종료 코드 0.
Same-named records from different modules remain distinct
링크import aimport bfunction wrong(value: b.Book) -> a.Book { value }
표준 출력 없음.export record Book { title: string }
export record Book { title: string }
기대 결과
Distinct structural fields may be written in either order
링크import std.iolet item: { value: int, label: string } = { label: "x", value: 1 }io.writeLine("{item.value}:{item.label}")
1:x
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
1:x
A structural type cannot repeat a field name
링크let item: { value: int, value: string } = { value: 1 }
표준 출력 없음.기대 결과
A structural literal cannot repeat a field name
링크let item = { value: 1, value: 2 }
표준 출력 없음.기대 결과
Map and Set infer unions and accept empty typed contexts
링크let mapping = map { 1: "one", "two": 2 }let checkedMap: Map<int | string, int | string> = mappinglet members = set { 1, "two" }let checkedSet: Set<int | string> = memberslet emptyMap: Map<string, int> = map {}let emptySet: Set<int> = set {}
표준 출력 없음.기대 결과
기대 결과
검사가 프로그램을 받아들입니다. 종료 코드 0.
An unannotated empty Map leaves its arguments unresolved
링크let values = map {}
표준 출력 없음.기대 결과
An unannotated empty Set leaves its element unresolved
링크let values = set {}
표준 출력 없음.기대 결과
Matching record branches complete Result payload arguments
링크let joined = if (true) { { result: Ok(1) } } else { { result: Err("missing") } }let checked: { result: Result<int, string> } = joined
표준 출력 없음.기대 결과
기대 결과
검사가 프로그램을 받아들입니다. 종료 코드 0.
A callback expectation supplies both Result payloads
링크function take(f: () -> { a: Result<int, string> } | { b: Result<int, string> }) -> int { 0 }let count = take(() => if (true) { { a: Ok(1) } } else { { b: Err("e") } })
표준 출력 없음.기대 결과
기대 결과
검사가 프로그램을 받아들입니다. 종료 코드 0.
Different record shapes do not erase unsolved Result arguments
링크let got = if (true) { { a: Ok(1) } } else { { b: Err("e") } }
표준 출력 없음.기대 결과
An omitted else discards values and preserves selected effects
링크import std.iolet taken = if (true) {io.writeLine("then")7}let skipped = if (false) {io.writeLine("unexpected")9}io.writeLine("{taken}")io.writeLine("{skipped}")
then
()
()
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
then()()
A unit context accepts discarded non-unit branch values
링크let taken: () = if (true) { 7 }let skipped: () = if (false) { "discarded" }
표준 출력 없음.기대 결과
기대 결과
검사가 프로그램을 받아들입니다. 종료 코드 0.
The inferred result can be assigned to unit
링크let inferred = if (true) { 7 }let checked: () = inferred
표준 출력 없음.기대 결과
기대 결과
검사가 프로그램을 받아들입니다. 종료 코드 0.
An omitted else cannot produce an int result
링크let value: int = if (true) { 7 }
표준 출력 없음.기대 결과
Evaluate a Map fallback when its key is absent
링크import std.iofunction makeMap() -> Map<string, int> {io.writeLine("map")map { "present": 7 }}function makeKey(name: string) -> string {io.writeLine("key")name}function makeDefault() -> int {io.writeLine("fallback")99}let present = makeMap().get(makeKey("present")) ?? makeDefault()io.writeLine("present={present}")let absent = makeMap().get(makeKey("missing")) ?? makeDefault()io.writeLine("absent={absent}")let inner: Option<int> = None()let nested = map { "present": inner }let payload: Option<int> = nested.get("present") ?? Some(99)io.writeLine("inner={payload ?? 77}")
map
key
present=7
map
key
fallback
absent=99
inner=77
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
mapkeypresent=7mapkeyfallbackabsent=99inner=77
Preserve eager fallback effects with ordered bindings
링크import std.iofunction makeMap() -> Map<string, int> {io.writeLine("map")map { "present": 7 }}function makeKey(name: string) -> string {io.writeLine("key")name}function makeDefault() -> int {io.writeLine("fallback")99}let receiver = makeMap()let key = makeKey("present")let default = makeDefault()let present = receiver.get(key) ?? defaultio.writeLine("present={present}")let absent = receiver.get("missing") ?? defaultio.writeLine("absent={absent}")
map
key
fallback
present=7
absent=99
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
mapkeyfallbackpresent=7absent=99
Use the empty structural type in aliases, parameters, and results
링크type Empty = {}function keep(value: Empty) -> {} { value }
표준 출력 없음.기대 결과
기대 결과
검사가 프로그램을 받아들입니다. 종료 코드 0.
An empty concurrent record passes through an explicit record signature
링크import std.iofunction keep(value: {}) -> {} { value }let joined: {} = keep(concurrent {})io.writeLine("completed")
completed
기대 결과
기대 결과
실행을 마칩니다. 종료 코드 0.
completed
The zero-field record is distinct from unit
링크let joined: () = concurrent {}
표준 출력 없음.