prometeu-studio/docs/specs/compiler-languages/pbs/4. Static Semantics Specification.md
2026-03-27 17:01:40 +00:00

46 KiB

PBS Static Semantics Specification

Status: Draft v1 (Normative)
Scope: binding, callable formation, type checking, and deterministic function application

1. Purpose

This document defines the static semantics of PBS core language.

It is intended to:

  • give deterministic meaning to declarations accepted by the syntax specification,
  • define callable identity and application rules,
  • make type checking stable across implementations,
  • preserve beginner-facing clarity without hidden callable behavior.

2. Core Semantic Model

2.1 Authority and precedence

Normative precedence:

  1. Runtime authority (docs/specs/hardware/topics/chapter-2.md, chapter-3.md, chapter-9.md, chapter-12.md, chapter-16.md)
  2. Bytecode authority (docs/specs/bytecode/ISA_CORE.md)
  3. 3. Core Syntax Specification.md
  4. This document

If a rule here conflicts with higher authority, it is invalid.

2.2 Semantic namespaces

PBS distinguishes at least:

  • type namespace,
  • value namespace,
  • callable namespace.
  • host-owner namespace.

Rules:

  • declare struct, declare service, declare contract, declare error, declare enum, declare callback, and declare builtin type introduce names in the type namespace.
  • declare host introduces names in the host-owner namespace.
  • let, parameters, declare const, and declare global introduce names in the value namespace.
  • declare service Name also introduces the canonical singleton value Name in the value namespace.
  • Top-level fn, service methods, and host methods participate in the callable namespace.
  • fn declarations are not first-class values in v1 core and therefore do not become ordinary value expressions.
  • bind(...) is a callback-formation construct, not a general function-value construct.
  • declare host declarations are reserved to SDK/toolchain-controlled modules and are not formable in ordinary user-authored PBS source.
  • declare builtin type declarations are reserved to SDK/toolchain-controlled modules and are not formable in ordinary user-authored PBS source.
  • A host owner name is not an ordinary value expression in v1 core; it is used only as a qualified host-call owner.
  • The builtin simple types int, float, bool, and str are always present in the type namespace.
  • Builtin simple types are resolved without import or declaration.
  • User-authored type declarations must not reuse builtin simple type names.

2.2.1 Backend-provided symbolic asset surface

PBS static semantics may consume a backend-provided symbolic asset surface.

Rules:

  • The frontend-facing symbolic asset surface is rooted at assets.
  • assets is a compile-time namespace root, not an ordinary runtime value by default.
  • Intermediate assets... nodes are namespace-only and MUST NOT be treated as terminal asset values.
  • Terminal asset leaves are represented statically as Addressable.
  • For PBS v1, Addressable is the canonical public/editorial type shape for symbolic asset references.
  • The PBS frontend may use a fake/frontend-owned Addressable type surface for semantics and tooling.
  • This frontend-owned surface MUST NOT transfer final validation or executable lowering ownership away from the backend.
  • The backend-provided asset surface contract for v1 is List<Addressable(address, asset_id)>.
  • The canonical symbolic identity of an asset reference is its normalized address, not asset_name.
  • asset_name MUST NOT remain the operational compile-time identity for symbolic asset lookup.
  • A terminal asset path MUST NOT also act as a namespace prefix for descendant assets.

2.3 Attribute metadata

Attributes are compile-time metadata attached to declaration surfaces.

Rules:

  • An attribute has no value-level or type-level meaning by itself.
  • Attributes are not first-class values and are not reflectable in v1 core.
  • Attributes do not automatically survive into runtime or bytecode artifacts.
  • An attribute affects runtime artifacts only when another specification defines an explicit lowering for its semantic effect.
  • In v1 core, the normative reserved attributes are Host, Capability, BuiltinType, BuiltinConst, IntrinsicCall, Init, Frame, and InitAllowed.
  • Host is valid only on a host method signature declared directly inside a reserved stdlib/interface-module declare host body.
  • Capability is valid only on a host method signature declared directly inside a reserved stdlib/interface-module declare host body.
  • Init is valid only on a top-level userland fn declaration with lifecycle-admissible signature.
  • Frame is valid only on a top-level userland fn declaration with lifecycle-admissible signature.
  • InitAllowed is valid only on a host method signature declared directly inside a reserved stdlib/interface-module declare host body.
  • Host is invalid on ordinary user-authored modules, top-level fn, struct methods, service methods, callbacks, contracts, and constants.
  • Host metadata is consumed by the compiler during host-binding lowering.
  • The Host attribute syntax itself is not exported as runtime metadata; instead, its canonical identity participates in PBX host-binding emission as defined by the Host ABI Binding specification.
  • BuiltinType is valid only on a reserved top-level declare builtin type declaration.
  • BuiltinConst is valid only on a reserved top-level declare const declaration that omits an initializer.
  • IntrinsicCall is valid only on a method signature declared directly inside a reserved declare builtin type body.
  • Builtin metadata is consumed by the compiler during VM-owned builtin and intrinsic lowering rather than by host-binding lowering.
  • Init and Frame are consumed by lifecycle analysis and lowering rather than by ordinary runtime reflection.
  • InitAllowed is consumed by init-time host-admission analysis and does not become ordinary userland metadata.

