Class: Tapioca::Gem::Pipeline

Inherits:
Object
  • Object
show all
Includes:
RBIHelper, Runtime::Reflection
Defined in:
lib/tapioca/gem/pipeline.rb

Defined Under Namespace

Classes: MethodDefinitionLookupResult, MethodInGemWithLocation, MethodInGemWithoutLocation, MethodNotInGem, MethodUnknown

Constant Summary collapse

IGNORED_SYMBOLS =

: Array

["YAML", "MiniTest", "Mutex"]

Constants included from SorbetHelper

SorbetHelper::FEATURE_REQUIREMENTS, SorbetHelper::SORBET_BIN, SorbetHelper::SORBET_EXE_PATH_ENV_VAR, SorbetHelper::SORBET_GEM_SPEC, SorbetHelper::SORBET_PAYLOAD_URL, SorbetHelper::SPOOM_CONTEXT

Constants included from Runtime::Reflection

Runtime::Reflection::ANCESTORS_METHOD, Runtime::Reflection::CLASS_METHOD, Runtime::Reflection::CONSTANTS_METHOD, Runtime::Reflection::EQUAL_METHOD, Runtime::Reflection::METHOD_METHOD, Runtime::Reflection::NAME_METHOD, Runtime::Reflection::OBJECT_ID_METHOD, Runtime::Reflection::PRIVATE_INSTANCE_METHODS_METHOD, Runtime::Reflection::PROTECTED_INSTANCE_METHODS_METHOD, Runtime::Reflection::PUBLIC_INSTANCE_METHODS_METHOD, Runtime::Reflection::REQUIRED_FROM_LABELS, Runtime::Reflection::SINGLETON_CLASS_METHOD, Runtime::Reflection::SUPERCLASS_METHOD, Runtime::Reflection::SignatureBlockError, Runtime::Reflection::UNDEFINED_CONSTANT

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from RBIHelper

#as_nilable_type, #as_non_nilable_type, #create_block_param, #create_kw_opt_param, #create_kw_param, #create_kw_rest_param, #create_opt_param, #create_param, #create_rest_param, #create_typed_param, #sanitize_signature_types, serialize_type_variable, #valid_method_name?, #valid_parameter_name?

Methods included from SorbetHelper

#sorbet, #sorbet_path, #sorbet_supports?

Methods included from Runtime::Reflection

#abstract_type_of, #ancestors_of, #are_equal?, #attached_class_of, #class_of, #const_source_location, #constant_defined?, #constantize, #constants_of, #descendants_of, #file_candidates_for, #final_module?, #inherited_ancestors_of, #method_of, #name_of_type, #object_id_of, #private_instance_methods_of, #protected_instance_methods_of, #public_instance_methods_of, #qualified_name_of, #resolve_loc, #sealed_module?, #signature_of, #signature_of!, #singleton_class_of, #superclass_of

Constructor Details

#initialize(gem, error_handler:, include_doc: false, include_loc: false) ⇒ Pipeline

: (Gemfile::GemSpec gem, error_handler: ^(String error) -> void, ?include_doc: bool, ?include_loc: bool) -> void



19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/tapioca/gem/pipeline.rb', line 19

def initialize(
  gem,
  error_handler:,
  include_doc: false,
  include_loc: false
)
  @root = RBI::Tree.new #: RBI::Tree
  @gem = gem
  @seen = Set.new #: Set[String]
  @alias_namespace = Set.new #: Set[String]
  @error_handler = error_handler

  @events = [] #: Array[Gem::Event]

  @payload_symbols = Static::SymbolLoader.payload_symbols #: Set[String]
  @bootstrap_symbols = load_bootstrap_symbols(@gem) #: Set[String]

  @bootstrap_symbols.each { |symbol| push_symbol(symbol) }

  @node_listeners = [] #: Array[Gem::Listeners::Base]
  @node_listeners << Gem::Listeners::SorbetTypeVariables.new(self)
  @node_listeners << Gem::Listeners::Mixins.new(self)
  @node_listeners << Gem::Listeners::DynamicMixins.new(self)
  @node_listeners << Gem::Listeners::Methods.new(self)
  @node_listeners << Gem::Listeners::SorbetHelpers.new(self)
  @node_listeners << Gem::Listeners::SorbetEnums.new(self)
  @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::SourceLocation.new(self) if include_loc
  @node_listeners << Gem::Listeners::RemoveEmptyPayloadScopes.new(self)
end

Instance Attribute Details

#error_handlerObject (readonly)

: ^(String error) -> void



16
17
18
# File 'lib/tapioca/gem/pipeline.rb', line 16

def error_handler
  @error_handler
end

#gemObject (readonly)

: Gemfile::GemSpec



13
14
15
# File 'lib/tapioca/gem/pipeline.rb', line 13

def gem
  @gem
end

Instance Method Details

#compileObject

: -> RBI::Tree



56
57
58
59
# File 'lib/tapioca/gem/pipeline.rb', line 56

