solargraph 0.54.0 → 0.54.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 (58) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +19 -0
  3. data/lib/solargraph/api_map/store.rb +6 -2
  4. data/lib/solargraph/api_map.rb +15 -8
  5. data/lib/solargraph/complex_type/type_methods.rb +10 -11
  6. data/lib/solargraph/complex_type/unique_type.rb +72 -9
  7. data/lib/solargraph/complex_type.rb +66 -17
  8. data/lib/solargraph/language_server/host/message_worker.rb +29 -3
  9. data/lib/solargraph/language_server/message/text_document/definition.rb +3 -3
  10. data/lib/solargraph/language_server/message/text_document/document_symbol.rb +3 -3
  11. data/lib/solargraph/language_server/message/text_document/hover.rb +1 -1
  12. data/lib/solargraph/language_server/message/text_document/type_definition.rb +3 -3
  13. data/lib/solargraph/language_server/message/workspace/workspace_symbol.rb +2 -2
  14. data/lib/solargraph/language_server/progress.rb +19 -2
  15. data/lib/solargraph/library.rb +30 -39
  16. data/lib/solargraph/location.rb +14 -1
  17. data/lib/solargraph/parser/parser_gem/node_chainer.rb +13 -7
  18. data/lib/solargraph/parser/parser_gem/node_methods.rb +1 -1
  19. data/lib/solargraph/parser/parser_gem/node_processors/args_node.rb +23 -19
  20. data/lib/solargraph/parser/parser_gem/node_processors/lvasgn_node.rb +2 -2
  21. data/lib/solargraph/parser/parser_gem/node_processors/masgn_node.rb +8 -2
  22. data/lib/solargraph/parser/parser_gem/node_processors/send_node.rb +1 -1
  23. data/lib/solargraph/parser.rb +2 -5
  24. data/lib/solargraph/pin/base.rb +15 -1
  25. data/lib/solargraph/pin/base_variable.rb +1 -1
  26. data/lib/solargraph/pin/block.rb +5 -23
  27. data/lib/solargraph/pin/callable.rb +147 -0
  28. data/lib/solargraph/pin/closure.rb +8 -3
  29. data/lib/solargraph/pin/common.rb +2 -6
  30. data/lib/solargraph/pin/conversions.rb +3 -2
  31. data/lib/solargraph/pin/instance_variable.rb +2 -2
  32. data/lib/solargraph/pin/method.rb +51 -31
  33. data/lib/solargraph/pin/namespace.rb +4 -4
  34. data/lib/solargraph/pin/parameter.rb +9 -11
  35. data/lib/solargraph/pin/proxy_type.rb +1 -1
  36. data/lib/solargraph/pin/signature.rb +3 -129
  37. data/lib/solargraph/pin.rb +4 -1
  38. data/lib/solargraph/range.rb +2 -4
  39. data/lib/solargraph/rbs_map/conversions.rb +70 -37
  40. data/lib/solargraph/rbs_map/core_fills.rb +6 -6
  41. data/lib/solargraph/shell.rb +17 -2
  42. data/lib/solargraph/source/chain/array.rb +6 -5
  43. data/lib/solargraph/source/chain/block_symbol.rb +1 -1
  44. data/lib/solargraph/source/chain/block_variable.rb +1 -1
  45. data/lib/solargraph/source/chain/call.rb +78 -50
  46. data/lib/solargraph/source/chain/link.rb +9 -0
  47. data/lib/solargraph/source/chain/or.rb +1 -1
  48. data/lib/solargraph/source/chain.rb +41 -17
  49. data/lib/solargraph/source/cursor.rb +13 -2
  50. data/lib/solargraph/source.rb +102 -85
  51. data/lib/solargraph/source_map/clip.rb +4 -4
  52. data/lib/solargraph/source_map/data.rb +30 -0
  53. data/lib/solargraph/source_map.rb +28 -16
  54. data/lib/solargraph/type_checker/rules.rb +6 -1
  55. data/lib/solargraph/type_checker.rb +7 -7
  56. data/lib/solargraph/version.rb +1 -1
  57. data/lib/solargraph/views/environment.erb +3 -5
  58. metadata +4 -2
