Skip to content

Align module-self type params across declarations in ModuleEntry#self_types - #3067

Open
soutaro wants to merge 2 commits into
masterfrom
claude/module-self-type-param-alignment-2wpbja
Open

Align module-self type params across declarations in ModuleEntry#self_types#3067
soutaro wants to merge 2 commits into
masterfrom
claude/module-self-type-param-alignment-2wpbja

Conversation

@soutaro

@soutaro soutaro commented Aug 6, 2026

Copy link
Copy Markdown
Member

Environment::ModuleEntry#self_types collected each declaration's self types as-is, so when declarations use different type parameter names (module M[A] : _Foo[A] in one file, module M[B] : _Foo[B] in another), the result contained free variables not bound to the module's type parameters — [_Foo[A], _Foo[B]] instead of [_Foo[A]]. This broke Steep's module self type check for every class including Enumerable, because rbs 4.1.2 renamed core/enumerable.rbs's Elem to E while sig/shims/enumerable.rbs still uses Elem (soutaro/steep#2256).

This PR applies the same per-declaration substitution as AncestorBuilder#mixin_ancestors in ModuleEntry#self_types, renaming the variables to the primary declaration's type parameter names. The rebuilt Module::Self keeps the original location, so error positions and ancestor sources are unchanged. Also, Module::Self#hash no longer includes location.hash==/eql? only compare name and args, and the mismatch prevented .uniq from deduplicating equal self types across files.

AncestorBuilder#validate_super_class! had the same problem for superclasses: class C[A] < Base[A] and class C[B] < Base[B] raised a false SuperclassMismatchError. It now aligns the superclass args to the entry's type parameter names before comparing.

🤖 Generated with Claude Code

https://claude.ai/code/session_01TBY8ct4HpkVkZNPdsNHEDE

claude added 2 commits August 6, 2026 02:10
…_types

When a module has multiple declarations with different (but compatible)
type parameter names, `Environment::ModuleEntry#self_types` collected the
self type constraints of each declaration as-is. The type variables from
non-primary declarations were left as free variables that are not bound
to any type parameter of the module, so downstream tools (e.g. Steep's
module self type check) could never satisfy the constraint:

    # a.rbs
    module M[out A] : _Foo[A]
    end

    # b.rbs
    module M[out B] : _Foo[B]
    end

    entry.self_types  # => [_Foo[A], _Foo[B]]  (B is unbound)

Mixin members already get this alignment via `align_params` in
`DefinitionBuilder::AncestorBuilder#mixin_ancestors`, but module self
types did not. Fix it in `ModuleEntry#self_types` — the aggregation
point every consumer goes through — by renaming the type variables of
each declaration's self types to the primary declaration's type
parameters, using the same substitution as `mixin_ancestors`. The
`location` of substituted self types keeps pointing to the original
declaration, so error locations (NoSelfTypeFoundError,
InvalidTypeApplicationError) are unchanged.

Also drop `location` from `AST::Declarations::Module::Self#hash` to make
it consistent with `#==`/`#eql?`, which only compare `name` and `args`.
The inconsistency made the `.uniq` in `ModuleEntry#self_types`
ineffective across files, so identical self types from different
declarations were duplicated.

With both fixes, the example above now yields `[_Foo[A]]`.

This is what broke Steep's self check with rbs 4.1.2, where
core/enumerable.rbs renamed `Elem` to `E` while other environments still
declare `module Enumerable[unchecked out Elem] : _Each[Elem]`:
`one_instance_ancestors(::Enumerable).self_types` became
`[_Each[E, void], _Each[Elem, void]]`, failing every class that includes
Enumerable. (soutaro/steep#2256)

Note: `sig/shims/enumerable.rbs` intentionally keeps the `Elem` name —
this repository's own `steep check` runs on rbs 3.9 whose core still
uses `Elem`, and renaming the shim to `E` makes the self check fail
there. With the alignment fix the name difference is harmless.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TBY8ct4HpkVkZNPdsNHEDE
The superclass comparison across multiple declarations compared the
superclass args as written, so declarations that declare the same
superclass with different type parameter names (`class C[A] < Base[A]`
and `class C[B] < Base[B]`) raised a false SuperclassMismatchError.
Align the args to the entry's type parameter names before comparing,
like ModuleEntry#self_types and mixin_ancestors do.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TBY8ct4HpkVkZNPdsNHEDE
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.

2 participants