solargraph 0.54.5 → 0.55.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +18 -0
  3. data/lib/solargraph/api_map/store.rb +3 -1
  4. data/lib/solargraph/api_map.rb +18 -8
  5. data/lib/solargraph/complex_type/unique_type.rb +88 -7
  6. data/lib/solargraph/complex_type.rb +35 -6
  7. data/lib/solargraph/convention/struct_definition/struct_assignment_node.rb +60 -0
  8. data/lib/solargraph/convention/struct_definition/struct_definition_node.rb +100 -0
  9. data/lib/solargraph/convention/struct_definition.rb +101 -0
  10. data/lib/solargraph/convention.rb +1 -0
  11. data/lib/solargraph/doc_map.rb +49 -18
  12. data/lib/solargraph/language_server/host/message_worker.rb +10 -7
  13. data/lib/solargraph/language_server/host.rb +1 -0
  14. data/lib/solargraph/location.rb +8 -0
  15. data/lib/solargraph/parser/comment_ripper.rb +11 -6
  16. data/lib/solargraph/parser/flow_sensitive_typing.rb +226 -0
  17. data/lib/solargraph/parser/node_methods.rb +14 -0
  18. data/lib/solargraph/parser/node_processor.rb +0 -1
  19. data/lib/solargraph/parser/parser_gem/class_methods.rb +9 -0
  20. data/lib/solargraph/parser/parser_gem/node_chainer.rb +10 -10
  21. data/lib/solargraph/parser/parser_gem/node_methods.rb +3 -1
  22. data/lib/solargraph/parser/parser_gem/node_processors/and_node.rb +21 -0
  23. data/lib/solargraph/parser/parser_gem/node_processors/casgn_node.rb +21 -1
  24. data/lib/solargraph/parser/parser_gem/node_processors/if_node.rb +21 -0
  25. data/lib/solargraph/parser/parser_gem/node_processors/namespace_node.rb +26 -5
  26. data/lib/solargraph/parser/parser_gem/node_processors/opasgn_node.rb +41 -0
  27. data/lib/solargraph/parser/parser_gem/node_processors/until_node.rb +28 -0
  28. data/lib/solargraph/parser/parser_gem/node_processors/while_node.rb +28 -0
  29. data/lib/solargraph/parser/parser_gem/node_processors.rb +10 -0
  30. data/lib/solargraph/parser.rb +1 -0
  31. data/lib/solargraph/pin/base.rb +9 -3
  32. data/lib/solargraph/pin/base_variable.rb +7 -1
  33. data/lib/solargraph/pin/block.rb +2 -0
  34. data/lib/solargraph/pin/breakable.rb +9 -0
  35. data/lib/solargraph/pin/delegated_method.rb +18 -1
  36. data/lib/solargraph/pin/local_variable.rb +7 -1
  37. data/lib/solargraph/pin/method.rb +20 -18
  38. data/lib/solargraph/pin/namespace.rb +10 -7
  39. data/lib/solargraph/pin/parameter.rb +13 -5
  40. data/lib/solargraph/pin/proxy_type.rb +12 -6
  41. data/lib/solargraph/pin/until.rb +18 -0
  42. data/lib/solargraph/pin/while.rb +18 -0
  43. data/lib/solargraph/pin.rb +3 -0
  44. data/lib/solargraph/rbs_map/conversions.rb +8 -8
  45. data/lib/solargraph/rbs_map/core_fills.rb +10 -3
  46. data/lib/solargraph/source/chain/array.rb +4 -3
  47. data/lib/solargraph/source/chain/call.rb +46 -17
  48. data/lib/solargraph/source/chain/constant.rb +1 -1
  49. data/lib/solargraph/source/chain/hash.rb +3 -2
  50. data/lib/solargraph/source/chain/link.rb +2 -0
  51. data/lib/solargraph/source/chain/literal.rb +22 -2
  52. data/lib/solargraph/source/chain/z_super.rb +1 -1
  53. data/lib/solargraph/source/chain.rb +77 -47
  54. data/lib/solargraph/source/source_chainer.rb +2 -2
  55. data/lib/solargraph/source_map/clip.rb +3 -1
  56. data/lib/solargraph/type_checker/checks.rb +4 -0
  57. data/lib/solargraph/type_checker.rb +35 -8
  58. data/lib/solargraph/version.rb +1 -1
  59. data/lib/solargraph/yard_map/mapper/to_method.rb +42 -15
  60. metadata +14 -2