@@ -27,11 +27,11 @@ module Solargraph
27
27
  def initialize workspace = Solargraph::Workspace.new, name = nil
28
28
  @workspace = workspace
29
29
  @name = name
30
- @threads = []
31
30
  # @type [Integer, nil]
32
31
  @total = nil
33
32
  # @type [Source, nil]
34
33
  @current = nil
34
+ @sync_count = 0
35
35
  end
36
36
 
37
37
  def inspect
@@ -44,7 +44,7 @@ module Solargraph
44
44
  #
45
45
  # @return [Boolean]
46
46
  def synchronized?
47
- !mutex.locked?
47
+ @sync_count < 2
48
48
  end
49
49
 
50
50
  # Attach a source to the library.
@@ -174,9 +174,9 @@ module Solargraph
174
174
  # @return [Array<Solargraph::Pin::Base>, nil]
175
175
  # @todo Take filename/position instead of filename/line/column
176
176
  def definitions_at filename, line, column
177
+ sync_catalog
177
178
  position = Position.new(line, column)
178
179
  cursor = Source::Cursor.new(read(filename), position)
179
- sync_catalog
180
180
  if cursor.comment?
181
181
  source = read(filename)
182
182
  offset = Solargraph::Position.to_offset(source.code, Solargraph::Position.new(line, column))
@@ -190,7 +190,14 @@ module Solargraph
190
190
  []
191
191
  end
192
192
  else
193
- mutex.synchronize { api_map.clip(cursor).define.map { |pin| pin.realize(api_map) } }
193
+ mutex.synchronize do
194
+ clip = api_map.clip(cursor)
195
+ if cursor.assign?
196
+ [Pin::ProxyType.new(name: cursor.word, return_type: clip.infer)]
197
+ else
198
+ clip.define.map { |pin| pin.realize(api_map) }
199
+ end
200
+ end
194
201
  end
195
202
  rescue FileNotFoundError => e
196
203
  handle_file_not_found(filename, e)
@@ -205,9 +212,9 @@ module Solargraph
205
212
  # @return [Array<Solargraph::Pin::Base>, nil]
206
213
  # @todo Take filename/position instead of filename/line/column
207
214
  def type_definitions_at filename, line, column
215
+ sync_catalog
208
216
  position = Position.new(line, column)
209
217
  cursor = Source::Cursor.new(read(filename), position)
210
- sync_catalog
211
218
  mutex.synchronize { api_map.clip(cursor).types }
212
219
  rescue FileNotFoundError => e
213
220
  handle_file_not_found filename, e
@@ -222,9 +229,9 @@ module Solargraph
222
229
  # @return [Array<Solargraph::Pin::Base>]
223
230
  # @todo Take filename/position instead of filename/line/column
224
231
  def signatures_at filename, line, column
232
+ sync_catalog
225
233
  position = Position.new(line, column)
226
234
  cursor = Source::Cursor.new(read(filename), position)
227
- sync_catalog
228
235
  mutex.synchronize { api_map.clip(cursor).signify }
229
236
  end
230
237
 
@@ -416,20 +423,7 @@ module Solargraph
416
423
  #
417
424
  # @return [void]
418
425
  def catalog
419
- @threads.delete_if(&:stop?)
420
- @threads.push(Thread.new do
421
- sleep 0.05 if RUBY_PLATFORM =~ /mingw/
422
- next unless @threads.last == Thread.current
423
-
424
- mutex.synchronize do
425
- logger.info "Cataloging #{workspace.directory.empty? ? 'generic workspace' : workspace.directory}"
426
- api_map.catalog bench
427
- logger.info "Catalog complete (#{api_map.source_maps.length} files, #{api_map.pins.length} pins)"
428
- logger.info "#{api_map.uncached_gemspecs.length} uncached gemspecs"
429
- cache_next_gemspec
430
- end
431
- end)
432
- @threads.last.run if RUBY_PLATFORM =~ /mingw/
426
+ @sync_count += 1
433
427
  end
