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.
Files changed (68) hide show
  1. package/README.md +52 -0
  2. package/StoneEngine.js +879 -0
  3. package/StoneEngineService.js +1727 -0
  4. package/adapters/FileSystemAdapter.js +230 -0
  5. package/adapters/OutputAdapter.js +208 -0
  6. package/adapters/index.js +6 -0
  7. package/cli/CLIOutputAdapter.js +196 -0
  8. package/cli/DaemonClient.js +349 -0
  9. package/cli/JSONOutputAdapter.js +135 -0
  10. package/cli/ReplSession.js +567 -0
  11. package/cli/ViewerServer.js +590 -0
  12. package/cli/commands/check.js +84 -0
  13. package/cli/commands/daemon.js +189 -0
  14. package/cli/commands/kill.js +66 -0
  15. package/cli/commands/package.js +713 -0
  16. package/cli/commands/ps.js +65 -0
  17. package/cli/commands/run.js +537 -0
  18. package/cli/entry.js +169 -0
  19. package/cli/index.js +14 -0
  20. package/cli/stonec.js +358 -0
  21. package/cli/test-compiler.js +181 -0
  22. package/cli/viewer/index.html +495 -0
  23. package/daemon/IPCServer.js +455 -0
  24. package/daemon/ProcessManager.js +327 -0
  25. package/daemon/ProcessRunner.js +307 -0
  26. package/daemon/daemon.js +398 -0
  27. package/daemon/index.js +16 -0
  28. package/frontend/analysis/index.js +5 -0
  29. package/frontend/analysis/livenessAnalyzer.js +568 -0
  30. package/frontend/analysis/treeShaker.js +265 -0
  31. package/frontend/index.js +20 -0
  32. package/frontend/parsing/astBuilder.js +2196 -0
  33. package/frontend/parsing/index.js +7 -0
  34. package/frontend/parsing/sonParser.js +592 -0
  35. package/frontend/parsing/stoneAstTypes.js +703 -0
  36. package/frontend/parsing/terminal-registry.js +435 -0
  37. package/frontend/parsing/tokenizer.js +692 -0
  38. package/frontend/type-checker/OverloadedFunctionType.js +43 -0
  39. package/frontend/type-checker/TypeEnvironment.js +165 -0
  40. package/frontend/type-checker/bidirectionalInference.js +149 -0
  41. package/frontend/type-checker/index.js +10 -0
  42. package/frontend/type-checker/moduleAnalysis.js +248 -0
  43. package/frontend/type-checker/operatorMappings.js +35 -0
  44. package/frontend/type-checker/overloadResolution.js +605 -0
  45. package/frontend/type-checker/typeChecker.js +452 -0
  46. package/frontend/type-checker/typeCompatibility.js +389 -0
  47. package/frontend/type-checker/visitors/controlFlow.js +483 -0
  48. package/frontend/type-checker/visitors/functions.js +604 -0
  49. package/frontend/type-checker/visitors/index.js +38 -0
  50. package/frontend/type-checker/visitors/literals.js +341 -0
  51. package/frontend/type-checker/visitors/modules.js +159 -0
  52. package/frontend/type-checker/visitors/operators.js +109 -0
  53. package/frontend/type-checker/visitors/statements.js +768 -0
  54. package/frontend/types/index.js +5 -0
  55. package/frontend/types/operatorMap.js +134 -0
  56. package/frontend/types/types.js +2046 -0
  57. package/frontend/utils/errorCollector.js +244 -0
  58. package/frontend/utils/index.js +5 -0
  59. package/frontend/utils/moduleResolver.js +479 -0
  60. package/package.json +50 -0
  61. package/packages/browserCache.js +359 -0
  62. package/packages/fetcher.js +236 -0
  63. package/packages/index.js +130 -0
  64. package/packages/lockfile.js +271 -0
  65. package/packages/manifest.js +291 -0
  66. package/packages/packageResolver.js +356 -0
  67. package/packages/resolver.js +310 -0
  68. package/packages/semver.js +635 -0
