solargraph 0.61.0 → 0.62.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 (82) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/linting.yml +24 -2
  3. data/.github/workflows/plugins.yml +18 -0
  4. data/.github/workflows/rspec.yml +14 -1
  5. data/.github/workflows/typecheck.yml +6 -0
  6. data/CHANGELOG.md +38 -0
  7. data/lib/solargraph/api_map/index.rb +14 -3
  8. data/lib/solargraph/api_map/store.rb +6 -0
  9. data/lib/solargraph/api_map.rb +34 -2
  10. data/lib/solargraph/collection/gem.rb +12 -10
  11. data/lib/solargraph/collection.rb +0 -1
  12. data/lib/solargraph/complex_type/unique_type.rb +13 -0
  13. data/lib/solargraph/complex_type.rb +23 -3
  14. data/lib/solargraph/convention.rb +0 -4
  15. data/lib/solargraph/diagnostics/type_check.rb +0 -1
  16. data/lib/solargraph/external.rb +56 -28
  17. data/lib/solargraph/language_server/host/diagnoser.rb +1 -1
  18. data/lib/solargraph/language_server/host/dispatch.rb +4 -2
  19. data/lib/solargraph/language_server/host.rb +10 -2
  20. data/lib/solargraph/language_server/message/completion_item/resolve.rb +10 -3
  21. data/lib/solargraph/language_server/message/text_document/definition.rb +1 -1
  22. data/lib/solargraph/library.rb +19 -33
  23. data/lib/solargraph/parser/node_processor/base.rb +6 -0
  24. data/lib/solargraph/parser/parser_gem/node_methods.rb +29 -6
  25. data/lib/solargraph/parser/parser_gem/node_processors/class_definition/class_assignment_node.rb +98 -0
  26. data/lib/solargraph/parser/parser_gem/node_processors/class_definition.rb +54 -0
  27. data/lib/solargraph/parser/parser_gem/node_processors/cvasgn_node.rb +1 -0
  28. data/lib/solargraph/parser/parser_gem/node_processors/data_definition/data_assignment_node.rb +65 -0
  29. data/lib/solargraph/parser/parser_gem/node_processors/data_definition/data_definition_node.rb +97 -0
  30. data/lib/solargraph/parser/parser_gem/node_processors/data_definition.rb +110 -0
  31. data/lib/solargraph/parser/parser_gem/node_processors/gvasgn_node.rb +1 -0
  32. data/lib/solargraph/parser/parser_gem/node_processors/ivasgn_node.rb +2 -0
  33. data/lib/solargraph/parser/parser_gem/node_processors/lvasgn_node.rb +1 -0
  34. data/lib/solargraph/parser/parser_gem/node_processors/send_node.rb +185 -0
  35. data/lib/solargraph/parser/parser_gem/node_processors/struct_definition/struct_assignment_node.rb +66 -0
  36. data/lib/solargraph/parser/parser_gem/node_processors/struct_definition/struct_definition_node.rb +108 -0
  37. data/lib/solargraph/parser/parser_gem/node_processors/struct_definition.rb +170 -0
  38. data/lib/solargraph/parser/parser_gem/node_processors.rb +33 -29
  39. data/lib/solargraph/pin/base.rb +25 -1
  40. data/lib/solargraph/pin/base_variable.rb +31 -1
  41. data/lib/solargraph/pin/callable.rb +11 -2
  42. data/lib/solargraph/pin/delegated_method.rb +5 -0
  43. data/lib/solargraph/pin/duck_method.rb +36 -16
  44. data/lib/solargraph/pin/instance_variable.rb +45 -0
  45. data/lib/solargraph/pin/method.rb +10 -0
  46. data/lib/solargraph/pin/method_alias.rb +4 -0
  47. data/lib/solargraph/pin/parameter.rb +1 -1
  48. data/lib/solargraph/pin/reference/included_mixin.rb +22 -0
  49. data/lib/solargraph/pin/reference.rb +1 -0
  50. data/lib/solargraph/plugin/active_support.rb +100 -0
  51. data/lib/solargraph/plugin/addon.rb +16 -0
  52. data/lib/solargraph/plugin/base.rb +25 -0
  53. data/lib/solargraph/plugin/panel.rb +37 -0
  54. data/lib/solargraph/plugin.rb +26 -0
  55. data/lib/solargraph/rbs_collection.rb +119 -0
  56. data/lib/solargraph/rbs_map/conversions.rb +106 -47
  57. data/lib/solargraph/rbs_map/gem.rb +4 -1
  58. data/lib/solargraph/rbs_map/helpers.rb +7 -6
  59. data/lib/solargraph/rbs_translator.rb +105 -70
  60. data/lib/solargraph/repo.rb +1 -1
  61. data/lib/solargraph/source/chain/call.rb +32 -13
  62. data/lib/solargraph/source/node_map.rb +27 -0
  63. data/lib/solargraph/source.rb +17 -0
  64. data/lib/solargraph/source_map/call.rb +18 -0
  65. data/lib/solargraph/source_map/mapper.rb +1 -1
  66. data/lib/solargraph/source_map.rb +18 -4
  67. data/lib/solargraph/type_checker/rules.rb +2 -2
  68. data/lib/solargraph/type_checker.rb +4 -1
  69. data/lib/solargraph/version.rb +1 -1
  70. data/lib/solargraph/yard_map/macro.rb +51 -1
  71. data/lib/solargraph.rb +2 -0
  72. data/rbs/fills/boolean/0/boolean.rbs +12 -0
  73. data/solargraph.gemspec +1 -1
  74. metadata +21 -11
  75. data/lib/solargraph/collection/rbs.rb +0 -49
  76. data/lib/solargraph/convention/active_support_concern.rb +0 -111
  77. data/lib/solargraph/convention/data_definition/data_assignment_node.rb +0 -61
  78. data/lib/solargraph/convention/data_definition/data_definition_node.rb +0 -93
  79. data/lib/solargraph/convention/data_definition.rb +0 -108
  80. data/lib/solargraph/convention/struct_definition/struct_assignment_node.rb +0 -62
  81. data/lib/solargraph/convention/struct_definition/struct_definition_node.rb +0 -103
  82. data/lib/solargraph/convention/struct_definition.rb +0 -168
