semantifold 0.13.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/build/src/backends/effects.js +3 -1
- package/build/src/backends/php.d.ts.map +1 -1
- package/build/src/backends/php.js +56 -3
- package/build/src/backends/scalars.js +2 -2
- package/build/src/backends/shared.d.ts +2 -1
- package/build/src/backends/shared.d.ts.map +1 -1
- package/build/src/backends/shared.js +209 -7
- package/build/src/backends/writer.d.ts +18 -4
- package/build/src/backends/writer.d.ts.map +1 -1
- package/build/src/backends/writer.js +40 -4
- package/build/src/frontends/java.d.ts +1 -1
- package/build/src/frontends/java.d.ts.map +1 -1
- package/build/src/frontends/java.js +14 -11
- package/build/src/frontends/javascript-typescript.d.ts +1 -1
- package/build/src/frontends/javascript-typescript.d.ts.map +1 -1
- package/build/src/frontends/javascript-typescript.js +17 -17
- package/build/src/frontends/php.d.ts +5 -1
- package/build/src/frontends/php.d.ts.map +1 -1
- package/build/src/frontends/php.js +159 -31
- package/build/src/frontends/ruby.d.ts +1 -1
- package/build/src/frontends/ruby.d.ts.map +1 -1
- package/build/src/frontends/ruby.js +12 -9
- package/build/src/frontends/types.d.ts +9 -2
- package/build/src/frontends/types.d.ts.map +1 -1
- package/build/src/frontends/types.js +26 -9
- package/build/src/language-registry.d.ts.map +1 -1
- package/build/src/language-registry.js +14 -6
- package/build/src/mapping.d.ts.map +1 -1
- package/build/src/mapping.js +4 -2
- package/build/src/semantic/effects.d.ts.map +1 -1
- package/build/src/semantic/effects.js +3 -1
- package/build/src/semantic/provenance.d.ts.map +1 -1
- package/build/src/semantic/provenance.js +37 -4
- package/build/src/semantic/types.d.ts +140 -9
- package/build/src/semantic/types.d.ts.map +1 -1
- package/build/src/semantic/types.js +37 -6
- package/build/src/semantic/validate.d.ts +31 -2
- package/build/src/semantic/validate.d.ts.map +1 -1
- package/build/src/semantic/validate.js +346 -27
- package/docs/adr-bounded-subtyping.md +180 -0
- package/docs/language-support.md +1 -1
- package/package.json +1 -1
- package/src/backends/effects.js +1 -0
- package/src/backends/php.js +56 -2
- package/src/backends/scalars.js +1 -1
- package/src/backends/shared.js +210 -6
- package/src/backends/writer.js +41 -3
- package/src/frontends/java.js +13 -10
- package/src/frontends/javascript-typescript.js +16 -16
- package/src/frontends/php.js +175 -31
- package/src/frontends/ruby.js +11 -8
- package/src/frontends/types.js +26 -8
- package/src/language-registry.js +13 -5
- package/src/mapping.js +3 -1
- package/src/semantic/effects.js +1 -0
- package/src/semantic/provenance.js +34 -3
- package/src/semantic/types.js +39 -5
- package/src/semantic/validate.js +377 -26
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# ADR: Bounded subtyping — nominal interfaces with exact implementation contracts
|
|
2
|
+
|
|
3
|
+
- Status: `accepted` (adopted for PHP in this issue's implementation; the remaining cohort languages land per the follow-up plan below)
|
|
4
|
+
- Issue: STOE-60 (Semantifold core — inheritance, interfaces and trait/mixin composition beyond closed reference classes)
|
|
5
|
+
- Depends on: Task 033 reference classes (`docs/language-support.md`, `todo/033-reference-classes-methods-and-constructors.md`) for the closed nominal class, receiver, privacy, and initialization model this ADR extends and never weakens.
|
|
6
|
+
|
|
7
|
+
## Context
|
|
8
|
+
|
|
9
|
+
Task 033 delivered closed reference classes: nominal identities, private mutable state, one exact constructor, a closed method set, and purely nominal closed dispatch. Inheritance, interfaces, traits/mixins, reopening, and virtual/dynamic dispatch are explicitly excluded, so no polymorphic domain model can be expressed: a function parameter, return, local, or collection can only name one concrete class, and there is no way to state a shared method contract across classes.
|
|
10
|
+
|
|
11
|
+
The repository's guiding principles (AGENTS.md, `todo/README.md`) constrain the design:
|
|
12
|
+
|
|
13
|
+
1. Model meaning once: the semantic IR carries types and resolution facts, never target spelling.
|
|
14
|
+
2. Fail loudly: a target that cannot honor a contract reports `UNSUPPORTED_CAPABILITY` before any partial artifact.
|
|
15
|
+
3. Prefer a precise portable profile: dispatch, override, and visibility rules require explicit contracts, not symbol substitution.
|
|
16
|
+
4. No structural substitution: a lookalike never satisfies a nominal type.
|
|
17
|
+
5. Task 033 semantics are frozen: freshness, exact-once field initialization, receiver-before-argument order, declaring-receiver privacy, and aliasing-not-copying stay exactly as delivered.
|
|
18
|
+
|
|
19
|
+
This ADR decides the shape of subtyping. Per-language source spellings, generated shapes, and acceptance corpora are separate per-language specs (see Follow-ups).
|
|
20
|
+
|
|
21
|
+
## Decision 1 — Interfaces with exact implementation contracts; no class-to-class inheritance
|
|
22
|
+
|
|
23
|
+
The admitted model is **nominal interfaces plus exact `implements` contracts**. A class may implement one or more interfaces; a class never extends another class, and there are no abstract classes, superclass constructors, inherited fields, inherited methods, or `super` calls.
|
|
24
|
+
|
|
25
|
+
Rationale:
|
|
26
|
+
|
|
27
|
+
- The polymorphism that domain models actually need — a shared method contract behind parameter/return/local/collection positions — is fully expressible with interfaces; implementation inheritance mostly buys code reuse, which the model can later supply through composition (holding records or references as private fields) without inheriting state or construction semantics.
|
|
28
|
+
- Implementation inheritance collides with Task 033's construction contract: "exactly one constructor" and "every field initialized exactly once before the reference escapes" do not survive parent-constructor chaining, inherited private fields, or partial initialization across a class hierarchy.
|
|
29
|
+
- Ruby has single class inheritance but no native interface construct, JavaScript has `extends`/`class` but no nominal interface, and PHP/Java/TypeScript have both — a class-inheritance contract would be the least common denominator and would import constructor-chaining and visibility-variance questions this model does not need.
|
|
30
|
+
- Excluding class inheritance keeps the class cohort closed and enumerable per module, which is what makes the override contract statically checkable.
|
|
31
|
+
|
|
32
|
+
`extends` in the admitted grammar means **interface-extends-interface only** (Decision 3). Every source spelling of class-to-class `extends`, `super`, abstract classes, and inherited members remains `UNSUPPORTED_SYNTAX`, exactly as today.
|
|
33
|
+
|
|
34
|
+
## Decision 2 — `InterfaceDeclaration` is a closed signature-only nominal type
|
|
35
|
+
|
|
36
|
+
The semantic IR gains:
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
InterfaceDeclaration
|
|
40
|
+
kind: "InterfaceDeclaration"
|
|
41
|
+
id?: string // stable module-local identity (`interface:N`), required after validation
|
|
42
|
+
name: string // source-visible name
|
|
43
|
+
extends?: InterfaceType[] // direct interface parents in source order; omitted when empty
|
|
44
|
+
methods: InterfaceMethodDeclaration[] // ordered closed signature set
|
|
45
|
+
|
|
46
|
+
InterfaceMethodDeclaration
|
|
47
|
+
kind: "InterfaceMethodDeclaration"
|
|
48
|
+
id?: string // stable interface-scoped identity (`interface:N:method:N`), required after validation
|
|
49
|
+
name: string
|
|
50
|
+
parameters: Parameter[] // exact positional required parameters with explicit types
|
|
51
|
+
returnType: SemanticFunctionReturnType // explicit value or void return
|
|
52
|
+
|
|
53
|
+
InterfaceType
|
|
54
|
+
kind: "InterfaceType"
|
|
55
|
+
declarationId: string // exact interface declaration identity
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
`ClassDeclaration` gains an optional `implements?: InterfaceType[]` member (source order; omitted when empty) and each `MethodDeclaration` gains an optional resolved `implements?: {interfaceId: string, methodId: string}` fact after validation.
|
|
59
|
+
|
|
60
|
+
Hard rules:
|
|
61
|
+
|
|
62
|
+
- Interfaces carry **no fields, no constructors, no static members, and no method bodies**. A body on an interface method (PHP/Python/Go-style default methods) is `UNSUPPORTED_SYNTAX`.
|
|
63
|
+
- Interface method signatures are explicit and complete exactly like Task 033 method signatures: named parameters, explicit types, explicit value-or-void return. No optional parameters, no defaults, no overloads, no variadics.
|
|
64
|
+
- Interface names join the existing nominal-name registry: one interface name per module, unique against classes, records, errors, functions, and other interfaces, and against the target reserved-name/collision baselines Task 033 already enforces.
|
|
65
|
+
- `InterfaceType` joins `SemanticValueType` and composes with the existing type grammar exactly like `ReferenceType`: parameters, returns, locals, list/map/ordered-map/optional constituents.
|
|
66
|
+
|
|
67
|
+
## Decision 3 — Interface composition is a closed extends DAG
|
|
68
|
+
|
|
69
|
+
An interface may extend zero or more interfaces. The transitive method set is the flattened union in deterministic declaration order (parents first, then the interface's own methods, in source order). Cycles, duplicate inherited names, and a redeclared method whose signature differs from the inherited one are located diagnostics (`INTERFACE_CYCLE`, `INTERFACE_MEMBER_DUPLICATE`). Interface-to-interface `extends` is the only admitted `extends` spelling.
|
|
70
|
+
|
|
71
|
+
## Decision 4 — The override contract is exact and total
|
|
72
|
+
|
|
73
|
+
A class with `implements [I1..In]` must supply, in its own closed method set, exactly one method matching each flattened interface signature:
|
|
74
|
+
|
|
75
|
+
- exact name, exact ordered parameter types, exact arity, exact return type (value type or void — never widened or narrowed);
|
|
76
|
+
- no interface method may be private to the class (visibility rules per Decision 7);
|
|
77
|
+
- duplicate class methods satisfying the same interface member, a missing member, a signature mismatch, and an arity mismatch are located diagnostics (`INTERFACE_MEMBER_MISSING`, `INTERFACE_MEMBER_SIGNATURE_MISMATCH`), reported before any writer allocation;
|
|
78
|
+
- a class may implement several interfaces, and one class method may satisfy several interfaces' identical signatures;
|
|
79
|
+
- interface method names follow the same target preflight rules as Task 033 method names (for example Ruby target preflight still rejects setter-style names ending in `=`).
|
|
80
|
+
|
|
81
|
+
The contract is checked at the declaration, once, statically. Nothing about it is deferred to runtime.
|
|
82
|
+
|
|
83
|
+
## Decision 5 — Dispatch is nominal at the call site; the target's native lookup selects the implementation
|
|
84
|
+
|
|
85
|
+
`MethodCallExpression` on an `InterfaceType` receiver resolves against the **interface's flattened signature set**: the resolved `ResolvedMethodSignature` records the declaring interface identity and the exact signature, exactly as Task 033 records the declaring class. Call-site validation never inspects which concrete classes exist.
|
|
86
|
+
|
|
87
|
+
At runtime the target's own native method lookup performs the selection (PHP interface dispatch, Ruby module inclusion, JavaScript prototype lookup, TypeScript compile-time erasure plus native lookup, Java virtual dispatch). This is target-native lowering, not a Semantifold dispatch model: the semantic IR carries no dispatch tables, virtual slots, method objects, vtables, or message sends, and Semantifold never resolves a method against a concrete class behind an interface receiver. Two classes implementing the same interface produce byte-identical generated call sites.
|
|
88
|
+
|
|
89
|
+
Explicitly still excluded (unchanged from Task 033): computed/dynamic method names, `send`/`apply`/`public_send`, `method_missing`, function-valued "lambdas stored in fields", reflection, and any construct that turns a method name into a runtime value.
|
|
90
|
+
|
|
91
|
+
## Decision 6 — Value flow: implicit upcast only, no casts, no type tests
|
|
92
|
+
|
|
93
|
+
- A `ReferenceConstruction` of class `C` is assignable to an `InterfaceType` `I` (and to `J`) exactly when `C implements` includes `I` (or a transitively extended `J`). This is the only admitted widening, and it is checked statically at every assignment, argument, return, and collection-element position.
|
|
94
|
+
- An interface-typed value never flows back to a class type or to a narrower interface: there are no downcasts, no `as`/`instanceof`/`is_a?`/`is`/`kind_of?`/type predicates on interface receivers, no exhaustive switch over implementations, and no runtime type queries of any kind. The existing general exclusion of casts/assertions stands.
|
|
95
|
+
- Interface-typed locals require the same explicit type carriers Task 033 requires wherever the source language does not enforce them (JSDoc `@type`, Ruby type comments, etc.).
|
|
96
|
+
- Task 033 identity semantics carry over unchanged: interface-typed bindings alias the same fresh instance; there is still no identity equality, hashing, ordering, cloning, or serialization.
|
|
97
|
+
|
|
98
|
+
## Decision 7 — Visibility and encapsulation
|
|
99
|
+
|
|
100
|
+
- Interface members are public to the module cohort by definition; the implementing class method must be public in the language profile (Java package-private follows the existing Task 033 Java profile: package-private interface, package-private implementing class methods).
|
|
101
|
+
- Private fields remain declaring-class-only through the semantic receiver. An interface receiver can never reach any field: `PrivateFieldRead`/`PrivateFieldWriteStatement` remain valid only on the declaring-class `ReceiverExpression` inside the declaring class, never on an interface-typed binding.
|
|
102
|
+
- Interfaces do not constrain a class's private state or extra methods: a class may have any Task 033-legal fields and methods beyond its interface contract.
|
|
103
|
+
|
|
104
|
+
## Decision 8 — Capability, cohort, and fail-loud boundaries
|
|
105
|
+
|
|
106
|
+
The registry gains one capability flag `boundedSubtyping`, valid only where `referenceClasses: true` (a language cannot advertise interfaces before it advertises closed reference classes). Adoption order follows the existing cohort discipline:
|
|
107
|
+
|
|
108
|
+
1. **Original five first** (PHP, Ruby, JavaScript/JSDoc, TypeScript, Java) — the Task 033 cohort, in the same single-module scope Task 033 shipped with. PHP is adopted today; Ruby, JavaScript/JSDoc, TypeScript, and Java keep `boundedSubtyping: false` until their per-language interface profiles land.
|
|
109
|
+
2. **Each Wave-2 language when it adopts `referenceClasses`** (Kotlin, Python, C#, Go, Rust, Swift, Dart, then C/C++ and Zig as their reference-class lanes land), each with its own exact native profile spec.
|
|
110
|
+
|
|
111
|
+
A language that advertises `boundedSubtyping: false` — today every language outside the original five, plus browser Wasm, Android/Flutter/iOS application roles until their lanes say otherwise — rejects any interface-bearing module transactionally with `UNSUPPORTED_CAPABILITY` before source, bytes, or a partial artifact set is exposed, exactly as non-cohort languages reject Task 033 class IR today.
|
|
112
|
+
|
|
113
|
+
## Decision 9 — Explicit permanent exclusions
|
|
114
|
+
|
|
115
|
+
Unless a future ADR admits them, all of the following stay out of the model and stay `UNSUPPORTED_SYNTAX`/`UNSUPPORTED_CAPABILITY`:
|
|
116
|
+
|
|
117
|
+
- class-to-class inheritance, abstract classes, `super`, inherited constructors/fields/methods, constructor chaining;
|
|
118
|
+
- multiple implementation inheritance, mixins/traits with state or behavior, Ruby module `prepend`/refinement, JavaScript prototype mutation, Python MRO beyond a single base;
|
|
119
|
+
- reopening, monkey-patching, duck typing, structural interface satisfaction, implicit interface conformance without an explicit `implements` declaration;
|
|
120
|
+
- default/implemented interface methods, static or constant interface members, interface generics (until Task 012 generics compose with interfaces in a separate decision), variance annotations (interfaces are invariant);
|
|
121
|
+
- downcasts, type tests, exhaustive matching over implementors, runtime type inspection, reflection;
|
|
122
|
+
- virtual/dynamic sends beyond the target's own closed native lookup for a statically resolved interface method (Decision 5);
|
|
123
|
+
- identity equality/hashing/ordering, serialization, cloning, destructors, async interface methods, concurrency.
|
|
124
|
+
|
|
125
|
+
## Per-language source profiles (exact native forms)
|
|
126
|
+
|
|
127
|
+
Frontends recognize exactly one canonical spelling per language and account for every parser child, modifier, and annotation; anything else is `UNSUPPORTED_SYNTAX`.
|
|
128
|
+
|
|
129
|
+
| Language | Interface declaration | Implementation | Interface-typed binding |
|
|
130
|
+
| --- | --- | --- | --- |
|
|
131
|
+
| PHP | `interface Name { public function m(T $p): R; }` (signature only) | `final class C implements I1, I2` | native parameter/return/local types |
|
|
132
|
+
| Ruby | `interface Name` declaration with per-method type-comment signatures (compiler-qualified spelling, mirroring the bounded RBS-style convention) | `class C` with `include Name` and matching `def` bodies | type comments, as Task 033 |
|
|
133
|
+
| JavaScript/JSDoc | `/** @interface */` class-shaped signature scaffold with complete `@param`/`@return` per method | `@implements {Name}` on the Task 033 canonical class | JSDoc `@type` carriers |
|
|
134
|
+
| TypeScript | `interface Name { m(p: T): R }` (non-exported, signature only) | `class C implements I1, I2` (Task 033 canonical class body) | native type annotations |
|
|
135
|
+
| Java | package-private `interface Name { R m(T p); }` | package-private `final class C implements I1, I2` (Task 033 canonical class) | native types, `Main.java` contract unchanged |
|
|
136
|
+
| Python (Wave 2) | `typing.Protocol`-shaped method stubs with full annotations | explicit inheritance of the protocol base in the class header | annotations |
|
|
137
|
+
| C# (Wave 2) | `interface IName { R M(T p); }` | `sealed class C : I1, I2` | native types |
|
|
138
|
+
| Go (Wave 2) | named `type I interface { M(p T) R }` method set | method-set satisfaction proven against the explicit named interface declaration only | named interface type |
|
|
139
|
+
| Rust (Wave 2) | `trait I { fn m(&self, p: T) -> R; }` with explicit `impl I for C` | `impl I for C` blocks | trait-object position decided per spec (no dynamic dispatch unless the spec pins a closed form) |
|
|
140
|
+
| Swift (Wave 2) | `protocol IName { func m(_ p: T) -> R }` | `final class C: I1, I2` | native protocol types |
|
|
141
|
+
| Kotlin (Wave 2) | `interface IName { fun m(p: T): R }` | `class C : I1, I2` | native types |
|
|
142
|
+
| Dart (Wave 2) | `abstract class IName { R m(T p); }` signature-only form | `class C implements I1, I2` | native types |
|
|
143
|
+
| C / C++ / Zig | no conformant native interface construct in the current profiles — remain excluded until a per-language spec decides a compiler-owned closed dispatch shape | — | — |
|
|
144
|
+
|
|
145
|
+
## Per-target backend representations
|
|
146
|
+
|
|
147
|
+
Backends emit the target's native interface construct and native call spelling from validated semantic nodes; they never erase an interface into a map, function table, or tagged union.
|
|
148
|
+
|
|
149
|
+
| Target | Representation |
|
|
150
|
+
| --- | --- |
|
|
151
|
+
| PHP | native `interface` + `implements`; typed signatures |
|
|
152
|
+
| Ruby | compiler-owned generated `module Name` with the contract documented and `include Name` on the class; method bodies remain the class's own |
|
|
153
|
+
| JavaScript | JSDoc `@interface`/`@implements` scaffold plus native method calls (nominal guarantee lives in the compiler, generated code stays plain ES) |
|
|
154
|
+
| TypeScript | native `interface` + `implements` |
|
|
155
|
+
| Java | package-private `interface` + `implements`; single-file `Main.java` composition rules extended to nested interfaces |
|
|
156
|
+
| Python / C# / Go / Rust / Swift / Kotlin / Dart | native protocol/interface/trait/protocol/interface/abstract-class forms per the Wave-2 specs; each spec pins the exact dispatch lowering and proves it with real toolchains |
|
|
157
|
+
| C / C++ / Zig / browser Wasm / application roles | `UNSUPPORTED_CAPABILITY` for interface-bearing IR until their lanes admit it |
|
|
158
|
+
|
|
159
|
+
Backend preflight revalidates the complete interface graph — identities, flattened method sets, override contracts, name collisions against target baselines, receiver types, and upcast legality — before writer allocation, and generated code reparses to the same interface/member identities wherever the target has a reparse lane.
|
|
160
|
+
|
|
161
|
+
## Diagnostics
|
|
162
|
+
|
|
163
|
+
New stable codes, all located and all raised before artifact exposure: `DUPLICATE_INTERFACE`, `UNKNOWN_INTERFACE`, `INTERFACE_CYCLE`, `INTERFACE_MEMBER_DUPLICATE`, `INTERFACE_MEMBER_MISSING`, `INTERFACE_MEMBER_SIGNATURE_MISMATCH`, `INTERFACE_NAME_COLLISION`, `INTERFACE_TYPE_MISMATCH` (illegal upcast/assignment), `INTERFACE_RECEIVER_INVALID` (non-interface receiver or private-field reach through an interface binding). Malformed caller-owned IR reports the existing transactional `UNSUPPORTED_CAPABILITY`/`TYPE_MISMATCH` family. Frontends locate excluded inheritance, `super`, reopened classes, default methods, implicit conformance, and type tests as `UNSUPPORTED_SYNTAX`.
|
|
164
|
+
|
|
165
|
+
## Rejected alternatives
|
|
166
|
+
|
|
167
|
+
- **Single-root class inheritance with virtual dispatch** (PHP/Java/TypeScript-shaped): rejected by Decision 1 — it reopens constructor chaining, partial initialization, inherited private state, and visibility variance, and its least-common-denominator across Ruby/JavaScript profiles is weaker than the interface contract.
|
|
168
|
+
- **Interface-only composition without interface `extends`**: rejected as unnecessarily restrictive; a closed extends DAG is statically checkable and native in every cohort language.
|
|
169
|
+
- **Structural/duck typing**: rejected outright — it violates the repository's nominal-typing principle and cannot be validated fail-loudly.
|
|
170
|
+
- **Composition-only (no subtyping; hand adapters)**: rejected as insufficient — it cannot express polymorphic parameters, which is the gap this issue names.
|
|
171
|
+
|
|
172
|
+
## Follow-ups
|
|
173
|
+
|
|
174
|
+
1. Semantic IR/validation: `InterfaceDeclaration`/`InterfaceMethodDeclaration`/`InterfaceType`, `implements` on classes, registry `boundedSubtyping` flag, shared acceptance/rejection specs (one top-level `describe` per spec file).
|
|
175
|
+
2. Original-five frontend/backend per-language specs: exact source spellings, generated shapes, reparse proofs, and real-toolchain execution (PHP, Ruby, Node, `tsc`, `javac`/`java`) including override-contract negatives.
|
|
176
|
+
3. Wave-2 per-language specs as each language's `referenceClasses` lane lands.
|
|
177
|
+
4. Docs: extend the Task 033 section of `docs/language-support.md` with the admitted interface profile once implemented.
|
|
178
|
+
5. Real-toolchain acceptance and exact-head TensorBuzz CI per the repo gates.
|
|
179
|
+
|
|
180
|
+
Recorded as roadmap task [048 — Bounded subtyping implementation](../todo/048-bounded-subtyping-implementation.md).
|
package/docs/language-support.md
CHANGED
|
@@ -170,7 +170,7 @@ Construction arguments evaluate left to right and all fields must be initialized
|
|
|
170
170
|
|
|
171
171
|
The source profiles are intentionally exact. TypeScript uses non-exported classes with explicit `private` fields, one unmodified constructor, and ordinary typed instance methods. JavaScript uses native `#private` fields with adjacent complete JSDoc on fields, constructor parameters, method parameters/returns, and reference-typed bindings. PHP uses `final class`, explicit private typed properties, one public `__construct`, and public typed instance methods. Ruby uses a closed class body with one documented `initialize`; its ordered required parameters initialize the correspondingly named instance variables, and every ordinary method has complete adjacent type comments. Ruby target preflight rejects setter-style method names ending in `=` because native setter invocation returns the assigned argument independently of the method body. Java uses a package-private `final class` with explicit private mutable fields, one package-private constructor, package-private ordinary typed methods, and a separate canonical public `Main`; when a reference method calls a semantic function, generated package-visible static `Main` methods and explicit `Main.method(...)` calls preserve access and evaluation order. All five generated forms reparse to the same semantic class/member identities and execute through the real registered toolchains.
|
|
172
172
|
|
|
173
|
-
Inheritance, interfaces, traits/mixins, class reopening or monkey-patching, prototypes, decorators/annotations, static/singleton members, computed or dynamic sends, overloads, virtual/duck dispatch, arbitrary host construction, reflection and host lifecycle/protocol hooks, public mutable fields, private access from outside the declaring receiver, identity equality/hashing/ordering, cloning, serialization, finalizers, generic or async methods/classes, and concurrency remain excluded. Constructors and methods may catch Task 011 errors internally but cannot expose an unchecked-error effect through a receiver call; that cross-feature effect contract is not invented by Task 033. Kotlin, Python, C#, Go, C, C++, Rust, Swift, and browser Wasm advertise `referenceClasses: false` and reject class-bearing modules transactionally. Task 033 is single-module: `parseProgram` and `generateProgramArtifactSet` reject class-bearing Task 010 modules before resolution or artifact allocation. It adds no Task 034 resource/effect model and no standard-library contract, facade, provider, or pairwise rewrite.
|
|
173
|
+
Inheritance, interfaces, traits/mixins, class reopening or monkey-patching, prototypes, decorators/annotations, static/singleton members, computed or dynamic sends, overloads, virtual/duck dispatch, arbitrary host construction, reflection and host lifecycle/protocol hooks, public mutable fields, private access from outside the declaring receiver, identity equality/hashing/ordering, cloning, serialization, finalizers, generic or async methods/classes, and concurrency remain excluded. The bounded subtyping model that extends this cohort — nominal interfaces with exact `implements` override contracts and no class-to-class inheritance — is decided in the [bounded subtyping ADR](adr-bounded-subtyping.md). It is adopted for PHP only today (`boundedSubtyping: true`); Ruby, JavaScript, TypeScript, and Java keep `boundedSubtyping: false` until their per-language interface profiles land, and every other target rejects interface-bearing modules transactionally. Nothing in that ADR changes the Task 033 semantics documented above. Constructors and methods may catch Task 011 errors internally but cannot expose an unchecked-error effect through a receiver call; that cross-feature effect contract is not invented by Task 033. Kotlin, Python, C#, Go, C, C++, Rust, Swift, and browser Wasm advertise `referenceClasses: false` and reject class-bearing modules transactionally. Task 033 is single-module: `parseProgram` and `generateProgramArtifactSet` reject class-bearing Task 010 modules before resolution or artifact allocation. It adds no Task 034 resource/effect model and no standard-library contract, facade, provider, or pairwise rewrite.
|
|
174
174
|
|
|
175
175
|
## Task 034 effectful capabilities and owned resource lifetime
|
|
176
176
|
|
package/package.json
CHANGED
package/src/backends/effects.js
CHANGED
|
@@ -266,6 +266,7 @@ function semanticTypeForIdentity(identity) {
|
|
|
266
266
|
if (identity.kind == "RecordType") return {arguments: identity.arguments?.map(semanticTypeForValueIdentity),
|
|
267
267
|
declarationId: identity.declarationId, kind: "RecordType"}
|
|
268
268
|
if (identity.kind == "ReferenceType") return {declarationId: identity.declarationId, kind: "ReferenceType"}
|
|
269
|
+
if (identity.kind == "InterfaceType") return {declarationId: identity.declarationId, kind: "InterfaceType"}
|
|
269
270
|
if (identity.kind == "OwnedResourceType") return {kind: "OwnedResourceType", resourceId: identity.resourceId}
|
|
270
271
|
if (identity.kind == "OwnedReferenceType") return {declarationId: identity.declarationId, kind: "OwnedReferenceType"}
|
|
271
272
|
return {kind: "TypeVariableReference", location: /** @type {never} */ (undefined), parameterId: identity.parameterId}
|
package/src/backends/php.js
CHANGED
|
@@ -15,6 +15,7 @@ export function generatePhp(module, writer) {
|
|
|
15
15
|
emitEffectSupport(writer, module, "php")
|
|
16
16
|
const records = module.records ?? []
|
|
17
17
|
const classes = module.classes ?? []
|
|
18
|
+
const interfaces = module.interfaces ?? []
|
|
18
19
|
const errors = module.errors ?? []
|
|
19
20
|
|
|
20
21
|
errors.forEach((error, index) => {
|
|
@@ -72,6 +73,12 @@ export function generatePhp(module, writer) {
|
|
|
72
73
|
})
|
|
73
74
|
if (records.length > 0) writer.synthetic("\n\n", "record/function separator", [module])
|
|
74
75
|
|
|
76
|
+
interfaces.forEach((declaration, interfaceIndex) => {
|
|
77
|
+
if (interfaceIndex > 0) writer.synthetic("\n\n", "interface separator", [declaration], [`/interfaces/${interfaceIndex}`])
|
|
78
|
+
emitInterface(writer, declaration, interfaceIndex)
|
|
79
|
+
})
|
|
80
|
+
if (interfaces.length > 0) writer.synthetic("\n\n", "interface/class separator", [module])
|
|
81
|
+
|
|
75
82
|
classes.forEach((declaration, classIndex) => {
|
|
76
83
|
if (classIndex > 0) writer.synthetic("\n\n", "reference class separator", [declaration], [`/classes/${classIndex}`])
|
|
77
84
|
emitReferenceClass(writer, declaration, classIndex)
|
|
@@ -156,6 +163,15 @@ function emitReferenceClass(writer, declaration, classIndex) {
|
|
|
156
163
|
writer.mapped("final class", {mappingKind: "anchor", node: declaration, path})
|
|
157
164
|
writer.synthetic(" ", "reference class spacing", [declaration], [path])
|
|
158
165
|
writer.mapped(declaration.name, {mappingKind: "exact", node: declaration, path, role: "name"})
|
|
166
|
+
const implemented = declaration.implements ?? []
|
|
167
|
+
|
|
168
|
+
if (implemented.length > 0) {
|
|
169
|
+
writer.mapped(" implements ", {mappingKind: "anchor", node: declaration, path})
|
|
170
|
+
implemented.forEach((reference, referenceIndex) => {
|
|
171
|
+
if (referenceIndex > 0) writer.mapped(", ", {mappingKind: "anchor", node: declaration, path})
|
|
172
|
+
emitType(writer, reference, `${path}/implements/${referenceIndex}`, "php")
|
|
173
|
+
})
|
|
174
|
+
}
|
|
159
175
|
writer.synthetic(" {\n", "reference class open", [declaration], [path])
|
|
160
176
|
declaration.fields.forEach((field, index) => {
|
|
161
177
|
const fieldPath = `${path}/fields/${index}`
|
|
@@ -191,6 +207,44 @@ function emitReferenceClass(writer, declaration, classIndex) {
|
|
|
191
207
|
writer.mapped("}", {mappingKind: "anchor", node: declaration, path})
|
|
192
208
|
}
|
|
193
209
|
|
|
210
|
+
/**
|
|
211
|
+
* Emits one PHP native interface declaration with signature-only members.
|
|
212
|
+
* @param {import("./writer.js").SourceWriter} writer - Source-aware writer.
|
|
213
|
+
* @param {import("../semantic/types.js").InterfaceDeclaration} declaration - Semantic interface.
|
|
214
|
+
* @param {number} interfaceIndex - Declaration index.
|
|
215
|
+
* @returns {void}
|
|
216
|
+
*/
|
|
217
|
+
function emitInterface(writer, declaration, interfaceIndex) {
|
|
218
|
+
const path = `/interfaces/${interfaceIndex}`
|
|
219
|
+
|
|
220
|
+
writer.mapped("interface", {mappingKind: "anchor", node: declaration, path})
|
|
221
|
+
writer.synthetic(" ", "interface spacing", [declaration], [path])
|
|
222
|
+
writer.mapped(declaration.name, {mappingKind: "exact", node: declaration, path, role: "name"})
|
|
223
|
+
const parents = declaration.extends ?? []
|
|
224
|
+
|
|
225
|
+
if (parents.length > 0) {
|
|
226
|
+
writer.mapped(" extends ", {mappingKind: "anchor", node: declaration, path})
|
|
227
|
+
parents.forEach((parent, parentIndex) => {
|
|
228
|
+
if (parentIndex > 0) writer.mapped(", ", {mappingKind: "anchor", node: declaration, path})
|
|
229
|
+
emitType(writer, parent, `${path}/extends/${parentIndex}`, "php")
|
|
230
|
+
})
|
|
231
|
+
}
|
|
232
|
+
writer.synthetic(" {\n", "interface open", [declaration], [path])
|
|
233
|
+
declaration.methods.forEach((method, methodIndex) => {
|
|
234
|
+
const methodPath = `${path}/methods/${methodIndex}`
|
|
235
|
+
|
|
236
|
+
if (methodIndex > 0) writer.synthetic("\n", "line break", [method], [methodPath])
|
|
237
|
+
writer.synthetic(" public function ", "interface method scaffolding", [method], [methodPath])
|
|
238
|
+
writer.mapped(method.name, {mappingKind: "exact", node: method, path: methodPath, role: "name"})
|
|
239
|
+
emitPhpParameters(writer, method.parameters, `${methodPath}/parameters`)
|
|
240
|
+
writer.synthetic(": ", "interface method return separator", [method], [methodPath])
|
|
241
|
+
emitNativePhpType(writer, method.returnType, `${methodPath}/returnType`)
|
|
242
|
+
writer.mapped(";", {mappingKind: "anchor", node: method, path: methodPath})
|
|
243
|
+
})
|
|
244
|
+
writer.synthetic("\n", "line break", [declaration], [path])
|
|
245
|
+
writer.mapped("}", {mappingKind: "anchor", node: declaration, path})
|
|
246
|
+
}
|
|
247
|
+
|
|
194
248
|
/**
|
|
195
249
|
* Emits one PHP parameter list.
|
|
196
250
|
* @param {import("./writer.js").SourceWriter} writer - Source-aware writer.
|
|
@@ -355,7 +409,7 @@ function emitNativePhpType(writer, type, path) {
|
|
|
355
409
|
})
|
|
356
410
|
return
|
|
357
411
|
}
|
|
358
|
-
if (type.kind == "ReferenceType" || type.kind == "OwnedResourceType" || type.kind == "OwnedReferenceType") {
|
|
412
|
+
if (type.kind == "ReferenceType" || type.kind == "InterfaceType" || type.kind == "OwnedResourceType" || type.kind == "OwnedReferenceType") {
|
|
359
413
|
emitType(writer, type, path, "php")
|
|
360
414
|
return
|
|
361
415
|
}
|
|
@@ -365,7 +419,7 @@ function emitNativePhpType(writer, type, path) {
|
|
|
365
419
|
}
|
|
366
420
|
if (type.kind == "OptionalType") {
|
|
367
421
|
writer.mapped("?", {mappingKind: "exact", node: type, path, role: "type"})
|
|
368
|
-
if (type.valueType.kind == "RecordType") emitNativePhpType(writer, type.valueType, `${path}/valueType`)
|
|
422
|
+
if (type.valueType.kind == "RecordType" || type.valueType.kind == "InterfaceType") emitNativePhpType(writer, type.valueType, `${path}/valueType`)
|
|
369
423
|
else if (type.valueType.kind == "TypeReference") emitType(writer, type.valueType, `${path}/valueType`, "php")
|
|
370
424
|
else writer.mapped("array", {mappingKind: "anchor", node: type, path})
|
|
371
425
|
return
|
package/src/backends/scalars.js
CHANGED
|
@@ -37,7 +37,7 @@ export function emitScalarType(language, type) {
|
|
|
37
37
|
* @returns {string} Exact target spelling.
|
|
38
38
|
*/
|
|
39
39
|
export function emitSemanticType(language, type, javaBoxed = false) {
|
|
40
|
-
if (type.kind == "RecordType" || type.kind == "ReferenceType") {
|
|
40
|
+
if (type.kind == "RecordType" || type.kind == "ReferenceType" || type.kind == "InterfaceType") {
|
|
41
41
|
throw new TypeError("Nominal type emission requires declaration context.")
|
|
42
42
|
}
|
|
43
43
|
if (type.kind == "OwnedResourceType" || type.kind == "OwnedReferenceType") {
|
package/src/backends/shared.js
CHANGED
|
@@ -38,6 +38,7 @@ const task014Languages = new Set(["php", "ruby", "javascript", "typescript", "ja
|
|
|
38
38
|
const task032Languages = new Set(["php", "ruby", "javascript", "typescript", "java"])
|
|
39
39
|
const task033Languages = new Set(["php", "ruby", "javascript", "typescript", "java"])
|
|
40
40
|
const task048ConditionalLanguages = new Set(["php", "ruby", "javascript", "typescript", "java"])
|
|
41
|
+
const boundedSubtypingLanguages = new Set(["php"])
|
|
41
42
|
const javaReferenceMethodHooks = new Set(["clone", "equals", "finalize", "getClass", "hashCode", "notify", "notifyAll", "toString", "wait"])
|
|
42
43
|
const javascriptReferenceMethodHooks = new Set([
|
|
43
44
|
"__defineGetter__", "__defineSetter__", "__lookupGetter__", "__lookupSetter__", "__proto__", "constructor",
|
|
@@ -56,7 +57,7 @@ const javaObjectInstanceMethodSignatures = new Set([
|
|
|
56
57
|
* Checks the intentionally narrow backend contract.
|
|
57
58
|
* @param {import("../semantic/types.js").SemanticModule} module - Semantic module.
|
|
58
59
|
* @param {import("../semantic/types.js").BackendLanguage} language - Backend language or binary target.
|
|
59
|
-
* @param {{externalDeclarationIds?: Set<string>, program?: boolean, visibleFunctions?: Map<string, import("../semantic/types.js").FunctionDeclaration>, visibleRecords?: Map<string, import("../semantic/types.js").RecordDeclaration>, visibleClasses?: Map<string, import("../semantic/types.js").ClassDeclaration>, visibleErrors?: Map<string, import("../semantic/types.js").ErrorDeclaration>, visibleCallEffects?: Map<string, Set<string>>}} [options] - Resolved program validation context.
|
|
60
|
+
* @param {{externalDeclarationIds?: Set<string>, program?: boolean, visibleFunctions?: Map<string, import("../semantic/types.js").FunctionDeclaration>, visibleRecords?: Map<string, import("../semantic/types.js").RecordDeclaration>, visibleClasses?: Map<string, import("../semantic/types.js").ClassDeclaration>, visibleInterfaces?: Map<string, import("../semantic/types.js").InterfaceDeclaration>, visibleErrors?: Map<string, import("../semantic/types.js").ErrorDeclaration>, visibleCallEffects?: Map<string, Set<string>>}} [options] - Resolved program validation context.
|
|
60
61
|
* @returns {void}
|
|
61
62
|
*/
|
|
62
63
|
export function validateBackendModule(module, language, options = {}) {
|
|
@@ -89,11 +90,19 @@ export function validateBackendModule(module, language, options = {}) {
|
|
|
89
90
|
if (classes.length > 0 && !task033Languages.has(language)) {
|
|
90
91
|
unsupportedCapability(language, "Task 033 reference classes, methods, and constructors", classes[0]?.location ?? module.location)
|
|
91
92
|
}
|
|
93
|
+
const declaredInterfaces = module.interfaces
|
|
94
|
+
const interfaces = declaredInterfaces === undefined ? [] : declaredInterfaces
|
|
95
|
+
|
|
96
|
+
if (!Array.isArray(interfaces)) unsupportedCapability(language, "missing or invalid interface declarations", module.location)
|
|
97
|
+
if (interfaces.length > 0 && !boundedSubtypingLanguages.has(language)) {
|
|
98
|
+
unsupportedCapability(language, "Task 048 bounded subtyping interfaces", interfaces[0]?.location ?? module.location)
|
|
99
|
+
}
|
|
92
100
|
if (!task011Languages.has(language)) rejectTypedErrors(module, language)
|
|
93
101
|
if (!task032Languages.has(language)) rejectConditionControlledLoops(module, language)
|
|
94
102
|
if (!task014Languages.has(language)) rejectOrderedMapCapability(module, language)
|
|
95
103
|
validateRecordTargets(records, module.functions, language, module.location)
|
|
96
|
-
|
|
104
|
+
validateInterfaceTargets(interfaces, records, errors, module.functions, language, module.location)
|
|
105
|
+
validateReferenceClassTargets(classes, records, errors, module.functions, interfaces, language, module.location)
|
|
97
106
|
if (!task008Languages.has(language)) rejectIterationStatements(module, language)
|
|
98
107
|
if (!task007Languages.has(language)) rejectOptionalTypes(module, language)
|
|
99
108
|
if (!task006Languages.has(language)) rejectCollectionTypes(module, language)
|
|
@@ -163,6 +172,7 @@ export function validateBackendModule(module, language, options = {}) {
|
|
|
163
172
|
classes: options.visibleClasses,
|
|
164
173
|
errors: options.visibleErrors,
|
|
165
174
|
functions: options.visibleFunctions,
|
|
175
|
+
interfaces: options.visibleInterfaces,
|
|
166
176
|
records: options.visibleRecords
|
|
167
177
|
})
|
|
168
178
|
}
|
|
@@ -382,7 +392,99 @@ function validateRecordTargets(records, functions, language, moduleLocation) {
|
|
|
382
392
|
* @param {import("../semantic/types.js").SourceLocation | undefined} moduleLocation - Fallback location.
|
|
383
393
|
* @returns {void}
|
|
384
394
|
*/
|
|
385
|
-
|
|
395
|
+
/**
|
|
396
|
+
* Validates the exact target-visible shape of every interface and its override contracts before output allocation.
|
|
397
|
+
* @param {import("../semantic/types.js").InterfaceDeclaration[]} interfaces - Candidate interfaces.
|
|
398
|
+
* @param {import("../semantic/types.js").RecordDeclaration[]} records - Existing records.
|
|
399
|
+
* @param {import("../semantic/types.js").ErrorDeclaration[]} errors - Existing typed errors.
|
|
400
|
+
* @param {import("../semantic/types.js").FunctionDeclaration[]} functions - Existing functions.
|
|
401
|
+
* @param {import("../semantic/types.js").BackendLanguage} language - Target language.
|
|
402
|
+
* @param {import("../semantic/types.js").SourceLocation | undefined} moduleLocation - Fallback location.
|
|
403
|
+
* @returns {void}
|
|
404
|
+
*/
|
|
405
|
+
function validateInterfaceTargets(interfaces, records, errors, functions, language, moduleLocation) {
|
|
406
|
+
if (interfaces.length == 0) return
|
|
407
|
+
const byId = new Map(interfaces.map((declaration) => [declaration.id, declaration]))
|
|
408
|
+
const targetNames = new Set()
|
|
409
|
+
|
|
410
|
+
for (const declaration of [...records, ...errors, ...functions]) {
|
|
411
|
+
if (!declaration || typeof declaration != "object" || Array.isArray(declaration)) continue
|
|
412
|
+
const name = Reflect.get(declaration, "name")
|
|
413
|
+
|
|
414
|
+
if (typeof name == "string") targetNames.add(language == "php" ? name.toLowerCase() : name)
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
for (let interfaceIndex = 0; interfaceIndex < interfaces.length; interfaceIndex += 1) {
|
|
418
|
+
const declaration = interfaces[interfaceIndex]
|
|
419
|
+
const location = diagnosticLocation(declaration?.location, moduleLocation)
|
|
420
|
+
const keys = declaration && typeof declaration == "object" && !Array.isArray(declaration)
|
|
421
|
+
? Object.keys(declaration).filter((key) => key != "sourceProvenance").sort().join(",") : ""
|
|
422
|
+
|
|
423
|
+
if (!declaration || declaration.kind != "InterfaceDeclaration" ||
|
|
424
|
+
!["extends,id,kind,location,methods,name", "id,kind,location,methods,name"].includes(keys) ||
|
|
425
|
+
!isDenseArray(declaration.methods) ||
|
|
426
|
+
!new RegExp(`^(?:[a-z][a-z0-9._-]*#)?interface:${interfaceIndex}$`, "u").test(String(declaration.id))) {
|
|
427
|
+
unsupportedCapability(language, "missing or invalid interface declaration", location)
|
|
428
|
+
}
|
|
429
|
+
const declarationLocation = requireSemanticLocation(declaration.location, language, "interface declaration location", location)
|
|
430
|
+
|
|
431
|
+
validateTargetTypeIdentifier(language, declaration.name, declarationLocation)
|
|
432
|
+
const targetName = language == "php" ? declaration.name.toLowerCase() : declaration.name
|
|
433
|
+
|
|
434
|
+
if (targetNames.has(targetName)) unsupportedCapability(language, `interface collision '${declaration.name}'`, declarationLocation)
|
|
435
|
+
targetNames.add(targetName)
|
|
436
|
+
const methodNames = new Set()
|
|
437
|
+
|
|
438
|
+
for (let methodIndex = 0; methodIndex < declaration.methods.length; methodIndex += 1) {
|
|
439
|
+
const method = declaration.methods[methodIndex]
|
|
440
|
+
const methodLocation = diagnosticLocation(method?.location, declarationLocation)
|
|
441
|
+
const methodKeys = method && typeof method == "object" && !Array.isArray(method)
|
|
442
|
+
? Object.keys(method).filter((key) => key != "sourceProvenance").sort().join(",") : ""
|
|
443
|
+
|
|
444
|
+
if (!method || method.kind != "InterfaceMethodDeclaration" || methodKeys != "id,kind,location,name,parameters,returnType" ||
|
|
445
|
+
method.id != `${declaration.id}:method:${methodIndex}` || !isDenseArray(method.parameters)) {
|
|
446
|
+
unsupportedCapability(language, "missing or invalid interface method declaration", methodLocation)
|
|
447
|
+
}
|
|
448
|
+
const exactMethodLocation = requireSemanticLocation(method.location, language, "interface method declaration location", declarationLocation)
|
|
449
|
+
|
|
450
|
+
validateTargetIdentifier(language, method.name, "interface method", exactMethodLocation)
|
|
451
|
+
const normalized = language == "php" ? method.name.toLowerCase() : method.name
|
|
452
|
+
|
|
453
|
+
if (methodNames.has(normalized)) unsupportedCapability(language, `duplicate interface method '${method.name}'`, exactMethodLocation)
|
|
454
|
+
methodNames.add(normalized)
|
|
455
|
+
validateReferenceClassParameters(method.parameters, "interface method", language, exactMethodLocation)
|
|
456
|
+
}
|
|
457
|
+
const parents = declaration.extends ?? []
|
|
458
|
+
|
|
459
|
+
for (const parent of parents) {
|
|
460
|
+
const parentKeys = parent && typeof parent == "object" && !Array.isArray(parent)
|
|
461
|
+
? Object.keys(parent).filter((key) => key != "sourceProvenance").sort().join(",") : ""
|
|
462
|
+
|
|
463
|
+
if (!parent || parent.kind != "InterfaceType" || parentKeys != "declarationId,kind" ||
|
|
464
|
+
!byId.has(parent.declarationId)) {
|
|
465
|
+
unsupportedCapability(language, "missing or invalid interface parent reference", declarationLocation)
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
for (const declaration of interfaces) {
|
|
470
|
+
for (const parent of declaration.extends ?? []) {
|
|
471
|
+
if (parent.declarationId == declaration.id) unsupportedCapability(language, "interface extends itself", declaration.location)
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Validates the exact target-visible shape of every reference class before output allocation.
|
|
478
|
+
* @param {import("../semantic/types.js").ClassDeclaration[]} classes - Candidate classes.
|
|
479
|
+
* @param {import("../semantic/types.js").RecordDeclaration[]} records - Existing records.
|
|
480
|
+
* @param {import("../semantic/types.js").ErrorDeclaration[]} errors - Existing typed errors.
|
|
481
|
+
* @param {import("../semantic/types.js").FunctionDeclaration[]} functions - Existing functions.
|
|
482
|
+
* @param {import("../semantic/types.js").InterfaceDeclaration[]} interfaces - Existing interfaces.
|
|
483
|
+
* @param {import("../semantic/types.js").BackendLanguage} language - Target language.
|
|
484
|
+
* @param {import("../semantic/types.js").SourceLocation | undefined} moduleLocation - Fallback location.
|
|
485
|
+
* @returns {void}
|
|
486
|
+
*/
|
|
487
|
+
function validateReferenceClassTargets(classes, records, errors, functions, interfaces, language, moduleLocation) {
|
|
386
488
|
const targetNames = new Set()
|
|
387
489
|
|
|
388
490
|
for (const declaration of [...records, ...errors, ...functions]) {
|
|
@@ -400,7 +502,8 @@ function validateReferenceClassTargets(classes, records, errors, functions, lang
|
|
|
400
502
|
? Object.keys(declaration).filter((key) => key != "sourceProvenance").sort().join(",") : ""
|
|
401
503
|
|
|
402
504
|
if (!declaration || declaration.kind != "ClassDeclaration" ||
|
|
403
|
-
!["constructor,fields,id,kind,location,methods,name", "constructor,fields,id,kind,location,methods,name,ownership"
|
|
505
|
+
!["constructor,fields,id,implements,kind,location,methods,name", "constructor,fields,id,implements,kind,location,methods,name,ownership",
|
|
506
|
+
"constructor,fields,id,kind,location,methods,name", "constructor,fields,id,kind,location,methods,name,ownership"].includes(keys) ||
|
|
404
507
|
!isDenseArray(declaration.fields) || declaration.fields.length == 0 || !isDenseArray(declaration.methods) ||
|
|
405
508
|
!new RegExp(`^(?:[a-z][a-z0-9._-]*#)?class:${classIndex}$`, "u").test(String(declaration.id))) {
|
|
406
509
|
unsupportedCapability(language, "missing or invalid reference class declaration", location)
|
|
@@ -459,7 +562,9 @@ function validateReferenceClassTargets(classes, records, errors, functions, lang
|
|
|
459
562
|
? Object.keys(method).filter((key) => key != "sourceProvenance").sort().join(",") : ""
|
|
460
563
|
|
|
461
564
|
if (!method || method.kind != "MethodDeclaration" ||
|
|
462
|
-
!["body,id,kind,location,name,parameters,returnType",
|
|
565
|
+
!["body,id,implements,kind,location,name,parameters,returnType",
|
|
566
|
+
"body,effects,failureIds,id,implements,kind,location,name,parameters,returnType",
|
|
567
|
+
"body,id,kind,location,name,parameters,returnType",
|
|
463
568
|
"body,effects,failureIds,id,kind,location,name,parameters,returnType"].includes(methodKeys) ||
|
|
464
569
|
method.id != `${declaration.id}:method:${methodIndex}` || !isDenseArray(method.parameters)) {
|
|
465
570
|
unsupportedCapability(language, "missing or invalid method declaration", methodLocation)
|
|
@@ -492,13 +597,104 @@ function validateReferenceClassTargets(classes, records, errors, functions, lang
|
|
|
492
597
|
methodNames.add(normalized)
|
|
493
598
|
validateReferenceClassParameters(method.parameters, "method", language, exactMethodLocation)
|
|
494
599
|
}
|
|
600
|
+
const implemented = declaration.implements ?? []
|
|
601
|
+
|
|
602
|
+
for (const reference of implemented) {
|
|
603
|
+
const referenceKeys = reference && typeof reference == "object" && !Array.isArray(reference)
|
|
604
|
+
? Object.keys(reference).filter((key) => key != "sourceProvenance").sort().join(",") : ""
|
|
605
|
+
|
|
606
|
+
if (!reference || reference.kind != "InterfaceType" || referenceKeys != "declarationId,kind" ||
|
|
607
|
+
!interfaces.some((candidate) => candidate.id == reference.declarationId)) {
|
|
608
|
+
unsupportedCapability(language, "missing or invalid implemented interface reference", location)
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
const satisfied = new Set()
|
|
612
|
+
|
|
613
|
+
for (const reference of implemented) {
|
|
614
|
+
for (const member of flattenedTargetInterfaceMembers(reference.declarationId, interfaces, language, location)) {
|
|
615
|
+
const method = declaration.methods.find((candidate) => candidate.name == member.name)
|
|
616
|
+
|
|
617
|
+
if (!method) unsupportedCapability(language, `interface method '${member.name}' is not implemented`, location)
|
|
618
|
+
if (satisfied.has(member.name)) unsupportedCapability(language, `duplicate interface implementation '${member.name}'`, location)
|
|
619
|
+
if (method.parameters.length != member.parameters.length ||
|
|
620
|
+
method.parameters.some((parameter, index) => !sameTargetType(parameter.type, member.parameters[index].type))) {
|
|
621
|
+
unsupportedCapability(language, `interface method signature mismatch '${member.name}'`, location)
|
|
622
|
+
}
|
|
623
|
+
if (!sameTargetType(method.returnType, member.returnType)) {
|
|
624
|
+
unsupportedCapability(language, `interface method return mismatch '${member.name}'`, location)
|
|
625
|
+
}
|
|
626
|
+
satisfied.add(member.name)
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
/**
|
|
633
|
+
* Flattens one target-validated interface signature set in declaration order.
|
|
634
|
+
* @param {string} interfaceId - Interface identity.
|
|
635
|
+
* @param {import("../semantic/types.js").InterfaceDeclaration[]} interfaces - Interfaces in declaration order.
|
|
636
|
+
* @param {import("../semantic/types.js").BackendLanguage} language - Target language.
|
|
637
|
+
* @param {import("../semantic/types.js").SourceLocation | undefined} location - Diagnostic location.
|
|
638
|
+
* @param {Set<string>} [active] - Active extends path.
|
|
639
|
+
* @returns {import("../semantic/types.js").InterfaceMethodDeclaration[]} Flattened members.
|
|
640
|
+
*/
|
|
641
|
+
function flattenedTargetInterfaceMembers(interfaceId, interfaces, language, location, active = new Set()) {
|
|
642
|
+
const declaration = interfaces.find((candidate) => candidate.id == interfaceId)
|
|
643
|
+
|
|
644
|
+
if (!declaration) unsupportedCapability(language, "missing or invalid implemented interface reference", location)
|
|
645
|
+
if (active.has(interfaceId)) unsupportedCapability(language, "interface extends itself", declaration.location)
|
|
646
|
+
active.add(interfaceId)
|
|
647
|
+
/** @type {import("../semantic/types.js").InterfaceMethodDeclaration[]} */
|
|
648
|
+
const members = []
|
|
649
|
+
|
|
650
|
+
for (const parent of declaration.extends ?? []) {
|
|
651
|
+
for (const member of flattenedTargetInterfaceMembers(parent.declarationId, interfaces, language, location, active)) {
|
|
652
|
+
if (!members.some((candidate) => candidate.name == member.name)) members.push(member)
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
for (const method of declaration.methods) {
|
|
656
|
+
if (!members.some((candidate) => candidate.name == method.name)) members.push(method)
|
|
495
657
|
}
|
|
658
|
+
active.delete(interfaceId)
|
|
659
|
+
|
|
660
|
+
return members
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
/**
|
|
664
|
+
* Compares two backend-validated semantic types structurally.
|
|
665
|
+
* @param {import("../semantic/types.js").SemanticFunctionReturnType} left - First type.
|
|
666
|
+
* @param {import("../semantic/types.js").SemanticFunctionReturnType} right - Second type.
|
|
667
|
+
* @returns {boolean} Whether the types are identical.
|
|
668
|
+
*/
|
|
669
|
+
function sameTargetType(left, right) {
|
|
670
|
+
return JSON.stringify(targetTypeIdentity(left)) == JSON.stringify(targetTypeIdentity(right))
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
/**
|
|
674
|
+
* Reduces one semantic type to its comparable target identity.
|
|
675
|
+
* @param {import("../semantic/types.js").SemanticFunctionReturnType} type - Semantic type.
|
|
676
|
+
* @returns {unknown} Comparable identity.
|
|
677
|
+
*/
|
|
678
|
+
function targetTypeIdentity(type) {
|
|
679
|
+
if (typeof type == "string") return type
|
|
680
|
+
if (type.kind == "TypeReference") return type.name
|
|
681
|
+
if (type.kind == "TypeVariableReference") return {kind: "TypeVariableReference", parameterId: type.parameterId}
|
|
682
|
+
if (type.kind == "RecordType") return {declarationId: type.declarationId, kind: "RecordType"}
|
|
683
|
+
if (type.kind == "ReferenceType") return {declarationId: type.declarationId, kind: "ReferenceType"}
|
|
684
|
+
if (type.kind == "InterfaceType") return {declarationId: type.declarationId, kind: "InterfaceType"}
|
|
685
|
+
if (type.kind == "OwnedResourceType") return {kind: "OwnedResourceType", resourceId: type.resourceId}
|
|
686
|
+
if (type.kind == "OwnedReferenceType") return {declarationId: type.declarationId, kind: "OwnedReferenceType"}
|
|
687
|
+
if (type.kind == "ListType") return {elementType: targetTypeIdentity(type.elementType), kind: "ListType"}
|
|
688
|
+
if (type.kind == "OptionalType") return {kind: "OptionalType", valueType: targetTypeIdentity(type.valueType)}
|
|
689
|
+
if (type.kind == "OrderedMapType") return {keyType: targetTypeIdentity(type.keyType), kind: "OrderedMapType", valueType: targetTypeIdentity(type.valueType)}
|
|
690
|
+
|
|
691
|
+
return {keyType: targetTypeIdentity(type.keyType), kind: "MapType", valueType: targetTypeIdentity(type.valueType)}
|
|
496
692
|
}
|
|
497
693
|
|
|
498
694
|
/**
|
|
499
695
|
* Validates one reference callable's exact parameter shape and target names.
|
|
500
696
|
* @param {import("../semantic/types.js").Parameter[]} parameters - Candidate parameters.
|
|
501
|
-
* @param {"constructor" | "method"} owner - Callable kind.
|
|
697
|
+
* @param {"constructor" | "interface method" | "method"} owner - Callable kind.
|
|
502
698
|
* @param {import("../semantic/types.js").BackendLanguage} language - Target language.
|
|
503
699
|
* @param {import("../semantic/types.js").SourceLocation | undefined} ownerLocation - Fallback location.
|
|
504
700
|
* @returns {void}
|
|
@@ -2095,6 +2291,14 @@ export function emitType(writer, type, path, language, javaBoxed = false) {
|
|
|
2095
2291
|
})
|
|
2096
2292
|
return
|
|
2097
2293
|
}
|
|
2294
|
+
if (type.kind == "InterfaceType") {
|
|
2295
|
+
const declaration = writer.interfaceForId(type.declarationId)
|
|
2296
|
+
|
|
2297
|
+
writer.mapped(writer.interfaceNameForId(type.declarationId), {
|
|
2298
|
+
mappingKind: "exact", name: declaration.name, node: type, path, role: "type"
|
|
2299
|
+
})
|
|
2300
|
+
return
|
|
2301
|
+
}
|
|
2098
2302
|
if (type.kind == "OwnedResourceType") {
|
|
2099
2303
|
const resource = writer.resourceForId(type.resourceId)
|
|
2100
2304
|
writer.mapped(writer.capabilityNominalName(resource.name), {mappingKind: "exact", name: resource.name, node: type, path, role: "type"})
|