mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-10 03:27:59 +00:00
fix(python): a method call on the receiver is not a construction
Review finding on f4e1ead0d. `constructorCallTypeName` accepted ANY call with an identifier or attribute callee, so `self.p = self.build()` bound `p` to the non-type `"self.build"` — and because that shares the weakest tier with a real construction, a later such assignment DISPLACED an earlier `self.p = Outer()` and left the field untyped again. Measured before the fix: `self.p = Outer()` followed by `self.p = self.rebuild()` emitted no CALLS edge at all from a method chaining off `self.p`, and `self.q = self.make()` bound a type name that resolves to nothing. After: the real construction survives the reassignment, and a pure method call binds nothing rather than something wrong. Rejects a callee rooted at the receiver name. `models.Outer()` still binds — only `self`-rooted callees are refused, which is exactly the method-call shape. The matrix gains a `reassigned-from-method-call` row that fails without this rejection; that discrimination is the only reason the row exists. Refs #2807 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
b6aa45d93d
commit
1aba80d942
2 changed files with 39 additions and 3 deletions
|
|
@ -126,13 +126,24 @@ export function synthesizeReceiverTypeBinding(fnNode: SyntaxNode): CaptureMatch
|
|||
* (subscript, await, comprehension, bare name) is left alone — that is the
|
||||
* "name-only guess" this module deliberately refuses.
|
||||
*/
|
||||
function constructorCallTypeName(right: SyntaxNode | null): string | undefined {
|
||||
function constructorCallTypeName(
|
||||
right: SyntaxNode | null,
|
||||
receiverName: string,
|
||||
): string | undefined {
|
||||
if (right === null || right.type !== 'call') return undefined;
|
||||
const callee = right.childForFieldName('function');
|
||||
if (callee === null) return undefined;
|
||||
if (callee.type !== 'identifier' && callee.type !== 'attribute') return undefined;
|
||||
const text = callee.text.trim();
|
||||
return text.length > 0 ? text : undefined;
|
||||
if (text.length === 0) return undefined;
|
||||
// `self.p = self.build()` is a METHOD call, not a construction. Accepting it
|
||||
// bound `p` to the non-type `"self.build"`, which resolves to nothing — and
|
||||
// because it shares this weakest tier, a later such assignment DISPLACED an
|
||||
// earlier real `self.p = Outer()`, leaving the field untyped again. Measured:
|
||||
// both `self.q = self.make()` and the displacement pair emitted no CALLS edge
|
||||
// at all. Rejecting a callee rooted at the receiver keeps the construction.
|
||||
if (text === receiverName || text.startsWith(`${receiverName}.`)) return undefined;
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -217,7 +228,7 @@ export function synthesizeConstructorFieldTypeBindings(fnNode: SyntaxNode): Capt
|
|||
// an explicit annotation or a parameter annotation still wins.
|
||||
const constructedType =
|
||||
explicitType === null && parameterType === undefined
|
||||
? constructorCallTypeName(right)
|
||||
? constructorCallTypeName(right, receiverName)
|
||||
: undefined;
|
||||
const typeName = explicitType?.text ?? parameterType ?? constructedType;
|
||||
if (typeName !== undefined) {
|
||||
|
|
|
|||
|
|
@ -142,6 +142,18 @@ class AssignedField:
|
|||
|
||||
def run(self, x):
|
||||
return self.q.inner().compute(x)
|
||||
|
||||
|
||||
class ReassignedField:
|
||||
def __init__(self):
|
||||
self.r = Outer()
|
||||
self.r = self.rebuild()
|
||||
|
||||
def rebuild(self):
|
||||
return Outer()
|
||||
|
||||
def run(self, x):
|
||||
return self.r.inner().compute(x)
|
||||
`;
|
||||
|
||||
// ── Ruby ─────────────────────────────────────────────────────────────────────
|
||||
|
|
@ -376,6 +388,19 @@ const CASES: readonly LanguageCase[] = [
|
|||
targets: [`Method:${PY_FILE}:Outer.inner#0`],
|
||||
status: 'resolves',
|
||||
},
|
||||
// A method call is not a construction. `self.r = Outer()` followed by
|
||||
// `self.r = self.rebuild()` must keep the FIRST binding: both would sit
|
||||
// in the weakest tier, so accepting `self.rebuild()` as a constructor let
|
||||
// the later one displace the real type and the field went untyped again —
|
||||
// measured as zero CALLS edges before `constructorCallTypeName` learned to
|
||||
// reject a callee rooted at the receiver. This row fails without that
|
||||
// rejection, which is the only reason it exists.
|
||||
{
|
||||
name: 'reassigned-from-method-call',
|
||||
callerId: `Method:${PY_FILE}:ReassignedField.run#1`,
|
||||
targets: [`Method:${PY_FILE}:Outer.inner#0`],
|
||||
status: 'resolves',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue