solargraph 0.53.0 → 0.54.0

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 (55) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +45 -0
  3. data/lib/solargraph/api_map/cache.rb +2 -12
  4. data/lib/solargraph/api_map/source_to_yard.rb +17 -10
  5. data/lib/solargraph/api_map/store.rb +11 -6
  6. data/lib/solargraph/api_map.rb +80 -38
  7. data/lib/solargraph/bench.rb +1 -0
  8. data/lib/solargraph/complex_type/type_methods.rb +62 -30
  9. data/lib/solargraph/complex_type/unique_type.rb +149 -75
  10. data/lib/solargraph/complex_type.rb +41 -25
  11. data/lib/solargraph/doc_map.rb +65 -24
  12. data/lib/solargraph/gem_pins.rb +10 -2
  13. data/lib/solargraph/language_server/host/dispatch.rb +13 -2
  14. data/lib/solargraph/language_server/host/sources.rb +1 -61
  15. data/lib/solargraph/language_server/host.rb +41 -69
  16. data/lib/solargraph/language_server/message/base.rb +1 -1
  17. data/lib/solargraph/language_server/message/initialize.rb +14 -0
  18. data/lib/solargraph/language_server/message/text_document/formatting.rb +1 -0
  19. data/lib/solargraph/language_server/progress.rb +118 -0
  20. data/lib/solargraph/language_server.rb +1 -0
  21. data/lib/solargraph/library.rb +149 -97
  22. data/lib/solargraph/page.rb +6 -0
  23. data/lib/solargraph/parser/node_processor/base.rb +3 -2
  24. data/lib/solargraph/parser/node_processor.rb +1 -0
  25. data/lib/solargraph/parser/parser_gem/class_methods.rb +3 -7
  26. data/lib/solargraph/parser/parser_gem/node_methods.rb +0 -4
  27. data/lib/solargraph/parser/parser_gem/node_processors/def_node.rb +6 -19
  28. data/lib/solargraph/parser/parser_gem/node_processors/masgn_node.rb +47 -0
  29. data/lib/solargraph/parser/parser_gem/node_processors/send_node.rb +5 -3
  30. data/lib/solargraph/parser/parser_gem/node_processors.rb +2 -0
  31. data/lib/solargraph/pin/base_variable.rb +35 -6
  32. data/lib/solargraph/pin/block.rb +70 -32
  33. data/lib/solargraph/pin/delegated_method.rb +5 -1
  34. data/lib/solargraph/pin/documenting.rb +2 -0
  35. data/lib/solargraph/pin/method.rb +4 -2
  36. data/lib/solargraph/pin/parameter.rb +5 -28
  37. data/lib/solargraph/rbs_map/conversions.rb +16 -47
  38. data/lib/solargraph/rbs_map/core_fills.rb +12 -6
  39. data/lib/solargraph/rbs_map/core_map.rb +2 -13
  40. data/lib/solargraph/rbs_map.rb +17 -7
  41. data/lib/solargraph/shell.rb +18 -13
  42. data/lib/solargraph/source/chain.rb +20 -0
  43. data/lib/solargraph/source/updater.rb +1 -0
  44. data/lib/solargraph/source.rb +0 -44
  45. data/lib/solargraph/source_map/clip.rb +3 -2
  46. data/lib/solargraph/source_map/mapper.rb +3 -2
  47. data/lib/solargraph/source_map.rb +8 -6
  48. data/lib/solargraph/type_checker.rb +58 -13
  49. data/lib/solargraph/version.rb +1 -1
  50. data/lib/solargraph/workspace/config.rb +2 -1
  51. data/lib/solargraph/workspace.rb +27 -1
  52. data/lib/solargraph/yard_map/mapper/to_method.rb +5 -2
  53. metadata +4 -4
  54. data/lib/solargraph/language_server/host/cataloger.rb +0 -57
  55. data/lib/solargraph/rbs_map/core_signs.rb +0 -35
@@ -17,6 +17,7 @@ module Solargraph
17
17
  autoload :LvasgnNode, 'solargraph/parser/parser_gem/node_processors/lvasgn_node'
18
18
  autoload :GvasgnNode, 'solargraph/parser/parser_gem/node_processors/gvasgn_node'
19
19
  autoload :CasgnNode, 'solargraph/parser/parser_gem/node_processors/casgn_node'
20
+ autoload :MasgnNode, 'solargraph/parser/parser_gem/node_processors/masgn_node'
20
21
  autoload :AliasNode, 'solargraph/parser/parser_gem/node_processors/alias_node'
21
22
  autoload :ArgsNode, 'solargraph/parser/parser_gem/node_processors/args_node'
22
23
  autoload :BlockNode, 'solargraph/parser/parser_gem/node_processors/block_node'
@@ -43,6 +44,7 @@ module Solargraph
43
44
  register :lvasgn, ParserGem::NodeProcessors::LvasgnNode
44
45
  register :gvasgn, ParserGem::NodeProcessors::GvasgnNode
45
46
  register :casgn, ParserGem::NodeProcessors::CasgnNode
47
+ register :masgn, ParserGem::NodeProcessors::MasgnNode
46
48
  register :alias, ParserGem::NodeProcessors::AliasNode
47
49
  register :args, ParserGem::NodeProcessors::ArgsNode
48
50
  register :forward_args, ParserGem::NodeProcessors::ArgsNode
@@ -9,10 +9,14 @@ module Solargraph
9
9
  # @return [Parser::AST::Node, nil]
10
10
  attr_reader :assignment
11
11
 
12
+ attr_accessor :mass_assignment
13
+
12
14
  # @param assignment [Parser::AST::Node, nil]
13
15
  def initialize assignment: nil, **splat
14
16
  super(**splat)
15
17
  @assignment = assignment
18
+ # @type [nil, ::Array(Parser::AST::Node, Integer)]
19
+ @mass_assignment = nil
16
20
  end
17
21
 
18
22
  def completion_item_kind
@@ -36,10 +40,12 @@ module Solargraph
36
40
  true
37
41
  end
38
42
 
39
- def probe api_map
40
- return ComplexType::UNDEFINED if @assignment.nil?
43
+ # @param parent_node [Parser::AST::Node]
44
+ # @param api_map [ApiMap]
45
+ # @return [::Array<ComplexType>]
46
+ def return_types_from_node(parent_node, api_map)
41
47
  types = []
42
- value_position_nodes_only(@assignment).each do |node|
48
+ value_position_nodes_only(parent_node).each do |node|
43
49
  # Nil nodes may not have a location
44
50
  if node.nil? || node.type == :NIL || node.type == :nil
45
51
  types.push ComplexType::NIL
@@ -51,12 +57,35 @@ module Solargraph
51
57
  # Use the return node for inference. The clip might infer from the
52
58
  # first node in a method call instead of the entire call.
53
59
  chain = Parser.chain(node, nil, nil)
54
- result = chain.infer(api_map, closure, clip.locals)
60
+ result = chain.infer(api_map, closure, clip.locals).self_to(closure.context.tag)
55
61
  types.push result unless result.undefined?
56
62
  end
57
63
  end
58
- return ComplexType::UNDEFINED if types.empty?
59
- ComplexType.try_parse(*types.map(&:to_s))
64
+ types
65
+ end
66
+
67
+ # @param api_map [ApiMap]
68
+ # @return [ComplexType]
69
+ def probe api_map
70
+ unless @assignment.nil?
71
+ types = return_types_from_node(@assignment, api_map)
72
+ return ComplexType.new(types.uniq) unless types.empty?
73
+ end
74
+
75
+ unless @mass_assignment.nil?
76
+ mass_node, index = @mass_assignment
77
+ types = return_types_from_node(mass_node, api_map)
78
+ types.map! do |type|
79
+ if type.tuple?
80
+ type.all_params[index]
81
+ elsif ['::Array', '::Set', '::Enumerable'].include?(type.rooted_name)
82
+ type.all_params.first
83
+ end
84
+ end.compact!
85
+ return ComplexType.new(types.uniq) unless types.empty?
86
+ end
87
+
88
+ ComplexType::UNDEFINED
60
89
  end
61
90
 
62
91
  def == other
@@ -18,17 +18,18 @@ module Solargraph
18
18
  @receiver = receiver
19
19
  @context = context
20
20
  @parameters = args
21
+ @return_type = ComplexType.parse('::Proc')
21
22
  @node = node
22
23
  end
23
24
 
24
25
  # @param api_map [ApiMap]
25
26
  # @return [void]
26
27
  def rebind api_map