434
428
 
435
429
  # @return [Bench]
@@ -467,7 +461,6 @@ module Solargraph
467
461
  # @param source [Source]
468
462
  # @return [Boolean] True if the source was merged into the workspace.
469
463
  def merge source
470
- Logging.logger.debug "Merging source: #{source.filename}"
471
464
  result = workspace.merge(source)
472
465
  maybe_map source
473
466
  result
@@ -489,7 +482,6 @@ module Solargraph
489
482
  if src
490
483
  Logging.logger.debug "Mapping #{src.filename}"
491
484
  source_map_hash[src.filename] = Solargraph::SourceMap.map(src)
492
- find_external_requires(source_map_hash[src.filename])
493
485
  source_map_hash[src.filename]
494
486
  else
495
487
  false
@@ -500,7 +492,7 @@ module Solargraph
500
492
  def map!
501
493
  workspace.sources.each do |src|
502
494
  source_map_hash[src.filename] = Solargraph::SourceMap.map(src)
503
- find_external_requires(source_map_hash[src.filename])
495
+ find_external_requires source_map_hash[src.filename]
504
496
  end
505
497
  self
506
498
  end
@@ -580,22 +572,10 @@ module Solargraph
580
572
  return unless source
581
573
  return unless @current == source || workspace.has_file?(source.filename)
582
574
  if source_map_hash.key?(source.filename)
583
- return if source_map_hash[source.filename].code == source.code &&
584
- source_map_hash[source.filename].source.synchronized? &&
585
- source.synchronized?
586
- if source.synchronized?
587
- new_map = Solargraph::SourceMap.map(source)
588
- unless source_map_hash[source.filename].try_merge!(new_map)
589
- source_map_hash[source.filename] = new_map
590
- find_external_requires(source_map_hash[source.filename])
591
- end
592
- else
593
- # @todo Smelly instance variable access
594
- source_map_hash[source.filename].instance_variable_set(:@source, source)
595
- end
575
+ new_map = Solargraph::SourceMap.map(source)
576
+ source_map_hash[source.filename] = new_map
596
577
  else
597
578
  source_map_hash[source.filename] = Solargraph::SourceMap.map(source)
598
- find_external_requires(source_map_hash[source.filename])
599
579
  end
600
580
  end
601
581
 
@@ -625,6 +605,7 @@ module Solargraph
625
605
  ensure
626
606
  end_cache_progress
627
607
  catalog
608
+ sync_catalog
628
609
  end
629
610
  end
630
611
 
@@ -666,8 +647,18 @@ module Solargraph
666
647
  end
667
648
 
668
649
  def sync_catalog
669
- @threads.delete_if(&:stop?)
670
- .last&.join
650
+ return if @sync_count == 0
651
+
652
+ mutex.synchronize do
653
+ logger.warn "CATALOG"
654
+ logger.info "Cataloging #{workspace.directory.empty? ? 'generic workspace' : workspace.directory}"
655
+ api_map.catalog bench
656
+ source_map_hash.values.each { |map| find_external_requires(map) }
657
+ logger.info "Catalog complete (#{api_map.source_maps.length} files, #{api_map.pins.length} pins)"
658
+ logger.info "#{api_map.uncached_gemspecs.length} uncached gemspecs"
659
+ cache_next_gemspec
660
+ @sync_count = 0
661
+ end
671
662
  end
672
663
  end
673
664
  end
@@ -1,7 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Solargraph
4
- # A section of text identified by its filename and range.
4
+ # A pointer to a section of source text identified by its filename
5
+ # and Range.
5
6
  #
6
7
  class Location
7
8
  # @return [String]
@@ -17,6 +18,11 @@ module Solargraph
17
18
  @range = range
18
19
  end
19
20
 
21
+ # @param location [self]
22
+ def contain? location
23
+ range.contain?(location.range.start) && range.contain?(location.range.ending) && filename == location.filename
24
+ end
25
+
20
26
  # @return [Hash]
