Skip to content

Support extend - #462

Merged
mame merged 3 commits into
ruby:masterfrom
sinsoku:support-extend-resolution
Sep 1, 2026
Merged

Support extend#462
mame merged 3 commits into
ruby:masterfrom
sinsoku:support-extend-resolution

Conversation

@sinsoku

@sinsoku sinsoku commented Jun 21, 2026

Copy link
Copy Markdown
Collaborator

TypeProf did not handle extend, so methods added via extend M were reported as "undefined method" and extend was missing from the dumped RBS.

Implement it symmetrically to include: parse Ruby/RBS extend, resolve the extended module's instance methods as the receiver's singleton methods, and dump extend for class bodies (like include).

Self-referential extend (e.g. extend self) is still unsupported.

TypeProf did not handle `extend`, so methods added via `extend M`
were reported as "undefined method" and `extend` was missing from
the dumped RBS.

Implement it symmetrically to `include`: parse Ruby/RBS `extend`,
resolve the extended module's instance methods as the receiver's
singleton methods, and dump `extend` for class bodies (like
`include`).

Self-referential `extend` (e.g. `extend self`) is still unsupported.
Passing a class object where a module it extends is expected
(e.g. `App` to a `(Helpers) -> untyped` parameter) was diagnosed
as "wrong type of arguments", although `App.is_a?(Helpers)` holds
in Ruby. Method resolution and narrowing already recognize
`extend`, so only this typecheck was inconsistent.
The extend scenarios only covered adding `extend` in an editing
session. The removal direction goes through a separate code path
(`remove_extend_def` and the `@extended_modules` cleanup), which
had no regression guard, unlike include's remove-included-module2.
@mame
mame force-pushed the support-extend-resolution branch from b98eb46 to f9c9391 Compare September 1, 2026 08:42
@mame
mame merged commit 1a32477 into ruby:master Sep 1, 2026
6 checks passed
@mame

mame commented Sep 1, 2026

Copy link
Copy Markdown
Member

Thanks, merged! The symmetry with include looks right to me.

One thing I noticed while testing: include and extend can form a cycle that is valid Ruby, and it makes on_ancestors_updated recurse forever.

module Foo
  module Util
    include Foo
    def helper = "h"
  end
  extend Util
end
Foo.helper

The guard was already missing before this PR, but include/prepend cycles are rejected by Ruby itself, so this combination is the only way to reach it. I'll push a follow-up with a one-line guard and a scenario.

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