705 lines
37 KiB
Markdown
705 lines
37 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`, and `declare callback` introduce names in the type namespace.
|
|
- `declare host` introduces names in the host-owner namespace.
|
|
- `let`, parameters, and `declare const` 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.
|
|
- 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.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 only normative reserved attribute is `Host`.
|
|
- `Host` is valid only on a host method signature declared directly inside a reserved stdlib/interface-module `declare host` body.
|
|
- `Host` is invalid on ordinary user-authored modules, top-level `fn`, struct methods, service methods, callbacks, contracts, and constants.
|
|
- `Host` metadata is consumed by the compiler during host-binding lowering.
|
|
- The `Host` attribute syntax itself is not exported as runtime metadata; instead, its canonical identity participates in PBX host-binding emission as defined by the Host ABI Binding specification.
|
|
|
|
### 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.
|
|
|
|
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 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 host method signature in a reserved stdlib/interface module MUST carry exactly one `Host` 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.
|
|
- 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` 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.
|
|
|
|
### 3.4 Constant expressions
|
|
|
|
PBS core v1 restricts top-level constant evaluation to a deterministic compile-time subset.
|
|
|
|
Rules:
|
|
|
|
- A `declare const` initializer is evaluated at compile time.
|
|
- A `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 `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.
|
|
- The initializer type must be compatible with the declared `const` type.
|
|
- 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.
|
|
|
|
### 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.
|
|
- The stored context follows normal value semantics for its type.
|
|
- `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, 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.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.
|
|
- 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(...)` do not denote ordinary value expressions in v1 core.
|
|
- `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`.
|
|
- Each mapped error path on the right side of a `handle` arm must resolve to a declared case of `E2`.
|
|
- `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.
|
|
- On error, `handle` performs an immediate enclosing-function return with the mapped `err(...)`.
|
|
|
|
### 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,
|
|
- 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`,
|
|
- 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`,
|
|
- 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 `return`,
|
|
- 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`,
|
|
- 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,
|
|
- bare method extraction from a struct, service, host, or contract value,
|
|
- member projection on a single-slot carrier value,
|
|
- invalid optional intrinsic member call,
|
|
- member projection on a missing named output label.
|