Skip to content

fix(types): bind package-local bare type refs to their own module identity (#2208)#2619

Closed
gertybotbot wants to merge 1 commit into
hew-lang:mainfrom
gertybotbot:fix/2208-package-local-bare-name-member-identity
Closed

fix(types): bind package-local bare type refs to their own module identity (#2208)#2619
gertybotbot wants to merge 1 commit into
hew-lang:mainfrom
gertybotbot:fix/2208-package-local-bare-name-member-identity

Conversation

@gertybotbot

Copy link
Copy Markdown
Contributor

Summary

Fixes #2208. A bare type reference inside a non-root module — e.g. a Holder { w: Wrap } field whose Wrap is declared in the same package-local module — resolved through the is_local arm of resolve_type_expr to the bare type_defs key. That key is last-write-wins across modules that declare the same bare name (pre_register_type_decl documents the qualified alias as the authority), so when a sibling module declares its own Wrap, the field silently binds to whichever module registered Wrap last.

Observability (correcting the issue note)

The issue notes this as "unobservable in correctly-disambiguated programs." It is in fact observable. Two package-local modules, each with a divergent Wrap and a Holder { w: Wrap }, mis-resolve one holder's field to the other module's layout:

error: no field `a_field` on type `Wrap`
  = help: b_field

…and the winner flips with import order (a program that reads a.w.a_field / b.w.b_field compiles or fails purely by which module's Wrap was registered last).

Fix

In the is_local arm, bind to the current module's own qualified identity ({short}.{name}) when that key exists — mirroring the sibling published_bare_type_qualified branch that already qualifies specifically to dodge this bare-key collapse. pre_register_type_decl always seeds the qualified key alongside the bare one, so:

  • non-colliding locals resolve identically to before,
  • root-module references (current_module_short() == None) are untouched,
  • only the cross-module collision case changes — from wrong-module to own-module.

Boundary / neighbours checked

  • Root program (current_module == None): unchanged, still binds bare.
  • Single-module local reference: unchanged (qualified key == own def).
  • The published_bare_type_qualified and handle_matches arms below are untouched.

Tests

New load-bearing regression same_bare_name_member_field_binds_to_own_module_identity asserts each holder's w field carries its own module's Wrap identity (alpha.Wrap / beta.Wrap). It fails on revert (both collapse to the single last-writer identity), verified locally.

  • hew-types: 1665 lib + all integration suites green (registry_core 281→282).
  • hew-hir: full suite green (field identity flows into HIR/MIR).
  • cargo fmt + cargo clippy --tests: clean.
  • e2e: the two-module repro (both import orders) now prints 42 / hello and exits 0.

…ntity (hew-lang#2208)

A bare type reference inside a non-root module (e.g. a `Holder { w: Wrap }`
field whose `Wrap` is declared in the same module) resolved through the
`is_local` arm to the BARE `type_defs` key. That key is last-write-wins across
modules that declare the same bare name — `pre_register_type_decl` documents
the qualified alias as the authority — so when a sibling module declares its
own `Wrap`, the field silently bound to whichever module registered `Wrap`
last.

This is observable in a correctly-disambiguated program (contradicting the
issue's "unobservable" note): two package-local modules each with a divergent
`Wrap` + `Holder { w: Wrap }` mis-resolve one holder's field to the other
module's layout, surfacing as `no field `a_field` on type `Wrap` — help:
b_field`, and the winner flips with import order.

Fix: in the `is_local` arm, bind to the current module's OWN qualified identity
(`{short}.{name}`) when that key exists, mirroring the sibling
`published_bare_type_qualified` branch that already qualifies to dodge the same
bare-key collapse. `pre_register_type_decl` always seeds the qualified key
alongside the bare one, so non-colliding locals and root-module refs
(`current_module_short() == None`) resolve exactly as before.

Load-bearing regression `same_bare_name_member_field_binds_to_own_module_identity`
asserts each holder's `w` field carries its own module's `Wrap` identity; it
fails on revert (both collapse to the last-writer). Full hew-types (1665 lib +
integration) and hew-hir suites green; fmt + clippy(--tests) clean.
@slepp

slepp commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Thank you for the #2208 report and the core own-qualified binding fix. I preserved your commit and authorship in #2626, which adds the private nested-module visibility fix and the full corpus proving needed to complete this change. Superseded by #2626.

@slepp slepp closed this Jul 11, 2026
@slepp

slepp commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Thanks for this — the #2208 collision fix is correct in intent and the observability correction is right. Review found one boundary hole: the own-qualified binding trips the visibility gate for same-module references to private types in nested modules (it broke std/net/tls), because the gate compares the short decl-module against the full accessor path. I've carried your fix forward in #2626 with that boundary closed and a regression covering the private-local-in-nested-module case (which this PR's pub-only test didn't exercise). Closing in favour of #2626, with credit for the original fix and the sharper issue diagnosis.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Package-local same-bare-name type-body resolution collision (pre-existing, surfaced during #2202)

2 participants