mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-05 02:43:32 +00:00
feat(python-scope): write ACCESSES edges for attribute assignments
Three changes that together produce ACCESSES (write) edges for \`obj.field = value\` assignments: - New \`@reference.write.member\` capture in query.ts and scopes.scm matching \`(assignment left: (attribute object: ... attribute: ...))\`. Reuses the existing receiver/name capture shape so the receiver-bound emit pass can resolve obj's class and look up the field. - \`populateMethodOwnerIds\` now sets ownerId on class-body fields too, not only on methods. Previously it only walked Function scopes whose parent was Class; class-body annotations like \`name: str\` live directly in the Class scope's ownedDefs and were missed, so \`findOwnedMember(User, "name")\` returned undefined. - \`emit-core isLinkableLabel\` extends to Variable and Property so field nodes appear in the graph-node lookup (the legacy parser emits both kinds for class-body annotations). - Case 4 in receiver-bound pass now uses the kind word as the edge reason for read/write sites — matches the legacy DAG convention the test asserts on. Verification: - Flag-off: 191/191 (identical baseline). - Flag-on: 3 fail / 188 pass (was 4/187; +1 — write-ACCESSES test). - tsc --noEmit clean.
This commit is contained in:
parent
b059dce252
commit
56d6a463f0
4 changed files with 54 additions and 19 deletions
|
|
@ -44,6 +44,11 @@ export function isLinkableLabel(label: NodeLabel): boolean {
|
|||
label === 'Class' ||
|
||||
label === 'Interface' ||
|
||||
label === 'Struct' ||
|
||||
label === 'Enum'
|
||||
label === 'Enum' ||
|
||||
// Variable / Property are linkable too — receiver-bound write/read
|
||||
// ACCESSES edges target field nodes (e.g. `user.name = "x"` →
|
||||
// ACCESSES edge to User's `name` Variable/Property node).
|
||||
label === 'Variable' ||
|
||||
label === 'Property'
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -235,6 +235,15 @@ export const PYTHON_SCOPE_QUERY = `
|
|||
function: (attribute
|
||||
object: (_) @reference.receiver
|
||||
attribute: (identifier) @reference.name)) @reference.call.member
|
||||
|
||||
;; References — attribute writes: \`obj.name = "x"\` emits a write
|
||||
;; ACCESSES edge from the enclosing function to the field on obj's
|
||||
;; class. The receiver-bound emit pass resolves obj → its class and
|
||||
;; \`name\` → the field def via the existing typeref-receiver path.
|
||||
(assignment
|
||||
left: (attribute
|
||||
object: (_) @reference.receiver
|
||||
attribute: (identifier) @reference.name)) @reference.write.member
|
||||
`;
|
||||
|
||||
let _parser: Parser | null = null;
|
||||
|
|
|
|||
|
|
@ -246,3 +246,10 @@
|
|||
function: (attribute
|
||||
object: (_) @reference.receiver
|
||||
attribute: (identifier) @reference.name)) @reference.call.member
|
||||
|
||||
; Attribute write: `obj.name = "x"` — emits ACCESSES (write) edge from
|
||||
; the enclosing function to the field on obj's class.
|
||||
(assignment
|
||||
left: (attribute
|
||||
object: (_) @reference.receiver
|
||||
attribute: (identifier) @reference.name)) @reference.write.member
|
||||
|
|
|
|||
|
|
@ -533,15 +533,14 @@ function emitReceiverBoundCalls(
|
|||
if (memberDef !== undefined) break;
|
||||
}
|
||||
if (memberDef !== undefined) {
|
||||
const ok = tryEmitEdge(
|
||||
graph,
|
||||
scopes,
|
||||
nodeLookup,
|
||||
site,
|
||||
memberDef,
|
||||
'python-scope: typeref-receiver',
|
||||
seen,
|
||||
);
|
||||
// For read/write ACCESSES, mirror the legacy DAG's reason
|
||||
// convention (just the kind word) so consumers asserting
|
||||
// `reason === 'write'` keep working.
|
||||
const reason =
|
||||
site.kind === 'write' || site.kind === 'read'
|
||||
? site.kind
|
||||
: 'python-scope: typeref-receiver';
|
||||
const ok = tryEmitEdge(graph, scopes, nodeLookup, site, memberDef, reason, seen);
|
||||
if (ok) {
|
||||
emitted++;
|
||||
handledSites.add(siteKey);
|
||||
|
|
@ -958,15 +957,30 @@ function populateMethodOwnerIds(parsed: ParsedFile): void {
|
|||
for (const scope of parsed.scopes) scopesById.set(scope.id, scope);
|
||||
|
||||
for (const scope of parsed.scopes) {
|
||||
if (scope.parent === null) continue;
|
||||
const parentScope = scopesById.get(scope.parent);
|
||||
if (parentScope === undefined || parentScope.kind !== 'Class') continue;
|
||||
|
||||
const classDef = parentScope.ownedDefs.find((d) => d.type === 'Class');
|
||||
if (classDef === undefined) continue;
|
||||
|
||||
for (const def of scope.ownedDefs) {
|
||||
(def as { ownerId?: string }).ownerId = classDef.nodeId;
|
||||
// Methods (Function scopes whose PARENT is Class): set ownerId
|
||||
// on every def in the function's own scope.
|
||||
if (scope.parent !== null) {
|
||||
const parentScope = scopesById.get(scope.parent);
|
||||
if (parentScope !== undefined && parentScope.kind === 'Class') {
|
||||
const classDef = parentScope.ownedDefs.find((d) => d.type === 'Class');
|
||||
if (classDef !== undefined) {
|
||||
for (const def of scope.ownedDefs) {
|
||||
(def as { ownerId?: string }).ownerId = classDef.nodeId;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Class-body fields (defs structurally owned by the Class scope
|
||||
// itself — class-body annotations like `name: str`): set ownerId
|
||||
// on every def except the class itself.
|
||||
if (scope.kind === 'Class') {
|
||||
const classDef = scope.ownedDefs.find((d) => d.type === 'Class');
|
||||
if (classDef !== undefined) {
|
||||
for (const def of scope.ownedDefs) {
|
||||
if (def === classDef) continue;
|
||||
(def as { ownerId?: string }).ownerId = classDef.nodeId;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue