fix(semantics): import a re-exported extension member through the re-exporting module #189

Merged
argus merged 1 commit from loop/182-reexported-member-import into main 2026-10-08 12:35:55 +00:00
Collaborator

Task

Closes #182

What and why

A member re-exported with public use Text.doubled from "./ext" could not be imported from the re-exporting module: use Text.doubled from "./lib" failed with "Text is not exported by ./lib".

bindMemberImport (compiler/src/semantics/scope.trb) handed on the re-exporting module's entries with only the span replaced, so entry.path stayed "./ext". memberImportOf matches the importer's item by span, the path written in its use, and the name, so it found nothing, and the item fell into the export check.

The fix:

  • The path written at each hop. A handed-on entry now takes the importer's path as well as its span (entry.copy(path: path, span: at)). memberImportOf finds it, and "<path> adds no member ..." names the path written on the importer's line, not the original ./ext.
  • Each use item owns its entries. bindMember de-duplicated on package and name, so the item's entry could be dropped when the scope already held one from another item. bindMembers now replaces the entries at the item's span (in members, and in exportedMembers for a public use) and reports a change only when they differ, so the fixpoint still ends.
  • A first pass no longer leaves a wrong entry. A pass that reaches the importer before the re-exporting module has bound its own re-export records the member as one of the re-exporting module's package. Once the re-export is bound, its entries replace that one. Before, both stayed: a package re-exporting a member of its own dependency then reported "acme/other adds no member ..." for a member that resolves.

memberImportOf and the prelude's addPreludes are unchanged: the prelude's re-exports (public use Int64.seconds from "std/time") are still added after the fixpoint, de-duplicated on package and name.

Test

New tests in compiler/tests/check.test.trb, group "Modules", in the messages(files) style of the existing member-import tests:

  • ext extends Text with doubled, lib re-exports it, main imports it from ./lib and calls it: no problems.
  • A two-hop chain (main -> lib -> mid -> ext), imported with as twice and called as value.twice(): no problems.
  • The same chain imported by the package path (use Text.doubled from "acme/other", other/src/lib.trb re-exporting from ./ext): no problems.
  • A package re-exporting a member of its own dependency (acme/other re-exports from acme/third), imported by app: no problems. This covers the replacement of the first pass's entry.
  • Regression: a member the package does not add, imported through the re-exporting module, reports "./lib adds no member tripled to Text" at the importer's use line (span 9..16 of main.trb). This holds both when lib does not re-export the member and when lib re-exports one that ./ext does not add.

The issue's fixture uses Bytes. The test workspace's prelude has no Bytes, so the tests extend Text, which is the same case. The existing member-import tests and the prelude tests are unchanged.