@@ -0,0 +1,604 @@
1
+ /**
2
+ * Function Visitors - mixin for TypeChecker
3
+ *
4
+ * Visitors for function definitions and function calls.
5
+ */
6
+
7
+ import {
8
+ FunctionType,
9
+ RecordType,
10
+ ArrayType,
11
+ TypeVariable,
12
+ PromotedType,
13
+ LiteralType,
14
+ LiteralUnionType,
15
+ UNIT,
16
+ freshTypeVar,
17
+ unify,
18
+ formatType,
19
+ fromTypeAnnotation,
20
+ typeEquals,
21
+ isAssignableTo,
22
+ } from "../../types/types.js";
23
+ import { OverloadedFunctionType } from "../OverloadedFunctionType.js";
24
+ import { ExtensionInfo } from "../TypeEnvironment.js";
25
+
26
+ /**
27
+ * Function visitor methods to be mixed into TypeChecker.prototype
28
+ */
29
+ export const functionVisitors = {
30
+ /**
31
+ * Visit a function definition
32
+ */
33
+ visitFunctionDefinition(node, env) {
34
+ const funcEnv = env.createChild();
35
+
36
+ // Start tracking deferred constraints for this function
37
+ this.pushConstraintContext();
38
+
39
+ // Process parameters and create a map from parameter name to index
40
+ const paramTypes = [];
41
+ const paramNameToIndex = new Map();
42
+ for (let i = 0; i < node.parameters.length; i++) {
43
+ const param = node.parameters[i];
44
+ let paramType;
45
+ if (param.typeAnnotation) {
46
+ paramType = fromTypeAnnotation(param.typeAnnotation, env);
47
+
48
+ // Validate promoted parameters: look up converter(s) and set sourceType(s)
49
+ if (paramType instanceof PromotedType) {
50
+ // Validate that paramRef matches the parameter name
51
+ if (paramType.paramRef !== param.name) {
52
+ this.addError(
53
+ `Promotion converter references '${paramType.paramRef}' but parameter is '${param.name}'`,
54
+ param.location
55
+ );
56
+ }
57
+
58
+ // Validate each converter in the multi-converter list
59
+ const allSourceTypes = [];
60
+ for (const converter of paramType.converters) {
61
+ const converterType = env.lookup(converter.funcName);
62
+ if (!converterType) {
63
+ this.addError(
64
+ `Converter function '${converter.funcName}' not found`,
65
+ param.location
66
+ );
67
+ continue;
68
+ }
69
+
70
+ if (converterType instanceof FunctionType) {
71
+ // Single function - validate it has exactly one parameter
72
+ if (converterType.paramTypes.length !== 1) {
73
+ this.addError(
74
+ `Converter '${converter.funcName}' must take exactly one parameter`,
75
+ param.location
76
+ );
77
+ } else {
78
+ // Set the sourceType from the converter's input type
79
+ converter.sourceType = converterType.paramTypes[0];
80
+
81
+ // Validate that converter returns the target type
82
+ if (!typeEquals(converterType.returnType, paramType.targetType)) {
83
+ this.addError(
84
+ `Converter '${converter.funcName}' returns ${formatType(converterType.returnType)}, expected ${formatType(paramType.targetType)}`,
85
+ param.location
86
+ );
87
+ }
88
+
89
+ allSourceTypes.push({ type: converter.sourceType, funcName: converter.funcName });
90
+ }
91
+ } else if (converterType instanceof OverloadedFunctionType) {
92
+ // Overloaded function - find matching overloads
93
+ const validSourceTypes = [];
94
+
95
+ // Check user-defined overloads
96
+ for (const { funcType } of converterType.userOverloads) {
97
+ if (funcType.paramTypes.length === 1 &&
98
+ typeEquals(funcType.returnType, paramType.targetType)) {
99
+ validSourceTypes.push(funcType.paramTypes[0]);
100
+ }
101
+ }
102
+
103
+ // Check primitive overloads
104
+ for (const primFn of converterType.overloads) {
105
+ if (primFn.stone && primFn.stone.in.length === 1) {
106
+ const outType = fromTypeAnnotation({ type: 'TypeAnnotation', name: primFn.stone.out }, env);
107
+ if (typeEquals(outType, paramType.targetType)) {
108
+ const inType = fromTypeAnnotation({ type: 'TypeAnnotation', name: primFn.stone.in[0] }, env);
109
+ validSourceTypes.push(inType);
110
+ }
111
+ }
112
+ }
113
+
114
+ if (validSourceTypes.length === 0) {
115
+ this.addError(
116
+ `No overload of '${converter.funcName}' takes one parameter and returns ${formatType(paramType.targetType)}`,
117
+ param.location
118
+ );
119
+ } else {
120
+ converter.sourceType = validSourceTypes[0];
121
+ converter.validSourceTypes = validSourceTypes;
122
+ allSourceTypes.push({ type: validSourceTypes[0], funcName: converter.funcName });
123
+ }
124
+ } else {
125
+ this.addError(
126
+ `'${converter.funcName}' is not a function`,
127
+ param.location
128
+ );
129
+ }
130
+ }
131
+
132
+ // Check for overlapping source types (error if two converters accept the same type)
133
+ for (let i = 0; i < allSourceTypes.length; i++) {
134
+ for (let j = i + 1; j < allSourceTypes.length; j++) {
135
+ if (allSourceTypes[i].type && allSourceTypes[j].type &&
136
+ typeEquals(allSourceTypes[i].type, allSourceTypes[j].type)) {
137
+ this.addError(
138
+ `Overlapping source types: converters '${allSourceTypes[i].funcName}' and '${allSourceTypes[j].funcName}' both accept ${formatType(allSourceTypes[i].type)}`,
139
+ param.location
140
+ );
141
+ }
142
+ }
143
+ }
144
+
145
+ // For backwards compatibility, set the legacy single sourceType
146
+ if (paramType.converters.length > 0 && paramType.converters[0].sourceType) {
147
+ paramType.sourceType = paramType.converters[0].sourceType;
148
+ }
149
+ }
150
+ } else {
151
+ paramType = freshTypeVar();
152
+ }
153
+ paramTypes.push(paramType);
154
+
155
+ // For promoted types, define the parameter with the target type in the function scope
156
+ let effectiveType = paramType instanceof PromotedType
157
+ ? paramType.targetType
158
+ : paramType;
159
+
160
+ // If the effective type is a schema (RecordType with requiredFields), convert to instance type
161
+ // This allows field access in the function body without "required type descriptor" errors
162
+ if (effectiveType instanceof RecordType && effectiveType.requiredFields) {
163
+ effectiveType = new RecordType(
164
+ effectiveType.fields, // Same fields
165
+ false, // Not open
166
+ false, // Not explicit
167
+ null // No required fields - this is an instance, not a schema
168
+ );
169
+ }
170
+ funcEnv.define(param.name, effectiveType);
171
+ funcEnv.markImmutable(param.name); // Function parameters are immutable
172
+ paramNameToIndex.set(param.name, i);
173
+ }
174
+
175
+ // Infer return type from body
176
+ let returnType;
177
+ if (node.isExpression) {
178
+ // First visit the body to type-check it
179
+ returnType = this.visit(node.body, funcEnv);
180
+
181
+ // For expression bodies that may contain return statements (e.g., block expressions with if/else),
182
+ // also collect and unify all return statements to ensure consistent types
183
+ const returnInfos = this.collectReturnStatements([node.body]);
184
+ if (returnInfos.length > 0) {
185
+ // Unify all return statement types
186
+ const { unifiedType, errors } = this.unifyReturnTypes(returnInfos);
187
+ for (const error of errors) {
188
+ this.addError(error.message, error.location);
189
+ }
190
+ // Use the unified return type instead of the expression type
191
+ returnType = unifiedType instanceof TypeVariable ? unifiedType.resolve() : unifiedType;
192
+ }
193
+ } else {
194
+ // Block function - look for return statements
195
+ returnType = this.visitFunctionBody(node.body, funcEnv);
196
+ }
197
+
198
+ // Collect deferred constraints from body analysis
199
+ const collectedConstraints = this.popConstraintContext();
200
+
201
+ // Check against declared return type
202
+ if (node.returnType) {
203
+ const declaredReturnType = fromTypeAnnotation(node.returnType, env);
204
+
205
+ // For literal types, use strict assignability check (inferred must be subset of declared)
206
+ // For other types, use unify for compatibility
207
+ const isLiteralReturn = declaredReturnType instanceof LiteralType ||
208
+ declaredReturnType instanceof LiteralUnionType;
209
+
210
+ if (isLiteralReturn) {
211
+ // Strict check: inferred return type must be assignable to declared type
212
+ if (!isAssignableTo(returnType, declaredReturnType)) {
213
+ this.addError(
214
+ `Function '${node.name}' declared to return ${formatType(declaredReturnType)} but inferred ${formatType(returnType)}`,
215
+ node.location
216
+ );
217
+ }
218
+ } else {
219
+ // Use unify for regular types
220
+ const unifyResult = unify(declaredReturnType, returnType);
221
+ if (!unifyResult.success) {
222
+ this.addError(
223
+ `Function '${node.name}' declared to return ${formatType(declaredReturnType)} but inferred ${formatType(returnType)}`,
224
+ node.location
225
+ );
226
+ }
227
+ }
228
+ returnType = declaredReturnType;
229
+ }
230
+
231
+ // Resolve any TypeVariables that were constrained during body analysis
232
+ // This makes function signatures more precise based on actual usage
233
+ const resolvedParamTypes = paramTypes.map((t, i) => {
234
+ if (t instanceof TypeVariable) {
235
+ const resolved = t.resolve();
236
+ return resolved;
237
+ }
238
+ return t;
239
+ });
240
+ const resolvedReturnType = returnType instanceof TypeVariable
241
+ ? returnType.resolve()
242
+ : returnType;
243
+
244
+ // Create function type with any collected deferred constraints
245
+ const funcType = new FunctionType(resolvedParamTypes, resolvedReturnType, collectedConstraints);
246
+
247
+ // Support function overloading: accumulate overloads instead of overwriting
248
+ const existing = env.lookup(node.name);
249
+
250
+ // Note: We allow user functions to shadow primitives. The executor will use
251
+ // the pre-resolved overloadId from type checking, falling back to primitives
252
+ // only if no user overload matches. If a user overload was resolved but can't
253
+ // be found at runtime, the executor throws a clear error.
254
+
255
+ // Generate overloadId for this definition using location or a counter as unique identifier
256
+ let locId;
257
+ if (node.location && node.location.start) {
258
+ locId = `${node.location.start.line}_${node.location.start.column}`;
259
+ } else if (node.location && typeof node.location.line === 'number') {
260
+ locId = `${node.location.line}_${node.location.column || 0}`;
261
+ } else {
262
+ // Fallback to a counter
263
+ this._overloadCounter = (this._overloadCounter || 0) + 1;
264
+ locId = `gen_${this._overloadCounter}`;
265
+ }
266
+ const overloadId = `${node.name}_${locId}`;
267
+ node.overloadId = overloadId;
268
+ // Store overloadId on funcType so we can retrieve it if this becomes overloaded
269
+ funcType._overloadId = overloadId;
270
+ funcType._sourceNode = node;
271
+
272
+ if (existing instanceof OverloadedFunctionType) {
273
+ // Add to existing user-defined overload set
274
+ existing.addUserOverload(funcType, node);
275
+ } else if (existing instanceof FunctionType) {
276
+ // Convert to overloaded: previous definition + new one
277
+ const overloaded = new OverloadedFunctionType(node.name, [], []);
278
+ // Use the stored overloadId and node from the first definition
279
+ const firstOverloadId = existing._overloadId || `${node.name}_first`;
280
+ const firstNode = existing._sourceNode || null;
281
+ overloaded.userOverloads.push({
282
+ funcType: existing,
283
+ node: firstNode,
284
+ overloadId: firstOverloadId
285
+ });
286
+ overloaded.addUserOverload(funcType, node);
287
+ env.define(node.name, overloaded);
288
+ } else {
289
+ // First definition - store as regular FunctionType
290
+ env.define(node.name, funcType);
291
+ }
292
+
293
+ // If this is an extension function, also register it in the extension map
294
+ if (node.isExtension && node.selfType) {
295
+ const extensionInfo = new ExtensionInfo(
296
+ node.name,
297
+ node.selfType,
298
+ funcType
299
+ );
300
+ env.defineExtension(node.name, extensionInfo);
301
+ }
302
+
303
+ return this.setType(node, funcType);
304
+ },
305
+
306
+ /**
307
+ * Visit function body to find return type using two-pass approach
308
+ * 1. Collect all return statements with their types
309
+ * 2. Unify all complete types, report mismatches
310
+ * 3. Back-propagate unified type to incomplete expressions (e.g., empty arrays)
311
+ */
312
+ visitFunctionBody(body, env) {
313
+ // First, visit all statements to type-check the body
314
+ for (const stmt of body) {
315
+ this.visit(stmt, env);
316
+ }
317
+
318
+ // Collect all return statements (they've already been visited above)
319
+ const returnInfos = [];
320
+ const collectReturns = (statements, isGuardPassthrough = false) => {
321
+ for (const stmt of statements) {
322
+ if (stmt.type === "ReturnStatement") {
323
+ const type = stmt.inferredType || (stmt.value ? stmt.value.inferredType : UNIT) || freshTypeVar();
324
+ const isIncomplete = this.isIncompleteType(type);
325
+ returnInfos.push({ node: stmt, type, isIncomplete, isGuardPassthrough });
326
+ } else if (stmt.type === "BranchExpression") {
327
+ // Check for guard passthrough pattern
328
+ // Pattern: if (x.field != "literal") { return x } - early return guard
329
+ let guardPassthroughInFirstBranch = false;
330
+ if (stmt.paths.length >= 1 && stmt.paths[0].condition) {
331
+ const discriminant = this.extractDiscriminantCheck(stmt.paths[0].condition);
332
+ if (discriminant && discriminant.isNegated) {
333
+ const returnVar = this.getSimpleReturnVar(stmt.paths[0].body);
334
+ if (returnVar === discriminant.varName) {
335
+ guardPassthroughInFirstBranch = true;
336
+ }
337
+ }
338
+ }
339
+
340
+ for (let pathIdx = 0; pathIdx < stmt.paths.length; pathIdx++) {
341
+ const branchPath = stmt.paths[pathIdx];
342
+ const isGuardPath = guardPassthroughInFirstBranch && pathIdx === 0;
343
+ collectReturns(branchPath.body, isGuardPath);
344
+ }
345
+ } else if (stmt.type === "LoopExpression") {
346
+ collectReturns(stmt.body);
347
+ } else if (stmt.type === "BlockExpression") {
348
+ collectReturns(stmt.statements || []);
349
+ }
350
+ }
351
+ };
352
+ collectReturns(body);
353
+
354
+ // If no return statements, return UNIT
355
+ if (returnInfos.length === 0) {
356
+ return UNIT;
357
+ }
358
+
359
+ // Unify return types
360
+ const { unifiedType, errors } = this.unifyReturnTypes(returnInfos);
361
+
362
+ // Report any errors
363
+ for (const error of errors) {
364
+ this.addError(error.message, error.location);
365
+ }
366
+
367
+ // Back-propagate unified type to incomplete expressions
368
+ this.resolveIncompleteTypes(returnInfos, unifiedType);
369
+
370
+ return unifiedType instanceof TypeVariable ? unifiedType.resolve() : unifiedType;
371
+ },
372
+
373
+ /**
374
+ * Visit a function call
375
+ * Uses static overload resolution for primitive functions
376
+ */
377
+ visitFunctionCall(node, env) {
378
+ if (node.callee) {
379
+ // Method call - get the method's FunctionType from visiting the callee
380
+ const calleeType = this.visit(node.callee, env);
381
+ const argTypes = (node.args || []).map((arg) => this.visit(arg, env));
382
+
383
+ // If the callee is a FunctionType, unify argument types and return the return type
384
+ if (calleeType instanceof FunctionType) {
385
+ // Special handling for push method on arrays without declared type:
386
+ // Infer and store the element type from the first push argument
387
+ if (node.callee.type === "MemberAccess" &&
388
+ node.callee.property?.name === "push" &&
389
+ node.callee.object?.type === "Identifier") {
390
+ let arrayVarName = node.callee.object.name;
391
+ // For primed variables (arr'), use base name for declared type storage
392
+ const baseName = arrayVarName.endsWith("'") ? arrayVarName.slice(0, -1) : arrayVarName;
393
+ const arrayType = env.lookup(arrayVarName);
394
+
395
+ // If array has unresolved element type and no declared type, infer from push arg
396
+ if (arrayType instanceof ArrayType &&
397
+ arrayType.elementType instanceof TypeVariable &&
398
+ !arrayType.elementType.resolved &&
399
+ !env.lookupDeclared(baseName) &&
400
+ argTypes.length > 0) {
401
+ // The first argument determines the array's element type
402
+ const argType = argTypes[0];
403
+ // Store as the "inferred declared type" for future pushes (use base name)
404
+ const inferredArrayType = new ArrayType(argType, arrayType.rank);
405
+ env.defineDeclared(baseName, inferredArrayType);
406
+ // Also unify the TypeVariable to resolve it
407
+ unify(arrayType.elementType, argType);
408
+ }
409
+ }
410
+
411
+ // Check argument types against parameter types
412
+ for (let i = 0; i < Math.min(argTypes.length, calleeType.paramTypes.length); i++) {
413
+ const unifyResult = unify(calleeType.paramTypes[i], argTypes[i]);
414
+ if (!unifyResult.success) {
415
+ // Only error if both types are fully resolved (no unresolved type variables)
416
+ const paramHasTypeVar = this.containsUnresolvedTypeVar(calleeType.paramTypes[i]);
417
+ const argHasTypeVar = this.containsUnresolvedTypeVar(argTypes[i]);
418
+ if (!paramHasTypeVar && !argHasTypeVar) {
419
+ this.addError(
420
+ `Method argument ${i + 1} type mismatch: expected ${formatType(calleeType.paramTypes[i])}, got ${formatType(argTypes[i])}`,
421
+ node.location
422
+ );
423
+ }
424
+ }
425
+ }
426
+ return this.setType(node, calleeType.returnType);
427
+ }
428
+
429
+ // Unknown callee type - return fresh type
430
+ return this.setType(node, freshTypeVar());
431
+ }
432
+
433
+ const funcName = node.funcName;
434
+ const argTypes = (node.args || []).map((arg) => this.visit(arg, env));
435
+
436
+ // Check environment first for user-defined functions
437
+ let funcType = env.lookup(funcName);
438
+
439
+ // Instantiate fresh type variables for each call (polymorphism)
440
+ // This ensures that calls like print("str") and print([1,2]) work independently
441
+ if (funcType instanceof FunctionType) {
442
+ funcType = funcType.instantiate();
443
+ }
444
+
445
+ // Handle user-defined function overloads
446
+ // If the function has multiple user-defined overloads, resolve statically
447
+ if (funcType instanceof OverloadedFunctionType && funcType.userOverloads.length > 0) {
448
+ const resolved = this.resolveUserOverloadStatic(funcName, argTypes, funcType, node);
449
+ if (resolved) {
450
+ return this.setType(node, resolved.returnType);
451
+ }
452
+ // If resolution failed, error was already added - return fresh type
453
+ return this.setType(node, freshTypeVar());
454
+ }
455
+
456
+ // Check if this function is defined in the scope chain as a FunctionType (not just an overload).
457
+ // User-defined functions shadow builtins.
458
+ const isUserDefinedFunction = funcType instanceof FunctionType && this.isDefinedInScope(env, funcName);
459
+
460
+ // If it's a user-defined FunctionType, use it directly.
461
+ // This allows user-defined functions to shadow builtins.
462
+ if (isUserDefinedFunction) {
463
+ // Track promotions for codegen
464
+ const promotions = [];
465
+
466
+ // Check argument types against function signature
467
+ if (argTypes.length !== funcType.paramTypes.length) {
468
+ this.addError(
469
+ `Function '${funcName}' expects ${funcType.paramTypes.length} arguments, got ${argTypes.length}`,
470
+ node.location
471
+ );
472
+ } else {
473
+ for (let i = 0; i < argTypes.length; i++) {
474
+ const unifyResult = unify(funcType.paramTypes[i], argTypes[i]);
475
+
476
+ // Track promotion info (converter function name or null)
477
+ promotions.push(unifyResult.promoted ? unifyResult.converter : null);
478
+
479
+ if (!unifyResult.success) {
480
+ const paramType = funcType.paramTypes[i];
481
+ const argType = argTypes[i];
482
+ const paramHasTypeVar = this.containsUnresolvedTypeVar(paramType);
483
+ const argHasTypeVar = this.containsUnresolvedTypeVar(argType);
484
+
485
+ // Check if param type is an explicit constraint (from destructuring)
486
+ // Explicit constraints should always report errors, while implicit constraints
487
+ // (from member access inference) may be flow-dependent and shouldn't error
488
+ const resolvedParam = paramType.resolve ? paramType.resolve() : paramType;
489
+ const isExplicitConstraint = resolvedParam instanceof RecordType && resolvedParam.explicit;
490
+
491
+ // "Missing required field" errors from explicit destructuring constraints
492
+ // should always be reported - the user explicitly asked for those fields
493
+ const isMissingRequiredFieldError = unifyResult.error &&
494
+ unifyResult.error.includes('Missing required field') &&
495
+ isExplicitConstraint;
496
+
497
+ // Other structural errors - only report when param type is fully resolved
498
+ // to avoid false positives when type variables could match different types
499
+ const isOtherStructuralError = !paramHasTypeVar && unifyResult.error &&
500
+ !isMissingRequiredFieldError &&
501
+ (unifyResult.error.includes('Missing') ||
502
+ unifyResult.error.includes('field'));
503
+
504
+ const isStructuralError = isMissingRequiredFieldError || isOtherStructuralError;
505
+
506
+ // Report error if:
507
+ // 1. Both types are fully resolved (no type variables), OR
508
+ // 2. Type kinds are incompatible (e.g., function vs primitive) and arg is concrete, OR
509
+ // 3. It's a structural error (missing required fields) AND param is fully resolved
510
+ const kindsIncompatible = paramType.kind !== argType.kind &&
511
+ !(paramType instanceof TypeVariable) &&
512
+ !(argType instanceof TypeVariable);
513
+ const argIsConcrete = !argHasTypeVar;
514
+
515
+ if ((!paramHasTypeVar && !argHasTypeVar) || (kindsIncompatible && argIsConcrete) || isStructuralError) {
516
+ const errorMsg = isStructuralError
517
+ ? `Argument ${i + 1}: ${unifyResult.error}`
518
+ : `Argument ${i + 1} type mismatch: expected ${formatType(paramType)}, got ${formatType(argType)}`;
519
+ this.addError(errorMsg, node.location);
520
+ }
521
+ }
522
+ }
523
+
524
+ // Check deferred constraints from function definition
525
+ this.checkDeferredConstraints(funcName, funcType, argTypes, node);
526
+ }
527
+
528
+ // Store promotions on the call node for codegen
529
+ // Use resolvedUserOverload to match the pattern in compiler.js
530
+ if (promotions.some(p => p !== null)) {
531
+ node.resolvedUserOverload = { promotions };
532
+ }
533
+
534
+ return this.setType(node, funcType.returnType);
535
+ }
536
+
537
+ // Special handling for parse(raw, schema) - returns an instance of the schema type
538
+ // Works for parse(string, schema) and parse(record, schema)
539
+ if (funcName === 'parse' && argTypes.length === 2 && argTypes[1] instanceof RecordType) {
540
+ // parse(raw, schema) returns a record matching the schema type
541
+ // The second argument is the schema record, but we return an "instance" type
542
+ // (same fields, but without requiredFields markers so field access is allowed)
543
+ const schemaType = argTypes[1];
544
+ const instanceType = new RecordType(
545
+ schemaType.fields, // Same fields
546
+ false, // Not open
547
+ false, // Not explicit
548
+ null // No required fields - this is an instance, not a schema
549
+ );
550
+ return this.setType(node, instanceType);
551
+ }
552
+
553
+ // Try overload resolution for primitives or OverloadedFunctionType
554
+ if (this.overloads[funcName] || funcType instanceof OverloadedFunctionType) {
555
+ // Use function's overloads if available, otherwise use global overloads
556
+ const overloadsToUse = funcType instanceof OverloadedFunctionType
557
+ ? funcType.overloads
558
+ : this.overloads[funcName];
559
+
560
+ // Temporarily set overloads for resolution
561
+ const originalOverloads = this.overloads[funcName];
562
+ this.overloads[funcName] = overloadsToUse;
563
+
564
+ const outputType = this.resolveOverloadStatic(funcName, argTypes, node);
565
+
566
+ // Restore original overloads
567
+ if (originalOverloads) {
568
+ this.overloads[funcName] = originalOverloads;
569
+ } else if (funcType instanceof OverloadedFunctionType) {
570
+ delete this.overloads[funcName];
571
+ }
572
+
573
+ if (outputType) {
574
+ const resultType = this.fromStructuredType(outputType);
575
+ return this.setType(node, resultType);
576
+ }
577
+
578
+ // Overload resolution failed - error already added
579
+ return this.setType(node, freshTypeVar());
580
+ }
581
+
582
+ // If it's a type variable (e.g., function parameter), constrain it to FunctionType
583
+ // This enables type inference for higher-order functions like ODE solvers
584
+ if (funcType instanceof TypeVariable) {
585
+ const returnTypeVar = freshTypeVar();
586
+ const expectedFuncType = new FunctionType(argTypes, returnTypeVar);
587
+ // Try to unify - this constrains the TypeVariable to be a function type
588
+ unify(funcType, expectedFuncType);
589
+ return this.setType(node, returnTypeVar);
590
+ }
591
+
592
+ // Not found in environment and not an overload
593
+ if (!funcType) {
594
+ return this.setType(node, freshTypeVar());
595
+ }
596
+
597
+ // Unknown type - not a function
598
+ this.addError(
599
+ `'${funcName}' is not a function`,
600
+ node.location
601
+ );
602
+ return this.setType(node, freshTypeVar());
603
+ },
604
+ };
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Visitor Registration - aggregates all visitor modules
3
+ *
4
+ * Registers all visitor methods on the TypeChecker prototype.
5
+ */
6
+
7
+ import { literalVisitors } from './literals.js';
8
+ import { operatorVisitors } from './operators.js';
9
+ import { functionVisitors } from './functions.js';
10
+ import { controlFlowVisitors } from './controlFlow.js';
11
+ import { statementVisitors } from './statements.js';
12
+ import { moduleVisitors } from './modules.js';
13
+
14
+ /**
15
+ * Register all visitor methods on the TypeChecker prototype.
16
+ *
17
+ * @param {Function} TypeChecker - The TypeChecker class constructor
18
+ */
19
+ export function registerVisitors(TypeChecker) {
20
+ Object.assign(TypeChecker.prototype,
21
+ literalVisitors,
22
+ operatorVisitors,
23
+ functionVisitors,
24
+ controlFlowVisitors,
25
+ statementVisitors,
26
+ moduleVisitors
27
+ );
28
+ }
29
+
30
+ // Re-export individual visitor modules for testing
31
+ export {
32
+ literalVisitors,
33
+ operatorVisitors,
34
+ functionVisitors,
35
+ controlFlowVisitors,
36
+ statementVisitors,
37
+ moduleVisitors,
38
+ };