21
27
  def to_hash
22
28
  {
@@ -25,6 +31,13 @@ module Solargraph
25
31
  }
26
32
  end
27
33
 
34
+ # @param node [Parser::AST::Node, nil]
35
+ def self.from_node(node)
36
+ return nil if node.nil? || node.loc.nil?
37
+ range = Range.from_node(node)
38
+ self.new(node.loc.expression.source_buffer.name, range)
39
+ end
40
+
28
41
  # @param other [BasicObject]
29
42
  def == other
30
43
  return false unless other.is_a?(Location)
@@ -89,15 +89,21 @@ module Solargraph
89
89
  elsif n.type == :const
90
90
  const = unpack_name(n)
91
91
  result.push Chain::Constant.new(const)
92
- elsif [:lvar, :lvasgn].include?(n.type)
92
+ elsif [:lvasgn, :ivasgn, :gvasgn, :cvasgn].include?(n.type)
93
+ result.concat generate_links(n.children[1])
94
+ elsif n.type == :lvar
93
95
  result.push Chain::Call.new(n.children[0].to_s)
94
- elsif [:ivar, :ivasgn].include?(n.type)
95
- result.push Chain::InstanceVariable.new(n.children[0].to_s)
96
- elsif [:cvar, :cvasgn].include?(n.type)
97
- result.push Chain::ClassVariable.new(n.children[0].to_s)
98
- elsif [:gvar, :gvasgn].include?(n.type)
99
- result.push Chain::GlobalVariable.new(n.children[0].to_s)
96
+ elsif n.type == :ivar
97
+ result.push Chain::InstanceVariable.new(n.children[0].to_s)
98
+ elsif n.type == :cvar
99
+ result.push Chain::ClassVariable.new(n.children[0].to_s)
100
+ elsif n.type == :gvar
101
+ result.push Chain::GlobalVariable.new(n.children[0].to_s)
100
102
  elsif n.type == :or_asgn
103
+ # @todo: Need a new Link class here that evaluates the
104
+ # existing variable type with the RHS, and generates a
105
+ # union type of the LHS alone if never nil, or minus nil +
106
+ # RHS if it is nilable.
101
107
  result.concat generate_links n.children[1]
102
108
  elsif [:class, :module, :def, :defs].include?(n.type)
103
109
  # @todo Undefined or what?
@@ -311,7 +311,7 @@ module Solargraph
311
311
  # statements in value positions.
312
312
  module DeepInference
313
313
  class << self
314
- CONDITIONAL_ALL_BUT_FIRST = [:if, :unless, :or_asgn]
314
+ CONDITIONAL_ALL_BUT_FIRST = [:if, :unless]
315
315
  CONDITIONAL_ALL = [:or]
316
316
  ONLY_ONE_CHILD = [:return]
317
317
  FIRST_TWO_CHILDREN = [:rescue]
@@ -6,22 +6,25 @@ module Solargraph
6
6
  module NodeProcessors
7
7
  class ArgsNode < Parser::NodeProcessor::Base
8
8
  def process
9
- if node.type == :forward_args
10
- forward
11
- else
12
- node.children.each do |u|
13
- loc = get_node_location(u)
14
- locals.push Solargraph::Pin::Parameter.new(
15
- location: loc,
16
- closure: region.closure,
17
- comments: comments_for(node),
18
- name: u.children[0].to_s,
19
- assignment: u.children[1],
20
- asgn_code: u.children[1] ? region.code_for(u.children[1]) : nil,
21
- presence: region.closure.location.range,
22
- decl: get_decl(u)
23
- )
24
- region.closure.parameters.push locals.last
9
+ callable = region.closure
10
+ if callable.is_a? Pin::Callable
11
+ if node.type == :forward_args
12
+ forward(callable)
13
+ else
14
+ node.children.each do |u|
15
+ loc = get_node_location(u)
16
+ locals.push Solargraph::Pin::Parameter.new(
17
+ location: loc,
18
+ closure: callable,
19
+ comments: comments_for(node),
20
+ name: u.children[0].to_s,
21
+ assignment: u.children[1],
22
+ asgn_code: u.children[1] ? region.code_for(u.children[1]) : nil,
23
+ presence: callable.location.range,
24
+ decl: get_decl(u)
25
+ )
26
+ callable.parameters.push locals.last
27
+ end
25
28
  end
