lib/tapioca/gem/pipeline.rb in tapioca-0.8.3 vs lib/tapioca/gem/pipeline.rb in tapioca-0.9.0

- old
+ new

@@ -1,16 +1,14 @@ # typed: strict # frozen_string_literal: true -require "pathname" - module Tapioca module Gem class Pipeline extend T::Sig include Runtime::Reflection - include SignaturesHelper + include RBIHelper IGNORED_SYMBOLS = T.let(["YAML", "MiniTest", "Mutex"], T::Array[String]) sig { returns(Gemfile::GemSpec) } attr_reader :gem @@ -23,12 +21,12 @@ @alias_namespace = T.let(Set.new, T::Set[String]) @events = T.let([], T::Array[Gem::Event]) @payload_symbols = T.let(Static::SymbolLoader.payload_symbols, T::Set[String]) - @bootstrap_symbols = T.let(Static::SymbolLoader.gem_symbols(@gem).union(Static::SymbolLoader.engine_symbols), - T::Set[String]) + @bootstrap_symbols = T.let(load_bootstrap_symbols(@gem), T::Set[String]) + @bootstrap_symbols.each { |symbol| push_symbol(symbol) } @node_listeners = T.let([], T::Array[Gem::Listeners::Base]) @node_listeners << Gem::Listeners::SorbetTypeVariables.new(self) @node_listeners << Gem::Listeners::Mixins.new(self) @@ -39,10 +37,11 @@ @node_listeners << Gem::Listeners::SorbetProps.new(self) @node_listeners << Gem::Listeners::SorbetRequiredAncestors.new(self) @node_listeners << Gem::Listeners::SorbetSignatures.new(self) @node_listeners << Gem::Listeners::Subconstants.new(self) @node_listeners << Gem::Listeners::YardDoc.new(self) if include_doc + @node_listeners << Gem::Listeners::ForeignConstants.new(self) @node_listeners << Gem::Listeners::RemoveEmptyPayloadScopes.new(self) end sig { returns(RBI::Tree) } def compile @@ -58,21 +57,35 @@ sig { params(symbol: String, constant: BasicObject).void.checked(:never) } def push_constant(symbol, constant) @events << Gem::ConstantFound.new(symbol, constant) end + sig { params(symbol: String, constant: Module).void.checked(:never) } + def push_foreign_constant(symbol, constant) + @events << Gem::ForeignConstantFound.new(symbol, constant) + end + sig { params(symbol: String, constant: Module, node: RBI::Const).void.checked(:never) } def push_const(symbol, constant, node) @events << Gem::ConstNodeAdded.new(symbol, constant, node) end - sig { params(symbol: String, constant: Module, node: RBI::Scope).void.checked(:never) } + sig do + params(symbol: String, constant: Module, node: RBI::Scope).void.checked(:never) + end def push_scope(symbol, constant, node) @events << Gem::ScopeNodeAdded.new(symbol, constant, node) end sig do + params(symbol: String, constant: Module, node: RBI::Scope).void.checked(:never) + end + def push_foreign_scope(symbol, constant, node) + @events << Gem::ForeignScopeNodeAdded.new(symbol, constant, node) + end + + sig do params( symbol: String, constant: Module, node: RBI::Method, signature: T.untyped, @@ -112,10 +125,18 @@ name end private + sig { params(gem: Gemfile::GemSpec).returns(T::Set[String]) } + def load_bootstrap_symbols(gem) + engine_symbols = Static::SymbolLoader.engine_symbols(gem) + gem_symbols = Static::SymbolLoader.gem_symbols(gem) + + gem_symbols.union(engine_symbols) + end + sig { returns(Gem::Event) } def next_event T.must(@events.shift) end @@ -148,26 +169,32 @@ return if name.strip.empty? return if name.start_with?("#<") return if name.downcase == name return if alias_namespaced?(name) - return if seen?(name) - constant = event.constant - return if T::Enum === constant # T::Enum instances are defined via `compile_enums` + return if T::Enum === event.constant # T::Enum instances are defined via `compile_enums` - mark_seen(name) - compile_constant(name, constant) + if event.is_a?(Gem::ForeignConstantFound) + compile_foreign_constant(name, event.constant) + else + compile_constant(name, event.constant) + end end sig { params(event: Gem::NodeAdded).void } def on_node(event) @node_listeners.each { |listener| listener.dispatch(event) } end # Compile + sig { params(symbol: String, constant: Module).void } + def compile_foreign_constant(symbol, constant) + compile_module(symbol, constant, foreign_constant: true) + end + sig { params(symbol: String, constant: BasicObject).void.checked(:never) } def compile_constant(symbol, constant) case constant when Module if name_of(constant) != symbol @@ -180,10 +207,14 @@ end end sig { params(name: String, constant: Module).void } def compile_alias(name, constant) + return if seen?(name) + + mark_seen(name) + return if symbol_in_payload?(name) target = name_of(constant) # If target has no name, let's make it an anonymous class or module with `Class.new` or `Module.new` target = "#{constant.class}.new" unless target @@ -197,10 +228,14 @@ @root << node end sig { params(name: String, value: BasicObject).void.checked(:never) } def compile_object(name, value) + return if seen?(name) + + mark_seen(name) + return if symbol_in_payload?(name) klass = class_of(value) klass_name = if klass == ObjectSpace::WeakMap @@ -226,23 +261,31 @@ node = RBI::Const.new(name, "T.let(T.unsafe(nil), #{type_name})") push_const(name, klass, node) @root << node end - sig { params(name: String, constant: Module).void } - def compile_module(name, constant) - return unless defined_in_gem?(constant, strict: false) + sig { params(name: String, constant: Module, foreign_constant: T::Boolean).void } + def compile_module(name, constant, foreign_constant: false) + return unless defined_in_gem?(constant, strict: false) || foreign_constant return if Tapioca::TypeVariableModule === constant + return if seen?(name) + mark_seen(name) + scope = if constant.is_a?(Class) superclass = compile_superclass(constant) RBI::Class.new(name, superclass_name: superclass) else RBI::Module.new(name) end - push_scope(name, constant, scope) + if foreign_constant + push_foreign_scope(name, constant, scope) + else + push_scope(name, constant, scope) + end + @root << scope end sig { params(constant: Class).returns(T.nilable(String)) } def compile_superclass(constant)