From 2dbe9736beb1c0fb34f78c6d93d1c677b99ea256 Mon Sep 17 00:00:00 2001 From: Soutaro Matsumoto Date: Tue, 10 Jan 2023 15:59:34 +0900 Subject: [PATCH 1/9] Add AST --- lib/rbs/ast/declarations.rb | 47 +++++++++++++++++++++++++++++++++++++ sig/declarations.rbs | 35 ++++++++++++++++++++++++++- 2 files changed, 81 insertions(+), 1 deletion(-) diff --git a/lib/rbs/ast/declarations.rb b/lib/rbs/ast/declarations.rb index be16dd809..f1654d4d0 100644 --- a/lib/rbs/ast/declarations.rb +++ b/lib/rbs/ast/declarations.rb @@ -379,6 +379,53 @@ def to_json(state = _ = nil) }.to_json(state) end end + + class AliasDecl < Base + attr_reader :new_name, :old_name, :location, :comment + + def initialize(new_name:, old_name:, location:, comment:) + @new_name = new_name + @old_name = old_name + @location = location + @comment = comment + end + + def ==(other) + other.is_a?(self.class) && + other.new_name == new_name && + other.old_name == old_name + end + + alias eql? == + + def hash + self.class.hash ^ new_name.hash ^ old_name.hash + end + end + + class ClassAlias < AliasDecl + def to_json(state = _ = nil) + { + declaration: :class_alias, + new_name: new_name, + old_name: old_name, + location: location, + comment: comment + }.to_json(state) + end + end + + class ModuleAlias < AliasDecl + def to_json(state = _ = nil) + { + declaration: :module_alias, + new_name: new_name, + old_name: old_name, + location: location, + comment: comment + }.to_json(state) + end + end end end end diff --git a/sig/declarations.rbs b/sig/declarations.rbs index 9d8bb2754..17fd5fe49 100644 --- a/sig/declarations.rbs +++ b/sig/declarations.rbs @@ -1,7 +1,7 @@ module RBS module AST module Declarations - type t = Class | Module | Interface | Constant | Global | TypeAlias + type t = Class | Module | Interface | Constant | Global | TypeAlias | ClassAlias | ModuleAlias class Base end @@ -223,6 +223,39 @@ module RBS include _HashEqual include _ToJson end + + class AliasDecl < Base + # module Foo = Bar + # ^^^^^^ keyword + # ^^^ new_name + # ^ eq + # ^^^ old_name + # + # class Foo = Bar + # ^^^^^ keyword + # + type loc = Location[:keyword | :new_name | :eq | :old_name, bot] + + attr_reader new_name: TypeName + + attr_reader old_name: TypeName + + attr_reader location: loc? + + attr_reader comment: Comment? + + def initialize: (new_name: TypeName, old_name: TypeName, location: loc?, comment: Comment?) -> void + + include _HashEqual + end + + class ClassAlias < AliasDecl + include _ToJson + end + + class ModuleAlias < AliasDecl + include _ToJson + end end end end From b6d89c9390206b423edc9cb9163daf79a31bdaa0 Mon Sep 17 00:00:00 2001 From: Soutaro Matsumoto Date: Tue, 10 Jan 2023 15:59:50 +0900 Subject: [PATCH 2/9] Implement parser --- ext/rbs_extension/constants.c | 4 + ext/rbs_extension/constants.h | 2 + ext/rbs_extension/parser.c | 113 ++++++++++++++++++++++------- ext/rbs_extension/ruby_objs.c | 28 +++++++ ext/rbs_extension/ruby_objs.h | 2 + test/rbs/signature_parsing_test.rb | 36 +++++++++ 6 files changed, 158 insertions(+), 27 deletions(-) diff --git a/ext/rbs_extension/constants.c b/ext/rbs_extension/constants.c index 716f77c3f..8bed6de71 100644 --- a/ext/rbs_extension/constants.c +++ b/ext/rbs_extension/constants.c @@ -18,6 +18,8 @@ VALUE RBS_AST_Declarations_Module; VALUE RBS_AST_Declarations_Module_Self; VALUE RBS_AST_Declarations_Class; VALUE RBS_AST_Declarations_Class_Super; +VALUE RBS_AST_Declarations_ModuleAlias; +VALUE RBS_AST_Declarations_ClassAlias; VALUE RBS_AST_Members; VALUE RBS_AST_Members_Alias; @@ -88,6 +90,8 @@ void rbs__init_constants(void) { RBS_AST_Declarations_Module_Self = rb_const_get(RBS_AST_Declarations_Module, rb_intern("Self")); RBS_AST_Declarations_Class = rb_const_get(RBS_AST_Declarations, rb_intern("Class")); RBS_AST_Declarations_Class_Super = rb_const_get(RBS_AST_Declarations_Class, rb_intern("Super")); + RBS_AST_Declarations_ClassAlias = rb_const_get(RBS_AST_Declarations, rb_intern("ClassAlias")); + RBS_AST_Declarations_ModuleAlias = rb_const_get(RBS_AST_Declarations, rb_intern("ModuleAlias")); RBS_AST_Members = rb_const_get(RBS_AST, rb_intern("Members")); RBS_AST_Members_Alias = rb_const_get(RBS_AST_Members, rb_intern("Alias")); diff --git a/ext/rbs_extension/constants.h b/ext/rbs_extension/constants.h index 992b79e2d..d757b4ea1 100644 --- a/ext/rbs_extension/constants.h +++ b/ext/rbs_extension/constants.h @@ -17,6 +17,8 @@ extern VALUE RBS_AST_Declarations_Global; extern VALUE RBS_AST_Declarations_Interface; extern VALUE RBS_AST_Declarations_Module_Self; extern VALUE RBS_AST_Declarations_Module; +extern VALUE RBS_AST_Declarations_ModuleAlias; +extern VALUE RBS_AST_Declarations_ClassAlias; extern VALUE RBS_AST_Members; extern VALUE RBS_AST_Members_Alias; diff --git a/ext/rbs_extension/parser.c b/ext/rbs_extension/parser.c index fc0d08e97..c3cf0a5ef 100644 --- a/ext/rbs_extension/parser.c +++ b/ext/rbs_extension/parser.c @@ -1034,7 +1034,6 @@ VALUE parse_type(parserstate *state) { type_param ::= tUIDENT (module_type_params == false) */ - VALUE parse_type_params(parserstate *state, range *rg, bool module_type_params) { VALUE params = rb_ary_new(); @@ -2267,13 +2266,11 @@ VALUE parse_module_members(parserstate *state) { } /* - module_decl ::= {`module`} module_name module_type_params module_members - | {`module`} module_name module_type_params `:` module_self_types module_members + module_decl ::= {module_name} module_type_params module_members + | {module_name} module_name module_type_params `:` module_self_types module_members */ -VALUE parse_module_decl(parserstate *state, position comment_pos, VALUE annotations) { +VALUE parse_module_decl0(parserstate *state, range keyword_range, VALUE module_name, range name_range, VALUE comment, VALUE annotations) { range decl_range; - range keyword_range; - range name_range; range end_range; range type_params_range; range colon_range; @@ -2281,15 +2278,8 @@ VALUE parse_module_decl(parserstate *state, position comment_pos, VALUE annotati parser_push_typevar_table(state, true); - position start = state->current_token.range.start; - comment_pos = nonnull_pos_or(comment_pos, start); - VALUE comment = get_comment(state, comment_pos.line); + decl_range.start = keyword_range.start; - keyword_range = state->current_token.range; - decl_range.start = state->current_token.range.start; - - parser_advance(state); - VALUE module_name = parse_type_name(state, CLASS_NAME, &name_range); VALUE type_params = parse_type_params(state, &type_params_range, true); VALUE self_types = rb_ary_new(); @@ -2332,6 +2322,46 @@ VALUE parse_module_decl(parserstate *state, position comment_pos, VALUE annotati ); } +/* + module_decl ::= {`module`} module_name `=` old_module_name + | {`module`} module_name module_decl0 + +*/ +VALUE parse_module_decl(parserstate *state, position comment_pos, VALUE annotations) { + range keyword_range = state->current_token.range; + range module_name_range; + + comment_pos = nonnull_pos_or(comment_pos, state->current_token.range.start); + VALUE comment = get_comment(state, comment_pos.line); + + parser_advance(state); + VALUE module_name = parse_type_name(state, CLASS_NAME, &module_name_range); + + if (state->next_token.type == pEQ) { + range eq_range = state->next_token.range; + parser_advance(state); + parser_advance(state); + + range old_name_range; + VALUE old_name = parse_type_name(state, CLASS_NAME, &old_name_range); + + range decl_range; + decl_range.start = keyword_range.start; + decl_range.end = old_name_range.end; + + VALUE location = rbs_new_location(state->buffer, decl_range); + rbs_loc *loc = rbs_check_location(location); + rbs_loc_add_required_child(loc, rb_intern("keyword"), keyword_range); + rbs_loc_add_required_child(loc, rb_intern("new_name"), module_name_range); + rbs_loc_add_required_child(loc, rb_intern("eq"), eq_range); + rbs_loc_add_optional_child(loc, rb_intern("old_name"), old_name_range); + + return rbs_ast_decl_module_alias(module_name, old_name, location, comment); + } else { + return parse_module_decl0(state, keyword_range, module_name, module_name_range, comment, annotations); + } +} + /* class_decl_super ::= {} `<` | {<>} @@ -2368,35 +2398,25 @@ VALUE parse_class_decl_super(parserstate *state, range *lt_range) { } /* - class_decl ::= {`class`} class_name type_params class_decl_super class_members <`end`> + class_decl ::= {class_name} type_params class_decl_super class_members <`end`> */ -VALUE parse_class_decl(parserstate *state, position comment_pos, VALUE annotations) { +VALUE parse_class_decl0(parserstate *state, range keyword_range, VALUE name, range name_range, VALUE comment, VALUE annotations) { range decl_range; - range keyword_range; - range name_range; range end_range; range type_params_range; range lt_range; - VALUE name; VALUE type_params; VALUE super; VALUE members; - VALUE comment; VALUE location; rbs_loc *loc; parser_push_typevar_table(state, true); - decl_range.start = state->current_token.range.start; - keyword_range = state->current_token.range; + decl_range.start = keyword_range.start; - comment_pos = nonnull_pos_or(comment_pos, decl_range.start); - comment = get_comment(state, comment_pos.line); - - parser_advance(state); - name = parse_type_name(state, CLASS_NAME, &name_range); type_params = parse_type_params(state, &type_params_range, true); super = parse_class_decl_super(state, <_range); members = parse_module_members(state); @@ -2426,6 +2446,45 @@ VALUE parse_class_decl(parserstate *state, position comment_pos, VALUE annotatio ); } +/* + class_decl ::= {`class`} class_name `=` + | {`class`} class_name +*/ +VALUE parse_class_decl(parserstate *state, position comment_pos, VALUE annotations) { + range keyword_range = state->current_token.range; + range class_name_range; + + comment_pos = nonnull_pos_or(comment_pos, state->current_token.range.start); + VALUE comment = get_comment(state, comment_pos.line); + + parser_advance(state); + VALUE class_name = parse_type_name(state, CLASS_NAME, &class_name_range); + + if (state->next_token.type == pEQ) { + range eq_range = state->next_token.range; + parser_advance(state); + parser_advance(state); + + range old_name_range; + VALUE old_name = parse_type_name(state, CLASS_NAME, &old_name_range); + + range decl_range; + decl_range.start = keyword_range.start; + decl_range.end = old_name_range.end; + + VALUE location = rbs_new_location(state->buffer, decl_range); + rbs_loc *loc = rbs_check_location(location); + rbs_loc_add_required_child(loc, rb_intern("keyword"), keyword_range); + rbs_loc_add_required_child(loc, rb_intern("new_name"), class_name_range); + rbs_loc_add_required_child(loc, rb_intern("eq"), eq_range); + rbs_loc_add_optional_child(loc, rb_intern("old_name"), old_name_range); + + return rbs_ast_decl_class_alias(class_name, old_name, location, comment); + } else { + return parse_class_decl0(state, keyword_range, class_name, class_name_range, comment, annotations); + } +} + /* nested_decl ::= {} | {} diff --git a/ext/rbs_extension/ruby_objs.c b/ext/rbs_extension/ruby_objs.c index 2db796771..baa528a4a 100644 --- a/ext/rbs_extension/ruby_objs.c +++ b/ext/rbs_extension/ruby_objs.c @@ -402,6 +402,34 @@ VALUE rbs_ast_decl_module(VALUE name, VALUE type_params, VALUE self_types, VALUE ); } +VALUE rbs_ast_decl_class_alias(VALUE new_name, VALUE old_name, VALUE location, VALUE comment) { + VALUE args = rb_hash_new(); + rb_hash_aset(args, ID2SYM(rb_intern("new_name")), new_name); + rb_hash_aset(args, ID2SYM(rb_intern("old_name")), old_name); + rb_hash_aset(args, ID2SYM(rb_intern("location")), location); + rb_hash_aset(args, ID2SYM(rb_intern("comment")), comment); + + return CLASS_NEW_INSTANCE( + RBS_AST_Declarations_ClassAlias, + 1, + &args + ); +} + +VALUE rbs_ast_decl_module_alias(VALUE new_name, VALUE old_name, VALUE location, VALUE comment) { + VALUE args = rb_hash_new(); + rb_hash_aset(args, ID2SYM(rb_intern("new_name")), new_name); + rb_hash_aset(args, ID2SYM(rb_intern("old_name")), old_name); + rb_hash_aset(args, ID2SYM(rb_intern("location")), location); + rb_hash_aset(args, ID2SYM(rb_intern("comment")), comment); + + return CLASS_NEW_INSTANCE( + RBS_AST_Declarations_ModuleAlias, + 1, + &args + ); +} + VALUE rbs_ast_members_method_definition_overload(VALUE annotations, VALUE method_type) { VALUE args = rb_hash_new(); rb_hash_aset(args, ID2SYM(rb_intern("annotations")), annotations); diff --git a/ext/rbs_extension/ruby_objs.h b/ext/rbs_extension/ruby_objs.h index c29b3a020..462d2c95d 100644 --- a/ext/rbs_extension/ruby_objs.h +++ b/ext/rbs_extension/ruby_objs.h @@ -15,6 +15,8 @@ VALUE rbs_ast_decl_global(VALUE name, VALUE type, VALUE location, VALUE comment) VALUE rbs_ast_decl_interface(VALUE name, VALUE type_params, VALUE members, VALUE annotations, VALUE location, VALUE comment); VALUE rbs_ast_decl_module_self(VALUE name, VALUE args, VALUE location); VALUE rbs_ast_decl_module(VALUE name, VALUE type_params, VALUE self_types, VALUE members, VALUE annotations, VALUE location, VALUE comment); +VALUE rbs_ast_decl_module_alias(VALUE new_name, VALUE old_name, VALUE location, VALUE comment); +VALUE rbs_ast_decl_class_alias(VALUE new_name, VALUE old_name, VALUE location, VALUE comment); VALUE rbs_ast_members_alias(VALUE new_name, VALUE old_name, VALUE kind, VALUE annotations, VALUE location, VALUE comment); VALUE rbs_ast_members_attribute(VALUE klass, VALUE name, VALUE type, VALUE ivar_name, VALUE kind, VALUE annotations, VALUE location, VALUE comment, VALUE visibility); VALUE rbs_ast_members_method_definition(VALUE name, VALUE kind, VALUE overloads, VALUE annotations, VALUE location, VALUE comment, VALUE overloading, VALUE visibility); diff --git a/test/rbs/signature_parsing_test.rb b/test/rbs/signature_parsing_test.rb index 517cade4c..dc62fa4b6 100644 --- a/test/rbs/signature_parsing_test.rb +++ b/test/rbs/signature_parsing_test.rb @@ -2053,4 +2053,40 @@ class Foo[X < string] EOF end end + + def test_module_alias_decl + Parser.parse_signature(<<~EOF).yield_self do |decls| + module RBS::Kernel = Kernel + EOF + + decls[0].tap do |decl| + assert_instance_of Declarations::ModuleAlias, decl + + assert_equal TypeName("RBS::Kernel"), decl.new_name + assert_equal TypeName("Kernel"), decl.old_name + assert_equal "module", decl.location[:keyword].source + assert_equal "RBS::Kernel", decl.location[:new_name].source + assert_equal "=", decl.location[:eq].source + assert_equal "Kernel", decl.location[:old_name].source + end + end + end + + def test_class_alias_decl + Parser.parse_signature(<<~EOF).yield_self do |decls| + class RBS::Object = Object + EOF + + decls[0].tap do |decl| + assert_instance_of Declarations::ClassAlias, decl + + assert_equal TypeName("RBS::Object"), decl.new_name + assert_equal TypeName("Object"), decl.old_name + assert_equal "class", decl.location[:keyword].source + assert_equal "RBS::Object", decl.location[:new_name].source + assert_equal "=", decl.location[:eq].source + assert_equal "Object", decl.location[:old_name].source + end + end + end end From 68027acbc012bcd9152629e571caf89af6c9a7d5 Mon Sep 17 00:00:00 2001 From: Soutaro Matsumoto Date: Mon, 30 Jan 2023 14:33:54 +0900 Subject: [PATCH 3/9] Fix Environment --- lib/rbs/environment.rb | 278 +++++++++++++++++++++++++---- lib/rbs/errors.rb | 10 ++ sig/constant.rbs | 2 +- sig/definition.rbs | 2 +- sig/environment.rbs | 83 ++++++++- sig/errors.rbs | 8 + sig/resolver/constant_resolver.rbs | 3 +- sig/validator.rbs | 2 +- test/rbs/environment_test.rb | 61 +++++++ 9 files changed, 402 insertions(+), 47 deletions(-) diff --git a/lib/rbs/environment.rb b/lib/rbs/environment.rb index 49e7d2692..4818cc7b2 100644 --- a/lib/rbs/environment.rb +++ b/lib/rbs/environment.rb @@ -9,6 +9,7 @@ class Environment attr_reader :type_alias_decls attr_reader :constant_decls attr_reader :global_decls + attr_reader :class_alias_decls module ContextUtil def calculate_context(decls) @@ -121,6 +122,24 @@ def context end end + class ModuleAliasEntry < SingleEntry + end + + class ClassAliasEntry < SingleEntry + end + + class InterfaceEntry < SingleEntry + end + + class TypeAliasEntry < SingleEntry + end + + class ConstantEntry < SingleEntry + end + + class GlobalEntry < SingleEntry + end + def initialize @buffers = [] @declarations = [] @@ -130,6 +149,8 @@ def initialize @type_alias_decls = {} @constant_decls = {} @global_decls = {} + @class_alias_decls = {} + @normalize_module_name_cache = {} end def initialize_copy(other) @@ -141,6 +162,7 @@ def initialize_copy(other) @type_alias_decls = other.type_alias_decls.dup @constant_decls = other.constant_decls.dup @global_decls = other.global_decls.dup + @class_alias_decls = other.class_alias_decls.dup end def self.from_loader(loader) @@ -158,7 +180,7 @@ def type_alias_name?(name) end def module_name?(name) - class_decls.key?(name) + class_decls.key?(name) || class_alias_decls.key?(name) end def type_name?(name) @@ -183,12 +205,131 @@ def module_decl?(name) class_decls[name].is_a?(ModuleEntry) end - def cache_name(cache, name:, decl:, outer:) - if cache.key?(name) - raise DuplicatedDeclarationError.new(_ = name, _ = decl, _ = cache[name].decl) + def module_alias?(name) + if decl = class_alias_decls[name] + decl.decl.is_a?(AST::Declarations::ModuleAlias) + else + false end + end + + def class_alias?(name) + if decl = class_alias_decls[name] + decl.decl.is_a?(AST::Declarations::ClassAlias) + else + false + end + end + + def class_entry(type_name) + case + when (class_entry = class_decls[type_name]).is_a?(ClassEntry) + class_entry + when (class_alias = class_alias_decls[type_name]).is_a?(ClassAliasEntry) + class_alias + end + end + + def module_entry(type_name) + case + when (module_entry = class_decls[type_name]).is_a?(ModuleEntry) + module_entry + when (module_alias = class_alias_decls[type_name]).is_a?(ModuleAliasEntry) + module_alias + end + end + + def normalized_class_entry(type_name) + entry = class_entry(normalize_module_name(type_name)) + case entry + when ClassEntry, nil + entry + when ClassAliasEntry + raise + end + end + + def normalized_module_entry(type_name) + entry = module_entry(normalize_module_name(type_name)) + case entry + when ModuleEntry, nil + entry + when ModuleAliasEntry + raise + end + end + + def module_class_entry(type_name) + class_entry(type_name) || module_entry(type_name) + end + + def normalized_module_class_entry(type_name) + normalized_class_entry(type_name) || normalized_module_entry(type_name) + end + + def constant_entry(type_name) + class_entry(type_name) || module_entry(type_name) || constant_decls[type_name] + end + + def normalize_module_name(name) + normalize_module_name?(name) or name + end + + def normalize_module_name?(name) + raise "Class/module name is expected: #{name}" unless name.class? + name = name.absolute! if name.relative! + + if @normalize_module_name_cache.key?(name) + case norm = @normalize_module_name_cache[name] + when TypeName + return norm + when nil + return nil + when false + entry = module_class_entry(name) + case entry + when ClassAliasEntry, ModuleAliasEntry + raise CyclicClassAliasDefinitionError.new(entry) + else + # This cannot happen because the recursion starts with an alias entry + raise + end + end + end + + @normalize_module_name_cache[name] = false + + entry = constant_entry(name) + case entry + when ClassEntry, ModuleEntry + @normalize_module_name_cache[name] = entry.name + entry.name + + when ClassAliasEntry, ModuleAliasEntry + old_name = entry.decl.old_name + if old_name.namespace.empty? + @normalize_module_name_cache[name] = normalize_module_name?(old_name) + else + parent = old_name.namespace.to_type_name + normalized_parent = normalize_module_name(parent) + + @normalize_module_name_cache[name] = + if normalized_parent == parent + normalize_module_name?(old_name) + else + normalize_module_name?( + TypeName.new(name: old_name.name, namespace: normalized_parent.to_namespace) + ) + end + end + + when ConstantEntry + raise "#{name} is a constant name" - cache[name] = SingleEntry.new(name: name, decl: decl, outer: outer) + else + @normalize_module_name_cache.delete(name) + nil + end end def insert_decl(decl, outer:, namespace:) @@ -196,8 +337,10 @@ def insert_decl(decl, outer:, namespace:) when AST::Declarations::Class, AST::Declarations::Module name = decl.name.with_prefix(namespace) - if constant_decl?(name) - raise DuplicatedDeclarationError.new(name, decl, constant_decls[name].decl) + if cdecl = constant_entry(name) + if cdecl.is_a?(ConstantEntry) || cdecl.is_a?(ModuleAliasEntry) || cdecl.is_a?(ClassAliasEntry) + raise DuplicatedDeclarationError.new(name, decl, cdecl.decl) + end end unless class_decls.key?(name) @@ -213,12 +356,8 @@ def insert_decl(decl, outer:, namespace:) case when decl.is_a?(AST::Declarations::Module) && existing_entry.is_a?(ModuleEntry) - # @type var existing_entry: ModuleEntry - # @type var decl: AST::Declarations::Module existing_entry.insert(decl: decl, outer: outer) when decl.is_a?(AST::Declarations::Class) && existing_entry.is_a?(ClassEntry) - # @type var existing_entry: ClassEntry - # @type var decl: AST::Declarations::Class existing_entry.insert(decl: decl, outer: outer) else raise DuplicatedDeclarationError.new(name, decl, existing_entry.decls[0].decl) @@ -231,22 +370,62 @@ def insert_decl(decl, outer:, namespace:) end when AST::Declarations::Interface - cache_name interface_decls, name: decl.name.with_prefix(namespace), decl: decl, outer: outer + name = decl.name.with_prefix(namespace) + + if interface_entry = interface_decls[name] + DuplicatedDeclarationError.new(name, decl, interface_entry.decl) + end + + interface_decls[name] = InterfaceEntry.new(name: name, decl: decl, outer: outer) when AST::Declarations::TypeAlias - cache_name type_alias_decls, name: decl.name.with_prefix(namespace), decl: decl, outer: outer + name = decl.name.with_prefix(namespace) + + if entry = type_alias_decls[name] + DuplicatedDeclarationError.new(name, decl, entry.decl) + end + + type_alias_decls[name] = TypeAliasEntry.new(name: name, decl: decl, outer: outer) when AST::Declarations::Constant name = decl.name.with_prefix(namespace) - if module_name?(name) - raise DuplicatedDeclarationError.new(name, decl, class_decls[name].decls[0].decl) + if entry = constant_entry(name) + case entry + when ClassAliasEntry, ModuleAliasEntry, ConstantEntry + raise DuplicatedDeclarationError.new(name, decl, entry.decl) + when ClassEntry, ModuleEntry + raise DuplicatedDeclarationError.new(name, decl, *entry.decls.map(&:decl)) + end end - cache_name constant_decls, name: name, decl: decl, outer: outer + constant_decls[name] = ConstantEntry.new(name: name, decl: decl, outer: outer) when AST::Declarations::Global - cache_name global_decls, name: decl.name, decl: decl, outer: outer + if entry = global_decls[decl.name] + raise DuplicatedDeclarationError.new(name, decl, entry.decl) + end + + global_decls[decl.name] = GlobalEntry.new(name: decl.name, decl: decl, outer: outer) + + when AST::Declarations::ClassAlias, AST::Declarations::ModuleAlias + name = decl.new_name.with_prefix(namespace) + + if entry = constant_entry(name) + case entry + when ClassAliasEntry, ModuleAliasEntry, ConstantEntry + raise DuplicatedDeclarationError.new(name, decl, entry.decl) + when ClassEntry, ModuleEntry + raise DuplicatedDeclarationError.new(name, decl, *entry.decls.map(&:decl)) + end + end + + case decl + when AST::Declarations::ClassAlias + class_alias_decls[name] = ClassAliasEntry.new(name: name, decl: decl, outer: outer) + when AST::Declarations::ModuleAlias + class_alias_decls[name] = ModuleAliasEntry.new(name: name, decl: decl, outer: outer) + end end end @@ -277,6 +456,21 @@ def resolve_type_names(only: nil) env end + def resolver_context(*nesting) + nesting.inject(nil) {|context, decl| #$ Resolver::context + append_context(context, decl) + } + end + + def append_context(context, decl) + if (_, last = context) + last or raise + [context, last + decl.name] + else + [nil, decl.name.absolute!] + end + end + def resolve_declaration(resolver, decl, outer:, prefix:) if decl.is_a?(AST::Declarations::Global) # @type var decl: AST::Declarations::Global @@ -288,24 +482,18 @@ def resolve_declaration(resolver, decl, outer:, prefix:) ) end - nesting = [*outer, decl] #: Array[module_decl] - context = nesting.inject(nil) {|context, decl| #$ Resolver::context - if (_, last = context) - last or raise - [context, last + decl.name] - else - [nil, decl.name.absolute!] - end - } - outer_context = context&.first + context = resolver_context(*outer) case decl when AST::Declarations::Class + outer_context = context + inner_context = append_context(outer_context, decl) + outer_ = outer + [decl] prefix_ = prefix + decl.name.to_namespace AST::Declarations::Class.new( name: decl.name.with_prefix(prefix), - type_params: resolve_type_params(resolver, decl.type_params, context: context), + type_params: resolve_type_params(resolver, decl.type_params, context: inner_context), super_class: decl.super_class&.yield_self do |super_class| AST::Declarations::Class::Super.new( name: absolute_type_name(resolver, super_class.name, context: outer_context), @@ -316,7 +504,7 @@ def resolve_declaration(resolver, decl, outer:, prefix:) members: decl.members.map do |member| case member when AST::Members::Base - resolve_member(resolver, member, context: context) + resolve_member(resolver, member, context: inner_context) when AST::Declarations::Base resolve_declaration( resolver, @@ -332,23 +520,27 @@ def resolve_declaration(resolver, decl, outer:, prefix:) annotations: decl.annotations, comment: decl.comment ) + when AST::Declarations::Module + outer_context = context + inner_context = append_context(outer_context, decl) + outer_ = outer + [decl] prefix_ = prefix + decl.name.to_namespace AST::Declarations::Module.new( name: decl.name.with_prefix(prefix), - type_params: resolve_type_params(resolver, decl.type_params, context: context), + type_params: resolve_type_params(resolver, decl.type_params, context: inner_context), self_types: decl.self_types.map do |module_self| AST::Declarations::Module::Self.new( - name: absolute_type_name(resolver, module_self.name, context: context), - args: module_self.args.map {|type| absolute_type(resolver, type, context: context) }, + name: absolute_type_name(resolver, module_self.name, context: inner_context), + args: module_self.args.map {|type| absolute_type(resolver, type, context: inner_context) }, location: module_self.location ) end, members: decl.members.map do |member| case member when AST::Members::Base - resolve_member(resolver, member, context: context) + resolve_member(resolver, member, context: inner_context) when AST::Declarations::Base resolve_declaration( resolver, @@ -364,6 +556,7 @@ def resolve_declaration(resolver, decl, outer:, prefix:) annotations: decl.annotations, comment: decl.comment ) + when AST::Declarations::Interface AST::Declarations::Interface.new( name: decl.name.with_prefix(prefix), @@ -375,6 +568,7 @@ def resolve_declaration(resolver, decl, outer:, prefix:) location: decl.location, annotations: decl.annotations ) + when AST::Declarations::TypeAlias AST::Declarations::TypeAlias.new( name: decl.name.with_prefix(prefix), @@ -392,6 +586,22 @@ def resolve_declaration(resolver, decl, outer:, prefix:) location: decl.location, comment: decl.comment ) + + when AST::Declarations::ClassAlias + AST::Declarations::ClassAlias.new( + new_name: decl.new_name.with_prefix(prefix), + old_name: absolute_type_name(resolver, decl.old_name, context: context), + location: decl.location, + comment: decl.comment + ) + + when AST::Declarations::ModuleAlias + AST::Declarations::ModuleAlias.new( + new_name: decl.new_name.with_prefix(prefix), + old_name: absolute_type_name(resolver, decl.old_name, context: context), + location: decl.location, + comment: decl.comment + ) end end @@ -520,7 +730,7 @@ def absolute_type(resolver, type, context:) end def inspect - ivars = %i[@declarations @class_decls @interface_decls @alias_decls @constant_decls @global_decls] + ivars = %i[@declarations @class_decls @class_alias_decls @interface_decls @type_alias_decls @constant_decls @global_decls] "\#" end diff --git a/lib/rbs/errors.rb b/lib/rbs/errors.rb index 2302c2374..b28543e70 100644 --- a/lib/rbs/errors.rb +++ b/lib/rbs/errors.rb @@ -447,4 +447,14 @@ def initialize(type_name:, method_name:, params:, location:) super "#{Location.to_string(location)}: Cyclic type parameter bound is prohibited" end end + + class CyclicClassAliasDefinitionError < BaseError + attr_reader :alias_entry + + def initialize(entry) + @alias_entry = entry + + super "#{Location.to_string(entry.decl.location&.[](:old_name))}: A #{alias_entry.decl.new_name} is a cyclic definition" + end + end end diff --git a/sig/constant.rbs b/sig/constant.rbs index d01908ed7..bf1d5796e 100644 --- a/sig/constant.rbs +++ b/sig/constant.rbs @@ -2,7 +2,7 @@ module RBS class Constant type constant_entry = Environment::ClassEntry | Environment::ModuleEntry - | Environment::SingleEntry[TypeName, AST::Declarations::Constant] + | Environment::ConstantEntry attr_reader name: TypeName diff --git a/sig/definition.rbs b/sig/definition.rbs index 743810e27..7079688d9 100644 --- a/sig/definition.rbs +++ b/sig/definition.rbs @@ -119,7 +119,7 @@ module RBS end type self_type = Types::ClassSingleton | Types::ClassInstance | Types::Interface - type definition_entry = Environment::ModuleEntry | Environment::ClassEntry | Environment::SingleEntry[TypeName, AST::Declarations::Interface] + type definition_entry = Environment::ModuleEntry | Environment::ClassEntry | Environment::InterfaceEntry attr_reader type_name: TypeName attr_reader entry: definition_entry diff --git a/sig/environment.rbs b/sig/environment.rbs index 52d9e2ed8..9fe4644a8 100644 --- a/sig/environment.rbs +++ b/sig/environment.rbs @@ -73,23 +73,43 @@ module RBS def context: () -> Resolver::context end + class ModuleAliasEntry < SingleEntry[TypeName, AST::Declarations::ModuleAlias] + end + + class ClassAliasEntry < SingleEntry[TypeName, AST::Declarations::ClassAlias] + end + + class InterfaceEntry < SingleEntry[TypeName, AST::Declarations::Interface] + end + + class TypeAliasEntry < SingleEntry[TypeName, AST::Declarations::TypeAlias] + end + + class ConstantEntry < SingleEntry[TypeName, AST::Declarations::Constant] + end + + class GlobalEntry < SingleEntry[Symbol, AST::Declarations::Global] + end + # Top level declarations attr_reader declarations: Array[AST::Declarations::t] # Class declarations attr_reader class_decls: Hash[TypeName, ModuleEntry | ClassEntry] + attr_reader class_alias_decls: Hash[TypeName, ModuleAliasEntry | ClassAliasEntry] + # Interface declarations - attr_reader interface_decls: Hash[TypeName, SingleEntry[TypeName, AST::Declarations::Interface]] + attr_reader interface_decls: Hash[TypeName, InterfaceEntry] # Type alias declarations - attr_reader type_alias_decls: Hash[TypeName, SingleEntry[TypeName, AST::Declarations::TypeAlias]] + attr_reader type_alias_decls: Hash[TypeName, TypeAliasEntry] # Constant declarations - attr_reader constant_decls: Hash[TypeName, SingleEntry[TypeName, AST::Declarations::Constant]] + attr_reader constant_decls: Hash[TypeName, ConstantEntry] # Global declarations - attr_reader global_decls: Hash[Symbol, SingleEntry[Symbol, AST::Declarations::Global]] + attr_reader global_decls: Hash[Symbol, GlobalEntry] def initialize: () -> void @@ -160,9 +180,59 @@ module RBS # def module_decl?: (TypeName) -> bool + # Returns true if the type name is a module alias + # + def module_alias?: (TypeName) -> bool + + # Returns true if the type name is a class alias + def class_alias?: (TypeName) -> bool + + def class_entry: (TypeName) -> (ClassEntry | ClassAliasEntry | nil) + + # Returns ClassEntry if the class definition is found, normalized in case of alias + # + def normalized_class_entry: (TypeName) -> ClassEntry? + + def module_entry: (TypeName) -> (ModuleEntry | ModuleAliasEntry | nil) + + # Returns ModuleEntry if the module definition is found, normalized in case of alias + # + def normalized_module_entry: (TypeName) -> ModuleEntry? + + def constant_entry: (TypeName) -> (ClassEntry | ClassAliasEntry | ModuleEntry | ModuleAliasEntry | ConstantEntry | nil) + + def module_class_entry: (TypeName) -> (ClassEntry | ClassAliasEntry | ModuleEntry | ModuleAliasEntry | nil) + + def normalized_module_class_entry: (TypeName) -> (ClassEntry | ModuleEntry | nil) + + @normalize_module_name_cache: Hash[TypeName, TypeName | false | nil] + + # Returns the original module name that is defined with `module` declaration + # + # * Calls `#absolute!` for relative module names + # * Raises an error if given type name is not module + # * Returns nil if the name cannot be normalized to a class/module + # + def normalize_module_name?: (TypeName) -> TypeName? + + # Returns the original module name that is defined with `module` declaration + # + # * Raises an error if given type name is not module + # * Calls `#absolute!` for relative module names + # * Returns the name itself if the name cannot be normalized to a class/module + # + def normalize_module_name: (TypeName) -> TypeName + + # Runs generics type params validation over each class definitions + def validate_type_params: () -> void + private - def cache_name: [N, D] (Hash[N, SingleEntry[N, D]] cache, name: N, decl: D, outer: Array[module_decl]) -> SingleEntry[N, D] + # Returns a context for inside given decls + # + def resolver_context: (*module_decl) -> Resolver::context + + def append_context: (Resolver::context, module_decl) -> Resolver::context def resolve_declaration: (Resolver::TypeNameResolver resolver, AST::Declarations::t decl, outer: Array[module_decl], prefix: Namespace) -> AST::Declarations::t @@ -172,9 +242,6 @@ module RBS def resolve_type_params: (Resolver::TypeNameResolver resolver, Array[AST::TypeParam], context: Resolver::context) -> Array[AST::TypeParam] - # Runs generics type params validation over each class definitions - def validate_type_params: () -> void - def absolute_type: (Resolver::TypeNameResolver, Types::t, context: Resolver::context) -> Types::t def absolute_type_name: (Resolver::TypeNameResolver, TypeName, context: Resolver::context) -> TypeName diff --git a/sig/errors.rbs b/sig/errors.rbs index e00b44e76..c4cc04c4b 100644 --- a/sig/errors.rbs +++ b/sig/errors.rbs @@ -280,4 +280,12 @@ module RBS def initialize: (type_name: TypeName, method_name: Symbol?, params: Array[AST::TypeParam], location: Location[untyped, untyped]?) -> void end + + # A module/class alias declaration is cyclic + # + class CyclicClassAliasDefinitionError < BaseError + attr_reader alias_entry: Environment::ModuleAliasEntry | Environment::ClassAliasEntry + + def initialize: (Environment::ModuleAliasEntry | Environment::ClassAliasEntry) -> void + end end diff --git a/sig/resolver/constant_resolver.rbs b/sig/resolver/constant_resolver.rbs index ca6cde3f6..c7d2629a8 100644 --- a/sig/resolver/constant_resolver.rbs +++ b/sig/resolver/constant_resolver.rbs @@ -37,7 +37,7 @@ module RBS def constant_of_module: (TypeName name, Environment::ClassEntry | Environment::ModuleEntry) -> Constant - def constant_of_constant: (TypeName name, Environment::SingleEntry[TypeName, AST::Declarations::Constant]) -> Constant + def constant_of_constant: (TypeName name, Environment::ConstantEntry) -> Constant end attr_reader builder: DefinitionBuilder @@ -65,7 +65,6 @@ module RBS # ^ <- constant_name # ``` # - # Find the def resolve_child: (TypeName module_name, Symbol constant_name) -> Constant? # Returns the table of all constants accessible with `::` (colon2) operator. diff --git a/sig/validator.rbs b/sig/validator.rbs index 6f7c6adeb..2f296f482 100644 --- a/sig/validator.rbs +++ b/sig/validator.rbs @@ -25,7 +25,7 @@ module RBS # # It yields the rhs type if block is given, so that you can validate the rhs type. # - def validate_type_alias: (entry: Environment::SingleEntry[TypeName, AST::Declarations::TypeAlias]) ?{ (Types::t rhs_type) -> void } -> void + def validate_type_alias: (entry: Environment::TypeAliasEntry) ?{ (Types::t rhs_type) -> void } -> void # Validates the type parameters in generic methods. # diff --git a/test/rbs/environment_test.rb b/test/rbs/environment_test.rb index 87c6bf9f5..fe0ee8168 100644 --- a/test/rbs/environment_test.rb +++ b/test/rbs/environment_test.rb @@ -50,6 +50,43 @@ module ::Baz assert_operator env.class_decls, :key?, type_name("::Baz") end + def test_insert_class_module_alias + env = Environment.new + + decls = RBS::Parser.parse_signature(< bool end RBS end + + def test_normalize_module_name + decls = RBS::Parser.parse_signature(<<~EOF) + class Foo + module Bar + module Baz + end + end + end + + module M = Foo::Bar + module N = M::Baz + EOF + + + env = Environment.new() + decls.each do |decl| + env << decl + end + env = env.resolve_type_names + + assert_equal type_name("::Foo::Bar"), env.normalize_module_name(type_name("::M")) + assert_equal type_name("::Foo::Bar::Baz"), env.normalize_module_name(type_name("::N")) + end end From d563c80f0b499808d94b07bf22b8104d30d5b454 Mon Sep 17 00:00:00 2001 From: Soutaro Matsumoto Date: Fri, 20 Jan 2023 17:10:02 +0900 Subject: [PATCH 4/9] Fix TypeNameResolver --- lib/rbs/resolver/type_name_resolver.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/rbs/resolver/type_name_resolver.rb b/lib/rbs/resolver/type_name_resolver.rb index 727748ff7..7d252e35e 100644 --- a/lib/rbs/resolver/type_name_resolver.rb +++ b/lib/rbs/resolver/type_name_resolver.rb @@ -13,6 +13,7 @@ def initialize(env) all_names.merge(env.class_decls.keys) all_names.merge(env.interface_decls.keys) all_names.merge(env.type_alias_decls.keys) + all_names.merge(env.class_alias_decls.keys) end def try_cache(query) From a7bbc5e31b0ceaad4cdeeda44be97e3ae012a2a1 Mon Sep 17 00:00:00 2001 From: Soutaro Matsumoto Date: Fri, 20 Jan 2023 17:10:22 +0900 Subject: [PATCH 5/9] Fix `AncestorBuilder` --- .../definition_builder/ancestor_builder.rb | 32 +++- lib/rbs/errors.rb | 2 +- test/rbs/ancestor_builder_test.rb | 145 ++++++++++++++++++ 3 files changed, 170 insertions(+), 9 deletions(-) diff --git a/lib/rbs/definition_builder/ancestor_builder.rb b/lib/rbs/definition_builder/ancestor_builder.rb index 597164f67..e452abd63 100644 --- a/lib/rbs/definition_builder/ancestor_builder.rb +++ b/lib/rbs/definition_builder/ancestor_builder.rb @@ -190,6 +190,8 @@ def validate_super_class!(type_name, entry) end def one_instance_ancestors(type_name) + type_name = env.normalize_module_name(type_name) + as = one_instance_ancestors_cache[type_name] and return as entry = env.class_decls[type_name] or raise "Unknown name for one_instance_ancestors: #{type_name}" @@ -210,6 +212,8 @@ def one_instance_ancestors(type_name) super_args = [] end + super_name = env.normalize_module_name(super_name) + NoSuperclassFoundError.check!(super_name, env: env, location: primary.decl.location) if super_class InheritModuleError.check!(super_class, env: env) @@ -236,7 +240,12 @@ def one_instance_ancestors(type_name) else entry.self_types.each do |module_self| NoSelfTypeFoundError.check!(module_self, env: env) - self_types.push Definition::Ancestor::Instance.new(name: module_self.name, args: module_self.args, source: module_self) + + module_name = module_self.name + if module_name.class? + module_name = env.normalize_module_name(module_name) + end + self_types.push Definition::Ancestor::Instance.new(name: module_name, args: module_self.args, source: module_self) end end end @@ -253,6 +262,7 @@ def one_instance_ancestors(type_name) end def one_singleton_ancestors(type_name) + type_name = env.normalize_module_name(type_name) as = one_singleton_ancestors_cache[type_name] and return as entry = env.class_decls[type_name] or raise "Unknown name for one_singleton_ancestors: #{type_name}" @@ -270,6 +280,8 @@ def one_singleton_ancestors(type_name) super_name = BuiltinNames::Object.name end + super_name = env.normalize_module_name(super_name) + NoSuperclassFoundError.check!(super_name, env: env, location: primary.decl.location) if super_class InheritModuleError.check!(super_class, env: env) @@ -328,16 +340,18 @@ def mixin_ancestors0(decl, type_name, align_params:, included_modules:, included when AST::Members::Include module_name = member.name module_args = member.args.map {|type| align_params ? type.sub(align_params) : type } - ancestor = Definition::Ancestor::Instance.new(name: module_name, args: module_args, source: member) case when member.name.class? && included_modules MixinClassError.check!(type_name: type_name, env: env, member: member) NoMixinFoundError.check!(member.name, env: env, member: member) - included_modules << ancestor + + module_name = env.normalize_module_name(module_name) + included_modules << Definition::Ancestor::Instance.new(name: module_name, args: module_args, source: member) when member.name.interface? && included_interfaces NoMixinFoundError.check!(member.name, env: env, member: member) - included_interfaces << ancestor + + included_interfaces << Definition::Ancestor::Instance.new(name: module_name, args: module_args, source: member) end when AST::Members::Prepend @@ -345,7 +359,7 @@ def mixin_ancestors0(decl, type_name, align_params:, included_modules:, included MixinClassError.check!(type_name: type_name, env: env, member: member) NoMixinFoundError.check!(member.name, env: env, member: member) - module_name = member.name + module_name = env.normalize_module_name(member.name) module_args = member.args.map {|type| align_params ? type.sub(align_params) : type } prepended_modules << Definition::Ancestor::Instance.new(name: module_name, args: module_args, source: member) @@ -354,16 +368,18 @@ def mixin_ancestors0(decl, type_name, align_params:, included_modules:, included when AST::Members::Extend module_name = member.name module_args = member.args - ancestor = Definition::Ancestor::Instance.new(name: module_name, args: module_args, source: member) case when member.name.class? && extended_modules MixinClassError.check!(type_name: type_name, env: env, member: member) NoMixinFoundError.check!(member.name, env: env, member: member) - extended_modules << ancestor + + module_name = env.normalize_module_name(module_name) + extended_modules << Definition::Ancestor::Instance.new(name: module_name, args: module_args, source: member) when member.name.interface? && extended_interfaces NoMixinFoundError.check!(member.name, env: env, member: member) - extended_interfaces << ancestor + + extended_interfaces << Definition::Ancestor::Instance.new(name: module_name, args: module_args, source: member) end end end diff --git a/lib/rbs/errors.rb b/lib/rbs/errors.rb index b28543e70..713c1f76e 100644 --- a/lib/rbs/errors.rb +++ b/lib/rbs/errors.rb @@ -154,7 +154,7 @@ def initialize(super_decl) end def self.check!(super_decl, env:) - return if env.class_decl?(super_decl.name) + return if env.class_decl?(super_decl.name) || env.class_alias?(super_decl.name) raise new(super_decl) end diff --git a/test/rbs/ancestor_builder_test.rb b/test/rbs/ancestor_builder_test.rb index 755ff38ac..50eb1228d 100644 --- a/test/rbs/ancestor_builder_test.rb +++ b/test/rbs/ancestor_builder_test.rb @@ -576,4 +576,149 @@ class Qux end end end + + def test_alias_class_instance_ancestor + SignatureManager.new do |manager| + manager.files[Pathname("foo.rbs")] = <<~EOF + class Super + end + + class SuperAlias = Super + + module M1 + end + + module M1Alias = M1 + + module M2 + end + + module M2Alias = M2 + + class Foo < SuperAlias + include M1Alias + prepend M2Alias + end + + class Bar = Foo + EOF + + manager.build do |env| + builder = DefinitionBuilder::AncestorBuilder.new(env: env) + + as = builder.one_instance_ancestors(type_name("::Bar")) + + assert_instance_of DefinitionBuilder::AncestorBuilder::OneAncestors, as + + assert_equal type_name("::Foo"), as.type_name + assert_equal type_name("::Super"), as.super_class.name + assert_equal [type_name("::M1")], as.included_modules.map(&:name) + assert_equal [type_name("::M2")], as.prepended_modules.map(&:name) + end + end + end + + def test_alias_class_singleton_ancestor + SignatureManager.new do |manager| + manager.files[Pathname("foo.rbs")] = <<~EOF + class Super + end + + class SuperAlias = Super + + module M1 + end + + module M1Alias = M1 + + class Foo < SuperAlias + extend M1Alias + end + + class Bar = Foo + EOF + + manager.build do |env| + builder = DefinitionBuilder::AncestorBuilder.new(env: env) + + as = builder.one_singleton_ancestors(type_name("::Bar")) + + assert_instance_of DefinitionBuilder::AncestorBuilder::OneAncestors, as + + assert_equal type_name("::Foo"), as.type_name + assert_equal type_name("::Super"), as.super_class.name + assert_equal [type_name("::M1")], as.extended_modules.map(&:name) + end + end + end + + def test_alias_module_instance_ancestor + SignatureManager.new do |manager| + manager.files[Pathname("foo.rbs")] = <<~EOF + module M1 + end + + module M1Alias = M1 + + module M2 + end + + module M2Alias = M2 + + module M3 + end + + module M3Alias = M3 + + module M : M1Alias + include M2Alias + prepend M3Alias + end + + module MAlias = M + EOF + + manager.build do |env| + builder = DefinitionBuilder::AncestorBuilder.new(env: env) + + as = builder.one_instance_ancestors(type_name("::MAlias")) + + assert_instance_of DefinitionBuilder::AncestorBuilder::OneAncestors, as + + assert_equal type_name("::M"), as.type_name + assert_equal [type_name("::M1")], as.self_types.map(&:name) + assert_equal [type_name("::M2")], as.included_modules.map(&:name) + assert_equal [type_name("::M3")], as.prepended_modules.map(&:name) + end + end + end + + def test_alias_module_singleton_ancestor + SignatureManager.new do |manager| + manager.files[Pathname("foo.rbs")] = <<~EOF + module Aliases + module M1 = ::M1 + + module M2 = ::M2 + end + + module M1 + extend Aliases::M2 + end + + module M2 end + EOF + + manager.build do |env| + builder = DefinitionBuilder::AncestorBuilder.new(env: env) + + as = builder.one_singleton_ancestors(type_name("::Aliases::M1")) + + assert_instance_of DefinitionBuilder::AncestorBuilder::OneAncestors, as + + assert_equal type_name("::M1"), as.type_name + assert_equal [type_name("::M2")], as.extended_modules.map(&:name) + end + end + end end From d58f79b0102872c01cb5aa6095e5c5465ce4703c Mon Sep 17 00:00:00 2001 From: Soutaro Matsumoto Date: Tue, 24 Jan 2023 11:29:49 +0900 Subject: [PATCH 6/9] Fix DefinitionBuilder --- lib/rbs/definition_builder.rb | 4 ++ test/rbs/definition_builder_test.rb | 57 +++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/lib/rbs/definition_builder.rb b/lib/rbs/definition_builder.rb index 1d1bc50aa..a4eece16b 100644 --- a/lib/rbs/definition_builder.rb +++ b/lib/rbs/definition_builder.rb @@ -148,6 +148,8 @@ def define_instance(definition, type_name, subst) end def build_instance(type_name) + type_name = env.normalize_module_name(type_name) + try_cache(type_name, cache: instance_cache) do entry = env.class_decls[type_name] or raise "Unknown name for build_instance: #{type_name}" ensure_namespace!(type_name.namespace, location: entry.decls[0].decl.location) @@ -276,6 +278,8 @@ def build_singleton0(type_name) end def build_singleton(type_name) + type_name = env.normalize_module_name(type_name) + try_cache type_name, cache: singleton_cache do entry = env.class_decls[type_name] or raise "Unknown name for build_singleton: #{type_name}" ensure_namespace!(type_name.namespace, location: entry.decls[0].decl.location) diff --git a/test/rbs/definition_builder_test.rb b/test/rbs/definition_builder_test.rb index 9c91530d0..f29752bec 100644 --- a/test/rbs/definition_builder_test.rb +++ b/test/rbs/definition_builder_test.rb @@ -2338,4 +2338,61 @@ class Foo < Foo1 end end end + + def test_build_instance_alias + SignatureManager.new do |manager| + manager.files[Pathname("foo.rbs")] = <<~EOF + class A + end + + class B = A + + module K = Kernel + EOF + + manager.build do |env| + builder = DefinitionBuilder.new(env: env) + + builder.build_instance(type_name("::B")).tap do |definition| + assert_instance_of Definition, definition + assert_equal type_name("::A"), definition.type_name + end + + builder.build_instance(type_name("::K")).tap do |definition| + assert_instance_of Definition, definition + assert_equal type_name("::Kernel"), definition.type_name + end + end + end + end + + def test_build_singleton_alias + SignatureManager.new do |manager| + manager.files[Pathname("foo.rbs")] = <<~EOF + class A + def initialize: (String) -> void + end + + class B = A + + module K = Kernel + EOF + + manager.build do |env| + builder = DefinitionBuilder.new(env: env) + + builder.build_singleton(type_name("::B")).tap do |definition| + assert_instance_of Definition, definition + assert_equal type_name("::A"), definition.type_name + + assert_equal [parse_method_type("(::String) -> ::A")], definition.methods[:new].defs.map(&:type) + end + + builder.build_singleton(type_name("::K")).tap do |definition| + assert_instance_of Definition, definition + assert_equal type_name("::Kernel"), definition.type_name + end + end + end + end end From 56b13842d1cf2b2aea898ee4631eb65ea5bc3df0 Mon Sep 17 00:00:00 2001 From: Soutaro Matsumoto Date: Mon, 30 Jan 2023 14:36:40 +0900 Subject: [PATCH 7/9] Fix Validator --- lib/rbs/cli.rb | 5 +++++ lib/rbs/errors.rb | 18 ++++++++++++++++++ lib/rbs/validator.rb | 20 +++++++++++++++++++- sig/errors.rbs | 15 ++++++++++++++- sig/validator.rbs | 7 +++++++ test/validator_test.rb | 35 +++++++++++++++++++++++++++++++++++ 6 files changed, 98 insertions(+), 2 deletions(-) diff --git a/lib/rbs/cli.rb b/lib/rbs/cli.rb index d8852d664..1d2a45e4a 100644 --- a/lib/rbs/cli.rb +++ b/lib/rbs/cli.rb @@ -467,6 +467,11 @@ def run_validate(args, options) end end + env.class_alias_decls.each do |name, entry| + stdout.puts "Validating class/module alias definition: `#{name}`..." + validator.validate_class_alias(entry: entry) + end + env.interface_decls.each do |name, decl| stdout.puts "Validating interface: `#{name}`..." builder.build_interface(name).each_type do |type| diff --git a/lib/rbs/errors.rb b/lib/rbs/errors.rb index 713c1f76e..000c34a41 100644 --- a/lib/rbs/errors.rb +++ b/lib/rbs/errors.rb @@ -448,6 +448,24 @@ def initialize(type_name:, method_name:, params:, location:) end end + class InconsistentClassModuleAliasError < BaseError + attr_reader :alias_entry + + def initialize(entry) + @alias_entry = entry + + expected_kind, actual_kind = + case entry + when Environment::ModuleAliasEntry + ["module", "class"] + when Environment::ClassAliasEntry + ["class", "module"] + end + + super "#{Location.to_string(entry.decl.location&.[](:old_name))}: A #{expected_kind} `#{entry.decl.new_name}` cannot be an alias of a #{actual_kind} `#{entry.decl.old_name}`" + end + end + class CyclicClassAliasDefinitionError < BaseError attr_reader :alias_entry diff --git a/lib/rbs/validator.rb b/lib/rbs/validator.rb index f98e319cc..cf3e6f829 100644 --- a/lib/rbs/validator.rb +++ b/lib/rbs/validator.rb @@ -33,7 +33,8 @@ def validate_type(type, context:) type_params = case type when Types::ClassInstance - env.class_decls[type.name].type_params + entry = env.normalized_module_class_entry(type.name) or raise + entry.type_params when Types::Interface env.interface_decls[type.name].decl.type_params when Types::Alias @@ -147,6 +148,23 @@ def validate_type_params(params, type_name: , method_name: nil, location:) end end + def validate_class_alias(entry:) + unless env.normalize_module_name?(entry.decl.new_name) + raise NoTypeFoundError.new(type_name: entry.decl.old_name, location: entry.decl.location&.[](:old_name)) + end + + case entry + when Environment::ClassAliasEntry + unless env.class_entry(entry.decl.old_name) + raise InconsistentClassModuleAliasError.new(entry) + end + when Environment::ModuleAliasEntry + unless env.module_entry(entry.decl.old_name) + raise InconsistentClassModuleAliasError.new(entry) + end + end + end + def type_alias_dependency @type_alias_dependency ||= TypeAliasDependency.new(env: env) end diff --git a/sig/errors.rbs b/sig/errors.rbs index c4cc04c4b..22b494e35 100644 --- a/sig/errors.rbs +++ b/sig/errors.rbs @@ -174,7 +174,7 @@ module RBS end # The *overloading* method definition cannot find *non-overloading* method definition - # + # class InvalidOverloadMethodError < DefinitionError attr_reader type_name: TypeName attr_reader method_name: Symbol @@ -281,6 +281,19 @@ module RBS def initialize: (type_name: TypeName, method_name: Symbol?, params: Array[AST::TypeParam], location: Location[untyped, untyped]?) -> void end + # A module/class alias declaration has inconsistent right-hand-side + # + # ```rbs + # module Foo = Object # Error + # class Bar = Kernel # Error + # ``` + # + class InconsistentClassModuleAliasError < BaseError + attr_reader alias_entry: Environment::ModuleAliasEntry | Environment::ClassAliasEntry + + def initialize: (Environment::ModuleAliasEntry | Environment::ClassAliasEntry) -> void + end + # A module/class alias declaration is cyclic # class CyclicClassAliasDefinitionError < BaseError diff --git a/sig/validator.rbs b/sig/validator.rbs index 2f296f482..42813054f 100644 --- a/sig/validator.rbs +++ b/sig/validator.rbs @@ -42,6 +42,13 @@ module RBS # def validate_type_params: (Array[AST::TypeParam] params, type_name: TypeName, ?method_name: Symbol?, location: Location[untyped, untyped]?) -> void + # Validates class alias declaration + # + # - The right hand side can be normalized + # - No mixing alias declaration between class and modules + # + def validate_class_alias: (entry: Environment::ClassAliasEntry | Environment::ModuleAliasEntry) -> void + private # Resolves relative type names to absolute type names in given context. diff --git a/test/validator_test.rb b/test/validator_test.rb index 9dc0d71b3..75b39a73f 100644 --- a/test/validator_test.rb +++ b/test/validator_test.rb @@ -207,4 +207,39 @@ def test_type_alias_unknown_type end end end + + def test_validate_class_alias + SignatureManager.new do |manager| + manager.add_file("bar.rbs", <<-EOF) +class Foo = Kernel + +module Bar = NoSuchClass + +class Baz = Baz + EOF + + manager.build do |env| + resolver = RBS::Resolver::TypeNameResolver.new(env) + validator = RBS::Validator.new(env: env, resolver: resolver) + + env.class_alias_decls[TypeName("::Foo")].tap do |entry| + assert_raises RBS::InconsistentClassModuleAliasError do + validator.validate_class_alias(entry: entry) + end + end + + env.class_alias_decls[TypeName("::Bar")].tap do |entry| + assert_raises RBS::NoTypeFoundError do + validator.validate_class_alias(entry: entry) + end + end + + env.class_alias_decls[TypeName("::Baz")].tap do |entry| + assert_raises RBS::CyclicClassAliasDefinitionError do + validator.validate_class_alias(entry: entry) + end + end + end + end + end end From 3ae6bdb58961675e50fe57154dfd70ac5b5403c9 Mon Sep 17 00:00:00 2001 From: Soutaro Matsumoto Date: Mon, 30 Jan 2023 14:36:50 +0900 Subject: [PATCH 8/9] Fix ConstantResolver --- lib/rbs/resolver/constant_resolver.rb | 30 +++++++--- test/rbs/resolver/constant_resolver_test.rb | 65 +++++++++++++++++++++ 2 files changed, 88 insertions(+), 7 deletions(-) diff --git a/lib/rbs/resolver/constant_resolver.rb b/lib/rbs/resolver/constant_resolver.rb index 7c75de218..372089e10 100644 --- a/lib/rbs/resolver/constant_resolver.rb +++ b/lib/rbs/resolver/constant_resolver.rb @@ -18,20 +18,33 @@ def initialize(environment) end environment.class_decls.each do |name, entry| + constant = constant_of_module(name, entry) + unless name.namespace.empty? parent = name.namespace.to_type_name - table = children_table[parent] or raise - constant = constant_of_module(name, entry) else table = toplevel - constant = constant_of_module(name, entry) end table[name.name] = constant constants_table[name] = constant end + environment.class_alias_decls.each do |name, entry| + normalized_entry = environment.normalized_module_class_entry(name) or raise + constant = constant_of_module(name, normalized_entry) + + # Insert class/module aliases into `children_table` and `toplevel` table + unless name.namespace.empty? + normalized_parent = environment.normalize_module_name?(name.namespace.to_type_name) or raise + table = children_table[normalized_parent] or raise + table[name.name] = constant + else + toplevel[name.name] = constant + end + end + environment.constant_decls.each do |name, entry| unless name.namespace.empty? parent = name.namespace.to_type_name @@ -97,6 +110,8 @@ def resolve_child(module_name, name) end def children(module_name) + module_name = builder.env.normalize_module_name(module_name) + unless child_constants_cache.key?(module_name) load_child_constants(module_name) end @@ -113,6 +128,7 @@ def load_context_constants(context) else constants_from_ancestors(BuiltinNames::Object.name, constants: consts) end + constants_from_context(context, constants: consts) or return constants_itself(context, constants: consts) @@ -151,7 +167,7 @@ def constants_from_context(context, constants:) constants_from_context(parent, constants: constants) or return false if last - consts = table.children(last) or return false + consts = table.children(builder.env.normalize_module_name(last)) or return false constants.merge!(consts) end end @@ -160,14 +176,14 @@ def constants_from_context(context, constants:) end def constants_from_ancestors(module_name, constants:) - entry = builder.env.class_decls[module_name] + entry = builder.env.normalized_module_class_entry(module_name) or raise - if entry.is_a?(Environment::ModuleEntry) + if entry.is_a?(Environment::ClassEntry) || entry.is_a?(Environment::ModuleEntry) constants.merge!(table.children(BuiltinNames::Object.name) || raise) constants.merge!(table.toplevel) end - builder.ancestor_builder.instance_ancestors(module_name).ancestors.reverse_each do |ancestor| + builder.ancestor_builder.instance_ancestors(entry.name).ancestors.reverse_each do |ancestor| if ancestor.is_a?(Definition::Ancestor::Instance) case ancestor.source when AST::Members::Include, :super, nil diff --git a/test/rbs/resolver/constant_resolver_test.rb b/test/rbs/resolver/constant_resolver_test.rb index 05da30e04..96621b80e 100644 --- a/test/rbs/resolver/constant_resolver_test.rb +++ b/test/rbs/resolver/constant_resolver_test.rb @@ -383,4 +383,69 @@ class BasicObject end end end + + def test_alias_constants_toplevel + SignatureManager.new do |manager| + manager.files[Pathname("foo.rbs")] = <<~EOF + class Hello = Object + EOF + + manager.build do |env| + builder = DefinitionBuilder.new(env: env) + resolver = Resolver::ConstantResolver.new(builder: builder) + + resolver.constants(nil).tap do |constants| + assert constants.key?(:Hello) + end + end + end + end + + def test_alias_constants_context + SignatureManager.new do |manager| + manager.files[Pathname("foo.rbs")] = <<~EOF + module M + class Hello = Object + + module M2 + end + end + EOF + + manager.build do |env| + builder = DefinitionBuilder.new(env: env) + resolver = Resolver::ConstantResolver.new(builder: builder) + + resolver.constants([[nil, TypeName("::M")], TypeName("::M::M2")]).tap do |constants| + assert constants.key?(:Hello) + end + end + end + end + + def test_alias_constants_aliased_context + SignatureManager.new do |manager| + manager.files[Pathname("foo.rbs")] = <<~EOF + module M + module M2 + end + end + + module M3 = M + EOF + + manager.build do |env| + builder = DefinitionBuilder.new(env: env) + resolver = Resolver::ConstantResolver.new(builder: builder) + + resolver.constants([nil, TypeName("::M3")]).tap do |constants| + assert constants.key?(:M2) + end + + resolver.constants([nil, TypeName("::M3")]).tap do |constants| + assert constants.key?(:M3) + end + end + end + end end From 5eb5b26c6c437180f238aa954d75f11d1b2637f4 Mon Sep 17 00:00:00 2001 From: Soutaro Matsumoto Date: Mon, 30 Jan 2023 14:36:54 +0900 Subject: [PATCH 9/9] Fixup --- sig/ancestor_graph.rbs | 24 ++++++++++++++++++++++-- test/rbs/locator_test.rb | 20 ++++++++++++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/sig/ancestor_graph.rbs b/sig/ancestor_graph.rbs index 53a404cd6..40933abe8 100644 --- a/sig/ancestor_graph.rbs +++ b/sig/ancestor_graph.rbs @@ -1,4 +1,22 @@ module RBS + # AncestorGraph is a utility class that helps iterating through ancestors and decendants of a class/module + # + # ```ruby + # graph = AncestorGraph.new(env: env, ancestor_builder: ancestor_builder) + # + # graph.each_parent(AncestorGraph::InstanceNode.new(type_name: TypeName("::Object"))) + # graph.each_ancestor(AncestorGraph::InstanceNode.new(type_name: TypeName("::Object"))) + # + # graph.each_child(AncestorGraph::InstanceNode.new(type_name: TypeName("::Object"))) + # graph.each_descendant(AncestorGraph::InstanceNode.new(type_name: TypeName("::Object"))) + # ``` + # + # Note that the class works for class/module declarations. + # All of the *alias* classes/modules are ignored. + # + # * Alias classes/modules doesn't count as a parent nor child + # * Passing alias classes/modules to the method doesn't yield anything + # class AncestorGraph class InstanceNode attr_reader type_name: TypeName @@ -19,8 +37,6 @@ module RBS def initialize: (env: Environment, ?ancestor_builder: DefinitionBuilder::AncestorBuilder) -> void - def build: () -> void - def each_parent: (node) { (node) -> void } -> void | (node) -> Enumerator[node, void] @@ -33,6 +49,10 @@ module RBS def each_descendant: (node, ?yielded: Set[node]) { (node) -> void } -> void | (node) -> Enumerator[node, void] + private + + def build: () -> void + def build_ancestors: (node, DefinitionBuilder::AncestorBuilder::OneAncestors) -> void def register: (parent: node, child: node) -> void diff --git a/test/rbs/locator_test.rb b/test/rbs/locator_test.rb index 71d174280..c50840bfc 100644 --- a/test/rbs/locator_test.rb +++ b/test/rbs/locator_test.rb @@ -124,4 +124,24 @@ def bar: [X < Numeric] () -> X assert_instance_of AST::Declarations::Module, cs[5] end end + + def test_find_class_alias + locator = locator(<<~RBS) + class Foo = Object + + module Bar = Kernel + RBS + + locator.find(line: 1, column: 16).tap do |cs| + assert_equal 2, cs.size + assert_equal :old_name, cs[0] + assert_instance_of AST::Declarations::ClassAlias, cs[1] + end + + locator.find(line: 3, column: 18).tap do |cs| + assert_equal 2, cs.size + assert_equal :old_name, cs[0] + assert_instance_of AST::Declarations::ModuleAlias, cs[1] + end + end end