@@ -351,34 +351,34 @@ module Solargraph
351
351
  parameters = []
352
352
  arg_num = -1
353
353
  type.type.required_positionals.each do |param|
354
- name = param.name ? param.name.to_s : "arg#{arg_num += 1}"
354
+ name = param.name ? param.name.to_s : "arg_#{arg_num += 1}"
355
355
  parameters.push Solargraph::Pin::Parameter.new(decl: :arg, name: name, closure: pin, return_type: ComplexType.try_parse(other_type_to_tag(param.type)).force_rooted)
356
356
  end
357
357
  type.type.optional_positionals.each do |param|
358
- name = param.name ? param.name.to_s : "arg#{arg_num += 1}"
358
+ name = param.name ? param.name.to_s : "arg_#{arg_num += 1}"
359
359
  parameters.push Solargraph::Pin::Parameter.new(decl: :optarg, name: name, closure: pin,
360
360
  return_type: ComplexType.try_parse(other_type_to_tag(param.type)).force_rooted)
361
361
  end
362
362
  if type.type.rest_positionals
363
- name = type.type.rest_positionals.name ? type.type.rest_positionals.name.to_s : "arg#{arg_num += 1}"
363
+ name = type.type.rest_positionals.name ? type.type.rest_positionals.name.to_s : "arg_#{arg_num += 1}"
364
364
  parameters.push Solargraph::Pin::Parameter.new(decl: :restarg, name: name, closure: pin)
365
365
  end
366
366
  type.type.trailing_positionals.each do |param|
367
- name = param.name ? param.name.to_s : "arg#{arg_num += 1}"
367
+ name = param.name ? param.name.to_s : "arg_#{arg_num += 1}"
368
368
  parameters.push Solargraph::Pin::Parameter.new(decl: :arg, name: name, closure: pin)
369
369
  end
370
370
  type.type.required_keywords.each do |orig, param|
371
- name = orig ? orig.to_s : "arg#{arg_num += 1}"
371
+ name = orig ? orig.to_s : "arg_#{arg_num += 1}"
372
372
  parameters.push Solargraph::Pin::Parameter.new(decl: :kwarg, name: name, closure: pin,
373
373
  return_type: ComplexType.try_parse(other_type_to_tag(param.type)).force_rooted)
374
374
  end
375
375
  type.type.optional_keywords.each do |orig, param|
376
- name = orig ? orig.to_s : "arg#{arg_num += 1}"
376
+ name = orig ? orig.to_s : "arg_#{arg_num += 1}"
377
377
  parameters.push Solargraph::Pin::Parameter.new(decl: :kwoptarg, name: name, closure: pin,
378
378
  return_type: ComplexType.try_parse(other_type_to_tag(param.type)).force_rooted)
379
379
  end
380
380
  if type.type.rest_keywords
381
- name = type.type.rest_keywords.name ? type.type.rest_keywords.name.to_s : "arg#{arg_num += 1}"
381
+ name = type.type.rest_keywords.name ? type.type.rest_keywords.name.to_s : "arg_#{arg_num += 1}"
382
382
  parameters.push Solargraph::Pin::Parameter.new(decl: :kwrestarg, name: type.type.rest_keywords.name.to_s, closure: pin)
383
383
  end
384
384
 
@@ -575,7 +575,7 @@ module Solargraph
575
575
  elsif type.is_a?(RBS::Types::Tuple)
576
576
  "Array(#{type.types.map { |t| other_type_to_tag(t) }.join(', ')})"
577
577
  elsif type.is_a?(RBS::Types::Literal)
578
- type.literal.to_s
578
+ type.literal.inspect
579
579
  elsif type.is_a?(RBS::Types::Union)
580
580
  type.types.map { |t| other_type_to_tag(t) }.join(', ')
581
581
  elsif type.is_a?(RBS::Types::Record)
@@ -16,8 +16,6 @@ module Solargraph
16
16
  ].map { |k| Pin::Keyword.new(k) }
17
17
 
18
18
  MISSING = [
19
- Solargraph::Pin::Method.new(name: 'tap', scope: :instance,
20
- closure: Solargraph::Pin::Namespace.new(name: 'Object')),
21
19
  Solargraph::Pin::Method.new(name: 'class', scope: :instance,
22
20
  closure: Solargraph::Pin::Namespace.new(name: 'Object'), comments: '@return [::Class<self>]')
23
21
  ]
