diff --git a/lib/rbs.rb b/lib/rbs.rb index 48fa0a6ef..b7d623eab 100644 --- a/lib/rbs.rb +++ b/lib/rbs.rb @@ -10,6 +10,7 @@ require "logger" require "tsort" require "strscan" +require "prism" require "rbs/errors" require "rbs/buffer" @@ -24,7 +25,11 @@ require "rbs/ast/members" require "rbs/ast/annotation" require "rbs/ast/visitor" +require "rbs/ast/ruby/helpers/constant_helper" +require "rbs/ast/ruby/helpers/location_helper" +require "rbs/ast/ruby/declarations" require "rbs/source" +require "rbs/inline_parser" require "rbs/environment" require "rbs/environment/use_map" require "rbs/environment/class_entry" diff --git a/lib/rbs/ast/ruby/declarations.rb b/lib/rbs/ast/ruby/declarations.rb new file mode 100644 index 000000000..85b4d3bf8 --- /dev/null +++ b/lib/rbs/ast/ruby/declarations.rb @@ -0,0 +1,86 @@ +# frozen_string_literal: true + +module RBS + module AST + module Ruby + module Declarations + class Base + attr_reader :buffer + + include Helpers::ConstantHelper + include Helpers::LocationHelper + + def initialize(buffer) + @buffer = buffer + end + end + + class ClassDecl < Base + attr_reader :class_name + + attr_reader :members + + attr_reader :node + + def initialize(buffer, name, node) + super(buffer) + @class_name = name + @node = node + @members = [] + end + + def each_decl(&block) + return enum_for(:each_decl) unless block + + @members.each do |member| + if member.is_a?(Base) + yield member + end + end + end + + def super_class = nil + + def type_params = [] + + def location + rbs_location(node.location) + end + end + + class ModuleDecl < Base + attr_reader :module_name + + attr_reader :members + + attr_reader :node + + def initialize(buffer, name, node) + super(buffer) + @module_name = name + @node = node + @members = [] + end + + def each_decl(&block) + return enum_for(:each_decl) unless block + + @members.each do |member| + if member.is_a?(Base) + yield member + end + end + end + + def type_params = [] + + def self_types = [] + + def location + rbs_location(node.location) + end + end + end + end + end +end diff --git a/lib/rbs/ast/ruby/helpers/constant_helper.rb b/lib/rbs/ast/ruby/helpers/constant_helper.rb new file mode 100644 index 000000000..630b610b7 --- /dev/null +++ b/lib/rbs/ast/ruby/helpers/constant_helper.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module RBS + module AST + module Ruby + module Helpers + module ConstantHelper + module_function + + def constant_as_type_name(node) + case node + when Prism::ConstantPathNode, Prism::ConstantReadNode + begin + TypeName.parse(node.full_name) + rescue Prism::ConstantPathNode::DynamicPartsInConstantPathError + nil + end + end + end + end + end + end + end +end diff --git a/lib/rbs/ast/ruby/helpers/location_helper.rb b/lib/rbs/ast/ruby/helpers/location_helper.rb new file mode 100644 index 000000000..677919cc6 --- /dev/null +++ b/lib/rbs/ast/ruby/helpers/location_helper.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module RBS + module AST + module Ruby + module Helpers + module LocationHelper + def rbs_location(location) + Location.new(buffer, location.start_character_offset, location.end_character_offset) + end + end + end + end + end +end diff --git a/lib/rbs/cli/validate.rb b/lib/rbs/cli/validate.rb index d6dc78c1f..f3338f5c5 100644 --- a/lib/rbs/cli/validate.rb +++ b/lib/rbs/cli/validate.rb @@ -170,38 +170,43 @@ def validate_class_module_definition TypeParamDefaultReferenceError.check!(d.type_params) entry.each_decl do |decl| - decl.each_member do |member| - case member - when AST::Members::MethodDefinition - @validator.validate_method_definition(member, type_name: name) - member.overloads.each do |ov| - void_type_context_validator(ov.method_type) - end - when AST::Members::Attribute - void_type_context_validator(member.type) - when AST::Members::Mixin - member.args.each do |arg| - no_self_type_validator(arg) - unless arg.is_a?(Types::Bases::Void) - void_type_context_validator(arg, true) + case decl + when AST::Declarations::Base + decl.each_member do |member| + case member + when AST::Members::MethodDefinition + @validator.validate_method_definition(member, type_name: name) + member.overloads.each do |ov| + void_type_context_validator(ov.method_type) end - end - params = - if member.name.class? - module_decl = @env.normalized_module_entry(member.name) or raise - module_decl.type_params - else - interface_decl = @env.interface_decls.fetch(member.name) - interface_decl.decl.type_params + when AST::Members::Attribute + void_type_context_validator(member.type) + when AST::Members::Mixin + member.args.each do |arg| + no_self_type_validator(arg) + unless arg.is_a?(Types::Bases::Void) + void_type_context_validator(arg, true) + end + end + params = + if member.name.class? + module_decl = @env.normalized_module_entry(member.name) or raise + module_decl.type_params + else + interface_decl = @env.interface_decls.fetch(member.name) + interface_decl.decl.type_params + end + InvalidTypeApplicationError.check!(type_name: member.name, params: params, args: member.args, location: member.location) + when AST::Members::Var + @validator.validate_variable(member) + void_type_context_validator(member.type) + if member.is_a?(AST::Members::ClassVariable) + no_self_type_validator(member.type) end - InvalidTypeApplicationError.check!(type_name: member.name, params: params, args: member.args, location: member.location) - when AST::Members::Var - @validator.validate_variable(member) - void_type_context_validator(member.type) - if member.is_a?(AST::Members::ClassVariable) - no_self_type_validator(member.type) end end + else + raise "Unknown declaration: #{decl.class}" end end rescue BaseError => error diff --git a/lib/rbs/definition_builder.rb b/lib/rbs/definition_builder.rb index 8f20e9d03..d69dadcd7 100644 --- a/lib/rbs/definition_builder.rb +++ b/lib/rbs/definition_builder.rb @@ -452,6 +452,10 @@ def source_location(source, decl) case decl when AST::Declarations::Class decl.super_class&.location + when AST::Ruby::Declarations::ClassDecl + nil + else + raise "Unexpected `:super` source location with #{decl.class}" end else source.location diff --git a/lib/rbs/definition_builder/ancestor_builder.rb b/lib/rbs/definition_builder/ancestor_builder.rb index 8048f2338..0f3198b5c 100644 --- a/lib/rbs/definition_builder/ancestor_builder.rb +++ b/lib/rbs/definition_builder/ancestor_builder.rb @@ -348,68 +348,73 @@ def one_interface_ancestors(type_name) end def mixin_ancestors0(decl, type_name, align_params:, included_modules:, included_interfaces:, extended_modules:, prepended_modules:, extended_interfaces:) - decl.each_mixin do |member| - case member - when AST::Members::Include - module_name = member.name - module_args = member.args.map {|type| align_params ? type.sub(align_params) : type } + case decl + when AST::Declarations::Base + decl.each_mixin do |member| + case member + when AST::Members::Include + module_name = member.name + module_args = member.args.map {|type| align_params ? type.sub(align_params) : type } - 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) + 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) - module_decl = env.normalized_module_entry(module_name) or raise - module_args = AST::TypeParam.normalize_args(module_decl.type_params, module_args) + module_decl = env.normalized_module_entry(module_name) or raise + module_args = AST::TypeParam.normalize_args(module_decl.type_params, module_args) - 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) + 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) - interface_decl = env.interface_decls.fetch(module_name) - module_args = AST::TypeParam.normalize_args(interface_decl.decl.type_params, module_args) + interface_decl = env.interface_decls.fetch(module_name) + module_args = AST::TypeParam.normalize_args(interface_decl.decl.type_params, module_args) - included_interfaces << Definition::Ancestor::Instance.new(name: module_name, args: module_args, source: member) - end + included_interfaces << Definition::Ancestor::Instance.new(name: module_name, args: module_args, source: member) + end - when AST::Members::Prepend - if prepended_modules - MixinClassError.check!(type_name: type_name, env: env, member: member) - NoMixinFoundError.check!(member.name, env: env, member: member) + when AST::Members::Prepend + if prepended_modules + MixinClassError.check!(type_name: type_name, env: env, member: member) + NoMixinFoundError.check!(member.name, env: env, member: member) - module_decl = env.normalized_module_entry(member.name) or raise - module_name = module_decl.name + module_decl = env.normalized_module_entry(member.name) or raise + module_name = module_decl.name - module_args = member.args.map {|type| align_params ? type.sub(align_params) : type } - module_args = AST::TypeParam.normalize_args(module_decl.type_params, module_args) + module_args = member.args.map {|type| align_params ? type.sub(align_params) : type } + module_args = AST::TypeParam.normalize_args(module_decl.type_params, module_args) - prepended_modules << Definition::Ancestor::Instance.new(name: module_name, args: module_args, source: member) - end + prepended_modules << Definition::Ancestor::Instance.new(name: module_name, args: module_args, source: member) + end - when AST::Members::Extend - module_name = member.name - module_args = member.args + when AST::Members::Extend + module_name = member.name + module_args = member.args - 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) + 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) - module_decl = env.normalized_module_entry(module_name) or raise - module_args = AST::TypeParam.normalize_args(module_decl.type_params, module_args) + module_decl = env.normalized_module_entry(module_name) or raise + module_args = AST::TypeParam.normalize_args(module_decl.type_params, module_args) - 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) + 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) - interface_decl = env.interface_decls.fetch(module_name) - module_args = AST::TypeParam.normalize_args(interface_decl.decl.type_params, module_args) + interface_decl = env.interface_decls.fetch(module_name) + module_args = AST::TypeParam.normalize_args(interface_decl.decl.type_params, module_args) - extended_interfaces << Definition::Ancestor::Instance.new(name: module_name, args: module_args, source: member) + extended_interfaces << Definition::Ancestor::Instance.new(name: module_name, args: module_args, source: member) + end end end + when AST::Ruby::Declarations::Base + # noop end end diff --git a/lib/rbs/definition_builder/method_builder.rb b/lib/rbs/definition_builder/method_builder.rb index 6f513b5a7..4f4c08363 100644 --- a/lib/rbs/definition_builder/method_builder.rb +++ b/lib/rbs/definition_builder/method_builder.rb @@ -105,35 +105,37 @@ def build_instance(type_name) Methods.new(type: type).tap do |methods| entry.each_decl do |decl| subst = Substitution.build(decl.type_params.each.map(&:name), args) - each_member_with_accessibility(decl.members) do |member, accessibility| - case member - when AST::Members::MethodDefinition - case member.kind - when :instance - build_method( - methods, - type, - member: member.update(overloads: member.overloads.map {|overload| overload.sub(subst) }), - accessibility: member.visibility || accessibility - ) - when :singleton_instance - build_method( - methods, - type, - member: member.update(overloads: member.overloads.map {|overload| overload.sub(subst) }), - accessibility: :private - ) - end - when AST::Members::AttrReader, AST::Members::AttrWriter, AST::Members::AttrAccessor - if member.kind == :instance - build_attribute(methods, - type, - member: member.update(type: member.type.sub(subst)), - accessibility: member.visibility || accessibility) - end - when AST::Members::Alias - if member.kind == :instance - build_alias(methods, type, member: member) + if decl.is_a?(AST::Declarations::Base) + each_rbs_member_with_accessibility(decl.members) do |member, accessibility| + case member + when AST::Members::MethodDefinition + case member.kind + when :instance + build_method( + methods, + type, + member: member.update(overloads: member.overloads.map {|overload| overload.sub(subst) }), + accessibility: member.visibility || accessibility + ) + when :singleton_instance + build_method( + methods, + type, + member: member.update(overloads: member.overloads.map {|overload| overload.sub(subst) }), + accessibility: :private + ) + end + when AST::Members::AttrReader, AST::Members::AttrWriter, AST::Members::AttrAccessor + if member.kind == :instance + build_attribute(methods, + type, + member: member.update(type: member.type.sub(subst)), + accessibility: member.visibility || accessibility) + end + when AST::Members::Alias + if member.kind == :instance + build_alias(methods, type, member: member) + end end end end @@ -223,7 +225,7 @@ def build_method(methods, type, member:, accessibility:) end end - def each_member_with_accessibility(members, accessibility: :public) + def each_rbs_member_with_accessibility(members, accessibility: :public) members.each do |member| case member when AST::Members::Public diff --git a/lib/rbs/environment.rb b/lib/rbs/environment.rb index 8f0d40cc1..b2f767e16 100644 --- a/lib/rbs/environment.rb +++ b/lib/rbs/environment.rb @@ -265,7 +265,7 @@ def normalize_module_name?(name) @normalize_module_name_cache[name] = normalized_type_name end - def insert_decl(decl, context:, namespace:) + def insert_rbs_decl(decl, context:, namespace:) case decl when AST::Declarations::Class, AST::Declarations::Module name = decl.name.with_prefix(namespace) @@ -299,7 +299,7 @@ def insert_decl(decl, context:, namespace:) inner_context = [context, name] #: Resolver::context inner_namespace = name.to_namespace decl.each_decl do |d| - insert_decl(d, context: inner_context, namespace: inner_namespace) + insert_rbs_decl(d, context: inner_context, namespace: inner_namespace) end when AST::Declarations::Interface @@ -362,24 +362,93 @@ def insert_decl(decl, context:, namespace:) end end + def insert_ruby_decl(decl, context:, namespace:) + case decl + when AST::Ruby::Declarations::ClassDecl + name = decl.class_name.with_prefix(namespace) + + if entry = constant_entry(name) + if entry.is_a?(ConstantEntry) || entry.is_a?(ModuleAliasEntry) || entry.is_a?(ClassAliasEntry) + raise DuplicatedDeclarationError.new(name, decl, entry.decl) + end + if entry.is_a?(ModuleEntry) + raise DuplicatedDeclarationError.new(name, decl, *entry.each_decl.to_a) + end + end + + entry = ClassEntry.new(name) + class_decls[name] = entry + + entry << [context, decl] + + inner_context = [context, name] #: Resolver::context + decl.each_decl do |member| + insert_ruby_decl(member, context: inner_context, namespace: name.to_namespace) + end + + when AST::Ruby::Declarations::ModuleDecl + name = decl.module_name.with_prefix(namespace) + + if entry = constant_entry(name) + if entry.is_a?(ConstantEntry) || entry.is_a?(ModuleAliasEntry) || entry.is_a?(ClassAliasEntry) + raise DuplicatedDeclarationError.new(name, decl, entry.decl) + end + if entry.is_a?(ClassEntry) + raise DuplicatedDeclarationError.new(name, decl, *entry.each_decl.to_a) + end + end + + entry = ModuleEntry.new(name) + class_decls[name] = entry + + entry << [context, decl] + + inner_context = [context, name] #: Resolver::context + decl.each_decl do |member| + insert_ruby_decl(member, context: inner_context, namespace: name.to_namespace) + end + end + end + def add_source(source) sources << source - source.declarations.each do |decl| - insert_decl(decl, context: nil, namespace: Namespace.root) + case source + when Source::RBS + source.declarations.each do |decl| + insert_rbs_decl(decl, context: nil, namespace: Namespace.root) + end + when Source::Ruby + source.declarations.each do |dir| + insert_ruby_decl(dir, context: nil, namespace: Namespace.root) + end end end def each_rbs_source(&block) if block sources.each do |source| - yield source + if source.is_a?(Source::RBS) + yield source + end end else enum_for(:each_rbs_source) end end + def each_ruby_source(&block) + if block + sources.each do |source| + if source.is_a?(Source::Ruby) + yield source + end + end + else + enum_for(:each_ruby_source) + end + end + def validate_type_params class_decls.each_value do |decl| decl.validate_type_params @@ -429,6 +498,14 @@ def resolve_type_names(only: nil) env.add_source(Source::RBS.new(source.buffer, source.directives, decls)) end + each_ruby_source do |source| + decls = source.declarations.map do |decl| + resolve_ruby_decl(resolver, decl, context: nil, prefix: Namespace.root) + end + + env.add_source(Source::Ruby.new(source.buffer, source.prism_result, decls, source.diagnostics)) + end + env end @@ -583,6 +660,45 @@ def resolve_declaration(resolver, map, decl, context:, prefix:) end end + def resolve_ruby_decl(resolver, decl, context:, prefix:) + case decl + when AST::Ruby::Declarations::ClassDecl + full_name = decl.class_name.with_prefix(prefix) + inner_context = [context, full_name] #: Resolver::context + inner_prefix = full_name.to_namespace + + AST::Ruby::Declarations::ClassDecl.new(decl.buffer, full_name, decl.node).tap do |resolved| + decl.members.each do |member| + case member + when AST::Ruby::Declarations::Base + resolved.members << resolve_ruby_decl(resolver, member, context: inner_context, prefix: inner_prefix) + else + raise "Unknown member type: #{member.class}" + end + end + end + + when AST::Ruby::Declarations::ModuleDecl + full_name = decl.module_name.with_prefix(prefix) + inner_context = [context, full_name] #: Resolver::context + inner_prefix = full_name.to_namespace + + AST::Ruby::Declarations::ModuleDecl.new(decl.buffer, full_name, decl.node).tap do |resolved| + decl.members.each do |member| + case member + when AST::Ruby::Declarations::Base + resolved.members << resolve_ruby_decl(resolver, member, context: inner_context, prefix: inner_prefix) + else + raise "Unknown member type: #{member.class}" + end + end + end + + else + raise "Unknown declaration type: #{decl.class}" + end + end + def resolve_member(resolver, map, member, context:) case member when AST::Members::MethodDefinition diff --git a/lib/rbs/errors.rb b/lib/rbs/errors.rb index ef22b1ccd..1d21eca9b 100644 --- a/lib/rbs/errors.rb +++ b/lib/rbs/errors.rb @@ -408,10 +408,11 @@ class GenericParameterMismatchError < LoadingError attr_reader :name attr_reader :decl - def initialize(name:, decl:) + def initialize(name:, decl:, location: nil) @name = name @decl = decl - super "#{Location.to_string decl.location}: Generic parameters mismatch: #{name}" + location ||= decl.location + super "#{Location.to_string location}: Generic parameters mismatch: #{name}" end end diff --git a/lib/rbs/inline_parser.rb b/lib/rbs/inline_parser.rb new file mode 100644 index 000000000..e744c1b2e --- /dev/null +++ b/lib/rbs/inline_parser.rb @@ -0,0 +1,113 @@ +# frozen_string_literal: true + +module RBS + class InlineParser + class Result + attr_reader :buffer, :prism_result, :declarations, :diagnostics + + def initialize(buffer, prism) + @buffer = buffer + @prism_result = prism + @declarations = [] + @diagnostics = [] + end + end + + module Diagnostic + class Base + attr_reader :message, :location + + def initialize(location, message) + @location = location + @message = message + end + end + + NonConstantClassName = _ = Class.new(Base) + NonConstantModuleName = _ = Class.new(Base) + end + + def self.parse(buffer, prism) + result = Result.new(buffer, prism) + + Parser.new(result).visit(prism.value) + + result + end + + class Parser < Prism::Visitor + attr_reader :module_nesting, :result + + include AST::Ruby::Helpers::ConstantHelper + include AST::Ruby::Helpers::LocationHelper + + def initialize(result) + @result = result + @module_nesting = [] + end + + def buffer + result.buffer + end + + def current_module + module_nesting.last + end + + def current_module! + current_module || raise("#current_module is nil") + end + + def diagnostics + result.diagnostics + end + + def push_module_nesting(mod) + module_nesting.push(mod) + yield + ensure + module_nesting.pop() + end + + def visit_class_node(node) + unless class_name = constant_as_type_name(node.constant_path) + diagnostics << Diagnostic::NonConstantClassName.new( + rbs_location(node.constant_path.location), + "Class name must be a constant" + ) + return + end + + class_decl = AST::Ruby::Declarations::ClassDecl.new(buffer, class_name, node) + insert_declaration(class_decl) + push_module_nesting(class_decl) do + visit_child_nodes(node) + end + end + + def visit_module_node(node) + unless module_name = constant_as_type_name(node.constant_path) + diagnostics << Diagnostic::NonConstantModuleName.new( + rbs_location(node.constant_path.location), + "Module name must be a constant" + ) + return + end + + module_decl = AST::Ruby::Declarations::ModuleDecl.new(buffer, module_name, node) + insert_declaration(module_decl) + push_module_nesting(module_decl) do + visit_child_nodes(node) + end + end + + def insert_declaration(decl) + if current_module + current_module.members << decl + else + result.declarations << decl + end + end + end + end +end diff --git a/lib/rbs/source.rb b/lib/rbs/source.rb index 0c912a547..cd5c4b71c 100644 --- a/lib/rbs/source.rb +++ b/lib/rbs/source.rb @@ -48,5 +48,52 @@ def each_declaration_type_name(names, decl, &block) end end end + + class Ruby + attr_reader :buffer + attr_reader :prism_result + attr_reader :declarations + attr_reader :diagnostics + + def initialize(buffer, prism, declarations, diagnostics) + @buffer = buffer + @prism_result = prism + @declarations = declarations + @diagnostics = diagnostics + end + + def each_type_name(&block) + if block + names = Set[] #: Set[TypeName] + declarations.each do |decl| + each_declaration_type_name(names, decl, &block) + end + else + enum_for :each_type_name + end + end + + def each_declaration_type_name(names, decl, &block) + case decl + when AST::Ruby::Declarations::ClassDecl + decl.each_decl do |d| + each_declaration_type_name(names, d, &block) + end + type_name = decl.class_name + when AST::Ruby::Declarations::ModuleDecl + decl.each_decl do |d| + each_declaration_type_name(names, d, &block) + end + type_name = decl.module_name + end + + if type_name + unless names.include?(type_name) + yield type_name + names << type_name + end + end + end + end end end diff --git a/lib/rbs/subtractor.rb b/lib/rbs/subtractor.rb index d0a7487aa..bf4440678 100644 --- a/lib/rbs/subtractor.rb +++ b/lib/rbs/subtractor.rb @@ -130,6 +130,7 @@ def call(minuend = @minuend, context: nil) entry = @subtrahend.class_decls[owner] return unless entry entry.each_decl do |d| + next unless d.is_a?(AST::Declarations::Base) d.members.each { |m| block.call(m) } end end diff --git a/sig/ancestor_builder.rbs b/sig/ancestor_builder.rbs index 5b1d78faa..fbe0097f5 100644 --- a/sig/ancestor_builder.rbs +++ b/sig/ancestor_builder.rbs @@ -146,7 +146,7 @@ module RBS extended_modules: Array[Definition::Ancestor::Instance]?, extended_interfaces: Array[Definition::Ancestor::Instance]?) -> void - def mixin_ancestors0: (AST::Declarations::Class | AST::Declarations::Module | AST::Declarations::Interface, + def mixin_ancestors0: (AST::Declarations::Class | AST::Declarations::Module | AST::Declarations::Interface | AST::Ruby::Declarations::ClassDecl | AST::Ruby::Declarations::ModuleDecl, TypeName, align_params: Substitution?, included_modules: Array[Definition::Ancestor::Instance]?, diff --git a/sig/ast/ruby/declarations.rbs b/sig/ast/ruby/declarations.rbs new file mode 100644 index 000000000..9694fee54 --- /dev/null +++ b/sig/ast/ruby/declarations.rbs @@ -0,0 +1,60 @@ +module RBS + module AST + module Ruby + module Declarations + type t = ClassDecl | ModuleDecl + + class Base + attr_reader buffer: Buffer + + include Helpers::ConstantHelper + include Helpers::LocationHelper + + def initialize: (Buffer) -> void + end + + class ClassDecl < Base + type member = t + + attr_reader class_name: TypeName + + attr_reader node: Prism::ClassNode + + attr_reader members: Array[member] + + def initialize: (Buffer, TypeName, Prism::ClassNode) -> void + + def each_decl: () { (t) -> void } -> void + | () -> Enumerator[t] + + def super_class: () -> nil + + def type_params: () -> Array[AST::TypeParam] + + def location: () -> Location + end + + class ModuleDecl < Base + type member = t + + attr_reader module_name: TypeName + + attr_reader node: Prism::ModuleNode + + attr_reader members: Array[member] + + def initialize: (Buffer, TypeName, Prism::ModuleNode) -> void + + def each_decl: () { (t) -> void } -> void + | () -> Enumerator[t] + + def type_params: () -> Array[AST::TypeParam] + + def location: () -> Location + + def self_types: () -> Array[AST::Declarations::Module::Self] + end + end + end + end +end diff --git a/sig/ast/ruby/helpers/constant_helper.rbs b/sig/ast/ruby/helpers/constant_helper.rbs new file mode 100644 index 000000000..0054fa694 --- /dev/null +++ b/sig/ast/ruby/helpers/constant_helper.rbs @@ -0,0 +1,11 @@ +module RBS + module AST + module Ruby + module Helpers + module ConstantHelper + def self?.constant_as_type_name: (Prism::node?) -> TypeName? + end + end + end + end +end diff --git a/sig/ast/ruby/helpers/location_helper.rbs b/sig/ast/ruby/helpers/location_helper.rbs new file mode 100644 index 000000000..33f3ad7ac --- /dev/null +++ b/sig/ast/ruby/helpers/location_helper.rbs @@ -0,0 +1,15 @@ +module RBS + module AST + module Ruby + module Helpers + module LocationHelper : _WithBuffer + interface _WithBuffer + def buffer: () -> Buffer + end + + def rbs_location: (Prism::Location) -> Location + end + end + end + end +end diff --git a/sig/definition_builder.rbs b/sig/definition_builder.rbs index 4b72fe6f9..79fdb9ab9 100644 --- a/sig/definition_builder.rbs +++ b/sig/definition_builder.rbs @@ -103,7 +103,7 @@ module RBS def validate_type_params: (Definition, ancestors: AncestorBuilder::OneAncestors, methods: MethodBuilder::Methods) -> void - def source_location: (Definition::Ancestor::Instance::source, AST::Declarations::t) -> Location[untyped, untyped]? + def source_location: (Definition::Ancestor::Instance::source, AST::Declarations::t | AST::Ruby::Declarations::t) -> Location[untyped, untyped]? def validate_variable: (Definition::Variable) -> void diff --git a/sig/environment.rbs b/sig/environment.rbs index 9d9e889ec..0d7e783aa 100644 --- a/sig/environment.rbs +++ b/sig/environment.rbs @@ -32,7 +32,7 @@ module RBS # Array of source objects loaded in the environment # - attr_reader sources: Array[Source::RBS] + attr_reader sources: Array[Source::t] # Class declarations attr_reader class_decls: Hash[TypeName, ModuleEntry | ClassEntry] @@ -59,14 +59,21 @@ module RBS # def self.from_loader: (EnvironmentLoader) -> Environment - def add_source: (Source::RBS) -> void + def add_source: (Source::t) -> void def each_rbs_source: () { (Source::RBS) -> void } -> void | () -> Enumerator[Source::RBS] + def each_ruby_source: () { (Source::Ruby) -> void } -> void + | () -> Enumerator[Source::Ruby] + # Insert a declaration into the environment # - private def insert_decl: (AST::Declarations::t, context: Resolver::context, namespace: Namespace) -> void + private def insert_rbs_decl: (AST::Declarations::t, context: Resolver::context, namespace: Namespace) -> void + + # Insert a declaration into the environment + # + private def insert_ruby_decl: (AST::Ruby::Declarations::t, context: Resolver::context, namespace: Namespace) -> void # Resolve all type names in the environment to absolute type names. # Relative type name will be left if absolute type name cannot be found. @@ -207,6 +214,8 @@ module RBS def resolve_type_params: (Resolver::TypeNameResolver resolver, UseMap map, Array[AST::TypeParam], context: Resolver::context) -> Array[AST::TypeParam] + def resolve_ruby_decl: (Resolver::TypeNameResolver, AST::Ruby::Declarations::t, context: Resolver::context, prefix: Namespace) -> AST::Ruby::Declarations::t + def absolute_type: (Resolver::TypeNameResolver, UseMap map, Types::t, context: Resolver::context) -> Types::t def absolute_type_name: (Resolver::TypeNameResolver, UseMap map, TypeName, context: Resolver::context) -> TypeName diff --git a/sig/environment/class_entry.rbs b/sig/environment/class_entry.rbs index 8fe988fb5..854e191eb 100644 --- a/sig/environment/class_entry.rbs +++ b/sig/environment/class_entry.rbs @@ -10,7 +10,7 @@ module RBS class ClassEntry attr_reader name: TypeName - type declaration = AST::Declarations::Class + type declaration = AST::Declarations::Class | AST::Ruby::Declarations::ClassDecl type context_decl = [Resolver::context, declaration] diff --git a/sig/environment/module_entry.rbs b/sig/environment/module_entry.rbs index fdbf90582..1dee2fefe 100644 --- a/sig/environment/module_entry.rbs +++ b/sig/environment/module_entry.rbs @@ -11,7 +11,7 @@ module RBS class ModuleEntry attr_reader name: TypeName - type declaration = AST::Declarations::Module + type declaration = AST::Declarations::Module | AST::Ruby::Declarations::ModuleDecl type context_decl = [Resolver::context, declaration] diff --git a/sig/errors.rbs b/sig/errors.rbs index 2566567f1..beec6bf52 100644 --- a/sig/errors.rbs +++ b/sig/errors.rbs @@ -237,17 +237,22 @@ module RBS end class GenericParameterMismatchError < LoadingError + type decl = AST::Declarations::Class | AST::Declarations::Module + | AST::Ruby::Declarations::ClassDecl | AST::Ruby::Declarations::ModuleDecl + attr_reader name: TypeName - attr_reader decl: AST::Declarations::Class | AST::Declarations::Module + attr_reader decl: decl - def initialize: (name: TypeName, decl: AST::Declarations::Class | AST::Declarations::Module) -> void + def initialize: (name: TypeName, decl: decl, ?location: Location) -> void end class DuplicatedDeclarationError < LoadingError + type declaration = AST::Declarations::t | AST::Ruby::Declarations::t + attr_reader name: TypeName | Symbol - attr_reader decls: Array[AST::Declarations::t] + attr_reader decls: Array[declaration] - def initialize: (TypeName | Symbol, *AST::Declarations::t) -> void + def initialize: (TypeName | Symbol, *declaration) -> void end class InvalidVarianceAnnotationError < DefinitionError diff --git a/sig/inline_parser.rbs b/sig/inline_parser.rbs new file mode 100644 index 000000000..38dc3be37 --- /dev/null +++ b/sig/inline_parser.rbs @@ -0,0 +1,60 @@ +use RBS::AST::Ruby::Declarations + +module RBS + class InlineParser + class Result + attr_reader buffer: Buffer + attr_reader prism_result: Prism::ParseResult + attr_reader declarations: Array[AST::Ruby::Declarations::t] + attr_reader diagnostics: Array[Diagnostic::t] + + def initialize: (Buffer, Prism::ParseResult) -> void + end + + module Diagnostic + class Base + attr_reader message: String + + attr_reader location: Location + + def initialize: (Location, String) -> void + end + + class NonConstantClassName < Base + end + + class NonConstantModuleName < Base + end + + type t = NonConstantClassName | NonConstantModuleName + end + + def self.parse: (Buffer, Prism::ParseResult) -> Result + + class Parser < Prism::Visitor + type module_context = Declarations::ClassDecl | Declarations::ModuleDecl + + include AST::Ruby::Helpers::ConstantHelper + + include AST::Ruby::Helpers::LocationHelper + + attr_reader result: Result + + attr_reader module_nesting: Array[module_context] + + def initialize: (Result) -> void + + def buffer: () -> Buffer + + %a{pure} def current_module: () -> module_context? + + %a{pure} def current_module!: () -> module_context + + def diagnostics: () -> Array[Diagnostic::t] + + def push_module_nesting: [T] (module_context) { () -> T } -> T + + def insert_declaration: (module_context) -> void + end + end +end diff --git a/sig/location.rbs b/sig/location.rbs index f9588ddc7..e6d7fac45 100644 --- a/sig/location.rbs +++ b/sig/location.rbs @@ -4,7 +4,7 @@ module RBS # # A location can have _child_ locations. # - class Location[in RequiredChildKeys, in OptionalChildKeys] + class Location[in RequiredChildKeys = untyped, in OptionalChildKeys = untyped] # The buffer this location points on. attr_reader buffer (): Buffer diff --git a/sig/method_builder.rbs b/sig/method_builder.rbs index e47b625c3..b78c93964 100644 --- a/sig/method_builder.rbs +++ b/sig/method_builder.rbs @@ -76,7 +76,7 @@ module RBS def build_method: (Methods, Methods::instance_type, member: AST::Members::MethodDefinition, accessibility: Definition::accessibility) -> void - def each_member_with_accessibility: (Array[AST::Members::t | AST::Declarations::t], ?accessibility: Definition::accessibility) { (AST::Members::t | AST::Declarations::t, Definition::accessibility) -> void } -> void + def each_rbs_member_with_accessibility: (Array[AST::Members::t | AST::Declarations::t], ?accessibility: Definition::accessibility) { (AST::Members::t | AST::Declarations::t, Definition::accessibility) -> void } -> void def update: (env: Environment, except: _Each[TypeName]) -> MethodBuilder end diff --git a/sig/source.rbs b/sig/source.rbs index 13257635b..befbc352c 100644 --- a/sig/source.rbs +++ b/sig/source.rbs @@ -1,5 +1,7 @@ module RBS module Source + type t = RBS | Ruby + class RBS attr_reader buffer: Buffer @@ -18,5 +20,29 @@ module RBS private def each_declaration_type_name: (Set[TypeName], AST::Declarations::t) { (TypeName) -> void } -> void end + + class Ruby + attr_reader buffer: Buffer + + attr_reader prism_result: Prism::ParseResult + + attr_reader declarations: Array[AST::Ruby::Declarations::t] + + attr_reader diagnostics: Array[untyped] + + def initialize: (Buffer, Prism::ParseResult, Array[AST::Ruby::Declarations::t], Array[untyped]) -> void + + def each_type_name: () { (TypeName) -> void } -> void + | () -> Enumerator[TypeName] + + private def each_declaration_type_name: (Set[TypeName], AST::Ruby::Declarations::t) { (TypeName) -> void } -> void + + # Compares the type declaration between `self` and `other` + # + # The comparison is based on the AST structure. + # Differences on Ruby code implementation may be ignored. + # + def ==: (other: Ruby) -> bool + end end end diff --git a/test/rbs/ancestor_builder_test.rb b/test/rbs/ancestor_builder_test.rb index ae8579c3b..517f8db23 100644 --- a/test/rbs/ancestor_builder_test.rb +++ b/test/rbs/ancestor_builder_test.rb @@ -1115,4 +1115,48 @@ module Bar : A, _D end end end + + def test__one_ancestors__class__ruby + SignatureManager.new(system_builtin: true) do |manager| + + manager.ruby_files[Pathname("lib/foo.rb")] = <<~EOF + class Foo + end + EOF + + manager.build do |env| + builder = DefinitionBuilder::AncestorBuilder.new(env: env) + + builder.one_instance_ancestors(type_name("::Foo")).tap do |a| + assert_equal type_name("::Foo"), a.type_name + assert_equal [], a.params + + assert_equal Ancestor::Instance.new(name: type_name("::Object"), args: [], source: :super), a.super_class + assert_equal [], + a.included_modules + assert_equal [], + a.included_interfaces + assert_equal [], a.prepended_modules + assert_nil a.extended_modules + assert_nil a.extended_interfaces + assert_nil a.self_types + end + + builder.one_singleton_ancestors(type_name("::Foo")).tap do |a| + assert_equal type_name("::Foo"), a.type_name + assert_nil a.params + + assert_equal Ancestor::Singleton.new(name: type_name("::Object")), a.super_class + assert_nil a.included_modules + assert_nil a.included_interfaces + assert_nil a.prepended_modules + assert_equal [], + a.extended_modules + assert_equal [], + a.extended_interfaces + assert_nil a.self_types + end + end + end + end end diff --git a/test/rbs/cli_test.rb b/test/rbs/cli_test.rb index 56af39def..04b5830c0 100644 --- a/test/rbs/cli_test.rb +++ b/test/rbs/cli_test.rb @@ -51,6 +51,8 @@ def run_rbs_collection(*commands, bundler:) def bundle_install(*gems) stdout, stderr, status = Bundler.with_unbundled_env do + gems << 'prism' unless gems.include?('prism') + gems = gems.map do |gem| if gem == :gemspec "gemspec" diff --git a/test/rbs/definition_builder_test.rb b/test/rbs/definition_builder_test.rb index 14e6bc5d3..7efa62575 100644 --- a/test/rbs/definition_builder_test.rb +++ b/test/rbs/definition_builder_test.rb @@ -3150,4 +3150,54 @@ class A end end end + + def test_inline_decl__class + SignatureManager.new do |manager| + manager.add_ruby_file("inherited.rbs", <<~RUBY) + class A + end + RUBY + + manager.build do |env| + builder = DefinitionBuilder.new(env: env) + + builder.build_instance(type_name("::A")).tap do |definition| + definition.methods[:__id__].tap do |method| + assert_equal type_name("::Object"), method.defined_in + end + end + + builder.build_singleton(type_name("::A")).tap do |definition| + definition.methods[:new].tap do |method| + assert_equal type_name("::BasicObject"), method.defined_in + end + end + end + end + end + + def test_inline_decl__module + SignatureManager.new do |manager| + manager.add_ruby_file("inherited.rbs", <<~RUBY) + module A + end + RUBY + + manager.build do |env| + builder = DefinitionBuilder.new(env: env) + + builder.build_instance(type_name("::A")).tap do |definition| + definition.methods[:__id__].tap do |method| + assert_equal type_name("::Object"), method.defined_in + end + end + + builder.build_singleton(type_name("::A")).tap do |definition| + definition.methods[:__id__].tap do |method| + assert_equal type_name("::Object"), method.defined_in + end + end + end + end + end end diff --git a/test/rbs/environment_test.rb b/test/rbs/environment_test.rb index 5f86112aa..ed9e80b48 100644 --- a/test/rbs/environment_test.rb +++ b/test/rbs/environment_test.rb @@ -20,7 +20,7 @@ module ::Baz end EOF - env.insert_decl(decls[0], context: nil, namespace: RBS::Namespace.root) + env.insert_rbs_decl(decls[0], context: nil, namespace: RBS::Namespace.root) assert_operator env.class_decls, :key?, type_name("::Foo") assert_operator env.class_decls, :key?, type_name("::Foo::Bar") @@ -39,7 +39,7 @@ class RbObject = Object EOF decls.each do |decl| - env.insert_decl(decl, context: nil, namespace: RBS::Namespace.root) + env.insert_rbs_decl(decl, context: nil, namespace: RBS::Namespace.root) end env.class_alias_decls[RBS::TypeName.parse("::RBS::Kernel")].tap do |decl| @@ -57,10 +57,10 @@ module Foo end EOF - env.insert_decl(decls[0], context: nil, namespace: RBS::Namespace.root) + env.insert_rbs_decl(decls[0], context: nil, namespace: RBS::Namespace.root) assert_raises RBS::DuplicatedDeclarationError do - env.insert_decl(decls[1], context: nil, namespace: RBS::Namespace.root) + env.insert_rbs_decl(decls[1], context: nil, namespace: RBS::Namespace.root) end end @@ -79,8 +79,8 @@ module Bar end EOF - env.insert_decl(decls[0], context: nil, namespace: RBS::Namespace.root) - env.insert_decl(decls[1], context: nil, namespace: RBS::Namespace.root) + env.insert_rbs_decl(decls[0], context: nil, namespace: RBS::Namespace.root) + env.insert_rbs_decl(decls[1], context: nil, namespace: RBS::Namespace.root) env.class_decls[type_name("::Foo")].tap do |entry| assert_instance_of Environment::ClassEntry, entry @@ -109,15 +109,15 @@ class Bar end EOF - env.insert_decl(decls[0], context: nil, namespace: RBS::Namespace.root) - env.insert_decl(decls[1], context: nil, namespace: RBS::Namespace.root) + env.insert_rbs_decl(decls[0], context: nil, namespace: RBS::Namespace.root) + env.insert_rbs_decl(decls[1], context: nil, namespace: RBS::Namespace.root) assert_raises RBS::DuplicatedDeclarationError do - env.insert_decl(decls[2], context: nil, namespace: RBS::Namespace.root) + env.insert_rbs_decl(decls[2], context: nil, namespace: RBS::Namespace.root) end assert_raises RBS::DuplicatedDeclarationError do - env.insert_decl(decls[3], context: nil, namespace: RBS::Namespace.root) + env.insert_rbs_decl(decls[3], context: nil, namespace: RBS::Namespace.root) end end @@ -133,8 +133,8 @@ class Foo EOF assert_raises RBS::DuplicatedDeclarationError do - env.insert_decl(decls[0], context: nil, namespace: RBS::Namespace.root) - env.insert_decl(decls[1], context: nil, namespace: RBS::Namespace.root) + env.insert_rbs_decl(decls[0], context: nil, namespace: RBS::Namespace.root) + env.insert_rbs_decl(decls[1], context: nil, namespace: RBS::Namespace.root) end end @@ -147,8 +147,8 @@ def test_const_twice_duplication_error EOF assert_raises RBS::DuplicatedDeclarationError do - env.insert_decl(decls[0], context: nil, namespace: RBS::Namespace.root) - env.insert_decl(decls[1], context: nil, namespace: RBS::Namespace.root) + env.insert_rbs_decl(decls[0], context: nil, namespace: RBS::Namespace.root) + env.insert_rbs_decl(decls[1], context: nil, namespace: RBS::Namespace.root) end end @@ -161,8 +161,8 @@ def test_type_alias_twice_duplication_error EOF assert_raises RBS::DuplicatedDeclarationError do - env.insert_decl(decls[0], context: nil, namespace: RBS::Namespace.root) - env.insert_decl(decls[1], context: nil, namespace: RBS::Namespace.root) + env.insert_rbs_decl(decls[0], context: nil, namespace: RBS::Namespace.root) + env.insert_rbs_decl(decls[1], context: nil, namespace: RBS::Namespace.root) end end @@ -177,8 +177,8 @@ def test_interface_twice_duplication_error EOF assert_raises RBS::DuplicatedDeclarationError do - env.insert_decl(decls[0], context: nil, namespace: RBS::Namespace.root) - env.insert_decl(decls[1], context: nil, namespace: RBS::Namespace.root) + env.insert_rbs_decl(decls[0], context: nil, namespace: RBS::Namespace.root) + env.insert_rbs_decl(decls[1], context: nil, namespace: RBS::Namespace.root) end end @@ -199,10 +199,10 @@ module Foo[X, in Y] # Variance mismatch end EOF - env.insert_decl(decls[0], context: nil, namespace: RBS::Namespace.root) - env.insert_decl(decls[1], context: nil, namespace: RBS::Namespace.root) - env.insert_decl(decls[2], context: nil, namespace: RBS::Namespace.root) - env.insert_decl(decls[3], context: nil, namespace: RBS::Namespace.root) + env.insert_rbs_decl(decls[0], context: nil, namespace: RBS::Namespace.root) + env.insert_rbs_decl(decls[1], context: nil, namespace: RBS::Namespace.root) + env.insert_rbs_decl(decls[2], context: nil, namespace: RBS::Namespace.root) + env.insert_rbs_decl(decls[3], context: nil, namespace: RBS::Namespace.root) assert_raises RBS::GenericParameterMismatchError do env.validate_type_params() @@ -257,7 +257,7 @@ def test_insert_global $VERSION: String EOF - env.insert_decl(decls[0], context: nil, namespace: RBS::Namespace.root) + env.insert_rbs_decl(decls[0], context: nil, namespace: RBS::Namespace.root) assert_operator env.global_decls, :key?, :$VERSION end @@ -284,9 +284,9 @@ module Bar : _Animal EOF Environment.new.tap do |env| - env.insert_decl(decls[0], context: nil, namespace: RBS::Namespace.root) - env.insert_decl(decls[1], context: nil, namespace: RBS::Namespace.root) - env.insert_decl(decls[2], context: nil, namespace: RBS::Namespace.root) + env.insert_rbs_decl(decls[0], context: nil, namespace: RBS::Namespace.root) + env.insert_rbs_decl(decls[1], context: nil, namespace: RBS::Namespace.root) + env.insert_rbs_decl(decls[2], context: nil, namespace: RBS::Namespace.root) foo = env.class_decls[type_name("::Foo")] @@ -306,9 +306,9 @@ module Bar : _Animal end Environment.new.tap do |env| - env.insert_decl(decls[0], context: nil, namespace: RBS::Namespace.root) - env.insert_decl(decls[3], context: nil, namespace: RBS::Namespace.root) - env.insert_decl(decls[4], context: nil, namespace: RBS::Namespace.root) + env.insert_rbs_decl(decls[0], context: nil, namespace: RBS::Namespace.root) + env.insert_rbs_decl(decls[3], context: nil, namespace: RBS::Namespace.root) + env.insert_rbs_decl(decls[4], context: nil, namespace: RBS::Namespace.root) foo = env.class_decls[type_name("::Bar")] @@ -562,4 +562,50 @@ def test_resolve_type_names_magic_comment__true assert_equal "::s", alias_decl.decl.type.to_s end end + + def parse_inline(src) + buffer = RBS::Buffer.new(name: Pathname("a.rb"), content: src) + prism = Prism.parse(src) + + RBS::InlineParser.parse(buffer, prism) + end + + def test__ruby__insert_decl_class + result = parse_inline(<<~RUBY) + class Hello + module World + end + end + RUBY + + env = Environment.new + env.add_source(RBS::Source::Ruby.new(result.buffer, result.prism_result, result.declarations, result.diagnostics)) + + assert_operator env.class_decls, :key?, type_name("::Hello") + assert_operator env.class_decls, :key?, type_name("::Hello::World") + end + + def test__ruby__absolute_class_module_name + result = parse_inline(<<~RUBY) + class Hello + module World + end + end + RUBY + + env = Environment.new + env.add_source(RBS::Source::Ruby.new(result.buffer, result.prism_result, result.declarations, result.diagnostics)) + + env.resolve_type_names.tap do |env| + class_decl = env.class_decls[RBS::TypeName.parse("::Hello")] + class_decl.each_decl do |decl| + assert_equal RBS::TypeName.parse("::Hello"), decl.class_name + end + + module_decl = env.class_decls[RBS::TypeName.parse("::Hello::World")] + module_decl.each_decl do |decl| + assert_equal RBS::TypeName.parse("::Hello::World"), decl.module_name + end + end + end end diff --git a/test/rbs/inline_parser_test.rb b/test/rbs/inline_parser_test.rb new file mode 100644 index 000000000..c8ae73cbb --- /dev/null +++ b/test/rbs/inline_parser_test.rb @@ -0,0 +1,90 @@ +require "test_helper" + +class RBS::InlineParserTest < Test::Unit::TestCase + include TestHelper + + def parse(src) + buffer = RBS::Buffer.new(name: Pathname("test.rbs"), content: src) + + prism = Prism.parse(src) + RBS::InlineParser.parse(buffer, prism) + end + + def test_parse__class + result = parse(<<~RUBY) + class Hello + class World + end + end + RUBY + + assert_empty result.diagnostics + + result.declarations[0].tap do |decl| + assert_instance_of RBS::AST::Ruby::Declarations::ClassDecl, decl + assert_equal RBS::TypeName.parse("Hello"), decl.class_name + + decl.members[0].tap do |member| + assert_instance_of RBS::AST::Ruby::Declarations::ClassDecl, member + assert_equal RBS::TypeName.parse("World"), member.class_name + end + end + end + + def test_error__class__non_constant_name + result = parse(<<~RUBY) + class (C = Object)::Hello + class World + end + end + RUBY + + assert_equal 1, result.diagnostics.size + assert_any!(result.diagnostics, size: 1) do |diagnostic| + assert_instance_of RBS::InlineParser::Diagnostic::NonConstantClassName, diagnostic + assert_equal ("(C = Object)::Hello"), diagnostic.location.source + assert_equal "Class name must be a constant", diagnostic.message + end + + assert_empty result.declarations + end + + def test_parse__module + result = parse(<<~RUBY) + module Hello + module World + end + end + RUBY + + assert_empty result.diagnostics + + result.declarations[0].tap do |decl| + assert_instance_of RBS::AST::Ruby::Declarations::ModuleDecl, decl + assert_equal RBS::TypeName.parse("Hello"), decl.module_name + + decl.members[0].tap do |member| + assert_instance_of RBS::AST::Ruby::Declarations::ModuleDecl, member + assert_equal RBS::TypeName.parse("World"), member.module_name + end + end + end + + def test_error__module__non_constant_name + result = parse(<<~RUBY) + module (C = Object)::Hello + module World + end + end + RUBY + + assert_equal 1, result.diagnostics.size + assert_any!(result.diagnostics, size: 1) do |diagnostic| + assert_instance_of RBS::InlineParser::Diagnostic::NonConstantModuleName, diagnostic + assert_equal ("(C = Object)::Hello"), diagnostic.location.source + assert_equal "Module name must be a constant", diagnostic.message + end + + assert_empty result.declarations + end +end diff --git a/test/rbs/runtime_prototype_test.rb b/test/rbs/runtime_prototype_test.rb index 608de1b7f..6f50e01f9 100644 --- a/test/rbs/runtime_prototype_test.rb +++ b/test/rbs/runtime_prototype_test.rb @@ -414,7 +414,7 @@ def test_decls_structure p = Runtime.new(patterns: ["RBS::RuntimePrototypeTest::TestForEnv"], env: env, merge: true) assert_equal(p.decls.length, 1) p.decls.each do |decl| - env.insert_decl(decl, context: nil, namespace: RBS::Namespace.root) + env.insert_rbs_decl(decl, context: nil, namespace: RBS::Namespace.root) end env.resolve_type_names assert(true) # nothing raised above @@ -428,7 +428,7 @@ def test_basic_object p = Runtime.new(patterns: ["BasicObject"], env: env, merge: true) assert_equal(p.decls.length, 1) p.decls.each do |decl| - env.insert_decl(decl, context: nil, namespace: RBS::Namespace.root) + env.insert_rbs_decl(decl, context: nil, namespace: RBS::Namespace.root) end env.resolve_type_names assert(true) # nothing raised above diff --git a/test/test_helper.rb b/test/test_helper.rb index f64292d22..9a077b1c0 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -77,10 +77,12 @@ def silence_warnings class SignatureManager attr_reader :files + attr_reader :ruby_files attr_reader :system_builtin def initialize(system_builtin: false) @files = {} + @ruby_files = {} @system_builtin = system_builtin files[Pathname("builtin.rbs")] = BUILTINS unless system_builtin @@ -159,6 +161,10 @@ def add_file(path, content) files[Pathname(path)] = content end + def add_ruby_file(path, content) + ruby_files[Pathname(path)] = content + end + def build Dir.mktmpdir do |tmpdir| tmppath = Pathname(tmpdir) @@ -179,7 +185,19 @@ def build loader = RBS::EnvironmentLoader.new(core_root: root) loader.add(path: tmppath) - yield RBS::Environment.from_loader(loader).resolve_type_names, tmppath + env = RBS::Environment.from_loader(loader) + + ruby_files.each do |path, content| + buffer = RBS::Buffer.new(name: path, content: content) + prism = Prism.parse(content) + result = RBS::InlineParser.parse(buffer, prism) + source = RBS::Source::Ruby.new(buffer, prism, result.declarations, result.diagnostics) + env.add_source(source) + end + + env = env.resolve_type_names + + yield env, tmppath end end end @@ -208,6 +226,8 @@ def assert_any!(collection, size: nil) end yield last + else + assert_block("assert_any! cannot hold for empty collection") { false } end end