27
- @binder ||= binder_or_nil(api_map)
28
+ @rebind ||= maybe_rebind(api_map)
28
29
  end
29
30
 
30
31
  def binder
31
- @binder || closure.binder
32
+ @rebind&.defined? ? @rebind : closure.binder
32
33
  end
33
34
 
34
35
  # @return [::Array<Parameter>]
@@ -41,42 +42,79 @@ module Solargraph
41
42
  @parameter_names ||= parameters.map(&:name)
42
43
  end
43
44
 
45
+ # @param yield_types [::Array<ComplexType>]
46
+ # @param parameters [::Array<Parameter>]
47
+ #
48
+ # @return [::Array<ComplexType>]
49
+ def destructure_yield_types(yield_types, parameters)
50
+ return yield_types if yield_types.length == parameters.length
51
+
52
+ # yielding a tuple into a block will destructure the tuple
53
+ if yield_types.length == 1
54
+ yield_type = yield_types.first
55
+ return yield_type.all_params if yield_type.tuple? && yield_type.all_params.length == parameters.length
56
+ end
57
+ parameters.map { ComplexType::UNDEFINED }
58
+ end
59
+
60
+ # @todo the next step with parameters, arguments, destructuring,
61
+ # kwargs, etc logic is probably either creating a Parameters
62
+ # or Callable pin that encapsulates and shares the logic
63
+ # between methods, blocks and signatures. It could live in
64
+ # Signature if Method didn't also own potentially different
65
+ # set of parameters, generics and return types.
66
+
67
+ # @param api_map [ApiMap]
68
+ # @return [::Array<ComplexType>]
69
+ def typify_parameters(api_map)
70
+ chain = Parser.chain(receiver, filename, node)
71
+ clip = api_map.clip_at(location.filename, location.range.start)
72
+ locals = clip.locals - [self]
73
+ meths = chain.define(api_map, closure, locals)
74
+ # @todo Convert logic to use signatures
75
+ meths.each do |meth|
76
+ next if meth.block.nil?
77
+
78
+ yield_types = meth.block.parameters.map(&:return_type)
79
+ # 'arguments' is what the method says it will yield to the
80
+ # block; 'parameters' is what the block accepts
81
+ argument_types = destructure_yield_types(yield_types, parameters)
82
+ param_types = argument_types.each_with_index.map do |arg_type, idx|
83
+ param = parameters[idx]
84
+ param_type = chain.base.infer(api_map, param, locals)
85
+ unless arg_type.nil?
86
+ if arg_type.generic? && param_type.defined?
87
+ namespace_pin = api_map.get_namespace_pins(meth.namespace, closure.namespace).first
88
+ arg_type.resolve_generics(namespace_pin, param_type)
89
+ else
90
+ arg_type.self_to(chain.base.infer(api_map, self, locals).namespace).qualify(api_map, meth.context.namespace)
91
+ end
92
+ end
93
+ end
94
+ return param_types if param_types.all?(&:defined?)
95
+ end
96
+ parameters.map { ComplexType::UNDEFINED }
97
+ end
98
+
44
99
  private
45
100
 
46
101
  # @param api_map [ApiMap]
47
- # @return [ComplexType, nil]
48
- def binder_or_nil api_map
49
- return nil unless receiver
50
- word = receiver.children.find { |c| c.is_a?(::Symbol) }.to_s
51
- return nil unless api_map.rebindable_method_names.include?(word)
102
+ # @return [ComplexType]
103
+ def maybe_rebind api_map
104
+ return ComplexType::UNDEFINED unless receiver
105
+
52
106
  chain = Parser.chain(receiver, location.filename)
53
107
  locals = api_map.source_map(location.filename).locals_at(location)
54
- links_last_word = chain.links.last.word
55
- if %w[instance_eval instance_exec class_eval class_exec module_eval module_exec].include?(links_last_word)
56
- return chain.base.infer(api_map, self, locals)
57
- end
58
- if 'define_method' == links_last_word and chain.define(api_map, self, locals).first&.path == 'Module#define_method' # change class type to instance type
59
- if chain.links.size > 1 # Class.define_method
60
- ty = chain.base.infer(api_map, self, locals)
61
- return Solargraph::ComplexType.parse(ty.namespace)
62
- else # define_method without self
63
- return Solargraph::ComplexType.parse(closure.binder.namespace)
64
- end
65
- end
66
- # other case without early return, read block yieldreceiver tags
67
108
  receiver_pin = chain.define(api_map, self, locals).first