@@ -10,36 +10,43 @@ module Solargraph
10
10
  'int' => 'Integer',
11
11
  'untyped' => '',
12
12
  'NilClass' => 'nil'
13
- }
13
+ }.freeze
14
14
 
15
- # @param type [RBS::Types::Bases::Base]
15
+ # Relative type names (e.g. `Bar` in an inline `#: () -> Bar`) stay
16
+ # unrooted so they resolve against the pin's namespace like YARD tags
17
+ # do. Absolute names (`::Bar`, or any name from an RBS environment
18
+ # after `resolve_type_names`) and RBS builtins stay rooted.
19
+ #
20
+ # @param type [RBS::Types::t]
21
+ # @param type_alias_decls [Hash{String => RBS::AST::Declarations::TypeAlias}]
16
22
  # @return [ComplexType]
17
- def self.to_complex_type(type)
18
- tag = type_to_tag(type)
19
- ComplexType.try_parse(tag).force_rooted
23
+ def self.to_complex_type type, type_alias_decls: {}
24
+ ComplexType.try_parse(type_to_tag(type, type_alias_decls))
20
25
  end
21
26
 
22
27
  # @param param_type [RBS::Types::Function::Param]
23
28
  # @param name [String]
24
29
  # @param decl [Symbol]
25
30
  # @param closure [Pin::Closure]
31
+ # @param type_alias_decls [Hash{String => RBS::AST::Declarations::TypeAlias}]
26
32
  # @return [Pin::Parameter]
27
- def self.to_parameter_pin(param_type, name, decl, closure)
33
+ def self.to_parameter_pin param_type, name, decl, closure, type_alias_decls: {}
28
34
  return_type = if decl == :restarg
29
- ComplexType.parse('Array')
30
- elsif decl == :kwrestarg
31
- ComplexType.parse('Hash{Symbol => Object}')
32
- else
33
- RbsTranslator.to_complex_type(param_type.type)
34
- end
35
+ ComplexType.parse('Array')
36
+ elsif decl == :kwrestarg
37
+ ComplexType.parse('Hash{Symbol => Object}')
38
+ else
39
+ RbsTranslator.to_complex_type(param_type.type, type_alias_decls: type_alias_decls)
40
+ end
35
41
  Solargraph::Pin::Parameter.new(decl: decl, name: name, closure: closure, return_type: return_type, source: :rbs, type_location: to_sg_location(param_type.location) || closure.type_location)
36
42
  end
37
43
 
38
- # @param method_type [RBS::MethodType]
44
+ # @param method_type [RBS::MethodType, RBS::Types::Block]
39
45
  # @param closure [Pin::Closure]
40
46
  # @param parameter_names [Array<String>]
47
+ # @param type_alias_decls [Hash{String => RBS::AST::Declarations::TypeAlias}]
41
48
  # @return [Array<Pin::Parameter>]
42
- def self.to_parameter_pins method_type, closure, parameter_names = []
49
+ def self.to_parameter_pins method_type, closure, parameter_names = [], type_alias_decls: {}
43
50
  if defined?(RBS::Types::UntypedFunction) && method_type.type.is_a?(RBS::Types::UntypedFunction)