2.4 Tuple shapes

Static checking uses tuple shapes as semantic objects.

Definitions:

  • Input tuple shape: ordered sequence of zero or more input slots.
  • Output tuple shape: ordered sequence of zero or more output slots.
  • Each slot has a type and a stable label.

Rules:

  • Tuple shapes in PBS core are always named, except for unit ().
  • Input slot labels come from parameter names and are local frame names.
  • Output slot labels come from return annotations and are visible for multi-slot member projection.
  • Tuple-shape identity for overload and resolution ignores labels.
  • Tuple-shape compatibility requires matching arity and positional slot types.
  • Labels must still be preserved as part of declaration surface and projection metadata.
  • A single-slot tuple shape has a distinguished carrier value type equal to its slot type.
  • In value positions, single-slot tuple values collapse to their carrier value type.
  • Single-slot tuple shapes have no dedicated tuple-literal surface in v1 core.

3. Functions and apply

3.1 Canonical function model

Every callable fn in PBS is modeled statically as:

Fn : InputTuple -> OutputTuple

Rules:

  • fn f(a: A, b: B) has input tuple shape (a: A, b: B).
  • fn f() has empty input tuple shape ().
  • -> void, -> (), and omitted return annotation all denote empty output tuple shape ().
  • -> T denotes a single-slot output tuple shape (value: T).
  • -> (x: T) denotes a single-slot output tuple shape (x: T).
  • -> (x: T, y: U) denotes a two-slot output tuple shape (x: T, y: U).
  • -> T and -> (x: T) are identical for overload identity and differ only in preserved label metadata.
  • optional T is a regular value type and may appear in parameters, locals, fields, and plain returns.
  • optional T with single-slot payload is internally normalized to optional<(value: T)>.
  • optional and optional void are invalid type surfaces.
  • -> result<E> T denotes a result return surface over payload shape T and error type E.
  • -> result<E> T with single-slot payload is internally normalized to result<E, (value: T)>.
  • -> result<E> is valid and denotes a result return surface with unit payload.
  • A function return surface may be plain or result<E>.
  • A function return surface must not combine optional and result in the same declaration.
  • result<E> wraps the declared success payload; the payload itself may be scalar or a named tuple shape.

3.2 Callable categories

The following constructs are callable in v1 core:

  • top-level fn,
  • struct methods on concrete receivers,
  • contract values,
  • callback values,
  • service methods,
  • host methods on declared host owners,
  • builtin intrinsic methods on declared builtin types.

Rules:

  • All callable categories use the same input/output tuple model.
  • A struct method is identified by (struct type, method name, input tuple shape excluding the receiver slot, output tuple shape).
  • A contract value is identified at the type level by its contract type and at runtime by an underlying concrete struct instance or service singleton value plus its selected implementation.
  • A callback declaration defines a nominal callable type with exactly one input/output tuple shape.
  • A service method is identified by (service name, method name, input tuple shape, output tuple shape).
  • A host method is identified by (host owner name, method name, input tuple shape, output tuple shape) within the PBS declaration surface.
  • A builtin intrinsic method is identified within PBS declaration surface by (builtin type name, method name, input tuple shape, output tuple shape) and lowers further by canonical builtin/intrinsic metadata.
  • A top-level fn is identified by (name, input tuple shape, output tuple shape).
  • A callback value is identified at the type level by its declared callback type and signature.
  • Contract signatures and barrel signatures must preserve the same callable shape.
  • Canonical host primitive identity and binding are governed by the Host ABI Binding specification rather than by ordinary v1 core declaration identity alone.

3.3 Declaration validity