68
- if receiver_pin && receiver_pin.docstring
69
- ys = receiver_pin.docstring.tag(:yieldreceiver)
70
- if ys && ys.types && !ys.types.empty?
71
- target = if chain.links.first.is_a?(Source::Chain::Constant)
72
- receiver_pin.full_context.namespace
73
- else
74
- full_context.namespace
75
- end
76
- return ComplexType.try_parse(*ys.types).qualify(api_map, receiver_pin.context.namespace).self_to(target)
77
- end
78
- end
79
- nil
109
+ return ComplexType::UNDEFINED unless receiver_pin
110
+
111
+ types = receiver_pin.docstring.tag(:yieldreceiver)&.types
112
+ return ComplexType::UNDEFINED unless types&.any?
113
+
114
+ target = chain.base.infer(api_map, receiver_pin, locals)
115
+ target = full_context unless target.defined?
116
+
117
+ ComplexType.try_parse(*types).qualify(api_map, receiver_pin.context.namespace).self_to(target.to_s)
80
118
  end
81
119
  end
82
120
  end
@@ -11,8 +11,9 @@ module Solargraph
11
11
  # given closure/scope, and the delegated method will then be resolved
12
12
  # to a method pin on that type.
13
13
  #
14
- # @param resolved_method [Method] an already resolved method pin.
14
+ # @param method [Method, nil] an already resolved method pin.
15
15
  # @param receiver [Source::Chain, nil] the source code used to resolve the receiver for this delegated method.
16
+ # @param name [String]
16
17
  # @param receiver_method_name [String] the method name that will be called on the receiver (defaults to :name).
17
18
  def initialize(method: nil, receiver: nil, name: method&.name, receiver_method_name: name, **splat)
18
19
  raise ArgumentError, 'either :method or :receiver is required' if (method && receiver) || (!method && !receiver)
@@ -37,6 +38,7 @@ module Solargraph
37
38
  end
38
39
  end
39
40
 
41
+ # @param api_map [ApiMap]
40
42
  def resolvable?(api_map)
41
43
  resolve_method(api_map)
42
44
  !!@resolved_method
@@ -79,6 +81,7 @@ module Solargraph
79
81
  # helper to print a source chain as code, probably not 100% correct.
80
82
  #
81
83
  # @param chain [Source::Chain]
84
+ # @return [String]
82
85
  def print_chain(chain)
83
86
  out = +''
84
87
  chain.links.each_with_index do |link, index|
@@ -91,6 +94,7 @@ module Solargraph
91
94
  end
92
95
  out << link.word
93
96
  end
97
+ out
94
98
  end
95
99
  end
96
100
  end
@@ -76,6 +76,8 @@ module Solargraph
76
76
  end
77
77
  end
78
78
 
79
+ # @param text [String]
80
+ # @return [String]
79
81
  def self.strip_html_comments text
80
82
  text.gsub(/<!--([\s\S]*?)-->/, '').strip
81
83
  end
@@ -135,7 +135,7 @@ module Solargraph
135
135
  result = []
136
136
  result.push generate_signature(parameters, top_type) if top_type.defined?
137
137
  result.concat(overloads.map { |meth| generate_signature(meth.parameters, meth.return_type) }) unless overloads.empty?
138
- result.push generate_signature(parameters, top_type) if result.empty?
138
+ result.push generate_signature(parameters, @return_type || ComplexType::UNDEFINED) if result.empty?
139
139
  result
140
140
  end
141
141
  end
@@ -288,7 +288,9 @@ module Solargraph
288
288
 
289
289
  # @return [::Array<Pin::Method>]
290
290
  def overloads