44
51
  return [
45
52
  Solargraph::Pin::Parameter.new(decl: :restarg, name: 'arg', closure: closure, source: :rbs)
@@ -49,27 +56,37 @@ module Solargraph
49
56
  arg_num = 0
50
57
  params = []
51
58
  method_type.type.required_positionals.each do |param|
52
- params.push RbsTranslator.to_parameter_pin(param, param.name&.to_s || parameter_names[arg_num] || "arg_#{arg_num}", :arg, closure)
59
+ # @sg-ignore Unresolved call to name on RBS::Types::Function::Param
60
+ params.push RbsTranslator.to_parameter_pin(param, param.name&.to_s || parameter_names[arg_num] || "arg_#{arg_num}", :arg, closure, type_alias_decls: type_alias_decls)
53
61
  arg_num += 1
54
62
  end
55
63
  method_type.type.optional_positionals.each do |param|
56
- params.push RbsTranslator.to_parameter_pin(param, param.name&.to_s || parameter_names[arg_num] || "arg_#{arg_num}", :optarg, closure)
64
+ # @sg-ignore Unresolved call to name on RBS::Types::Function::Param
65
+ params.push RbsTranslator.to_parameter_pin(param, param.name&.to_s || parameter_names[arg_num] || "arg_#{arg_num}", :optarg, closure, type_alias_decls: type_alias_decls)
57
66
  arg_num += 1
58
67
  end
59
68
  if method_type.type.rest_positionals
60
- params.push RbsTranslator.to_parameter_pin(method_type.type.rest_positionals, method_type.type.rest_positionals.name&.to_s || parameter_names[arg_num] || "arg_#{arg_num}", :restarg, closure)
69
+ rest_positionals = method_type.type.rest_positionals
70
+ # @sg-ignore Unresolved call to name on RBS::Types::Function::Param
71
+ rest_name = rest_positionals.name&.to_s || parameter_names[arg_num] || "arg_#{arg_num}"
72
+ params.push RbsTranslator.to_parameter_pin(rest_positionals, rest_name, :restarg, closure, type_alias_decls: type_alias_decls)
61
73
  arg_num += 1
62
74
  end
63
75
  method_type.type.required_keywords.each do |param|
64
- params.push RbsTranslator.to_parameter_pin(param.last, param.first.to_s, :kwarg, closure)
76
+ # @sg-ignore Unresolved calls to last, first on generic<D>
77
+ params.push RbsTranslator.to_parameter_pin(param.last, param.first.to_s, :kwarg, closure, type_alias_decls: type_alias_decls)
65
78
  arg_num += 1
66
79
  end
67
80
  method_type.type.optional_keywords.each do |param|
68
- params.push RbsTranslator.to_parameter_pin(param.last, param.first.to_s, :kwoptarg, closure)
81
+ # @sg-ignore Unresolved calls to last, first on generic<D>
82
+ params.push RbsTranslator.to_parameter_pin(param.last, param.first.to_s, :kwoptarg, closure, type_alias_decls: type_alias_decls)
69
83
  arg_num += 1
70
84
  end
71
85
  if method_type.type.rest_keywords
72
- params.push RbsTranslator.to_parameter_pin(method_type.type.rest_keywords, method_type.type.rest_keywords.name&.to_s || parameter_names[arg_num] || "arg_#{arg_num}", :kwrestarg, closure)
86
+ rest_keywords = method_type.type.rest_keywords
87
+ # @sg-ignore Unresolved call to name on RBS::Types::Function::Param
88
+ rest_keywords_name = rest_keywords.name&.to_s || parameter_names[arg_num] || "arg_#{arg_num}"
89
+ params.push RbsTranslator.to_parameter_pin(rest_keywords, rest_keywords_name, :kwrestarg, closure, type_alias_decls: type_alias_decls)
73
90
  end
74
91
  params
75
92
  end
@@ -77,41 +94,29 @@ module Solargraph
77
94
  # @param method_type [RBS::MethodType]
78
95
  # @param closure [Pin::Closure]
79
96
  # @param parameter_names [Array<String>]
97
+ # @param type_alias_decls [Hash{String => RBS::AST::Declarations::TypeAlias}]
80
98
  # @return [Pin::Signature]
81
- def self.to_signature method_type, closure, parameter_names = []
99
+ def self.to_signature method_type, closure, parameter_names = [], type_alias_decls: {}
82
100
  # There may be edge cases here around different signatures
83
101
  # having different type params / orders - we may need to match
84
102
  # this data model and have generics live in signatures to
85
103
  # handle those correctly
86
104
  generics = method_type.type_params.map(&:name).map(&:to_s).uniq
87
- parameters = to_parameter_pins(method_type, closure, parameter_names)
88
- return_type = to_complex_type(method_type.type.return_type)
105
+ parameters = to_parameter_pins(method_type, closure, parameter_names, type_alias_decls: type_alias_decls)
106
+ return_type = to_complex_type(method_type.type.return_type, type_alias_decls: type_alias_decls)
89
107
  block = if method_type.block
90
- block_parameters = to_parameter_pins(method_type.block, closure)
91
- block_return_type = to_complex_type(method_type.block.type.return_type)
92
- Pin::Signature.new(generics: generics, parameters: block_parameters, return_type: block_return_type, source: :rbs, type_location: closure.location, closure: closure)
93
- end
94
- Pin::Signature.new(generics: generics, parameters: parameters, return_type: return_type, block: block, source: :rbs, type_location: closure.location, closure: closure)
95
- end
96
-
97
- # @param type_name [RBS::TypeName]
98
- # @param type_args [Enumerable<RBS::Types::Bases::Base>]
99
- # @return [ComplexType::UniqueType]
100
- def self.build_unique_type(type_name, type_args = [])
101
- base = RBS_TO_YARD_TYPE[type_name.relative!.to_s] || type_name.relative!.to_s
102
- params = type_args.map do |a|
103
- RbsTranslator.to_complex_type(a)
104
- end
105
- if base == 'Hash' && params.length == 2
106
- ComplexType::UniqueType.new(base, [params.first], [params.last], rooted: true, parameters_type: :hash)
107
- else
108
- ComplexType::UniqueType.new(base, [], params.reject(&:undefined?), rooted: true, parameters_type: :list)
109
- end
108
+ # @sg-ignore https://github.com/castwide/solargraph/issues/1249
109
+ block_parameters = to_parameter_pins(method_type.block, closure, type_alias_decls: type_alias_decls)
110
+ block_return_type = to_complex_type(method_type.block.type.return_type, type_alias_decls: type_alias_decls)
111
+ Pin::Signature.new(generics: generics, parameters: block_parameters, return_type: block_return_type, source: :rbs, type_location: closure.location, closure: closure)
112
+ end
113
+ Pin::Signature.new(generics: generics, parameters: parameters, return_type: return_type, block: block,
114
+ block_required: method_type.block&.required || false, source: :rbs, type_location: closure.location, closure: closure)
110
115
  end
111
116
 
112
117
  # @param location [RBS::Location, nil]
113
118
  # @return [Solargraph::Location, nil]
114
- def self.to_sg_location(location)
119
+ def self.to_sg_location location
115
120
  return nil if location&.name.nil?
116
121
 
117
122
  start_pos = Position.new(location.start_line - 1, location.start_column)
@@ -123,50 +128,74 @@ module Solargraph
123
128
  class << self
124
129
  private
125
130
 
126
- # @param type [RBS::Types::Bases::Base]
131
+ # @param type [RBS::Types::t]
132
+ # @param type_alias_decls [Hash{String => RBS::AST::Declarations::TypeAlias}]
133
+ # @param expanding_aliases [Array<String>] Names of aliases already
134
+ # being expanded in this call chain, to detect recursive aliases
127
135
  # @return [String]
128
- def type_to_tag type
136
+ def type_to_tag type, type_alias_decls = {}, expanding_aliases = []
129
137
  case type
130
138
  when RBS::Types::Optional
131
- "#{type_to_tag(type.type)}, nil"
139
+ # @sg-ignore flow sensitive typing should support case/when
140
+ "#{type_to_tag(type.type, type_alias_decls, expanding_aliases)}, nil"
132
141
  when RBS::Types::Bases::Bool
133
- 'Boolean'
142
+ '::Boolean'
134
143
  when RBS::Types::Tuple
135
- "Array(#{type.types.map { |t| type_to_tag(t) }.join(', ')})"
144
+ # @sg-ignore flow sensitive typing should support case/when
145
+ "::Array(#{type.types.map { |t| type_to_tag(t, type_alias_decls, expanding_aliases) }.join(', ')})"
136
146
  when RBS::Types::Literal
147
+ # @sg-ignore flow sensitive typing should support case/when
137
148
  type.literal.inspect
138
149
  when RBS::Types::Union
139
- type.types.map { |t| type_to_tag(t) }.join(', ')
150
+ # @sg-ignore flow sensitive typing should support case/when
151
+ type.types.map { |t| type_to_tag(t, type_alias_decls, expanding_aliases) }.join(', ')
140
152
  when RBS::Types::Record
141
153
  # @todo Better record support
142
- 'Hash'
154
+ '::Hash'
143
155
  when RBS::Types::Bases::Nil
144
156
  'nil'
145
157
  when RBS::Types::Bases::Void
146
158
  'void'
147
159
  when RBS::Types::Variable
160
+ # @sg-ignore flow sensitive typing should support case/when
148
161
  "#{Solargraph::ComplexType::GENERIC_TAG_NAME}<#{type.name}>"
149
162
  when RBS::Types::Bases::Self, RBS::Types::Bases::Instance
150
163
  'self'
151
164
  when RBS::Types::Bases::Top
152
165
  # `Top` is the most super superclass
153
- 'BasicObject'
166
+ '::BasicObject'
154
167
  when RBS::Types::Intersection
155
- type.types.map { |member| type_to_tag(member) }.join(', ')
168
+ # @sg-ignore flow sensitive typing should support case/when
169
+ type.types.map { |member| type_to_tag(member, type_alias_decls, expanding_aliases) }.join(', ')
156
170
  when RBS::Types::Proc
157
- 'Proc'
158
- when RBS::Types::ClassInstance, RBS::Types::Alias, RBS::Types::Interface
159
- # `Alias` is a top-level type alias, e.g., 'bool' in "type bool = true | false"
160
- # @todo ensure these get resolved after processing all aliases
161
- # @todo handle recursive aliases
162
- #
163
- # `Interface represents a mix-in module which can be considered a
171
+ '::Proc'
172
+ when RBS::Types::Alias
173
+ # Expand so conformance checks see the members, not the name. A
174
+ # generic alias falls back: args are not substituted, so expanding
175
+ # would leak an unbound generic<T>.
176
+ # @sg-ignore flow sensitive typing should support case/when
177
+ alias_name = type.name.to_s
178
+ alias_decl = type_alias_decls[alias_name]
179
+ # @sg-ignore flow sensitive typing should support case/when
180
+ if alias_decl.nil? || expanding_aliases.include?(alias_name) || !type.args.empty?
181
+ # @sg-ignore flow sensitive typing should support case/when
182
+ type_tag(type.name, type.args, type_alias_decls, expanding_aliases)
183
+ else
184
+ # The alias body belongs to the alias's declaration, not the
185
+ # use site, so its names stay rooted even when a core-less
186
+ # environment left them relative.
187
+ expanded = type_to_tag(alias_decl.type, type_alias_decls, expanding_aliases + [alias_name])
188
+ ComplexType.try_parse(expanded).force_rooted.rooted_tags
189
+ end
190
+ when RBS::Types::ClassInstance, RBS::Types::Interface
191
+ # `Interface` represents a mix-in module which can be considered a
164
192
  # subtype of a consumer of it
165
- #
166
- type_tag(type.name, type.args)
193
+ # @sg-ignore flow sensitive typing should support case/when
194
+ type_tag(type.name, type.args, type_alias_decls, expanding_aliases)
167
195
  when RBS::Types::ClassSingleton
168
196
  # e.g., singleton(String)
169
- type_tag(type.name)
197
+ # @sg-ignore flow sensitive typing should support case/when
198
+ type_tag(type.name, [], type_alias_decls, expanding_aliases)
170
199
  when RBS::Types::Bases::Any, RBS::Types::Bases::Bottom
171
200
  # `Bottom`` is used in contexts where nothing will ever return
172
201
  # - e.g., it could be the return type of 'exit()' or 'raise'
@@ -182,23 +211,29 @@ module Solargraph
182
211
 
183
212
  # @param type_name [RBS::TypeName]
184
213
  # @param type_args [Enumerable<RBS::Types::Bases::Base>]
214
+ # @param type_alias_decls [Hash{String => RBS::AST::Declarations::TypeAlias}]
215
+ # @param expanding_aliases [Array<String>]
185
216
  # @return [String]
186
- def type_tag(type_name, type_args = [])
187
- build_type(type_name, type_args).tags
217
+ def type_tag type_name, type_args = [], type_alias_decls = {}, expanding_aliases = []
218
+ build_type(type_name, type_args, type_alias_decls, expanding_aliases).rooted_tags
188
219
  end
189
220
 
190
221
  # @param type_name [RBS::TypeName]
191
222
  # @param type_args [Enumerable<RBS::Types::Bases::Base>]
223
+ # @param type_alias_decls [Hash{String => RBS::AST::Declarations::TypeAlias}]
224
+ # @param expanding_aliases [Array<String>]
192
225
  # @return [ComplexType::UniqueType]
193
- def build_type(type_name, type_args = [])
194
- base = RBS_TO_YARD_TYPE[type_name.relative!.to_s] || type_name.relative!.to_s
195
- params = type_args.map { |a| type_to_tag(a) }.map do |t|
226
+ def build_type type_name, type_args = [], type_alias_decls = {}, expanding_aliases = []
227
+ yard_base = RBS_TO_YARD_TYPE[type_name.relative!.to_s]
228
+ base = yard_base || type_name.relative!.to_s
229
+ rooted = !yard_base.nil? || type_name.absolute?
230
+ params = type_args.map { |a| type_to_tag(a, type_alias_decls, expanding_aliases) }.map do |t|
196
231
  ComplexType.try_parse(t)
197
232
  end
198
233
  if base == 'Hash' && params.length == 2
199
- ComplexType::UniqueType.new(base, [params.first], [params.last], rooted: true, parameters_type: :hash)
234
+ ComplexType::UniqueType.new(base, [params.first], [params.last], rooted: rooted, parameters_type: :hash)
200
235
  else
201
- ComplexType::UniqueType.new(base, [], params.reject(&:undefined?), rooted: true, parameters_type: :list)
236
+ ComplexType::UniqueType.new(base, [], params.reject(&:undefined?), rooted: rooted, parameters_type: :list)
202
237
  end
203
238
  end
204
239
  end
@@ -122,7 +122,7 @@ module Solargraph
122
122
  source: spec.source.to_s,
123
123
  version: spec.version,
124
124
  require_paths: spec.require_paths,
125
- dependencies: spec.dependencies.map(&:name)
125
+ dependencies: spec.runtime_dependencies.map(&:name)
126
126
  }
