From ebc88d73e3a6fa11194a2e606f817f5f42412a71 Mon Sep 17 00:00:00 2001 From: SamW Date: Wed, 25 Mar 2026 19:08:15 -0700 Subject: [PATCH 01/11] Singleton methods done --- core/module.rbs | 8 ++++---- test/stdlib/Module_test.rb | 28 ++++++++++++++++++++++++---- test/stdlib/Module_test_helper.rb | 12 ++++++++++++ 3 files changed, 40 insertions(+), 8 deletions(-) create mode 100644 test/stdlib/Module_test_helper.rb diff --git a/core/module.rbs b/core/module.rbs index b585bc43c..46852bad7 100644 --- a/core/module.rbs +++ b/core/module.rbs @@ -19,7 +19,7 @@ # Mod.constants #=> [:CONST, :PI, :E] # Mod.instance_methods #=> [:meth] # -class Module < Object +class Module # # Invokes Module.append_features on each parameter in reverse order. # - def include: (Module, *Module arg0) -> self + def include: (Module module, *Module additional_modules) -> self # # Invokes Module.prepend_features on each parameter in reverse order. # - def prepend: (Module, *Module arg0) -> self + def prepend: (Module module, *Module additional_modules) -> self # # Evaluates the given block in the context of the class/module. The method @@ -416,7 +415,7 @@ class Module # # Hello there! # - def class_exec: [U] (*untyped, **untyped) { (?) [self: self] -> U } -> U + alias class_exec module_exec # # Makes a list of existing constants private. # - def private_constant: (*interned arg0) -> self + def private_constant: () -> self + | (interned name, *interned more_names) -> self # # Makes a list of existing constants public. # - def public_constant: (*interned arg0) -> self + def public_constant: () -> self + | (interned name, *interned more_names) -> self # # The equivalent of `included`, but for extended modules. # @@ -952,8 +951,9 @@ class Module # Assign the module to a constant (name starting uppercase) if you want to treat # it like a regular module. # - def initialize: () -> void - | () { (Module arg0) -> untyped } -> void + def initialize: () ?{ (Module mod) [self: self] -> void } -> void + + def initialize_clone: (Module source, ?freeze: bool?) -> void # # Makes a list of existing constants private. # - def private_constant: () -> self - | (interned name, *interned more_names) -> self + def private_constant: (*interned names) -> self # # Makes a list of existing constants public. # - def public_constant: () -> self - | (interned name, *interned more_names) -> self + def public_constant: (*interned names) -> self # # Returns a string representing this module or class. For basic classes and diff --git a/test/stdlib/Module_test.rb b/test/stdlib/Module_test.rb index 01892d6a5..99af2bac3 100644 --- a/test/stdlib/Module_test.rb +++ b/test/stdlib/Module_test.rb @@ -55,6 +55,14 @@ module RefinedModule end end + def with_untyped_singleton_possible + with_untyped do |untyped| + next if Integer === untyped || Float === untyped || Symbol === untyped + untyped = ::Kernel.instance_method(:dup).bind_call(untyped) if ::Kernel.instance_method(:frozen?).bind_call(untyped) + yield untyped + end + end + def test_op_lt with Object, Float, Hash do |mod| assert_send_type '(Module) -> bool?', @@ -1071,72 +1079,191 @@ def foo = 4 def test_append_features assert_visibility :private, Module.new, :append_features - omit 'todo' + + assert_send_type '(Module) -> Module', + Module.new, :append_features, Module.new end def test_const_added assert_visibility :private, Module.new, :const_added - omit 'todo' + + # const_added directly works + assert_send_type '(Symbol) -> void', + Module.new, :const_added, :foo + + # Setting the constant also works + const_added_module = proc do + assert_type_meth = method(:assert_type) + mod = Module.new do + define_singleton_method :const_added do |name| + assert_type_meth.call('Symbol', name) + end + end + end + + # Make sure the `::` assignment passes a symbol + eval <<~EOS + const_added_module.call()::Foo = 3 + EOS + + # Make sure that `const_set` also always passes a symbol to the `const_added` + with_interned :Foo do |name| + const_added_module.call().const_set(name, 2r) + end end def test_extend_object assert_visibility :private, Module.new, :extend_object - omit 'todo' + + with_untyped_singleton_possible do |untyped| + assert_send_type '[T] (T) -> T', + Module.new, :extend_object, untyped + end + + # No need to make sure `object.extend(module)` works because the signature + # is `(T) -> T`, which means it can take any type (and we aren't testing the + # return value of `extend`) end def test_extended assert_visibility :private, Module.new, :extended - omit 'todo' + + with_untyped_singleton_possible do |untyped| + assert_send_type '(untyped) -> void', + Module.new, :extended, untyped + end + + # No need to make sure `object.extend(module)` works because the signature + # is `(untyped) -> void`, which means it can take any type, and we dont care + # about the return value. end def test_included assert_visibility :private, Module.new, :included - omit 'todo' + + assert_send_type '(Module) -> void', + Module.new, :included, Module.new + + assert_send_type '(Module) -> void', + Module.new, :included, Class.new end def test_method_added assert_visibility :private, Module.new, :method_added - omit 'todo' + + + # method_added directly works + assert_send_type '(Symbol) -> void', + Module.new, :method_added, :foo + + # make sure using `with_intern` always passes a symbol + assert_type_meth = method(:assert_type) + mod = Module.new do + define_singleton_method :method_added do |name| + assert_type_meth.call('Symbol', name) + end + end + + with_interned :foo do |name| + mod.define_method(:foo) {} + mod.undef_method(:foo) # avoid warnings + end end def test_method_removed assert_visibility :private, Module.new, :method_removed - omit 'todo' + + # method_removed directly works + assert_send_type '(Symbol) -> void', + Module.new, :method_removed, :foo + + # make sure using `with_intern` always passes a symbol + assert_type_meth = method(:assert_type) + mod = Module.new do + define_singleton_method :method_removed do |name| + assert_type_meth.call('Symbol', name) + end + end + + with_interned :foo do |name| + mod.define_method(:foo) {} + mod.remove_method(name) + end end def test_method_undefined assert_visibility :private, Module.new, :method_undefined - omit 'todo' + + # method_undefined directly works + assert_send_type '(Symbol) -> void', + Module.new, :method_undefined, :foo + + # make sure using `with_intern` always passes a symbol + assert_type_meth = method(:assert_type) + mod = Module.new do + define_singleton_method :method_undefined do |name| + assert_type_meth.call('Symbol', name) + end + end + + with_interned :foo do |name| + mod.define_method(:foo) {} + mod.undef_method(name) + end end def test_prepend_features assert_visibility :private, Module.new, :prepend_features - omit 'todo' + + assert_send_type '(Module) -> Module', + Module.new, :prepend_features, Module.new + + assert_send_type '(Module) -> Module', + Module.new, :prepend_features, Class.new end def test_prepended assert_visibility :private, Module.new, :prepended - omit 'todo' + + assert_send_type '(Module) -> void', + Module.new, :prepended, Module.new + + assert_send_type '(Module) -> void', + Module.new, :prepended, Class.new end def test_remove_const assert_visibility :private, Module.new, :remove_const - omit 'todo' + + with_interned :Foo do |name| + mod = Module.new + mod.const_set :Foo, 1r + + assert_send_type '(interned) -> untyped', + mod, :remove_const, name + end + end + + + module UsingModule + UsingReturnValue = using Module.new end def test_using assert_visibility :private, Module.new, :using - omit 'todo' + + # Cant actually test `using` in modules, so this is the best we got + assert_type 'Module', UsingModule::UsingModule end end From 033ef0832e2b8e1e08e42173ce0aa2d2353dc74f Mon Sep 17 00:00:00 2001 From: SamW Date: Tue, 14 Apr 2026 16:18:29 -0700 Subject: [PATCH 10/11] update module --- core/module.rbs | 4 ++-- lib/rbs/unit_test/type_assertions.rb | 9 +++++++-- test/stdlib/Module_test.rb | 30 +++++++++++++++++++++++++--- 3 files changed, 36 insertions(+), 7 deletions(-) diff --git a/core/module.rbs b/core/module.rbs index 9c6dc9c6f..c07193b57 100644 --- a/core/module.rbs +++ b/core/module.rbs @@ -348,7 +348,7 @@ class Module # # Files that are currently being loaded must not be registered for autoload. # - def autoload: (interned constant, String filename) -> NilClass + def autoload: (interned constant, path filename) -> nil # # The equivalent of `included`, but for extended modules. # diff --git a/lib/rbs/unit_test/type_assertions.rb b/lib/rbs/unit_test/type_assertions.rb index 2fdb3856b..dee8345ca 100644 --- a/lib/rbs/unit_test/type_assertions.rb +++ b/lib/rbs/unit_test/type_assertions.rb @@ -296,8 +296,13 @@ def assert_const_type(type, constant_name) assert typecheck.value(constant, definition_type), "`#{constant_name}` (#{constant.inspect}) must be compatible with RBS type definition `#{definition_type}`" end - def assert_visibility(vis, receiver, method) - puts 'TODO: visibility for types' + def assert_visibility(visibility, receiver, method) + _, definition = target + method_entry = definition.methods[method] + + assert method_entry, "Method `#{method}` not found in RBS definition" + assert_equal visibility, method_entry.accessibility, + "Expected `#{method}` to be #{visibility}, but was #{method_entry.accessibility}" end def assert_type(type, value) diff --git a/test/stdlib/Module_test.rb b/test/stdlib/Module_test.rb index 99af2bac3..866298318 100644 --- a/test/stdlib/Module_test.rb +++ b/test/stdlib/Module_test.rb @@ -511,11 +511,35 @@ def test_ancestors end def test_autoload - omit 'todo' + with_interned :Constant do |constant| + with_path do |path| + assert_send_type '(interned, path) -> nil', + Module.new, :autoload, constant, path + end + end end def test_autoload? - omit 'todo' + autoloaded = Module.new + autoloaded.autoload(:Constant, 'Bar') + + with_interned :Constant do |constant| + assert_send_type '(interned) -> nil', + Module.new, :autoload?, constant + assert_send_type '(interned) -> String', + autoloaded, :autoload?, constant + assert_send_type '(interned) -> String', + Module.new.include(autoloaded), :autoload?, constant + + with_boolish do |inherit| + assert_send_type '(interned, boolish) -> nil', + Module.new, :autoload?, constant, inherit + assert_send_type '(interned, boolish) -> String', + autoloaded, :autoload?, constant, inherit + assert_send_type '(interned, boolish) -> String?', + Module.new.include(autoloaded), :autoload?, constant, inherit + end + end end def test_class_variable_defined? @@ -1264,6 +1288,6 @@ def test_using Module.new, :using # Cant actually test `using` in modules, so this is the best we got - assert_type 'Module', UsingModule::UsingModule + assert_type 'Module', UsingModule::UsingReturnValue end end From 205f42c9057ddebdfb9d86f767014d80a3189757 Mon Sep 17 00:00:00 2001 From: SamW Date: Tue, 14 Apr 2026 16:29:08 -0700 Subject: [PATCH 11/11] cleanup of assert_visibility --- lib/rbs/unit_test/type_assertions.rb | 4 +-- sig/unit_test/type_assertions.rbs | 4 +++ test/stdlib/Module_test.rb | 51 ++++++++++------------------ 3 files changed, 24 insertions(+), 35 deletions(-) diff --git a/lib/rbs/unit_test/type_assertions.rb b/lib/rbs/unit_test/type_assertions.rb index 4370f8421..6794218a2 100644 --- a/lib/rbs/unit_test/type_assertions.rb +++ b/lib/rbs/unit_test/type_assertions.rb @@ -323,12 +323,12 @@ def assert_const_type(type, constant_name) assert typecheck.value(constant, definition_type), "`#{constant_name}` (#{constant.inspect}) must be compatible with RBS type definition `#{definition_type}`" end - def assert_visibility(visibility, receiver, method) + def assert_visibility(visibility, method) _, definition = target method_entry = definition.methods[method] assert method_entry, "Method `#{method}` not found in RBS definition" - assert_equal visibility, method_entry.accessibility, + assert visibility == method_entry.accessibility, "Expected `#{method}` to be #{visibility}, but was #{method_entry.accessibility}" end diff --git a/sig/unit_test/type_assertions.rbs b/sig/unit_test/type_assertions.rbs index 9e7d32fc5..253a8fb18 100644 --- a/sig/unit_test/type_assertions.rbs +++ b/sig/unit_test/type_assertions.rbs @@ -179,6 +179,10 @@ module RBS # def assert_type: (String | Types::t value_type, untyped value) -> void + # Asserts if given `value` has a type of `value_type` + # + def assert_visibility: (:private | :public visibility, Symbol method_name) -> void + # Allow non _simple-type_ method types given to `assert_send_type` and `refute_send_type` # # ```ruby diff --git a/test/stdlib/Module_test.rb b/test/stdlib/Module_test.rb index 866298318..25d1a1288 100644 --- a/test/stdlib/Module_test.rb +++ b/test/stdlib/Module_test.rb @@ -133,11 +133,10 @@ def test_prepend end def test_refine + assert_visibility :private, :refine + assert_send_type "(Module) { () -> void } -> Refinement", RefinedModule, :refine, Integer do nil end - - assert_visibility :private, - RefinedModule, :refine end def test_refinements @@ -212,8 +211,7 @@ def foo; end def bar; end end - assert_visibility :private, - mod, :module_function + assert_visibility :private, :module_function # No arguments assert_send_type '() -> nil', @@ -281,8 +279,8 @@ def foo; end def bar; end end - assert_visibility :private, - mod, visibility + assert_visibility :private, visibility + # No arguments assert_send_type '() -> nil', mod, visibility @@ -471,8 +469,7 @@ def foo(*x) = 3 def bar(*x) = 3 end - assert_visibility :private, - mod, :ruby2_keywords + assert_visibility :private, :ruby2_keywords with_interned :foo do |foo| assert_send_type '(interned) -> nil', @@ -1101,16 +1098,14 @@ def foo = 4 end def test_append_features - assert_visibility :private, - Module.new, :append_features + assert_visibility :private, :append_features assert_send_type '(Module) -> Module', Module.new, :append_features, Module.new end def test_const_added - assert_visibility :private, - Module.new, :const_added + assert_visibility :private, :const_added # const_added directly works assert_send_type '(Symbol) -> void', @@ -1138,8 +1133,7 @@ def test_const_added end def test_extend_object - assert_visibility :private, - Module.new, :extend_object + assert_visibility :private, :extend_object with_untyped_singleton_possible do |untyped| assert_send_type '[T] (T) -> T', @@ -1152,8 +1146,7 @@ def test_extend_object end def test_extended - assert_visibility :private, - Module.new, :extended + assert_visibility :private, :extended with_untyped_singleton_possible do |untyped| assert_send_type '(untyped) -> void', @@ -1166,8 +1159,7 @@ def test_extended end def test_included - assert_visibility :private, - Module.new, :included + assert_visibility :private, :included assert_send_type '(Module) -> void', Module.new, :included, Module.new @@ -1177,8 +1169,7 @@ def test_included end def test_method_added - assert_visibility :private, - Module.new, :method_added + assert_visibility :private, :method_added # method_added directly works @@ -1200,8 +1191,7 @@ def test_method_added end def test_method_removed - assert_visibility :private, - Module.new, :method_removed + assert_visibility :private, :method_removed # method_removed directly works assert_send_type '(Symbol) -> void', @@ -1222,8 +1212,7 @@ def test_method_removed end def test_method_undefined - assert_visibility :private, - Module.new, :method_undefined + assert_visibility :private, :method_undefined # method_undefined directly works assert_send_type '(Symbol) -> void', @@ -1244,8 +1233,7 @@ def test_method_undefined end def test_prepend_features - assert_visibility :private, - Module.new, :prepend_features + assert_visibility :private, :prepend_features assert_send_type '(Module) -> Module', Module.new, :prepend_features, Module.new @@ -1255,8 +1243,7 @@ def test_prepend_features end def test_prepended - assert_visibility :private, - Module.new, :prepended + assert_visibility :private, :prepended assert_send_type '(Module) -> void', Module.new, :prepended, Module.new @@ -1266,8 +1253,7 @@ def test_prepended end def test_remove_const - assert_visibility :private, - Module.new, :remove_const + assert_visibility :private, :remove_const with_interned :Foo do |name| mod = Module.new @@ -1284,8 +1270,7 @@ module UsingModule end def test_using - assert_visibility :private, - Module.new, :using + assert_visibility :private, :using # Cant actually test `using` in modules, so this is the best we got assert_type 'Module', UsingModule::UsingReturnValue