Scope and ownership
LinkEdition: Topaz 5.21 language mode.
Authority: the SELF compiler at source revision d61f9fd0efb0c358d382203ebe0cc0a1e187670a.
Status: reference draft. The rules below have source evidence. Pending contracts remain publication blockers for a complete reference page.
This page owns declarations, parameter binding, calls, function type parameters, and return inference. General type compatibility belongs to Types. Protocol conformance belongs to Protocols and implementations. Capture and mutation belong to Bindings and scope.
Declaration syntax
LinkNamedFunction ="function" Identifier TypeParameters?"(" Parameters? ")" ("->" Type)? Block ;TypeParameters ="<" TypeParameter ("," TypeParameter)* ">" ;TypeParameter =Identifier (":" Identifier ("+" Identifier)*)? ;ParameterShape =FixedParameterShape | VariadicParameterShape ;FixedParameterShape =Identifier ":" Type ("=" Expression)? ;VariadicParameterShape ="..." Identifier ":" Type ;
A named function has a name, an optional type-parameter list, a parameter list, an optional return annotation, and a block body.
This syntax excerpt distinguishes fixed parameters, which may have defaults, from variadic parameters, which have no initializer. A variadic parameter must be final. Names are unique within each named-function or lambda parameter list. Type, Expression, and Block require their own productions.
This excerpt omits the special self form and does not establish its permitted declaration contexts. Receiver rules belong to Protocols and implementations.
Required fixed parameters come before defaulted fixed parameters. A final variadic parameter follows either sequence and has no default. Calls use positional values and explicit spread.
Source evidence
- Declaration parser at
d61f9fd0efb0· 100-188 - Lambda parser at
d61f9fd0efb0· 200-265 - issues/31 at
570530e69141 - issues/31 at
570530e69141
Unannotated self parameter
The parser also recognizes an unannotated first, nonvariadic parameter named self.
Why
A receiver gets its type from the enclosing implementation. A bare first self identifies that receiver without repeating the target type.
Source evidence
- Declaration parser at
d61f9fd0efb0· 100-188
Lambda parameter lists
Every lambda has a parenthesized parameter list followed by => and an expression. Write () => expression for no parameters, (name) => expression for one, and (left, right) => expression for several. A parameter may include a type annotation. The bare form name => expression is rejected during parsing with TPZ2001.
Omitted lambda parameter types come from the expected function type or from inference. The parameter-context rules describe that typing behavior.
Why
A bare single parameter and a parenthesized single parameter introduce the same binding into the same lambda body. Parentheses preserve that intent and give lambdas the same parameter-list shape for every arity. Adding a parameter or an annotation does not require another syntax.
Source evidence
- Lambda parser at
d61f9fd0efb0· 200-265 - parseLambdaParameters and parseParenLambda at
9b1bbdeceba9· 200-286 - parseIdentifierAtom current bare lambda dispatch at
9b1bbdeceba9· 1004-1013 - itemBoundary existing statement separator diagnostic at
9b1bbdeceba9· 443-455 - issues/31 at
23582e1d56db
Parameters and default arguments
LinkAn ordinary parameter is written as name: Type. A variadic parameter places the ellipsis before its name.
Parameter bindings
Parameter bindings are immutable inside the function.
Collection arguments and the values reached through parameters follow collection value semantics.
Why
A parameter keeps the argument value bound at entry. Rebinding requires an explicit mutable local, as it does for other local names.
Source evidence
- Parameter checking at
d61f9fd0efb0· 200-247
Variadic parameters
Inside the function, a variadic parameter annotated with T has type Array<T>.
Why
Packing the tail into Array<T> lets the body use the same indexing and iteration operations as any other array. When a call supplies no tail arguments, the body can inspect the empty array and select any replacement values. A variadic default would express that same choice in a second place.
Source evidence
- Parameter checking at
d61f9fd0efb0· 200-247 - issues/31 at
570530e69141
Variadic parameter syntax fragment
Link...values: T
Expected result
Expected result
Syntax fragment; not a complete executable program.
The following is a syntax fragment. It is not passed to the CLI as a complete program.
Source evidence
- Parameter checking at
d61f9fd0efb0· 200-247 - Functions and generics manuscript at
d61f9fd0efb0· 5.3D
Default arguments
A parameter can contain a default expression after =. The checker checks that expression against the parameter type and applies a constant-expression restriction. Failure of the constant-expression check produces TPZ5001.
TPZ5001 is also used for other conditions; this rule describes the confirmed constant-expression check.
Why
A supplied argument and its default must satisfy the same parameter type. Constant defaults make omission independent of runtime calls and parameter values.
Source evidence
- Default restriction at
d61f9fd0efb0· 100-121 - Parameter checking at
d61f9fd0efb0· 200-247
Default evaluation and declaration scope
A default is evaluated whenever its argument is omitted. Supplied arguments are evaluated first. Missing defaults are then evaluated in parameter order. Default names resolve in the declaration environment, where the new parameters are unavailable. Defaults must be literals or const expressions. The admitted forms are parentheses, unary and binary expressions except in, untagged strings with constant interpolations, arrays and records without spread, and the checked numeric constant operations. Ordinary function calls and tagged templates do not satisfy this restriction.
A default must satisfy its parameter type and the constant-expression restrictions. Numeric constant operations include integer and, or, xor, and shift; float trunc, floor, ceil, and round; and the checked length constructors and conversions. The current identifier admission discrepancy is recorded as an implementation limitation.
Why
Declaration scope makes a default mean the same thing for every caller. Supplied arguments followed by omitted defaults give calls one evaluation order.
Source evidence
- constantExpressionInvalid and admitted numeric operations at
151141d7bb44· 1033-1253 - checkFunctionDefault at
151141d7bb44· 100-121 - walkCallableSignature before parameter bindings at
151141d7bb44· 905-926 - signature resolution precedes the callable scope at
151141d7bb44· 1163-1171 - emitDirectCall evaluates supplied arguments before missing defaults at
151141d7bb44· 1259-1290 - defaultValues creates per-invocation thunks at
151141d7bb44· 1633-1647 - call_closure awaits defaults for unfilled slots at
151141d7bb44· 111-139 - Published function default and variadic contract at
151141d7bb44· 96-109
Parameter annotations, lambdas, and receivers
Named function parameters require a type annotation, with special syntax for a bare first self. Required fixed parameters precede defaulted fixed parameters. A later required parameter is rejected during parsing with TPZ2001. A final variadic parameter may follow the defaulted suffix. Lambda parameter annotations are optional. An omitted lambda type comes from the expected function parameter at that position, or becomes an inference variable when no context supplies it. A concrete nonvariadic function context also fixes the lambda’s parameter count. Parameters are immutable bindings. Receiver methods on own-module records, enums, and newtypes require bare self first, without a default or a variadic marker.
A receiver implementation targets a record, enum, or newtype declared in the same module. Generic targets use an explicit enclosing binder list, and each method may introduce ordinary function binders. The first bare self has the complete target type and has no annotation, default, or variadic marker. Protocol signatures use explicit annotated parameters and the same ordinary variadic/default rules.
Why
Required fixed parameters precede defaulted fixed parameters so every default can be selected by omitting a suffix. A named function supplies its own parameter types. A lambda can obtain them from the expected function type. Independent named settings remain expressible through a record parameter, whose field names preserve their roles without exposing local parameter names.
Source evidence
- named parameter annotations and bare-self syntax at
151141d7bb44· 148-183 - optional lambda parameter annotations at
151141d7bb44· 200-265 - expected function parameter types and lambda arity at
151141d7bb44· 305-381 - bareSelfParameter at
151141d7bb44· 730-745 - receiver method first-parameter checks at
151141d7bb44· 792-837 - own-module nongeneric nominal receiver implementations at
151141d7bb44· 909-1007 - independent default and required fixed parameter slots at
151141d7bb44· 251-284 - same-name local map insertion at
151141d7bb44· 79-100 - parameter bindings inserted in declaration order at
151141d7bb44· 951-978 - explicit protocol-method parameter annotations at
151141d7bb44· 490-528 - named-call lookup selects the first fixed parameter with the name at
151141d7bb44· 234-249 - issues/31 at
570530e69141 - parseCallArguments at
271329b482bc· 99-141 - pull/44 at
72a59a31125f - issues/31 at
23582e1d56db
Variadic placement, defaults, and call arguments
A variadic parameter must be final and cannot have a default initializer. A variadic default is rejected during parsing with TPZ2001. Its element annotation T gives the parameter the body type Array<T>. An empty tail supplies an empty array. Required fixed parameters come before defaulted fixed parameters. Spread arguments require a variadic parameter and cannot fill fixed slots. Before a spread, every skipped fixed slot must already be supplied or have a default. Supply fixed overrides before the spread. Spread contents and subsequent positional values are appended to the variadic tail in source order.
Defaulted fixed parameters may precede the final variadic parameter. Supply a fixed override before a spread, as in total(1, ...tail). A spread cannot fill a required fixed slot. Positional arguments after a spread join the variadic tail. A variadic default initializer is a syntax error, even when the tail would be empty.
Why
A final variadic parameter gives each positional argument one destination. An absent tail consistently becomes an empty array without a second source of default values. An explicit array spread supplies the same ordered tail that a named tail argument would supply. Independent named settings keep their distinct configuration intent through record fields. When a call has no tail, the body can check its length and supply a replacement. Declaring a default tail would create a second spelling for the same intent as the empty tail.
Source evidence
- parameter loop admits variadic markers without a final-position check at
151141d7bb44· 148-183 - fixed parameter defaults and variadic element types are stored separately at
151141d7bb44· 1222-1279 - variadic parameter is bound to Array of its element type at
151141d7bb44· 203-247 - named lookup ranges over fixed parameter count at
151141d7bb44· 234-249 - spread skips fixed slots when already filled or defaulted and precedes named arguments at
151141d7bb44· 412-470 - positional arguments after a spread enter the variadic tail at
151141d7bb44· 492-561 - spread requires a variadic signature at
151141d7bb44· 640-685 - defaultValues excludes the final variadic slot at
151141d7bb44· 1633-1647 - published final variadic parameter and call rules at
151141d7bb44· 96-106 - issues/31 at
570530e69141 - parameter parser admits a default after a variadic marker at
570530e69141· 148-183 - variadic defaults are checked as array constants at
570530e69141· 202-248 - defaultValues excludes the final variadic slot at
570530e69141· 1633-1647 - existing expected-token message used by the intended parse rejection at
570530e69141· 195-205 - parseCallArguments at
271329b482bc· 99-141 - issues/31 at
23582e1d56db
Unique parameter names
Each parameter in a named function or lambda must have a distinct name within that parameter list. A repeated name is rejected with TPZ5008, whether or not the function is called. Separate parameter lists may reuse the same name.
Why
One parameter name identifies one body binding. Rejecting repeated names prevents later-name shadowing while positional slots remain distinct.
Source evidence
- issues/31 at
570530e69141 - parameter bindings inserted without a uniqueness check at
570530e69141· 951-978 - named parameter bindings formed without a repeated-name check at
570530e69141· 202-248 - lambda parameter bindings formed without a repeated-name check at
570530e69141· 342-381
Required after default
Linkfunction add(first: int = 1, second: int) -> int { first + second }
Expected result
Expected result
Rejection during parse. Nonzero exit.
TPZ2001function-required-after-default atexample.tpz:1:41
Source evidence
- parseCallArguments at
271329b482bc· 99-141
Type parameters and bounds
LinkDeclaring type parameters
Declare type parameters immediately after the function name. A type parameter can list protocol requirements using : and +, as in T: Show + JSON.
Protocol conformance and the operations made available by a bound are defined under Protocols and implementations.
Protocol signatures use these same method binders and bounds. Their call type-parameter list places implicit Self before the declared method parameters.
A type binder belongs to its lexical declaration. An implementation binder scopes over its target and methods; a method may add nested binders. Binder lists contain distinct names, and ordinary nested shadowing resolves to the inner declaration. Protocol matching compares those resolved identities after pairing method binders by position.
Why
Declaring type parameters with the function makes their scope visible. Bounds use the same <T: Protocol> notation as generic implementations.
Source evidence
- Declaration parser at
d61f9fd0efb0· 100-188
Generic function declaration
Linkfunction identity<T>(value: T) -> T {value}
example.tpz: types-ok (1 module) example.tpz: resolve-ok (1 module)
Expected result
Expected result
Checking accepts the program. Exit code 0.
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
This is the complete declaration used by the generic-call examples.
Observed: The authored assertions passed. Recorded with Topaz 5.21.3 in run 42147883818 at 2026-. Document source: 1d6f9265d918.
Source evidence
- Functions and generics manuscript at
d61f9fd0efb0· 5.3D
Repeated protocol bounds
Each type parameter names a resolved protocol identity once. Repeating that identity produces TPZ5523, including through namespace qualification or aliases. A distinct protocol declaration remains a different requirement even when another module gives it the same name.
The duplicate-bound check is per type parameter and compares resolved protocol declarations. Namespace qualification, aliases, and whitespace do not create a second protocol identity.
Why
Repeating a protocol identity adds no capability. Resolve each bound before comparing it, so an alias or a space around a namespace dot cannot evade uniqueness. Distinct protocol declarations retain separate requirements even when their defining modules use the same spelling.
Source evidence
- Bound diagnostics at
d61f9fd0efb0· 177-191 - checkFunctionDeclarationInner bound iteration at
151141d7bb44· 177-191 - checkFunctionDeclarationInner exported bound guard at
72a59a31125f· 158-191 - pull/41 at
72a59a31125f
Locations and additional bound diagnostics
A resolved protocol identity may appear once for each type parameter. Aliases, namespace qualification, and whitespace preserve that identity. Each later occurrence produces TPZ5523 at the repeated bound. The same protocol may constrain another type parameter. Checking continues after reporting a repeated bound, so other applicable diagnostics may also appear.
Why
Reporting each repeated protocol identity points to the redundant requirement to remove. Source spelling must not change that result. Continuing the check lets independent mistakes be reported in the same run.
Source evidence
- checkFunctionDeclarationInner bound iteration at
151141d7bb44· 177-191 - checkFunctionDeclarationInner exported bound guard at
72a59a31125f· 158-191 - pull/41 at
72a59a31125f
Bounds exposed by public callables
An exported function or a method signature of an exported protocol may expose built-in protocols and exported user protocols in its bounds. Resolve each bound through its defining protocol identity, including selected aliases and namespace qualification. A private protocol bound is rejected with TPZ5524. Default expressions keep their ordinary lexical environment. This visibility check concerns the public signature.
Why
A caller must be able to name every protocol contract exposed by a public callable. Visibility follows the resolved declaration identity, so user protocols can cross a module boundary by the same explicit export rule as other declarations. Renaming an import does not change whether that contract is public.
Source evidence
- Bound diagnostics at
d61f9fd0efb0· 177-191 - checkFunctionDeclarationInner exported bound guard at
72a59a31125f· 158-191 - pull/41 at
72a59a31125f
Compiler-owned type-parameter names
Topaz 6.0 rejects Length, Pt, Mm, Cm, In, Em as function type parameter names.
The compiler-owned type-parameter names are not a new list of globally reserved identifiers.
Why
Length and unit names identify built-in measurement types. Reserving them in type parameter lists prevents a generic binding from hiding those meanings.
Source evidence
- Declaration parser at
d61f9fd0efb0· 100-188
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
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
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
Return inference
LinkCollecting an unannotated result
A named function may omit its - annotation syntactically. When the result is unannotated, the checker gathers explicit return types and the body's tail-expression type. A block that falls through without a tail expression contributes unit; an empty collected result also falls back to unit.
Why
Every return path contributes to the result type. Including fallthrough as unit prevents inference from promising a value on a path that returns none.
Source evidence
- Return inference at
d61f9fd0efb0· 249-330
When inference needs an annotation
Confirmed TPZ5022 paths include an inferred result containing a generic projection without a nameable return type, and an unresolved result when the function calls a definition whose return type is still pending.
Omission does not guarantee successful inference. The identified pending-result case includes recursive or mutually recursive calls. An explicit result annotation is the indicated remedy for these paths. This does not mean every recursive function requires one.
An undetermined Result error type follows the Result annotation rule, using the same TPZ5022 diagnostic.
Why
An explicit result gives recursive calls a type before their bodies are solved. It also supplies a named result where generic projection inference cannot.
Source evidence
- Return inference at
d61f9fd0efb0· 249-330
Inferring a named function result
For a named function without a result annotation, collect every explicit return type and the body’s tail-expression type. A body that reaches its end without a tail contributes (). An empty collection also produces (). If a collected component is unresolved, the result remains unresolved. Otherwise, combine the collected types into a union and widen fresh scalar literal types. TPZ5022 requires an explicit annotation when the result contains an unnameable field or element type extracted from a generic value. It also requires one when an unresolved result depends on a function whose return type is still being inferred, including recursive dependencies.
The checker examines the final statement or tail to recognize a terminating block. An explicit return terminates; a block or parenthesized expression delegates to its contents; an if needs both branches to terminate; a match needs every arm to terminate. Partial type components are solved while joining, and fresh scalar literal results widen to their primitive types.
Why
Unioning returned types preserves all paths through the body. Widening fresh literals keeps the inferred signature independent of the particular literal returned.
Source evidence
- unannotated result collection and annotation diagnostics at
151141d7bb44· 249-330 - appendReturnType and checkReturn at
151141d7bb44· 596-668 - blockTerminates and expressionTerminates at
151141d7bb44· 224-291 - joinedTypes solves partial components and forms a widened union at
151141d7bb44· 1148-1186 - generic structural record field projection at
151141d7bb44· 888-943
Calling a function
LinkArgument shapes
A call supplies positional expressions in parameter order. . expands an array into the variadic tail. Omitted trailing fixed arguments use their defaults. A name: expression argument is rejected during parsing with TPZ2001. Pass named settings as fields of an ordinary record argument. Field names associate values with settings, so reordering the fields cannot exchange their roles.
Pass named settings in an ordinary record argument. Field names keep each value attached to its setting when the fields are reordered.
Why
Named arguments and record fields can both identify which setting a value configures. A record preserves that intent as data while the call passes one positional value. Reordering fields cannot exchange the roles of two settings with the same type. Function types describe parameter positions, so this call convention also works for callbacks, methods, and constructors. Parameter names remain local bindings and can change without rewriting callers. Spread preserves the separate intent of supplying an ordered variadic tail.
Source evidence
- Argument parser at
d61f9fd0efb0· 99-142 - parseCallArguments at
271329b482bc· 99-141 - issues/31 at
23582e1d56db
Generic calls
Generic calls can infer type arguments or supply them explicitly after a name or member access. A <. sequence after a parenthesized expression is not a generic call. Parentheses preserve a named function’s declaration signature, so (identity)(value) can infer its type arguments. Write identity<Type>(value) to supply them explicitly.
Why
Topaz has one way to say each thing. Explicit type arguments have one place, directly after the name or member being called. A parenthesized callee already keeps the declaration it names, so a second spelling would add nothing. One position also keeps the lookahead that separates a call from a comparison simple. Explicit type arguments on a computed generic callable currently have little demonstrated use. This syntax can be revisited when a concrete program needs it.
Source evidence
- Argument parser at
d61f9fd0efb0· 99-142 - Lookahead condition at
d61f9fd0efb0· 143-168 - Related generic callback test at
d61f9fd0efb0· 1154-1221 - issues/31 at
570530e69141 - explicit type arguments require an identifier or member-access callee at
570530e69141· 1411-1425 - parenthesized callee preserves declaration identity at
570530e69141· 86-110 - TPZ5002 for an unresolved value read at
570530e69141· 630-653
Evaluation, binding, and type inference at a call
Evaluate the callee once, then evaluate supplied arguments once from left to right as written. Each positional argument fills the next fixed parameter until the variadic tail begins. A spread supplies the variadic tail and cannot fill an unsatisfied fixed parameter. Missing fixed arguments use their defaults, evaluated after supplied arguments in declaration order. Named call syntax is rejected with TPZ2001 during parsing. Explicit type arguments supply the complete declared list in order and must agree with both the arguments and the expected result. Each call starts fresh type inference. Later evidence cannot replace an already fixed type. Parentheses preserve a named function’s call signature. Lambda and generic-function callbacks receive the expected function type from their parameter slot.
Type inference may inspect independent arguments before a callback to learn its expected input types. This static dependency order does not change runtime evaluation order. The existing explicit-type-argument spacing limitation remains an implementation discrepancy.
For a call that previously used names, map each supplied value to the declaration position. Preserve effect order by evaluating expressions into fresh local bindings in their original order, then pass those bindings positionally. If a required parameter followed a defaulted one, move required parameters first and migrate every call against the new signature. Reordering parameters or adding required parameters changes the API. Adding a trailing defaulted fixed parameter preserves existing fixed-arity callers; inserting it before a variadic tail changes that tail and requires migration. Use an options record when independent optional settings must evolve. Parameter renaming alone does not change call sites.
Why
Each call gets fresh type variables so one use cannot constrain another. Evaluating the callee and each argument once makes effects predictable. A record argument preserves named configuration through its fields, so a function value can receive the same settings as a direct call.
Source evidence
- valueCallPrefix evaluates the callee and source-ordered argument expressions once at
151141d7bb44· 946-990 - emitDirectCall stages each argument before binding parameter slots at
151141d7bb44· 1209-1290 - named ownership and spread admission at
151141d7bb44· 366-470 - positional-after-named diagnostics at
151141d7bb44· 492-561 - explicitCallBindings requires the entire declared type-parameter list at
151141d7bb44· 1156-1217 - call-scoped schemes and first type-variable binding at
151141d7bb44· 2004-2052 - parameter-directed type evidence and contextual callback inference at
151141d7bb44· 368-442 - expected call result contributes type evidence at
151141d7bb44· 1445-1467 - lambda and generic function arguments receive expected function types at
151141d7bb44· 167-209 - calleeReference preserves a parenthesized declaration identity at
151141d7bb44· 86-110 - parseCallArguments at
271329b482bc· 99-141 - issues/31 at
23582e1d56db
Named form
Linkfunction pick(value: int) -> int { value }pick(value: 3)
Expected result
Expected result
Rejection during parse. Nonzero exit.
TPZ2001function-named-call-syntax atexample.tpz:2:11
Source evidence
- parseCallArguments at
271329b482bc· 99-141
Positional defaults
Linkimport std.io { writeLine }function add(left: int, right: int = 2) -> int { left + right }let callback: (int, int) -> int = (left, right) => left + rightwriteLine("{add(3)}:{add(3, 4)}:{callback(3, 4)}")
5:7:7
Expected result
Expected result
Execution completes. Exit code 0.
5:7:7
Source evidence
- parseCallArguments at
271329b482bc· 99-141
Examples
LinkInferred and explicit type arguments
Linkfunction identity<T>(value: T) -> T {value}let number: int = identity(3)let text: string = identity("Topaz")let explicit: int = identity<int>(1)
example.tpz: types-ok (1 module) example.tpz: resolve-ok (1 module)
Expected result
Expected result
Checking accepts the program. Exit code 0.
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
Expected observation: checking accepts the program. Existing test source contains a related generic id<T> callback example. Its existence does not verify this exact program or every call context.
Observed: The authored assertions passed. Recorded with Topaz 5.21.3 in run 42147883818 at 2026-. Document source: 1d6f9265d918.
Source evidence
- Functions and generics manuscript at
d61f9fd0efb0· 5.3D - Related generic callback test at
d61f9fd0efb0· 1154-1221
Repeated-bound example
Linkfunction duplicate<T: Show + Show>(value: T) -> T {value}
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5523repeated-protocol-bound atexample.tpz:1:30
TPZ5523 points to the second Show at example.. Each later occurrence for the same type parameter is diagnosed. Checking continues, so other applicable diagnostics may also appear. Remove the repeated bound to correct this program. The earlier receipt predates the location assertion; CI must execute this revision.
Observed: The authored exit and diagnostic-cause assertions passed. The primary-location assertion and additional-diagnostics policy remain pending. Recorded with Topaz 5.21.3 in run 42147883818 at 2026-. Document source: 1d6f9265d918.
Source evidence
- Functions and generics manuscript at
d61f9fd0efb0· 5.3D - checkFunctionDeclarationInner bound iteration at
151141d7bb44· 177-191
Repeated bound removed
Linkfunction duplicate<T: Show>(value: T) -> T {value}
example.tpz: types-ok (1 module) example.tpz: resolve-ok (1 module)
Expected result
Expected result
Checking accepts the program. Exit code 0.
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
Acceptance of this corrected complete program is a separate check.
Observed: The authored assertions passed. Recorded with Topaz 5.21.3 in run 42147883818 at 2026-. Document source: 1d6f9265d918.
Source evidence
- Functions and generics manuscript at
d61f9fd0efb0· 5.3D
Adjacent explicit type argument
Linkfunction identity<T>(value: T) -> T {value}let explicit: int = identity<int>(1)
example.tpz: types-ok (1 module) example.tpz: resolve-ok (1 module)
Expected result
Expected result
Checking accepts the program. Exit code 0.
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
The original comparison fragment is completed with the same identity declaration and an annotated result binding.
Observed: The authored assertions passed. Recorded with Topaz 5.21.3 in run 42147883818 at 2026-. Document source: 1d6f9265d918.
Source evidence
- Functions and generics manuscript at
d61f9fd0efb0· 5.3D
Spaced explicit type argument
Linkfunction identity<T>(value: T) -> T {value}let explicit: int = identity<int> (1)
No stdout.
error[TPZ5002]: `int` is not bound --> example.tpz:5:30 | 5 | let explicit: int = identity<int> (1) | ^^^ example.tpz: 1 type diagnostic
Expected result
Expected result
Checking accepts the program. Exit code 0.
Observed assertions · failed · 5.21.3
Exit code 1.
No stdout.
error[TPZ5002]: `int` is not bound--> example.tpz:5:30|5 | let explicit: int = identity<int> (1)| ^^^example.tpz: 1 type diagnostic
- Failed · expected 0; observed 1
The expected language behavior is the same as the adjacent spelling. IL-001 remains an implementation limitation; rejection must not become a negative conformance rule.
Observed: The authored acceptance assertion failed. Recorded with Topaz 5.21.3 in run 42147883818 at 2026-. Document source: 1d6f9265d918.
Source evidence
- Functions and generics manuscript at
d61f9fd0efb0· 5.3D
A repeated protocol after a different bound
Linkfunction repeated<T: Show + Eq + Show>(value: T) -> T { value }
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5523repeated-protocol-bound atexample.tpz:1:34
Source evidence
- checkFunctionDeclarationInner bound iteration at
151141d7bb44· 177-191
The same protocol on different type parameters
Linkfunction choose<T: Show, U: Show>(first: T, second: U) -> T { first }
Expected result
Expected result
Checking accepts the program. Exit code 0.
Source evidence
- checkFunctionDeclarationInner bound iteration at
151141d7bb44· 177-191
Omitted and supplied defaults
Linkimport std.io { writeLine }const base: int = 40function answer(value: int = base + 2) -> int { value }writeLine("{answer()}")writeLine("{answer(7)}")writeLine("{(answer)(9)}")
42 7 9
Expected result
Expected result
Execution completes. Exit code 0.
4279
Source evidence
- constantExpressionInvalid and admitted numeric operations at
151141d7bb44· 1033-1253 - checkFunctionDefault at
151141d7bb44· 100-121 - walkCallableSignature before parameter bindings at
151141d7bb44· 905-926 - signature resolution precedes the callable scope at
151141d7bb44· 1163-1171 - emitDirectCall evaluates supplied arguments before missing defaults at
151141d7bb44· 1259-1290 - defaultValues creates per-invocation thunks at
151141d7bb44· 1633-1647 - call_closure awaits defaults for unfilled slots at
151141d7bb44· 111-139
An ordinary call is not a constant default
Linkfunction compute() -> int { 42 }function answer(value: int = compute()) -> int { value }
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5001function-default-constant-expression atexample.tpz:2:30
Source evidence
- constantExpressionInvalid and admitted numeric operations at
151141d7bb44· 1033-1253 - checkFunctionDefault at
151141d7bb44· 100-121 - walkCallableSignature before parameter bindings at
151141d7bb44· 905-926 - signature resolution precedes the callable scope at
151141d7bb44· 1163-1171 - emitDirectCall evaluates supplied arguments before missing defaults at
151141d7bb44· 1259-1290 - defaultValues creates per-invocation thunks at
151141d7bb44· 1633-1647 - call_closure awaits defaults for unfilled slots at
151141d7bb44· 111-139
A runtime binding is not a const default
Linklet mut current: int = 1function answer(value: int = current) -> int { value }
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5001function-default-constant-expression atexample.tpz:2:30
The intended literal-or-const restriction requires rejection. SELF currently accepts an identifier in a default without testing its declaration kind. Keep this rejection expectation while the implementation discrepancy is reviewed.
Source evidence
- constantExpressionInvalid and admitted numeric operations at
151141d7bb44· 1033-1253 - checkFunctionDefault at
151141d7bb44· 100-121 - walkCallableSignature before parameter bindings at
151141d7bb44· 905-926 - signature resolution precedes the callable scope at
151141d7bb44· 1163-1171 - emitDirectCall evaluates supplied arguments before missing defaults at
151141d7bb44· 1259-1290 - defaultValues creates per-invocation thunks at
151141d7bb44· 1633-1647 - call_closure awaits defaults for unfilled slots at
151141d7bb44· 111-139 - Published function default and variadic contract at
151141d7bb44· 96-109
Return values, tail values, and unit fallthrough
Linkfunction choose(flag: bool) {if (flag) { return 1 }"other"}function maybe(flag: bool) {if (flag) { return 1 }}function empty() {}let choice: int | string = choose(false)let absence: int | () = maybe(false)let nothing: () = empty()
Expected result
Expected result
Checking accepts the program. Exit code 0.
Source evidence
- unannotated result collection and annotation diagnostics at
151141d7bb44· 249-330 - appendReturnType and checkReturn at
151141d7bb44· 596-668 - blockTerminates and expressionTerminates at
151141d7bb44· 224-291 - joinedTypes solves partial components and forms a widened union at
151141d7bb44· 1148-1186 - generic structural record field projection at
151141d7bb44· 888-943
An unresolved recursive result needs an annotation
Linkfunction recur() { recur() }
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5022function-unresolved-recursive-return atexample.tpz:1:10
Source evidence
- unannotated result collection and annotation diagnostics at
151141d7bb44· 249-330 - appendReturnType and checkReturn at
151141d7bb44· 596-668 - blockTerminates and expressionTerminates at
151141d7bb44· 224-291 - joinedTypes solves partial components and forms a widened union at
151141d7bb44· 1148-1186 - generic structural record field projection at
151141d7bb44· 888-943
An explicit result for recursive calls
Linkimport std.io { writeLine }function countDown(value: int) -> int {if (value == 0) { 0 } else { countDown(value - 1) }}writeLine("{countDown(3)}")
0
Expected result
Expected result
Execution completes. Exit code 0.
0
Source evidence
- unannotated result collection and annotation diagnostics at
151141d7bb44· 249-330 - appendReturnType and checkReturn at
151141d7bb44· 596-668 - blockTerminates and expressionTerminates at
151141d7bb44· 224-291 - joinedTypes solves partial components and forms a widened union at
151141d7bb44· 1148-1186 - generic structural record field projection at
151141d7bb44· 888-943
A destructured generic field cannot escape as an inferred result
Linkfunction field<T>(value: T) {match (value) {case { x: x } => xcase _ => 0}}
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5022function-unnameable-projection-return atexample.tpz:1:10
Source evidence
- unannotated result collection and annotation diagnostics at
151141d7bb44· 249-330 - appendReturnType and checkReturn at
151141d7bb44· 596-668 - blockTerminates and expressionTerminates at
151141d7bb44· 224-291 - joinedTypes solves partial components and forms a widened union at
151141d7bb44· 1148-1186 - generic structural record field projection at
151141d7bb44· 888-943
Callee and arguments run once in source order
Linkimport std.io { writeLine }let mut trace: string = ""function combine(first: int, second: int) -> int { first + second }function pick() -> (int, int) -> int {trace += "c"combine}function mark(label: string, value: int) -> int {trace += labelvalue}let total = pick()(mark("a", 1), mark("b", 2))writeLine("{trace}:{total}")trace = ""let secondValue = mark("b", 2)let firstValue = mark("a", 1)let positional = combine(firstValue, secondValue)writeLine("{trace}:{positional}")
cab:3 ba:3
Expected result
Expected result
Execution completes. Exit code 0.
cab:3ba:3
Source evidence
- valueCallPrefix evaluates the callee and source-ordered argument expressions once at
151141d7bb44· 946-990 - emitDirectCall stages each argument before binding parameter slots at
151141d7bb44· 1209-1290 - named ownership and spread admission at
151141d7bb44· 366-470 - positional-after-named diagnostics at
151141d7bb44· 492-561 - explicitCallBindings requires the entire declared type-parameter list at
151141d7bb44· 1156-1217 - call-scoped schemes and first type-variable binding at
151141d7bb44· 2004-2052 - parameter-directed type evidence and contextual callback inference at
151141d7bb44· 368-442 - expected call result contributes type evidence at
151141d7bb44· 1445-1467 - lambda and generic function arguments receive expected function types at
151141d7bb44· 167-209 - calleeReference preserves a parenthesized declaration identity at
151141d7bb44· 86-110
Each call has its own type inference and callback context
Linkimport std.io { writeLine }function identity<T>(value: T) -> T { value }function apply<T, U>(value: T, step: (T) -> U) -> U { step(value) }let number: int = (identity)(3)let text: string = identity("ok")let stepped: int = apply(3, (value) => value + 1)let generic: int = apply(4, identity)writeLine("{number}")writeLine(text)writeLine("{stepped}")writeLine("{generic}")
3 ok 4 4
Expected result
Expected result
Execution completes. Exit code 0.
3ok44
Source evidence
- valueCallPrefix evaluates the callee and source-ordered argument expressions once at
151141d7bb44· 946-990 - emitDirectCall stages each argument before binding parameter slots at
151141d7bb44· 1209-1290 - named ownership and spread admission at
151141d7bb44· 366-470 - positional-after-named diagnostics at
151141d7bb44· 492-561 - explicitCallBindings requires the entire declared type-parameter list at
151141d7bb44· 1156-1217 - call-scoped schemes and first type-variable binding at
151141d7bb44· 2004-2052 - parameter-directed type evidence and contextual callback inference at
151141d7bb44· 368-442 - expected call result contributes type evidence at
151141d7bb44· 1445-1467 - lambda and generic function arguments receive expected function types at
151141d7bb44· 167-209 - calleeReference preserves a parenthesized declaration identity at
151141d7bb44· 86-110
Named call syntax is rejected before argument binding
Linkfunction choose(first: int, second: int) -> int { first }choose(first: 1, first: 2, second: 3)
Expected result
Expected result
Rejection during parse. Nonzero exit.
TPZ2001function-named-call-syntax atexample.tpz:2:13
Source evidence
- valueCallPrefix evaluates the callee and source-ordered argument expressions once at
151141d7bb44· 946-990 - emitDirectCall stages each argument before binding parameter slots at
151141d7bb44· 1209-1290 - named ownership and spread admission at
151141d7bb44· 366-470 - positional-after-named diagnostics at
151141d7bb44· 492-561 - explicitCallBindings requires the entire declared type-parameter list at
151141d7bb44· 1156-1217 - call-scoped schemes and first type-variable binding at
151141d7bb44· 2004-2052 - parameter-directed type evidence and contextual callback inference at
151141d7bb44· 368-442 - expected call result contributes type evidence at
151141d7bb44· 1445-1467 - lambda and generic function arguments receive expected function types at
151141d7bb44· 167-209 - calleeReference preserves a parenthesized declaration identity at
151141d7bb44· 86-110
Named call syntax is rejected before argument binding
Linkfunction choose(first: int, second: int) -> int { first }choose(first: 1, 2, second: 3)
Expected result
Expected result
Rejection during parse. Nonzero exit.
TPZ2001function-named-call-syntax atexample.tpz:2:13
Source evidence
- valueCallPrefix evaluates the callee and source-ordered argument expressions once at
151141d7bb44· 946-990 - emitDirectCall stages each argument before binding parameter slots at
151141d7bb44· 1209-1290 - named ownership and spread admission at
151141d7bb44· 366-470 - positional-after-named diagnostics at
151141d7bb44· 492-561 - explicitCallBindings requires the entire declared type-parameter list at
151141d7bb44· 1156-1217 - call-scoped schemes and first type-variable binding at
151141d7bb44· 2004-2052 - parameter-directed type evidence and contextual callback inference at
151141d7bb44· 368-442 - expected call result contributes type evidence at
151141d7bb44· 1445-1467 - lambda and generic function arguments receive expected function types at
151141d7bb44· 167-209 - calleeReference preserves a parenthesized declaration identity at
151141d7bb44· 86-110
Explicit type arguments must supply the full list
Linkfunction choose<T, U>(first: T, second: U) -> T { first }choose<int>(1, "two")
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5510function-type-argument-count atexample.tpz:2:1
Source evidence
- valueCallPrefix evaluates the callee and source-ordered argument expressions once at
151141d7bb44· 946-990 - emitDirectCall stages each argument before binding parameter slots at
151141d7bb44· 1209-1290 - named ownership and spread admission at
151141d7bb44· 366-470 - positional-after-named diagnostics at
151141d7bb44· 492-561 - explicitCallBindings requires the entire declared type-parameter list at
151141d7bb44· 1156-1217 - call-scoped schemes and first type-variable binding at
151141d7bb44· 2004-2052 - parameter-directed type evidence and contextual callback inference at
151141d7bb44· 368-442 - expected call result contributes type evidence at
151141d7bb44· 1445-1467 - lambda and generic function arguments receive expected function types at
151141d7bb44· 167-209 - calleeReference preserves a parenthesized declaration identity at
151141d7bb44· 86-110
A non-generic call has no type arguments
Linkfunction identity(value: int) -> int { value }identity<int>(1)
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5512function-non-generic-type-arguments atexample.tpz:2:1
Source evidence
- valueCallPrefix evaluates the callee and source-ordered argument expressions once at
151141d7bb44· 946-990 - emitDirectCall stages each argument before binding parameter slots at
151141d7bb44· 1209-1290 - named ownership and spread admission at
151141d7bb44· 366-470 - positional-after-named diagnostics at
151141d7bb44· 492-561 - explicitCallBindings requires the entire declared type-parameter list at
151141d7bb44· 1156-1217 - call-scoped schemes and first type-variable binding at
151141d7bb44· 2004-2052 - parameter-directed type evidence and contextual callback inference at
151141d7bb44· 368-442 - expected call result contributes type evidence at
151141d7bb44· 1445-1467 - lambda and generic function arguments receive expected function types at
151141d7bb44· 167-209 - calleeReference preserves a parenthesized declaration identity at
151141d7bb44· 86-110
A later argument cannot replace an inferred type
Linkfunction same<T>(first: T, second: T) -> T { first }same(1, "two")
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5001function-conflicting-inference atexample.tpz:2:9
Source evidence
- valueCallPrefix evaluates the callee and source-ordered argument expressions once at
151141d7bb44· 946-990 - emitDirectCall stages each argument before binding parameter slots at
151141d7bb44· 1209-1290 - named ownership and spread admission at
151141d7bb44· 366-470 - positional-after-named diagnostics at
151141d7bb44· 492-561 - explicitCallBindings requires the entire declared type-parameter list at
151141d7bb44· 1156-1217 - call-scoped schemes and first type-variable binding at
151141d7bb44· 2004-2052 - parameter-directed type evidence and contextual callback inference at
151141d7bb44· 368-442 - expected call result contributes type evidence at
151141d7bb44· 1445-1467 - lambda and generic function arguments receive expected function types at
151141d7bb44· 167-209 - calleeReference preserves a parenthesized declaration identity at
151141d7bb44· 86-110
A required fixed parameter may follow a defaulted one
Linkimport std.io { writeLine }function add(second: int, first: int = 1) -> int { first + second }let step: (int) -> int = (value) => value + 1writeLine("{step(add(2))}")
4
Expected result
Expected result
Execution completes. Exit code 0.
4
Source evidence
- named parameter annotations and bare-self syntax at
151141d7bb44· 148-183 - optional lambda parameter annotations at
151141d7bb44· 200-265 - expected function parameter types and lambda arity at
151141d7bb44· 305-381 - bareSelfParameter at
151141d7bb44· 730-745 - receiver method first-parameter checks at
151141d7bb44· 792-837 - own-module nongeneric nominal receiver implementations at
151141d7bb44· 909-1007 - independent default and required fixed parameter slots at
151141d7bb44· 251-284 - same-name local map insertion at
151141d7bb44· 79-100 - parameter bindings inserted in declaration order at
151141d7bb44· 951-978
An own-module nominal method uses a bare receiver
Linkimport std.io { writeLine }record Counter { value: int }impl Counter {function readValue(self) -> int { self.value }}writeLine("{Counter { value: 3 }.readValue()}")
3
Expected result
Expected result
Execution completes. Exit code 0.
3
Source evidence
- named parameter annotations and bare-self syntax at
151141d7bb44· 148-183 - optional lambda parameter annotations at
151141d7bb44· 200-265 - expected function parameter types and lambda arity at
151141d7bb44· 305-381 - bareSelfParameter at
151141d7bb44· 730-745 - receiver method first-parameter checks at
151141d7bb44· 792-837 - own-module nongeneric nominal receiver implementations at
151141d7bb44· 909-1007 - independent default and required fixed parameter slots at
151141d7bb44· 251-284 - same-name local map insertion at
151141d7bb44· 79-100 - parameter bindings inserted in declaration order at
151141d7bb44· 951-978
Named value parameters need an annotation
Linkfunction identity(value) -> int { 1 }
Expected result
Expected result
Rejection during parse. Nonzero exit.
TPZ2001function-parameter-annotation atexample.tpz:1:24
Source evidence
- named parameter annotations and bare-self syntax at
151141d7bb44· 148-183 - optional lambda parameter annotations at
151141d7bb44· 200-265 - expected function parameter types and lambda arity at
151141d7bb44· 305-381 - bareSelfParameter at
151141d7bb44· 730-745 - receiver method first-parameter checks at
151141d7bb44· 792-837 - own-module nongeneric nominal receiver implementations at
151141d7bb44· 909-1007 - independent default and required fixed parameter slots at
151141d7bb44· 251-284 - same-name local map insertion at
151141d7bb44· 79-100 - parameter bindings inserted in declaration order at
151141d7bb44· 951-978
A contextual lambda must have the expected arity
Linklet step: (int) -> int = (first, second) => first
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5004function-lambda-arity atexample.tpz:1:26
Source evidence
- named parameter annotations and bare-self syntax at
151141d7bb44· 148-183 - optional lambda parameter annotations at
151141d7bb44· 200-265 - expected function parameter types and lambda arity at
151141d7bb44· 305-381 - bareSelfParameter at
151141d7bb44· 730-745 - receiver method first-parameter checks at
151141d7bb44· 792-837 - own-module nongeneric nominal receiver implementations at
151141d7bb44· 909-1007 - independent default and required fixed parameter slots at
151141d7bb44· 251-284 - same-name local map insertion at
151141d7bb44· 79-100 - parameter bindings inserted in declaration order at
151141d7bb44· 951-978
A receiver method requires bare self
Linkrecord Counter { value: int }impl Counter {function readValue(self: Counter) -> int { self.value }}
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5022function-bare-receiver atexample.tpz:3:24
Source evidence
- named parameter annotations and bare-self syntax at
151141d7bb44· 148-183 - optional lambda parameter annotations at
151141d7bb44· 200-265 - expected function parameter types and lambda arity at
151141d7bb44· 305-381 - bareSelfParameter at
151141d7bb44· 730-745 - receiver method first-parameter checks at
151141d7bb44· 792-837 - own-module nongeneric nominal receiver implementations at
151141d7bb44· 909-1007 - independent default and required fixed parameter slots at
151141d7bb44· 251-284 - same-name local map insertion at
151141d7bb44· 79-100 - parameter bindings inserted in declaration order at
151141d7bb44· 951-978
Fixed defaults and a variadic tail compose
Linkimport std.io { writeLine }function total(base: int = 10, ...values: int) -> int {let mut sum: int = basefor value in (values) { sum += value }sum}let tail: Array<int> = [2, 3]writeLine("{total()}")writeLine("{total(1, 2, 3)}")writeLine("{total(...tail)}")writeLine("{total(1, ...tail)}")writeLine("{total(1, ...tail, 4)}")
10 6 15 6 10
Expected result
Expected result
Execution completes. Exit code 0.
10615610
Source evidence
- parameter loop admits variadic markers without a final-position check at
151141d7bb44· 148-183 - fixed parameter defaults and variadic element types are stored separately at
151141d7bb44· 1222-1279 - variadic parameter is bound to Array of its element type at
151141d7bb44· 203-247 - named lookup ranges over fixed parameter count at
151141d7bb44· 234-249 - spread skips fixed slots when already filled or defaulted and precedes named arguments at
151141d7bb44· 412-470 - positional arguments after a spread enter the variadic tail at
151141d7bb44· 492-561 - spread requires a variadic signature at
151141d7bb44· 640-685 - defaultValues excludes the final variadic slot at
151141d7bb44· 1633-1647 - published final variadic parameter and call rules at
151141d7bb44· 96-106
Spread cannot fill a required fixed parameter
Linkfunction need(base: int, ...values: int) -> int { base }need(...[1, 2])
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5004function-spread-skips-fixed atexample.tpz:2:9
A spread cannot fill an unsatisfied required fixed slot. Supply a positional value before the spread, or give the fixed parameter a default.
Source evidence
- parameter loop admits variadic markers without a final-position check at
151141d7bb44· 148-183 - fixed parameter defaults and variadic element types are stored separately at
151141d7bb44· 1222-1279 - variadic parameter is bound to Array of its element type at
151141d7bb44· 203-247 - named lookup ranges over fixed parameter count at
151141d7bb44· 234-249 - spread skips fixed slots when already filled or defaulted and precedes named arguments at
151141d7bb44· 412-470 - positional arguments after a spread enter the variadic tail at
151141d7bb44· 492-561 - spread requires a variadic signature at
151141d7bb44· 640-685 - defaultValues excludes the final variadic slot at
151141d7bb44· 1633-1647
A spread call requires a variadic parameter
Linkfunction one(value: int) -> int { value }one(...[1])
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5004function-spread-requires-rest atexample.tpz:2:1
Source evidence
- parameter loop admits variadic markers without a final-position check at
151141d7bb44· 148-183 - fixed parameter defaults and variadic element types are stored separately at
151141d7bb44· 1222-1279 - variadic parameter is bound to Array of its element type at
151141d7bb44· 203-247 - named lookup ranges over fixed parameter count at
151141d7bb44· 234-249 - spread skips fixed slots when already filled or defaulted and precedes named arguments at
151141d7bb44· 412-470 - positional arguments after a spread enter the variadic tail at
151141d7bb44· 492-561 - spread requires a variadic signature at
151141d7bb44· 640-685 - defaultValues excludes the final variadic slot at
151141d7bb44· 1633-1647
Named call syntax is rejected before argument binding
Linkfunction total(base: int = 10, ...values: int) -> int { base + values.length }total(base: 1, ...[2])
Expected result
Expected result
Rejection during parse. Nonzero exit.
TPZ2001function-named-call-syntax atexample.tpz:2:11
Source evidence
- parameter loop admits variadic markers without a final-position check at
151141d7bb44· 148-183 - fixed parameter defaults and variadic element types are stored separately at
151141d7bb44· 1222-1279 - variadic parameter is bound to Array of its element type at
151141d7bb44· 203-247 - named lookup ranges over fixed parameter count at
151141d7bb44· 234-249 - spread skips fixed slots when already filled or defaulted and precedes named arguments at
151141d7bb44· 412-470 - positional arguments after a spread enter the variadic tail at
151141d7bb44· 492-561 - spread requires a variadic signature at
151141d7bb44· 640-685 - defaultValues excludes the final variadic slot at
151141d7bb44· 1633-1647
Named call syntax is rejected before argument binding
Linkfunction total(base: int = 10, ...values: int) -> int { base + values.length }total(values: [1])
Expected result
Expected result
Rejection during parse. Nonzero exit.
TPZ2001function-named-call-syntax atexample.tpz:2:13
Source evidence
- parameter loop admits variadic markers without a final-position check at
151141d7bb44· 148-183 - fixed parameter defaults and variadic element types are stored separately at
151141d7bb44· 1222-1279 - variadic parameter is bound to Array of its element type at
151141d7bb44· 203-247 - named lookup ranges over fixed parameter count at
151141d7bb44· 234-249 - spread skips fixed slots when already filled or defaulted and precedes named arguments at
151141d7bb44· 412-470 - positional arguments after a spread enter the variadic tail at
151141d7bb44· 492-561 - spread requires a variadic signature at
151141d7bb44· 640-685 - defaultValues excludes the final variadic slot at
151141d7bb44· 1633-1647
A variadic parameter before a fixed parameter
Linkfunction misplaced(...values: int, last: int) -> int { last }
Expected result
Expected result
The final-parameter rule requires rejection. SELF has no placement-diagnostic producer, so the dedicated installed-CLI product test asserts a nonzero exit without assigning a diagnostic code.
This declaration must be rejected. The product check in node - keeps that nonzero-exit expectation. It is marked TODO for the recorded SELF placement-check limitation; its actual result must be reviewed in CI.
Source evidence
- parameter loop admits variadic markers without a final-position check at
151141d7bb44· 148-183 - fixed parameter defaults and variadic element types are stored separately at
151141d7bb44· 1222-1279 - published final variadic parameter and call rules at
151141d7bb44· 96-106
Multiple variadic parameters
Linkfunction repeated(...first: int, ...second: int) -> () {}
Expected result
Expected result
The final-parameter rule requires rejection. SELF has no placement-diagnostic producer, so the dedicated installed-CLI product test asserts a nonzero exit without assigning a diagnostic code.
This declaration must be rejected. The product check in node - keeps that nonzero-exit expectation. It is marked TODO for the recorded SELF placement-check limitation; its actual result must be reviewed in CI.
Source evidence
- parameter loop admits variadic markers without a final-position check at
151141d7bb44· 148-183 - fixed parameter defaults and variadic element types are stored separately at
151141d7bb44· 1222-1279 - published final variadic parameter and call rules at
151141d7bb44· 96-106
Distinct parameter names in functions and lambdas
Linkimport std.io { writeLine }function add(left: int, right: int) -> int { left + right }let addLambda: (int, int) -> int = (left, right) => left + rightwriteLine("{add(1, 2)}")writeLine("{addLambda(3, 4)}")
3 7
Expected result
Expected result
Execution completes. Exit code 0.
37
Source evidence
- issues/31 at
570530e69141 - parameter bindings inserted without a uniqueness check at
570530e69141· 951-978 - named parameter bindings formed without a repeated-name check at
570530e69141· 202-248 - lambda parameter bindings formed without a repeated-name check at
570530e69141· 342-381
A named function rejects a repeated parameter name
Linkfunction choose(value: int, value: int) -> int { value }
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5008bindings-local-same-scope-redeclaration atexample.tpz:1:29
Source evidence
- issues/31 at
570530e69141 - parameter bindings inserted without a uniqueness check at
570530e69141· 951-978 - named parameter bindings formed without a repeated-name check at
570530e69141· 202-248 - lambda parameter bindings formed without a repeated-name check at
570530e69141· 342-381
A lambda rejects a repeated parameter name
Linklet choose: (int, int) -> int = (value, value) => value
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5008bindings-local-same-scope-redeclaration atexample.tpz:1:41
Source evidence
- issues/31 at
570530e69141 - parameter bindings inserted without a uniqueness check at
570530e69141· 951-978 - named parameter bindings formed without a repeated-name check at
570530e69141· 202-248 - lambda parameter bindings formed without a repeated-name check at
570530e69141· 342-381
Separate parameter lists may reuse a name
Linkimport std.io { writeLine }function outer(value: int) -> int {let next: (int) -> int = (value) => value + 1next(value)}writeLine("{outer(4)}")
5
Expected result
Expected result
Execution completes. Exit code 0.
5
Source evidence
- issues/31 at
570530e69141 - parameter bindings inserted without a uniqueness check at
570530e69141· 951-978 - named parameter bindings formed without a repeated-name check at
570530e69141· 202-248 - lambda parameter bindings formed without a repeated-name check at
570530e69141· 342-381
Empty and populated variadic tails without a default
Linkimport std.io { writeLine }function count(...values: int) -> int { values.length }let empty: Array<int> = []writeLine("{count()}")writeLine("{count(1, 2)}")writeLine("{count(...empty)}")
0 2 0
Expected result
Expected result
Execution completes. Exit code 0.
020
Source evidence
- issues/31 at
570530e69141 - parameter parser admits a default after a variadic marker at
570530e69141· 148-183 - variadic defaults are checked as array constants at
570530e69141· 202-248 - defaultValues excludes the final variadic slot at
570530e69141· 1633-1647 - existing expected-token message used by the intended parse rejection at
570530e69141· 195-205
A variadic parameter rejects a default initializer
Linkfunction count(...values: int = [1]) -> int { values.length }
Expected result
Expected result
Rejection during parse. Nonzero exit.
TPZ2001function-variadic-default atexample.tpz:1:31
Source evidence
- issues/31 at
570530e69141 - parameter parser admits a default after a variadic marker at
570530e69141· 148-183 - variadic defaults are checked as array constants at
570530e69141· 202-248 - defaultValues excludes the final variadic slot at
570530e69141· 1633-1647 - existing expected-token message used by the intended parse rejection at
570530e69141· 195-205
A fixed default does not permit a variadic default
Linkfunction count(base: int = 10, ...values: int = [1]) -> int { base + values.length }
Expected result
Expected result
Rejection during parse. Nonzero exit.
TPZ2001function-variadic-default atexample.tpz:1:47
Source evidence
- issues/31 at
570530e69141 - parameter parser admits a default after a variadic marker at
570530e69141· 148-183 - variadic defaults are checked as array constants at
570530e69141· 202-248 - defaultValues excludes the final variadic slot at
570530e69141· 1633-1647 - existing expected-token message used by the intended parse rejection at
570530e69141· 195-205
A name accepts explicit type arguments
Linkimport std.io { writeLine }function identity<T>(value: T) -> T { value }let value: int = identity<int>(1)writeLine("{value}")
1
Expected result
Expected result
Execution completes. Exit code 0.
1
Source evidence
- issues/31 at
570530e69141 - explicit type arguments require an identifier or member-access callee at
570530e69141· 1411-1425 - parenthesized callee preserves declaration identity at
570530e69141· 86-110 - TPZ5002 for an unresolved value read at
570530e69141· 630-653
A member access accepts explicit type arguments
Linkimport std.io { writeLine }import helperslet value: int = helpers.identity<int>(2)writeLine("{value}")
2
export function identity<T>(value: T) -> T { value }
Expected result
Expected result
Execution completes. Exit code 0.
2
Source evidence
- issues/31 at
570530e69141 - explicit type arguments require an identifier or member-access callee at
570530e69141· 1411-1425 - parenthesized callee preserves declaration identity at
570530e69141· 86-110 - TPZ5002 for an unresolved value read at
570530e69141· 630-653
A parenthesized callee infers type arguments
Linkimport std.io { writeLine }function identity<T>(value: T) -> T { value }let value: int = (identity)(3)writeLine("{value}")
3
Expected result
Expected result
Execution completes. Exit code 0.
3
Source evidence
- issues/31 at
570530e69141 - explicit type arguments require an identifier or member-access callee at
570530e69141· 1411-1425 - parenthesized callee preserves declaration identity at
570530e69141· 86-110 - TPZ5002 for an unresolved value read at
570530e69141· 630-653
A parenthesized expression does not introduce explicit type arguments
Linkfunction identity<T>(value: T) -> T { value }let value: int = (identity)<int>(1)
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5002function-parenthesized-callee-type-argument atexample.tpz:2:29
After (identity), <int> is parsed as expression tokens. The name int is therefore a value read without a binding, which produces TPZ5002. Use identity<int>(1) for explicit type arguments or (identity)(1) for inference.
Source evidence
- issues/31 at
570530e69141 - explicit type arguments require an identifier or member-access callee at
570530e69141· 1411-1425 - parenthesized callee preserves declaration identity at
570530e69141· 86-110 - TPZ5002 for an unresolved value read at
570530e69141· 630-653
Parenthesized lambda parameter lists
Linkimport std.io { writeLine }let zero: () -> int = () => 0let one: (int) -> int = (value) => value + 1let typed: (int) -> int = (value: int) => value + 1let pair: (int, int) -> int = (left, right) => left + rightwriteLine("{zero()}")writeLine("{one(1)}")writeLine("{typed(2)}")writeLine("{pair(3, 4)}")
0 2 3 7
Expected result
Expected result
Execution completes. Exit code 0.
0237
Source evidence
- Lambda parser at
d61f9fd0efb0· 200-265 - parseLambdaParameters and parseParenLambda at
9b1bbdeceba9· 200-286 - parseIdentifierAtom current bare lambda dispatch at
9b1bbdeceba9· 1004-1013 - itemBoundary existing statement separator diagnostic at
9b1bbdeceba9· 443-455
A bare lambda parameter is rejected
Linklet step: (int) -> int = value => value + 1
Expected result
Expected result
Rejection during parse. Nonzero exit.
TPZ2001expected-statement-separator atexample.tpz:1:32
Source evidence
- Lambda parser at
d61f9fd0efb0· 200-265 - parseLambdaParameters and parseParenLambda at
9b1bbdeceba9· 200-286 - parseIdentifierAtom current bare lambda dispatch at
9b1bbdeceba9· 1004-1013 - itemBoundary existing statement separator diagnostic at
9b1bbdeceba9· 443-455
Record fields preserve settings when reordered
Linkimport std.io { writeLine }function dimensions(settings: { width: int, height: int }) -> string {"{settings.width}x{settings.height}"}let width = 80let height = 24let describe: ({ width: int, height: int }) -> string = dimensionswriteLine(dimensions({ width: width, height: height }))writeLine(dimensions({ height: height, width: width }))writeLine(describe({ height: height, width: width }))
80x24 80x24 80x24
Expected result
Expected result
Execution completes. Exit code 0.
80x2480x2480x24
Source evidence
- issues/31 at
23582e1d56db
Related diagnostics
LinkThe catalog owns causes, correction examples, and output details. This page owns the function rules.
Wrong number of explicit type arguments