From 5014473f7138066ce2665f80f11d118105a12e5d Mon Sep 17 00:00:00 2001 From: Hashim1999164 <64767361+Hashim1999164@users.noreply.github.com> Date: Wed, 19 Aug 2026 00:51:37 +0500 Subject: [PATCH 1/5] Skip ActiveSupport deprecation proxies when gathering DSL constants. --- lib/tapioca/dsl/compiler.rb | 14 +++++++++++++- .../compilers/active_support_concern_spec.rb | 17 +++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/lib/tapioca/dsl/compiler.rb b/lib/tapioca/dsl/compiler.rb index fbe33fcea..e5afac7ac 100644 --- a/lib/tapioca/dsl/compiler.rb +++ b/lib/tapioca/dsl/compiler.rb @@ -75,7 +75,19 @@ def all_modules @@requested_constants.grep(Module) else ObjectSpace.each_object(Module).to_a - end.freeze #: Enumerable[Module[top]]? + end.reject { |mod| deprecated_constant_proxy?(mod) }.freeze #: Enumerable[Module[top]]? + end + + # Rails 8.1+ wraps deprecated constants in DeprecatedConstantProxy, which + # undefines most instance methods and warns from method_missing. Inspecting + # those modules during DSL discovery (is_a?, singleton_class, etc.) emits + # deprecation warnings even though Tapioca is only enumerating ObjectSpace. + # class_of uses Kernel#class so we can recognize the proxy without warning. + #: (Module[top] mod) -> bool + def deprecated_constant_proxy?(mod) + proxy_class_name = name_of(class_of(mod)) + proxy_class_name == "ActiveSupport::Deprecation::DeprecatedConstantProxy" || + proxy_class_name == "ActiveSupport::DeprecatedConstantProxy" end end diff --git a/spec/tapioca/dsl/compilers/active_support_concern_spec.rb b/spec/tapioca/dsl/compilers/active_support_concern_spec.rb index 2b46844e5..ccec51956 100644 --- a/spec/tapioca/dsl/compilers/active_support_concern_spec.rb +++ b/spec/tapioca/dsl/compilers/active_support_concern_spec.rb @@ -14,6 +14,23 @@ def before_setup end describe "gather_constants" do + it "does not gather ActiveSupport deprecation proxies and does not warn" do + require "active_support/concurrency/load_interlock_aware_monitor" + + warnings = [] + previous_behavior = ActiveSupport.deprecator.behavior + ActiveSupport.deprecator.behavior = ->(message, *) { warnings << message.to_s } + + begin + constants = gathered_constants + ensure + ActiveSupport.deprecator.behavior = previous_behavior + end + + refute_includes(constants, "ActiveSupport::Concurrency::LoadInterlockAwareMonitor") + assert_empty(warnings.grep(/LoadInterlockAwareMonitor/)) + end + it "does not gather anonymous constants" do add_ruby_file("test_case.rb", <<~RUBY) module TestCase From 14662786af33f3cb545cbff94799edcdd86a7c1e Mon Sep 17 00:00:00 2001 From: Hashim1999164 <64767361+Hashim1999164@users.noreply.github.com> Date: Wed, 26 Aug 2026 18:13:19 +0500 Subject: [PATCH 2/5] Skip deprecation proxies only during ObjectSpace DSL discovery Requested constants stay in the set even if they warn. The spec now checks the proxy by identity so it fails without the skip. --- lib/tapioca/dsl/compiler.rb | 4 ++-- spec/tapioca/dsl/compilers/active_support_concern_spec.rb | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/tapioca/dsl/compiler.rb b/lib/tapioca/dsl/compiler.rb index e5afac7ac..5cac1d9a6 100644 --- a/lib/tapioca/dsl/compiler.rb +++ b/lib/tapioca/dsl/compiler.rb @@ -74,8 +74,8 @@ def all_modules @all_modules ||= if @@requested_constants.any? @@requested_constants.grep(Module) else - ObjectSpace.each_object(Module).to_a - end.reject { |mod| deprecated_constant_proxy?(mod) }.freeze #: Enumerable[Module[top]]? + ObjectSpace.each_object(Module).reject { |mod| deprecated_constant_proxy?(mod) }.to_a + end.freeze #: Enumerable[Module[top]]? end # Rails 8.1+ wraps deprecated constants in DeprecatedConstantProxy, which diff --git a/spec/tapioca/dsl/compilers/active_support_concern_spec.rb b/spec/tapioca/dsl/compilers/active_support_concern_spec.rb index ccec51956..82c03042f 100644 --- a/spec/tapioca/dsl/compilers/active_support_concern_spec.rb +++ b/spec/tapioca/dsl/compilers/active_support_concern_spec.rb @@ -22,12 +22,14 @@ def before_setup ActiveSupport.deprecator.behavior = ->(message, *) { warnings << message.to_s } begin - constants = gathered_constants + proxy = ActiveSupport::Concurrency.const_get(:LoadInterlockAwareMonitor, false) + gathered_constants + all_modules = Tapioca::Dsl::Compilers::ActiveSupportConcern.send(:all_modules) ensure ActiveSupport.deprecator.behavior = previous_behavior end - refute_includes(constants, "ActiveSupport::Concurrency::LoadInterlockAwareMonitor") + refute(all_modules.any? { |mod| Tapioca::Runtime::Reflection.are_equal?(mod, proxy) }) assert_empty(warnings.grep(/LoadInterlockAwareMonitor/)) end From 9156fa16e69f02d7fa43be5643102989eef857e7 Mon Sep 17 00:00:00 2001 From: Kaan Ozkan Date: Wed, 26 Aug 2026 21:51:18 +0300 Subject: [PATCH 3/5] Test deprecation proxy discovery with a synthetic proxy --- .../dsl/compilers/active_support_concern_spec.rb | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/spec/tapioca/dsl/compilers/active_support_concern_spec.rb b/spec/tapioca/dsl/compilers/active_support_concern_spec.rb index 82c03042f..999f866af 100644 --- a/spec/tapioca/dsl/compilers/active_support_concern_spec.rb +++ b/spec/tapioca/dsl/compilers/active_support_concern_spec.rb @@ -15,22 +15,28 @@ def before_setup describe "gather_constants" do it "does not gather ActiveSupport deprecation proxies and does not warn" do - require "active_support/concurrency/load_interlock_aware_monitor" - warnings = [] previous_behavior = ActiveSupport.deprecator.behavior ActiveSupport.deprecator.behavior = ->(message, *) { warnings << message.to_s } begin - proxy = ActiveSupport::Concurrency.const_get(:LoadInterlockAwareMonitor, false) + proxy = ActiveSupport::Deprecation::DeprecatedConstantProxy.new( + "FakeDeprecatedConstant", + "Module", + ActiveSupport.deprecator, + ) + # Name the proxy so discovery can trigger its warning. + self.class.const_set(:FakeDeprecatedConstant, proxy) gathered_constants all_modules = Tapioca::Dsl::Compilers::ActiveSupportConcern.send(:all_modules) ensure + self.class.send(:remove_const, :FakeDeprecatedConstant) if + self.class.const_defined?(:FakeDeprecatedConstant, false) ActiveSupport.deprecator.behavior = previous_behavior end refute(all_modules.any? { |mod| Tapioca::Runtime::Reflection.are_equal?(mod, proxy) }) - assert_empty(warnings.grep(/LoadInterlockAwareMonitor/)) + assert_empty(warnings.grep(/FakeDeprecatedConstant/)) end it "does not gather anonymous constants" do From 8f22e258b08665d7c33a024d1148506bdd8465b4 Mon Sep 17 00:00:00 2001 From: Hashim1999164 <64767361+Hashim1999164@users.noreply.github.com> Date: Thu, 27 Aug 2026 00:16:39 +0500 Subject: [PATCH 4/5] Allow the deprecation proxy spec to remove its temp constant --- spec/tapioca/dsl/compilers/active_support_concern_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/tapioca/dsl/compilers/active_support_concern_spec.rb b/spec/tapioca/dsl/compilers/active_support_concern_spec.rb index 999f866af..4fd332956 100644 --- a/spec/tapioca/dsl/compilers/active_support_concern_spec.rb +++ b/spec/tapioca/dsl/compilers/active_support_concern_spec.rb @@ -30,7 +30,7 @@ def before_setup gathered_constants all_modules = Tapioca::Dsl::Compilers::ActiveSupportConcern.send(:all_modules) ensure - self.class.send(:remove_const, :FakeDeprecatedConstant) if + self.class.send(:remove_const, :FakeDeprecatedConstant) if # rubocop:disable RSpec/RemoveConst self.class.const_defined?(:FakeDeprecatedConstant, false) ActiveSupport.deprecator.behavior = previous_behavior end From 276df701159f5e9800af0a16a476327961f396a8 Mon Sep 17 00:00:00 2001 From: Kaan Ozkan Date: Wed, 26 Aug 2026 22:43:08 +0300 Subject: [PATCH 5/5] Exercise deprecation warnings with a named proxy The nested Minitest `describe` block uses a dynamic class that is not registered in the Ruby constant tree. Assigning the proxy through `self.class` leaves `Module#name` unset, so discovery skips it before invoking warning-producing methods. Assign the proxy through `ActiveSupportConcernSpec` so the test covers the warning path. `DslSpec` runs each example in an isolated process, so the temporary constant does not need manual cleanup. --- spec/tapioca/dsl/compilers/active_support_concern_spec.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/spec/tapioca/dsl/compilers/active_support_concern_spec.rb b/spec/tapioca/dsl/compilers/active_support_concern_spec.rb index 4fd332956..3e1c3b3dc 100644 --- a/spec/tapioca/dsl/compilers/active_support_concern_spec.rb +++ b/spec/tapioca/dsl/compilers/active_support_concern_spec.rb @@ -26,12 +26,10 @@ def before_setup ActiveSupport.deprecator, ) # Name the proxy so discovery can trigger its warning. - self.class.const_set(:FakeDeprecatedConstant, proxy) + ActiveSupportConcernSpec.const_set(:FakeDeprecatedConstant, proxy) gathered_constants all_modules = Tapioca::Dsl::Compilers::ActiveSupportConcern.send(:all_modules) ensure - self.class.send(:remove_const, :FakeDeprecatedConstant) if # rubocop:disable RSpec/RemoveConst - self.class.const_defined?(:FakeDeprecatedConstant, false) ActiveSupport.deprecator.behavior = previous_behavior end