127
127
  end
128
128
  groups = Bundler.definition.groups.to_h do |group|
@@ -55,20 +55,39 @@ module Solargraph
55
55
  # chain.rb#maybe_nil will add the nil type later, we just
56
56
  # need to worry about the not-nil case
57
57
 
58
- # @sg-ignore Need to handle duck-typed method calls on union types
59
58
  binder = binder.without_nil if nullable?
60
- # @sg-ignore Need to handle duck-typed method calls on union types
61
- pin_groups = binder.each_unique_type.map do |context|
62
- ns_tag = context.namespace == '' ? '' : context.namespace_type.tag
63
- stack = api_map.get_method_stack(ns_tag, word, scope: context.scope)
64
- [stack.first].compact
59
+ # Resolve one unique type at a time, so a `self` return type narrows to the one that supplied the pin.
60
+ # @type [::Array<Pin::Base>]
61
+ resolved = []
62
+ unresolved_type = false
63
+ binder.each_unique_type do |context|
64
+ if context.duck_type? && context.name[1..] == word
65
+ # explicit: false skips arity checking; the duck type
66
+ # only tells us the method exists, not its signature
67
+ pin = Pin::DuckMethod.new(name: word, source: :chain, explicit: false)
68
+ else
69
+ ns_tag = context.namespace == '' ? '' : context.namespace_type.tag
70
+ stack = api_map.get_method_stack(ns_tag, word, scope: context.scope)
71
+ pin = stack.first
72
+ end
73
+ if pin.nil?
74
+ unresolved_type = true
75
+ next
76
+ end
77
+ # Bind name_pin to this type alone, so #inferred_pins (incl. Class#new) cannot see the others.
78
+ type_name_pin = Pin::ProxyType.anonymous(name_pin.context,
79
+ closure: name_pin.closure,
80
+ gates: name_pin.gates,
81
+ binder: context,
82
+ source: :chain)
83
+ resolved.concat inferred_pins([pin], api_map, type_name_pin, locals)
65
84
  end
