solargraph 0.53.4 → 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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +22 -0
  3. data/lib/solargraph/api_map/cache.rb +2 -12
  4. data/lib/solargraph/api_map/store.rb +11 -6
  5. data/lib/solargraph/api_map.rb +53 -17
  6. data/lib/solargraph/complex_type/type_methods.rb +62 -30
  7. data/lib/solargraph/complex_type/unique_type.rb +117 -66
  8. data/lib/solargraph/complex_type.rb +41 -25
  9. data/lib/solargraph/doc_map.rb +19 -3
  10. data/lib/solargraph/gem_pins.rb +9 -1
  11. data/lib/solargraph/language_server/host/dispatch.rb +8 -1
  12. data/lib/solargraph/language_server/host/sources.rb +1 -61
  13. data/lib/solargraph/language_server/host.rb +21 -68
  14. data/lib/solargraph/language_server/message/base.rb +1 -1
  15. data/lib/solargraph/language_server/message/initialize.rb +14 -0
  16. data/lib/solargraph/language_server/message/text_document/formatting.rb +1 -0
  17. data/lib/solargraph/language_server/progress.rb +118 -0
  18. data/lib/solargraph/language_server.rb +1 -0
  19. data/lib/solargraph/library.rb +136 -96
  20. data/lib/solargraph/parser/node_processor/base.rb +3 -2
  21. data/lib/solargraph/parser/node_processor.rb +1 -0
  22. data/lib/solargraph/parser/parser_gem/class_methods.rb +3 -7
  23. data/lib/solargraph/parser/parser_gem/node_methods.rb +0 -4
  24. data/lib/solargraph/parser/parser_gem/node_processors/masgn_node.rb +47 -0
  25. data/lib/solargraph/parser/parser_gem/node_processors/send_node.rb +5 -3
  26. data/lib/solargraph/parser/parser_gem/node_processors.rb +2 -0
  27. data/lib/solargraph/pin/base_variable.rb +34 -5
  28. data/lib/solargraph/pin/block.rb +55 -0
  29. data/lib/solargraph/pin/delegated_method.rb +5 -1
  30. data/lib/solargraph/pin/documenting.rb +2 -0
  31. data/lib/solargraph/pin/method.rb +3 -1
  32. data/lib/solargraph/pin/parameter.rb +5 -28
  33. data/lib/solargraph/rbs_map/conversions.rb +10 -6
  34. data/lib/solargraph/rbs_map.rb +11 -3
  35. data/lib/solargraph/shell.rb +18 -13
  36. data/lib/solargraph/source/chain.rb +20 -0
  37. data/lib/solargraph/source/updater.rb +1 -0
  38. data/lib/solargraph/source.rb +0 -44
  39. data/lib/solargraph/source_map/mapper.rb +3 -2
  40. data/lib/solargraph/source_map.rb +10 -0
  41. data/lib/solargraph/type_checker.rb +43 -18
  42. data/lib/solargraph/version.rb +1 -1
  43. data/lib/solargraph/workspace/config.rb +2 -1
  44. data/lib/solargraph/workspace.rb +13 -0
  45. metadata +4 -3
  46. data/lib/solargraph/language_server/host/cataloger.rb +0 -57
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solargraph
4
+ module Parser
5
+ module ParserGem
6
+ module NodeProcessors
7
+ class MasgnNode < Parser::NodeProcessor::Base
8
+ include ParserGem::NodeMethods
9
+
10
+ def process
11
+ # Example:
12
+ #
13
+ # s(:masgn,
14
+ # s(:mlhs,
15
+ # s(:send,
16
+ # s(:send, nil, :a), :b=),
17
+ # s(:lvasgn, :b),
18
+ # s(:ivasgn, :@c)),
19
+ # s(:array,
20
+ # s(:int, 1),
21
+ # s(:int, 2),
22
+ # s(:int, 3)))
23
+ masgn = node
24
+ mlhs = masgn.children.fetch(0)
25
+ lhs_arr = mlhs.children
26
+ mass_rhs = node.children.fetch(1)
27
+
28
+ # Get pins created for the mlhs node
29
+ process_children
30
+
31
+ lhs_arr.each_with_index do |lhs, i|
32
+ location = get_node_location(lhs)
33
+ # @todo in line below, nothing in typechecking alerts
34
+ # when a non-existant method is called on 'l'
35
+ pin = locals.find { |l| l.location == location }
36
+ if pin.nil?
37
+ Solargraph.logger.debug "Could not find pin in location #{location}"
38
+ next
39
+ end
40
+ pin.mass_assignment = [mass_rhs, i]
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -80,7 +80,7 @@ module Solargraph
80
80
  )
