Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 14 additions & 7 deletions lib/solargraph/rbs_map/conversions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -552,16 +552,19 @@ def method_def_to_pin decl, closure, context
# @param pin [Pin::Method]
# @return [void]
def method_def_to_sigs decl, pin
# rubocop:disable Style/SafeNavigationChainLength
implicit_nil = decl.overloads.first&.annotations&.map(&:string)&.include?('implicitly-returns-nil') || false
# rubocop:enable Style/SafeNavigationChainLength
# @param overload [RBS::AST::Members::MethodDefinition::Overload]
decl.overloads.map do |overload|
# @sg-ignore Wrong argument type for Solargraph::RbsMap::Conversions#location_decl_to_pin_location:
# location expected RBS::Location, nil, received RBS::Location<:type, :type_params>, RBS::AST::Members::Attribute::loc, nil
type_location = location_decl_to_pin_location(overload.method_type.location)
generics = type_parameter_names(overload.method_type)
signature_parameters, signature_return_type = parts_of_function(overload.method_type, pin)
signature_parameters, signature_return_type = parts_of_function(overload.method_type, pin, implicit_nil)
rbs_block = overload.method_type.block
block = if rbs_block
block_parameters, block_return_type = parts_of_function(rbs_block, pin)
block_parameters, block_return_type = parts_of_function(rbs_block, pin, implicit_nil)
Pin::Signature.new(generics: generics, parameters: block_parameters,
return_type: block_return_type, source: :rbs,
type_location: type_location, closure: pin)
Expand All @@ -588,14 +591,15 @@ def location_decl_to_pin_location location

# @param type [RBS::MethodType, RBS::Types::Block]
# @param pin [Pin::Method]
# @param implicit_nil [Boolean]
# @return [Array(Array<Pin::Parameter>, ComplexType)]
def parts_of_function type, pin
def parts_of_function type, pin, implicit_nil
type_location = pin.type_location
if defined?(RBS::Types::UntypedFunction) && type.type.is_a?(RBS::Types::UntypedFunction)
return [
[Solargraph::Pin::Parameter.new(decl: :restarg, name: 'arg', closure: pin, source: :rbs,
type_location: type_location)],
method_type_to_type(type)
method_type_to_type(type, implicit_nil)
]
end

Expand Down Expand Up @@ -659,7 +663,7 @@ def parts_of_function type, pin
source: :rbs, type_location: type_location)
end

return_type = method_type_to_type(type)
return_type = method_type_to_type(type, implicit_nil)
[parameters, return_type]
end

Expand Down Expand Up @@ -842,9 +846,12 @@ def alias_to_pin decl, closure
end

# @param type [RBS::MethodType, RBS::Types::Block]
# @param implicit_nil [Boolean]
# @return [ComplexType, ComplexType::UniqueType]
def method_type_to_type type
other_type_to_type type.type.return_type
def method_type_to_type type, implicit_nil
tag = other_type_to_type type.type.return_type
return ComplexType.parse("#{tag}, nil") if tag && implicit_nil
tag
end

# @param type [RBS::Types::Bases::Base,Object] RBS type object.
Expand Down
2 changes: 2 additions & 0 deletions lib/solargraph/source.rb
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ def from_to l1, c1, l2, c2
# @param line [Integer]
# @param column [Integer]
# @return [AST::Node]
# @sg-ignore assume return value is not nil
def node_at line, column
tree_at(line, column).first
end
Expand Down Expand Up @@ -277,6 +278,7 @@ def associated_comments
# @return [Integer]
def first_not_empty_from line
cursor = line
# @sg-ignore while condition ensures code_lines[cursor] exists
cursor += 1 while cursor < code_lines.length && code_lines[cursor].strip.empty?
cursor = line if cursor > code_lines.length - 1
cursor
Expand Down
7 changes: 4 additions & 3 deletions lib/solargraph/source_map.rb
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ def cursor_at position
end

# @param path [String]
# @return [Pin::Base]
# @return [Pin::Base, nil]
def first_pin path
pins.select { |p| p.path == path }.first
end
Expand All @@ -123,14 +123,14 @@ def locate_pins location

# @param line [Integer]
# @param character [Integer]
# @return [Pin::Method,Pin::Namespace]
# @return [Pin::Method, Pin::Namespace, nil]
def locate_named_path_pin line, character
_locate_pin line, character, Pin::Namespace, Pin::Method
end

# @param line [Integer]
# @param character [Integer]
# @return [Pin::Closure]
# @return [Pin::Closure, nil]
def locate_closure_pin line, character
_locate_pin line, character, Pin::Closure
end
Expand All @@ -149,6 +149,7 @@ def references name
def locals_at location
return [] if location.filename != filename
closure = locate_closure_pin(location.range.start.line, location.range.start.character)
# @sg-ignore assume closure exists
locals.select { |pin| pin.visible_at?(closure, location) }
end

