prometeu-studio/docs/specs/compiler-languages/pbs/4. Static Semantics Specification.md
2026-03-27 18:23:20 +00:00

834 lines
47 KiB
Markdown

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