81
81
  end
82
82
  if node.children[1] == :attr_writer || node.children[1] == :attr_accessor
83
- pins.push Solargraph::Pin::Method.new(
83
+ method_pin = Solargraph::Pin::Method.new(
84
84
  location: loc,
85
85
  closure: clos,
86
86
  name: "#{a.children[0]}=",
@@ -89,8 +89,9 @@ module Solargraph
89
89
  visibility: region.visibility,
90
90
  attribute: true
91
91
  )
92
- pins.last.parameters.push Pin::Parameter.new(name: 'value', decl: :arg, closure: pins.last)
93
- if pins.last.return_type.defined?
92
+ pins.push method_pin
93
+ method_pin.parameters.push Pin::Parameter.new(name: 'value', decl: :arg, closure: pins.last)
94
+ if method_pin.return_type.defined?
94
95
  pins.last.docstring.add_tag YARD::Tags::Tag.new(:param, '', pins.last.return_type.to_s.split(', '), 'value')
95
96
  end
96
97
  end
@@ -112,6 +113,7 @@ module Solargraph
112
113
  end
113
114
  end
114
115
 
116
+ # @return [void]
115
117
  def process_prepend
116
118
  if node.children[2].is_a?(AST::Node) && node.children[2].type == :const
117
119
  cp = region.closure
@@ -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
@@ -55,8 +61,31 @@ module Solargraph
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,6 +18,7 @@ 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
 
@@ -41,6 +42,60 @@ 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]
@@ -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
@@ -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
@@ -516,13 +516,17 @@ module Solargraph
516
516
 
517
517
  # @param type_name [RBS::TypeName]
518
518
  # @param type_args [Enumerable<RBS::Types::Bases::Base>]
519
- # @return [ComplexType]
519
+ # @return [ComplexType::UniqueType]
520
520
  def generic_type(type_name, type_args)
521
521
  base = RBS_TO_YARD_TYPE[type_name.relative!.to_s] || type_name.relative!.to_s
522
- params = type_args.map { |a| other_type_to_tag(a) }.reject { |t| t == 'undefined' }
523
- params_str = params.empty? ? '' : "<#{params.join(', ')}>"
524
- type_string = "#{base}#{params_str}"
525
- 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
526
530
  end
527
531
 
528
532
  # @param type_name [RBS::TypeName]
@@ -586,7 +590,7 @@ module Solargraph
586
590
  end
587
591
 
588
592
  # @param decl [RBS::AST::Declarations::Class, RBS::AST::Declarations::Module]
589
- # @param closure [Pin::Namespace]
593
+ # @param namespace [Pin::Namespace]
590
594
  # @return [void]
591
595
  def add_mixins decl, namespace
592
596
  decl.each_mixin do |mixin|
@@ -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
@@ -17,10 +18,13 @@ module Solargraph
17
18
  attr_reader :library
18
19
 
19
20
  # @param library [String]
20
- def initialize library, version = nil
21
+ # @param version [String, nil]
22
+ # @param directories [Array<Pathname, String>]
23
+ def initialize library, version = nil, directories: []
21
24
  @library = library
22
25
  @version = version
23
26
  @collection = nil
27
+ @directories = directories
24
28
  loader = RBS::EnvironmentLoader.new(core_root: nil, repository: repository)
25
29
  add_library loader, library, version
26
30
  return unless resolved?
@@ -47,7 +51,11 @@ module Solargraph
47
51
  end
48
52
 
49
53
  def repository
50
- @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
51
59
  end
52
60
 
53
61
  # @param library [String]
@@ -71,7 +79,7 @@ module Solargraph
71
79
  Solargraph.logger.info "#{short_name} successfully loaded library #{library}"