Rules:

  • Parameter names in a single ParamList must be unique.
  • Output labels in a named tuple return annotation must be unique.
  • Error case labels in a single declare error body must be unique.
  • Enum case labels in a single declare enum must be unique.
  • Enum case integer identifiers in a single declare enum must be unique.
  • Two declarations with the same callable name and the same input/output tuple shape are duplicates.
  • Changing only parameter names does not create a new callable.
  • Changing only input labels in ParamList does not create a new callable.
  • Changing only output labels does not create a new callable.
  • mod.barrel function entries are matched by callable identity, ignoring labels.
  • Barrel labels may differ from implementation labels.
  • Any return surface that combines optional and result is statically invalid.
  • Any payload-less optional type surface is statically invalid.
  • Any optional void type surface is statically invalid.
  • A declare global declaration must include an explicit type annotation.
  • A declare global declaration must include an explicit initializer.
  • declare global initializer analysis is dependency-graph based rather than source-order based.
  • A declare global initializer may depend on other visible globals only through their canonical owner identity.
  • Cycles between globals are statically invalid.
  • A global import is valid only when the imported symbol resolves through a global entry in mod.barrel.
  • Imported visible names from fn, service, global, and const must not collide with already-visible names in those same cross-category spaces; aliasing is required to disambiguate.
  • [Init] and [Frame] are valid only on top-level fn declarations of shape fn name() -> void.
  • [Frame] is required exactly once for an executable project.
  • [Init] is optional.
  • A file may declare at most one [Init].
  • The [Init] colocated with [Frame] is the project init.
  • Project init must be colocated with [Frame].
  • InitAllowed is valid only on SDK host methods and is invalid on userland fn, global, service, const, struct, contract, and callback surfaces.
  • A host method signature in a reserved stdlib/interface module MUST carry exactly one Host attribute.
  • A host method signature in a reserved stdlib/interface module MUST carry exactly one Capability attribute.
  • A Host attribute MUST declare exactly the named arguments module, name, and version.
  • Host.module and Host.name MUST be non-empty string literals.
  • Host.version MUST be a positive integer literal.
  • A Capability attribute MUST declare exactly the named argument name.
  • Capability.name MUST be a non-empty lowercase capability identifier.
  • Two host methods in the same resolved stdlib environment MUST NOT lower to the same canonical (module, name, version) unless they denote the same host method declaration after project/module resolution.
  • A declare const declaration must include an explicit type annotation.
  • A declare const declaration without BuiltinConst metadata must include an initializer.
  • A declare const declaration with BuiltinConst metadata must omit the initializer.
  • A non-builtin declare const initializer must be a constant expression.
  • declare const dependency resolution is module-wide and must not depend on source-file processing order.
  • declare const dependencies must form an acyclic graph.
  • A BuiltinType attribute MUST declare exactly the named arguments name and version.
  • BuiltinType.name MUST be a non-empty string literal.
  • BuiltinType.version MUST be a positive integer literal.
  • Two builtin type declarations in the same resolved stdlib environment MUST NOT lower to the same canonical (name, version) unless they denote the same builtin declaration after project/module resolution.
  • A builtin field type must be builtin-layout-admissible as defined by the builtin/intrinsics specification.
  • Builtin field slot offsets are inferred by the compiler from canonical field order and flattened builtin layout width.
  • Builtin layout composition must be acyclic.
  • A builtin field that depends on a builtin layout with unresolved flattened width is statically invalid.
  • IntrinsicCall MUST declare exactly the named argument name and MAY additionally declare version.
  • IntrinsicCall.name MUST be a non-empty string literal.
  • IntrinsicCall.version, when present, MUST be a positive integer literal.
  • Two builtin methods in the same canonical builtin owner/version line MUST NOT lower to the same canonical intrinsic (owner, name, version) unless they denote the same builtin method declaration after project/module resolution.
  • BuiltinConst MUST declare exactly the named arguments target, name, and version.
  • BuiltinConst.target and BuiltinConst.name MUST be non-empty string literals.
  • BuiltinConst.version MUST be a positive integer literal.
  • Two builtin constant declarations in the same resolved stdlib environment MUST NOT lower to the same canonical (target, name, version) unless they denote the same builtin constant declaration after project/module resolution.
  • Symbolic asset references admitted by the frontend MUST resolve against the backend-provided Addressable surface rather than by direct packer queries.
  • A valid symbolic asset reference MUST correspond to a terminal Addressable entry in the backend-provided surface.
  • Intermediate assets... namespace nodes MUST NOT type-check as Addressable values.

3.4 Constant expressions

PBS core v1 restricts top-level constant evaluation to a deterministic compile-time subset.

Rules:

  • A non-builtin declare const initializer is evaluated at compile time.
  • A non-builtin declare const may depend only on previously resolved visible declare const declarations and enum case values.
  • Cross-file constant resolution within a module is defined by dependency analysis, not source-file order.
  • Constant evaluation fails if the dependency graph contains a cycle.
  • The allowed constant-expression forms in v1 core are:
    • scalar literals: IntLit, FloatLit, StringLit, BoolLit,
    • enum case paths EnumName.caseName,
    • a reference to another visible declare const,
    • unary - applied to an integer or float constant expression,
    • unary not or ! applied to a boolean constant expression,
    • binary arithmetic operators over numeric constant expressions,
    • binary comparison operators over compatible constant expressions,
    • binary boolean operators over boolean constant expressions.
  • A non-builtin declare const initializer must not use:
    • callable application or call sugar,
    • new,
    • bind(...),
    • some(...) or none,
    • tuple expressions,
    • block expressions,
    • if, switch, handle, else extraction, or ! result propagation,
    • method calls or member access except enum case paths.
  • For non-builtin declare const, the initializer type must be compatible with the declared const type.
  • A BuiltinConst declaration is not part of ordinary compile-time constant evaluation and lowers through VM-owned builtin constant materialization instead.
  • A declare const exported through mod.barrel may be imported from another module only through ordinary module import rules.
  • A declare const does not introduce mutable runtime storage; implementations may inline, fold, or materialize it as an immutable constant as long as observable semantics remain unchanged.
  • These declare const rules do not define declare global initializer admissibility.
  • declare global initializers are not general procedural bootstrap surfaces.
  • In v1, a declare global initializer may use:
    • primitive literals and simple value operations,
    • value-bearing member access,
    • new Struct(...),
    • and reads of other globals that preserve an acyclic dependency graph.
  • In v1, a declare global initializer must reject:
    • top-level fn calls,
    • some(...),
    • none,
    • if,
    • and switch.

