diff --git a/Gemfile.lock b/Gemfile.lock index 1c1326f33..1465fcdab 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -3,6 +3,7 @@ PATH specs: rbs (4.0.0.dev) logger + prism (>= 1.3.0) PATH remote: test/assets/test-gem diff --git a/Rakefile b/Rakefile index 59f4b4bb7..77d41105e 100644 --- a/Rakefile +++ b/Rakefile @@ -141,16 +141,21 @@ task :stdlib_test => :compile do end task :typecheck_test => :compile do - FileList["test/typecheck/*"].each do |test| - Dir.chdir(test) do - expectations = File.join(test, "steep_expectations.yml") - if File.exist?(expectations) - sh "steep check --with_expectations" - else - sh "steep check" - end - end - end + puts + puts + puts "⛔️⛔️⛔️⛔️⛔️⛔️ Skipping type check test because RBS is incompatible with Steep (#{__FILE__}:#{__LINE__})" + puts + puts + # FileList["test/typecheck/*"].each do |test| + # Dir.chdir(test) do + # expectations = File.join(test, "steep_expectations.yml") + # if File.exist?(expectations) + # sh "steep check --with_expectations" + # else + # sh "steep check" + # end + # end + # end end task :raap => :compile do diff --git a/Steepfile b/Steepfile index de1a0b4e9..60689d543 100644 --- a/Steepfile +++ b/Steepfile @@ -9,6 +9,7 @@ target :lib do ) library "pathname", "json", "logger", "monitor", "tsort", "uri", 'dbm', 'pstore', 'singleton', 'shellwords', 'fileutils', 'find', 'digest', 'prettyprint', 'yaml', "psych", "securerandom" + library "prism" signature "stdlib/strscan/0/" signature "stdlib/optparse/0/" signature "stdlib/rdoc/0/" diff --git a/lib/rbs.rb b/lib/rbs.rb index d0b8d24d4..48fa0a6ef 100644 --- a/lib/rbs.rb +++ b/lib/rbs.rb @@ -24,8 +24,11 @@ require "rbs/ast/members" require "rbs/ast/annotation" require "rbs/ast/visitor" +require "rbs/source" require "rbs/environment" require "rbs/environment/use_map" +require "rbs/environment/class_entry" +require "rbs/environment/module_entry" require "rbs/environment_loader" require "rbs/builtin_names" require "rbs/definition" diff --git a/lib/rbs/cli.rb b/lib/rbs/cli.rb index a47d2aac0..2706d8679 100644 --- a/lib/rbs/cli.rb +++ b/lib/rbs/cli.rb @@ -175,10 +175,9 @@ def run_ast(args, options) env = Environment.from_loader(loader).resolve_type_names - decls = env.declarations.select do |decl| - loc = decl.location or raise + decls = env.sources.select do |source| # @type var name: String - name = loc.buffer.name + name = source.buffer.name.to_s patterns.empty? || patterns.any? do |pat| case pat @@ -188,7 +187,7 @@ def run_ast(args, options) name.end_with?(pat) || File.fnmatch(pat, name, File::FNM_EXTGLOB) end end - end + end.flat_map { _1.declarations } stdout.print JSON.generate(decls) stdout.flush @@ -913,7 +912,7 @@ def run_parse(args, options) Buffer.new(content: file_path.read, name: file_path) end end - bufs << Buffer.new(content: e_code, name: '-e') if e_code + bufs << Buffer.new(content: e_code, name: Pathname('-e')) if e_code bufs.each do |buf| RBS.logger.info "Parsing #{buf.name}..." diff --git a/lib/rbs/cli/validate.rb b/lib/rbs/cli/validate.rb index b4ba30897..d6dc78c1f 100644 --- a/lib/rbs/cli/validate.rb +++ b/lib/rbs/cli/validate.rb @@ -109,8 +109,8 @@ def validate_class_module_definition case entry when Environment::ClassEntry - entry.decls.each do |decl| - if super_class = decl.decl.super_class + entry.each_decl do |decl| + if super_class = decl.super_class super_class.args.each do |arg| void_type_context_validator(arg, true) no_self_type_validator(arg) @@ -120,8 +120,8 @@ def validate_class_module_definition end end when Environment::ModuleEntry - entry.decls.each do |decl| - decl.decl.self_types.each do |self_type| + entry.each_decl do |decl| + decl.self_types.each do |self_type| self_type.args.each do |arg| void_type_context_validator(arg, true) no_self_type_validator(arg) @@ -143,7 +143,7 @@ def validate_class_module_definition end end - d = entry.primary.decl + d = entry.primary_decl @validator.validate_type_params( d.type_params, @@ -169,8 +169,8 @@ def validate_class_module_definition TypeParamDefaultReferenceError.check!(d.type_params) - entry.decls.each do |d| - d.decl.each_member do |member| + entry.each_decl do |decl| + decl.each_member do |member| case member when AST::Members::MethodDefinition @validator.validate_method_definition(member, type_name: name) diff --git a/lib/rbs/definition_builder.rb b/lib/rbs/definition_builder.rb index 4c4ede267..8f20e9d03 100644 --- a/lib/rbs/definition_builder.rb +++ b/lib/rbs/definition_builder.rb @@ -126,10 +126,10 @@ def define_instance(definition, type_name, subst, define_class_vars:) entry = env.class_decls[type_name] or raise "Unknown name for build_instance: #{type_name}" args = entry.type_params.map {|param| Types::Variable.new(name: param.name, location: param.location) } - entry.decls.each do |d| - subst_ = subst + Substitution.build(d.decl.type_params.each.map(&:name), args) + entry.each_decl do |decl| + subst_ = subst + Substitution.build(decl.type_params.each.map(&:name), args) - d.decl.members.each do |member| + decl.members.each do |member| case member when AST::Members::AttrReader, AST::Members::AttrAccessor, AST::Members::AttrWriter if member.kind == :instance @@ -174,7 +174,7 @@ def build_instance(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) + ensure_namespace!(type_name.namespace, location: entry.primary_decl.location) ancestors = ancestor_builder.instance_ancestors(type_name) args = entry.type_params.map {|param| Types::Variable.new(name: param.name, location: param.location) } @@ -234,7 +234,7 @@ def build_instance(type_name) def build_singleton0(type_name) try_cache type_name, cache: singleton0_cache do entry = env.class_decls[type_name] or raise "Unknown name for build_singleton0: #{type_name}" - ensure_namespace!(type_name.namespace, location: entry.decls[0].decl.location) + ensure_namespace!(type_name.namespace, location: entry.primary_decl.location) ancestors = ancestor_builder.singleton_ancestors(type_name) self_type = Types::ClassSingleton.new(name: type_name, location: nil) @@ -272,8 +272,8 @@ def build_singleton0(type_name) interface_methods = interface_methods(all_interfaces) import_methods(definition, type_name, methods, interface_methods, Substitution.new, nil) - entry.decls.each do |d| - d.decl.members.each do |member| + entry.each_decl do |decl| + decl.members.each do |member| case member when AST::Members::AttrReader, AST::Members::AttrAccessor, AST::Members::AttrWriter if member.kind == :singleton @@ -306,7 +306,7 @@ def build_singleton(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) + ensure_namespace!(type_name.namespace, location: entry.primary_decl.location) ancestors = ancestor_builder.singleton_ancestors(type_name) self_type = Types::ClassSingleton.new(name: type_name, location: nil) @@ -471,7 +471,7 @@ def validate_type_params(definition, ancestors:, methods:) validate_params_with(type_params, result: result) do |param| decl = case entry = definition.entry when Environment::ModuleEntry, Environment::ClassEntry - entry.primary.decl + entry.primary_decl when Environment::SingleEntry entry.decl end diff --git a/lib/rbs/definition_builder/ancestor_builder.rb b/lib/rbs/definition_builder/ancestor_builder.rb index 0b513779b..8048f2338 100644 --- a/lib/rbs/definition_builder/ancestor_builder.rb +++ b/lib/rbs/definition_builder/ancestor_builder.rb @@ -173,12 +173,12 @@ def initialize(env:) end def validate_super_class!(type_name, entry) - with_super_classes = entry.decls.select {|d| d.decl.super_class } + with_super_classes = entry.each_decl.select {|decl| decl.super_class } return if with_super_classes.size <= 1 - super_types = with_super_classes.map do |d| - super_class = d.decl.super_class or raise + super_types = with_super_classes.map do |decl| + super_class = decl.super_class or raise Types::ClassInstance.new(name: super_class.name, args: super_class.args, location: nil) end @@ -200,8 +200,8 @@ def one_instance_ancestors(type_name) case entry when Environment::ClassEntry validate_super_class!(type_name, entry) - primary = entry.primary - super_class = primary.decl.super_class + primary = entry.primary_decl + super_class = primary.super_class if type_name != BuiltinNames::BasicObject.name if super_class @@ -214,7 +214,7 @@ def one_instance_ancestors(type_name) super_name = env.normalize_module_name(super_name) - NoSuperclassFoundError.check!(super_name, env: env, location: primary.decl.location) + NoSuperclassFoundError.check!(super_name, env: env, location: primary.location) if super_class InheritModuleError.check!(super_class, env: env) InvalidTypeApplicationError.check2!(type_name: super_class.name, args: super_class.args, env: env, location: super_class.location) @@ -283,8 +283,8 @@ def one_singleton_ancestors(type_name) case entry when Environment::ClassEntry validate_super_class!(type_name, entry) - primary = entry.primary - super_class = primary.decl.super_class + primary = entry.primary_decl + super_class = primary.super_class if type_name != BuiltinNames::BasicObject.name if super_class @@ -295,7 +295,7 @@ def one_singleton_ancestors(type_name) super_name = env.normalize_module_name(super_name) - NoSuperclassFoundError.check!(super_name, env: env, location: primary.decl.location) + NoSuperclassFoundError.check!(super_name, env: env, location: primary.location) if super_class InheritModuleError.check!(super_class, env: env) end @@ -414,9 +414,7 @@ def mixin_ancestors0(decl, type_name, align_params:, included_modules:, included end def mixin_ancestors(entry, type_name, included_modules:, included_interfaces:, extended_modules:, prepended_modules:, extended_interfaces:) - entry.decls.each do |d| - decl = d.decl - + entry.each_decl do |decl| align_params = Substitution.build( decl.type_params.each.map(&:name), entry.type_params.map {|param| Types::Variable.new(name: param.name, location: param.location) } @@ -445,7 +443,7 @@ def instance_ancestors(type_name, building_ancestors: []) RecursiveAncestorError.check!(self_ancestor, ancestors: building_ancestors, - location: entry.primary.decl.location) + location: entry.primary_decl.location) building_ancestors.push self_ancestor one_ancestors = one_instance_ancestors(type_name) @@ -462,7 +460,7 @@ def instance_ancestors(type_name, building_ancestors: []) super_ancestors = instance_ancestors(super_name, building_ancestors: building_ancestors) - .apply(super_args, env: env, location: entry.primary.decl.super_class&.location) + .apply(super_args, env: env, location: entry.primary_decl.super_class&.location) super_ancestors.map! {|ancestor| fill_ancestor_source(ancestor, name: super_name, source: :super) } ancestors.unshift(*super_ancestors) end @@ -522,7 +520,7 @@ def singleton_ancestors(type_name, building_ancestors: []) RecursiveAncestorError.check!(self_ancestor, ancestors: building_ancestors, - location: entry.primary.decl.location) + location: entry.primary_decl.location) building_ancestors.push self_ancestor one_ancestors = one_singleton_ancestors(type_name) diff --git a/lib/rbs/definition_builder/method_builder.rb b/lib/rbs/definition_builder/method_builder.rb index f137aed4e..6f513b5a7 100644 --- a/lib/rbs/definition_builder/method_builder.rb +++ b/lib/rbs/definition_builder/method_builder.rb @@ -103,9 +103,9 @@ def build_instance(type_name) args = entry.type_params.map {|param| Types::Variable.new(name: param.name, location: param.location) } type = Types::ClassInstance.new(name: type_name, args: args, location: nil) Methods.new(type: type).tap do |methods| - entry.decls.each do |d| - subst = Substitution.build(d.decl.type_params.each.map(&:name), args) - each_member_with_accessibility(d.decl.members) do |member, accessibility| + 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 @@ -149,8 +149,8 @@ def build_singleton(type_name) type = Types::ClassSingleton.new(name: type_name, location: nil) Methods.new(type: type).tap do |methods| - entry.decls.each do |d| - d.decl.members.each do |member| + entry.each_decl do |decl| + decl.members.each do |member| case member when AST::Members::MethodDefinition if member.singleton? diff --git a/lib/rbs/environment.rb b/lib/rbs/environment.rb index 03c27ba4c..8f0d40cc1 100644 --- a/lib/rbs/environment.rb +++ b/lib/rbs/environment.rb @@ -2,8 +2,6 @@ module RBS class Environment - attr_reader :declarations - attr_reader :class_decls attr_reader :interface_decls attr_reader :type_alias_decls @@ -11,116 +9,17 @@ class Environment attr_reader :global_decls attr_reader :class_alias_decls - attr_reader :signatures - - module ContextUtil - def calculate_context(decls) - decls.inject(nil) do |context, decl| #$ Resolver::context - if (_, last = context) - last or raise - [context, last + decl.name] - else - [nil, decl.name.absolute!] - end - end - end - end - - class MultiEntry - D = _ = Struct.new(:decl, :outer, keyword_init: true) do - # @implements D[M] - - include ContextUtil - - def context - @context ||= calculate_context(outer + [decl]) - end - end - - attr_reader :name - attr_reader :decls - - def initialize(name:) - @name = name - @decls = [] - end - - def insert(decl:, outer:) - decls << D.new(decl: decl, outer: outer) - @primary = nil - end - - def validate_type_params - unless decls.empty? - hd_decl, *tl_decls = decls - raise unless hd_decl - - hd_params = hd_decl.decl.type_params - - tl_decls.each do |tl_decl| - tl_params = tl_decl.decl.type_params - - unless compatible_params?(hd_params, tl_params) - raise GenericParameterMismatchError.new(name: name, decl: _ = tl_decl.decl) - end - end - end - end - - def compatible_params?(ps1, ps2) - if ps1.size == ps2.size - ps1 == AST::TypeParam.rename(ps2, new_names: ps1.map(&:name)) - end - end - - def type_params - primary.decl.type_params - end - - def primary - raise "Not implemented" - end - end - - class ModuleEntry < MultiEntry - def self_types - decls.flat_map do |d| - d.decl.self_types - end.uniq - end - - def primary - @primary ||= begin - validate_type_params - decls.first or raise("decls cannot be empty") - end - end - end - - class ClassEntry < MultiEntry - def primary - @primary ||= begin - validate_type_params - decls.find {|d| d.decl.super_class } || decls.first or raise("decls cannot be empty") - end - end - end + attr_reader :sources class SingleEntry attr_reader :name - attr_reader :outer + attr_reader :context attr_reader :decl - def initialize(name:, decl:, outer:) + def initialize(name:, decl:, context:) @name = name @decl = decl - @outer = outer - end - - include ContextUtil - - def context - @context ||= calculate_context(outer) + @context = context end end @@ -143,9 +42,7 @@ class GlobalEntry < SingleEntry end def initialize - @signatures = {} - @declarations = [] - + @sources = [] @class_decls = {} @interface_decls = {} @type_alias_decls = {} @@ -156,9 +53,7 @@ def initialize end def initialize_copy(other) - @signatures = other.signatures.dup - @declarations = other.declarations.dup - + @sources = other.sources.dup @class_decls = other.class_decls.dup @interface_decls = other.interface_decls.dup @type_alias_decls = other.type_alias_decls.dup @@ -370,7 +265,7 @@ def normalize_module_name?(name) @normalize_module_name_cache[name] = normalized_type_name end - def insert_decl(decl, outer:, namespace:) + def insert_decl(decl, context:, namespace:) case decl when AST::Declarations::Class, AST::Declarations::Module name = decl.name.with_prefix(namespace) @@ -384,9 +279,9 @@ def insert_decl(decl, outer:, namespace:) unless class_decls.key?(name) case decl when AST::Declarations::Class - class_decls[name] ||= ClassEntry.new(name: name) + class_decls[name] ||= ClassEntry.new(name) when AST::Declarations::Module - class_decls[name] ||= ModuleEntry.new(name: name) + class_decls[name] ||= ModuleEntry.new(name) end end @@ -394,17 +289,17 @@ def insert_decl(decl, outer:, namespace:) case when decl.is_a?(AST::Declarations::Module) && existing_entry.is_a?(ModuleEntry) - existing_entry.insert(decl: decl, outer: outer) + existing_entry << [context, decl] when decl.is_a?(AST::Declarations::Class) && existing_entry.is_a?(ClassEntry) - existing_entry.insert(decl: decl, outer: outer) + existing_entry << [context, decl] else - raise DuplicatedDeclarationError.new(name, decl, existing_entry.decls[0].decl) + raise DuplicatedDeclarationError.new(name, decl, existing_entry.primary_decl) end - prefix = outer + [decl] - ns = name.to_namespace + inner_context = [context, name] #: Resolver::context + inner_namespace = name.to_namespace decl.each_decl do |d| - insert_decl(d, outer: prefix, namespace: ns) + insert_decl(d, context: inner_context, namespace: inner_namespace) end when AST::Declarations::Interface @@ -414,7 +309,7 @@ def insert_decl(decl, outer:, namespace:) raise DuplicatedDeclarationError.new(name, decl, interface_entry.decl) end - interface_decls[name] = InterfaceEntry.new(name: name, decl: decl, outer: outer) + interface_decls[name] = InterfaceEntry.new(name: name, decl: decl, context: context) when AST::Declarations::TypeAlias name = decl.name.with_prefix(namespace) @@ -423,7 +318,7 @@ def insert_decl(decl, outer:, namespace:) raise DuplicatedDeclarationError.new(name, decl, entry.decl) end - type_alias_decls[name] = TypeAliasEntry.new(name: name, decl: decl, outer: outer) + type_alias_decls[name] = TypeAliasEntry.new(name: name, decl: decl, context: context) when AST::Declarations::Constant name = decl.name.with_prefix(namespace) @@ -433,18 +328,18 @@ def insert_decl(decl, outer:, namespace:) when ClassAliasEntry, ModuleAliasEntry, ConstantEntry raise DuplicatedDeclarationError.new(name, decl, entry.decl) when ClassEntry, ModuleEntry - raise DuplicatedDeclarationError.new(name, decl, *entry.decls.map(&:decl)) + raise DuplicatedDeclarationError.new(name, decl, *entry.each_decl.to_a) end end - constant_decls[name] = ConstantEntry.new(name: name, decl: decl, outer: outer) + constant_decls[name] = ConstantEntry.new(name: name, decl: decl, context: context) when AST::Declarations::Global if entry = global_decls[decl.name] raise DuplicatedDeclarationError.new(decl.name, decl, entry.decl) end - global_decls[decl.name] = GlobalEntry.new(name: decl.name, decl: decl, outer: outer) + global_decls[decl.name] = GlobalEntry.new(name: decl.name, decl: decl, context: context) when AST::Declarations::ClassAlias, AST::Declarations::ModuleAlias name = decl.new_name.with_prefix(namespace) @@ -454,35 +349,40 @@ def insert_decl(decl, outer:, namespace:) when ClassAliasEntry, ModuleAliasEntry, ConstantEntry raise DuplicatedDeclarationError.new(name, decl, entry.decl) when ClassEntry, ModuleEntry - raise DuplicatedDeclarationError.new(name, decl, *entry.decls.map(&:decl)) + raise DuplicatedDeclarationError.new(name, decl, *entry.each_decl.to_a) end end case decl when AST::Declarations::ClassAlias - class_alias_decls[name] = ClassAliasEntry.new(name: name, decl: decl, outer: outer) + class_alias_decls[name] = ClassAliasEntry.new(name: name, decl: decl, context: context) when AST::Declarations::ModuleAlias - class_alias_decls[name] = ModuleAliasEntry.new(name: name, decl: decl, outer: outer) + class_alias_decls[name] = ModuleAliasEntry.new(name: name, decl: decl, context: context) end end end - def <<(decl) - declarations << decl - insert_decl(decl, outer: [], namespace: Namespace.root) - self + def add_source(source) + sources << source + + source.declarations.each do |decl| + insert_decl(decl, context: nil, namespace: Namespace.root) + end end - def add_signature(buffer:, directives:, decls:) - signatures[buffer] = [directives, decls] - decls.each do |decl| - self << decl + def each_rbs_source(&block) + if block + sources.each do |source| + yield source + end + else + enum_for(:each_rbs_source) end end def validate_type_params class_decls.each_value do |decl| - decl.primary + decl.validate_type_params end end @@ -501,7 +401,7 @@ def resolve_signature(resolver, table, dirs, decls, only: nil) if only && !only.member?(decl) decl else - resolve_declaration(resolver, map, decl, outer: [], prefix: Namespace.root) + resolve_declaration(resolver, map, decl, context: nil, prefix: Namespace.root) end end @@ -519,12 +419,14 @@ def resolve_type_names(only: nil) table.known_types.merge(interface_decls.keys) table.compute_children - signatures.each do |buffer, (dirs, decls)| - resolve = dirs.find { _1.is_a?(AST::Directives::ResolveTypeNames) } #: AST::Directives::ResolveTypeNames? + each_rbs_source do |source| + resolve = source.directives.find { _1.is_a?(AST::Directives::ResolveTypeNames) } #: AST::Directives::ResolveTypeNames? if !resolve || resolve.value - _, decls = resolve_signature(resolver, table, dirs, decls) + _, decls = resolve_signature(resolver, table, source.directives, source.declarations) + else + decls = source.declarations end - env.add_signature(buffer: buffer, directives: dirs, decls: decls) + env.add_source(Source::RBS.new(source.buffer, source.directives, decls)) end env @@ -545,7 +447,7 @@ def append_context(context, decl) end end - def resolve_declaration(resolver, map, decl, outer:, prefix:) + def resolve_declaration(resolver, map, decl, context:, prefix:) if decl.is_a?(AST::Declarations::Global) # @type var decl: AST::Declarations::Global return AST::Declarations::Global.new( @@ -557,14 +459,11 @@ def resolve_declaration(resolver, map, decl, outer:, prefix:) ) end - 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), @@ -585,7 +484,7 @@ def resolve_declaration(resolver, map, decl, outer:, prefix:) resolver, map, member, - outer: outer_, + context: inner_context, prefix: prefix_ ) else @@ -601,7 +500,6 @@ def resolve_declaration(resolver, map, decl, outer:, prefix:) 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), @@ -622,7 +520,7 @@ def resolve_declaration(resolver, map, decl, outer:, prefix:) resolver, map, member, - outer: outer_, + context: inner_context, prefix: prefix_ ) else @@ -816,15 +714,16 @@ def inspect end def buffers - signatures.keys + sources.map(&:buffer) end def unload(buffers) env = Environment.new + bufs = buffers.to_set - signatures.each do |buf, (dirs, decls)| - next if buffers.include?(buf) - env.add_signature(buffer: buf, directives: dirs, decls: decls) + each_rbs_source do |source| + next if bufs.include?(source.buffer) + env.add_source(source) end env diff --git a/lib/rbs/environment/class_entry.rb b/lib/rbs/environment/class_entry.rb new file mode 100644 index 000000000..121310cf6 --- /dev/null +++ b/lib/rbs/environment/class_entry.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +module RBS + class Environment + class ClassEntry + attr_reader :name + + attr_reader :context_decls + + def initialize(name) + @name = name + @context_decls = [] + end + + def <<(context_decl) + context_decls << context_decl + @primary_decl = nil + self + end + + def each_decl(&block) + if block + context_decls.each do |_, decl| + yield decl + end + else + enum_for(__method__ || raise) + end + end + + def empty? + context_decls.empty? + end + + def primary_decl + @primary_decl ||= nil.tap do + # @type break: declaration + + decl = each_decl.find {|decl| decl.super_class } + break decl if decl + + decl = each_decl.first + break decl if decl + end || raise("Cannot find primary declaration for #{name}") + end + + def type_params + validate_type_params + primary_decl.type_params + end + + def validate_type_params + unless context_decls.empty? + first_decl, *rest_decls = each_decl.to_a + first_decl or raise + + first_params = first_decl.type_params + first_names = first_params.map(&:name) + rest_decls.each do |other_decl| + other_params = other_decl.type_params + unless first_names.size == other_params.size && first_params == AST::TypeParam.rename(other_params, new_names: first_names) + raise GenericParameterMismatchError.new(name: name, decl: other_decl) + end + end + end + end + end + end +end diff --git a/lib/rbs/environment/module_entry.rb b/lib/rbs/environment/module_entry.rb new file mode 100644 index 000000000..be466169c --- /dev/null +++ b/lib/rbs/environment/module_entry.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +module RBS + class Environment + class ModuleEntry + attr_reader :name + + attr_reader :context_decls + + def initialize(name) + @name = name + @context_decls = [] + end + + def <<(context_decl) + context_decls << context_decl + self + end + + def each_decl(&block) + if block + context_decls.each do |_, decl| + yield decl + end + else + enum_for(__method__ || raise) + end + end + + def empty? + context_decls.empty? + end + + def primary_decl + each_decl.first or raise + end + + def type_params + validate_type_params + primary_decl.type_params + end + + def self_types + each_decl.flat_map do |decl| + decl.self_types + end.uniq + end + + def validate_type_params + unless context_decls.empty? + first_decl, *rest_decls = each_decl.to_a + first_decl or raise + + first_params = first_decl.type_params + first_names = first_params.map(&:name) + rest_decls.each do |other_decl| + other_params = other_decl.type_params + unless first_names.size == other_params.size && first_params == AST::TypeParam.rename(other_params, new_names: first_names) + raise GenericParameterMismatchError.new(name: name, decl: other_decl) + end + end + end + end + end + end +end diff --git a/lib/rbs/environment_loader.rb b/lib/rbs/environment_loader.rb index cdfde8335..61a64c7a8 100644 --- a/lib/rbs/environment_loader.rb +++ b/lib/rbs/environment_loader.rb @@ -122,7 +122,7 @@ def load(env:) decls.each do |decl| loaded << [decl, path, source] end - env.add_signature(buffer: buffer, directives: dirs, decls: decls) + env.add_source(Source::RBS.new(buffer, dirs, decls)) end loaded @@ -161,7 +161,7 @@ def each_signature next if files.include?(path) files << path - buffer = Buffer.new(name: path.to_s, content: path.read(encoding: "UTF-8")) + buffer = Buffer.new(name: path, content: path.read(encoding: "UTF-8")) _, dirs, decls = Parser.parse_signature(buffer) diff --git a/lib/rbs/errors.rb b/lib/rbs/errors.rb index 88ceb69f8..ef22b1ccd 100644 --- a/lib/rbs/errors.rb +++ b/lib/rbs/errors.rb @@ -371,7 +371,7 @@ class SuperclassMismatchError < DefinitionError def initialize(name:, entry:) @name = name @entry = entry - super "#{Location.to_string entry.primary.decl.location}: Superclass mismatch: #{name}" + super "#{Location.to_string entry.primary_decl.location}: Superclass mismatch: #{name}" end end diff --git a/lib/rbs/parser_aux.rb b/lib/rbs/parser_aux.rb index ea4ecd82d..8b746d39b 100644 --- a/lib/rbs/parser_aux.rb +++ b/lib/rbs/parser_aux.rb @@ -71,7 +71,7 @@ def self.lex(source) def self.buffer(source) case source when String - Buffer.new(content: source, name: "a.rbs") + Buffer.new(content: source, name: Pathname("a.rbs")) when Buffer source end diff --git a/lib/rbs/prototype/runtime.rb b/lib/rbs/prototype/runtime.rb index 7a8548b0c..1b3b1d9f9 100644 --- a/lib/rbs/prototype/runtime.rb +++ b/lib/rbs/prototype/runtime.rb @@ -50,8 +50,8 @@ def mixin_decls(type_name) type_name_absolute = type_name.absolute! @mixin_decls_cache ||= {} #: Hash[TypeName, Array[AST::Members::Mixin]] @mixin_decls_cache.fetch(type_name_absolute) do - @mixin_decls_cache[type_name_absolute] = @builder.env.class_decls[type_name_absolute].decls.flat_map do |d| - d.decl.members.select { |m| m.kind_of?(AST::Members::Mixin) } + @mixin_decls_cache[type_name_absolute] = @builder.env.class_decls[type_name_absolute].each_decl.flat_map do |decl| + decl.members.select { |m| m.kind_of?(AST::Members::Mixin) } end end end diff --git a/lib/rbs/source.rb b/lib/rbs/source.rb new file mode 100644 index 000000000..0c912a547 --- /dev/null +++ b/lib/rbs/source.rb @@ -0,0 +1,52 @@ +# frozen_string_literal: true + +module RBS + module Source + class RBS + attr_reader :buffer, :directives, :declarations + + def initialize(buffer, directives, decls) + @buffer = buffer + @directives = directives + @declarations = decls + end + + def each_type_name(&block) + if block + set = Set[] #: Set[TypeName] + declarations.each do |decl| + each_declaration_type_name(set, decl, &block) + end + else + enum_for :each_type_name + end + end + + def each_declaration_type_name(names, decl, &block) + case decl + when AST::Declarations::Class + decl.each_decl { each_declaration_type_name(names, _1, &block) } + type_name = decl.name + when AST::Declarations::Module + decl.each_decl { each_declaration_type_name(names, _1, &block) } + type_name = decl.name + when AST::Declarations::Interface + type_name = decl.name + when AST::Declarations::TypeAlias + type_name = decl.name + when AST::Declarations::ModuleAlias + type_name = decl.new_name + when AST::Declarations::ClassAlias + type_name = decl.new_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 ed8fa14f1..d0a7487aa 100644 --- a/lib/rbs/subtractor.rb +++ b/lib/rbs/subtractor.rb @@ -129,9 +129,9 @@ def call(minuend = @minuend, context: nil) entry = @subtrahend.class_decls[owner] return unless entry - decls = entry.decls.map { |d| d.decl } - - decls.each { |d| d.members.each { |m| block.call(m) } } + entry.each_decl do |d| + d.members.each { |m| block.call(m) } + end end private def mixin_exist?(owner, mixin, context:) diff --git a/rbs.gemspec b/rbs.gemspec index a95dd6822..0c66dab4a 100644 --- a/rbs.gemspec +++ b/rbs.gemspec @@ -46,4 +46,5 @@ Gem::Specification.new do |spec| spec.require_paths = ["lib"] spec.required_ruby_version = ">= 3.1" spec.add_dependency "logger" + spec.add_dependency "prism", ">= 1.3.0" end diff --git a/sig/buffer.rbs b/sig/buffer.rbs index 296ead2e0..9d23ad6aa 100644 --- a/sig/buffer.rbs +++ b/sig/buffer.rbs @@ -6,7 +6,7 @@ module RBS type loc = [Integer, Integer] # Name to identify Buffer. - attr_reader name: untyped + attr_reader name: Pathname # The content of the buffer. attr_reader content: String @@ -15,7 +15,7 @@ module RBS @ranges: Array[Range[Integer]] - def initialize: (name: untyped name, content: String content) -> void + def initialize: (name: Pathname name, content: String content) -> void def lines: () -> Array[String] diff --git a/sig/environment.rbs b/sig/environment.rbs index 95b2d1fd1..9d9e889ec 100644 --- a/sig/environment.rbs +++ b/sig/environment.rbs @@ -2,75 +2,14 @@ module RBS class Environment type module_decl = AST::Declarations::Class | AST::Declarations::Module - interface _ModuleOrClass - def name: () -> TypeName - - def type_params: () -> Array[AST::TypeParam] - end - - interface _NamedDecl - def name: () -> TypeName - end - - module ContextUtil - def calculate_context: (Array[_NamedDecl]) -> Resolver::context - end - - # Name of object, it's (multiple) declarations with the outer module declarations - # - class MultiEntry[M < _ModuleOrClass] - class D[M < _ModuleOrClass] - attr_reader decl: M - attr_reader outer: Array[module_decl] - - def initialize: (decl: M, outer: Array[module_decl]) -> void - - include ContextUtil - - @context: Resolver::context - - def context: () -> Resolver::context - end - - attr_reader name: TypeName - attr_reader decls: Array[D[M]] - - @primary: D[M]? - - def initialize: (name: TypeName) -> void - - def insert: (decl: M, outer: Array[module_decl]) -> void - - def validate_type_params: () -> void - - def compatible_params?: (Array[AST::TypeParam], Array[AST::TypeParam]) -> boolish - - def type_params: () -> Array[AST::TypeParam] - - def primary: () -> D[M] - end - - class ModuleEntry < MultiEntry[AST::Declarations::Module] - def self_types: () -> Array[AST::Declarations::Module::Self] - end - - class ClassEntry < MultiEntry[AST::Declarations::Class] - end - # Name of object, it's (single) declaration, and the outer module declarations # class SingleEntry[N, D] attr_reader name: N attr_reader decl: D - attr_reader outer: Array[module_decl] - - def initialize: (name: N, decl: D, outer: Array[module_decl]) -> void - - include ContextUtil - - @context: Resolver::context + attr_reader context: Resolver::context - def context: () -> Resolver::context + def initialize: (name: N, decl: D, context: Resolver::context) -> void end class ModuleAliasEntry < SingleEntry[TypeName, AST::Declarations::ModuleAlias] @@ -91,8 +30,9 @@ module RBS class GlobalEntry < SingleEntry[Symbol, AST::Declarations::Global] end - # Top level declarations - attr_reader declarations: Array[AST::Declarations::t] + # Array of source objects loaded in the environment + # + attr_reader sources: Array[Source::RBS] # Class declarations attr_reader class_decls: Hash[TypeName, ModuleEntry | ClassEntry] @@ -111,10 +51,6 @@ module RBS # Global declarations attr_reader global_decls: Hash[Symbol, GlobalEntry] - # A hash from Buffer to it's contents - # - attr_reader signatures: Hash[Buffer, [Array[AST::Directives::t], Array[AST::Declarations::t]]] - def initialize: () -> void def initialize_copy: (Environment) -> void @@ -123,15 +59,14 @@ module RBS # def self.from_loader: (EnvironmentLoader) -> Environment - def add_signature: (buffer: Buffer, directives: Array[AST::Directives::t], decls: Array[AST::Declarations::t]) -> void + def add_source: (Source::RBS) -> void - # Insert a toplevel declaration into the environment - # - def <<: (AST::Declarations::t decl) -> self + def each_rbs_source: () { (Source::RBS) -> void } -> void + | () -> Enumerator[Source::RBS] # Insert a declaration into the environment # - private def insert_decl: (AST::Declarations::t, outer: Array[module_decl], namespace: Namespace) -> void + private def insert_decl: (AST::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. @@ -264,7 +199,7 @@ module RBS def append_context: (Resolver::context, module_decl) -> Resolver::context - def resolve_declaration: (Resolver::TypeNameResolver resolver, UseMap map, AST::Declarations::t decl, outer: Array[module_decl], prefix: Namespace) -> AST::Declarations::t + def resolve_declaration: (Resolver::TypeNameResolver resolver, UseMap map, AST::Declarations::t decl, context: Resolver::context, prefix: Namespace) -> AST::Declarations::t def resolve_member: (Resolver::TypeNameResolver, UseMap map, AST::Members::t, context: Resolver::context) -> AST::Members::t diff --git a/sig/environment/class_entry.rbs b/sig/environment/class_entry.rbs new file mode 100644 index 000000000..8fe988fb5 --- /dev/null +++ b/sig/environment/class_entry.rbs @@ -0,0 +1,50 @@ +module RBS + class Environment + # Represents a class entry in the environment + # + # ```rb + # entry = ClassEntry.new(TypeName.parse("::String")) + # entry << [nil, declaration] + # entry << [[nil, TypeName.parse("::Kernel")], declaration] + # ``` + class ClassEntry + attr_reader name: TypeName + + type declaration = AST::Declarations::Class + + type context_decl = [Resolver::context, declaration] + + attr_reader context_decls: Array[context_decl] + + @primary_decl: declaration? + + def initialize: (TypeName) -> void + + def <<: (context_decl) -> self + + def each_decl: { (declaration) -> void } -> void + | () -> Enumerator[declaration] + + # Returns true if the entry doesn't have any declaration + # + def empty?: () -> bool + + # Find the *primary* declaration of the class + # + # * Returns the first declaration with super class + # * Returns the first declaration if the declaration doesn't have super class + # + %a{pure} def primary_decl: () -> declaration + + # Returns the generics declaration + # + def type_params: () -> Array[AST::TypeParam] + + # Confirms if the type parameters in the declaration are compatible + # + # * Raises `GenericParameterMismatchError` if incompatible declaration is detected. + # + def validate_type_params: () -> void + end + end +end diff --git a/sig/environment/module_entry.rbs b/sig/environment/module_entry.rbs new file mode 100644 index 000000000..fdbf90582 --- /dev/null +++ b/sig/environment/module_entry.rbs @@ -0,0 +1,50 @@ +module RBS + class Environment + # Represents a class entry in the environment + # + # ```rb + # entry = ModuleEntry.new(TypeName.parse("::Kernel")) + # entry << [nil, declaration] + # entry << [[nil, TypeName.parse("::Object")], declaration] + # ``` + # + class ModuleEntry + attr_reader name: TypeName + + type declaration = AST::Declarations::Module + + type context_decl = [Resolver::context, declaration] + + attr_reader context_decls: Array[context_decl] + + def initialize: (TypeName) -> void + + def <<: (context_decl) -> self + + def each_decl: { (declaration) -> void } -> void + | () -> Enumerator[declaration] + + # Returns true if the entry doesn't have any declaration + # + def empty?: () -> bool + + # Returns the first declaration + # + # This method helps using the class with `ClassEntry` objects. + # + def primary_decl: () -> declaration + + # Returns the generics declaration + # + def type_params: () -> Array[AST::TypeParam] + + # Confirms if the type parameters in the declaration are compatible + # + # * Raises `GenericParameterMismatchError` if incompatible declaration is detected. + # + def validate_type_params: () -> void + + def self_types: () -> Array[AST::Declarations::Module::Self] + end + end +end diff --git a/sig/source.rbs b/sig/source.rbs new file mode 100644 index 000000000..13257635b --- /dev/null +++ b/sig/source.rbs @@ -0,0 +1,22 @@ +module RBS + module Source + class RBS + attr_reader buffer: Buffer + + attr_reader directives: Array[AST::Directives::t] + + attr_reader declarations: Array[AST::Declarations::t] + + def initialize: (Buffer, Array[AST::Directives::t], Array[AST::Declarations::t]) -> void + + # Enumerates defined type names in the source + # + # The order is undefined. Deduplicated per source object. + # + def each_type_name: () { (TypeName) -> void } -> void + | () -> Enumerator[TypeName] + + private def each_declaration_type_name: (Set[TypeName], AST::Declarations::t) { (TypeName) -> void } -> void + end + end +end diff --git a/steep/Gemfile b/steep/Gemfile index 9fc874c55..fb1f283f5 100644 --- a/steep/Gemfile +++ b/steep/Gemfile @@ -2,3 +2,5 @@ source "https://rubygems.org" gem "rbs", "~> 3.9.pre" gem "steep", "~> 1.10" +gem "ruby-lsp" +gem "test-unit" diff --git a/steep/Gemfile.lock b/steep/Gemfile.lock index 13484e3a3..171a02def 100644 --- a/steep/Gemfile.lock +++ b/steep/Gemfile.lock @@ -37,6 +37,8 @@ GEM parser (3.3.7.4) ast (~> 2.4.1) racc + power_assert (2.0.5) + prism (1.4.0) racc (1.8.1) rainbow (3.1.1) rb-fsevent (0.11.2) @@ -44,7 +46,13 @@ GEM ffi (~> 1.0) rbs (3.9.0) logger + ruby-lsp (0.23.13) + language_server-protocol (~> 3.17.0) + prism (>= 1.2, < 2.0) + rbs (>= 3, < 4) + sorbet-runtime (>= 0.5.10782) securerandom (0.4.1) + sorbet-runtime (0.5.11980) steep (1.10.0) activesupport (>= 5.1) concurrent-ruby (>= 1.1.10) @@ -65,6 +73,8 @@ GEM strscan (3.1.2) terminal-table (4.0.0) unicode-display_width (>= 1.1.1, < 4) + test-unit (3.6.7) + power_assert tzinfo (2.0.6) concurrent-ruby (~> 1.0) unicode-display_width (3.1.4) @@ -77,7 +87,9 @@ PLATFORMS DEPENDENCIES rbs (~> 3.9.pre) + ruby-lsp steep (~> 1.10) + test-unit BUNDLED WITH 2.6.3 diff --git a/test/rbs/definition_builder_test.rb b/test/rbs/definition_builder_test.rb index 9e3bf0e7b..14e6bc5d3 100644 --- a/test/rbs/definition_builder_test.rb +++ b/test/rbs/definition_builder_test.rb @@ -2323,7 +2323,7 @@ class Foo end DEF RBS::Parser.parse_signature(rbs).tap do |buf, dirs, decls| - env.add_signature(buffer: buf, directives: dirs, decls: decls) + env.add_source(RBS::Source::RBS.new(buf, dirs, decls)) end definition_builder = RBS::DefinitionBuilder.new(env: env.resolve_type_names) definition_builder.build_instance(RBS::TypeName.parse("::Foo")).tap do |defn| diff --git a/test/rbs/environment_test.rb b/test/rbs/environment_test.rb index 833b55e57..5f86112aa 100644 --- a/test/rbs/environment_test.rb +++ b/test/rbs/environment_test.rb @@ -7,29 +7,6 @@ class RBS::EnvironmentTest < Test::Unit::TestCase Namespace = RBS::Namespace InvalidTypeApplicationError = RBS::InvalidTypeApplicationError - def test_entry_context - _, _, decls = RBS::Parser.parse_signature(< bool end RBS - env.add_signature(buffer: buf, directives: dirs, decls: decls) + env.add_source(RBS::Source::RBS.new(buf, dirs, decls)) env_ = env.resolve_type_names writer = RBS::Writer.new(out: StringIO.new) - writer.write(env_.declarations) + writer.write(env_.each_rbs_source.flat_map { _1.declarations }) assert_equal(<= "3.4" - + SignatureManager.new do |manager| manager.build do |env| p = Runtime.new(patterns: ["RBS::RuntimePrototypeTest::TestForYield"], env: env, merge: true) @@ -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 << decl + env.insert_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 << decl + env.insert_decl(decl, context: nil, namespace: RBS::Namespace.root) end env.resolve_type_names assert(true) # nothing raised above diff --git a/test/rbs/subtractor_test.rb b/test/rbs/subtractor_test.rb index 8d4e9f6e8..a534289d7 100644 --- a/test/rbs/subtractor_test.rb +++ b/test/rbs/subtractor_test.rb @@ -667,14 +667,15 @@ module M2 private def to_decls(rbs) # It ignores directives, is it ok? - RBS::Parser.parse_signature(rbs).last + _, _, decls = RBS::Parser.parse_signature(rbs) + decls end private def to_env(rbs) RBS::Environment.new.tap do |env| - to_decls(rbs).each do |decl| - env << decl - end + buf, dirs, decls = RBS::Parser.parse_signature(rbs) + source = RBS::Source::RBS.new(buf, dirs, decls) + env.add_source(source) end end diff --git a/test/validator_test.rb b/test/validator_test.rb index 8772a8695..29212b6af 100644 --- a/test/validator_test.rb +++ b/test/validator_test.rb @@ -411,7 +411,7 @@ class Foo resolver = RBS::Resolver::TypeNameResolver.new(env) validator = RBS::Validator.new(env: env, resolver: resolver) - env.class_decls[RBS::TypeName.parse("::Foo")].decls.first.decl.members.tap do |members| + env.class_decls[RBS::TypeName.parse("::Foo")].primary_decl.members.tap do |members| members[0].tap do |member| assert_raises(RBS::NoTypeFoundError) do validator.validate_variable(member)