Skip to content

tstypes: the extent guard drops the extractor's own attribution for partial and conditionally-compiled C# properties #731

Description

@zzet

Follow-up from the review of #722.

buildIndex's third admission guard tests the call edge's line against the owner node's StartLine/EndLine:

if e.Line < n.StartLine || e.Line > n.EndLine {
    // Framework-dispatch synthesis (Rails callbacks, Laravel middleware)
    // parks an owner's edge on a line outside the owner's own span —
    // not site evidence at that line.
    continue
}

The C# extractor deliberately owns calls by a different span. csharpOwnerRanges says as much: "The recorded line span wins over the node's: a partial property's node keeps the declaring fragment's lines while the code lives in the implementing fragment." emitProperty re-records ownership via csharpRecordPropertyOwnership on the second, body-bearing fragment without re-minting the node.

So whenever a property id is declared twice in one file and the body-bearing fragment is not first, the extractor parks a byte-precise stub on the property at a line outside the node's span, the guard drops it, stubOwnersAt returns zero owners, applyCall falls back to enclosingCallable (functions only, so nil), and the site is refused. Adoption silently never fires for exactly the owner kind this PR exists to serve.

Two shapes that hit it

C# 13 partial property, declaring fragment first:

namespace B {
    public partial class App {
        private Svc worker;
        public partial int Tick { get; set; }
    }

    public partial class App {
        public partial int Tick {
            get { worker.Run(); return 1; }
        }
    }
}

Node span [4..4]; extractor stub B/App.cs::App.Tick -> unresolved::*.Run @ line 9. Enrich reports EdgesConfirmed: 0, the edge stays unresolved::*.Run with empty Origin.

Conditional compilation — a property declared in both arms of an #if / #else (tree-sitter parses both): node span [5..5], stub at line 7, same refusal.

With the guard neutered, both resolve to A/Svc.cs::Svc.Run with Origin=ast_resolved, semantic_source=csharp-types, EdgesConfirmed: 1.

On the guard's stated justification

The framework-dispatch shape the comment names is real — I confirmed Ruby before_action edges parked on PostsController.show[13..15] at lines 2–3, and Laravel HomeController.index[6..8] / show[9..11] edges on the constructor line 4. So the guard is doing a genuine job and should not simply be deleted. But "not site evidence at that line" is false for the C# stubs above: they are the authored site.

The fix is probably to test against the extractor's recorded ownership span rather than the node's declaration span, so the two disagree only where the owner genuinely did not author the call.

Scope

Not a regression — 903b520b refuses the same sites via nil enclosingCallable. It is a hole in the fix. A census over 21,381 calls edges extracted from 400 of this repo's own Go files found 0 out-of-span, 0 zero-line and 0 empty-FilePath edges, so the guard costs nothing outside this C# fragment-order shape.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions