Skip to content

Shared import-capture vocabulary instead of ~21 per-language import extractors #358

Open

Description

@joyful-ii-V-I

Part of the language-architecture cleanup tracked in #354 ("common stays common"). This is the largest step, so it goes last.

Problem. src/ingest_relations.h has about 21 per-language import-target extractors (csharpUsingTarget, luaRequireTarget, phpUseTarget, rubyRequireTarget, jsModuleLoadTarget, elixirDirectiveTarget, …). Most do the same thing: find the import statement's target text and normalise it.

Change. A shared import capture vocabulary in tags.scm (for example @import.path, @import.alias, @import.names) plus one small specifier normaliser per DepDialect, instead of an extractor per language. Dependency resolution stays in src/resolve.h.

Acceptance. Dependency edges byte-identical on every dependency-capable language (existing resolve/deps gates); the per-language extractors removed as each language moves over. It can land one language at a time.

Size: about 5 days in total, but splittable per language. Comment here to claim a language.

Activity

  1. lennix1337 commented on Oct 1, 2026

    @lennix1337
    Contributor

    Claiming C-family (#include / #import for c, cpp, objc, cuda, metal) for the first slice.

    Rationale: it is the largest family, its extractors are the simplest ones (preprocIncludeTarget, preprocImportTarget, includePathOf in src/ingest_relations.h), and it exercises both hard parts of the proposed design — the quote-vs-angle delimiter and a directive-text guard (#import vs #pragma) that a tree-sitter query cannot express. If the shared vocabulary works here it works everywhere; a language that needs a workaround would be a bad first choice.

    Two things I found reading the code that shape the implementation, and that the issue does not mention:

    1. Tags-pass predicates never run here (stated in queries/python/tags.scm, pinned by test/matchgrammarcheck.sh). So the #import vs #pragma/#error discrimination cannot move into tags.scm as an #eq? — it stays a C++ gate reading the captured directive text. Same for the require/import bare-callee guard later.
    2. captureIncludes runs before the tags pass (src/ingest_sidecap.h:1550 vs :1706) and carries walk-frame state the tags pass has no access to (insideFn for lazy requires, depth for the ImportNestingTooDeep disclosure, openIdx for the Ruby receiver dedupe). For C-family none of that is set, so this slice can move cleanly, but the shared plumbing has to re-derive frame state from ancestry per captured node for the languages that need it.

    Acceptance as stated: dependency edges byte-identical. For C-family that means test/preproccondcheck.sh, test/includeprecisecheck.sh, test/includeanglecheck.sh, test/crossdirincludecheck.sh, test/depsprecisecheck.sh, test/resolvecheck.sh plus the full suite, and a test/importcapturecheck.sh added to test/regression.sh in the same commit.

    Not claiming: the languages where the walk-frame state is load-bearing (TS/JS lazy requires, Ruby receivers) — those need the ancestry re-derivation to land first.

  2. added 8 commits that reference this issue on Oct 1, 2026
  3. added a commit that references this issue on Oct 2, 2026
  4. joyful-ii-V-I commented on Oct 4, 2026

    @joyful-ii-V-I
    CollaboratorAuthor

    Thanks @lennix1337 — #368 (C-family on one @import.path capture) is merged, with byte-identical dependency edges exactly as the acceptance asked. Nicely done.

    The remaining slices are open. If you — or anyone — would like one, comment here to claim it; same acceptance as above (dependency edges byte-identical on the existing resolve/deps gates; the per-language extractor removed as each language moves over). Go, JS/TS and Python are the ones we'd most like to see next, ideally with @import.alias / @import.names alongside @import.path, since those are our most-used languages.

    If a slice is still unclaimed by Thursday 2026-10-08, we'll start picking up the unclaimed ones ourselves. A claimed slice stays yours for as long as you're working on it.

  5. LaimaWu commented on Oct 7, 2026

    @LaimaWu
    Contributor

    I’d like to take the Python slice.

    I’ll keep this scoped to Python: move its import extraction onto the shared capture vocabulary (@import.path, and @import.alias / @import.names where the current Python semantics support them), remove only the Python-specific extractor that becomes redundant, and preserve existing dependency/resolve behavior byte-for-byte.

    I’ll stop rather than broaden scope if Python exposes shared-plumbing requirements that belong in a separate slice.

  6. joyful-ii-V-I commented on Oct 7, 2026

    @joyful-ii-V-I
    CollaboratorAuthor

    Thanks @LaimaWu — the Python slice is yours. Your scoping is exactly right: Python only, @import.path plus @import.alias / @import.names where the current semantics support them, remove only the Python extractor that becomes redundant, dependency/resolve output byte-identical, and stop rather than broaden if shared plumbing turns up (a short note here is the perfect way to flag that).

    The Go and JS/TS slices are still open to anyone until Thursday 2026-10-08, as posted above; after that we'll pick up whatever is unclaimed ourselves.

  7. 6 remaining items

  8. added a commit that references this issue on Oct 8, 2026
  9. joyful-ii-V-I commented on Oct 8, 2026

    @joyful-ii-V-I
    CollaboratorAuthor

    @LaimaWu a heads-up for the Python slice, so you don't hit surprises: our JS/TS slice (branch lane/import-capture-jsts-358, reviewed, merging with the next train) changes the shared emission in src/ingest_importcap.h.

    • importContainerReach now returns {depth, reachable, insideFn} from one descent from the root (the old upward ts_node_parent walk was quadratic on deeply nested files). The normaliser runs before the depth test, so return empty only for non-imports and never depend on depth.
    • To switch the old walk off for Python, add DepDialect::Python to importsFromCapture(), not to captureSideFacts.
    • importDirectiveOf picks the node the Include is sited at and whose ancestry is read: for Python that should be the import_statement / import_from_statement, not an inner dotted_name under aliased_import.
    • captureIncludes also writes Python's bound names (capturePythonImportBinds → RawBind), and emitCapturedImport has no binds channel yet. That is exactly the shared-plumbing point your scoping says to flag — happy to sort it out with you here before you build on it.

    If your branch predates the JS/TS merge, expect conflicts in those spots; we're glad to help with the rebase.

  10. added a commit that references this issue on Oct 8, 2026
  11. LaimaWu commented on Oct 8, 2026

    @LaimaWu
    Contributor

    @LaimaWu a heads-up for the Python slice, so you don't hit surprises: our JS/TS slice (branch lane/import-capture-jsts-358, reviewed, merging with the next train) changes the shared emission in src/ingest_importcap.h.

    • importContainerReach now returns {depth, reachable, insideFn} from one descent from the root (the old upward ts_node_parent walk was quadratic on deeply nested files). The normaliser runs before the depth test, so return empty only for non-imports and never depend on depth.
    • To switch the old walk off for Python, add DepDialect::Python to importsFromCapture(), not to captureSideFacts.
    • importDirectiveOf picks the node the Include is sited at and whose ancestry is read: for Python that should be the import_statement / import_from_statement, not an inner dotted_name under aliased_import.
    • captureIncludes also writes Python's bound names (capturePythonImportBinds → RawBind), and emitCapturedImport has no binds channel yet. That is exactly the shared-plumbing point your scoping says to flag — happy to sort it out with you here before you build on it.

    If your branch predates the JS/TS merge, expect conflicts in those spots; we're glad to help with the rebase.

    Thanks for the heads-up. The Python slice is now in PR #382, based on 60dd3b3.

    The current implementation emits Python dependencies through @import.path and bindings through a Python-specific RawBind helper using @import.names / @import.alias. It retains the bounded Python walk solely for nesting disclosure, including empty deep containers.

    I can see the overlap with your incoming importContainerReach, importDirectiveOf, and importsFromCapture changes. I’ll hold off on rebasing against the unmerged JS/TS branch or making speculative shared-plumbing changes.

    Would you prefer keeping the Python-specific binding/disclosure handling in #382 while adapting to the new shared interfaces, or moving binding emission into the shared API as part of integration? Happy to align with whichever approach preserves existing behavior with the smallest scope.

    Once coordinated, I’ll rebase and rerun the differential gates.

  12. added 6 commits that reference this issue on Oct 12, 2026
  13. added a commit that references this issue on Oct 12, 2026
  14. added a commit that references this issue on Oct 12, 2026
  15. LaimaWu commented on Oct 13, 2026

    @LaimaWu
    Contributor

    Hi @joyful-ii-V-I, I'd like to claim the C# slice 1a of #358, following the merged Python slice #382.

    I'll keep this limited to moving C# import extraction onto @import.path and removing the redundant extractor, preserving existing dependency and binding behavior without touching the shared binds API.

    Preflight identified two points to resolve before implementation:

    1. A possible discrepancy in C# using-alias extraction. I'll first establish the actual behavior on current main and preserve it in this refactor.
    2. The same import-free deep-container disclosure/cacheability difference encountered in Python refactor(#358): move Python import edges to shared captures #382. Would the corresponding exception be acceptable for C#?

    I'll keep implementation on hold until the baseline is verified and the scope is clear. Thanks!

  16. joyful-ii-V-I commented on Oct 15, 2026

    @joyful-ii-V-I
    CollaboratorAuthor

    Thanks @LaimaWu, and sorry for the slow reply. The C# slice of 1a is yours.

    Same acceptance as your Python slice (#382):

    • C# using directives move onto the shared @import.path capture plus a normaliser arm.
    • csharpUsingTarget is deleted from directiveTargetOf.
    • Dependency edges stay byte-identical.

    Leave using X = … aliases where they are for now. They belong to slice 2b, which follows the shared binds channel (2a).

    One new tool since #382: test/langidentitycheck.sh BASE HEAD (from #399, now on main) runs the byte-identity sweep for you. Its output is the evidence the definition of done in CONTRIBUTING asks for.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requesthelp wantedExtra attention is needed

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions