ucn 5.5.1 → 5.5.2

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 (93) hide show
  1. package/.claude/skills/ucn/SKILL.md +35 -9
  2. package/.claude/skills/ucn/references/commands.md +38 -16
  3. package/.claude/skills/ucn/references/resolution-rules.md +126 -20
  4. package/.claude/skills/ucn/references/trust-contract.md +5 -3
  5. package/README.md +30 -21
  6. package/cli/index.js +1 -1
  7. package/core/accessors.js +341 -54
  8. package/core/account.js +74 -20
  9. package/core/analysis.js +257 -125
  10. package/core/ast-analysis.js +12 -3
  11. package/core/brief.js +8 -4
  12. package/core/build-worker.js +2 -1
  13. package/core/cache.js +137 -6
  14. package/core/call-name-cache.js +134 -0
  15. package/core/callable-scope.js +21 -0
  16. package/core/callers.js +4726 -940
  17. package/core/check.js +22 -5
  18. package/core/class-identity.js +399 -12
  19. package/core/constructor-calls.js +831 -0
  20. package/core/constructor-hierarchy.js +147 -0
  21. package/core/constructor-protocol.js +196 -0
  22. package/core/contract-membership.js +346 -140
  23. package/core/cpp-conversion-constraints.js +123 -0
  24. package/core/cpp-conversion-syntax.js +216 -0
  25. package/core/cpp-conversions.js +414 -0
  26. package/core/cpp-scope.js +245 -44
  27. package/core/deadcode.js +35 -15
  28. package/core/decorator-binding.js +137 -3
  29. package/core/enum-reference-syntax.js +134 -0
  30. package/core/execute.js +17 -1
  31. package/core/external-macros.js +42 -10
  32. package/core/go-packages.js +171 -0
  33. package/core/graph-build.js +2 -0
  34. package/core/graph.js +75 -59
  35. package/core/ignore-rules.js +2 -1
  36. package/core/imports.js +145 -19
  37. package/core/index-ir.js +41 -4
  38. package/core/ir.js +27 -6
  39. package/core/macro-expansion.js +38 -2
  40. package/core/out-of-class-members.js +182 -0
  41. package/core/output/analysis-ext.js +4 -2
  42. package/core/output/analysis.js +89 -20
  43. package/core/output/graph.js +12 -3
  44. package/core/output/lines.js +4 -3
  45. package/core/output/public.js +1 -0
  46. package/core/output/reporting.js +6 -1
  47. package/core/output/shared.js +2 -0
  48. package/core/output/tracing.js +28 -22
  49. package/core/output-budget.js +2 -2
  50. package/core/parallel-build.js +52 -1
  51. package/core/project.js +217 -112
  52. package/core/provenance-constructor-base.js +161 -0
  53. package/core/provenance-facts.js +257 -27
  54. package/core/provenance-overload.js +458 -54
  55. package/core/provenance-type.js +487 -0
  56. package/core/provenance.js +234 -15
  57. package/core/public-command.js +17 -2
  58. package/core/query-bodies.js +159 -0
  59. package/core/reflection.js +255 -2
  60. package/core/rename-token-syntax.js +111 -0
  61. package/core/reporting.js +13 -7
  62. package/core/rust-macro-expansion.js +151 -19
  63. package/core/rust-modules.js +1 -1
  64. package/core/rust-names.js +607 -0
  65. package/core/search.js +70 -17
  66. package/core/shared.js +98 -1
  67. package/core/source-memo.js +201 -0
  68. package/core/text-usage-syntax.js +38 -0
  69. package/core/tracing.js +7 -1
  70. package/core/type-denotation.js +124 -37
  71. package/core/type-references.js +615 -246
  72. package/core/value-references.js +2777 -0
  73. package/core/verify.js +1097 -281
  74. package/languages/adapter.js +28 -4
  75. package/languages/c-family.js +1118 -164
  76. package/languages/constructor-bases.js +105 -0
  77. package/languages/cpp-platform.js +26 -0
  78. package/languages/cpp-site-syntax.js +83 -0
  79. package/languages/csharp-scope.js +439 -0
  80. package/languages/csharp.js +428 -68
  81. package/languages/go.js +385 -207
  82. package/languages/html.js +4 -2
  83. package/languages/index.js +397 -2
  84. package/languages/java-platform.js +226 -0
  85. package/languages/java-unicode.js +269 -0
  86. package/languages/java.js +461 -117
  87. package/languages/javascript.js +418 -66
  88. package/languages/lexical-scope.js +190 -23
  89. package/languages/python.js +404 -54
  90. package/languages/rust.js +510 -82
  91. package/languages/utils.js +67 -8
  92. package/mcp/server.js +8 -2
  93. package/package.json +2 -2