Expand Down
18 changes: 13 additions & 5 deletions lib/solargraph/source_map/clip.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def types
# @return [Completion]
def complete
return package_completions([]) if !source_map.source.parsed? || cursor.string?
if cursor.chain.literal? && cursor.chain.links.last.word == '<Symbol>'
if cursor.chain.literal? && cursor.chain.links.last&.word == '<Symbol>'
return package_completions(api_map.get_symbols)
end
return Completion.new([], cursor.range) if cursor.chain.literal?
Expand Down Expand Up @@ -118,12 +118,12 @@ def location

# @return [Solargraph::Pin::Closure]
def closure
@closure ||= source_map.locate_closure_pin(cursor.node_position.line, cursor.node_position.character)
@closure ||= source_map.locate_closure_pin(cursor.node_position.line, cursor.node_position.character) || Pin::ROOT_PIN
end

# The context at the current position.
#
# @return [Pin::Base]
# @return [Pin::Base, nil]
def context_pin
@context_pin ||= source_map.locate_named_path_pin(cursor.node_position.line, cursor.node_position.character)
end
Expand All @@ -141,7 +141,7 @@ def complete_keyword_parameters
next unless param.keyword?
result.push Pin::KeywordParam.new(pin.location, "#{param.name}:")
end
next unless !pin.parameters.empty? && pin.parameters.last.kwrestarg?
next unless !pin.parameters.empty? && pin.parameters.last&.kwrestarg?
pin.docstring.tags(:param).each do |tag|
next if done.include?(tag.name)
done.push tag.name
Expand Down Expand Up @@ -193,9 +193,12 @@ def code_complete
result = []
result.concat complete_keyword_parameters
if cursor.chain.constant? || cursor.start_of_constant?
# @sg-ignore assume no nils in method calls
full = cursor.chain.links.first.word
type = if cursor.chain.undefined?
# @sg-ignore assume context_pin exists
cursor.chain.base.infer(api_map, context_pin, locals)
# @sg-ignore assume full exists
elsif full.include?('::') && cursor.chain.links.length == 1
# @sg-ignore Need to add nil check here
ComplexType.try_parse(full.split('::')[0..-2].join('::'))
Expand All @@ -205,20 +208,24 @@ def code_complete
ComplexType::UNDEFINED
end
if type.undefined?
# @sg-ignore assume full exists
if full.include?('::')
result.concat api_map.get_constants(full, *gates)
else
result.concat api_map.get_constants('', cursor.start_of_constant? ? '' : context_pin.full_context.namespace, *gates) # .select { |pin| pin.name.start_with?(full) }
# @sg-ignore assume context_pin exists
result.concat api_map.get_constants('', cursor.start_of_constant? ? '' : context_pin.full_context.namespace, *gates)
end
else
result.concat api_map.get_constants(type.namespace,
# @sg-ignore assume context_pin exists
cursor.start_of_constant? ? '' : context_pin.full_context.namespace, *gates)
end
else
type = cursor.chain.base.infer(api_map, closure, locals)
result.concat api_map.get_complex_type_methods(type, closure.binder.namespace, cursor.chain.links.length == 1)
if cursor.chain.links.length == 1
if cursor.word.start_with?('@@')
# @sg-ignore assume context_pin exists
return package_completions(api_map.get_class_variable_pins(context_pin.full_context.namespace))
elsif cursor.word.start_with?('@')
return package_completions(api_map.get_instance_variable_pins(closure.full_context.namespace,
Expand All @@ -228,6 +235,7 @@ def code_complete
end
result.concat locals
result.concat file_global_methods unless closure.binder.namespace.empty?
# @sg-ignore assume context_pin exists
result.concat api_map.get_constants(context_pin.context.namespace, *gates)
result.concat api_map.get_methods(closure.binder.namespace, scope: closure.binder.scope,
visibility: %i[public private protected])
Expand Down
8 changes: 4 additions & 4 deletions lib/solargraph/source_map/mapper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
end

# @param position [Solargraph::Position]
# @return [Solargraph::Pin::Closure]
# @return [Solargraph::Pin::Closure, nil]
def closure_at position
# @sg-ignore Need to add nil check here
pins.select { |pin| pin.is_a?(Pin::Closure) and pin.location.range.contain?(position) }.last
Expand Down Expand Up @@ -107,7 +107,7 @@
# @param comment_position [Position]
# @param directive [YARD::Tags::Directive]
# @return [void]
def process_directive source_position, comment_position, directive

Check warning on line 110 in lib/solargraph/source_map/mapper.rb

View workflow job for this annotation

GitHub Actions / rubocop

[rubocop] reported by reviewdog 🐶 Perceived complexity for `process_directive` is too high. [41/40] Raw Output: lib/solargraph/source_map/mapper.rb:110:7: C: Metrics/PerceivedComplexity: Perceived complexity for `process_directive` is too high. [41/40]

Check warning on line 110 in lib/solargraph/source_map/mapper.rb

View workflow job for this annotation

GitHub Actions / rubocop

[rubocop] reported by reviewdog 🐶 Cyclomatic complexity for `process_directive` is too high. [44/40] Raw Output: lib/solargraph/source_map/mapper.rb:110:7: C: Metrics/CyclomaticComplexity: Cyclomatic complexity for `process_directive` is too high. [44/40]
# @sg-ignore Need to add nil check here
docstring = Solargraph::Source.parse_docstring(directive.tag.text).to_docstring
location = Location.new(@filename, Range.new(comment_position, comment_position))
Expand All @@ -119,7 +119,6 @@
begin
src = Solargraph::Source.load_string("def #{directive.tag.name};end", @source.filename)
region = Parser::Region.new(source: src, closure: namespace)
# @type [Array<Pin::Method>]
method_gen_pins = Parser.process_node(src.node, region).first.select { |pin| pin.is_a?(Pin::Method) }
gen_pin = method_gen_pins.last
return if gen_pin.nil?
Expand Down Expand Up @@ -166,7 +165,8 @@
pins.push method_pin
method_pin.parameters.push Pin::Parameter.new(name: 'value', decl: :arg, closure: pins.last,
source: :source_map)
if pins.last.return_type.defined?
if pins.last&.return_type&.defined?
# @sg-ignore assume pins.last exists
pins.last.docstring.add_tag YARD::Tags::Tag.new(:param, '', pins.last.return_type.to_s.split(', '),
'value')
end
Expand Down Expand Up @@ -201,7 +201,7 @@
region = Parser::Region.new(source: src, closure: ns)
# @todo These pins may need to be marked not explicit
index = @pins.length
loff = if @code.lines[comment_position.line].strip.end_with?('@!parse')
loff = if @code.lines[comment_position.line]&.strip&.end_with?('@!parse')
comment_position.line + 1
else
comment_position.line
Expand Down
33 changes: 19 additions & 14 deletions lib/solargraph/type_checker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -226,9 +226,8 @@
# @param name [String]
# @param data [Hash{Symbol => BasicObject}]
params.each_pair do |name, data|
# @type [ComplexType]
type = data[:qualified]
if type.undefined?
if type&.undefined?
result.push Problem.new(pin.location, "Unresolved type #{data[:tagged]} for #{name} param on #{pin.path}",
pin: pin)
end
Expand Down Expand Up @@ -295,10 +294,12 @@
chain = Solargraph::Parser.chain(const, filename)
# @sg-ignore Need to add nil check here
closure_pin = source_map.locate_closure_pin(rng.start.line, rng.start.column)
# @sg-ignore assume closure_pin exists
closure_pin.rebind(api_map)
# @sg-ignore Need to add nil check here
location = Location.new(filename, rng)
locals = source_map.locals_at(location)
# @sg-ignore assume closure_pin exists
pins = chain.define(api_map, closure_pin, locals)
if pins.empty?
result.push Problem.new(location, "Unresolved constant #{Solargraph::Parser::NodeMethods.unpack_name(const)}")
Expand All @@ -322,8 +323,7 @@
# blocks in the AST include the method call as well, so the
# node returned by #call_nodes_from needs to be backed out
# one closure
# @todo Need to add nil check here
# @todo Should warn on nil deference here
# @sg-ignore assume closure_pin exists
closure_pin = closure_pin.closure
end
# @sg-ignore Need to add nil check here
Expand All @@ -340,7 +340,7 @@
found = nil
# @type [Array<Solargraph::Pin::Base>]
all_found = []
until base.links.first.undefined?
until base.links.first&.undefined?
# @sg-ignore Need to add nil check here
all_found = base.define(api_map, closure_pin, locals)
found = all_found.first
Expand All @@ -353,9 +353,9 @@
# @todo remove the internal_or_core? check at a higher-than-strict level
if (!found || found.is_a?(Pin::BaseVariable) || (closest.defined? && internal_or_core?(found))) && !(closest.generic? || ignored_pins.include?(found))
if closest.defined?
result.push Problem.new(location, "Unresolved call to #{missing.links.last.word} on #{closest}")
result.push Problem.new(location, "Unresolved call to #{missing.links.last&.word} on #{closest}")
else
result.push Problem.new(location, "Unresolved call to #{missing.links.last.word}")
result.push Problem.new(location, "Unresolved call to #{missing.links.last&.word}")
end
@marked_ranges.push rng
end
Expand Down Expand Up @@ -515,7 +515,8 @@
result = []
kwargs = convert_hash(argchain.node)
par = sig.parameters[idx]
# @type [Solargraph::Source::Chain]
return [] unless par # @todo Safeguard for typechecking errors
# @type [Source::Chain]
argchain = kwargs[par.name.to_sym]
if par.decl == :kwrestarg || (par.decl == :optarg && idx == pin.parameters.length - 1 && par.asgn_code == '{}')
result.concat kwrestarg_problems_for(api_map, closure_pin, locals, location, pin, params, kwargs)
Expand All @@ -537,7 +538,7 @@
end
end
end
elsif par.decl == :kwarg
elsif par&.decl == :kwarg
result.push Problem.new(location, "Call to #{pin.path} is missing keyword argument #{par.name}")
end
result
Expand Down Expand Up @@ -633,7 +634,9 @@
next unless param_names.include?(param_name)

param_details[param_name] ||= {}
# @sg-ignore assuming param_details values
param_details[param_name][:tagged] ||= details[:tagged]
# @sg-ignore assuming param_details values
param_details[param_name][:qualified] ||= details[:qualified]
end
end
Expand Down Expand Up @@ -691,14 +694,16 @@
# @sg-ignore flow sensitive typing needs to handle "if foo.nil?"
location = Location.new(filename, Range.from_node(pin.assignment))
locals = source_map.locals_at(location)
# @sg-ignore assume closure_pin exists
type = chain.infer(api_map, closure_pin, locals)
if type.undefined? && !rules.ignore_all_undefined?
base = chain
# @type [Solargraph::Pin::Base, nil]
found = nil
# @type [Array<Solargraph::Pin::Base>]
all_found = []
until base.links.first.undefined?
until base.links.first&.undefined?
# @sg-ignore assume closure_pin exists
all_found = base.define(api_map, closure_pin, locals)
found = all_found.first
break if found
Expand All @@ -721,7 +726,7 @@
return [] if r.empty?
r
end
results.first
results.first || []
end

# @param pin [Pin::Method]
Expand All @@ -743,7 +748,7 @@
if any_splatted_call?(unchecked.map(&:node))
settled_kwargs = parameters.count(&:keyword?)
else
kwargs = convert_hash(unchecked.last.node)
kwargs = convert_hash(unchecked.last&.node)
if parameters.any? { |param| %i[kwarg kwoptarg].include?(param.decl) || param.kwrestarg? }
if kwargs.empty?
add_params += 1
Expand All @@ -755,7 +760,7 @@
kwargs.delete param.name.to_sym
settled_kwargs += 1
elsif param.decl == :kwarg
last_arg_last_link = arguments.last.links.last
last_arg_last_link = arguments.last&.links&.last
return [] if last_arg_last_link.is_a?(Solargraph::Source::Chain::Hash) && last_arg_last_link.splatted?
return [Problem.new(location, "Missing keyword argument #{param.name} to #{pin.path}")]
end
Expand All @@ -780,7 +785,7 @@
end
return [] if arguments.length - req == parameters.select { |p| %i[optarg kwoptarg].include?(p.decl) }.length
return [Problem.new(location, "Too many arguments to #{pin.path}")]
elsif unchecked.length < req - settled_kwargs && (arguments.empty? || (!arguments.last.splat? && !arguments.last.links.last.is_a?(Solargraph::Source::Chain::Hash)))
elsif unchecked.length < req - settled_kwargs && (arguments.empty? || (!arguments.last&.splat? && !arguments.last&.links.last.is_a?(Solargraph::Source::Chain::Hash)))

Check warning on line 788 in lib/solargraph/type_checker.rb

View workflow job for this annotation

GitHub Actions / rubocop

[rubocop] reported by reviewdog 🐶 Do not chain ordinary method call after safe navigation operator. Raw Output: lib/solargraph/type_checker.rb:788:127: W: Lint/SafeNavigationChain: Do not chain ordinary method call after safe navigation operator.
# HACK: Kernel#raise signature is incorrect in Ruby 2.7 core docs.
# See https://github.com/castwide/solargraph/issues/418
unless arguments.empty? && pin.path == 'Kernel#raise'
Expand Down
3 changes: 2 additions & 1 deletion lib/solargraph/workspace.rb
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ def has_file? filename
# Get a source by its filename.
#
# @param filename [String]
# @return [Solargraph::Source]
# @return [Solargraph::Source, nil]
def source filename
source_hash[filename]
end
Expand Down Expand Up @@ -155,6 +155,7 @@ def find_gem name, version = nil, out: nil
# @param updater [Source::Updater]
# @return [void]
def synchronize! updater
# @sg-ignore nil errors in strong checks
source_hash[updater.filename] = source_hash[updater.filename].synchronize(updater)
end

Expand Down
Loading
Loading