ucn 5.5.1 → 5.5.3

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 (97) hide show
  1. package/.claude/skills/ucn/SKILL.md +35 -9
  2. package/.claude/skills/ucn/references/commands.md +40 -18
  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 +32 -21
  6. package/cli/index.js +2 -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/bridge.js +15 -65
  12. package/core/brief.js +8 -4
  13. package/core/build-worker.js +2 -1
  14. package/core/cache.js +137 -6
  15. package/core/call-name-cache.js +134 -0
  16. package/core/callable-scope.js +21 -0
  17. package/core/callers.js +4726 -940
  18. package/core/check.js +22 -5
  19. package/core/class-identity.js +399 -12
  20. package/core/client-receivers.js +189 -0
  21. package/core/constructor-calls.js +831 -0
  22. package/core/constructor-hierarchy.js +147 -0
  23. package/core/constructor-protocol.js +196 -0
  24. package/core/contract-membership.js +346 -140
  25. package/core/cpp-conversion-constraints.js +123 -0
  26. package/core/cpp-conversion-syntax.js +216 -0
  27. package/core/cpp-conversions.js +414 -0
  28. package/core/cpp-scope.js +245 -44
  29. package/core/deadcode.js +61 -16
  30. package/core/decorator-binding.js +137 -3
  31. package/core/enum-reference-syntax.js +134 -0
  32. package/core/execute.js +17 -1
  33. package/core/external-macros.js +42 -10
  34. package/core/go-packages.js +171 -0
  35. package/core/graph-build.js +2 -0
  36. package/core/graph.js +75 -59
  37. package/core/ignore-rules.js +2 -1
  38. package/core/imports.js +145 -19
  39. package/core/index-ir.js +41 -4
  40. package/core/inprocess-clients.js +41 -15
  41. package/core/ir.js +27 -6
  42. package/core/macro-expansion.js +38 -2
  43. package/core/out-of-class-members.js +182 -0
  44. package/core/output/analysis-ext.js +4 -2
  45. package/core/output/analysis.js +89 -20
  46. package/core/output/graph.js +12 -3
  47. package/core/output/lines.js +4 -3
  48. package/core/output/public.js +2 -0
  49. package/core/output/reporting.js +12 -1
  50. package/core/output/shared.js +2 -0
  51. package/core/output/tracing.js +28 -22
  52. package/core/output-budget.js +2 -2
  53. package/core/parallel-build.js +52 -1
  54. package/core/project.js +217 -112
  55. package/core/provenance-constructor-base.js +161 -0
  56. package/core/provenance-facts.js +257 -27
  57. package/core/provenance-overload.js +458 -54
  58. package/core/provenance-type.js +487 -0
  59. package/core/provenance.js +234 -15
  60. package/core/public-command.js +17 -2
  61. package/core/query-bodies.js +159 -0
  62. package/core/reflection.js +270 -2
  63. package/core/rename-token-syntax.js +111 -0
  64. package/core/reporting.js +13 -7
  65. package/core/request-helpers.js +63 -7
  66. package/core/rust-macro-expansion.js +151 -19
  67. package/core/rust-modules.js +1 -1
  68. package/core/rust-names.js +607 -0
  69. package/core/search.js +70 -17
  70. package/core/shared.js +98 -1
  71. package/core/source-memo.js +201 -0
  72. package/core/text-usage-syntax.js +38 -0
  73. package/core/tracing.js +7 -1
  74. package/core/type-denotation.js +124 -37
  75. package/core/type-references.js +615 -246
  76. package/core/value-references.js +2777 -0
  77. package/core/verify.js +1097 -281
  78. package/languages/adapter.js +28 -4
  79. package/languages/c-family.js +1118 -164
  80. package/languages/constructor-bases.js +105 -0
  81. package/languages/cpp-platform.js +26 -0
  82. package/languages/cpp-site-syntax.js +83 -0
  83. package/languages/csharp-scope.js +439 -0
  84. package/languages/csharp.js +428 -68
  85. package/languages/go.js +385 -207
  86. package/languages/html.js +4 -2
  87. package/languages/index.js +397 -2
  88. package/languages/java-platform.js +226 -0
  89. package/languages/java-unicode.js +269 -0
  90. package/languages/java.js +461 -117
  91. package/languages/javascript.js +418 -66
  92. package/languages/lexical-scope.js +190 -23
  93. package/languages/python.js +404 -54
  94. package/languages/rust.js +510 -82
  95. package/languages/utils.js +67 -8
  96. package/mcp/server.js +8 -2
  97. package/package.json +3 -3
@@ -76,6 +76,94 @@ function pythonClassBodyDefs(body) {
76
76
  return out;
77
77
  }
78
78
 
79
+ /** The nodes an assignment target binds, through tuple and list targets:
80
+ * identifiers (`kind` 'identifier') or attributes ('attribute'). */
81
+ function pythonTargetNames(target, kind) {
82
+ const out = [];
83
+ const visit = node => {
84
+ if (!node) return;
85
+ if (node.type === kind) { out.push(node); return; }
86
+ if (['pattern_list', 'tuple_pattern', 'list_pattern', 'expression_list', 'tuple', 'list',
87
+ 'parenthesized_expression'].includes(node.type)) {
88
+ for (let i = 0; i < node.namedChildCount; i++) visit(node.namedChild(i));
89
+ }
90
+ };
91
+ visit(target);
92
+ return out;
93
+ }
94
+
95
+ /** The first positional parameter's name of a function definition. */
96
+ function pythonReceiverName(fn) {
97
+ const params = fn.childForFieldName('parameters');
98
+ for (let i = 0; i < (params?.namedChildCount || 0); i++) {
99
+ const param = params.namedChild(i);
100
+ if (param.type === 'identifier') return param.text;
101
+ if (['typed_parameter', 'default_parameter', 'typed_default_parameter'].includes(param.type)) {
102
+ const name = param.childForFieldName('name') || param.namedChild(0);
103
+ return name?.type === 'identifier' ? name.text : null;
104
+ }
105
+ if (param.type !== 'positional_separator') return null;
106
+ }
107
+ return null;
108
+ }
109
+
110
+ /** Whether a nested function or lambda binds `name` as a parameter. */
111
+ function pythonParamsBind(fn, name) {
112
+ const params = fn.childForFieldName('parameters');
113
+ for (let i = 0; i < (params?.namedChildCount || 0); i++) {
114
+ let param = params.namedChild(i);
115
+ if (param.type === 'default_parameter' || param.type === 'typed_default_parameter') {
116
+ param = param.childForFieldName('name');
117
+ } else if (param.type === 'typed_parameter') {
118
+ param = param.namedChild(0);
119
+ }
120
+ if (param?.type === 'list_splat_pattern' || param?.type === 'dictionary_splat_pattern') {
121
+ param = param.namedChild(0);
122
+ }
123
+ if (param?.type === 'identifier' && param.text === name) return true;
124
+ }
125
+ return false;
126
+ }
127
+
128
+ /** Instance attributes the methods of a class body assign on their receiver,
129
+ * one per name in source order, names in `declared` excluded (fix #430).
130
+ * A static method has no receiver; a nested class has its own; a nested
131
+ * function or lambda that rebinds the receiver's name assigns another value. */
132
+ function pythonInstanceAttributes(bodyNode, declared) {
133
+ const out = [];
134
+ const seen = new Set(declared);
135
+ for (const def of pythonClassBodyDefs(bodyNode)) {
136
+ const fn = def.type === 'decorated_definition' ? def.childForFieldName('definition') : def;
137
+ if (def.type === 'decorated_definition' && def.namedChildren.some(child =>
138
+ child.type === 'decorator' && /^@\s*staticmethod\b/.test(child.text))) continue;
139
+ const receiver = fn && pythonReceiverName(fn);
140
+ const body = fn?.childForFieldName('body');
141
+ if (!receiver || !body) continue;
142
+ traverseTree(body, node => {
143
+ if (node.type === 'class_definition') return false;
144
+ if ((node.type === 'function_definition' || node.type === 'lambda') &&
145
+ pythonParamsBind(node, receiver)) return false;
146
+ if (node.type !== 'assignment' && node.type !== 'augmented_assignment') return true;
147
+ const type = node.type === 'assignment' ? node.childForFieldName('type') : null;
148
+ for (const target of pythonTargetNames(node.childForFieldName('left'), 'attribute')) {
149
+ const object = target.childForFieldName('object');
150
+ const attr = target.childForFieldName('attribute');
151
+ if (object?.type !== 'identifier' || object.text !== receiver || !attr) continue;
152
+ if (seen.has(attr.text)) continue;
153
+ // Reading the previous member on the RHS does not change
154
+ // the assignment's storage: `self.m = wrap(self.m)` creates
155
+ // an instance attribute even if the class inherits `m`.
156
+ seen.add(attr.text);
157
+ out.push({ name: attr.text, startLine: attr.startPosition.row + 1,
158
+ endLine: node.endPosition.row + 1, memberType: 'field', instanceAttribute: true,
159
+ ...(type && { returnType: type.text, fieldType: type.text }) });
160
+ }
161
+ return true;
162
+ });
163
+ }
164
+ return out;
165
+ }
166
+
79
167
  function parseTree(parser, code) {
80
168
  return safeParse(parser, code, undefined, PARSE_OPTIONS);
81
169
  }