291
- @overloads ||= docstring.tags(:overload).map do |tag|
291
+ # Ignore overload tags with nil parameters. If it's not an array, the
292
+ # tag's source is likely malformed.
293
+ @overloads ||= docstring.tags(:overload).select(&:parameters).map do |tag|
292
294
  Pin::Signature.new(
293
295
  generics,
294
296
  tag.parameters.map do |src|
@@ -76,6 +76,7 @@ module Solargraph
76
76
  end
77
77
  end
78
78
 
79
+ # @return [ComplexType]
79
80
  def return_type
80
81
  if @return_type.nil?
81
82
  @return_type = ComplexType::UNDEFINED
@@ -99,7 +100,9 @@ module Solargraph
99
100
  #
100
101
  # @return [Integer]
101
102
  def index
102
- closure.parameter_names.index(name)
103
+ # @type [Method, Block]
104
+ method_pin = closure
105
+ method_pin.parameter_names.index(name)
103
106
  end
104
107
 
105
108
  # @param api_map [ApiMap]
@@ -140,33 +143,7 @@ module Solargraph
140
143
  # @return [ComplexType]
141
144
  def typify_block_param api_map
142
145
  if closure.is_a?(Pin::Block) && closure.receiver
143
- chain = Parser.chain(closure.receiver, filename, closure.node)
144
- clip = api_map.clip_at(location.filename, location.range.start)
145
- locals = clip.locals - [self]
146
- meths = chain.define(api_map, closure, locals)
147
- receiver_type = chain.base.infer(api_map, closure, locals)
148
- meths.each do |meth|
149
- block_signature = meth.block
150
- if block_signature
151
- yield_type = block_signature.parameters[index]&.return_type
152
- else
153
- # @todo move the yieldparam tag parsing logic into the
154
- # creation of Method pins so we don't need this else
155
- # statement
156
- yps = meth.docstring.tags(:yieldparam)
157
- unless yps[index].nil? or yps[index].types.nil? or yps[index].types.empty?
158
- yield_type = ComplexType.try_parse(yps[index].types.first)
159
- end
160
- end
161
- unless yield_type.nil?
162
- if yield_type.generic? && receiver_type.defined?
163
- namespace_pin = api_map.get_namespace_pins(meth.namespace, closure.namespace).first
164
- return yield_type.resolve_generics(namespace_pin, receiver_type)
165
- else
166
- return yield_type.self_to(chain.base.infer(api_map, closure, locals).namespace).qualify(api_map, meth.context.namespace)
167
- end
168
- end
169
- end
146
+ return closure.typify_parameters(api_map)[index]
170
147
  end
171
148
  ComplexType::UNDEFINED
172
149
  end
@@ -39,24 +39,7 @@ module Solargraph
39
39
  cursor = pins.length
40
40
  environment.declarations.each { |decl| convert_decl_to_pin(decl, Solargraph::Pin::ROOT_PIN) }
41
41
  added_pins = pins[cursor..-1]
42
- add_back_implicit_pins(added_pins)
43
- end
44
-
45
- # @param added_pins [::Enumerable<Pin>]
46
- # @return [void]
47
- def add_back_implicit_pins(added_pins)
48
- added_pins.each do |pin|
49
- pin.source = :rbs
50
- next unless pin.is_a?(Pin::Namespace) && pin.type == :class
51
- next if pins.any? { |p| p.path == "#{pin.path}.new"}
52
- pins.push Solargraph::Pin::Method.new(
53
- location: nil,
54
- closure: pin,
55
- name: 'new',
56
- comments: pin.comments,
57
- scope: :class
58
- )
59
- end
42
+ added_pins.each { |pin| pin.source = :rbs }
60
43
  end
61
44
 
62
45
  # @param decl [RBS::AST::Declarations::Base]
@@ -173,7 +156,7 @@ module Solargraph
173
156
  name: decl.super_class.name.relative!.to_s
174
157
  )
175
158
  end
176
- add_mixins decl, class_pin.closure
159
+ add_mixins decl, class_pin
177
160
  convert_members_to_pins decl, class_pin
178
161
  end
179
162
 
@@ -303,26 +286,8 @@ module Solargraph
303
286
  pin.signatures.concat method_def_to_sigs(decl, pin)
304
287
  pins.push pin
305
288
  if pin.name == 'initialize'
306
- pins.push Solargraph::Pin::Method.new(
307
- location: pin.location,
308
- closure: pin.closure,
309
- name: 'new',
310
- comments: pin.comments,
311
- scope: :class,
312
- signatures: pin.signatures
313
- )
314
- pins.last.signatures.replace(
315
- pin.signatures.map do |p|
316
- Pin::Signature.new(
317
- p.generics,
318
- p.parameters,
319
- ComplexType::SELF
320
- )
321
- end
322
- )
323
- # @todo Is this necessary?
324
- # pin.instance_variable_set(:@visibility, :private)
325
- # pin.instance_variable_set(:@return_type, ComplexType::VOID)
289
+ pin.instance_variable_set(:@visibility, :private)
290
+ pin.instance_variable_set(:@return_type, ComplexType::VOID)
326
291
  end