72
80
  true
73
81
  else
74
- Solargraph.logger.debug "#{short_name} failed to load library #{library}"
82
+ Solargraph.logger.info "#{short_name} failed to load library #{library}"
75
83
  false
76
84
  end
77
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)
@@ -42,6 +42,7 @@ module Solargraph
42
42
  @output
43
43
  end
44
44
 
45
+ # @param text [String]
45
46
  # @return [String]
46
47
  def repair text
47
48
  changes.each do |ch|
@@ -92,50 +92,6 @@ module Solargraph
92
92
  stack
93
93
  end
94
94
 
95
- # Start synchronizing the source. This method updates the code without
96
- # parsing a new AST. The resulting Source object will be marked not
97
- # synchronized (#synchronized? == false).
98
- #
99
- # @param updater [Source::Updater]
100
- # @return [Source]
101
- def start_synchronize updater
102
- raise 'Invalid synchronization' unless updater.filename == filename
103
- real_code = updater.write(@code)
104
- src = Source.allocate
105
- src.filename = filename
106
- src.code = real_code
107
- src.version = updater.version
108
- src.parsed = parsed?
109
- src.repaired = updater.repair(@repaired)
110
- src.synchronized = false
111
- src.node = @node
112
- src.comments = @comments
113
- src.error_ranges = error_ranges
114
- src.last_updater = updater
115
- return src.finish_synchronize unless real_code.lines.length == @code.lines.length
116
- src
117
- end
118
-
119
- # Finish synchronizing a source that was updated via #start_synchronize.
120
- # This method returns self if the source is already synchronized. Otherwise
121
- # it parses the AST and returns a new synchronized Source.
122
- #
123
- # @return [Source]
124
- def finish_synchronize
125
- return self if synchronized?
126
- synced = Source.new(@code, filename)
127
- if synced.parsed?
128
- synced.version = version
129
- return synced
130
- end
131
- synced = Source.new(@repaired, filename)
132
- synced.error_ranges.concat (error_ranges + last_updater.changes.map(&:range))
133
- synced.code = @code
134
- synced.synchronized = true
135
- synced.version = version
136
- synced
137
- end
138
-
139
95
  # Synchronize the Source with an update. This method applies changes to the
140
96
  # code, parses the new code's AST, and returns the resulting Source object.
141
97
  #
@@ -144,7 +144,7 @@ module Solargraph
144
144
  )
145
145
  end
146
146
  if t.nil? || t.include?('w')
147
- pins.push Solargraph::Pin::Method.new(
147
+ method_pin = Solargraph::Pin::Method.new(
148
148
  location: location,
149
149
  closure: namespace,
150
150
  name: "#{directive.tag.name}=",
@@ -153,7 +153,8 @@ module Solargraph
153
153
  visibility: :public,
154
154
  attribute: true
155
155
  )
156
- pins.last.parameters.push Pin::Parameter.new(name: 'value', decl: :arg, closure: pins.last)
156
+ pins.push method_pin
157
+ method_pin.parameters.push Pin::Parameter.new(name: 'value', decl: :arg, closure: pins.last)
157
158
  if pins.last.return_type.defined?
158
159
  pins.last.docstring.add_tag YARD::Tags::Tag.new(:param, '', pins.last.return_type.to_s.split(', '), 'value')
159
160
  end
@@ -40,6 +40,16 @@ module Solargraph
40
40
  @pin_select_cache[klass] ||= @pin_class_hash.select { |key, _| key <= klass }.values.flatten
41
41
  end
42
42
 
43
+ # A hash representing the state of the source map's API.
44
+ #
45
+ # ApiMap#catalog uses this value to determine whether it needs to clear its
46
+ # cache.
47
+ #
48
+ # @return [Integer]
49
+ def api_hash
50
+ @api_hash ||= (pins_by_class(Pin::Constant) + pins_by_class(Pin::Namespace).select { |pin| pin.namespace.to_s > '' } + pins_by_class(Pin::Reference) + pins_by_class(Pin::Method).map(&:node)).hash
51
+ end
52
+
43
53
  # @return [String]
44
54
  def filename
45
55
  source.filename