From 001dca402fe280938f271a87fa1cc5002d40f49c Mon Sep 17 00:00:00 2001 From: ksss Date: Wed, 28 Oct 2026 13:43:16 +0900 Subject: [PATCH 1/2] Parse `singleton(C)` in module self type constraints Allow `module Foo : singleton(Bar)` (optionally with type arguments, `singleton(Bar)[T]`) in addition to class instance and interface self types. The new syntax is represented by a new AST node, `RBS::AST::Declarations::Module::SingletonSelf`, so that existing consumers of `Module::Self` keep working unchanged. - parser: accept `singleton` `(` class_name `)` type_args? in `parse_module_self_types` - AST: add `Module::SingletonSelf` (C node generated from config.yml) and `#update` to `Module::Self`/`SingletonSelf` - environment: resolve type names and align type parameters of singleton self types through `#update` - docs: describe the syntax in docs/syntax.md Part of #1334 (proposal 1). Co-Authored-By: Claude Fable 5.1 --- config.yml | 10 ++ docs/syntax.md | 21 +++- ext/rbs_extension/ast_translation.c | 23 +++++ ext/rbs_extension/class_constants.c | 2 + ext/rbs_extension/class_constants.h | 1 + include/rbs/ast.h | 146 +++++++++++++++------------ lib/rbs/ast/declarations.rb | 47 +++++++++ lib/rbs/environment.rb | 5 +- lib/rbs/environment/module_entry.rb | 6 +- lib/rbs/wasm/serialization_schema.rb | 3 +- sig/declarations.rbs | 37 ++++++- sig/environment/module_entry.rbs | 2 +- sig/locator.rbs | 1 + src/ast.c | 19 ++++ src/parser.c | 28 ++++- src/serialize.c | 146 ++++++++++++++------------- test/rbs/environment_test.rb | 39 +++++++ test/rbs/locator_test.rb | 21 ++++ test/rbs/signature_parsing_test.rb | 45 +++++++++ test/rbs/wasm/serialization_test.rb | 1 + test/rbs/writer_test.rb | 7 ++ 21 files changed, 455 insertions(+), 155 deletions(-) diff --git a/config.yml b/config.yml index 1cb7aefd2a..d835e84d17 100644 --- a/config.yml +++ b/config.yml @@ -151,6 +151,16 @@ nodes: locations: - required: name - optional: args + - name: RBS::AST::Declarations::Module::SingletonSelf + rust_name: ModuleSingletonSelfNode + fields: + - name: name + c_type: rbs_type_name + - name: args + c_type: rbs_node_list + locations: + - required: name + - optional: args - name: RBS::AST::Declarations::ModuleAlias rust_name: ModuleAliasNode fields: diff --git a/docs/syntax.md b/docs/syntax.md index e3848e7015..eb7d980f70 100644 --- a/docs/syntax.md +++ b/docs/syntax.md @@ -637,8 +637,9 @@ _class-alias-decl_ ::= `class` _class-name_ `=` _class-name_ _module-alias-decl_ ::= `module` _module-name_ `=` _module-name_ -_module-self-types_ ::= _class-name_ _type-arguments_ `,` _module-self-types_ (Class instance) - | _interface-name_ _type-arguments_ `,` _module-self-types_ (Interface) +_module-self-types_ ::= _class-name_ _type-arguments_ `,` _module-self-types_ (Class instance) + | _interface-name_ _type-arguments_ `,` _module-self-types_ (Interface) + | `singleton(` _class-name_ `)` _type-arguments_ `,` _module-self-types_ (Class singleton) _interface-decl_ ::= `interface` _interface-name_ _module-type-parameters_ _interface-members_ `end` @@ -681,6 +682,22 @@ end The `Enumerable` module above requires `each` method for enumerating objects. +The self type can be a class singleton type, which declares that the module is `extend`ed to the class (or its subclasses). + +```rbs +class Model + def self.find: (Integer) -> instance +end + +module ClassMethods : singleton(Model) + def find_or_raise: (Integer) -> Model # Can call `find` of `singleton(Model)` +end + +class User < Model + extend ClassMethods +end +``` + * Self type arguments and generic class bounds are not *classish-context* nor *self-context* ### Class/module alias declaration diff --git a/ext/rbs_extension/ast_translation.c b/ext/rbs_extension/ast_translation.c index 8717d61bc7..94284bc8f0 100644 --- a/ext/rbs_extension/ast_translation.c +++ b/ext/rbs_extension/ast_translation.c @@ -487,6 +487,29 @@ VALUE rbs_struct_to_ruby_value(rbs_translation_context_t ctx, rbs_node_t *instan rb_hash_aset(h, ID2SYM(rb_intern("args")), arg_args); return CLASS_NEW_INSTANCE(RBS_AST_Declarations_Module_Self, 1, &h); } + case RBS_AST_DECLARATIONS_MODULE_SINGLETON_SELF: { + rbs_ast_declarations_module_singleton_self_t *node = (rbs_ast_declarations_module_singleton_self_t *) instance; + + // Compute child VALUEs into locals variables first, before any recursion into `rbs_struct_to_ruby_value()`. + VALUE arg_location = rbs_location_range_to_ruby_location(ctx, node->base.location); + rbs_loc *loc = rbs_check_location(arg_location); + { + rbs_loc_legacy_alloc_children(loc, 2); + rbs_loc_legacy_add_required_child(loc, rb_intern("name"), (rbs_loc_range) { .start = node->name_range.start_char, .end = node->name_range.end_char }); + rbs_loc_legacy_add_optional_child(loc, rb_intern("args"), (rbs_loc_range) { .start = node->args_range.start_char, .end = node->args_range.end_char }); + } + VALUE arg_name = rbs_struct_to_ruby_value(ctx, (rbs_node_t *) node->name); // rbs_type_name + VALUE arg_args = rbs_node_list_to_ruby_array(ctx, node->args); + + // Claim the shared kwargs hash, clear it, fill it, and hand it to `.new`. + // Must not recurse between `rb_hash_clear()` and `CLASS_NEW_INSTANCE()`. + VALUE h = ctx.reusable_kwargs_hash; + rb_hash_clear(h); + rb_hash_aset(h, ID2SYM(rb_intern("location")), arg_location); + rb_hash_aset(h, ID2SYM(rb_intern("name")), arg_name); + rb_hash_aset(h, ID2SYM(rb_intern("args")), arg_args); + return CLASS_NEW_INSTANCE(RBS_AST_Declarations_Module_SingletonSelf, 1, &h); + } case RBS_AST_DECLARATIONS_MODULE_ALIAS: { rbs_ast_declarations_module_alias_t *node = (rbs_ast_declarations_module_alias_t *) instance; diff --git a/ext/rbs_extension/class_constants.c b/ext/rbs_extension/class_constants.c index ea5e859e2f..729196c61c 100644 --- a/ext/rbs_extension/class_constants.c +++ b/ext/rbs_extension/class_constants.c @@ -28,6 +28,7 @@ VALUE RBS_AST_Declarations_Global; VALUE RBS_AST_Declarations_Interface; VALUE RBS_AST_Declarations_Module; VALUE RBS_AST_Declarations_Module_Self; +VALUE RBS_AST_Declarations_Module_SingletonSelf; VALUE RBS_AST_Declarations_ModuleAlias; VALUE RBS_AST_Declarations_TypeAlias; VALUE RBS_AST_Directives_Use; @@ -123,6 +124,7 @@ void rbs__init_constants(void) { IMPORT_CONSTANT(RBS_AST_Declarations_Interface, RBS_AST_Declarations, "Interface"); IMPORT_CONSTANT(RBS_AST_Declarations_Module, RBS_AST_Declarations, "Module"); IMPORT_CONSTANT(RBS_AST_Declarations_Module_Self, RBS_AST_Declarations_Module, "Self"); + IMPORT_CONSTANT(RBS_AST_Declarations_Module_SingletonSelf, RBS_AST_Declarations_Module, "SingletonSelf"); IMPORT_CONSTANT(RBS_AST_Declarations_ModuleAlias, RBS_AST_Declarations, "ModuleAlias"); IMPORT_CONSTANT(RBS_AST_Declarations_TypeAlias, RBS_AST_Declarations, "TypeAlias"); IMPORT_CONSTANT(RBS_AST_Directives_Use, RBS_AST_Directives, "Use"); diff --git a/ext/rbs_extension/class_constants.h b/ext/rbs_extension/class_constants.h index 744b3680a1..fb56a53eab 100644 --- a/ext/rbs_extension/class_constants.h +++ b/ext/rbs_extension/class_constants.h @@ -36,6 +36,7 @@ extern VALUE RBS_AST_Declarations_Global; extern VALUE RBS_AST_Declarations_Interface; extern VALUE RBS_AST_Declarations_Module; extern VALUE RBS_AST_Declarations_Module_Self; +extern VALUE RBS_AST_Declarations_Module_SingletonSelf; extern VALUE RBS_AST_Declarations_ModuleAlias; extern VALUE RBS_AST_Declarations_TypeAlias; extern VALUE RBS_AST_Directives_Use; diff --git a/include/rbs/ast.h b/include/rbs/ast.h index c33bc56f1c..527ad349dc 100644 --- a/include/rbs/ast.h +++ b/include/rbs/ast.h @@ -71,73 +71,74 @@ enum rbs_node_type { RBS_AST_DECLARATIONS_INTERFACE = 9, RBS_AST_DECLARATIONS_MODULE = 10, RBS_AST_DECLARATIONS_MODULE_SELF = 11, - RBS_AST_DECLARATIONS_MODULE_ALIAS = 12, - RBS_AST_DECLARATIONS_TYPE_ALIAS = 13, - RBS_AST_DIRECTIVES_USE = 14, - RBS_AST_DIRECTIVES_USE_SINGLE_CLAUSE = 15, - RBS_AST_DIRECTIVES_USE_WILDCARD_CLAUSE = 16, - RBS_AST_INTEGER = 17, - RBS_AST_MEMBERS_ALIAS = 18, - RBS_AST_MEMBERS_ATTR_ACCESSOR = 19, - RBS_AST_MEMBERS_ATTR_READER = 20, - RBS_AST_MEMBERS_ATTR_WRITER = 21, - RBS_AST_MEMBERS_CLASS_INSTANCE_VARIABLE = 22, - RBS_AST_MEMBERS_CLASS_VARIABLE = 23, - RBS_AST_MEMBERS_EXTEND = 24, - RBS_AST_MEMBERS_INCLUDE = 25, - RBS_AST_MEMBERS_INSTANCE_VARIABLE = 26, - RBS_AST_MEMBERS_METHOD_DEFINITION = 27, - RBS_AST_MEMBERS_METHOD_DEFINITION_OVERLOAD = 28, - RBS_AST_MEMBERS_PREPEND = 29, - RBS_AST_MEMBERS_PRIVATE = 30, - RBS_AST_MEMBERS_PUBLIC = 31, - RBS_AST_RUBY_ANNOTATIONS_BLOCK_PARAM_TYPE_ANNOTATION = 32, - RBS_AST_RUBY_ANNOTATIONS_CLASS_ALIAS_ANNOTATION = 33, - RBS_AST_RUBY_ANNOTATIONS_COLON_METHOD_TYPE_ANNOTATION = 34, - RBS_AST_RUBY_ANNOTATIONS_DOUBLE_SPLAT_PARAM_TYPE_ANNOTATION = 35, - RBS_AST_RUBY_ANNOTATIONS_INSTANCE_VARIABLE_ANNOTATION = 36, - RBS_AST_RUBY_ANNOTATIONS_METHOD_TYPES_ANNOTATION = 37, - RBS_AST_RUBY_ANNOTATIONS_MODULE_ALIAS_ANNOTATION = 38, - RBS_AST_RUBY_ANNOTATIONS_MODULE_SELF_ANNOTATION = 39, - RBS_AST_RUBY_ANNOTATIONS_NODE_TYPE_ASSERTION = 40, - RBS_AST_RUBY_ANNOTATIONS_PARAM_TYPE_ANNOTATION = 41, - RBS_AST_RUBY_ANNOTATIONS_RETURN_TYPE_ANNOTATION = 42, - RBS_AST_RUBY_ANNOTATIONS_SKIP_ANNOTATION = 43, - RBS_AST_RUBY_ANNOTATIONS_SPLAT_PARAM_TYPE_ANNOTATION = 44, - RBS_AST_RUBY_ANNOTATIONS_TYPE_APPLICATION_ANNOTATION = 45, - RBS_AST_STRING = 46, - RBS_AST_TYPE_PARAM = 47, - RBS_METHOD_TYPE = 48, - RBS_NAMESPACE = 49, - RBS_SIGNATURE = 50, - RBS_TYPE_NAME = 51, - RBS_TYPES_ALIAS = 52, - RBS_TYPES_BASES_ANY = 53, - RBS_TYPES_BASES_BOOL = 54, - RBS_TYPES_BASES_BOTTOM = 55, - RBS_TYPES_BASES_CLASS = 56, - RBS_TYPES_BASES_INSTANCE = 57, - RBS_TYPES_BASES_NIL = 58, - RBS_TYPES_BASES_SELF = 59, - RBS_TYPES_BASES_TOP = 60, - RBS_TYPES_BASES_VOID = 61, - RBS_TYPES_BLOCK = 62, - RBS_TYPES_CLASS_INSTANCE = 63, - RBS_TYPES_CLASS_SINGLETON = 64, - RBS_TYPES_FUNCTION = 65, - RBS_TYPES_FUNCTION_FORWARDING_PARAM = 66, - RBS_TYPES_FUNCTION_PARAM = 67, - RBS_TYPES_INTERFACE = 68, - RBS_TYPES_INTERSECTION = 69, - RBS_TYPES_LITERAL = 70, - RBS_TYPES_OPTIONAL = 71, - RBS_TYPES_PROC = 72, - RBS_TYPES_RECORD = 73, - RBS_TYPES_RECORD_FIELD_TYPE = 74, - RBS_TYPES_TUPLE = 75, - RBS_TYPES_UNION = 76, - RBS_TYPES_UNTYPED_FUNCTION = 77, - RBS_TYPES_VARIABLE = 78, + RBS_AST_DECLARATIONS_MODULE_SINGLETON_SELF = 12, + RBS_AST_DECLARATIONS_MODULE_ALIAS = 13, + RBS_AST_DECLARATIONS_TYPE_ALIAS = 14, + RBS_AST_DIRECTIVES_USE = 15, + RBS_AST_DIRECTIVES_USE_SINGLE_CLAUSE = 16, + RBS_AST_DIRECTIVES_USE_WILDCARD_CLAUSE = 17, + RBS_AST_INTEGER = 18, + RBS_AST_MEMBERS_ALIAS = 19, + RBS_AST_MEMBERS_ATTR_ACCESSOR = 20, + RBS_AST_MEMBERS_ATTR_READER = 21, + RBS_AST_MEMBERS_ATTR_WRITER = 22, + RBS_AST_MEMBERS_CLASS_INSTANCE_VARIABLE = 23, + RBS_AST_MEMBERS_CLASS_VARIABLE = 24, + RBS_AST_MEMBERS_EXTEND = 25, + RBS_AST_MEMBERS_INCLUDE = 26, + RBS_AST_MEMBERS_INSTANCE_VARIABLE = 27, + RBS_AST_MEMBERS_METHOD_DEFINITION = 28, + RBS_AST_MEMBERS_METHOD_DEFINITION_OVERLOAD = 29, + RBS_AST_MEMBERS_PREPEND = 30, + RBS_AST_MEMBERS_PRIVATE = 31, + RBS_AST_MEMBERS_PUBLIC = 32, + RBS_AST_RUBY_ANNOTATIONS_BLOCK_PARAM_TYPE_ANNOTATION = 33, + RBS_AST_RUBY_ANNOTATIONS_CLASS_ALIAS_ANNOTATION = 34, + RBS_AST_RUBY_ANNOTATIONS_COLON_METHOD_TYPE_ANNOTATION = 35, + RBS_AST_RUBY_ANNOTATIONS_DOUBLE_SPLAT_PARAM_TYPE_ANNOTATION = 36, + RBS_AST_RUBY_ANNOTATIONS_INSTANCE_VARIABLE_ANNOTATION = 37, + RBS_AST_RUBY_ANNOTATIONS_METHOD_TYPES_ANNOTATION = 38, + RBS_AST_RUBY_ANNOTATIONS_MODULE_ALIAS_ANNOTATION = 39, + RBS_AST_RUBY_ANNOTATIONS_MODULE_SELF_ANNOTATION = 40, + RBS_AST_RUBY_ANNOTATIONS_NODE_TYPE_ASSERTION = 41, + RBS_AST_RUBY_ANNOTATIONS_PARAM_TYPE_ANNOTATION = 42, + RBS_AST_RUBY_ANNOTATIONS_RETURN_TYPE_ANNOTATION = 43, + RBS_AST_RUBY_ANNOTATIONS_SKIP_ANNOTATION = 44, + RBS_AST_RUBY_ANNOTATIONS_SPLAT_PARAM_TYPE_ANNOTATION = 45, + RBS_AST_RUBY_ANNOTATIONS_TYPE_APPLICATION_ANNOTATION = 46, + RBS_AST_STRING = 47, + RBS_AST_TYPE_PARAM = 48, + RBS_METHOD_TYPE = 49, + RBS_NAMESPACE = 50, + RBS_SIGNATURE = 51, + RBS_TYPE_NAME = 52, + RBS_TYPES_ALIAS = 53, + RBS_TYPES_BASES_ANY = 54, + RBS_TYPES_BASES_BOOL = 55, + RBS_TYPES_BASES_BOTTOM = 56, + RBS_TYPES_BASES_CLASS = 57, + RBS_TYPES_BASES_INSTANCE = 58, + RBS_TYPES_BASES_NIL = 59, + RBS_TYPES_BASES_SELF = 60, + RBS_TYPES_BASES_TOP = 61, + RBS_TYPES_BASES_VOID = 62, + RBS_TYPES_BLOCK = 63, + RBS_TYPES_CLASS_INSTANCE = 64, + RBS_TYPES_CLASS_SINGLETON = 65, + RBS_TYPES_FUNCTION = 66, + RBS_TYPES_FUNCTION_FORWARDING_PARAM = 67, + RBS_TYPES_FUNCTION_PARAM = 68, + RBS_TYPES_INTERFACE = 69, + RBS_TYPES_INTERSECTION = 70, + RBS_TYPES_LITERAL = 71, + RBS_TYPES_OPTIONAL = 72, + RBS_TYPES_PROC = 73, + RBS_TYPES_RECORD = 74, + RBS_TYPES_RECORD_FIELD_TYPE = 75, + RBS_TYPES_TUPLE = 76, + RBS_TYPES_UNION = 77, + RBS_TYPES_UNTYPED_FUNCTION = 78, + RBS_TYPES_VARIABLE = 79, RBS_AST_SYMBOL, }; @@ -317,6 +318,16 @@ typedef struct rbs_ast_declarations_module_self { rbs_location_range args_range; /* Optional */ } rbs_ast_declarations_module_self_t; +typedef struct rbs_ast_declarations_module_singleton_self { + rbs_node_t base; + + struct rbs_type_name *RBS_NONNULL name; + struct rbs_node_list *RBS_NONNULL args; + + rbs_location_range name_range; /* Required */ + rbs_location_range args_range; /* Optional */ +} rbs_ast_declarations_module_singleton_self_t; + typedef struct rbs_ast_declarations_module_alias { rbs_node_t base; @@ -984,6 +995,7 @@ rbs_ast_declarations_global_t *RBS_NONNULL rbs_ast_declarations_global_new(rbs_a rbs_ast_declarations_interface_t *RBS_NONNULL rbs_ast_declarations_interface_new(rbs_allocator_t *RBS_NONNULL allocator, rbs_location_range location, rbs_type_name_t *RBS_NONNULL name, rbs_node_list_t *RBS_NONNULL type_params, rbs_node_list_t *RBS_NONNULL members, rbs_node_list_t *RBS_NONNULL annotations, rbs_ast_comment_t *RBS_NULLABLE comment, rbs_location_range keyword_range, rbs_location_range name_range, rbs_location_range end_range); rbs_ast_declarations_module_t *RBS_NONNULL rbs_ast_declarations_module_new(rbs_allocator_t *RBS_NONNULL allocator, rbs_location_range location, rbs_type_name_t *RBS_NONNULL name, rbs_node_list_t *RBS_NONNULL type_params, rbs_node_list_t *RBS_NONNULL self_types, rbs_node_list_t *RBS_NONNULL members, rbs_node_list_t *RBS_NONNULL annotations, rbs_ast_comment_t *RBS_NULLABLE comment, rbs_location_range keyword_range, rbs_location_range name_range, rbs_location_range end_range); rbs_ast_declarations_module_self_t *RBS_NONNULL rbs_ast_declarations_module_self_new(rbs_allocator_t *RBS_NONNULL allocator, rbs_location_range location, rbs_type_name_t *RBS_NONNULL name, rbs_node_list_t *RBS_NONNULL args, rbs_location_range name_range); +rbs_ast_declarations_module_singleton_self_t *RBS_NONNULL rbs_ast_declarations_module_singleton_self_new(rbs_allocator_t *RBS_NONNULL allocator, rbs_location_range location, rbs_type_name_t *RBS_NONNULL name, rbs_node_list_t *RBS_NONNULL args, rbs_location_range name_range); rbs_ast_declarations_module_alias_t *RBS_NONNULL rbs_ast_declarations_module_alias_new(rbs_allocator_t *RBS_NONNULL allocator, rbs_location_range location, rbs_type_name_t *RBS_NONNULL new_name, rbs_type_name_t *RBS_NONNULL old_name, rbs_ast_comment_t *RBS_NULLABLE comment, rbs_node_list_t *RBS_NONNULL annotations, rbs_location_range keyword_range, rbs_location_range new_name_range, rbs_location_range eq_range, rbs_location_range old_name_range); rbs_ast_declarations_type_alias_t *RBS_NONNULL rbs_ast_declarations_type_alias_new(rbs_allocator_t *RBS_NONNULL allocator, rbs_location_range location, rbs_type_name_t *RBS_NONNULL name, rbs_node_list_t *RBS_NONNULL type_params, rbs_node_t *RBS_NONNULL type, rbs_node_list_t *RBS_NONNULL annotations, rbs_ast_comment_t *RBS_NULLABLE comment, rbs_location_range keyword_range, rbs_location_range name_range, rbs_location_range eq_range); rbs_ast_directives_use_t *RBS_NONNULL rbs_ast_directives_use_new(rbs_allocator_t *RBS_NONNULL allocator, rbs_location_range location, rbs_node_list_t *RBS_NONNULL clauses, rbs_location_range keyword_range); diff --git a/lib/rbs/ast/declarations.rb b/lib/rbs/ast/declarations.rb index a989805d95..9c6610a7d2 100644 --- a/lib/rbs/ast/declarations.rb +++ b/lib/rbs/ast/declarations.rb @@ -156,6 +156,10 @@ def initialize(name:, args:, location:) @location = location end + def update(name: self.name, args: self.args, location: self.location) + self.class.new(name: name, args: args, location: location) + end + def ==(other) other.is_a?(Self) && other.name == name && other.args == args end @@ -183,6 +187,49 @@ def to_s end end + class SingletonSelf + attr_reader :name + attr_reader :args + attr_reader :location + + def initialize(name:, args:, location:) + @name = name + @args = args + @location = location + end + + def update(name: self.name, args: self.args, location: self.location) + self.class.new(name: name, args: args, location: location) + end + + def ==(other) + other.is_a?(SingletonSelf) && other.name == name && other.args == args + end + + alias eql? == + + def hash + self.class.hash ^ name.hash ^ args.hash + end + + def to_json(state = nil) + { + singleton: true, + name: name, + args: args, + location: location + }.to_json(state) + end + + def to_s + if args.empty? + "singleton(#{name})" + else + "singleton(#{name})[#{args.join(", ")}]" + end + end + end + include NestedDeclarationHelper include MixinHelper diff --git a/lib/rbs/environment.rb b/lib/rbs/environment.rb index 0fb4347142..93d06878dc 100644 --- a/lib/rbs/environment.rb +++ b/lib/rbs/environment.rb @@ -632,10 +632,9 @@ def resolve_declaration(resolver, map, decl, context:, prefix:) name: decl.name.with_prefix(prefix), type_params: resolve_type_params(resolver, map, decl.type_params, context: inner_context), self_types: decl.self_types.map do |module_self| - AST::Declarations::Module::Self.new( + module_self.update( name: absolute_type_name(resolver, map, module_self.name, context: inner_context), - args: module_self.args.map {|type| absolute_type(resolver, map, type, context: inner_context) }, - location: module_self.location + args: module_self.args.map {|type| absolute_type(resolver, map, type, context: inner_context) } ) end, members: decl.members.map do |member| diff --git a/lib/rbs/environment/module_entry.rb b/lib/rbs/environment/module_entry.rb index 95db8eb14f..eac8b372dc 100644 --- a/lib/rbs/environment/module_entry.rb +++ b/lib/rbs/environment/module_entry.rb @@ -50,11 +50,7 @@ def self_types self_types else self_types.map do |self_type| - AST::Declarations::Module::Self.new( - name: self_type.name, - args: self_type.args.map {|type| type.sub(subst) }, - location: self_type.location - ) + self_type.update(args: self_type.args.map {|type| type.sub(subst) }) end end end.uniq diff --git a/lib/rbs/wasm/serialization_schema.rb b/lib/rbs/wasm/serialization_schema.rb index bfd4c69e7a..b89523194f 100644 --- a/lib/rbs/wasm/serialization_schema.rb +++ b/lib/rbs/wasm/serialization_schema.rb @@ -24,7 +24,7 @@ module WASM # [:enum, [value_or_nil, ...]]. A `nil` name means the field is read but not # passed to the constructor (`expose_to_ruby: false` in config.yml). module SerializationSchema - SYMBOL_TAG = 79 + SYMBOL_TAG = 80 SCHEMA = [ nil, # tag 0 is reserved for NULL @@ -39,6 +39,7 @@ module SerializationSchema [:node, "RBS::AST::Declarations::Interface", true, [[:keyword, true], [:name, true], [:end, true], [:type_params, false]], [[:name, :node], [:type_params, :node_list], [:members, :node_list], [:annotations, :node_list], [:comment, :node]], true], [:node, "RBS::AST::Declarations::Module", true, [[:keyword, true], [:name, true], [:end, true], [:type_params, false], [:colon, false], [:self_types, false]], [[:name, :node], [:type_params, :node_list], [:self_types, :node_list], [:members, :node_list], [:annotations, :node_list], [:comment, :node]], true], [:node, "RBS::AST::Declarations::Module::Self", true, [[:name, true], [:args, false]], [[:name, :node], [:args, :node_list]], false], + [:node, "RBS::AST::Declarations::Module::SingletonSelf", true, [[:name, true], [:args, false]], [[:name, :node], [:args, :node_list]], false], [:node, "RBS::AST::Declarations::ModuleAlias", true, [[:keyword, true], [:new_name, true], [:eq, true], [:old_name, true]], [[:new_name, :node], [:old_name, :node], [:comment, :node], [:annotations, :node_list]], false], [:node, "RBS::AST::Declarations::TypeAlias", true, [[:keyword, true], [:name, true], [:eq, true], [:type_params, false]], [[:name, :node], [:type_params, :node_list], [:type, :node], [:annotations, :node_list], [:comment, :node]], true], [:node, "RBS::AST::Directives::Use", true, [[:keyword, true]], [[:clauses, :node_list]], false], diff --git a/sig/declarations.rbs b/sig/declarations.rbs index 1a39f099e5..be8a806bfd 100644 --- a/sig/declarations.rbs +++ b/sig/declarations.rbs @@ -97,12 +97,43 @@ module RBS def initialize: (name: TypeName, args: Array[Types::t], location: loc?) -> void + def update: (?name: TypeName, ?args: Array[Types::t], ?location: loc?) -> Self + + include _HashEqual + include _ToJson + + def to_s: () -> String + end + + # Self type constraint of singleton class + # + # ```rbs + # module Foo : singleton(Bar) + # ``` + # + class SingletonSelf + # singleton(Bar)[String] + # ^^^ name + # ^^^^^^^^ args + # + type loc = Location[:name, :args] + + attr_reader name: TypeName + attr_reader args: Array[Types::t] + attr_reader location: loc? + + def initialize: (name: TypeName, args: Array[Types::t], location: loc?) -> void + + def update: (?name: TypeName, ?args: Array[Types::t], ?location: loc?) -> SingletonSelf + include _HashEqual include _ToJson def to_s: () -> String end + type self_type = Self | SingletonSelf + type member = Members::t | t # module Foo end @@ -128,12 +159,12 @@ module RBS attr_reader members: Array[member] attr_reader location: loc? attr_reader annotations: Array[Annotation] - attr_reader self_types: Array[Self] + attr_reader self_types: Array[self_type] attr_reader comment: Comment? - def initialize: (name: TypeName, type_params: Array[TypeParam], members: Array[member], location: loc?, annotations: Array[Annotation], self_types: Array[Self], comment: Comment?) -> void + def initialize: (name: TypeName, type_params: Array[TypeParam], members: Array[member], location: loc?, annotations: Array[Annotation], self_types: Array[self_type], comment: Comment?) -> void - def update: (?name: TypeName, ?type_params: Array[TypeParam], ?members: Array[member], ?location: loc?, ?annotations: Array[Annotation], ?self_types: Array[Self], ?comment: Comment?) -> Declarations::Module + def update: (?name: TypeName, ?type_params: Array[TypeParam], ?members: Array[member], ?location: loc?, ?annotations: Array[Annotation], ?self_types: Array[self_type], ?comment: Comment?) -> Declarations::Module include _HashEqual include _ToJson diff --git a/sig/environment/module_entry.rbs b/sig/environment/module_entry.rbs index 3230a4b355..51e93b6ce6 100644 --- a/sig/environment/module_entry.rbs +++ b/sig/environment/module_entry.rbs @@ -55,7 +55,7 @@ module RBS # Note that the returned objects may be different from the ones in the # declarations, but `#location` points to the original declaration. # - def self_types: () -> Array[AST::Declarations::Module::Self] + def self_types: () -> Array[AST::Declarations::Module::self_type] # Returns a substitution that renames the type parameters of the declaration to the entry's type parameters (`#type_params`) # diff --git a/sig/locator.rbs b/sig/locator.rbs index a16543bc05..d607870e4f 100644 --- a/sig/locator.rbs +++ b/sig/locator.rbs @@ -10,6 +10,7 @@ module RBS | AST::TypeParam | AST::Declarations::Class::Super | AST::Declarations::Module::Self + | AST::Declarations::Module::SingletonSelf | AST::Directives::t | AST::Directives::Use::clause diff --git a/src/ast.c b/src/ast.c index a41b673940..150e6ec565 100644 --- a/src/ast.c +++ b/src/ast.c @@ -35,6 +35,8 @@ const char *RBS_NONNULL rbs_node_type_name(rbs_node_t *RBS_NONNULL node) { return "RBS::AST::Declarations::Module"; case RBS_AST_DECLARATIONS_MODULE_SELF: return "RBS::AST::Declarations::Module::Self"; + case RBS_AST_DECLARATIONS_MODULE_SINGLETON_SELF: + return "RBS::AST::Declarations::Module::SingletonSelf"; case RBS_AST_DECLARATIONS_MODULE_ALIAS: return "RBS::AST::Declarations::ModuleAlias"; case RBS_AST_DECLARATIONS_TYPE_ALIAS: @@ -501,6 +503,23 @@ rbs_ast_declarations_module_self_t *RBS_NONNULL rbs_ast_declarations_module_self return instance; } #line 140 "templates/src/ast.c.erb" +rbs_ast_declarations_module_singleton_self_t *RBS_NONNULL rbs_ast_declarations_module_singleton_self_new(rbs_allocator_t *RBS_NONNULL allocator, rbs_location_range location, rbs_type_name_t *RBS_NONNULL name, rbs_node_list_t *RBS_NONNULL args, rbs_location_range name_range) { + rbs_ast_declarations_module_singleton_self_t *instance = rbs_allocator_alloc(allocator, rbs_ast_declarations_module_singleton_self_t); + + *instance = (rbs_ast_declarations_module_singleton_self_t) { + .base = (rbs_node_t) { + .type = RBS_AST_DECLARATIONS_MODULE_SINGLETON_SELF, + .location = location, + }, + .name = name, + .args = args, + .name_range = name_range, + .args_range = RBS_LOCATION_NULL_RANGE, + }; + + return instance; +} +#line 140 "templates/src/ast.c.erb" rbs_ast_declarations_module_alias_t *RBS_NONNULL rbs_ast_declarations_module_alias_new(rbs_allocator_t *RBS_NONNULL allocator, rbs_location_range location, rbs_type_name_t *RBS_NONNULL new_name, rbs_type_name_t *RBS_NONNULL old_name, rbs_ast_comment_t *RBS_NULLABLE comment, rbs_node_list_t *RBS_NONNULL annotations, rbs_location_range keyword_range, rbs_location_range new_name_range, rbs_location_range eq_range, rbs_location_range old_name_range) { rbs_ast_declarations_module_alias_t *instance = rbs_allocator_alloc(allocator, rbs_ast_declarations_module_alias_t); diff --git a/src/parser.c b/src/parser.c index fc6f59c72d..2d34c6a72f 100644 --- a/src/parser.c +++ b/src/parser.c @@ -2649,6 +2649,8 @@ static bool parse_interface_decl(rbs_parser_t *parser, rbs_position_t comment_po module_self_type ::= | module_name `[` type_list <`]`> + | `singleton` `(` module_name <`)`> + | `singleton` `(` module_name `)` `[` type_list <`]`> */ RBS_NODISCARD static bool parse_module_self_types(rbs_parser_t *parser, rbs_node_list_t *array) { @@ -2658,10 +2660,20 @@ static bool parse_module_self_types(rbs_parser_t *parser, rbs_node_list_t *array rbs_range_t self_range; self_range.start = parser->current_token.range.start; + bool singleton = false; rbs_range_t name_range; rbs_type_name_t *module_name = NULL; - CHECK_PARSE(parse_type_name(parser, (TypeNameKind) (CLASS_NAME | INTERFACE_NAME), &name_range, &module_name)); - self_range.end = name_range.end; + + if (parser->current_token.type == kSINGLETON) { + singleton = true; + ADVANCE_ASSERT(parser, pLPAREN); + rbs_parser_advance(parser); + CHECK_PARSE(parse_type_name(parser, CLASS_NAME, &name_range, &module_name)); + ADVANCE_ASSERT(parser, pRPAREN); + } else { + CHECK_PARSE(parse_type_name(parser, (TypeNameKind) (CLASS_NAME | INTERFACE_NAME), &name_range, &module_name)); + } + self_range.end = parser->current_token.range.end; rbs_node_list_t *args = rbs_node_list_new(ALLOCATOR()); rbs_range_t args_range = NULL_RANGE; @@ -2675,9 +2687,15 @@ static bool parse_module_self_types(rbs_parser_t *parser, rbs_node_list_t *array rbs_location_range loc = RBS_RANGE_LEX2AST(self_range); - rbs_ast_declarations_module_self_t *self_type = rbs_ast_declarations_module_self_new(ALLOCATOR(), loc, module_name, args, RBS_RANGE_LEX2AST(name_range)); - self_type->args_range = RBS_RANGE_LEX2AST(args_range); - rbs_node_list_append(array, (rbs_node_t *) self_type); + if (singleton) { + rbs_ast_declarations_module_singleton_self_t *self_type = rbs_ast_declarations_module_singleton_self_new(ALLOCATOR(), loc, module_name, args, RBS_RANGE_LEX2AST(name_range)); + self_type->args_range = RBS_RANGE_LEX2AST(args_range); + rbs_node_list_append(array, (rbs_node_t *) self_type); + } else { + rbs_ast_declarations_module_self_t *self_type = rbs_ast_declarations_module_self_new(ALLOCATOR(), loc, module_name, args, RBS_RANGE_LEX2AST(name_range)); + self_type->args_range = RBS_RANGE_LEX2AST(args_range); + rbs_node_list_append(array, (rbs_node_t *) self_type); + } if (parser->next_token.type == pCOMMA) { rbs_parser_advance(parser); diff --git a/src/serialize.c b/src/serialize.c index 9d96aec023..a7ec5b7e39 100644 --- a/src/serialize.c +++ b/src/serialize.c @@ -132,7 +132,7 @@ static void w_hash(rbs_serialize_state *state, rbs_hash_t *hash) { // The node tag written ahead of every node. Tag 0 is reserved for NULL, tags // 1..N are the nodes below (in the same order the Ruby schema is generated), // and the final tag is `rbs_ast_symbol`, which is not a config.yml node. -#define RBS_SERIALIZE_TAG_SYMBOL 79 +#define RBS_SERIALIZE_TAG_SYMBOL 80 static void serialize_node(rbs_serialize_state *state, rbs_node_t *instance) { if (instance == NULL) { @@ -268,8 +268,18 @@ static void serialize_node(rbs_serialize_state *state, rbs_node_t *instance) { w_node_list(state, node->args); return; } - case RBS_AST_DECLARATIONS_MODULE_ALIAS: { + case RBS_AST_DECLARATIONS_MODULE_SINGLETON_SELF: { w_u8(state, 12); + rbs_ast_declarations_module_singleton_self_t *node = (rbs_ast_declarations_module_singleton_self_t *) instance; + w_loc_range(state, node->base.location); + w_loc_range(state, node->name_range); + w_loc_range(state, node->args_range); + serialize_node(state, (rbs_node_t *) node->name); + w_node_list(state, node->args); + return; + } + case RBS_AST_DECLARATIONS_MODULE_ALIAS: { + w_u8(state, 13); rbs_ast_declarations_module_alias_t *node = (rbs_ast_declarations_module_alias_t *) instance; w_loc_range(state, node->base.location); w_loc_range(state, node->keyword_range); @@ -283,7 +293,7 @@ static void serialize_node(rbs_serialize_state *state, rbs_node_t *instance) { return; } case RBS_AST_DECLARATIONS_TYPE_ALIAS: { - w_u8(state, 13); + w_u8(state, 14); rbs_ast_declarations_type_alias_t *node = (rbs_ast_declarations_type_alias_t *) instance; w_loc_range(state, node->base.location); w_loc_range(state, node->keyword_range); @@ -298,7 +308,7 @@ static void serialize_node(rbs_serialize_state *state, rbs_node_t *instance) { return; } case RBS_AST_DIRECTIVES_USE: { - w_u8(state, 14); + w_u8(state, 15); rbs_ast_directives_use_t *node = (rbs_ast_directives_use_t *) instance; w_loc_range(state, node->base.location); w_loc_range(state, node->keyword_range); @@ -306,7 +316,7 @@ static void serialize_node(rbs_serialize_state *state, rbs_node_t *instance) { return; } case RBS_AST_DIRECTIVES_USE_SINGLE_CLAUSE: { - w_u8(state, 15); + w_u8(state, 16); rbs_ast_directives_use_single_clause_t *node = (rbs_ast_directives_use_single_clause_t *) instance; w_loc_range(state, node->base.location); w_loc_range(state, node->type_name_range); @@ -317,7 +327,7 @@ static void serialize_node(rbs_serialize_state *state, rbs_node_t *instance) { return; } case RBS_AST_DIRECTIVES_USE_WILDCARD_CLAUSE: { - w_u8(state, 16); + w_u8(state, 17); rbs_ast_directives_use_wildcard_clause_t *node = (rbs_ast_directives_use_wildcard_clause_t *) instance; w_loc_range(state, node->base.location); w_loc_range(state, node->namespace_range); @@ -326,12 +336,12 @@ static void serialize_node(rbs_serialize_state *state, rbs_node_t *instance) { return; } case RBS_AST_INTEGER: { - w_u8(state, 17); + w_u8(state, 18); w_string(state, ((rbs_ast_integer_t *) instance)->string_representation); return; } case RBS_AST_MEMBERS_ALIAS: { - w_u8(state, 18); + w_u8(state, 19); rbs_ast_members_alias_t *node = (rbs_ast_members_alias_t *) instance; w_loc_range(state, node->base.location); w_loc_range(state, node->keyword_range); @@ -347,7 +357,7 @@ static void serialize_node(rbs_serialize_state *state, rbs_node_t *instance) { return; } case RBS_AST_MEMBERS_ATTR_ACCESSOR: { - w_u8(state, 19); + w_u8(state, 20); rbs_ast_members_attr_accessor_t *node = (rbs_ast_members_attr_accessor_t *) instance; w_loc_range(state, node->base.location); w_loc_range(state, node->keyword_range); @@ -367,7 +377,7 @@ static void serialize_node(rbs_serialize_state *state, rbs_node_t *instance) { return; } case RBS_AST_MEMBERS_ATTR_READER: { - w_u8(state, 20); + w_u8(state, 21); rbs_ast_members_attr_reader_t *node = (rbs_ast_members_attr_reader_t *) instance; w_loc_range(state, node->base.location); w_loc_range(state, node->keyword_range); @@ -387,7 +397,7 @@ static void serialize_node(rbs_serialize_state *state, rbs_node_t *instance) { return; } case RBS_AST_MEMBERS_ATTR_WRITER: { - w_u8(state, 21); + w_u8(state, 22); rbs_ast_members_attr_writer_t *node = (rbs_ast_members_attr_writer_t *) instance; w_loc_range(state, node->base.location); w_loc_range(state, node->keyword_range); @@ -407,7 +417,7 @@ static void serialize_node(rbs_serialize_state *state, rbs_node_t *instance) { return; } case RBS_AST_MEMBERS_CLASS_INSTANCE_VARIABLE: { - w_u8(state, 22); + w_u8(state, 23); rbs_ast_members_class_instance_variable_t *node = (rbs_ast_members_class_instance_variable_t *) instance; w_loc_range(state, node->base.location); w_loc_range(state, node->name_range); @@ -419,7 +429,7 @@ static void serialize_node(rbs_serialize_state *state, rbs_node_t *instance) { return; } case RBS_AST_MEMBERS_CLASS_VARIABLE: { - w_u8(state, 23); + w_u8(state, 24); rbs_ast_members_class_variable_t *node = (rbs_ast_members_class_variable_t *) instance; w_loc_range(state, node->base.location); w_loc_range(state, node->name_range); @@ -431,7 +441,7 @@ static void serialize_node(rbs_serialize_state *state, rbs_node_t *instance) { return; } case RBS_AST_MEMBERS_EXTEND: { - w_u8(state, 24); + w_u8(state, 25); rbs_ast_members_extend_t *node = (rbs_ast_members_extend_t *) instance; w_loc_range(state, node->base.location); w_loc_range(state, node->name_range); @@ -444,7 +454,7 @@ static void serialize_node(rbs_serialize_state *state, rbs_node_t *instance) { return; } case RBS_AST_MEMBERS_INCLUDE: { - w_u8(state, 25); + w_u8(state, 26); rbs_ast_members_include_t *node = (rbs_ast_members_include_t *) instance; w_loc_range(state, node->base.location); w_loc_range(state, node->name_range); @@ -457,7 +467,7 @@ static void serialize_node(rbs_serialize_state *state, rbs_node_t *instance) { return; } case RBS_AST_MEMBERS_INSTANCE_VARIABLE: { - w_u8(state, 26); + w_u8(state, 27); rbs_ast_members_instance_variable_t *node = (rbs_ast_members_instance_variable_t *) instance; w_loc_range(state, node->base.location); w_loc_range(state, node->name_range); @@ -469,7 +479,7 @@ static void serialize_node(rbs_serialize_state *state, rbs_node_t *instance) { return; } case RBS_AST_MEMBERS_METHOD_DEFINITION: { - w_u8(state, 27); + w_u8(state, 28); rbs_ast_members_method_definition_t *node = (rbs_ast_members_method_definition_t *) instance; w_loc_range(state, node->base.location); w_loc_range(state, node->keyword_range); @@ -487,14 +497,14 @@ static void serialize_node(rbs_serialize_state *state, rbs_node_t *instance) { return; } case RBS_AST_MEMBERS_METHOD_DEFINITION_OVERLOAD: { - w_u8(state, 28); + w_u8(state, 29); rbs_ast_members_method_definition_overload_t *node = (rbs_ast_members_method_definition_overload_t *) instance; w_node_list(state, node->annotations); serialize_node(state, (rbs_node_t *) node->method_type); return; } case RBS_AST_MEMBERS_PREPEND: { - w_u8(state, 29); + w_u8(state, 30); rbs_ast_members_prepend_t *node = (rbs_ast_members_prepend_t *) instance; w_loc_range(state, node->base.location); w_loc_range(state, node->name_range); @@ -507,19 +517,19 @@ static void serialize_node(rbs_serialize_state *state, rbs_node_t *instance) { return; } case RBS_AST_MEMBERS_PRIVATE: { - w_u8(state, 30); + w_u8(state, 31); rbs_ast_members_private_t *node = (rbs_ast_members_private_t *) instance; w_loc_range(state, node->base.location); return; } case RBS_AST_MEMBERS_PUBLIC: { - w_u8(state, 31); + w_u8(state, 32); rbs_ast_members_public_t *node = (rbs_ast_members_public_t *) instance; w_loc_range(state, node->base.location); return; } case RBS_AST_RUBY_ANNOTATIONS_BLOCK_PARAM_TYPE_ANNOTATION: { - w_u8(state, 32); + w_u8(state, 33); rbs_ast_ruby_annotations_block_param_type_annotation_t *node = (rbs_ast_ruby_annotations_block_param_type_annotation_t *) instance; w_loc_range(state, node->base.location); w_loc_range(state, node->prefix_location); @@ -533,7 +543,7 @@ static void serialize_node(rbs_serialize_state *state, rbs_node_t *instance) { return; } case RBS_AST_RUBY_ANNOTATIONS_CLASS_ALIAS_ANNOTATION: { - w_u8(state, 33); + w_u8(state, 34); rbs_ast_ruby_annotations_class_alias_annotation_t *node = (rbs_ast_ruby_annotations_class_alias_annotation_t *) instance; w_loc_range(state, node->base.location); w_loc_range(state, node->prefix_location); @@ -543,7 +553,7 @@ static void serialize_node(rbs_serialize_state *state, rbs_node_t *instance) { return; } case RBS_AST_RUBY_ANNOTATIONS_COLON_METHOD_TYPE_ANNOTATION: { - w_u8(state, 34); + w_u8(state, 35); rbs_ast_ruby_annotations_colon_method_type_annotation_t *node = (rbs_ast_ruby_annotations_colon_method_type_annotation_t *) instance; w_loc_range(state, node->base.location); w_loc_range(state, node->prefix_location); @@ -552,7 +562,7 @@ static void serialize_node(rbs_serialize_state *state, rbs_node_t *instance) { return; } case RBS_AST_RUBY_ANNOTATIONS_DOUBLE_SPLAT_PARAM_TYPE_ANNOTATION: { - w_u8(state, 35); + w_u8(state, 36); rbs_ast_ruby_annotations_double_splat_param_type_annotation_t *node = (rbs_ast_ruby_annotations_double_splat_param_type_annotation_t *) instance; w_loc_range(state, node->base.location); w_loc_range(state, node->prefix_location); @@ -564,7 +574,7 @@ static void serialize_node(rbs_serialize_state *state, rbs_node_t *instance) { return; } case RBS_AST_RUBY_ANNOTATIONS_INSTANCE_VARIABLE_ANNOTATION: { - w_u8(state, 36); + w_u8(state, 37); rbs_ast_ruby_annotations_instance_variable_annotation_t *node = (rbs_ast_ruby_annotations_instance_variable_annotation_t *) instance; w_loc_range(state, node->base.location); w_loc_range(state, node->prefix_location); @@ -576,7 +586,7 @@ static void serialize_node(rbs_serialize_state *state, rbs_node_t *instance) { return; } case RBS_AST_RUBY_ANNOTATIONS_METHOD_TYPES_ANNOTATION: { - w_u8(state, 37); + w_u8(state, 38); rbs_ast_ruby_annotations_method_types_annotation_t *node = (rbs_ast_ruby_annotations_method_types_annotation_t *) instance; w_loc_range(state, node->base.location); w_loc_range(state, node->prefix_location); @@ -586,7 +596,7 @@ static void serialize_node(rbs_serialize_state *state, rbs_node_t *instance) { return; } case RBS_AST_RUBY_ANNOTATIONS_MODULE_ALIAS_ANNOTATION: { - w_u8(state, 38); + w_u8(state, 39); rbs_ast_ruby_annotations_module_alias_annotation_t *node = (rbs_ast_ruby_annotations_module_alias_annotation_t *) instance; w_loc_range(state, node->base.location); w_loc_range(state, node->prefix_location); @@ -596,7 +606,7 @@ static void serialize_node(rbs_serialize_state *state, rbs_node_t *instance) { return; } case RBS_AST_RUBY_ANNOTATIONS_MODULE_SELF_ANNOTATION: { - w_u8(state, 39); + w_u8(state, 40); rbs_ast_ruby_annotations_module_self_annotation_t *node = (rbs_ast_ruby_annotations_module_self_annotation_t *) instance; w_loc_range(state, node->base.location); w_loc_range(state, node->prefix_location); @@ -611,7 +621,7 @@ static void serialize_node(rbs_serialize_state *state, rbs_node_t *instance) { return; } case RBS_AST_RUBY_ANNOTATIONS_NODE_TYPE_ASSERTION: { - w_u8(state, 40); + w_u8(state, 41); rbs_ast_ruby_annotations_node_type_assertion_t *node = (rbs_ast_ruby_annotations_node_type_assertion_t *) instance; w_loc_range(state, node->base.location); w_loc_range(state, node->prefix_location); @@ -619,7 +629,7 @@ static void serialize_node(rbs_serialize_state *state, rbs_node_t *instance) { return; } case RBS_AST_RUBY_ANNOTATIONS_PARAM_TYPE_ANNOTATION: { - w_u8(state, 41); + w_u8(state, 42); rbs_ast_ruby_annotations_param_type_annotation_t *node = (rbs_ast_ruby_annotations_param_type_annotation_t *) instance; w_loc_range(state, node->base.location); w_loc_range(state, node->prefix_location); @@ -630,7 +640,7 @@ static void serialize_node(rbs_serialize_state *state, rbs_node_t *instance) { return; } case RBS_AST_RUBY_ANNOTATIONS_RETURN_TYPE_ANNOTATION: { - w_u8(state, 42); + w_u8(state, 43); rbs_ast_ruby_annotations_return_type_annotation_t *node = (rbs_ast_ruby_annotations_return_type_annotation_t *) instance; w_loc_range(state, node->base.location); w_loc_range(state, node->prefix_location); @@ -641,7 +651,7 @@ static void serialize_node(rbs_serialize_state *state, rbs_node_t *instance) { return; } case RBS_AST_RUBY_ANNOTATIONS_SKIP_ANNOTATION: { - w_u8(state, 43); + w_u8(state, 44); rbs_ast_ruby_annotations_skip_annotation_t *node = (rbs_ast_ruby_annotations_skip_annotation_t *) instance; w_loc_range(state, node->base.location); w_loc_range(state, node->prefix_location); @@ -650,7 +660,7 @@ static void serialize_node(rbs_serialize_state *state, rbs_node_t *instance) { return; } case RBS_AST_RUBY_ANNOTATIONS_SPLAT_PARAM_TYPE_ANNOTATION: { - w_u8(state, 44); + w_u8(state, 45); rbs_ast_ruby_annotations_splat_param_type_annotation_t *node = (rbs_ast_ruby_annotations_splat_param_type_annotation_t *) instance; w_loc_range(state, node->base.location); w_loc_range(state, node->prefix_location); @@ -662,7 +672,7 @@ static void serialize_node(rbs_serialize_state *state, rbs_node_t *instance) { return; } case RBS_AST_RUBY_ANNOTATIONS_TYPE_APPLICATION_ANNOTATION: { - w_u8(state, 45); + w_u8(state, 46); rbs_ast_ruby_annotations_type_application_annotation_t *node = (rbs_ast_ruby_annotations_type_application_annotation_t *) instance; w_loc_range(state, node->base.location); w_loc_range(state, node->prefix_location); @@ -672,12 +682,12 @@ static void serialize_node(rbs_serialize_state *state, rbs_node_t *instance) { return; } case RBS_AST_STRING: { - w_u8(state, 46); + w_u8(state, 47); w_string(state, ((rbs_ast_string_t *) instance)->string); return; } case RBS_AST_TYPE_PARAM: { - w_u8(state, 47); + w_u8(state, 48); rbs_ast_type_param_t *node = (rbs_ast_type_param_t *) instance; w_loc_range(state, node->base.location); w_loc_range(state, node->name_range); @@ -695,7 +705,7 @@ static void serialize_node(rbs_serialize_state *state, rbs_node_t *instance) { return; } case RBS_METHOD_TYPE: { - w_u8(state, 48); + w_u8(state, 49); rbs_method_type_t *node = (rbs_method_type_t *) instance; w_loc_range(state, node->base.location); w_loc_range(state, node->type_range); @@ -706,28 +716,28 @@ static void serialize_node(rbs_serialize_state *state, rbs_node_t *instance) { return; } case RBS_NAMESPACE: { - w_u8(state, 49); + w_u8(state, 50); rbs_namespace_t *node = (rbs_namespace_t *) instance; w_node_list(state, node->path); w_u8(state, node->absolute ? 1 : 0); return; } case RBS_SIGNATURE: { - w_u8(state, 50); + w_u8(state, 51); rbs_signature_t *node = (rbs_signature_t *) instance; w_node_list(state, node->directives); w_node_list(state, node->declarations); return; } case RBS_TYPE_NAME: { - w_u8(state, 51); + w_u8(state, 52); rbs_type_name_t *node = (rbs_type_name_t *) instance; serialize_node(state, (rbs_node_t *) node->rbs_namespace); serialize_node(state, (rbs_node_t *) node->name); return; } case RBS_TYPES_ALIAS: { - w_u8(state, 52); + w_u8(state, 53); rbs_types_alias_t *node = (rbs_types_alias_t *) instance; w_loc_range(state, node->base.location); w_loc_range(state, node->name_range); @@ -737,62 +747,62 @@ static void serialize_node(rbs_serialize_state *state, rbs_node_t *instance) { return; } case RBS_TYPES_BASES_ANY: { - w_u8(state, 53); + w_u8(state, 54); rbs_types_bases_any_t *node = (rbs_types_bases_any_t *) instance; w_loc_range(state, node->base.location); w_u8(state, node->todo ? 1 : 0); return; } case RBS_TYPES_BASES_BOOL: { - w_u8(state, 54); + w_u8(state, 55); rbs_types_bases_bool_t *node = (rbs_types_bases_bool_t *) instance; w_loc_range(state, node->base.location); return; } case RBS_TYPES_BASES_BOTTOM: { - w_u8(state, 55); + w_u8(state, 56); rbs_types_bases_bottom_t *node = (rbs_types_bases_bottom_t *) instance; w_loc_range(state, node->base.location); return; } case RBS_TYPES_BASES_CLASS: { - w_u8(state, 56); + w_u8(state, 57); rbs_types_bases_class_t *node = (rbs_types_bases_class_t *) instance; w_loc_range(state, node->base.location); return; } case RBS_TYPES_BASES_INSTANCE: { - w_u8(state, 57); + w_u8(state, 58); rbs_types_bases_instance_t *node = (rbs_types_bases_instance_t *) instance; w_loc_range(state, node->base.location); return; } case RBS_TYPES_BASES_NIL: { - w_u8(state, 58); + w_u8(state, 59); rbs_types_bases_nil_t *node = (rbs_types_bases_nil_t *) instance; w_loc_range(state, node->base.location); return; } case RBS_TYPES_BASES_SELF: { - w_u8(state, 59); + w_u8(state, 60); rbs_types_bases_self_t *node = (rbs_types_bases_self_t *) instance; w_loc_range(state, node->base.location); return; } case RBS_TYPES_BASES_TOP: { - w_u8(state, 60); + w_u8(state, 61); rbs_types_bases_top_t *node = (rbs_types_bases_top_t *) instance; w_loc_range(state, node->base.location); return; } case RBS_TYPES_BASES_VOID: { - w_u8(state, 61); + w_u8(state, 62); rbs_types_bases_void_t *node = (rbs_types_bases_void_t *) instance; w_loc_range(state, node->base.location); return; } case RBS_TYPES_BLOCK: { - w_u8(state, 62); + w_u8(state, 63); rbs_types_block_t *node = (rbs_types_block_t *) instance; w_loc_range(state, node->base.location); serialize_node(state, (rbs_node_t *) node->type); @@ -801,7 +811,7 @@ static void serialize_node(rbs_serialize_state *state, rbs_node_t *instance) { return; } case RBS_TYPES_CLASS_INSTANCE: { - w_u8(state, 63); + w_u8(state, 64); rbs_types_class_instance_t *node = (rbs_types_class_instance_t *) instance; w_loc_range(state, node->base.location); w_loc_range(state, node->name_range); @@ -811,7 +821,7 @@ static void serialize_node(rbs_serialize_state *state, rbs_node_t *instance) { return; } case RBS_TYPES_CLASS_SINGLETON: { - w_u8(state, 64); + w_u8(state, 65); rbs_types_class_singleton_t *node = (rbs_types_class_singleton_t *) instance; w_loc_range(state, node->base.location); w_loc_range(state, node->name_range); @@ -821,7 +831,7 @@ static void serialize_node(rbs_serialize_state *state, rbs_node_t *instance) { return; } case RBS_TYPES_FUNCTION: { - w_u8(state, 65); + w_u8(state, 66); rbs_types_function_t *node = (rbs_types_function_t *) instance; w_node_list(state, node->required_positionals); w_node_list(state, node->optional_positionals); @@ -835,13 +845,13 @@ static void serialize_node(rbs_serialize_state *state, rbs_node_t *instance) { return; } case RBS_TYPES_FUNCTION_FORWARDING_PARAM: { - w_u8(state, 66); + w_u8(state, 67); rbs_types_function_forwarding_param_t *node = (rbs_types_function_forwarding_param_t *) instance; w_loc_range(state, node->base.location); return; } case RBS_TYPES_FUNCTION_PARAM: { - w_u8(state, 67); + w_u8(state, 68); rbs_types_function_param_t *node = (rbs_types_function_param_t *) instance; w_loc_range(state, node->base.location); w_loc_range(state, node->name_range); @@ -850,7 +860,7 @@ static void serialize_node(rbs_serialize_state *state, rbs_node_t *instance) { return; } case RBS_TYPES_INTERFACE: { - w_u8(state, 68); + w_u8(state, 69); rbs_types_interface_t *node = (rbs_types_interface_t *) instance; w_loc_range(state, node->base.location); w_loc_range(state, node->name_range); @@ -860,28 +870,28 @@ static void serialize_node(rbs_serialize_state *state, rbs_node_t *instance) { return; } case RBS_TYPES_INTERSECTION: { - w_u8(state, 69); + w_u8(state, 70); rbs_types_intersection_t *node = (rbs_types_intersection_t *) instance; w_loc_range(state, node->base.location); w_node_list(state, node->types); return; } case RBS_TYPES_LITERAL: { - w_u8(state, 70); + w_u8(state, 71); rbs_types_literal_t *node = (rbs_types_literal_t *) instance; w_loc_range(state, node->base.location); serialize_node(state, (rbs_node_t *) node->literal); return; } case RBS_TYPES_OPTIONAL: { - w_u8(state, 71); + w_u8(state, 72); rbs_types_optional_t *node = (rbs_types_optional_t *) instance; w_loc_range(state, node->base.location); serialize_node(state, (rbs_node_t *) node->type); return; } case RBS_TYPES_PROC: { - w_u8(state, 72); + w_u8(state, 73); rbs_types_proc_t *node = (rbs_types_proc_t *) instance; w_loc_range(state, node->base.location); serialize_node(state, (rbs_node_t *) node->type); @@ -890,41 +900,41 @@ static void serialize_node(rbs_serialize_state *state, rbs_node_t *instance) { return; } case RBS_TYPES_RECORD: { - w_u8(state, 73); + w_u8(state, 74); rbs_types_record_t *node = (rbs_types_record_t *) instance; w_loc_range(state, node->base.location); w_hash(state, node->all_fields); return; } case RBS_TYPES_RECORD_FIELD_TYPE: { - w_u8(state, 74); + w_u8(state, 75); rbs_types_record_field_type_t *node = (rbs_types_record_field_type_t *) instance; serialize_node(state, node->type); w_u8(state, node->required ? 1 : 0); return; } case RBS_TYPES_TUPLE: { - w_u8(state, 75); + w_u8(state, 76); rbs_types_tuple_t *node = (rbs_types_tuple_t *) instance; w_loc_range(state, node->base.location); w_node_list(state, node->types); return; } case RBS_TYPES_UNION: { - w_u8(state, 76); + w_u8(state, 77); rbs_types_union_t *node = (rbs_types_union_t *) instance; w_loc_range(state, node->base.location); w_node_list(state, node->types); return; } case RBS_TYPES_UNTYPED_FUNCTION: { - w_u8(state, 77); + w_u8(state, 78); rbs_types_untyped_function_t *node = (rbs_types_untyped_function_t *) instance; serialize_node(state, (rbs_node_t *) node->return_type); return; } case RBS_TYPES_VARIABLE: { - w_u8(state, 78); + w_u8(state, 79); rbs_types_variable_t *node = (rbs_types_variable_t *) instance; w_loc_range(state, node->base.location); serialize_node(state, (rbs_node_t *) node->name); diff --git a/test/rbs/environment_test.rb b/test/rbs/environment_test.rb index 2198da9261..084fcf0709 100644 --- a/test/rbs/environment_test.rb +++ b/test/rbs/environment_test.rb @@ -1067,4 +1067,43 @@ def test__ruby__class_module_alias_with_skip_annotation assert_operator resolved_env.class_alias_decls, :key?, type_name("::ProcessedKernel") assert_instance_of Environment::ModuleAliasEntry, resolved_env.class_alias_decls[type_name("::ProcessedKernel")] end + + def test_module_singleton_self_type + buf, dirs, decls = RBS::Parser.parse_signature(< Date: Wed, 28 Oct 2026 13:43:16 +0900 Subject: [PATCH 2/2] Build module definitions with singleton self types `module Foo : singleton(Bar)` now contributes the singleton methods (and class instance variables) of `Bar` to the instance definition of `Foo`, the same way class instance self types contribute instance methods. - ancestor_builder: push `Definition::Ancestor::Singleton` (with the new `source` attribute) for singleton self types, and check for recursive ancestors through `singleton_ancestors`, consistent with the existing check for class instance self types - definition_builder: merge `build_singleton(Bar)` into the module definition; methods defined in the module take precedence - errors: `NoSelfTypeFoundError.check!` rejects `singleton(_Interface)` Method types are taken as written: `instance`/`class`/`self` in the singleton methods are not rewritten, consistent with class instance self types. Resolving them is left to the type checker. Part of #1334 (proposal 1). Co-Authored-By: Claude Fable 5.1 --- lib/rbs/definition.rb | 5 +- lib/rbs/definition_builder.rb | 39 ++++-- .../definition_builder/ancestor_builder.rb | 49 +++++--- lib/rbs/errors.rb | 9 +- sig/ancestor_builder.rbs | 6 +- sig/definition.rbs | 6 +- sig/errors.rbs | 2 +- test/rbs/ancestor_builder_test.rb | 118 ++++++++++++++++++ test/rbs/definition_builder_test.rb | 57 +++++++++ 9 files changed, 254 insertions(+), 37 deletions(-) diff --git a/lib/rbs/definition.rb b/lib/rbs/definition.rb index 0503f795e1..b41119f4c5 100644 --- a/lib/rbs/definition.rb +++ b/lib/rbs/definition.rb @@ -228,10 +228,11 @@ def hash end class Singleton - attr_reader :name + attr_reader :name, :source - def initialize(name:) + def initialize(name:, source: nil) @name = name + @source = source end def ==(other) diff --git a/lib/rbs/definition_builder.rb b/lib/rbs/definition_builder.rb index ab0f6067de..03e3714c17 100644 --- a/lib/rbs/definition_builder.rb +++ b/lib/rbs/definition_builder.rb @@ -87,13 +87,19 @@ def define_instance(definition, type_name, subst, define_class_vars:) methods = method_builder.build_instance(type_name) self_type_methods = one_ancestors.each_self_type.with_object({}) do |self_type, hash| #$ Hash[Symbol, Definition::Method] - self_type.args.each do |arg| - validate_type_presence(arg) - end + case self_type + when Definition::Ancestor::Singleton + self_type_defn = build_singleton(self_type.name) + s = subst + when Definition::Ancestor::Instance + self_type.args.each do |arg| + validate_type_presence(arg) + end - self_type_defn = self_type.name.interface? ? build_interface(self_type.name) : build_instance(self_type.name) + self_type_defn = self_type.name.interface? ? build_interface(self_type.name) : build_instance(self_type.name) + s = subst + tapp_subst(self_type.name, self_type.args) + end - s = subst + tapp_subst(self_type.name, self_type.args) self_type_defn.methods.each do |method_name, method_def| hash[method_name] = method_def.sub(s) end @@ -236,15 +242,22 @@ def build_instance(type_name) if entry.is_a?(Environment::ModuleEntry) if self_types = one_ancestors.self_types self_types.each do |ans| - ans.args.each do |arg| - validate_type_presence(arg) - end + case ans + when Definition::Ancestor::Singleton + defn = build_singleton(ans.name) + definition.methods.merge!(defn.methods) + definition.instance_variables.merge!(defn.instance_variables) + when Definition::Ancestor::Instance + ans.args.each do |arg| + validate_type_presence(arg) + end - subst = tapp_subst(ans.name, ans.args) - if ans.name.interface? - define_interface(definition, ans.name, subst) - else - define_instance(definition, ans.name, subst, define_class_vars: true) + subst = tapp_subst(ans.name, ans.args) + if ans.name.interface? + define_interface(definition, ans.name, subst) + else + define_instance(definition, ans.name, subst, define_class_vars: true) + end end end end diff --git a/lib/rbs/definition_builder/ancestor_builder.rb b/lib/rbs/definition_builder/ancestor_builder.rb index 7cc2c75430..88f3e71089 100644 --- a/lib/rbs/definition_builder/ancestor_builder.rb +++ b/lib/rbs/definition_builder/ancestor_builder.rb @@ -250,21 +250,33 @@ def one_instance_ancestors(type_name) else entry.self_types.each do |module_self| NoSelfTypeFoundError.check!(module_self, env: env) - InvalidTypeApplicationError.check2!(type_name: module_self.name, args: module_self.args, env: env, location: module_self.location) - module_name = module_self.name - if module_name.class? - module_entry = env.module_class_entry(module_name, normalized: true) or raise - module_name = module_entry.name - self_args = AST::TypeParam.normalize_args(module_entry.type_params, module_self.args) - end - if module_name.interface? - interface_entry = env.interface_decls.fetch(module_name) - self_args = AST::TypeParam.normalize_args(interface_entry.decl.type_params, module_self.args) - end - self_args or raise + case module_self + when AST::Declarations::Module::SingletonSelf + module_entry = env.module_class_entry(module_self.name, normalized: true) or raise + # Type arguments are optional for class singleton types, like `singleton(Array)` + unless module_self.args.empty? + InvalidTypeApplicationError.check2!(type_name: module_self.name, args: module_self.args, env: env, location: module_self.location) + end - self_types.push Definition::Ancestor::Instance.new(name: module_name, args: self_args, source: module_self) + self_types.push Definition::Ancestor::Singleton.new(name: module_entry.name, source: module_self) + when AST::Declarations::Module::Self + InvalidTypeApplicationError.check2!(type_name: module_self.name, args: module_self.args, env: env, location: module_self.location) + + module_name = module_self.name + if module_name.class? + module_entry = env.module_class_entry(module_name, normalized: true) or raise + module_name = module_entry.name + self_args = AST::TypeParam.normalize_args(module_entry.type_params, module_self.args) + end + if module_name.interface? + interface_entry = env.interface_decls.fetch(module_name) + self_args = AST::TypeParam.normalize_args(interface_entry.decl.type_params, module_self.args) + end + self_args or raise + + self_types.push Definition::Ancestor::Instance.new(name: module_name, args: self_args, source: module_self) + end end end end @@ -529,9 +541,14 @@ def instance_ancestors(type_name, building_ancestors: []) if self_types = one_ancestors.self_types self_types.each do |mod| - if mod.name.class? - # Ensure there is no loop in ancestors chain - instance_ancestors(mod.name, building_ancestors: building_ancestors) + # Ensure there is no loop in ancestors chain + case mod + when Definition::Ancestor::Singleton + singleton_ancestors(mod.name, building_ancestors: building_ancestors) + when Definition::Ancestor::Instance + if mod.name.class? + instance_ancestors(mod.name, building_ancestors: building_ancestors) + end end end end diff --git a/lib/rbs/errors.rb b/lib/rbs/errors.rb index 8c368daf5f..e42438033c 100644 --- a/lib/rbs/errors.rb +++ b/lib/rbs/errors.rb @@ -217,7 +217,14 @@ def initialize(type_name:, location:) def self.check!(self_type, env:) self_name = env.normalize_type_name(self_type.name) - (env.module_name?(self_name) || env.interface_name?(self_name)) or raise new(type_name: self_type.name, location: self_type.location) + found = + case self_type + when AST::Declarations::Module::SingletonSelf + env.module_name?(self_name) + else + env.module_name?(self_name) || env.interface_name?(self_name) + end + found or raise new(type_name: self_type.name, location: self_type.location) end end diff --git a/sig/ancestor_builder.rbs b/sig/ancestor_builder.rbs index fbe0097f55..1dd9d8b8d6 100644 --- a/sig/ancestor_builder.rbs +++ b/sig/ancestor_builder.rbs @@ -33,7 +33,7 @@ module RBS # # * Returns `nil` if it is not a module instance. # - attr_reader self_types: Array[Definition::Ancestor::Instance]? + attr_reader self_types: Array[Definition::Ancestor::t]? # Returns the list of included modules # @@ -68,7 +68,7 @@ module RBS def initialize: (type_name: TypeName, params: Array[Symbol]?, super_class: Definition::Ancestor::t?, - self_types: Array[Definition::Ancestor::Instance]?, + self_types: Array[Definition::Ancestor::t]?, included_modules: Array[Definition::Ancestor::Instance]?, included_interfaces: Array[Definition::Ancestor::Instance]?, prepended_modules: Array[Definition::Ancestor::Instance]?, @@ -105,7 +105,7 @@ module RBS def each_extended_interface: () { (Definition::Ancestor::Instance) -> void } -> void | () -> Enumerator[Definition::Ancestor::Instance, void] - def each_self_type: () { (Definition::Ancestor::Instance) -> void } -> void + def each_self_type: () { (Definition::Ancestor::t) -> void } -> void | () -> Enumerator[Definition::Ancestor::Instance, void] end diff --git a/sig/definition.rbs b/sig/definition.rbs index 61ee6f0ea0..4d5666c3f6 100644 --- a/sig/definition.rbs +++ b/sig/definition.rbs @@ -170,9 +170,13 @@ module RBS end class Singleton + type source = nil # Itself, or super class singleton + | AST::Declarations::Module::SingletonSelf + attr_reader name: TypeName + attr_reader source: source - def initialize: (name: TypeName) -> void + def initialize: (name: TypeName, ?source: source) -> void end end diff --git a/sig/errors.rbs b/sig/errors.rbs index c2c716ade6..e162ca4e63 100644 --- a/sig/errors.rbs +++ b/sig/errors.rbs @@ -109,7 +109,7 @@ module RBS # The type name in `self` is automatically normalized # - def self.check!: (AST::Declarations::Module::Self, env: Environment) -> void + def self.check!: (AST::Declarations::Module::self_type, env: Environment) -> void end class NoMixinFoundError < DefinitionError diff --git a/test/rbs/ancestor_builder_test.rb b/test/rbs/ancestor_builder_test.rb index bbd4d2d613..fc41d23b91 100644 --- a/test/rbs/ancestor_builder_test.rb +++ b/test/rbs/ancestor_builder_test.rb @@ -1418,4 +1418,122 @@ class C end end end + + def test_one_ancestors_module_singleton_self_types + SignatureManager.new do |manager| + manager.files[Pathname("foo.rbs")] = < instance + def self.table: () -> String + self.@count: Integer + def save: () -> bool +end + +module ClassMethods : singleton(Model) + def find_or_raise: (Integer) -> Model + def table: () -> Symbol +end + +class User < Model + extend ClassMethods +end + +class Other + include ClassMethods +end +EOF + manager.build do |env| + builder = DefinitionBuilder.new(env: env) + + builder.build_instance(type_name("::ClassMethods")).tap do |definition| + # Singleton methods of Model are available, with their types as written + assert_method_definition definition.methods[:find], ["(::Integer) -> instance"], accessibility: :public + assert_equal type_name("::Model"), definition.methods[:find].defined_in + + # Methods defined in the module win + assert_method_definition definition.methods[:table], ["() -> ::Symbol"], accessibility: :public + assert_equal type_name("::ClassMethods"), definition.methods[:table].defined_in + + assert_method_definition definition.methods[:find_or_raise], ["(::Integer) -> ::Model"], accessibility: :public + + # Instance methods of Model are not available + assert_nil definition.methods[:save] + + # Class instance variables of Model are available + assert_equal parse_type("::Integer"), definition.instance_variables[:@count].type + end + + builder.build_singleton(type_name("::User")).tap do |definition| + assert_method_definition definition.methods[:find_or_raise], ["(::Integer) -> ::Model"], accessibility: :public + assert_method_definition definition.methods[:find], ["(::Integer) -> instance"], accessibility: :public + end + + builder.build_instance(type_name("::Other")).tap do |definition| + assert_method_definition definition.methods[:find_or_raise], ["(::Integer) -> ::Model"], accessibility: :public + assert_nil definition.methods[:find] + end + end + end + end end