66
- pin_groups = [] if !api_map.loose_unions && pin_groups.any?(&:empty?)
67
- # Several union members can end up with the same pin and return type.
68
- # @sg-ignore Array#flatten returns a bare Array
69
- pins = pin_groups.flatten.uniq { |p| [p.path, p.return_type.rooted_tags] }
70
- return [] if pins.empty?
71
- inferred_pins(pins, api_map, name_pin, locals)
85
+ return [] if unresolved_type && !api_map.loose_unions
86
+ return [] if resolved.empty?
87
+ # Accumulating every type's result treats the binder as a union, which is the only
88
+ # multi-type binder ComplexType builds; dedup on rooted_tags rather than #tag, which
89
+ # reports only the first member and so collapses two distinct unions.
90
+ resolved.uniq { |pin| [pin.path, pin.return_type.rooted_tags] }
72
91
  end
73
92
 
74
93
  private
@@ -166,7 +185,7 @@ module Solargraph
166
185
 
167
186
  # @param pins [::Enumerable<Pin::Base>]
168
187
  # @param api_map [ApiMap]
169
- # @param name_pin [Pin::Base]
188
+ # @param name_pin [Pin::Base] its #binder resolves `self` in the declarations of `pins`
170
189
  # @param locals [::Array<Solargraph::Pin::LocalVariable, Solargraph::Pin::Parameter>]