26
29
  end
27
30
  process_children
@@ -29,16 +32,17 @@ module Solargraph
29
32
 
30
33
  private
31
34
 
35
+ # @param callable [Pin::Callable]
32
36
  # @return [void]
33
- def forward
37
+ def forward(callable)
34
38
  loc = get_node_location(node)
35
39
  locals.push Solargraph::Pin::Parameter.new(
36
40
  location: loc,
37
- closure: region.closure,
41
+ closure: callable,
38
42
  presence: region.closure.location.range,
39
43
  decl: get_decl(node)
40
44
  )
41
- region.closure.parameters.push locals.last
45
+ callable.parameters.push locals.last
42
46
  end
43
47
 
44
48
  # @param node [AST::Node]
@@ -8,8 +8,8 @@ module Solargraph
8
8
  include ParserGem::NodeMethods
9
9
 
10
10
  def process
11
- here = get_node_start_position(node)
12
- presence = Range.new(here, region.closure.location.range.ending)
11
+ # variable not visible until next statement
12
+ presence = Range.new(get_node_end_position(node), region.closure.location.range.ending)
13
13
  loc = get_node_location(node)
14
14
  locals.push Solargraph::Pin::LocalVariable.new(
15
15
  location: loc,
@@ -30,11 +30,17 @@ module Solargraph
30
30
 
31
31
  lhs_arr.each_with_index do |lhs, i|
32
32
  location = get_node_location(lhs)
33
+ pin = if lhs.type == :lvasgn
34
+ # lvasgn is a local variable
35
+ locals.find { |l| l.location == location }
36
+ else
37
+ # e.g., ivasgn is an instance variable, etc
38
+ pins.find { |iv| iv.location == location && iv.is_a?(Pin::BaseVariable) }
39
+ end
33
40
  # @todo in line below, nothing in typechecking alerts
34
41
  # when a non-existant method is called on 'l'
35
- pin = locals.find { |l| l.location == location }
36
42
  if pin.nil?
37
- Solargraph.logger.debug "Could not find pin in location #{location}"
43
+ Solargraph.logger.debug { "Could not find local for masgn= value in location #{location.inspect} in #{lhs_arr} - masgn = #{masgn}, lhs.type = #{lhs.type}" }
38
44
  next
39
45
  end
40
46
  pin.mass_assignment = [mass_rhs, i]
@@ -92,7 +92,7 @@ module Solargraph
92
92
  pins.push method_pin
93
93
  method_pin.parameters.push Pin::Parameter.new(name: 'value', decl: :arg, closure: pins.last)
94
94
  if method_pin.return_type.defined?
95
- pins.last.docstring.add_tag YARD::Tags::Tag.new(:param, '', pins.last.return_type.to_s.split(', '), 'value')
95
+ pins.last.docstring.add_tag YARD::Tags::Tag.new(:param, '', pins.last.return_type.items.map(&:rooted_tags), 'value')
96
96
  end
97
97
  end
98
98
  end
@@ -9,15 +9,12 @@ module Solargraph
9
9
  class SyntaxError < StandardError
10
10
  end
11
11
 
12
- def self.rubyvm?
13
- false
14
- end
15
-
16
12
  # @deprecated
17
13
  Legacy = ParserGem
18
14
 
19
15
  ClassMethods = ParserGem::ClassMethods
20
-
16
+ # @todo should be able to just 'extend ClassMethods' here and
17
+ # typecheck things off it in strict mode
21
18
  extend ParserGem::ClassMethods
22
19
 
23
20
  NodeMethods = ParserGem::NodeMethods
@@ -15,6 +15,9 @@ module Solargraph
15
15
  # @return [Solargraph::Location]
16
16
  attr_reader :location
17
17
 
18
+ # @return [Solargraph::Location]
19
+ attr_reader :type_location
20
+
18
21
  # @return [String]
19
22
  attr_reader :name
20
23
 
@@ -25,11 +28,13 @@ module Solargraph
25
28
  attr_accessor :source
26
29
 
27
30
  # @param location [Solargraph::Location, nil]
31
+ # @param type_location [Solargraph::Location, nil]
28
32
  # @param closure [Solargraph::Pin::Closure, nil]
29
33
  # @param name [String]
30
34
  # @param comments [String]
31
- def initialize location: nil, closure: nil, name: '', comments: ''
35
+ def initialize location: nil, type_location: nil, closure: nil, name: '', comments: ''
32
36
  @location = location
37
+ @type_location = type_location
33
38
  @closure = closure
34
39
  @name = name
35
40
  @comments = comments
@@ -70,6 +75,10 @@ module Solargraph
70
75
  transformed.erase_generics(definitions.generics)
71
76
  end
72
77
 
78
+ def all_rooted?
79
+ !return_type || return_type.all_rooted?
80
+ end
81
+
73
82
  # @param generics_to_erase [Enumerable<String>]
74
83
  # @return [self]
75
84
  def erase_generics(generics_to_erase)
@@ -102,6 +111,11 @@ module Solargraph
102
111
  false
103
112
  end
104
113
 
114
+ # @return [Location, nil]
115
+ def best_location
116
+ location || type_location
117
+ end
118
+
105
119
  # Pin equality is determined using the #nearly? method and also
106
120
  # requiring both pins to have the same location.
107
121
  #
@@ -57,7 +57,7 @@ module Solargraph
57
57
  # Use the return node for inference. The clip might infer from the
58
58
  # first node in a method call instead of the entire call.
59
59
  chain = Parser.chain(node, nil, nil)
60
- result = chain.infer(api_map, closure, clip.locals).self_to(closure.context.tag)
60
+ result = chain.infer(api_map, closure, clip.locals).self_to_type(closure.context)
61
61
  types.push result unless result.undefined?
62
62
  end
63
63
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Solargraph
4
4
  module Pin
5
- class Block < Closure
5
+ class Block < Callable
6
6
  # @return [Parser::AST::Node]
7
7
  attr_reader :receiver
8
8
 
@@ -14,10 +14,9 @@ module Solargraph
14
14
  # @param context [ComplexType, nil]
15
15
  # @param args [::Array<Parameter>]
16
16
  def initialize receiver: nil, args: [], context: nil, node: nil, **splat
17
- super(**splat)
17
+ super(**splat, parameters: args)
18
18
  @receiver = receiver
19
19
  @context = context
20
- @parameters = args
21
20
  @return_type = ComplexType.parse('::Proc')
22
21
  @node = node
23
22
  end
@@ -32,16 +31,6 @@ module Solargraph
32
31
  @rebind&.defined? ? @rebind : closure.binder
33
32
  end
34
33
 
35
- # @return [::Array<Parameter>]
36
- def parameters
37
- @parameters ||= []
38
- end
39
-
40
- # @return [::Array<String>]
41
- def parameter_names
42
- @parameter_names ||= parameters.map(&:name)
43
- end
44
-
45
34
  # @param yield_types [::Array<ComplexType>]
46
35
  # @param parameters [::Array<Parameter>]
47
36
  #
@@ -57,13 +46,6 @@ module Solargraph
57
46
  parameters.map { ComplexType::UNDEFINED }
58
47
  end
59
48
 
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
49
  # @param api_map [ApiMap]
68
50
  # @return [::Array<ComplexType>]
69
51
  def typify_parameters(api_map)
@@ -87,7 +69,7 @@ module Solargraph
87
69
  namespace_pin = api_map.get_namespace_pins(meth.namespace, closure.namespace).first
88
70
  arg_type.resolve_generics(namespace_pin, param_type)
89
71
  else
90
- arg_type.self_to(chain.base.infer(api_map, self, locals).namespace).qualify(api_map, meth.context.namespace)
72
+ arg_type.self_to_type(chain.base.infer(api_map, self, locals)).qualify(api_map, meth.context.namespace)
91
73
  end
92
74
  end
93
75
  end
@@ -105,7 +87,7 @@ module Solargraph
105
87
 
106
88
  chain = Parser.chain(receiver, location.filename)
107
89
  locals = api_map.source_map(location.filename).locals_at(location)
108
- receiver_pin = chain.define(api_map, self, locals).first
90
+ receiver_pin = chain.define(api_map, closure, locals).first
109
91
  return ComplexType::UNDEFINED unless receiver_pin
110
92
 
111
93
  types = receiver_pin.docstring.tag(:yieldreceiver)&.types
@@ -114,7 +96,7 @@ module Solargraph
114
96
  target = chain.base.infer(api_map, receiver_pin, locals)
115
97
  target = full_context unless target.defined?
116
98
 
117
- ComplexType.try_parse(*types).qualify(api_map, receiver_pin.context.namespace).self_to(target.to_s)
99
+ ComplexType.try_parse(*types).qualify(api_map, receiver_pin.context.namespace).self_to_type(target)
118
100
  end
119
101
  end
120
102
  end
@@ -0,0 +1,147 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solargraph
4
+ module Pin
5
+ class Callable < Closure
6
+ # @return [self]
7
+ attr_reader :block
8
+
9
+ attr_reader :parameters
10
+
11
+ # @return [ComplexType, nil]
12
+ attr_reader :return_type
13
+
14
+ # @param block [Signature, nil]
15
+ # @param return_type [ComplexType, nil]
16
+ # @param parameters [::Array<Pin::Parameter>]
17
+ def initialize block: nil, return_type: nil, parameters: [], **splat
18
+ super(**splat)
19
+ @block = block
20
+ @return_type = return_type
21
+ @parameters = parameters
22
+ end
23
+
24
+ # @return [::Array<String>]
25
+ def parameter_names
26
+ @parameter_names ||= parameters.map(&:name)
27
+ end
28
+
29
+ # @param generics_to_resolve [Enumerable<String>]
30
+ # @param arg_types [Array<ComplexType>, nil]
31
+ # @param return_type_context [ComplexType, nil]
32
+ # @param yield_arg_types [Array<ComplexType>, nil]
33
+ # @param yield_return_type_context [ComplexType, nil]
34
+ # @param context [ComplexType, nil]
35
+ # @param resolved_generic_values [Hash{String => ComplexType}]
36
+ # @return [self]
37
+ def resolve_generics_from_context(generics_to_resolve,
38
+ arg_types = nil,
39
+ return_type_context = nil,
40
+ yield_arg_types = nil,
41
+ yield_return_type_context = nil,
42
+ resolved_generic_values: {})
43
+ callable = super(generics_to_resolve, return_type_context, resolved_generic_values: resolved_generic_values)
44
+ callable.parameters = callable.parameters.each_with_index.map do |param, i|
45
+ if arg_types.nil?
46
+ param.dup
47
+ else
48
+ param.resolve_generics_from_context(generics_to_resolve,
49
+ arg_types[i],
50
+ resolved_generic_values: resolved_generic_values)
51
+ end
52
+ end
53
+ callable.block = block.resolve_generics_from_context(generics_to_resolve,
54
+ yield_arg_types,
55
+ yield_return_type_context,
56
+ resolved_generic_values: resolved_generic_values) if callable.block?
57
+ callable
58
+ end
59
+
60
+ # @param generics_to_resolve [Enumerable<String>]
61
+ # @param arg_types [Array<ComplexType>, nil]
62
+ # @param return_type_context [ComplexType, nil]
63
+ # @param yield_arg_types [Array<ComplexType>, nil]
64
+ # @param yield_return_type_context [ComplexType, nil]
65
+ # @param context [ComplexType, nil]
66
+ # @param resolved_generic_values [Hash{String => ComplexType}]
67
+ # @return [self]
68
+ def resolve_generics_from_context_until_complete(generics_to_resolve,
69
+ arg_types = nil,
70
+ return_type_context = nil,
71
+ yield_arg_types = nil,
72
+ yield_return_type_context = nil,
73
+ resolved_generic_values: {})
74
+ # See
75
+ # https://github.com/soutaro/steep/tree/master/lib/steep/type_inference
76
+ # and
77
+ # https://github.com/sorbet/sorbet/blob/master/infer/inference.cc
78
+ # for other implementations
79
+
80
+ return self if generics_to_resolve.empty?
81
+
82
+ last_resolved_generic_values = resolved_generic_values.dup
83
+ new_pin = resolve_generics_from_context(generics_to_resolve,
84
+ arg_types,
85
+ return_type_context,
86
+ yield_arg_types,
87
+ yield_return_type_context,
88
+ resolved_generic_values: resolved_generic_values)
89
+ if last_resolved_generic_values == resolved_generic_values
90
+ # erase anything unresolved
91
+ return new_pin.erase_generics(self.generics)
92
+ end
93
+ new_pin.resolve_generics_from_context_until_complete(generics_to_resolve,
94
+ arg_types,
95
+ return_type_context,
96
+ yield_arg_types,
97
+ yield_return_type_context,
98
+ resolved_generic_values: resolved_generic_values)
99
+ end
100
+
101
+ # @return [Array<String>]
102
+ # @yieldparam [ComplexType]
103
+ # @yieldreturn [ComplexType]
104
+ # @return [self]
105
+ def transform_types(&transform)
106
+ # @todo 'super' alone should work here I think, but doesn't typecheck at level typed
107
+ callable = super(&transform)
108
+ callable.block = block.transform_types(&transform) if block?
109
+ callable.parameters = parameters.map do |param|
110
+ param.transform_types(&transform)
111
+ end
112
+ callable
113
+ end
114
+
115
+ # @param arguments [::Array<Chain>]
116
+ # @param signature [Pin::Signature]
117
+ # @return [Boolean]
118
+ def arity_matches? arguments, with_block
119
+ argcount = arguments.length
120
+ parcount = mandatory_positional_param_count
121
+ parcount -= 1 if !parameters.empty? && parameters.last.block?
122
+ return false if block? && !with_block
123
+ return false if argcount < parcount && !(argcount == parcount - 1 && parameters.last.restarg?)
124
+ true
125
+ end
126
+
127
+ def mandatory_positional_param_count
128
+ parameters.count(&:arg?)
129
+ end
130
+
131
+ # @return [String]
132
+ def to_rbs
133
+ rbs_generics + '(' + parameters.map { |param| param.to_rbs }.join(', ') + ') ' + (block.nil? ? '' : '{ ' + block.to_rbs + ' } ') + '-> ' + return_type.to_rbs
134
+ end
135
+
136
+ def block?
137
+ !!@block
138
+ end
139
+
140
+ protected
141
+
142
+ attr_writer :block
143
+
144
+ attr_writer :parameters
145
+ end
146
+ end
147
+ end
@@ -18,7 +18,7 @@ module Solargraph
18
18
  @context ||= begin
19
19
  result = super
20
20
  if scope == :instance
21
- Solargraph::ComplexType.parse(result.namespace)
21
+ result.reduce_class_type
22
22
  else
23
23
  result
24
24
  end
@@ -42,10 +42,15 @@ module Solargraph
42
42
  end
43
43
 
44
44
  # @return [String]
45
- def generics_as_rbs
45
+ def to_rbs
46
+ rbs_generics + return_type.to_rbs
47
+ end
48
+
49
+ # @return [String]
50
+ def rbs_generics
46
51
  return '' if generics.empty?
47
52
 
48
- generics.join(', ') + ' '
53
+ '[' + generics.map { |gen| gen.to_s }.join(', ') + '] '
49
54
  end
50
55
  end
51
56
  end