stone-lang 0.1.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 +52 -0
- package/StoneEngine.js +879 -0
- package/StoneEngineService.js +1727 -0
- package/adapters/FileSystemAdapter.js +230 -0
- package/adapters/OutputAdapter.js +208 -0
- package/adapters/index.js +6 -0
- package/cli/CLIOutputAdapter.js +196 -0
- package/cli/DaemonClient.js +349 -0
- package/cli/JSONOutputAdapter.js +135 -0
- package/cli/ReplSession.js +567 -0
- package/cli/ViewerServer.js +590 -0
- package/cli/commands/check.js +84 -0
- package/cli/commands/daemon.js +189 -0
- package/cli/commands/kill.js +66 -0
- package/cli/commands/package.js +713 -0
- package/cli/commands/ps.js +65 -0
- package/cli/commands/run.js +537 -0
- package/cli/entry.js +169 -0
- package/cli/index.js +14 -0
- package/cli/stonec.js +358 -0
- package/cli/test-compiler.js +181 -0
- package/cli/viewer/index.html +495 -0
- package/daemon/IPCServer.js +455 -0
- package/daemon/ProcessManager.js +327 -0
- package/daemon/ProcessRunner.js +307 -0
- package/daemon/daemon.js +398 -0
- package/daemon/index.js +16 -0
- package/frontend/analysis/index.js +5 -0
- package/frontend/analysis/livenessAnalyzer.js +568 -0
- package/frontend/analysis/treeShaker.js +265 -0
- package/frontend/index.js +20 -0
- package/frontend/parsing/astBuilder.js +2196 -0
- package/frontend/parsing/index.js +7 -0
- package/frontend/parsing/sonParser.js +592 -0
- package/frontend/parsing/stoneAstTypes.js +703 -0
- package/frontend/parsing/terminal-registry.js +435 -0
- package/frontend/parsing/tokenizer.js +692 -0
- package/frontend/type-checker/OverloadedFunctionType.js +43 -0
- package/frontend/type-checker/TypeEnvironment.js +165 -0
- package/frontend/type-checker/bidirectionalInference.js +149 -0
- package/frontend/type-checker/index.js +10 -0
- package/frontend/type-checker/moduleAnalysis.js +248 -0
- package/frontend/type-checker/operatorMappings.js +35 -0
- package/frontend/type-checker/overloadResolution.js +605 -0
- package/frontend/type-checker/typeChecker.js +452 -0
- package/frontend/type-checker/typeCompatibility.js +389 -0
- package/frontend/type-checker/visitors/controlFlow.js +483 -0
- package/frontend/type-checker/visitors/functions.js +604 -0
- package/frontend/type-checker/visitors/index.js +38 -0
- package/frontend/type-checker/visitors/literals.js +341 -0
- package/frontend/type-checker/visitors/modules.js +159 -0
- package/frontend/type-checker/visitors/operators.js +109 -0
- package/frontend/type-checker/visitors/statements.js +768 -0
- package/frontend/types/index.js +5 -0
- package/frontend/types/operatorMap.js +134 -0
- package/frontend/types/types.js +2046 -0
- package/frontend/utils/errorCollector.js +244 -0
- package/frontend/utils/index.js +5 -0
- package/frontend/utils/moduleResolver.js +479 -0
- package/package.json +50 -0
- package/packages/browserCache.js +359 -0
- package/packages/fetcher.js +236 -0
- package/packages/index.js +130 -0
- package/packages/lockfile.js +271 -0
- package/packages/manifest.js +291 -0
- package/packages/packageResolver.js +356 -0
- package/packages/resolver.js +310 -0
- package/packages/semver.js +635 -0
|
@@ -0,0 +1,2046 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stone Type System
|
|
3
|
+
*
|
|
4
|
+
* This module defines the internal type representation for Stone's type system.
|
|
5
|
+
* Types are structural and support inference via unification.
|
|
6
|
+
*
|
|
7
|
+
* Key design principles:
|
|
8
|
+
* - Types are always optional (annotations are hints, not requirements)
|
|
9
|
+
* - Compile-time errors when annotated types don't match inferred types
|
|
10
|
+
* - No implicit coercion or broadcasting
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
// ============================================================================
|
|
14
|
+
// TYPE CONSTRUCTORS
|
|
15
|
+
// ============================================================================
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Primitive types: Num, Bool, String, Unit
|
|
19
|
+
*/
|
|
20
|
+
export class PrimitiveType {
|
|
21
|
+
constructor(name) {
|
|
22
|
+
this.kind = "primitive";
|
|
23
|
+
this.name = name; // 'num' | 'bool' | 'string' | 'unit'
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
toString() {
|
|
27
|
+
return this.name;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
equals(other) {
|
|
31
|
+
return other instanceof PrimitiveType && this.name === other.name;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Singleton instances for primitive types (lowercase names)
|
|
36
|
+
export const NUM = new PrimitiveType("num");
|
|
37
|
+
export const BOOL = new PrimitiveType("bool");
|
|
38
|
+
export const STRING = new PrimitiveType("string");
|
|
39
|
+
export const UNIT = new PrimitiveType("unit");
|
|
40
|
+
// Note: Complex numbers are now represented as records {re: num, im: num}
|
|
41
|
+
// The "complex" primitive type is kept for backwards compatibility in type annotations
|
|
42
|
+
|
|
43
|
+
// Backwards compatibility aliases
|
|
44
|
+
export const INT = NUM;
|
|
45
|
+
export const FLOAT = NUM;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Function type: (T1, T2, ...) -> R
|
|
49
|
+
*/
|
|
50
|
+
export class FunctionType {
|
|
51
|
+
constructor(paramTypes, returnType, constraints = []) {
|
|
52
|
+
this.kind = "function";
|
|
53
|
+
this.paramTypes = paramTypes; // Array<Type>
|
|
54
|
+
this.returnType = returnType; // Type
|
|
55
|
+
this.constraints = constraints; // Array of deferred constraints (e.g., rank requirements)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
toString() {
|
|
59
|
+
const params = this.paramTypes.map((t) => t.toString()).join(", ");
|
|
60
|
+
let str = `(${params}) -> ${this.returnType.toString()}`;
|
|
61
|
+
if (this.constraints.length > 0) {
|
|
62
|
+
const constraintStrs = this.constraints.map(c => {
|
|
63
|
+
if (c.type === 'rank') {
|
|
64
|
+
return `param${c.paramIndex}.rank=${c.requiredRank}`;
|
|
65
|
+
}
|
|
66
|
+
return JSON.stringify(c);
|
|
67
|
+
});
|
|
68
|
+
str += ` where [${constraintStrs.join(', ')}]`;
|
|
69
|
+
}
|
|
70
|
+
return str;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
equals(other) {
|
|
74
|
+
if (!(other instanceof FunctionType)) return false;
|
|
75
|
+
if (this.paramTypes.length !== other.paramTypes.length) return false;
|
|
76
|
+
for (let i = 0; i < this.paramTypes.length; i++) {
|
|
77
|
+
if (!typeEquals(this.paramTypes[i], other.paramTypes[i])) return false;
|
|
78
|
+
}
|
|
79
|
+
return typeEquals(this.returnType, other.returnType);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Create a fresh copy of this function type with fresh type variables.
|
|
84
|
+
* This is used for polymorphic functions like print() where each call
|
|
85
|
+
* should get its own type variable instance.
|
|
86
|
+
*
|
|
87
|
+
* Important: Resolved TypeVariables are instantiated as their resolved type,
|
|
88
|
+
* preserving constraints (e.g., if a TypeVariable was constrained to FunctionType).
|
|
89
|
+
*/
|
|
90
|
+
instantiate() {
|
|
91
|
+
const varMap = new Map();
|
|
92
|
+
const instantiateType = (type) => {
|
|
93
|
+
// Resolve TypeVariables first to preserve constraints
|
|
94
|
+
if (type instanceof TypeVariable) {
|
|
95
|
+
const resolved = type.resolve();
|
|
96
|
+
if (resolved !== type) {
|
|
97
|
+
// TypeVariable was constrained to a concrete type - instantiate that
|
|
98
|
+
return instantiateType(resolved);
|
|
99
|
+
}
|
|
100
|
+
// Still unresolved - create fresh TypeVariable
|
|
101
|
+
if (!varMap.has(type.id)) {
|
|
102
|
+
varMap.set(type.id, freshTypeVar());
|
|
103
|
+
}
|
|
104
|
+
return varMap.get(type.id);
|
|
105
|
+
}
|
|
106
|
+
if (type instanceof FunctionType) {
|
|
107
|
+
return new FunctionType(
|
|
108
|
+
type.paramTypes.map(instantiateType),
|
|
109
|
+
instantiateType(type.returnType),
|
|
110
|
+
type.constraints // Copy constraints
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
if (type instanceof ArrayType) {
|
|
114
|
+
return new ArrayType(instantiateType(type.elementType), type.rank);
|
|
115
|
+
}
|
|
116
|
+
if (type instanceof RecordType) {
|
|
117
|
+
const newFields = new Map();
|
|
118
|
+
for (const [k, v] of type.fields) {
|
|
119
|
+
newFields.set(k, instantiateType(v));
|
|
120
|
+
}
|
|
121
|
+
return new RecordType(newFields, type.open, type.explicit);
|
|
122
|
+
}
|
|
123
|
+
if (type instanceof DictionaryType) {
|
|
124
|
+
return new DictionaryType(
|
|
125
|
+
instantiateType(type.keyType),
|
|
126
|
+
instantiateType(type.valueType)
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
return type;
|
|
130
|
+
};
|
|
131
|
+
return new FunctionType(
|
|
132
|
+
this.paramTypes.map(instantiateType),
|
|
133
|
+
instantiateType(this.returnType),
|
|
134
|
+
this.constraints // Preserve constraints in instantiated type
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Record type: { field1: T1, field2: T2, ... }
|
|
141
|
+
* All records are closed (exact fields required)
|
|
142
|
+
*/
|
|
143
|
+
export class RecordType {
|
|
144
|
+
constructor(fields, open = false, explicit = false, requiredFields = null) {
|
|
145
|
+
this.kind = "record";
|
|
146
|
+
this.fields = fields; // Map<string, Type>
|
|
147
|
+
// open = true means this record type is an inference constraint from field access
|
|
148
|
+
// and can be extended with additional fields during type inference
|
|
149
|
+
// open = false means this is a concrete record (from object literal or explicit type)
|
|
150
|
+
this.open = open;
|
|
151
|
+
this.explicit = explicit; // true if from destructuring
|
|
152
|
+
// requiredFields tracks type descriptor fields without defaults (for schema records)
|
|
153
|
+
// If null, this is a regular record. If a Set, this is a schema record where
|
|
154
|
+
// these fields cannot be accessed directly (they have no default value).
|
|
155
|
+
this.requiredFields = requiredFields; // Set<string> | null
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
toString() {
|
|
159
|
+
const fieldStrs = [];
|
|
160
|
+
for (const [name, type] of this.fields) {
|
|
161
|
+
fieldStrs.push(`${name}: ${type.toString()}`);
|
|
162
|
+
}
|
|
163
|
+
return `{ ${fieldStrs.join(", ")} }`;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
equals(other) {
|
|
167
|
+
if (!(other instanceof RecordType)) return false;
|
|
168
|
+
if (this.fields.size !== other.fields.size) return false;
|
|
169
|
+
for (const [name, type] of this.fields) {
|
|
170
|
+
const otherType = other.fields.get(name);
|
|
171
|
+
if (!otherType || !typeEquals(type, otherType)) return false;
|
|
172
|
+
}
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Dictionary type: {[K]: V}
|
|
179
|
+
* For homogeneous key-value maps with uniform value types
|
|
180
|
+
*/
|
|
181
|
+
export class DictionaryType {
|
|
182
|
+
constructor(keyType, valueType) {
|
|
183
|
+
this.kind = "dictionary";
|
|
184
|
+
this.keyType = keyType; // Type (typically STRING)
|
|
185
|
+
this.valueType = valueType; // Type
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
toString() {
|
|
189
|
+
return `{[${this.keyType.toString()}]: ${this.valueType.toString()}}`;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
equals(other) {
|
|
193
|
+
if (!(other instanceof DictionaryType)) return false;
|
|
194
|
+
return typeEquals(this.keyType, other.keyType) &&
|
|
195
|
+
typeEquals(this.valueType, other.valueType);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Promoted type: Type | converter(param) | converter2(param) | ...
|
|
201
|
+
* Wraps a target type with promotion metadata, allowing arguments of the
|
|
202
|
+
* converter's input type to be automatically converted to the target type.
|
|
203
|
+
*
|
|
204
|
+
* Supports multiple converters (tried in order during type checking).
|
|
205
|
+
*
|
|
206
|
+
* Example: `a: complex | to_complex(a)` means:
|
|
207
|
+
* - targetType: complex
|
|
208
|
+
* - converters: [{funcName: "to_complex", sourceType: num}]
|
|
209
|
+
* - paramRef: "a"
|
|
210
|
+
*
|
|
211
|
+
* Example with multiple converters: `p: Point | from_num(p) | from_arr(p)` means:
|
|
212
|
+
* - targetType: Point
|
|
213
|
+
* - converters: [{funcName: "from_num", sourceType: num}, {funcName: "from_arr", sourceType: array<num,1>}]
|
|
214
|
+
* - paramRef: "p"
|
|
215
|
+
*/
|
|
216
|
+
export class PromotedType {
|
|
217
|
+
constructor(targetType, converterFuncOrConverters, paramRef, sourceType = null) {
|
|
218
|
+
this.kind = "promoted";
|
|
219
|
+
this.targetType = targetType; // The type after conversion
|
|
220
|
+
this.paramRef = paramRef; // Parameter name reference (for validation)
|
|
221
|
+
|
|
222
|
+
// Support both old single-converter API and new multi-converter API
|
|
223
|
+
if (typeof converterFuncOrConverters === 'string') {
|
|
224
|
+
// Old API: single converter function name
|
|
225
|
+
this.converters = [{ funcName: converterFuncOrConverters, sourceType: sourceType }];
|
|
226
|
+
this.converterFunc = converterFuncOrConverters;
|
|
227
|
+
this.sourceType = sourceType;
|
|
228
|
+
} else if (Array.isArray(converterFuncOrConverters)) {
|
|
229
|
+
// New API: array of converter objects
|
|
230
|
+
this.converters = converterFuncOrConverters;
|
|
231
|
+
// Backwards compatibility
|
|
232
|
+
this.converterFunc = this.converters[0]?.funcName || null;
|
|
233
|
+
this.sourceType = this.converters[0]?.sourceType || null;
|
|
234
|
+
} else {
|
|
235
|
+
// Fallback
|
|
236
|
+
this.converters = [];
|
|
237
|
+
this.converterFunc = null;
|
|
238
|
+
this.sourceType = null;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
toString() {
|
|
243
|
+
const converterStrs = this.converters.map(c => {
|
|
244
|
+
const sourceStr = c.sourceType ? ` (from ${c.sourceType.toString()})` : "";
|
|
245
|
+
return `${c.funcName}(${this.paramRef})${sourceStr}`;
|
|
246
|
+
});
|
|
247
|
+
return `${this.targetType.toString()} | ${converterStrs.join(" | ")}`;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
equals(other) {
|
|
251
|
+
if (!(other instanceof PromotedType)) return false;
|
|
252
|
+
if (!typeEquals(this.targetType, other.targetType)) return false;
|
|
253
|
+
if (this.converters.length !== other.converters.length) return false;
|
|
254
|
+
for (let i = 0; i < this.converters.length; i++) {
|
|
255
|
+
if (this.converters[i].funcName !== other.converters[i].funcName) return false;
|
|
256
|
+
}
|
|
257
|
+
return true;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Get the effective type for type checking purposes (the target type)
|
|
262
|
+
*/
|
|
263
|
+
getEffectiveType() {
|
|
264
|
+
return this.targetType;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Find a converter that can accept the given source type.
|
|
269
|
+
* Returns the converter object or null if none match.
|
|
270
|
+
*/
|
|
271
|
+
findConverterForType(sourceType) {
|
|
272
|
+
for (const converter of this.converters) {
|
|
273
|
+
if (converter.sourceType && typeEquals(converter.sourceType, sourceType)) {
|
|
274
|
+
return converter;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return null;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Get all source types that this promoted type can accept.
|
|
282
|
+
*/
|
|
283
|
+
getAllSourceTypes() {
|
|
284
|
+
return this.converters.map(c => c.sourceType).filter(Boolean);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Literal type: a single literal value used as a type
|
|
290
|
+
* Example: mode: "on" or level: 2
|
|
291
|
+
*
|
|
292
|
+
* Literal types are subtypes of their primitive kind:
|
|
293
|
+
* - "on" is a subtype of string
|
|
294
|
+
* - 42 is a subtype of num
|
|
295
|
+
*
|
|
296
|
+
* Only string and number literals are supported (no booleans).
|
|
297
|
+
*/
|
|
298
|
+
export class LiteralType {
|
|
299
|
+
constructor(value, primitiveKind) {
|
|
300
|
+
this.kind = "literal";
|
|
301
|
+
this.value = value; // The literal value: "on", 42
|
|
302
|
+
this.primitiveKind = primitiveKind; // 'string' | 'num'
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
toString() {
|
|
306
|
+
return this.primitiveKind === 'string' ? `"${this.value}"` : String(this.value);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
equals(other) {
|
|
310
|
+
if (other instanceof LiteralType) {
|
|
311
|
+
return this.value === other.value && this.primitiveKind === other.primitiveKind;
|
|
312
|
+
}
|
|
313
|
+
return false;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Check if this literal type is compatible with another type.
|
|
318
|
+
* A literal type is compatible with:
|
|
319
|
+
* - The same literal type
|
|
320
|
+
* - A LiteralUnionType that includes this value
|
|
321
|
+
* - Its base primitive type (for general compatibility)
|
|
322
|
+
*/
|
|
323
|
+
isCompatibleWith(other) {
|
|
324
|
+
if (other instanceof LiteralType) {
|
|
325
|
+
return this.value === other.value && this.primitiveKind === other.primitiveKind;
|
|
326
|
+
}
|
|
327
|
+
if (other instanceof LiteralUnionType) {
|
|
328
|
+
return other.primitiveKind === this.primitiveKind && other.values.includes(this.value);
|
|
329
|
+
}
|
|
330
|
+
if (other instanceof PrimitiveType) {
|
|
331
|
+
return other.name === this.primitiveKind;
|
|
332
|
+
}
|
|
333
|
+
return false;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Literal union type: a finite set of literal values
|
|
339
|
+
* Example: mode: "on" | "off" or level: 0 | 1 | 2
|
|
340
|
+
*
|
|
341
|
+
* All values must be of the same primitive kind.
|
|
342
|
+
*/
|
|
343
|
+
export class LiteralUnionType {
|
|
344
|
+
constructor(values, primitiveKind) {
|
|
345
|
+
this.kind = "literalUnion";
|
|
346
|
+
this.values = values; // Array of literal values: ["on", "off"]
|
|
347
|
+
this.primitiveKind = primitiveKind; // 'string' | 'num'
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
toString() {
|
|
351
|
+
const formatted = this.values.map(v =>
|
|
352
|
+
this.primitiveKind === 'string' ? `"${v}"` : String(v)
|
|
353
|
+
);
|
|
354
|
+
return formatted.join(" | ");
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
equals(other) {
|
|
358
|
+
if (!(other instanceof LiteralUnionType)) return false;
|
|
359
|
+
if (this.primitiveKind !== other.primitiveKind) return false;
|
|
360
|
+
if (this.values.length !== other.values.length) return false;
|
|
361
|
+
// Check same values (order-independent)
|
|
362
|
+
const thisSet = new Set(this.values);
|
|
363
|
+
return other.values.every(v => thisSet.has(v));
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Check if a value is one of the allowed values
|
|
368
|
+
*/
|
|
369
|
+
includes(value) {
|
|
370
|
+
return this.values.includes(value);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Check if another type is a subset of this literal union.
|
|
375
|
+
* Used for type compatibility checking.
|
|
376
|
+
*/
|
|
377
|
+
isSupersetOf(other) {
|
|
378
|
+
if (other instanceof LiteralType) {
|
|
379
|
+
return other.primitiveKind === this.primitiveKind && this.values.includes(other.value);
|
|
380
|
+
}
|
|
381
|
+
if (other instanceof LiteralUnionType) {
|
|
382
|
+
if (other.primitiveKind !== this.primitiveKind) return false;
|
|
383
|
+
const thisSet = new Set(this.values);
|
|
384
|
+
return other.values.every(v => thisSet.has(v));
|
|
385
|
+
}
|
|
386
|
+
return false;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// ListType has been removed - Stone uses only typed arrays (array<T,R>)
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Array type: array<T, R>
|
|
394
|
+
* For dense, rectangular, contiguous numeric arrays
|
|
395
|
+
* @param elementType - The element type (typically int or float)
|
|
396
|
+
* @param rank - The number of dimensions (compile-time integer)
|
|
397
|
+
*
|
|
398
|
+
* Automatically normalizes nested numeric arrays:
|
|
399
|
+
* array<array<num,R1>,R2> -> array<num,R1+R2>
|
|
400
|
+
* array<array<complex,R1>,R2> -> array<complex,R1+R2>
|
|
401
|
+
*/
|
|
402
|
+
export class ArrayType {
|
|
403
|
+
constructor(elementType, rank) {
|
|
404
|
+
this.kind = "array";
|
|
405
|
+
|
|
406
|
+
// Normalize nested numeric/complex arrays
|
|
407
|
+
if (elementType instanceof ArrayType) {
|
|
408
|
+
// Get the base element type and total rank
|
|
409
|
+
let baseElem = elementType.elementType;
|
|
410
|
+
let totalRank = rank + elementType.rank;
|
|
411
|
+
|
|
412
|
+
// Keep unwrapping while we have nested ArrayTypes
|
|
413
|
+
while (baseElem instanceof ArrayType) {
|
|
414
|
+
totalRank += baseElem.rank;
|
|
415
|
+
baseElem = baseElem.elementType;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// Only normalize if base element is numeric (num or complex)
|
|
419
|
+
if (baseElem instanceof PrimitiveType &&
|
|
420
|
+
(baseElem.name === "num" || baseElem.name === "complex")) {
|
|
421
|
+
this.elementType = baseElem;
|
|
422
|
+
this.rank = totalRank;
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
this.elementType = elementType;
|
|
428
|
+
this.rank = rank; // Integer >= 1
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
toString() {
|
|
432
|
+
return `array<${this.elementType.toString()}, ${this.rank}>`;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
equals(other) {
|
|
436
|
+
return (
|
|
437
|
+
other instanceof ArrayType &&
|
|
438
|
+
this.rank === other.rank &&
|
|
439
|
+
typeEquals(this.elementType, other.elementType)
|
|
440
|
+
);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Union type: T | U
|
|
446
|
+
* Used for heterogeneous data and optional patterns
|
|
447
|
+
*/
|
|
448
|
+
export class UnionType {
|
|
449
|
+
constructor(types) {
|
|
450
|
+
this.kind = "union";
|
|
451
|
+
this.types = types; // Array<Type>
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
toString() {
|
|
455
|
+
return this.types.map((t) => t.toString()).join(" | ");
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
equals(other) {
|
|
459
|
+
if (!(other instanceof UnionType)) return false;
|
|
460
|
+
if (this.types.length !== other.types.length) return false;
|
|
461
|
+
// Union equality is order-independent
|
|
462
|
+
for (const t of this.types) {
|
|
463
|
+
if (!other.types.some((ot) => typeEquals(t, ot))) return false;
|
|
464
|
+
}
|
|
465
|
+
return true;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/**
|
|
470
|
+
* Type variable: for inference
|
|
471
|
+
* Represents an unknown type that will be resolved via unification
|
|
472
|
+
*/
|
|
473
|
+
export class TypeVariable {
|
|
474
|
+
static counter = 0;
|
|
475
|
+
|
|
476
|
+
constructor(id = null) {
|
|
477
|
+
this.kind = "typevar";
|
|
478
|
+
this.id = id !== null ? id : TypeVariable.counter++;
|
|
479
|
+
this.resolved = null; // Will be set during unification
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
toString() {
|
|
483
|
+
if (this.resolved) {
|
|
484
|
+
return this.resolved.toString();
|
|
485
|
+
}
|
|
486
|
+
return `T${this.id}`;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
equals(other) {
|
|
490
|
+
if (this.resolved) {
|
|
491
|
+
return typeEquals(this.resolved, other);
|
|
492
|
+
}
|
|
493
|
+
return other instanceof TypeVariable && this.id === other.id;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Get the actual type, following resolution chain
|
|
498
|
+
*/
|
|
499
|
+
resolve() {
|
|
500
|
+
if (this.resolved) {
|
|
501
|
+
if (this.resolved instanceof TypeVariable) {
|
|
502
|
+
return this.resolved.resolve();
|
|
503
|
+
}
|
|
504
|
+
return this.resolved;
|
|
505
|
+
}
|
|
506
|
+
return this;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// ============================================================================
|
|
511
|
+
// TYPE UTILITIES
|
|
512
|
+
// ============================================================================
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* Normalize a type by resolving type variables and flattening nested numeric arrays.
|
|
516
|
+
* array<array<num,R1>,R2> -> array<num,R1+R2>
|
|
517
|
+
* array<array<complex,R1>,R2> -> array<complex,R1+R2>
|
|
518
|
+
*
|
|
519
|
+
* Handles both class-based types (ArrayType, etc.) and plain object types
|
|
520
|
+
* ({kind: "array", elem: ...}) used in overload resolution.
|
|
521
|
+
*/
|
|
522
|
+
export function normalizeType(type) {
|
|
523
|
+
if (!type) return type;
|
|
524
|
+
|
|
525
|
+
// Resolve type variables first
|
|
526
|
+
if (type instanceof TypeVariable) {
|
|
527
|
+
type = type.resolve();
|
|
528
|
+
if (type instanceof TypeVariable) {
|
|
529
|
+
return type; // Still unresolved
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// Handle class-based ArrayType
|
|
534
|
+
if (type instanceof ArrayType) {
|
|
535
|
+
// Recursively normalize the element type
|
|
536
|
+
const normalizedElem = normalizeType(type.elementType);
|
|
537
|
+
|
|
538
|
+
// If element type is also an ArrayType with numeric/complex base, flatten
|
|
539
|
+
if (normalizedElem instanceof ArrayType) {
|
|
540
|
+
// Get the base element type
|
|
541
|
+
let baseElem = normalizedElem.elementType;
|
|
542
|
+
let totalRank = type.rank + normalizedElem.rank;
|
|
543
|
+
|
|
544
|
+
// Check if base is numeric or complex
|
|
545
|
+
if (baseElem instanceof PrimitiveType &&
|
|
546
|
+
(baseElem.name === "num" || baseElem.name === "complex")) {
|
|
547
|
+
return new ArrayType(baseElem, totalRank);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// If element changed after normalization, create new ArrayType
|
|
552
|
+
if (normalizedElem !== type.elementType) {
|
|
553
|
+
return new ArrayType(normalizedElem, type.rank);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// Handle plain object array types (used in overload resolution)
|
|
558
|
+
if (type.kind === "array" && type.elem) {
|
|
559
|
+
// Recursively normalize the element type
|
|
560
|
+
const normalizedElem = normalizeType(type.elem);
|
|
561
|
+
|
|
562
|
+
// If element is also an array with numeric/complex base, flatten
|
|
563
|
+
if (normalizedElem.kind === "array") {
|
|
564
|
+
let baseElem = normalizedElem.elem;
|
|
565
|
+
let totalRank = type.rank + normalizedElem.rank;
|
|
566
|
+
|
|
567
|
+
// Check if base is numeric or complex
|
|
568
|
+
if (baseElem.kind === "primitive" &&
|
|
569
|
+
(baseElem.name === "num" || baseElem.name === "complex")) {
|
|
570
|
+
return { kind: "array", elem: baseElem, rank: totalRank };
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// If element changed after normalization, create new object
|
|
575
|
+
if (normalizedElem !== type.elem) {
|
|
576
|
+
return { kind: "array", elem: normalizedElem, rank: type.rank };
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// Normalize FunctionType
|
|
581
|
+
if (type instanceof FunctionType) {
|
|
582
|
+
const normalizedParams = type.paramTypes.map(normalizeType);
|
|
583
|
+
const normalizedReturn = normalizeType(type.returnType);
|
|
584
|
+
const paramsChanged = normalizedParams.some((p, i) => p !== type.paramTypes[i]);
|
|
585
|
+
if (paramsChanged || normalizedReturn !== type.returnType) {
|
|
586
|
+
return new FunctionType(normalizedParams, normalizedReturn);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// Normalize RecordType
|
|
591
|
+
if (type instanceof RecordType) {
|
|
592
|
+
const normalizedFields = new Map();
|
|
593
|
+
let changed = false;
|
|
594
|
+
for (const [name, fieldType] of type.fields) {
|
|
595
|
+
const normalizedField = normalizeType(fieldType);
|
|
596
|
+
normalizedFields.set(name, normalizedField);
|
|
597
|
+
if (normalizedField !== fieldType) changed = true;
|
|
598
|
+
}
|
|
599
|
+
if (changed) {
|
|
600
|
+
return new RecordType(normalizedFields, type.open);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
return type;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
/**
|
|
608
|
+
* Check if two types are structurally equal
|
|
609
|
+
*/
|
|
610
|
+
export function typeEquals(t1, t2) {
|
|
611
|
+
// Guard against undefined
|
|
612
|
+
if (!t1 || !t2) return false;
|
|
613
|
+
|
|
614
|
+
// Handle type variables
|
|
615
|
+
if (t1 instanceof TypeVariable) {
|
|
616
|
+
t1 = t1.resolve();
|
|
617
|
+
}
|
|
618
|
+
if (t2 instanceof TypeVariable) {
|
|
619
|
+
t2 = t2.resolve();
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
// Same reference
|
|
623
|
+
if (t1 === t2) return true;
|
|
624
|
+
|
|
625
|
+
// Both type variables (unresolved)
|
|
626
|
+
if (t1 instanceof TypeVariable && t2 instanceof TypeVariable) {
|
|
627
|
+
return t1.id === t2.id;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
// Different kinds
|
|
631
|
+
if (t1.kind !== t2.kind) return false;
|
|
632
|
+
|
|
633
|
+
// Delegate to type-specific equals
|
|
634
|
+
return t1.equals(t2);
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
/**
|
|
638
|
+
* Check if 'from' type is assignable to 'to' type.
|
|
639
|
+
* This considers literals as subtypes of their base primitives.
|
|
640
|
+
* LiteralType("on", "string") is assignable to string (primitive)
|
|
641
|
+
* LiteralType(5, "num") is assignable to num (primitive)
|
|
642
|
+
* LiteralType("on") is assignable to LiteralUnionType(["on", "off"])
|
|
643
|
+
* LiteralUnionType(["on"]) is assignable to LiteralUnionType(["on", "off"])
|
|
644
|
+
*/
|
|
645
|
+
export function isAssignableTo(from, to) {
|
|
646
|
+
// Guard against undefined
|
|
647
|
+
if (!from || !to) return false;
|
|
648
|
+
|
|
649
|
+
// Resolve type variables
|
|
650
|
+
if (from instanceof TypeVariable) {
|
|
651
|
+
from = from.resolve();
|
|
652
|
+
}
|
|
653
|
+
if (to instanceof TypeVariable) {
|
|
654
|
+
to = to.resolve();
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
// Exact equality
|
|
658
|
+
if (typeEquals(from, to)) return true;
|
|
659
|
+
|
|
660
|
+
// LiteralType is assignable to its base primitive
|
|
661
|
+
if (from instanceof LiteralType && to instanceof PrimitiveType) {
|
|
662
|
+
if (from.primitiveKind === "string" && to.name === "string") return true;
|
|
663
|
+
if (from.primitiveKind === "num" && to.name === "num") return true;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
// LiteralUnionType is assignable to its base primitive
|
|
667
|
+
if (from instanceof LiteralUnionType && to instanceof PrimitiveType) {
|
|
668
|
+
if (from.primitiveKind === "string" && to.name === "string") return true;
|
|
669
|
+
if (from.primitiveKind === "num" && to.name === "num") return true;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// LiteralType is assignable to LiteralUnionType if the value is in the union
|
|
673
|
+
if (from instanceof LiteralType && to instanceof LiteralUnionType) {
|
|
674
|
+
if (from.primitiveKind === to.primitiveKind && to.values.includes(from.value)) {
|
|
675
|
+
return true;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
// LiteralUnionType is assignable to LiteralUnionType if from is a subset of to
|
|
680
|
+
if (from instanceof LiteralUnionType && to instanceof LiteralUnionType) {
|
|
681
|
+
if (from.primitiveKind === to.primitiveKind) {
|
|
682
|
+
const toSet = new Set(to.values);
|
|
683
|
+
return from.values.every(v => toSet.has(v));
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
return false;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
/**
|
|
691
|
+
* Check if a type is numeric (num)
|
|
692
|
+
*/
|
|
693
|
+
export function isNumeric(type) {
|
|
694
|
+
if (type instanceof TypeVariable) {
|
|
695
|
+
type = type.resolve();
|
|
696
|
+
}
|
|
697
|
+
return type instanceof PrimitiveType && type.name === "num";
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
/**
|
|
701
|
+
* Check if a type is numeric or complex (for arithmetic operations)
|
|
702
|
+
* Recognizes both primitive "complex" and record {re: num, im: num}
|
|
703
|
+
*/
|
|
704
|
+
export function isNumericOrComplex(type) {
|
|
705
|
+
if (type instanceof TypeVariable) {
|
|
706
|
+
type = type.resolve();
|
|
707
|
+
}
|
|
708
|
+
if (type instanceof PrimitiveType) {
|
|
709
|
+
return type.name === "num" || type.name === "complex";
|
|
710
|
+
}
|
|
711
|
+
// Also recognize complex record type {re: num, im: num}
|
|
712
|
+
return isComplexRecord(type);
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
/**
|
|
716
|
+
* Check if a type is the complex record type {re: num, im: num}
|
|
717
|
+
*/
|
|
718
|
+
function isComplexRecord(type) {
|
|
719
|
+
if (!type || type.kind !== "record") return false;
|
|
720
|
+
const fields = type.fields;
|
|
721
|
+
if (!(fields instanceof Map)) return false;
|
|
722
|
+
if (fields.size !== 2) return false;
|
|
723
|
+
const reType = fields.get("re");
|
|
724
|
+
const imType = fields.get("im");
|
|
725
|
+
if (!reType || !imType) return false;
|
|
726
|
+
return (reType.kind === "primitive" && reType.name === "num" &&
|
|
727
|
+
imType.kind === "primitive" && imType.name === "num");
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
/**
|
|
731
|
+
* Check if a type is complex (primitive or record)
|
|
732
|
+
* Recognizes both primitive "complex" and record {re: num, im: num}
|
|
733
|
+
*/
|
|
734
|
+
export function isComplex(type) {
|
|
735
|
+
if (type instanceof TypeVariable) {
|
|
736
|
+
type = type.resolve();
|
|
737
|
+
}
|
|
738
|
+
if (type instanceof PrimitiveType && type.name === "complex") {
|
|
739
|
+
return true;
|
|
740
|
+
}
|
|
741
|
+
return isComplexRecord(type);
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
/**
|
|
745
|
+
* Check if a type is an array type
|
|
746
|
+
*/
|
|
747
|
+
export function isArrayType(type) {
|
|
748
|
+
if (type instanceof TypeVariable) {
|
|
749
|
+
type = type.resolve();
|
|
750
|
+
}
|
|
751
|
+
return type instanceof ArrayType;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
// isListType has been removed - Stone uses only typed arrays
|
|
755
|
+
|
|
756
|
+
/**
|
|
757
|
+
* Create a fresh type variable
|
|
758
|
+
*/
|
|
759
|
+
export function freshTypeVar() {
|
|
760
|
+
return new TypeVariable();
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
/**
|
|
764
|
+
* Flatten nested array types to a single multi-dimensional array
|
|
765
|
+
* e.g., array<array<num, 1>, 1> -> array<num, 2>
|
|
766
|
+
* Returns the original type if no flattening needed
|
|
767
|
+
*/
|
|
768
|
+
export function flattenNestedArrayType(t) {
|
|
769
|
+
if (t.kind !== "array") return t;
|
|
770
|
+
|
|
771
|
+
// Resolve the element type if it's a TypeVariable
|
|
772
|
+
let elemType = t.elementType;
|
|
773
|
+
if (elemType instanceof TypeVariable) {
|
|
774
|
+
elemType = elemType.resolve();
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
// If element type is also an array with concrete ranks, flatten
|
|
778
|
+
if (elemType.kind === "array" &&
|
|
779
|
+
typeof t.rank === "number" &&
|
|
780
|
+
typeof elemType.rank === "number") {
|
|
781
|
+
// Recursively flatten the inner array first
|
|
782
|
+
const flatInner = flattenNestedArrayType(elemType);
|
|
783
|
+
// Combine ranks: outer rank + inner (flattened) rank
|
|
784
|
+
const combinedRank = t.rank + flatInner.rank;
|
|
785
|
+
return new ArrayType(flatInner.elementType, combinedRank);
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
return t;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
/**
|
|
792
|
+
* Attempt to unify two types
|
|
793
|
+
* Returns { success: true, substitution } or { success: false, error }
|
|
794
|
+
*/
|
|
795
|
+
export function unify(t1, t2) {
|
|
796
|
+
// Guard against undefined
|
|
797
|
+
if (!t1 || !t2) {
|
|
798
|
+
return { success: false, error: "Cannot unify undefined types" };
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
// Resolve type variables
|
|
802
|
+
if (t1 instanceof TypeVariable) {
|
|
803
|
+
t1 = t1.resolve();
|
|
804
|
+
}
|
|
805
|
+
if (t2 instanceof TypeVariable) {
|
|
806
|
+
t2 = t2.resolve();
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
// Same type
|
|
810
|
+
if (typeEquals(t1, t2)) {
|
|
811
|
+
return { success: true };
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
// Handle LiteralType and LiteralUnionType:
|
|
815
|
+
// - Literal types are subtypes of their primitive type
|
|
816
|
+
// - LiteralType("on") is assignable to STRING
|
|
817
|
+
// - STRING is NOT assignable to LiteralType("on") (strict compile-time checking)
|
|
818
|
+
// - Different literals can unify to their common primitive when types flow together (e.g., branches)
|
|
819
|
+
// Note: Skip this block if either type is PromotedType - let the PromotedType handler deal with it
|
|
820
|
+
|
|
821
|
+
if ((t1 instanceof LiteralType || t2 instanceof LiteralType ||
|
|
822
|
+
t1 instanceof LiteralUnionType || t2 instanceof LiteralUnionType) &&
|
|
823
|
+
!(t1 instanceof PromotedType) && !(t2 instanceof PromotedType)) {
|
|
824
|
+
|
|
825
|
+
// Exact literal match
|
|
826
|
+
if (t1 instanceof LiteralType && t2 instanceof LiteralType) {
|
|
827
|
+
if (t1.value === t2.value && t1.primitiveKind === t2.primitiveKind) {
|
|
828
|
+
return { success: true };
|
|
829
|
+
}
|
|
830
|
+
// Different literals of the same kind - create a union type
|
|
831
|
+
// This happens in branch unification (if/else returning different literals)
|
|
832
|
+
if (t1.primitiveKind === t2.primitiveKind) {
|
|
833
|
+
// Return the unified type as a LiteralUnionType containing both values
|
|
834
|
+
const unifiedType = new LiteralUnionType([t1.value, t2.value], t1.primitiveKind);
|
|
835
|
+
return { success: true, unifiedType };
|
|
836
|
+
}
|
|
837
|
+
return { success: false, error: `Type mismatch: expected ${formatType(t1)}, got ${formatType(t2)}` };
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
// Literal union matches
|
|
841
|
+
// When t1 is expected and t2 is actual, t2 must be a subset of t1
|
|
842
|
+
if (t1 instanceof LiteralUnionType && t2 instanceof LiteralUnionType) {
|
|
843
|
+
if (t1.primitiveKind === t2.primitiveKind) {
|
|
844
|
+
const t1Set = new Set(t1.values);
|
|
845
|
+
const t2Set = new Set(t2.values);
|
|
846
|
+
const t2IsSubsetOfT1 = t2.values.every(v => t1Set.has(v));
|
|
847
|
+
const t1IsSubsetOfT2 = t1.values.every(v => t2Set.has(v));
|
|
848
|
+
|
|
849
|
+
if (t2IsSubsetOfT1) {
|
|
850
|
+
return { success: true }; // t2 is subset of t1
|
|
851
|
+
}
|
|
852
|
+
if (t1IsSubsetOfT2) {
|
|
853
|
+
return { success: true }; // t1 is subset of t2 (symmetric for inference)
|
|
854
|
+
}
|
|
855
|
+
// Neither is subset - for branch unification return merged type
|
|
856
|
+
const mergedValues = [...new Set([...t1.values, ...t2.values])];
|
|
857
|
+
const unifiedType = new LiteralUnionType(mergedValues, t1.primitiveKind);
|
|
858
|
+
return { success: true, unifiedType };
|
|
859
|
+
}
|
|
860
|
+
return { success: false, error: `Type mismatch: expected ${formatType(t1)}, got ${formatType(t2)}` };
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
// Literal into literal union
|
|
864
|
+
if (t1 instanceof LiteralUnionType && t2 instanceof LiteralType) {
|
|
865
|
+
if (t1.primitiveKind === t2.primitiveKind) {
|
|
866
|
+
if (t1.values.includes(t2.value)) {
|
|
867
|
+
return { success: true }; // Literal is in the union - valid
|
|
868
|
+
}
|
|
869
|
+
// Literal not in union - for branch unification return expanded type
|
|
870
|
+
const unifiedType = new LiteralUnionType([...t1.values, t2.value], t1.primitiveKind);
|
|
871
|
+
return { success: true, unifiedType };
|
|
872
|
+
}
|
|
873
|
+
return { success: false, error: `Type mismatch: expected ${formatType(t1)}, got ${formatType(t2)}` };
|
|
874
|
+
}
|
|
875
|
+
if (t2 instanceof LiteralUnionType && t1 instanceof LiteralType) {
|
|
876
|
+
if (t2.primitiveKind === t1.primitiveKind) {
|
|
877
|
+
if (t2.values.includes(t1.value)) {
|
|
878
|
+
return { success: true }; // Literal is in the union - valid
|
|
879
|
+
}
|
|
880
|
+
// Literal not in union - for branch unification return expanded type
|
|
881
|
+
const unifiedType = new LiteralUnionType([...t2.values, t1.value], t2.primitiveKind);
|
|
882
|
+
return { success: true, unifiedType };
|
|
883
|
+
}
|
|
884
|
+
return { success: false, error: `Type mismatch: expected ${formatType(t2)}, got ${formatType(t1)}` };
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
// Literal -> Primitive (subtype relationship): a literal is assignable to its base primitive
|
|
888
|
+
if (t1 instanceof PrimitiveType && t2 instanceof LiteralType) {
|
|
889
|
+
if ((t1.name === "string" && t2.primitiveKind === "string") ||
|
|
890
|
+
(t1.name === "num" && t2.primitiveKind === "num")) {
|
|
891
|
+
return { success: true };
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
if (t1 instanceof PrimitiveType && t2 instanceof LiteralUnionType) {
|
|
895
|
+
if ((t1.name === "string" && t2.primitiveKind === "string") ||
|
|
896
|
+
(t1.name === "num" && t2.primitiveKind === "num")) {
|
|
897
|
+
return { success: true };
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
// Primitive -> Literal: NOT allowed (strict compile-time checking)
|
|
902
|
+
// A general string cannot satisfy a literal constraint
|
|
903
|
+
if (t2 instanceof PrimitiveType && t1 instanceof LiteralType) {
|
|
904
|
+
return { success: false, error: `Type mismatch: expected ${formatType(t1)}, got ${formatType(t2)}. A general ${t2.name} cannot satisfy a literal constraint.` };
|
|
905
|
+
}
|
|
906
|
+
if (t2 instanceof PrimitiveType && t1 instanceof LiteralUnionType) {
|
|
907
|
+
return { success: false, error: `Type mismatch: expected ${formatType(t1)}, got ${formatType(t2)}. A general ${t2.name} cannot satisfy a literal constraint.` };
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
// Other mismatches
|
|
911
|
+
return { success: false, error: `Type mismatch: expected ${formatType(t1)}, got ${formatType(t2)}` };
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
// Handle PromotedType: unify against target type or source types (multi-converter)
|
|
915
|
+
if (t1 instanceof PromotedType) {
|
|
916
|
+
// First try to unify with target type (no promotion needed)
|
|
917
|
+
const targetResult = unify(t1.targetType, t2);
|
|
918
|
+
if (targetResult.success) {
|
|
919
|
+
return { success: true, promoted: false };
|
|
920
|
+
}
|
|
921
|
+
// Then try each converter's source type (for multi-converter support)
|
|
922
|
+
for (const converter of t1.converters) {
|
|
923
|
+
if (converter.sourceType) {
|
|
924
|
+
const sourceResult = unify(converter.sourceType, t2);
|
|
925
|
+
if (sourceResult.success) {
|
|
926
|
+
return { success: true, promoted: true, converter: converter.funcName };
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
// Legacy: try single sourceType for backwards compatibility
|
|
931
|
+
if (t1.sourceType) {
|
|
932
|
+
const sourceResult = unify(t1.sourceType, t2);
|
|
933
|
+
if (sourceResult.success) {
|
|
934
|
+
return { success: true, promoted: true, converter: t1.converterFunc };
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
return { success: false, error: `Cannot match ${formatType(t2)} to ${formatType(t1)}` };
|
|
938
|
+
}
|
|
939
|
+
if (t2 instanceof PromotedType) {
|
|
940
|
+
// Reverse case - less common but handle symmetrically
|
|
941
|
+
const targetResult = unify(t1, t2.targetType);
|
|
942
|
+
if (targetResult.success) {
|
|
943
|
+
return { success: true };
|
|
944
|
+
}
|
|
945
|
+
// Try each converter's source type
|
|
946
|
+
for (const converter of t2.converters) {
|
|
947
|
+
if (converter.sourceType) {
|
|
948
|
+
const sourceResult = unify(t1, converter.sourceType);
|
|
949
|
+
if (sourceResult.success) {
|
|
950
|
+
return { success: true };
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
// Legacy: try single sourceType
|
|
955
|
+
if (t2.sourceType) {
|
|
956
|
+
const sourceResult = unify(t1, t2.sourceType);
|
|
957
|
+
if (sourceResult.success) {
|
|
958
|
+
return { success: true };
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
return { success: false, error: `Cannot match ${formatType(t1)} to ${formatType(t2)}` };
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
// Type variable on left - bind it
|
|
965
|
+
if (t1 instanceof TypeVariable) {
|
|
966
|
+
// Occurs check: prevent infinite types
|
|
967
|
+
if (occursIn(t1, t2)) {
|
|
968
|
+
return { success: false, error: `Infinite type: ${t1} occurs in ${t2}` };
|
|
969
|
+
}
|
|
970
|
+
t1.resolved = t2;
|
|
971
|
+
return { success: true };
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
// Type variable on right - bind it
|
|
975
|
+
if (t2 instanceof TypeVariable) {
|
|
976
|
+
if (occursIn(t2, t1)) {
|
|
977
|
+
return { success: false, error: `Infinite type: ${t2} occurs in ${t1}` };
|
|
978
|
+
}
|
|
979
|
+
t2.resolved = t1;
|
|
980
|
+
return { success: true };
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
// Special case: DictionaryType can unify with RecordType if all values match
|
|
984
|
+
// This allows: dict: {[string]: num} = {"a": 1, "b": 2}
|
|
985
|
+
if (t1.kind === "dictionary" && t2.kind === "record") {
|
|
986
|
+
// Check that all record field values match the dictionary value type
|
|
987
|
+
for (const [fieldName, fieldType] of t2.fields) {
|
|
988
|
+
const valueResult = unify(t1.valueType, fieldType);
|
|
989
|
+
if (!valueResult.success) {
|
|
990
|
+
return {
|
|
991
|
+
success: false,
|
|
992
|
+
error: `Field '${fieldName}' has type ${fieldType.toString()}, expected ${t1.valueType.toString()}`,
|
|
993
|
+
};
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
return { success: true };
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
// Special case: Flatten nested array types
|
|
1000
|
+
// array<num, 2> should unify with array<array<num, 1>, 1>
|
|
1001
|
+
// This handles matrix construction via push of rows
|
|
1002
|
+
if (t1.kind === "array" && t2.kind === "array") {
|
|
1003
|
+
const flat1 = flattenNestedArrayType(t1);
|
|
1004
|
+
const flat2 = flattenNestedArrayType(t2);
|
|
1005
|
+
// If flattening changed anything, retry unification with flattened types
|
|
1006
|
+
if (flat1 !== t1 || flat2 !== t2) {
|
|
1007
|
+
return unify(flat1, flat2);
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
// Different kinds
|
|
1012
|
+
if (t1.kind !== t2.kind) {
|
|
1013
|
+
return {
|
|
1014
|
+
success: false,
|
|
1015
|
+
error: `Type mismatch: expected ${t1.toString()}, got ${t2.toString()}`,
|
|
1016
|
+
};
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
// Unify based on kind
|
|
1020
|
+
switch (t1.kind) {
|
|
1021
|
+
case "primitive":
|
|
1022
|
+
if (t1.name !== t2.name) {
|
|
1023
|
+
// Check if types are promotable (num -> complex)
|
|
1024
|
+
if (canPromote(t1, t2).can || canPromote(t2, t1).can) {
|
|
1025
|
+
return { success: true };
|
|
1026
|
+
}
|
|
1027
|
+
return {
|
|
1028
|
+
success: false,
|
|
1029
|
+
error: `Type mismatch: expected ${t1.name}, got ${t2.name}`,
|
|
1030
|
+
};
|
|
1031
|
+
}
|
|
1032
|
+
return { success: true };
|
|
1033
|
+
|
|
1034
|
+
case "function":
|
|
1035
|
+
if (t1.paramTypes.length !== t2.paramTypes.length) {
|
|
1036
|
+
return {
|
|
1037
|
+
success: false,
|
|
1038
|
+
error: `Function arity mismatch: expected ${t1.paramTypes.length} params, got ${t2.paramTypes.length}`,
|
|
1039
|
+
};
|
|
1040
|
+
}
|
|
1041
|
+
for (let i = 0; i < t1.paramTypes.length; i++) {
|
|
1042
|
+
const result = unify(t1.paramTypes[i], t2.paramTypes[i]);
|
|
1043
|
+
if (!result.success) return result;
|
|
1044
|
+
}
|
|
1045
|
+
return unify(t1.returnType, t2.returnType);
|
|
1046
|
+
|
|
1047
|
+
case "record":
|
|
1048
|
+
// If either record is open (from inference), merge fields
|
|
1049
|
+
// Otherwise require exact field match (closed records)
|
|
1050
|
+
if (t1.open || t2.open) {
|
|
1051
|
+
// Merge fields from both records into the one that should accumulate constraints
|
|
1052
|
+
// The open record gets extended with fields from the other
|
|
1053
|
+
const targetRecord = t1.open ? t1 : t2;
|
|
1054
|
+
const sourceRecord = t1.open ? t2 : t1;
|
|
1055
|
+
|
|
1056
|
+
// Unify common fields
|
|
1057
|
+
for (const [name, type] of sourceRecord.fields) {
|
|
1058
|
+
const existingType = targetRecord.fields.get(name);
|
|
1059
|
+
if (existingType) {
|
|
1060
|
+
const result = unify(existingType, type);
|
|
1061
|
+
if (!result.success) return result;
|
|
1062
|
+
} else {
|
|
1063
|
+
// Add new field from source to target
|
|
1064
|
+
targetRecord.fields.set(name, type);
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
return { success: true };
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
// Both records are closed - must have exactly the same fields
|
|
1071
|
+
if (t1.fields.size !== t2.fields.size) {
|
|
1072
|
+
return { success: false, error: "Record field count mismatch" };
|
|
1073
|
+
}
|
|
1074
|
+
for (const [name, type] of t1.fields) {
|
|
1075
|
+
const otherType = t2.fields.get(name);
|
|
1076
|
+
if (!otherType) {
|
|
1077
|
+
return { success: false, error: `Missing field: ${name}` };
|
|
1078
|
+
}
|
|
1079
|
+
const result = unify(type, otherType);
|
|
1080
|
+
if (!result.success) return result;
|
|
1081
|
+
}
|
|
1082
|
+
return { success: true };
|
|
1083
|
+
|
|
1084
|
+
case "dictionary":
|
|
1085
|
+
// Unify key types
|
|
1086
|
+
const keyResult = unify(t1.keyType, t2.keyType);
|
|
1087
|
+
if (!keyResult.success) {
|
|
1088
|
+
return { success: false, error: `Dictionary key type mismatch: ${keyResult.error}` };
|
|
1089
|
+
}
|
|
1090
|
+
// Unify value types
|
|
1091
|
+
const valueResult = unify(t1.valueType, t2.valueType);
|
|
1092
|
+
if (!valueResult.success) {
|
|
1093
|
+
return { success: false, error: `Dictionary value type mismatch: ${valueResult.error}` };
|
|
1094
|
+
}
|
|
1095
|
+
return { success: true };
|
|
1096
|
+
|
|
1097
|
+
case "list":
|
|
1098
|
+
return unify(t1.elementType, t2.elementType);
|
|
1099
|
+
|
|
1100
|
+
case "array":
|
|
1101
|
+
// Handle rank unification
|
|
1102
|
+
let rank1 = t1.rank;
|
|
1103
|
+
let rank2 = t2.rank;
|
|
1104
|
+
|
|
1105
|
+
// Helper to check if rank is a variable representation
|
|
1106
|
+
const isRankVar = (r) => {
|
|
1107
|
+
if (r instanceof TypeVariable) return true;
|
|
1108
|
+
if (typeof r === 'object' && r !== null && r.var) return true;
|
|
1109
|
+
return false;
|
|
1110
|
+
};
|
|
1111
|
+
|
|
1112
|
+
// Helper to get resolved rank (for TypeVariable instances)
|
|
1113
|
+
const resolveRank = (r) => {
|
|
1114
|
+
if (r instanceof TypeVariable) {
|
|
1115
|
+
const resolved = r.resolve();
|
|
1116
|
+
if (resolved instanceof TypeVariable) return r; // Still variable
|
|
1117
|
+
return resolved; // Resolved to a value
|
|
1118
|
+
}
|
|
1119
|
+
return r;
|
|
1120
|
+
};
|
|
1121
|
+
|
|
1122
|
+
rank1 = resolveRank(rank1);
|
|
1123
|
+
rank2 = resolveRank(rank2);
|
|
1124
|
+
|
|
1125
|
+
const rank1IsVar = isRankVar(rank1);
|
|
1126
|
+
const rank2IsVar = isRankVar(rank2);
|
|
1127
|
+
|
|
1128
|
+
// Check if ranks can be unified
|
|
1129
|
+
if (rank1IsVar && !rank2IsVar) {
|
|
1130
|
+
// rank1 is variable, rank2 is concrete - bind/constrain rank1
|
|
1131
|
+
if (rank1 instanceof TypeVariable) {
|
|
1132
|
+
rank1.resolved = true;
|
|
1133
|
+
rank1.resolvedTo = rank2;
|
|
1134
|
+
}
|
|
1135
|
+
// For { var: 'R' } format, we accept it (defer constraint)
|
|
1136
|
+
// The actual binding should happen elsewhere
|
|
1137
|
+
} else if (rank2IsVar && !rank1IsVar) {
|
|
1138
|
+
// rank2 is variable, rank1 is concrete - bind/constrain rank2
|
|
1139
|
+
if (rank2 instanceof TypeVariable) {
|
|
1140
|
+
rank2.resolved = true;
|
|
1141
|
+
rank2.resolvedTo = rank1;
|
|
1142
|
+
}
|
|
1143
|
+
// For { var: 'R' } format, we accept it (defer constraint)
|
|
1144
|
+
} else if (rank1IsVar && rank2IsVar) {
|
|
1145
|
+
// Both are variable - try to unify them
|
|
1146
|
+
if (rank1 instanceof TypeVariable && rank2 instanceof TypeVariable) {
|
|
1147
|
+
if (rank1.id !== rank2.id) {
|
|
1148
|
+
rank1.resolved = true;
|
|
1149
|
+
rank1.resolvedTo = rank2;
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
// For { var: 'R' } objects, we accept as compatible
|
|
1153
|
+
} else if (rank1 !== rank2) {
|
|
1154
|
+
// Both are concrete but different
|
|
1155
|
+
return {
|
|
1156
|
+
success: false,
|
|
1157
|
+
error: `Array rank mismatch: expected rank ${rank1}, got rank ${rank2}`,
|
|
1158
|
+
};
|
|
1159
|
+
}
|
|
1160
|
+
return unify(t1.elementType, t2.elementType);
|
|
1161
|
+
|
|
1162
|
+
case "union":
|
|
1163
|
+
// Union unification is complex - for now, require exact match
|
|
1164
|
+
if (t1.types.length !== t2.types.length) {
|
|
1165
|
+
return { success: false, error: "Union type mismatch" };
|
|
1166
|
+
}
|
|
1167
|
+
// Try to match each type in t1 with one in t2
|
|
1168
|
+
for (const ut1 of t1.types) {
|
|
1169
|
+
let matched = false;
|
|
1170
|
+
for (const ut2 of t2.types) {
|
|
1171
|
+
const result = unify(ut1, ut2);
|
|
1172
|
+
if (result.success) {
|
|
1173
|
+
matched = true;
|
|
1174
|
+
break;
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
if (!matched) {
|
|
1178
|
+
return { success: false, error: "Union type mismatch" };
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
return { success: true };
|
|
1182
|
+
|
|
1183
|
+
default:
|
|
1184
|
+
return { success: false, error: `Unknown type kind: ${t1.kind}` };
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
/**
|
|
1189
|
+
* Check if a type variable occurs in a type (for occurs check)
|
|
1190
|
+
*/
|
|
1191
|
+
function occursIn(tvar, type) {
|
|
1192
|
+
if (type instanceof TypeVariable) {
|
|
1193
|
+
type = type.resolve();
|
|
1194
|
+
if (type instanceof TypeVariable) {
|
|
1195
|
+
return tvar.id === type.id;
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
switch (type.kind) {
|
|
1200
|
+
case "primitive":
|
|
1201
|
+
return false;
|
|
1202
|
+
case "function":
|
|
1203
|
+
return (
|
|
1204
|
+
type.paramTypes.some((t) => occursIn(tvar, t)) ||
|
|
1205
|
+
occursIn(tvar, type.returnType)
|
|
1206
|
+
);
|
|
1207
|
+
case "record":
|
|
1208
|
+
for (const t of type.fields.values()) {
|
|
1209
|
+
if (occursIn(tvar, t)) return true;
|
|
1210
|
+
}
|
|
1211
|
+
return false;
|
|
1212
|
+
case "dictionary":
|
|
1213
|
+
return occursIn(tvar, type.keyType) || occursIn(tvar, type.valueType);
|
|
1214
|
+
case "list":
|
|
1215
|
+
return occursIn(tvar, type.elementType);
|
|
1216
|
+
case "array":
|
|
1217
|
+
return occursIn(tvar, type.elementType);
|
|
1218
|
+
case "union":
|
|
1219
|
+
return type.types.some((t) => occursIn(tvar, t));
|
|
1220
|
+
default:
|
|
1221
|
+
return false;
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
/**
|
|
1226
|
+
* Numeric promotion: num + num -> num
|
|
1227
|
+
* Simplified since we only have one numeric type
|
|
1228
|
+
*/
|
|
1229
|
+
export function numericPromotion(t1, t2) {
|
|
1230
|
+
if (t1 instanceof TypeVariable) t1 = t1.resolve();
|
|
1231
|
+
if (t2 instanceof TypeVariable) t2 = t2.resolve();
|
|
1232
|
+
|
|
1233
|
+
if (!isNumeric(t1) || !isNumeric(t2)) {
|
|
1234
|
+
return null; // Not numeric types
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
return NUM;
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
// ============================================================================
|
|
1241
|
+
// TYPE PARSING HELPERS
|
|
1242
|
+
// ============================================================================
|
|
1243
|
+
|
|
1244
|
+
/**
|
|
1245
|
+
* Parse a type name string into a Type
|
|
1246
|
+
* Used for converting parsed type annotations to internal types
|
|
1247
|
+
*/
|
|
1248
|
+
export function parseTypeName(name) {
|
|
1249
|
+
const lowered = name.toLowerCase();
|
|
1250
|
+
switch (lowered) {
|
|
1251
|
+
case "num":
|
|
1252
|
+
case "int": // backwards compatibility
|
|
1253
|
+
case "float": // backwards compatibility
|
|
1254
|
+
case "number": // convenience alias
|
|
1255
|
+
return NUM;
|
|
1256
|
+
case "bool":
|
|
1257
|
+
case "boolean":
|
|
1258
|
+
return BOOL;
|
|
1259
|
+
case "string":
|
|
1260
|
+
return STRING;
|
|
1261
|
+
case "unit":
|
|
1262
|
+
case "void":
|
|
1263
|
+
return UNIT;
|
|
1264
|
+
case "complex":
|
|
1265
|
+
// Complex is now a record type {re: num, im: num}
|
|
1266
|
+
// Return RecordType for class-based type system
|
|
1267
|
+
const complexFields = new Map();
|
|
1268
|
+
complexFields.set("re", NUM);
|
|
1269
|
+
complexFields.set("im", NUM);
|
|
1270
|
+
return new RecordType(complexFields, false);
|
|
1271
|
+
case "array":
|
|
1272
|
+
// Bare "array" type without generics - use fresh type variables for element type and rank
|
|
1273
|
+
return new ArrayType(freshTypeVar(), null); // null rank = any rank
|
|
1274
|
+
default:
|
|
1275
|
+
return null; // Unknown type
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
/**
|
|
1280
|
+
* Convert a parsed TypeAnnotation AST node to an internal Type
|
|
1281
|
+
* @param {Object} annotation - TypeAnnotation AST node
|
|
1282
|
+
* @param {Object} env - Optional type environment for looking up type aliases
|
|
1283
|
+
*/
|
|
1284
|
+
export function fromTypeAnnotation(annotation, env = null) {
|
|
1285
|
+
if (!annotation) return freshTypeVar();
|
|
1286
|
+
|
|
1287
|
+
// Handle record types: { name: string, age: num }
|
|
1288
|
+
if (annotation.kind === "record") {
|
|
1289
|
+
const fields = new Map();
|
|
1290
|
+
for (const field of annotation.details.fields) {
|
|
1291
|
+
const fieldType = fromTypeAnnotation(field.type, env);
|
|
1292
|
+
fields.set(field.name, fieldType);
|
|
1293
|
+
}
|
|
1294
|
+
return new RecordType(fields, annotation.details.open || false);
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
// Handle function types: (T1, T2) -> R
|
|
1298
|
+
if (annotation.kind === "function") {
|
|
1299
|
+
const paramTypes = annotation.details.paramTypes.map(t => fromTypeAnnotation(t, env));
|
|
1300
|
+
const returnType = fromTypeAnnotation(annotation.details.returnType, env);
|
|
1301
|
+
return new FunctionType(paramTypes, returnType);
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
// Handle dictionary types: {[K]: V}
|
|
1305
|
+
if (annotation.kind === "dictionary") {
|
|
1306
|
+
const keyType = fromTypeAnnotation(annotation.details.keyType, env);
|
|
1307
|
+
const valueType = fromTypeAnnotation(annotation.details.valueType, env);
|
|
1308
|
+
return new DictionaryType(keyType, valueType);
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
// Handle literal types: "on" or 42
|
|
1312
|
+
if (annotation.kind === "literal") {
|
|
1313
|
+
return new LiteralType(
|
|
1314
|
+
annotation.details.value,
|
|
1315
|
+
annotation.details.primitiveKind
|
|
1316
|
+
);
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
// Handle literal union types: "on" | "off" or 0 | 1 | 2
|
|
1320
|
+
if (annotation.kind === "literalUnion") {
|
|
1321
|
+
return new LiteralUnionType(
|
|
1322
|
+
annotation.details.values,
|
|
1323
|
+
annotation.details.primitiveKind
|
|
1324
|
+
);
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
// Handle promoted types: Type | converter(param) or Type | conv1(p) | conv2(p)
|
|
1328
|
+
if (annotation.kind === "promoted") {
|
|
1329
|
+
const targetType = fromTypeAnnotation(annotation.details.targetType, env);
|
|
1330
|
+
const paramRef = annotation.details.paramRef;
|
|
1331
|
+
|
|
1332
|
+
// Check for new multi-converter format
|
|
1333
|
+
if (annotation.details.converters) {
|
|
1334
|
+
// Multi-converter format: converters is an array
|
|
1335
|
+
const converters = annotation.details.converters.map(c => ({
|
|
1336
|
+
funcName: c.funcName,
|
|
1337
|
+
sourceType: null // Will be set during function definition validation
|
|
1338
|
+
}));
|
|
1339
|
+
return new PromotedType(targetType, converters, paramRef);
|
|
1340
|
+
} else {
|
|
1341
|
+
// Old single-converter format for backwards compatibility
|
|
1342
|
+
const converterFunc = annotation.details.converterFunc;
|
|
1343
|
+
return new PromotedType(targetType, converterFunc, paramRef);
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
const { baseType, genericParams } = annotation;
|
|
1348
|
+
|
|
1349
|
+
// Handle generic types
|
|
1350
|
+
if (genericParams && genericParams.length > 0) {
|
|
1351
|
+
const baseLower = baseType.toLowerCase();
|
|
1352
|
+
|
|
1353
|
+
if (baseLower === "array") {
|
|
1354
|
+
if (genericParams.length !== 2) {
|
|
1355
|
+
throw new Error("array type requires 2 parameters: array<T, R>");
|
|
1356
|
+
}
|
|
1357
|
+
const elemType = fromTypeAnnotation(genericParams[0], env);
|
|
1358
|
+
const rank = genericParams[1]; // Should be a number
|
|
1359
|
+
if (typeof rank !== "number") {
|
|
1360
|
+
throw new Error("array rank must be a number");
|
|
1361
|
+
}
|
|
1362
|
+
return new ArrayType(elemType, rank);
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
// list<T> is deprecated - treat as array<T,1> for backwards compatibility
|
|
1366
|
+
if (baseLower === "list") {
|
|
1367
|
+
if (genericParams.length !== 1) {
|
|
1368
|
+
throw new Error("list type requires 1 parameter: list<T>");
|
|
1369
|
+
}
|
|
1370
|
+
const elemType = fromTypeAnnotation(genericParams[0], env);
|
|
1371
|
+
return new ArrayType(elemType, 1);
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
// IMPORTANT: Check schema variables FIRST (exact case match)
|
|
1376
|
+
// This allows user-defined types like "Complex" to shadow primitive "complex"
|
|
1377
|
+
// when the user explicitly defines a schema with that name
|
|
1378
|
+
if (env && typeof env.lookup === "function") {
|
|
1379
|
+
const varType = env.lookup(baseType);
|
|
1380
|
+
if (varType instanceof RecordType) {
|
|
1381
|
+
// When using a schema as a type annotation (e.g., parameter or return type),
|
|
1382
|
+
// we want to refer to instances of that schema, not the schema itself.
|
|
1383
|
+
// Create an "instance" type with the same fields but without requiredFields markers,
|
|
1384
|
+
// so field access on results is allowed.
|
|
1385
|
+
if (varType.requiredFields && varType.requiredFields.size > 0) {
|
|
1386
|
+
return new RecordType(varType.fields, false, false, null);
|
|
1387
|
+
}
|
|
1388
|
+
return varType;
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
// Then try to look up as a type alias
|
|
1393
|
+
if (env && typeof env.lookupTypeAlias === "function") {
|
|
1394
|
+
const aliasType = env.lookupTypeAlias(baseType);
|
|
1395
|
+
if (aliasType) {
|
|
1396
|
+
return aliasType;
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
// Finally check primitive types (case-insensitive)
|
|
1401
|
+
const primType = parseTypeName(baseType);
|
|
1402
|
+
if (primType) {
|
|
1403
|
+
return primType;
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
// Unknown type - return a type variable
|
|
1407
|
+
return freshTypeVar();
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
// ============================================================================
|
|
1411
|
+
// TYPE STRING PARSING (for primitive signatures)
|
|
1412
|
+
// ============================================================================
|
|
1413
|
+
|
|
1414
|
+
/**
|
|
1415
|
+
* Parse a type string from primitive signatures into a structured type
|
|
1416
|
+
* Handles: "num", "complex", "bool", "string", "object", "list", "array<num,R>", "array<num,2>"
|
|
1417
|
+
*
|
|
1418
|
+
* @param {string} str - Type string like "array<num,R>" or "num"
|
|
1419
|
+
* @returns {Object} Structured type representation
|
|
1420
|
+
*/
|
|
1421
|
+
export function parseTypeString(str) {
|
|
1422
|
+
str = str.trim();
|
|
1423
|
+
|
|
1424
|
+
// Handle array types: array<elem,rank>
|
|
1425
|
+
if (str.startsWith("array<")) {
|
|
1426
|
+
const inner = str.slice(6, -1); // Remove "array<" and ">"
|
|
1427
|
+
const commaIdx = findTopLevelComma(inner);
|
|
1428
|
+
if (commaIdx === -1) {
|
|
1429
|
+
throw new Error(`Invalid array type: ${str}`);
|
|
1430
|
+
}
|
|
1431
|
+
const elemStr = inner.slice(0, commaIdx).trim();
|
|
1432
|
+
const rankStr = inner.slice(commaIdx + 1).trim();
|
|
1433
|
+
|
|
1434
|
+
const elem = parseTypeString(elemStr);
|
|
1435
|
+
const rank = rankStr === "R" ? { var: "R" } : parseInt(rankStr, 10);
|
|
1436
|
+
|
|
1437
|
+
return { kind: "array", elem, rank };
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
// Handle list types: list<elem> -> treat as array<elem,1> for backwards compatibility
|
|
1441
|
+
if (str.startsWith("list<")) {
|
|
1442
|
+
const inner = str.slice(5, -1); // Remove "list<" and ">"
|
|
1443
|
+
const elem = parseTypeString(inner);
|
|
1444
|
+
return { kind: "array", elem, rank: 1 };
|
|
1445
|
+
}
|
|
1446
|
+
|
|
1447
|
+
// Handle primitive types
|
|
1448
|
+
switch (str) {
|
|
1449
|
+
case "num":
|
|
1450
|
+
return { kind: "primitive", name: "num" };
|
|
1451
|
+
case "complex":
|
|
1452
|
+
// Complex is now a record type {re: num, im: num}
|
|
1453
|
+
return {
|
|
1454
|
+
kind: "record",
|
|
1455
|
+
fields: new Map([
|
|
1456
|
+
["re", { kind: "primitive", name: "num" }],
|
|
1457
|
+
["im", { kind: "primitive", name: "num" }],
|
|
1458
|
+
]),
|
|
1459
|
+
open: false,
|
|
1460
|
+
};
|
|
1461
|
+
case "bool":
|
|
1462
|
+
return { kind: "primitive", name: "bool" };
|
|
1463
|
+
case "string":
|
|
1464
|
+
return { kind: "primitive", name: "string" };
|
|
1465
|
+
case "unit":
|
|
1466
|
+
return { kind: "primitive", name: "unit" };
|
|
1467
|
+
case "object":
|
|
1468
|
+
case "record":
|
|
1469
|
+
// Generic object/record type - matches any record
|
|
1470
|
+
return { kind: "record", fields: new Map(), open: true };
|
|
1471
|
+
case "list":
|
|
1472
|
+
// Generic list type - treat as array for backwards compatibility
|
|
1473
|
+
return { kind: "array", elem: { kind: "primitive", name: "num" }, rank: 1, open: true };
|
|
1474
|
+
case "any":
|
|
1475
|
+
// Any type - matches any value
|
|
1476
|
+
return { kind: "any" };
|
|
1477
|
+
default:
|
|
1478
|
+
// Single uppercase letters are type variables (T, U, V, etc.)
|
|
1479
|
+
if (str.length === 1 && str >= "A" && str <= "Z") {
|
|
1480
|
+
return { kind: "typevar", name: str };
|
|
1481
|
+
}
|
|
1482
|
+
throw new Error(`Unknown type: ${str}`);
|
|
1483
|
+
}
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
/**
|
|
1487
|
+
* Find the index of the top-level comma in a type string
|
|
1488
|
+
* (not inside nested angle brackets)
|
|
1489
|
+
*/
|
|
1490
|
+
function findTopLevelComma(str) {
|
|
1491
|
+
let depth = 0;
|
|
1492
|
+
for (let i = 0; i < str.length; i++) {
|
|
1493
|
+
const ch = str[i];
|
|
1494
|
+
if (ch === "<") depth++;
|
|
1495
|
+
else if (ch === ">") depth--;
|
|
1496
|
+
else if (ch === "," && depth === 0) return i;
|
|
1497
|
+
}
|
|
1498
|
+
return -1;
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
/**
|
|
1502
|
+
* Convert a structured type back to a string for display
|
|
1503
|
+
*/
|
|
1504
|
+
export function typeToString(type) {
|
|
1505
|
+
if (type.kind === "primitive") {
|
|
1506
|
+
return type.name;
|
|
1507
|
+
}
|
|
1508
|
+
if (type.kind === "array") {
|
|
1509
|
+
const elemStr = typeToString(type.elem);
|
|
1510
|
+
const rankStr = typeof type.rank === "object" ? type.rank.var : type.rank;
|
|
1511
|
+
return `array<${elemStr},${rankStr}>`;
|
|
1512
|
+
}
|
|
1513
|
+
return JSON.stringify(type);
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
/**
|
|
1517
|
+
* Format a type for display in error messages
|
|
1518
|
+
* Handles both class-based types and structured type objects
|
|
1519
|
+
*
|
|
1520
|
+
* @param {Object} type - Type to format (class or structured)
|
|
1521
|
+
* @returns {string}
|
|
1522
|
+
*/
|
|
1523
|
+
export function formatType(type) {
|
|
1524
|
+
if (!type) return "unknown";
|
|
1525
|
+
|
|
1526
|
+
// Class-based types have toString() method
|
|
1527
|
+
if (typeof type.toString === "function" && type.constructor !== Object) {
|
|
1528
|
+
return type.toString();
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
// Structured type objects
|
|
1532
|
+
if (type.kind === "primitive") {
|
|
1533
|
+
return type.name;
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
if (type.kind === "array") {
|
|
1537
|
+
const elemStr = formatType(type.elem);
|
|
1538
|
+
const rankStr = typeof type.rank === "object" ? type.rank.var : type.rank;
|
|
1539
|
+
return `array<${elemStr},${rankStr}>`;
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
// DEPRECATED: list types are converted to array<T,1> at parse time
|
|
1543
|
+
// This handler exists only for backward compatibility with old code
|
|
1544
|
+
if (type.kind === "list") {
|
|
1545
|
+
const elemStr = formatType(type.elem);
|
|
1546
|
+
return `array<${elemStr},1>`;
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1549
|
+
if (type.kind === "function") {
|
|
1550
|
+
const paramsStr = (type.params || []).map(formatType).join(", ");
|
|
1551
|
+
const retStr = formatType(type.ret);
|
|
1552
|
+
return `(${paramsStr}) -> ${retStr}`;
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
if (type.kind === "record") {
|
|
1556
|
+
if (type.fields instanceof Map && type.fields.size > 0) {
|
|
1557
|
+
const fieldStrs = [];
|
|
1558
|
+
for (const [name, fieldType] of type.fields) {
|
|
1559
|
+
fieldStrs.push(`${name}: ${formatType(fieldType)}`);
|
|
1560
|
+
}
|
|
1561
|
+
return `{ ${fieldStrs.join(", ")} }`;
|
|
1562
|
+
}
|
|
1563
|
+
return "{}";
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
if (type.kind === "dictionary") {
|
|
1567
|
+
const keyStr = formatType(type.keyType);
|
|
1568
|
+
const valueStr = formatType(type.valueType);
|
|
1569
|
+
return `{[${keyStr}]: ${valueStr}}`;
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
if (type.kind === "union") {
|
|
1573
|
+
return (type.types || []).map(formatType).join(" | ");
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
if (type.kind === "typevar") {
|
|
1577
|
+
if (type.resolved) {
|
|
1578
|
+
return formatType(type.resolved);
|
|
1579
|
+
}
|
|
1580
|
+
return `T${type.id ?? "?"}`;
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
if (type.kind === "unknown") {
|
|
1584
|
+
return "unknown";
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
return type.kind || "unknown";
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
// ============================================================================
|
|
1591
|
+
// TYPE MATCHING (for overload resolution)
|
|
1592
|
+
// ============================================================================
|
|
1593
|
+
|
|
1594
|
+
/**
|
|
1595
|
+
* Match a pattern type against a concrete type
|
|
1596
|
+
* Pattern may contain type variables (like R in array<num,R>)
|
|
1597
|
+
*
|
|
1598
|
+
* Returns structured result:
|
|
1599
|
+
* - { success: true, bindings } - exact match
|
|
1600
|
+
* - { success: false } - incompatible types
|
|
1601
|
+
* - { deferred: true, constraint: {...}, bindings } - can match if constraint is satisfied
|
|
1602
|
+
*
|
|
1603
|
+
* @param {Object} pattern - Pattern type from signature
|
|
1604
|
+
* @param {Object} concrete - Concrete type from runtime
|
|
1605
|
+
* @param {Object} bindings - Existing bindings (mutated)
|
|
1606
|
+
* @returns {Object} Match result with success/deferred status and bindings
|
|
1607
|
+
*/
|
|
1608
|
+
export function matchType(pattern, concrete, bindings = {}) {
|
|
1609
|
+
// Guard against undefined
|
|
1610
|
+
if (!pattern || !concrete) return { success: false };
|
|
1611
|
+
|
|
1612
|
+
// Any type - matches everything
|
|
1613
|
+
if (pattern.kind === "any") {
|
|
1614
|
+
return { success: true, bindings };
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1617
|
+
// Type variable - matches any type and binds
|
|
1618
|
+
if (pattern.kind === "typevar") {
|
|
1619
|
+
const varName = pattern.name;
|
|
1620
|
+
if (varName in bindings) {
|
|
1621
|
+
// Already bound - concrete must match the bound type
|
|
1622
|
+
// For simplicity, we compare stringified types
|
|
1623
|
+
if (JSON.stringify(bindings[varName]) !== JSON.stringify(concrete)) {
|
|
1624
|
+
return { success: false };
|
|
1625
|
+
}
|
|
1626
|
+
} else {
|
|
1627
|
+
// Bind the variable to the concrete type
|
|
1628
|
+
bindings[varName] = concrete;
|
|
1629
|
+
}
|
|
1630
|
+
return { success: true, bindings };
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
// Literal type: "on", 42
|
|
1634
|
+
if (pattern.kind === "literal") {
|
|
1635
|
+
// Literal pattern must match exactly
|
|
1636
|
+
if (concrete.kind === "literal") {
|
|
1637
|
+
if (pattern.value === concrete.value && pattern.primitiveKind === concrete.primitiveKind) {
|
|
1638
|
+
return { success: true, bindings };
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
return { success: false };
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
// Literal union type: "on" | "off"
|
|
1645
|
+
if (pattern.kind === "literalUnion") {
|
|
1646
|
+
// Concrete can be:
|
|
1647
|
+
// - Same literalUnion (subset check)
|
|
1648
|
+
// - A single literal that's in the union
|
|
1649
|
+
if (concrete.kind === "literal") {
|
|
1650
|
+
if (pattern.primitiveKind === concrete.primitiveKind &&
|
|
1651
|
+
pattern.values.includes(concrete.value)) {
|
|
1652
|
+
return { success: true, bindings };
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
if (concrete.kind === "literalUnion") {
|
|
1656
|
+
// All concrete values must be in pattern values
|
|
1657
|
+
if (pattern.primitiveKind === concrete.primitiveKind) {
|
|
1658
|
+
const patternSet = new Set(pattern.values);
|
|
1659
|
+
const allIncluded = concrete.values.every(v => patternSet.has(v));
|
|
1660
|
+
if (allIncluded) {
|
|
1661
|
+
return { success: true, bindings };
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
return { success: false };
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
// Primitive type
|
|
1669
|
+
if (pattern.kind === "primitive") {
|
|
1670
|
+
// Direct primitive match
|
|
1671
|
+
if (concrete.kind === "primitive" && pattern.name === concrete.name) {
|
|
1672
|
+
return { success: true, bindings };
|
|
1673
|
+
}
|
|
1674
|
+
// Special case: "complex" primitive matches {re: num, im: num} record
|
|
1675
|
+
if (pattern.name === "complex" && isComplexRecord(concrete)) {
|
|
1676
|
+
return { success: true, bindings };
|
|
1677
|
+
}
|
|
1678
|
+
// Subtype relationship: literal types are subtypes of their base primitive
|
|
1679
|
+
// "on" (LiteralType) matches string (PrimitiveType) when string is the pattern
|
|
1680
|
+
if (concrete.kind === "literal") {
|
|
1681
|
+
if ((pattern.name === "string" && concrete.primitiveKind === "string") ||
|
|
1682
|
+
(pattern.name === "num" && concrete.primitiveKind === "num")) {
|
|
1683
|
+
return { success: true, bindings };
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
if (concrete.kind === "literalUnion") {
|
|
1687
|
+
if ((pattern.name === "string" && concrete.primitiveKind === "string") ||
|
|
1688
|
+
(pattern.name === "num" && concrete.primitiveKind === "num")) {
|
|
1689
|
+
return { success: true, bindings };
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
return { success: false };
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1695
|
+
// Array type
|
|
1696
|
+
if (pattern.kind === "array") {
|
|
1697
|
+
if (concrete.kind !== "array") return { success: false };
|
|
1698
|
+
|
|
1699
|
+
// Match element type
|
|
1700
|
+
const elemResult = matchType(pattern.elem, concrete.elem, bindings);
|
|
1701
|
+
if (!elemResult.success && !elemResult.deferred) return { success: false };
|
|
1702
|
+
|
|
1703
|
+
const elemBindings = elemResult.bindings || bindings;
|
|
1704
|
+
const deferredConstraints = elemResult.deferred && elemResult.constraints ? [...elemResult.constraints] : [];
|
|
1705
|
+
|
|
1706
|
+
// Match rank
|
|
1707
|
+
if (typeof pattern.rank === "object" && pattern.rank.var) {
|
|
1708
|
+
// Pattern has variable rank - can bind to any concrete rank
|
|
1709
|
+
const varName = pattern.rank.var;
|
|
1710
|
+
if (varName in elemBindings) {
|
|
1711
|
+
// Already bound - check if concrete matches
|
|
1712
|
+
const boundValue = elemBindings[varName];
|
|
1713
|
+
if (typeof concrete.rank === "object" && concrete.rank.var) {
|
|
1714
|
+
// Concrete has variable rank, pattern's variable is bound
|
|
1715
|
+
if (typeof boundValue === "number") {
|
|
1716
|
+
// Pattern bound to concrete, arg is variable - defer constraint
|
|
1717
|
+
deferredConstraints.push({
|
|
1718
|
+
type: 'rank',
|
|
1719
|
+
rankVar: concrete.rank.var,
|
|
1720
|
+
requiredRank: boundValue
|
|
1721
|
+
});
|
|
1722
|
+
} else if (typeof boundValue === "object" && boundValue.var) {
|
|
1723
|
+
// Both are variable objects - check if same variable name
|
|
1724
|
+
if (boundValue.var !== concrete.rank.var) {
|
|
1725
|
+
// Different variable names - we need to unify them
|
|
1726
|
+
// For now, defer a constraint that they must be equal
|
|
1727
|
+
// This case is complex, treat as compatible for now
|
|
1728
|
+
}
|
|
1729
|
+
} else if (boundValue !== concrete.rank) {
|
|
1730
|
+
// Unexpected case - fail
|
|
1731
|
+
return { success: false };
|
|
1732
|
+
}
|
|
1733
|
+
} else if (typeof boundValue === "object" && boundValue.var) {
|
|
1734
|
+
// Pattern bound to variable, concrete is a number
|
|
1735
|
+
// Defer constraint: the bound variable must equal the concrete rank
|
|
1736
|
+
deferredConstraints.push({
|
|
1737
|
+
type: 'rank',
|
|
1738
|
+
rankVar: boundValue.var,
|
|
1739
|
+
requiredRank: concrete.rank
|
|
1740
|
+
});
|
|
1741
|
+
} else if (boundValue !== concrete.rank) {
|
|
1742
|
+
// Both concrete values must match
|
|
1743
|
+
return { success: false };
|
|
1744
|
+
}
|
|
1745
|
+
} else {
|
|
1746
|
+
// Bind the variable
|
|
1747
|
+
elemBindings[varName] = concrete.rank;
|
|
1748
|
+
}
|
|
1749
|
+
} else if (typeof concrete.rank === "object" && concrete.rank.var) {
|
|
1750
|
+
// Concrete (argument) has variable rank, pattern has concrete rank
|
|
1751
|
+
// This is the case where we need a deferred constraint
|
|
1752
|
+
deferredConstraints.push({
|
|
1753
|
+
type: 'rank',
|
|
1754
|
+
rankVar: concrete.rank.var,
|
|
1755
|
+
requiredRank: pattern.rank
|
|
1756
|
+
});
|
|
1757
|
+
} else {
|
|
1758
|
+
// Both concrete - must match exactly
|
|
1759
|
+
if (pattern.rank !== concrete.rank) return { success: false };
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
// Return with any collected deferred constraints
|
|
1763
|
+
if (deferredConstraints.length > 0) {
|
|
1764
|
+
return {
|
|
1765
|
+
deferred: true,
|
|
1766
|
+
constraints: deferredConstraints,
|
|
1767
|
+
bindings: elemBindings
|
|
1768
|
+
};
|
|
1769
|
+
}
|
|
1770
|
+
return { success: true, bindings: elemBindings };
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1773
|
+
// Record type (object) - all records are closed, must match exactly
|
|
1774
|
+
if (pattern.kind === "record") {
|
|
1775
|
+
if (concrete.kind !== "record") return { success: false };
|
|
1776
|
+
// Check that concrete has all required fields
|
|
1777
|
+
const deferredConstraints = [];
|
|
1778
|
+
for (const [name, fieldType] of pattern.fields) {
|
|
1779
|
+
const concreteFieldType = concrete.fields?.get?.(name);
|
|
1780
|
+
if (!concreteFieldType) return { success: false };
|
|
1781
|
+
const fieldResult = matchType(fieldType, concreteFieldType, bindings);
|
|
1782
|
+
if (!fieldResult.success && !fieldResult.deferred) return { success: false };
|
|
1783
|
+
bindings = fieldResult.bindings || bindings;
|
|
1784
|
+
if (fieldResult.deferred && fieldResult.constraints) {
|
|
1785
|
+
deferredConstraints.push(...fieldResult.constraints);
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1788
|
+
if (deferredConstraints.length > 0) {
|
|
1789
|
+
return { deferred: true, constraints: deferredConstraints, bindings };
|
|
1790
|
+
}
|
|
1791
|
+
return { success: true, bindings };
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1794
|
+
// DEPRECATED: list type - now matches array<T,1> for backward compatibility
|
|
1795
|
+
if (pattern.kind === "list") {
|
|
1796
|
+
// List patterns match both list and array types (rank 1)
|
|
1797
|
+
if (concrete.kind === "list") {
|
|
1798
|
+
return matchType(pattern.elem, concrete.elem, bindings);
|
|
1799
|
+
}
|
|
1800
|
+
if (concrete.kind === "array" && concrete.rank === 1) {
|
|
1801
|
+
return matchType(pattern.elem, concrete.elem, bindings);
|
|
1802
|
+
}
|
|
1803
|
+
return { success: false };
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1806
|
+
// Function type matching - supports bidirectional inference
|
|
1807
|
+
// When concrete function has TypeVariable params, they get constrained by pattern params
|
|
1808
|
+
if (pattern.kind === "function") {
|
|
1809
|
+
if (concrete.kind !== "function") return { success: false };
|
|
1810
|
+
|
|
1811
|
+
// Parameter counts must match
|
|
1812
|
+
if (!pattern.params || !concrete.params) return { success: false };
|
|
1813
|
+
if (pattern.params.length !== concrete.params.length) return { success: false };
|
|
1814
|
+
|
|
1815
|
+
const deferredConstraints = [];
|
|
1816
|
+
const typeVarUnifications = [];
|
|
1817
|
+
|
|
1818
|
+
// Match each parameter type (contravariant - pattern ← concrete)
|
|
1819
|
+
// For higher-order functions, if concrete has TypeVar params, constrain them
|
|
1820
|
+
for (let i = 0; i < pattern.params.length; i++) {
|
|
1821
|
+
const patternParam = pattern.params[i];
|
|
1822
|
+
const concreteParam = concrete.params[i];
|
|
1823
|
+
|
|
1824
|
+
// If concrete param is a TypeVariable, record unification with expected type
|
|
1825
|
+
if (concreteParam.kind === "typevar" && !concreteParam.resolved) {
|
|
1826
|
+
typeVarUnifications.push({
|
|
1827
|
+
typeVarId: concreteParam.id,
|
|
1828
|
+
targetType: patternParam
|
|
1829
|
+
});
|
|
1830
|
+
continue; // TypeVariable matches, will be constrained later
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1833
|
+
// Regular matching (pattern param should be satisfied by concrete param)
|
|
1834
|
+
const paramResult = matchType(patternParam, concreteParam, bindings);
|
|
1835
|
+
if (!paramResult.success && !paramResult.deferred) return { success: false };
|
|
1836
|
+
bindings = paramResult.bindings || bindings;
|
|
1837
|
+
if (paramResult.deferred && paramResult.constraints) {
|
|
1838
|
+
deferredConstraints.push(...paramResult.constraints);
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
|
|
1842
|
+
// Match return type (covariant - pattern ← concrete)
|
|
1843
|
+
const retResult = matchType(pattern.ret, concrete.ret, bindings);
|
|
1844
|
+
if (!retResult.success && !retResult.deferred) return { success: false };
|
|
1845
|
+
bindings = retResult.bindings || bindings;
|
|
1846
|
+
if (retResult.deferred && retResult.constraints) {
|
|
1847
|
+
deferredConstraints.push(...retResult.constraints);
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1850
|
+
// Return with collected information
|
|
1851
|
+
const result = { success: true, bindings };
|
|
1852
|
+
if (deferredConstraints.length > 0) {
|
|
1853
|
+
result.deferred = true;
|
|
1854
|
+
result.constraints = deferredConstraints;
|
|
1855
|
+
}
|
|
1856
|
+
if (typeVarUnifications.length > 0) {
|
|
1857
|
+
result.typeVarUnifications = typeVarUnifications;
|
|
1858
|
+
}
|
|
1859
|
+
return result;
|
|
1860
|
+
}
|
|
1861
|
+
|
|
1862
|
+
return { success: false };
|
|
1863
|
+
}
|
|
1864
|
+
|
|
1865
|
+
/**
|
|
1866
|
+
* Simple match that returns bindings or null (for backward compatibility)
|
|
1867
|
+
* Use this when you don't care about deferred constraints
|
|
1868
|
+
*/
|
|
1869
|
+
export function matchTypeSimple(pattern, concrete, bindings = {}) {
|
|
1870
|
+
const result = matchType(pattern, concrete, bindings);
|
|
1871
|
+
if (result.success) return result.bindings;
|
|
1872
|
+
if (result.deferred) return result.bindings; // Accept deferred as success for simple cases
|
|
1873
|
+
return null;
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1876
|
+
/**
|
|
1877
|
+
* Substitute type variables in a type using bindings
|
|
1878
|
+
*
|
|
1879
|
+
* @param {Object} type - Type with possible variables
|
|
1880
|
+
* @param {Object} bindings - Variable bindings
|
|
1881
|
+
* @returns {Object} Substituted type
|
|
1882
|
+
*/
|
|
1883
|
+
export function substituteType(type, bindings) {
|
|
1884
|
+
// Guard against undefined
|
|
1885
|
+
if (!type) return { kind: "unknown" };
|
|
1886
|
+
|
|
1887
|
+
if (type.kind === "primitive") {
|
|
1888
|
+
return type;
|
|
1889
|
+
}
|
|
1890
|
+
|
|
1891
|
+
if (type.kind === "array") {
|
|
1892
|
+
const elem = substituteType(type.elem, bindings);
|
|
1893
|
+
let rank = type.rank;
|
|
1894
|
+
if (typeof rank === "object" && rank.var && rank.var in bindings) {
|
|
1895
|
+
rank = bindings[rank.var];
|
|
1896
|
+
}
|
|
1897
|
+
return { kind: "array", elem, rank };
|
|
1898
|
+
}
|
|
1899
|
+
|
|
1900
|
+
return type;
|
|
1901
|
+
}
|
|
1902
|
+
|
|
1903
|
+
// ============================================================================
|
|
1904
|
+
// TYPE PROMOTION
|
|
1905
|
+
// ============================================================================
|
|
1906
|
+
|
|
1907
|
+
/**
|
|
1908
|
+
* Check if a type can be promoted to another type.
|
|
1909
|
+
* Supports both built-in promotions (num → complex) and user-defined
|
|
1910
|
+
* promotions via PromotedType.
|
|
1911
|
+
*
|
|
1912
|
+
* @param {Object} from - Source type (argument type)
|
|
1913
|
+
* @param {Object} to - Target type (parameter type, may be PromotedType)
|
|
1914
|
+
* @returns {{can: boolean, converter: string|null}} Promotion result
|
|
1915
|
+
* - can: whether promotion is possible
|
|
1916
|
+
* - converter: converter function name (null for built-in promotion)
|
|
1917
|
+
*/
|
|
1918
|
+
export function canPromote(from, to) {
|
|
1919
|
+
// Guard against undefined
|
|
1920
|
+
if (!from || !to) return { can: false, converter: null };
|
|
1921
|
+
|
|
1922
|
+
// Handle LiteralType: no promotion possible, must match exactly
|
|
1923
|
+
// (literal types are strictly compile-time checked)
|
|
1924
|
+
if (to instanceof LiteralType) {
|
|
1925
|
+
// from must be a LiteralType with the same value
|
|
1926
|
+
if (from instanceof LiteralType &&
|
|
1927
|
+
from.value === to.value &&
|
|
1928
|
+
from.primitiveKind === to.primitiveKind) {
|
|
1929
|
+
return { can: false, converter: null }; // Exact match, no promotion needed
|
|
1930
|
+
}
|
|
1931
|
+
return { can: false, converter: null };
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1934
|
+
// Handle LiteralUnionType: no promotion, just subset checking
|
|
1935
|
+
if (to instanceof LiteralUnionType) {
|
|
1936
|
+
// from must be a literal or literal union that's a subset
|
|
1937
|
+
if (from instanceof LiteralType) {
|
|
1938
|
+
if (from.primitiveKind === to.primitiveKind && to.values.includes(from.value)) {
|
|
1939
|
+
return { can: false, converter: null }; // Subset match, no promotion
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1942
|
+
if (from instanceof LiteralUnionType) {
|
|
1943
|
+
if (from.primitiveKind === to.primitiveKind) {
|
|
1944
|
+
const toSet = new Set(to.values);
|
|
1945
|
+
const isSubset = from.values.every(v => toSet.has(v));
|
|
1946
|
+
if (isSubset) {
|
|
1947
|
+
return { can: false, converter: null }; // Subset match, no promotion
|
|
1948
|
+
}
|
|
1949
|
+
}
|
|
1950
|
+
}
|
|
1951
|
+
return { can: false, converter: null };
|
|
1952
|
+
}
|
|
1953
|
+
|
|
1954
|
+
// Handle PromotedType: check if from matches any converter's sourceType
|
|
1955
|
+
if (to instanceof PromotedType) {
|
|
1956
|
+
// First check if from matches the target type (no conversion needed)
|
|
1957
|
+
// Use isAssignableTo to allow literals to match their base primitives
|
|
1958
|
+
if (isAssignableTo(from, to.targetType)) {
|
|
1959
|
+
return { can: false, converter: null }; // No promotion needed - assignable match
|
|
1960
|
+
}
|
|
1961
|
+
|
|
1962
|
+
// Check each converter's sourceType (for multi-converter support)
|
|
1963
|
+
// Use isAssignableTo to allow literals to match their base primitives
|
|
1964
|
+
for (let i = 0; i < to.converters.length; i++) {
|
|
1965
|
+
const converter = to.converters[i];
|
|
1966
|
+
if (converter.sourceType && isAssignableTo(from, converter.sourceType)) {
|
|
1967
|
+
return {
|
|
1968
|
+
can: true,
|
|
1969
|
+
converter: converter.funcName,
|
|
1970
|
+
converterIndex: i
|
|
1971
|
+
};
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1974
|
+
|
|
1975
|
+
// Legacy: check single sourceType (backwards compatibility)
|
|
1976
|
+
if (to.sourceType && isAssignableTo(from, to.sourceType)) {
|
|
1977
|
+
return { can: true, converter: to.converterFunc };
|
|
1978
|
+
}
|
|
1979
|
+
|
|
1980
|
+
return { can: false, converter: null };
|
|
1981
|
+
}
|
|
1982
|
+
|
|
1983
|
+
// Built-in: num → complex (complex is now {re: num, im: num} record)
|
|
1984
|
+
if (
|
|
1985
|
+
from.kind === "primitive" &&
|
|
1986
|
+
from.name === "num" &&
|
|
1987
|
+
isComplexRecord(to)
|
|
1988
|
+
) {
|
|
1989
|
+
return { can: true, converter: "to_complex" };
|
|
1990
|
+
}
|
|
1991
|
+
|
|
1992
|
+
// Built-in: array<num,R> → array<complex,R> (with same rank)
|
|
1993
|
+
// Complex element type is now a record
|
|
1994
|
+
if (
|
|
1995
|
+
from.kind === "array" &&
|
|
1996
|
+
to.kind === "array" &&
|
|
1997
|
+
from.elem?.kind === "primitive" &&
|
|
1998
|
+
from.elem?.name === "num" &&
|
|
1999
|
+
isComplexRecord(to.elem)
|
|
2000
|
+
) {
|
|
2001
|
+
// Ranks must match (either both concrete and equal, or both variables)
|
|
2002
|
+
if (typeof from.rank === "number" && typeof to.rank === "number") {
|
|
2003
|
+
if (from.rank === to.rank) {
|
|
2004
|
+
return { can: true, converter: "to_complex" };
|
|
2005
|
+
}
|
|
2006
|
+
}
|
|
2007
|
+
return { can: false, converter: null };
|
|
2008
|
+
}
|
|
2009
|
+
|
|
2010
|
+
return { can: false, converter: null };
|
|
2011
|
+
}
|
|
2012
|
+
|
|
2013
|
+
/**
|
|
2014
|
+
* Legacy wrapper for boolean-returning canPromote behavior.
|
|
2015
|
+
* Used by existing code that expects a boolean.
|
|
2016
|
+
*/
|
|
2017
|
+
export function canPromoteBool(from, to) {
|
|
2018
|
+
return canPromote(from, to).can;
|
|
2019
|
+
}
|
|
2020
|
+
|
|
2021
|
+
/**
|
|
2022
|
+
* Get the promotion cost between two types
|
|
2023
|
+
* 0 = exact match
|
|
2024
|
+
* 1 = promotable
|
|
2025
|
+
* Infinity = not compatible
|
|
2026
|
+
*
|
|
2027
|
+
* @param {Object} from - Actual type
|
|
2028
|
+
* @param {Object} to - Expected type (pattern)
|
|
2029
|
+
* @param {Object} bindings - Type variable bindings
|
|
2030
|
+
* @returns {number}
|
|
2031
|
+
*/
|
|
2032
|
+
export function promotionCost(from, to, bindings = {}) {
|
|
2033
|
+
// Try exact match first
|
|
2034
|
+
const matchResult = matchType(to, from, { ...bindings });
|
|
2035
|
+
if (matchResult.success || matchResult.deferred) {
|
|
2036
|
+
return 0;
|
|
2037
|
+
}
|
|
2038
|
+
|
|
2039
|
+
// Try with promotion
|
|
2040
|
+
const promoResult = canPromote(from, to);
|
|
2041
|
+
if (promoResult.can) {
|
|
2042
|
+
return 1;
|
|
2043
|
+
}
|
|
2044
|
+
|
|
2045
|
+
return Infinity;
|
|
2046
|
+
}
|