3.5 Enum declarations and values

Rules:

  • declare enum Name(case1, case2, ...); introduces a nominal enum type Name.
  • declare enum Name(case1 = n1, case2 = n2, ...); introduces explicit integer identifiers for enum cases.
  • If no enum case uses an explicit identifier, identifiers are assigned as 0..n-1 in declaration order.
  • If any enum case uses an explicit identifier, then every enum case in that declaration must use an explicit identifier.
  • Name.case is a valid enum value surface only when Name resolves to a declared enum type and case resolves to a declared case of that enum.
  • The static type of Name.case is the enum type Name.
  • Error labels are not enum cases and must not be used where enum case values are required.

3.6 Structs, services, methods, and contracts

Rules:

  • Struct values are heap-backed reference values in v1 core.
  • Service values are canonical module-owned singleton values in v1 core.
  • new Struct(args...) allocates a new struct instance of the declared struct type.
  • new Struct.ctorName(args...) invokes a named constructor declared in the enclosing struct body and produces a new struct instance of that type.
  • Struct construction arguments are matched positionally to the declared struct field order.
  • Assigning a struct value copies the reference, not the underlying field storage.
  • Assigning a service value copies the same canonical singleton identity; it does not create a new instance.
  • A struct field with no access modifier is readable and writable only from methods and ctor bodies of its enclosing struct.
  • A pub struct field is externally readable but remains externally non-assignable.
  • A pub mut struct field is externally readable and externally assignable.
  • An assignment target expr.name to a struct field is valid only when the access site is permitted to write that field.
  • Writes to a private struct field are valid only through this.name inside methods and ctor bodies of the enclosing struct.
  • A struct method declared in a struct body has an implicit receiver binding this of type Self.
  • A service method declared in a service body has an implicit receiver binding this of type Self.
  • this is valid only inside struct method, service method, and ctor bodies.
  • Self is valid only in struct method, service method, and ctor declarations and denotes the enclosing owner type.
  • The enclosing struct name is in scope inside its own field declarations, enabling self-referential field types.
  • A named ctor declared in a struct body is not a method, but it does introduce an implicit under-construction this.
  • A ctor has no declared return annotation and is not a fn.
  • A ctor completes by falling off the end of its body after initializing the enclosing struct instance.
  • Every field of the enclosing struct must be definitely assigned on every completing ctor path.
  • Named constructors participate in overload resolution by name plus parameter signature.
  • return; and return expr; are both invalid inside ctor bodies.
  • Methods declared in a struct body belong to the direct method namespace of that concrete struct type.
  • Methods declared in a service body belong to the direct method namespace of that concrete service type.
  • receiver.method(...) on a concrete struct receiver resolves only against methods declared directly in that struct body.
  • receiver.method(...) on a service receiver resolves only against methods declared directly in that service body.
  • receiver.ctorName(...) is never valid on a concrete struct value.
  • Service values are ordinary value expressions and may be assigned, passed, and returned under their declared service type.
  • Services do not have fields.
  • Services do not declare named constructors and are never targets of new.
  • Contract implementations do not inject their methods into the direct method namespace of the concrete struct or service.
  • implements Contract for Owner using s { ... } is unique per (Contract, Owner) pair, where Owner is a declared struct or service.
  • A contract for a struct or service may be implemented only in the module where that owner itself is declared.
  • Contracts may be declared in other modules and implemented for the local struct or service in its owner module.
  • An implements block has no independent visibility; its observability follows the visibility of the owner declaration, and the contract must also be nameable at the use site.
  • Methods declared directly inside a struct or service follow the visibility of their enclosing owner declaration; they do not have independent visibility.
  • A struct value or service singleton value may be coerced to a contract value when exactly one visible implements Contract for Owner ... applies.
  • Contract coercion may happen by explicit expr as ContractType or by contextual typing in assignments, parameter passing, and returns.
  • as in expression position is reserved for explicit contract coercion only; no other cast semantics exist in v1 core.
  • The contract name in expr as ContractName must be a simple visible identifier resolved through ordinary scope and import rules.
  • A contract value carries the underlying struct reference or service singleton value together with the selected contract implementation for runtime dispatch.
  • Calling contractValue.method(...) uses dynamic dispatch through the contract value.
  • Calling structValue.contractMethod(...) is not valid unless that method is also declared directly on the struct.
  • Calling serviceValue.contractMethod(...) is not valid unless that method is also declared directly on the service.
  • Structs do not have static methods in v1 core; named constructors are their only type-qualified callable members.
  • Services are accessed through their canonical singleton value, not through new or static method declarations.

Example:

declare contract StasisProcess {
  fn stasis() -> int;
}

declare struct Struct(pub a: int) {
  fn compute() -> int {
    return this.a;
  }

  ctor createWithSomethingSpecial(x: int) {
    this.a = x;
  }
}

implements StasisProcess for Struct using s {
  fn stasis() -> int {
    return s.compute();
  }
}

fn demo() -> int {
  let s = new Struct(1);
  let s2 = new Struct.createWithSomethingSpecial(2);
  let sp: StasisProcess = s;
  let sp2 = s as StasisProcess;
  let a = s.a;
  let _ = s2.compute();
  let b = sp.stasis();
  let c = sp2.stasis();
  return a + b + c;
}

3.7 Nominal callbacks

Rules:

  • declare callback Name(params...) -> R; introduces a nominal callback type Name.
  • A callback type is not a fn, and a fn declaration does not implicitly define a callback type.
  • A value of callback type may be formed only from a compatible top-level fn in v1 core.
  • A value of callback type may also be formed by bind(context, fn_name) when the surrounding static type context expects a callback type.
  • Service methods are not assignable to callback values in v1 core.
  • Host methods are not assignable to callback values in v1 core.
  • Callback values do not capture local environment implicitly.
  • Assignment of a top-level fn to a callback type succeeds only when exactly one visible overload matches the callback signature.
  • bind(context, fn_name) succeeds only when exactly one visible top-level fn overload matches after consuming the first parameter with context.
  • Valid expected callback contexts for bind(context, fn_name) include variable initialization with an explicit callback type, assignment to an explicitly typed callback location, argument passing to a callback-typed parameter, and return from a callback-typed function.
  • The context expression in bind(context, fn_name) is evaluated once at bind time and stored explicitly in the resulting callback value.
  • When that stored context is identity-bearing, the resulting callback preserves the same captured identity rather than copying an independent underlying object.
  • bind(context, fn_name) does not introduce general closures; it is explicit partial binding of the first parameter only.
  • Callback compatibility ignores labels and compares ordered input/output slot types plus the return-surface kind.

Example:

declare callback TickCb(dt: int) -> void;

fn on_tick(dt: int) -> void {
}

fn install_tick() -> void {
  let cb: TickCb = on_tick;
  let _ = cb;
}

Bound example:

declare callback UpdateCb(dt: int) -> void;

declare struct Enemy(hp: int);

fn update_enemy(self: Enemy, dt: int) -> void {
}

fn install_update(enemy: Enemy) -> void {
  let cb: UpdateCb = bind(enemy, update_enemy);
  let _ = cb;
}

Ambiguous overload example:

declare callback TickCb(dt: int) -> void;

fn tick(dt: int) -> void {
}

fn tick(time: int) -> void {
}

fn install_tick() -> void {
  let cb: TickCb = tick; // error: ambiguous overloaded fn assignment
}

3.8 Canonical application

The canonical surface form for function application is:

callee apply arg

Rules:

  • apply is an infix operator that is right-associative.
  • f1 apply f2 apply x is defined as f1 apply (f2 apply x).
  • f1 apply f2 apply f3 apply params is defined as f1 apply (f2 apply (f3 apply params)).
  • The left operand of apply must resolve to one of: a callable candidate set, a bound struct method target, a bound service method target, a contract-method target, or a callback value.
  • The right operand of apply is an argument expression.
  • If the callable input arity is 0, the argument expression must be ().
  • If the callable input arity is 1, the argument expression is checked against the carrier type of the single input slot.
  • If the callable input arity is 2..6, the argument expression must have a named tuple shape with matching arity and positional types.
  • Input labels are not used for compatibility.
  • No single-slot tuple argument literal is required or recognized in surface syntax.
  • There is no partial application in v1 core.
  • There is no currying in v1 core.
  • Chained apply is nested ordinary application, not callable composition as a first-class value.
  • Callback values, service methods, host methods, struct methods, and contract-method targets use the same apply rules as top-level fn.

3.9 Call sugar

The surface form:

callee(arg1, arg2, ..., argN)

desugars to the canonical apply surface according to arity.

Rules:

  • The desugaring is purely syntactic.
  • f() is exact sugar for f apply ().
  • f(x) is exact sugar for f apply x.
  • f(x1, x2, ..., xn) for n >= 2 is exact sugar for f apply (x1, x2, ..., xn).
  • No semantic distinction exists between direct call sugar and its canonical apply form.
  • When (arg1, ..., argN) is used as positional tuple sugar, the callee input tuple shape supplies the effective labels.
  • All overload resolution, diagnostics, and type checking are defined on the canonical apply form.

3.10 Result type of application

Rules:

  • If the selected callable output arity is 0, the result of apply is unit.
  • If the selected callable output arity is 1, the result of apply is the carrier type of the single output slot.
  • If the selected callable output arity is 2..6, the result of apply has the named output tuple shape of the selected callable.
  • Multi-slot output tuple results expose their labels for member projection.
  • If the selected callable is a callback value, the visible multi-slot output labels come from the callback type rather than from the original source fn.
  • If the selected callable returns (), the application result is unit.