@@ -35,6 +33,15 @@ module Solargraph
35
33
  Override.method_return('Class#allocate', 'self')
36
34
  ]
37
35
 
36
+ # @todo I don't see any direct link in RBS to build this from -
37
+ # presumably RBS is using duck typing to match interfaces
38
+ # against concrete classes
39
+ INCLUDES = [
40
+ Solargraph::Pin::Reference::Include.new(name: '_ToAry',
41
+ closure: Solargraph::Pin::Namespace.new(name: 'Array'),
42
+ generic_values: ['generic<Elem>'])
43
+ ]
44
+
38
45
  # HACK: Add Errno exception classes
39
46
  errno = Solargraph::Pin::Namespace.new(name: 'Errno')
40
47
  errnos = []
@@ -44,7 +51,7 @@ module Solargraph
44
51
  end
45
52
  ERRNOS = errnos
46
53
 
47
- ALL = KEYWORDS + MISSING + OVERRIDES + ERRNOS
54
+ ALL = KEYWORDS + MISSING + OVERRIDES + ERRNOS + INCLUDES
48
55
  end
49
56
  end
50
57
  end
@@ -3,8 +3,9 @@ module Solargraph
3
3
  class Chain
4
4
  class Array < Literal
5
5
  # @param children [::Array<Chain>]
6
- def initialize children
7
- super('::Array')
6
+ # @param node [Parser::AST::Node]
7
+ def initialize children, node
8
+ super('::Array', node)
8
9
  @children = children
9
10
  end
10
11
 
@@ -17,7 +18,7 @@ module Solargraph
17
18
  # @param locals [::Array<Pin::Parameter, Pin::LocalVariable>]
18
19
  def resolve api_map, name_pin, locals
19
20
  child_types = @children.map do |child|
20
- child.infer(api_map, name_pin, locals)
21
+ child.infer(api_map, name_pin, locals).simplify_literals
21
22
  end
22
23
 
23
24
  type = if child_types.uniq.length == 1 && child_types.first.defined?
@@ -3,12 +3,20 @@
3
3
  module Solargraph
4
4
  class Source
5
5
  class Chain
6
+ #
7
+ # Handles both method calls and local variable references by
8
+ # first looking for a variable with the name 'word', then
9
+ # proceeding to method signature resolution if not found.
10
+ #
6
11
  class Call < Chain::Link
7
12
  include Solargraph::Parser::NodeMethods
8
13
 
9
14
  # @return [String]
10
15
  attr_reader :word
11
16
 
17
+ # @return [Location]
18
+ attr_reader :location
19
+
12
20
  # @return [::Array<Chain>]
13
21
  attr_reader :arguments
14
22
 
@@ -16,10 +24,12 @@ module Solargraph
16
24
  attr_reader :block
17
25
 
18
26
  # @param word [String]
27
+ # @param location [Location, nil]
19
28
  # @param arguments [::Array<Chain>]
20
29
  # @param block [Chain, nil]
21
- def initialize word, arguments = [], block = nil
30
+ def initialize word, location = nil, arguments = [], block = nil
22
31
  @word = word
32
+ @location = location
23
33
  @arguments = arguments
24
34
  @block = block
25
35
  fix_block_pass
@@ -35,20 +45,21 @@ module Solargraph
35
45
  end
36
46
 
37
47
  # @param api_map [ApiMap]
38
- # @param name_pin [Pin::Closure] name_pin.binder should give us the object on which 'word' will be invoked
48
+ # @param name_pin [Pin::Closure] name_pin.binder should give us the type of the object on which 'word' will be invoked
39
49
  # @param locals [::Array<Pin::LocalVariable>]
40
50
  def resolve api_map, name_pin, locals
41
51
  return super_pins(api_map, name_pin) if word == 'super'
42
52
  return yield_pins(api_map, name_pin) if word == 'yield'
43
53
  found = if head?
44
- locals.select { |p| p.name == word }
54
+ api_map.visible_pins(locals, word, name_pin, location)
45
55
  else
46
56
  []
47
57
  end
48
58
  return inferred_pins(found, api_map, name_pin, locals) unless found.empty?
49
59
  pins = name_pin.binder.each_unique_type.flat_map do |context|
