class Solargraph::Source::Chain
Represents an expression as a single call chain at the parse tree level, made up of constants, variables, and method calls, each represented as a Link object.
Computes Pins and/or ComplexTypes representing the interpreted expression.
Constants
- UNDEFINED_CALL
- UNDEFINED_CONSTANT
Attributes
@return [::Array<Source::Chain::Link>]
Public Class Methods
Source
# File lib/solargraph/source/chain.rb, line 59 def initialize links, node = nil, splat = false @links = links.clone @links.push UNDEFINED_CALL if @links.empty? head = true @links.map! do |link| result = (head ? link.clone_head : link.clone_body) head = false result end @node = node @splat = splat end
@param node [Parser::AST::Node, nil] @param links [::Array<Chain::Link>] @param splat [Boolean]
Public Instance Methods
Source
# File lib/solargraph/source/chain.rb, line 73 def base @base ||= Chain.new(links[0..-2]) end
@return [Chain]
Source
# File lib/solargraph/source/chain.rb, line 184 def constant? links.last.is_a?(Chain::Constant) end
@return [Boolean]
Source
# File lib/solargraph/source/chain.rb, line 106 def define api_map, name_pin, locals return [] if undefined? # working_pin is the surrounding closure pin for the link # being processed, whose #binder method will provide the LHS / # 'self type' of the next link (same as the #return_type method # --the type of the result so far). # # @todo ProxyType uses 'type' for the binder, but ' working_pin = name_pin links[0..-2].each do |link| pins = link.resolve(api_map, working_pin, locals) type = infer_from_definitions(pins, working_pin, api_map, locals) if type.undefined? logger.debug { "Chain#define(links=#{links.map(&:desc)}, name_pin=#{name_pin.inspect}, locals=#{locals}) => [] - undefined type from #{link.desc}" } return [] end # We continue to use the context from the head pin, in case # we need it to, for instance, provide context for a block # evaluation. However, we use the last link's return type # for the binder, as this is chaining off of it, and the # binder is now the lhs of the rhs we are evaluating. working_pin = Pin::ProxyType.anonymous(name_pin.context, binder: type, closure: name_pin, source: :chain) logger.debug { "Chain#define(links=#{links.map(&:desc)}, name_pin=#{name_pin.inspect}, locals=#{locals}) - after processing #{link.desc}, new working_pin=#{working_pin} with binder #{working_pin.binder}" } end links.last.last_context = working_pin links.last.resolve(api_map, working_pin, locals) end
Determine potential Pins returned by this chain of words
@param api_map [ApiMap]
@param name_pin [Pin::Base] A pin representing the place in which expression is evaluated (e.g., a Method pin, or a Module or Class pin if not run within a method - both in terms of the closure around the chain, as well as the self type used for any method calls in head position.
Requirements for name_pin:
* name_pin.context: This should be a type representing the
namespace where we can look up non-local variables and
method names. If it is a Class<X>, we will look up
:class scoped methods/variables.
* name_pin.binder: Used for method call lookups only
(Chain::Call links). For method calls as the first
element in the chain, 'name_pin.binder' should be the
same as name_pin.context above. For method calls later
in the chain (e.g., 'b' in a.b.c), it should represent
'a'.
@param locals [::Array<Pin::LocalVariable>] Any local
variables / method parameters etc visible by the statement
@return [::Array<Pin::Base>] Pins representing possible return
types of this method.
Source
# File lib/solargraph/source/chain.rb, line 199 def desc links.map(&:desc).to_s end
@return [String]
Source
# File lib/solargraph/source/chain.rb, line 140 def infer api_map, name_pin, locals cache_key = [node, node&.location, links, name_pin&.return_type, locals] if @@inference_invalidation_key == api_map.hash cached = @@inference_cache[cache_key] return cached if cached else @@inference_invalidation_key = api_map.hash @@inference_cache = {} end out = infer_uncached(api_map, name_pin, locals).downcast_to_literal_if_possible logger.debug { "Chain#infer() - caching result - cache_key_hash=#{cache_key.hash}, links.map(&:hash)=#{links.map(&:hash)}, links=#{links}, cache_key.map(&:hash) = #{cache_key.map(&:hash)}, cache_key=#{cache_key}" } @@inference_cache[cache_key] = out end
@param api_map [ApiMap] @param name_pin [Pin::Base] The pin for the closure in which this code runs @param locals [::Array<Pin::LocalVariable>] @return [ComplexType] @sg-ignore
Source
# File lib/solargraph/source/chain.rb, line 158 def infer_uncached api_map, name_pin, locals pins = define(api_map, name_pin, locals) if pins.empty? logger.debug { "Chain#infer_uncached(links=#{links.map(&:desc)}, locals=#{locals.map(&:desc)}) => undefined - no pins" } return ComplexType::UNDEFINED end type = infer_from_definitions(pins, links.last.last_context, api_map, locals) out = maybe_nil(type) logger.debug { "Chain#infer_uncached(links=#{self.links.map(&:desc)}, locals=#{locals.map(&:desc)}, name_pin=#{name_pin}, name_pin.closure=#{name_pin.closure.inspect}, name_pin.binder=#{name_pin.binder}) => #{out.rooted_tags.inspect}" } out end
@param api_map [ApiMap] @param name_pin [Pin::Base] @param locals [::Array<Pin::LocalVariable>] @return [ComplexType]
Source
# File lib/solargraph/source/chain.rb, line 171 def literal? links.last.is_a?(Chain::Literal) end
@return [Boolean]
Source
# File lib/solargraph/source/chain.rb, line 192 def nullable? links.any?(&:nullable?) end
Source
# File lib/solargraph/source/chain.rb, line 175 def undefined? links.any?(&:undefined?) end
Protected Instance Methods
Source
# File lib/solargraph/source/chain.rb, line 52 def equality_fields [links, node] end
@sg-ignore Fix “Not enough arguments to Module#protected”
Private Instance Methods
Source
# File lib/solargraph/source/chain.rb, line 216 def infer_from_definitions pins, context, api_map, locals # @type [::Array<ComplexType>] types = [] unresolved_pins = [] # @todo this param tag shouldn't be needed to probe the type # @todo ...but given it is needed, typecheck should complain that it is needed # @param pin [Pin::Base] pins.each do |pin| # Avoid infinite recursion next if @@inference_stack.include?(pin) @@inference_stack.push pin type = pin.typify(api_map) @@inference_stack.pop if type.defined? if type.generic? # @todo even at strong, no typechecking complaint # happens when a [Pin::Base,nil] is passed into a method # that accepts only [Pin::Namespace] as an argument type = type.resolve_generics(pin.closure, context.binder) end types << type else unresolved_pins << pin end end # Limit method inference recursion if @@inference_depth >= 10 && pins.first.is_a?(Pin::Method) return ComplexType::UNDEFINED end @@inference_depth += 1 # @param pin [Pin::Base] unresolved_pins.each do |pin| # Avoid infinite recursion if @@inference_stack.include?(pin.identity) next end @@inference_stack.push(pin.identity) type = pin.probe(api_map) @@inference_stack.pop types.push type if type end @@inference_depth -= 1 type = if types.empty? ComplexType::UNDEFINED elsif types.length > 1 # Move nil to the end by convention # @param a [ComplexType::UniqueType] sorted = types.flat_map(&:items).sort { |a, _| a.tag == 'nil' ? 1 : 0 } ComplexType.new(sorted.uniq) else ComplexType.new(types) end if context.nil? || context.return_type.undefined? # up to downstream to resolve self type return type end type.self_to_type(context.return_type) end
@param pins [::Array<Pin::Base>] @param context [Pin::Base] @param api_map [ApiMap] @param locals [::Enumerable<Pin::LocalVariable>] @return [ComplexType]
Source
# File lib/solargraph/source/chain.rb, line 284 def maybe_nil type return type if type.undefined? || type.void? || type.nullable? return type unless nullable? ComplexType.new(type.items + [ComplexType::NIL]) end
@param type [ComplexType] @return [ComplexType]