From 89293a81a3cc68bbe6c18baf00134a22d2635f51 Mon Sep 17 00:00:00 2001 From: Soutaro Matsumoto Date: Fri, 20 Feb 2026 16:09:39 +0900 Subject: [PATCH 1/6] Update docs --- docs/inline.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/inline.md b/docs/inline.md index a51afd179..027a2204e 100644 --- a/docs/inline.md +++ b/docs/inline.md @@ -188,6 +188,18 @@ The type of both methods is `(Integer, Integer) -> Integer | (Float, Float) -> F > The `@rbs METHOD-TYPE` syntax allows overloads with the `|` operator, just like in RBS files. > Multiple `: METHOD-TYPE` declarations are required for overloads. +The `@rbs METHOD-TYPE` syntax allows having `...` at the last part. + +```ruby +class Calculator2 < Calculator + # @rbs (Float, Float) -> Float | ... + def add(x, y) = x + y + + # @rbs ... + def subtract(x, y) = super +end +``` + #### Doc-style syntax The `@rbs return: T` syntax declares the return type of a method: From 7dedea2e6ca8d5b092bd0720a5c16eaa297dd6d8 Mon Sep 17 00:00:00 2001 From: Soutaro Matsumoto Date: Fri, 20 Feb 2026 16:10:00 +0900 Subject: [PATCH 2/6] Implement Ruby AST --- lib/rbs/ast/ruby/annotations.rb | 10 ++++++---- sig/ast/ruby/annotations.rbs | 12 ++++++++---- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/lib/rbs/ast/ruby/annotations.rb b/lib/rbs/ast/ruby/annotations.rb index 7be93f867..a8524bcdd 100644 --- a/lib/rbs/ast/ruby/annotations.rb +++ b/lib/rbs/ast/ruby/annotations.rb @@ -109,12 +109,13 @@ def type_fingerprint class MethodTypesAnnotation < Base Overload = AST::Members::MethodDefinition::Overload - attr_reader :overloads, :vertical_bar_locations + attr_reader :overloads, :vertical_bar_locations, :dot3_location - def initialize(location:, prefix_location:, overloads:, vertical_bar_locations:) + def initialize(location:, prefix_location:, overloads:, vertical_bar_locations:, dot3_location:) super(location, prefix_location) @overloads = overloads @vertical_bar_locations = vertical_bar_locations + @dot3_location = dot3_location end def map_type_name(&block) @@ -125,13 +126,14 @@ def map_type_name(&block) ) end - self.class.new(location:, prefix_location:, overloads: ovs, vertical_bar_locations:) #: self + self.class.new(location:, prefix_location:, overloads: ovs, vertical_bar_locations:, dot3_location:) #: self end def type_fingerprint [ "annots/method_types", - overloads.map { |o| [o.annotations.map(&:to_s), o.method_type.to_s] } + overloads.map { |o| [o.annotations.map(&:to_s), o.method_type.to_s] }, + overloading: dot3_location ? true : false ] end end diff --git a/sig/ast/ruby/annotations.rbs b/sig/ast/ruby/annotations.rbs index 6242aa56c..be1ffcbf9 100644 --- a/sig/ast/ruby/annotations.rbs +++ b/sig/ast/ruby/annotations.rbs @@ -104,9 +104,11 @@ module RBS # `@rbs METHOD-TYPEs` annotation in leading comments # # ``` - # @rbs () -> void | %a{foo} () -> String - # ^^^^ -- prefix_location - # ^ -- vertical_bar_locations[0] + # @rbs () -> void | %a{foo} () -> String | ... + # ^^^^ -- prefix_location + # ^ -- vertical_bar_locations[0] + # ^ -- vertical_bar_locations[1] + # ^^^ -- dot3_location # ``` class MethodTypesAnnotation < Base class Overload = AST::Members::MethodDefinition::Overload @@ -115,7 +117,9 @@ module RBS attr_reader vertical_bar_locations: Array[Location] - def initialize: (location: Location, prefix_location: Location, overloads: Array[Overload], vertical_bar_locations: Array[Location]) -> void + attr_reader dot3_location: Location? + + def initialize: (location: Location, prefix_location: Location, overloads: Array[Overload], vertical_bar_locations: Array[Location], dot3_location: Location?) -> void def map_type_name: () { (TypeName) -> TypeName } -> self From 1d1f75b4239febf36bcec905158effe3372d4d77 Mon Sep 17 00:00:00 2001 From: Soutaro Matsumoto Date: Fri, 20 Feb 2026 16:11:09 +0900 Subject: [PATCH 3/6] Implement parser --- config.yml | 3 +++ ext/rbs_extension/ast_translation.c | 1 + include/rbs/ast.h | 3 ++- src/ast.c | 3 ++- src/parser.c | 41 ++++++++++++++++++++++++++--- 5 files changed, 45 insertions(+), 6 deletions(-) diff --git a/config.yml b/config.yml index c9932753c..bf44dde25 100644 --- a/config.yml +++ b/config.yml @@ -706,6 +706,9 @@ nodes: c_type: rbs_node_list - name: vertical_bar_locations c_type: rbs_location_range_list + - name: dot3_location + c_type: rbs_location_range + optional: true - name: RBS::AST::Ruby::Annotations::SkipAnnotation rust_name: SkipAnnotationNode fields: diff --git a/ext/rbs_extension/ast_translation.c b/ext/rbs_extension/ast_translation.c index 04fe05d70..3b88cbd2d 100644 --- a/ext/rbs_extension/ast_translation.c +++ b/ext/rbs_extension/ast_translation.c @@ -883,6 +883,7 @@ VALUE rbs_struct_to_ruby_value(rbs_translation_context_t ctx, rbs_node_t *instan rb_hash_aset(h, ID2SYM(rb_intern("prefix_location")), rbs_location_range_to_ruby_location(ctx, node->prefix_location)); rb_hash_aset(h, ID2SYM(rb_intern("overloads")), rbs_node_list_to_ruby_array(ctx, node->overloads)); rb_hash_aset(h, ID2SYM(rb_intern("vertical_bar_locations")), rbs_location_range_list_to_ruby_array(ctx, node->vertical_bar_locations)); + rb_hash_aset(h, ID2SYM(rb_intern("dot3_location")), rbs_location_range_to_ruby_location(ctx, node->dot3_location)); // optional return CLASS_NEW_INSTANCE( RBS_AST_Ruby_Annotations_MethodTypesAnnotation, diff --git a/include/rbs/ast.h b/include/rbs/ast.h index b3f6aa302..2225dde9e 100644 --- a/include/rbs/ast.h +++ b/include/rbs/ast.h @@ -593,6 +593,7 @@ typedef struct rbs_ast_ruby_annotations_method_types_annotation { rbs_location_range prefix_location; struct rbs_node_list *overloads; rbs_location_range_list_t *vertical_bar_locations; + rbs_location_range dot3_location; /* Optional */ } rbs_ast_ruby_annotations_method_types_annotation_t; typedef struct rbs_ast_ruby_annotations_module_alias_annotation { @@ -925,7 +926,7 @@ rbs_ast_members_public_t *rbs_ast_members_public_new(rbs_allocator_t *allocator, rbs_ast_ruby_annotations_class_alias_annotation_t *rbs_ast_ruby_annotations_class_alias_annotation_new(rbs_allocator_t *allocator, rbs_location_range location, rbs_location_range prefix_location, rbs_location_range keyword_location, rbs_type_name_t *type_name, rbs_location_range type_name_location); rbs_ast_ruby_annotations_colon_method_type_annotation_t *rbs_ast_ruby_annotations_colon_method_type_annotation_new(rbs_allocator_t *allocator, rbs_location_range location, rbs_location_range prefix_location, rbs_node_list_t *annotations, rbs_node_t *method_type); rbs_ast_ruby_annotations_instance_variable_annotation_t *rbs_ast_ruby_annotations_instance_variable_annotation_new(rbs_allocator_t *allocator, rbs_location_range location, rbs_location_range prefix_location, rbs_ast_symbol_t *ivar_name, rbs_location_range ivar_name_location, rbs_location_range colon_location, rbs_node_t *type, rbs_location_range comment_location); -rbs_ast_ruby_annotations_method_types_annotation_t *rbs_ast_ruby_annotations_method_types_annotation_new(rbs_allocator_t *allocator, rbs_location_range location, rbs_location_range prefix_location, rbs_node_list_t *overloads, rbs_location_range_list_t *vertical_bar_locations); +rbs_ast_ruby_annotations_method_types_annotation_t *rbs_ast_ruby_annotations_method_types_annotation_new(rbs_allocator_t *allocator, rbs_location_range location, rbs_location_range prefix_location, rbs_node_list_t *overloads, rbs_location_range_list_t *vertical_bar_locations, rbs_location_range dot3_location); rbs_ast_ruby_annotations_module_alias_annotation_t *rbs_ast_ruby_annotations_module_alias_annotation_new(rbs_allocator_t *allocator, rbs_location_range location, rbs_location_range prefix_location, rbs_location_range keyword_location, rbs_type_name_t *type_name, rbs_location_range type_name_location); rbs_ast_ruby_annotations_node_type_assertion_t *rbs_ast_ruby_annotations_node_type_assertion_new(rbs_allocator_t *allocator, rbs_location_range location, rbs_location_range prefix_location, rbs_node_t *type); rbs_ast_ruby_annotations_return_type_annotation_t *rbs_ast_ruby_annotations_return_type_annotation_new(rbs_allocator_t *allocator, rbs_location_range location, rbs_location_range prefix_location, rbs_location_range return_location, rbs_location_range colon_location, rbs_node_t *return_type, rbs_location_range comment_location); diff --git a/src/ast.c b/src/ast.c index f5305a59b..6b5eda614 100644 --- a/src/ast.c +++ b/src/ast.c @@ -934,7 +934,7 @@ rbs_ast_ruby_annotations_instance_variable_annotation_t *rbs_ast_ruby_annotation return instance; } #line 140 "prism/templates/src/ast.c.erb" -rbs_ast_ruby_annotations_method_types_annotation_t *rbs_ast_ruby_annotations_method_types_annotation_new(rbs_allocator_t *allocator, rbs_location_range location, rbs_location_range prefix_location, rbs_node_list_t *overloads, rbs_location_range_list_t *vertical_bar_locations) { +rbs_ast_ruby_annotations_method_types_annotation_t *rbs_ast_ruby_annotations_method_types_annotation_new(rbs_allocator_t *allocator, rbs_location_range location, rbs_location_range prefix_location, rbs_node_list_t *overloads, rbs_location_range_list_t *vertical_bar_locations, rbs_location_range dot3_location) { rbs_ast_ruby_annotations_method_types_annotation_t *instance = rbs_allocator_alloc(allocator, rbs_ast_ruby_annotations_method_types_annotation_t); *instance = (rbs_ast_ruby_annotations_method_types_annotation_t) { @@ -945,6 +945,7 @@ rbs_ast_ruby_annotations_method_types_annotation_t *rbs_ast_ruby_annotations_met .prefix_location = prefix_location, .overloads = overloads, .vertical_bar_locations = vertical_bar_locations, + .dot3_location = dot3_location, }; return instance; diff --git a/src/parser.c b/src/parser.c index 428180a9e..ffa32dec3 100644 --- a/src/parser.c +++ b/src/parser.c @@ -3575,12 +3575,13 @@ static bool parse_method_overload(rbs_parser_t *parser, rbs_node_list_t *annotat } /* - inline_method_overloads ::= {} -- returns true + inline_method_overloads ::= {} -- returns true | {} overload `|` ... `|` overload -- returns true + | {} overload `|` ... `|` `...` -- returns true (dot3_location is set) | {<>} -- returns false */ NODISCARD -static bool parse_inline_method_overloads(rbs_parser_t *parser, rbs_node_list_t *overloads, rbs_location_range_list_t *bar_locations) { +static bool parse_inline_method_overloads(rbs_parser_t *parser, rbs_node_list_t *overloads, rbs_location_range_list_t *bar_locations, rbs_location_range *dot3_location) { while (true) { rbs_node_list_t *annotations = rbs_node_list_new(ALLOCATOR()); rbs_method_type_t *method_type = NULL; @@ -3605,6 +3606,12 @@ static bool parse_inline_method_overloads(rbs_parser_t *parser, rbs_node_list_t rbs_location_range_list_append(bar_locations, bar_range); + if (parser->next_token.type == pDOT3) { + *dot3_location = RBS_RANGE_LEX2AST(parser->next_token.range); + rbs_parser_advance(parser); + return true; + } + continue; } @@ -3660,14 +3667,39 @@ static bool parse_inline_leading_annotation(rbs_parser_t *parser, rbs_ast_ruby_a rbs_parser_advance(parser); switch (parser->next_token.type) { + case pDOT3: { + rbs_location_range dot3_range = RBS_RANGE_LEX2AST(parser->next_token.range); + rbs_parser_advance(parser); + + rbs_node_list_t *overloads = rbs_node_list_new(ALLOCATOR()); + rbs_location_range_list_t *bar_locations = rbs_location_range_list_new(ALLOCATOR()); + + rbs_range_t full_range = { + .start = rbs_range.start, + .end = parser->current_token.range.end + }; + + rbs_location_range full_loc = RBS_RANGE_LEX2AST(full_range); + + *annotation = (rbs_ast_ruby_annotations_t *) rbs_ast_ruby_annotations_method_types_annotation_new( + ALLOCATOR(), + full_loc, + RBS_RANGE_LEX2AST(rbs_range), + overloads, + bar_locations, + dot3_range + ); + return true; + } case pLPAREN: case pLBRACKET: case pLBRACE: case tANNOTATION: { rbs_node_list_t *overloads = rbs_node_list_new(ALLOCATOR()); rbs_location_range_list_t *bar_locations = rbs_location_range_list_new(ALLOCATOR()); + rbs_location_range dot3_location = RBS_LOCATION_NULL_RANGE; - if (!parse_inline_method_overloads(parser, overloads, bar_locations)) { + if (!parse_inline_method_overloads(parser, overloads, bar_locations, &dot3_location)) { return false; } @@ -3683,7 +3715,8 @@ static bool parse_inline_leading_annotation(rbs_parser_t *parser, rbs_ast_ruby_a full_loc, RBS_RANGE_LEX2AST(rbs_range), overloads, - bar_locations + bar_locations, + dot3_location ); return true; } From ede61e1c61fcf3233e55013b34e1697ad59ec1a7 Mon Sep 17 00:00:00 2001 From: Soutaro Matsumoto Date: Fri, 20 Feb 2026 16:11:13 +0900 Subject: [PATCH 4/6] Add test --- test/rbs/inline_annotation_parsing_test.rb | 70 ++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/test/rbs/inline_annotation_parsing_test.rb b/test/rbs/inline_annotation_parsing_test.rb index 3fe768db9..020e8df78 100644 --- a/test/rbs/inline_annotation_parsing_test.rb +++ b/test/rbs/inline_annotation_parsing_test.rb @@ -292,4 +292,74 @@ def test_error__module_alias_with_type_variable Parser.parse_inline_trailing_annotation(": module-alias element", 0...) end end + + def test_parse__method_types_annotation + Parser.parse_inline_leading_annotation("@rbs (String) -> void", 0...).tap do |annot| + assert_instance_of AST::Ruby::Annotations::MethodTypesAnnotation, annot + assert_equal "@rbs (String) -> void", annot.location.source + assert_equal "@rbs", annot.prefix_location.source + assert_equal 1, annot.overloads.size + annot.overloads[0].tap do |overload| + assert_equal "(String) -> void", overload.method_type.location.source + assert_empty overload.annotations + end + assert_empty annot.vertical_bar_locations + assert_nil annot.dot3_location + end + + Parser.parse_inline_leading_annotation("@rbs (String) -> void | (Integer) -> void", 0...).tap do |annot| + assert_instance_of AST::Ruby::Annotations::MethodTypesAnnotation, annot + assert_equal "@rbs (String) -> void | (Integer) -> void", annot.location.source + assert_equal "@rbs", annot.prefix_location.source + assert_equal 2, annot.overloads.size + annot.overloads[0].tap do |overload| + assert_equal "(String) -> void", overload.method_type.location.source + end + annot.overloads[1].tap do |overload| + assert_equal "(Integer) -> void", overload.method_type.location.source + end + assert_equal ["|"], annot.vertical_bar_locations.map(&:source) + assert_nil annot.dot3_location + end + end + + def test_parse__method_types_annotation__with_dot3 + Parser.parse_inline_leading_annotation("@rbs (Float, Float) -> Float | ...", 0...).tap do |annot| + assert_instance_of AST::Ruby::Annotations::MethodTypesAnnotation, annot + assert_equal "@rbs (Float, Float) -> Float | ...", annot.location.source + assert_equal "@rbs", annot.prefix_location.source + assert_equal 1, annot.overloads.size + annot.overloads[0].tap do |overload| + assert_equal "(Float, Float) -> Float", overload.method_type.location.source + end + assert_equal ["|"], annot.vertical_bar_locations.map(&:source) + assert_equal "...", annot.dot3_location.source + end + + Parser.parse_inline_leading_annotation("@rbs () -> void | %a{foo} () -> String | ...", 0...).tap do |annot| + assert_instance_of AST::Ruby::Annotations::MethodTypesAnnotation, annot + assert_equal "@rbs () -> void | %a{foo} () -> String | ...", annot.location.source + assert_equal 2, annot.overloads.size + annot.overloads[0].tap do |overload| + assert_equal "() -> void", overload.method_type.location.source + end + annot.overloads[1].tap do |overload| + assert_equal "() -> String", overload.method_type.location.source + assert_equal ["foo"], overload.annotations.map(&:string) + end + assert_equal ["|", "|"], annot.vertical_bar_locations.map(&:source) + assert_equal "...", annot.dot3_location.source + end + end + + def test_parse__method_types_annotation__only_dot3 + Parser.parse_inline_leading_annotation("@rbs ...", 0...).tap do |annot| + assert_instance_of AST::Ruby::Annotations::MethodTypesAnnotation, annot + assert_equal "@rbs ...", annot.location.source + assert_equal "@rbs", annot.prefix_location.source + assert_empty annot.overloads + assert_empty annot.vertical_bar_locations + assert_equal "...", annot.dot3_location.source + end + end end From a31a6ce027ef627fcf61b00280ae77e0cf238768 Mon Sep 17 00:00:00 2001 From: Soutaro Matsumoto Date: Fri, 20 Feb 2026 16:21:26 +0900 Subject: [PATCH 5/6] Update AST --- lib/rbs/ast/ruby/members.rb | 13 +++++++++++- sig/ast/ruby/members.rbs | 2 ++ test/rbs/inline_parser_test.rb | 39 ++++++++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 1 deletion(-) diff --git a/lib/rbs/ast/ruby/members.rb b/lib/rbs/ast/ruby/members.rb index c72bf64e6..b91300365 100644 --- a/lib/rbs/ast/ruby/members.rb +++ b/lib/rbs/ast/ruby/members.rb @@ -180,6 +180,17 @@ def overloads end end + def overloading? + case type_annotations + when Array + type_annotations.any? do |annotation| + annotation.is_a?(Annotations::MethodTypesAnnotation) && annotation.dot3_location + end + else + false + end + end + def type_fingerprint case type_annotations when DocStyle @@ -217,7 +228,7 @@ def overloads end def overloading? - false + method_type.overloading? end def annotations diff --git a/sig/ast/ruby/members.rbs b/sig/ast/ruby/members.rbs index c50e2b542..66f82e1d0 100644 --- a/sig/ast/ruby/members.rbs +++ b/sig/ast/ruby/members.rbs @@ -55,6 +55,8 @@ module RBS # def overloads: () -> Array[AST::Members::MethodDefinition::Overload] + def overloading?: () -> bool + def type_fingerprint: () -> untyped end diff --git a/test/rbs/inline_parser_test.rb b/test/rbs/inline_parser_test.rb index 3c5a6248c..6f834817a 100644 --- a/test/rbs/inline_parser_test.rb +++ b/test/rbs/inline_parser_test.rb @@ -236,6 +236,45 @@ def foo(x = nil) end end + def test_parse__def_method_types_dot3 + result = parse(<<~RUBY) + class Foo + # @rbs (Float, Float) -> Float | ... + def add(x, y) + x + y + end + end + RUBY + + assert_empty result.diagnostics + + result.declarations[0].tap do |decl| + decl.members[0].tap do |member| + assert_instance_of RBS::AST::Ruby::Members::DefMember, member + assert_equal ["(Float, Float) -> Float"], member.overloads.map { _1.method_type.to_s } + assert_predicate member, :overloading? + end + end + + result = parse(<<~RUBY) + class Foo + # @rbs ... + def add(x, y) + x + y + end + end + RUBY + + assert_empty result.diagnostics + + result.declarations[0].tap do |decl| + decl.members[0].tap do |member| + assert_instance_of RBS::AST::Ruby::Members::DefMember, member + assert_predicate member, :overloading? + end + end + end + def test_parse__skip_class_module result = parse(<<~RUBY) # @rbs skip -- not a constant From 611cec1380a661fd789d16374cef43617e1ddf0d Mon Sep 17 00:00:00 2001 From: Soutaro Matsumoto Date: Fri, 20 Feb 2026 16:26:54 +0900 Subject: [PATCH 6/6] Add test --- test/rbs/definition_builder_test.rb | 38 +++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/test/rbs/definition_builder_test.rb b/test/rbs/definition_builder_test.rb index ca18278bd..c34ae027a 100644 --- a/test/rbs/definition_builder_test.rb +++ b/test/rbs/definition_builder_test.rb @@ -3767,4 +3767,42 @@ def initialize(name, age) end end end + + def test_inline_method_types__overloading + SignatureManager.new do |manager| + manager.files[Pathname("base.rbs")] = <<~RBS + class Base + def foo: () -> String + def bar: () -> String + end + RBS + + manager.add_ruby_file("child.rb", <<~RUBY) + class Child < Base + # @rbs (Integer) -> String | ... + def foo(x = nil) = "" + + # @rbs ... + def bar = "" + end + RUBY + + manager.build do |env| + builder = DefinitionBuilder.new(env: env) + + builder.build_instance(type_name("::Child")).tap do |definition| + definition.methods[:foo].tap do |method| + assert_equal ["(::Integer) -> ::String", "() -> ::String"], method.method_types.map(&:to_s) + assert_equal type_name("::Child"), method.defs[0].defined_in + assert_equal type_name("::Base"), method.defs[1].defined_in + end + + definition.methods[:bar].tap do |method| + assert_equal ["() -> ::String"], method.method_types.map(&:to_s) + assert_equal type_name("::Base"), method.defs[0].defined_in + end + end + end + end + end end