50
- ns = context.namespace == '' ? '' : context.namespace_type.tag
51
- api_map.get_method_stack(ns, word, scope: context.scope)
60
+ ns_tag = context.namespace == '' ? '' : context.namespace_type.tag
61
+ stack = api_map.get_method_stack(ns_tag, word, scope: context.scope)
62
+ [stack.first].compact
52
63
  end
53
64
  return [] if pins.empty?
54
65
  inferred_pins(pins, api_map, name_pin, locals)
@@ -72,7 +83,8 @@ module Solargraph
72
83
  # use it. If we didn't pass a block, the logic below will
73
84
  # reject it regardless
74
85
 
75
- sorted_overloads = overloads.sort { |ol| ol.block? ? -1 : 1 }
86
+ with_block, without_block = overloads.partition(&:block?)
87
+ sorted_overloads = with_block + without_block
76
88
  new_signature_pin = nil
77
89
  sorted_overloads.each do |ol|
78
90
  next unless ol.arity_matches?(arguments, with_block?)
@@ -85,13 +97,9 @@ module Solargraph
85
97
  match = ol.parameters.any?(&:restarg?)
86
98
  break
87
99
  end
100
+
88
101
  atype = atypes[idx] ||= arg.infer(api_map, Pin::ProxyType.anonymous(name_pin.context), locals)
89
- # make sure we get types from up the method
90
- # inheritance chain if we don't have them on this pin
91
- ptype = param.typify api_map
92
- # @todo Weak type comparison
93
- # unless atype.tag == param.return_type.tag || api_map.super_and_sub?(param.return_type.tag, atype.tag)
94
- unless ptype.undefined? || atype.name == ptype.name || ptype.any? { |current_ptype| api_map.super_and_sub?(current_ptype.name, atype.name) } || ptype.generic? || param.restarg?
102
+ unless param.compatible_arg?(atype, api_map) || param.restarg?
95
103
  match = false
96
104
  break
97
105
  end
@@ -108,7 +116,27 @@ module Solargraph
108
116
  end
109
117
  new_signature_pin = ol.resolve_generics_from_context_until_complete(ol.generics, atypes, nil, nil, blocktype)
110
118
  new_return_type = new_signature_pin.return_type
111
- type = with_params(new_return_type.self_to_type(name_pin.context), name_pin.context).qualify(api_map, name_pin.context.namespace) if new_return_type.defined?
119
+ if head?
120
+ # If we're at the head of the chain, we called a
121
+ # method somewhere that marked itself as returning
122
+ # self. Given we didn't invoke this on an object,
123
+ # this must be a method in this same class - so we
124
+ # use our own self type
125
+ self_type = name_pin.context
126
+ else
127
+ # if we're past the head in the chain, whatever the
128
+ # type of the lhs side is what 'self' will be in its
129
+ # declaration - we can't just use the type of the
130
+ # method pin, as this might be a subclass of the
131
+ # place where the method is defined
132
+ self_type = name_pin.binder
133
+ end
134
+ # This same logic applies to the YARD work done by
135
+ # 'with_params()'.
136
+ #
137
+ # qualify(), however, happens in the namespace where
138
+ # the docs were written - from the method pin.
139
+ type = with_params(new_return_type.self_to_type(self_type), self_type).qualify(api_map, p.namespace) if new_return_type.defined?
112
140
  type ||= ComplexType::UNDEFINED
113
141
  end
114
142
  break if type.defined?
@@ -124,13 +152,14 @@ module Solargraph
124
152
  end
125
153
  p
126
154
  end
127
- result.map do |pin|
128
- if pin.path == 'Class#new' && name_pin.context.tag != 'Class'
129
- reduced_context = name_pin.context.reduce_class_type
155
+ logger.debug { "Call#inferred_pins(name_pin.binder=#{name_pin.binder}, word=#{word}, pins=#{pins.map(&:desc)}, name_pin=#{name_pin}) - result=#{result}" }
156
+ out = result.map do |pin|
157
+ if pin.path == 'Class#new' && name_pin.binder.tag != 'Class'
158
+ reduced_context = name_pin.binder.reduce_class_type
130
159
  pin.proxy(reduced_context)
131
160
  else
132
161
  next pin if pin.return_type.undefined?
133
- selfy = pin.return_type.self_to_type(name_pin.context)
162
+ selfy = pin.return_type.self_to_type(name_pin.binder)
134
163
  selfy == pin.return_type ? pin : pin.proxy(selfy)
135
164
  end
136
165
  end
