Scope and ownership
LinkUse this page to declare protocol requirements, implement them, and add receiver methods.
This chapter specifies Topaz 6.0. Source references identify the inspected SELF revision.
This page owns protocol declarations, implementation conformance, protocol resolution, receiver self, and using. Records, enums and newtypes owns nominal declarations and derives placement. Functions and generics owns ordinary function signatures, calls, and bound syntax. Standard library entries own the behavior of built-in methods.
Declaration forms
LinkProtocolDeclaration ="export"? "protocol" Identifier"{" ProtocolSignature* "}" ;ProtocolSignature ="function" Identifier FunctionTypeParameters?"(" FunctionParameters? ")" ("->" Type)? ;Implementation ="impl" FunctionTypeParameters? NamedType"{" ImplementationMethod* "}" ;ImplementationMethod ="export"? NamedFunction ;ProtocolReference = Identifier ("." Identifier)? ;UsingStatement ="using" Identifier "=" ControlOperand Block ;ConditionalDerivesClause ="derives" FunctionTypeParameters ProtocolReference "<" Type ">"("," ProtocolReference "<" Type ">")* ;
An export prefix publishes a module-level protocol declaration. Protocol references in bounds and conformance clauses use P or ns.P through the ordinary import boundary. One implementation head uses the ordinary named-type and type-argument grammar. Name resolution distinguishes a protocol application, which takes one conforming type, from a nominal receiver. Explicit binders precede either head. Protocol signatures use ordinary annotated parameters; their result annotation is required during checking. A conditional derives clause appears before the nominal body or newtype base.
Protocol bodies
A protocol body contains function signatures.
Why
A protocol states required operations independently of their implementations. Signatures give callers a contract without choosing the code that provides it.
Source evidence
- Protocol, implementation, and using parsers at
af88afbc1d99· 439-601
Protocol implementation form
impl Protocol<Type> supplies definitions for one protocol and a complete conforming type. An explicit binder list precedes the protocol name for a conditional implementation. The protocol application has exactly one conforming type argument. Type aliases are expanded, and generic heads match the resolved static type through type equality, not value subtyping. A protocol call chooses the conformance before execution.
Why
Naming the resolved protocol and target type identifies one contract. Using the ordinary type grammar admits built-in, structural, and nominal targets without a new implementation notation.
Source evidence
- Protocol, implementation, and using parsers at
af88afbc1d99· 439-601 - pull/44 at
72a59a31125f - checkProtocolImplMethodAgainstDefinition at
72a59a31125f· 338-456 - collectProtocolImplForDefinition and collectProtocolImplForTarget at
72a59a31125f· 529-650 - prepareFunctionDeclaration at
72a59a31125f· 1282-1355 - issues/31 at
9b1bbdeceba9
Receiver implementation form
impl Type declares receiver methods for a nominal type owned by the current module. For a generic receiver, write explicit binders before its complete target, as in impl<T> Box<T>. Those binders enter method signatures and bodies; a method may introduce its own nested binders. The first bare self retains the instantiated receiver type even if a method binder shadows an outer name. The existing receiver name, field-collision, and export rules apply to every instantiation.
Why
Receiver methods already identify their type through the receiver expression. Explicit enclosing binders let generic receivers use the same method declarations and calls as concrete receivers.
Source evidence
- Protocol, implementation, and using parsers at
af88afbc1d99· 439-601 - Receiver methods and bare self at
af88afbc1d99· 730-1004 - pull/44 at
72a59a31125f - checkProtocolImplMethodAgainstDefinition at
72a59a31125f· 338-456 - collectProtocolImplForDefinition and collectProtocolImplForTarget at
72a59a31125f· 529-650 - prepareFunctionDeclaration at
72a59a31125f· 1282-1355 - issues/31 at
9b1bbdeceba9
Protocol method requirements
LinkProtocol declarations use Self
A protocol declaration has no explicit type parameter list. Use Self for its conforming type. An explicit receiver binder is rejected during parsing with TPZ2001.
Why
An explicit receiver binder and Self both name the type that implements the protocol. The binder renames that role rather than introducing a different type relationship. Self preserves the same receiver intent and matches built-in protocol signatures.
Source evidence
- Protocol declaration and signature checking at
af88afbc1d99· 993-1270 - pull/38 at
9b1bbdeceba9 - parseProtocol reads the protocol name and optional parameters before expectLeftBrace at
9b1bbdeceba9· 553-561 - fail and expectLeftBrace emit TPZ2001 with expected brace at the current token at
9b1bbdeceba9· 188-208 - issues/31 at
26da6a582932
The conforming type in signatures
Within a protocol method signature, Self denotes the conforming type.
Why
The conforming type must be substituted consistently throughout a protocol signature. This keeps its receiver and related arguments tied to the same implementation target.
Source evidence
- Protocol declaration and signature checking at
af88afbc1d99· 993-1270 - pull/38 at
9b1bbdeceba9
Selecting the conforming type
A protocol method has an implicit conforming type parameter named Self. It may occur in any parameter or result type. Methods may have no value parameters. A call infers Self from its arguments and expected result through the ordinary generic-call rules. An explicit call type-argument list supplies Self first, followed by the method parameters. Dispatch requires one resolved conforming type; a call without enough inference evidence must supply that complete list.
Why
A constructor or decoder has no existing receiver value. Selecting the conforming type with the ordinary generic-call rules makes the same protocol operation usable before and after a value exists. No position in a value-parameter list owns dispatch.
Source evidence
- Protocol declaration and signature checking at
af88afbc1d99· 993-1270 - pull/38 at
9b1bbdeceba9 - pull/44 at
72a59a31125f - checkProtocolMethod at
72a59a31125f· 1023-1166 - prepareCall at
72a59a31125f· 1432-1513
Protocol return annotation
Every protocol method requires an explicit return annotation, including - for unit. An omitted annotation produces TPZ5022.
Why
A protocol signature has no body from which to infer a result. An explicit result type gives callers and implementations the same complete contract.
Source evidence
- Protocol declaration and signature checking at
af88afbc1d99· 993-1270
Ordinary function parameters in protocols
Protocol signatures use the type parameter, bound, annotated parameter, variadic, and default-expression rules of named functions. Method binders are distinct from implicit Self and are in scope throughout that method signature, including bounds and result types. They do not enter another method. Parameters remain immutable. Required fixed parameters precede the defaulted suffix; one final variadic parameter has no default and binds an array. The protocol declaration owns each default. Its names resolve in the protocol declaration environment, where value parameters are unavailable. Supplied arguments and omitted defaults follow ordinary call evaluation order.
The method type parameters follow implicit Self in the generic call signature. Implementation signature matching compares their resolved types and bounds after renaming corresponding binders. Protocol declarations have no body, so the result annotation remains required.
Why
A protocol promises callable operations. Giving those operations the same generic, variadic, and omission rules as named functions avoids a parallel helper-function interface. Keeping defaults in that promise gives every implementation and caller one omission behavior.
Source evidence
- Protocol declaration and signature checking at
af88afbc1d99· 993-1270 - issues/31 at
570530e69141 - pull/44 at
72a59a31125f - checkProtocolMethod at
72a59a31125f· 1023-1166 - prepareCall at
72a59a31125f· 1432-1513
Unique protocol names
Redeclaring a user protocol name in the same module produces TPZ3008 during resolution.
Why
A protocol name must identify one contract in its module. Reusing the name would make conformance declarations ambiguous.
Source evidence
- Protocol declaration and signature checking at
af88afbc1d99· 993-1270 - issues/31 at
23582e1d56db
Unique protocol method names
Declaring the same method name twice in a protocol produces TPZ5008.
Why
A method name must identify one operation within a protocol. Duplicate names would make implementation coverage and calls ambiguous.
Source evidence
- Protocol declaration and signature checking at
af88afbc1d99· 993-1270
Built-in protocol names
Redeclaring the built-in protocols Eq, Order, Show, or JSON produces TPZ5008.
Why
Built-in operations and bounds use these protocol names. Redeclaring them could change the meaning of existing equality, ordering, display, or serialization code.
Source evidence
- Protocol declaration and signature checking at
af88afbc1d99· 993-1270
Protocol and type name collisions
Reusing a declared type name for a protocol in the same module produces TPZ3008 during resolution.
Why
Protocol and type names occur in implementation heads. Distinct names keep an implementation target from being mistaken for its protocol.
Source evidence
- Protocol declaration and signature checking at
af88afbc1d99· 993-1270 - issues/31 at
23582e1d56db
Module-level declarations
A protocol or implementation declaration inside a block produces TPZ5022.
Why
Conformance is checked for the program before execution. Module declarations keep the available implementation from depending on whether a block has executed.
Source evidence
- Module-level declaration context at
af88afbc1d99· 808-876
Implementation conformance
LinkImplementation ownership
The implementing module must define either the resolved protocol or the target type's outermost nominal declaration. A module may therefore define its own protocol for int, string, a structural type, or a generic type pattern. A foreign protocol requires an own-module record, enum, or newtype as the target's outermost constructor. A local type argument inside a foreign Array or Map does not transfer ownership. Transparent aliases preserve the underlying type and its owner. Implementing a foreign protocol for a foreign type produces TPZ5520. Every admitted implementation also passes the complete dependency graph's uniqueness check.
Why
The protocol owner can give an existing type a new operation without inventing a wrapper or a separate helper-function interface. The type owner can adopt an existing operation. Requiring one of those owners and rejecting graph-wide overlap gives each protocol and target pair one meaning.
Source evidence
- Implementation targets at
af88afbc1d99· 146-212 - Manual implementation conformance at
af88afbc1d99· 297-660 - pull/44 at
72a59a31125f - checkProtocolImplMethodAgainstDefinition at
72a59a31125f· 338-456 - collectProtocolImplForDefinition and collectProtocolImplForTarget at
72a59a31125f· 529-650 - prepareFunctionDeclaration at
72a59a31125f· 1282-1355 - issues/31 at
9b1bbdeceba9
Required implementation methods
An implementation supplies every declared protocol method and no undeclared method. A missing or undeclared method produces TPZ5022.
Why
Every operation promised by a protocol must be callable. Rejecting extra methods also catches a misspelled implementation name instead of leaving the intended operation missing.
Source evidence
- Manual implementation conformance at
af88afbc1d99· 297-660
Unique implementation methods
Implementing the same method twice in a protocol implementation produces TPZ5008.
Why
One implementation must supply one definition for each operation. Repeated definitions would make dispatch depend on declaration order.
Source evidence
- Manual implementation conformance at
af88afbc1d99· 297-660
Matching the protocol signature
Substitute the complete target for Self, keep implementation binders rigid, and pair method binders by position. Method binder counts, their sets of resolved protocol bounds, fixed parameter types, variadic position and element type, and result type must match. Type aliases and binder names do not create differences; bound order and parameter names do not affect a positional call. Repeated bounds still follow the ordinary duplicate-bound rule. The implementation may neither strengthen nor weaken a method's bound set. An explicit result annotation must equal the protocol result. When it is omitted, use the protocol result as the ordinary expected result while checking the body. Each return and tail must be assignable to that result; omission does not narrow the public signature. A signature mismatch produces TPZ5022.
Why
Callers have one complete signature before they see an implementation. Alpha-equivalence allows harmless renaming, while exact callable structure and bound identity preserve the same obligations. The protocol already supplies the result context, so repeating its annotation cannot be mandatory.
Source evidence
- Manual implementation conformance at
af88afbc1d99· 297-660 - pull/44 at
72a59a31125f - checkProtocolImplMethodAgainstDefinition at
72a59a31125f· 338-456 - collectProtocolImplForDefinition and collectProtocolImplForTarget at
72a59a31125f· 529-650 - prepareFunctionDeclaration at
72a59a31125f· 1282-1355 - issues/31 at
9b1bbdeceba9
Implementation methods use ordinary functions
An implementation method is checked as an ordinary function against the protocol signature. Matching method generics and a matching variadic tail are admitted, and the result annotation may be omitted. Method-level export and parameter-default initializers produce TPZ5022. Conformance visibility already exposes the promised operations, and the protocol declaration already supplies omitted arguments. An implementation cannot create a second export or default contract, even by repeating the same default expression.
The implementation binder list encloses every method. Each method binder list is a nested lexical scope. A shadowed name denotes the inner binder; it is not silently identified with the outer binder during signature matching. Generic conformance defines the same scope for receiver implementations and conditional derives.
Why
Generic methods, variadic tails, and contextual results have the same function meaning inside an implementation. Visibility and defaults each already have one owner, so repeating either on a method would create competing descriptions of the same operation.
Source evidence
- Manual implementation conformance at
af88afbc1d99· 297-660 - issues/31 at
570530e69141 - pull/44 at
72a59a31125f - checkProtocolImplMethodAgainstDefinition at
72a59a31125f· 338-456 - collectProtocolImplForDefinition and collectProtocolImplForTarget at
72a59a31125f· 529-650 - prepareFunctionDeclaration at
72a59a31125f· 1282-1355 - issues/31 at
9b1bbdeceba9
One conformance across the dependency graph
At most one conformance may apply to a resolved protocol and normalized target type throughout the complete resolved dependency graph. Expand aliases, retain defining-module nominal identities, and rename each declaration's binders before comparing target heads. Heads overlap when some consistent substitution makes their full target types equal. Repeated variables must keep the same substitution. Different positive bound sets cannot establish disjointness, even when no current type satisfies both. Reject every overlap with TPZ5521 before module initialization, whether it is manual/manual, manual/derive, or derive/derive. A concrete implementation has no priority over a generic one. Declaration order, import order, aliases, and method bodies cannot choose a winner. Distinct heads that cannot unify remain independent. Overlap diagnostics use the target of the later manual declaration in deterministic dependency-first module and source order. A conflict involving derive points to the derived protocol head; two derives use the later clause by the same order.
For example, Eq<Box<T>> overlaps Eq<Box<int>>. Mark<Box<int>> and Mark<Box<string>> are disjoint. Mark<Box<T>> and Mark<Box<U>> overlap even if their binders carry different positive bounds. Derive-generated conformances enter this same comparison. Normalization uses static type equality, so the rule does not add value-based or subtype-based implementation selection.
For two conflicting manual declarations, report the target of the later declaration in deterministic dependency-first module order, using source order within a module. If a derive clause participates, report its protocol head; for two derive clauses, choose the later clause by that same order. A secondary location may identify the other declaration. This reporting order never selects an implementation.
Why
Dispatch must identify one operation from its protocol and static type. Rejecting overlap makes that meaning independent of import and declaration order, and prevents later conformances from changing an earlier specialization choice. A stable primary location lets a caller fix the conflicting declaration without making import order affect which source is reported.
Source evidence
- Manual implementation conformance at
af88afbc1d99· 297-660 - Derive checking at
af88afbc1d99· 249-374 - issues/31 at
570530e69141 - pull/44 at
72a59a31125f - checkProtocolImplMethodAgainstDefinition at
72a59a31125f· 338-456 - collectProtocolImplForDefinition and collectProtocolImplForTarget at
72a59a31125f· 529-650 - prepareFunctionDeclaration at
72a59a31125f· 1282-1355 - issues/31 at
9b1bbdeceba9
Generic binders and conditional conformance
An implementation places its type binders before its head, as in impl<T: Eq> Eq<Box<T>>. A receiver implementation uses the same placement, impl<T> Box<T>. Resolve the head to distinguish a protocol application from a nominal receiver; spelling, capitalization, and whitespace do not choose the meaning. Implementation binders scope over their bounds, the complete target type, and every method signature and body. Every implementation binder must occur in the target, so the selected target determines its arguments. Method binders have ordinary nested lexical scope and may shadow outer binders; matching uses resolved binder identities, never name strings. Each binder list has distinct names. A protocol method already has implicit Self in its binder scope. Bounds must have independent conformance evidence; an active obligation cannot prove itself. A generic nominal writes an explicit conditional derive binder list even when it has no bounds. Those binders scope only over that clause, correspond positionally to the nominal parameters, and may be renamed. Each derive target is the declaring nominal applied to all of those binders in declaration order. Fields and enum payloads keep the nominal declaration binders. Concrete and conditional manual and derived conformances use the same uniqueness rule.
Write record Box<T> derives<U: Eq> Eq<Box<U>> { value: T } to rename the clause-local binder. Its positional correspondence is with the nominal parameter T; it does not rebind T inside the fields. A concrete impl Eq<Box<int>> is a concrete head, while impl<T: Eq> Eq<Box<T>> covers every target satisfying the bound. They overlap and cannot coexist. A blanket implementation such as impl<T> Local<T> is permitted in the module defining Local and excludes every overlapping Local implementation. A type alias is expanded before ownership, matching, or overlap is decided.
Why
Explicit lexical binders make a generic conformance one reusable definition. Resolving types before comparing signatures prevents renaming or shadowing from capturing a different type variable. One conformance lookup for every instantiated target avoids overload or specialization priorities.
Source evidence
- issues/31 at
9b1bbdeceba9 - issues/31 at
9b1bbdeceba9 - pull/41 at
271329b482bc - pull/44 at
72a59a31125f - checkProtocolImplMethodAgainstDefinition at
72a59a31125f· 338-456 - collectProtocolImplForDefinition and collectProtocolImplForTarget at
72a59a31125f· 529-650 - prepareFunctionDeclaration at
72a59a31125f· 1282-1355
Implement equality for a generic wrapper
Linkrecord Box<T> { value: T }impl<T: Eq> Eq<Box<T>> {function equals(a: Box<T>, b: Box<T>) -> bool {Eq.equals(a.value, b.value)}}let left: Box<int> = Box { value: 3 }let right: Box<int> = Box { value: 3 }let same: bool = Eq.equals(left, right)
No stdout.
Expected result
Expected result
Checking accepts the program. Exit code 0.
Source evidence
- issues/31 at
9b1bbdeceba9 - issues/31 at
9b1bbdeceba9 - collectProtocolImplForTarget at
570530e69141· 578-603 - collectReceiverImplForTarget at
570530e69141· 970-1007 - checkDerives at
570530e69141· 284-318
Protocol calls and resolution
LinkProtocol-qualified calls
Resolve the protocol and method, then apply the ordinary generic-function call rules. The call type-parameter list is Self followed by the method binders in declaration order. Infer the complete list, or write it after the method name as Protocol.. Implementation binders are determined by the selected conforming type and are not extra call arguments. Parentheses around the callee preserve the same declaration signature. No separate Protocol<Type>. call form is introduced.
Why
A protocol-qualified operation is still one generic callable. One inference and explicit-specialization rule keeps ordinary functions, callbacks, and protocol calls compatible.
Source evidence
- Protocol-qualified calls at
af88afbc1d99· 133-178 - pull/44 at
72a59a31125f - checkProtocolMethod at
72a59a31125f· 1023-1166 - prepareCall at
72a59a31125f· 1432-1513
Protocol call conformance
Check conformance for the resolved Self type, independently of whether a value of that type occurs first, later, or only in the result. A generic body may use its declared protocol bound as conformance evidence. A concrete type without the required conformance produces TPZ5522. Dispatch uses that one conformance and does not select an implementation from the runtime value or from argument order.
Why
Arguments and results may establish the same conforming type. Looking up that type and the resolved protocol identity gives the operation one behavior without requiring a dummy receiver value.
Source evidence
- Protocol-qualified calls at
af88afbc1d99· 133-178 - Protocol call conformance and diagnostics at
af88afbc1d99· 965-1054 - Conformance lookup and function bounds at
af88afbc1d99· 2244-2334 - pull/44 at
72a59a31125f - checkProtocolMethod at
72a59a31125f· 1023-1166 - prepareCall at
72a59a31125f· 1432-1513
Protocol argument forms
Protocol calls supply positional values and may spread an array into a declared variadic tail. Fixed omissions use the protocol defaults. The callee and supplied arguments are evaluated once from left to right; missing defaults follow in parameter order. Spread cannot fill an unsatisfied fixed slot. A named argument is rejected during parsing with TPZ2001, as in every other call.
Why
One argument-binding and evaluation rule lets an operation move between an ordinary function and a protocol without changing its call behavior.
Source evidence
- Protocol argument form checking at
af88afbc1d99· 650-667 - pull/44 at
72a59a31125f - checkProtocolMethod at
72a59a31125f· 1023-1166 - prepareCall at
72a59a31125f· 1432-1513
Protocol bounds
A function protocol bound checks the inferred concrete type argument through conformance lookup. An unsatisfied bound produces TPZ5522.
Why
A generic body may use every operation promised by its bounds. Checking conformance when binding a concrete type keeps those operations available.
Source evidence
- Conformance lookup and function bounds at
af88afbc1d99· 2244-2334
Conformance across modules
Manual and derived conformances are available in the defining module and in modules that import the nominal type. Import aliases preserve that conformance.
Why
A conformance belongs to the type and protocol identities. Importing that type must preserve its operations whether the methods are written or derived.
Source evidence
- Conformance lookup and function bounds at
af88afbc1d99· 2244-2334 - nominalDefinitionModule and typeConforms manual visibility gates at
9b1bbdeceba9· 2224-2285 - protocol method lookup uses the calling module at
9b1bbdeceba9· 1119-1125 - emitImplementation registers the defining module at
9b1bbdeceba9· 1191-1205
Conformance follows nominal origin across imports
Resolve a conformance by the protocol declaration identity and the normalized receiver type. User protocols and nominal types retain their defining module through selected or namespace imports and aliases. Same-spelled declarations in different modules remain distinct. Manual and derived conformances in the resolved dependency graph participate in the same coherence check. Import paths, aliases, and item order do not create or select another conformance. A manual call uses the implementation from the conformance-defining module. A receiver without an applicable conformance is rejected with TPZ5522. Conformance ownership and overlap follow the implementation rules.
Why
A call must keep its meaning when an import is renamed or moved. Resolve both the protocol and receiver type to their defining identities, and check one coherent set of conformances for the dependency graph. The defining module of a conformance determines its implementation, so aliases and import order cannot select a different body.
Source evidence
- collectProtocol at
151141d7bb44· 1178-1273 - userProtocolObject at
151141d7bb44· 1863-1890 - referenceTargetType / nominal definition lookup at
151141d7bb44· 1529-1669 - nominalDefinitionModule / typeConforms at
151141d7bb44· 2224-2285 - userProtocolCallConformance / gateUserProtocolCall at
151141d7bb44· 965-1054 - userProtocolCallTarget at
151141d7bb44· 133-178 - exportDeclarationHead at
151141d7bb44· 1237-1257 - namedStatementBinding / bindingForStatement at
151141d7bb44· 219-233;296-314 - nominalDefinitionModule and typeConforms manual visibility gates at
9b1bbdeceba9· 2224-2285 - protocol method lookup uses the calling module at
9b1bbdeceba9· 1119-1125 - emitImplementation registers the defining module at
9b1bbdeceba9· 1191-1205 - exportDeclarationHead at
72a59a31125f· 1237-1257 - namedStatementBinding / buildTable at
72a59a31125f· 219-260;317-374 - checkFunctionDeclarationInner exported bound guard at
72a59a31125f· 158-191 - pull/41 at
72a59a31125f
Protocol heads respect local values and declaration identity
A protocol declaration creates a non-value protocol binding in the same lexical and module name table as other declarations. It is private unless prefixed with export. Selected imports, selected aliases, and namespace imports resolve that binding by its defining module and declaration. Use P or ns.P in protocol bounds and conformance heads, and P.method or ns.P.method for calls. Aliases preserve identity; equal spellings in different modules remain different protocols. A same-scope name collision follows the module duplicate-binding rule. A nearer value or namespace owns an expression member access and is not reinterpreted as a protocol. Eq, Order, Show, and JSON remain predeclared protocol identities. Protocol names are not runtime values or nominal value types. A public callable may expose a user-protocol bound when that protocol is exported; a private bound is rejected with TPZ5524. Exported protocol method signatures obey the ordinary public-type visibility check. Importing a public type does not implicitly export the private protocols in its defining module. Derive eligibility is unchanged by importing a protocol name.
Why
Moving a generic API into a module must preserve its protocol contract. Protocols therefore use the ordinary explicit export and import boundary. One lexical binding table prevents a spelling from silently denoting separate declarations in the same scope. Defining identities survive aliases and keep independent modules from accidentally sharing conformances. Public signatures expose public contracts; private implementation details remain private.
Source evidence
- collectProtocol at
151141d7bb44· 1178-1273 - userProtocolObject at
151141d7bb44· 1863-1890 - referenceTargetType / nominal definition lookup at
151141d7bb44· 1529-1669 - nominalDefinitionModule / typeConforms at
151141d7bb44· 2224-2285 - userProtocolCallConformance / gateUserProtocolCall at
151141d7bb44· 965-1054 - userProtocolCallTarget at
151141d7bb44· 133-178 - exportDeclarationHead at
151141d7bb44· 1237-1257 - namedStatementBinding / bindingForStatement at
151141d7bb44· 219-233;296-314 - exportDeclarationHead at
72a59a31125f· 1237-1257 - namedStatementBinding / buildTable at
72a59a31125f· 219-260;317-374 - checkFunctionDeclarationInner exported bound guard at
72a59a31125f· 158-191 - pull/41 at
72a59a31125f
A selected protocol alias preserves a public conformance
Linkimport std.io { writeLine }import contracts { Labelled as Described }import item { Item }function describe<T: Described>(value: T) -> string { Described.label(value) }writeLine(describe(Item { text: "selected" }))
selected
export protocol Labelled { function label(value: Self) -> string }
import contracts { Labelled }export record Item { text: string }impl Labelled<Item> {function label(value: Item) -> string { value.text }}
Expected result
Expected result
Execution completes. Exit code 0.
selected
Source evidence
- exportDeclarationHead at
72a59a31125f· 1237-1257 - namedStatementBinding / buildTable at
72a59a31125f· 219-260;317-374 - checkFunctionDeclarationInner exported bound guard at
72a59a31125f· 158-191 - pull/41 at
72a59a31125f
A namespace-qualified protocol resolves in bounds, impl heads, and calls
Linkimport std.io { writeLine }import contracts as apiimport item { Item }function describe<T: api.Labelled>(value: T) -> string { api.Labelled.label(value) }writeLine(describe(Item { text: "qualified" }))
qualified
export protocol Labelled { function label(value: Self) -> string }
import contracts as apiexport record Item { text: string }impl api.Labelled<Item> {function label(value: Item) -> string { value.text }}
Expected result
Expected result
Execution completes. Exit code 0.
qualified
Source evidence
- exportDeclarationHead at
72a59a31125f· 1237-1257 - namedStatementBinding / buildTable at
72a59a31125f· 219-260;317-374 - checkFunctionDeclarationInner exported bound guard at
72a59a31125f· 158-191 - pull/41 at
72a59a31125f
Equal protocol spellings in different modules keep different conformances
Linkimport std.io { writeLine }import first { Marked as First }import second { Marked as Second }import value { Item }function printBoth<T: First + Second>(value: T) -> () {writeLine(First.mark(value))writeLine(Second.mark(value))}printBoth(Item { text: "value" })
first value second value
export record Item { text: string }
import value { Item }export protocol Marked { function mark(value: Self) -> string }impl Marked<Item> { function mark(value: Item) -> string { "first {value.text}" } }
import value { Item }export protocol Marked { function mark(value: Self) -> string }impl Marked<Item> { function mark(value: Item) -> string { "second {value.text}" } }
Expected result
Expected result
Execution completes. Exit code 0.
first valuesecond value
Source evidence
- exportDeclarationHead at
72a59a31125f· 1237-1257 - namedStatementBinding / buildTable at
72a59a31125f· 219-260;317-374 - checkFunctionDeclarationInner exported bound guard at
72a59a31125f· 158-191 - pull/41 at
72a59a31125f
An exported function may expose an exported user protocol bound
Linkexport protocol Labelled { function label(value: Self) -> string }export function describe<T: Labelled>(value: T) -> string { Labelled.label(value) }
No stdout.
Expected result
Expected result
Checking accepts the program. Exit code 0.
Source evidence
- exportDeclarationHead at
72a59a31125f· 1237-1257 - namedStatementBinding / buildTable at
72a59a31125f· 219-260;317-374 - checkFunctionDeclarationInner exported bound guard at
72a59a31125f· 158-191 - pull/41 at
72a59a31125f
Reject a public callable that exposes a private protocol bound
Linkprotocol Hidden { function label(value: Self) -> string }export function describe<T: Hidden>(value: T) -> string { Hidden.label(value) }
No stdout.
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5524protocol-private-public-bound atexample.tpz:2:29
Source evidence
- exportDeclarationHead at
72a59a31125f· 1237-1257 - namedStatementBinding / buildTable at
72a59a31125f· 219-260;317-374 - checkFunctionDeclarationInner exported bound guard at
72a59a31125f· 158-191 - pull/41 at
72a59a31125f
A private protocol is not selected through import
Linkimport contracts { Hidden }
No stdout.
protocol Hidden { function label(value: Self) -> string }export let marker = 0
Expected result
Expected result
Rejection during resolve. Nonzero exit.
TPZ3009protocol-name-not-exported atexample.tpz:1:20
Source evidence
- exportDeclarationHead at
72a59a31125f· 1237-1257 - namedStatementBinding / buildTable at
72a59a31125f· 219-260;317-374 - checkFunctionDeclarationInner exported bound guard at
72a59a31125f· 158-191 - pull/41 at
72a59a31125f
A protocol and a value cannot bind the same module-level name
Linkprotocol Labelled { function label(value: Self) -> string }let Labelled = 0
No stdout.
Expected result
Expected result
Rejection during resolve. Nonzero exit.
TPZ3008protocol-binding-name-collision atexample.tpz:2:5
Source evidence
- exportDeclarationHead at
72a59a31125f· 1237-1257 - namedStatementBinding / buildTable at
72a59a31125f· 219-260;317-374 - checkFunctionDeclarationInner exported bound guard at
72a59a31125f· 158-191 - pull/41 at
72a59a31125f
An exported protocol cannot expose a private nominal parameter type
Linkrecord Hidden { n: int }export protocol Render { function render(value: Self, input: Hidden) -> string }
No stdout.
Expected result
Expected result
Rejection during resolve. Nonzero exit.
TPZ3014modules-public-private-nominal atexample.tpz:2:62
Source evidence
- exportDeclarationHead at
72a59a31125f· 1237-1257 - namedStatementBinding / buildTable at
72a59a31125f· 219-260;317-374 - checkFunctionDeclarationInner exported bound guard at
72a59a31125f· 158-191 - pull/41 at
72a59a31125f
An exported protocol signature may use an exported nominal type
Linkexport record Input { n: int }export protocol Render { function render(value: Self, input: Input) -> string }
No stdout.
Expected result
Expected result
Checking accepts the program. Exit code 0.
Source evidence
- exportDeclarationHead at
72a59a31125f· 1237-1257 - namedStatementBinding / buildTable at
72a59a31125f· 219-260;317-374 - checkFunctionDeclarationInner exported bound guard at
72a59a31125f· 158-191 - pull/41 at
72a59a31125f
Namespace qualification whitespace cannot repeat one protocol bound
Linkimport contracts as apifunction describe<T: api.Labelled + api . Labelled>(value: T) -> string { api.Labelled.label(value) }
No stdout.
export protocol Labelled { function label(value: Self) -> string }
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5523protocol-repeated-resolved-bound atexample.tpz:2:37
Source evidence
- checkFunctionDeclarationInner bound iteration at
151141d7bb44· 177-191 - checkFunctionDeclarationInner exported bound guard at
72a59a31125f· 158-191 - pull/41 at
72a59a31125f
Derived conformance
LinkFor declaration grammar, see Records, enums and newtypes.
Derivable protocols
For a nongeneric record, enum, or newtype, derives requests Eq, Order, Show, or JSON conformance. Another protocol name produces TPZ5530.
Why
Generated operations need a defined derivation for each field, variant payload, or newtype base. The built-in protocols supply those rules for all three nominal declaration forms.
Source evidence
- Derive checking at
af88afbc1d99· 249-374 - pull/41 at
271329b482bc
Show derivation
Show derivation for a nongeneric record, enum, or newtype requires no member capability test. A newtype is displayed by its type name followed by its rendered base value in parentheses.
Why
Derived Show uses the shared value renderer, which handles every runtime value, including opaque values and functions. Its fields need no separate Show conformance.
Source evidence
- Derive checking at
af88afbc1d99· 249-374 - builtin_protocol_dispatch at
151141d7bb44· 246-269 - render_into nominal record at
151141d7bb44· 306-329 - pull/41 at
271329b482bc
Derivation field and payload requirements
Eq, Order, and JSON derivation check every record field, enum payload, or newtype base for equality, total ordering, or both JSON conversion operations. Each operation uses the capability domain described below. JSON decoding follows the declared normalization rules, including Option null conversion. A member that lacks the required capability produces TPZ5530.
Why
A generated operation visits the constituent fields or payloads. Each constituent must support the corresponding equality, ordering, or JSON conversion operation.
Source evidence
- Field and payload derivability at
af88afbc1d99· 42-204 - Derive checking at
af88afbc1d99· 249-374 - pull/41 at
271329b482bc
JSON conformance
JSON is an ordinary conformance with encode(value: Self) - and decode(value: JSON. An implementation supplies both methods. An empty marker produces the missing-method error TPZ5022. The ordinary protocol call rules infer Self or select it through the first explicit call type argument, as in JSON.. std. calls encode and then the canonical JSONValue writer. std. parses the text and calls the same decode implementation selected by JSON.. A codec returns an Err result for invalid input or a value it cannot encode. These failures do not become checker diagnostics. The manual decoder owns the accepted JSONValue shape. Derived record decoders ignore unknown object fields and use declared field defaults when a field is missing. A missing required field, wrong kind, unknown enum tag, or wrong payload arity returns an Err result. Malformed text fails before the decoder is called. JSON promises both conversion operations, with the representation and normalization defined for the type. Conversion may normalize distinct source values to the same decoded value. Existing Option conversion maps None() to null and Some(v) to the representation of v, so Some(None()) and Some(()) normalize to None() when decoded through an enclosing Option. Decode a JSONValue with JSON.. The receiver member tree. is removed and produces the ordinary missing-member diagnostic TPZ5006. Parsing text and writing text remain distinct operations through parseAs and stringify.
Why
A public codec should customize the same operation that library callers use. Supplying encode and decode through ordinary protocol methods lets a nominal type define its external representation without a second ad hoc serialization API. JSONValue separates typed conversion from parsing and canonical text output. The Result channel reports data failures in the same way for manual and derived implementations. Existing record default and Option normalization rules remain explicit conversion semantics. Decoding an existing tree has one public spelling, the ordinary protocol-qualified call. A receiver alias would repeat the same operation with identical input and output.
Source evidence
- compiler/crates/topaz_self_frontend/topaz/checker/protocols_methods.tpz at
72a59a31125f· 100-143 - compiler/crates/topaz_self_frontend/topaz/checker/protocols_methods.tpz at
72a59a31125f· 529-541 - compiler/crates/topaz_value/src/value/json/codec.rs at
72a59a31125f· 1-222 - compiler/crates/topaz_value/src/value/json/schema.rs at
72a59a31125f· 855-1000 - pull/44 at
72a59a31125f - compiler/crates/topaz_self_frontend/topaz/checker/members_capabilities.tpz at
72a59a31125f· 1348-1359
Built-in protocol signatures
The built-in method signatures are Show., Eq., Order., JSON., and JSON.. JSONValue is the parsed JSON tree type exported by std.json. JSON decode uses the ordinary protocol rule for selecting Self when there is no conforming input value.
Why
Shared signatures let user types participate in the same display, equality, and ordering operations. Callers do not need separate APIs for each type.
Source evidence
- Built-in protocol signatures at
af88afbc1d99· 100-144 - compiler/crates/topaz_self_frontend/topaz/checker/protocols_methods.tpz at
72a59a31125f· 100-143 - compiler/crates/topaz_self_frontend/topaz/checker/protocols_methods.tpz at
72a59a31125f· 529-541 - compiler/crates/topaz_value/src/value/json/codec.rs at
72a59a31125f· 1-222 - compiler/crates/topaz_value/src/value/json/schema.rs at
72a59a31125f· 855-1000 - pull/44 at
72a59a31125f
Nested and recursive derivation requirements
Deriving Eq, Order, or JSON composes the same operation on every record field, enum payload, or newtype base. A nested nominal uses its applicable manual or derived conformance after type arguments are substituted in its defining module. A manual method replaces structural inspection of that nominal; an outer derive must not bypass it or reject it because of private representation fields. A nested nominal with no required conformance produces TPZ5530. Built-in component families use the corresponding operation domain below.
Declared recursive and mutually recursive conformances may refer to one another. A recursive edge closes a declared conformance reference; it does not excuse a missing component capability elsewhere in the cycle. Establish each conditional instance using independent generic bound evidence before linking its recursive descriptors. An active generic bound cannot prove itself; a back-edge between already applicable declared derives is a descriptor reference, not evidence for that bound. A descriptor is identified by defining module, nominal declaration, and normalized type arguments. Keep references and captured substitutions and instantiate them as values are traversed, rather than eagerly expanding a schema tree. Imported aliases keep the same descriptor identity. Conditional generic instances follow the ordinary binder and coherence rules.
A derived JSON decoder follows the finite input tree. Each recursive aggregate step consumes a child JSON value; a descriptor loop that revisits the same input without structural progress returns Err. JSON starts at depth zero, permits depths through 128, and increments depth when descending to a record field, array element, map value, or enum payload. Descriptor references, Option wrapping, and newtype wrapping do not add a level. Data values follow value semantics. A depth above 128 returns an Err string beginning JSON_. The same traversal context covers nested derived dispatch. Descriptor expansion does not reset it. Manual method bodies retain ordinary function evaluation and resource behavior.
Derived equality and ordering use the same nested conformance as direct protocol calls. A declared nominal Eq or Order implementation also supplies the corresponding comparison operators, so moving a comparison inside a wrapper does not change its meaning. A structural comparison that exhausts its budget raises the existing TPZ5007 guard. Show retains its total debug rendering operation and does not introduce an Eq, Order, or JSON member requirement.
Why
A nominal conformance is the type author’s chosen operation, so composition calls it. Recursive declarations describe finite data trees under value semantics. Data values cannot contain cycles. Lazy descriptor references and bounded traversal support recursive declarations and deep values.
Source evidence
- compiler/crates/topaz_self_frontend/topaz/checker/diagnostics_derives.tpz at
72a59a31125f· 42-204 - compiler/crates/topaz_self_frontend/topaz/checker/protocols_methods.tpz at
72a59a31125f· 529-541 - compiler/crates/topaz_value/src/value/compare.rs at
72a59a31125f· 85-180 - compiler/crates/topaz_value/src/value/compare.rs at
72a59a31125f· 198-353 - compiler/crates/topaz_value/src/value/json/schema.rs at
72a59a31125f· 212-226 - compiler/crates/topaz_value/src/value/json/codec.rs at
72a59a31125f· 1-47 - compiler/crates/topaz_value/src/value/guards.rs at
72a59a31125f· 88-96 - pull/44 at
72a59a31125f - issues/31 at
1162031127a0
Field capability domains for derivation
Derivation follows the operation it generates. Eq asks whether two values are equal. Order supplies a total comparison. JSON supplies conversion to and from a JSON tree under the type's documented representation. These are different obligations, so admission is determined by the operation rather than a common whitelist. A nested nominal always uses the conformance described above. This table defines the listed component families; it does not assign capabilities to other built-ins. Duration capabilities follow its operations rule.
| Component | Eq | Order | JSON |
|---|---|---|---|
| int, string, bool | Value equality | Numeric, Unicode scalar, or false-before-true order | JSON number, string, or boolean |
| unit and null | Equality within the singleton type | Zero within the same singleton type | JSON null, recovered through the requested target type |
| float | IEEE numerical equality | No total Order because NaN has no place in the existing numerical relation | Finite numbers convert; NaN and infinities return Err |
| Array | Same length and elementwise equality | Lexicographic elements; a prefix precedes its extension | JSON array with recursive conversion |
| Option | Same constructor and payload equality | None() precedes Some(value). Compare present payloads | None() encodes as null. Some(value) unwraps its payload. Null payloads normalize on decoding |
| Result | Same constructor and payload equality | Err before Ok; compare payloads within the same constructor | The ordinary enum object with tag Ok or Err and one values entry |
| Map | Same lookup key identities and equal values, independent of insertion order | Requires an application-selected ordering | String keys form a JSON object; other key types need an explicit schema |
| Set | Same membership key identities, independent of insertion order | Requires an application-selected ordering | Requires an explicit sequence representation and duplicate policy |
| Structural record | Same field set and fieldwise equality | Compare fields in Unicode scalar order of field names | String field names form a JSON object |
| Union | Compare active member identities, then values within a member | Admitted when every member widens to the same ordered scalar domain | Untagged overlapping representations need an explicit nominal schema |
Map and Set equality reuse the key identity already used by lookup and membership. A custom Eq method does not replace stored-key equality. Map values use their Eq operation, including a manual nominal implementation. Insertion order does not affect either equality result. Functions and resources have no data-value equality, and regex denotation has no general equality operation. JSONValue and TOMLValue preserve representation information and require decoding or an explicit projection into the intended equality domain. Templates, ranges, mutable ByteBuffer values, and namespaces likewise have no automatic Eq conformance. Bytes compare bytes; Path and URL compare their canonical representations; Date, BigInt, and Decimal compare their values; Match and RoundingMode retain their declared value equality. IEEE equality is not an equivalence relation for NaN-containing values: NaN differs from itself and positive and negative zero compare equal. Eq does not promise otherwise.
For Order, Bytes compare bytewise; Path and URL compare canonical strings; Date compares calendar days; BigInt and Decimal compare numbers. A record uses sorted field names, including a nominal record, so rearranging declarations cannot change priority. A derived enum compares variant names in Unicode scalar order and then positional payloads. A newtype compares its base. Each component must itself have Order. Map and Set describe membership rather than a selected key order; a heterogeneous union has no declared cross-type priority. A nominal manual implementation can state those application-specific choices. Float keeps its ordinary IEEE relational predicates, which may all be false for NaN, and therefore does not satisfy total Order. Opaque families without a declared total order do not gain one through derivation.
JSON uses the existing record object, enum tag/values object, and transparent newtype representations. Result uses the enum representation rather than treating an Err payload as codec failure. Option converts through null and its payload values. Some(None()) and Some(()) can decode as None(). This normalization can map distinct source values to the same decoded value. JSONValue conversion preserves its tree, including stored number lexemes. Semantic scalar types such as Bytes, Path, URL, Date, BigInt, Decimal, Match, and RoundingMode need an explicitly chosen external schema through a nominal manual JSON implementation. Conversion does not silently pick a binary encoding, path base, unit, precision loss, or tag convention. Functions, resources, and templates cannot be reconstructed as data values. Derived conversion failure is TPZ5530 when a component type has no JSON capability. Malformed data and value-level encoding failures return an Err result during conversion.
Finite float encoding chooses the shortest decimal significand that recovers the identical binary64 value under nearest, ties-to-even decoding. If shortest candidates differ, choose the decimal closest to the exact binary value, then the candidate with an even final significand digit. Render fixed notation when the normalized decimal exponent is at least -6 and below 21; otherwise use lowercase e, with no plus sign or redundant exponent zeroes. Omit redundant fractional zeroes. Positive zero is 0 and negative zero is -0. A nonfinite input returns Err without substituting null or a string. Decoding accepts any JSON number, including integer lexemes, rounds once to binary64 with nearest, ties-to-even, preserves a negative zero sign, and rejects overflow to a nonfinite result. Underflow rounds to a finite subnormal or signed zero. The shortest-number rule applies when encoding a float; parsing and re-serializing an existing JSONValue preserves its accepted number lexeme, and numberText/asInt retain their existing semantics. Recursive conversion follows the depth rule above and value semantics. Show remains the total debug-render operation and has no member capability gate.
Duration has Eq and Order under its operations rule. Duration has no JSON capability in 6.0.
Why
Equal membership, lexicographic sequence order, total comparison, and JSON conversion answer different questions. Their requirements follow those questions. Map and Set equality should agree with lookup and membership instead of requiring an arbitrary insertion order. A total Order cannot inherit IEEE NaN comparison. A finite float can use the JSON number domain while the existing Result channel handles nonfinite values. JSON schemas that require an application choice stay explicit, and declaration layout does not choose a comparison priority.
Source evidence
- compiler/crates/topaz_self_frontend/topaz/checker/diagnostics_derives.tpz at
72a59a31125f· 137-204 - compiler/crates/topaz_value/src/value/compare.rs at
72a59a31125f· 26-75 - compiler/crates/topaz_value/src/value/compare.rs at
72a59a31125f· 198-353 - compiler/crates/topaz_value/src/value/json/codec.rs at
72a59a31125f· 10-222 - compiler/crates/topaz_value/src/value/json/schema.rs at
72a59a31125f· 140-212 - pull/44 at
72a59a31125f
Derived and manual conformance must be unique
Why
Generated implementations must participate in the same dispatch model as handwritten implementations.
Source evidence
- deriveTypeCapability at
151141d7bb44· 42-204 - firstRecordDeriveFailure / firstEnumDeriveFailure at
151141d7bb44· 207-246 - recordDerivedConformance at
151141d7bb44· 249-283 - checkDerives at
151141d7bb44· 286-374 - jsonCapability at
151141d7bb44· 408-518
Derive equality under an element bound
Linkrecord Box<T> derives<T: Eq> Eq<Box<T>> { value: T }let left: Box<int> = Box { value: 3 }let right: Box<int> = Box { value: 3 }let same: bool = Eq.equals(left, right)
No stdout.
Expected result
Expected result
Checking accepts the program. Exit code 0.
Source evidence
- issues/31 at
9b1bbdeceba9 - issues/31 at
9b1bbdeceba9 - collectProtocolImplForTarget at
570530e69141· 578-603 - collectReceiverImplForTarget at
570530e69141· 970-1007 - checkDerives at
570530e69141· 284-318
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
A manual JSON implementation supplies both conversions
Linkimport std.json { JSONValue }record Token { text: string }impl JSON<Token> {function encode(value: Token) -> Result<JSONValue, string> {JSON.encode(value.text)}function decode(value: JSONValue) -> Result<Token, string> {match (value.asString()) {case Some(text) => Ok(Token { text: text })case None() => Err("expected token string")}}}
No stdout.
Expected result
Expected result
Checking accepts the program. Exit code 0.
Source evidence
- compiler/crates/topaz_self_frontend/topaz/checker/protocols_methods.tpz at
72a59a31125f· 100-143 - compiler/crates/topaz_self_frontend/topaz/checker/protocols_methods.tpz at
72a59a31125f· 529-541 - compiler/crates/topaz_value/src/value/json/codec.rs at
72a59a31125f· 1-222 - compiler/crates/topaz_value/src/value/json/schema.rs at
72a59a31125f· 855-1000 - pull/44 at
72a59a31125f
Text and tree conversion use the same manual JSON implementation
Linkimport std.io { writeLine }import std.json { JSONValue, parse, parseAs, stringify }record Token { text: string }impl JSON<Token> {function encode(value: Token) -> Result<JSONValue, string> {JSON.encode(value.text)}function decode(value: JSONValue) -> Result<Token, string> {match (value.asString()) {case Some(text) => Ok(Token { text: text })case None() => Err("expected token string")}}}writeLine(match (stringify(Token { text: "paper" })) {case Ok(text) => textcase Err(message) => message})writeLine(match (parseAs<Token>("\"paper\"")) {case Ok(value) => value.textcase Err(message) => message})writeLine(match (parseAs<Token>("17")) {case Ok(value) => value.textcase Err(message) => message})writeLine(match (parse("\"ink\"")) {case Ok(tree) => match (JSON.decode<Token>(tree)) {case Ok(value) => value.textcase Err(message) => message}case Err(_) => "parse failed"})
"paper" paper expected token string ink
Expected result
Expected result
Execution completes. Exit code 0.
"paper"paperexpected token stringink
Source evidence
- compiler/crates/topaz_self_frontend/topaz/checker/protocols_methods.tpz at
72a59a31125f· 100-143 - compiler/crates/topaz_self_frontend/topaz/checker/protocols_methods.tpz at
72a59a31125f· 529-541 - compiler/crates/topaz_value/src/value/json/codec.rs at
72a59a31125f· 1-222 - compiler/crates/topaz_value/src/value/json/schema.rs at
72a59a31125f· 855-1000 - pull/44 at
72a59a31125f
An empty JSON marker omits the required conversion methods
Linkrecord Token { text: string }impl JSON<Token> {}
No stdout.
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5022missing-protocol-method atexample.tpz:2:6
Source evidence
- compiler/crates/topaz_self_frontend/topaz/checker/protocols_methods.tpz at
72a59a31125f· 100-143 - compiler/crates/topaz_self_frontend/topaz/checker/protocols_methods.tpz at
72a59a31125f· 529-541 - compiler/crates/topaz_value/src/value/json/codec.rs at
72a59a31125f· 1-222 - compiler/crates/topaz_value/src/value/json/schema.rs at
72a59a31125f· 855-1000 - pull/44 at
72a59a31125f
Derived JSON keeps record defaults and ignores unknown object fields
Linkimport std.io { writeLine }import std.json { parseAs, stringify }record Item derives JSON { name: string, count: int = 7 }let text = match (stringify({ name: "ink", extra: true })) {case Ok(value) => valuecase Err(message) => message}writeLine(match (parseAs<Item>(text)) {case Ok(value) => "{value.name}:{value.count}"case Err(message) => message})writeLine(match (parseAs<Item>("17")) {case Ok(_) => "unexpected value"case Err(_) => "wrong shape"})writeLine(match (parseAs<Item>("[")) {case Ok(_) => "unexpected value"case Err(_) => "malformed text"})
ink:7 wrong shape malformed text
Expected result
Expected result
Execution completes. Exit code 0.
ink:7wrong shapemalformed text
Source evidence
- compiler/crates/topaz_self_frontend/topaz/checker/protocols_methods.tpz at
72a59a31125f· 100-143 - compiler/crates/topaz_self_frontend/topaz/checker/protocols_methods.tpz at
72a59a31125f· 529-541 - compiler/crates/topaz_value/src/value/json/codec.rs at
72a59a31125f· 1-222 - compiler/crates/topaz_value/src/value/json/schema.rs at
72a59a31125f· 855-1000 - pull/44 at
72a59a31125f
JSONValue decoding uses the ordinary protocol call
Linkimport std.json { JSONValue }function read(tree: JSONValue) -> Result<int, string> {tree.decode<int>()}
No stdout.
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5006stdlib-removed-member atexample.tpz:3:8
Source evidence
- compiler/crates/topaz_self_frontend/topaz/checker/members_capabilities.tpz at
72a59a31125f· 1348-1359
Decode a JSONValue with one protocol-qualified form
Linkimport std.json { JSONValue }function read(tree: JSONValue) -> Result<int, string> {JSON.decode<int>(tree)}
No stdout.
Expected result
Expected result
Checking accepts the program. Exit code 0.
Source evidence
- compiler/crates/topaz_self_frontend/topaz/checker/members_capabilities.tpz at
72a59a31125f· 1348-1359
Derived equality and operators use the inner manual equality
Linkimport std.io { writeLine }record Token { key: int, noise: int }impl Eq<Token> {function equals(a: Token, b: Token) -> bool { a.key == b.key }}record Box derives Eq { item: Token }let a = Box { item: Token { key: 1, noise: 10 } }let b = Box { item: Token { key: 1, noise: 20 } }let c = Box { item: Token { key: 2, noise: 10 } }writeLine("{Eq.equals(a, b)}")writeLine("{a == b}")writeLine("{Eq.equals(a, c)}")writeLine("{a != c}")
true true false true
Expected result
Expected result
Execution completes. Exit code 0.
truetruefalsetrue
Source evidence
- compiler/crates/topaz_self_frontend/topaz/checker/diagnostics_derives.tpz at
72a59a31125f· 42-204 - compiler/crates/topaz_self_frontend/topaz/checker/protocols_methods.tpz at
72a59a31125f· 529-541 - compiler/crates/topaz_value/src/value/compare.rs at
72a59a31125f· 85-180 - compiler/crates/topaz_value/src/value/compare.rs at
72a59a31125f· 198-353 - compiler/crates/topaz_value/src/value/json/schema.rs at
72a59a31125f· 212-226 - compiler/crates/topaz_value/src/value/json/codec.rs at
72a59a31125f· 1-47 - compiler/crates/topaz_value/src/value/guards.rs at
72a59a31125f· 88-96 - pull/44 at
72a59a31125f
A manual equality hides an opaque field from outer operators
Linkimport std.io { writeLine }record Callback { key: int, run: () -> int }impl Eq<Callback> {function equals(a: Callback, b: Callback) -> bool { a.key == b.key }}record Box derives Eq { item: Callback }let a = Box { item: Callback { key: 1, run: () => 7 } }let b = Box { item: Callback { key: 1, run: () => 9 } }writeLine("{a == b}")writeLine("{Eq.equals(a, b)}")
true true
Expected result
Expected result
Execution completes. Exit code 0.
truetrue
Source evidence
- compiler/crates/topaz_self_frontend/topaz/checker/diagnostics_derives.tpz at
72a59a31125f· 42-204 - compiler/crates/topaz_self_frontend/topaz/checker/protocols_methods.tpz at
72a59a31125f· 529-541 - compiler/crates/topaz_value/src/value/compare.rs at
72a59a31125f· 85-180 - compiler/crates/topaz_value/src/value/compare.rs at
72a59a31125f· 198-353 - compiler/crates/topaz_value/src/value/json/schema.rs at
72a59a31125f· 212-226 - compiler/crates/topaz_value/src/value/json/codec.rs at
72a59a31125f· 1-47 - compiler/crates/topaz_value/src/value/guards.rs at
72a59a31125f· 88-96 - pull/44 at
72a59a31125f
Derived ordering and operators use the inner manual ordering
Linkimport std.io { writeLine }record Rank { score: int }impl Order<Rank> {function compare(a: Rank, b: Rank) -> int {if (a.score > b.score) { -1 } else if (a.score < b.score) { 1 } else { 0 }}}record Box derives Order { item: Rank }let a = Box { item: Rank { score: 9 } }let b = Box { item: Rank { score: 3 } }writeLine("{Order.compare(a, b)}")writeLine("{a < b}")writeLine("{Order.compare(b, a)}")writeLine("{b > a}")
-1 true 1 true
Expected result
Expected result
Execution completes. Exit code 0.
-1true1true
Source evidence
- compiler/crates/topaz_self_frontend/topaz/checker/diagnostics_derives.tpz at
72a59a31125f· 42-204 - compiler/crates/topaz_self_frontend/topaz/checker/protocols_methods.tpz at
72a59a31125f· 529-541 - compiler/crates/topaz_value/src/value/compare.rs at
72a59a31125f· 85-180 - compiler/crates/topaz_value/src/value/compare.rs at
72a59a31125f· 198-353 - compiler/crates/topaz_value/src/value/json/schema.rs at
72a59a31125f· 212-226 - compiler/crates/topaz_value/src/value/json/codec.rs at
72a59a31125f· 1-47 - compiler/crates/topaz_value/src/value/guards.rs at
72a59a31125f· 88-96 - pull/44 at
72a59a31125f
Derived JSON uses the nested manual codec in both directions
Linkimport std.io { writeLine }import std.json { JSONValue, parseAs, stringify }record Token { text: string }impl JSON<Token> {function encode(value: Token) -> Result<JSONValue, string> {JSON.encode(value.text)}function decode(value: JSONValue) -> Result<Token, string> {match (value.asString()) {case Some(text) => Ok(Token { text: text })case None() => Err("expected token string")}}}record Envelope derives JSON { token: Token }let text = match (stringify(Envelope { token: Token { text: "paper" } })) {case Ok(value) => valuecase Err(message) => message}writeLine(text)writeLine(match (parseAs<Envelope>(text)) {case Ok(value) => value.token.textcase Err(message) => message})
{"token":"paper"}
paper
Expected result
Expected result
Execution completes. Exit code 0.
{"token":"paper"}paper
Source evidence
- compiler/crates/topaz_self_frontend/topaz/checker/diagnostics_derives.tpz at
72a59a31125f· 42-204 - compiler/crates/topaz_self_frontend/topaz/checker/protocols_methods.tpz at
72a59a31125f· 529-541 - compiler/crates/topaz_value/src/value/compare.rs at
72a59a31125f· 85-180 - compiler/crates/topaz_value/src/value/compare.rs at
72a59a31125f· 198-353 - compiler/crates/topaz_value/src/value/json/schema.rs at
72a59a31125f· 212-226 - compiler/crates/topaz_value/src/value/json/codec.rs at
72a59a31125f· 1-47 - compiler/crates/topaz_value/src/value/guards.rs at
72a59a31125f· 88-96 - pull/44 at
72a59a31125f
A finite recursive enum value encodes and decodes
Linkimport std.io { writeLine }import std.json { parseAs, stringify }enum Chain derives Eq, JSON { End, Next(int, Chain) }let original = Chain.Next(7, Chain.End())let text = match (stringify(original)) {case Ok(value) => valuecase Err(message) => message}writeLine(text)writeLine(match (parseAs<Chain>(text)) {case Ok(value) => "{Eq.equals(value, original)}"case Err(message) => message})
{"tag":"Next","values":[7,{"tag":"End"}]}
true
Expected result
Expected result
Execution completes. Exit code 0.
{"tag":"Next","values":[7,{"tag":"End"}]}true
Source evidence
- compiler/crates/topaz_self_frontend/topaz/checker/diagnostics_derives.tpz at
72a59a31125f· 42-204 - compiler/crates/topaz_self_frontend/topaz/checker/protocols_methods.tpz at
72a59a31125f· 529-541 - compiler/crates/topaz_value/src/value/compare.rs at
72a59a31125f· 85-180 - compiler/crates/topaz_value/src/value/compare.rs at
72a59a31125f· 198-353 - compiler/crates/topaz_value/src/value/json/schema.rs at
72a59a31125f· 212-226 - compiler/crates/topaz_value/src/value/json/codec.rs at
72a59a31125f· 1-47 - compiler/crates/topaz_value/src/value/guards.rs at
72a59a31125f· 88-96 - pull/44 at
72a59a31125f
Mutually recursive declarations share conformance references
Linkrecord Folder derives Eq, JSON { entries: Array<Entry> }enum Entry derives Eq, JSON { File(string), Directory(Folder) }
No stdout.
Expected result
Expected result
Checking accepts the program. Exit code 0.
Source evidence
- compiler/crates/topaz_self_frontend/topaz/checker/diagnostics_derives.tpz at
72a59a31125f· 42-204 - compiler/crates/topaz_self_frontend/topaz/checker/protocols_methods.tpz at
72a59a31125f· 529-541 - compiler/crates/topaz_value/src/value/compare.rs at
72a59a31125f· 85-180 - compiler/crates/topaz_value/src/value/compare.rs at
72a59a31125f· 198-353 - compiler/crates/topaz_value/src/value/json/schema.rs at
72a59a31125f· 212-226 - compiler/crates/topaz_value/src/value/json/codec.rs at
72a59a31125f· 1-47 - compiler/crates/topaz_value/src/value/guards.rs at
72a59a31125f· 88-96 - pull/44 at
72a59a31125f
Inserted values keep recursive JSON finite
Linkimport std.io { writeLine }import std.json { stringify }record Node derives JSON { children: Array<Node> }let mut children: Array<Node> = []let root = Node { children: children }children.push(root)writeLine("{root.children.length}:{children.length}")writeLine(match (stringify(root)) {case Ok(value) => "Ok {value}"case Err(_) => "unexpected error"})
0:1
Ok {"children":[]}
Expected result
Expected result
Execution completes. Exit code 0.
0:1Ok {"children":[]}
The record retains its empty children value after children.. Insertion follows value semantics and cannot create a cycle.
Source evidence
- compiler/crates/topaz_self_frontend/topaz/checker/diagnostics_derives.tpz at
72a59a31125f· 42-204 - compiler/crates/topaz_self_frontend/topaz/checker/protocols_methods.tpz at
72a59a31125f· 529-541 - compiler/crates/topaz_value/src/value/compare.rs at
72a59a31125f· 85-180 - compiler/crates/topaz_value/src/value/compare.rs at
72a59a31125f· 198-353 - compiler/crates/topaz_value/src/value/json/schema.rs at
72a59a31125f· 212-226 - compiler/crates/topaz_value/src/value/json/codec.rs at
72a59a31125f· 1-47 - compiler/crates/topaz_value/src/value/guards.rs at
72a59a31125f· 88-96 - pull/44 at
72a59a31125f - issues/31 at
1162031127a0
Inserted values keep recursive comparison finite
Linkimport std.io { writeLine }record Node derives Eq { children: Array<Node> }let mut children: Array<Node> = []let root = Node { children: children }let peer = Node { children: children }children.push(root)writeLine("{Eq.equals(root, peer)}:{root.children.length}:{children.length}")
true:0:1
Expected result
Expected result
Execution completes. Exit code 0.
true:0:1
The record retains its empty children value after children.. Insertion follows value semantics and cannot create a cycle.
Source evidence
- compiler/crates/topaz_self_frontend/topaz/checker/diagnostics_derives.tpz at
72a59a31125f· 42-204 - compiler/crates/topaz_self_frontend/topaz/checker/protocols_methods.tpz at
72a59a31125f· 529-541 - compiler/crates/topaz_value/src/value/compare.rs at
72a59a31125f· 85-180 - compiler/crates/topaz_value/src/value/compare.rs at
72a59a31125f· 198-353 - compiler/crates/topaz_value/src/value/json/schema.rs at
72a59a31125f· 212-226 - compiler/crates/topaz_value/src/value/json/codec.rs at
72a59a31125f· 1-47 - compiler/crates/topaz_value/src/value/guards.rs at
72a59a31125f· 88-96 - pull/44 at
72a59a31125f - issues/31 at
1162031127a0
Outer derivation does not invent an inner nominal conformance
Linkrecord Inner { value: int }record Outer derives Eq { item: Inner }
No stdout.
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5530structures-derive-eq-field atexample.tpz:2:22
Source evidence
- compiler/crates/topaz_self_frontend/topaz/checker/diagnostics_derives.tpz at
72a59a31125f· 42-204 - compiler/crates/topaz_self_frontend/topaz/checker/protocols_methods.tpz at
72a59a31125f· 529-541 - compiler/crates/topaz_value/src/value/compare.rs at
72a59a31125f· 85-180 - compiler/crates/topaz_value/src/value/compare.rs at
72a59a31125f· 198-353 - compiler/crates/topaz_value/src/value/json/schema.rs at
72a59a31125f· 212-226 - compiler/crates/topaz_value/src/value/json/codec.rs at
72a59a31125f· 1-47 - compiler/crates/topaz_value/src/value/guards.rs at
72a59a31125f· 88-96 - pull/44 at
72a59a31125f
Nested dispatch preserves the imported nominal identity
Linkimport std.io { writeLine }import tokens { Token as Ink }record Box derives Eq { value: Ink }let a = Box { value: Ink { key: 4, label: "first" } }let b = Box { value: Ink { key: 4, label: "second" } }writeLine("{Eq.equals(a, b)}")
true
export record Token { key: int, label: string }impl Eq<Token> {function equals(a: Token, b: Token) -> bool { a.key == b.key }}
Expected result
Expected result
Execution completes. Exit code 0.
true
Source evidence
- compiler/crates/topaz_self_frontend/topaz/checker/diagnostics_derives.tpz at
72a59a31125f· 42-204 - compiler/crates/topaz_self_frontend/topaz/checker/protocols_methods.tpz at
72a59a31125f· 529-541 - compiler/crates/topaz_value/src/value/compare.rs at
72a59a31125f· 85-180 - compiler/crates/topaz_value/src/value/compare.rs at
72a59a31125f· 198-353 - compiler/crates/topaz_value/src/value/json/schema.rs at
72a59a31125f· 212-226 - compiler/crates/topaz_value/src/value/json/codec.rs at
72a59a31125f· 1-47 - compiler/crates/topaz_value/src/value/guards.rs at
72a59a31125f· 88-96 - pull/44 at
72a59a31125f
Map and Set equality follows membership while Map values use Eq
Linkimport std.io { writeLine }record Token { key: int, label: string }impl Eq<Token> {function equals(a: Token, b: Token) -> bool { a.key == b.key }}record Index derives Eq { values: Map<string, Token>, keys: Set<int> }let a = Index {values: map { "a": Token { key: 1, label: "first" }, "b": Token { key: 2, label: "second" } },keys: set { 1, 2 },}let b = Index {values: map { "b": Token { key: 2, label: "changed" }, "a": Token { key: 1, label: "changed" } },keys: set { 2, 1 },}let c = Index {values: map { "a": Token { key: 9, label: "first" }, "b": Token { key: 2, label: "second" } },keys: set { 1, 2 },}writeLine("{Eq.equals(a, b)}")writeLine("{a == b}")writeLine("{Eq.equals(a, c)}")
true true false
Expected result
Expected result
Execution completes. Exit code 0.
truetruefalse
Source evidence
- compiler/crates/topaz_self_frontend/topaz/checker/diagnostics_derives.tpz at
72a59a31125f· 137-204 - compiler/crates/topaz_value/src/value/compare.rs at
72a59a31125f· 26-75 - compiler/crates/topaz_value/src/value/compare.rs at
72a59a31125f· 198-353 - compiler/crates/topaz_value/src/value/json/codec.rs at
72a59a31125f· 10-222 - compiler/crates/topaz_value/src/value/json/schema.rs at
72a59a31125f· 140-212 - pull/44 at
72a59a31125f
Boolean, singleton, sequence, sum, and structural record orders compose
Linkimport std.io { writeLine }record Flag derives Order { value: bool }record UnitBox derives Order { value: () }record NullBox derives Order { value: null }record Sequence derives Order { value: Array<int> }record Optional derives Order { value: Option<int> }record Outcome derives Order { value: Result<int, int> }record Fields derives Order { value: { z: int, a: int } }writeLine("{Order.compare(Flag { value: false }, Flag { value: true })}")writeLine("{Order.compare(UnitBox { value: () }, UnitBox { value: () })}")writeLine("{Order.compare(NullBox { value: null }, NullBox { value: null })}")writeLine("{Order.compare(Sequence { value: [1, 2] }, Sequence { value: [1, 3] })}")writeLine("{Order.compare(Sequence { value: [1] }, Sequence { value: [1, 0] })}")writeLine("{Order.compare(Optional { value: None() }, Optional { value: Some(0) })}")writeLine("{Order.compare(Outcome { value: Err(7) }, Outcome { value: Ok(0) })}")writeLine("{Order.compare(Fields { value: { z: 0, a: 2 } }, Fields { value: { z: 100, a: 1 } })}")
-1 0 0 -1 -1 -1 -1 1
Expected result
Expected result
Execution completes. Exit code 0.
-100-1-1-1-11
Source evidence
- compiler/crates/topaz_self_frontend/topaz/checker/diagnostics_derives.tpz at
72a59a31125f· 137-204 - compiler/crates/topaz_value/src/value/compare.rs at
72a59a31125f· 26-75 - compiler/crates/topaz_value/src/value/compare.rs at
72a59a31125f· 198-353 - compiler/crates/topaz_value/src/value/json/codec.rs at
72a59a31125f· 10-222 - compiler/crates/topaz_value/src/value/json/schema.rs at
72a59a31125f· 140-212 - pull/44 at
72a59a31125f
Reordering record fields or enum declarations does not change derived order
Linkimport std.io { writeLine }record First derives Order { z: int, a: int }record Second derives Order { a: int, z: int }enum Forward derives Order { Zed, Alpha }enum Reordered derives Order { Alpha, Zed }writeLine("{Order.compare(First { z: 0, a: 2 }, First { z: 100, a: 1 })}")writeLine("{Order.compare(Second { z: 0, a: 2 }, Second { z: 100, a: 1 })}")writeLine("{Order.compare(Forward.Alpha(), Forward.Zed())}")writeLine("{Order.compare(Reordered.Alpha(), Reordered.Zed())}")
1 1 -1 -1
Expected result
Expected result
Execution completes. Exit code 0.
11-1-1
Source evidence
- compiler/crates/topaz_self_frontend/topaz/checker/diagnostics_derives.tpz at
72a59a31125f· 137-204 - compiler/crates/topaz_value/src/value/compare.rs at
72a59a31125f· 26-75 - compiler/crates/topaz_value/src/value/compare.rs at
72a59a31125f· 198-353 - compiler/crates/topaz_value/src/value/json/codec.rs at
72a59a31125f· 10-222 - compiler/crates/topaz_value/src/value/json/schema.rs at
72a59a31125f· 140-212 - pull/44 at
72a59a31125f
A union within one ordered scalar domain keeps that order
Linkimport std.io { writeLine }record Choice derives Order { value: 1 | 2 }writeLine("{Order.compare(Choice { value: 1 }, Choice { value: 2 })}")
-1
Expected result
Expected result
Execution completes. Exit code 0.
-1
Source evidence
- compiler/crates/topaz_self_frontend/topaz/checker/diagnostics_derives.tpz at
72a59a31125f· 137-204 - compiler/crates/topaz_value/src/value/compare.rs at
72a59a31125f· 26-75 - compiler/crates/topaz_value/src/value/compare.rs at
72a59a31125f· 198-353 - compiler/crates/topaz_value/src/value/json/codec.rs at
72a59a31125f· 10-222 - compiler/crates/topaz_value/src/value/json/schema.rs at
72a59a31125f· 140-212 - pull/44 at
72a59a31125f
Map membership has no implicit total ordering
Linkrecord Lookup derives Order { values: Map<string, int> }
No stdout.
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5530structures-derive-order-field atexample.tpz:1:23
Source evidence
- compiler/crates/topaz_self_frontend/topaz/checker/diagnostics_derives.tpz at
72a59a31125f· 137-204 - compiler/crates/topaz_value/src/value/compare.rs at
72a59a31125f· 26-75 - compiler/crates/topaz_value/src/value/compare.rs at
72a59a31125f· 198-353 - compiler/crates/topaz_value/src/value/json/codec.rs at
72a59a31125f· 10-222 - compiler/crates/topaz_value/src/value/json/schema.rs at
72a59a31125f· 140-212 - pull/44 at
72a59a31125f
Set membership has no implicit total ordering
Linkrecord Membership derives Order { values: Set<int> }
No stdout.
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5530structures-derive-order-field atexample.tpz:1:27
Source evidence
- compiler/crates/topaz_self_frontend/topaz/checker/diagnostics_derives.tpz at
72a59a31125f· 137-204 - compiler/crates/topaz_value/src/value/compare.rs at
72a59a31125f· 26-75 - compiler/crates/topaz_value/src/value/compare.rs at
72a59a31125f· 198-353 - compiler/crates/topaz_value/src/value/json/codec.rs at
72a59a31125f· 10-222 - compiler/crates/topaz_value/src/value/json/schema.rs at
72a59a31125f· 140-212 - pull/44 at
72a59a31125f
Different ordered scalar domains do not acquire a cross-type order
Linkrecord Mixed derives Order { value: int | string }
No stdout.
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5530structures-derive-order-field atexample.tpz:1:22
Source evidence
- compiler/crates/topaz_self_frontend/topaz/checker/diagnostics_derives.tpz at
72a59a31125f· 137-204 - compiler/crates/topaz_value/src/value/compare.rs at
72a59a31125f· 26-75 - compiler/crates/topaz_value/src/value/compare.rs at
72a59a31125f· 198-353 - compiler/crates/topaz_value/src/value/json/codec.rs at
72a59a31125f· 10-222 - compiler/crates/topaz_value/src/value/json/schema.rs at
72a59a31125f· 140-212 - pull/44 at
72a59a31125f
JSON object keys require strings without an implicit key conversion
Linkrecord NumericKeys derives JSON { values: Map<int, string> }
No stdout.
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5530structures-derive-json-field atexample.tpz:1:28
Source evidence
- compiler/crates/topaz_self_frontend/topaz/checker/diagnostics_derives.tpz at
72a59a31125f· 137-204 - compiler/crates/topaz_value/src/value/compare.rs at
72a59a31125f· 26-75 - compiler/crates/topaz_value/src/value/compare.rs at
72a59a31125f· 198-353 - compiler/crates/topaz_value/src/value/json/codec.rs at
72a59a31125f· 10-222 - compiler/crates/topaz_value/src/value/json/schema.rs at
72a59a31125f· 140-212 - pull/44 at
72a59a31125f
A Set needs an explicitly chosen JSON representation
Linkrecord Membership derives JSON { values: Set<int> }
No stdout.
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5530structures-derive-json-field atexample.tpz:1:27
Source evidence
- compiler/crates/topaz_self_frontend/topaz/checker/diagnostics_derives.tpz at
72a59a31125f· 137-204 - compiler/crates/topaz_value/src/value/compare.rs at
72a59a31125f· 26-75 - compiler/crates/topaz_value/src/value/compare.rs at
72a59a31125f· 198-353 - compiler/crates/topaz_value/src/value/json/codec.rs at
72a59a31125f· 10-222 - compiler/crates/topaz_value/src/value/json/schema.rs at
72a59a31125f· 140-212 - pull/44 at
72a59a31125f
Eq uses IEEE float equality and does not claim NaN reflexivity
Linkimport std.io { writeLine }record Reading derives Eq { value: float }let sample = Reading { value: 0.0 / 0.0 }writeLine("{Eq.equals(sample, sample)}")writeLine("{sample == sample}")
false false
Expected result
Expected result
Execution completes. Exit code 0.
falsefalse
Source evidence
- compiler/crates/topaz_self_frontend/topaz/checker/diagnostics_derives.tpz at
72a59a31125f· 137-204 - compiler/crates/topaz_value/src/value/compare.rs at
72a59a31125f· 26-75 - compiler/crates/topaz_value/src/value/compare.rs at
72a59a31125f· 198-353 - compiler/crates/topaz_value/src/value/json/codec.rs at
72a59a31125f· 10-222 - compiler/crates/topaz_value/src/value/json/schema.rs at
72a59a31125f· 140-212 - pull/44 at
72a59a31125f
Finite floats have one JSON number conversion and preserve negative zero
Linkimport std.io { writeLine }import std.json { parseAs, stringify }record Reading derives JSON { value: float }writeLine(match (stringify(Reading { value: 1.5 })) {case Ok(text) => textcase Err(message) => message})writeLine(match (stringify(-0.0)) {case Ok(text) => textcase Err(message) => message})writeLine(match (stringify(1000.0)) {case Ok(text) => textcase Err(message) => message})writeLine(match (parseAs<float>("-0")) {case Ok(value) => "{(1.0 / value) < 0.0}"case Err(message) => message})writeLine(match (parseAs<float>("1")) {case Ok(value) => "{value == 1.0}"case Err(message) => message})writeLine(match (stringify(0.0 / 0.0)) {case Ok(_) => "unexpected value"case Err(_) => "nonfinite rejected"})writeLine(match (parseAs<float>("1e9999")) {case Ok(_) => "unexpected value"case Err(_) => "overflow rejected"})
{"value":1.5}
-0
1000
true
true
nonfinite rejected
overflow rejected
Expected result
Expected result
Execution completes. Exit code 0.
{"value":1.5}-01000truetruenonfinite rejectedoverflow rejected
Source evidence
- compiler/crates/topaz_self_frontend/topaz/checker/diagnostics_derives.tpz at
72a59a31125f· 137-204 - compiler/crates/topaz_value/src/value/compare.rs at
72a59a31125f· 26-75 - compiler/crates/topaz_value/src/value/compare.rs at
72a59a31125f· 198-353 - compiler/crates/topaz_value/src/value/json/codec.rs at
72a59a31125f· 10-222 - compiler/crates/topaz_value/src/value/json/schema.rs at
72a59a31125f· 140-212 - pull/44 at
72a59a31125f
Option JSON conversion explicitly normalizes null payloads
Linkimport std.io { writeLine }import std.json { parseAs, stringify }let original: Option<Option<int>> = Some(None())writeLine(match (stringify(original)) {case Ok(text) => textcase Err(message) => message})writeLine(match (parseAs<Option<Option<int>>>("null")) {case Ok(value) => match (value) {case None() => "none"case Some(_) => "some"}case Err(message) => message})
null none
Expected result
Expected result
Execution completes. Exit code 0.
nullnone
Source evidence
- compiler/crates/topaz_self_frontend/topaz/checker/diagnostics_derives.tpz at
72a59a31125f· 137-204 - compiler/crates/topaz_value/src/value/compare.rs at
72a59a31125f· 26-75 - compiler/crates/topaz_value/src/value/compare.rs at
72a59a31125f· 198-353 - compiler/crates/topaz_value/src/value/json/codec.rs at
72a59a31125f· 10-222 - compiler/crates/topaz_value/src/value/json/schema.rs at
72a59a31125f· 140-212 - pull/44 at
72a59a31125f
Result data uses the same tagged representation as enum data
Linkimport std.io { writeLine }import std.json { parseAs, stringify }record Outcome derives JSON { value: Result<int, string> }let value = Outcome { value: Err("paper") }let text = match (stringify(value)) {case Ok(encoded) => encodedcase Err(message) => message}writeLine(text)writeLine(match (parseAs<Outcome>(text)) {case Ok(decoded) => match (decoded.value) {case Ok(number) => "{number}"case Err(message) => message}case Err(message) => message})
{"value":{"tag":"Err","values":["paper"]}}
paper
Expected result
Expected result
Execution completes. Exit code 0.
{"value":{"tag":"Err","values":["paper"]}}paper
Source evidence
- compiler/crates/topaz_self_frontend/topaz/checker/diagnostics_derives.tpz at
72a59a31125f· 137-204 - compiler/crates/topaz_value/src/value/compare.rs at
72a59a31125f· 26-75 - compiler/crates/topaz_value/src/value/compare.rs at
72a59a31125f· 198-353 - compiler/crates/topaz_value/src/value/json/codec.rs at
72a59a31125f· 10-222 - compiler/crates/topaz_value/src/value/json/schema.rs at
72a59a31125f· 140-212 - pull/44 at
72a59a31125f
Receiver methods and self
LinkReceiver self parameter
A receiver method starts with bare self, with no annotation, default, or variadic marker. Its type is the implementation target, and a missing or differently written receiver produces TPZ5022.
Why
The implementation target already determines the receiver type. A bare self avoids repeating that type in every method and gives receiver binding one form.
Source evidence
- Receiver methods and bare self at
af88afbc1d99· 730-1004
Unique receiver method names
Repeating a receiver method name for the same type produces TPZ5008.
Why
Member lookup needs one method for a given type and name. Duplicate definitions would make a call depend on declaration order.
Source evidence
- Receiver methods and bare self at
af88afbc1d99· 730-1004
Receiver member collisions
A receiver method name that collides with a record field or reserved built-in member produces TPZ5022.
Why
Fields, built-in members, and receiver methods share member access syntax. Distinct names prevent the same access from denoting different operations.
Source evidence
- Receiver methods and bare self at
af88afbc1d99· 730-1004
Exporting receiver methods
export may precede an individual receiver method declaration.
Why
Exporting individual methods lets a type expose useful operations while keeping helpers private. It uses the same declaration prefix as other module exports.
Source evidence
- Protocol, implementation, and using parsers at
af88afbc1d99· 439-601 - Receiver methods and bare self at
af88afbc1d99· 730-1004
Using a resource
LinkScope cleanup runs on normal completion and structured exits. The File resource contract defines repeated close and host close failures. Ordinary runtime faults follow the fault cleanup rule.
File resource bindings
using name = (expression) { . checks the resource expression against File and gives its block binding type File.
Why
The cleanup path invokes the File close operation. Requiring a File ensures that this operation is defined for the acquired resource.
Source evidence
- Protocol, implementation, and using parsers at
af88afbc1d99· 439-601 - Using input and binding checks at
af88afbc1d99· 537-579 - File type spelling at
af88afbc1d99· 83-99
Immutable resource bindings
A using resource binding is immutable.
Why
Cleanup is registered for the acquired resource. Preventing reassignment keeps the visible binding tied to the resource that will be closed.
Source evidence
- Using input and binding checks at
af88afbc1d99· 537-579
Using acquisition and cleanup order
A using initializer is evaluated once. After successful acquisition, its automatic close is registered before the body. An initializer that propagates an Err registers no close for that resource. Body defers run before its close under the scope cleanup rule, including ? propagation. Capturing the File does not keep it open beyond the using scope. Ordinary runtime faults follow the fault cleanup rule.
Ordinary runtime faults follow the fault cleanup rule.
Why
Body defers may still need the resource. Running them before close and leaving nested scopes from inside to outside keeps those dependencies alive.
Source evidence
- emitUsing at
570530e69141· 1945-1959 - emitBlock at
570530e69141· 1360-1400 - drain / emitControl / emitTry / emitReturn at
570530e69141· 1749-1759;1793-1813;1927-1934;1983-1991 - Host.close at
570530e69141· 438-449 - File.close at
570530e69141· 1659-1665 - NativeHost read / write / close at
570530e69141· 214-242 - run_defers at
570530e69141· 38-60 - using exit and unspecified host behavior at
151141d7bb44· 59-95 - checkUsingStatement at
570530e69141· 537-579 - issues/31 at
570530e69141 - issues/31 at
1cbf38d3d422
Explicit close and failure boundaries
using keeps automatic File. registered after an explicit close.
File. follows the File resource contract. That contract defines repeated close, shared closed state and close faults.
The scope cleanup rule defines cleanup order, structured exits and retained body values. Ordinary runtime faults follow the fault cleanup rule.
Deferred runtime and host errors follow the run exit contract and test result contract. These contracts determine process failure and test output independently of the stored body value.
At the cited native-host source revision, close sets the shared closed flag. Repeating that assignment preserves the closed state. Later reads and writes report a closed file. The File resource contract defines the required behavior across hosts.
Why
Cleanup registration belongs to the scope, so an explicit close does not cancel it. The File resource contract owns the result of closing. Command contracts own exit status and test output.
Source evidence
- emitUsing at
570530e69141· 1945-1959 - emitBlock at
570530e69141· 1360-1400 - drain / emitControl / emitTry / emitReturn at
570530e69141· 1749-1759;1793-1813;1927-1934;1983-1991 - Host.close at
570530e69141· 438-449 - File.close at
570530e69141· 1659-1665 - NativeHost read / write / close at
570530e69141· 214-242 - run_defers at
570530e69141· 38-60 - using exit and unspecified host behavior at
151141d7bb44· 59-95 - checkUsingStatement at
570530e69141· 537-579 - issues/31 at
570530e69141 - int_div at
570530e69141· 39-50 - issues/31 at
a545e8811f8f - issues/31 at
a545e8811f8f - issues/31 at
1cbf38d3d422
Complete examples
LinkImplement and call a protocol
Linkprotocol Labelled {function label(value: Self) -> string}record Item { name: string }impl Labelled<Item> {function label(value: Item) -> string { value.name }}let text: string = Labelled.label(Item { name: "Moss" })
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
- Protocol declaration and signature checking at
af88afbc1d99· 993-1270 - Manual implementation conformance at
af88afbc1d99· 297-660 - Protocol-qualified calls at
af88afbc1d99· 133-178 - Protocol call conformance and diagnostics at
af88afbc1d99· 965-1054
Reject an explicit protocol receiver binder
Linkprotocol Labelled<T> {function label(value: T) -> string}record Item { name: string }impl Labelled<Item> {function label(value: Item) -> string { value.name }}let text: string = Labelled.label(Item { name: "Moss" })
No stdout.
Expected result
Expected result
Rejection during parse. Nonzero exit.
TPZ2001protocol-explicit-receiver-binder atexample.tpz:1:18
Source evidence
- Protocol declaration and signature checking at
af88afbc1d99· 993-1270 - Manual implementation conformance at
af88afbc1d99· 297-660 - pull/38 at
9b1bbdeceba9 - parseProtocol reads the protocol name and optional parameters before expectLeftBrace at
9b1bbdeceba9· 553-561 - fail and expectLeftBrace emit TPZ2001 with expected brace at the current token at
9b1bbdeceba9· 188-208
Use Self for the conforming type
Linkprotocol Labelled {function label(value: Self) -> string}record Item { name: string }impl Labelled<Item> {function label(value: Item) -> string { value.name }}let text: string = Labelled.label(Item { name: "Moss" })
No stdout.
Expected result
Expected result
Checking accepts the program. Exit code 0.
Source evidence
- Protocol declaration and signature checking at
af88afbc1d99· 993-1270 - Manual implementation conformance at
af88afbc1d99· 297-660 - pull/38 at
9b1bbdeceba9
Declare a receiver method
Linkrecord Item { name: string }impl Item {function label(self) -> string { self.name }}let item = Item { name: "Moss" }let text: string = item.label()
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
- Receiver methods and bare self at
af88afbc1d99· 730-1004
Derive built-in protocols
Linkrecord Pair derives Eq, Order, Show, JSON {left: int,right: int,}let pair = Pair { left: 1, right: 2 }let same: bool = Eq.equals(pair, pair)
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
- Derive checking at
af88afbc1d99· 249-374 - Field and payload derivability at
af88afbc1d99· 42-204 - Built-in protocol signatures at
af88afbc1d99· 100-144
Check a File resource binding
Linkfunction manage(file: File) -> () {using resource = (file) {let handle: File = resource}}
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
- Using input and binding checks at
af88afbc1d99· 537-579 - File type spelling at
af88afbc1d99· 83-99
Reject an incomplete implementation
Linkprotocol Labelled {function label(value: Self) -> string}record Item { name: string }impl Labelled<Item> {}
No stdout.
error[TPZ5022]: `impl Labelled<Item>` is missing method `label`
--> example.tpz:7:6
|
7 | impl Labelled<Item> {}
| ^^^^^^^^
example.tpz: 1 type diagnostic
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5022missing-protocol-method atexample.tpz:7:6
Observed assertions · passed · 5.21.3
Exit code 1.
No stdout.
error[TPZ5022]: `impl Labelled<Item>` is missing method `label`--> example.tpz:7:6|7 | impl Labelled<Item> {}| ^^^^^^^^example.tpz: 1 type diagnostic
- Passed · expected nonzero; observed 1
- Passed · stdout matches the authored expectation
- Passed · checking TPZ5022; cause missing-protocol-method; primary location asserted; additional diagnostics allow
Source evidence
- Manual implementation conformance at
af88afbc1d99· 297-660
A foreign protocol on a foreign type violates ownership
Linkimpl Show<int> {function show(value: int) -> string { "number" }}
No stdout.
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5520protocol-orphan-implementation atexample.tpz:1:11
The preserved receipt belongs to an earlier revision that rejected a local protocol on int. This revision checks the orphan case where both Show and int belong to another module. The 6.0 contract permits the local-protocol case shown separately.
Source evidence
- Manual implementation conformance at
af88afbc1d99· 297-660 - Implementation targets at
af88afbc1d99· 146-212 - pull/44 at
72a59a31125f - checkProtocolImplMethodAgainstDefinition at
72a59a31125f· 338-456 - collectProtocolImplForDefinition and collectProtocolImplForTarget at
72a59a31125f· 529-650 - prepareFunctionDeclaration at
72a59a31125f· 1282-1355 - issues/31 at
9b1bbdeceba9
Reject a repeated conformance
Linkprotocol Labelled {function label(value: Self) -> string}record Item { name: string }impl Labelled<Item> {function label(value: Item) -> string { value.name }}impl Labelled<Item> {function label(value: Item) -> string { value.name }}
No stdout.
error[TPZ5521]: `Item` already conforms to `Labelled` by a previous `impl`; a conformance must be unique
--> example.tpz:11:15
|
11 | impl Labelled<Item> {
| ^^^^
example.tpz: 1 type diagnostic
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5521duplicate-protocol-conformance atexample.tpz:11:15
Observed assertions · passed · 5.21.3
Exit code 1.
No stdout.
error[TPZ5521]: `Item` already conforms to `Labelled` by a previous `impl`; a conformance must be unique--> example.tpz:11:15|11 | impl Labelled<Item> {| ^^^^example.tpz: 1 type diagnostic
- Passed · expected nonzero; observed 1
- Passed · stdout matches the authored expectation
- Passed · checking TPZ5521; cause duplicate-protocol-conformance; primary location asserted; additional diagnostics allow
Source evidence
- Manual implementation conformance at
af88afbc1d99· 297-660
Reject a call without conformance
Linkprotocol Labelled {function label(value: Self) -> string}record Item { name: string }let text: string = Labelled.label(Item { name: "Moss" })
No stdout.
error[TPZ5522]: `Item` does not conform to `Labelled`
--> example.tpz:7:35
|
7 | let text: string = Labelled.label(Item { name: "Moss" })
| ^^^^^^^^^^^^^^^^^^^^^
example.tpz: 1 type diagnostic
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5522protocol-call-argument-does-not-conform atexample.tpz:7:35
Observed assertions · passed · 5.21.3
Exit code 1.
No stdout.
error[TPZ5522]: `Item` does not conform to `Labelled`--> example.tpz:7:35|7 | let text: string = Labelled.label(Item { name: "Moss" })| ^^^^^^^^^^^^^^^^^^^^^example.tpz: 1 type diagnostic
- Passed · expected nonzero; observed 1
- Passed · stdout matches the authored expectation
- Passed · checking TPZ5522; cause protocol-call-argument-does-not-conform; primary location asserted; additional diagnostics allow
Source evidence
- Protocol call conformance and diagnostics at
af88afbc1d99· 965-1054
Reject a user-defined protocol in derives
Linkprotocol Labelled {function label(value: Self) -> string}record Item derives Labelled { name: string }
No stdout.
error[TPZ5530]: unknown protocol `Labelled` in `derives` for `Item`; derivable protocols are `Eq`, `Order`, `Show`, `JSON`
--> example.tpz:5:21
|
5 | record Item derives Labelled { name: string }
| ^^^^^^^^
example.tpz: 1 type diagnostic
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5530protocol-is-not-derivable atexample.tpz:5:21
Observed assertions · passed · 5.21.3
Exit code 1.
No stdout.
error[TPZ5530]: unknown protocol `Labelled` in `derives` for `Item`; derivable protocols are `Eq`, `Order`, `Show`, `JSON`--> example.tpz:5:21|5 | record Item derives Labelled { name: string }| ^^^^^^^^example.tpz: 1 type diagnostic
- Passed · expected nonzero; observed 1
- Passed · stdout matches the authored expectation
- Passed · checking TPZ5530; cause protocol-is-not-derivable; primary location asserted; additional diagnostics allow
Source evidence
- Derive checking at
af88afbc1d99· 249-374
Acquire once, run body defers, then close
Linkimport std.iolet mut acquisitions = 0let mut later: () -> string = () => "unset"function acquire() -> Result<File, string> {acquisitions += 1io.open("input.txt")}function scenario() -> Result<int, string> {using file = (acquire()?) {later = () => match (file.read()) {case Ok(text) => textcase Err(e) => e}defer { io.writeLine(match (file.read()) {case Ok(text) => "defer:{text}"case Err(e) => "defer:{e}"}) }io.writeLine("body")99}Ok(7)}let ignored = scenario()io.writeLine("acquisitions:{acquisitions}")io.writeLine(later())
body defer:payload acquisitions:1 file is closed
payload
Expected result
Expected result
Execution completes. Exit code 0.
bodydefer:payloadacquisitions:1file is closed
Source evidence
- emitUsing at
570530e69141· 1945-1959 - emitBlock at
570530e69141· 1360-1400 - drain / emitControl / emitTry / emitReturn at
570530e69141· 1749-1759;1793-1813;1927-1934;1983-1991 - Host.close at
570530e69141· 438-449 - File.close at
570530e69141· 1659-1665 - NativeHost read / write / close at
570530e69141· 214-242 - run_defers at
570530e69141· 38-60 - using exit and unspecified host behavior at
151141d7bb44· 59-95 - checkUsingStatement at
570530e69141· 537-579 - issues/31 at
570530e69141
Close on return, propagation, break, and continue
Linkimport std.iolet mut later: () -> string = () => "unset"function fail() -> Result<int, string> { Err("stop") }function onReturn() -> Result<int, string> {using file = (io.open("input.txt")?) {later = () => match (file.read()) {case Ok(text) => textcase Err(e) => e}return Ok(7)}Ok(0)}let ignoredonReturn = onReturn()io.writeLine("onReturn:{later()}")function onQuestion() -> Result<int, string> {using file = (io.open("input.txt")?) {later = () => match (file.read()) {case Ok(text) => textcase Err(e) => e}fail()?}Ok(0)}let ignoredonQuestion = onQuestion()io.writeLine("onQuestion:{later()}")function onBreak() -> Result<int, string> {let mut i = 0while (i < 1) {using file = (io.open("input.txt")?) {later = () => match (file.read()) {case Ok(text) => textcase Err(e) => e}break}}Ok(0)}let ignoredonBreak = onBreak()io.writeLine("onBreak:{later()}")function onContinue() -> Result<int, string> {let mut i = 0while (i < 1) {using file = (io.open("input.txt")?) {later = () => match (file.read()) {case Ok(text) => textcase Err(e) => e}i += 1continue}}Ok(0)}let ignoredonContinue = onContinue()io.writeLine("onContinue:{later()}")
onReturn:file is closed onQuestion:file is closed onBreak:file is closed onContinue:file is closed
payload
Expected result
Expected result
Execution completes. Exit code 0.
onReturn:file is closedonQuestion:file is closedonBreak:file is closedonContinue:file is closed
Source evidence
- emitUsing at
570530e69141· 1945-1959 - emitBlock at
570530e69141· 1360-1400 - drain / emitControl / emitTry / emitReturn at
570530e69141· 1749-1759;1793-1813;1927-1934;1983-1991 - Host.close at
570530e69141· 438-449 - File.close at
570530e69141· 1659-1665 - NativeHost read / write / close at
570530e69141· 214-242 - run_defers at
570530e69141· 38-60 - using exit and unspecified host behavior at
151141d7bb44· 59-95 - checkUsingStatement at
570530e69141· 537-579 - issues/31 at
570530e69141
Explicit and automatic close on the installed native host
Linkimport std.iolet mut later: () -> string = () => "unset"function scenario() -> Result<int, string> {using file = (io.open("input.txt")?) {later = () => match (file.read()) {case Ok(text) => textcase Err(e) => e}file.close()file.close()io.writeLine(later())}Ok(0)}let ignored = scenario()io.writeLine(later())
file is closed file is closed
payload
Expected result
Expected result
Execution completes. Exit code 0.
file is closedfile is closed
This example tests repeated close on the installed native host. The File resource contract requires the same close result across hosts.
Source evidence
- emitUsing at
570530e69141· 1945-1959 - emitBlock at
570530e69141· 1360-1400 - drain / emitControl / emitTry / emitReturn at
570530e69141· 1749-1759;1793-1813;1927-1934;1983-1991 - Host.close at
570530e69141· 438-449 - File.close at
570530e69141· 1659-1665 - NativeHost read / write / close at
570530e69141· 214-242 - run_defers at
570530e69141· 38-60 - using exit and unspecified host behavior at
151141d7bb44· 59-95 - checkUsingStatement at
570530e69141· 537-579 - issues/31 at
570530e69141
Reject a non-File resource initializer
Linkusing resource = (1) {}
No stdout.
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5001structures-using-requires-file atexample.tpz:1:19
Source evidence
- emitUsing at
570530e69141· 1945-1959 - emitBlock at
570530e69141· 1360-1400 - drain / emitControl / emitTry / emitReturn at
570530e69141· 1749-1759;1793-1813;1927-1934;1983-1991 - Host.close at
570530e69141· 438-449 - File.close at
570530e69141· 1659-1665 - NativeHost read / write / close at
570530e69141· 214-242 - run_defers at
570530e69141· 38-60 - using exit and unspecified host behavior at
151141d7bb44· 59-95 - checkUsingStatement at
570530e69141· 537-579 - issues/31 at
570530e69141
Use a File resource initializer
Linkfunction manage(file: File) -> () {using resource = (file) {}}
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
- emitUsing at
570530e69141· 1945-1959 - emitBlock at
570530e69141· 1360-1400 - drain / emitControl / emitTry / emitReturn at
570530e69141· 1749-1759;1793-1813;1927-1934;1983-1991 - Host.close at
570530e69141· 438-449 - File.close at
570530e69141· 1659-1665 - NativeHost read / write / close at
570530e69141· 214-242 - run_defers at
570530e69141· 38-60 - using exit and unspecified host behavior at
151141d7bb44· 59-95 - checkUsingStatement at
570530e69141· 537-579 - issues/31 at
570530e69141
Check nested concrete members and recursive Eq or Order
Linkrecord Leaf derives Eq, Order, JSON { value: int }record Box derives Eq, Order, JSON { value: Leaf }record Nested derives Eq, Order, Show, JSON { value: Box }enum Tree derives Eq, Show { Leaf(int), Branch(Array<Tree>) }enum Chain derives Eq, Order { End, Next(int, Chain) }record Callable derives Show { run: () -> int }record Document derives Eq, JSON { items: Array<Option<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
- deriveTypeCapability at
151141d7bb44· 42-204 - firstRecordDeriveFailure / firstEnumDeriveFailure at
151141d7bb44· 207-246 - recordDerivedConformance at
151141d7bb44· 249-283 - checkDerives at
151141d7bb44· 286-374 - jsonCapability at
151141d7bb44· 408-518
Map value equality satisfies Eq derivation
Linkrecord Index derives Eq { values: Map<string, int> }
No stdout.
Expected result
Expected result
Checking accepts the program. Exit code 0.
Source evidence
- deriveTypeCapability at
151141d7bb44· 42-204 - firstRecordDeriveFailure / firstEnumDeriveFailure at
151141d7bb44· 207-246 - recordDerivedConformance at
151141d7bb44· 249-283 - checkDerives at
151141d7bb44· 286-374 - jsonCapability at
151141d7bb44· 408-518
Use comparable array elements
Linkrecord Comparable derives Eq { values: Array<int> }
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
- deriveTypeCapability at
151141d7bb44· 42-204 - firstRecordDeriveFailure / firstEnumDeriveFailure at
151141d7bb44· 207-246 - recordDerivedConformance at
151141d7bb44· 249-283 - checkDerives at
151141d7bb44· 286-374 - jsonCapability at
151141d7bb44· 408-518
Reject a float field when deriving total Order
Linkrecord NonTotal derives Order { value: float }
No stdout.
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5530structures-derive-order-field atexample.tpz:1:25
Source evidence
- deriveTypeCapability at
151141d7bb44· 42-204 - firstRecordDeriveFailure / firstEnumDeriveFailure at
151141d7bb44· 207-246 - recordDerivedConformance at
151141d7bb44· 249-283 - checkDerives at
151141d7bb44· 286-374 - jsonCapability at
151141d7bb44· 408-518
Use a totally orderable field
Linkrecord Total derives Order { value: int }
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
- deriveTypeCapability at
151141d7bb44· 42-204 - firstRecordDeriveFailure / firstEnumDeriveFailure at
151141d7bb44· 207-246 - recordDerivedConformance at
151141d7bb44· 249-283 - checkDerives at
151141d7bb44· 286-374 - jsonCapability at
151141d7bb44· 408-518
A recursive declaration has a reusable JSON conformance
Linkenum Recursive derives JSON { End, Next(Recursive) }
No stdout.
Expected result
Expected result
Checking accepts the program. Exit code 0.
Source evidence
- deriveTypeCapability at
151141d7bb44· 42-204 - firstRecordDeriveFailure / firstEnumDeriveFailure at
151141d7bb44· 207-246 - recordDerivedConformance at
151141d7bb44· 249-283 - checkDerives at
151141d7bb44· 286-374 - jsonCapability at
151141d7bb44· 408-518
Use finite JSON payloads
Linkenum Finite derives JSON { End, Value(int) }
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
- deriveTypeCapability at
151141d7bb44· 42-204 - firstRecordDeriveFailure / firstEnumDeriveFailure at
151141d7bb44· 207-246 - recordDerivedConformance at
151141d7bb44· 249-283 - checkDerives at
151141d7bb44· 286-374 - jsonCapability at
151141d7bb44· 408-518
A nested manual Eq implementation satisfies outer derivation
Linkrecord Callback { run: () -> int }impl Eq<Callback> {function equals(a: Callback, b: Callback) -> bool { true }}record Outer derives Eq { item: Callback }
No stdout.
Expected result
Expected result
Checking accepts the program. Exit code 0.
Source evidence
- deriveTypeCapability at
151141d7bb44· 42-204 - firstRecordDeriveFailure / firstEnumDeriveFailure at
151141d7bb44· 207-246 - recordDerivedConformance at
151141d7bb44· 249-283 - checkDerives at
151141d7bb44· 286-374 - jsonCapability at
151141d7bb44· 408-518
Show does not require nested comparability
Linkrecord Callback { run: () -> int }impl Eq<Callback> {function equals(a: Callback, b: Callback) -> bool { true }}record Outer derives Show { item: Callback }
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
- deriveTypeCapability at
151141d7bb44· 42-204 - firstRecordDeriveFailure / firstEnumDeriveFailure at
151141d7bb44· 207-246 - recordDerivedConformance at
151141d7bb44· 249-283 - checkDerives at
151141d7bb44· 286-374 - jsonCapability at
151141d7bb44· 408-518
Reject duplicate manual and derived Eq
Linkrecord Item derives Eq { value: int }impl Eq<Item> {function equals(a: Item, b: Item) -> bool { a.value == b.value }}
No stdout.
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5521structures-duplicate-conformance atexample.tpz:1:21
Source evidence
- deriveTypeCapability at
151141d7bb44· 42-204 - firstRecordDeriveFailure / firstEnumDeriveFailure at
151141d7bb44· 207-246 - recordDerivedConformance at
151141d7bb44· 249-283 - checkDerives at
151141d7bb44· 286-374 - jsonCapability at
151141d7bb44· 408-518
Keep one conformance per protocol
Linkrecord Item derives Show { value: int }impl Eq<Item> {function equals(a: Item, b: Item) -> bool { a.value == b.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
- deriveTypeCapability at
151141d7bb44· 42-204 - firstRecordDeriveFailure / firstEnumDeriveFailure at
151141d7bb44· 207-246 - recordDerivedConformance at
151141d7bb44· 249-283 - checkDerives at
151141d7bb44· 286-374 - jsonCapability at
151141d7bb44· 408-518
Null supports Eq and JSON derivation
Linkrecord Nil derives Eq, JSON { value: null }
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
- widen preserves null literal at
151141d7bb44· 543-553 - deriveTypeCapability at
151141d7bb44· 42-204
An imported nominal alias keeps its derived conformance
Linkimport remote { Item as RemoteItem }record Item { n: int }let imported = RemoteItem { n: 1 }let same: bool = Eq.equals(imported, imported)
example.tpz: types-ok (2 modules) example.tpz: resolve-ok (2 modules)
export record Item derives Eq { n: int }
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
- collectProtocol at
151141d7bb44· 1178-1273 - userProtocolObject at
151141d7bb44· 1863-1890 - referenceTargetType / nominal definition lookup at
151141d7bb44· 1529-1669 - nominalDefinitionModule / typeConforms at
151141d7bb44· 2224-2285 - userProtocolCallConformance / gateUserProtocolCall at
151141d7bb44· 965-1054 - userProtocolCallTarget at
151141d7bb44· 133-178 - exportDeclarationHead at
151141d7bb44· 1237-1257 - namedStatementBinding / bindingForStatement at
151141d7bb44· 219-233;296-314
A local type does not inherit the imported namesake conformance
Linkimport remote { Item as RemoteItem }record Item { n: int }let localValue = Item { n: 1 }let same = Eq.equals(localValue, localValue)
No stdout.
export record Item derives Eq { n: int }
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5522structures-no-visible-eq-conformance atexample.tpz:4:22
Source evidence
- collectProtocol at
151141d7bb44· 1178-1273 - userProtocolObject at
151141d7bb44· 1863-1890 - referenceTargetType / nominal definition lookup at
151141d7bb44· 1529-1669 - nominalDefinitionModule / typeConforms at
151141d7bb44· 2224-2285 - userProtocolCallConformance / gateUserProtocolCall at
151141d7bb44· 965-1054 - userProtocolCallTarget at
151141d7bb44· 133-178 - exportDeclarationHead at
151141d7bb44· 1237-1257 - namedStatementBinding / bindingForStatement at
151141d7bb44· 219-233;296-314
Give the local type its own conformance
Linkimport remote { Item as RemoteItem }record Item derives Eq { n: int }let localValue = Item { n: 1 }let same = Eq.equals(localValue, localValue)
example.tpz: types-ok (2 modules) example.tpz: resolve-ok (2 modules)
export record Item derives Eq { n: int }
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
- collectProtocol at
151141d7bb44· 1178-1273 - userProtocolObject at
151141d7bb44· 1863-1890 - referenceTargetType / nominal definition lookup at
151141d7bb44· 1529-1669 - nominalDefinitionModule / typeConforms at
151141d7bb44· 2224-2285 - userProtocolCallConformance / gateUserProtocolCall at
151141d7bb44· 965-1054 - userProtocolCallTarget at
151141d7bb44· 133-178 - exportDeclarationHead at
151141d7bb44· 1237-1257 - namedStatementBinding / bindingForStatement at
151141d7bb44· 219-233;296-314
A manual conformance is visible through a nominal import
Linkimport remote { Item as RemoteItem }let imported = RemoteItem { n: 1 }let same = Eq.equals(imported, imported)
No stdout.
export record Item { n: int }impl Eq<Item> {function equals(a: Item, b: Item) -> bool { a.n == b.n }}
Expected result
Expected result
Checking accepts the program. Exit code 0.
Source evidence
- collectProtocol at
151141d7bb44· 1178-1273 - userProtocolObject at
151141d7bb44· 1863-1890 - referenceTargetType / nominal definition lookup at
151141d7bb44· 1529-1669 - nominalDefinitionModule / typeConforms at
151141d7bb44· 2224-2285 - userProtocolCallConformance / gateUserProtocolCall at
151141d7bb44· 965-1054 - userProtocolCallTarget at
151141d7bb44· 133-178 - exportDeclarationHead at
151141d7bb44· 1237-1257 - namedStatementBinding / bindingForStatement at
151141d7bb44· 219-233;296-314 - nominalDefinitionModule and typeConforms manual visibility gates at
9b1bbdeceba9· 2224-2285 - protocol method lookup uses the calling module at
9b1bbdeceba9· 1119-1125 - emitImplementation registers the defining module at
9b1bbdeceba9· 1191-1205
A derived conformance is visible through a nominal import
Linkimport remote { Item as RemoteItem }let imported = RemoteItem { n: 1 }let same = Eq.equals(imported, imported)
No stdout.
export record Item derives Eq { n: int }
Expected result
Expected result
Checking accepts the program. Exit code 0.
Source evidence
- collectProtocol at
151141d7bb44· 1178-1273 - userProtocolObject at
151141d7bb44· 1863-1890 - referenceTargetType / nominal definition lookup at
151141d7bb44· 1529-1669 - nominalDefinitionModule / typeConforms at
151141d7bb44· 2224-2285 - userProtocolCallConformance / gateUserProtocolCall at
151141d7bb44· 965-1054 - userProtocolCallTarget at
151141d7bb44· 133-178 - exportDeclarationHead at
151141d7bb44· 1237-1257 - namedStatementBinding / bindingForStatement at
151141d7bb44· 219-233;296-314 - nominalDefinitionModule and typeConforms manual visibility gates at
9b1bbdeceba9· 2224-2285 - protocol method lookup uses the calling module at
9b1bbdeceba9· 1119-1125 - emitImplementation registers the defining module at
9b1bbdeceba9· 1191-1205
A local value owns the member call despite a protocol namesake
Linkimport std.ioprotocol Labelled { function label(value: Self) -> string }record Item { name: string }impl Labelled<Item> {function label(value: Item) -> string { value.name }}function localLabel() -> string {let Labelled = { label: (value: Item) => "local" }Labelled.label(Item { name: "protocol" })}io.writeLine(localLabel())
local
Expected result
Expected result
Execution completes. Exit code 0.
local
Source evidence
- collectProtocol at
151141d7bb44· 1178-1273 - userProtocolObject at
151141d7bb44· 1863-1890 - referenceTargetType / nominal definition lookup at
151141d7bb44· 1529-1669 - nominalDefinitionModule / typeConforms at
151141d7bb44· 2224-2285 - userProtocolCallConformance / gateUserProtocolCall at
151141d7bb44· 965-1054 - userProtocolCallTarget at
151141d7bb44· 133-178 - exportDeclarationHead at
151141d7bb44· 1237-1257 - namedStatementBinding / bindingForStatement at
151141d7bb44· 219-233;296-314
Reject a user protocol with a built-in protocol name
Linkprotocol Eq { function custom(value: Self) -> int }
No stdout.
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5008structures-builtin-protocol-redeclaration atexample.tpz:1:10
Source evidence
- collectProtocol at
151141d7bb44· 1178-1273 - userProtocolObject at
151141d7bb44· 1863-1890 - referenceTargetType / nominal definition lookup at
151141d7bb44· 1529-1669 - nominalDefinitionModule / typeConforms at
151141d7bb44· 2224-2285 - userProtocolCallConformance / gateUserProtocolCall at
151141d7bb44· 965-1054 - userProtocolCallTarget at
151141d7bb44· 133-178 - exportDeclarationHead at
151141d7bb44· 1237-1257 - namedStatementBinding / bindingForStatement at
151141d7bb44· 219-233;296-314
Choose a distinct user protocol name
Linkprotocol Custom { function custom(value: Self) -> int }
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
- collectProtocol at
151141d7bb44· 1178-1273 - userProtocolObject at
151141d7bb44· 1863-1890 - referenceTargetType / nominal definition lookup at
151141d7bb44· 1529-1669 - nominalDefinitionModule / typeConforms at
151141d7bb44· 2224-2285 - userProtocolCallConformance / gateUserProtocolCall at
151141d7bb44· 965-1054 - userProtocolCallTarget at
151141d7bb44· 133-178 - exportDeclarationHead at
151141d7bb44· 1237-1257 - namedStatementBinding / bindingForStatement at
151141d7bb44· 219-233;296-314
Evaluate the return value, then close nested resources
Linkimport std.iolet mut outerClosed: () -> bool = () => falselet mut innerClosed: () -> bool = () => falsefunction isClosed(file: File) -> bool {match (file.read()) {case Ok(text) => falsecase Err(error) => true}}function makeResult() -> Result<int, string> {io.writeLine("return-value")Ok(7)}function scenario() -> Result<int, string> {using outer = (io.open("input.txt")?) {outerClosed = () => isClosed(outer)defer { io.writeLine("outer-defer:inner-closed={innerClosed()}") }using inner = (io.open("input.txt")?) {innerClosed = () => isClosed(inner)defer { io.writeLine("inner-defer") }return makeResult()}}Ok(0)}let outcome = scenario()io.writeLine(match (outcome) {case Ok(value) => "result:{value}"case Err(error) => "open:{error}"})io.writeLine("outer-closed:{outerClosed()}")io.writeLine("inner-closed:{innerClosed()}")
return-value inner-defer outer-defer:inner-closed=true result:7 outer-closed:true inner-closed:true
payload
Expected result
Expected result
Execution completes. Exit code 0.
return-valueinner-deferouter-defer:inner-closed=trueresult:7outer-closed:trueinner-closed:true
The native File read result makes closing observable. The outer body defer sees the inner resource closed before the outer resource closes.
Source evidence
- issues/31 at
570530e69141 - emitUsing at
570530e69141· 1945-1959 - emitBlock at
570530e69141· 1360-1400 - drain / emitControl / emitTry / emitReturn at
570530e69141· 1749-1759;1793-1813;1927-1934;1983-1991 - Host.close at
570530e69141· 438-449 - File.close at
570530e69141· 1659-1665 - NativeHost read / write / close at
570530e69141· 214-242 - run_defers at
570530e69141· 38-60 - checkUsingStatement at
570530e69141· 537-579
An ordinary fault skips remaining cleanup
Linkimport std.iofunction divide(divisor: int) -> int {42 / divisor}function scenario() -> Result<int, string> {using file = (io.open("input.txt")?) {defer { io.writeLine("cleanup-observed") }divide(0)}Ok(0)}let result = scenario()
No stdout.
payload
Expected result
The division fault stops execution. The remaining defer and using cleanup are skipped under the fault cleanup rule. Standard output contains no cleanup message.
Source evidence
- issues/31 at
570530e69141 - emitUsing at
570530e69141· 1945-1959 - drain / emitControl / emitTry / emitReturn at
570530e69141· 1749-1759;1793-1813;1927-1934;1983-1991 - int_div at
570530e69141· 39-50 - issues/31 at
1cbf38d3d422
An imported protocol call executes its manual method
Linkimport std.io { writeLine }import remote { Item as RemoteItem }let left = RemoteItem { n: 1 }let right = RemoteItem { n: 11 }writeLine("{Eq.equals(left, right)}")
true
export record Item { n: int }impl Eq<Item> {function equals(a: Item, b: Item) -> bool { a.n % 10 == b.n % 10 }}
Expected result
Expected result
Execution completes. Exit code 0.
true
Source evidence
- nominalDefinitionModule and typeConforms manual visibility gates at
9b1bbdeceba9· 2224-2285 - protocol method lookup uses the calling module at
9b1bbdeceba9· 1119-1125 - emitImplementation registers the defining module at
9b1bbdeceba9· 1191-1205
A protocol method has ordinary generic bounds
Linkprotocol Echo {function echo<T: Show>(value: Self, item: T) -> T}
No stdout.
Expected result
Expected result
Checking accepts the program. Exit code 0.
Source evidence
- pull/44 at
72a59a31125f - checkProtocolMethod at
72a59a31125f· 1023-1166 - prepareCall at
72a59a31125f· 1432-1513
Fixed defaults precede the variadic protocol tail
Linkprotocol Sum {function sum(value: Self, start: int = 0, ...items: int) -> int}
No stdout.
Expected result
Expected result
Checking accepts the program. Exit code 0.
Source evidence
- pull/44 at
72a59a31125f - checkProtocolMethod at
72a59a31125f· 1023-1166 - prepareCall at
72a59a31125f· 1432-1513
Every caller uses the protocol-owned default
Linkimport std.io { writeLine }const ending = "!"protocol Labelled {function label(value: Self, suffix: string = ending) -> string}record Item { name: string }impl Labelled<Item> {function label(value: Item, suffix: string) -> string { value.name + suffix }}let item = Item { name: "Moss" }writeLine(Labelled.label(item))writeLine(Labelled.label(item, "?"))
Moss! Moss?
Expected result
Expected result
Execution completes. Exit code 0.
Moss!Moss?
Source evidence
- pull/44 at
72a59a31125f - checkProtocolMethod at
72a59a31125f· 1023-1166 - prepareCall at
72a59a31125f· 1432-1513
A protocol constructs a value without a receiver argument
Linkimport std.io { writeLine }protocol Factory {function make(value: int) -> Selffunction zero() -> Self}record Item { value: int }impl Factory<Item> {function make(value: int) -> Item { Item { value: value } }function zero() -> Item { Item { value: 0 } }}let explicit = Factory.make<Item>(9)let inferred: Item = Factory.zero()writeLine("{explicit.value}")writeLine("{inferred.value}")
9 0
Expected result
Expected result
Execution completes. Exit code 0.
90
Source evidence
- pull/44 at
72a59a31125f - checkProtocolMethod at
72a59a31125f· 1023-1166 - prepareCall at
72a59a31125f· 1432-1513
Protocol calls reject named arguments during parsing
Linkprotocol Labelled {function label(value: Self) -> string}record Item { name: string }impl Labelled<Item> {function label(value: Item) -> string { value.name }}let item = Item { name: "Moss" }Labelled.label(value: item)
No stdout.
Expected result
Expected result
Rejection during parse. Nonzero exit.
TPZ2001function-named-call-syntax atexample.tpz:9:21
Source evidence
- pull/44 at
72a59a31125f - checkProtocolMethod at
72a59a31125f· 1023-1166 - prepareCall at
72a59a31125f· 1432-1513
A required protocol parameter cannot follow a default
Linkprotocol Sum {function sum(value: Self, first: int = 1, second: int) -> int}
No stdout.
Expected result
Expected result
Rejection during parse. Nonzero exit.
TPZ2001function-required-after-default atexample.tpz:2:58
Source evidence
- pull/44 at
72a59a31125f - checkProtocolMethod at
72a59a31125f· 1023-1166 - prepareCall at
72a59a31125f· 1432-1513
A bodyless protocol signature still declares its result
Linkprotocol Labelled {function label(value: Self)}
No stdout.
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5022protocol-signature-return-required atexample.tpz:2:14
Source evidence
- checkProtocolMethod at
72a59a31125f· 1023-1166
Implementation binders and renamed method binders remain distinct
Linkimport std.io { writeLine }protocol Echo {function echo<U: Show>(value: Self, item: U) -> U}record Box<T> { value: T }impl<T> Echo<Box<T>> {function echo<V: Show>(value: Box<T>, item: V) { item }}let box: Box<int> = Box { value: 3 }writeLine(Echo.echo<Box<int>, string>(box, "ok"))let inferred: int = Echo.echo(box, 9)writeLine("{inferred}")
ok 9
Expected result
Expected result
Execution completes. Exit code 0.
ok9
Source evidence
- pull/44 at
72a59a31125f - checkProtocolImplMethodAgainstDefinition at
72a59a31125f· 338-456 - collectProtocolImplForDefinition and collectProtocolImplForTarget at
72a59a31125f· 529-650 - prepareFunctionDeclaration at
72a59a31125f· 1282-1355 - issues/31 at
9b1bbdeceba9
Protocol defaults and a variadic implementation share one call plan
Linkimport std.io { writeLine }protocol Sum {function sum(value: Self, start: int = 10, ...items: int) -> int}record Item { offset: int }impl Sum<Item> {function sum(value: Item, start: int, ...items: int) {let mut total = value.offset + startfor item in (items) { total = total + item }total}}let item = Item { offset: 1 }writeLine("{Sum.sum(item)}")writeLine("{Sum.sum(item, 2, ...[3, 4], 5)}")writeLine("{Sum.sum(item, ...[2, 3])}")
11 15 16
Expected result
Expected result
Execution completes. Exit code 0.
111516
Source evidence
- pull/44 at
72a59a31125f - checkProtocolImplMethodAgainstDefinition at
72a59a31125f· 338-456 - collectProtocolImplForDefinition and collectProtocolImplForTarget at
72a59a31125f· 529-650 - prepareFunctionDeclaration at
72a59a31125f· 1282-1355 - issues/31 at
9b1bbdeceba9
An omitted implementation result uses the protocol result context
Linkprotocol Choose {function choose(value: Self) -> int | string}record Item {}impl Choose<Item> {function choose(value: Item) { 7 }}let chosen: int | string = Choose.choose(Item {})
No stdout.
Expected result
Expected result
Checking accepts the program. Exit code 0.
Source evidence
- pull/44 at
72a59a31125f - checkProtocolImplMethodAgainstDefinition at
72a59a31125f· 338-456 - collectProtocolImplForDefinition and collectProtocolImplForTarget at
72a59a31125f· 529-650 - prepareFunctionDeclaration at
72a59a31125f· 1282-1355 - issues/31 at
9b1bbdeceba9
A generic receiver keeps its outer binder and local method binders
Linkimport std.io { writeLine }record Box<T> { value: T }impl<T> Box<T> {function readValue(self) -> T { self.value }function echo<U>(self, item: U) -> U { item }}let box: Box<int> = Box { value: 3 }writeLine("{box.readValue()}")writeLine(box.echo<string>("ok"))
3 ok
Expected result
Expected result
Execution completes. Exit code 0.
3ok
Source evidence
- pull/44 at
72a59a31125f - checkProtocolImplMethodAgainstDefinition at
72a59a31125f· 338-456 - collectProtocolImplForDefinition and collectProtocolImplForTarget at
72a59a31125f· 529-650 - prepareFunctionDeclaration at
72a59a31125f· 1282-1355 - issues/31 at
9b1bbdeceba9
A protocol owner can give int its declared operation
Linkimport std.io { writeLine }protocol Labelled {function label(value: Self) -> string}impl Labelled<int> {function label(value: int) -> string { "number" }}writeLine(Labelled.label(7))
number
Expected result
Expected result
Execution completes. Exit code 0.
number
Source evidence
- pull/44 at
72a59a31125f - checkProtocolImplMethodAgainstDefinition at
72a59a31125f· 338-456 - collectProtocolImplForDefinition and collectProtocolImplForTarget at
72a59a31125f· 529-650 - prepareFunctionDeclaration at
72a59a31125f· 1282-1355 - issues/31 at
9b1bbdeceba9
Different concrete type arguments select distinct conformances
Linkimport std.io { writeLine }protocol Mark {function mark(value: Self) -> string}record Box<T> { value: T }impl Mark<Box<int>> {function mark(value: Box<int>) -> string { "integer" }}impl Mark<Box<string>> {function mark(value: Box<string>) -> string { "text" }}let number: Box<int> = Box { value: 3 }let text: Box<string> = Box { value: "three" }writeLine(Mark.mark(number))writeLine(Mark.mark(text))
integer text
Expected result
Expected result
Execution completes. Exit code 0.
integertext
Source evidence
- pull/44 at
72a59a31125f - checkProtocolImplMethodAgainstDefinition at
72a59a31125f· 338-456 - collectProtocolImplForDefinition and collectProtocolImplForTarget at
72a59a31125f· 529-650 - prepareFunctionDeclaration at
72a59a31125f· 1282-1355 - issues/31 at
9b1bbdeceba9
Conditional derive binders correspond by position without capturing fields
Linkrecord Box<T> derives<U: Eq> Eq<Box<U>> { value: T }let first: Box<int> = Box { value: 3 }let second: Box<int> = Box { value: 3 }let same: bool = Eq.equals(first, second)
No stdout.
Expected result
Expected result
Checking accepts the program. Exit code 0.
Source evidence
- pull/44 at
72a59a31125f - checkProtocolImplMethodAgainstDefinition at
72a59a31125f· 338-456 - collectProtocolImplForDefinition and collectProtocolImplForTarget at
72a59a31125f· 529-650 - prepareFunctionDeclaration at
72a59a31125f· 1282-1355 - issues/31 at
9b1bbdeceba9
Conformance visibility makes a separate method export invalid
Linkprotocol Labelled {function label(value: Self) -> string}record Item {}impl Labelled<Item> {export function label(value: Item) -> string { "item" }}
No stdout.
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5022protocol-method-export atexample.tpz:6:5
Source evidence
- pull/44 at
72a59a31125f - checkProtocolImplMethodAgainstDefinition at
72a59a31125f· 338-456 - collectProtocolImplForDefinition and collectProtocolImplForTarget at
72a59a31125f· 529-650 - prepareFunctionDeclaration at
72a59a31125f· 1282-1355 - issues/31 at
9b1bbdeceba9
An implementation cannot introduce its own omitted argument
Linkprotocol Labelled {function label(value: Self, suffix: string) -> string}record Item {}impl Labelled<Item> {function label(value: Item, suffix: string = "!") -> string { suffix }}
No stdout.
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5022protocol-method-default atexample.tpz:6:33
Source evidence
- pull/44 at
72a59a31125f - checkProtocolImplMethodAgainstDefinition at
72a59a31125f· 338-456 - collectProtocolImplForDefinition and collectProtocolImplForTarget at
72a59a31125f· 529-650 - prepareFunctionDeclaration at
72a59a31125f· 1282-1355 - issues/31 at
9b1bbdeceba9
Implementation method bounds must match the declared operation
Linkprotocol Echo {function echo<U: Show>(value: Self, item: U) -> U}record Item {}impl Echo<Item> {function echo<V: Eq>(value: Item, item: V) -> V { item }}
No stdout.
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5022protocol-method-signature-mismatch atexample.tpz:6:14
Source evidence
- pull/44 at
72a59a31125f - checkProtocolImplMethodAgainstDefinition at
72a59a31125f· 338-456 - collectProtocolImplForDefinition and collectProtocolImplForTarget at
72a59a31125f· 529-650 - prepareFunctionDeclaration at
72a59a31125f· 1282-1355 - issues/31 at
9b1bbdeceba9
An array parameter does not replace a variadic tail
Linkprotocol Count {function count(value: Self, ...items: int) -> int}record Item {}impl Count<Item> {function count(value: Item, items: Array<int>) -> int { items.length }}
No stdout.
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5022protocol-method-signature-mismatch atexample.tpz:6:14
Source evidence
- pull/44 at
72a59a31125f - checkProtocolImplMethodAgainstDefinition at
72a59a31125f· 338-456 - collectProtocolImplForDefinition and collectProtocolImplForTarget at
72a59a31125f· 529-650 - prepareFunctionDeclaration at
72a59a31125f· 1282-1355 - issues/31 at
9b1bbdeceba9
An inner binder cannot be mistaken for the receiver binder
Linkprotocol Echo {function echo<U>(value: Self, item: U) -> U}record Box<T> { value: T }impl<T> Echo<Box<T>> {function echo<T>(value: Box<T>, item: T) -> T { item }}
No stdout.
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5022protocol-method-signature-mismatch atexample.tpz:6:14
Source evidence
- pull/44 at
72a59a31125f - checkProtocolImplMethodAgainstDefinition at
72a59a31125f· 338-456 - collectProtocolImplForDefinition and collectProtocolImplForTarget at
72a59a31125f· 529-650 - prepareFunctionDeclaration at
72a59a31125f· 1282-1355 - issues/31 at
9b1bbdeceba9
A concrete conformance cannot specialize an overlapping generic head
Linkrecord Box<T> { value: T }impl<T: Eq> Eq<Box<T>> {function equals(a: Box<T>, b: Box<T>) -> bool { Eq.equals(a.value, b.value) }}impl Eq<Box<int>> {function equals(a: Box<int>, b: Box<int>) -> bool { a.value == b.value }}
No stdout.
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5521duplicate-protocol-conformance atexample.tpz:5:9
Source evidence
- pull/44 at
72a59a31125f - checkProtocolImplMethodAgainstDefinition at
72a59a31125f· 338-456 - collectProtocolImplForDefinition and collectProtocolImplForTarget at
72a59a31125f· 529-650 - prepareFunctionDeclaration at
72a59a31125f· 1282-1355 - issues/31 at
9b1bbdeceba9
Different positive bounds do not prove two heads disjoint
Linkprotocol Mark {function mark(value: Self) -> int}record Box<T> { value: T }impl<T: Eq> Mark<Box<T>> {function mark(value: Box<T>) -> int { 1 }}impl<U: Show> Mark<Box<U>> {function mark(value: Box<U>) -> int { 2 }}
No stdout.
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5521duplicate-protocol-conformance atexample.tpz:8:20
Source evidence
- pull/44 at
72a59a31125f - checkProtocolImplMethodAgainstDefinition at
72a59a31125f· 338-456 - collectProtocolImplForDefinition and collectProtocolImplForTarget at
72a59a31125f· 529-650 - prepareFunctionDeclaration at
72a59a31125f· 1282-1355 - issues/31 at
9b1bbdeceba9
A conditional derive and a manual specialization share the overlap check
Linkrecord Box<T> derives<T: Eq> Eq<Box<T>> { value: T }impl Eq<Box<int>> {function equals(a: Box<int>, b: Box<int>) -> bool { a.value == b.value }}
No stdout.
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5521duplicate-protocol-conformance atexample.tpz:1:30
Source evidence
- pull/44 at
72a59a31125f - checkProtocolImplMethodAgainstDefinition at
72a59a31125f· 338-456 - collectProtocolImplForDefinition and collectProtocolImplForTarget at
72a59a31125f· 529-650 - prepareFunctionDeclaration at
72a59a31125f· 1282-1355 - issues/31 at
9b1bbdeceba9 - recordDerivedConformance at
151141d7bb44· 249-283
A transparent alias cannot create a second conformance
Linkrecord Item {}type Alias = Itemimpl Eq<Item> {function equals(a: Item, b: Item) -> bool { true }}impl Eq<Alias> {function equals(a: Alias, b: Alias) -> bool { true }}
No stdout.
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5521duplicate-protocol-conformance atexample.tpz:6:9
Source evidence
- pull/44 at
72a59a31125f - checkProtocolImplMethodAgainstDefinition at
72a59a31125f· 338-456 - collectProtocolImplForDefinition and collectProtocolImplForTarget at
72a59a31125f· 529-650 - prepareFunctionDeclaration at
72a59a31125f· 1282-1355 - issues/31 at
9b1bbdeceba9
The dependency graph rejects protocol-owner and type-owner overlap
Linkimport rulesimport kindslet value = 0
No stdout.
export protocol Mark { function mark(value: Self) -> int }impl<T> Mark<T> { function mark(value: T) -> int { 1 } }
import rules { Mark }export record Item {}impl Mark<Item> { function mark(value: Item) -> int { 2 } }
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5521duplicate-protocol-conformance atkinds.tpz:3:11
Source evidence
- pull/44 at
72a59a31125f - checkProtocolImplMethodAgainstDefinition at
72a59a31125f· 338-456 - collectProtocolImplForDefinition and collectProtocolImplForTarget at
72a59a31125f· 529-650 - prepareFunctionDeclaration at
72a59a31125f· 1282-1355 - issues/31 at
9b1bbdeceba9
Changing import order cannot choose a conformance winner
Linkimport kindsimport ruleslet value = 0
No stdout.
export protocol Mark { function mark(value: Self) -> int }impl<T> Mark<T> { function mark(value: T) -> int { 1 } }
import rules { Mark }export record Item {}impl Mark<Item> { function mark(value: Item) -> int { 2 } }
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5521duplicate-protocol-conformance atkinds.tpz:3:11
Source evidence
- pull/44 at
72a59a31125f - checkProtocolImplMethodAgainstDefinition at
72a59a31125f· 338-456 - collectProtocolImplForDefinition and collectProtocolImplForTarget at
72a59a31125f· 529-650 - prepareFunctionDeclaration at
72a59a31125f· 1282-1355 - issues/31 at
9b1bbdeceba9
An implementation cannot assume its own conditional bound
Linkprotocol Required { function required(value: Self) -> bool }protocol Mark { function mark(value: Self) -> int }record Box<T> { value: T }impl<T: Required> Mark<Box<T>> {function mark(value: Box<T>) -> int { 1 }}let box: Box<int> = Box { value: 3 }Mark.mark(box)
No stdout.
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5522protocol-call-argument-does-not-conform atexample.tpz:8:11
Source evidence
- pull/44 at
72a59a31125f - checkProtocolImplMethodAgainstDefinition at
72a59a31125f· 338-456 - collectProtocolImplForDefinition and collectProtocolImplForTarget at
72a59a31125f· 529-650 - prepareFunctionDeclaration at
72a59a31125f· 1282-1355 - issues/31 at
9b1bbdeceba9
Pending contracts
LinkRelated definitions and diagnostics
LinkSee Functions and generics for function and bound syntax, and Records, enums and newtypes for nominal construction and declaration syntax.
Protocol implementation violates the orphan rule
Duplicate protocol implementation
Protocol cannot be derived for this type
Variadic parameter is not final
Wrong number of explicit type arguments
Private type in exported surface