Locally: torb format (today's nightly) on the two touched files. Check, format and the suite run in the forge CI (tier A; tier B is not required because nothing under compiler/src/ir, backend or runtime changed).

Seed: no. There is no syntax change, no new native, no driver flag, and no std name looked up by string.

CI round 1 (run 225): tier A red only on format --check of the two touched files (two command calls, one blank line); test compiler/tests (native) passed. Fixed in the same commit.

## Task Closes #182 ## What and why A member re-exported with `public use Text.doubled from "./ext"` could not be imported from the re-exporting module: `use Text.doubled from "./lib"` failed with "`Text` is not exported by `./lib`". `bindMemberImport` (`compiler/src/semantics/scope.trb`) handed on the re-exporting module's entries with only the span replaced, so `entry.path` stayed `"./ext"`. `memberImportOf` matches the importer's item by span, the path written in its `use`, and the name, so it found nothing, and the item fell into the export check. The fix: - **The path written at each hop.** A handed-on entry now takes the importer's path as well as its span (`entry.copy(path: path, span: at)`). `memberImportOf` finds it, and "`<path>` adds no member ..." names the path written on the importer's line, not the original `./ext`. - **Each `use` item owns its entries.** `bindMember` de-duplicated on package and name, so the item's entry could be dropped when the scope already held one from another item. `bindMembers` now replaces the entries at the item's span (in `members`, and in `exportedMembers` for a `public use`) and reports a change only when they differ, so the fixpoint still ends. - **A first pass no longer leaves a wrong entry.** A pass that reaches the importer before the re-exporting module has bound its own re-export records the member as one of the re-exporting module's package. Once the re-export is bound, its entries replace that one. Before, both stayed: a package re-exporting a member of its own dependency then reported "`acme/other` adds no member ..." for a member that resolves. `memberImportOf` and the prelude's `addPreludes` are unchanged: the prelude's re-exports (`public use Int64.seconds from "std/time"`) are still added after the fixpoint, de-duplicated on package and name. ## Test New tests in `compiler/tests/check.test.trb`, group "Modules", in the `messages(files)` style of the existing member-import tests: - `ext` extends `Text` with `doubled`, `lib` re-exports it, `main` imports it from `./lib` and calls it: no problems. - A two-hop chain (`main` -> `lib` -> `mid` -> `ext`), imported with `as twice` and called as `value.twice()`: no problems. - The same chain imported by the package path (`use Text.doubled from "acme/other"`, `other/src/lib.trb` re-exporting from `./ext`): no problems. - A package re-exporting a member of its own dependency (`acme/other` re-exports from `acme/third`), imported by `app`: no problems. This covers the replacement of the first pass's entry. - Regression: a member the package does not add, imported through the re-exporting module, reports "`./lib` adds no member `tripled` to `Text`" at the importer's `use` line (span 9..16 of `main.trb`). This holds both when `lib` does not re-export the member and when `lib` re-exports one that `./ext` does not add. The issue's fixture uses `Bytes`. The test workspace's prelude has no `Bytes`, so the tests extend `Text`, which is the same case. The existing member-import tests and the prelude tests are unchanged. Locally: `torb format` (today's nightly) on the two touched files. Check, format and the suite run in the forge CI (tier A; tier B is not required because nothing under `compiler/src/ir`, `backend` or `runtime` changed). Seed: no. There is no syntax change, no new native, no driver flag, and no std name looked up by string. CI round 1 (run 225): tier A red only on `format --check` of the two touched files (two command calls, one blank line); `test compiler/tests (native)` passed. Fixed in the same commit.
fix(semantics): import a re-exported extension member through the re-exporting module
Some checks failed
ci / commit messages (pull_request) Successful in 5s
ci / what the change touches (pull_request) Successful in 5s
ci / tier A (linux-x64) (pull_request) Failing after 2m49s
ci / tools/release-sync and tools/registry (linux-x64) (pull_request) Successful in 2m57s
ci / gates (pull_request) Has been cancelled
ci / pack the seed (pull_request) Has been cancelled
ci / archives (pull_request) Has been cancelled
ci / every host emits the same C (pull_request) Has been cancelled
ci / the playground (torb as WebAssembly) (pull_request) Has been cancelled
ci / which runners exist (pull_request) Successful in 0s
ci / bootstrap and conformance (windows-x64) (pull_request) Has been skipped
ci / bootstrap and conformance (linux-arm64) (pull_request) Has been skipped
ci / bootstrap and conformance (macos-arm64) (pull_request) Has been skipped
ci / the VS Code extension (editors/vscode) (pull_request) Successful in 22s
ci / bootstrap (linux-x64) (pull_request) Successful in 4m9s
ci / tier B (linux-x64) (pull_request) Has been skipped
ci / cross-compiled on linux-x64 (windows-x64) (pull_request) Has been skipped
ci / cross-compiled on linux-x64 (linux-arm64) (pull_request) Has been skipped
ci / cross-compiled on linux-x64 (macos-arm64) (pull_request) Has been skipped
053c25cb4a
A re-exported member kept the path of the first `use`, so `memberImportOf` did not find the importer's
entry and the item fell into the export check. Each hop now records the path written there, and the
entries of a `use` item replace what an earlier pass of the fixpoint bound for it.
orion requested review from argus 2026-10-08 10:46:54 +00:00
orion force-pushed loop/182-reexported-member-import from 053c25cb4a
Some checks failed
ci / commit messages (pull_request) Successful in 5s
ci / what the change touches (pull_request) Successful in 5s
ci / tier A (linux-x64) (pull_request) Failing after 2m49s
ci / tools/release-sync and tools/registry (linux-x64) (pull_request) Successful in 2m57s
ci / gates (pull_request) Has been cancelled
ci / pack the seed (pull_request) Has been cancelled
ci / archives (pull_request) Has been cancelled
ci / every host emits the same C (pull_request) Has been cancelled
ci / the playground (torb as WebAssembly) (pull_request) Has been cancelled
ci / which runners exist (pull_request) Successful in 0s
ci / bootstrap and conformance (windows-x64) (pull_request) Has been skipped
ci / bootstrap and conformance (linux-arm64) (pull_request) Has been skipped
ci / bootstrap and conformance (macos-arm64) (pull_request) Has been skipped
ci / the VS Code extension (editors/vscode) (pull_request) Successful in 22s
ci / bootstrap (linux-x64) (pull_request) Successful in 4m9s
ci / tier B (linux-x64) (pull_request) Has been skipped
ci / cross-compiled on linux-x64 (windows-x64) (pull_request) Has been skipped
ci / cross-compiled on linux-x64 (linux-arm64) (pull_request) Has been skipped
ci / cross-compiled on linux-x64 (macos-arm64) (pull_request) Has been skipped
to dafc0b2808
All checks were successful
ci / bootstrap (linux-x64) (pull_request) Successful in 4m11s
ci / tier B (linux-x64) (pull_request) Has been skipped
ci / cross-compiled on linux-x64 (windows-x64) (pull_request) Has been skipped
ci / cross-compiled on linux-x64 (linux-arm64) (pull_request) Has been skipped
ci / cross-compiled on linux-x64 (macos-arm64) (pull_request) Has been skipped
ci / the playground (torb as WebAssembly) (pull_request) Successful in 2m0s
ci / every host emits the same C (pull_request) Successful in 4m23s
ci / tools/release-sync and tools/registry (linux-x64) (pull_request) Successful in 2m55s
ci / which runners exist (pull_request) Successful in 1s
ci / bootstrap and conformance (windows-x64) (pull_request) Has been skipped
ci / bootstrap and conformance (linux-arm64) (pull_request) Has been skipped
ci / commit messages (pull_request) Successful in 5s
ci / the VS Code extension (editors/vscode) (pull_request) Successful in 18s
ci / bootstrap and conformance (macos-arm64) (pull_request) Has been skipped
ci / what the change touches (pull_request) Successful in 5s
ci / tier A (linux-x64) (pull_request) Successful in 2m50s
ci / pack the seed (pull_request) Successful in 11s
ci / archives (pull_request) Has been skipped
ci / gates (pull_request) Successful in 0s
2026-10-08 11:31:58 +00:00
Compare
argus approved these changes 2026-10-08 12:35:53 +00:00
argus left a comment

Checklist for dafc0b2:

  • Tests: ok. Five new tests in check.test.trb cover one hop, two hops with as, the package path, a package re-exporting from its own dependency (the first-pass replacement), and the "adds no member" regression with the span at the importer's use line. CI green for this head (tier A, bootstrap, gates).
  • Correctness: ok. entry.copy(path: path, span: at) lets memberImportOf match the importer's item. replacedAt compares as sets and reports a change only on a difference, so the fixpoint still ends. Entries are replaced per span within one module's scope, so items cannot clobber each other.
  • Scope: ok. Matches #182 (re-export importable from the re-exporting module and from the package path, with a test); only scope.trb and its test file are touched.
  • Security, data, docs, architecture, workflows: nothing affected (no syntax change, no new boundary, no workflow file).

Non-blocking: the test uses Text rather than the issue's Bytes, and the PR body explains why; fine.

Checklist for dafc0b2: - Tests: ok. Five new tests in `check.test.trb` cover one hop, two hops with `as`, the package path, a package re-exporting from its own dependency (the first-pass replacement), and the "adds no member" regression with the span at the importer's `use` line. CI green for this head (tier A, bootstrap, gates). - Correctness: ok. `entry.copy(path: path, span: at)` lets `memberImportOf` match the importer's item. `replacedAt` compares as sets and reports a change only on a difference, so the fixpoint still ends. Entries are replaced per span within one module's scope, so items cannot clobber each other. - Scope: ok. Matches #182 (re-export importable from the re-exporting module and from the package path, with a test); only `scope.trb` and its test file are touched. - Security, data, docs, architecture, workflows: nothing affected (no syntax change, no new boundary, no workflow file). Non-blocking: the test uses `Text` rather than the issue's `Bytes`, and the PR body explains why; fine.
argus merged commit 0f8792ec7f into main 2026-10-08 12:35:55 +00:00
argus deleted branch loop/182-reexported-member-import 2026-10-08 12:35:56 +00:00
Sign in to join this conversation.
No reviewers
No labels
loop
ready
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
torbscript/language!189
No description provided.