Skip to content

Rebind block self from RBS block self types - #1335

Draft
apiology wants to merge 1 commit into
castwide:masterfrom
apiology:diagnose-rbs-self-type
Draft

Rebind block self from RBS block self types#1335
apiology wants to merge 1 commit into
castwide:masterfrom
apiology:diagnose-rbs-self-type

Conversation

@apiology

@apiology apiology commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Claude:

Problem: Calling an instance method inside an after_commit block
is reported as unresolved, even though activerecord's RBS declares the
block's self to be the model instance.

class Thing < ActiveRecord::Base
  def helper; end

  after_commit do
    helper  # Unresolved call to helper
  end
end

Every RBS block binding is affected, not just this one:
{ () [self: Foo] -> void } is dropped during conversion, so today only
a YARD @yieldreceiver tag can rebind a block.

Solution: Pin::Signature now carries the RBS block self type, and
Pin::Block#maybe_rebind falls back to it when no @yieldreceiver tag
applies.

RBS records a block self rebinding as { () [self: Foo] -> void }, but
the conversion built the block Pin::Signature without reading
RBS::Types::Block#self_type, and Pin::Block#maybe_rebind consulted only
a YARD @yieldreceiver tag. Every RBS-declared block binding was dropped:
ActiveRecord::Base.after_commit is declared ?{ () [self: instance] ->
untyped } and still left instance methods unresolved inside its block.

Signature now carries the binding as self_type, and maybe_rebind falls
back to it when no @yieldreceiver tag is present. Putting it on the
signature rather than synthesizing a tag keeps the pin cache and the
rendered documentation free of tags no author wrote, and leaves the
precedence explicit: CoreFills states several core bindings more
precisely than RBS does, and those keep winning.

Three RBS forms are deliberately not converted. RBS self means the
receiver unchanged, which no Solargraph tag expresses -- it translates
to self, and self_to_type reduces Class<Foo> to Foo, which is right for
RBS instance and wrong here; converting it broke Module#class_eval
rebinding. A type variable cannot name a namespace. An untyped self type
carries nothing.

Per-overload bindings collapse to the first signature, matching how
Pin::Method#block already exposes signatures.first.block for block
parameter types.

RbsTranslator.to_complex_type declared @PARAM type
[RBS::Types::Bases::Base], which only the Bases::* members satisfy --
Union, Optional, ClassInstance and Literal do not descend from it, so
every call site was reported as passing a wrong argument type. The tag
now names RBS::Types::t, the alias the rbs gem declares for every type
node, which Solargraph expands: passing a String still errors with the
full union spelled out, so the check is satisfied rather than skipped.
That takes to_complex_type argument-type errors from 12 to 1, the
survivor being a call site genuinely outside the union.
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.

1 participant