Skip to content
Open
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
6 changes: 3 additions & 3 deletions lib/solargraph/api_map.rb
Original file line number Diff line number Diff line change
Expand Up @@ -759,7 +759,7 @@ def workspace
end

# @param fq_reference_tag [String] A fully qualified whose method should be pulled in
# @param namespace_pin [Pin::Base] Namespace pin for the rooted_type
# @param namespace_pin [Pin::Base, nil] Namespace pin for the rooted_type
# parameter - used to pull generics information
# @param type [ComplexType] The type which is having its
# methods supplemented from fq_reference_tag
Expand Down Expand Up @@ -844,7 +844,7 @@ def inner_get_methods rooted_tag, scope, visibility, deep, skip, no_core = false
if deep && scope == :instance
store.get_prepends(fqns).reverse.each do |im|
fqim = store.constants.dereference(im)
result.concat inner_get_methods(fqim, scope, visibility, deep, skip, true) unless fqim.nil?
result.concat inner_get_methods_from_reference(fqim, namespace_pin, rooted_type, scope, visibility, deep, skip, true) unless fqim.nil?
end
end
# Store#get_methods doesn't know about full tags, just
Expand Down Expand Up @@ -876,7 +876,7 @@ def inner_get_methods rooted_tag, scope, visibility, deep, skip, no_core = false
end
store.get_extends(fqns).reverse.each do |em|
fqem = dereference(em)
result.concat inner_get_methods(fqem, :instance, visibility, deep, skip, true) unless fqem.nil?
result.concat inner_get_methods_from_reference(fqem, namespace_pin, rooted_type, :instance, visibility, deep, skip, true) unless fqem.nil?
end
rooted_sc_tag = qualify_superclass(rooted_tag)
unless rooted_sc_tag.nil?
Expand Down
2 changes: 1 addition & 1 deletion lib/solargraph/complex_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ def force_rooted
end
end

# @param definitions [Pin::Namespace, Pin::Method]
# @param definitions [Pin::Namespace, Pin::Method, nil]
# @param context_type [ComplexType]
# @return [ComplexType]
def resolve_generics definitions, context_type
Expand Down
2 changes: 1 addition & 1 deletion lib/solargraph/complex_type/unique_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,7 @@ def resolve_param_generics_from_context generics_to_resolve, context_type, resol
# parameters used in this type, and return a new type if
# possible.
#
# @param definitions [Pin::Namespace, Pin::Method] The module/class/method which uses generic types
# @param definitions [Pin::Namespace, Pin::Method, nil] The module/class/method which uses generic types
# @param context_type [ComplexType] The receiver type
# @return [UniqueType, ComplexType]
def resolve_generics definitions, context_type
Expand Down
39 changes: 39 additions & 0 deletions lib/solargraph/parser/parser_gem/node_methods.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

require 'parser'
require 'ast'
require 'rbs'

# https://github.com/whitequark/parser
# rubocop:disable Metrics/ModuleLength
Expand Down Expand Up @@ -81,6 +82,44 @@ def get_node_end_position node
Position.new(node.loc.last_line, node.loc.last_column)
end

# A trailing inline RBS type argument list, e.g. the `#[String]` in
# `include Enumerable #[String]`. The inner group recurses so that a
# nested `Hash[String, Integer]` is not cut short at its first `]`.
TRAILING_TYPE_ARGS = /\A\s*\#(?<outer>\[(?<body>(?:[^\[\]]++|\g<outer>)*)\])/

# Type arguments from an inline RBS annotation directly after +node+,
# as in `class Foo < Array #[String]` or `include Enumerable #[String]`.
# RBS requires no space between the hash mark and the bracket; with one,
# this is an ordinary comment.
#
# @param node [Parser::AST::Node] the node the annotation follows
# @param code [String] source the node was parsed from
# @return [Array<String>]
def trailing_rbs_type_args node, code
pos = get_node_end_position(node)
offset = Position.line_char_to_offset(code, pos.line, pos.character)
eol = code.index("\n", offset) || code.length
match = code[offset...eol].to_s.match(TRAILING_TYPE_ARGS)
return [] unless match

parse_rbs_type_args match[:body].to_s
end

# Parse an RBS type argument list by wrapping it in a throwaway
# generic, which lets RBS split the arguments and gives each one to
# RbsTranslator for conversion to Solargraph's own type syntax.
#
# @param code [String]
# @return [Array<String>]
def parse_rbs_type_args code
type = RBS::Parser.parse_type("Object[#{code}]")
return [] unless type.is_a?(RBS::Types::ClassInstance)

type.args.map { |arg| RbsTranslator.to_complex_type(arg).rooted_tags }
rescue RBS::ParsingError
[]
end