@@ -41,7 +41,7 @@ module Solargraph
41
41
 
42
42
  private
43
43
 
44
- # @param pin [Pin::Base]
44
+ # @param pin [Pin::Closure]
45
45
  # @return [::Array<String>]
46
46
  def crawl_gates pin
47
47
  clos = pin
@@ -5,9 +5,10 @@ module Solargraph
5
5
  class Chain
6
6
  class Hash < Literal
7
7
  # @param type [String]
8
+ # @param node [Parser::AST::Node]
8
9
  # @param splatted [Boolean]
9
- def initialize type, splatted = false
10
- super(type)
10
+ def initialize type, node, splatted = false
11
+ super(type, node)
11
12
  @splatted = splatted
12
13
  end
13
14
 
@@ -82,6 +82,8 @@ module Solargraph
82
82
  "#<#{self.class} - `#{self.desc}`>"
83
83
  end
84
84
 
85
+ include Logging
86
+
85
87
  protected
86
88
 
87
89
  # Mark whether this link is the head of a chain
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'parser'
4
+
3
5
  module Solargraph
4
6
  class Source
5
7
  class Chain
@@ -8,9 +10,22 @@ module Solargraph
8
10
  @word ||= "<#{@type}>"
9
11
  end
10
12
 
13
+ attr_reader :value
14
+
11
15
  # @param type [String]
12
- def initialize type
16
+ # @param node [Parser::AST::Node, Object]
17
+ def initialize type, node
18
+ if node.is_a?(::Parser::AST::Node)
19
+ if node.type == :true
20
+ @value = true
21
+ elsif node.type == :false
22
+ @value = false
23
+ elsif [:int, :sym].include?(node.type)
24
+ @value = node.children.first
25
+ end
26
+ end
13
27
  @type = type
28
+ @literal_type = ComplexType.try_parse(@value.inspect)
14
29
  @complex_type = ComplexType.try_parse(type)
15
30
  end
16
31
 
@@ -20,7 +35,12 @@ module Solargraph
20
35
  end
21
36
 
22
37
  def resolve api_map, name_pin, locals
23
- [Pin::ProxyType.anonymous(@complex_type)]
38
+ if api_map.super_and_sub?(@complex_type.name, @literal_type.name)
39
+ [Pin::ProxyType.anonymous(@literal_type)]
40
+ else
41
+ # we don't support this value as a literal type
42
+ [Pin::ProxyType.anonymous(@complex_type)]
43
+ end
24
44
  end
25
45
  end
26
46
  end
@@ -15,7 +15,7 @@ module Solargraph
15
15
  # @param with_block [Boolean] True if the chain is inside a block
16
16
  # @param head [Boolean] True if the call is the start of its chain
17
17
  def initialize word, with_block = false
18
- super(word, [], with_block)
18
+ super(word, nil, [], with_block)
19
19
  end
20
20
 
21
21
  # @param api_map [ApiMap]
@@ -39,7 +39,7 @@ module Solargraph
39
39
  @@inference_invalidation_key = nil
40
40
  @@inference_cache = {}
41
41
 
42
- UNDEFINED_CALL = Chain::Call.new('<undefined>')
42
+ UNDEFINED_CALL = Chain::Call.new('<undefined>', nil)
43
43
  UNDEFINED_CONSTANT = Chain::Constant.new('<undefined>')
44
44
 
45
45
  # @return [::Array<Source::Chain::Link>]
@@ -75,16 +75,27 @@ module Solargraph
75
75
 
76
76
  # Determine potential Pins returned by this chain of words
77
77
  #
78
- # @param api_map [ApiMap]
79
- # @param name_pin [Pin::Closure] the surrounding closure pin for
80
- # the statement represented by this chain for type resolution
81
- # and method pin lookup.
78
+ # @param api_map [ApiMap] @param name_pin [Pin::Base] A pin
79
+ # representing the place in which expression is evaluated (e.g.,
80
+ # a Method pin, or a Module or Class pin if not run within a
81
+ # method - both in terms of the closure around the chain, as well
82
+ # as the self type used for any method calls in head position.
83
+ #
84
+ # Requirements for name_pin:
85
+ #
86
+ # * name_pin.context: This should be a type representing the
87
+ # namespace where we can look up non-local variables and
88
+ # method names. If it is a Class<X>, we will look up
89
+ # :class scoped methods/variables.
82
90
  #
