gofront 0.0.2 → 0.0.4

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,447 @@
1
+ // TypeChecker expression-checking methods — installed as a mixin on TypeChecker.prototype.
2
+
3
+ import {
4
+ ANY,
5
+ BOOL,
6
+ ERROR,
7
+ INT,
8
+ isAny,
9
+ isBool,
10
+ isNumeric,
11
+ isString,
12
+ NIL,
13
+ Scope,
14
+ STRING,
15
+ typeStr,
16
+ UNTYPED_BOOL,
17
+ UNTYPED_FLOAT,
18
+ UNTYPED_INT,
19
+ UNTYPED_STRING,
20
+ VOID,
21
+ } from "./types.js";
22
+
23
+ export const expressionCheckMethods = {
24
+ checkExpr(expr, scope) {
25
+ const t = this._checkExpr(expr, scope);
26
+ expr._type = t;
27
+ return t;
28
+ },
29
+
30
+ _checkExpr(expr, scope) {
31
+ switch (expr.kind) {
32
+ case "BasicLit": {
33
+ switch (expr.litKind) {
34
+ case "INT":
35
+ return UNTYPED_INT;
36
+ case "FLOAT":
37
+ return UNTYPED_FLOAT;
38
+ case "STRING":
39
+ return UNTYPED_STRING;
40
+ case "BOOL":
41
+ return UNTYPED_BOOL;
42
+ case "NIL":
43
+ return NIL;
44
+ }
45
+ break;
46
+ }
47
+
48
+ case "Ident": {
49
+ const t = scope.lookup(expr.name);
50
+ if (!t) return this.err(`Undefined: '${expr.name}'`, expr);
51
+ // Mark type-name identifiers so SelectorExpr can detect method expressions
52
+ if (this.types.has(expr.name)) expr._isTypeRef = true;
53
+ return t;
54
+ }
55
+
56
+ case "UnaryExpr": {
57
+ const ot = this.checkExpr(expr.operand, scope);
58
+ if (expr.op === "!") {
59
+ if (!isBool(ot) && !isAny(ot)) this.err("! requires bool", expr);
60
+ return BOOL;
61
+ }
62
+ if (expr.op === "-" || expr.op === "+") {
63
+ if (!isNumeric(ot) && !isAny(ot))
64
+ this.err(`${expr.op} requires numeric`, expr);
65
+ return ot;
66
+ }
67
+ return ot;
68
+ }
69
+
70
+ case "BinaryExpr": {
71
+ const lt = this.checkExpr(expr.left, scope);
72
+ const rt = this.checkExpr(expr.right, scope);
73
+ if (isAny(lt) || isAny(rt))
74
+ return this.binaryResultType(expr.op, ANY, ANY, expr);
75
+ return this.binaryResultType(expr.op, lt, rt, expr);
76
+ }
77
+
78
+ case "CallExpr": {
79
+ return this.checkCall(expr, scope);
80
+ }
81
+
82
+ case "SelectorExpr": {
83
+ const baseType = this.checkExpr(expr.expr, scope);
84
+ // Method expression: TypeName.MethodName → func(ReceiverType, ...params) ret
85
+ if (expr.expr._isTypeRef) {
86
+ let base = baseType.kind === "named" ? baseType.underlying : baseType;
87
+ base = this.resolveType(base);
88
+ if (base?.kind === "struct" && base.methods?.has(expr.field)) {
89
+ const methodType = base.methods.get(expr.field);
90
+ // Return a func type with the receiver prepended to params
91
+ expr._isMethodExpr = true;
92
+ expr._methodExprRecv = baseType;
93
+ return {
94
+ kind: "func",
95
+ params: [baseType, ...(methodType.params ?? [])],
96
+ returns: methodType.returns ?? [],
97
+ variadic: methodType.variadic,
98
+ };
99
+ }
100
+ }
101
+ const ft = this.fieldType(baseType, expr.field, expr);
102
+ // Mark method selectors so codegen can emit .bind() when used as a value
103
+ if (ft?.kind === "func") {
104
+ let base = baseType.kind === "named" ? baseType.underlying : baseType;
105
+ base = this.resolveType(base);
106
+ if (base?.kind === "struct" && base.methods?.has(expr.field)) {
107
+ expr._isMethodValue = true;
108
+ }
109
+ }
110
+ return ft;
111
+ }
112
+
113
+ case "IndexExpr": {
114
+ const bt = this.checkExpr(expr.expr, scope);
115
+ this.checkExpr(expr.index, scope);
116
+ if (isAny(bt)) return ANY;
117
+ if (bt.kind === "slice" || bt.kind === "array") return bt.elem;
118
+ if (bt.kind === "map") {
119
+ expr._mapValueType = bt.value; // for codegen zero-value fallback
120
+ return bt.value;
121
+ }
122
+ if (isString(bt)) return INT; // byte
123
+ return this.err(`Cannot index type ${typeStr(bt)}`, expr);
124
+ }
125
+
126
+ case "SliceExpr": {
127
+ const bt = this.checkExpr(expr.expr, scope);
128
+ if (expr.low) this.checkExpr(expr.low, scope);
129
+ if (expr.high) this.checkExpr(expr.high, scope);
130
+ if (expr.max) this.checkExpr(expr.max, scope);
131
+ if (isAny(bt)) return ANY;
132
+ if (bt.kind === "slice" || bt.kind === "array")
133
+ return { kind: "slice", elem: bt.elem };
134
+ if (isString(bt)) return STRING;
135
+ return this.err(`Cannot slice type ${typeStr(bt)}`, expr);
136
+ }
137
+
138
+ case "CompositeLit": {
139
+ return this.checkCompositeLit(expr, scope);
140
+ }
141
+
142
+ case "FuncLit": {
143
+ const inner = new Scope(scope);
144
+ for (const p of expr.params)
145
+ inner.define(
146
+ p.name,
147
+ p.type ? this.resolveTypeNode(p.type, scope) : ANY,
148
+ );
149
+ const ret = expr.returnType
150
+ ? this.resolveTypeNode(expr.returnType, scope)
151
+ : VOID;
152
+ const savedDefer = this._deferCount;
153
+ this._deferCount = 0;
154
+ this.checkBlock(expr.body, inner, ret);
155
+ this._reportUnused(inner, expr);
156
+ if (this._deferCount > 0) expr.body._hasDefer = true;
157
+ this._deferCount = savedDefer;
158
+ const paramTypes = expr.params.map((p) =>
159
+ p.type ? this.resolveTypeNode(p.type, scope) : ANY,
160
+ );
161
+ return {
162
+ kind: "func",
163
+ params: paramTypes,
164
+ returns: [ret],
165
+ async: expr.async,
166
+ };
167
+ }
168
+
169
+ case "AwaitExpr": {
170
+ // await unwraps whatever the expression produces — no Promise type modelling
171
+ return this.checkExpr(expr.expr, scope);
172
+ }
173
+
174
+ case "TypeConversion": {
175
+ this.checkExpr(expr.expr, scope);
176
+ return this.resolveTypeNode(expr.targetType, scope);
177
+ }
178
+
179
+ case "TypeAssertExpr": {
180
+ const srcType = this.checkExpr(expr.expr, scope);
181
+ const targetType = this.resolveTypeNode(expr.type, scope);
182
+
183
+ // In Go, the source of a type assertion must be an interface type.
184
+ // We allow `any` (which is GoFront's basic any) and interface types.
185
+ if (srcType && !isAny(srcType)) {
186
+ let underlying =
187
+ srcType.kind === "named" ? srcType.underlying : srcType;
188
+ underlying = this.resolveType(underlying);
189
+ if (underlying?.kind !== "interface") {
190
+ this.err(
191
+ `invalid type assertion: ${typeStr(srcType)} is not an interface`,
192
+ expr,
193
+ );
194
+ }
195
+ }
196
+
197
+ return targetType;
198
+ }
199
+
200
+ case "RangeExpr": {
201
+ // Type-check the iterated expression so its _type is annotated
202
+ const collType = this.checkExpr(expr.expr, scope);
203
+ const resolved =
204
+ collType?.kind === "named" ? collType.underlying : collType;
205
+ // Return a tuple of (index/key type, value type) so DefineStmt can
206
+ // assign the correct types to range variables.
207
+ const isString =
208
+ (resolved?.kind === "basic" && resolved.name === "string") ||
209
+ (resolved?.kind === "untyped" && resolved.base === "string");
210
+ if (isString) {
211
+ // string range: (int index, rune value) — rune is an alias for int
212
+ return { kind: "tuple", types: [INT, INT] };
213
+ }
214
+ if (resolved?.kind === "slice") {
215
+ return { kind: "tuple", types: [INT, resolved.elem ?? ANY] };
216
+ }
217
+ if (resolved?.kind === "map") {
218
+ return {
219
+ kind: "tuple",
220
+ types: [resolved.key ?? ANY, resolved.value ?? ANY],
221
+ };
222
+ }
223
+ // integer range or unknown — return single-element tuple
224
+ return { kind: "tuple", types: [collType] };
225
+ }
226
+
227
+ default:
228
+ return ANY;
229
+ }
230
+ return ANY;
231
+ },
232
+
233
+ checkCall(expr, scope) {
234
+ const fnType = this.checkExpr(expr.func, scope);
235
+
236
+ // Handle built-ins before evaluating args; some (e.g. new) take type names as
237
+ // arguments, which would otherwise produce false "Undefined" errors.
238
+ if (fnType.kind === "builtin") {
239
+ const argTypes = expr.args.map((a, i) => {
240
+ // new(T) — first arg is a type name, not a value expression
241
+ if (fnType.name === "new" && i === 0) return ANY;
242
+ return this.checkExpr(a, scope);
243
+ });
244
+ return this.checkBuiltin(fnType.name, expr, argTypes, scope);
245
+ }
246
+
247
+ // Multi-value forwarding: f(g()) where g() returns multiple values
248
+ // When a single argument is a multi-return call, flatten its tuple into args.
249
+ let argTypes;
250
+ if (expr.args.length === 1 && expr.args[0].kind === "CallExpr") {
251
+ const argType = this.checkExpr(expr.args[0], scope);
252
+ if (argType?.kind === "tuple") {
253
+ expr._multiForward = true; // flag for codegen
254
+ argTypes = argType.types;
255
+ } else {
256
+ argTypes = [argType];
257
+ }
258
+ } else {
259
+ argTypes = expr.args.map((a) => this.checkExpr(a, scope));
260
+ }
261
+
262
+ if (isAny(fnType)) return ANY;
263
+
264
+ if (fnType.kind !== "func") {
265
+ return this.err(`Cannot call non-function type ${typeStr(fnType)}`, expr);
266
+ }
267
+
268
+ // Check arg count (allow variadic slack)
269
+ const minArgs = fnType.params.filter(
270
+ (_, i) => !fnType.variadic || i < fnType.params.length - 1,
271
+ ).length;
272
+ if (argTypes.length < minArgs) {
273
+ this.err(
274
+ `Too few arguments: expected ${fnType.params.length}, got ${argTypes.length}`,
275
+ expr,
276
+ );
277
+ }
278
+ if (!fnType.variadic && argTypes.length > fnType.params.length) {
279
+ this.err(
280
+ `Too many arguments: expected ${fnType.params.length}, got ${argTypes.length}`,
281
+ expr,
282
+ );
283
+ }
284
+
285
+ // Check each argument is assignable to its parameter type
286
+ for (let i = 0; i < fnType.params.length && i < argTypes.length; i++) {
287
+ const paramIdx =
288
+ fnType.variadic && i >= fnType.params.length - 1
289
+ ? fnType.params.length - 1
290
+ : i;
291
+ // Spread arg (f(slice...)) passes a whole slice into a variadic — skip per-element check
292
+ if (expr.args[i]?._spread) continue;
293
+ this.assertAssignable(fnType.params[paramIdx], argTypes[i], expr.args[i]);
294
+ }
295
+
296
+ const ret = fnType.returns;
297
+ if (!ret || ret.length === 0) return VOID;
298
+ if (ret.length === 1) return ret[0];
299
+ return { kind: "tuple", types: ret };
300
+ },
301
+
302
+ checkBuiltin(name, expr, argTypes, scope) {
303
+ switch (name) {
304
+ case "len":
305
+ case "cap":
306
+ return INT;
307
+ case "append":
308
+ return argTypes[0] ?? ANY;
309
+ case "copy":
310
+ return INT;
311
+ case "delete":
312
+ return VOID;
313
+ case "make": {
314
+ if (expr.args.length < 1) return ANY;
315
+ const typeArg = expr.args[0];
316
+ const typeNode = typeArg.kind === "TypeExpr" ? typeArg.type : typeArg;
317
+ return this.resolveTypeNode(typeNode, scope) ?? ANY;
318
+ }
319
+ case "new": {
320
+ if (expr.args.length < 1) return ANY;
321
+ const inner = this.resolveTypeNode(expr.args[0], scope);
322
+ return { kind: "pointer", base: inner };
323
+ }
324
+ case "print":
325
+ case "println":
326
+ case "panic":
327
+ return VOID;
328
+ case "recover":
329
+ return ANY;
330
+ case "error":
331
+ return ERROR;
332
+ case "min":
333
+ case "max":
334
+ return argTypes[0] ?? ANY;
335
+ case "clear":
336
+ return VOID;
337
+ default:
338
+ return ANY;
339
+ }
340
+ },
341
+
342
+ checkCompositeLit(expr, scope, hintType = null) {
343
+ // null typeExpr = implicit lit inside a slice/map: {X:1} in []Point{{X:1}}
344
+ const t =
345
+ expr.typeExpr === null
346
+ ? (hintType ?? ANY)
347
+ : this.resolveTypeNode(expr.typeExpr, scope);
348
+ const base = t.kind === "named" ? t.underlying : t;
349
+
350
+ if (base?.kind === "struct") {
351
+ // Detect positional (unkeyed) literal: all elements must be non-KeyValueExpr
352
+ const hasPositional = expr.elems.some((e) => e.kind !== "KeyValueExpr");
353
+ const hasKeyed = expr.elems.some((e) => e.kind === "KeyValueExpr");
354
+ if (hasPositional && hasKeyed) {
355
+ this.err("mixture of field:value and value initializers", expr);
356
+ }
357
+ if (hasPositional) {
358
+ // Map positional elements to struct fields by declaration order
359
+ const fieldNames = [...(base.fields?.keys() ?? [])];
360
+ for (let i = 0; i < expr.elems.length; i++) {
361
+ const elem = expr.elems[i];
362
+ const fieldName = fieldNames[i];
363
+ if (!fieldName) {
364
+ this.err("too many values in struct literal", elem);
365
+ continue;
366
+ }
367
+ const fieldType = base.fields.get(fieldName);
368
+ const vt = this.checkExpr(elem, scope);
369
+ if (fieldType) this.assertAssignable(fieldType, vt, elem);
370
+ // Annotate for codegen
371
+ elem._positionalField = fieldName;
372
+ }
373
+ return t;
374
+ }
375
+ for (const elem of expr.elems) {
376
+ if (elem.kind === "KeyValueExpr") {
377
+ const keyName = elem.key.name;
378
+ const fieldType = base.fields?.get(keyName);
379
+ if (fieldType) {
380
+ const vt = this.checkExpr(elem.value, scope);
381
+ this.assertAssignable(fieldType, vt, elem.value);
382
+ } else {
383
+ // Check if it's an embedded type name (e.g. Dog{Animal: Animal{...}})
384
+ const embed = base._embeds?.find(
385
+ (e) => (e.kind === "named" ? e.name : null) === keyName,
386
+ );
387
+ if (embed) {
388
+ const vt = this.checkExpr(elem.value, scope);
389
+ this.assertAssignable(embed, vt, elem.value);
390
+ elem._isEmbedInit = true;
391
+ } else {
392
+ this.err(`Unknown field '${keyName}'`, elem.key);
393
+ }
394
+ }
395
+ } else {
396
+ this.checkExpr(elem, scope);
397
+ }
398
+ }
399
+ return t;
400
+ }
401
+
402
+ if (base?.kind === "slice" || base?.kind === "array") {
403
+ for (const elem of expr.elems) {
404
+ if (elem.kind === "CompositeLit" && elem.typeExpr === null) {
405
+ const et = this.checkCompositeLit(elem, scope, base.elem);
406
+ elem._type = et;
407
+ } else if (elem.kind !== "KeyValueExpr") {
408
+ const et = this.checkExpr(elem, scope);
409
+ this.assertAssignable(base.elem, et, elem);
410
+ }
411
+ }
412
+ return t;
413
+ }
414
+
415
+ if (base?.kind === "map") {
416
+ for (const elem of expr.elems) {
417
+ if (elem.kind === "KeyValueExpr") {
418
+ const kt = this.checkExpr(elem.key, scope);
419
+ // value may be an implicit composite lit
420
+ let vt;
421
+ if (
422
+ elem.value.kind === "CompositeLit" &&
423
+ elem.value.typeExpr === null
424
+ ) {
425
+ vt = this.checkCompositeLit(elem.value, scope, base.value);
426
+ elem.value._type = vt;
427
+ } else {
428
+ vt = this.checkExpr(elem.value, scope);
429
+ }
430
+ this.assertAssignable(base.key, kt, elem.key);
431
+ this.assertAssignable(base.value, vt, elem.value);
432
+ }
433
+ }
434
+ return t;
435
+ }
436
+
437
+ // Unknown type context — still check elements
438
+ for (const elem of expr.elems) {
439
+ if (elem.kind === "KeyValueExpr") {
440
+ this.checkExpr(elem.value, scope);
441
+ } else {
442
+ this.checkExpr(elem, scope);
443
+ }
444
+ }
445
+ return t ?? ANY;
446
+ },
447
+ };