Skip to content

Allow abstract methods to be implemented via method_missing - #36

Open
kshtzkr wants to merge 1 commit into
Shopify:mainfrom
kshtzkr:method-missing-abstract-impls
Open

Allow abstract methods to be implemented via method_missing#36
kshtzkr wants to merge 1 commit into
Shopify:mainfrom
kshtzkr:method-missing-abstract-impls

Conversation

@kshtzkr

@kshtzkr kshtzkr commented Jul 27, 2026

Copy link
Copy Markdown

Closes #11

Problem

AbstractInstanceMethodReceiver#method_missing raised AbstractMethodNotImplementedError before calling super, so a method_missing defined further up the ancestor chain (e.g. on a superclass) never got the chance to provide a dynamic implementation of an abstract method:

module MyInterface
  interface!

  abstract def demo; end
end

class Parent
  def method_missing(m, ...)
    return super unless m == :demo

    "dynamic #demo"
  end
end

class Child < Parent
  include MyInterface
end

Child.new.demo # raised AbstractMethodNotImplementedError, now returns "dynamic #demo"

Approach

As suggested in #11 (comment): call super first, and only translate the resulting NoMethodError into an AbstractMethodNotImplementedError if nothing handled the call.

Two refinements over the sketch in the issue:

  • The NoMethodError is only translated when e.name == method_name. A NoMethodError for a different method — e.g. raised from inside a broken dynamic implementation of the abstract method — is a real error and propagates unchanged, instead of being misreported as "abstract method never implemented".
  • The translated error is raised with cause: nil (as in the issue's sketch), since the underlying "undefined method" error is unactionable noise beneath the more precise error.

I went with rescuing NoMethodError rather than checking respond_to_missing? first, per the reasoning in the issue: lots of code overrides method_missing without overriding respond_to_missing?.

Tests

Three new specs in interface_spec.rb:

  • an abstract method implemented via an inherited method_missing can be called
  • an unimplemented abstract method still raises AbstractMethodNotImplementedError, with no NoMethodError cause attached
  • a NoMethodError raised inside a dynamic implementation propagates as-is

All local checks pass: rake typecheck (incl. Prism), rake test (92 runs, 0 failures), rake rubocop, bin/tapioca gem --verify, bin/tapioca check-shims.

Note: #11 had an assignment request from April that was never assigned and has no linked PR — happy to step aside if that work is still in flight.

Previously, AbstractInstanceMethodReceiver#method_missing raised
AbstractMethodNotImplementedError before calling super, so a
method_missing defined further up the ancestor chain never got the
chance to provide a dynamic implementation of an abstract method.

Now super is called first, and the resulting NoMethodError is only
translated into AbstractMethodNotImplementedError when it is about
the same method that was called. NoMethodErrors raised for other
methods (e.g. from inside a broken dynamic implementation) propagate
unchanged, and the translated error hides its NoMethodError cause,
which is unactionable noise.

Closes Shopify#11
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.

Allow abstract methods to be implemented via method_missing

1 participant