@@ -243,6 +331,7 @@ function _processFunction(node, functions, processedRanges, lines, code) {
243
331
  params: extractPythonParams(paramsNode),
244
332
  paramsStructured,
245
333
  startLine: decoratorStartLine,
334
+ sourceSpan: nodeToLocation(node.parent?.type === 'decorated_definition' ? node.parent : node, lines).sourceSpan,
246
335
  endLine,
247
336
  indent,
248
337
  isAsync,
@@ -329,47 +418,33 @@ function pythonFunctionLocalScope(node) {
329
418
  return {};
330
419
  }
331
420
 
332
- // Module-level state detection patterns
333
- const _STATE_PATTERN = /^(CONFIG|SETTINGS|[A-Z][A-Z0-9_]+|[A-Z][a-zA-Z]*(?:Config|Settings|Options|State|Store|Context))$/;
334
- // Pattern for UPPER_CASE constants that may have scalar values (string, number, bool, etc.)
335
- const _CONSTANT_PATTERN = /^[A-Z][A-Z0-9_]{1,}$/;
336
- // RHS types that are scalar/simple values (not dict/list which are handled separately)
337
- const _SCALAR_TYPES = new Set([
338
- 'string', 'concatenated_string', 'integer', 'float', 'true', 'false', 'none',
339
- 'unary_operator', 'binary_operator', 'tuple', 'set', 'parenthesized_expression',
340
- 'call', 'attribute', 'identifier', 'subscript',
341
- ]);
342
-
343
421
  /**
344
422
  * Process a node for state object extraction (single-pass helper)
345
423
  * Returns true if node was matched, false otherwise
346
424
  */
347
425
  function _processState(node, objects, lines) {
348
- if (node.type === 'expression_statement' && node.parent && node.parent.parent === null) {
349
- const child = node.namedChild(0);
350
- if (child && child.type === 'assignment') {
351
- const leftNode = child.childForFieldName('left');
352
- const rightNode = child.childForFieldName('right');
353
-
354
- if (leftNode && leftNode.type === 'identifier' && rightNode) {
355
- const name = leftNode.text;
356
- const isObject = rightNode.type === 'dictionary';
357
- const isArray = rightNode.type === 'list';
358
-
359
- if ((isObject || isArray) && _STATE_PATTERN.test(name)) {
360
- const { startLine, endLine } = nodeToLocation(node, lines);
361
- objects.push({ name, startLine, endLine });
362
- return true;
363
- } else if (_CONSTANT_PATTERN.test(name) && _SCALAR_TYPES.has(rightNode.type)) {
364
- // Module-level UPPER_CASE constants with scalar values
365
- const { startLine, endLine } = nodeToLocation(node, lines);
366
- objects.push({ name, startLine, endLine, isConstant: true });
367
- return true;
368
- }
369
- }
426
+ if (node.type !== 'assignment') return false;
427
+ for (let scope = node.parent; scope; scope = scope.parent) {
428
+ if (scope.type === 'function_definition' || scope.type === 'lambda' ||
429
+ scope.type === 'class_definition') return false;
430
+ }
431
+ if (node.parent?.type === 'expression_statement' &&
432
+ _processTypeAlias(node.parent, [], new Set(), lines)) return false;
433
+ const target = node.childForFieldName('left');
434
+ if (!target) return false;
435
+ const bindings = [];
436
+ const collect = pattern => {
437
+ if (pattern.type === 'identifier') bindings.push(pattern);
438
+ else if (['pattern_list', 'tuple_pattern', 'list_pattern', 'list_splat_pattern'].includes(pattern.type)) {
439
+ for (const child of pattern.namedChildren) collect(child);
370
440
  }
441
+ };
442
+ collect(target);
443
+ for (const binding of bindings) {
444
+ objects.push({ name: binding.text, startLine: binding.startPosition.row + 1,
445
+ endLine: nodeToLocation(node, lines).endLine });
371
446
  }
372
- return false;
447
+ return bindings.length > 0;
373
448
  }
374
449
 
375
450
  /**
@@ -694,12 +769,12 @@ function pythonConfigurationItems(root, bindingNodes = null, reboundNodes = null
694
769
  const rebound = new Set(); // `global` / `nonlocal` names
695
770
  const lineOf = node => node.startPosition.row + 1;
696
771
  const endOf = node => node.endPosition.row + 1;
697
- const add = (scope, name, kind, line, branch) => {
772
+ const add = (scope, name, kind, line, branch, target = null) => {
698
773
  if (!name) return;
699
774
  let names = scopes.get(scope);
700
775
  if (!names) scopes.set(scope, names = new Map());
701
776
  if (!names.has(name)) names.set(name, []);
702
- names.get(name).push({ kind, line, branch });
777
+ names.get(name).push({ kind, line, branch, ...(target && { target }) });
703
778
  };
704
779
  const bindTarget = (scope, node, branch) => {
705
780
  if (!node) return;
@@ -753,8 +828,20 @@ function pythonConfigurationItems(root, bindingNodes = null, reboundNodes = null
753
828
  case 'expression_statement':
754
829
  for (let expr of node.namedChildren) {
755
830
  while (expr?.type === 'assignment' || expr?.type === 'augmented_assignment') {
756
- bindTarget(scope, expr.childForFieldName('left'), branch);
757
- expr = expr.type === 'assignment' ? expr.childForFieldName('right') : null;
831
+ const left = expr.childForFieldName('left');
832
+ const right = expr.childForFieldName('right');
833
+ // An alias value (`f = other`, `f = mod.other`)
834
+ // names what it binds (fix #424): the chase follows it.
835
+ if (expr.type === 'assignment' && left?.type === 'identifier' &&
836
+ !expr.childForFieldName('type') &&
837
+ (right?.type === 'identifier' || right?.type === 'attribute') &&
838
+ /^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)?$/.test(right.text) &&
839
+ right.text !== left.text) {
840
+ add(scope, left.text, 'value', lineOf(left), branch, right.text);
841
+ } else {
842
+ bindTarget(scope, left, branch);
843
+ }
844
+ expr = expr.type === 'assignment' ? right : null;
758
845
  }
759
846
  }
760
847
  return;
@@ -866,7 +953,8 @@ function pythonConfigurationItems(root, bindingNodes = null, reboundNodes = null
866
953
  const statements = new Set(sites.flatMap(site => site.branch.map(([line]) => line)));
867
954
  items.push({
868
955
  name, scope,
869
- sites: sites.map(site => ({ kind: site.kind, line: site.line, branch: site.branch })),
956
+ sites: sites.map(site => ({ kind: site.kind, line: site.line, branch: site.branch,
957
+ ...(site.target && { target: site.target }) })),
870
958
  clauses: [...statements].sort((a, b) => a - b).map(line => [line, clauseRanges.get(line) || []]),
871
959
  });
872
960
  }
@@ -1007,7 +1095,7 @@ function _processTypeAlias(node, classes, processedRanges, lines) {
1007
1095
  const rangeKey = `${node.startIndex}-${node.endIndex}`;
1008
1096
  if (processedRanges.has(rangeKey)) return true;
1009
1097
  processedRanges.add(rangeKey);
1010
- const { startLine, endLine } = nodeToLocation(node, lines);
1098
+ const { startLine, endLine, sourceSpan } = nodeToLocation(node, lines);
1011
1099
  const aliasOf = pythonAliasBase(valueNode);
1012
1100
  const aliasMembers = pythonAliasMembers(valueNode);
1013
1101
  classes.push({
@@ -1015,6 +1103,7 @@ function _processTypeAlias(node, classes, processedRanges, lines) {
1015
1103
  type: 'type',
1016
1104
  startLine,
1017
1105
  endLine,
1106
+ sourceSpan,
1018
1107
  methods: [],
1019
1108
  members: [],
1020
1109
  ...(aliasOf && { aliasOf }),
@@ -1069,6 +1158,36 @@ function extractClassMembers(classNode, code) {
1069
1158
  const bodyNode = classNode.childForFieldName('body');
1070
1159
  if (!bodyNode) return members;
1071
1160
 
1161
+ // A class-body assignment declares an attribute, including an annotated
1162
+ // dataclass field and each name of a tuple target. Function and
1163
+ // nested-class bodies have their own owners.
1164
+ const declared = new Set();
1165
+ traverseTree(bodyNode, node => {
1166
+ if (node.type === 'function_definition' || node.type === 'class_definition') return false;
1167
+ if (node.type !== 'assignment') return true;
1168
+ const type = node.childForFieldName('type');
1169
+ for (const name of pythonTargetNames(node.childForFieldName('left'), 'identifier')) {
1170
+ declared.add(name.text);
1171
+ members.push({ name: name.text, startLine: name.startPosition.row + 1,
1172
+ endLine: node.endPosition.row + 1, memberType: 'field',
1173
+ ...(type && { returnType: type.text, fieldType: type.text }) });
1174
+ }
1175
+ return true;
1176
+ });
1177
+ // An attribute a method assigns on its receiver (`self.x = v`, `self.x:
1178
+ // T = v`, `self.x += v`) is an instance attribute of the class (fix
1179
+ // #430): one declaration per name, the first in source order, unless
1180
+ // the class body declares the name.
1181
+ // A property the class body declares is a data descriptor: assigning
1182
+ // it on the receiver runs its setter, never declares an attribute.
1183
+ for (const def of pythonClassBodyDefs(bodyNode)) {
1184
+ if (def.type !== 'decorated_definition') continue;
1185
+ const name = def.childForFieldName('definition')?.childForFieldName('name')?.text;
1186
+ if (name && def.namedChildren.some(child => child.type === 'decorator' &&
1187
+ /^@\s*(?:property|[A-Za-z_]\w*\.(?:setter|deleter))\s*$/.test(child.text))) declared.add(name);
1188
+ }
1189
+ for (const attribute of pythonInstanceAttributes(bodyNode, declared)) members.push(attribute);
1190
+
1072
1191
  for (const child of pythonClassBodyDefs(bodyNode)) {
1073
1192
  let funcNode = child;
1074
1193
  let decoratorStart = null;
@@ -1138,6 +1257,7 @@ function extractClassMembers(classNode, code) {
1138
1257
  params: extractPythonParams(paramsNode),
1139
1258
  paramsStructured,
1140
1259
  startLine,
1260
+ sourceSpan: nodeToLocation(child, code).sourceSpan,
1141
1261
  endLine,
1142
1262
  memberType,
1143
1263
  isAsync,
@@ -1625,6 +1745,30 @@ function narrowedReceiverType(refNode, receiverName, declaredUnion) {
1625
1745
  if (remaining.length === 1) return remaining[0];
1626
1746
  }
1627
1747
  }
1748
+ // A class pattern (fix #425, the Go type-switch rule): in `case
1749
+ // Sq() as s:` the capture s is a Sq instance, and `match x: case
1750
+ // Sq():` narrows x to Sq, in the clause's body. A union of class
1751
+ // patterns names no single type.
1752
+ if (parent.type === 'case_clause' && sameNode(parent.childForFieldName('consequence'), current)) {
1753
+ const casePatterns = parent.namedChildren.filter(child => child.type === 'case_pattern');
1754
+ const pattern = casePatterns.length === 1 ? casePatterns[0].namedChild(0) : null;
1755
+ let classPattern = null;
1756
+ if (pattern?.type === 'as_pattern') {
1757
+ const target = pattern.namedChild(pattern.namedChildCount - 1);
1758
+ const targetName = target?.type === 'as_pattern_target' ? target.namedChild(0)?.text : target?.text;
1759
+ const inner = pattern.namedChild(0);
1760
+ if (targetName === receiverName && inner?.type === 'case_pattern' &&
1761
+ inner.namedChild(0)?.type === 'class_pattern') classPattern = inner.namedChild(0);
1762
+ } else if (pattern?.type === 'class_pattern') {
1763
+ const subject = parent.parent?.parent?.type === 'match_statement'
1764
+ ? parent.parent.parent.childForFieldName('subject') : null;
1765
+ if (subject?.type === 'identifier' && subject.text === receiverName) classPattern = pattern;
1766
+ }
1767
+ const dotted = classPattern?.namedChild(0);
1768
+ if (dotted?.type === 'dotted_name' && dotted.namedChildCount > 0) {
1769
+ return dotted.namedChild(dotted.namedChildCount - 1).text;
1770
+ }
1771
+ }
1628
1772
  if (parent.type === 'conditional_expression' &&
1629
1773
  parent.namedChildCount >= 3) {
1630
1774
  const consequence = parent.namedChild(0);
@@ -2242,6 +2386,102 @@ function pythonClosureScopeLine(refNode, name) {
2242
2386
  return null;
2243
2387
  }
2244
2388
 
2389
+ // Methods Python calls with the class as their first argument without a
2390
+ // decorator (data model: implicit classmethods).
2391
+ const PY_IMPLICIT_CLASS_METHODS = new Set(['__init_subclass__', '__class_getitem__']);
2392
+
2393
+ /**
2394
+ * A construction through a method's own receiver (fix #434): `cls(..)` in a
2395
+ * classmethod, `type(self)(..)` and `self.__class__(..)` in an instance
2396
+ * method build the method's class or a subclass of it. The receiver must be
2397
+ * the method's first parameter, never rebound, and read from the method
2398
+ * itself or a closure inside it. Returns the class (by its name line), the
2399
+ * method, the receiver binding ('class' or 'instance') and the spelling of
2400
+ * the callee, or null.
2401
+ */
2402
+ function pythonOwnClassConstruction(callNode) {
2403
+ const funcNode = callNode.childForFieldName('function');
2404
+ let receiver = null, binding = null, spelling = null;
2405
+ if (funcNode?.type === 'identifier') {
2406
+ receiver = funcNode; binding = 'class'; spelling = funcNode.text;
2407
+ } else if (funcNode?.type === 'call') {
2408
+ const inner = funcNode.childForFieldName('function');
2409
+ const args = funcNode.childForFieldName('arguments');
2410
+ const named = args?.type === 'argument_list'
2411
+ ? args.namedChildren.filter(child => !child.type.endsWith('comment')) : [];
2412
+ if (inner?.type === 'identifier' && inner.text === 'type' && named.length === 1 &&
2413
+ named[0].type === 'identifier') {
2414
+ receiver = named[0]; binding = 'instance'; spelling = funcNode.text;
2415
+ }
2416
+ } else if (funcNode?.type === 'attribute' &&
2417
+ funcNode.childForFieldName('attribute')?.text === '__class__' &&
2418
+ funcNode.childForFieldName('object')?.type === 'identifier') {
2419
+ receiver = funcNode.childForFieldName('object'); binding = 'instance'; spelling = '__class__';
2420
+ }
2421
+ if (!receiver) return null;
2422
+ const name = receiver.text;
2423
+ let method = null;
2424
+ for (let parent = callNode.parent; parent && !method; parent = parent.parent) {
2425
+ if (parent.type === 'class_definition' || parent.type === 'module') return null;
2426
+ if (parent.type === 'lambda') {
2427
+ const params = parent.childForFieldName('parameters');
2428
+ if (params && pythonParametersBindName(params, name)) return null;
2429
+ } else if (PY_COMPREHENSIONS.has(parent.type)) {
2430
+ for (const clause of parent.namedChildren) {
2431
+ if (clause.type === 'for_in_clause' &&
2432
+ pythonTargetBindsName(clause.childForFieldName('left'), name)) return null;
2433
+ }
2434
+ } else if (parent.type === 'function_definition' || parent.type === 'async_function_definition') {
2435
+ const params = parent.childForFieldName('parameters');
2436
+ const body = parent.childForFieldName('body');
2437
+ if (params && pythonParametersBindName(params, name)) method = parent;
2438
+ else if (body && pythonScopeBindsName(body, name)) return null;
2439
+ }
2440
+ }
2441
+ if (!method) return null;
2442
+ const params = method.childForFieldName('parameters');
2443
+ const first = params?.namedChildren.find(child => !child.type.endsWith('comment'));
2444
+ const body = method.childForFieldName('body');
2445
+ if (first?.type !== 'identifier' || first.text !== name || (body && pythonScopeBindsName(body, name)) ||
2446
+ isPythonIterationBindingAt(receiver, name)) return null;
2447
+ const holder = method.parent?.type === 'decorated_definition' ? method.parent : method;
2448
+ const classNode = holder.parent?.type === 'block' && holder.parent.parent?.type === 'class_definition'
2449
+ ? holder.parent.parent : null;
2450
+ const className = classNode?.childForFieldName('name');
2451
+ const methodName = method.childForFieldName('name');
2452
+ if (!className || !methodName) return null;
2453
+ const decorators = holder.type === 'decorated_definition'
2454
+ ? holder.namedChildren.filter(child => child.type === 'decorator').map(child => child.namedChild(0)?.text)
2455
+ : [];
2456
+ const classMethod = decorators.includes('classmethod') || PY_IMPLICIT_CLASS_METHODS.has(methodName.text);
2457
+ if (binding === 'class' ? !classMethod
2458
+ : classMethod || decorators.includes('staticmethod') || methodName.text === '__new__') return null;
2459
+ const own = { name: className.text, line: className.startPosition.row + 1, method: methodName.text,
2460
+ methodLine: methodName.startPosition.row + 1, receiver: name, binding, spelling };
2461
+ // The receiver token names the class at the site (`self` of `type(self)`).
2462
+ Object.defineProperty(own, 'receiverNode', { value: receiver, enumerable: false });
2463
+ return own;
2464
+ }
2465
+
2466
+ /** The `ownClass` field of a call record (fix #434). */
2467
+ function ownClassFields(callNode) {
2468
+ const ownClass = pythonOwnClassConstruction(callNode);
2469
+ return ownClass ? { ownClass } : null;
2470
+ }
2471
+
2472
+ /** Whether a parameter list binds `name`. */
2473
+ function pythonParametersBindName(params, name) {
2474
+ for (const param of params.namedChildren) {
2475
+ let paramName = param.type === 'identifier' ? param
2476
+ : (param.childForFieldName('name') || param.namedChild(0));
2477
+ if (paramName && ['dictionary_splat_pattern', 'list_splat_pattern'].includes(paramName.type)) {
2478
+ paramName = paramName.namedChild(0);
2479
+ }
2480
+ if (paramName?.type === 'identifier' && paramName.text === name) return true;
2481
+ }
2482
+ return false;
2483
+ }
2484
+
2245
2485
  /** Shadow fields of a bare reference: `localShadow`, and the enclosing
2246
2486
  * function a closure binds it in. */
2247
2487
  function pythonShadowFields(refNode, name) {
@@ -2413,10 +2653,14 @@ function isPythonConstructorValueShadowedAt(refNode, name) {
2413
2653
  return classDeclarations > 1;
2414
2654
  }
2415
2655
 
2656
+ const moduleAliasesByTree = new WeakMap();
2416
2657
  function pythonModuleAliases(tree) {
2658
+ const cached = moduleAliasesByTree.get(tree);
2659
+ if (cached) return cached;
2417
2660
  const aliases = new Set();
2418
- traverseTreeCached(tree.rootNode, node => {
2419
- if (node.type !== 'import_statement') return true;
2661
+ // A name query needs import statements, not a wrapper for every node
2662
+ // in the file. Keep this source-only answer with its immutable tree.
2663
+ for (const node of tree.rootNode.descendantsOfType('import_statement')) {
2420
2664
  for (let i = 0; i < node.namedChildCount; i++) {
2421
2665
  const child = node.namedChild(i);
2422
2666
  if (child.type === 'dotted_name') {
@@ -2427,8 +2671,8 @@ function pythonModuleAliases(tree) {
2427
2671
  if (alias?.type === 'identifier') aliases.add(alias.text);
2428
2672
  }
2429
2673
  }
2430
- return true;
2431
- });
2674
+ }
2675
+ moduleAliasesByTree.set(tree, aliases);
2432
2676
  return aliases;
2433
2677
  }
2434
2678
 
@@ -3423,7 +3667,9 @@ function findCallsInTree(code, tree, parser, sink = null) {
3423
3667
  receiverTypeEvidence: { ...typeOrigin('constructor', objNode), type: ctor.type } };
3424
3668
  }
3425
3669
  }
3426
- if (valueType) {
3670
+ const valuePath = !valueType && objNode?.type === 'attribute'
3671
+ ? attributeReceiverPath(objNode) : null;
3672
+ if (valueType || valuePath) {
3427
3673
  // The receiver context a call on it carries.
3428
3674
  const recv = objNode.type === 'identifier' ? objNode.text : null;
3429
3675
  calls.push({
@@ -3432,19 +3678,27 @@ function findCallsInTree(code, tree, parser, sink = null) {
3432
3678
  line: attrName.startPosition.row + 1,
3433
3679
  isMethod: true,
3434
3680
  ...(recv && { receiver: recv }),
3435
- receiverType: valueType,
3681
+ ...(valueType && { receiverType: valueType }),
3436
3682
  ...valueFields,
3683
+ ...(valuePath && {
3684
+ receiverRoot: valuePath.root,
3685
+ receiverField: valuePath.fields[valuePath.fields.length - 1],
3686
+ receiverFields: valuePath.fields,
3687
+ ...(localVarTypes.has(valuePath.root) && {
3688
+ receiverRootType: localVarTypes.get(valuePath.root),
3689
+ }),
3690
+ }),
3437
3691
  ...(recv && localVarTypeQualifiers.get(recv) && {
3438
3692
  receiverTypeQualifier: localVarTypeQualifiers.get(recv),
3439
3693
  }),
3440
3694
  ...(recv && localVarStdlibContracts.has(recv) && {
3441
3695
  receiverTypeStdlibModule: localVarStdlibContracts.get(recv),
3442
3696
  }),
3443
- ...((!recv || constructedReceiverVars.has(recv)) && { receiverConstructed: true }),
3697
+ ...(((!recv && !valuePath) || constructedReceiverVars.has(recv)) && { receiverConstructed: true }),
3444
3698
  ...(recv && withBindingVars.has(recv) && { receiverWithBinding: true }),
3445
3699
  ...(recv && isShadowedByLocal(objNode, recv) && { receiverLocalBinding: true }),
3446
3700
  isFunctionReference: true,
3447
- memberRead: true,
3701
+ ...(!valuePath && { memberRead: true }),
3448
3702
  enclosingFunction: getCurrentEnclosingFunction(),
3449
3703
  });
3450
3704
  }
@@ -3639,10 +3893,28 @@ function findCallsInTree(code, tree, parser, sink = null) {
3639
3893
  enclosingFunction,
3640
3894
  uncertain,
3641
3895
  ...pythonShadowFields(funcNode, funcNode.text),
3896
+ ...ownClassFields(node),
3642
3897
  ...(firstArg && { firstStringArg: firstArg.value, firstStringArgInterp: firstArg.interp }),
3643
3898
  ...(requestConfig && { requestConfig })
3644
3899
  });
3645
3900
  }
3901
+ } else if (funcNode.type === 'call') {
3902
+ // `type(self)(..)` constructs the method's class (fix #434).
3903
+ const ownClass = pythonOwnClassConstruction(node);
3904
+ if (ownClass) {
3905
+ calls.push({
3906
+ callSite: typeOrigin('call', ownClass.receiverNode),
3907
+ column: ownClass.receiverNode.startPosition.column,
3908
+ name: ownClass.name,
3909
+ line: node.startPosition.row + 1,
3910
+ isMethod: false,
3911
+ argCount,
3912
+ ...(argSpread && { argSpread: true }),
3913
+ enclosingFunction,
3914
+ uncertain,
3915
+ ownClass,
3916
+ });
3917
+ }
3646
3918
  } else if (funcNode.type === 'attribute') {
3647
3919
  // Method/attribute call: obj.foo() or self.attr.foo()
3648
3920
  const attrNode = funcNode.childForFieldName('attribute');
@@ -3670,7 +3942,8 @@ function findCallsInTree(code, tree, parser, sink = null) {
3670
3942
  // Detect super().method() pattern
3671
3943
  if (objNode?.type === 'call') {
3672
3944
  const superFunc = objNode.childForFieldName('function');
3673
- if (superFunc?.type === 'identifier' && superFunc.text === 'super') {
3945
+ if (superFunc?.type === 'identifier' && superFunc.text === 'super' &&
3946
+ objNode.childForFieldName('arguments')?.namedChildCount === 0) {
3674
3947
  receiver = 'super';
3675
3948
  }
3676
3949
  }
@@ -3905,6 +4178,22 @@ function findCallsInTree(code, tree, parser, sink = null) {
3905
4178
  ...(firstArg && { firstStringArg: firstArg.value, firstStringArgInterp: firstArg.interp }),
3906
4179
  ...(requestConfig && { requestConfig })
3907
4180
  });
4181
+ // `self.__class__(..)` constructs the method's class (fix #434).
4182
+ const ownClass = attrNode.text === '__class__' ? pythonOwnClassConstruction(node) : null;
4183
+ if (ownClass) {
4184
+ calls.push({
4185
+ callSite: typeOrigin('call', ownClass.receiverNode),
4186
+ column: ownClass.receiverNode.startPosition.column,
4187
+ name: ownClass.name,
4188
+ line: node.startPosition.row + 1,
4189
+ isMethod: false,
4190
+ argCount,
4191
+ ...(argSpread && { argSpread: true }),
4192
+ enclosingFunction,
4193
+ uncertain,
4194
+ ownClass,
4195
+ });
4196
+ }
3908
4197
  }
3909
4198
  }
3910
4199
 
@@ -4602,6 +4891,16 @@ function findUsagesInTree(code, name, parser, tree, options = {}) {
4602
4891
  }
4603
4892
  }
4604
4893
 
4894
+ // A module path segment (`from a.b import c`, `import a.b [as x]`)
4895
+ // names a module, never a function, class or variable; the name of
4896
+ // `from m import name as alias` is m's attribute, decided by m (fix
4897
+ // #424). Neither is a reference resolved in this file's scopes.
4898
+ const importRole = pythonImportTokenRole(node);
4899
+ if (importRole) {
4900
+ usages.push({ line, column, usageType, ...(importRole === 'module-path'
4901
+ ? { modulePath: true } : { importSource: true }) });
4902
+ return true;
4903
+ }
4605
4904
  // Where a bare reference resolves (fix #392): plan edits it only
4606
4905
  // when the language's scoping proves it names the renamed binding.
4607
4906
  const scope = scopeMemo && usageType === 'reference'
@@ -4613,6 +4912,29 @@ function findUsagesInTree(code, name, parser, tree, options = {}) {
4613
4912
  return usages;
4614
4913
  }
4615
4914
 
4915
+ /**
4916
+ * The role of an identifier inside an import statement (fix #424):
4917
+ * 'module-path' for a segment of the imported module's dotted path,
4918
+ * 'import-source' for the imported name of `from m import name as alias`,
4919
+ * null otherwise (bound names and aliases).
4920
+ */
4921
+ function pythonImportTokenRole(node) {
4922
+ const dotted = node.parent;
4923
+ if (dotted?.type !== 'dotted_name') return null;
4924
+ const holder = dotted.parent;
4925
+ if (!holder) return null;
4926
+ if (holder.type === 'relative_import' || holder.type === 'import_statement') return 'module-path';
4927
+ if (holder.type === 'import_from_statement') {
4928
+ return sameNode(holder.childForFieldName('module_name'), dotted) ? 'module-path' : null;
4929
+ }
4930
+ if (holder.type === 'aliased_import' && sameNode(holder.childForFieldName('name'), dotted)) {
4931
+ const statement = holder.parent?.type;
4932
+ if (statement === 'import_statement') return 'module-path';
4933
+ if (statement === 'import_from_statement') return 'import-source';
4934
+ }
4935
+ return null;
4936
+ }
4937
+
4616
4938
  /**
4617
4939
  * Find instance attribute types from __init__ constructor assignments.
4618
4940
  * Parses self.X = ClassName(...) patterns in __init__ methods.
@@ -5085,10 +5407,13 @@ function findClassFieldFacts(tree, line, name) {
5085
5407
  visitBody(body);
5086
5408
  const written = new Set();
5087
5409
  let dynamic = false;
5088
- const pending = [body];
5089
- while (pending.length > 0) {
5090
- const node = pending.pop();
5091
- if (node.type === 'class_definition') continue;
5410
+ let nestedClassEnd = -1;
5411
+ for (const node of body.descendantsOfType(['class_definition', 'attribute', 'call'])) {
5412
+ if (node.startIndex < nestedClassEnd) continue;
5413
+ if (node.type === 'class_definition') {
5414
+ nestedClassEnd = node.endIndex;
5415
+ continue;
5416
+ }
5092
5417
  if (node.type === 'attribute') {
5093
5418
  const object = node.childForFieldName('object');
5094
5419
  const attribute = node.childForFieldName('attribute');
@@ -5102,7 +5427,6 @@ function findClassFieldFacts(tree, line, name) {
5102
5427
  if (fn?.type === 'identifier' && (fn.text === 'setattr' || fn.text === 'vars' ||
5103
5428
  fn.text === 'delattr') && first?.type === 'identifier' && receivers.has(first.text)) dynamic = true;
5104
5429
  }
5105
- for (let i = node.namedChildCount - 1; i >= 0; i--) pending.push(node.namedChild(i));
5106
5430
  }
5107
5431
  return { members, written, dynamic };
5108
5432
  }
@@ -5498,7 +5822,33 @@ function findScopeAlternativeBindings(tree, defLine, name) {
5498
5822
  return out;
5499
5823
  }
5500
5824
 
5825
+ /**
5826
+ * The annotated type of a plain receiver of an attribute reference (fix
5827
+ * #424): `env.config` inside `def f(env: Environment)` reads a member of
5828
+ * Environment when the parameter is the only binding of `env` in the scope
5829
+ * the reference resolves to. `column` is the attribute token's column.
5830
+ * Undefined when the receiver is rebound, unannotated or not a parameter.
5831
+ */
5832
+ function annotatedReceiverType(tree, line, column, receiver) {
5833
+ if (!tree || !receiver || receiver === 'self' || receiver === 'cls') return undefined;
5834
+ const token = tree.rootNode.descendantForPosition({ row: line - 1, column });
5835
+ const attribute = token?.parent;
5836
+ if (attribute?.type !== 'attribute' || !sameNode(attribute.childForFieldName('attribute'), token)) return undefined;
5837
+ const object = attribute.childForFieldName('object');
5838
+ if (object?.type !== 'identifier' || object.text !== receiver) return undefined;
5839
+ const { referenceScopeNode, pythonBindingSites } = require('./lexical-scope');
5840
+ const scope = referenceScopeNode(object, receiver);
5841
+ if (scope?.type !== 'function_definition') return undefined;
5842
+ const sites = pythonBindingSites(scope, receiver);
5843
+ if (sites.length !== 1) return undefined;
5844
+ const parameter = sites[0].parent;
5845
+ if (!parameter || !['typed_parameter', 'typed_default_parameter'].includes(parameter.type) ||
5846
+ parameter.parent?.type !== 'parameters') return undefined;
5847
+ return typeNameFromAnnotation(parameter.childForFieldName('type')) || undefined;
5848
+ }
5849
+
5501
5850
  module.exports = {
5851
+ annotatedReceiverType,
5502
5852
  findScopeAlternativeBindings,
5503
5853
  findPythonModuleEvidence,
5504
5854
  findFunctions,