Example:

fn func(a: int, b: int) -> (c: int, d: float) {
  return (c: a + b, d: 2.0);
}

fn demo_apply() -> int {
  let params: (a: int, b: int) = (1, 2);
  let r = func apply params;
  let c = r.c;
  let d = r.d;
  let _ = d;
  return c;
}

In the example above:

  • func has input tuple shape (a: int, b: int),
  • func has output tuple shape (c: int, d: float),
  • r has named output tuple type (c: int, d: float),
  • r.c has type int,
  • r.d has type float.

Single-slot example:

fn plus_one(x: int) -> int {
  return x + 1;
}

fn demo_single() -> void {
  let r = plus_one apply 1;
  if r == 2 {
  }
}

In the example above:

  • plus_one has input tuple shape (x: int),
  • the argument 1 is accepted directly as the carrier value for the single input slot,
  • plus_one has output tuple shape (value: int),
  • the value produced by plus_one apply 1 has carrier type int,
  • r.value is not required and should not be used.

3.11 Overload resolution

Rules:

  • Overload resolution starts from the visible callable candidates with the selected name.
  • Candidate filtering first uses callable kind and input tuple arity.
  • Remaining candidates are filtered by positional input type compatibility.
  • If exactly one candidate remains, resolution succeeds.
  • If multiple candidates remain, expected output types or expected multi-slot output tuple shapes may be used as a tie-breaker when statically available.
  • If multiple candidates still remain, the apply is ambiguous and compilation fails.
  • If no candidate remains, the apply is unresolved and compilation fails.
  • Overloads that differ only by labels are duplicates and are forbidden.
  • Overloads that differ only by output positional types are permitted, but uncontextualized apply sites may become ambiguous.

3.12 Member access and projection

Rules:

  • TypeName.case is valid when TypeName resolves to a declared enum type and case resolves to one of its declared enum cases.
  • TypeName.case has static type TypeName.
  • expr.name is valid when the static type of expr is a declared struct containing field name and the access site is permitted to read that field.
  • For struct field access, the projected member type is the declared field type.
  • Reads of a private struct field are valid only through this.name inside methods and ctor bodies of the enclosing struct.
  • expr.method is a valid method-call target when the static type of expr is a concrete struct type declaring method method.
  • expr.method over a concrete struct receiver does not search visible contract implementations.
  • expr.method is a valid method-call target when the static type of expr is a concrete service type declaring method method.
  • expr.method over a concrete service receiver does not search visible contract implementations.
  • HostName.method is a valid method-call target when HostName resolves to a visible declared host owner whose body declares method method.
  • TypeName.ctorName is not a general member access surface; named constructors are entered only through new TypeName.ctorName(...).
  • expr.method is a valid method-call target when the static type of expr is a contract value whose contract declares method method.
  • Bare extraction of a struct, service, host, builtin, or contract method as a value is invalid in v1 core because methods are not first-class.
  • expr.name is valid when the static type of expr is a multi-slot named output tuple containing label name.
  • For named output tuples, the projected member type is the type of the corresponding output slot.
  • Projection is label-based, not position-based.
  • expr.name is valid when the static type of expr is a builtin type declaring projection field name.
  • For builtin projection fields, the projected member type is the declared builtin field type.
  • expr.method is a valid method-call target when the static type of expr is a builtin type declaring intrinsic member method.
  • expr.hasSome() and expr.hasNone() are valid intrinsic method-call surfaces when the static type of expr is optional<P>.
  • expr.hasSome() and expr.hasNone() both have static type bool.
  • expr.name() and expr.key() are valid intrinsic method-call surfaces when the static type of expr is an enum type.
  • expr.name() has static type str.
  • expr.key() has static type int.
  • Access to a missing output label is a compile-time error.
  • Access to a missing struct field is a compile-time error.
  • Read access to an inaccessible private struct field is a compile-time error.
  • Write access to an inaccessible or non-pub mut struct field is a compile-time error.
  • Access to a missing struct method is a compile-time error.
  • Access to a missing service method is a compile-time error.
  • Access to a missing host method is a compile-time error.
  • Access to a missing contract method is a compile-time error.
  • Access to a missing builtin projection field is a compile-time error.
  • Access to a missing builtin intrinsic member is a compile-time error.
  • Member projection is not defined for single-slot collapsed carrier values.

3.13 Block expressions and function fallthrough

Rules:

  • A block expression with a TailExpr has the static type of that final expression.
  • A block expression without a TailExpr has static type unit.
  • Only the final unsemicoloned expression in a block contributes to the block result.
  • A function body does not implicitly return its trailing TailExpr; explicit return and ordinary control-flow rules still apply.
  • Reaching the end of a plain non-unit function is a compile-time error unless all control-flow paths are proven to return explicitly.
  • Reaching the end of a void or () function is valid and yields unit.
  • Reaching the end of an optional P function yields none.
  • Reaching the end of a result<E> function is a compile-time error.

