# 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`. - 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`, `AssetLowering`, `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. - `AssetLowering` 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. - `AssetLowering` MUST declare exactly the named argument `param`. - `AssetLowering.param` MUST be a zero-based integer literal that names a valid host parameter position. - The parameter targeted by `AssetLowering.param` MUST be statically typed as `Addressable`. - `AssetLowering` metadata is consumed by backend-owned host-call lowering to rewrite the targeted symbolic `Addressable` operand into runtime-facing `asset_id`. - 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: ```text 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 T` denotes a result return surface over payload shape `T` and error type `E`. - `-> result T` with single-slot payload is internally normalized to `result`. - `-> result` is valid and denotes a result return surface with unit payload. - A function return surface may be plain or `result`. - A function return surface must not combine `optional` and `result` in the same declaration. - `result` 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. - An expression statement MAY evaluate an expression that has a materialized value type. - When an expression statement ignores a materialized value, PBS v1 MUST emit the ignored-value warning defined by the diagnostics specification. - Unit-like expression statements MUST NOT emit the ignored-value warning. ### 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: ```pbs 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: ```pbs 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: ```pbs 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: ```pbs 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: ```pbs 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: ```pbs 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: ```pbs 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: ```pbs 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

`. - `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` 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

` value where `P` is the static type of `expr`, unless an expected `optional` 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

` type is available from context. - `opt else fallback` requires the left operand to have static type `optional

`. - 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` return flow and handling Rules: - `return ok(expr);` is valid only inside a function whose declared return surface is `result 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 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 P`. - `expr!` is valid only when the enclosing function returns `result 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 P`. - `handle expr { ... }` is valid only when the enclosing function returns `result 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.