# @param node [Parser::AST::Node]
# @param signature [String]
#
Expand Down
17 changes: 4 additions & 13 deletions lib/solargraph/parser/parser_gem/node_processors/namespace_node.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,26 +41,17 @@ def process
private

# Type arguments for a generic superclass in inline RBS syntax, e.g.,
# `class Foo < Array #[String]`. Only recognized where RBS defines
# it: directly after the superclass, on the same line, with no space
# between `#` and `[`. Anything else is an ordinary comment.
# `class Foo < Array #[String]`.
#
# @return [String, nil]
def parameters_from_inline_rbs
superclass = node.children[1]
return unless superclass

source = region.source.code
pos = get_node_end_position(superclass)
offset = Position.line_char_to_offset(source, pos.line, pos.character)
eol = source.index("\n", offset) || source.length
match = source[offset...eol].to_s.match(/\A\s*#\[([^\]]*)\]/)
return unless match
args = trailing_rbs_type_args(superclass, region.source.code)
return if args.empty?

code = match[1].strip
return if code.empty?

"<#{code}>"
"<#{args.join(', ')}>"
end

def type_from_node
Expand Down
18 changes: 18 additions & 0 deletions lib/solargraph/parser/parser_gem/node_processors/send_node.rb
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ def process_include
location: get_node_location(i),
closure: cp,
name: unpack_name(i),
generic_values: mixin_generic_values,
source: :parser
)
end
Expand All @@ -153,6 +154,7 @@ def process_prepend
location: get_node_location(i),
closure: cp,
name: unpack_name(i),
generic_values: mixin_generic_values,
source: :parser
)
end
Expand All @@ -175,12 +177,28 @@ def process_extend
location: loc,
closure: region.closure,
name: unpack_name(i),
generic_values: mixin_generic_values,
source: :parser
)
end
end
end

# Type arguments for a generic module mixed in with inline RBS
# syntax, e.g. `include Enumerable #[String]`. RBS recognizes this
# for one module argument only, so `include A, B` takes none.
#
# @return [Array<String>]
def mixin_generic_values
args = node.children[2..]
return [] unless args && args.length == 1

arg = args.first
return [] unless arg.is_a?(AST::Node)

trailing_rbs_type_args arg, region.source.code
end

