gofront 0.0.6 → 0.0.7

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/src/codegen.js CHANGED
@@ -35,6 +35,7 @@ export class CodeGen {
35
35
  this.out = [];
36
36
  this.indent = 0;
37
37
  this.structNames = new Set();
38
+ this.namedWrapperNames = new Set();
38
39
  this.jsImports = jsImports;
39
40
  this.bundledPackages = bundledPackages;
40
41
  this.namedReturnVars = null; // names of current function's named return vars
@@ -92,7 +93,7 @@ export class CodeGen {
92
93
  }
93
94
  }
94
95
 
95
- // Build a map: structName → [MethodDecl, ...]
96
+ // Build a map: typeName → [MethodDecl, ...]
96
97
  const methods = new Map();
97
98
  for (const d of program.decls) {
98
99
  if (d.kind === "MethodDecl") {
@@ -102,6 +103,18 @@ export class CodeGen {
102
103
  }
103
104
  }
104
105
 
106
+ // Collect named non-struct types that have methods (emitted as wrapper classes)
107
+ for (const d of program.decls) {
108
+ if (
109
+ d.kind === "TypeDecl" &&
110
+ d.type.kind !== "StructType" &&
111
+ d.type.kind !== "InterfaceType" &&
112
+ (methods.get(d.name) ?? []).length > 0
113
+ ) {
114
+ this.namedWrapperNames.add(d.name);
115
+ }
116
+ }
117
+
105
118
  // Emit ESM import statements for npm packages
106
119
  for (const [importPath, names] of this.jsImports) {
107
120
  if (names.length === 0) continue;
@@ -248,13 +261,41 @@ export class CodeGen {
248
261
  } else if (decl.type.kind === "InterfaceType") {
249
262
  // Interfaces are compile-time only — no JS output needed.
250
263
  this.line(`// interface ${decl.name} (compile-time only)`);
264
+ } else if (methodDecls.length > 0) {
265
+ // Named non-struct type with methods — emit an ES6 wrapper class.
266
+ this.genNamedTypeClass(decl.name, methodDecls);
251
267
  } else {
252
- // Type alias — no JS equivalent needed unless it's used as a constructor.
253
- // e.g. type MyInt int → just a comment
254
268
  this.line(`// type ${decl.name} = ${this.typeComment(decl.type)}`);
255
269
  }
256
270
  }
257
271
 
272
+ genNamedTypeClass(name, methodDecls) {
273
+ const namedType = this.checker?.types.get(name);
274
+ const underlying = namedType?.underlying;
275
+ let field, ctorDefault;
276
+ if (underlying?.kind === "func") {
277
+ field = "_fn";
278
+ ctorDefault = "null";
279
+ } else if (underlying?.kind === "map") {
280
+ field = "_map";
281
+ ctorDefault = "{}";
282
+ } else {
283
+ field = "_items";
284
+ ctorDefault = "[]";
285
+ }
286
+ this.line(`class ${name} {`);
287
+ this.indented(() => {
288
+ this.line(
289
+ `constructor(${field} = ${ctorDefault}) { this.${field} = ${field}; }`,
290
+ );
291
+ for (const m of methodDecls) {
292
+ this.blank();
293
+ this.genMethod(m, field);
294
+ }
295
+ });
296
+ this.line("}");
297
+ }
298
+
258
299
  genStruct(name, structTypeAst, methodDecls) {
259
300
  const fields = [];
260
301
  if (this.checker) {
@@ -317,23 +358,45 @@ export class CodeGen {
317
358
  this.line("}");
318
359
  }
319
360
 
320
- genMethod(decl) {
361
+ genMethod(decl, recvField = null) {
321
362
  const params = decl.params.map((p) => p.name).join(", ");
322
363
  const asyncPrefix = decl.async ? "async " : "";
323
364
  this.line(`${asyncPrefix}${decl.name}(${params}) {`);
324
365
  const prevBoxed = this._boxedVars;
325
366
  this._boxedVars = new Set();
326
367
  this._scanAddressTaken(decl.body);
368
+ const prevUnwrapped = this._unwrappedRecv;
327
369
  this.indented(() => {
328
370
  if (decl.recvName && decl.recvName !== "_") {
329
- this.line(`const ${decl.recvName} = this;`);
371
+ if (recvField) {
372
+ this.line(`const ${decl.recvName} = this.${recvField};`);
373
+ this._unwrappedRecv = decl.recvName;
374
+ } else {
375
+ this.line(`const ${decl.recvName} = this;`);
376
+ }
330
377
  }
331
378
  this._withNamedReturns(decl, () => this._genBody(decl.body));
332
379
  });
380
+ this._unwrappedRecv = prevUnwrapped;
333
381
  this._boxedVars = prevBoxed;
334
382
  this.line("}");
335
383
  }
336
384
 
385
+ // Returns the wrapper field name ("_fn", "_items", "_map") if `type` is a named
386
+ // non-struct type emitted as a wrapper class, or null otherwise.
387
+ // Pass `expr` so we can skip unwrapping when the expression is the method receiver
388
+ // (which was already unwrapped to `this.<field>` at the top of the method body).
389
+ _namedWrapperField(type, expr = null) {
390
+ if (type?.kind !== "named") return null;
391
+ if (!this.namedWrapperNames.has(type.name)) return null;
392
+ if (expr?.kind === "Ident" && expr.name === this._unwrappedRecv)
393
+ return null;
394
+ const u = type.underlying;
395
+ if (u?.kind === "func") return "_fn";
396
+ if (u?.kind === "map") return "_map";
397
+ return "_items";
398
+ }
399
+
337
400
  // ── Function declarations ────────────────────────────────────
338
401
 
339
402
  genFuncDecl(decl, nameOverride) {
package/src/parser.js CHANGED
@@ -308,16 +308,6 @@ export class Parser {
308
308
  return this.looksLikeType(next) || next.type === T.ELLIPSIS;
309
309
  }
310
310
 
311
- // Heuristic: peek2 is a param-name separator if it's a comma, rparen, or a type token
312
- isParamType(tok) {
313
- if (!tok) return false;
314
- // T.RPAREN is intentionally excluded: when peek2 is ")", the current IDENT
315
- // is the type of an unnamed param (e.g. func(int)), not a param name.
316
- return (
317
- tok.type === T.COMMA || tok.type === T.ELLIPSIS || this.looksLikeType(tok)
318
- );
319
- }
320
-
321
311
  looksLikeType(tok) {
322
312
  return (
323
313
  tok.type === T.LBRACKET ||
@@ -111,8 +111,16 @@ export const expressionCheckMethods = {
111
111
  if (expr.expr._isTypeRef) {
112
112
  let base = baseType.kind === "named" ? baseType.underlying : baseType;
113
113
  base = this.resolveType(base);
114
+ let methodType = null;
114
115
  if (base?.kind === "struct" && base.methods?.has(expr.field)) {
115
- const methodType = base.methods.get(expr.field);
116
+ methodType = base.methods.get(expr.field);
117
+ } else if (
118
+ baseType.kind === "named" &&
119
+ baseType.methods?.has(expr.field)
120
+ ) {
121
+ methodType = baseType.methods.get(expr.field);
122
+ }
123
+ if (methodType) {
116
124
  // Return a func type with the receiver prepended to params
117
125
  expr._isMethodExpr = true;
118
126
  expr._methodExprRecv = baseType;
@@ -129,7 +137,10 @@ export const expressionCheckMethods = {
129
137
  if (ft?.kind === "func") {
130
138
  let base = baseType.kind === "named" ? baseType.underlying : baseType;
131
139
  base = this.resolveType(base);
132
- if (base?.kind === "struct" && base.methods?.has(expr.field)) {
140
+ if (
141
+ (base?.kind === "struct" && base.methods?.has(expr.field)) ||
142
+ (baseType.kind === "named" && baseType.methods?.has(expr.field))
143
+ ) {
133
144
  expr._isMethodValue = true;
134
145
  }
135
146
  }
@@ -140,9 +151,14 @@ export const expressionCheckMethods = {
140
151
  const bt = this.checkExpr(expr.expr, scope);
141
152
  this.checkExpr(expr.index, scope);
142
153
  if (isAny(bt)) return ANY;
143
- if (bt.kind === "slice" || bt.kind === "array") {
154
+ // Named types (e.g. type Nums []int) are indexable via their underlying
155
+ const btu =
156
+ bt.kind === "named"
157
+ ? (this.resolveType(bt.underlying) ?? bt.underlying)
158
+ : bt;
159
+ if (btu.kind === "slice" || btu.kind === "array") {
144
160
  // Compile-time bounds checking for arrays
145
- if (bt.kind === "array" && bt.size != null) {
161
+ if (btu.kind === "array" && btu.size != null) {
146
162
  const idx = this._constIntValue(expr.index);
147
163
  if (idx !== null) {
148
164
  if (idx < 0) {
@@ -150,7 +166,7 @@ export const expressionCheckMethods = {
150
166
  `invalid array index ${idx} (index must not be negative)`,
151
167
  expr,
152
168
  );
153
- } else if (idx >= bt.size) {
169
+ } else if (idx >= btu.size) {
154
170
  this.err(
155
171
  `invalid array index ${idx} (out of bounds for ${typeStr(bt)})`,
156
172
  expr,
@@ -158,11 +174,11 @@ export const expressionCheckMethods = {
158
174
  }
159
175
  }
160
176
  }
161
- return bt.elem;
177
+ return btu.elem;
162
178
  }
163
- if (bt.kind === "map") {
164
- expr._mapValueType = bt.value; // for codegen zero-value fallback
165
- return bt.value;
179
+ if (btu.kind === "map") {
180
+ expr._mapValueType = btu.value; // for codegen zero-value fallback
181
+ return btu.value;
166
182
  }
167
183
  if (isString(bt)) return INT; // byte
168
184
  return this.err(`Cannot index type ${typeStr(bt)}`, expr);
@@ -410,6 +426,19 @@ export const expressionCheckMethods = {
410
426
 
411
427
  if (isAny(fnType)) return ANY;
412
428
 
429
+ // Named type used as a constructor/conversion: Greeter(fn), NodeFunc(fn)
430
+ // The callee is a type name, not a callable variable.
431
+ if (fnType.kind === "named" && expr.func._isTypeRef) {
432
+ expr._isTypeConversion = true;
433
+ expr._conversionTargetType = fnType;
434
+ return fnType;
435
+ }
436
+
437
+ // Named func types (e.g. type Handler func(int) int) are callable via variable
438
+ if (fnType.kind === "named" && fnType.underlying?.kind === "func") {
439
+ fnType = fnType.underlying;
440
+ }
441
+
413
442
  // Generic function inference
414
443
  if (fnType.kind === "generic") {
415
444
  const typeArgs = this.inferTypeArgs(fnType, argTypes);