Scope and ownership
LinkEdition: Topaz 5.21 language mode with the SELF engine. The source revision is af88afbc1d99a8c0220b5984f9d74f895f8156f2.
This page covers nominal declarations, their fields and variants, construction, record updates, and declaration type parameters. General type compatibility belongs to Types. Protocols and implementations owns conformance, derives requirements, and method resolution. Receiver APIs and pattern matching have their own references.
Declaration syntax
LinkRecordDeclaration ="record" Identifier NominalTypeParameters? DerivesClause?"{" (RecordField ("," RecordField)* ","?)? "}" ;RecordField = Identifier ":" Type ("=" Expression)? ;EnumDeclaration ="enum" Identifier NominalTypeParameters? DerivesClause?"{" (Variant ("," Variant)* ","?)? "}" ;Variant = Identifier ("(" Type ("," Type)* ")")? ;NewtypeDeclaration ="newtype" Identifier NominalTypeParameters? DerivesClause? "=" Type ;NominalTypeParameters ="<" Identifier ("," Identifier)* ">" ;DerivesClause ="derives" (Identifier ("," Identifier)*| FunctionTypeParameters ConditionalConformance ("," ConditionalConformance)*) ;ConditionalConformance = Identifier "<" Type ">" ;
The excerpt uses Identifier, Type, and Expression from their respective language definitions. Export placement and declaration contexts are covered separately.
Source evidence
- parseTypeParameters at
af88afbc1d99· 78-98 - parseDerives, parseEnum, parseRecord, parseNewtype at
af88afbc1d99· 233-437 - issues/31 at
9b1bbdeceba9 - issues/31 at
9b1bbdeceba9 - pull/41 at
271329b482bc
Named and typed record fields
A record declaration gives each field a name and a type.
Why
Named fields give each part of a record one identity. Declared types let construction, access, and updates share the same checks.
Source evidence
- parseDerives, parseEnum, parseRecord, parseNewtype at
af88afbc1d99· 233-437
Enum variants and payload types
An enum declaration lists named variants with optional positional payload types.
Why
A variant names one alternative, and its payload carries the data needed for that alternative. Declared payload types make matching and construction agree.
Source evidence
- parseDerives, parseEnum, parseRecord, parseNewtype at
af88afbc1d99· 233-437
A newtype base type
A newtype declaration names one base type after =.
Why
A newtype gives one existing value type a distinct identity. One base type preserves that wrapper relationship without introducing another record shape.
Source evidence
- parseDerives, parseEnum, parseRecord, parseNewtype at
af88afbc1d99· 233-437
Derives clause placement
A nominal derives clause follows the declaration name and type parameters. It precedes the body of a record or enum, or = and the base type of a newtype.
Why
Placing derivation beside the type name makes generated protocol behavior visible before the representation. Records, enums, and newtypes use the same clause position after the name and type parameters.
Source evidence
- parseDerives, parseEnum, parseRecord, parseNewtype at
af88afbc1d99· 233-437 - pull/41 at
271329b482bc
Newtype derivation
A newtype declaration may request the same derived protocols as a record or enum. Its base type supplies the member capability checks. Derivation is explicit; the base type does not implicitly give the newtype a protocol conformance. The clause precedes = and accepts the same conditional generic binder.
Why
A newtype has a distinct nominal identity even when its representation is an existing type. Explicit derivation lets it opt into the same protocol contracts as records and enums. Inspecting the base type uses the existing capability rules without introducing implicit conformance inheritance or a wrapper-specific prohibition.
Source evidence
- parseDerives, parseEnum, parseRecord, parseNewtype at
af88afbc1d99· 233-437 - pull/41 at
271329b482bc
Record fields and construction
LinkConstruct a record and supply a spread value
Linkrecord Point { x: int, y: int = 0 }let point = Point { x: 1 }let moved = Point { ...point, y: 2 }
example.tpz: types-ok (1 module) example.tpz: resolve-ok (1 module)
Expected result
Expected result
Checking accepts the program. Exit code 0.
example.tpz: types-ok (1 module)example.tpz: resolve-ok (1 module)
Observed assertions · passed · 5.21.3
Exit code 0.
example.tpz: types-ok (1 module)example.tpz: resolve-ok (1 module)
No stderr.
- Passed · expected 0; observed 0
- Passed · stdout matches the authored expectation
- Passed · stderr matches the authored expectation
Source evidence
- parseDerives, parseEnum, parseRecord, parseNewtype at
af88afbc1d99· 233-437 - parseFieldInitializers, parseRecordLiteral, parseRecordUpdate at
af88afbc1d99· 323-412 - inferRecordUpdateFields, inferRecordUpdateSpread, reportMissingRecordConstructionFields at
af88afbc1d99· 1896-2098 - recordUpdateFieldExpectation, inferRecordFieldValue at
af88afbc1d99· 1802-1889
Declaring a field default
A record field default is an expression written after its type and =.
Why
The field type states the required value shape, and the following initializer supplies its default. This matches the type and initializer order used by bindings.
Source evidence
- parseDerives, parseEnum, parseRecord, parseNewtype at
af88afbc1d99· 233-437
Repeated field declarations
Declaring the same field name twice in one record produces TPZ5008.
Why
Each field name must identify one stored value and one declared type. Duplicate declarations would make access and construction ambiguous.
Source evidence
- fillRecordNominal at
af88afbc1d99· 798-845
Required construction fields
A named record construction without a spread produces TPZ5004 when it omits a field with no default.
Why
A constructed record must contain a value for every field. Requiring fields without defaults prevents a later read from encountering uninitialized data.
Source evidence
- inferRecordUpdateFields, inferRecordUpdateSpread, reportMissingRecordConstructionFields at
af88afbc1d99· 1896-2098
Omitting a defaulted field
A named record construction may omit a field that has a declared default.
Why
A declaration supplies the shared default for each omitted field. Call sites can use that policy without repeating the initializer expression.
Source evidence
- fillRecordNominal at
af88afbc1d99· 798-845 - inferRecordUpdateFields, inferRecordUpdateSpread, reportMissingRecordConstructionFields at
af88afbc1d99· 1896-2098
Repeated construction fields
Supplying the same field twice in a named record construction produces TPZ5008.
Why
A field must receive one explicit initializer. Rejecting duplicates avoids choosing a value by overwrite order and evaluating a discarded initializer.
Source evidence
- inferRecordUpdateFields, inferRecordUpdateSpread, reportMissingRecordConstructionFields at
af88afbc1d99· 1896-2098
Undeclared construction fields
Supplying a field absent from the declared record produces TPZ5006.
Why
Rejecting undeclared names catches misspellings at construction. It also keeps every stored field within the record type that later access checks use.
Source evidence
- inferRecordUpdateFields, inferRecordUpdateSpread, reportMissingRecordConstructionFields at
af88afbc1d99· 1896-2098
Construction field types
A named construction checks each uniquely supplied field against its resolved declared type after substituting record type arguments.
Why
Reads rely on the declared field type. Checking supplied values after generic substitution preserves that guarantee for each record instantiation.
Source evidence
- recordUpdateFieldExpectation, inferRecordFieldValue at
af88afbc1d99· 1802-1889 - inferRecordUpdateFields, inferRecordUpdateSpread, reportMissingRecordConstructionFields at
af88afbc1d99· 1896-2098
Evaluation of omitted record fields
Each construction evaluates defaults for omitted fields after its explicit initializers, in field declaration order. A supplied field or a spread supplies the field instead. Defaults refer to names in their declaration environment.
Why
A fresh evaluation gives each construction its own default values. The declaration environment keeps defaults independent of names at the call site, and a fixed order makes effects predictable.
Source evidence
- checkRecordDefaults infers each default without checking against its field type at
151141d7bb44· 77-98 - defaultContext, recordDefault, emitRecordUpdate at
151141d7bb44· 640-735
Declared types also constrain defaults
A record field default must satisfy the declared field type. A string default for an int field is a type error.
Why
A default can become the field value without an explicit initializer at a call site. Checking it against the declared type preserves the same requirement for both paths.
Source evidence
- checkRecordDefaults infers each default without checking against its field type at
151141d7bb44· 77-98
Record literals and updates
LinkNominalRecordConstruction =Identifier "{" RecordContents? "}" ;RecordContents =(RecordSpread ("," FieldInitializers)? | FieldInitializers) ","? ;RecordSpread = "..." Expression ;FieldInitializers = FieldInitializer ("," FieldInitializer)* ;FieldInitializer = FieldName ":" Expression ;StructuralRecordLiteral ="{" FieldInitializers ","? "}" ;RecordValueUpdate =Identifier "{" FieldInitializers ","? "}" ;
This excerpt shows nonempty updates with identifier bases. Types covers structural compatibility.
Source evidence
- parseFieldInitializers, parseRecordLiteral, parseRecordUpdate at
af88afbc1d99· 323-412 - Record value update recognition at
af88afbc1d99· 1394-1430 - Structural record update result and field checking at
af88afbc1d99· 2099-2182 - parseCollectionAtom structural record versus block dispatch at
af88afbc1d99· 1153-1186
The name in a record construction
The name in a named record construction denotes a declared record.
Why
Resolving the head as a record type gives construction its field declarations and nominal identity. It does not depend on an unrelated value with that spelling.
Source evidence
- parseFieldInitializers, parseRecordLiteral, parseRecordUpdate at
af88afbc1d99· 323-412 - contextualRecordUpdateBase, recordUpdateConstructorName at
af88afbc1d99· 1734-1782
Record spread placement
A spread in a named record construction must appear first and occur at most once.
Why
One initial spread gives construction a single base value. Explicit fields can then replace named values without competing spreads or an implicit overwrite order.
Source evidence
- parseFieldInitializers, parseRecordLiteral, parseRecordUpdate at
af88afbc1d99· 323-412
The type of a record spread
A named record construction checks the spread value against the constructed record type.
Why
The copied fields must belong to the record being constructed. Requiring the same instantiated record type prevents incompatible fields from entering through a spread.
Source evidence
- inferRecordUpdateFields, inferRecordUpdateSpread, reportMissingRecordConstructionFields at
af88afbc1d99· 1896-2098
Spreads on a value update
A spread on a record value update produces TPZ5001.
Why
A value update already names the record to copy. Another spread would introduce a second base and a choice of overwrite order.
Source evidence
- inferRecordUpdateFields, inferRecordUpdateSpread, reportMissingRecordConstructionFields at
af88afbc1d99· 1896-2098
Structural record literals
A structural record literal encloses at least one named field initializer in braces and has no nominal record name.
Why
Field names and values can describe a record without assigning a separate nominal identity. This supports small grouped values whose shape is their type.
Source evidence
- parseFieldInitializers, parseRecordLiteral, parseRecordUpdate at
af88afbc1d99· 323-412 - parseCollectionAtom structural record versus block dispatch at
af88afbc1d99· 1153-1186
Empty braces in expressions
In an expression, empty braces form a block.
Why
Empty braces in an expression denote an empty block. Keeping that reading makes empty bodies consistent and avoids selecting between a block and a record from context.
Source evidence
- parseCollectionAtom structural record versus block dispatch at
af88afbc1d99· 1153-1186
Record update evaluation and copied fields
A record update evaluates its base once before explicit field initializers. Initializers run in source order. A spread supplies omitted fields without reevaluating their defaults. The result has new record storage and retains the field values from the base.
Why
Evaluating the base once avoids duplicating its effects. Explicit fields then replace named values while omitted fields retain the copied values.
Source evidence
- emitRecordUpdate seed/base, explicit fields, retained fields and defaults at
151141d7bb44· 669-735 - record_update_merge clones the base map before replacing fields at
151141d7bb44· 626-649
Structural record value updates
LinkUpdate a structural record value
Linklet point = { x: 1, y: 2 }let moved = point { y: 3 }let updated: int = moved.y
example.tpz: types-ok (1 module) example.tpz: resolve-ok (1 module)
Expected result
Expected result
Checking accepts the program. Exit code 0.
example.tpz: types-ok (1 module)example.tpz: resolve-ok (1 module)
Observed assertions · passed · 5.21.3
Exit code 0.
example.tpz: types-ok (1 module)example.tpz: resolve-ok (1 module)
No stderr.
- Passed · expected 0; observed 0
- Passed · stdout matches the authored expectation
- Passed · stderr matches the authored expectation
Source evidence
- parseFieldInitializers, parseRecordLiteral, parseRecordUpdate at
af88afbc1d99· 323-412 - recordUpdateFieldExpectation, inferRecordFieldValue at
af88afbc1d99· 1802-1889 - Structural record update result and field checking at
af88afbc1d99· 2099-2182 - Record value update recognition at
af88afbc1d99· 1394-1430
The base of a structural update
In a structural record update value { field: expression }, value denotes an existing structural record.
Why
An update needs an existing value to supply fields that are not replaced. Resolving the head in the value namespace identifies that source directly.
Source evidence
- Record value update recognition at
af88afbc1d99· 1394-1430 - Structural record update result and field checking at
af88afbc1d99· 2099-2182
Structural update field types
For a field supplied once, a structural record update checks its value against the base record field type.
Why
Replacing a field must preserve the type used by existing readers. Checking against the base field type prevents an update from invalidating those readers.
Source evidence
- recordUpdateFieldExpectation, inferRecordFieldValue at
af88afbc1d99· 1802-1889 - inferRecordUpdateFields, inferRecordUpdateSpread, reportMissingRecordConstructionFields at
af88afbc1d99· 1896-2098
Structural update result type
A structural record value update has the type of its base record.
Why
An update changes field values while preserving the record shape. Keeping the base type lets the result serve the same uses as the original value.
Source evidence
- Structural record update result and field checking at
af88afbc1d99· 2099-2182
Update a nominal record through its declaration
Value-update syntax requires a structural record. Update a nominal record with its declared name and a spread, as in Point { ..
Why
Nominal updates use named construction with a leading spread. Rejecting a value-headed update keeps one update form for a nominal record.
Source evidence
- checkedRecordUpdateResult excludes nominal-record values at
151141d7bb44· 2099-2121
Repeated structural update fields
Each explicit field name in a structural record update must occur once. Repeating a field is rejected with TPZ5008 even when an earlier initializer has an incompatible type. A field copied from the base may be replaced by one explicit initializer.
Why
Each explicit field must select one value. Rejecting repeated names prevents initializer order from choosing a winner or hiding an incompatible earlier value.
Source evidence
- inferRecordUpdateField and inferRecordUpdateFields at
9b1bbdeceba9· 1939-2013 - record_update_merge at
9b1bbdeceba9· 626-649
Enum variants and construction
LinkConstruct variants with and without payloads
Linkenum Reply { Waiting, Done(int, string) }let waiting: Reply = Reply.Waiting()let done: Reply = Reply.Done(2, "ready")
example.tpz: types-ok (1 module) example.tpz: resolve-ok (1 module)
Expected result
Expected result
Checking accepts the program. Exit code 0.
example.tpz: types-ok (1 module)example.tpz: resolve-ok (1 module)
Observed assertions · passed · 5.21.3
Exit code 0.
example.tpz: types-ok (1 module)example.tpz: resolve-ok (1 module)
No stderr.
- Passed · expected 0; observed 0
- Passed · stdout matches the authored expectation
- Passed · stderr matches the authored expectation
Source evidence
- parseDerives, parseEnum, parseRecord, parseNewtype at
af88afbc1d99· 233-437 - enumVariantType at
af88afbc1d99· 691-717
See Pattern matching for variant patterns, exhaustiveness, and unreachable cases. Functions and generics covers argument binding.
Variants without payloads
Calling a qualified enum constructor without payloads, such as Choice., produces a value of the enum type.
Why
A bare nullary variant and a call with no arguments construct the same variant without a payload. Parentheses preserve that construction intent and use the same argument-list shape as variants with payloads and constructor patterns.
Source evidence
- enumVariantType at
af88afbc1d99· 691-717 - issues/31 at
039476b62c76
Variants with payloads
A payload-bearing enum variant is a constructor function from its substituted payload types to the enum type.
Why
A payload variant needs values before it can exist. Giving its constructor a function type reuses ordinary argument checking and function passing.
Source evidence
- enumVariantType at
af88afbc1d99· 691-717
Variant names and prelude constructors
Enum variants may be named None, Some, Ok, or Err. Construct them through the enum name, such as Reply. or Reply.. The prelude Ok(value) constructs a Result value. An unqualified constructor pattern such as None() resolves through the normalized scrutinee type. Prelude spelling does not take priority. For a union, collect the constructor declarations with that head before checking payload arity. One declaration is required. Multiple instantiations of that same declaration project the union of the corresponding payload types. Distinct declarations with the same head are ambiguous even when their payload shapes agree. Narrow with an existing typed pattern and match the narrowed value. Ambiguity is a TPZ5001 pattern-type error. Wrong payload arity remains TPZ5004. Constructor patterns use unqualified heads.
Why
A constructor belongs to its defining nominal type. A prelude spelling must not reserve the same field-like name inside every enum. Resolving a pattern to one constructor keeps unrelated nominal types distinct. If a union introduces a second constructor with that name, requiring type narrowing prevents an existing arm from silently acquiring new cases. The ordinary typed pattern supplies that narrowing without another constructor notation.
Source evidence
- fillEnumNominal at
af88afbc1d99· 847-906 - fillEnumNominal at
72a59a31125f· 847-866 - bindNamedPattern / bindConstructorPattern / patternBindingTypeAt at
72a59a31125f· 595-642;1078-1279;1590-1644 - planPattern constructor identity and prelude branches at
72a59a31125f· 2150-2183 - pull/41 at
72a59a31125f
Enum declarations and payload types
An enum declaration may have no variants. Nominal type names are predeclared, so enum payloads may refer to their own enum or another declared enum, including through Array. This admits the declaration; it does not create a value for an enum with no finite constructor path. Payloads follow the same union type rules as other type positions. A union payload may contain enum types.
Why
Nominal declarations establish type identities before values are constructed. Empty and recursive types can therefore be declared without requiring a finite value. Payloads compose the same type expressions used elsewhere. An enum member in a union must not require an extra wrapper or a different variant structure.
Source evidence
- parseEnum at
151141d7bb44· 294-352 - predeclareNominalTypes at
151141d7bb44· 647-727 - fillEnumNominal at
151141d7bb44· 847-906 - pull/41 at
271329b482bc
Unique enum variant declarations
Each variant name must be unique within its enum. The retained checker diagnoses repeated declarations with TPZ5008; current SELF appends them without that guard. The intended rejection remains a product expectation and the missing SELF check is an implementation limitation.
Why
A variant name must select one alternative within its enum. Reusing the name would make construction and pattern matching ambiguous.
Source evidence
- parseEnum at
151141d7bb44· 294-352 - fillEnumNominal at
151141d7bb44· 847-906 - collect_enum_phase1 (retained checker intent/parity evidence) at
151141d7bb44· 167-218
Accept an enum type in a union payload
Linkenum Inner { Leaf }enum Outer { Wrapped(Inner | int) }
No stdout.
Expected result
Expected result
Checking accepts the program. Exit code 0.
Source evidence
- fillEnumNominal at
151141d7bb44· 847-906 - fillEnumNominal at
271329b482bc· 847-906 - pull/41 at
271329b482bc
Use distinct variants for distinct payload cases
Linkenum Inner { Leaf }enum Outer { Wrapped(Inner), Number(int) }let value: Outer = Outer.Wrapped(Inner.Leaf())
No stdout.
Expected result
Expected result
Checking accepts the program. Exit code 0.
Source evidence
- predeclareNominalTypes at
151141d7bb44· 647-727 - fillEnumNominal at
151141d7bb44· 847-906
Construct and match both union payload alternatives
Linkimport std.io { writeLine }enum Inner { Leaf }enum Outer { Wrapped(Inner | int) }function describe(value: Outer) -> string {match (value) {case Wrapped(payload) => match (payload) {case number: int => "{number}"case _ => "leaf"}}}writeLine(describe(Outer.Wrapped(Inner.Leaf())))writeLine(describe(Outer.Wrapped(7)))
leaf 7
Expected result
Expected result
Execution completes. Exit code 0.
leaf7
Source evidence
- fillEnumNominal at
271329b482bc· 847-906 - enumVariantType at
af88afbc1d99· 691-717 - pull/41 at
271329b482bc - bindNamedPattern, bindConstructorPattern at
271329b482bc· 595-642;1078-1184
Enum variants and prelude constructors retain separate identities
Linkimport std.io { writeLine }enum Reply { None, Some(int), Ok(string), Err(bool) }function describe(value: Reply) -> string {match (value) {case None() => "enum none"case Some(number) => "enum some {number}"case Ok(text) => "enum ok {text}"case Err(flag) => if (flag) { "enum error" } else { "enum false" }}}function option(value: Option<int>) -> string {match (value) {case None() => "option none"case Some(number) => "option some {number}"}}function result(value: Result<int, string>) -> string {match (value) {case Ok(number) => "result ok {number}"case Err(text) => "result error {text}"}}writeLine(describe(Reply.None()))writeLine(describe(Reply.Some(4)))writeLine(describe(Reply.Ok("yes")))writeLine(describe(Reply.Err(true)))writeLine(option(None()))writeLine(option(Some(5)))writeLine(result(Ok(6)))writeLine(result(Err("no")))
enum none enum some 4 enum ok yes enum error option none option some 5 result ok 6 result error no
Expected result
Expected result
Execution completes. Exit code 0.
enum noneenum some 4enum ok yesenum erroroption noneoption some 5result ok 6result error no
Source evidence
- fillEnumNominal at
72a59a31125f· 847-866 - bindNamedPattern / bindConstructorPattern / patternBindingTypeAt at
72a59a31125f· 595-642;1078-1279;1590-1644 - planPattern constructor identity and prelude branches at
72a59a31125f· 2150-2183 - pull/41 at
72a59a31125f
A unique constructor head projects its declared payload from a union
Linkimport std.io { writeLine }enum Left { Number(int) }enum Right { Text(string) }function describe(value: Left | Right) -> string {match (value) {case Number(number) => "{number + 1}"case Text(text) => text}}writeLine(describe(Left.Number(4)))writeLine(describe(Right.Text("right")))
5 right
Expected result
Expected result
Execution completes. Exit code 0.
5right
Source evidence
- fillEnumNominal at
72a59a31125f· 847-866 - bindNamedPattern / bindConstructorPattern / patternBindingTypeAt at
72a59a31125f· 595-642;1078-1279;1590-1644 - planPattern constructor identity and prelude branches at
72a59a31125f· 2150-2183 - pull/41 at
72a59a31125f
A typed pattern selects one nominal owner before matching a shared variant
Linkimport std.io { writeLine }enum Left { Shared(int) }enum Right { Shared(string) }function describe(value: Left | Right) -> string {match (value) {case left: Left => match (left) { case Shared(number) => "left {number}" }case right: Right => match (right) { case Shared(text) => "right {text}" }}}writeLine(describe(Left.Shared(3)))writeLine(describe(Right.Shared("R")))
left 3 right R
Expected result
Expected result
Execution completes. Exit code 0.
left 3right R
Source evidence
- fillEnumNominal at
72a59a31125f· 847-866 - bindNamedPattern / bindConstructorPattern / patternBindingTypeAt at
72a59a31125f· 595-642;1078-1279;1590-1644 - planPattern constructor identity and prelude branches at
72a59a31125f· 2150-2183 - pull/41 at
72a59a31125f
Typed narrowing distinguishes an enum Ok from a Result Ok
Linkimport std.io { writeLine }enum Reply { Ok(int) }function describe(value: Reply | Result<int, string>) -> string {match (value) {case reply: Reply => match (reply) { case Ok(number) => "reply {number}" }case result: Result<int, string> => match (result) {case Ok(number) => "result {number}"case Err(text) => text}}}let result: Result<int, string> = Ok(8)writeLine(describe(Reply.Ok(7)))writeLine(describe(result))
reply 7 result 8
Expected result
Expected result
Execution completes. Exit code 0.
reply 7result 8
Source evidence
- fillEnumNominal at
72a59a31125f· 847-866 - bindNamedPattern / bindConstructorPattern / patternBindingTypeAt at
72a59a31125f· 595-642;1078-1279;1590-1644 - planPattern constructor identity and prelude branches at
72a59a31125f· 2150-2183 - pull/41 at
72a59a31125f
Reject a shared bare constructor head before inspecting its payload
Linkenum Left { Shared(int) }enum Right { Shared(int) }function describe(value: Left | Right) -> int {match (value) {case Shared(number) => numbercase _ => 0}}
No stdout.
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5001structures-ambiguous-constructor-pattern atexample.tpz:5:10
Source evidence
- fillEnumNominal at
72a59a31125f· 847-866 - bindNamedPattern / bindConstructorPattern / patternBindingTypeAt at
72a59a31125f· 595-642;1078-1279;1590-1644 - planPattern constructor identity and prelude branches at
72a59a31125f· 2150-2183 - pull/41 at
72a59a31125f
A prelude-spelled enum variant retains its declared payload arity
Linkenum Reply { Ok(int, int) }function total(value: Reply) -> int {match (value) { case Ok(number) => number }}
No stdout.
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5004structures-enum-pattern-arity atexample.tpz:3:24
Source evidence
- fillEnumNominal at
72a59a31125f· 847-866 - bindNamedPattern / bindConstructorPattern / patternBindingTypeAt at
72a59a31125f· 595-642;1078-1279;1590-1644 - planPattern constructor identity and prelude branches at
72a59a31125f· 2150-2183 - pull/41 at
72a59a31125f
Newtype construction
LinkWrap one base value
Linknewtype UserId = intlet user: UserId = UserId(7)
example.tpz: types-ok (1 module) example.tpz: resolve-ok (1 module)
Expected result
Expected result
Checking accepts the program. Exit code 0.
example.tpz: types-ok (1 module)example.tpz: resolve-ok (1 module)
Observed assertions · passed · 5.21.3
Exit code 0.
example.tpz: types-ok (1 module)example.tpz: resolve-ok (1 module)
No stderr.
- Passed · expected 0; observed 0
- Passed · stdout matches the authored expectation
- Passed · stderr matches the authored expectation
Source evidence
- parseDerives, parseEnum, parseRecord, parseNewtype at
af88afbc1d99· 233-437 - newtypeCallType, planNewtypeArguments at
af88afbc1d99· 1345-1400
Newtype constructor argument type
A newtype constructor checks its single positional argument against the resolved base type and returns the newtype.
Why
The constructor creates a distinct nominal value from its declared base representation. Checking that representation prevents the wrapper from hiding an incompatible value.
Source evidence
- newtypeCallType, planNewtypeArguments at
af88afbc1d99· 1345-1400
Invalid newtype arguments
A newtype call with an argument count other than one or a spread argument produces TPZ5004. Named arguments follow the ordinary call argument rule and are rejected during parsing with TPZ2001.
Why
A newtype wraps one value and declares no field name for it. One positional argument expresses that relationship without invented labels or argument expansion.
Source evidence
- newtypeCallType, planNewtypeArguments at
af88afbc1d99· 1345-1400
Derive the existing protocols for a newtype
Linknewtype Item derives Eq, Order, Show, JSON = intfunction jsonIdentity<T: JSON>(value: T) -> T { value }let value = Item(7)let same: bool = Eq.equals(value, value)let order: int = Order.compare(value, Item(8))let shown: string = Show.show(value)let retained: Item = jsonIdentity(value)
No stdout.
Expected result
Expected result
Checking accepts the program. Exit code 0.
Source evidence
- parseNewtype, parseStatement, exportDeclarationHead at
271329b482bc· 416-437;911-915;1237-1255 - deriveTypeCapability, checkDerives at
271329b482bc· 112-136;285-374 - typeConforms at
271329b482bc· 2244-2282 - transparent newtype encoding at
271329b482bc· 154-163 - newtype schema and decode reconstruction at
271329b482bc· 336-368;925-939 - pull/41 at
271329b482bc
Call derived Eq and Show on a newtype
Linkimport std.io { writeLine }newtype Item derives Eq, Show = intlet value = Item(7)writeLine("{Eq.equals(value, Item(7))}")writeLine("{Eq.equals(value, Item(8))}")writeLine(Show.show(value))
true false Item(7)
Expected result
Expected result
Execution completes. Exit code 0.
truefalseItem(7)
Source evidence
- parseNewtype, parseStatement, exportDeclarationHead at
271329b482bc· 416-437;911-915;1237-1255 - deriveTypeCapability, checkDerives at
271329b482bc· 112-136;285-374 - newtype equality and order at
271329b482bc· 141-164;321-346 - newtype rendering at
271329b482bc· 330-342 - pull/41 at
271329b482bc
Use the conditional derive binder on a newtype
Linknewtype Box<T> derives<T: Eq> Eq<Box<T>> = Tlet left: Box<int> = Box(3)let right: Box<int> = Box(3)let same: bool = Eq.equals(left, right)
No stdout.
Expected result
Expected result
Checking accepts the program. Exit code 0.
Source evidence
- parseNewtype, parseStatement, exportDeclarationHead at
271329b482bc· 416-437;911-915;1237-1255 - deriveTypeCapability, checkDerives at
271329b482bc· 112-136;285-374 - issues/31 at
9b1bbdeceba9 - issues/31 at
9b1bbdeceba9 - pull/41 at
271329b482bc
Require an explicit newtype conformance
Linknewtype Item = intlet value = Item(7)let same = Eq.equals(value, value)
No stdout.
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5522structures-no-visible-eq-conformance atexample.tpz:3:22
Source evidence
- typeConforms at
271329b482bc· 2244-2282 - userProtocolCallConformance / gateUserProtocolCall at
151141d7bb44· 965-1054
Declaration type parameters
LinkGive a generic record an expected type
Linkrecord Box<T> { value: T }let box: Box<int> = Box { value: 3 }
example.tpz: types-ok (1 module) example.tpz: resolve-ok (1 module)
Expected result
Expected result
Checking accepts the program. Exit code 0.
example.tpz: types-ok (1 module)example.tpz: resolve-ok (1 module)
Observed assertions · passed · 5.21.3
Exit code 0.
example.tpz: types-ok (1 module)example.tpz: resolve-ok (1 module)
No stderr.
- Passed · expected 0; observed 0
- Passed · stdout matches the authored expectation
- Passed · stderr matches the authored expectation
Source evidence
- parseTypeParameters at
af88afbc1d99· 78-98 - checkNominalConstructionContext at
af88afbc1d99· 1644-1676 - contextualRecordUpdateBase, recordUpdateConstructorName at
af88afbc1d99· 1734-1782 - recordUpdateFieldExpectation, inferRecordFieldValue at
af88afbc1d99· 1802-1889
Nominal type parameter lists
Record, enum, and newtype type parameters are comma-separated names inside angle brackets after the declaration name.
Why
Records, enums, and newtypes introduce type parameters in the same position and with the same delimiters. Readers do not need a separate binder notation for each declaration.
Source evidence
- parseTypeParameters at
af88afbc1d99· 78-98 - parseDerives, parseEnum, parseRecord, parseNewtype at
af88afbc1d99· 233-437
Expected types in generic record construction
Why
Records participate in the same nominal construction model as enums and newtypes.
Source evidence
- checkNominalConstructionContext at
af88afbc1d99· 1644-1676 - contextualRecordUpdateBase, recordUpdateConstructorName at
af88afbc1d99· 1734-1782 - recordUpdateFieldExpectation, inferRecordFieldValue at
af88afbc1d99· 1802-1889
Supply the complete nominal type through the expected context
Construct a generic record, enum, or newtype in a context that supplies the same nominal declaration and all its type arguments. The expected arguments may themselves be enclosing generic parameters. Field or payload values alone do not supply omitted nominal arguments; a missing matching context is a type-argument arity error (TPZ5022). The declaration origin is part of that match; the current record-context retargeting defect is an implementation limitation.
Why
A complete expected nominal type determines every argument, including arguments with no field or payload evidence. Matching its declaration keeps unrelated nominal types distinct.
Source evidence
- checkNominalConstructionContext at
151141d7bb44· 1644-1676 - addTypeArgumentArityDiagnostic at
151141d7bb44· 112-131 - contextualRecordUpdateBase / recordUpdateFieldExpectation / inferRecordUpdateExpression at
151141d7bb44· 1734-1757;1784-1880;2124-2175 - newtypeCallType / checkNewtypeCallArity at
151141d7bb44· 1345-1400 - explicitCallBindings / callTypeParameters at
151141d7bb44· 1158-1269 - prepareCall at
151141d7bb44· 1403-1478 - parsePostfix type-argument call branch at
151141d7bb44· 1394-1430 - inferBindingValue / bareBindingExpression / block tail at
151141d7bb44· 139-214;922-949 - currentExpectedExpression / return context at
151141d7bb44· 514-594;638-658 - inferContextualCallArgument at
151141d7bb44· 167-208 - parenthesized inference / inferComprehension at
151141d7bb44· 1375-1385;1690-1778 - referenceTargetType / nominal definition lookup at
151141d7bb44· 1529-1669
Nested construction follows explicit expected-type propagation
A binding annotation, a declared argument type, a declared return or block-tail type, and parentheses can carry a complete nominal expectation to construction. Once a record is instantiated, its substituted declared field types provide expectations for nested constructions. A generic newtype uses the substituted base type from its matching expected newtype. Collection nesting follows the collection contextual-checking rule and preserves the matching nominal identity requirement.
Why
An instantiated field already has a substituted type. Passing it to nested construction lets that type information remain useful through grouping, calls, and returns.
Source evidence
- checkNominalConstructionContext at
151141d7bb44· 1644-1676 - addTypeArgumentArityDiagnostic at
151141d7bb44· 112-131 - contextualRecordUpdateBase / recordUpdateFieldExpectation / inferRecordUpdateExpression at
151141d7bb44· 1734-1757;1784-1880;2124-2175 - newtypeCallType / checkNewtypeCallArity at
151141d7bb44· 1345-1400 - explicitCallBindings / callTypeParameters at
151141d7bb44· 1158-1269 - prepareCall at
151141d7bb44· 1403-1478 - parsePostfix type-argument call branch at
151141d7bb44· 1394-1430 - inferBindingValue / bareBindingExpression / block tail at
151141d7bb44· 139-214;922-949 - currentExpectedExpression / return context at
151141d7bb44· 514-594;638-658 - inferContextualCallArgument at
151141d7bb44· 167-208 - parenthesized inference / inferComprehension at
151141d7bb44· 1375-1385;1690-1778 - referenceTargetType / nominal definition lookup at
151141d7bb44· 1529-1669
Constructor type arguments come from nominal context
Explicit call type-argument lists belong to generic call targets. Nominal constructors do not contribute declaration parameters to that call-parameter list: a newtype or enum constructor call with explicit type arguments is rejected as a non-generic call (TPZ5512). Put the full nominal type in the surrounding annotation or other expected context instead. Record construction keeps its brace syntax; this rule makes no spacing-dependent promise about parser lookahead.
Why
Nominal construction obtains its type arguments from the expected nominal type. Keeping that rule across constructors avoids a second instantiation path through call syntax.
Source evidence
- explicitCallBindings / callTypeParameters at
151141d7bb44· 1158-1269 - prepareCall at
151141d7bb44· 1403-1478 - parsePostfix type-argument call branch at
151141d7bb44· 1394-1430 - checkNominalConstructionContext at
151141d7bb44· 1644-1676 - newtypeCallType / checkNewtypeCallArity at
151141d7bb44· 1345-1400
Construction examples and repairs
LinkMissing a required field
Linkrecord Point { x: int, y: int }let point = Point { x: 1 }
No stdout.
error[TPZ5004]: record `Point` is missing field `y`
--> example.tpz:2:13
|
2 | let point = Point { x: 1 }
| ^^^^^^^^^^^^^^
example.tpz: 1 type diagnostic
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5004recordPointis missing fieldyatexample.tpz:2:13
Observed assertions · passed · 5.21.3
Exit code 1.
No stdout.
error[TPZ5004]: record `Point` is missing field `y`--> example.tpz:2:13|2 | let point = Point { x: 1 }| ^^^^^^^^^^^^^^example.tpz: 1 type diagnostic
- Passed · expected nonzero; observed 1
- Passed · stdout matches the authored expectation
- Passed · checking TPZ5004; cause record `Point` is missing field `y`; primary location asserted; additional diagnostics allow
Source evidence
- inferRecordUpdateFields, inferRecordUpdateSpread, reportMissingRecordConstructionFields at
af88afbc1d99· 1896-2098
Supply the required field
Linkrecord Point { x: int, y: int }let point = Point { x: 1, y: 2 }
example.tpz: types-ok (1 module) example.tpz: resolve-ok (1 module)
Expected result
Expected result
Checking accepts the program. Exit code 0.
example.tpz: types-ok (1 module)example.tpz: resolve-ok (1 module)
Observed assertions · passed · 5.21.3
Exit code 0.
example.tpz: types-ok (1 module)example.tpz: resolve-ok (1 module)
No stderr.
- Passed · expected 0; observed 0
- Passed · stdout matches the authored expectation
- Passed · stderr matches the authored expectation
Source evidence
- inferRecordUpdateFields, inferRecordUpdateSpread, reportMissingRecordConstructionFields at
af88afbc1d99· 1896-2098 - recordUpdateFieldExpectation, inferRecordFieldValue at
af88afbc1d99· 1802-1889
A repeated field declaration
Linkrecord Point {x: int,x: int,}
No stdout.
error[TPZ5008]: record field `x` is declared twice in `Point` --> example.tpz:3:3 | 3 | x: int, | ^^^^^^ example.tpz: 1 type diagnostic
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5008record fieldxis declared twice inPointatexample.tpz:3:3
Observed assertions · passed · 5.21.3
Exit code 1.
No stdout.
error[TPZ5008]: record field `x` is declared twice in `Point`--> example.tpz:3:3|3 | x: int,| ^^^^^^example.tpz: 1 type diagnostic
- Passed · expected nonzero; observed 1
- Passed · stdout matches the authored expectation
- Passed · checking TPZ5008; cause record field `x` is declared twice in `Point`; primary location asserted; additional diagnostics allow
Source evidence
- fillRecordNominal at
af88afbc1d99· 798-845
Give each declared field its own name
Linkrecord Point {x: int,y: int,}let point = Point { x: 1, y: 2 }
example.tpz: types-ok (1 module) example.tpz: resolve-ok (1 module)
Expected result
Expected result
Checking accepts the program. Exit code 0.
example.tpz: types-ok (1 module)example.tpz: resolve-ok (1 module)
Observed assertions · passed · 5.21.3
Exit code 0.
example.tpz: types-ok (1 module)example.tpz: resolve-ok (1 module)
No stderr.
- Passed · expected 0; observed 0
- Passed · stdout matches the authored expectation
- Passed · stderr matches the authored expectation
Source evidence
- fillRecordNominal at
af88afbc1d99· 798-845 - inferRecordUpdateFields, inferRecordUpdateSpread, reportMissingRecordConstructionFields at
af88afbc1d99· 1896-2098
An extra newtype constructor argument
Linknewtype UserId = intlet user = UserId(7, 8)
No stdout.
error[TPZ5004]: newtype `UserId` constructor takes 1 argument (the `int` to wrap), found 2 --> example.tpz:2:12 | 2 | let user = UserId(7, 8) | ^^^^^^^^^^^^ example.tpz: 1 type diagnostic
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5004newtypeUserconstructor takes 1 argument atId example.tpz:2:12
Observed assertions · passed · 5.21.3
Exit code 1.
No stdout.
error[TPZ5004]: newtype `UserId` constructor takes 1 argument (the `int` to wrap), found 2--> example.tpz:2:12|2 | let user = UserId(7, 8)| ^^^^^^^^^^^^example.tpz: 1 type diagnostic
- Passed · expected nonzero; observed 1
- Passed · stdout matches the authored expectation
- Passed · checking TPZ5004; cause newtype `UserId` constructor takes 1 argument; primary location asserted; additional diagnostics allow
Source evidence
- newtypeCallType, planNewtypeArguments at
af88afbc1d99· 1345-1400
Defaults run for each omitted field
Linkimport std.iofunction fallback() -> int {io.writeLine("default")7}record Sample { value: int = fallback() }let a = Sample {}let b = Sample { value: 9 }let c = Sample {}io.writeLine("{a.value}:{b.value}:{c.value}")
default default 7:9:7
Expected result
Expected result
Execution completes. Exit code 0.
defaultdefault7:9:7
Source evidence
- checkRecordDefaults infers each default without checking against its field type at
151141d7bb44· 77-98 - defaultContext, recordDefault, emitRecordUpdate at
151141d7bb44· 640-735
Reject a default of the wrong field type
Linkrecord BadDefault { value: int = "bad" }let value = BadDefault {}
No stdout.
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5001structures-default-type-mismatch atexample.tpz:1:34
Source evidence
- checkRecordDefaults infers each default without checking against its field type at
151141d7bb44· 77-98
Use a default of the declared field type
Linkrecord BadDefault { value: int = 7 }let value = BadDefault {}
example.tpz: types-ok (1 module) example.tpz: resolve-ok (1 module)
Expected result
Expected result
Checking accepts the program. Exit code 0.
example.tpz: types-ok (1 module)example.tpz: resolve-ok (1 module)
Source evidence
- checkRecordDefaults infers each default without checking against its field type at
151141d7bb44· 77-98 - defaultContext, recordDefault, emitRecordUpdate at
151141d7bb44· 640-735
A spread precedes source-ordered replacements
Linkimport std.iofunction mark(label: string, value: int) -> int {io.writeLine(label)value}record Pair { x: int = mark("default-x", 1), y: int = mark("default-y", 2) }function seed() -> Pair {io.writeLine("seed")Pair { x: 10, y: 20 }}let updated = Pair { ...seed(), y: mark("y", 7), x: mark("x", 8) }let point = { x: 1, y: 2 }let moved = point { y: mark("structural-y", 3) }io.writeLine("{updated.x}:{updated.y}:{point.y}:{moved.y}")
seed y x structural-y 8:7:2:3
Expected result
Expected result
Execution completes. Exit code 0.
seedyxstructural-y8:7:2:3
Source evidence
- emitRecordUpdate seed/base, explicit fields, retained fields and defaults at
151141d7bb44· 669-735 - record_update_merge clones the base map before replacing fields at
151141d7bb44· 626-649
Reject value-update syntax on a nominal record
Linkrecord Point { x: int }let point = Point { x: 1 }let moved = point { x: 2 }
No stdout.
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5001structures-nominal-value-update atexample.tpz:3:13
Source evidence
- checkedRecordUpdateResult excludes nominal-record values at
151141d7bb44· 2099-2121
Use a named construction with a spread
Linkrecord Point { x: int }let point = Point { x: 1 }let moved = Point { ...point, x: 2 }
example.tpz: types-ok (1 module) example.tpz: resolve-ok (1 module)
Expected result
Expected result
Checking accepts the program. Exit code 0.
example.tpz: types-ok (1 module)example.tpz: resolve-ok (1 module)
Source evidence
- emitRecordUpdate seed/base, explicit fields, retained fields and defaults at
151141d7bb44· 669-735 - checkedRecordUpdateResult excludes nominal-record values at
151141d7bb44· 2099-2121
Check an empty enum and a finite recursive enum construction
Linkenum Empty {}enum Tree { Leaf(int), Branch(Array<Tree>) }let leaf: Tree = Tree.Leaf(3)let tree: Tree = Tree.Branch([leaf])
example.tpz: types-ok (1 module) example.tpz: resolve-ok (1 module)
Expected result
Expected result
Checking accepts the program. Exit code 0.
example.tpz: types-ok (1 module)example.tpz: resolve-ok (1 module)
Source evidence
- parseEnum at
151141d7bb44· 294-352 - predeclareNominalTypes at
151141d7bb44· 647-727 - fillEnumNominal at
151141d7bb44· 847-906
Reject a repeated variant in one enum
Linkenum Choice { A, A }
No stdout.
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5008structures-duplicate-enum-variant atexample.tpz:1:18
Source evidence
- parseEnum at
151141d7bb44· 294-352 - fillEnumNominal at
151141d7bb44· 847-906 - collect_enum_phase1 (retained checker intent/parity evidence) at
151141d7bb44· 167-218
Check distinct enum variants
Linkenum Choice { A, B }let value: Choice = Choice.B()
example.tpz: types-ok (1 module) example.tpz: resolve-ok (1 module)
Expected result
Expected result
Checking accepts the program. Exit code 0.
example.tpz: types-ok (1 module)example.tpz: resolve-ok (1 module)
Source evidence
- parseEnum at
151141d7bb44· 294-352 - fillEnumNominal at
151141d7bb44· 847-906 - collect_enum_phase1 (retained checker intent/parity evidence) at
151141d7bb44· 167-218
Annotations, arguments, returns and substituted record fields
Linkrecord Box<T> { value: T }newtype Wrapped<T> = Tfunction make<T>(value: T) -> Box<T> { Box { value: value } }function wrap<T>(value: T) -> Wrapped<T> { Wrapped(value) }function consume(value: Box<int>) -> int { value.value }let nested: Box<Box<int>> = Box { value: Box { value: 3 } }let argument = consume(Box { value: 2 })let made: Box<string> = make("seven")let wrapped: Wrapped<int> = Wrapped(6)let genericWrapped: Wrapped<string> = wrap("value")
example.tpz: types-ok (1 module) example.tpz: resolve-ok (1 module)
Expected result
Expected result
Checking accepts the program. Exit code 0.
example.tpz: types-ok (1 module)example.tpz: resolve-ok (1 module)
Source evidence
- checkNominalConstructionContext at
151141d7bb44· 1644-1676 - addTypeArgumentArityDiagnostic at
151141d7bb44· 112-131 - contextualRecordUpdateBase / recordUpdateFieldExpectation / inferRecordUpdateExpression at
151141d7bb44· 1734-1757;1784-1880;2124-2175 - newtypeCallType / checkNewtypeCallArity at
151141d7bb44· 1345-1400 - explicitCallBindings / callTypeParameters at
151141d7bb44· 1158-1269 - prepareCall at
151141d7bb44· 1403-1478 - parsePostfix type-argument call branch at
151141d7bb44· 1394-1430 - inferBindingValue / bareBindingExpression / block tail at
151141d7bb44· 139-214;922-949 - currentExpectedExpression / return context at
151141d7bb44· 514-594;638-658 - inferContextualCallArgument at
151141d7bb44· 167-208 - parenthesized inference / inferComprehension at
151141d7bb44· 1375-1385;1690-1778 - referenceTargetType / nominal definition lookup at
151141d7bb44· 1529-1669
Fields do not infer a generic record specialization
Linkrecord Box<T> { value: T }let box = Box { value: 1 }
No stdout.
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5022structures-generic-record-context atexample.tpz:2:11
Source evidence
- checkNominalConstructionContext at
151141d7bb44· 1644-1676 - addTypeArgumentArityDiagnostic at
151141d7bb44· 112-131 - contextualRecordUpdateBase / recordUpdateFieldExpectation / inferRecordUpdateExpression at
151141d7bb44· 1734-1757;1784-1880;2124-2175 - newtypeCallType / checkNewtypeCallArity at
151141d7bb44· 1345-1400 - explicitCallBindings / callTypeParameters at
151141d7bb44· 1158-1269 - prepareCall at
151141d7bb44· 1403-1478 - parsePostfix type-argument call branch at
151141d7bb44· 1394-1430 - inferBindingValue / bareBindingExpression / block tail at
151141d7bb44· 139-214;922-949 - currentExpectedExpression / return context at
151141d7bb44· 514-594;638-658 - inferContextualCallArgument at
151141d7bb44· 167-208 - parenthesized inference / inferComprehension at
151141d7bb44· 1375-1385;1690-1778 - referenceTargetType / nominal definition lookup at
151141d7bb44· 1529-1669
An annotation supplies the record specialization
Linkrecord Box<T> { value: T }let box: Box<int> = Box { value: 1 }
example.tpz: types-ok (1 module) example.tpz: resolve-ok (1 module)
Expected result
Expected result
Checking accepts the program. Exit code 0.
example.tpz: types-ok (1 module)example.tpz: resolve-ok (1 module)
Source evidence
- checkNominalConstructionContext at
151141d7bb44· 1644-1676 - addTypeArgumentArityDiagnostic at
151141d7bb44· 112-131 - contextualRecordUpdateBase / recordUpdateFieldExpectation / inferRecordUpdateExpression at
151141d7bb44· 1734-1757;1784-1880;2124-2175 - newtypeCallType / checkNewtypeCallArity at
151141d7bb44· 1345-1400 - explicitCallBindings / callTypeParameters at
151141d7bb44· 1158-1269 - prepareCall at
151141d7bb44· 1403-1478 - parsePostfix type-argument call branch at
151141d7bb44· 1394-1430 - inferBindingValue / bareBindingExpression / block tail at
151141d7bb44· 139-214;922-949 - currentExpectedExpression / return context at
151141d7bb44· 514-594;638-658 - inferContextualCallArgument at
151141d7bb44· 167-208 - parenthesized inference / inferComprehension at
151141d7bb44· 1375-1385;1690-1778 - referenceTargetType / nominal definition lookup at
151141d7bb44· 1529-1669
A payload does not infer a generic newtype specialization
Linknewtype Wrapped<T> = Tlet wrapped = Wrapped(1)
No stdout.
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5022structures-generic-newtype-context atexample.tpz:2:15
Source evidence
- checkNominalConstructionContext at
151141d7bb44· 1644-1676 - addTypeArgumentArityDiagnostic at
151141d7bb44· 112-131 - contextualRecordUpdateBase / recordUpdateFieldExpectation / inferRecordUpdateExpression at
151141d7bb44· 1734-1757;1784-1880;2124-2175 - newtypeCallType / checkNewtypeCallArity at
151141d7bb44· 1345-1400 - explicitCallBindings / callTypeParameters at
151141d7bb44· 1158-1269 - prepareCall at
151141d7bb44· 1403-1478 - parsePostfix type-argument call branch at
151141d7bb44· 1394-1430 - inferBindingValue / bareBindingExpression / block tail at
151141d7bb44· 139-214;922-949 - currentExpectedExpression / return context at
151141d7bb44· 514-594;638-658 - inferContextualCallArgument at
151141d7bb44· 167-208 - parenthesized inference / inferComprehension at
151141d7bb44· 1375-1385;1690-1778 - referenceTargetType / nominal definition lookup at
151141d7bb44· 1529-1669
An annotation supplies the newtype specialization
Linknewtype Wrapped<T> = Tlet wrapped: Wrapped<int> = Wrapped(1)
example.tpz: types-ok (1 module) example.tpz: resolve-ok (1 module)
Expected result
Expected result
Checking accepts the program. Exit code 0.
example.tpz: types-ok (1 module)example.tpz: resolve-ok (1 module)
Source evidence
- checkNominalConstructionContext at
151141d7bb44· 1644-1676 - addTypeArgumentArityDiagnostic at
151141d7bb44· 112-131 - contextualRecordUpdateBase / recordUpdateFieldExpectation / inferRecordUpdateExpression at
151141d7bb44· 1734-1757;1784-1880;2124-2175 - newtypeCallType / checkNewtypeCallArity at
151141d7bb44· 1345-1400 - explicitCallBindings / callTypeParameters at
151141d7bb44· 1158-1269 - prepareCall at
151141d7bb44· 1403-1478 - parsePostfix type-argument call branch at
151141d7bb44· 1394-1430 - inferBindingValue / bareBindingExpression / block tail at
151141d7bb44· 139-214;922-949 - currentExpectedExpression / return context at
151141d7bb44· 514-594;638-658 - inferContextualCallArgument at
151141d7bb44· 167-208 - parenthesized inference / inferComprehension at
151141d7bb44· 1375-1385;1690-1778 - referenceTargetType / nominal definition lookup at
151141d7bb44· 1529-1669
Nominal parameters are not generic call parameters
Linknewtype Wrapped<T> = Tlet wrapped: Wrapped<int> = Wrapped<int>(1)
No stdout.
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5512structures-nominal-call-type-arguments atexample.tpz:2:29
Source evidence
- checkNominalConstructionContext at
151141d7bb44· 1644-1676 - addTypeArgumentArityDiagnostic at
151141d7bb44· 112-131 - contextualRecordUpdateBase / recordUpdateFieldExpectation / inferRecordUpdateExpression at
151141d7bb44· 1734-1757;1784-1880;2124-2175 - newtypeCallType / checkNewtypeCallArity at
151141d7bb44· 1345-1400 - explicitCallBindings / callTypeParameters at
151141d7bb44· 1158-1269 - prepareCall at
151141d7bb44· 1403-1478 - parsePostfix type-argument call branch at
151141d7bb44· 1394-1430 - inferBindingValue / bareBindingExpression / block tail at
151141d7bb44· 139-214;922-949 - currentExpectedExpression / return context at
151141d7bb44· 514-594;638-658 - inferContextualCallArgument at
151141d7bb44· 167-208 - parenthesized inference / inferComprehension at
151141d7bb44· 1375-1385;1690-1778 - referenceTargetType / nominal definition lookup at
151141d7bb44· 1529-1669
Put newtype arguments in the annotation
Linknewtype Wrapped<T> = Tlet wrapped: Wrapped<int> = Wrapped(1)
example.tpz: types-ok (1 module) example.tpz: resolve-ok (1 module)
Expected result
Expected result
Checking accepts the program. Exit code 0.
example.tpz: types-ok (1 module)example.tpz: resolve-ok (1 module)
Source evidence
- checkNominalConstructionContext at
151141d7bb44· 1644-1676 - addTypeArgumentArityDiagnostic at
151141d7bb44· 112-131 - contextualRecordUpdateBase / recordUpdateFieldExpectation / inferRecordUpdateExpression at
151141d7bb44· 1734-1757;1784-1880;2124-2175 - newtypeCallType / checkNewtypeCallArity at
151141d7bb44· 1345-1400 - explicitCallBindings / callTypeParameters at
151141d7bb44· 1158-1269 - prepareCall at
151141d7bb44· 1403-1478 - parsePostfix type-argument call branch at
151141d7bb44· 1394-1430 - inferBindingValue / bareBindingExpression / block tail at
151141d7bb44· 139-214;922-949 - currentExpectedExpression / return context at
151141d7bb44· 514-594;638-658 - inferContextualCallArgument at
151141d7bb44· 167-208 - parenthesized inference / inferComprehension at
151141d7bb44· 1375-1385;1690-1778 - referenceTargetType / nominal definition lookup at
151141d7bb44· 1529-1669
A foreign expectation cannot change the constructor declaration
Linkimport models { Box as RemoteBox }record Box<T> { value: T }let wrong: RemoteBox<int> = Box { value: 1 }
No stdout.
export record Box<T> { value: T }
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5022structures-generic-record-context atexample.tpz:3:29
Source-derived candidate: the checker can retarget the constructor before the origin guard. The intended TPZ5022 expectation is retained; no product execution receipt has been fabricated.
Source evidence
- checkNominalConstructionContext at
151141d7bb44· 1644-1676 - addTypeArgumentArityDiagnostic at
151141d7bb44· 112-131 - contextualRecordUpdateBase / recordUpdateFieldExpectation / inferRecordUpdateExpression at
151141d7bb44· 1734-1757;1784-1880;2124-2175 - newtypeCallType / checkNewtypeCallArity at
151141d7bb44· 1345-1400 - explicitCallBindings / callTypeParameters at
151141d7bb44· 1158-1269 - prepareCall at
151141d7bb44· 1403-1478 - parsePostfix type-argument call branch at
151141d7bb44· 1394-1430 - inferBindingValue / bareBindingExpression / block tail at
151141d7bb44· 139-214;922-949 - currentExpectedExpression / return context at
151141d7bb44· 514-594;638-658 - inferContextualCallArgument at
151141d7bb44· 167-208 - parenthesized inference / inferComprehension at
151141d7bb44· 1375-1385;1690-1778 - referenceTargetType / nominal definition lookup at
151141d7bb44· 1529-1669
Use the constructor whose declaration matches the expectation
Linkimport models { Box as RemoteBox }record Box<T> { value: T }let wrong: RemoteBox<int> = RemoteBox { value: 1 }
example.tpz: types-ok (2 modules) example.tpz: resolve-ok (2 modules)
export record Box<T> { value: T }
Expected result
Expected result
Checking accepts the program. Exit code 0.
example.tpz: types-ok (2 modules)example.tpz: resolve-ok (2 modules)
Source evidence
- checkNominalConstructionContext at
151141d7bb44· 1644-1676 - addTypeArgumentArityDiagnostic at
151141d7bb44· 112-131 - contextualRecordUpdateBase / recordUpdateFieldExpectation / inferRecordUpdateExpression at
151141d7bb44· 1734-1757;1784-1880;2124-2175 - newtypeCallType / checkNewtypeCallArity at
151141d7bb44· 1345-1400 - explicitCallBindings / callTypeParameters at
151141d7bb44· 1158-1269 - prepareCall at
151141d7bb44· 1403-1478 - parsePostfix type-argument call branch at
151141d7bb44· 1394-1430 - inferBindingValue / bareBindingExpression / block tail at
151141d7bb44· 139-214;922-949 - currentExpectedExpression / return context at
151141d7bb44· 514-594;638-658 - inferContextualCallArgument at
151141d7bb44· 167-208 - parenthesized inference / inferComprehension at
151141d7bb44· 1375-1385;1690-1778 - referenceTargetType / nominal definition lookup at
151141d7bb44· 1529-1669
A structural update cannot repeat a field
Linklet point = { x: 1 }let moved = point {x: 2,x: 3,}
No stdout.
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5008duplicate-record-field-initializer atexample.tpz:4:3
Source evidence
- inferRecordUpdateField and inferRecordUpdateFields at
9b1bbdeceba9· 1939-2013 - record_update_merge at
9b1bbdeceba9· 626-649
A later initializer cannot hide an incompatible earlier value
Linklet point = { x: 1 }let moved = point {x: "bad",x: 2,}
No stdout.
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5008duplicate-record-field-initializer atexample.tpz:4:3
Source evidence
- inferRecordUpdateField and inferRecordUpdateFields at
9b1bbdeceba9· 1939-2013 - record_update_merge at
9b1bbdeceba9· 626-649
One explicit initializer can replace a copied field
Linkimport std.iorecord Point { x: int }let original = { x: 1 }let updated = original { x: 2 }let named = Point { x: 3 }let copied = Point { ...named, x: 4 }io.writeLine("{updated.x}:{original.x}:{copied.x}:{named.x}")
2:1:4:3
Expected result
Expected result
Execution completes. Exit code 0.
2:1:4:3
Source evidence
- inferRecordUpdateField and inferRecordUpdateFields at
9b1bbdeceba9· 1939-2013 - record_update_merge at
9b1bbdeceba9· 626-649