From 3382061d38e3380c4e866794a57a058eecfefbde Mon Sep 17 00:00:00 2001 From: goshanraj-g Date: Fri, 27 Mar 2026 23:52:34 -0400 Subject: [PATCH] fix: delegate Module#abstract! to super implementation if one exists --- lib/type_toolkit/ext/module.rb | 5 +++++ lib/type_toolkit/interface.rb | 8 ++++++++ spec/interface_spec.rb | 36 ++++++++++++++++++++++++++++++++++ 3 files changed, 49 insertions(+) diff --git a/lib/type_toolkit/ext/module.rb b/lib/type_toolkit/ext/module.rb index 1621a96..f96f600 100644 --- a/lib/type_toolkit/ext/module.rb +++ b/lib/type_toolkit/ext/module.rb @@ -4,4 +4,9 @@ class Module def interface! TypeToolkit.make_interface!(self) end + + def abstract! + super if defined?(super) + TypeToolkit.make_abstract!(self) + end end diff --git a/lib/type_toolkit/interface.rb b/lib/type_toolkit/interface.rb index a548f8b..02fdc75 100644 --- a/lib/type_toolkit/interface.rb +++ b/lib/type_toolkit/interface.rb @@ -19,6 +19,14 @@ def make_interface!(mod) mod.extend(TypeToolkit::MethodDefRecorder) mod.extend(TypeToolkit::HasAbstractMethods) end + + #: (Class[top]) -> void + def make_abstract!(klass) + klass.extend(TypeToolkit::DSL) + klass.extend(TypeToolkit::MethodDefRecorder) + klass.extend(TypeToolkit::HasAbstractMethods) + klass.include(TypeToolkit::AbstractInstanceMethodReceiver) + end end # This module is extended onto any module that represents an interface. diff --git a/spec/interface_spec.rb b/spec/interface_spec.rb index 24ddc54..f243986 100644 --- a/spec/interface_spec.rb +++ b/spec/interface_spec.rb @@ -341,6 +341,42 @@ def something_else; end end end + describe "abstract!" do + it "makes a class abstract" do + klass = Class.new do + abstract! + + abstract def my_method = assert_never_called! + end + + assert klass.abstract_method?(:my_method) + end + + it "delegates to an existing abstract! if one is defined" do + called = false + + # Simulate another gem (e.g. Rails) defining abstract! on Object, + # which is Module's superclass. Module#abstract! should call super to reach it. + Object.define_method(:abstract!) { called = true } + + klass = Class.new + klass.abstract! + + assert called, "expected the other abstract! to have been called via super" + assert_respond_to klass, :abstract_instance_methods + ensure + Object.remove_method(:abstract!) + end + + it "works when no parent abstract! exists" do + klass = Class.new do + abstract! + end + + assert_respond_to klass, :abstract_instance_methods + end + end + describe "A class that fully implements the interface, partially via inheritance" do before do @class = PartiallyInheritsItsImpl