Skip to content

Commit d1196ab

Browse files
Merge pull request #1651 from AxiomCodeAI/fix/schema-guide-per-language-layout
python: 1-based field lines, and a per-language schema guide for the skill
2 parents b271a22 + a7dac96 commit d1196ab

10 files changed

Lines changed: 396 additions & 63 deletions

File tree

‎graph/bundle/SCHEMA.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -672,7 +672,7 @@ Methods the runtime invokes without a client call site — process roots, test m
672672

673673
**Notes**
674674

675-
- **python** — EMPTY. The Python rule set does not derive entry points; entry_reachable is therefore empty too.
675+
- **python** — Framework entry points only: url, http, orm_hook, task, signal_receiver, fixture, di_provider and grpc_service. There is no test and no main reason: a pytest test is recognised by the query layer from its file and name, not here.
676676

677677
### `entry_reachable`
678678

@@ -729,7 +729,7 @@ Every field-like storage location the graph refers to: all client fields and enu
729729

730730
**Notes**
731731

732-
- **all** — JAVA AND TYPESCRIPT, for the same reason as field_access: declared everywhere, populated by those two front ends.
732+
- **all** — JAVA, TYPESCRIPT AND C#. EMPTY for Python and JavaScript: a Python attribute is a symbols row of kind field. In C# it holds true fields only; a property is a symbols row of kind field with a property id, and its accessors are methods rows (PROPERTY_GET, PROPERTY_SET, PROPERTY_INIT).
733733
- **typescript** — An enum member is absent: the parser gives it its own table with no declared type, and the property-access relation resolves through the field table. `Colour.Red` is therefore an unresolved field access, unlike Java where an enum constant is a fields row.
734734
- **java** — A library field is listed when some field_access edge reaches it, exactly as methods lists only the library methods an edge reaches, OR when a config_binding row names it as the field a configuration key binds to. The second was added in #890: a @Value field on a library type that nothing reads has no access edge, so config_binding named a field the table did not list and the join lost the row silently.
735735

@@ -888,7 +888,7 @@ THE OTHER HALF OF CHANGE IMPACT: one row per place a type is NAMED, with the con
888888

889889
**Notes**
890890

