graph-indexer 3.0.0 → 3.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 (60) hide show
  1. package/CHANGELOG.md +456 -0
  2. package/README.md +72 -15
  3. package/SECURITY.md +21 -4
  4. package/bin/graph-indexer.mjs +46 -5
  5. package/integrations/claude-code/.claude-plugin/plugin.json +3 -2
  6. package/integrations/claude-code/agents/code-structure.md +0 -3
  7. package/integrations/claude-code/skills/setup/SKILL.md +26 -0
  8. package/integrations/claude-code-local/.claude-plugin/plugin.json +9 -0
  9. package/integrations/{claude-code → claude-code-local}/.mcp.json +1 -1
  10. package/integrations/claude-code-local/README.md +10 -0
  11. package/integrations/claude-code-local/agents/code-structure.md +22 -0
  12. package/integrations/{claude-code → claude-code-local}/hooks/gi-hook.mjs +12 -6
  13. package/integrations/opencode/graph-indexer.js +13 -8
  14. package/package.json +2 -2
  15. package/src/cli/helper.mjs +15 -1
  16. package/src/cli/hook.mjs +140 -48
  17. package/src/cli/init.mjs +49 -17
  18. package/src/cli/resident.mjs +15 -5
  19. package/src/index/discover.mjs +3 -5
  20. package/src/index/indexer.mjs +77 -41
  21. package/src/index/modules.mjs +82 -7
  22. package/src/index/parse-worker.mjs +12 -0
  23. package/src/index/parsepool.mjs +73 -0
  24. package/src/index/resolver.mjs +371 -25
  25. package/src/mcp/server.mjs +16 -12
  26. package/src/mcp/tools.mjs +419 -26
  27. package/src/parse/arity.mjs +41 -3
  28. package/src/parse/extract.mjs +277 -46
  29. package/src/parse/lang/ecma.mjs +2 -0
  30. package/src/parse/lang/go.mjs +2 -0
  31. package/src/parse/lang/jvm.mjs +33 -0
  32. package/src/parse/lang/markdown.mjs +92 -0
  33. package/src/parse/lang/python.mjs +43 -0
  34. package/src/parse/lang/rust.mjs +215 -15
  35. package/src/parse/lang/sfc.mjs +125 -0
  36. package/src/parse/languages.mjs +9 -2
  37. package/src/query/check.mjs +55 -17
  38. package/src/query/facts.mjs +3 -2
  39. package/src/query/injection-spring.mjs +405 -0
  40. package/src/query/injection.mjs +173 -0
  41. package/src/query/intel.mjs +87 -22
  42. package/src/query/issue.mjs +156 -0
  43. package/src/query/orm.mjs +286 -0
  44. package/src/query/read.mjs +6 -1
  45. package/src/query/report.mjs +53 -0
  46. package/src/query/routes.mjs +142 -0
  47. package/src/query/showcase.mjs +59 -0
  48. package/src/query/testcmd.mjs +25 -14
  49. package/src/query/textsearch.mjs +29 -11
  50. package/src/search/search.mjs +85 -17
  51. package/src/search/semantic.mjs +201 -0
  52. package/src/search/vectors.mjs +122 -0
  53. package/src/store/db.mjs +22 -4
  54. package/src/util/git.mjs +30 -0
  55. package/src/util/paths.mjs +40 -1
  56. package/src/util/regex.mjs +2 -0
  57. package/vendor/grammars/README.md +47 -0
  58. package/vendor/grammars/kotlin.wasm.gz +0 -0
  59. package/vendor/grammars/manifest.json +4 -4
  60. /package/integrations/{claude-code → claude-code-local}/hooks/hooks.json +0 -0
@@ -15,15 +15,23 @@
15
15
  */
16
16
  import path from 'node:path';
17
17
  import { normalizeType } from '../parse/extract.mjs';
18
- import { fits, sigArity } from '../parse/arity.mjs';
18
+ import { fits, sigArity, sigParamTypes, simpleParamType } from '../parse/arity.mjs';
19
+ import { escapeRegExp } from '../util/regex.mjs';
19
20
 
20
21
  const TYPE_KINDS = new Set(['class', 'interface', 'struct', 'enum', 'trait', 'type', 'object', 'module', 'impl']);
22
+ // generic wrappers whose members are those of their argument (`InstanceType<typeof Repl>`): a call on
23
+ // one is not on a type the index lacks, so it keeps the by-name fallback
24
+ const TYPE_WRAPPERS = new Set(['ReturnType', 'InstanceType', 'Awaited', 'Partial', 'Required', 'Readonly', 'NonNullable', 'Pick', 'Omit', 'Mocked', 'MockedObject', 'Type']);
25
+ // element accesses one descriptor may stack (`xs[i][j]`…): a field assigned an element of itself
26
+ // (`self.impl = self.impl[0]`) would add one on every lookup, with no end
27
+ const MAX_ELEM = 8;
21
28
  const VALUE_TYPED_KINDS = new Set(['variable', 'constant', 'field', 'property']);
22
29
  const CALLABLE_KINDS = new Set(['function', 'method', 'constructor', 'macro']);
23
30
  const MEMBER_HOLDERS = new Set(['class', 'interface', 'struct', 'enum', 'trait', 'object', 'impl', 'module']);
24
31
  const MEMBER_KINDS = new Set(['field', 'property', 'method', 'constructor']);
25
32
  const PACKAGE_LANGS = new Set(['java', 'kotlin', 'scala', 'csharp', 'php']);
26
33
  const DIR_PACKAGE_LANGS = new Set(['go']);
34
+ const SINGLE_TYPE_IMPORTS = new Set(['java', 'kotlin', 'scala']);
27
35
  const MAX_GLOBAL_CANDIDATES = 12;
28
36
 
29
37
  /**
@@ -100,7 +108,7 @@ function paramType(param) {
100
108
  */
