diff --git a/config.yml b/config.yml index f8c0767ac..0965c548e 100644 --- a/config.yml +++ b/config.yml @@ -798,6 +798,40 @@ nodes: - name: comment_location c_type: rbs_location_range optional: true + - name: RBS::AST::Ruby::Annotations::SplatParamTypeAnnotation + rust_name: SplatParamTypeAnnotationNode + fields: + - name: prefix_location + c_type: rbs_location_range + - name: star_location + c_type: rbs_location_range + - name: name_location + c_type: rbs_location_range + optional: true + - name: colon_location + c_type: rbs_location_range + - name: param_type + c_type: rbs_node + - name: comment_location + c_type: rbs_location_range + optional: true + - name: RBS::AST::Ruby::Annotations::DoubleSplatParamTypeAnnotation + rust_name: DoubleSplatParamTypeAnnotationNode + fields: + - name: prefix_location + c_type: rbs_location_range + - name: star2_location + c_type: rbs_location_range + - name: name_location + c_type: rbs_location_range + optional: true + - name: colon_location + c_type: rbs_location_range + - name: param_type + c_type: rbs_node + - name: comment_location + c_type: rbs_location_range + optional: true enums: attribute_visibility: diff --git a/docs/inline.md b/docs/inline.md index bc668fc91..10f1eb21a 100644 --- a/docs/inline.md +++ b/docs/inline.md @@ -227,8 +227,10 @@ The `@rbs PARAM_NAME: T` syntax declares the type of a parameter: class Calculator # @rbs x: Integer # @rbs y: Integer - def add(x, y:) - x + y + # @rbs a: String + # @rbs b: bool + def add(x, y = 1, a:, b: false) + pp(x:, y:, a:, b:) end end ``` @@ -237,10 +239,28 @@ You can add a description after `--`: ```ruby class Calculator - # @rbs x: Integer -- the first operand - # @rbs y: Integer -- the second operand - def add(x, y:) - x + y + # @rbs x: Integer -- required positional argument + # @rbs y: Integer -- optional positional argument + # @rbs a: String -- required keyword argument + # @rbs b: bool -- optional keyword argument + def add(x, y = 1, a:, b: false) + pp(x:, y:, a:, b:) + end +end +``` + +Types of splat (`*a`) and double-splat (`**b`) parameters can be declared too. + +```ruby +class Foo + # @rbs *a: String -- The type of `a` is `Array[String]` + # @rbs **b: bool -- The type of `b` is `Hash[Symbol, bool]` + def foo(*a, **b) + end + + # @rbs *: String -- Parameter name is optional + # @rbs **: bool -- Parameter name can be omitted in Ruby too + def bar(*a, **) end end ``` @@ -272,7 +292,7 @@ end ### Current Limitations - Class methods and singleton methods are not supported -- Only positional and keyword parameters are supported. Splat parameters (`*x`, `**y`) and block parameter (`&block`) are not supported yet. +- Only positional and keyword parameters are supported. Splat parameters (`*x`, `**y`) and block parameter (`&block`) are not supported yet. - Method visibility declaration is not supported yet ## Attributes diff --git a/ext/rbs_extension/ast_translation.c b/ext/rbs_extension/ast_translation.c index ecd1b65f2..8c4163ffe 100644 --- a/ext/rbs_extension/ast_translation.c +++ b/ext/rbs_extension/ast_translation.c @@ -859,6 +859,24 @@ VALUE rbs_struct_to_ruby_value(rbs_translation_context_t ctx, rbs_node_t *instan &h ); } + case RBS_AST_RUBY_ANNOTATIONS_DOUBLE_SPLAT_PARAM_TYPE_ANNOTATION: { + rbs_ast_ruby_annotations_double_splat_param_type_annotation_t *node = (rbs_ast_ruby_annotations_double_splat_param_type_annotation_t *) instance; + + VALUE h = rb_hash_new(); + rb_hash_aset(h, ID2SYM(rb_intern("location")), rbs_location_range_to_ruby_location(ctx, node->base.location)); + 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("star2_location")), rbs_location_range_to_ruby_location(ctx, node->star2_location)); + rb_hash_aset(h, ID2SYM(rb_intern("name_location")), rbs_location_range_to_ruby_location(ctx, node->name_location)); // optional + rb_hash_aset(h, ID2SYM(rb_intern("colon_location")), rbs_location_range_to_ruby_location(ctx, node->colon_location)); + rb_hash_aset(h, ID2SYM(rb_intern("param_type")), rbs_struct_to_ruby_value(ctx, (rbs_node_t *) node->param_type)); // rbs_node + rb_hash_aset(h, ID2SYM(rb_intern("comment_location")), rbs_location_range_to_ruby_location(ctx, node->comment_location)); // optional + + return CLASS_NEW_INSTANCE( + RBS_AST_Ruby_Annotations_DoubleSplatParamTypeAnnotation, + 1, + &h + ); + } case RBS_AST_RUBY_ANNOTATIONS_INSTANCE_VARIABLE_ANNOTATION: { rbs_ast_ruby_annotations_instance_variable_annotation_t *node = (rbs_ast_ruby_annotations_instance_variable_annotation_t *) instance; @@ -972,6 +990,24 @@ VALUE rbs_struct_to_ruby_value(rbs_translation_context_t ctx, rbs_node_t *instan &h ); } + case RBS_AST_RUBY_ANNOTATIONS_SPLAT_PARAM_TYPE_ANNOTATION: { + rbs_ast_ruby_annotations_splat_param_type_annotation_t *node = (rbs_ast_ruby_annotations_splat_param_type_annotation_t *) instance; + + VALUE h = rb_hash_new(); + rb_hash_aset(h, ID2SYM(rb_intern("location")), rbs_location_range_to_ruby_location(ctx, node->base.location)); + 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("star_location")), rbs_location_range_to_ruby_location(ctx, node->star_location)); + rb_hash_aset(h, ID2SYM(rb_intern("name_location")), rbs_location_range_to_ruby_location(ctx, node->name_location)); // optional + rb_hash_aset(h, ID2SYM(rb_intern("colon_location")), rbs_location_range_to_ruby_location(ctx, node->colon_location)); + rb_hash_aset(h, ID2SYM(rb_intern("param_type")), rbs_struct_to_ruby_value(ctx, (rbs_node_t *) node->param_type)); // rbs_node + rb_hash_aset(h, ID2SYM(rb_intern("comment_location")), rbs_location_range_to_ruby_location(ctx, node->comment_location)); // optional + + return CLASS_NEW_INSTANCE( + RBS_AST_Ruby_Annotations_SplatParamTypeAnnotation, + 1, + &h + ); + } case RBS_AST_RUBY_ANNOTATIONS_TYPE_APPLICATION_ANNOTATION: { rbs_ast_ruby_annotations_type_application_annotation_t *node = (rbs_ast_ruby_annotations_type_application_annotation_t *) instance; diff --git a/ext/rbs_extension/class_constants.c b/ext/rbs_extension/class_constants.c index 1a1e37690..c4ad86adb 100644 --- a/ext/rbs_extension/class_constants.c +++ b/ext/rbs_extension/class_constants.c @@ -49,6 +49,7 @@ VALUE RBS_AST_Members_Private; VALUE RBS_AST_Members_Public; VALUE RBS_AST_Ruby_Annotations_ClassAliasAnnotation; VALUE RBS_AST_Ruby_Annotations_ColonMethodTypeAnnotation; +VALUE RBS_AST_Ruby_Annotations_DoubleSplatParamTypeAnnotation; VALUE RBS_AST_Ruby_Annotations_InstanceVariableAnnotation; VALUE RBS_AST_Ruby_Annotations_MethodTypesAnnotation; VALUE RBS_AST_Ruby_Annotations_ModuleAliasAnnotation; @@ -56,6 +57,7 @@ VALUE RBS_AST_Ruby_Annotations_NodeTypeAssertion; VALUE RBS_AST_Ruby_Annotations_ParamTypeAnnotation; VALUE RBS_AST_Ruby_Annotations_ReturnTypeAnnotation; VALUE RBS_AST_Ruby_Annotations_SkipAnnotation; +VALUE RBS_AST_Ruby_Annotations_SplatParamTypeAnnotation; VALUE RBS_AST_Ruby_Annotations_TypeApplicationAnnotation; VALUE RBS_AST_TypeParam; VALUE RBS_MethodType; @@ -139,6 +141,7 @@ void rbs__init_constants(void) { IMPORT_CONSTANT(RBS_AST_Members_Public, RBS_AST_Members, "Public"); IMPORT_CONSTANT(RBS_AST_Ruby_Annotations_ClassAliasAnnotation, RBS_AST_Ruby_Annotations, "ClassAliasAnnotation"); IMPORT_CONSTANT(RBS_AST_Ruby_Annotations_ColonMethodTypeAnnotation, RBS_AST_Ruby_Annotations, "ColonMethodTypeAnnotation"); + IMPORT_CONSTANT(RBS_AST_Ruby_Annotations_DoubleSplatParamTypeAnnotation, RBS_AST_Ruby_Annotations, "DoubleSplatParamTypeAnnotation"); IMPORT_CONSTANT(RBS_AST_Ruby_Annotations_InstanceVariableAnnotation, RBS_AST_Ruby_Annotations, "InstanceVariableAnnotation"); IMPORT_CONSTANT(RBS_AST_Ruby_Annotations_MethodTypesAnnotation, RBS_AST_Ruby_Annotations, "MethodTypesAnnotation"); IMPORT_CONSTANT(RBS_AST_Ruby_Annotations_ModuleAliasAnnotation, RBS_AST_Ruby_Annotations, "ModuleAliasAnnotation"); @@ -146,6 +149,7 @@ void rbs__init_constants(void) { IMPORT_CONSTANT(RBS_AST_Ruby_Annotations_ParamTypeAnnotation, RBS_AST_Ruby_Annotations, "ParamTypeAnnotation"); IMPORT_CONSTANT(RBS_AST_Ruby_Annotations_ReturnTypeAnnotation, RBS_AST_Ruby_Annotations, "ReturnTypeAnnotation"); IMPORT_CONSTANT(RBS_AST_Ruby_Annotations_SkipAnnotation, RBS_AST_Ruby_Annotations, "SkipAnnotation"); + IMPORT_CONSTANT(RBS_AST_Ruby_Annotations_SplatParamTypeAnnotation, RBS_AST_Ruby_Annotations, "SplatParamTypeAnnotation"); IMPORT_CONSTANT(RBS_AST_Ruby_Annotations_TypeApplicationAnnotation, RBS_AST_Ruby_Annotations, "TypeApplicationAnnotation"); IMPORT_CONSTANT(RBS_AST_TypeParam, RBS_AST, "TypeParam"); IMPORT_CONSTANT(RBS_MethodType, RBS, "MethodType"); diff --git a/ext/rbs_extension/class_constants.h b/ext/rbs_extension/class_constants.h index cdf2e58a3..91aa89ec1 100644 --- a/ext/rbs_extension/class_constants.h +++ b/ext/rbs_extension/class_constants.h @@ -57,6 +57,7 @@ extern VALUE RBS_AST_Members_Private; extern VALUE RBS_AST_Members_Public; extern VALUE RBS_AST_Ruby_Annotations_ClassAliasAnnotation; extern VALUE RBS_AST_Ruby_Annotations_ColonMethodTypeAnnotation; +extern VALUE RBS_AST_Ruby_Annotations_DoubleSplatParamTypeAnnotation; extern VALUE RBS_AST_Ruby_Annotations_InstanceVariableAnnotation; extern VALUE RBS_AST_Ruby_Annotations_MethodTypesAnnotation; extern VALUE RBS_AST_Ruby_Annotations_ModuleAliasAnnotation; @@ -64,6 +65,7 @@ extern VALUE RBS_AST_Ruby_Annotations_NodeTypeAssertion; extern VALUE RBS_AST_Ruby_Annotations_ParamTypeAnnotation; extern VALUE RBS_AST_Ruby_Annotations_ReturnTypeAnnotation; extern VALUE RBS_AST_Ruby_Annotations_SkipAnnotation; +extern VALUE RBS_AST_Ruby_Annotations_SplatParamTypeAnnotation; extern VALUE RBS_AST_Ruby_Annotations_TypeApplicationAnnotation; extern VALUE RBS_AST_TypeParam; extern VALUE RBS_MethodType; diff --git a/include/rbs/ast.h b/include/rbs/ast.h index a19fa791a..69d85d9c6 100644 --- a/include/rbs/ast.h +++ b/include/rbs/ast.h @@ -92,46 +92,48 @@ enum rbs_node_type { RBS_AST_MEMBERS_PUBLIC = 31, RBS_AST_RUBY_ANNOTATIONS_CLASS_ALIAS_ANNOTATION = 32, RBS_AST_RUBY_ANNOTATIONS_COLON_METHOD_TYPE_ANNOTATION = 33, - RBS_AST_RUBY_ANNOTATIONS_INSTANCE_VARIABLE_ANNOTATION = 34, - RBS_AST_RUBY_ANNOTATIONS_METHOD_TYPES_ANNOTATION = 35, - RBS_AST_RUBY_ANNOTATIONS_MODULE_ALIAS_ANNOTATION = 36, - RBS_AST_RUBY_ANNOTATIONS_NODE_TYPE_ASSERTION = 37, - RBS_AST_RUBY_ANNOTATIONS_PARAM_TYPE_ANNOTATION = 38, - RBS_AST_RUBY_ANNOTATIONS_RETURN_TYPE_ANNOTATION = 39, - RBS_AST_RUBY_ANNOTATIONS_SKIP_ANNOTATION = 40, - RBS_AST_RUBY_ANNOTATIONS_TYPE_APPLICATION_ANNOTATION = 41, - RBS_AST_STRING = 42, - RBS_AST_TYPE_PARAM = 43, - RBS_METHOD_TYPE = 44, - RBS_NAMESPACE = 45, - RBS_SIGNATURE = 46, - RBS_TYPE_NAME = 47, - RBS_TYPES_ALIAS = 48, - RBS_TYPES_BASES_ANY = 49, - RBS_TYPES_BASES_BOOL = 50, - RBS_TYPES_BASES_BOTTOM = 51, - RBS_TYPES_BASES_CLASS = 52, - RBS_TYPES_BASES_INSTANCE = 53, - RBS_TYPES_BASES_NIL = 54, - RBS_TYPES_BASES_SELF = 55, - RBS_TYPES_BASES_TOP = 56, - RBS_TYPES_BASES_VOID = 57, - RBS_TYPES_BLOCK = 58, - RBS_TYPES_CLASS_INSTANCE = 59, - RBS_TYPES_CLASS_SINGLETON = 60, - RBS_TYPES_FUNCTION = 61, - RBS_TYPES_FUNCTION_PARAM = 62, - RBS_TYPES_INTERFACE = 63, - RBS_TYPES_INTERSECTION = 64, - RBS_TYPES_LITERAL = 65, - RBS_TYPES_OPTIONAL = 66, - RBS_TYPES_PROC = 67, - RBS_TYPES_RECORD = 68, - RBS_TYPES_RECORD_FIELD_TYPE = 69, - RBS_TYPES_TUPLE = 70, - RBS_TYPES_UNION = 71, - RBS_TYPES_UNTYPED_FUNCTION = 72, - RBS_TYPES_VARIABLE = 73, + RBS_AST_RUBY_ANNOTATIONS_DOUBLE_SPLAT_PARAM_TYPE_ANNOTATION = 34, + RBS_AST_RUBY_ANNOTATIONS_INSTANCE_VARIABLE_ANNOTATION = 35, + RBS_AST_RUBY_ANNOTATIONS_METHOD_TYPES_ANNOTATION = 36, + RBS_AST_RUBY_ANNOTATIONS_MODULE_ALIAS_ANNOTATION = 37, + RBS_AST_RUBY_ANNOTATIONS_NODE_TYPE_ASSERTION = 38, + RBS_AST_RUBY_ANNOTATIONS_PARAM_TYPE_ANNOTATION = 39, + RBS_AST_RUBY_ANNOTATIONS_RETURN_TYPE_ANNOTATION = 40, + RBS_AST_RUBY_ANNOTATIONS_SKIP_ANNOTATION = 41, + RBS_AST_RUBY_ANNOTATIONS_SPLAT_PARAM_TYPE_ANNOTATION = 42, + RBS_AST_RUBY_ANNOTATIONS_TYPE_APPLICATION_ANNOTATION = 43, + RBS_AST_STRING = 44, + RBS_AST_TYPE_PARAM = 45, + RBS_METHOD_TYPE = 46, + RBS_NAMESPACE = 47, + RBS_SIGNATURE = 48, + RBS_TYPE_NAME = 49, + RBS_TYPES_ALIAS = 50, + RBS_TYPES_BASES_ANY = 51, + RBS_TYPES_BASES_BOOL = 52, + RBS_TYPES_BASES_BOTTOM = 53, + RBS_TYPES_BASES_CLASS = 54, + RBS_TYPES_BASES_INSTANCE = 55, + RBS_TYPES_BASES_NIL = 56, + RBS_TYPES_BASES_SELF = 57, + RBS_TYPES_BASES_TOP = 58, + RBS_TYPES_BASES_VOID = 59, + RBS_TYPES_BLOCK = 60, + RBS_TYPES_CLASS_INSTANCE = 61, + RBS_TYPES_CLASS_SINGLETON = 62, + RBS_TYPES_FUNCTION = 63, + RBS_TYPES_FUNCTION_PARAM = 64, + RBS_TYPES_INTERFACE = 65, + RBS_TYPES_INTERSECTION = 66, + RBS_TYPES_LITERAL = 67, + RBS_TYPES_OPTIONAL = 68, + RBS_TYPES_PROC = 69, + RBS_TYPES_RECORD = 70, + RBS_TYPES_RECORD_FIELD_TYPE = 71, + RBS_TYPES_TUPLE = 72, + RBS_TYPES_UNION = 73, + RBS_TYPES_UNTYPED_FUNCTION = 74, + RBS_TYPES_VARIABLE = 75, RBS_AST_SYMBOL, }; @@ -577,6 +579,17 @@ typedef struct rbs_ast_ruby_annotations_colon_method_type_annotation { struct rbs_node *method_type; } rbs_ast_ruby_annotations_colon_method_type_annotation_t; +typedef struct rbs_ast_ruby_annotations_double_splat_param_type_annotation { + rbs_node_t base; + + rbs_location_range prefix_location; + rbs_location_range star2_location; + rbs_location_range name_location; /* Optional */ + rbs_location_range colon_location; + struct rbs_node *param_type; + rbs_location_range comment_location; /* Optional */ +} rbs_ast_ruby_annotations_double_splat_param_type_annotation_t; + typedef struct rbs_ast_ruby_annotations_instance_variable_annotation { rbs_node_t base; @@ -641,6 +654,17 @@ typedef struct rbs_ast_ruby_annotations_skip_annotation { rbs_location_range comment_location; /* Optional */ } rbs_ast_ruby_annotations_skip_annotation_t; +typedef struct rbs_ast_ruby_annotations_splat_param_type_annotation { + rbs_node_t base; + + rbs_location_range prefix_location; + rbs_location_range star_location; + rbs_location_range name_location; /* Optional */ + rbs_location_range colon_location; + struct rbs_node *param_type; + rbs_location_range comment_location; /* Optional */ +} rbs_ast_ruby_annotations_splat_param_type_annotation_t; + typedef struct rbs_ast_ruby_annotations_type_application_annotation { rbs_node_t base; @@ -937,6 +961,7 @@ rbs_ast_members_private_t *rbs_ast_members_private_new(rbs_allocator_t *allocato rbs_ast_members_public_t *rbs_ast_members_public_new(rbs_allocator_t *allocator, rbs_location_range location); 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_double_splat_param_type_annotation_t *rbs_ast_ruby_annotations_double_splat_param_type_annotation_new(rbs_allocator_t *allocator, rbs_location_range location, rbs_location_range prefix_location, rbs_location_range star2_location, rbs_location_range name_location, rbs_location_range colon_location, rbs_node_t *param_type, rbs_location_range comment_location); 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_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); @@ -944,6 +969,7 @@ rbs_ast_ruby_annotations_node_type_assertion_t *rbs_ast_ruby_annotations_node_ty rbs_ast_ruby_annotations_param_type_annotation_t *rbs_ast_ruby_annotations_param_type_annotation_new(rbs_allocator_t *allocator, rbs_location_range location, rbs_location_range prefix_location, rbs_location_range name_location, rbs_location_range colon_location, rbs_node_t *param_type, rbs_location_range comment_location); 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); rbs_ast_ruby_annotations_skip_annotation_t *rbs_ast_ruby_annotations_skip_annotation_new(rbs_allocator_t *allocator, rbs_location_range location, rbs_location_range prefix_location, rbs_location_range skip_location, rbs_location_range comment_location); +rbs_ast_ruby_annotations_splat_param_type_annotation_t *rbs_ast_ruby_annotations_splat_param_type_annotation_new(rbs_allocator_t *allocator, rbs_location_range location, rbs_location_range prefix_location, rbs_location_range star_location, rbs_location_range name_location, rbs_location_range colon_location, rbs_node_t *param_type, rbs_location_range comment_location); rbs_ast_ruby_annotations_type_application_annotation_t *rbs_ast_ruby_annotations_type_application_annotation_new(rbs_allocator_t *allocator, rbs_location_range location, rbs_location_range prefix_location, rbs_node_list_t *type_args, rbs_location_range close_bracket_location, rbs_location_range_list_t *comma_locations); rbs_ast_string_t *rbs_ast_string_new(rbs_allocator_t *allocator, rbs_location_range location, rbs_string_t string); rbs_ast_type_param_t *rbs_ast_type_param_new(rbs_allocator_t *allocator, rbs_location_range location, rbs_ast_symbol_t *name, enum rbs_type_param_variance variance, rbs_node_t *upper_bound, rbs_node_t *lower_bound, rbs_node_t *default_type, bool unchecked, rbs_location_range name_range); diff --git a/lib/rbs/ast/ruby/annotations.rb b/lib/rbs/ast/ruby/annotations.rb index 746abae5c..d1a542b14 100644 --- a/lib/rbs/ast/ruby/annotations.rb +++ b/lib/rbs/ast/ruby/annotations.rb @@ -275,6 +275,56 @@ def map_type_name(&block) ) #: self end end + + class SplatParamTypeAnnotation < Base + attr_reader :star_location, :name_location, :colon_location, :param_type, :comment_location + + def initialize(location:, prefix_location:, star_location:, name_location:, colon_location:, param_type:, comment_location:) + super(location, prefix_location) + @star_location = star_location + @name_location = name_location + @colon_location = colon_location + @param_type = param_type + @comment_location = comment_location + end + + def map_type_name(&block) + self.class.new( + location:, + prefix_location:, + star_location: star_location, + name_location: name_location, + colon_location: colon_location, + param_type: param_type.map_type_name { yield _1 }, + comment_location: comment_location + ) #: self + end + end + + class DoubleSplatParamTypeAnnotation < Base + attr_reader :star2_location, :name_location, :colon_location, :param_type, :comment_location + + def initialize(location:, prefix_location:, star2_location:, name_location:, colon_location:, param_type:, comment_location:) + super(location, prefix_location) + @star2_location = star2_location + @name_location = name_location + @colon_location = colon_location + @param_type = param_type + @comment_location = comment_location + end + + def map_type_name(&block) + self.class.new( + location:, + prefix_location:, + star2_location: star2_location, + name_location: name_location, + colon_location: colon_location, + param_type: param_type.map_type_name { yield _1 }, + comment_location: comment_location + ) #: self + end + end end end end diff --git a/lib/rbs/ast/ruby/members.rb b/lib/rbs/ast/ruby/members.rb index 4b44f394d..b06a9ac18 100644 --- a/lib/rbs/ast/ruby/members.rb +++ b/lib/rbs/ast/ruby/members.rb @@ -27,29 +27,55 @@ class DocStyle def initialize @return_type_annotation = nil - @required_positionals = [] #: Array[Annotations::ParamTypeAnnotation?] - @optional_positionals = [] #: Array[Annotations::ParamTypeAnnotation?] - @rest_positionals = nil #: Annotations::ParamTypeAnnotation? - @trailing_positionals = [] #: Array[Annotations::ParamTypeAnnotation?] - @required_keywords = {} #: Hash[Symbol, Annotations::ParamTypeAnnotation] - @optional_keywords = {} #: Hash[Symbol, Annotations::ParamTypeAnnotation] - @rest_keywords = nil #: Annotations::ParamTypeAnnotation? + @required_positionals = [] + @optional_positionals = [] + @rest_positionals = nil + @trailing_positionals = [] + @required_keywords = {} + @optional_keywords = {} + @rest_keywords = nil end def self.build(param_type_annotations, return_type_annotation, node) doc = DocStyle.new doc.return_type_annotation = return_type_annotation - unused = param_type_annotations.dup + splat_annotation = nil #: Annotations::SplatParamTypeAnnotation? + double_splat_annotation = nil #: Annotations::DoubleSplatParamTypeAnnotation? + param_annotations = {} #: Hash[Symbol, Annotations::ParamTypeAnnotation] + unused = [] #: Array[Annotations::ParamTypeAnnotation | Annotations::SplatParamTypeAnnotation | Annotations::DoubleSplatParamTypeAnnotation] + + param_type_annotations.each do |annot| + case annot + when Annotations::SplatParamTypeAnnotation + if splat_annotation + unused << annot + else + splat_annotation = annot + end + when Annotations::DoubleSplatParamTypeAnnotation + if double_splat_annotation + unused << annot + else + double_splat_annotation = annot + end + when Annotations::ParamTypeAnnotation + name = annot.name_location.source.to_sym + if param_annotations.key?(name) + unused << annot + else + param_annotations[name] = annot + end + end + end if node.parameters params = node.parameters #: Prism::ParametersNode params.requireds.each do |param| if param.is_a?(Prism::RequiredParameterNode) - annotation = unused.find { _1.name_location.source.to_sym == param.name } + annotation = param_annotations.delete(param.name) if annotation - unused.delete(annotation) doc.required_positionals << annotation else doc.required_positionals << param.name @@ -59,9 +85,8 @@ def self.build(param_type_annotations, return_type_annotation, node) params.optionals.each do |param| if param.is_a?(Prism::OptionalParameterNode) - annotation = unused.find { _1.name_location.source.to_sym == param.name } + annotation = param_annotations.delete(param.name) if annotation - unused.delete(annotation) doc.optional_positionals << annotation else doc.optional_positionals << param.name @@ -69,21 +94,19 @@ def self.build(param_type_annotations, return_type_annotation, node) end end - if (rest = params.rest) && rest.is_a?(Prism::RestParameterNode) && rest.name - annotation = unused.find { _1.name_location.source.to_sym == rest.name } - if annotation - unused.delete(annotation) - doc.rest_positionals = annotation + if (rest = params.rest) && rest.is_a?(Prism::RestParameterNode) + if splat_annotation && (splat_annotation.name_location.nil? || rest.name.nil? || splat_annotation.name_location.source.to_sym == rest.name) + doc.rest_positionals = splat_annotation + splat_annotation = nil else - doc.rest_positionals = rest.name + doc.rest_positionals = rest.name || true end end params.posts.each do |param| if param.is_a?(Prism::RequiredParameterNode) - annotation = unused.find { _1.name_location.source.to_sym == param.name } + annotation = param_annotations.delete(param.name) if annotation - unused.delete(annotation) doc.trailing_positionals << annotation else doc.trailing_positionals << param.name @@ -94,17 +117,15 @@ def self.build(param_type_annotations, return_type_annotation, node) params.keywords.each do |param| case param when Prism::RequiredKeywordParameterNode - annotation = unused.find { _1.name_location.source.to_sym == param.name } + annotation = param_annotations.delete(param.name) if annotation - unused.delete(annotation) doc.required_keywords[param.name] = annotation else doc.required_keywords[param.name] = param.name end when Prism::OptionalKeywordParameterNode - annotation = unused.find { _1.name_location.source.to_sym == param.name } + annotation = param_annotations.delete(param.name) if annotation - unused.delete(annotation) doc.optional_keywords[param.name] = annotation else doc.optional_keywords[param.name] = param.name @@ -112,23 +133,26 @@ def self.build(param_type_annotations, return_type_annotation, node) end end - if (kw_rest = params.keyword_rest) && kw_rest.is_a?(Prism::KeywordRestParameterNode) && kw_rest.name - annotation = unused.find { _1.name_location.source.to_sym == kw_rest.name } - if annotation - unused.delete(annotation) - doc.rest_keywords = annotation + if (kw_rest = params.keyword_rest) && kw_rest.is_a?(Prism::KeywordRestParameterNode) + if double_splat_annotation && (double_splat_annotation.name_location.nil? || kw_rest.name.nil? || double_splat_annotation.name_location.source.to_sym == kw_rest.name) + doc.rest_keywords = double_splat_annotation + double_splat_annotation = nil else - doc.rest_keywords = kw_rest.name + doc.rest_keywords = kw_rest.name || true end end end + unused.concat(param_annotations.values) + unused << splat_annotation if splat_annotation + unused << double_splat_annotation if double_splat_annotation + [doc, unused] end def all_param_annotations - annotations = [] #: Array[param_type_annotation | Symbol | nil] - # + annotations = [] #: Array[param_type_annotation | Symbol | true | nil] + required_positionals.each { |a| annotations << a } optional_positionals.each { |a| annotations << a } annotations << rest_positionals @@ -165,7 +189,7 @@ def map_type_name(&block) ) new.rest_positionals = case rest_positionals - when Annotations::ParamTypeAnnotation + when Annotations::SplatParamTypeAnnotation rest_positionals.map_type_name(&block) else rest_positionals @@ -202,7 +226,7 @@ def map_type_name(&block) ) new.rest_keywords = case rest_keywords - when Annotations::ParamTypeAnnotation + when Annotations::DoubleSplatParamTypeAnnotation rest_keywords.map_type_name(&block) else rest_keywords @@ -255,10 +279,12 @@ def method_type end rest_pos = case rest_positionals - when Annotations::ParamTypeAnnotation - Types::Function::Param.new(type: rest_positionals.param_type, name: rest_positionals.name_location.source.to_sym) + when Annotations::SplatParamTypeAnnotation + Types::Function::Param.new(type: rest_positionals.param_type, name: rest_positionals.name_location&.source&.to_sym) when Symbol Types::Function::Param.new(type: any.call, name: rest_positionals) + when true + Types::Function::Param.new(type: any.call, name: nil) else nil end @@ -290,9 +316,11 @@ def method_type rest_kw = case rest_keywords - when Annotations::ParamTypeAnnotation - Types::Function::Param.new(type: rest_keywords.param_type, name: nil) + when Annotations::DoubleSplatParamTypeAnnotation + Types::Function::Param.new(type: rest_keywords.param_type, name: rest_keywords.name_location&.source&.to_sym) when Symbol + Types::Function::Param.new(type: any.call, name: rest_keywords) + when true Types::Function::Param.new(type: any.call, name: nil) else nil @@ -343,7 +371,7 @@ def self.build(leading_block, trailing_block, variables, node) type_annotations = nil #: type_annotations return_annotation = nil #: Annotations::ReturnTypeAnnotation | Annotations::NodeTypeAssertion | nil - param_annotations = [] #: Array[Annotations::ParamTypeAnnotation] + param_annotations = [] #: Array[Annotations::ParamTypeAnnotation | Annotations::SplatParamTypeAnnotation | Annotations::DoubleSplatParamTypeAnnotation] if trailing_block case annotation = trailing_block.trailing_annotation(variables) @@ -377,7 +405,7 @@ def self.build(leading_block, trailing_block, variables, node) next end end - when Annotations::ParamTypeAnnotation + when Annotations::ParamTypeAnnotation, Annotations::SplatParamTypeAnnotation, Annotations::DoubleSplatParamTypeAnnotation unless type_annotations param_annotations << paragraph next diff --git a/rust/ruby-rbs-sys/build.rs b/rust/ruby-rbs-sys/build.rs index 701456cf7..bf297e508 100644 --- a/rust/ruby-rbs-sys/build.rs +++ b/rust/ruby-rbs-sys/build.rs @@ -113,6 +113,8 @@ fn generate_bindings(include_path: &Path) -> Result TypeName } -> self end + + # `@rbs *name: Type -- comment` annotation in leading comments + # + # ``` + # @rbs *a: String -- comment + # ^^^^ -- prefix_location + # ^ -- star_location + # ^ -- name_location + # ^ -- colon_location + # ^^^^^^^^^^ -- comment_location + # ``` + # + # ``` + # @rbs *: String + # ^^^^ -- prefix_location + # ^ -- star_location + # ^ -- colon_location + # ``` + # + class SplatParamTypeAnnotation < Base + attr_reader star_location: Location + + attr_reader name_location: Location? + + attr_reader colon_location: Location + + attr_reader param_type: Types::t + + attr_reader comment_location: Location? + + def initialize: ( + location: Location, + prefix_location: Location, + star_location: Location, + name_location: Location?, + colon_location: Location, + param_type: Types::t, + comment_location: Location?, + ) -> void + + def map_type_name: () { (TypeName) -> TypeName } -> self + end + + # `@rbs **name: Type -- comment` annotation in leading comments + # + # ``` + # @rbs **b: bool -- comment + # ^^^^ -- prefix_location + # ^^ -- star2_location + # ^ -- name_location + # ^ -- colon_location + # ^^^^^^^^^^ -- comment_location + # ``` + # + # ``` + # @rbs **: bool + # ^^^^ -- prefix_location + # ^^ -- star2_location + # ^ -- colon_location + # ``` + # + class DoubleSplatParamTypeAnnotation < Base + attr_reader star2_location: Location + + attr_reader name_location: Location? + + attr_reader colon_location: Location + + attr_reader param_type: Types::t + + attr_reader comment_location: Location? + + def initialize: ( + location: Location, + prefix_location: Location, + star2_location: Location, + name_location: Location?, + colon_location: Location, + param_type: Types::t, + comment_location: Location?, + ) -> void + + def map_type_name: () { (TypeName) -> TypeName } -> self + end end end end diff --git a/sig/ast/ruby/members.rbs b/sig/ast/ruby/members.rbs index 12c775ffb..7db06e6a2 100644 --- a/sig/ast/ruby/members.rbs +++ b/sig/ast/ruby/members.rbs @@ -20,22 +20,24 @@ module RBS class MethodTypeAnnotation class DocStyle type param_type_annotation = Annotations::ParamTypeAnnotation + | Annotations::SplatParamTypeAnnotation + | Annotations::DoubleSplatParamTypeAnnotation attr_accessor return_type_annotation: Annotations::ReturnTypeAnnotation | Annotations::NodeTypeAssertion | nil - attr_reader required_positionals: Array[param_type_annotation | Symbol] - attr_reader optional_positionals: Array[param_type_annotation | Symbol] - attr_accessor rest_positionals: param_type_annotation | Symbol | nil - attr_reader trailing_positionals: Array[param_type_annotation | Symbol] - attr_reader required_keywords: Hash[Symbol, param_type_annotation | Symbol] - attr_reader optional_keywords: Hash[Symbol, param_type_annotation | Symbol] - attr_accessor rest_keywords: param_type_annotation | Symbol | nil + attr_reader required_positionals: Array[Annotations::ParamTypeAnnotation | Symbol] + attr_reader optional_positionals: Array[Annotations::ParamTypeAnnotation | Symbol] + attr_accessor rest_positionals: Annotations::SplatParamTypeAnnotation | Symbol | true | nil + attr_reader trailing_positionals: Array[Annotations::ParamTypeAnnotation | Symbol] + attr_reader required_keywords: Hash[Symbol, Annotations::ParamTypeAnnotation | Symbol] + attr_reader optional_keywords: Hash[Symbol, Annotations::ParamTypeAnnotation | Symbol] + attr_accessor rest_keywords: Annotations::DoubleSplatParamTypeAnnotation | Symbol | true | nil def initialize: () -> void def self.build: (Array[param_type_annotation], Annotations::ReturnTypeAnnotation | Annotations::NodeTypeAssertion | nil, Prism::DefNode) -> [DocStyle, Array[param_type_annotation]] - def all_param_annotations: () -> Array[param_type_annotation | Symbol | nil] + def all_param_annotations: () -> Array[param_type_annotation | Symbol | true | nil] def map_type_name: () { (TypeName) -> TypeName } -> self diff --git a/src/ast.c b/src/ast.c index c42021f50..4932adf2b 100644 --- a/src/ast.c +++ b/src/ast.c @@ -79,6 +79,8 @@ const char *rbs_node_type_name(rbs_node_t *node) { return "RBS::AST::Ruby::Annotations::ClassAliasAnnotation"; case RBS_AST_RUBY_ANNOTATIONS_COLON_METHOD_TYPE_ANNOTATION: return "RBS::AST::Ruby::Annotations::ColonMethodTypeAnnotation"; + case RBS_AST_RUBY_ANNOTATIONS_DOUBLE_SPLAT_PARAM_TYPE_ANNOTATION: + return "RBS::AST::Ruby::Annotations::DoubleSplatParamTypeAnnotation"; case RBS_AST_RUBY_ANNOTATIONS_INSTANCE_VARIABLE_ANNOTATION: return "RBS::AST::Ruby::Annotations::InstanceVariableAnnotation"; case RBS_AST_RUBY_ANNOTATIONS_METHOD_TYPES_ANNOTATION: @@ -93,6 +95,8 @@ const char *rbs_node_type_name(rbs_node_t *node) { return "RBS::AST::Ruby::Annotations::ReturnTypeAnnotation"; case RBS_AST_RUBY_ANNOTATIONS_SKIP_ANNOTATION: return "RBS::AST::Ruby::Annotations::SkipAnnotation"; + case RBS_AST_RUBY_ANNOTATIONS_SPLAT_PARAM_TYPE_ANNOTATION: + return "RBS::AST::Ruby::Annotations::SplatParamTypeAnnotation"; case RBS_AST_RUBY_ANNOTATIONS_TYPE_APPLICATION_ANNOTATION: return "RBS::AST::Ruby::Annotations::TypeApplicationAnnotation"; case RBS_AST_STRING: @@ -917,6 +921,25 @@ rbs_ast_ruby_annotations_colon_method_type_annotation_t *rbs_ast_ruby_annotation return instance; } #line 140 "prism/templates/src/ast.c.erb" +rbs_ast_ruby_annotations_double_splat_param_type_annotation_t *rbs_ast_ruby_annotations_double_splat_param_type_annotation_new(rbs_allocator_t *allocator, rbs_location_range location, rbs_location_range prefix_location, rbs_location_range star2_location, rbs_location_range name_location, rbs_location_range colon_location, rbs_node_t *param_type, rbs_location_range comment_location) { + rbs_ast_ruby_annotations_double_splat_param_type_annotation_t *instance = rbs_allocator_alloc(allocator, rbs_ast_ruby_annotations_double_splat_param_type_annotation_t); + + *instance = (rbs_ast_ruby_annotations_double_splat_param_type_annotation_t) { + .base = (rbs_node_t) { + .type = RBS_AST_RUBY_ANNOTATIONS_DOUBLE_SPLAT_PARAM_TYPE_ANNOTATION, + .location = location, + }, + .prefix_location = prefix_location, + .star2_location = star2_location, + .name_location = name_location, + .colon_location = colon_location, + .param_type = param_type, + .comment_location = comment_location, + }; + + return instance; +} +#line 140 "prism/templates/src/ast.c.erb" 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_instance_variable_annotation_t *instance = rbs_allocator_alloc(allocator, rbs_ast_ruby_annotations_instance_variable_annotation_t); @@ -1037,6 +1060,25 @@ rbs_ast_ruby_annotations_skip_annotation_t *rbs_ast_ruby_annotations_skip_annota return instance; } #line 140 "prism/templates/src/ast.c.erb" +rbs_ast_ruby_annotations_splat_param_type_annotation_t *rbs_ast_ruby_annotations_splat_param_type_annotation_new(rbs_allocator_t *allocator, rbs_location_range location, rbs_location_range prefix_location, rbs_location_range star_location, rbs_location_range name_location, rbs_location_range colon_location, rbs_node_t *param_type, rbs_location_range comment_location) { + rbs_ast_ruby_annotations_splat_param_type_annotation_t *instance = rbs_allocator_alloc(allocator, rbs_ast_ruby_annotations_splat_param_type_annotation_t); + + *instance = (rbs_ast_ruby_annotations_splat_param_type_annotation_t) { + .base = (rbs_node_t) { + .type = RBS_AST_RUBY_ANNOTATIONS_SPLAT_PARAM_TYPE_ANNOTATION, + .location = location, + }, + .prefix_location = prefix_location, + .star_location = star_location, + .name_location = name_location, + .colon_location = colon_location, + .param_type = param_type, + .comment_location = comment_location, + }; + + return instance; +} +#line 140 "prism/templates/src/ast.c.erb" rbs_ast_ruby_annotations_type_application_annotation_t *rbs_ast_ruby_annotations_type_application_annotation_new(rbs_allocator_t *allocator, rbs_location_range location, rbs_location_range prefix_location, rbs_node_list_t *type_args, rbs_location_range close_bracket_location, rbs_location_range_list_t *comma_locations) { rbs_ast_ruby_annotations_type_application_annotation_t *instance = rbs_allocator_alloc(allocator, rbs_ast_ruby_annotations_type_application_annotation_t); diff --git a/src/parser.c b/src/parser.c index 1e9c1fa61..3d0bf3755 100644 --- a/src/parser.c +++ b/src/parser.c @@ -3914,6 +3914,86 @@ static bool parse_inline_leading_annotation(rbs_parser_t *parser, rbs_ast_ruby_a PARAM_NAME_CASES { return parse_inline_param_type_annotation(parser, annotation, rbs_range); } + case pSTAR: { + rbs_parser_advance(parser); + rbs_location_range star_loc = rbs_location_range_current_token(parser); + + rbs_location_range name_loc = RBS_LOCATION_NULL_RANGE; + if (parser->next_token.type == tLIDENT) { + rbs_parser_advance(parser); + name_loc = rbs_location_range_current_token(parser); + } + + ADVANCE_ASSERT(parser, pCOLON); + rbs_location_range colon_loc = rbs_location_range_current_token(parser); + + rbs_node_t *param_type = NULL; + if (!rbs_parse_type(parser, ¶m_type, false, true, true)) { + return false; + } + + rbs_location_range comment_loc = RBS_LOCATION_NULL_RANGE; + if (!parse_inline_comment(parser, &comment_loc)) { + return false; + } + + rbs_range_t full_range = { + .start = rbs_range.start, + .end = parser->current_token.range.end + }; + + *annotation = (rbs_ast_ruby_annotations_t *) rbs_ast_ruby_annotations_splat_param_type_annotation_new( + ALLOCATOR(), + RBS_RANGE_LEX2AST(full_range), + RBS_RANGE_LEX2AST(rbs_range), + star_loc, + name_loc, + colon_loc, + param_type, + comment_loc + ); + return true; + } + case pSTAR2: { + rbs_parser_advance(parser); + rbs_location_range star2_loc = rbs_location_range_current_token(parser); + + rbs_location_range name_loc = RBS_LOCATION_NULL_RANGE; + if (parser->next_token.type == tLIDENT) { + rbs_parser_advance(parser); + name_loc = rbs_location_range_current_token(parser); + } + + ADVANCE_ASSERT(parser, pCOLON); + rbs_location_range colon_loc = rbs_location_range_current_token(parser); + + rbs_node_t *param_type = NULL; + if (!rbs_parse_type(parser, ¶m_type, false, true, true)) { + return false; + } + + rbs_location_range comment_loc = RBS_LOCATION_NULL_RANGE; + if (!parse_inline_comment(parser, &comment_loc)) { + return false; + } + + rbs_range_t full_range = { + .start = rbs_range.start, + .end = parser->current_token.range.end + }; + + *annotation = (rbs_ast_ruby_annotations_t *) rbs_ast_ruby_annotations_double_splat_param_type_annotation_new( + ALLOCATOR(), + RBS_RANGE_LEX2AST(full_range), + RBS_RANGE_LEX2AST(rbs_range), + star2_loc, + name_loc, + colon_loc, + param_type, + comment_loc + ); + return true; + } default: { rbs_parser_set_error(parser, parser->next_token, true, "unexpected token for @rbs annotation"); return false; diff --git a/test/rbs/inline_annotation_parsing_test.rb b/test/rbs/inline_annotation_parsing_test.rb index 1cce40e54..9416a308f 100644 --- a/test/rbs/inline_annotation_parsing_test.rb +++ b/test/rbs/inline_annotation_parsing_test.rb @@ -379,6 +379,72 @@ def test_parse__param_type end end + def test_parse__splat_param_type + Parser.parse_inline_leading_annotation("@rbs *a: String", 0...).tap do |annot| + assert_instance_of AST::Ruby::Annotations::SplatParamTypeAnnotation, annot + assert_equal "@rbs *a: String", annot.location.source + assert_equal "@rbs", annot.prefix_location.source + assert_equal "*", annot.star_location.source + assert_equal "a", annot.name_location.source + assert_equal ":", annot.colon_location.source + assert_equal "String", annot.param_type.location.source + assert_nil annot.comment_location + end + + Parser.parse_inline_leading_annotation("@rbs *a: String -- The rest args", 0...).tap do |annot| + assert_instance_of AST::Ruby::Annotations::SplatParamTypeAnnotation, annot + assert_equal "@rbs *a: String -- The rest args", annot.location.source + assert_equal "*", annot.star_location.source + assert_equal "a", annot.name_location.source + assert_equal ":", annot.colon_location.source + assert_equal "String", annot.param_type.location.source + assert_equal "-- The rest args", annot.comment_location.source + end + + Parser.parse_inline_leading_annotation("@rbs *: String", 0...).tap do |annot| + assert_instance_of AST::Ruby::Annotations::SplatParamTypeAnnotation, annot + assert_equal "@rbs *: String", annot.location.source + assert_equal "*", annot.star_location.source + assert_nil annot.name_location + assert_equal ":", annot.colon_location.source + assert_equal "String", annot.param_type.location.source + assert_nil annot.comment_location + end + end + + def test_parse__double_splat_param_type + Parser.parse_inline_leading_annotation("@rbs **b: bool", 0...).tap do |annot| + assert_instance_of AST::Ruby::Annotations::DoubleSplatParamTypeAnnotation, annot + assert_equal "@rbs **b: bool", annot.location.source + assert_equal "@rbs", annot.prefix_location.source + assert_equal "**", annot.star2_location.source + assert_equal "b", annot.name_location.source + assert_equal ":", annot.colon_location.source + assert_equal "bool", annot.param_type.location.source + assert_nil annot.comment_location + end + + Parser.parse_inline_leading_annotation("@rbs **opts: bool -- The keyword args", 0...).tap do |annot| + assert_instance_of AST::Ruby::Annotations::DoubleSplatParamTypeAnnotation, annot + assert_equal "@rbs **opts: bool -- The keyword args", annot.location.source + assert_equal "**", annot.star2_location.source + assert_equal "opts", annot.name_location.source + assert_equal ":", annot.colon_location.source + assert_equal "bool", annot.param_type.location.source + assert_equal "-- The keyword args", annot.comment_location.source + end + + Parser.parse_inline_leading_annotation("@rbs **: bool", 0...).tap do |annot| + assert_instance_of AST::Ruby::Annotations::DoubleSplatParamTypeAnnotation, annot + assert_equal "@rbs **: bool", annot.location.source + assert_equal "**", annot.star2_location.source + assert_nil annot.name_location + assert_equal ":", annot.colon_location.source + assert_equal "bool", annot.param_type.location.source + assert_nil annot.comment_location + end + end + def test_parse__param_type__skip Parser.parse_inline_leading_annotation("@rbs skip: untyped", 0...).tap do |annot| assert_instance_of AST::Ruby::Annotations::ParamTypeAnnotation, annot diff --git a/test/rbs/inline_parser_test.rb b/test/rbs/inline_parser_test.rb index d9b4f72ef..73697bab5 100644 --- a/test/rbs/inline_parser_test.rb +++ b/test/rbs/inline_parser_test.rb @@ -298,7 +298,6 @@ def add(x, y = 3, a:, b: nil) end end - def test_error__def_method_docs result = parse(<<~RUBY) class Foo @@ -324,6 +323,149 @@ def add(y) end end + def test_parse__def_method_docs__splat + result = parse(<<~RUBY) + class Foo + # @rbs *args: String + # @rbs **kwargs: Integer + def foo(*args, **kwargs) #: void + end + + # @rbs *: String + # @rbs **: Integer + def bar(*, **) #: void + end + + # @rbs *args: String + # @rbs **kwargs: Integer + def baz(*, **) #: void + end + + # @rbs *: String + # @rbs **: Integer + def baz_(*args, **kwargs) #: void + 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 ["(*String args, **Integer kwargs) -> void"], member.overloads.map { _1.method_type.to_s } + end + + decl.members[1].tap do |member| + assert_instance_of RBS::AST::Ruby::Members::DefMember, member + assert_equal ["(*String, **Integer) -> void"], member.overloads.map { _1.method_type.to_s } + end + + decl.members[2].tap do |member| + assert_instance_of RBS::AST::Ruby::Members::DefMember, member + assert_equal ["(*String args, **Integer kwargs) -> void"], member.overloads.map { _1.method_type.to_s } + end + + decl.members[3].tap do |member| + assert_instance_of RBS::AST::Ruby::Members::DefMember, member + assert_equal ["(*String, **Integer) -> void"], member.overloads.map { _1.method_type.to_s } + end + end + end + + def test_parse__def_method_unnanotated__splat + result = parse(<<~RUBY) + class Foo + def foo(*args, **kwargs) #: void + end + + def bar(*, **) #: void + 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 ["(*untyped args, **untyped kwargs) -> void"], member.overloads.map { _1.method_type.to_s } + end + + decl.members[1].tap do |member| + assert_instance_of RBS::AST::Ruby::Members::DefMember, member + assert_equal ["(*untyped, **untyped) -> void"], member.overloads.map { _1.method_type.to_s } + end + end + end + + def test_error__def_method_docs__splat + result = parse(<<~RUBY) + class Foo + # @rbs *foo_args: String + # @rbs *foo_args: Integer -- Error: duplicated annotation + def foo(*foo_args) #: void + end + + # @rbs **bar_kwargs: String + # @rbs **bar_kwargs: Integer -- Error: duplicated annotation + def bar(**bar_kwargs) #: void + end + + # @rbs baz_args: String -- Error: baz_args is a splat parameter + # @rbs *baz_arg: Symbol -- Error: baz_arg is not a splat parameter + # @rbs baz_kwargs: Integer -- Error: baz_kwargs is a double splat parameter + # @rbs **baz_kw: Symbol -- Error: baz_kw is not a double splat parameter + def baz(baz_arg, *baz_args, baz_kw:, **baz_kwargs) #: void + end + end + RUBY + + assert_equal 6, result.diagnostics.size + + assert_any!(result.diagnostics) do |diagnostic| + assert_instance_of RBS::InlineParser::Diagnostic::UnusedInlineAnnotation, diagnostic + assert_equal "@rbs *foo_args: Integer -- Error: duplicated annotation", diagnostic.location.source + end + assert_any!(result.diagnostics) do |diagnostic| + assert_instance_of RBS::InlineParser::Diagnostic::UnusedInlineAnnotation, diagnostic + assert_equal "@rbs **bar_kwargs: Integer -- Error: duplicated annotation", diagnostic.location.source + end + assert_any!(result.diagnostics) do |diagnostic| + assert_instance_of RBS::InlineParser::Diagnostic::UnusedInlineAnnotation, diagnostic + assert_equal "@rbs baz_args: String -- Error: baz_args is a splat parameter", diagnostic.location.source + end + assert_any!(result.diagnostics) do |diagnostic| + assert_instance_of RBS::InlineParser::Diagnostic::UnusedInlineAnnotation, diagnostic + assert_equal "@rbs baz_kwargs: Integer -- Error: baz_kwargs is a double splat parameter", diagnostic.location.source + end + assert_any!(result.diagnostics) do |diagnostic| + assert_instance_of RBS::InlineParser::Diagnostic::UnusedInlineAnnotation, diagnostic + assert_equal "@rbs *baz_arg: Symbol -- Error: baz_arg is not a splat parameter", diagnostic.location.source + end + assert_any!(result.diagnostics) do |diagnostic| + assert_instance_of RBS::InlineParser::Diagnostic::UnusedInlineAnnotation, diagnostic + assert_equal "@rbs **baz_kw: Symbol -- Error: baz_kw is not a double splat parameter", diagnostic.location.source + end + + result.declarations[0].tap do |decl| + decl.members[0].tap do |member| + assert_instance_of RBS::AST::Ruby::Members::DefMember, member + assert_equal ["(*String foo_args) -> void"], member.overloads.map { _1.method_type.to_s } + end + + decl.members[1].tap do |member| + assert_instance_of RBS::AST::Ruby::Members::DefMember, member + assert_equal ["(**String bar_kwargs) -> void"], member.overloads.map { _1.method_type.to_s } + end + + decl.members[2].tap do |member| + assert_instance_of RBS::AST::Ruby::Members::DefMember, member + assert_equal ["(untyped baz_arg, *untyped baz_args, baz_kw: untyped, **untyped baz_kwargs) -> void"], member.overloads.map { _1.method_type.to_s } + end + end + end + def test_parse__skip_class_module result = parse(<<~RUBY) # @rbs skip -- not a constant