83
- # For method calls (Chain::Call objects) as the first element
84
- # in the chain, 'name_pin.binder' should return the
85
- # ComplexType representing the LHS / "self type" of the call.
91
+ # * name_pin.binder: Used for method call lookups only
92
+ # (Chain::Call links). For method calls as the first
93
+ # element in the chain, 'name_pin.binder' should be the
94
+ # same as name_pin.context above. For method calls later
95
+ # in the chain (e.g., 'b' in a.b.c), it should represent
96
+ # 'a'.
86
97
  #
87
- # @param locals [::Enumerable<Pin::LocalVariable>] Any local
98
+ # @param locals [::Array<Pin::LocalVariable>] Any local
88
99
  # variables / method parameters etc visible by the statement
89
100
  #
90
101
  # @return [::Array<Pin::Base>] Pins representing possible return
@@ -101,9 +112,18 @@ module Solargraph
101
112
  working_pin = name_pin
102
113
  links[0..-2].each do |link|
103
114
  pins = link.resolve(api_map, working_pin, locals)
104
- type = infer_first_defined(pins, working_pin, api_map, locals)
105
- return [] if type.undefined?
106
- working_pin = Pin::ProxyType.anonymous(type)
115
+ type = infer_from_definitions(pins, working_pin, api_map, locals)
116
+ if type.undefined?
117
+ logger.debug { "Chain#define(links=#{links.map(&:desc)}, name_pin=#{name_pin.inspect}, locals=#{locals}) => [] - undefined type from #{link.desc}" }
118
+ return []
119
+ end
120
+ # We continue to use the context from the head pin, in case
121
+ # we need it to, for instance, provide context for a block
122
+ # evaluation. However, we use the last link's return type
123
+ # for the binder, as this is chaining off of it, and the
124
+ # binder is now the lhs of the rhs we are evaluating.
125
+ working_pin = Pin::ProxyType.anonymous(name_pin.context, binder: type, closure: name_pin)
126
+ 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}" }
107
127
  end
108
128
  links.last.last_context = working_pin
109
129
  links.last.resolve(api_map, working_pin, locals)
@@ -123,7 +143,7 @@ module Solargraph
123
143
  @@inference_invalidation_key = api_map.hash
124
144
  @@inference_cache = {}
125
145
  end
126
- out = infer_uncached api_map, name_pin, locals
146
+ out = infer_uncached(api_map, name_pin, locals).downcast_to_literal_if_possible
127
147
  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}" }
128
148
  @@inference_cache[cache_key] = out
129
149
  end
@@ -134,8 +154,14 @@ module Solargraph
134
154
  # @return [ComplexType]
135
155
  def infer_uncached api_map, name_pin, locals
136
156
  pins = define(api_map, name_pin, locals)
137
- type = infer_first_defined(pins, links.last.last_context, api_map, locals)
138
- maybe_nil(type)
157
+ if pins.empty?
158
+ logger.debug { "Chain#infer_uncached(links=#{links.map(&:desc)}, locals=#{locals.map(&:desc)}) => undefined - no pins" }
159
+ return ComplexType::UNDEFINED
160
+ end
161
+ type = infer_from_definitions(pins, links.last.last_context, api_map, locals)
162
+ out = maybe_nil(type)
163
+ 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}" }
164
+ out
139
165
  end
140
166
 
141
167
  # @return [Boolean]
@@ -174,6 +200,8 @@ module Solargraph
174
200
  desc
175
201
  end
176
202
 
203
+ include Logging
204
+
177
205
  private
178
206
 
179
207
  # @param pins [::Array<Pin::Base>]
@@ -181,8 +209,10 @@ module Solargraph
181
209
  # @param api_map [ApiMap]
182
210
  # @param locals [::Enumerable<Pin::LocalVariable>]
183
211
  # @return [ComplexType]
184
- def infer_first_defined pins, context, api_map, locals
185
- possibles = []
212
+ def infer_from_definitions pins, context, api_map, locals
213
+ # @type [::Array<ComplexType>]
214
+ types = []
215
+ unresolved_pins = []
186
216
  # @todo this param tag shouldn't be needed to probe the type
187
217
  # @todo ...but given it is needed, typecheck should complain that it is needed
188
218
  # @param pin [Pin::Base]
@@ -200,41 +230,41 @@ module Solargraph
200
230
  # that accepts only [Pin::Namespace] as an argument