# @return [void]
def process_require
return unless node.children[2].is_a?(AST::Node) && node.children[2].type == :str
Expand Down
25 changes: 13 additions & 12 deletions lib/solargraph/rbs_map/conversions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -164,9 +164,9 @@ def fqns type_name
# @return [void]
def convert_self_type_to_pins decl, closure
type = build_type(decl.name, decl.args)
generic_values = type.all_params.map(&:to_s)
generic_values = type.all_params.map(&:rooted_tags)
include_pin = Solargraph::Pin::Reference::Include.new(
name: decl.name.relative!.to_s,
name: type.rooted_name, # reference pins use rooted names
type_location: location_decl_to_pin_location(decl.location),
generic_values: generic_values,
closure: closure,
Expand Down Expand Up @@ -279,8 +279,7 @@ def class_decl_to_pin decl
pins.push class_pin
if decl.super_class
type = build_type(decl.super_class.name, decl.super_class.args)
generic_values = type.all_params.map(&:to_s)
superclass_name = decl.super_class.name.to_s
generic_values = type.all_params.map(&:rooted_tags)
pins.push Solargraph::Pin::Reference::Superclass.new(
type_location: location_decl_to_pin_location(decl.super_class.location),
closure: class_pin,
Expand All @@ -299,7 +298,7 @@ def interface_decl_to_pin decl
class_pin = Solargraph::Pin::Namespace.new(
type: :module,
type_location: location_decl_to_pin_location(decl.location),
name: decl.name.relative!.to_s,
name: fqns(decl.name),
closure: Solargraph::Pin::ROOT_PIN,
comments: decl.comment&.string,
generics: type_parameter_names(decl),
Expand All @@ -318,7 +317,7 @@ def interface_decl_to_pin decl
def module_decl_to_pin decl
module_pin = Solargraph::Pin::Namespace.new(
type: :module,
name: decl.name.relative!.to_s,
name: fqns(decl.name),
type_location: location_decl_to_pin_location(decl.location),
closure: Solargraph::Pin::ROOT_PIN,
comments: decl.comment&.string,
Expand Down Expand Up @@ -701,9 +700,9 @@ def civar_to_pin decl, closure
# @return [void]
def include_to_pin decl, closure
type = build_type(decl.name, decl.args)
generic_values = type.all_params.map(&:to_s)
generic_values = type.all_params.map(&:rooted_tags)
pins.push Solargraph::Pin::Reference::Include.new(
name: decl.name.relative!.to_s,
name: type.rooted_name, # reference pins use rooted names
type_location: location_decl_to_pin_location(decl.location),
generic_values: generic_values,
closure: closure,
Expand All @@ -718,8 +717,9 @@ def prepend_to_pin decl, closure
type = build_type(decl.name, decl.args)
generic_values = type.all_params.map(&:rooted_tags)
pins.push Solargraph::Pin::Reference::Prepend.new(
name: decl.name.relative!.to_s,
name: type.rooted_name, # reference pins use rooted names
type_location: location_decl_to_pin_location(decl.location),
generic_values: generic_values,
closure: closure,
source: :rbs
)
Expand All @@ -732,8 +732,9 @@ def extend_to_pin decl, closure
type = build_type(decl.name, decl.args)
generic_values = type.all_params.map(&:rooted_tags)
pins.push Solargraph::Pin::Reference::Extend.new(
name: decl.name.relative!.to_s,
name: type.rooted_name, # reference pins use rooted names
type_location: location_decl_to_pin_location(decl.location),
generic_values: generic_values,
closure: closure,
source: :rbs
)
Expand Down Expand Up @@ -797,9 +798,9 @@ def add_mixins decl, namespace
# @todo are we handling prepend correctly?
klass = mixin.is_a?(RBS::AST::Members::Include) ? Pin::Reference::Include : Pin::Reference::Extend
type = build_type(mixin.name, mixin.args)
generic_values = type.all_params.map(&:to_s)
generic_values = type.all_params.map(&:rooted_tags)
pins.push klass.new(
name: mixin.name.relative!.to_s,
name: type.rooted_name, # reference pins use rooted names
type_location: location_decl_to_pin_location(mixin.location),
generic_values: generic_values,
closure: namespace,
Expand Down
75 changes: 75 additions & 0 deletions spec/parser/node_processor_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,15 @@ class Foo < Array #[String]
expect(map.pins.last.type.to_s).to eq('Array<String>')
end

it 'translates RBS parameter syntax on a superclass into Solargraph syntax' do
map = Solargraph::SourceMap.load_string(%(
class Foo < Array #[Hash[String, Integer]]
end
), 'test.rb')

expect(map.pins.last.type.to_s).to eq('Array<Hash{String => Integer}>')
end

it 'ignores bracketed comments in the class body' do
map = Solargraph::SourceMap.load_string(%(
class Foo < Array
Expand All @@ -98,4 +107,70 @@ class Foo < Array # [String]

expect(map.pins.last.type.to_s).to eq('Array')
end

# @param code [String]
# @param klass [Class<Solargraph::Pin::Reference>]
# @return [String]
def mixin_type_for code, klass
map = Solargraph::SourceMap.load_string(code, 'test.rb')
map.pins.find { |pin| pin.instance_of?(klass) }.type.to_s
end

it 'parses RBS parameters for included modules' do
expect(mixin_type_for(%(
class Foo
include Bar #[String]
end
), Solargraph::Pin::Reference::Include)).to eq('Bar<String>')
end

it 'parses RBS parameters for prepended modules' do
expect(mixin_type_for(%(
class Foo
prepend Bar #[String]
end
), Solargraph::Pin::Reference::Prepend)).to eq('Bar<String>')
end

it 'parses RBS parameters for extended modules' do
expect(mixin_type_for(%(
class Foo
extend Bar #[String]
end
), Solargraph::Pin::Reference::Extend)).to eq('Bar<String>')
end

it 'translates RBS parameter syntax on mixins into Solargraph syntax' do
expect(mixin_type_for(%(
class Foo
include Bar #[Hash[String, Integer]]
end
), Solargraph::Pin::Reference::Include)).to eq('Bar<Hash{String => Integer}>')
end

it 'ignores a bracketed mixin comment separated from the hash' do
expect(mixin_type_for(%(
class Foo
include Bar # [String]
end
), Solargraph::Pin::Reference::Include)).to eq('Bar')
end

it 'ignores mixin parameters when one call mixes in several modules' do
# RBS rejects this too, as "Mixing multiple modules with one call is
# not supported", since the parameters could apply to either module.
expect(mixin_type_for(%(
class Foo
include Bar, Baz #[String]
end
), Solargraph::Pin::Reference::Include)).to eq('Bar')
end

it 'ignores mixin parameters that RBS cannot parse as a type' do
expect(mixin_type_for(%(
class Foo
include Bar #[def]
end
), Solargraph::Pin::Reference::Include)).to eq('Bar')
end
end
Loading
Loading