327
292
  end
328
293
  if decl.singleton?
@@ -551,13 +516,17 @@ module Solargraph
551
516
 
552
517
  # @param type_name [RBS::TypeName]
553
518
  # @param type_args [Enumerable<RBS::Types::Bases::Base>]
554
- # @return [ComplexType]
519
+ # @return [ComplexType::UniqueType]
555
520
  def generic_type(type_name, type_args)
556
521
  base = RBS_TO_YARD_TYPE[type_name.relative!.to_s] || type_name.relative!.to_s
557
- params = type_args.map { |a| other_type_to_tag(a) }.reject { |t| t == 'undefined' }
558
- params_str = params.empty? ? '' : "<#{params.join(', ')}>"
559
- type_string = "#{base}#{params_str}"
560
- ComplexType.parse(type_string)
522
+ params = type_args.map { |a| other_type_to_tag(a) }.reject { |t| t == 'undefined' }.map do |t|
523
+ ComplexType.try_parse(t)
524
+ end
525
+ if base == 'Hash' && params.length == 2
526
+ ComplexType::UniqueType.new(base, [params.first], [params.last], rooted: true, parameters_type: :hash)
527
+ else
528
+ ComplexType::UniqueType.new(base, [], params, rooted: true, parameters_type: :list)
529
+ end
561
530
  end
562
531
 
563
532
  # @param type_name [RBS::TypeName]
@@ -621,15 +590,15 @@ module Solargraph
621
590
  end
622
591
 
623
592
  # @param decl [RBS::AST::Declarations::Class, RBS::AST::Declarations::Module]
624
- # @param closure [Pin::Closure]
593
+ # @param namespace [Pin::Namespace]
625
594
  # @return [void]
626
- def add_mixins decl, closure
595
+ def add_mixins decl, namespace
627
596
  decl.each_mixin do |mixin|
628
597
  klass = mixin.is_a?(RBS::AST::Members::Include) ? Pin::Reference::Include : Pin::Reference::Extend
629
598
  pins.push klass.new(
630
599
  name: mixin.name.relative!.to_s,
631
600
  location: rbs_location_to_location(mixin.location),
632
- closure: closure
601
+ closure: namespace
633
602
  )
634
603
  end
635
604
  end
@@ -22,11 +22,17 @@ module Solargraph
22
22
  closure: Solargraph::Pin::Namespace.new(name: 'Object'), comments: '@return [Class<self>]')
23
23
  ]
24
24
 
