gofront 0.0.6 → 0.0.8

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.
@@ -16,9 +16,10 @@ function vlqEncode(value) {
16
16
  return result;
17
17
  }
18
18
 
19
- export function buildSourceMap(sources, mappings) {
20
- // sources: string[]
21
- // mappings: Array<{ genLine: number, srcLine: number, srcFileIdx?: number }> (0-based)
19
+ export function buildSourceMap(sources, mappings, sourcesContent) {
20
+ // sources: string[]
21
+ // mappings: Array<{ genLine: number, srcLine: number, srcFileIdx?: number }> (0-based)
22
+ // sourcesContent: string[] | undefined — original file contents, embedded for DevTools
22
23
  // Emits one segment per generated line, column 0 → source line (delta-encoded).
23
24
  const lines = [];
24
25
  let prevSrcLine = 0;
@@ -44,10 +45,12 @@ export function buildSourceMap(sources, mappings) {
44
45
  lines.push(""); // no mapping for this generated line
45
46
  }
46
47
  }
47
- return JSON.stringify({
48
+ const map = {
48
49
  version: 3,
49
50
  sources,
50
51
  names: [],
51
52
  mappings: lines.join(";"),
52
- });
53
+ };
54
+ if (sourcesContent) map.sourcesContent = sourcesContent;
55
+ return JSON.stringify(map);
53
56
  }
@@ -7,6 +7,30 @@ export const statementGenMethods = {
7
7
  for (const stmt of block.stmts) this.genStmt(stmt);
8
8
  },
9
9
 