def compile
  dispatch(next_event) until @events.empty?
  @root
end

#constant_in_gem?(name) ⇒ Boolean

: ((String | Symbol) name) -> bool

Returns:

  • (Boolean)


117
118
119
120
121
122
123
124
# File 'lib/tapioca/gem/pipeline.rb', line 117

def constant_in_gem?(name)
  loc = const_source_location(name)

  # If the source location of the constant isn't available or is "(eval)", all bets are off.
  return true if loc.nil? || loc.file.nil? || loc.file == "(eval)"

  gem.contains_path?(loc.file)
end

#method_definition_in_gem(method_name, owner) ⇒ Object

: (Symbol method_name, T::Module owner) -> MethodDefinitionLookupResult



153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/tapioca/gem/pipeline.rb', line 153

def method_definition_in_gem(method_name, owner)
  definitions = Tapioca::Runtime::Trackers::MethodDefinition.method_definitions_for(method_name, owner)

  # If the source location of the method isn't available, signal that by returning nil.
  return MethodUnknown.new if definitions.empty?

  # Look up the first entry that matches a file in the gem.
  found = definitions.find { |loc| @gem.contains_path?(loc.file) }

  unless found
    # If the source location of the method is "(eval)", err on the side of caution and include the method.
    found = definitions.find { |loc| loc.file == "(eval)" }
    # However, we can just return true to signal that the method should be included.
    # We can't provide a source location for it, but we want it to be included in the gem RBI.
    return MethodInGemWithoutLocation.new if found
  end

  # If we searched but couldn't find a source location in the gem, return false to signal that.
  return MethodNotInGem.new unless found

  MethodInGemWithLocation.new(found)
end

#name_of(constant) ⇒ Object

: (T::Module constant) -> String?



179
180
181
182
183
184
185
186
187
188
189
# File 'lib/tapioca/gem/pipeline.rb', line 179

def name_of(constant)
  name = name_of_proxy_target(constant, super(class_of(constant)))
  return name if name

  name = super(constant)
  return if name.nil?
  return unless are_equal?(constant, constantize(name, inherit: true))

  name = "Struct" if name =~ /^(::)?Struct::[^:]+$/
  name
end

#push_const(symbol, constant, node) ⇒ Object

: (String symbol, T::Module constant, RBI::Const node) -> void



80
81
82
# File 'lib/tapioca/gem/pipeline.rb', line 80

def push_const(symbol, constant, node)
  @events << Gem::ConstNodeAdded.new(symbol, constant, node)
end

#push_constant(symbol, constant) ⇒ Object

: (String symbol, BasicObject constant) -> void



70
71
72
# File 'lib/tapioca/gem/pipeline.rb', line 70

def push_constant(symbol, constant)
  @events << Gem::ConstantFound.new(symbol, constant)
end

#push_foreign_constant(symbol, constant) ⇒ Object

: (String symbol, T::Module constant) -> void



75
76
77
# File 'lib/tapioca/gem/pipeline.rb', line 75

def push_foreign_constant(symbol, constant)
  @events << Gem::ForeignConstantFound.new(symbol, constant)
end

#push_foreign_scope(symbol, constant, node) ⇒ Object

: (String symbol, T::Module constant, RBI::Scope node) -> void



90
91
92
# File 'lib/tapioca/gem/pipeline.rb', line 90

def push_foreign_scope(symbol, constant, node)
  @events << Gem::ForeignScopeNodeAdded.new(symbol, constant, node)
end

#push_method(symbol, constant, method, node, signature, parameters) ⇒ Object

: ( | String symbol, | T::Module constant, | UnboundMethod method, | RBI::Method node, | untyped signature, | Array[[Symbol, String]] parameters | ) -> void



102
103
104
# File 'lib/tapioca/gem/pipeline.rb', line 102

def push_method(symbol, constant, method, node, signature, parameters) # rubocop:disable Metrics/ParameterLists
  @events << Gem::MethodNodeAdded.new(symbol, constant, method, node, signature, parameters)
end

#push_scope(symbol, constant, node) ⇒ Object

: (String symbol, T::Module constant, RBI::Scope node) -> void



85
86
87
# File 'lib/tapioca/gem/pipeline.rb', line 85

def push_scope(symbol, constant, node)
  @events << Gem::ScopeNodeAdded.new(symbol, constant, node)
end

#push_symbol(symbol) ⇒ Object

: (String symbol) -> void



64
65
66
# File 'lib/tapioca/gem/pipeline.rb', line 64

def push_symbol(symbol)
  @events << Gem::SymbolFound.new(symbol)
end

#symbol_in_payload?(symbol_name) ⇒ Boolean

: (String symbol_name) -> bool

Returns:

  • (Boolean)


109
110
111
112
113
114
# File 'lib/tapioca/gem/pipeline.rb', line 109

def symbol_in_payload?(symbol_name)
  symbol_name = symbol_name[2..-1] if symbol_name.start_with?("::")
  return false unless symbol_name

  @payload_symbols.include?(symbol_name)
end