Skip to content

Commit 5cbbf15

Browse files
committed
Support auto extending module
In Ruby language, developers need to use both "include" and "extend" to implement mix-in having instance methods and class methods. The auto extending module is a well-known technique to realize it (ex. `ActiveSupport::Concern`, "extend" call in `included` block, and so on). This supports the auto extending modules via modules having "autoextend:..." annotation. The `RBS::DefinitionBuilder` searches the extended modules from the annotations of the included modules.
1 parent ed748bf commit 5cbbf15

File tree

4 files changed

+60
-2
lines changed

4 files changed

+60
-2
lines changed

docs/syntax.md

+14
Original file line numberDiff line numberDiff line change
@@ -780,3 +780,17 @@ _annotation_ ::= `%a{` _annotation-text_ `}` # Annotation using {}
780780

781781
_annotation-text_ ::= /[^\x00]*/ # Any characters except NUL (and parenthesis)
782782
```
783+
784+
#### Auto extending modules
785+
786+
Module having "autoextend:..." annotation is considered as an auto extending module.
787+
When such auto extending modules are included, the including class will be extended by annotated modules.
788+
789+
```
790+
%a{autoextend:Mod::ClassMethods}
791+
module Mod
792+
module ClassMethods
793+
def foo: () -> void
794+
end
795+
end
796+
```

lib/rbs/definition_builder.rb

+13
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,19 @@ def build_singleton0(type_name)
239239
definition.class_variables.merge!(defn.class_variables)
240240
end
241241

242+
one_ancestors.each_included_module do |mod|
243+
included_module = env.class_decls[mod.name] or raise "Unknown name for one_ancestors.included_module: #{type_name}"
244+
included_module.decls.each do |decl|
245+
decl.decl.annotations.each do |annotation|
246+
if annotation.string.start_with? "autoextend:"
247+
mod_name = TypeName("::" + annotation.string.split(":", 2)[1])
248+
subst = tapp_subst(mod_name, [])
249+
define_instance(definition, mod_name, subst)
250+
end
251+
end
252+
end
253+
end
254+
242255
one_ancestors.each_extended_module do |mod|
243256
mod.args.each do |arg|
244257
validate_type_presence(arg)

lib/rbs/definition_builder/ancestor_builder.rb

+2-2
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ def self.singleton(type_name:, super_class:)
111111
params: nil,
112112
super_class: super_class,
113113
self_types: nil,
114-
included_modules: nil,
114+
included_modules: [],
115115
included_interfaces: nil,
116116
prepended_modules: nil,
117117
extended_modules: [],
@@ -306,7 +306,7 @@ def one_singleton_ancestors(type_name)
306306

307307
mixin_ancestors(entry,
308308
type_name,
309-
included_modules: nil,
309+
included_modules: ancestors.included_modules,
310310
included_interfaces: nil,
311311
prepended_modules: nil,
312312
extended_modules: ancestors.extended_modules,

test/rbs/definition_builder_test.rb

+31
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,37 @@ def get: () -> X
281281
end
282282
end
283283

284+
def test_build_singleton_including_autoextending_module
285+
SignatureManager.new do |manager|
286+
manager.files[Pathname("foo.rbs")] = <<EOF
287+
%a{autoextend:Mod::ClassMethods}
288+
module Mod
289+
module ClassMethods
290+
def count: -> Integer
291+
end
292+
end
293+
294+
class Class
295+
include Mod
296+
end
297+
EOF
298+
manager.build do |env|
299+
builder = DefinitionBuilder.new(env: env)
300+
301+
builder.build_singleton(type_name("::Class")).yield_self do |definition|
302+
assert_instance_of Definition, definition
303+
304+
assert_equal [:__id__, :count, :initialize, :new, :puts, :respond_to_missing?, :to_i], definition.methods.keys.sort
305+
assert_method_definition definition.methods[:__id__], ["() -> ::Integer"]
306+
assert_method_definition definition.methods[:initialize], ["() -> void"]
307+
assert_method_definition definition.methods[:puts], ["(*untyped) -> nil"]
308+
assert_method_definition definition.methods[:respond_to_missing?], ["(::Symbol, bool) -> bool"]
309+
end
310+
end
311+
end
312+
end
313+
314+
284315
def test_build_instance_module_include_module
285316
SignatureManager.new do |manager|
286317
manager.files[Pathname("foo.rbs")] = <<EOF

0 commit comments

Comments
 (0)