10
+ // Shared helper for comma-ok map index in both DefineStmt and AssignStmt.
11
+ // isDefine=true → emit `let v = m[k]; let ok = k in m;`
12
+ // isDefine=false → emit `v = k in m ? m[k] : zero; ok = k in m;`
13
+ _genCommaOkMapIndex(stmt, rhsNode, isDefine) {
14
+ const mapExpr = this.genExpr(rhsNode.expr);
15
+ const keyExpr = this.genExpr(rhsNode.index);
16
+ const [vName, okName] = stmt.lhs.map((e) => e.name ?? this.genExpr(e));
17
+ const decl = isDefine ? "let " : "";
18
+ if (vName !== "_") {
19
+ if (isDefine) {
20
+ this.line(`let ${vName} = ${mapExpr}[${keyExpr}];`);
21
+ } else {
22
+ const zero = rhsNode._mapValueType
23
+ ? this.zeroValueForType(rhsNode._mapValueType)
24
+ : "undefined";
25
+ this.line(
26
+ `${vName} = (${keyExpr}) in ${mapExpr} ? ${mapExpr}[${keyExpr}] : ${zero};`,
27
+ );
28
+ }
29
+ }
30
+ if (okName !== "_")
31
+ this.line(`${decl}${okName} = (${keyExpr}) in ${mapExpr};`);
32
+ },
33
+
10
34
  genStmt(stmt) {
11
35
  // Record source mapping for the first line this statement produces
12
36
  const srcLine = stmt._line ?? null;
@@ -31,15 +55,7 @@ export const statementGenMethods = {
31
55
  rhsNode?.kind === "IndexExpr" &&
32
56
  rhsNode.expr?._type?.kind === "map"
33
57
  ) {
34
- const mapExpr = this.genExpr(rhsNode.expr);
35
- const keyExpr = this.genExpr(rhsNode.index);
36
- const [vName, okName] = stmt.lhs.map(
37
- (e) => e.name ?? this.genExpr(e),
38
- );
39
- if (vName !== "_")
40
- this.line(`let ${vName} = ${mapExpr}[${keyExpr}];`);
41
- if (okName !== "_")
42
- this.line(`let ${okName} = (${keyExpr}) in ${mapExpr};`);
58
+ this._genCommaOkMapIndex(stmt, rhsNode, true);
43
59
  break;
44
60
  }
45
61
  const rhs = stmt.rhs.map((e) => this.genExpr(e));
@@ -117,22 +133,7 @@ export const statementGenMethods = {
117
133
  }
118
134
  // comma-ok map index: v, ok = m["key"]
119
135
  if (stmt._commaOkMap) {
120
- const rhsNode = stmt.rhs[0];
121
- const mapExpr = this.genExpr(rhsNode.expr);
122
- const keyExpr = this.genExpr(rhsNode.index);
123
- const [vName, okName] = stmt.lhs.map(
124
- (e) => e.name ?? this.genExpr(e),
125
- );
126
- if (vName !== "_") {
127
- const zero = rhsNode._mapValueType
128
- ? this.zeroValueForType(rhsNode._mapValueType)
129
- : "undefined";
130
- this.line(
131
- `${vName} = (${keyExpr}) in ${mapExpr} ? ${mapExpr}[${keyExpr}] : ${zero};`,
132
- );
133
- }
134
- if (okName !== "_")
135
- this.line(`${okName} = (${keyExpr}) in ${mapExpr};`);
136
+ this._genCommaOkMapIndex(stmt, stmt.rhs[0], false);
136
137
  break;
137
138
  }
138
139
  // comma-ok type assertion: v, ok = x.(T)
@@ -383,8 +384,11 @@ export const statementGenMethods = {
383
384
  const init = stmt.init;
384
385
  const range = init.rhs[0];
385
386
  const lhs = init.lhs.map((e) => e.name ?? this.genExpr(e));
386
- const iteree = this.genExpr(range.expr);
387
387
  const iterType = range.expr._type;
388
+ const wrapField = this._namedWrapperField(iterType, range.expr);
389
+ const iteree = wrapField
390
+ ? `${this.genExpr(range.expr)}.${wrapField}`
391
+ : this.genExpr(range.expr);
388
392
 
389
393
  let iterExpr;
390
394
  if (
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
@@ -51,6 +52,9 @@ export class CodeGen {
51
52
  this._usesCdiv = false;
52
53
  this._usesError = false;
53
54
  this._usesErrorIs = false;
55
+ this._usesPathClean = false;
56
+ this._usesTimeFmt = false;
57
+ this._usesTimeParse = false;
54
58
  // Iterator (range-over-func) context
55
59
  this._inIteratorBody = false;
56
60
  this._iterDepth = 0;
@@ -92,7 +96,7 @@ export class CodeGen {
92
96
  }
93
97
  }
94
98
 
95
- // Build a map: structName → [MethodDecl, ...]
99
+ // Build a map: typeName → [MethodDecl, ...]
96
100
  const methods = new Map();
97
101
  for (const d of program.decls) {
98
102
  if (d.kind === "MethodDecl") {
@@ -102,6 +106,18 @@ export class CodeGen {
102
106
  }
103
107
  }
104
108
 
109
+ // Collect named non-struct types that have methods (emitted as wrapper classes)
110
+ for (const d of program.decls) {
111
+ if (
112
+ d.kind === "TypeDecl" &&
113
+ d.type.kind !== "StructType" &&
114
+ d.type.kind !== "InterfaceType" &&
115
+ (methods.get(d.name) ?? []).length > 0
116
+ ) {
117
+ this.namedWrapperNames.add(d.name);
118
+ }
119
+ }
120
+
105
121
  // Emit ESM import statements for npm packages
106
122
  for (const [importPath, names] of this.jsImports) {
107
123
  if (names.length === 0) continue;
@@ -213,6 +229,47 @@ export class CodeGen {
213
229
  helpers.push(
214
230
  'var __errorIs = __errorIs || function(err, target) { while (err !== null && err !== undefined) { if (err === target) return true; if (typeof err === "object" && typeof target === "object" && err._msg !== undefined && target._msg !== undefined && err._msg === target._msg) return true; err = err?._cause ?? null; } return false; };',
215
231
  );
232
+ if (this._usesPathClean)
233
+ helpers.push(
234
+ 'var __pathClean = __pathClean || function(p) { if (!p) return "."; const abs = p.startsWith("/"); const parts = p.split("/").reduce((acc, s) => { if (s === "" || s === ".") return acc; if (s === "..") { if (acc.length && acc[acc.length-1] !== "..") acc.pop(); else if (!abs) acc.push(".."); } else acc.push(s); return acc; }, []); return (abs ? "/" : "") + (parts.join("/") || "."); };',
235
+ );
236
+ if (this._usesTimeFmt)
237
+ helpers.push(
238
+ [
239
+ "var __timeFmt = __timeFmt || function(d, layout) {",
240
+ 'const pad = (n, w=2) => String(n).padStart(w, "0");',
241
+ 'const DAYS=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];',
242
+ 'const MONTHS=["January","February","March","April","May","June","July","August","September","October","November","December"];',
243
+ "return layout.replace(/2006|06|Monday|Mon|January|Jan|01|1|02|2|15|PM|3|04|4|05|5|Z07:00|\\.000|\\.999/g, t => {",
244
+ "switch(t){",
245
+ 'case "2006": return d.getFullYear();',
246
+ 'case "06": return String(d.getFullYear()).slice(-2);',
247
+ 'case "Monday": return DAYS[d.getDay()];',
248
+ 'case "Mon": return DAYS[d.getDay()].slice(0,3);',
249
+ 'case "January": return MONTHS[d.getMonth()];',
250
+ 'case "Jan": return MONTHS[d.getMonth()].slice(0,3);',
251
+ 'case "01": return pad(d.getMonth()+1);',
252
+ 'case "1": return String(d.getMonth()+1);',
253
+ 'case "02": return pad(d.getDate());',
254
+ 'case "2": return String(d.getDate());',
255
+ 'case "15": return pad(d.getHours());',
256
+ 'case "PM": return d.getHours()<12?"AM":"PM";',
257
+ 'case "3": return String(d.getHours()%12||12);',
258
+ 'case "04": return pad(d.getMinutes());',
259
+ 'case "4": return String(d.getMinutes());',
260
+ 'case "05": return pad(d.getSeconds());',
261
+ 'case "5": return String(d.getSeconds());',
262
+ 'case "Z07:00": { const o=-d.getTimezoneOffset(); return o===0?"Z":(o>0?"+":"-")+pad(Math.floor(Math.abs(o)/60))+":"+pad(Math.abs(o)%60); }',
263
+ 'case ".000": return "."+pad(d.getMilliseconds(),3);',
264
+ 'case ".999": return d.getMilliseconds()>0?"."+String(d.getMilliseconds()).replace(/0+$/,""):"";',
265
+ "default: return t;}});",
266
+ "};",
267
+ ].join(""),
268
+ );
269
+ if (this._usesTimeParse)
270
+ helpers.push(
271
+ 'var __timeParse = __timeParse || function(layout, value) { try { const d = new Date(value); if (isNaN(d.getTime())) throw 0; return [{_d:d}, null]; } catch { return [{_d:new Date(0)}, "parsing time: cannot parse"]; } };',
272
+ );
216
273
 
217
274
  if (helpers.length > 0) this.out.unshift(...helpers, "");
218
275
 
@@ -235,9 +292,10 @@ export class CodeGen {
235
292
 
236
293
  // Returns a source map JSON string for the last generate() call.
237
294
  // sources: string[] of source filenames (relative to the output file).
238
- getSourceMap(sources) {
295
+ // sourcesContent: string[] of original file contents (embedded for DevTools breakpoints).
296
+ getSourceMap(sources, sourcesContent) {
239
297
  const srcArray = Array.isArray(sources) ? sources : [sources];
240
- return buildSourceMap(srcArray, this._srcMappings);
298
+ return buildSourceMap(srcArray, this._srcMappings, sourcesContent);
241
299
  }
242
300
 
243
301
  // ── Type declarations ────────────────────────────────────────
@@ -248,13 +306,41 @@ export class CodeGen {
248
306
  } else if (decl.type.kind === "InterfaceType") {
249
307
  // Interfaces are compile-time only — no JS output needed.
250
308
  this.line(`// interface ${decl.name} (compile-time only)`);
309
+ } else if (methodDecls.length > 0) {
310
+ // Named non-struct type with methods — emit an ES6 wrapper class.
311
+ this.genNamedTypeClass(decl.name, methodDecls);
251
312
  } 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
313
  this.line(`// type ${decl.name} = ${this.typeComment(decl.type)}`);
255
314
  }
256
315
  }
257
316
 
317
+ genNamedTypeClass(name, methodDecls) {
318
+ const namedType = this.checker?.types.get(name);
319
+ const underlying = namedType?.underlying;
320
+ let field, ctorDefault;
321
+ if (underlying?.kind === "func") {
322
+ field = "_fn";
323
+ ctorDefault = "null";
324
+ } else if (underlying?.kind === "map") {
325
+ field = "_map";
326
+ ctorDefault = "{}";
327
+ } else {
328
+ field = "_items";
329
+ ctorDefault = "[]";
330
+ }
331
+ this.line(`class ${name} {`);
332
+ this.indented(() => {
333
+ this.line(
334
+ `constructor(${field} = ${ctorDefault}) { this.${field} = ${field}; }`,
335
+ );
336
+ for (const m of methodDecls) {
337
+ this.blank();
338
+ this.genMethod(m, field);
339
+ }
340
+ });
341
+ this.line("}");
342
+ }
343
+
258
344
  genStruct(name, structTypeAst, methodDecls) {
259
345
  const fields = [];
260
346
  if (this.checker) {
@@ -317,23 +403,45 @@ export class CodeGen {
317
403
  this.line("}");
318
404
  }
319
405
 
320
- genMethod(decl) {
406
+ genMethod(decl, recvField = null) {
321
407
  const params = decl.params.map((p) => p.name).join(", ");
322
408
  const asyncPrefix = decl.async ? "async " : "";
323
409
  this.line(`${asyncPrefix}${decl.name}(${params}) {`);
324
410
  const prevBoxed = this._boxedVars;
325
411
  this._boxedVars = new Set();
326
412
  this._scanAddressTaken(decl.body);
413
+ const prevUnwrapped = this._unwrappedRecv;
327
414
  this.indented(() => {
328
415
  if (decl.recvName && decl.recvName !== "_") {
329
- this.line(`const ${decl.recvName} = this;`);
416
+ if (recvField) {
417
+ this.line(`const ${decl.recvName} = this.${recvField};`);
418
+ this._unwrappedRecv = decl.recvName;
419
+ } else {
420
+ this.line(`const ${decl.recvName} = this;`);
421
+ }
330
422
  }
331
423
  this._withNamedReturns(decl, () => this._genBody(decl.body));
332
424
  });
425
+ this._unwrappedRecv = prevUnwrapped;
333
426
  this._boxedVars = prevBoxed;
334
427
  this.line("}");
335
428
  }
336
429
 
430
+ // Returns the wrapper field name ("_fn", "_items", "_map") if `type` is a named
431
+ // non-struct type emitted as a wrapper class, or null otherwise.
432
+ // Pass `expr` so we can skip unwrapping when the expression is the method receiver
433
+ // (which was already unwrapped to `this.<field>` at the top of the method body).
434
+ _namedWrapperField(type, expr = null) {
435
+ if (type?.kind !== "named") return null;
436
+ if (!this.namedWrapperNames.has(type.name)) return null;
437
+ if (expr?.kind === "Ident" && expr.name === this._unwrappedRecv)
438
+ return null;
439
+ const u = type.underlying;
440
+ if (u?.kind === "func") return "_fn";
441
+ if (u?.kind === "map") return "_map";
442
+ return "_items";
443
+ }
444
+
337
445
  // ── Function declarations ────────────────────────────────────
338
446
 
339
447
  genFuncDecl(decl, nameOverride) {
@@ -471,7 +579,10 @@ export class CodeGen {
471
579
  }
472
580
  } else {
473
581
  const zero = spec.type ? this.zeroValueForTypeNode(spec.type) : "null";
474
- for (const name of spec.names) this.line(`let ${name} = ${zero};`);
582
+ for (const name of spec.names) {
583
+ const val = this._boxedVars.has(name) ? `{ value: ${zero} }` : zero;
584
+ this.line(`let ${name} = ${val};`);
585
+ }
475
586
  }
476
587
  }
477
588
  }
package/src/compiler.js CHANGED
@@ -170,7 +170,8 @@ export function compileFiles(files, options = {}) {
170
170
  if (options.sourceMap) {
171
171
  const outputDir = options.outputDir ?? fromDir;
172
172
  const sourceFiles = files.map((f) => relative(outputDir, f));
173
- const map = codegen.getSourceMap(sourceFiles);
173
+ const sourcesContent = files.map((f) => readFileSync(f, "utf8"));
174
+ const map = codegen.getSourceMap(sourceFiles, sourcesContent);
174
175
  const b64 = Buffer.from(map).toString("base64");
175
176
  js += `\n//# sourceMappingURL=data:application/json;base64,${b64}`;
176
177
  }
package/src/dev-server.js CHANGED
@@ -10,6 +10,7 @@ const MIME = {
10
10
  ".js": "application/javascript; charset=utf-8",
11
11
  ".css": "text/css; charset=utf-8",
12
12
  ".json": "application/json; charset=utf-8",
13
+ ".go": "text/plain; charset=utf-8",
13
14
  ".svg": "image/svg+xml",
14
15
  ".png": "image/png",
15
16
  ".jpg": "image/jpeg",
package/src/index.js CHANGED
@@ -232,7 +232,7 @@ function runCompile() {
232
232
  if (sourceMap) {
233
233
  const outputDir = outputFile ? dirname(resolve(outputFile)) : resolve(".");
234
234
  const srcPath = relative(outputDir, inputPath);
235
- const map = cg.getSourceMap(srcPath);
235
+ const map = cg.getSourceMap(srcPath, [source]);
236
236
  const b64 = Buffer.from(map).toString("base64");
237
237
  js += `\n//# sourceMappingURL=data:application/json;base64,${b64}`;
238
238
  }
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 ||
package/src/resolver.js CHANGED
@@ -25,11 +25,39 @@ function findNodeModules(startDir) {
25
25
  }
26
26
  }
27
27
 
28
+ // Built-in stdlib package paths that are always available as globals — no resolution needed.
29
+ const BUILTIN_PACKAGES = new Set([
30
+ "fmt",
31
+ "strings",
32
+ "bytes",
33
+ "strconv",
34
+ "sort",
35
+ "math",
36
+ "math/rand",
37
+ "errors",
38
+ "time",
39
+ "unicode",
40
+ "unicode/utf8",
41
+ "os",
42
+ "slices",
43
+ "html",
44
+ "io",
45
+ "gom",
46
+ "maps",
47
+ "regexp",
48
+ "path",
49
+ "path/filepath",
50
+ ]);
51
+
28
52
  // Returns true for local relative paths (start with ./ or ../)
29
53
  export function isLocalPath(importPath) {
30
54
  return importPath.startsWith("./") || importPath.startsWith("../");
31
55
  }
32
56
 
57
+ export function isBuiltinPackage(importPath) {
58
+ return BUILTIN_PACKAGES.has(importPath);
59
+ }
60
+
33
61
  // Resolve a local relative import path to an absolute directory of .go files.
34
62
  // Returns the absolute dir path, or null if it doesn't contain any .go files.
35
63
  export function resolveGwDir(importPath, fromFile) {
@@ -85,6 +113,7 @@ export function resolveAll(imports, fromFile, parseDts) {
85
113
  for (const { path } of imp.imports) {
86
114
  if (path.startsWith("js:")) continue; // handled separately
87
115
  if (isLocalPath(path)) continue; // local .go packages — handled by compiler
116
+ if (isBuiltinPackage(path)) continue; // built-in stdlib namespaces — always available
88
117
 
89
118
  if (resolved.has(path)) continue;
90
119
 
@@ -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);