891-
- **all** — JAVA AND TYPESCRIPT. Declared in every bundle and EMPTY for Python and JavaScript, so the schema does not churn as the remaining front ends land (#663).
891+
- **all** — JAVA AND TYPESCRIPT. Declared in every bundle and EMPTY for Python, JavaScript and C#, so the schema does not churn as the remaining front ends land (#663).
892892
- **typescript** — The context set is TypeScript's own and is wider than Java's: AS_TARGET, SATISFIES_TARGET, TYPE_ALIAS_RHS, the CONDITIONAL_* family, MAPPED_*, INDEX_SIGNATURE_* and TEMPLATE_SPAN have no Java counterpart. A use inside a conditional type IS a use of that type and is recorded as one.
893893
- **typescript** — Only a reference whose KIND can name a declaration is a row: TYPE_REFERENCE and IMPORT_TYPE. ARRAY, UNION, TUPLE and PARENTHESIZED are structure whose CHILDREN are the named references; PRIMITIVE, LITERAL, TYPE_VARIABLE, MAPPED, CONDITIONAL, INDEXED_ACCESS and INTRINSIC name nothing declared.
894894
- **java** — EVERY DEPTH is here, unlike the receiver-typing relations the engine uses internally, which filter to depth 0. A field of type `Map<String, Widget>` produces three rows. Filter on `depth = 0` when you want the type an expression has rather than every type its declaration mentions.

‎graph/bundle/schema.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -747,21 +747,21 @@ export const NOTES: readonly NoteSpec[] = [
747747
{ language: 'python', table: 'call_edges', note: 'A `boundary_lib` edge may point at a builtin (callee_provenance builtin, callee_label `builtin:NAME`) or at an unstaged import path (callee_provenance external) — neither has a methods row.' },
748748
{ language: 'java', table: 'call_edges', note: 'A `boundary_lib` edge with callee_provenance external names a method of an ancestor type no staged IR declares (callee_label `external:<type>.<name>`, no methods row). A site whose receiver is declared as such a type is multi_inferred even with one client override: the platform method itself, and the platform\'s own subclasses, are the other possible targets. Stage the library to replace the label with the real method.' },
749749
{ language: 'python', table: 'call_edges', note: 'The reason a site is ambiguous_unknown is exported per site in ext_call_site_unresolved (site, caller, reason, detail).' },
750-
{ language: 'python', table: 'entry_points', note: 'EMPTY. The Python rule set does not derive entry points; entry_reachable is therefore empty too.' },
750+
{ language: 'python', table: 'entry_points', note: 'Framework entry points only: url, http, orm_hook, task, signal_receiver, fixture, di_provider and grpc_service. There is no test and no main reason: a pytest test is recognised by the query layer from its file and name, not here.' },
751751
{ language: 'python', table: 'overrides', note: 'EMPTY — this table is Java-shaped. The Python dispatch envelope is in dispatch_candidates with basis `mro`, and `value` for a function assigned onto an instance\'s member; the raw linearisation is in ext_mro_position.' },
752752
{ language: 'python', table: 'type_instantiated', note: 'Every row has how = `new`: the rule set records that some client call constructs the class, not which form.' },
753753
{ language: 'all', table: 'call_edges', note: 'THE TRUST LINE, and it is not the same set of tiers in every language. RESOLVED (callee_method_id is set): known_edge and multi_inferred in every language, and boundary_lib where the library is staged (--library) — without it boundary_lib names the target in callee_label and leaves callee_method_id NULL; ALSO ambient_terminal in TypeScript, fan_capped in Java and C# (the declared base, the fan refused), and runtime_observed in C#. HANDED OVER (callee set, but the site passes the function rather than calling it): callback_registered and event_dispatch in JavaScript; event_dispatch in C# too, where a mediator Send or Publish runs the handler for the request type, beside the row for the site itself. CORRECT END (callee NULL, and nothing is missing): intrinsic_terminal in TypeScript; ambient_terminal, implicit_constructor and dynamic_terminal in JavaScript; known_implicit_ctor, known_builtin_operator and boundary_generated (callee_label set) in C#. BLIND SPOT (callee NULL; exactly the tiers named `ambiguous_*`, which are what unresolved_sites holds): ambiguous_unknown everywhere, ALSO ambiguous_anon in Java and ambiguous_dynamic in C#. CAPPED (callee NULL, not in unresolved_sites): fan_capped in JavaScript. Python emits only the four shared tiers. A filter written as `tier IN (known_edge, multi_inferred)` therefore drops resolved edges in every language but Python — derive the set from this note or from unresolved_sites, never from a hardcoded list.' },
754754
{ language: 'java', table: 'call_edges', note: 'A multi_inferred fan is CHA-wide: it is every override the hierarchy admits, bounded only by the dispatch cap. type_instantiated is computed and exported but NOT read by any rule, so the fan is not narrowed to types the program constructs. Narrow it yourself by joining dispatch_candidates to type_instantiated — see the dispatch_envelope_of query. A receiver is ALSO typed by what flows into it (a local\'s initializer, the arguments callers pass to a parameter, the receivers callers invoke a method on for its `this`), and each flow-in type resolves its member directly, outside the fan: that is why a `fan_capped` site still carries edges, and why they are the types the program was seen to hand over, not the whole hierarchy.' },
755755
{ language: 'typescript', table: 'call_edges', note: 'A multi_inferred fan is CHA-wide, as in Java: type_instantiated is computed and exported but NOT read by any rule. The fan also has sources that are not virtual dispatch at all — an overload set or a union-typed receiver produces one too.' },
756756
{ language: 'python', table: 'call_edges', note: 'A multi_inferred fan IS narrowed by the instantiation set: type_instantiated_reachable (the constructed classes and their bases) bounds dispatch in resolution/dispatch.dl. Python is the only front end where that narrowing is applied, so a fan here is tighter than the same shape would be in Java or TypeScript.' },
757-
{ language: 'all', table: 'type_use', note: 'JAVA AND TYPESCRIPT. Declared in every bundle and EMPTY for Python and JavaScript, so the schema does not churn as the remaining front ends land (#663).' },
757+
{ language: 'all', table: 'type_use', note: 'JAVA AND TYPESCRIPT. Declared in every bundle and EMPTY for Python, JavaScript and C#, so the schema does not churn as the remaining front ends land (#663).' },
758758
{ language: 'typescript', table: 'type_use', note: 'The context set is TypeScript\'s own and is wider than Java\'s: AS_TARGET, SATISFIES_TARGET, TYPE_ALIAS_RHS, the CONDITIONAL_* family, MAPPED_*, INDEX_SIGNATURE_* and TEMPLATE_SPAN have no Java counterpart. A use inside a conditional type IS a use of that type and is recorded as one.' },
759759
{ language: 'typescript', table: 'type_use', note: 'Only a reference whose KIND can name a declaration is a row: TYPE_REFERENCE and IMPORT_TYPE. ARRAY, UNION, TUPLE and PARENTHESIZED are structure whose CHILDREN are the named references; PRIMITIVE, LITERAL, TYPE_VARIABLE, MAPPED, CONDITIONAL, INDEXED_ACCESS and INTRINSIC name nothing declared.' },
760760
{ language: 'java', table: 'type_use', note: 'EVERY DEPTH is here, unlike the receiver-typing relations the engine uses internally, which filter to depth 0. A field of type `Map<String, Widget>` produces three rows. Filter on `depth = 0` when you want the type an expression has rather than every type its declaration mentions.' },
761761
{ language: 'java', table: 'type_use', note: 'A TYPE_VARIABLE reference (`T`, `E`) is not a row: it names the declaration\'s own parameter, not a type. Where the parameter has a written bound the USE resolves to that bound and IS a row, so `<T extends Node> void f(T t)` records a use of Node.' },
762762
{ language: 'java', table: 'type_use', note: 'The reference rows carry no line in the Java IR (every all-type-references row has an empty startLine), so this table has no position columns. Use owner_method_id, or owner_type_id plus the types row, to locate a use.' },
763763
{ language: 'all', table: 'field_access', note: 'JAVA AND TYPESCRIPT. The table is declared in every bundle and is EMPTY for Python, JavaScript and C# (C# fills `fields`, not `field_access`), so the schema does not churn as the remaining front ends land (#663). Check `SELECT count(*) FROM field_access` before reading an empty result as "nothing reads this field".' },
764-
{ language: 'all', table: 'fields', note: 'JAVA AND TYPESCRIPT, for the same reason as field_access: declared everywhere, populated by those two front ends.' },
764+
{ language: 'all', table: 'fields', note: 'JAVA, TYPESCRIPT AND C#. EMPTY for Python and JavaScript: a Python attribute is a symbols row of kind field. In C# it holds true fields only; a property is a symbols row of kind field with a property id, and its accessors are methods rows (PROPERTY_GET, PROPERTY_SET, PROPERTY_INIT).' },
765765
{ language: 'typescript', table: 'field_access', note: 'AN ACCESSOR IS NOT HERE. `get url()` read as `c.url` is a CALL, and call_edges already carries it with kind PROPERTY_READ or PROPERTY_WRITE (#703). The two tables are disjoint by construction: this one holds properties, call_edges holds accessors. Ask both when you want every read of a member.' },
766766
{ language: 'typescript', table: 'field_access', note: 'AN ELEMENT ACCESS IS NOT HERE either: `obj["x"]` with a literal key is a different node kind and is not yet a site. A known gap, not a silent one.' },
767767
{ language: 'typescript', table: 'field_access', note: 'A METHOD IS NOT A SITE. The callee of `obj.m()` is a PROPERTY_ACCESS node (37% of them, measured on one TypeScript library), and `const f = obj.m` reads a method as a value; neither is a data edge, and admitting them would fill the ambiguous_unknown tier with sites the engine HAS resolved elsewhere. Both are excluded and counted in ext_field_site_excluded with reasons method_callee and method_value.' },

‎parser/src/parsers/python/extractors/python-field-extractor.ts‎

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,11 @@ interface WriteObservation {
8383
*/
8484
writtenBuiltinType: string;
8585
origin: PythonFieldOrigin;
86+
/**
87+
* 1-based, like every other Python relation. tree-sitter rows are 0-based, so
88+
* each site adds 1; without it every field sat one line above its write, and a
89+
* nested class's first field landed on the `class Meta:` line itself.
90+
*/
8691
line: number;
8792
endLine: number;
8893
methodHash: string;
@@ -416,8 +421,8 @@ export class PythonFieldExtractor {
416421
collection.observations.push({
417422
name,
418423
origin,
419-
line: target.startPosition.row,
420-
endLine: assignment.endPosition.row,
424+
line: target.startPosition.row + 1,
425+
endLine: assignment.endPosition.row + 1,
421426
methodHash: '',
422427
methodName: '',
423428
receiverName: '',
@@ -448,8 +453,8 @@ export class PythonFieldExtractor {
448453
collection.observations.push({
449454
name: this.mangle(collection.typeName, literal),
450455
origin: PythonFieldOrigin.SLOTS_ENTRY,
451-
line: node.startPosition.row,
452-
endLine: node.endPosition.row,
456+
line: node.startPosition.row + 1,
457+
endLine: node.endPosition.row + 1,
453458
methodHash: '',
454459
methodName: '',
455460
receiverName: '',
@@ -728,8 +733,8 @@ export class PythonFieldExtractor {
728733
collection.observations.push({
729734
name: this.mangle(collection.typeName, literal),
730735
origin: PythonFieldOrigin.SETATTR_DYNAMIC,
731-
line: call.startPosition.row,
732-
endLine: call.endPosition.row,
736+
line: call.startPosition.row + 1,
737+
endLine: call.endPosition.row + 1,
733738
methodHash,
734739
methodName,
735740
receiverName,
@@ -764,8 +769,8 @@ export class PythonFieldExtractor {
764769
collection.observations.push({
765770
name: this.mangle(collection.typeName, nameOverride || attributeName!.text),
766771
origin,
767-
line: target.startPosition.row,
768-
endLine: statement.endPosition.row,
772+
line: target.startPosition.row + 1,
773+
endLine: statement.endPosition.row + 1,
769774
methodHash,
770775
methodName,
771776
receiverName,

0 commit comments

Comments
 (0)