171
190
  # @return [::Array<Pin::Base>]
172
191
  def inferred_pins pins, api_map, name_pin, locals
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solargraph
4
+ class Source
5
+ class NodeMap
6
+ # @param node [Parser::AST::Node]
7
+ def initialize node
8
+ @node = node
9
+ end
10
+
11
+ def find_by_type type
12
+ node_map[type] || []
13
+ end
14
+
15
+ private
16
+
17
+ def node_map
18
+ @node_map ||= flatten(@node).to_set.classify(&:type)
19
+ end
20
+
21
+ def flatten from
22
+ return [] unless from.is_a?(::Parser::AST::Node)
23
+ [from].concat(from.children.flat_map { |child| flatten child })
24
+ end
25
+ end
26
+ end
27
+ end
@@ -12,6 +12,7 @@ module Solargraph
12
12
  autoload :Cursor, 'solargraph/source/cursor'
13
13
  autoload :Chain, 'solargraph/source/chain'
14
14
  autoload :SourceChainer, 'solargraph/source/source_chainer'
15
+ autoload :NodeMap, 'solargraph/source/node_map'
15
16
 
16
17
  include EncodingFixes
17
18
 
@@ -213,6 +214,21 @@ module Solargraph
213
214
  end
214
215
  end
215
216
 
217
+ # The RBS type in an rbs-inline type assertion, i.e., a `#: TYPE`
218
+ # comment that trails the node on its last line.
219
+ #
220
+ # @example
221
+ # x = [] #: Array[String]
222
+ #
223
+ # @param node [AST::Node]
224
+ # @return [String, nil]
225
+ def type_assertion_for node
226
+ rng = Range.from_node(node)
227
+ return nil if rng.nil?
228
+ rest = code_lines[rng.ending.line]&.[](rng.ending.column..)
229
+ rest&.[](/\A\s*#:(.*)$/, 1)&.strip
230
+ end
231
+
216
232
  # A location representing the file in its entirety.
217
233
  #
218
234
  # @return [Location]
@@ -457,6 +473,7 @@ module Solargraph
457
473
  # @return [String]
458
474
  def code= val
459
475
  @code_lines = nil
476
+ @node_map = nil
460
477
  @finalized = false
461
478
  @code = val
462
479
  end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Solargraph
4
+ class SourceMap
5
+ class Call
6
+ attr_reader :source_map, :chain, :closure
7
+
8
+ # @param source_map [SourceMap]
9
+ # @param chain [Source::Chain]
10
+ # @param closure [Pin::Closure]
11
+ def initialize source_map, chain, closure
12
+ @source_map = source_map
13
+ @chain = chain
14
+ @closure = closure
15
+ end
16
+ end
17
+ end
18
+ end
@@ -12,7 +12,7 @@ module Solargraph
12
12
 
13
13
  private_class_method :new
14
14
 
15
- DIRECTIVE_REGEXP = /(@!method|@!attribute|@!visibility|@!scope|@!domain|@!macro|@!parse|@!override)/
15
+ DIRECTIVE_REGEXP = /(@!method|@method|@!attribute|@!visibility|@!scope|@!domain|@!macro|@!parse|@!override)/
16
16
 
17
17
  # Generate the data.
18
18
  #
@@ -12,6 +12,7 @@ module Solargraph
12
12
  autoload :Clip, 'solargraph/source_map/clip'
13
13
  autoload :Completion, 'solargraph/source_map/completion'
14
14
  autoload :Data, 'solargraph/source_map/data'
15
+ autoload :Call, 'solargraph/source_map/call'
15
16
 
16
17
  # @return [Source]
17
18
  attr_reader :source
@@ -82,6 +83,16 @@ module Solargraph
82
83
  pins_by_class(Pin::Reference::Require)
83
84
  end
84
85
 
86
+ def calls *method_names
87
+ symbolized = method_names.map(&:to_sym)
88
+ node_map.find_by_type(:send).select { |node| node.children[0].nil? && symbolized.include?(node.children[1].to_sym) }
89
+ .map { |node| Call.new(self, Parser.chain(node), locate_namespace_pin(node.loc.line, node.loc.column)) }
90
+ end
91
+
92
+ def node_map
93
+ @node_map ||= Source::NodeMap.new(source.node)
94
+ end
95
+
85
96
  # @return [Environ]
86
97
  def conventions_environ
87
98
  @conventions_environ ||= Environ.new
@@ -128,6 +139,10 @@ module Solargraph
128
139
  _locate_pin line, character, Pin::Namespace, Pin::Method
129
140
  end
130
141
 
142
+ def locate_namespace_pin line, character
143
+ _locate_pin line, character, Pin::Namespace, Pin::Singleton
144
+ end
145
+
131
146
  # @param line [Integer]
132
147
  # @param character [Integer]
133
148
  # @return [Pin::Closure]
@@ -197,7 +212,7 @@ module Solargraph
197
212
 
198
213
  private
199
214
 
200
- # @return [Hash{Class<Pin::Base> => Array<Pin::Base>}]
215
+ # @return [Hash{Class, Pin::Base => Array<Pin::Base>}]
201
216
  def pin_class_hash
202
217
  # @todo Need to support generic resolution in classify and transform_values
203
218
  @pin_class_hash ||= pins.to_set.classify(&:class).transform_values(&:to_a)
@@ -226,9 +241,8 @@ module Solargraph
226
241
  position = Position.new(line, character)
227
242
  found = nil
228
243
  pins.each do |pin|
229
- # @todo Attribute pins should not be treated like closures, but
230
- # there's probably a better way to handle it
231
- next if pin.is_a?(Pin::Method) && pin.attribute?
244
+ # A bodyless method pin covers its declaring statement's range but isn't a closure.
245
+ next if pin.is_a?(Pin::Method) && !pin.body?
232
246
  found = pin if (klasses.empty? || klasses.any? do |kls|
233
247
  pin.is_a?(kls)
234
248
  # @sg-ignore Need to add nil check here
@@ -75,11 +75,11 @@ module Solargraph
75
75
  # @todo 22: Translate to something flow sensitive typing understands
76
76
  # @todo 3: Need a downcast here
77
77
  #
78
- # flow sensitive typing could handle (97):
78
+ # flow sensitive typing could handle (99):
79
79
  #
80
80
  # @todo 36: flow sensitive typing needs to handle attrs
81
81
  # @todo 29: flow sensitive typing should be able to handle redefinition
82
- # @todo 19: flow sensitive typing needs to narrow down type with an if is_a? check
82
+ # @todo 21: flow sensitive typing needs to narrow down type with an if is_a? check
83
83
  # @todo 13: Need to validate config
84
84
  # @todo 8: flow sensitive typing should support .class == .class
85
85
  # @todo 6: need boolish support for ? methods
@@ -141,6 +141,8 @@ module Solargraph
141
141
  # @return [Array<Problem>]
142
142
  def method_return_type_problems_for pin
143
143
  return [] if pin.is_a?(Pin::MethodAlias)
144
+ # An unresolvable DelegatedMethod's type comes from its @!method directive, not a @return tag.
145
+ return [] if pin.is_a?(Pin::DelegatedMethod) && !pin.resolvable?(api_map)
144
146
  result = []
145
147
  declared = pin.typify(api_map).self_to_type(pin.full_context).qualify(api_map, *pin.gates)
146
148
  if declared.undefined?
@@ -219,7 +221,7 @@ module Solargraph
219
221
  params = param_details_from_stack(sig, stack)
220
222
  if rules.require_type_tags?
221
223
  sig.parameters.each do |par|
222
- break if %i[restarg kwrestarg blockarg].include?(par.decl)
224
+ break if %i[restarg kwrestarg blockarg forward_args].include?(par.decl)
223
225
  unless params[par.name]
224
226
  if pin.attribute?
225
227
  inferred = pin.probe(api_map).self_to_type(pin.full_context)
@@ -388,6 +390,7 @@ module Solargraph
388
390
  return [] unless last_base_link.is_a?(Solargraph::Source::Chain::Call)
389
391
 
390
392
  arguments = last_base_link.arguments
393
+ return [] if any_forwarded_args?(arguments.map(&:node))
391
394
 
392
395
  pins = base.define(api_map, closure_pin, locals)
393
396
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Solargraph
4
- VERSION = ENV.fetch('SOLARGRAPH_FORCE_VERSION', '0.61.0')
4
+ VERSION = ENV.fetch('SOLARGRAPH_FORCE_VERSION', '0.62.0')
5
5
  end
@@ -11,6 +11,17 @@ module Solargraph
11
11
  # its content.
12
12
  VALUE_DIRECTIVES = %w[parse scope].freeze
13
13
 
14
+ # Directives that define an object capable of owning @overload tags.
15
+ OVERLOAD_OWNERS = %w[method attribute parse].freeze
16
+
17
+ # The directive used to generate a pin for the @overload tags a macro
18
+ # declares without an owning @!method directive. Its arguments are
19
+ # represented by (...) so that any of the overloads remains valid.
20
+ OVERLOAD_METHOD_DIRECTIVE = '@!method $1(...)'
21
+
22
+ # An @overload tag that starts a line of a macro's text.
23
+ OVERLOAD_TAG_LINE = /^\s*@overload(\s|$)/
24
+
14
25
  class << self
15
26
  # @param directive [YARD::Tags::Directive]
16
27
  # @param method_pin [Pin::Method]
@@ -123,9 +134,17 @@ module Solargraph
123
134
  code = source_map.source.code_for(chain.node)
124
135
  expanded_comment = macro_object.expand([name, *values], code)
125
136
  .gsub(/\n(?!@!|\s)/, "\n ")
126
- directives = Solargraph::Source.parse_docstring(expanded_comment).directives.select do |directive|
137
+ parsed_docstring = Solargraph::Source.parse_docstring(expanded_comment)
138
+ directives = parsed_docstring.directives.select do |directive|
127
139
  PROCESSABLE_DIRECTIVES.include?(directive.tag.tag_name)
128
140
  end
141
+ # A macro can document the methods it generates with bare @overload
142
+ # tags instead of wrapping them in a @!method directive. Those overloads
143
+ # have no pin to attach to and would otherwise be discarded, so
144
+ # generate the pin they describe.
145
+ if declares_overloads? && directives.none? { |directive| OVERLOAD_OWNERS.include?(directive.tag.tag_name) }
146
+ directives += generate_overload_directives([name, *values], code)
147
+ end
129
148
  directives.each do |directive|
130
149
  next if VALUE_DIRECTIVES.include? directive.tag.tag_name
131
150
 
@@ -137,6 +156,37 @@ module Solargraph
137
156
  end
138
157
  directives
139
158
  end
159
+
160
+ # True if the macro declares @overload tags. The tags are read from the
161
+ # macro's text and not from its parsed docstring because the indentation
162
+ # a macro's content ends up with can nest the tags inside other objects.
163
+ #
164
+ # @return [Boolean]
165
+ def declares_overloads?
166
+ text.match?(OVERLOAD_TAG_LINE)
167
+ end
168
+
169
+ # Generates the @!method directive for a macro's bare @overload tags. The
170
+ # pin takes the name the macro generates, that is, the pin a
171
+ # @!method $1(...) directive would produce, and keeps the overloads in
172
+ # its comments.
173
+ #
174
+ # The macro's unprocessed text is used here and not its macro data. The
175
+ # data is re-indented for @! directives, which flattens the nesting that
176
+ # keeps a @param attached to the @overload it describes.
177
+ #
178
+ # @param call_params [Array<String>]
179
+ # @param code [String]
180
+ # @return [Array<YARD::Tags::Directive>]
181
+ def generate_overload_directives call_params, code
182
+ body = text.each_line.map { |line| " #{line}" }.join
183
+ expanded = YARD::CodeObjects::MacroObject.expand(
184
+ "#{OVERLOAD_METHOD_DIRECTIVE}\n#{body}", call_params, code
185
+ )
186
+ Solargraph::Source.parse_docstring(expanded).directives.select do |directive|
187
+ directive.tag.tag_name == 'method'
188
+ end
189
+ end
140
190
  end
141
191
  end
142
192
  end
data/lib/solargraph.rb CHANGED
@@ -53,6 +53,8 @@ module Solargraph
53
53
  autoload :RbsMap, 'solargraph/rbs_map'
54
54
  autoload :CacheDir, 'solargraph/cache_dir'
55
55
  autoload :RbsTranslator, 'solargraph/rbs_translator'
56
+ autoload :RbsCollection, 'solargraph/rbs_collection'
57
+ autoload :Plugin, 'solargraph/plugin'
56
58
 
57
59
  dir = File.dirname(__FILE__)
58
60
  VIEWS_PATH = File.join(dir, 'solargraph', 'views')