3.14 Loop forms

Rules:

  • while cond { ... } requires cond to have static type bool.
  • for i: T from start until end { ... } is the declarative counted loop form in v1 core.
  • for i: T from start until end step s { ... } is the explicit-step counted loop form in v1 core.
  • In for, the declared iteration variable i introduces a loop-local binding of type T.
  • start, end, and step expressions in for must be type-compatible with the declared iteration type T.
  • If step is omitted, the loop uses the integer value 1 of the iteration type domain.
  • for iteration proceeds by evaluating start, end, and optional step once before loop entry.
  • for ... until ... uses an exclusive upper bound.
  • continue inside while transfers control to the next condition check.
  • continue inside for transfers control to the step update and then to the next bound check.
  • break exits the nearest enclosing loop.

3.15 if and switch conditional expressions

Rules:

  • In if cond { a } else { b }, cond must have static type bool.
  • Only the selected branch is evaluated at runtime.
  • The branch expressions a and b must have the same static type, subject only to ordinary contextual typing already defined elsewhere in the language.
  • The static result type of the if expression is that shared static type.
  • In switch selector { pattern: { ... }, ... }, the selector expression is evaluated once.
  • switch accepts enum selectors and literal-comparable scalar selectors; error selectors are invalid.
  • Each non-wildcard switch arm pattern must be unique within the same switch.
  • Each non-wildcard switch arm pattern must be type-compatible with the selector expression.
  • Enum-case switch patterns must resolve to declared cases of the selector's enum type.
  • Error labels are not valid switch patterns.
  • default and _ are equivalent wildcard spellings for switch.
  • At most one wildcard arm may appear in a switch.
  • A switch must not mix default and _ in the same arm set.
  • The blocks of all switch arms must have the same static type, subject only to ordinary contextual typing already defined elsewhere in the language.
  • The static result type of the switch expression is that shared static type.
  • A switch used as an ExprStmt is valid when the selected arm blocks evaluate to unit.
  • A switch expression used in a value-required position must be exhaustive, either by a wildcard arm or by another statically proven exhaustive case set.

3.16 optional construction and extraction

Rules:

  • some(expr) forms an optional<P> value where P is the static type of expr, unless an expected optional<Q> context is present and constrains expr to be compatible with Q.
  • some(expr) is invalid when the payload normalizes to void.
  • none is valid only when an expected optional<P> type is available from context.
  • opt else fallback requires the left operand to have static type optional<P>.
  • In opt else fallback, the fallback expression must be compatible with payload shape P.
  • The static result type of opt else fallback is the carrier value type for single-slot payloads and the named tuple payload shape for multi-slot payloads.
  • opt else fallback does not introduce implicit mutation or unwrapping side effects beyond normal expression evaluation.

3.17 result<E> return flow and handling

Rules:

  • return ok(expr); is valid only inside a function whose declared return surface is result<E> P.
  • In return ok(expr);, expr must be compatible with the declared success payload shape P.
  • return err(E.label); is valid only inside a function whose declared return surface is result<E> P.
  • In return err(E.label);, the error path must resolve to a declared case of the same error type E.
  • ok(...) and err(...) are special result-flow forms rather than ordinary value expressions.
  • expr! requires expr to have static type result<E> P.
  • expr! is valid only when the enclosing function returns result<E> Q for the same error type E.
  • The static result type of expr! is the extracted success payload shape P, collapsed to its carrier value when single-slot.
  • handle expr { ... } requires expr to have static type result<E1> P.
  • handle expr { ... } is valid only when the enclosing function returns result<E2> Q for some payload Q.
  • Each non-wildcard handle arm must match a distinct declared case of E1.
  • handle arms must be exhaustive over E1, either by naming all cases exactly once or by providing _.
  • The static result type of handle expr { ... } is the extracted success payload shape P, collapsed to its carrier value when single-slot.
  • A short handle arm E1.case -> E2.case is valid only when E2.case resolves to a declared case of the target error type E2.
  • A block handle arm must terminate with either ok(payload) or err(E2.case).
  • In a handle arm, ok(payload) is valid only when payload is compatible with the success payload shape P.
  • In a handle arm, err(E2.case) is valid only when E2.case resolves to a declared case of the target error type E2.
  • In a handle arm, ok(payload) yields the value of the handle expression, while err(E2.case) performs immediate enclosing-function return.

3.18 Required static diagnostics

At minimum, deterministic static diagnostics are required for:

  • duplicate parameter names in fn declarations,
  • duplicate declaration names in the same namespace/scope,
  • duplicate output labels in named tuple returns,
  • duplicate error case labels in declare error,
  • duplicate enum case labels in declare enum,
  • duplicate enum case identifiers in declare enum,
  • invalid mixed implicit/explicit enum case identifiers,
  • invalid this usage outside struct method, service method, or ctor body,
  • invalid Self type usage outside struct method, service method, or ctor declarations,
  • invalid struct method declaration shape,
  • invalid service method declaration shape,
  • invalid host declaration shape,
  • invalid host method declaration shape,
  • invalid attribute surface,
  • invalid Host attribute target,
  • invalid Capability attribute target,
  • invalid BuiltinType attribute target,
  • invalid BuiltinConst attribute target,
  • invalid IntrinsicCall attribute target,
  • duplicate Host attribute on one host method,
  • missing required Host attribute on a reserved host method,
  • malformed Host(module=..., name=..., version=...) argument set,
  • invalid empty Host.module or Host.name,
  • invalid non-positive Host.version,
  • malformed Capability(name=...) argument set,
  • invalid empty or non-canonical Capability.name,
  • invalid builtin declaration shape,
  • invalid builtin method declaration shape,
  • missing required BuiltinType attribute on a reserved builtin declaration,
  • malformed BuiltinType(name=..., version=...) argument set,
  • invalid empty BuiltinType.name,
  • invalid non-positive BuiltinType.version,
  • malformed IntrinsicCall(name=..., version=...) argument set,
  • invalid empty IntrinsicCall.name,
  • invalid non-positive IntrinsicCall.version,
  • malformed BuiltinConst(target=..., name=..., version=...) argument set,
  • invalid empty BuiltinConst.target or BuiltinConst.name,
  • invalid non-positive BuiltinConst.version,
  • builtin field type not builtin-layout-admissible,
  • cyclic builtin layout composition,
  • unresolved flattened builtin layout width,
  • duplicate canonical builtin type identity,
  • duplicate canonical builtin intrinsic identity,
  • duplicate canonical builtin constant identity,
  • invalid ctor declaration shape,
  • invalid return inside ctor,
  • incomplete field initialization on a completing ctor path,
  • duplicate implements Contract for Owner pair,
  • implements declared outside the owner module of the struct or service,
  • incompatible or missing contract method implementation,
  • invalid contract coercion from concrete struct or service value,
  • unresolved barrel function signature after label-insensitive matching,
  • unresolved host barrel entry,
  • unresolved callback barrel entry,
  • invalid enum case path,
  • invalid enum intrinsic member call,
  • invalid named constructor target,
  • unresolved named constructor overload,
  • ambiguous named constructor overload,
  • incompatible top-level fn assignment to callback type,
  • ambiguous overloaded top-level fn assignment to callback type,
  • attempted assignment of service method to callback type,
  • attempted assignment of host method to callback type,
  • use of bind(...) without an expected callback type,
  • incompatible bind(...) context type for callback target,
  • ambiguous overloaded top-level fn target in bind(...),
  • apply on a non-callable target,
  • arity mismatch in apply,
  • argument type mismatch in apply,
  • apply chain with incompatible intermediate carrier/tuple shape,
  • ambiguous overload resolution,
  • unresolved callable application,
  • possible fallthrough of plain non-unit function,
  • non-boolean while condition,
  • invalid for iteration type,
  • incompatible for bound or step type,
  • non-boolean if expression condition,
  • incompatible if expression branch types,
  • duplicate switch arm pattern,
  • mixed default and _ wildcard arms in switch,
  • invalid switch selector type,
  • selector-pattern type mismatch in switch,
  • invalid enum case switch pattern,
  • incompatible switch arm block types,
  • non-exhaustive switch expression in value position,
  • invalid mixed optional/result return surface,
  • invalid payload-less optional type surface,
  • invalid optional void type surface,
  • invalid reuse of a builtin simple type name for a user-authored type declaration,
  • missing explicit type annotation on declare const,
  • missing initializer on non-builtin declare const,
  • non-constant declare const initializer,
  • incompatible declare const initializer type,
  • cyclic dependency among declare const declarations,
  • unresolved referenced declare const in constant evaluation,
  • invalid none usage without expected optional type,
  • invalid else extraction over non-optional operand,
  • fallback type mismatch in else extraction,
  • invalid some(...) construction form,
  • use of ok(...) or err(...) outside allowed result-flow positions,
  • invalid ok(...) construction form,
  • invalid err(...) construction form,
  • invalid error label in err(...),
  • invalid ! propagation on non-result expression,
  • mismatched error type in ! propagation,
  • invalid handle on non-result expression,
  • non-exhaustive handle mapping,
  • duplicate handle mapping arm,
  • invalid mapped error label in handle,
  • invalid ok(...) payload in handle,
  • invalid terminal form of a handle arm block,
  • attempted use of a single-slot tuple literal where no such surface form exists,
  • member access on a missing struct field,
  • read access to an inaccessible private struct field,
  • write access to an inaccessible or non-pub mut struct field,
  • member access on a missing struct method,
  • member access on a missing service method,
  • member access on a missing host method,
  • member access on a missing contract method,
  • member access on a missing builtin projection field,
  • member access on a missing builtin intrinsic member,
  • bare method extraction from a struct, service, host, builtin, or contract value,
  • member projection on a single-slot carrier value,
  • invalid optional intrinsic member call,
  • invalid builtin intrinsic member call,
  • member projection on a missing named output label.