tova 0.1.1 → 0.2.2

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.
@@ -0,0 +1,72 @@
1
+ // Type Registry for Tova Language Server
2
+ // Provides type-driven completions and member resolution
3
+
4
+ import { ADTType, RecordType } from './types.js';
5
+
6
+ export class TypeRegistry {
7
+ constructor() {
8
+ this.types = new Map(); // type name → ADTType | RecordType
9
+ this.impls = new Map(); // type name → [{ name, params, paramTypes, returnType }]
10
+ this.traits = new Map(); // trait name → [{ name, paramTypes, returnType }]
11
+ }
12
+
13
+ /**
14
+ * Populate from an analyzer's type registry data.
15
+ */
16
+ static fromAnalyzer(analyzer) {
17
+ const registry = new TypeRegistry();
18
+ if (analyzer.typeRegistry) {
19
+ registry.types = analyzer.typeRegistry.types;
20
+ registry.impls = analyzer.typeRegistry.impls;
21
+ registry.traits = analyzer.typeRegistry.traits;
22
+ }
23
+ return registry;
24
+ }
25
+
26
+ /**
27
+ * Get all members (fields + impl methods) for a type name.
28
+ * Used for dot-completion.
29
+ */
30
+ getMembers(typeName) {
31
+ const fields = new Map();
32
+ const methods = [];
33
+
34
+ // Get fields from type structure
35
+ const typeStructure = this.types.get(typeName);
36
+ if (typeStructure) {
37
+ if (typeStructure instanceof ADTType) {
38
+ // For ADTs, collect fields from all variants
39
+ for (const [, variantFields] of typeStructure.variants) {
40
+ for (const [fieldName, fieldType] of variantFields) {
41
+ fields.set(fieldName, fieldType);
42
+ }
43
+ }
44
+ } else if (typeStructure instanceof RecordType) {
45
+ for (const [fieldName, fieldType] of typeStructure.fields) {
46
+ fields.set(fieldName, fieldType);
47
+ }
48
+ }
49
+ }
50
+
51
+ // Get impl methods
52
+ const implMethods = this.impls.get(typeName);
53
+ if (implMethods) {
54
+ for (const method of implMethods) {
55
+ methods.push(method);
56
+ }
57
+ }
58
+
59
+ return { fields, methods };
60
+ }
61
+
62
+ /**
63
+ * Get variant names for a type (for match completion).
64
+ */
65
+ getVariantNames(typeName) {
66
+ const typeStructure = this.types.get(typeName);
67
+ if (typeStructure instanceof ADTType) {
68
+ return typeStructure.getVariantNames();
69
+ }
70
+ return [];
71
+ }
72
+ }
@@ -0,0 +1,478 @@
1
+ // Type system for the Tova analyzer
2
+ // Replaces string-based type representations with a proper class hierarchy
3
+
4
+ export class Type {
5
+ equals(other) { return false; }
6
+ isAssignableTo(target) { return false; }
7
+ toString() { return 'unknown'; }
8
+ getFieldType(name) { return null; }
9
+ }
10
+
11
+ // ─── Primitive Types ──────────────────────────────────────
12
+
13
+ export class PrimitiveType extends Type {
14
+ constructor(name) {
15
+ super();
16
+ this.name = name;
17
+ }
18
+
19
+ equals(other) {
20
+ return other instanceof PrimitiveType && this.name === other.name;
21
+ }
22
+
23
+ isAssignableTo(target) {
24
+ if (!target) return true;
25
+ if (target instanceof AnyType || target instanceof UnknownType) return true;
26
+ if (target instanceof PrimitiveType) {
27
+ if (this.name === target.name) return true;
28
+ // Int -> Float widening is always allowed
29
+ if (this.name === 'Int' && target.name === 'Float') return true;
30
+ // Float -> Int: allowed in non-strict (checked at call site)
31
+ if (this.name === 'Float' && target.name === 'Int') return true;
32
+ }
33
+ return false;
34
+ }
35
+
36
+ toString() { return this.name; }
37
+ }
38
+
39
+ // ─── Nil Type ──────────────────────────────────────────────
40
+
41
+ export class NilType extends Type {
42
+ equals(other) { return other instanceof NilType; }
43
+
44
+ isAssignableTo(target) {
45
+ if (!target) return true;
46
+ if (target instanceof AnyType || target instanceof UnknownType) return true;
47
+ if (target instanceof NilType) return true;
48
+ // Nil is compatible with Option types
49
+ if (target instanceof GenericType && target.base === 'Option') return true;
50
+ if (target instanceof PrimitiveType && target.name === 'Option') return true;
51
+ return false;
52
+ }
53
+
54
+ toString() { return 'Nil'; }
55
+ }
56
+
57
+ // ─── Any Type ──────────────────────────────────────────────
58
+
59
+ export class AnyType extends Type {
60
+ equals(other) { return other instanceof AnyType; }
61
+ isAssignableTo(_target) { return true; }
62
+ toString() { return 'Any'; }
63
+ }
64
+
65
+ // ─── Unknown Type (gradual typing — compatible with everything) ────
66
+
67
+ export class UnknownType extends Type {
68
+ equals(other) { return other instanceof UnknownType; }
69
+ isAssignableTo(_target) { return true; }
70
+ toString() { return 'unknown'; }
71
+ }
72
+
73
+ // ─── Array Type ────────────────────────────────────────────
74
+
75
+ export class ArrayType extends Type {
76
+ constructor(elementType) {
77
+ super();
78
+ this.elementType = elementType || Type.ANY;
79
+ }
80
+
81
+ equals(other) {
82
+ return other instanceof ArrayType && this.elementType.equals(other.elementType);
83
+ }
84
+
85
+ isAssignableTo(target) {
86
+ if (!target) return true;
87
+ if (target instanceof AnyType || target instanceof UnknownType) return true;
88
+ if (target instanceof ArrayType) {
89
+ return this.elementType.isAssignableTo(target.elementType);
90
+ }
91
+ return false;
92
+ }
93
+
94
+ toString() { return `[${this.elementType.toString()}]`; }
95
+ }
96
+
97
+ // ─── Tuple Type ────────────────────────────────────────────
98
+
99
+ export class TupleType extends Type {
100
+ constructor(elementTypes) {
101
+ super();
102
+ this.elementTypes = elementTypes || [];
103
+ }
104
+
105
+ equals(other) {
106
+ if (!(other instanceof TupleType)) return false;
107
+ if (this.elementTypes.length !== other.elementTypes.length) return false;
108
+ return this.elementTypes.every((t, i) => t.equals(other.elementTypes[i]));
109
+ }
110
+
111
+ isAssignableTo(target) {
112
+ if (!target) return true;
113
+ if (target instanceof AnyType || target instanceof UnknownType) return true;
114
+ if (target instanceof TupleType) {
115
+ if (this.elementTypes.length !== target.elementTypes.length) return false;
116
+ return this.elementTypes.every((t, i) => t.isAssignableTo(target.elementTypes[i]));
117
+ }
118
+ return false;
119
+ }
120
+
121
+ toString() {
122
+ return `(${this.elementTypes.map(t => t.toString()).join(', ')})`;
123
+ }
124
+ }
125
+
126
+ // ─── Function Type ─────────────────────────────────────────
127
+
128
+ export class FunctionType extends Type {
129
+ constructor(paramTypes, returnType) {
130
+ super();
131
+ this.paramTypes = paramTypes || [];
132
+ this.returnType = returnType || Type.ANY;
133
+ }
134
+
135
+ equals(other) {
136
+ if (!(other instanceof FunctionType)) return false;
137
+ if (this.paramTypes.length !== other.paramTypes.length) return false;
138
+ if (!this.returnType.equals(other.returnType)) return false;
139
+ return this.paramTypes.every((t, i) => t.equals(other.paramTypes[i]));
140
+ }
141
+
142
+ isAssignableTo(target) {
143
+ if (!target) return true;
144
+ if (target instanceof AnyType || target instanceof UnknownType) return true;
145
+ if (target instanceof FunctionType) return this.equals(target);
146
+ return false;
147
+ }
148
+
149
+ toString() { return 'Function'; }
150
+ }
151
+
152
+ // ─── Record Type ───────────────────────────────────────────
153
+
154
+ export class RecordType extends Type {
155
+ constructor(name, fields) {
156
+ super();
157
+ this.name = name;
158
+ this.fields = fields || new Map(); // name -> Type
159
+ }
160
+
161
+ equals(other) {
162
+ if (!(other instanceof RecordType)) return false;
163
+ return this.name === other.name;
164
+ }
165
+
166
+ isAssignableTo(target) {
167
+ if (!target) return true;
168
+ if (target instanceof AnyType || target instanceof UnknownType) return true;
169
+ if (target instanceof RecordType) return this.name === target.name;
170
+ if (target instanceof PrimitiveType && target.name === this.name) return true;
171
+ if (target instanceof GenericType && target.base === this.name) return true;
172
+ return false;
173
+ }
174
+
175
+ getFieldType(name) {
176
+ return this.fields.get(name) || null;
177
+ }
178
+
179
+ toString() { return this.name; }
180
+ }
181
+
182
+ // ─── ADT Type ──────────────────────────────────────────────
183
+
184
+ export class ADTType extends Type {
185
+ constructor(name, typeParams, variants) {
186
+ super();
187
+ this.name = name;
188
+ this.typeParams = typeParams || [];
189
+ this.variants = variants || new Map(); // variantName -> Map<fieldName, Type>
190
+ }
191
+
192
+ equals(other) {
193
+ if (!(other instanceof ADTType)) return false;
194
+ return this.name === other.name;
195
+ }
196
+
197
+ isAssignableTo(target) {
198
+ if (!target) return true;
199
+ if (target instanceof AnyType || target instanceof UnknownType) return true;
200
+ if (target instanceof ADTType) return this.name === target.name;
201
+ if (target instanceof PrimitiveType && target.name === this.name) return true;
202
+ if (target instanceof GenericType && target.base === this.name) return true;
203
+ return false;
204
+ }
205
+
206
+ getFieldType(name) {
207
+ // Look through all variants for the field
208
+ for (const [, fields] of this.variants) {
209
+ if (fields.has(name)) return fields.get(name);
210
+ }
211
+ return null;
212
+ }
213
+
214
+ getVariantNames() {
215
+ return [...this.variants.keys()];
216
+ }
217
+
218
+ toString() {
219
+ if (this.typeParams.length > 0) {
220
+ return `${this.name}<${this.typeParams.join(', ')}>`;
221
+ }
222
+ return this.name;
223
+ }
224
+ }
225
+
226
+ // ─── Generic Type ──────────────────────────────────────────
227
+
228
+ export class GenericType extends Type {
229
+ constructor(base, typeArgs) {
230
+ super();
231
+ this.base = base;
232
+ this.typeArgs = typeArgs || [];
233
+ }
234
+
235
+ equals(other) {
236
+ if (!(other instanceof GenericType)) return false;
237
+ if (this.base !== other.base) return false;
238
+ if (this.typeArgs.length !== other.typeArgs.length) return false;
239
+ return this.typeArgs.every((t, i) => t.equals(other.typeArgs[i]));
240
+ }
241
+
242
+ isAssignableTo(target) {
243
+ if (!target) return true;
244
+ if (target instanceof AnyType || target instanceof UnknownType) return true;
245
+ if (target instanceof GenericType) {
246
+ if (this.base !== target.base) return false;
247
+ // If one side has no type args (bare `Result`), compatible (gradual typing)
248
+ if (this.typeArgs.length === 0 || target.typeArgs.length === 0) return true;
249
+ if (this.typeArgs.length !== target.typeArgs.length) return false;
250
+ return this.typeArgs.every((t, i) => t.isAssignableTo(target.typeArgs[i]));
251
+ }
252
+ // Compatible with a PrimitiveType of same base name (e.g. Result<Int, String> assignable to Result)
253
+ if (target instanceof PrimitiveType && target.name === this.base) return true;
254
+ // Compatible with ADTType of same name
255
+ if (target instanceof ADTType && target.name === this.base) return true;
256
+ return false;
257
+ }
258
+
259
+ getFieldType(name) {
260
+ // Delegate to the base type if we had structural info — handled via TypeRegistry
261
+ return null;
262
+ }
263
+
264
+ toString() {
265
+ if (this.typeArgs.length === 0) return this.base;
266
+ return `${this.base}<${this.typeArgs.map(t => t.toString()).join(', ')}>`;
267
+ }
268
+ }
269
+
270
+ // ─── Type Variable ─────────────────────────────────────────
271
+
272
+ export class TypeVariable extends Type {
273
+ constructor(name) {
274
+ super();
275
+ this.name = name;
276
+ }
277
+
278
+ equals(other) {
279
+ return other instanceof TypeVariable && this.name === other.name;
280
+ }
281
+
282
+ isAssignableTo(target) {
283
+ if (!target) return true;
284
+ if (target instanceof AnyType || target instanceof UnknownType) return true;
285
+ if (target instanceof TypeVariable) return this.name === target.name;
286
+ // Type variables are compatible with anything (they're placeholders)
287
+ return true;
288
+ }
289
+
290
+ toString() { return this.name; }
291
+ }
292
+
293
+ // ─── Union Type ────────────────────────────────────────────
294
+
295
+ export class UnionType extends Type {
296
+ constructor(members) {
297
+ super();
298
+ this.members = members || [];
299
+ }
300
+
301
+ equals(other) {
302
+ if (!(other instanceof UnionType)) return false;
303
+ if (this.members.length !== other.members.length) return false;
304
+ return this.members.every((m, i) => m.equals(other.members[i]));
305
+ }
306
+
307
+ isAssignableTo(target) {
308
+ if (!target) return true;
309
+ if (target instanceof AnyType || target instanceof UnknownType) return true;
310
+ if (target instanceof UnionType) {
311
+ // Every member of this must be assignable to some member of target
312
+ return this.members.every(m =>
313
+ target.members.some(t => m.isAssignableTo(t))
314
+ );
315
+ }
316
+ // A union is assignable to T if every member is assignable to T
317
+ return this.members.every(m => m.isAssignableTo(target));
318
+ }
319
+
320
+ toString() {
321
+ return this.members.map(m => m.toString()).join(' | ');
322
+ }
323
+ }
324
+
325
+ // ─── Singleton Caching ────────────────────────────────────
326
+
327
+ Type.INT = new PrimitiveType('Int');
328
+ Type.FLOAT = new PrimitiveType('Float');
329
+ Type.STRING = new PrimitiveType('String');
330
+ Type.BOOL = new PrimitiveType('Bool');
331
+ Type.NIL = new NilType();
332
+ Type.ANY = new AnyType();
333
+ Type.UNKNOWN = new UnknownType();
334
+ Type.FUNCTION = new FunctionType([], Type.ANY);
335
+
336
+ const PRIMITIVE_CACHE = new Map([
337
+ ['Int', Type.INT],
338
+ ['Float', Type.FLOAT],
339
+ ['String', Type.STRING],
340
+ ['Bool', Type.BOOL],
341
+ ['Nil', Type.NIL],
342
+ ['Any', Type.ANY],
343
+ ]);
344
+
345
+ // ─── Helper Functions ──────────────────────────────────────
346
+
347
+ /**
348
+ * Convert a parser TypeAnnotation AST node to a Type object.
349
+ */
350
+ export function typeAnnotationToType(ann) {
351
+ if (!ann) return null;
352
+ if (typeof ann === 'string') return typeFromString(ann);
353
+
354
+ switch (ann.type) {
355
+ case 'TypeAnnotation': {
356
+ if (ann.typeParams && ann.typeParams.length > 0) {
357
+ const args = ann.typeParams.map(p => typeAnnotationToType(p) || Type.UNKNOWN);
358
+ return new GenericType(ann.name, args);
359
+ }
360
+ return typeFromString(ann.name);
361
+ }
362
+ case 'ArrayTypeAnnotation': {
363
+ const elType = typeAnnotationToType(ann.elementType) || Type.ANY;
364
+ return new ArrayType(elType);
365
+ }
366
+ case 'TupleTypeAnnotation': {
367
+ const elTypes = ann.elementTypes.map(t => typeAnnotationToType(t) || Type.ANY);
368
+ return new TupleType(elTypes);
369
+ }
370
+ case 'FunctionTypeAnnotation':
371
+ return Type.FUNCTION;
372
+ default:
373
+ return null;
374
+ }
375
+ }
376
+
377
+ /**
378
+ * Convert a type string to a Type object.
379
+ * Bridge for migrating existing string-based code.
380
+ */
381
+ export function typeFromString(s) {
382
+ if (!s) return null;
383
+
384
+ // Check primitive cache
385
+ if (PRIMITIVE_CACHE.has(s)) return PRIMITIVE_CACHE.get(s);
386
+
387
+ // Wildcard / underscore
388
+ if (s === '_') return Type.UNKNOWN;
389
+
390
+ // Array type: [ElementType]
391
+ if (s.startsWith('[') && s.endsWith(']')) {
392
+ const inner = s.slice(1, -1);
393
+ return new ArrayType(typeFromString(inner) || Type.ANY);
394
+ }
395
+
396
+ // Tuple type: (Type1, Type2)
397
+ if (s.startsWith('(') && s.endsWith(')')) {
398
+ const inner = s.slice(1, -1);
399
+ const parts = splitTopLevel(inner, ',');
400
+ return new TupleType(parts.map(p => typeFromString(p.trim()) || Type.ANY));
401
+ }
402
+
403
+ // Generic type: Result<Int, String>
404
+ const ltIdx = s.indexOf('<');
405
+ if (ltIdx !== -1) {
406
+ const base = s.slice(0, ltIdx);
407
+ const inner = s.slice(ltIdx + 1, s.lastIndexOf('>'));
408
+ const params = splitTopLevel(inner, ',');
409
+ const args = params.map(p => typeFromString(p.trim()) || Type.UNKNOWN);
410
+ return new GenericType(base, args);
411
+ }
412
+
413
+ // Named type (user-defined) — treated as a primitive-like name
414
+ return new PrimitiveType(s);
415
+ }
416
+
417
+ /**
418
+ * Split a string on a delimiter at the top level (respecting nested <> and ()).
419
+ */
420
+ function splitTopLevel(str, delimiter) {
421
+ const parts = [];
422
+ let depth = 0;
423
+ let parenDepth = 0;
424
+ let start = 0;
425
+ for (let i = 0; i < str.length; i++) {
426
+ const ch = str[i];
427
+ if (ch === '<') depth++;
428
+ else if (ch === '>') depth--;
429
+ else if (ch === '(') parenDepth++;
430
+ else if (ch === ')') parenDepth--;
431
+ else if (ch === delimiter && depth === 0 && parenDepth === 0) {
432
+ parts.push(str.slice(start, i));
433
+ start = i + 1;
434
+ }
435
+ }
436
+ parts.push(str.slice(start));
437
+ return parts;
438
+ }
439
+
440
+ /**
441
+ * Null-safe type compatibility check.
442
+ * Returns true if source is assignable to target, or if either is null/unknown.
443
+ */
444
+ export function typesCompatible(target, source) {
445
+ if (!target || !source) return true;
446
+ if (target instanceof Type && source instanceof Type) {
447
+ return source.isAssignableTo(target);
448
+ }
449
+ // Fallback for string comparison during migration
450
+ if (typeof target === 'string' || typeof source === 'string') {
451
+ const t = typeof target === 'string' ? typeFromString(target) : target;
452
+ const s = typeof source === 'string' ? typeFromString(source) : source;
453
+ if (!t || !s) return true;
454
+ return s.isAssignableTo(t);
455
+ }
456
+ return true;
457
+ }
458
+
459
+ /**
460
+ * Check if a type is numeric (Int or Float)
461
+ */
462
+ export function isNumericType(type) {
463
+ if (!type) return false;
464
+ if (type instanceof PrimitiveType) {
465
+ return type.name === 'Int' || type.name === 'Float';
466
+ }
467
+ return false;
468
+ }
469
+
470
+ /**
471
+ * Check strict Float -> Int narrowing.
472
+ * Returns true if this is a Float-to-Int assignment (potential data loss).
473
+ */
474
+ export function isFloatNarrowing(source, target) {
475
+ if (!source || !target) return false;
476
+ return (source instanceof PrimitiveType && source.name === 'Float' &&
477
+ target instanceof PrimitiveType && target.name === 'Int');
478
+ }