201
231
  type = type.resolve_generics(pin.closure, context.binder)
202
232
  end
203
- if type.defined?
204
- possibles.push type
205
- break if pin.is_a?(Pin::Method)
206
- end
233
+ types << type
234
+ else
235
+ unresolved_pins << pin
207
236
  end
208
237
  end
209
- if possibles.empty?
210
- # Limit method inference recursion
211
- return ComplexType::UNDEFINED if @@inference_depth >= 10 && pins.first.is_a?(Pin::Method)
212
-
213
- @@inference_depth += 1
214
- # @param pin [Pin::Base]
215
- pins.each do |pin|
216
- # Avoid infinite recursion
217
- next if @@inference_stack.include?(pin)
218
-
219
- @@inference_stack.push pin
220
- type = pin.probe(api_map)
221
- @@inference_stack.pop
222
- if type.defined?
223
- possibles.push type
224
- break if pin.is_a?(Pin::Method)
225
- end
226
- end
227
- @@inference_depth -= 1
238
+
239
+ # Limit method inference recursion
240
+ if @@inference_depth >= 10 && pins.first.is_a?(Pin::Method)
241
+ return ComplexType::UNDEFINED
228
242
  end
229
- return ComplexType::UNDEFINED if possibles.empty?
230
243
 
231
- type = if possibles.length > 1
232
- # Move nil to the end by convention
233
- sorted = possibles.sort { |a, _| a.tag == 'nil' ? 1 : 0 }
234
- ComplexType.new(sorted.uniq)
235
- else
236
- ComplexType.new(possibles)
244
+ @@inference_depth += 1
245
+ # @param pin [Pin::Base]
246
+ unresolved_pins.each do |pin|
247
+ # Avoid infinite recursion
248
+ if @@inference_stack.include?(pin.identity)
249
+ next
250
+ end
251
+
252
+ @@inference_stack.push(pin.identity)
253
+ type = pin.probe(api_map)
254
+ @@inference_stack.pop
255
+ types.push type if type
237
256
  end
257
+ @@inference_depth -= 1
258
+
259
+ type = if types.empty?
260
+ ComplexType::UNDEFINED
261
+ elsif types.length > 1
262
+ # Move nil to the end by convention
263
+ sorted = types.flat_map(&:items).sort { |a, _| a.tag == 'nil' ? 1 : 0 }
264
+ ComplexType.new(sorted.uniq)
265
+ else
266
+ ComplexType.new(types)
267
+ end
238
268
  return type if context.nil? || context.return_type.undefined?
239
269
 
240
270
  type.self_to_type(context.return_type)
@@ -32,8 +32,8 @@ module Solargraph
32
32
  # @return [Source::Chain]
33
33
  def chain
34
34
  # Special handling for files that end with an integer and a period
35
- return Chain.new([Chain::Literal.new('Integer'), Chain::UNDEFINED_CALL]) if phrase =~ /^[0-9]+\.$/
36
- return Chain.new([Chain::Literal.new('Symbol')]) if phrase.start_with?(':') && !phrase.start_with?('::')
35
+ return Chain.new([Chain::Literal.new('Integer', Integer(phrase[0..-2])), Chain::UNDEFINED_CALL]) if phrase =~ /^[0-9]+\.$/
36
+ return Chain.new([Chain::Literal.new('Symbol', phrase[1..].to_sym)]) if phrase.start_with?(':') && !phrase.start_with?('::')
37
37
  return SourceChainer.chain(source, Position.new(position.line, position.character + 1)) if end_of_phrase.strip == '::' && source.code[Position.to_offset(source.code, position)].to_s.match?(/[a-z]/i)
38
38
  begin
39
39
  return Chain.new([]) if phrase.end_with?('..')
@@ -11,7 +11,9 @@ module Solargraph
11
11
  def initialize api_map, cursor
12
12
  @api_map = api_map
13
13
  @cursor = cursor
14
- block.rebind(api_map) if block.is_a?(Pin::Block)
14
+ block_pin = block
15
+ block_pin.rebind(api_map) if block_pin.is_a?(Pin::Block) && !Solargraph::Range.from_node(block_pin.receiver).contain?(cursor.range.start)
16
+ @in_block = nil
15
17
  end
16
18
 
17
19
  # @return [Array<Pin::Base>] Relevant pins for infering the type of the Cursor's position
@@ -50,6 +50,8 @@ module Solargraph
50
50
  # @param inferred [ComplexType]