101
109
  export function callbackParamTypeText(sig, name, i, j) {
102
110
  if (!sig) return null;
103
- const m = new RegExp(`(?:^|[^\\w$])${name.replace(/[$]/g, '\\$')}\\s*(?:<[^()]*>)?\\s*\\(`).exec(sig);
111
+ const m = new RegExp(`(?:^|[^\\w$])${escapeRegExp(name)}\\s*(?:<[^()]*>)?\\s*\\(`).exec(sig);
104
112
  const list = m ? bracketed(sig, m.index + m[0].length - 1) : null;
105
113
  let type = list ? paramType(splitTopLevel(list.inner)[i] ?? '') : null;
106
114
  if (!type) return null;
@@ -216,11 +224,15 @@ export class SymbolTable {
216
224
  file(id) { return this.files.get(id); }
217
225
  }
218
226
 
227
+ /** A resolved type as membersOf takes it: {id, name, fileId}, and `allOf` for a value of several traits. */
228
+ const asType = (t) => (t.allOf ? t : { id: t.id ?? null, name: t.name, fileId: t.fileId ?? null });
229
+
219
230
  export class Resolver {
220
- constructor(table, specsById, { sigOf = null } = {}) {
231
+ constructor(table, specsById, { sigOf = null, modules = null } = {}) {
221
232
  this.t = table;
222
233
  this.specs = specsById; // lang id -> spec (implicitThis etc.)
223
234
  this.sigOf = sigOf; // symbol id -> signature text (read on demand: callback parameter types)
235
+ this.modules = modules; // module resolver (Rust paths: `crate::a::B`, `super::f`, `other_crate::x`)
224
236
  this.memo = new Map();
225
237
  }
226
238
 
@@ -261,22 +273,39 @@ export class Resolver {
261
273
  /** Members named `name` of a type (by id and/or name), including inherited ones. */
262
274
  membersOf(type, name, depth = 0, seen = new Set()) {
263
275
  if (!type || depth > 6) return [];
276
+ // a value bound by several traits (`W: AsyncWrite + AsyncSeek`): the member of each that has it
277
+ if (type.allOf) return [...new Set(type.allOf.flatMap(t => this.membersOf(t, name, depth + 1, new Set(seen))))];
264
278
  const key = (type.id ?? '') + ':' + type.name;
265
279
  if (seen.has(key)) return [];
266
280
  seen.add(key);
267
281
  const out = [];
268
282
  if (type.id != null) for (const id of this.t.byParent.get(type.id)?.get(name) ?? []) out.push(id);
283
+ const ownSym = type.id != null ? this.t.sym(type.id) : null;
269
284
  for (const id of this.t.byOwner.get(type.name) ?? []) {
270
285
  const s = this.t.sym(id);
271
286
  if (s.name !== name) continue;
287
+ // a blanket impl's method (`impl<M: Matcher> Matcher for &M`) is the trait's own, called through its declaration
288
+ if (ownSym?.kind === 'trait' && s.bases?.includes(type.name) && out.length) continue;
272
289
  // owner-declared members must live near the type (same package/dir/file family)
273
290
  if (type.fileId != null) {
274
291
  const a = this.t.file(s.fileId), b = this.t.file(type.fileId);
275
- if (a && b && a.lang === b.lang && (a.dir === b.dir || a.package === b.package || a.lang === 'cpp' || a.lang === 'c' || a.lang === 'rust' || a.lang === 'javascript' || a.lang === 'typescript')) out.push(id);
292
+ if (a && b && a.lang === 'rust' && b.lang === 'rust') { if (this.#implFor(s, type, a, b)) out.push(id); }
293
+ else if (a && b && a.lang === b.lang && (a.dir === b.dir || a.package === b.package || a.lang === 'cpp' || a.lang === 'c' || a.lang === 'javascript' || a.lang === 'typescript')) out.push(id);
276
294
  } else out.push(id);
277
295
  }
278
- if (out.length) return out;
279
296
  const typeSym = type.id != null ? this.t.sym(type.id) : null;
297
+ if (out.length) {
298
+ // Python: `self.x = …` hoisted as a field of a class whose base declares `x` as a property
299
+ // runs the base's setter — the attribute is the base's property, not a new field
300
+ if (typeSym && out.every(id => this.t.sym(id)?.kind === 'field') && this.t.file(typeSym.fileId)?.lang === 'python') {
301
+ for (const base of typeSym.bases ?? []) {
302
+ const bt = this.resolveTypeName(base, typeSym.fileId);
303
+ const inherited = bt ? this.membersOf({ id: bt.id, name: bt.name, fileId: bt.fileId }, name, depth + 1, new Set(seen)) : [];
304
+ if (inherited.some(id => this.t.sym(id)?.kind === 'method')) return inherited;
305
+ }
306
+ }
307
+ return out;
308
+ }
280
309
  for (const base of typeSym?.bases ?? []) {
281
310
  const bt = this.resolveTypeName(base, typeSym.fileId);
282
311
  if (bt) {
@@ -290,6 +319,20 @@ export class Resolver {
290
319
  return out;
291
320
  }
292
321
 
322
+ /**
323
+ * Is a Rust member declared in an `impl Owner` block a member of this type? Several crates of a
324
+ * workspace may each have a type of that name (ripgrep has four `RegexMatcher`s): the impl's
325
+ * owner is the type its own file sees under that name, or, when that file cannot tell, a type of
326
+ * the same Cargo package (an inherent impl lives in the type's crate).
327
+ */
328
+ #implFor(s, type, a, b) {
329
+ if (type.id == null || s.fileId === type.fileId) return true;
330
+ const seen = this.resolveTypeName(type.name, s.fileId);
331
+ if (seen) return seen.id === type.id;
332
+ const pa = this.modules?.rustPackageOf?.(a.path), pb = this.modules?.rustPackageOf?.(b.path);
333
+ return pa == null || pb == null || pa === pb;
334
+ }
335
+
293
336
  /**
294
337
  * Method resolution order of a type: C3 linearization over its resolved bases (Python's MRO; for
295
338
  * single inheritance it is the chain of supertypes). Unresolved bases are left out; an inconsistent
@@ -376,8 +419,13 @@ export class Resolver {
376
419
  */
377
420
  typeFromString(str, fileId, elem = 0) {
378
421
  if (!str) return null;
379
- if (str.startsWith('call:') || str.startsWith('cb:') || str.includes('#')) return this.resolveRecvType(str + '[]'.repeat(elem), fileId, null);
422
+ if (str.startsWith('call:') || str.startsWith('cb:') || str.includes('#')) {
423
+ const desc = str + '[]'.repeat(elem);
424
+ return desc.split('[]').length - 1 > MAX_ELEM ? null : this.resolveRecvType(desc, fileId, null);
425
+ }
380
426
  let s = str;
427
+ // a tuple (`(A,B)`, Rust): its elements are reached by position (`#0`), see resolveRecvType
428
+ if (s.startsWith('(') && s.endsWith(')')) return elem ? null : { tupleOf: s.slice(1, -1).split(','), fileId };
381
429
  for (let i = 0; i < elem; i++) {
382
430
  if (!s.endsWith('[]') && !s.endsWith('{}')) {
383
431
  // an element of a collection class (`class Registry extends Map<string, Module>`)
@@ -387,6 +435,13 @@ export class Resolver {
387
435
  s = s.slice(0, -2);
388
436
  }
389
437
  if (s === '!') return EXTERNAL;
438
+ if (s.includes('+') && !s.endsWith('[]') && !s.endsWith('{}')) {
439
+ // `A+B` (Rust bounds, see normalizeType): the traits of the repository among them
440
+ const ts = s.split('+').map(x => this.typeFromString(x, fileId)).filter(Boolean);
441
+ const own = ts.filter(x => x !== EXTERNAL && x.id != null);
442
+ if (!own.length) return ts.length ? EXTERNAL : null;
443
+ return own.length === 1 ? own[0] : { id: null, name: s, fileId, allOf: own };
444
+ }
390
445
  if (s.endsWith('[]')) return { arrayOf: s.slice(0, -2), fileId }; // a collection value
391
446
  if (s.endsWith('{}')) return { mapOf: s.slice(0, -2), fileId }; // a map value
392
447
  if (this.#qualifiedType(s, fileId) === EXTERNAL || this.#nestedType(s, fileId) === EXTERNAL) return EXTERNAL;
@@ -425,7 +480,7 @@ export class Resolver {
425
480
  if (TYPE_KINDS.has(s.kind)) cur = head.elem ? null : s; // constructor call
426
481
  else if (s.type) cur = this.typeFromString(s.type, s.fileId, head.elem);
427
482
  }
428
- if ((cur?.arrayOf != null || cur?.mapOf != null) && parts.length === 1) cur = EXTERNAL;
483
+ if ((cur?.arrayOf != null || cur?.mapOf != null || cur?.tupleOf != null) && parts.length === 1) cur = EXTERNAL;
429
484
  } else if (head.name.startsWith('cb:')) {
430
485
  cur = this.#callbackParam(head.name, fileId, srcId, head.elem);
431
486
  } else if (!head.elem) {
@@ -449,6 +504,20 @@ export class Resolver {
449
504
  for (let i = 1; i < parts.length && cur && cur !== EXTERNAL; i++) {
450
505
  const { name, elem } = parts[i];
451
506
  if (!name) { cur = elem ? this.#elementOf(cur, elem) : cur; continue; } // `T#[]`: an element of T itself
507
+ // `~m`: member m of the type if it has one, else the same value (`x.clone()`, `opt.unwrap()`)
508
+ if (name.startsWith('~')) {
509
+ const real = name.slice(1);
510
+ const own = cur.arrayOf == null && cur.mapOf == null && cur.tupleOf == null ? this.membersOf(asType(cur), real) : [];
511
+ if (!own.length) { cur = elem ? this.#elementOf(cur, elem) : cur; continue; }
512
+ parts[i] = { name: real, elem };
513
+ i--;
514
+ continue;
515
+ }
516
+ if (cur.tupleOf != null) {
517
+ const el = /^\d+$/.test(name) ? cur.tupleOf[Number(name)] : null;
518
+ cur = el && el !== '?' ? this.typeFromString(el, cur.fileId, elem) : EXTERNAL;
519
+ continue;
520
+ }
452
521
  if (cur.arrayOf != null) {
453
522
  // `list.get(0)` / `xs.first()` reach the element; other members are the language's
454
523
  cur = spec?.elementMethods?.has(name) ? this.typeFromString(cur.arrayOf, cur.fileId, elem) : EXTERNAL;
@@ -460,7 +529,7 @@ export class Resolver {
460
529
  : spec?.mapValueMethods?.has(name) ? this.typeFromString(cur.mapOf + '[]', cur.fileId, elem) : EXTERNAL;
461
530
  continue;
462
531
  }
463
- const mem = this.membersOf({ id: cur.id ?? null, name: cur.name, fileId: cur.fileId }, name);
532
+ const mem = this.membersOf(asType(cur), name);
464
533
  if (!mem.length && cur.id != null && spec?.qualifiedTypes) {
465
534
  // Go: an embedded field is named after its type (`m.MatchRegexp` in a struct embedding MatchRegexp)
466
535
  const os = this.t.sym(cur.id);
@@ -477,6 +546,8 @@ export class Resolver {
477
546
  let next = null;
478
547
  for (const id of mem) {
479
548
  const m = this.t.sym(id);
549
+ // a nested type reached through its outer one (`Token.TokenType.valueOf(…)`)
550
+ if (TYPE_KINDS.has(m.kind) && !elem) { next = { id: m.id, name: m.name, fileId: m.fileId }; break; }
480
551
  if (m.type) { next = this.typeFromString(m.type, m.fileId, elem); if (next) break; }
481
552
  }
482
553
  if (!next) {
@@ -492,7 +563,7 @@ export class Resolver {
492
563
  }
493
564
  cur = next;
494
565
  }
495
- const res = cur === EXTERNAL || cur?.arrayOf != null || cur?.mapOf != null ? EXTERNAL : cur ? { id: cur.id ?? null, name: cur.name, fileId: cur.fileId ?? null } : null;
566
+ const res = cur === EXTERNAL || cur?.arrayOf != null || cur?.mapOf != null || cur?.tupleOf != null ? EXTERNAL : cur ? asType(cur) : null;
496
567
  this.memo.set(k, res);
497
568
  return res;
498
569
  }
@@ -555,9 +626,11 @@ export class Resolver {
555
626
  if (targetFileId == null || depth > 6 || seen.has(targetFileId)) return [];
556
627
  seen.add(targetFileId);
557
628
  const out = [];
629
+ // a Rust or Go method (declared in an `impl` block or with a receiver) is no item of its module
630
+ const lang = this.t.file(targetFileId)?.lang, typedOwners = lang === 'rust' || lang === 'go';
558
631
  for (const id of this.t.fileSyms.get(targetFileId) ?? []) {
559
632
  const s = this.t.sym(id);
560
- if (s.name === name && s.parentId == null) out.push(id);
633
+ if (s.name === name && s.parentId == null && !(typedOwners && s.owner && (MEMBER_KINDS.has(s.kind) || s.kind === 'type'))) out.push(id);
561
634
  }
562
635
  if (out.length) return out;
563
636
  // in Python a later import rebinds the name (`from a import *` then `from b import *`)
@@ -567,12 +640,41 @@ export class Resolver {
567
640
  const passes = imp.reexport === name || imp.reexport === '*' || imp.local === name || (imp.wildcard && imp.imported === '*');
568
641
  if (!passes || imp.targetFileId == null) continue;
569
642
  const inner = imp.reexport === name && imp.imported && imp.imported !== '*' ? imp.imported : (imp.local === name && imp.imported && imp.imported !== '*' ? imp.imported : name);
570
- const r = this.exportedFrom(imp.targetFileId, inner, depth + 1, seen);
643
+ // `export { default as X } from './x'`: x's default export, whatever it is called there
644
+ const r = inner === 'default' && name !== 'default' ? this.#defaultExport(imp.targetFileId, name) : this.exportedFrom(imp.targetFileId, inner, depth + 1, seen);
571
645
  if (r.length) return r;
572
646
  }
573
647
  return out;
574
648
  }
575
649
 
650
+ /** The module a Python package or module file exposes as `seg`: a submodule file, or a module it imports. */
651
+ #submodule(fileId, seg) {
652
+ const f = this.t.file(fileId);
653
+ if (f?.path.endsWith('/__init__.py')) {
654
+ const dir = f.path.slice(0, -'/__init__.py'.length);
655
+ const sub = this.t.fileByPath.get(`${dir}/${seg}.py`) ?? this.t.fileByPath.get(`${dir}/${seg}/__init__.py`);
656
+ if (sub) return sub.id;
657
+ }
658
+ return this.#moduleFileOf(fileId, seg);
659
+ }
660
+
661
+ /** Whether a module binds `name` through an import of a module outside the repository, following re-exports. */
662
+ #reexportsExternal(targetFileId, name, depth = 0) {
663
+ if (targetFileId == null || depth > 6) return false;
664
+ for (const imp of this.t.imports.get(targetFileId) ?? []) {
665
+ // `export * from './icons'` where ./icons re-exports `name` from a package
666
+ if (imp.reexport === '*' && imp.targetFileId != null && imp.local == null) {
667
+ if (this.#reexportsExternal(imp.targetFileId, name, depth + 1)) return true;
668
+ continue;
669
+ }
670
+ if (imp.wildcard || !(imp.local === name || imp.reexport === name)) continue;
671
+ if (imp.targetFileId == null && !imp.targetDir) return true;
672
+ const inner = imp.imported && imp.imported !== '*' && imp.imported !== 'default' ? imp.imported : name;
673
+ if (imp.targetFileId != null && !this.exportedFrom(imp.targetFileId, inner).length) return this.#reexportsExternal(imp.targetFileId, inner, depth + 1);
674
+ }
675
+ return false;
676
+ }
677
+
576
678
  #symbolsInDir(dir, name, fileId) {
577
679
  const out = [];
578
680
  for (const fid of this.t.dirFiles.get(dir) ?? []) {
@@ -584,6 +686,24 @@ export class Resolver {
584
686
  return out;
585
687
  }
586
688
 
689
+ /**
690
+ * `import java.io.UncheckedIOException;`: a single-type import (Java, Kotlin, Scala) from a
691
+ * package no file of the repository is in, nor an enclosing one — the type is the library's,
692
+ * not a same-name class of the repository.
693
+ */
694
+ #importsLibraryType(file, imp) {
695
+ if (!SINGLE_TYPE_IMPORTS.has(file.lang) || imp.wildcard || !imp.imported || imp.imported === '*' || !imp.source) return false;
696
+ const segs = imp.source.split('.');
697
+ for (let n = segs.length; n > 0; n--) if (this.t.packageFiles.has(segs.slice(0, n).join('.'))) return false;
698
+ // nor a package that encloses some of the repository's (`import org.acme.X` with `org.acme.core` here)
699
+ if (this.pkgPrefixes?.size !== this.t.packageFiles.size) {
700
+ const set = new Set();
701
+ for (const p of this.t.packageFiles.keys()) { const s = p.split('.'); for (let n = 1; n < s.length; n++) set.add(s.slice(0, n).join('.')); }
702
+ this.pkgPrefixes = { size: this.t.packageFiles.size, set };
703
+ }
704
+ return !this.pkgPrefixes.set.has(imp.source);
705
+ }
706
+
587
707
  #symbolsInPackage(pkg, name) {
588
708
  const out = [];
589
709
  for (const fid of this.t.packageFiles.get(pkg) ?? []) {
@@ -604,12 +724,19 @@ export class Resolver {
604
724
  */
605
725
  resolve(ref) {
606
726
  this.argc = ref.argc ?? null;
727
+ try { this.argTypes = ref.arg_types ? JSON.parse(ref.arg_types) : (ref.argTypes ?? null); } catch { this.argTypes = null; }
607
728
  const fileId = ref.file_id;
608
729
  const srcId = ref.src_id ?? null;
609
730
  const recv = ref.recv || '';
610
731
  const kind = ref.kind;
611
732
  const name = ref.name;
612
- if (recv) return this.#resolveMember(name, kind, recv, ref.recv_type, fileId, srcId);
733
+ if (recv) {
734
+ const r = this.#resolveMember(name, kind, recv, ref.recv_type, fileId, srcId);
735
+ // `f32::to_bits(self)` inside the `to_bits` of `impl Float for f32` is the primitive's own
736
+ // method (inherent methods come first), never a call of itself
737
+ if (r?.id != null && r.id === srcId && /^[a-z]\w*$/.test(recv) && this.specs[this.t.file(fileId)?.lang]?.isPrimitiveType?.(recv)) return { id: null, conf: 0, ncand: 0, external: true };
738
+ return r;
739
+ }
613
740
  return this.#resolveName(name, kind, fileId, srcId) ?? { id: null, conf: 0, ncand: 0 };
614
741
  }
615
742
 
@@ -635,6 +762,32 @@ export class Resolver {
635
762
  return fits({ n: Math.max(0, this.argc), open: this.argc < 0 }, sigArity(this.sigOf(s.id), s.name));
636
763
  }
637
764
 
765
+ /**
766
+ * Overloads whose parameter types the call's argument types fit, best fits first kept: an
767
+ * exact type beats a widening (`int` → `long`), boxing or a type parameter; a primitive or
768
+ * String argument never fits a different primitive or String parameter (`'-'` is no String).
769
+ */
770
+ #byArgTypes(cands) {
771
+ const scored = [];
772
+ for (const s of cands) {
773
+ const params = sigParamTypes(this.sigOf(s.id), s.name);
774
+ if (!params) return cands;
775
+ let score = 0, ok = true;
776
+ this.argTypes.forEach((a, i) => {
777
+ if (!ok || a === '?') return;
778
+ let p = params[Math.min(i, params.length - 1)];
779
+ if (p == null) { ok = false; return; }
780
+ if (/\.\.\.$/.test(p)) p = i >= params.length - 1 && !(this.argTypes.length === params.length && a.endsWith('[]')) ? p.slice(0, -3) : p.slice(0, -3) + '[]';
781
+ const fit = argFits(a, simpleParamType(p));
782
+ if (fit < 0) ok = false; else score += fit;
783
+ });
784
+ if (ok) scored.push({ s, score });
785
+ }
786
+ if (!scored.length) return cands;
787
+ const best = Math.max(...scored.map(x => x.score));
788
+ return scored.filter(x => x.score === best).map(x => x.s);
789
+ }
790
+
638
791
  #compatible(s, kind) {
639
792
  if (kind === 'value' && CALLABLE_KINDS.has(s.kind) && this.specs[this.t.file(s.fileId)?.lang]?.localValues) return false; // Java: methods are no values
640
793
  if (kind === 'type' || kind === 'inherit') return TYPE_KINDS.has(s.kind);
@@ -646,8 +799,11 @@ export class Resolver {
646
799
  }
647
800
 
648
801
  #pick(ids, kind, conf, fileId) {
649
- const cands = ids.map(id => this.t.sym(id)).filter(s => s && this.#compatible(s, kind));
802
+ let cands = ids.map(id => this.t.sym(id)).filter(s => s && this.#compatible(s, kind));
650
803
  if (!cands.length) return null;
804
+ // Rust: a method of a trait implemented for the type and for references to it (`impl
805
+ // IntoIterator for Map` and `for &'a Map`): a call on the value reaches the value's impl
806
+ if (cands.length > 1 && cands.some(s => s.byRef) && cands.some(s => !s.byRef) && cands.every(s => s.qname === cands[0].qname)) cands = cands.filter(s => !s.byRef);
651
807
  if (cands.length === 1) return { id: cands[0].id, conf, ncand: 1 };
652
808
  // overloads / redeclarations in one type or file: prefer the definition with a body (larger span)
653
809
  const sameQ = cands.every(s => s.qname === cands[0].qname);
@@ -659,6 +815,12 @@ export class Resolver {
659
815
  const fixed = fit.filter(s => sigArity(this.sigOf(s.id), s.name)?.max !== Infinity);
660
816
  if (fixed.length && fixed.length < fit.length) fit = fixed;
661
817
  if (fit.length === 1) return { id: fit[0].id, conf, ncand: 1 };
818
+ // several of that count: the one the arguments' types fit (`append('-')` is append(char))
819
+ if (fit.length > 1 && this.argTypes) {
820
+ const typed = this.#byArgTypes(fit);
821
+ if (typed.length === 1) return { id: typed[0].id, conf, ncand: 1 };
822
+ if (typed.length && typed.length < fit.length) fit = typed;
823
+ }
662
824
  if (fit.length > 1 && fit.length < cands.length) return this.#pick(fit.map(s => s.id), kind, conf, fileId);
663
825
  }
664
826
  const scored = cands.map(s => ({ s, score: this.#proximity(s, fileId) + (s.endLine - s.startLine) / 1e4 }));
@@ -700,7 +862,8 @@ export class Resolver {
700
862
  if (s.name === name && kind === 'call' && CALLABLE_KINDS.has(s.kind) && (s.kind !== 'method' || spec?.implicitThis) && this.#reaches(s)) return { id: s.id, conf: 0.9, ncand: 1 };
701
863
  }
702
864
  // 2. implicit this: members of the enclosing type (Java/Kotlin/C#/Scala/C++/Ruby)
703
- if (spec?.implicitThis && srcId != null && kind !== 'type' && kind !== 'inherit') {
865
+ // (`new AVLNode<>(…)` inside AVLNode names the class, not one of its constructors)
866
+ if (spec?.implicitThis && srcId != null && kind !== 'type' && kind !== 'inherit' && kind !== 'new') {
704
867
  const type = this.enclosingType(srcId);
705
868
  if (type) {
706
869
  const r = this.#pick(this.#overloadsReached(type, this.membersOf(type, name), name), kind, 0.9, fileId);
@@ -708,7 +871,9 @@ export class Resolver {
708
871
  }
709
872
  }
710
873
  // 3. same file, top-level
711
- const local = (this.t.fileSyms.get(fileId) ?? []).filter(id => { const s = this.t.sym(id); return s.name === name && (s.parentId == null || TYPE_KINDS.has(s.kind)); });
874
+ // a member declared outside its type's body (a Rust `impl` block, a Go receiver, a C++
875
+ // `T::m`) is reached through a receiver or `self`, never by its bare name
876
+ const local = (this.t.fileSyms.get(fileId) ?? []).filter(id => { const s = this.t.sym(id); return s.name === name && this.#fileLevel(s); });
712
877
  if (local.length) {
713
878
  const r = this.#pick(local, kind, 0.9, fileId);
714
879
  if (r) return r;
@@ -719,8 +884,14 @@ export class Resolver {
719
884
  const target = imp.imported && imp.imported !== '*' && imp.imported !== 'default' ? imp.imported : name;
720
885
  let ids = [];
721
886
  if (imp.targetFileId != null) {
722
- ids = this.exportedFrom(imp.targetFileId, target);
723
- if (!ids.length && imp.imported === 'default') ids = this.#defaultExport(imp.targetFileId, name);
887
+ if (imp.imported === 'default') {
888
+ // a default import names the module's default export, whatever the importer calls it:
889
+ // a definition of that name in the module itself, else its default export; only then
890
+ // what the module imports under that name (it may import another `name` of its own)
891
+ ids = (this.t.fileSyms.get(imp.targetFileId) ?? []).filter(id => { const s = this.t.sym(id); return s.name === name && s.parentId == null; });
892
+ if (!ids.length) ids = this.#defaultExport(imp.targetFileId, name);
893
+ if (!ids.length) ids = this.exportedFrom(imp.targetFileId, target);
894
+ } else ids = this.exportedFrom(imp.targetFileId, target);
724
895
  }
725
896
  if (!ids.length && imp.targetDir) ids = this.#symbolsInDir(imp.targetDir, target, fileId);
726
897
  if (!ids.length && imp.source && PACKAGE_LANGS.has(file.lang)) ids = this.#symbolsInPackage(imp.source, target);
@@ -728,7 +899,10 @@ export class Resolver {
728
899
  const r = this.#pick(ids, kind, 0.95, fileId);
729
900
  if (r) return r;
730
901
  }
731
- if (imp.targetFileId == null && !imp.targetDir && !PACKAGE_LANGS.has(file.lang)) return { id: null, conf: 0, ncand: 0, external: true };
902
+ if (imp.targetFileId == null && !imp.targetDir && (!PACKAGE_LANGS.has(file.lang) || this.#importsLibraryType(file, imp))) return { id: null, conf: 0, ncand: 0, external: true };
903
+ // the module re-exports the name from a package outside the repository (`fastapi/__init__.py`:
904
+ // `from starlette.responses import Response`): not a same-name definition found globally
905
+ if (!ids.length && imp.targetFileId != null && this.#reexportsExternal(imp.targetFileId, target)) return { id: null, conf: 0, ncand: 0, external: true };
732
906
  }
733
907
  // 5. same package / directory
734
908
  if (PACKAGE_LANGS.has(file.lang) && file.package) {
@@ -765,22 +939,51 @@ export class Resolver {
765
939
  const r = ids.length ? this.#pick(ids, kind, 0.8, fileId) : null;
766
940
  if (r) return r;
767
941
  }
768
- // 8. global by name
942
+ // 8. global by name — not for a name Python code evaluates (a call, a value): one its module
943
+ // neither defines nor imports is a NameError, unless a star import of a module outside the
944
+ // index brought it. Types are still looked up (an annotation's name may be imported only
945
+ // under TYPE_CHECKING, and a type recorded for a receiver has lost its module: `routing.APIRouter`)
946
+ if (file.lang === 'python' && kind !== 'type' && kind !== 'inherit' && !(this.t.imports.get(fileId) ?? []).some(w => w.wildcard && w.targetFileId == null)) return null;
769
947
  return this.#global(name, kind, fileId);
770
948
  }
771
949
 
950
+ /**
951
+ * A definition its file's other code names directly: a top-level one, or a type nested in a type
952
+ * (Java's inner classes). Not a member declared outside its type's body (a Rust `impl` block, a Go
953
+ * receiver, a C++ `T::m`: reached through a receiver or `self`), not a type local to a function
954
+ * (seen from inside it, step 1), and not an associated type (`impl Visitor for V { type Value = …; }`).
955
+ */
956
+ #fileLevel(s) {
957
+ if (s.owner && (MEMBER_KINDS.has(s.kind) || (s.kind === 'type' && s.parentId == null))) return false;
958
+ if (s.parentId == null) return true;
959
+ if (!TYPE_KINDS.has(s.kind)) return false;
960
+ for (let p = this.t.sym(s.parentId), g = 0; p && g < 16; p = p.parentId != null ? this.t.sym(p.parentId) : null, g++) if (CALLABLE_KINDS.has(p.kind)) return false;
961
+ return true;
962
+ }
963
+
772
964
  #defaultExport(targetFileId, localName) {
773
965
  // `export default class Foo` / `module.exports = Foo` → the file's main exported symbol
774
966
  const ids = (this.t.fileSyms.get(targetFileId) ?? []).filter(id => { const s = this.t.sym(id); return s.parentId == null && s.exported; });
775
967
  const same = ids.filter(id => this.t.sym(id).name.toLowerCase() === localName.toLowerCase());
776
968
  if (same.length) return same;
969
+ // a Vue or Svelte file's default export is the component, named after the file whatever the importer calls it
970
+ const file = this.t.file(targetFileId);
971
+ if (file && (file.lang === 'vue' || file.lang === 'svelte')) {
972
+ const base = file.path.split('/').pop().replace(/\.[^.]+$/, '');
973
+ const stem = (base === 'index' ? file.path.split('/').slice(-2, -1)[0] ?? base : base).replace(/(^|[-_.])(\w)/g, (_, __, c) => c.toUpperCase());
974
+ const comp = ids.filter(id => this.t.sym(id).name === stem || this.t.sym(id).name === `${stem}Component`);
975
+ if (comp.length) return comp;
976
+ }
777
977
  return ids.length === 1 ? ids : [];
778
978
  }
779
979
 
780
980
  #global(name, kind, fileId) {
781
981
  const all = (this.t.byName.get(name) ?? []).filter(id => {
782
982
  const s = this.t.sym(id);
783
- return s && this.#compatible(s, kind) && (s.parentId == null || TYPE_KINDS.has(s.kind) || kind === 'call');
983
+ if (!s || !this.#compatible(s, kind) || !(this.#fileLevel(s) || kind === 'call')) return false;
984
+ // a function nested in a function is visible only inside it (found in step 1 there)
985
+ const parent = s.parentId != null ? this.t.sym(s.parentId) : null;
986
+ return !(parent && CALLABLE_KINDS.has(parent.kind) && CALLABLE_KINDS.has(s.kind));
784
987
  });
785
988
  const file = this.t.file(fileId);
786
989
  // never bind across unrelated languages (a TS call never resolves to a Python def)
@@ -788,10 +991,15 @@ export class Resolver {
788
991
  const f = this.t.file(this.t.sym(id).fileId);
789
992
  return f && (f.lang === file.lang || familyOf(f.lang) === familyOf(file.lang));
790
993
  });
994
+ // an ES module sees another module's top-level names only through an import (resolved
995
+ // above): a value read that reached here is a local the extractor did not scope (a lambda
996
+ // parameter, a block-scoped const), not that name. Calls are left alone (CommonJS patterns).
997
+ const isolated = (kind === 'value' || kind === 'read') && familyOf(file.lang) === 'ecma' && this.#isModule(fileId);
791
998
  // unqualified calls cannot target methods of unrelated classes in most languages
792
999
  const cands = sameFamily.filter(id => {
793
1000
  const s = this.t.sym(id);
794
1001
  if (s.kind === 'method' || s.kind === 'field' || s.kind === 'property') return false;
1002
+ if (isolated && s.fileId !== fileId && s.parentId == null && this.#isModule(s.fileId)) return false;
795
1003
  return true;
796
1004
  });
797
1005
  if (!cands.length) return null;
@@ -800,6 +1008,12 @@ export class Resolver {
800
1008
  return this.#pick(cands, kind, base, fileId);
801
1009
  }
802
1010
 
1011
+ /** A JS/TS file that imports or exports something: its top-level names are its own. */
1012
+ #isModule(fileId) {
1013
+ if ((this.t.imports.get(fileId) ?? []).length) return true;
1014
+ return (this.t.fileSyms.get(fileId) ?? []).some(id => { const s = this.t.sym(id); return s?.parentId == null && s?.exported; });
1015
+ }
1016
+
803
1017
  #resolveMember(name, kind, recv, recvType, fileId, srcId) {
804
1018
  const file = this.t.file(fileId);
805
1019
  const root = recv.split('.')[0];
@@ -813,13 +1027,23 @@ export class Resolver {
813
1027
  const ts = type.id != null ? this.t.sym(type.id) : null;
814
1028
  ids = [];
815
1029
  for (const b of ts?.bases ?? []) { const bt = this.resolveTypeName(b, ts.fileId); if (bt) ids.push(...this.membersOf({ id: bt.id, name: bt.name, fileId: bt.fileId }, name)); }
816
- } else ids = this.membersOf(type, name);
1030
+ } else ids = this.#sameImpl(this.membersOf(type, name), srcId, fileId);
817
1031
  const r = this.#pick(this.#byStatic(ids, false), kind, 0.95, fileId);
818
1032
  if (r) return r;
819
1033
  return { id: null, conf: 0, ncand: 0 };
820
1034
  }
821
1035
  }
822
1036
  }
1037
+ // a Rust trait-path call: `SerializeSeq::serialize_element(self, v)`, `Debug::fmt(n, f)`
1038
+ if (file.lang === 'rust' && kind === 'call' && this.argTypes?.length === 1) {
1039
+ const r = this.#traitPathCall(name, recv, fileId);
1040
+ if (r) return r;
1041
+ }
1042
+ // a Rust path: `crate::a::f`, `super::B::new`, `other_crate::m::T`, `alias::sub::f`
1043
+ if (file.lang === 'rust' && this.modules) {
1044
+ const r = this.#rustPath(name, kind, recv, fileId);
1045
+ if (r) return r;
1046
+ }
823
1047
  // inferred receiver type (locals, params, fields, factories; chains across files)
824
1048
  if (recvType) {
825
1049
  const type = this.resolveRecvType(recvType, fileId, srcId);
@@ -831,6 +1055,13 @@ export class Resolver {
831
1055
  // interface / abstract receiver: the member may be declared only on implementations
832
1056
  return { id: null, conf: 0, ncand: 0 };
833
1057
  }
1058
+ if (this.#unknownDeclaredType(recvType)) {
1059
+ // the receiver is declared as a type the index does not hold (a library type, or a
1060
+ // declaration the parser could not read): another type's member of that name is not
1061
+ // a guess, so the call stays unbound and is counted with the same-name candidates
1062
+ const guess = kind === 'read' ? null : this.#anyMember(name, kind, fileId);
1063
+ return { id: null, conf: 0, ncand: guess ? Math.max(1, guess.ncand) : 0 };
1064
+ }
834
1065
  }
835
1066
  // receiver is an imported module / namespace / package alias
836
1067
  const imp = this.#importFor(fileId, root) ?? this.#importFor(fileId, recv);
@@ -844,8 +1075,11 @@ export class Resolver {
844
1075
  if (!owners.length && PACKAGE_LANGS.has(file.lang)) owners = this.#symbolsInPackage(imp.source, importedName);
845
1076
  for (const oid of owners) ids.push(...this.#membersVia(this.t.sym(oid), name));
846
1077
  if (!ids.length && imp.targetFileId != null && owners.length === 0) {
847
- // `from pkg import submodule` then submodule.fn()
848
- ids = this.exportedFrom(imp.targetFileId, name);
1078
+ // `from pkg import submodule` then submodule.fn(): the submodule's own name first —
1079
+ // pkg/__init__.py may re-export something else under it (`from fastapi import
1080
+ // params; params.File` is the class in params.py, not the File function __init__ exports)
1081
+ const mod = file.lang === 'python' ? this.#moduleFileOf(fileId, imp.local) : null;
1082
+ ids = this.exportedFrom(mod ?? imp.targetFileId, name);
849
1083
  }
850
1084
  } else if (rest.length === 0) {
851
1085
  if (imp.targetFileId != null) ids = this.exportedFrom(imp.targetFileId, name);
@@ -860,6 +1094,12 @@ export class Resolver {
860
1094
  const mod = this.#moduleFileOf(fileId, imp.local);
861
1095
  if (mod != null && rest.length === 0) ids = this.exportedFrom(mod, name);
862
1096
  else if (mod != null && rest.length === 1) for (const oid of this.exportedFrom(mod, rest[0])) ids.push(...this.#membersVia(this.t.sym(oid), name));
1097
+ // `import flask; flask.json.dumps(…)`: the rest of the receiver names submodules
1098
+ if (!ids.length && mod != null && rest.length) {
1099
+ let cur = mod;
1100
+ for (const seg of rest) { cur = cur != null ? this.#submodule(cur, seg) : null; }
1101
+ if (cur != null) ids = this.exportedFrom(cur, name);
1102
+ }
863
1103
  }
864
1104
  if (ids.length) {
865
1105
  const r = this.#pick(ids, kind, 0.95, fileId);
@@ -895,6 +1135,82 @@ export class Resolver {
895
1135
  return this.#anyMember(name, kind, fileId) ?? { id: null, conf: 0, ncand: 0 };
896
1136
  }
897
1137
 
1138
+ /**
1139
+ * `self.m()` in a Rust impl where several impls of the type declare `m` (tokio's `impl AsyncWrite`
1140
+ * for `Cursor<&mut [u8]>`, `Cursor<Vec<u8>>`…): the one in the caller's own impl block, the
1141
+ * nearest in the file among those implementing the caller's trait.
1142
+ */
1143
+ #sameImpl(ids, srcId, fileId) {
1144
+ if (ids.length < 2 || srcId == null || this.t.file(fileId)?.lang !== 'rust') return ids;
1145
+ let src = this.t.sym(srcId);
1146
+ while (src && !CALLABLE_KINDS.has(src.kind) && src.parentId != null) src = this.t.sym(src.parentId);
1147
+ if (!src) return ids;
1148
+ const key = (s) => JSON.stringify(s.bases ?? []);
1149
+ const same = ids.map(id => this.t.sym(id)).filter(s => s && s.fileId === fileId && key(s) === key(src));
1150
+ if (same.length < 2 || same.length < ids.length) return ids;
1151
+ same.sort((a, b) => Math.abs(a.startLine - src.startLine) - Math.abs(b.startLine - src.startLine));
1152
+ return [same[0].id];
1153
+ }
1154
+
1155
+ /**
1156
+ * `Trait::m(x)` / `<T as Trait>::m(x)` (Rust): the method `m` of the impl of `Trait` for the
1157
+ * type of `x` (or `T`), so a type with several traits declaring `m` (`Debug::fmt` and
1158
+ * `Display::fmt` on one type) is told apart. Null when that type has no such impl in the
1159
+ * repository: the path names a type, or the trait's own declaration is what is called.
1160
+ */
1161
+ #traitPathCall(name, recv, fileId) {
1162
+ const trait = recv.split('.').pop();
1163
+ if (!/^[A-Z]/.test(trait ?? '')) return null;
1164
+ const self = this.typeFromString(this.argTypes[0], fileId);
1165
+ if (!self || self === EXTERNAL || (self.id == null && !self.allOf)) return null;
1166
+ const ids = this.membersOf(self, name).filter(id => { const m = this.t.sym(id); return m && CALLABLE_KINDS.has(m.kind) && m.bases?.includes(trait); });
1167
+ return ids.length ? this.#pick(ids, 'call', 0.9, fileId) : null;
1168
+ }
1169
+
1170
+ /**
1171
+ * A Rust path receiver, walked module by module from where it starts — `crate`, `self`,
1172
+ * `super`, a crate of the repository, or an imported module — to the module (and at most one
1173
+ * type in it) that declares `name`. Null when the path does not start at a module the index
1174
+ * knows; then the other rules apply.
1175
+ */
1176
+ #rustPath(name, kind, recv, fileId) {
1177
+ const segs = recv.split('.');
1178
+ const from = this.t.file(fileId)?.path;
1179
+ const isModule = (p, seg) => p && (seg === 'crate' || seg === 'self' || seg === 'super' || this.modules.rustCrates?.get(seg) === p || new RegExp(`(^|/)${seg}(\\.rs|/mod\\.rs)$`).test(p));
1180
+ let cur = null, rest = segs;
1181
+ if (segs[0] === 'crate' || segs[0] === 'self' || segs[0] === 'super' || this.modules.rustCrates?.has(segs[0])) {
1182
+ let k = 1;
1183
+ while (segs[k] === 'super') k++;
1184
+ const t = this.modules.resolve('rust', from, segs.slice(0, k).join('::'));
1185
+ if (!t?.file || !isModule(t.file, segs[k - 1])) return null;
1186
+ cur = t.file; rest = segs.slice(k);
1187
+ } else {
1188
+ const imp = this.#importFor(fileId, segs[0]);
1189
+ const mod = imp?.imported === 'self' ? imp.source?.split('::').pop() : imp?.imported;
1190
+ let target = imp && (imp.targetPath ?? this.t.file(imp.targetFileId)?.path);
1191
+ if (!imp) {
1192
+ // a child module of this one (`mod chan;` then `chan::channel()`)
1193
+ target = this.modules.resolve('rust', from, `self::${segs[0]}`)?.file;
1194
+ if (target === from) target = null;
1195
+ }
1196
+ else if (!isModule(target, mod)) return null;
1197
+ if (!target || !isModule(target, imp ? mod : segs[0])) return null;
1198
+ cur = target; rest = segs.slice(1);
1199
+ }
1200
+ // submodules, while a segment names one
1201
+ while (rest.length) {
1202
+ const t = this.modules.resolve('rust', cur, `self::${rest[0]}`);
1203
+ if (!t?.file || t.file === cur || !isModule(t.file, rest[0])) break;
1204
+ cur = t.file; rest = rest.slice(1);
1205
+ }
1206
+ const fid = this.t.fileByPath.get(cur)?.id;
1207
+ if (fid == null || rest.length > 1) return null;
1208
+ let ids = [];
1209
+ if (!rest.length) ids = this.exportedFrom(fid, name);
1210
+ else for (const oid of this.exportedFrom(fid, rest[0])) ids.push(...this.#membersVia(this.t.sym(oid), name));
1211
+ return this.#pick(ids, kind, 0.95, fileId);
1212
+ }
1213
+
898
1214
  /**
899
1215
  * Members named `name` reached through an imported/package symbol: a type's static members, a
900
1216
  * typed value's instance members (`export const api = new Api()`), or members declared on the
@@ -904,7 +1220,7 @@ export class Resolver {
904
1220
  if (!o) return [];
905
1221
  if (!TYPE_KINDS.has(o.kind) && o.type) {
906
1222
  const t = this.typeFromString(o.type, o.fileId);
907
- if (t && t !== EXTERNAL && t.arrayOf == null && t.mapOf == null) return this.#byStatic(this.membersOf(t, name), false);
1223
+ if (t && t !== EXTERNAL && t.arrayOf == null && t.mapOf == null && t.tupleOf == null) return this.#byStatic(this.membersOf(t, name), false);
908
1224
  if (t) return [];
909
1225
  }
910
1226
  const ids = this.membersOf({ id: o.id, name: o.name, fileId: o.fileId }, name);
@@ -918,6 +1234,17 @@ export class Resolver {
918
1234
  return same.length ? same : ids;
919
1235
  }
920
1236
 
1237
+ /**
1238
+ * A receiver type written as a plain type name that no type in the index has. Short names and
1239
+ * `TName` are left out: they are usually type parameters (`T`, `K`, `TItem`), whose calls reach
1240
+ * the members of their bound.
1241
+ */
1242
+ #unknownDeclaredType(desc) {
1243
+ if (!/^[A-Z][\w$]*$/.test(desc) || /^[A-Z][A-Z0-9]?$|^T[A-Z]\w*$/.test(desc) || TYPE_WRAPPERS.has(desc)) return false;
1244
+ // a descriptor may name a value rather than a type (Go `s := ByCid`, a package-level constant)
1245
+ return !(this.t.byName.get(desc) ?? []).some(id => { const k = this.t.sym(id)?.kind; return TYPE_KINDS.has(k) || VALUE_TYPED_KINDS.has(k); });
1246
+ }
1247
+
921
1248
  /**
922
1249
  * Unknown receiver: bind by name only when the repository has exactly one member with that name
923
1250
  * and the name is not also a standard-library method of the language (`get`, `set`, `apply`…).
@@ -939,8 +1266,27 @@ export class Resolver {
939
1266
  }
940
1267
  }
941
1268
 
1269
+ const WIDENING = { byte: ['short', 'int', 'long', 'float', 'double'], short: ['int', 'long', 'float', 'double'], char: ['int', 'long', 'float', 'double'],
1270
+ int: ['long', 'float', 'double'], long: ['float', 'double'], float: ['double'] };
1271
+ const BOXED = { int: 'Integer', char: 'Character', long: 'Long', double: 'Double', float: 'Float', boolean: 'Boolean', byte: 'Byte', short: 'Short' };
1272
+ const CLOSED = new Set([...Object.keys(BOXED), 'String']);
1273
+
1274
+ /** How well an argument type fits a parameter type: 3 exact, 2 widening/boxing, 1 maybe (a class, a type parameter), -1 never. */
1275
+ function argFits(a, p) {
1276
+ if (!p) return 1;
1277
+ if (a === p) return 3;
1278
+ if (a === '*') return CLOSED.has(p) && p !== 'String' ? -1 : 1; // null: any reference type
1279
+ if (WIDENING[a]?.includes(p) || BOXED[a] === p || BOXED[p] === a) return 2;
1280
+ if (p === 'Object' || /^[A-Z]\w?$/.test(p)) return 1; // anything, or a type parameter
1281
+ if (CLOSED.has(a) && CLOSED.has(p)) return -1;
1282
+ if (BOXED[a]) return ['Number', 'Comparable', 'Serializable'].includes(p) ? 1 : -1; // a primitive
1283
+ if (BOXED[p]) return -1; // a primitive parameter, an object argument
1284
+ if (a.endsWith('[]') !== p.endsWith('[]')) return -1;
1285
+ return 1;
1286
+ }
1287
+
942
1288
  export function familyOf(lang) {
943
- if (lang === 'javascript' || lang === 'typescript' || lang === 'tsx') return 'ecma';
1289
+ if (lang === 'javascript' || lang === 'typescript' || lang === 'tsx' || lang === 'vue' || lang === 'svelte') return 'ecma';
944
1290
  if (lang === 'c' || lang === 'cpp') return 'c';
945
1291
  if (lang === 'java' || lang === 'kotlin' || lang === 'scala') return 'jvm';
946
1292
  return lang;