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,341 @@
1
+ /**
2
+ * Literal Visitors - mixin for TypeChecker
3
+ *
4
+ * Visitors for literals, identifiers, arrays, objects, and binding structures.
5
+ */
6
+
7
+ import {
8
+ ArrayType,
9
+ RecordType,
10
+ TypeVariable,
11
+ NUM,
12
+ BOOL,
13
+ STRING,
14
+ UNIT,
15
+ LiteralType,
16
+ freshTypeVar,
17
+ typeEquals,
18
+ isNumeric,
19
+ numericPromotion,
20
+ unify,
21
+ formatType,
22
+ fromTypeAnnotation,
23
+ } from "../../types/types.js";
24
+ import { OverloadedFunctionType } from "../OverloadedFunctionType.js";
25
+
26
+ /**
27
+ * Literal visitor methods to be mixed into TypeChecker.prototype
28
+ */
29
+ export const literalVisitors = {
30
+ /**
31
+ * Visit a program
32
+ */
33
+ visitProgram(node, env) {
34
+ for (const stmt of node.statements) {
35
+ this.visit(stmt, env);
36
+ }
37
+ return UNIT;
38
+ },
39
+
40
+ /**
41
+ * Main visitor dispatch
42
+ */
43
+ visit(node, env) {
44
+ if (!node) return UNIT;
45
+
46
+ const methodName = `visit${node.type}`;
47
+ if (typeof this[methodName] === "function") {
48
+ return this[methodName](node, env);
49
+ }
50
+
51
+ // Unknown node type - return fresh type variable
52
+ return this.setType(node, freshTypeVar());
53
+ },
54
+
55
+ /**
56
+ * Visit a literal and infer its type
57
+ * @param {Object} node - Literal AST node
58
+ * @param {TypeEnvironment} env - Type environment
59
+ * @returns {Object} Inferred type
60
+ *
61
+ * For string and number literals, we infer LiteralType to enable
62
+ * compile-time checking against literal constraints.
63
+ * Example: "on" gets type LiteralType("on", "string"), not STRING
64
+ */
65
+ visitLiteral(node, env) {
66
+ let type;
67
+ switch (node.literalType) {
68
+ case "number":
69
+ // Infer literal type for numbers to support literal constraints like level: 0 | 1 | 2
70
+ type = new LiteralType(node.value, "num");
71
+ break;
72
+ case "complex": {
73
+ // Complex is now a record type {re: num, im: num}
74
+ const fields = new Map();
75
+ fields.set("re", NUM);
76
+ fields.set("im", NUM);
77
+ type = new RecordType(fields, false);
78
+ break;
79
+ }
80
+ case "string":
81
+ // Infer literal type for strings to support literal constraints like mode: "on" | "off"
82
+ type = new LiteralType(node.value, "string");
83
+ break;
84
+ case "bool":
85
+ type = BOOL;
86
+ break;
87
+ case "none":
88
+ type = UNIT;
89
+ break;
90
+ default:
91
+ type = freshTypeVar();
92
+ }
93
+ return this.setType(node, type);
94
+ },
95
+
96
+ /**
97
+ * Visit an identifier
98
+ */
99
+ visitIdentifier(node, env) {
100
+ const type = env.lookup(node.name);
101
+ if (!type) {
102
+ // Check if this is a known overloaded primitive function
103
+ if (this.overloads[node.name]) {
104
+ // It's an overloaded primitive - create an OverloadedFunctionType
105
+ return this.setType(node, new OverloadedFunctionType(node.name, this.overloads[node.name]));
106
+ }
107
+
108
+ // Unknown identifier - report error for undefined variable
109
+ this.addError(`Undefined variable: ${node.name}`, node.location);
110
+
111
+ // Still return a fresh type variable for error recovery
112
+ // This allows type checking to continue and catch other errors
113
+ return this.setType(node, freshTypeVar());
114
+ }
115
+ return this.setType(node, type);
116
+ },
117
+
118
+ /**
119
+ * Visit an array literal
120
+ * Always creates Array<T, R> type (lists are disabled for now)
121
+ */
122
+ visitArrayLiteral(node, env) {
123
+ if (node.elements.length === 0) {
124
+ // Empty array - element type is inferred from usage (e.g., push operations)
125
+ return this.setType(node, new ArrayType(freshTypeVar(), 1));
126
+ }
127
+
128
+ // Infer types of all elements
129
+ const elementTypes = node.elements.map((elem) => this.visit(elem, env));
130
+
131
+ // Check if rectangular (for Array type)
132
+ const { isRectangular, shape, baseType } = this.analyzeArrayStructure(
133
+ node,
134
+ elementTypes,
135
+ env
136
+ );
137
+
138
+ if (isRectangular && baseType && isNumeric(baseType)) {
139
+ // Dense numeric array: Array<T, R>
140
+ const rank = shape.length;
141
+ return this.setType(node, new ArrayType(baseType, rank));
142
+ } else {
143
+ // Non-rectangular or non-numeric - still create Array with inferred element type
144
+ // For now, treat as 1D array of the unified element type
145
+ const elemType = this.unifyTypes(elementTypes, node.location);
146
+ return this.setType(node, new ArrayType(elemType, 1));
147
+ }
148
+ },
149
+
150
+ /**
151
+ * Analyze array structure to determine if rectangular
152
+ */
153
+ analyzeArrayStructure(node, elementTypes, env) {
154
+ if (node.elements.length === 0) {
155
+ return { isRectangular: true, shape: [0], baseType: freshTypeVar() };
156
+ }
157
+
158
+ // Check if all elements are the same type
159
+ const firstType = elementTypes[0];
160
+
161
+ // If first element is also an array, check nested structure
162
+ if (firstType instanceof ArrayType) {
163
+ // Check all elements have same rank and element type
164
+ for (const elemType of elementTypes) {
165
+ if (!(elemType instanceof ArrayType)) {
166
+ return { isRectangular: false, shape: null, baseType: null };
167
+ }
168
+ if (elemType.rank !== firstType.rank) {
169
+ return { isRectangular: false, shape: null, baseType: null };
170
+ }
171
+ if (!typeEquals(elemType.elementType, firstType.elementType)) {
172
+ return { isRectangular: false, shape: null, baseType: null };
173
+ }
174
+ }
175
+ // Rectangular: total rank = 1 (outer) + inner rank
176
+ // Shape array needs enough elements to match total rank
177
+ const totalRank = 1 + firstType.rank;
178
+ const shape = new Array(totalRank).fill(node.elements.length);
179
+ return {
180
+ isRectangular: true,
181
+ shape: shape, // shape.length = totalRank
182
+ baseType: firstType.elementType,
183
+ };
184
+ }
185
+
186
+ // Base case: all elements should be primitives
187
+ for (const elemType of elementTypes) {
188
+ if (elemType instanceof ArrayType) {
189
+ return { isRectangular: false, shape: null, baseType: null };
190
+ }
191
+ }
192
+
193
+ // All primitives - check if all numeric
194
+ const promotedType = this.unifyTypes(elementTypes, node.location);
195
+ if (isNumeric(promotedType)) {
196
+ return {
197
+ isRectangular: true,
198
+ shape: [node.elements.length],
199
+ baseType: promotedType,
200
+ };
201
+ }
202
+
203
+ return { isRectangular: false, shape: null, baseType: promotedType };
204
+ },
205
+
206
+ /**
207
+ * Unify multiple types into one
208
+ */
209
+ unifyTypes(types, location) {
210
+ if (types.length === 0) return freshTypeVar();
211
+ if (types.length === 1) return types[0];
212
+
213
+ let result = types[0];
214
+ for (let i = 1; i < types.length; i++) {
215
+ const unifyResult = unify(result, types[i]);
216
+ if (!unifyResult.success) {
217
+ // Try numeric promotion
218
+ const promoted = numericPromotion(result, types[i]);
219
+ if (promoted) {
220
+ result = promoted;
221
+ } else {
222
+ // Heterogeneous arrays are not allowed
223
+ this.addError(
224
+ `Heterogeneous array: cannot mix ${formatType(result)} and ${formatType(types[i])}`,
225
+ location
226
+ );
227
+ result = types[i]; // Continue with last type for further checking
228
+ }
229
+ } else {
230
+ // Unification succeeded, use resolved type
231
+ if (result instanceof TypeVariable) {
232
+ result = result.resolve();
233
+ }
234
+ }
235
+ }
236
+ return result;
237
+ },
238
+
239
+ /**
240
+ * Visit an object literal
241
+ */
242
+ visitObjectLiteral(node, env) {
243
+ // Empty object {} is an error
244
+ if (!node.properties || node.properties.length === 0) {
245
+ this.addError(
246
+ "Empty object literal {} is not allowed. Records must have at least one field.",
247
+ node.location
248
+ );
249
+ return this.setType(node, new RecordType(new Map(), false));
250
+ }
251
+
252
+ const fields = new Map();
253
+ for (const prop of node.properties) {
254
+ const valueType = this.visit(prop.value, env);
255
+ fields.set(prop.key, valueType);
256
+ }
257
+ return this.setType(node, new RecordType(fields, false));
258
+ },
259
+
260
+ /**
261
+ * Visit a binding structure
262
+ * Can be either:
263
+ * 1. Object literal: { x = 1, y = 2 } → RecordType
264
+ * 2. Block with bindings: { x = 1, y = 2, trailingExpr } → type of trailingExpr
265
+ */
266
+ visitBindingStructure(node, env) {
267
+ // Check if this is a block expression (has trailing expr or return statements)
268
+ const hasReturnBinding = node.bindings?.some(
269
+ (b) => !b.name && b.value && b.value.type === "ReturnStatement"
270
+ );
271
+
272
+ if (node.trailingExpr || hasReturnBinding) {
273
+ // Block with bindings that returns a value
274
+ const blockEnv = env.createChild();
275
+
276
+ // Type all bindings
277
+ for (const binding of node.bindings || []) {
278
+ if (binding.name) {
279
+ // Handle type descriptor bindings (may have null value)
280
+ if (binding.isTypeDescriptor) {
281
+ // Type descriptor: get type from annotation, optionally check default value
282
+ const declaredType = binding.typeAnnotation
283
+ ? fromTypeAnnotation(binding.typeAnnotation, blockEnv)
284
+ : freshTypeVar();
285
+ if (binding.value) {
286
+ // Has default value - type check it
287
+ const valueType = this.visit(binding.value, blockEnv);
288
+ // Could add unification here to ensure default matches declared type
289
+ }
290
+ blockEnv.define(binding.name, declaredType);
291
+ } else if (binding.value) {
292
+ const valueType = this.visit(binding.value, blockEnv);
293
+ blockEnv.define(binding.name, valueType);
294
+ }
295
+ } else if (binding.value) {
296
+ // Effect (null name) - just type check for side effects
297
+ this.visit(binding.value, blockEnv);
298
+ }
299
+ }
300
+
301
+ // Return type of trailing expression if present
302
+ if (node.trailingExpr) {
303
+ return this.setType(node, this.visit(node.trailingExpr, blockEnv));
304
+ }
305
+
306
+ return this.setType(node, UNIT);
307
+ }
308
+
309
+ // No trailing expression - this is an object literal (record)
310
+ const fields = new Map();
311
+ const requiredFields = new Set(); // Track type descriptors without defaults
312
+ let hasTypeDescriptors = false;
313
+
314
+ for (const binding of node.bindings || []) {
315
+ if (binding.name) {
316
+ // Handle type descriptor bindings (may have null value)
317
+ if (binding.isTypeDescriptor) {
318
+ hasTypeDescriptors = true;
319
+ // Type descriptor: get type from annotation
320
+ const declaredType = binding.typeAnnotation
321
+ ? fromTypeAnnotation(binding.typeAnnotation, env)
322
+ : freshTypeVar();
323
+ if (binding.value) {
324
+ // Has default value - type check it
325
+ this.visit(binding.value, env);
326
+ } else {
327
+ // No default value - this is a required field
328
+ requiredFields.add(binding.name);
329
+ }
330
+ fields.set(binding.name, declaredType);
331
+ } else if (binding.value) {
332
+ const valueType = this.visit(binding.value, env);
333
+ fields.set(binding.name, valueType);
334
+ }
335
+ }
336
+ }
337
+ // Only pass requiredFields if this is a schema record (has type descriptors)
338
+ const recordType = new RecordType(fields, false, false, hasTypeDescriptors ? requiredFields : null);
339
+ return this.setType(node, recordType);
340
+ },
341
+ };
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Module Visitors - mixin for TypeChecker
3
+ *
4
+ * Visitors for import/export statements, type aliases, and block expressions.
5
+ */
6
+
7
+ import {
8
+ RecordType,
9
+ UNIT,
10
+ STRING,
11
+ freshTypeVar,
12
+ fromTypeAnnotation,
13
+ } from "../../types/types.js";
14
+ import { OverloadedFunctionType } from "../OverloadedFunctionType.js";
15
+ import { ExtensionInfo } from "../TypeEnvironment.js";
16
+
17
+ /**
18
+ * Module visitor methods to be mixed into TypeChecker.prototype
19
+ */
20
+ export const moduleVisitors = {
21
+ /**
22
+ * Visit a type alias declaration
23
+ * type Name = TypeAnnotation
24
+ */
25
+ visitTypeAlias(node, env) {
26
+ // Convert the type annotation to an internal type
27
+ const aliasedType = fromTypeAnnotation(node.typeAnnotation, env);
28
+
29
+ // Store in the environment's type alias registry
30
+ env.defineTypeAlias(node.name, aliasedType);
31
+
32
+ // Type alias declarations don't have a value, return unit
33
+ return this.setType(node, UNIT);
34
+ },
35
+
36
+ /**
37
+ * Visit an export statement
38
+ */
39
+ visitExportStatement(node, env) {
40
+ return this.visit(node.declaration, env);
41
+ },
42
+
43
+ /**
44
+ * Visit an import statement
45
+ * Resolves types from source module when possible for static type checking.
46
+ * Reports errors for imports that don't exist in the module.
47
+ */
48
+ visitImportStatement(node, env) {
49
+ // Register complex module overloads when any import from complex module is seen
50
+ // This allows operators like + - * / to work with complex numbers
51
+ if (node.modulePath === 'complex') {
52
+ this._registerComplexOverloads();
53
+ }
54
+
55
+ // Try to get types from the source module
56
+ const sourceExports = this.getModuleExportTypes(node.modulePath);
57
+ const sourceExtensions = sourceExports?._extensions || new Set();
58
+
59
+ for (const spec of node.specifiers) {
60
+ // Support both spec.name (old format) and spec.importedName (new format)
61
+ const importedName = spec.importedName || spec.name;
62
+ const localName = spec.localName || importedName;
63
+
64
+ if (sourceExports) {
65
+ const sourceType = sourceExports.get(importedName);
66
+ if (sourceType) {
67
+ // If it's an overloaded function, register it for overload resolution
68
+ if (sourceType instanceof OverloadedFunctionType) {
69
+ // Register the overloads in our overload registry for this import
70
+ if (!this.overloads[localName]) {
71
+ this.overloads[localName] = sourceType.overloads;
72
+ }
73
+ // Store overloadIds on the specifier for the compiler to use
74
+ // This enables compile-time resolution of cross-module overloads
75
+ spec.overloadIds = sourceType.userOverloads.map(o => o.overloadId);
76
+ spec.isOverloaded = true;
77
+ }
78
+ env.define(localName, sourceType);
79
+
80
+ // If this import is an extension function, register it as such
81
+ if (sourceExtensions.has(importedName)) {
82
+ // For imported extensions, we create minimal ExtensionInfo
83
+ // The selfType info comes from the function's first parameter (already captured in sourceType)
84
+ env.defineExtension(localName, new ExtensionInfo(localName, null, sourceType));
85
+ }
86
+
87
+ // Store type on specifier node for IDE hover support
88
+ this.setType(spec, sourceType);
89
+ continue;
90
+ }
91
+ // Module was analyzed but import name doesn't exist - report error
92
+ this.addError(
93
+ `'${importedName}' is not exported from module '${node.modulePath}'`,
94
+ spec.location || node.location
95
+ );
96
+ }
97
+ // Fall back to type variable to allow continued type checking
98
+ const typeVar = freshTypeVar();
99
+ env.define(localName, typeVar);
100
+ this.setType(spec, typeVar);
101
+ }
102
+ return UNIT;
103
+ },
104
+
105
+ /**
106
+ * Visit a namespace import statement
107
+ */
108
+ visitNamespaceImportStatement(node, env) {
109
+ const alias = node.alias || node.modulePath.split("/").pop();
110
+
111
+ // Try to get the actual exports from the module
112
+ const sourceExports = this.getModuleExportTypes(node.modulePath);
113
+
114
+ if (sourceExports && sourceExports.size > 0) {
115
+ // Create a record type with all the module's exports as fields
116
+ const fields = new Map();
117
+ for (const [name, type] of sourceExports) {
118
+ fields.set(name, type);
119
+ // Register overloaded functions for resolution
120
+ if (type instanceof OverloadedFunctionType) {
121
+ // Store with qualified name for potential future use
122
+ const qualifiedName = `${alias}.${name}`;
123
+ if (!this.overloads[qualifiedName]) {
124
+ this.overloads[qualifiedName] = type.overloads;
125
+ }
126
+ }
127
+ }
128
+ env.define(alias, new RecordType(fields, false));
129
+ } else {
130
+ // Fall back to open record if we can't analyze the module
131
+ env.define(alias, new RecordType(new Map(), true));
132
+ }
133
+ return UNIT;
134
+ },
135
+
136
+ /**
137
+ * Visit a block expression
138
+ */
139
+ visitBlockExpression(node, env) {
140
+ const blockEnv = env.createChild();
141
+ let lastType = UNIT;
142
+
143
+ for (const stmt of node.body) {
144
+ lastType = this.visit(stmt, blockEnv);
145
+ }
146
+
147
+ return this.setType(node, lastType);
148
+ },
149
+
150
+ /**
151
+ * Visit string interpolation
152
+ */
153
+ visitStringInterpolation(node, env) {
154
+ for (const expr of node.expressions) {
155
+ this.visit(expr, env);
156
+ }
157
+ return this.setType(node, STRING);
158
+ },
159
+ };
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Operator Visitors - mixin for TypeChecker
3
+ *
4
+ * Visitors for binary and unary operations.
5
+ */
6
+
7
+ import {
8
+ BOOL,
9
+ freshTypeVar,
10
+ } from "../../types/types.js";
11
+ import { BINARY_OP_NAMES, UNARY_OP_NAMES } from "../operatorMappings.js";
12
+
13
+ /**
14
+ * Operator visitor methods to be mixed into TypeChecker.prototype
15
+ */
16
+ export const operatorVisitors = {
17
+ /**
18
+ * Visit a binary operation
19
+ * Uses static overload resolution to determine result type.
20
+ * Checks user-defined overloads first, then falls back to primitives.
21
+ */
22
+ visitBinaryOp(node, env) {
23
+ const leftType = this.visit(node.left, env);
24
+ const rightType = this.visit(node.right, env);
25
+
26
+ const op = node.operator;
27
+
28
+ // Short-circuit logical operators don't go through overloads
29
+ if (op === "&&" || op === "||") {
30
+ return this.setType(node, BOOL);
31
+ }
32
+
33
+ // Map operator to function name
34
+ const funcName = BINARY_OP_NAMES[op];
35
+ if (funcName) {
36
+ // Use left operand's location for better error positioning
37
+ // (node.location points to after the expression, which is often wrong)
38
+ const errorLocation = node.left?.location || node.location;
39
+ const argTypes = [leftType, rightType];
40
+
41
+ // Operators only use built-in primitives, not user-defined functions
42
+ // Users cannot overload operators like + by defining 'add' functions
43
+ const outputType = this.resolveOverloadStatic(
44
+ funcName,
45
+ argTypes,
46
+ node,
47
+ errorLocation
48
+ );
49
+
50
+ if (outputType) {
51
+ // Convert structured type back to class-based type
52
+ const resultType = this.fromStructuredType(outputType);
53
+ return this.setType(node, resultType);
54
+ }
55
+
56
+ // Overload resolution failed - error already added
57
+ // Fall back to a reasonable type
58
+ if (["<", ">", "<=", ">=", "==", "!="].includes(op)) {
59
+ return this.setType(node, BOOL);
60
+ }
61
+ return this.setType(node, freshTypeVar());
62
+ }
63
+
64
+ // Unknown operator
65
+ return this.setType(node, freshTypeVar());
66
+ },
67
+
68
+ /**
69
+ * Visit a unary operation
70
+ * Uses static overload resolution to determine result type.
71
+ * Checks user-defined overloads first, then falls back to primitives.
72
+ */
73
+ visitUnaryOp(node, env) {
74
+ const operandType = this.visit(node.operand, env);
75
+
76
+ const op = node.operator;
77
+
78
+ // Map operator to function name
79
+ const funcName = UNARY_OP_NAMES[op];
80
+ if (funcName) {
81
+ // Use operand's location for better error positioning
82
+ const errorLocation = node.operand?.location || node.location;
83
+ const argTypes = [operandType];
84
+
85
+ // Operators only use built-in primitives, not user-defined functions
86
+ const outputType = this.resolveOverloadStatic(
87
+ funcName,
88
+ argTypes,
89
+ node,
90
+ errorLocation
91
+ );
92
+
93
+ if (outputType) {
94
+ const resultType = this.fromStructuredType(outputType);
95
+ return this.setType(node, resultType);
96
+ }
97
+
98
+ // Overload resolution failed - error already added
99
+ // Fall back based on operator
100
+ if (op === "!") {
101
+ return this.setType(node, BOOL);
102
+ }
103
+ return this.setType(node, operandType);
104
+ }
105
+
106
+ // Unknown operator - preserve operand type
107
+ return this.setType(node, operandType);
108
+ },
109
+ };