@@ -15,6 +15,7 @@ const {
15
15
  traverseTree,
16
16
  nodeTextWithoutComments,
17
17
  traverseTreeCached,
18
+ getCachedNodeList,
18
19
  nodeToLocation,
19
20
  extractJSDocstring,
20
21
  visitNameNodes,
@@ -403,6 +404,18 @@ const MAX_TYPE_EVIDENCE_OCCURRENCES = 512;
403
404
  const definitionReadingMemo = new Map(); // grammar + replacement list -> reading (fix #387)
404
405
  const IDENTIFIER_TEXT = /^[A-Za-z_][A-Za-z0-9_]*$/;
405
406
 
407
+ /**
408
+ * A replacement list without the object-like macro names `isDecoration`
409
+ * accepts (names that expand to declaration specifiers or nothing), as
410
+ * preprocessing tokens joined by spaces (fix #427).
411
+ */
412
+ function withoutDecorationMacros(body, isDecoration) {
413
+ const tokens = lexPP(String(body || ''));
414
+ if (!tokens.some(token => token.k === 'id' && isDecoration(token.v))) return body;
415
+ return tokens.filter((token, i) => !(token.k === 'id' && tokens[i + 1]?.v !== '(' && isDecoration(token.v)))
416
+ .map(token => token.v).join(' ');
417
+ }
418
+
406
419
  /**
407
420
  * Per-file evidence for decoration decisions, gathered lazily from the
408
421
  * file's own tree and directives: names proven types by an occurrence, names
@@ -445,6 +458,9 @@ class DecorationEvidence {
445
458
  }
446
459
  }
447
460
  if (this.defines.has(name)) return this.defines.get(name);
461
+ // A definition that names itself (or one that names it) reads as
462
+ // no reading while it is being read.
463
+ this.defines.set(name, null);
448
464
  const bodies = [];
449
465
  const code = this.code;
450
466
  // A name the file does not define reads by the project's definitions
@@ -470,6 +486,14 @@ class DecorationEvidence {
470
486
  }
471
487
  bodies.push(body.replace(/\/\*[\s\S]*?\*\/|\/\/.*$/gm, ' '));
472
488
  }
489
+ // Object-like macros a body names that themselves read as
490
+ // decoration expand to specifiers or nothing: the body reads as the
491
+ // rest of it (fix #427: `#define API __declspec(dllexport) VIS` with
492
+ // `#define VIS __attribute__((visibility("default")))` or empty).
493
+ const reduced = bodies.map(body => withoutDecorationMacros(body, word => word !== name &&
494
+ (this.defineEnds.has(word) || externalMacroContext?.objectBodies?.has(word)) &&
495
+ this.defined(word) === 'decoration'));
496
+ bodies.splice(0, bodies.length, ...reduced);
473
497
  const readings = new Set(bodies.map(body => this.readBody(body)));
474
498
  let reading = readings.size === 1 ? [...readings][0] : null;
475
499
  // A statement macro defined empty in some configuration
@@ -503,11 +527,24 @@ class DecorationEvidence {
503
527
  }
504
528
 
505
529
  probeBody(text) {
506
- if (/^catch\b/.test(text)) {
507
- const tree = safeParse(this.parser, `void __ucn_probe__() { try { } ${text} }`, undefined, PARSE_OPTIONS);
530
+ const statementProbe = source => {
531
+ const tree = safeParse(this.parser, source, undefined, PARSE_OPTIONS);
508
532
  const clean = !!tree && !tree.rootNode.hasError;
509
533
  tree?.delete?.();
510
- return clean ? 'statement' : null;
534
+ return clean;
535
+ };
536
+ if (/^catch\b/.test(text)) {
537
+ // A complete handler, or (fix #427) a handler head the block
538
+ // after the invocation completes (`catch(...)`).
539
+ return statementProbe(`void __ucn_probe__() { try { } ${text} }`) ||
540
+ statementProbe(`void __ucn_probe__() { try { } ${text} { } }`) ? 'statement' : null;
541
+ }
542
+ // A conditional head with its condition (`if(true)`, `if(false)`):
543
+ // removed, the block after it stays a statement (fix #427: one
544
+ // configuration's `try`/`catch(...)` is another's `if(true)` /
545
+ // `if(false)`).
546
+ if (/^(?:if|while|for|switch)\s*\(/.test(text)) {
547
+ return statementProbe(`void __ucn_probe__() { ${text} { } }`) ? 'statement' : null;
511
548
  }
512
549
  const probe = source => {
513
550
  const tree = safeParse(this.parser, source, undefined, PARSE_OPTIONS);
@@ -997,6 +1034,14 @@ function dependentTypeInBodyError(node) {
997
1034
  return false;
998
1035
  }
999
1036
 
1037
+ /** `Class::*` with no declarator name after the `*` (the grammar's MISSING name). */
1038
+ function memberPointerAbstractDeclarator(node) {
1039
+ const name = node.childForFieldName('name');
1040
+ if (name?.type !== 'pointer_type_declarator') return false;
1041
+ const declarator = name.childForFieldName('declarator');
1042
+ return !!declarator?.isMissing && name.namedChildCount === 1;
1043
+ }
1044
+
1000
1045
  function macroTypeRanges(tree, code, evidence = null) {
1001
1046
  const ranges = [];
1002
1047
  const pairs = [];
@@ -1043,6 +1088,15 @@ function macroTypeRanges(tree, code, evidence = null) {
1043
1088
  return true;
1044
1089
  };
1045
1090
  traverseTree(tree.rootNode, node => {
1091
+ // An abstract pointer-to-member declarator (`Ret (Class::*)(Args...)`,
1092
+ // `Type Class::*`) has no reading in the grammar, which then misreads
1093
+ // the whole declaration (fix #427). Read with `*` in place of
1094
+ // `Class::*`, it is the pointer declarator of the same shape; the
1095
+ // parameter keeps its written type (structuredParams).
1096
+ if (treeHasError && node.type === 'qualified_identifier' && memberPointerAbstractDeclarator(node)) {
1097
+ ranges.push([node.startIndex, node.endIndex, '*'.padStart(node.endIndex - node.startIndex, ' ')]);
1098
+ return false;
1099
+ }
1046
1100
  ranges.push(...errorMacroRanges(node, code, pairs, evidence));
1047
1101
  const statementMacro = statementMacroRange(node, code, evidence);
1048
1102
  if (statementMacro) ranges.push(statementMacro);
@@ -1575,6 +1629,12 @@ function preprocessorDirective(line) {
1575
1629
  */
1576
1630
  const CONDITIONAL_RECOVERY_MAX_BYTES = 256 * 1024;
1577
1631
 
1632
+ // Keep every branch's tokens while removing only conditional directives.
1633
+ // Consumers must still parse the combined tokens to establish their meaning.
1634
+ function conditionalHeaderText(code) {
1635
+ return code.split(/(?<=\n)/).map(line => preprocessorDirective(line) ? blankLine(line) : line).join('');
1636
+ }
1637
+
1578
1638
  function conditionalRecoverySources(code) {
1579
1639
  if (Buffer.byteLength(code) > CONDITIONAL_RECOVERY_MAX_BYTES) return [];
1580
1640
  const lines = code.match(/.*(?:\r\n|\n|\r|$)/g)?.filter(Boolean) || [];
@@ -1675,6 +1735,22 @@ function conditionalRecoverySources(code) {
1675
1735
  const source = selected.join('');
1676
1736
  if (!sources.includes(source)) sources.push(source);
1677
1737
  }
1738
+ // Every alternative head of one construct at once (fix #430).
1739
+ const directiveRows = [];
1740
+ const directiveEnd = new Map();
1741
+ for (let row = 0; row < lines.length; row++) {
1742
+ if (!directives[row]) continue;
1743
+ let end = row;
1744
+ while (end + 1 < lines.length && /\\[ \t]*\r?\n?$/.test(lines[end])) end++;
1745
+ directiveEnd.set(row, end);
1746
+ directiveRows.push({ row, kind: directives[row].kind });
1747
+ }
1748
+ const closures = alternativeBranchClosures(lines, directiveRows, directiveEnd);
1749
+ if (closures) {
1750
+ const source = lines.map((line, row) => closures.replace.has(row) ? closures.replace.get(row)
1751
+ : closures.rows.has(row) ? blankLine(line) : line).join('');
1752
+ if (!sources.includes(source)) sources.push(source);
1753
+ }
1678
1754
  return sources;
1679
1755
  }
1680
1756
 
@@ -1683,6 +1759,77 @@ const DIRECTIVE_TOKEN_TYPES = new Set([
1683
1759
  '#if', '#ifdef', '#ifndef', '#elif', '#elifdef', '#elifndef', '#else', '#endif',
1684
1760
  ]);
1685
1761
 
1762
+ /**
1763
+ * The `{` minus `}` count of the program tokens on rows [from, to): directive
1764
+ * lines (a `#define` replacement list, with its continuation lines) are not
1765
+ * program tokens, nor are braces in comments and literals.
1766
+ */
1767
+ function programBraceBalance(lines, from, to) {
1768
+ const kept = [];
1769
+ for (let row = from; row < to; row++) {
1770
+ if (/^\s*#/.test(lines[row])) {
1771
+ while (row + 1 < to && /\\[ \t]*\r?\n?$/.test(lines[row])) row++;
1772
+ continue;
1773
+ }
1774
+ kept.push(lines[row]);
1775
+ }
1776
+ const text = kept.join('');
1777
+ let balance = 0;
1778
+ for (let index = 0; index < text.length; index++) {
1779
+ const skipped = skipLexicalRegion(text, index);
1780
+ if (skipped != null) {
1781
+ index = skipped - 1;
1782
+ continue;
1783
+ }
1784
+ if (text[index] === '{') balance++;
1785
+ else if (text[index] === '}') balance--;
1786
+ }
1787
+ return balance;
1788
+ }
1789
+
1790
+ /**
1791
+ * Alternative heads of one construct (fix #430): a conditional whose every
1792
+ * branch opens the same number k of braces the code after it closes
1793
+ * (`#if X T f() { #else T f() { #endif ... }`, entt `type_hash::value`).
1794
+ * Each branch is read as its own construct, closed by k braces written into
1795
+ * the directive line that ends it (the `#elif`/`#else` line, blank in every
1796
+ * configuration), so every alternative is a declaration; directive lines are
1797
+ * blanked and every other byte and row kept. Returns { rows, replace } for
1798
+ * the groups of `directives` (`{ row, kind }` in order, `directiveEnd` maps a
1799
+ * row to its last continuation row) that qualify, or null.
1800
+ */
1801
+ function alternativeBranchClosures(lines, directives, directiveEnd) {
1802
+ const rows = new Set();
1803
+ const replace = new Map();
1804
+ const stack = [];
1805
+ for (const directive of directives) {
1806
+ if (directive.kind === 'if' || directive.kind === 'ifdef' || directive.kind === 'ifndef') {
1807
+ stack.push({ branches: [directive.row] });
1808
+ } else if (directive.kind === 'elif' || directive.kind === 'else') {
1809
+ stack[stack.length - 1]?.branches.push(directive.row);
1810
+ } else if (directive.kind === 'endif') {
1811
+ const group = stack.pop();
1812
+ if (!group) return null;
1813
+ if (group.branches.length < 2) continue;
1814
+ const edges = [...group.branches, directive.row];
1815
+ const balances = group.branches.map((row, b) => programBraceBalance(lines, row + 1, edges[b + 1]));
1816
+ const k = balances[0];
1817
+ if (!(k > 0) || balances.some(balance => balance !== k)) continue;
1818
+ const closers = group.branches.slice(1);
1819
+ if (closers.some(row => directiveEnd.get(row) !== row ||
1820
+ lines[row].replace(/[\r\n]+$/, '').length < k)) continue;
1821
+ for (const row of edges) {
1822
+ for (let line = row; line <= directiveEnd.get(row); line++) rows.add(line);
1823
+ }
1824
+ for (const row of closers) {
1825
+ const blank = blankLine(lines[row]);
1826
+ replace.set(row, '}'.repeat(k) + blank.slice(k));
1827
+ }
1828
+ }
1829
+ }
1830
+ return rows.size > 0 ? { rows, replace } : null;
1831
+ }
1832
+
1686
1833
  /**
1687
1834
  * fix #391: the source with the preprocessor conditionals the grammar could
1688
1835
  * not place resolved, or null. tree-sitter models a conditional as a node
@@ -1697,7 +1844,7 @@ const DIRECTIVE_TOKEN_TYPES = new Set([
1697
1844
  * number. Only the conditionals the grammar could not place change; every
1698
1845
  * other line reads as before.
1699
1846
  */
1700
- function misplacedDirectiveRepair(tree, source) {
1847
+ function misplacedDirectiveRepair(tree, source, info = null) {
1701
1848
  const misplaced = new Set();
1702
1849
  const visit = node => {
1703
1850
  const error = node.type === 'ERROR';
@@ -1712,30 +1859,7 @@ function misplacedDirectiveRepair(tree, source) {
1712
1859
  // conditionals (`#ifdef X else { #endif ... #ifdef X } #endif`, fix
1713
1860
  // #393): the grammar reads it as a block that swallows the declarations
1714
1861
  // after it, with no ERROR near the directives.
1715
- const braceBalance = (from, to) => {
1716
- // Program tokens only: directive lines (a `#define` replacement
1717
- // list, with its continuation lines) are not.
1718
- const kept = [];
1719
- for (let row = from; row < to; row++) {
1720
- if (/^\s*#/.test(lines[row])) {
1721
- while (row + 1 < to && /\\[ \t]*\r?\n?$/.test(lines[row])) row++;
1722
- continue;
1723
- }
1724
- kept.push(lines[row]);
1725
- }
1726
- const text = kept.join('');
1727
- let balance = 0;
1728
- for (let index = 0; index < text.length; index++) {
1729
- const skipped = skipLexicalRegion(text, index);
1730
- if (skipped != null) {
1731
- index = skipped - 1;
1732
- continue;
1733
- }
1734
- if (text[index] === '{') balance++;
1735
- else if (text[index] === '}') balance--;
1736
- }
1737
- return balance;
1738
- };
1862
+ const braceBalance = (from, to) => programBraceBalance(lines, from, to);
1739
1863
  // Directive line spans (a directive continues over backslash-newlines).
1740
1864
  const directiveEnd = new Map();
1741
1865
  const directives = [];
@@ -1748,6 +1872,8 @@ function misplacedDirectiveRepair(tree, source) {
1748
1872
  directives.push({ row, kind: directive.kind });
1749
1873
  }
1750
1874
  const blank = new Set();
1875
+ // Groups of alternative heads keep every branch, each closed in place.
1876
+ const closures = alternativeBranchClosures(lines, directives, directiveEnd);
1751
1877
  const stack = [];
1752
1878
  for (const directive of directives) {
1753
1879
  if (directive.kind === 'if' || directive.kind === 'ifdef' || directive.kind === 'ifndef') {
@@ -1758,6 +1884,7 @@ function misplacedDirectiveRepair(tree, source) {
1758
1884
  const group = stack.pop();
1759
1885
  if (!group) return null;
1760
1886
  const rows = [...group.branches, directive.row];
1887
+ if (closures?.rows.has(directive.row)) continue;
1761
1888
  if (!rows.some(row => misplaced.has(row))) {
1762
1889
  const edges = [...group.branches, directive.row];
1763
1890
  let split = false;
@@ -1792,8 +1919,10 @@ function misplacedDirectiveRepair(tree, source) {
1792
1919
  }
1793
1920
  }
1794
1921
  }
1795
- if (stack.length > 0 || blank.size === 0) return null;
1796
- return lines.map((line, row) => (blank.has(row) ? blankLine(line) : line)).join('');
1922
+ if (stack.length > 0 || (blank.size === 0 && !closures)) return null;
1923
+ if (info && closures) info.closedAlternatives = true;
1924
+ return lines.map((line, row) => closures?.replace.has(row) ? closures.replace.get(row)
1925
+ : blank.has(row) || closures?.rows.has(row) ? blankLine(line) : line).join('');
1797
1926
  }
1798
1927
 
1799
1928
  const STRUCTURE_SCORE_TYPES = [
@@ -1928,8 +2057,9 @@ const KNOWN_MACRO_ROUNDS = 3;
1928
2057
  * statements again), so a few rounds run. Returns { source, tree,
1929
2058
  * invocations }; `blank` stays set only on invocations the source blanks.
1930
2059
  */
1931
- function knownMacroInvocationRecovery(parser, source) {
2060
+ function knownMacroInvocationRecovery(parser, source, rawCapture = null) {
1932
2061
  let tree = safeParse(parser, source, undefined, PARSE_OPTIONS);
2062
+ if (rawCapture?.source === source) rawCapture.tree = tree;
1933
2063
  const own = directiveFunctionMacros(source);
1934
2064
  // The project's attribute and return-type macros matter only where the
1935
2065
  // grammar failed (fix #396).
@@ -1961,7 +2091,7 @@ function knownMacroInvocationRecovery(parser, source) {
1961
2091
  for (const invocation of blanks) kept.set(invocation.start, { ...invocation, blank: null });
1962
2092
  break;
1963
2093
  }
1964
- tree.delete?.();
2094
+ if (tree !== rawCapture?.tree) tree.delete?.();
1965
2095
  tree = candidate;
1966
2096
  current = blanked;
1967
2097
  }
@@ -1991,7 +2121,7 @@ function withExternalFunctionMacros(macros) {
1991
2121
  return { defs, undefs: merged.undefs };
1992
2122
  }
1993
2123
 
1994
- function parseTree(parser, code) {
2124
+ function parseTree(parser, code, rawCapture = null) {
1995
2125
  const cacheKey = treeCacheKey(code);
1996
2126
  const cached = cachedCFamilyTree(parser, cacheKey);
1997
2127
  if (cached) return cached;
@@ -2016,7 +2146,7 @@ function parseTree(parser, code) {
2016
2146
  // Invocations of the file's own function-like macros that the grammar
2017
2147
  // read as declarations are the preprocessor's view before any other
2018
2148
  // recovery (fix #385).
2019
- const known = knownMacroInvocationRecovery(parser, pinnedSource);
2149
+ const known = knownMacroInvocationRecovery(parser, pinnedSource, rawCapture);
2020
2150
  const source = known.source;
2021
2151
  const tree = known.tree;
2022
2152
  const knownSource = source !== pinnedSource ? source : null;
@@ -2064,7 +2194,7 @@ function parseTree(parser, code) {
2064
2194
  const releaseTree = candidate => {
2065
2195
  if (!candidate || !liveTrees.has(candidate)) return;
2066
2196
  liveTrees.delete(candidate);
2067
- candidate.delete?.();
2197
+ if (candidate !== rawCapture?.tree) candidate.delete?.();
2068
2198
  };
2069
2199
  // Misreads a damage hid (fix #396): once a configuration or a directive
2070
2200
  // repair restores the structure around them, the decoration rules and
@@ -2221,7 +2351,8 @@ function parseTree(parser, code) {
2221
2351
  // round over the repaired tree, which exposes misreads the damage hid.
2222
2352
  if (!conditionalApplied && !(repairFolded && best) &&
2223
2353
  (largeSource || attributeSelected.rootNode.hasError)) {
2224
- const repairedSource = misplacedDirectiveRepair(attributeSelected, attributeSource);
2354
+ const repairInfo = {};
2355
+ const repairedSource = misplacedDirectiveRepair(attributeSelected, attributeSource, repairInfo);
2225
2356
  if (repairedSource) {
2226
2357
  let candidate = safeParse(parser, repairedSource, undefined, PARSE_OPTIONS);
2227
2358
  liveTrees.add(candidate);
@@ -2236,7 +2367,12 @@ function parseTree(parser, code) {
2236
2367
  candidateErrors = round.errors;
2237
2368
  }
2238
2369
  }
2239
- if (candidateErrors < selectedErrors) {
2370
+ // A repair that reads alternative heads (fix #430) is also
2371
+ // kept at an equal error count when it recovers structure,
2372
+ // as the configuration sweep prefers the richest view.
2373
+ if (candidateErrors < selectedErrors || repairInfo.closedAlternatives &&
2374
+ candidateErrors === selectedErrors &&
2375
+ treeStructureScore(candidate) > treeStructureScore(attributeSelected)) {
2240
2376
  best = candidate;
2241
2377
  bestCode = candidateSource;
2242
2378
  } else {
@@ -2502,6 +2638,15 @@ function withoutShadowedFreeFunctions(secondary, primaryClasses) {
2502
2638
  });
2503
2639
  }
2504
2640
 
2641
+ /** Whether a declarator names a qualified entity (`H<T>::x`, `*Foo::p`). */
2642
+ function qualifiedDeclarator(node) {
2643
+ for (let current = node, depth = 0; current && depth < 16; depth++) {
2644
+ if (current.type === 'qualified_identifier') return true;
2645
+ current = current.childForFieldName('declarator');
2646
+ }
2647
+ return false;
2648
+ }
2649
+
2505
2650
  function unwrapDeclarator(node) {
2506
2651
  let current = node;
2507
2652
  const seen = new Set();
@@ -2539,6 +2684,9 @@ function functionDeclarator(node) {
2539
2684
  }
2540
2685
  const direct = node.childForFieldName('declarator');
2541
2686
  if (direct?.type === 'operator_cast') return direct;
2687
+ // An out-of-class conversion operator (`T::operator int() const {..}`,
2688
+ // fix #427): the qualified name holds the operator_cast.
2689
+ if (direct?.type === 'qualified_identifier' && qualifiedOperatorCast(direct)) return direct;
2542
2690
  const unwrapped = unwrapDeclarator(direct);
2543
2691
  if (unwrapped) return unwrapped;
2544
2692
  for (const child of node.namedChildren || []) {
@@ -2549,6 +2697,39 @@ function functionDeclarator(node) {
2549
2697
  return null;
2550
2698
  }
2551
2699
 
2700
+ /**
2701
+ * A C++17 deduction guide (`template<class T> W(T) -> W<T>;`, `explicit
2702
+ * W(const char*) -> W<int>;`): a declaration with no type whose function
2703
+ * declarator names a class template and whose trailing return type is a
2704
+ * template-id of that same template ([temp.deduct.guide]). It declares no
2705
+ * function; its name token names the class template (fix #427). Returns the
2706
+ * name node, or null.
2707
+ */
2708
+ function deductionGuideOf(node) {
2709
+ if (!node || !['declaration', 'field_declaration', 'ERROR'].includes(node.type)) return null;
2710
+ if (node.childForFieldName?.('type')) return null;
2711
+ const declarator = node.type === 'ERROR'
2712
+ ? (node.namedChildren || []).find(child => child.type === 'function_declarator')
2713
+ : node.childForFieldName('declarator');
2714
+ if (declarator?.type !== 'function_declarator') return null;
2715
+ const nameNode = declarator.childForFieldName('declarator');
2716
+ if (!nameNode || !['identifier', 'type_identifier', 'field_identifier'].includes(nameNode.type)) return null;
2717
+ const trailing = (declarator.namedChildren || []).find(child => child.type === 'trailing_return_type');
2718
+ let type = trailing?.namedChildren.find(child => child.type === 'type_descriptor')?.childForFieldName('type');
2719
+ while (type?.type === 'qualified_identifier') type = type.childForFieldName('name');
2720
+ if (type?.type !== 'template_type') return null;
2721
+ return type.childForFieldName('name')?.text === nameNode.text ? nameNode : null;
2722
+ }
2723
+
2724
+ /** The operator_cast a qualified name ends in (`A::B::operator int`), or null. */
2725
+ function qualifiedOperatorCast(node) {
2726
+ let current = node;
2727
+ while (current?.type === 'qualified_identifier') {
2728
+ current = current.childForFieldName('name') || current.namedChildren[current.namedChildCount - 1];
2729
+ }
2730
+ return current?.type === 'operator_cast' ? current : null;
2731
+ }
2732
+
2552
2733
  function canonicalCallableName(raw) {
2553
2734
  const text = String(raw || '').trim();
2554
2735
  if (!text.startsWith('operator')) return text;
@@ -2626,6 +2807,27 @@ function declaratorIdentity(declarator) {
2626
2807
  const scopeNode = scopes[scopes.length - 1];
2627
2808
  const qualifier = scopes.slice(0, -1).map(scope => scope.text.replace(/\s+/g, ''))
2628
2809
  .filter(Boolean).join('::');
2810
+ if (nameNode?.type === 'operator_cast') {
2811
+ // `Text::operator Text::bool_type()` converts to the member type
2812
+ // the class body spells `bool_type` (fix #427): the owner's own
2813
+ // qualification is not part of the in-class name.
2814
+ const cast = declaratorIdentity(nameNode);
2815
+ if (!cast.name) return {};
2816
+ const owner = [...scopes.map(scope => scope.text.replace(/\s+/g, ''))].join('::');
2817
+ let name = cast.name;
2818
+ for (const prefix of [owner, scopeNode?.text?.replace(/\s+/g, '')].filter(Boolean)) {
2819
+ if (name.startsWith(`operator ${prefix}::`)) {
2820
+ name = `operator ${name.slice(`operator ${prefix}::`.length)}`;
2821
+ break;
2822
+ }
2823
+ }
2824
+ return {
2825
+ ...cast,
2826
+ name,
2827
+ className: scopeNode?.text?.split('::').pop(),
2828
+ ...(qualifier && { ownerQualifier: qualifier }),
2829
+ };
2830
+ }
2629
2831
  return {
2630
2832
  name: canonicalCallableName(nameNode?.text),
2631
2833
  className: scopeNode?.text?.split('::').pop(),
@@ -3161,9 +3363,31 @@ function paramTypeText(param, identity) {
3161
3363
  return typeText || null;
3162
3364
  }
3163
3365
 
3164
- function structuredParams(paramsNode) {
3366
+ /**
3367
+ * The written text of a node the recovery read with `*` in place of an
3368
+ * abstract pointer-to-member declarator's `Class::*` (fix #427), or null.
3369
+ */
3370
+ function writtenMemberPointerText(node, lines) {
3371
+ if (!node || !lines) return null;
3372
+ const start = node.startPosition;
3373
+ const end = node.endPosition;
3374
+ if (start.row !== end.row) return null;
3375
+ const written = lines[start.row]?.slice(start.column, end.column);
3376
+ return written && written !== node.text && /::\s*\*/.test(written)
3377
+ ? written.replace(/\s+/g, ' ').trim() : null;
3378
+ }
3379
+
3380
+ function paramsDisplayText(paramsNode, lines) {
3381
+ const text = writtenMemberPointerText(paramsNode, lines) || nodeTextWithoutComments(paramsNode);
3382
+ return text.replace(/^\(|\)$/g, '').trim();
3383
+ }
3384
+
3385
+ function structuredParams(paramsNode, lines = null) {
3165
3386
  if (!paramsNode) return [];
3166
3387
  const result = [];
3388
+ // A parameter the recovery read as a pointer declarator in place of an
3389
+ // abstract pointer-to-member one keeps its written type (fix #427).
3390
+ const writtenMemberPointer = param => writtenMemberPointerText(param, lines);
3167
3391
  for (const param of paramsNode.namedChildren || []) {
3168
3392
  if (param.type !== 'parameter_declaration' &&
3169
3393
  param.type !== 'optional_parameter_declaration' &&
@@ -3196,6 +3420,8 @@ function structuredParams(paramsNode) {
3196
3420
  info.unnamed = true;
3197
3421
  }
3198
3422
  }
3423
+ const memberPointer = writtenMemberPointer(param);
3424
+ if (memberPointer && info.unnamed) info.name = memberPointer;
3199
3425
  if (param.type === 'optional_parameter_declaration') info.optional = true;
3200
3426
  let declaratorCursor = declarator;
3201
3427
  let variadicDeclarator = false;
@@ -3387,25 +3613,47 @@ function templatedDeclaration(node) {
3387
3613
  return current || node;
3388
3614
  }
3389
3615
 
3616
+ /**
3617
+ * The cv- and ref-qualifiers of a member function's implicit object
3618
+ * parameter (`int f() const &`, fix #427): { memberConst, refQualifier }.
3619
+ */
3620
+ function memberQualifiers(declarator) {
3621
+ let holder = declarator;
3622
+ if (holder?.type === 'qualified_identifier') holder = qualifiedOperatorCast(holder);
3623
+ if (holder?.type === 'operator_cast') {
3624
+ holder = holder.childForFieldName('declarator') ||
3625
+ (holder.namedChildren || []).find(child => child.type === 'abstract_function_declarator');
3626
+ }
3627
+ if (!holder) return {};
3628
+ const out = {};
3629
+ for (const child of holder.namedChildren || []) {
3630
+ if (child.type === 'type_qualifier' && child.text === 'const') out.memberConst = true;
3631
+ if (child.type === 'ref_qualifier') out.refQualifier = child.text.replace(/\s+/g, '');
3632
+ }
3633
+ return out;
3634
+ }
3635
+
3390
3636
  function memberFromNode(node, className, access, lines, mode) {
3391
3637
  const declarator = functionDeclarator(node);
3392
3638
  if (!declarator) return null;
3393
3639
  const identity = declaratorIdentity(declarator);
3394
3640
  if (!identity.name) return null;
3395
3641
  const paramsNode = parameterListOf(declarator);
3396
- const { startLine, endLine, indent } = nodeToLocation(node, lines);
3642
+ const { startLine, endLine, indent, sourceSpan } = nodeToLocation(node, lines);
3643
+ const ownerName = enclosingClass(node)?.name || className;
3397
3644
  const isConstructor = mode === 'cpp' &&
3398
- (identity.name === className || identity.name === `~${className}`);
3645
+ (identity.name === ownerName || identity.name === `~${ownerName}`);
3399
3646
  const modifiers = modifiersOf(node, access ? [access] : []);
3400
3647
  if (isConstructor && identity.name.startsWith('~')) modifiers.push('destructor');
3401
3648
  return {
3402
3649
  name: identity.name,
3403
- params: paramsNode ? nodeTextWithoutComments(paramsNode).replace(/^\(|\)$/g, '').trim() : '...',
3404
- paramsStructured: structuredParams(paramsNode),
3650
+ params: paramsNode ? paramsDisplayText(paramsNode, lines) : '...',
3651
+ paramsStructured: structuredParams(paramsNode, lines),
3405
3652
  returnType: isConstructor ? null :
3406
3653
  (identity.conversionType || returnTypeOf(templatedDeclaration(node))),
3407
3654
  startLine,
3408
3655
  endLine,
3656
+ sourceSpan,
3409
3657
  ...(identity.nameNode?.startPosition.row + 1 !== startLine && {
3410
3658
  nameLine: identity.nameNode.startPosition.row + 1,
3411
3659
  }),
@@ -3414,6 +3662,7 @@ function memberFromNode(node, className, access, lines, mode) {
3414
3662
  memberType: isConstructor ? 'constructor' : 'method',
3415
3663
  isMethod: true,
3416
3664
  isConstructor,
3665
+ ...(mode === 'cpp' && memberQualifiers(declarator)),
3417
3666
  ...(enclosingNamespace(node) && {
3418
3667
  namespace: enclosingNamespace(node),
3419
3668
  }),
@@ -3442,30 +3691,83 @@ function memberFromNode(node, className, access, lines, mode) {
3442
3691
  };
3443
3692
  }
3444
3693
 
3445
- function fieldMembers(node, access, lines) {
3694
+ function fieldMembers(node, access, lines, paths = null) {
3446
3695
  if (node.type !== 'field_declaration') return [];
3447
3696
  if (functionDeclarator(node)) return [];
3448
3697
  const typeNode = node.childForFieldName('type') ||
3449
3698
  node.namedChildren.find(child => TYPE_NODES.has(child.type));
3450
3699
  const fields = [];
3451
- for (const child of node.namedChildren || []) {
3452
- if (!IDENTIFIER_NODES.has(child.type) && child.type !== 'field_declarator') continue;
3700
+ const declarators = node.childrenForFieldName('declarator');
3701
+ // Members of an unnamed struct or union (fix #430): without a declarator
3702
+ // (an anonymous member) they are members of the enclosing class; with
3703
+ // declarators they are reached through them (`e.data.start`), recorded
3704
+ // as the member paths from the enclosing class.
3705
+ const nestedBody = (typeNode?.type === 'struct_specifier' || typeNode?.type === 'union_specifier') &&
3706
+ !typeNode.childForFieldName('name') ? typeNode.childForFieldName('body') : null;
3707
+ if (nestedBody) {
3708
+ const declaredNames = declarators.map(child => declaratorIdentity(child).name).filter(Boolean);
3709
+ const nestedPaths = declaredNames.length === 0 ? paths
3710
+ : (paths || [[]]).flatMap(path => declaredNames.map(name => [...path, name]));
3711
+ for (const entry of memberListEntries(nestedBody)) {
3712
+ if (entry.type === 'field_declaration') fields.push(...fieldMembers(entry, access, lines, nestedPaths));
3713
+ }
3714
+ }
3715
+ for (const child of declarators) {
3453
3716
  const identity = declaratorIdentity(child);
3454
3717
  if (!identity.name || identity.name === typeNode?.text) continue;
3455
- const { startLine, endLine, indent } = nodeToLocation(child, lines);
3718
+ const { startLine, endLine, indent, sourceSpan } = nodeToLocation(child, lines);
3719
+ // Pointer declarators of the field (`State* m_raw`): `->` on it
3720
+ // dereferences the pointer, on an object it calls operator-> (fix #427).
3721
+ let pointers = 0;
3722
+ let arrays = false;
3723
+ for (let current = child; current && !IDENTIFIER_NODES.has(current.type);) {
3724
+ if (current.type === 'pointer_declarator') pointers++;
3725
+ if (current.type === 'array_declarator' || current.type === 'function_declarator') arrays = true;
3726
+ current = current.childForFieldName('declarator') ||
3727
+ (current.namedChildren || []).find(next => next.type.endsWith('_declarator'));
3728
+ }
3456
3729
  fields.push({
3457
3730
  name: identity.name,
3458
3731
  startLine,
3459
3732
  endLine,
3733
+ sourceSpan,
3460
3734
  indent,
3461
3735
  modifiers: access ? [access] : [],
3462
3736
  memberType: 'field',
3463
- fieldType: typeNode?.text || null,
3737
+ fieldType: nestedBody ? null : typeNode?.text || null,
3738
+ ...(!arrays && { fieldPointers: pointers }),
3739
+ ...(paths?.length && { memberPaths: paths.map(path => [...path]) }),
3464
3740
  });
3465
3741
  }
3466
3742
  return fields;
3467
3743
  }
3468
3744
 
3745
+ /** The data members a member variable template declares (fix #430:
3746
+ * `template <class> static constexpr bool value = true;`). */
3747
+ function variableTemplateMembers(node, access, lines) {
3748
+ const declaration = (node.namedChildren || []).find(child => child.type === 'declaration');
3749
+ if (!declaration || functionDeclarator(declaration)) return [];
3750
+ const typeNode = declaration.childForFieldName('type');
3751
+ const members = [];
3752
+ for (const child of declaration.childrenForFieldName('declarator')) {
3753
+ const identity = declaratorIdentity(child);
3754
+ if (!identity.name) continue;
3755
+ const { startLine, endLine, indent, sourceSpan } = nodeToLocation(child, lines);
3756
+ members.push({
3757
+ name: identity.name,
3758
+ startLine,
3759
+ endLine,
3760
+ sourceSpan,
3761
+ indent,
3762
+ modifiers: modifiersOf(declaration, access ? [access] : []),
3763
+ memberType: 'field',
3764
+ fieldType: typeNode?.text || null,
3765
+ ...(templateParameterNames(declaration) && { templateParams: templateParameterNames(declaration) }),
3766
+ });
3767
+ }
3768
+ return members;
3769
+ }
3770
+
3469
3771
  /**
3470
3772
  * Member-list entries in document order, with preprocessor conditionals
3471
3773
  * opened (fix #385): `#ifdef _WIN32 bool f() {...} #else bool f() {...}
@@ -3603,11 +3905,12 @@ function classMembers(node, lines, mode) {
3603
3905
  (child.namedChildren || []).find(candidate =>
3604
3906
  candidate.type === 'identifier');
3605
3907
  if (nameNode?.text) {
3606
- const { startLine, endLine, indent } = nodeToLocation(nameNode, lines);
3908
+ const { startLine, endLine, indent, sourceSpan } = nodeToLocation(nameNode, lines);
3607
3909
  members.push({
3608
3910
  name: nameNode.text,
3609
3911
  startLine,
3610
3912
  endLine,
3913
+ sourceSpan,
3611
3914
  indent,
3612
3915
  modifiers: ['public'],
3613
3916
  memberType: 'field',
@@ -3634,7 +3937,9 @@ function classMembers(node, lines, mode) {
3634
3937
  const member = macroName ? null : memberFromNode(
3635
3938
  child, identity.ownerName || identity.name, effectiveAccess, lines, mode);
3636
3939
  if (member) members.push(member);
3637
- else if (!macroName) members.push(...fieldMembers(child, effectiveAccess, lines));
3940
+ else if (!macroName && child.type === 'template_declaration') {
3941
+ members.push(...variableTemplateMembers(child, effectiveAccess, lines));
3942
+ } else if (!macroName) members.push(...fieldMembers(child, effectiveAccess, lines));
3638
3943
  if (unknownAccess) {
3639
3944
  for (let i = before; i < members.length; i++) {
3640
3945
  members[i].accessAfterMacro = { ...unknownAccess };
@@ -3750,7 +4055,7 @@ function typedefEntries(node, lines) {
3750
4055
  const identity = declaratorIdentity(child);
3751
4056
  if (!identity.name || RESERVED_TYPE_KEYWORDS.has(identity.name)) continue;
3752
4057
  if (innerIsClass && (!innerClassName || innerClassName === identity.name)) continue;
3753
- const { startLine, endLine, indent } = nodeToLocation(child, lines);
4058
+ const { startLine, endLine, indent, sourceSpan } = nodeToLocation(child, lines);
3754
4059
  // A function-pointer typedef aliases a function shape, not the return
3755
4060
  // type — record no aliasOf for it.
3756
4061
  const aliasOf = unwrapDeclarator(child)
@@ -3761,6 +4066,7 @@ function typedefEntries(node, lines) {
3761
4066
  type: 'type',
3762
4067
  startLine,
3763
4068
  endLine,
4069
+ sourceSpan,
3764
4070
  ...(identity.nameNode?.startPosition.row + 1 !== startLine && {
3765
4071
  nameLine: identity.nameNode.startPosition.row + 1,
3766
4072
  }),
@@ -3778,6 +4084,18 @@ function typedefEntries(node, lines) {
3778
4084
  return entries;
3779
4085
  }
3780
4086
 
4087
+ function classNamespaceScope(node, named) {
4088
+ const parts = [];
4089
+ let anonymous = false;
4090
+ for (let parent = node.parent; parent; parent = parent.parent) {
4091
+ if (parent.type !== 'namespace_definition') continue;
4092
+ const name = parent.childForFieldName('name');
4093
+ if (name) parts.unshift(name.text);
4094
+ else { parts.unshift('<anonymous>'); anonymous = true; }
4095
+ }
4096
+ return { anonymous, key: anonymous ? `${parts.join('::')}\0${named || ''}` : named || '' };
4097
+ }
4098
+
3781
4099
  function findClassesInTree(code, tree, mode, sourceLines = null) {
3782
4100
  const lines = sourceLines || code.split('\n');
3783
4101
  const classes = [];
@@ -3786,7 +4104,7 @@ function findClassesInTree(code, tree, mode, sourceLines = null) {
3786
4104
  // the same name (forward declarations, `struct S` in a parameter or
3787
4105
  // field type position) are references to it, never second definitions.
3788
4106
  const bodiedNames = new Set();
3789
- const bodylessEntries = new Set();
4107
+ const bodylessEntries = new Map();
3790
4108
  const forwardDeclared = new Set();
3791
4109
  traverseTreeCached(tree.rootNode, node => {
3792
4110
  if (mode === 'cpp' && node.type === 'alias_declaration') {
@@ -3800,13 +4118,14 @@ function findClassesInTree(code, tree, mode, sourceLines = null) {
3800
4118
  (child.type === 'type_descriptor' ||
3801
4119
  TYPE_NODES.has(child.type)));
3802
4120
  if (!nameNode?.text) return false;
3803
- const { startLine, endLine, indent } =
4121
+ const { startLine, endLine, indent, sourceSpan } =
3804
4122
  nodeToLocation(node, lines);
3805
4123
  classes.push({
3806
4124
  name: nameNode.text,
3807
4125
  type: 'type',
3808
4126
  startLine,
3809
4127
  endLine,
4128
+ sourceSpan,
3810
4129
  indent,
3811
4130
  modifiers: ['public'],
3812
4131
  members: [],
@@ -3827,24 +4146,43 @@ function findClassesInTree(code, tree, mode, sourceLines = null) {
3827
4146
  const identity = classIdentity(node);
3828
4147
  if (!identity?.name) return true;
3829
4148
  const hasBody = !!node.childForFieldName('body');
4149
+ if (!hasBody) {
4150
+ let container = node.parent;
4151
+ while (container && (PP_CONDITIONAL_TYPES.has(container.type) ||
4152
+ container.type === 'template_declaration')) container = container.parent;
4153
+ if (!['translation_unit', 'declaration_list'].includes(container?.type)) return true;
4154
+ }
4155
+ const outer = identity.enclosingType ? null : mode === 'cpp' ? enclosingClass(node) : null;
4156
+ const enclosingType = identity.enclosingType || outer?.name || null;
4157
+ const namespace = identity.ownerQualifier
4158
+ ? [enclosingNamespace(node), identity.ownerQualifier].filter(Boolean).join('::')
4159
+ : enclosingNamespace(node);
4160
+ const namespaceScope = classNamespaceScope(node, namespace);
4161
+ const localScope = functionLocalClassScope(node);
4162
+ const generics = mode === 'cpp' ? templateParameterNames(node) : null;
4163
+ const branch = conditionalBranchLines(node);
4164
+ const tag = node.type === 'union_specifier' || node.type === 'enum_specifier'
4165
+ ? node.type : 'record';
4166
+ const scopeKey = `${namespaceScope.key}\0${enclosingType || ''}\0${identity.ownerName}` +
4167
+ `\0${generics?.length || 0}\0${tag}\0${localScope.lexicalScopeStartLine || 0}`;
3830
4168
  if (hasBody) {
3831
- bodiedNames.add(identity.name);
4169
+ // A same-scope declaration denotes this class even when its
4170
+ // body is conditional. Keep every alternative body, without an
4171
+ // extra incomplete class competing with its own definition.
4172
+ bodiedNames.add(scopeKey);
3832
4173
  } else {
3833
- if (bodiedNames.has(identity.name)) return true;
4174
+ if (bodiedNames.has(scopeKey)) return true;
3834
4175
  // A bodyless specifier is only a DECLARATION at declaration
3835
4176
  // level (`struct S;`); in a type position (`void f(struct S *)`)
3836
4177
  // it is a reference. One entry per opaque forward-declared name.
3837
- const parentType = node.parent?.type;
3838
- if (parentType !== 'translation_unit' && parentType !== 'declaration_list') return true;
3839
- if (forwardDeclared.has(identity.name)) return true;
3840
- forwardDeclared.add(identity.name);
4178
+ if (forwardDeclared.has(scopeKey)) return true;
4179
+ forwardDeclared.add(scopeKey);
3841
4180
  }
3842
4181
  const key = `${node.startIndex}:${identity.name}`;
3843
4182
  if (seen.has(key)) return false;
3844
4183
  seen.add(key);
3845
- const { startLine, endLine, indent } = nodeToLocation(node, lines);
3846
- let type = node.type.replace('_specifier', '');
3847
- if (type === 'union') type = 'type';
4184
+ const { startLine, endLine, indent, sourceSpan } = nodeToLocation(node, lines);
4185
+ const type = node.type.replace('_specifier', '');
3848
4186
  const baseClause = node.namedChildren.find(child => child.type === 'base_class_clause');
3849
4187
  const bases = baseClause
3850
4188
  ? baseClause.namedChildren
@@ -3858,19 +4196,18 @@ function findClassesInTree(code, tree, mode, sourceLines = null) {
3858
4196
  // A nested class names its enclosing class (fix #396): lexically
3859
4197
  // (`class A { struct B { .. }; }`) or by its qualified definition
3860
4198
  // (`struct A::B { .. }`, whose outer scopes qualify its namespace).
3861
- const outer = identity.enclosingType ? null : mode === 'cpp' ? enclosingClass(node) : null;
3862
- const enclosingType = identity.enclosingType || outer?.name || null;
3863
- const namespace = identity.ownerQualifier
3864
- ? [enclosingNamespace(node), identity.ownerQualifier].filter(Boolean).join('::')
3865
- : enclosingNamespace(node);
3866
4199
  const entry = {
3867
4200
  name: identity.name,
3868
4201
  ...(identity.ownerName !== identity.name && {
3869
4202
  specialization: identity.ownerName,
3870
4203
  }),
3871
4204
  type,
4205
+ ...(!hasBody && { forwardDeclaration: true }),
4206
+ ...(namespaceScope.anonymous && { anonymousNamespace: true }),
4207
+ ...(branch && { ppBranch: branch }),
3872
4208
  startLine,
3873
4209
  endLine,
4210
+ sourceSpan,
3874
4211
  ...(identity.nameNode?.startPosition.row + 1 !== startLine && {
3875
4212
  nameLine: identity.nameNode.startPosition.row + 1,
3876
4213
  }),
@@ -3881,21 +4218,21 @@ function findClassesInTree(code, tree, mode, sourceLines = null) {
3881
4218
  ...(namespace && { namespace }),
3882
4219
  ...(enclosingType && { enclosingType }),
3883
4220
  ...(bases.length > 0 && { extends: bases.join(', ') }),
3884
- ...(mode === 'cpp' && templateParameterNames(node) && {
3885
- generics: templateParameterNames(node),
4221
+ ...(generics && {
4222
+ generics,
3886
4223
  }),
3887
- ...functionLocalClassScope(node),
4224
+ ...localScope,
3888
4225
  docstring: extractJSDocstring(lines, startLine),
3889
4226
  };
3890
4227
  classes.push(entry);
3891
- if (!hasBody) bodylessEntries.add(entry);
4228
+ if (!hasBody) bodylessEntries.set(entry, scopeKey);
3892
4229
  return true;
3893
4230
  });
3894
4231
  // A forward declaration can precede its body. Filtering after the single
3895
4232
  // traversal preserves the old "body wins" result without paying a full
3896
4233
  // preliminary tree walk merely to discover future bodied names.
3897
4234
  return classes.filter(entry =>
3898
- !bodylessEntries.has(entry) || !bodiedNames.has(entry.name));
4235
+ !bodylessEntries.has(entry) || !bodiedNames.has(bodylessEntries.get(entry)));
3899
4236
  }
3900
4237
 
3901
4238
  function findClasses(code, parser, mode) {
@@ -3914,6 +4251,7 @@ function findClasses(code, parser, mode) {
3914
4251
 
3915
4252
  function findFunctionsInTree(code, tree, mode, sourceLines = null) {
3916
4253
  const lines = sourceLines || code.split('\n');
4254
+ let constructorDecorations = null;
3917
4255
  const lineStarts = [0];
3918
4256
  for (let index = 0; index < code.length; index++) {
3919
4257
  if (code.charCodeAt(index) === 10) lineStarts.push(index + 1);
@@ -3931,7 +4269,16 @@ function findFunctionsInTree(code, tree, mode, sourceLines = null) {
3931
4269
  }
3932
4270
  if (!FUNCTION_CONTAINERS.has(node.type)) return true;
3933
4271
  const declarator = functionDeclarator(node);
3934
- if (!declarator) return true;
4272
+ if (!declarator) {
4273
+ // `M(name) { .. }` read as a definition with no function
4274
+ // declarator (C, fix #427).
4275
+ const generated = node.type === 'function_definition' ? generatedCallableOf(node, lines) : null;
4276
+ if (generated) {
4277
+ functions.push(generated);
4278
+ return false;
4279
+ }
4280
+ return true;
4281
+ }
3935
4282
  // A macro invocation read as a function is not one (fix #385); an
3936
4283
  // invocation with a body at namespace scope defines the callable
3937
4284
  // the expansion declares around that body (fix #391).
@@ -3943,6 +4290,8 @@ function findFunctionsInTree(code, tree, mode, sourceLines = null) {
3943
4290
  const owner = enclosingClass(node);
3944
4291
  const identity = declaratorIdentity(declarator);
3945
4292
  if (!identity.name) return true;
4293
+ // A deduction guide declares no function (fix #427).
4294
+ if (mode === 'cpp' && deductionGuideOf(node)) return false;
3946
4295
  // A name joined by `##` is replacement-list text of a macro the
3947
4296
  // grammar could not read as a directive, never a declared function
3948
4297
  // (fix #387).
@@ -3961,9 +4310,13 @@ function findFunctionsInTree(code, tree, mode, sourceLines = null) {
3961
4310
  const location = nodeToLocation(node, lines);
3962
4311
  const startLine = location.startLine;
3963
4312
  const indent = location.indent;
3964
- const ownerName = identity.className && identity.className.replace(/<.*>$/s, '');
4313
+ const declaredType = templatedDeclaration(node).childForFieldName('type');
4314
+ const decorationType = mode === 'cpp' && identity.className &&
4315
+ !identity.name.startsWith('operator') && declaredType?.type === 'type_identifier' &&
4316
+ (constructorDecorations ||= new DecorationEvidence(require('./index').getParser(mode), code, tree))
4317
+ .defined(declaredType.text) === 'decoration';
3965
4318
  const isConstructor = mode === 'cpp' && !!identity.className &&
3966
- (identity.name === ownerName || identity.name === `~${ownerName}`);
4319
+ !identity.name.startsWith('operator') && (!declaredType || decorationType);
3967
4320
  const namespace = [enclosingNamespace(node), identity.className && identity.ownerQualifier]
3968
4321
  .filter(Boolean).join('::');
3969
4322
  const lexicalEnd = functionRangeEnd(
@@ -3972,6 +4325,7 @@ function findFunctionsInTree(code, tree, mode, sourceLines = null) {
3972
4325
  ? location.endLine
3973
4326
  : lineNumberAtIndex(lineStarts, Math.max(0, lexicalEnd - 1));
3974
4327
  const modifiers = modifiersOf(node);
4328
+ if (isConstructor && identity.name.startsWith('~')) modifiers.push('destructor');
3975
4329
  if (friendOf && !modifiers.includes('friend')) modifiers.unshift('friend');
3976
4330
  if (!modifiers.includes('static')) modifiers.push('export');
3977
4331
  const returnedConcreteType = mode === 'cpp' && variableTypes
@@ -3979,8 +4333,10 @@ function findFunctionsInTree(code, tree, mode, sourceLines = null) {
3979
4333
  : null;
3980
4334
  functions.push({
3981
4335
  name: identity.name,
3982
- params: paramsNode ? nodeTextWithoutComments(paramsNode).replace(/^\(|\)$/g, '').trim() : '...',
3983
- paramsStructured: structuredParams(paramsNode),
4336
+ sourceSpan: lexicalEnd == null ? location.sourceSpan : { ...location.sourceSpan,
4337
+ end: lexicalEnd, endLine, endColumn: lexicalEnd - lineStarts[endLine - 1] },
4338
+ params: paramsNode ? paramsDisplayText(paramsNode, lines) : '...',
4339
+ paramsStructured: structuredParams(paramsNode, lines),
3984
4340
  returnType: isConstructor ? null :
3985
4341
  (identity.conversionType || returnTypeOf(node)),
3986
4342
  startLine,
@@ -3996,6 +4352,7 @@ function findFunctionsInTree(code, tree, mode, sourceLines = null) {
3996
4352
  receiver: identity.className,
3997
4353
  isMethod: true,
3998
4354
  }),
4355
+ ...(mode === 'cpp' && identity.className && memberQualifiers(declarator)),
3999
4356
  ...(friendOf && { friendOf: friendOf.ownerName || friendOf.name }),
4000
4357
  ...(mode === 'cpp' && isTemplateDependentCallable(node) && {
4001
4358
  templateDependent: true,
@@ -4119,6 +4476,22 @@ function findFunctions(code, parser, mode) {
4119
4476
 
4120
4477
  function findStateObjectsInTree(tree, lines) {
4121
4478
  const states = [];
4479
+ // An unnamed enum without a typedef owner declares values directly
4480
+ // in its enclosing scope. A typedef's members already carry them.
4481
+ traverseTreeCached(tree.rootNode, node => {
4482
+ if (node.type !== 'enum_specifier' || classIdentity(node)) return true;
4483
+ const body = node.childForFieldName('body');
4484
+ for (const member of body?.namedChildren || []) {
4485
+ if (member.type !== 'enumerator') continue;
4486
+ const name = member.childForFieldName('name');
4487
+ if (!name) continue;
4488
+ const { startLine, endLine, indent, sourceSpan } = nodeToLocation(name, lines);
4489
+ const namespace = enclosingNamespace(node);
4490
+ states.push({ name: name.text, startLine, endLine, sourceSpan, indent,
4491
+ ...(namespace && { namespace }) });
4492
+ }
4493
+ return true;
4494
+ });
4122
4495
  traverseTreeCached(tree.rootNode, node => {
4123
4496
  if (node.type !== 'declaration') return true;
4124
4497
  if (functionDeclarator(node)) return false;
@@ -4127,18 +4500,29 @@ function findStateObjectsInTree(tree, lines) {
4127
4500
  // an #if/#else remain part of the source inventory even though only
4128
4501
  // one arm can exist in any particular build configuration.
4129
4502
  let scope = node.parent;
4130
- while (scope && /^preproc_/.test(scope.type)) scope = scope.parent;
4503
+ // A variable template's `template <..>` head is transparent too
4504
+ // (fix #430: `template <class T> inline constexpr bool v = ..;`);
4505
+ // a qualified declarator under it defines a class template's static
4506
+ // member (`template <class T> bool H<T>::x = false;`), no variable.
4507
+ let templated = false;
4508
+ while (scope && (/^preproc_/.test(scope.type) || scope.type === 'template_declaration')) {
4509
+ if (scope.type === 'template_declaration') templated = true;
4510
+ scope = scope.parent;
4511
+ }
4131
4512
  if (scope?.type !== 'translation_unit' && scope?.type !== 'declaration_list') return false;
4132
4513
  for (const child of node.namedChildren || []) {
4133
4514
  const identity = declaratorIdentity(child);
4134
4515
  if (!identity.name || TYPE_NODES.has(child.type)) continue;
4135
- const { startLine, endLine, indent } = nodeToLocation(child, lines);
4516
+ if (templated && qualifiedDeclarator(child)) continue;
4517
+ const { startLine, endLine, indent, sourceSpan } = nodeToLocation(child, lines);
4136
4518
  states.push({
4137
4519
  name: identity.name,
4138
4520
  startLine,
4139
4521
  endLine,
4522
+ sourceSpan,
4140
4523
  indent,
4141
4524
  modifiers: modifiersOf(node),
4525
+ ...(enclosingNamespace(node) && { namespace: enclosingNamespace(node) }),
4142
4526
  });
4143
4527
  }
4144
4528
  return false;
@@ -4400,8 +4784,10 @@ function findCppUsingFacts(tree) {
4400
4784
  if (parent.type === 'namespace_definition' ||
4401
4785
  parent.type === 'translation_unit') break;
4402
4786
  }
4787
+ const groups = conditionalGroupsOf(node);
4403
4788
  const location = {
4404
4789
  line: node.startPosition.row + 1,
4790
+ ...(groups.length > 0 && { ppGroups: groups }),
4405
4791
  ...(enclosingNamespace(node) && { namespace: enclosingNamespace(node) }),
4406
4792
  ...(block && {
4407
4793
  scopeStartLine: block.startPosition.row + 1,
@@ -4441,6 +4827,29 @@ const PP_CONDITIONAL_TYPES = new Set([
4441
4827
  'preproc_if', 'preproc_ifdef', 'preproc_elif', 'preproc_elifdef', 'preproc_else',
4442
4828
  ]);
4443
4829
 
4830
+ /** Original preprocessor groups containing a declaration (fix #413). */
4831
+ function conditionalGroupsOf(node) {
4832
+ const groups = [];
4833
+ const seen = new Set();
4834
+ for (let up = node.parent; up; up = up.parent) {
4835
+ if (!PP_CONDITIONAL_TYPES.has(up.type)) continue;
4836
+ let head = up;
4837
+ while (head.parent && PP_CONDITIONAL_TYPES.has(head.parent.type) &&
4838
+ sameNode(head.parent.childForFieldName('alternative'), head)) head = head.parent;
4839
+ if (seen.has(head.startIndex) || isIncludeGuard(head)) continue;
4840
+ seen.add(head.startIndex);
4841
+ const branches = [];
4842
+ for (let arm = head; arm; arm = arm.childForFieldName('alternative')) {
4843
+ const next = arm.childForFieldName('alternative');
4844
+ branches.push([arm.startPosition.row + 1,
4845
+ next ? next.startPosition.row : arm.endPosition.row + 1]);
4846
+ }
4847
+ groups.push({ start: head.startPosition.row + 1,
4848
+ end: head.endPosition.row + 1, branches });
4849
+ }
4850
+ return groups;
4851
+ }
4852
+
4444
4853
  /** `#ifndef G` whose first directive is `#define G` and that has no else. */
4445
4854
  function isIncludeGuard(node) {
4446
4855
  if (node.type !== 'preproc_ifdef' || node.child(0)?.type !== '#ifndef') return false;
@@ -4824,7 +5233,22 @@ function generatedCallableOf(node, lines) {
4824
5233
  let macroNode;
4825
5234
  let argsNode;
4826
5235
  let body;
4827
- if (node.type === 'function_definition') {
5236
+ if (node.type === 'function_definition' &&
5237
+ node.childForFieldName('declarator')?.type === 'parenthesized_declarator') {
5238
+ // C reads `M(name) { .. }` as a definition of `name` returning the
5239
+ // type `M` (fix #427); a function definition needs a function
5240
+ // declarator, so the shape is an invocation with a body.
5241
+ const declarator = node.childForFieldName('declarator');
5242
+ const typeNode = node.childForFieldName('type');
5243
+ body = node.childForFieldName('body');
5244
+ if (body?.type !== 'compound_statement' || typeNode?.type !== 'type_identifier' ||
5245
+ declarator.namedChildCount !== 1 || declarator.namedChild(0).type !== 'identifier' ||
5246
+ (node.namedChildren || []).some(child => child.type === 'storage_class_specifier' ||
5247
+ child.type === 'type_qualifier')) return null;
5248
+ macroNode = { type: 'identifier', text: typeNode.text };
5249
+ argsNode = declarator;
5250
+ head = node;
5251
+ } else if (node.type === 'function_definition') {
4828
5252
  const declarator = node.childForFieldName('declarator');
4829
5253
  body = node.childForFieldName('body');
4830
5254
  if (body?.type !== 'compound_statement' || declarator?.type !== 'function_declarator') return null;
@@ -4882,20 +5306,100 @@ function generatedCallableOf(node, lines) {
4882
5306
  const startLine = head.startPosition.row + 1;
4883
5307
  const endLine = body.endPosition.row + 1;
4884
5308
  const namespace = enclosingNamespace(head);
5309
+ // The function the expansion declares, when the project defines the
5310
+ // macro (fix #427): the file's own definition in effect, else the
5311
+ // headers' function-head macros.
5312
+ let templates = definition ? [definition] : null;
5313
+ if (!templates && externalMacroContext?.functionHeads?.has(macroName)) {
5314
+ templates = externalMacroContext.functionHeads.get(macroName);
5315
+ externalMacroContext.consulted?.add(macroName);
5316
+ }
5317
+ const expanded = templates && expandedFunctionHead(templates, argsNode.text, isCppTree(node.tree));
4885
5318
  return {
4886
- name,
4887
- params: '...',
4888
- returnType: null,
5319
+ name: expanded?.name || name,
5320
+ params: expanded?.params ?? '...',
5321
+ ...(expanded && { paramsStructured: expanded.paramsStructured }),
5322
+ returnType: expanded?.returnType || null,
4889
5323
  startLine,
4890
5324
  endLine,
4891
5325
  indent: head.startPosition.column,
4892
- modifiers: [],
5326
+ modifiers: expanded?.modifiers || [],
4893
5327
  ...(namespace && { namespace }),
4894
- generatedByMacro: { name: macroName, ...(names.length > 0 && { args: names }) },
5328
+ generatedByMacro: { name: macroName, ...(names.length > 0 && { args: names }),
5329
+ ...(expanded && { expanded: true }) },
4895
5330
  docstring: extractJSDocstring(lines, startLine),
4896
5331
  };
4897
5332
  }
4898
5333
 
5334
+ /**
5335
+ * The function a function-like macro invocation's expansion declares (fix
5336
+ * #427): the replacement list with the invocation's arguments substituted
5337
+ * (`#` stringizes, `##` pastes), read as a function head. Its name,
5338
+ * parameters, return type and storage class, when every definition gives
5339
+ * one unqualified function; null otherwise (a nested macro, a member, a
5340
+ * head the grammar cannot read).
5341
+ */
5342
+ function expandedFunctionHead(templates, argsText, cpp) {
5343
+ const args = splitInvocationArguments(argsText).map(arg => arg.trim());
5344
+ let result = null;
5345
+ for (const template of templates) {
5346
+ const params = template.params || [];
5347
+ const variadicIndex = params.indexOf('__VA_ARGS__');
5348
+ if (variadicIndex < 0 ? args.length !== params.length && !(params.length === 0 && args.length === 1 && !args[0])
5349
+ : args.length < variadicIndex) return null;
5350
+ const valueOf = param => {
5351
+ const at = params.indexOf(param);
5352
+ if (at < 0) return null;
5353
+ return at === variadicIndex ? args.slice(at).join(', ') : (args[at] ?? '');
5354
+ };
5355
+ const tokens = lexPP(String(template.body || ''));
5356
+ const out = [];
5357
+ for (let i = 0; i < tokens.length; i++) {
5358
+ const token = tokens[i];
5359
+ if (token.v === '#' && tokens[i + 1]?.k === 'id' && valueOf(tokens[i + 1].v) != null) {
5360
+ out.push(JSON.stringify(valueOf(tokens[i + 1].v)));
5361
+ i++;
5362
+ continue;
5363
+ }
5364
+ if (token.v === '##') {
5365
+ const right = tokens[i + 1];
5366
+ if (!right || out.length === 0) return null;
5367
+ const value = right.k === 'id' && valueOf(right.v) != null ? valueOf(right.v) : right.v;
5368
+ out[out.length - 1] = `${out[out.length - 1]}${value}`;
5369
+ i++;
5370
+ continue;
5371
+ }
5372
+ out.push(token.k === 'id' && valueOf(token.v) != null ? valueOf(token.v) : token.v);
5373
+ }
5374
+ const text = out.join(' ');
5375
+ const parser = require('./index').getParser(cpp ? 'cpp' : 'c');
5376
+ const tree = parser && safeParse(parser, `${text} { }`, undefined, PARSE_OPTIONS);
5377
+ try {
5378
+ const definition = tree && !tree.rootNode.hasError && tree.rootNode.namedChildCount === 1
5379
+ ? tree.rootNode.namedChild(0) : null;
5380
+ if (definition?.type !== 'function_definition') return null;
5381
+ const declarator = functionDeclarator(definition);
5382
+ const identity = declarator && declaratorIdentity(declarator);
5383
+ if (!identity?.name || identity.className || !/^[A-Za-z_]\w*$/.test(identity.name)) return null;
5384
+ const paramsNode = parameterListOf(declarator);
5385
+ const modifiers = modifiersOf(definition);
5386
+ if (!modifiers.includes('static')) modifiers.push('export');
5387
+ const head = {
5388
+ name: identity.name,
5389
+ params: paramsNode ? nodeTextWithoutComments(paramsNode).replace(/^\(|\)$/g, '').trim() : '',
5390
+ paramsStructured: structuredParams(paramsNode),
5391
+ returnType: returnTypeOf(definition),
5392
+ modifiers,
5393
+ };
5394
+ if (result && (result.name !== head.name || result.params !== head.params)) return null;
5395
+ result = head;
5396
+ } finally {
5397
+ tree?.delete?.();
5398
+ }
5399
+ }
5400
+ return result;
5401
+ }
5402
+
4899
5403
  function macroInvocationDeclaration(node) {
4900
5404
  if (!FUNCTION_CONTAINERS.has(node.type) || node.childForFieldName('type')) return false;
4901
5405
  const declarator = node.childForFieldName('declarator');
@@ -5081,6 +5585,8 @@ function buildVariableTypes(tree) {
5081
5585
  const memberReceiverUses = [];
5082
5586
  const ambiguousDirectInitializers = [];
5083
5587
  const autoElementInitializers = [];
5588
+ const autoCopyInitializers = [];
5589
+ const decltypeDeclarations = [];
5084
5590
  const declaratorStaticType = (type, declarator, holder) => {
5085
5591
  // The declaration's type node carries only the base type. Preserve
5086
5592
  // array rank from the AST declarator for overload arguments:
@@ -5096,7 +5602,10 @@ function buildVariableTypes(tree) {
5096
5602
  if (current.type === 'array_declarator') arrays++;
5097
5603
  for (const child of current.namedChildren || []) stack.push(child);
5098
5604
  }
5099
- if (arrays > 0) return `${type}${'[]'.repeat(arrays)}`;
5605
+ const qualifiers = (holder?.children || []).filter(child => child.type === 'type_qualifier')
5606
+ .map(child => child.text).join(' ');
5607
+ const qualified = qualifiers ? `${qualifiers} ${type}` : type;
5608
+ if (arrays > 0) return `${qualified}${'[]'.repeat(arrays)}`;
5100
5609
  // Pointer levels are part of the argument's type (fix #393):
5101
5610
  // `const char* format` passes a C string, never a `char`. The chain
5102
5611
  // of declarators is followed (an initializer's own declarators are
@@ -5105,7 +5614,7 @@ function buildVariableTypes(tree) {
5105
5614
  // decides overloads (`f(char*)` versus `f(const char*)`).
5106
5615
  let pointers = 0;
5107
5616
  for (let current = declarator; current;) {
5108
- if (current.type === 'function_declarator') return type;
5617
+ if (current.type === 'function_declarator') return qualified;
5109
5618
  if (current.type === 'pointer_declarator') pointers++;
5110
5619
  current = current.type === 'init_declarator' || current.type === 'pointer_declarator' ||
5111
5620
  current.type === 'reference_declarator' || current.type === 'parenthesized_declarator'
@@ -5114,12 +5623,18 @@ function buildVariableTypes(tree) {
5114
5623
  child.type.endsWith('declarator') || child.type === 'identifier'))
5115
5624
  : null;
5116
5625
  }
5117
- if (pointers === 0) return type;
5118
- const pointeeConst = (holder?.children || []).some(child =>
5119
- child.type === 'type_qualifier' && child.text === 'const');
5120
- return `${pointeeConst ? 'const ' : ''}${type} ${'*'.repeat(pointers)}`;
5626
+ if (pointers === 0) return qualified;
5627
+ return `${qualified} ${'*'.repeat(pointers)}`;
5121
5628
  };
5122
- const addBindings = (node, type, pointeeType, scope, declarators, containerElementType, typeNamespace) => {
5629
+ const addBindings = (node, type, pointeeType, scope, declarators, containerElementType, typeNamespace,
5630
+ constKnown = true) => {
5631
+ const writtenType = node.childForFieldName('type');
5632
+ const staticBase = writtenType && typeName(writtenType) === type ? writtenType.text : type;
5633
+ // The decl-specifier `const` qualifies the object a value or
5634
+ // reference names, or what a pointer points at (fix #427).
5635
+ const declConst = constKnown
5636
+ ? (node.children || []).some(child => child.type === 'type_qualifier' && child.text === 'const')
5637
+ : undefined;
5123
5638
  for (const declarator of declarators) {
5124
5639
  const identity = declaratorIdentity(declarator);
5125
5640
  if (!identity.name) continue;
@@ -5146,13 +5661,15 @@ function buildVariableTypes(tree) {
5146
5661
  ...(elementType && { elementType }),
5147
5662
  name: identity.name,
5148
5663
  type,
5149
- staticType: declaratorStaticType(type, declarator, node),
5664
+ staticType: declaratorStaticType(staticBase, declarator, node),
5665
+ staticOrigin: writtenType && typeName(writtenType) === type ? argumentTypeContext(writtenType) : null,
5150
5666
  origin: typeOrigin(node.type === 'declaration' &&
5151
5667
  (declarator.type === 'identifier' ||
5152
5668
  declarator.childForFieldName('value')?.type === 'new_expression')
5153
5669
  ? 'constructor' : 'annotation', declarator),
5154
5670
  ...(pointeeType && { pointeeType }),
5155
5671
  ...(pointers === 0 && arrays === 0 && { valueDeclarator: true }),
5672
+ ...(declConst !== undefined && arrays === 0 && pointers <= 1 && { declConst, pointers }),
5156
5673
  ...(typeNamespace?.name === type && { typeNamespace: typeNamespace.namespace }),
5157
5674
  declaredAt: node.type === 'parameter_declaration'
5158
5675
  ? scope.startIndex : declarator.startIndex,
@@ -5190,6 +5707,19 @@ function buildVariableTypes(tree) {
5190
5707
  }
5191
5708
  const typeNode = node.childForFieldName('type') ||
5192
5709
  (node.namedChildren || []).find(child => TYPE_NODES.has(child.type));
5710
+ // `decltype(x) v` declares v with x's declared type; any other
5711
+ // decltype (`decltype(auto)`, an expression) leaves v untyped, never
5712
+ // a type named by the decltype's text (fix #427).
5713
+ if (typeNode?.type === 'decltype' || (typeNode?.type === 'placeholder_type_specifier' &&
5714
+ (typeNode.namedChildren || []).some(child => child.type === 'decltype'))) {
5715
+ const operand = typeNode.type === 'decltype' && typeNode.namedChildCount === 1
5716
+ ? typeNode.namedChild(0) : null;
5717
+ if (operand?.type === 'identifier') {
5718
+ decltypeDeclarations.push({ node, operand, scope: variableBindingScope(node),
5719
+ declarators: variableDeclarators(node) });
5720
+ }
5721
+ return true;
5722
+ }
5193
5723
  const type = typeName(typeNode);
5194
5724
  if (type === 'auto') {
5195
5725
  // `auto &c = items[i]` binds the declared element (fix #359);
@@ -5203,6 +5733,20 @@ function buildVariableTypes(tree) {
5203
5733
  autoElementInitializers.push({
5204
5734
  node, declarator, root, scope: variableBindingScope(node),
5205
5735
  });
5736
+ continue;
5737
+ }
5738
+ // `auto x = b`, `auto &r = b`, `auto *p = &b` deduce b's type
5739
+ // (fix #434): an object copied, referenced, or pointed at by
5740
+ // the declarator's own pointer. A pointer source keeps its
5741
+ // value untyped (the declarator would read as an object).
5742
+ const declared = declarator.childForFieldName('declarator');
5743
+ const pointerDeclarator = declared?.type === 'pointer_declarator';
5744
+ const source = pointerDeclarator
5745
+ ? (value?.type === 'pointer_expression' && value.children.some(child => child.type === '&')
5746
+ ? value.childForFieldName('argument') : null)
5747
+ : value;
5748
+ if (source?.type === 'identifier') {
5749
+ autoCopyInitializers.push({ node, declarator, source, scope: variableBindingScope(node) });
5206
5750
  }
5207
5751
  }
5208
5752
  return true;
@@ -5295,6 +5839,40 @@ function buildVariableTypes(tree) {
5295
5839
  [candidate.declarator]);
5296
5840
  }
5297
5841
  }
5842
+ // In source order, each sees the bindings before it (fix #434).
5843
+ autoCopyInitializers.sort((a, b) => a.node.startIndex - b.node.startIndex);
5844
+ for (const candidate of autoCopyInitializers) {
5845
+ const at = candidate.source.startIndex;
5846
+ const sources = bindings.filter(binding =>
5847
+ binding.name === candidate.source.text &&
5848
+ binding.scopeStart <= at && at < binding.scopeEnd &&
5849
+ binding.declaredAt <= at);
5850
+ sources.sort((a, b) => (a.scopeEnd - a.scopeStart) - (b.scopeEnd - b.scopeStart) ||
5851
+ b.declaredAt - a.declaredAt);
5852
+ const source = sources[0];
5853
+ if (source?.type && source.valueDeclarator && !source.pointeeType) {
5854
+ addBindings(candidate.node, source.type, undefined, candidate.scope, [candidate.declarator],
5855
+ undefined, undefined, false);
5856
+ }
5857
+ }
5858
+ // In source order: `decltype(copy) c2` sees the binding `copy` got.
5859
+ decltypeDeclarations.sort((a, b) => a.node.startIndex - b.node.startIndex);
5860
+ for (const candidate of decltypeDeclarations) {
5861
+ const at = candidate.operand.startIndex;
5862
+ const operands = bindings.filter(binding =>
5863
+ binding.name === candidate.operand.text &&
5864
+ binding.scopeStart <= at && at < binding.scopeEnd &&
5865
+ binding.declaredAt <= at);
5866
+ operands.sort((a, b) => (a.scopeEnd - a.scopeStart) - (b.scopeEnd - b.scopeStart) ||
5867
+ b.declaredAt - a.declaredAt);
5868
+ const operand = operands[0];
5869
+ // An object (not a pointer) of a named type: the declared variable
5870
+ // has its type.
5871
+ if (operand?.type && operand.valueDeclarator && !operand.pointeeType) {
5872
+ addBindings(candidate.node, operand.type, undefined, candidate.scope, candidate.declarators,
5873
+ undefined, undefined, false);
5874
+ }
5875
+ }
5298
5876
  // Bindings are complete before call extraction. Partition by spelling
5299
5877
  // once: every argument and receiver lookup otherwise scans all locals
5300
5878
  // in the translation unit, including unrelated functions.
@@ -5318,6 +5896,7 @@ function buildVariableTypes(tree) {
5318
5896
  return {
5319
5897
  get: (name, atNode) => resolveBinding(name, atNode)?.type,
5320
5898
  getStatic: (name, atNode) => resolveBinding(name, atNode)?.staticType,
5899
+ getStaticOrigin: (name, atNode) => resolveBinding(name, atNode)?.staticOrigin,
5321
5900
  evidence: (name, atNode) => {
5322
5901
  const binding = resolveBinding(name, atNode);
5323
5902
  return { receiverTypeSource: binding?.origin?.source || 'unknown',
@@ -5327,6 +5906,14 @@ function buildVariableTypes(tree) {
5327
5906
  },
5328
5907
  getPointee: (name, atNode) =>
5329
5908
  resolveBinding(name, atNode)?.pointeeType,
5909
+ // Is the object `name` denotes (through `->` when `arrow`) const?
5910
+ // true / false where the declaration decides, else undefined.
5911
+ objectConst: (name, atNode, arrow) => {
5912
+ const binding = resolveBinding(name, atNode);
5913
+ if (!binding || binding.declConst === undefined) return undefined;
5914
+ if (arrow ? binding.pointers !== 1 : binding.pointers !== 0) return undefined;
5915
+ return binding.declConst;
5916
+ },
5330
5917
  // fix #379: `v->m()` on a variable declared as an object (no
5331
5918
  // pointer declarator) goes through the class's operator->: the
5332
5919
  // declared type is a smart pointer or iterator (possibly behind an
@@ -5396,14 +5983,20 @@ function staticArgumentKind(node, variableTypes) {
5396
5983
  }
5397
5984
  if (node.type === 'number_literal') {
5398
5985
  const text = node.text || '';
5986
+ // Integer literal zero is a null pointer constant, including its
5987
+ // base, digit separators and standard integer suffixes.
5988
+ if (/^(?:0[xX]0+|0[bB]0+|0+)(?:[uU](?:ll|LL|[lLzZ])?|(?:ll|LL|[lLzZ])[uU]?)?$/
5989
+ .test(text.replace(/'/g, ''))) return 'number:zero';
5990
+ const hexadecimal = /^0[xX]/.test(text);
5399
5991
  const floating = text.includes('.') ||
5400
5992
  /[pP][+-]?[0-9]/.test(text) ||
5401
- /[eE][+-]?[0-9]/.test(text) ||
5402
- /[fF]$/.test(text);
5993
+ !hexadecimal && (/[eE][+-]?[0-9]/.test(text) || /[fF]$/.test(text));
5403
5994
  return floating ? 'number:floating' : 'number:integer';
5404
5995
  }
5405
5996
  if (node.type === 'true' || node.type === 'false') return 'bool';
5406
- if (node.type === 'null' || node.type === 'nullptr') return 'null';
5997
+ // The grammar also calls NULL a null node, but that macro can expand
5998
+ // to integer zero. Only the keyword has nullptr's conversion rules.
5999
+ if (node.type === 'null' || node.type === 'nullptr') return node.text === 'nullptr' ? 'null' : 'expr';
5407
6000
  if (node.type === 'identifier') {
5408
6001
  const type = variableTypes?.getStatic(node.text, node) ||
5409
6002
  variableTypes?.get(node.text, node);
@@ -5414,21 +6007,21 @@ function staticArgumentKind(node, variableTypes) {
5414
6007
  (node.namedChildren || []).find(child =>
5415
6008
  TYPE_NODES.has(child.type) || child.type === 'type_descriptor');
5416
6009
  const type = typeName(typeNode);
5417
- return type ? `type:${type}` : 'expr';
6010
+ return type ? `type:${typeNode.text}` : 'expr';
5418
6011
  }
5419
6012
  if (node.type === 'new_expression') {
5420
6013
  const typeNode = node.childForFieldName('type') ||
5421
6014
  (node.namedChildren || []).find(child =>
5422
6015
  TYPE_NODES.has(child.type) || child.type === 'type_descriptor');
5423
6016
  const type = typeName(typeNode);
5424
- return type ? `type:${type}` : 'expr';
6017
+ return type ? `type:${typeNode.text}*` : 'expr';
5425
6018
  }
5426
6019
  if (node.type === 'cast_expression') {
5427
6020
  const typeNode = node.childForFieldName('type') ||
5428
6021
  (node.namedChildren || []).find(child =>
5429
6022
  TYPE_NODES.has(child.type) || child.type === 'type_descriptor');
5430
6023
  const type = typeName(typeNode);
5431
- return type ? `type:${type}` : 'expr';
6024
+ return type ? `type:${typeNode.text}` : 'expr';
5432
6025
  }
5433
6026
  if (node.type === 'call_expression') {
5434
6027
  const fnNode = node.childForFieldName('function');
@@ -5439,7 +6032,7 @@ function staticArgumentKind(node, variableTypes) {
5439
6032
  (fnNode?.namedChildren || []).find(child =>
5440
6033
  child.type === 'type_descriptor' || TYPE_NODES.has(child.type));
5441
6034
  const type = typeName(typeNode);
5442
- if (type) return `type:${type}`;
6035
+ if (type) return `type:${typeNode.text}`;
5443
6036
  }
5444
6037
  // Bare-identifier producers are name-resolvable (fix #299B): mark
5445
6038
  // them `bcall:` so the overload discipline can type the argument
@@ -5458,13 +6051,34 @@ function staticArgumentKind(node, variableTypes) {
5458
6051
  return 'expr';
5459
6052
  }
5460
6053
 
6054
+ function argumentTypeContext(node) {
6055
+ const typeParameters = [];
6056
+ for (const [name, ranges] of templateScopes(node.tree)) {
6057
+ if (ranges.some(range => range.start <= node.startIndex && node.endIndex <= range.end)) typeParameters.push(name);
6058
+ }
6059
+ return { line: node.startPosition.row + 1, ...(typeParameters.length && { typeParameters }) };
6060
+ }
6061
+
6062
+ function argumentTypeOrigin(node, variableTypes) {
6063
+ if (node.type === 'parenthesized_expression') return argumentTypeOrigin(node.namedChild(0), variableTypes);
6064
+ if (node.type === 'identifier') return variableTypes?.getStaticOrigin(node.text, node) || null;
6065
+ return argumentTypeContext(node);
6066
+ }
6067
+
6068
+ function argumentFacts(values, variableTypes) {
6069
+ const argKinds = values.map(value => staticArgumentKind(value, variableTypes));
6070
+ const argTypeOrigins = values.map((value, i) => argKinds[i].startsWith('type:')
6071
+ ? argumentTypeOrigin(value, variableTypes) : null);
6072
+ return { argKinds, ...(argTypeOrigins.some(Boolean) && { argTypeOrigins }) };
6073
+ }
6074
+
5461
6075
  function callArguments(node, variableTypes) {
5462
6076
  const args = node.childForFieldName('arguments');
5463
6077
  if (!args) return { argCount: 0, argKinds: [] };
5464
6078
  const values = args.namedChildren.filter(child => !child.type.endsWith('comment'));
5465
6079
  return {
5466
6080
  argCount: values.length,
5467
- argKinds: values.map(value => staticArgumentKind(value, variableTypes)),
6081
+ ...argumentFacts(values, variableTypes),
5468
6082
  firstArg: values[0],
5469
6083
  };
5470
6084
  }
@@ -5485,8 +6099,11 @@ function templateScopes(tree) {
5485
6099
  if (!parameter.type.endsWith('parameter_declaration') ||
5486
6100
  parameter.type === 'parameter_declaration' ||
5487
6101
  parameter.type === 'optional_parameter_declaration') continue;
5488
- const nameNode = parameter.childForFieldName('name') ||
5489
- (parameter.namedChildren || []).find(child =>
6102
+ const namedParameter = parameter.type === 'template_template_parameter_declaration'
6103
+ ? parameter.namedChildren.find(child => child.type.endsWith('type_parameter_declaration'))
6104
+ : parameter;
6105
+ const nameNode = namedParameter?.childForFieldName('name') ||
6106
+ (namedParameter?.namedChildren || []).find(child =>
5490
6107
  child.type === 'type_identifier');
5491
6108
  if (nameNode?.text) names.push(nameNode.text);
5492
6109
  }
@@ -5558,12 +6175,12 @@ function callIdentity(fnNode) {
5558
6175
  const globalQualified = fnNode.text.startsWith('::') &&
5559
6176
  (!scopeNode || sameNode(scopeNode, rawNameNode));
5560
6177
  if (globalQualified) {
6178
+ const nested = callIdentity(rawNameNode);
5561
6179
  return {
5562
- name: rawNameNode?.text,
5563
- nameNode: rawNameNode,
5564
- isMethod: false,
6180
+ ...nested,
5565
6181
  isPathCall: true,
5566
- globalQualified: true,
6182
+ ...(nested.receiver ? { receiver: `::${nested.receiver}` }
6183
+ : { isMethod: false, globalQualified: true }),
5567
6184
  };
5568
6185
  }
5569
6186
  const nested = rawNameNode?.type === 'template_function' ||
@@ -5630,8 +6247,7 @@ function recoveredExplicitCallOperator(node, variableTypes) {
5630
6247
  isMethod: false,
5631
6248
  explicitTemplateCall: true,
5632
6249
  argCount: values.length,
5633
- argKinds: values.map(value =>
5634
- staticArgumentKind(value, variableTypes)),
6250
+ ...argumentFacts(values, variableTypes),
5635
6251
  enclosingFunction: enclosingFunctionOf(node),
5636
6252
  };
5637
6253
  }
@@ -5666,8 +6282,7 @@ function recoveredExplicitCallOperator(node, variableTypes) {
5666
6282
  isMethod: false,
5667
6283
  explicitTemplateCall: true,
5668
6284
  argCount: values.length,
5669
- argKinds: values.map(value =>
5670
- staticArgumentKind(value, variableTypes)),
6285
+ ...argumentFacts(values, variableTypes),
5671
6286
  enclosingFunction: enclosingFunctionOf(node),
5672
6287
  };
5673
6288
  }
@@ -5763,26 +6378,105 @@ function enclosingMacroArguments(node) {
5763
6378
  * #401): the shape of `T v(X());` that is an object initialized by a call
5764
6379
  * whenever X is not a type. null for any other parameter.
5765
6380
  */
5766
- function vexingParseCallName(node) {
6381
+ function vexingParseCallIdentity(node) {
5767
6382
  const list = node.parent;
5768
6383
  const declarator = list?.type === 'parameter_list' ? list.parent : null;
5769
6384
  const declaration = declarator?.type === 'function_declarator' ? declarator.parent : null;
5770
6385
  if (declaration?.type !== 'declaration' || declaration.parent?.type !== 'compound_statement') return null;
5771
6386
  const typeNode = node.childForFieldName('type');
5772
6387
  const abstract = node.childForFieldName('declarator');
5773
- if (typeNode?.type !== 'type_identifier' || abstract?.type !== 'abstract_function_declarator') return null;
6388
+ if (!['type_identifier', 'template_type'].includes(typeNode?.type) ||
6389
+ abstract?.type !== 'abstract_function_declarator') return null;
5774
6390
  const params = abstract.childForFieldName('parameters');
5775
6391
  if (!params || params.namedChildCount !== 0) return null;
5776
6392
  if (node.namedChildren.some(child => child.type === 'type_qualifier')) return null;
5777
- return typeNode.text;
6393
+ const identity = callIdentity(typeNode);
6394
+ // A template-template parameter is a type even when a project function
6395
+ // has the same name. Its empty function-type parameter is no call.
6396
+ if (typeNode.type === 'template_type' && enclosedByTemplateParameter(node, identity.name)) return null;
6397
+ return identity;
6398
+ }
6399
+
6400
+ function constructorFieldNames(owner) {
6401
+ const fields = new Set();
6402
+ const visit = body => {
6403
+ for (const field of body?.namedChildren || []) {
6404
+ if (field.type !== 'field_declaration' || functionDeclarator(field)) continue;
6405
+ const declarators = field.namedChildren.filter(child =>
6406
+ child.type === 'field_identifier' || child.type.endsWith('declarator'));
6407
+ for (const child of declarators) {
6408
+ const identity = declaratorIdentity(child);
6409
+ if (identity.name) fields.add(identity.name);
6410
+ }
6411
+ const aggregate = field.childForFieldName('type');
6412
+ if (!declarators.length && ['union_specifier', 'struct_specifier'].includes(aggregate?.type) &&
6413
+ !aggregate.childForFieldName('name')) visit(aggregate.childForFieldName('body'));
6414
+ }
6415
+ };
6416
+ visit(owner.childForFieldName('body'));
6417
+ return fields;
6418
+ }
6419
+
6420
+ function implicitConstructorInfo(node, ownerName, owner) {
6421
+ const declarator = functionDeclarator(node);
6422
+ if (!declarator || declaratorIdentity(declarator).name !== ownerName) return null;
6423
+ const name = declarator.childForFieldName('declarator');
6424
+ if (!name) return null;
6425
+ const { baseSpelling } = require('./constructor-bases');
6426
+ const fields = constructorFieldNames(owner);
6427
+ const initializers = node.namedChildren.find(child => child.type === 'field_initializer_list');
6428
+ const initialized = (initializers?.namedChildren || []).map(child => baseSpelling(child.namedChild(0), '::'))
6429
+ .filter(Boolean).map(entry => ({ ...entry, field: !entry.qualifier && fields.has(entry.name) }));
6430
+ const defaulted = node.namedChildren.some(child => child.type === 'default_method_clause');
6431
+ const parameters = parameterListOf(declarator);
6432
+ return { node, name, initialized,
6433
+ params: parameters ? nodeTextWithoutComments(parameters).replace(/^\(|\)$/g, '').trim() : null,
6434
+ body: node.childForFieldName('body') || (defaulted ? node : null),
6435
+ potential: defaulted,
6436
+ suppressed: node.namedChildren.some(child => child.type === 'delete_method_clause') ||
6437
+ defaulted && parameterListOf(declarator)?.namedChildCount > 0 ||
6438
+ initialized.some(entry => !entry.field && !entry.qualifier && entry.name === ownerName) };
6439
+ }
6440
+
6441
+ /**
6442
+ * The call a mem-initializer of an out-of-class constructor definition makes
6443
+ * (fix #421), or null when the definition is no constructor. Its own class
6444
+ * name (unqualified, or qualified as the definition qualifies the class)
6445
+ * delegates; any other name is a base or a member, decided by the engine.
6446
+ */
6447
+ function outOfClassMemInitializer(definition, target, args, variableTypes) {
6448
+ const identity = declaratorIdentity(functionDeclarator(definition));
6449
+ const className = String(identity.className || '').replace(/<.*$/s, '').trim();
6450
+ if (!identity.name || identity.name !== className) return null;
6451
+ const name = typeName(target);
6452
+ if (!name) return null;
6453
+ const scope = target.childForFieldName('scope');
6454
+ const delegates = name === className && (!scope || scope.text.replace(/\s+/g, '') === identity.ownerQualifier);
6455
+ const values = args.namedChildren.filter(child => !child.type.endsWith('comment'));
6456
+ return { name, isMethod: false, isConstructor: true, constructorSyntax: 'delegation',
6457
+ ctorDelegation: delegates ? 'this' : 'super',
6458
+ ...(scope && { constructorQualifier: scope.text }),
6459
+ ...(!delegates && { memberInitializer: true }),
6460
+ argCount: values.length, ...argumentFacts(values, variableTypes) };
6461
+ }
6462
+
6463
+ function implicitOwnerScope(node) {
6464
+ const identity = classIdentity(node);
6465
+ const namespace = identity.ownerQualifier
6466
+ ? [enclosingNamespace(node), identity.ownerQualifier].filter(Boolean).join('::') : enclosingNamespace(node);
6467
+ return { namespace, enclosingType: identity.enclosingType || enclosingClass(node)?.name || null,
6468
+ anonymousNamespace: classNamespaceScope(node, namespace).anonymous };
5778
6469
  }
5779
6470
 
5780
6471
  function findCallsInTree(code, parser, _options = {}, existingTree = null,
5781
- includeMacroBodies = true) {
6472
+ includeMacroBodies = true, mode = 'cpp') {
5782
6473
  const tree = existingTree || parseTree(parser, code);
5783
6474
  const variableTypes = buildVariableTypes(tree);
5784
6475
  const fieldTypes = variableTypes.fieldTypes;
5785
6476
  const calls = [];
6477
+ const implicitConfig = require('./index').langTraits(mode)?.implicitBaseConstruction;
6478
+ const implicitOwners = implicitConfig ? getCachedNodeList(tree.rootNode).nodes
6479
+ .filter(node => implicitConfig.classes.includes(node.type)) : null;
5786
6480
  traverseTreeCached(tree.rootNode, node => {
5787
6481
  const recoveredOperator = recoveredExplicitCallOperator(
5788
6482
  node, variableTypes);
@@ -5794,10 +6488,12 @@ function findCallsInTree(code, parser, _options = {}, existingTree = null,
5794
6488
  // grammar reads a function declaration whose parameter is an
5795
6489
  // unnamed function type; when `CurrentOptions` is no type (decided at
5796
6490
  // query time) it is a call initializing the object.
5797
- if (node.type === 'parameter_declaration' && vexingParseCallName(node)) {
5798
- const nameNode = node.childForFieldName('type');
6491
+ const declarationCall = node.type === 'parameter_declaration' ? vexingParseCallIdentity(node) : null;
6492
+ if (declarationCall) {
6493
+ const nameNode = declarationCall.nameNode;
5799
6494
  calls.push({
5800
- name: nameNode.text,
6495
+ name: declarationCall.name,
6496
+ ...(declarationCall.explicitTemplateCall && { explicitTemplateCall: true }),
5801
6497
  line: nameNode.startPosition.row + 1,
5802
6498
  column: nameNode.startPosition.column,
5803
6499
  callStart: node.startIndex,
@@ -5911,6 +6607,15 @@ function findCallsInTree(code, parser, _options = {}, existingTree = null,
5911
6607
  ...(functionNode?.type === 'identifier' &&
5912
6608
  cLocalBindingNames(tree).has(identity.name) &&
5913
6609
  cBareNameShadowedByLocal(node, identity.name) && { localShadow: true }),
6610
+ // `(N)(x)` (fix #427): a call of N, or a cast to the type N;
6611
+ // which one depends on what N names where it is written.
6612
+ ...(functionNode?.type === 'parenthesized_expression' && functionNode.namedChildCount === 1 &&
6613
+ ['identifier', 'qualified_identifier'].includes(functionNode.namedChild(0).type) && {
6614
+ parenthesizedCallee: true,
6615
+ ...(functionNode.namedChild(0).type === 'identifier' &&
6616
+ cLocalBindingNames(tree).has(identity.name) &&
6617
+ cBareNameShadowedByLocal(node, identity.name) && { localShadow: true }),
6618
+ }),
5914
6619
  ...(identity.receiver && { receiver: identity.receiver }),
5915
6620
  ...(identity.isPathCall && { isPathCall: true }),
5916
6621
  ...(identity.globalQualified && { globalQualified: true }),
@@ -5929,6 +6634,12 @@ function findCallsInTree(code, parser, _options = {}, existingTree = null,
5929
6634
  // as an object records its declared type for that lookup.
5930
6635
  ...(identity.pointerAccess && receiverNode?.type === 'identifier' &&
5931
6636
  receiverFields.length === 0 && { receiverArrow: true }),
6637
+ // The implicit object's constness where the declaration of
6638
+ // a plain variable receiver decides it (fix #427).
6639
+ ...(receiverNode?.type === 'identifier' && receiverFields.length === 0 && !arrowThroughObject &&
6640
+ variableTypes.objectConst?.(receiverNode.text, node, identity.pointerAccess) !== undefined && {
6641
+ receiverConst: variableTypes.objectConst(receiverNode.text, node, identity.pointerAccess),
6642
+ }),
5932
6643
  ...(arrowThroughObject && variableTypes.get(receiverRoot, node) && {
5933
6644
  receiverArrowObject: variableTypes.get(receiverRoot, node),
5934
6645
  receiverArrowObjectAt: variableTypes.evidence(receiverRoot, node)
@@ -5963,10 +6674,14 @@ function findCallsInTree(code, parser, _options = {}, existingTree = null,
5963
6674
  receiverField: receiverFields[0],
5964
6675
  receiverFields,
5965
6676
  ...(receiverRootType && { receiverRootType }),
6677
+ // `field->m()` (fix #427): the member is looked up in
6678
+ // what `->` yields from the last field's declared type.
6679
+ ...(identity.pointerAccess && { receiverFieldArrow: true }),
5966
6680
  }),
5967
6681
  ...(assignedTo && { assignedTo }),
5968
6682
  argCount: args.argCount,
5969
6683
  argKinds: args.argKinds,
6684
+ ...(args.argTypeOrigins && { argTypeOrigins: args.argTypeOrigins }),
5970
6685
  enclosingFunction,
5971
6686
  ...(first && {
5972
6687
  firstStringArg: first.value,
@@ -5983,7 +6698,10 @@ function findCallsInTree(code, parser, _options = {}, existingTree = null,
5983
6698
  // parenthesized-value shape and recover its base/path identity.
5984
6699
  const typeNode = node.childForFieldName('type');
5985
6700
  const valueNode = node.childForFieldName('value');
5986
- if (typeNode?.type === 'type_descriptor' &&
6701
+ // A declarator or a qualifier in the parentheses (`(BYTE*)(p)`,
6702
+ // `(const T)(x)`) only spells a type (fix #427).
6703
+ if (typeNode?.type === 'type_descriptor' && !typeNode.childForFieldName('declarator') &&
6704
+ !(typeNode.namedChildren || []).some(child => child.type === 'type_qualifier') &&
5987
6705
  valueNode?.type === 'parenthesized_expression') {
5988
6706
  const identity = callIdentity(typeNode);
5989
6707
  if (identity.name) {
@@ -5997,15 +6715,50 @@ function findCallsInTree(code, parser, _options = {}, existingTree = null,
5997
6715
  isMethod: identity.isMethod,
5998
6716
  ...(identity.receiver && { receiver: identity.receiver }),
5999
6717
  ...(identity.isPathCall && { isPathCall: true }),
6718
+ parenthesizedCallee: true,
6000
6719
  argCount: values.length,
6001
- argKinds: values.map(value =>
6002
- staticArgumentKind(value, variableTypes)),
6720
+ ...argumentFacts(values, variableTypes),
6003
6721
  enclosingFunction: enclosingFunctionOf(node),
6004
6722
  });
6005
6723
  return false;
6006
6724
  }
6007
6725
  }
6008
6726
  }
6727
+ if (mode === 'cpp' && node.type === 'field_initializer') {
6728
+ let owner = node.parent, definition = null;
6729
+ while (owner && !['class_specifier', 'struct_specifier', 'union_specifier'].includes(owner.type)) {
6730
+ if (!definition && owner.type === 'function_definition') definition = owner;
6731
+ owner = owner.parent;
6732
+ }
6733
+ const target = node.namedChild(0), args = node.namedChildren.find(child => child.type === 'argument_list');
6734
+ if (!owner && definition && target && args) {
6735
+ // An out-of-class constructor definition (`W::W(..) : W(..)`,
6736
+ // `D::D() : B(..)`, fix #421): its class body is in another
6737
+ // place, so a name other than the class's own may be a
6738
+ // member. The engine decides that by the class at query time.
6739
+ const outOfClass = outOfClassMemInitializer(definition, target, args, variableTypes);
6740
+ if (outOfClass) calls.push({ ...outOfClass, line: target.startPosition.row + 1,
6741
+ column: target.startPosition.column, callStart: node.startIndex, callEnd: node.endIndex,
6742
+ enclosingFunction: enclosingFunctionOf(node) });
6743
+ }
6744
+ if (owner && target && args) {
6745
+ const hidden = constructorFieldNames(owner).has(target.text);
6746
+ const self = owner.childForFieldName('name');
6747
+ const bases = owner.namedChildren.find(child => child.type === 'base_class_clause')?.namedChildren || [];
6748
+ const written = [self, ...bases].filter(Boolean).find(type => typeName(type) === typeName(target));
6749
+ if (!hidden && written) {
6750
+ const values = args.namedChildren.filter(child => !child.type.endsWith('comment'));
6751
+ const scope = target.childForFieldName('scope') || written.childForFieldName('scope');
6752
+ calls.push({ name: typeName(target), line: target.startPosition.row + 1,
6753
+ column: target.startPosition.column, callStart: node.startIndex, callEnd: node.endIndex,
6754
+ isMethod: false, isConstructor: true, constructorSyntax: 'delegation',
6755
+ ctorDelegation: sameNode(written, self) ? 'this' : 'super',
6756
+ ...(scope && { constructorQualifier: scope.text }),
6757
+ argCount: values.length, ...argumentFacts(values, variableTypes),
6758
+ enclosingFunction: enclosingFunctionOf(node) });
6759
+ }
6760
+ }
6761
+ }
6009
6762
  if (node.type === 'new_expression') {
6010
6763
  const typeNode = node.childForFieldName('type') ||
6011
6764
  node.namedChildren.find(child => child.type === 'type_identifier' ||
@@ -6020,18 +6773,87 @@ function findCallsInTree(code, parser, _options = {}, existingTree = null,
6020
6773
  column: typeNode.startPosition.column,
6021
6774
  isMethod: false,
6022
6775
  isConstructor: true,
6776
+ ...(mode === 'cpp' && node.childForFieldName('arguments')?.type === 'initializer_list' && {
6777
+ constructorSyntax: 'list',
6778
+ }),
6779
+ ...(typeNode.childForFieldName('scope') && {
6780
+ constructorQualifier: typeNode.childForFieldName('scope').text,
6781
+ }),
6023
6782
  argCount: args.argCount,
6024
6783
  argKinds: args.argKinds,
6784
+ ...(args.argTypeOrigins && { argTypeOrigins: args.argTypeOrigins }),
6025
6785
  enclosingFunction: enclosingFunctionOf(node),
6026
6786
  });
6027
6787
  }
6028
6788
  }
6789
+ if (mode === 'cpp' && node.type === 'init_declarator' && node.parent?.type === 'declaration') {
6790
+ const declared = node.childForFieldName('declarator');
6791
+ const value = node.childForFieldName('value');
6792
+ const type = node.parent.childForFieldName('type');
6793
+ if (declared?.type === 'identifier' && ['argument_list', 'initializer_list'].includes(value?.type) &&
6794
+ ['type_identifier', 'qualified_identifier', 'template_type'].includes(type?.type)) {
6795
+ const name = typeName(type);
6796
+ const values = value.namedChildren.filter(child => !child.type.endsWith('comment'));
6797
+ if (name) calls.push({
6798
+ name, line: declared.startPosition.row + 1, column: declared.startPosition.column,
6799
+ callStart: node.startIndex, callEnd: node.endIndex,
6800
+ isMethod: false, isConstructor: true,
6801
+ constructorSyntax: value.type === 'initializer_list' ? 'list' : 'direct',
6802
+ ...(type.childForFieldName('scope') && { constructorQualifier: type.childForFieldName('scope').text }),
6803
+ argCount: values.length, ...argumentFacts(values, variableTypes),
6804
+ enclosingFunction: enclosingFunctionOf(node),
6805
+ });
6806
+ }
6807
+ }
6808
+ // `Box b;`, `static Box s;`, `Box a[2];` (fix #421): default
6809
+ // initialization runs the default constructor of a class type. The
6810
+ // engine keeps the record only where the name denotes a class.
6811
+ if (mode === 'cpp' && node.type === 'declaration') {
6812
+ const type = node.childForFieldName('type');
6813
+ const external = node.namedChildren.some(child => child.type === 'storage_class_specifier' && child.text === 'extern');
6814
+ const name = !external && ['type_identifier', 'qualified_identifier', 'template_type'].includes(type?.type)
6815
+ ? typeName(type) : null;
6816
+ for (const declarator of name ? node.childrenForFieldName('declarator') : []) {
6817
+ const target = declarator.type === 'array_declarator' ? declarator.childForFieldName('declarator') : declarator;
6818
+ if (target?.type !== 'identifier') continue;
6819
+ calls.push({ name, line: target.startPosition.row + 1, column: target.startPosition.column,
6820
+ callStart: declarator.startIndex, callEnd: declarator.endIndex,
6821
+ isMethod: false, isConstructor: true, constructorSyntax: 'default',
6822
+ ...(type.childForFieldName('scope') && { constructorQualifier: type.childForFieldName('scope').text }),
6823
+ argCount: 0, argKinds: [], enclosingFunction: enclosingFunctionOf(node) });
6824
+ }
6825
+ }
6826
+ if (mode === 'cpp' && node.type === 'compound_literal_expression') {
6827
+ const type = node.childForFieldName('type'), value = node.childForFieldName('value');
6828
+ const name = type && typeName(type);
6829
+ if (name && value?.type === 'initializer_list') {
6830
+ const values = value.namedChildren.filter(child => !child.type.endsWith('comment'));
6831
+ calls.push({ name, line: type.startPosition.row + 1, column: type.startPosition.column,
6832
+ callStart: node.startIndex, callEnd: node.endIndex,
6833
+ isMethod: false, isConstructor: true, constructorSyntax: 'list',
6834
+ ...(type.childForFieldName('scope') && { constructorQualifier: type.childForFieldName('scope').text }),
6835
+ argCount: values.length, ...argumentFacts(values, variableTypes),
6836
+ enclosingFunction: enclosingFunctionOf(node) });
6837
+ }
6838
+ }
6029
6839
  return true;
6030
6840
  });
6031
6841
  if (includeMacroBodies) calls.push(...findMacroBodyCalls(tree, code, parser));
6842
+ calls.push(...require('./constructor-bases').implicitBaseCalls(tree.rootNode, mode, null, implicitOwners));
6032
6843
  return calls;
6033
6844
  }
6034
6845
 
6846
+ /** Omitted base initializer calls of one class, read as findCallsInCode reads them (fix #421). */
6847
+ function implicitBaseCallsOf(code, parser, ownerLine, ownerEndLine, mode) {
6848
+ const { implicitBaseCallsOfOwner } = require('./constructor-bases');
6849
+ const tree = parseTree(parser, code);
6850
+ const primary = implicitBaseCallsOfOwner(tree.rootNode, mode, null, ownerLine, ownerEndLine);
6851
+ const literal = literalRecoveryTree(parser, code, tree);
6852
+ if (!literal) return primary;
6853
+ return mergeExtracted(primary, implicitBaseCallsOfOwner(literal.rootNode, mode, null, ownerLine, ownerEndLine)
6854
+ .map(call => ({ ...call, configurationVariant: true })), callIdentityKey);
6855
+ }
6856
+
6035
6857
  function callIdentityKey(call) {
6036
6858
  return [
6037
6859
  call.name,
@@ -6126,6 +6948,36 @@ function directiveRanges(code) {
6126
6948
  return ranges;
6127
6949
  }
6128
6950
 
6951
+ /**
6952
+ * A namespace-scope function declared `static` keeps internal linkage in
6953
+ * every later declaration of the file that omits the storage class, its
6954
+ * definition included (C11 6.2.2p4-5; C++ [dcl.stc]: successive
6955
+ * declarations of an entity agree on linkage). Those declarations are
6956
+ * recorded `static` (fix #427). A `static` declaration in a conditional
6957
+ * branch reaches only the later declarations inside that branch.
6958
+ */
6959
+ function inheritDeclaredLinkage(functions, mode) {
6960
+ const paramKey = fn => (fn.paramsStructured || []).map(param => param.variadic ? '...'
6961
+ : String(param.type || (param.unnamed ? param.name : '') || '').replace(/\s+/g, '')).join(',');
6962
+ const keyOf = fn => mode === 'cpp' ? `${fn.namespace || ''}\0${fn.name}\0${paramKey(fn)}` : fn.name;
6963
+ const free = fn => !fn.className && !fn.receiver && !fn.isMethod && !fn.friendOf &&
6964
+ !fn.lexicalScopeStartLine && Array.isArray(fn.modifiers);
6965
+ const internal = new Map();
6966
+ const ordered = functions.filter(free).sort((a, b) => a.startLine - b.startLine);
6967
+ for (const fn of ordered) {
6968
+ const key = keyOf(fn);
6969
+ if (fn.modifiers.includes('static')) {
6970
+ if (!internal.has(key)) internal.set(key, []);
6971
+ internal.get(key).push(fn);
6972
+ continue;
6973
+ }
6974
+ const reaches = (internal.get(key) || []).some(prior => prior.startLine < fn.startLine &&
6975
+ (!prior.ppBranch || (fn.startLine >= prior.ppBranch[0] && fn.startLine <= prior.ppBranch[1])));
6976
+ if (!reaches) continue;
6977
+ fn.modifiers = [...fn.modifiers.filter(modifier => modifier !== 'export'), 'static'];
6978
+ }
6979
+ }
6980
+
6129
6981
  function attributeCallsToLexicalFunctions(calls, functions) {
6130
6982
  const bodies = functions.filter(fn => !fn.isSignature)
6131
6983
  .sort((a, b) =>
@@ -6133,7 +6985,8 @@ function attributeCallsToLexicalFunctions(calls, functions) {
6133
6985
  b.startLine - a.startLine);
6134
6986
  for (const call of calls) {
6135
6987
  const owner = bodies.find(fn =>
6136
- fn.startLine <= call.line && call.line <= fn.endLine);
6988
+ fn.startLine <= call.line && call.line <= fn.endLine &&
6989
+ require('../core/callable-scope').containsCall(fn, call) !== false);
6137
6990
  if (!owner) continue;
6138
6991
  call.enclosingFunction = {
6139
6992
  name: owner.name,
@@ -6301,13 +7154,13 @@ function findCallsInCode(code, parser, options = {}, existingTree = null,
6301
7154
  // in preprocessor-configuration conservation.
6302
7155
  if (existingTree) {
6303
7156
  const calls = findCallsInTree(
6304
- code, parser, options, existingTree, includeMacroBodies);
7157
+ code, parser, options, existingTree, includeMacroBodies, mode);
6305
7158
  const functions = findFunctionsInTree(code, existingTree, mode);
6306
7159
  return markDirectiveBodyCalls(code, attributeCallsToLexicalFunctions(calls, functions));
6307
7160
  }
6308
7161
  const tree = parseTree(parser, code);
6309
7162
  const primary = findCallsInTree(
6310
- code, parser, options, tree, includeMacroBodies);
7163
+ code, parser, options, tree, includeMacroBodies, mode);
6311
7164
  const literal = literalRecoveryTree(parser, code, tree);
6312
7165
  if (!literal) {
6313
7166
  return markDirectiveBodyCalls(code, attributeCallsToLexicalFunctions(
@@ -6315,7 +7168,7 @@ function findCallsInCode(code, parser, options = {}, existingTree = null,
6315
7168
  }
6316
7169
  try {
6317
7170
  const literalCalls = findCallsInTree(
6318
- code, parser, options, literal, includeMacroBodies)
7171
+ code, parser, options, literal, includeMacroBodies, mode)
6319
7172
  .map(call => ({ ...call, configurationVariant: true }));
6320
7173
  // Selected-configuration facts retain their stronger evidence. Only
6321
7174
  // literal-only sites carry configurationVariant and are therefore
@@ -6343,6 +7196,32 @@ function findCallsInCode(code, parser, options = {}, existingTree = null,
6343
7196
  * for conservation but marked as lexical parameter dispatch; they must never
6344
7197
  * resolve to an unrelated project function that happens to share `fn`'s name.
6345
7198
  */
7199
+ /**
7200
+ * A replacement list with its stringizing operators read as the string
7201
+ * literals they produce, every byte kept in place (fix #430):
7202
+ * `AssertPred1Helper(#pred, #v1, pred, v1)` is a call of four arguments, not
7203
+ * a parse error of three. Token pasting stays as written: the pasted
7204
+ * parameter is what the macro's parameter effects read.
7205
+ */
7206
+ function withoutPreprocessingOperators(body, parameters) {
7207
+ if (!body.includes('#')) return body;
7208
+ const tokens = lexPP(body);
7209
+ const chars = body.split('');
7210
+ for (let i = 0; i < tokens.length; i++) {
7211
+ const token = tokens[i];
7212
+ if (token.k !== 'punct') continue;
7213
+ if (token.v === '#' && tokens[i + 1]?.k === 'id' &&
7214
+ (parameters.has(tokens[i + 1].v) || tokens[i + 1].v === '__VA_ARGS__')) {
7215
+ const end = tokens[i + 1].start + tokens[i + 1].v.length;
7216
+ for (let at = token.start; at < end; at++) chars[at] = 'x';
7217
+ chars[token.start] = '"';
7218
+ chars[end - 1] = '"';
7219
+ i++;
7220
+ }
7221
+ }
7222
+ return chars.join('');
7223
+ }
7224
+
6346
7225
  function findMacroBodyCalls(tree, code, parser, onlyName = null) {
6347
7226
  const calls = [];
6348
7227
  for (const node of macroDefinitionNodes(tree)) {
@@ -6363,7 +7242,7 @@ function findMacroBodyCalls(tree, code, parser, onlyName = null) {
6363
7242
  const parameters = new Set(parameterNames);
6364
7243
  // Replace only the continuation backslash. Keeping the newline and
6365
7244
  // every other byte makes source-line/column and span remapping exact.
6366
- const body = valueNode.text.replace(/\\(?=\r?\n)/g, ' ');
7245
+ const body = withoutPreprocessingOperators(valueNode.text.replace(/\\(?=\r?\n)/g, ' '), parameters);
6367
7246
  const prefix = 'void __ucn_macro_body__(void) {\n';
6368
7247
  const synthetic = `${prefix}${body}\n;}`;
6369
7248
  // Synthetic replacement-list wrappers are one-shot parse inputs. Do
@@ -6408,6 +7287,7 @@ function findMacroBodyCalls(tree, code, parser, onlyName = null) {
6408
7287
  : valueNode.startIndex + call.callEnd - prefix.length;
6409
7288
  calls.push({
6410
7289
  ...withoutParameterReceiverTypes(call, parameters),
7290
+ ...(call.argTypeOrigins && { argTypeOrigins: call.argTypeOrigins.map(() => null) }),
6411
7291
  line,
6412
7292
  column,
6413
7293
  ...(callStart != null && callStart >= valueNode.startIndex && {
@@ -6494,7 +7374,77 @@ function findImportsInCode(code, parser) {
6494
7374
  } finally { /* cached with the selected tree */ }
6495
7375
  }
6496
7376
 
6497
- const CALLEE_WRAPPER_NODES = new Set(['template_method', 'dependent_name', 'field_expression']);
7377
+ // Follow only the terminal name of a template, qualifier or member access.
7378
+ // Template arguments and receiver tokens keep their own expression roles.
7379
+ function usageNameExpression(node) {
7380
+ let current = node;
7381
+ while (current.parent) {
7382
+ const parent = current.parent;
7383
+ let head;
7384
+ switch (parent.type) {
7385
+ case 'template_function': case 'template_method': case 'template_type':
7386
+ case 'qualified_identifier':
7387
+ head = parent.childForFieldName('name');
7388
+ break;
7389
+ case 'field_expression':
7390
+ head = parent.childForFieldName('field');
7391
+ break;
7392
+ case 'dependent_name': case 'parenthesized_expression':
7393
+ head = parent.namedChildCount === 1 ? parent.namedChild(0) : null;
7394
+ break;
7395
+ default:
7396
+ return current;
7397
+ }
7398
+ if (!sameNode(head, current)) return current;
7399
+ current = parent;
7400
+ }
7401
+ return current;
7402
+ }
7403
+
7404
+ function invokesNameExpression(expression) {
7405
+ return expression.parent?.type === 'call_expression' &&
7406
+ sameNode(expression.parent.childForFieldName('function'), expression);
7407
+ }
7408
+
7409
+ function isInvokedName(node, options = {}) {
7410
+ const expression = usageNameExpression(node);
7411
+ if (!invokesNameExpression(expression)) return false;
7412
+ if (options.unqualified) {
7413
+ const identity = callIdentity(expression);
7414
+ if (identity.isMethod || identity.isPathCall) return false;
7415
+ }
7416
+ return true;
7417
+ }
7418
+
7419
+ // The name introduced by a declarator, including `A::f`, pointer/array
7420
+ // wrappers and data members. Keep this separate from the public usage kind:
7421
+ // an explicit instantiation still references a function the index declares
7422
+ // elsewhere. Refactoring uses the flag only on indexed declaration lines.
7423
+ function isDeclarationNameToken(node) {
7424
+ let current = node;
7425
+ for (let depth = 0; current?.parent && depth < 16; depth++) {
7426
+ const parent = current.parent;
7427
+ if (parent.type === 'qualified_identifier') {
7428
+ if (!sameNode(parent.childForFieldName('name'), current)) return false;
7429
+ } else if (parent.type.endsWith('_declarator')) {
7430
+ const declarator = parent.childForFieldName('declarator') ||
7431
+ (parent.type === 'parenthesized_declarator' && parent.namedChildCount === 1
7432
+ ? parent.namedChild(0) : null);
7433
+ if (!sameNode(declarator, current)) return false;
7434
+ if (parent.type === 'function_declarator') return true;
7435
+ } else {
7436
+ if (['declaration', 'field_declaration', 'function_definition', 'type_definition',
7437
+ 'parameter_declaration', 'optional_parameter_declaration', 'alias_declaration',
7438
+ 'enumerator', 'namespace_definition', 'namespace_alias_definition'].includes(parent.type)) {
7439
+ return sameNode(parent.childForFieldName('name'), current) ||
7440
+ parent.childrenForFieldName('declarator').some(declarator => sameNode(declarator, current));
7441
+ }
7442
+ return false;
7443
+ }
7444
+ current = parent;
7445
+ }
7446
+ return false;
7447
+ }
6498
7448
 
6499
7449
  function findUsagesInCode(code, name, parser, existingTree, options = {}) {
6500
7450
  // Usage is the raw literal-name inventory. The literal C/C++ tree retains
@@ -6506,6 +7456,9 @@ function findUsagesInCode(code, name, parser, existingTree, options = {}) {
6506
7456
  safeParse(parser, code, undefined, PARSE_OPTIONS);
6507
7457
  const usages = [];
6508
7458
  const scopeMemo = options.lexicalScopes && options.mode === 'c' ? new Map() : null;
7459
+ // Occurrence accounts consume positions and kinds, never receiver facts.
7460
+ // Keep the same invocation classifier while omitting unrelated ownership.
7461
+ const kindsOnly = options.usageKindsOnly && !options.lexicalScopes;
6509
7462
  const seenUsages = new Set();
6510
7463
  const addUsage = usage => {
6511
7464
  const key = `${usage.line}:${usage.column ?? ''}:${usage.usageType}:${usage.receiver || ''}`;
@@ -6519,27 +7472,10 @@ function findUsagesInCode(code, name, parser, existingTree, options = {}) {
6519
7472
  let receiver = null;
6520
7473
  const parent = node.parent;
6521
7474
  if (parent) {
6522
- let call = parent.type === 'call_expression'
6523
- ? parent
6524
- : parent.parent?.type === 'call_expression' ? parent.parent : null;
6525
- // `obj.template conv<long>()` nests the member name up to three
6526
- // levels below the call (fix #378).
6527
- if (!call && CALLEE_WRAPPER_NODES.has(parent.type)) {
6528
- let up = parent.parent;
6529
- for (let depth = 0; up && depth < 3 && CALLEE_WRAPPER_NODES.has(up.type); depth++) {
6530
- up = up.parent;
6531
- }
6532
- if (up?.type === 'call_expression') call = up;
6533
- }
6534
- // Only the callable's terminal name is a call usage. The previous
6535
- // descendant test also matched receiver identifiers, classifying
6536
- // `copy.descriptor()` as a call to a free function named `copy`.
6537
- // callIdentity already unwraps qualified/template/parenthesized
6538
- // callees while preserving the exact terminal name node.
6539
- const calledIdentity = call
6540
- ? callIdentity(call.childForFieldName('function')) : null;
6541
- if (calledIdentity?.nameNode &&
6542
- sameNode(calledIdentity.nameNode, node)) {
7475
+ const expression = usageNameExpression(node);
7476
+ const invoked = invokesNameExpression(expression);
7477
+ const identity = kindsOnly && !invoked ? null : callIdentity(expression);
7478
+ if (invoked && sameNode(identity.nameNode, node)) {
6543
7479
  usageType = 'call';
6544
7480
  } else if ((parent.type === 'function_declarator' ||
6545
7481
  parent.type === 'parameter_declaration') &&
@@ -6562,22 +7498,14 @@ function findUsagesInCode(code, name, parser, existingTree, options = {}) {
6562
7498
  } else if (parent.type === 'preproc_include') {
6563
7499
  usageType = 'import';
6564
7500
  }
6565
- if (parent.type === 'qualified_identifier' &&
6566
- sameNode(parent.childForFieldName('name'), node)) {
6567
- receiver = typeName(parent.childForFieldName('scope') ||
6568
- parent.namedChild(0));
6569
- } else if (parent.type === 'field_expression' &&
6570
- sameNode(parent.childForFieldName('field'), node)) {
6571
- const argument = parent.childForFieldName('argument') ||
6572
- parent.namedChild(0);
6573
- receiver = argument?.text || null;
6574
- }
7501
+ // A generic wrapper does not erase an explicit receiver.
7502
+ if (!kindsOnly && sameNode(identity.nameNode, node)) receiver = identity.receiver || null;
6575
7503
  }
6576
7504
  // A bare name lexically inside a class method participates in C++
6577
7505
  // member lookup on that class. This is ownership evidence for test
6578
7506
  // discovery and usage presentation; receiver-qualified forms above
6579
7507
  // retain their explicit receiver instead.
6580
- if (!receiver) receiver = enclosingClassName(node);
7508
+ if (!kindsOnly && !receiver) receiver = enclosingClassName(node);
6581
7509
  // Where a bare C reference resolves (fix #396): block declarations,
6582
7510
  // parameters and `for` initializers bind it, else file scope.
6583
7511
  const scope = scopeMemo && usageType === 'reference' && !receiver
@@ -6586,6 +7514,8 @@ function findUsagesInCode(code, name, parser, existingTree, options = {}) {
6586
7514
  line: node.startPosition.row + 1,
6587
7515
  column: node.startPosition.column,
6588
7516
  usageType,
7517
+ ...(options.lexicalScopes && usageType === 'reference' &&
7518
+ isDeclarationNameToken(node) && { declarationName: true }),
6589
7519
  ...(receiver && { receiver }),
6590
7520
  ...scope,
6591
7521
  });
@@ -6601,7 +7531,7 @@ function findUsagesInCode(code, name, parser, existingTree, options = {}) {
6601
7531
  .map(usage => `${usage.line}:${usage.column ?? ''}`));
6602
7532
  const macroCalls = findMacroBodyCalls(tree, code, parser, name);
6603
7533
  for (const call of macroCalls) {
6604
- if (call.name !== name) continue;
7534
+ if (call.name !== name || call.constructorSyntax === 'implicit') continue;
6605
7535
  const key = `${call.line}:${call.column ?? ''}`;
6606
7536
  if (seenCalls.has(key)) continue;
6607
7537
  seenCalls.add(key);
@@ -6635,7 +7565,8 @@ function parse(code, parser, mode, options = {}) {
6635
7565
  if (options.externalMacros && !externalMacroContext) {
6636
7566
  return withExternalMacros(options.externalMacros, () => parse(code, parser, mode, options));
6637
7567
  }
6638
- const tree = parseTree(parser, code);
7568
+ const rawCapture = mode === 'cpp' ? { source: code, tree: null } : null;
7569
+ const tree = parseTree(parser, code, rawCapture);
6639
7570
  const literal = literalRecoveryTree(parser, code, tree);
6640
7571
  try {
6641
7572
  const lines = code.split('\n');
@@ -6664,11 +7595,11 @@ function parse(code, parser, mode, options = {}) {
6664
7595
  item => `${item.module}:${item.line}:${item.type}`,
6665
7596
  )
6666
7597
  : findImportsInTree(code, tree);
6667
- const primaryCalls = findCallsInTree(code, parser, {}, tree, true);
7598
+ const primaryCalls = findCallsInTree(code, parser, {}, tree, true, mode);
6668
7599
  const calls = literal
6669
7600
  ? mergeExtracted(
6670
7601
  primaryCalls,
6671
- findCallsInTree(code, parser, {}, literal, true)
7602
+ findCallsInTree(code, parser, {}, literal, true, mode)
6672
7603
  .map(call => ({ ...call, configurationVariant: true })),
6673
7604
  callIdentityKey,
6674
7605
  )
@@ -6689,6 +7620,7 @@ function parse(code, parser, mode, options = {}) {
6689
7620
  classes.sort(byLine);
6690
7621
  calls.sort(byLine);
6691
7622
  }
7623
+ inheritDeclaredLinkage(functions, mode);
6692
7624
  attributeCallsToLexicalFunctions(calls, functions);
6693
7625
  markDirectiveBodyCalls(code, calls);
6694
7626
  const result = {
@@ -6728,9 +7660,20 @@ function parse(code, parser, mode, options = {}) {
6728
7660
  result.externalMacroNames = [...externalMacroContext.consulted].sort();
6729
7661
  }
6730
7662
  if (mode === 'cpp') {
7663
+ result.cppBaseSyntax = require('./cpp-site-syntax').cppBaseSyntax(code, parser,
7664
+ calls.filter(call => call.implicitBase?.header).map(call => call.implicitBase.header),
7665
+ rawCapture.tree || (selectedSourceByTree.get(tree) === code ? tree : null));
7666
+ result.typeParameterNames = require('./utils').declaredTypeParameterNames(tree, literal);
6731
7667
  const markers = findMacroScopeMarkers(code, tree, lines);
6732
7668
  if (markers.length > 0) result.macroScopeMarkers = markers;
6733
- const usings = findCppUsingFacts(tree);
7669
+ const primaryUsings = findCppUsingFacts(tree);
7670
+ const originalUsings = literal ? findCppUsingFacts(literal) : primaryUsings;
7671
+ const key = fact => `${fact.line}:${fact.kind}:${fact.target}`;
7672
+ const original = new Map(originalUsings.map(fact => [key(fact), fact]));
7673
+ const usings = mergeExtracted(primaryUsings, originalUsings, key).map(fact => {
7674
+ const groups = original.get(key(fact))?.ppGroups;
7675
+ return groups ? { ...fact, ppGroups: groups } : fact;
7676
+ });
6734
7677
  if (usings.length > 0) result.cppUsings = usings;
6735
7678
  }
6736
7679
  // Adapter-only full-analysis fact: keep the public parse result shape
@@ -6743,6 +7686,7 @@ function parse(code, parser, mode, options = {}) {
6743
7686
  });
6744
7687
  return result;
6745
7688
  } finally {
7689
+ if (rawCapture?.tree && rawCapture.tree !== tree && rawCapture.tree !== literal) rawCapture.tree.delete?.();
6746
7690
  if (options.releaseAnalysisTree) {
6747
7691
  releaseCFamilyTree(parser, code, tree);
6748
7692
  }
@@ -6766,7 +7710,7 @@ function languageBranchExtraction(code, mode) {
6766
7710
  return {
6767
7711
  functions: findFunctionsInTree(branch.source, tree, branch.language, lines).map(tag),
6768
7712
  classes,
6769
- calls: findCallsInTree(branch.source, branchParser, {}, tree, true).map(tag),
7713
+ calls: findCallsInTree(branch.source, branchParser, {}, tree, true, branch.language).map(tag),
6770
7714
  };
6771
7715
  } finally {
6772
7716
  if (isMainThread) tree.delete?.();
@@ -6793,6 +7737,8 @@ function createCFamilyLanguage(mode) {
6793
7737
  findMacros,
6794
7738
  findCallsInCode: (code, parser, options, existingTree, includeMacroBodies) =>
6795
7739
  findCallsInCode(code, parser, options, existingTree, includeMacroBodies, mode),
7740
+ implicitBaseCallsOf: (code, parser, ownerLine, ownerEndLine) =>
7741
+ implicitBaseCallsOf(code, parser, ownerLine, ownerEndLine, mode),
6796
7742
  findImportsInCode,
6797
7743
  findExportsInCode: (code, parser) => findExportsInCodeShallow(code, parser, mode),
6798
7744
  findUsagesInCode: (code, name, parser, existingTree, options) =>
@@ -6859,11 +7805,19 @@ function objectMacroReading(parser, bodies) {
6859
7805
  }
6860
7806
 
6861
7807
  module.exports = {
7808
+ deductionGuideOf,
7809
+ endsFunctionHead,
7810
+ withoutDecorationMacros,
7811
+ implicitOwnerScope,
7812
+ implicitConstructorInfo,
7813
+ isInvokedName,
6862
7814
  createCFamilyLanguage,
6863
7815
  objectMacroReading,
6864
7816
  withExternalMacros,
7817
+ hasExternalMacroContext: () => externalMacroContext !== null,
6865
7818
  // Deterministic test seams for the recovery memory/order contracts.
6866
7819
  conditionalRecoverySources,
7820
+ conditionalHeaderText,
6867
7821
  mergeExtracted,
6868
7822
  macroAccessEffect,
6869
7823
  };