51
51
  # @return [Boolean]
52
52
  def any_types_match? api_map, expected, inferred
53
+ expected = expected.downcast_to_literal_if_possible
54
+ inferred = inferred.downcast_to_literal_if_possible
53
55
  return duck_types_match?(api_map, expected, inferred) if expected.duck_type?
54
56
  # walk through the union expected type and see if any members
55
57
  # of the union match the inferred type
@@ -71,6 +73,8 @@ module Solargraph
71
73
  # @param expected [ComplexType]
72
74
  # @return [Boolean]
73
75
  def all_types_match? api_map, inferred, expected
76
+ expected = expected.downcast_to_literal_if_possible
77
+ inferred = inferred.downcast_to_literal_if_possible
74
78
  return duck_types_match?(api_map, expected, inferred) if expected.duck_type?
75
79
  inferred.each do |inf|
76
80
  next if inf.duck_type?
@@ -470,12 +470,22 @@ module Solargraph
470
470
  # @param pin [Pin::Method]
471
471
  # @return [Hash{String => Hash{Symbol => String, ComplexType}}]
472
472
  def param_hash(pin)
473
- tags = pin.docstring.tags(:param)
474
- return {} if tags.empty?
475
473
  # @type [Hash{String => Hash{Symbol => String, ComplexType}}]
476
474
  result = {}
475
+ pin.parameters.each do |param|
476
+ type = param.typify(api_map)
477
+ next if type.nil? || type.undefined?
478
+ result[param.name.to_s] = {
479
+ tagged: type.tags,
480
+ qualified: type
481
+ }
482
+ end
483
+ # see if we have additional tags to pay attention to from YARD -
484
+ # e.g., kwargs in a **restkwargs splat
485
+ tags = pin.docstring.tags(:param)
477
486
  tags.each do |tag|
478
- next if tag.types.nil? || tag.types.empty?
487
+ next if result.key? tag.name.to_s
488
+ next if tag.types.nil?
479
489
  result[tag.name.to_s] = {
480
490
  tagged: tag.types.join(', '),
481
491
  qualified: Solargraph::ComplexType.try_parse(*tag.types).qualify(api_map, pin.full_context.namespace)
@@ -487,11 +497,27 @@ module Solargraph
487
497
  # @param pins [Array<Pin::Method>]
488
498
  # @return [Hash{String => Hash{Symbol => String, ComplexType}}]
489
499
  def first_param_hash(pins)
490
- pins.each do |pin|
491
- result = param_hash(pin)
492
- return result unless result.empty?
500
+ return {} if pins.empty?
501
+ first_pin_type = pins.first.typify(api_map)
502
+ first_pin = pins.first.proxy first_pin_type
503
+ param_names = first_pin.parameter_names
504
+ results = param_hash(first_pin)
505
+ pins[1..].each do |pin|
506
+ # @todo this assignment from parametric use of Hash should not lose its generic
507
+ # @type [Hash{String => Hash{Symbol => BasicObject}}]
508
+
509
+ # documentation of types in superclasses should fail back to
510
+ # subclasses if the subclass hasn't documented something
511
+ superclass_results = param_hash(pin)
512
+ superclass_results.each do |param_name, details|
513
+ next unless param_names.include?(param_name)
514
+
515
+ results[param_name] ||= {}
516
+ results[param_name][:tagged] ||= details[:tagged]
517
+ results[param_name][:qualified] ||= details[:qualified]
518
+ end
493
519
  end
494
- {}
520
+ results
495
521
  end
496
522
 
497
523
  # @param pin [Pin::Base]
@@ -584,7 +610,8 @@ module Solargraph
584
610
  kwargs.delete param.name.to_sym
585
611
  settled_kwargs += 1
586
612
  elsif param.decl == :kwarg
587
- return [] if arguments.last.links.last.is_a?(Solargraph::Source::Chain::Hash) && arguments.last.links.last.splatted?
613
+ last_arg_last_link = arguments.last.links.last
614
+ return [] if last_arg_last_link.is_a?(Solargraph::Source::Chain::Hash) && last_arg_last_link.splatted?
588
615
  return [Problem.new(location, "Missing keyword argument #{param.name} to #{pin.path}")]
589
616
  end
590
617
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Solargraph
4
- VERSION = '0.54.5'
4
+ VERSION = '0.55.1'
5
5
  end