25
- CLASS_RETURN_TYPES = [
26
- Override.method_return('Class#new', 'self'),
27
- Override.method_return('Class.new', 'Class<BasicObject>'),
28
- Override.method_return('Class#allocate', 'self'),
29
- Override.method_return('Class.allocate', 'Class<BasicObject>')
25
+ OVERRIDES = [
26
+ Override.from_comment('BasicObject#instance_eval', '@yieldreceiver [self]'),
27
+ Override.from_comment('BasicObject#instance_exec', '@yieldreceiver [self]'),
28
+ Override.from_comment('Module#define_method', '@yieldreceiver [Object<self>]'),
29
+ Override.from_comment('Module#class_eval', '@yieldreceiver [Class<self>]'),
30
+ Override.from_comment('Module#class_exec', '@yieldreceiver [Class<self>]'),
31
+ Override.from_comment('Module#module_eval', '@yieldreceiver [Module<self>]'),
32
+ Override.from_comment('Module#module_exec', '@yieldreceiver [Module<self>]'),
33
+ # RBS does not define Class with a generic, so all calls to
34
+ # generic() return an 'untyped'. We can do better:
35
+ Override.method_return('Class#allocate', 'self')
30
36
  ]
31
37
 
32
38
  # HACK: Add Errno exception classes
@@ -38,7 +44,7 @@ module Solargraph
38
44
  end
39
45
  ERRNOS = errnos
40
46
 
41
- ALL = KEYWORDS + MISSING + CLASS_RETURN_TYPES + ERRNOS
47
+ ALL = KEYWORDS + MISSING + OVERRIDES + ERRNOS
42
48
  end
43
49
  end
44
50
  end
@@ -13,27 +13,16 @@ module Solargraph
13
13
  pins.replace cache
14
14
  else
15
15
  loader = RBS::EnvironmentLoader.new(repository: RBS::Repository.new(no_stdlib: false))
16
- environment = RBS::Environment.from_loader(loader).resolve_type_names
16
+ RBS::Environment.from_loader(loader).resolve_type_names
17
17
  load_environment_to_pins(loader)
18
18
  pins.concat RbsMap::CoreFills::ALL
19
19
  processed = ApiMap::Store.new(pins).pins.reject { |p| p.is_a?(Solargraph::Pin::Reference::Override) }
20
+ processed.each { |pin| pin.source = :rbs }
20
21
  pins.replace processed
21
22
 
22
23
  Cache.save('core.ser', pins)
23
24
  end
24
25
  end
25
-
26
- def method_def_to_sigs decl, pin
27
- stubs = CoreSigns.sign(pin.path)
28
- return super unless stubs
29
- stubs.map do |stub|
30
- Pin::Signature.new(
31
- [],
32
- [],
33
- ComplexType.try_parse(stub.return_type)
34
- )
35
- end
36
- end
37
26
  end
38
27
  end
39
28
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'pathname'
3
4
  require 'rbs'
4
5
 
5
6
  module Solargraph
@@ -7,7 +8,6 @@ module Solargraph
7
8
  autoload :Conversions, 'solargraph/rbs_map/conversions'
8
9
  autoload :CoreMap, 'solargraph/rbs_map/core_map'
9
10
  autoload :CoreFills, 'solargraph/rbs_map/core_fills'
10
- autoload :CoreSigns, 'solargraph/rbs_map/core_signs'
11
11
  autoload :StdlibMap, 'solargraph/rbs_map/stdlib_map'
12
12
 
13
13
  include Conversions
@@ -18,20 +18,26 @@ module Solargraph
18
18
  attr_reader :library
19
19
 
20
20
  # @param library [String]
21
- def initialize library, version = nil
21
+ # @param version [String, nil]
22
+ # @param directories [Array<Pathname, String>]
23
+ def initialize library, version = nil, directories: []
22
24
  @library = library
23
25
  @version = version
24
26
  @collection = nil
27
+ @directories = directories
25
28
  loader = RBS::EnvironmentLoader.new(core_root: nil, repository: repository)
26
29
  add_library loader, library, version
27
30
  return unless resolved?
28
31
  load_environment_to_pins(loader)
29
32
  end
30
33
 
34
+ # @generic T
31
35
  # @param path [String]
32
- # @return [Pin::Base, nil]
33
- def path_pin path
34
- pins.find { |p| p.path == path }
36
+ # @param klass [Class<generic<T>>]
37
+ # @return [generic<T>, nil]
38
+ def path_pin path, klass = Pin::Base
39
+ pin = pins.find { |p| p.path == path }
40
+ pin if pin&.is_a?(klass)
35
41
  end
36
42
 
37
43
  # @param path [String]
@@ -45,7 +51,11 @@ module Solargraph
45
51
  end
46
52
 
47
53
  def repository
48
- @repository ||= RBS::Repository.new(no_stdlib: false)
54
+ @repository ||= RBS::Repository.new(no_stdlib: false).tap do |repo|
55
+ # @todo Temporarily ignoring external directories due to issues with
56
+ # incomplete/broken gem_rbs_collection installations
57
+ # @directories.each { |dir| repo.add(Pathname.new(dir)) }
58
+ end
49
59
  end
50
60
 
51
61
  # @param library [String]
@@ -69,7 +79,7 @@ module Solargraph
69
79
  Solargraph.logger.info "#{short_name} successfully loaded library #{library}"
70
80
  true
71
81
  else
72
- Solargraph.logger.debug "#{short_name} failed to load library #{library}"
82
+ Solargraph.logger.info "#{short_name} failed to load library #{library}"
73
83
  false
74
84
  end
75
85
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'benchmark'
3
4
  require 'thor'
4
5
  require 'yard'
5
6
 
@@ -149,23 +150,28 @@ module Solargraph
149
150
  # @return [void]
150
151
  def typecheck *files
151
152
  directory = File.realpath(options[:directory])
152
- api_map = Solargraph::ApiMap.load_with_cache(directory)
153
+ api_map = Solargraph::ApiMap.load_with_cache(directory, $stdout)
154
+ probcount = 0
153
155
  if files.empty?
154
156
  files = api_map.source_maps.map(&:filename)
155
157
  else
156
158
  files.map! { |file| File.realpath(file) }
157
159
  end
158
- probcount = 0
159
160
  filecount = 0
160
- files.each do |file|
161
- checker = TypeChecker.new(file, api_map: api_map, level: options[:level].to_sym)
162
- problems = checker.problems
163
- next if problems.empty?
164
- problems.sort! { |a, b| a.location.range.start.line <=> b.location.range.start.line }
165
- puts problems.map { |prob| "#{prob.location.filename}:#{prob.location.range.start.line + 1} - #{prob.message}" }.join("\n")
166
- filecount += 1
167
- probcount += problems.length
168
- end
161
+
162
+ time = Benchmark.measure {
163
+ files.each do |file|
164
+ checker = TypeChecker.new(file, api_map: api_map, level: options[:level].to_sym)
165
+ problems = checker.problems
166
+ next if problems.empty?
167
+ problems.sort! { |a, b| a.location.range.start.line <=> b.location.range.start.line }
168
+ puts problems.map { |prob| "#{prob.location.filename}:#{prob.location.range.start.line + 1} - #{prob.message}" }.join("\n")
169
+ filecount += 1
170
+ probcount += problems.length
171
+ end
172
+ # "
173
+ }
174
+ puts "Typecheck finished in #{time.real} seconds."
169
175
  puts "#{probcount} problem#{probcount != 1 ? 's' : ''} found#{files.length != 1 ? " in #{filecount} of #{files.length} files" : ''}."
170
176
  # "
171
177
  exit 1 if probcount > 0
@@ -182,11 +188,10 @@ module Solargraph
182
188
  option :verbose, type: :boolean, aliases: :v, desc: 'Verbose output', default: false
183
189
  # @return [void]
184
190
  def scan
185
- require 'benchmark'
186
191
  directory = File.realpath(options[:directory])
187
192
  api_map = nil
188
193
  time = Benchmark.measure {
189
- api_map = Solargraph::ApiMap.load_with_cache(directory)
194
+ api_map = Solargraph::ApiMap.load_with_cache(directory, $stdout)
190
195
  api_map.pins.each do |pin|
191
196
  begin
192
197
  puts pin_description(pin) if options[:verbose]
@@ -29,6 +29,8 @@ module Solargraph
29
29
 
30
30
  @@inference_stack = []
31
31
  @@inference_depth = 0
32
+ @@inference_invalidation_key = nil
33
+ @@inference_cache = {}
32
34
 
33
35
  UNDEFINED_CALL = Chain::Call.new('<undefined>')
34
36
  UNDEFINED_CONSTANT = Chain::Constant.new('<undefined>')
@@ -81,7 +83,25 @@ module Solargraph
81
83
  # @param name_pin [Pin::Base]
82
84
  # @param locals [::Enumerable<Pin::LocalVariable>]
83
85
  # @return [ComplexType]
86
+ # @sg-ignore
84
87
  def infer api_map, name_pin, locals
88
+ out = nil
89
+ cached = @@inference_cache[[node, node.location, links.map(&:word), name_pin&.return_type, locals]] unless node.nil?
90
+ return cached if cached && @@inference_invalidation_key == api_map.hash
91
+ out = infer_uncached api_map, name_pin, locals
92
+ if @@inference_invalidation_key != api_map.hash
93
+ @@inference_cache = {}
94
+ @@inference_invalidation_key = api_map.hash
95
+ end
96
+ @@inference_cache[[node, node.location, links.map(&:word), name_pin&.return_type, locals]] = out unless node.nil?
97
+ out
98
+ end
99
+
100
+ # @param api_map [ApiMap]
101
+ # @param name_pin [Pin::Base]
102
+ # @param locals [::Enumerable<Pin::LocalVariable>]
103
+ # @return [ComplexType]
104
+ def infer_uncached api_map, name_pin, locals
85
105
  from_here = base.infer(api_map, name_pin, locals) unless links.length == 1
86
106
  if from_here
87
107
  name_pin = name_pin.proxy(from_here)