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
11 changes: 7 additions & 4 deletions lib/rbi/model.rb
Original file line number Diff line number Diff line change
Expand Up @@ -269,13 +269,16 @@ def fully_qualified_name

class Const < NodeWithComments
#: String
attr_reader :name, :value
attr_reader :name

#: (String name, String value, ?loc: Loc?, ?comments: Array[Comment]) ?{ (Const node) -> void } -> void
def initialize(name, value, loc: nil, comments: [], &block)
#: (String | Type)?
attr_reader :type

#: (String name, (String | Type)? type, ?loc: Loc?, ?comments: Array[Comment]) ?{ (Const node) -> void } -> void
def initialize(name, type: nil, loc: nil, comments: [], &block)
super(loc: loc, comments: comments)
@name = name
@value = value
@type = type
block&.call(self)
end

Expand Down
135 changes: 40 additions & 95 deletions lib/rbi/parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -138,17 +138,6 @@ def node_string(node)
def node_string!(node)
T.must(node_string(node))
end

#: (Prism::Node node) -> Prism::Location
def adjust_prism_location_for_heredoc(node)
visitor = HeredocLocationVisitor.new(
node.location.send(:source),
node.location.start_offset,
node.location.end_offset,
)
visitor.visit(node)
visitor.location
end
end

class TreeBuilder < Visitor
Expand Down Expand Up @@ -228,6 +217,18 @@ def visit_constant_assign(node)

current_scope << if struct
struct
elsif type_variable_definition?(node.value)
TypeMember.new(
case node
when Prism::ConstantWriteNode
node.name.to_s
when Prism::ConstantPathWriteNode
node_string!(node.target)
end,
node.value.slice,
loc: node_loc(node),
comments: node_comments(node),
)
elsif t_enum_value?(node)
TEnumValue.new(
case node
Expand All @@ -240,39 +241,17 @@ def visit_constant_assign(node)
comments: node_comments(node),
)
else
adjusted_node_location = adjust_prism_location_for_heredoc(node)

adjusted_value_location = Prism::Location.new(
node.value.location.send(:source),
node.value.location.start_offset,
adjusted_node_location.end_offset - node.value.location.start_offset,
Const.new(
case node
when Prism::ConstantWriteNode
node.name.to_s
when Prism::ConstantPathWriteNode
node_string!(node.target)
end,
type: parse_t_let(node.value),
loc: node_loc(node),
comments: node_comments(node),
)

if type_variable_definition?(node.value)
TypeMember.new(
case node
when Prism::ConstantWriteNode
node.name.to_s
when Prism::ConstantPathWriteNode
node_string!(node.target)
end,
adjusted_value_location.slice,
loc: Loc.from_prism(@file, adjusted_node_location),
comments: node_comments(node),
)
else
Const.new(
case node
when Prism::ConstantWriteNode
node.name.to_s
when Prism::ConstantPathWriteNode
node_string!(node.target)
end,
adjusted_value_location.slice,
loc: Loc.from_prism(@file, adjusted_node_location),
comments: node_comments(node),
)
end
end
end

Expand Down Expand Up @@ -772,6 +751,24 @@ def parse_struct(node)
struct
end

# Parse a node as a `T.let(x, X)` and extract the type
#
# Returns `nil` is the node is not a `T.let` call.
#: (Prism::Node?) -> String?
def parse_t_let(node)
return unless node

return unless node.is_a?(Prism::CallNode)
return unless node.name == :let
return unless node.receiver&.slice =~ /^(::)?T$/

arguments = node.arguments&.arguments
return unless arguments
return unless arguments.size == 2

arguments.fetch(1, nil)&.slice
end

#: (Prism::CallNode send) -> void
def parse_tstruct_field(send)
args = send.arguments
Expand Down Expand Up @@ -948,57 +945,5 @@ def visit_assoc_node(node)
)
end
end

class HeredocLocationVisitor < Prism::Visitor
#: (Prism::Source source, Integer begin_offset, Integer end_offset) -> void
def initialize(source, begin_offset, end_offset)
super()
@source = source
@begin_offset = begin_offset
@end_offset = end_offset
@offset_last_newline = false #: bool
end

#: (Prism::StringNode node) -> void
def visit_string_node(node)
return unless node.heredoc?

closing_loc = node.closing_loc
return unless closing_loc

handle_string_node(node)
end

#: (Prism::InterpolatedStringNode node) -> void
def visit_interpolated_string_node(node)
return super unless node.heredoc?

closing_loc = node.closing_loc
return super unless closing_loc

handle_string_node(node)
end

#: -> Prism::Location
def location
Prism::Location.new(
@source,
@begin_offset,
@end_offset - @begin_offset - (@offset_last_newline ? 1 : 0),
)
end

private

#: (Prism::StringNode | Prism::InterpolatedStringNode node) -> void
def handle_string_node(node)
closing_loc = T.must(node.closing_loc)

if closing_loc.end_offset > @end_offset
@end_offset = closing_loc.end_offset
@offset_last_newline = true if node.closing_loc&.slice&.end_with?("\n")
end
end
end
end
end
14 changes: 6 additions & 8 deletions lib/rbi/printer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,12 @@ def visit_const(node)
print_loc(node)
visit_all(node.comments)

printl("#{node.name} = #{node.value}")
type = node.type
if type
printl("#{node.name} = T.let(T.unsafe(nil), #{type})")
else
printl("#{node.name} = T.let(T.unsafe(nil), T.untyped)")
end
end

# @override
Expand Down Expand Up @@ -685,13 +690,6 @@ def oneline?(node)
node.comments.empty? && node.empty?
when Attr
node.comments.empty? && node.sigs.empty?
when Const
return false unless node.comments.empty?

loc = node.loc
return true unless loc

loc.begin_line == loc.end_line
when Method
node.comments.empty? && node.sigs.empty? && node.params.all? { |p| p.comments.empty? }
when Sig
Expand Down
29 changes: 2 additions & 27 deletions lib/rbi/rbs_printer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -229,10 +229,9 @@ def visit_const(node)
print_loc(node)
visit_all(node.comments)

type = parse_t_let(node.value)
type = node.type
if type
type = parse_type(type)
printl("#{node.name}: #{type.rbs_string}")
printl("#{node.name}: #{parse_type(type).rbs_string}")
else
printl("#{node.name}: untyped")
end
Expand Down Expand Up @@ -853,30 +852,6 @@ def parse_type(type)
rescue Type::Error => e
raise Error, "Failed to parse type `#{type}` (#{e.message})"
end

# Parse a string containing a `T.let(x, X)` and extract the type
#
# Returns `nil` is the string is not a `T.let`.
#: (String? code) -> String?
def parse_t_let(code)
return unless code

res = Prism.parse(code)
return unless res.success?

node = res.value
return unless node.is_a?(Prism::ProgramNode)

node = node.statements.body.first
return unless node.is_a?(Prism::CallNode)
return unless node.name == :let
return unless node.receiver&.slice =~ /^(::)?T$/

arguments = node.arguments&.arguments
return unless arguments

arguments.fetch(1, nil)&.slice
end
end

class TypePrinter
Expand Down
2 changes: 1 addition & 1 deletion lib/rbi/rewriters/merge_trees.rb
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,7 @@ class Const
# @override
#: (Node other) -> bool
def compatible_with?(other)
other.is_a?(Const) && name == other.name && value == other.value
other.is_a?(Const) && name == other.name && type == other.type
end
end

Expand Down
14 changes: 7 additions & 7 deletions test/rbi/model_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def test_model_block_builders
tree << SingletonClass.new do |node|
node.comments << Comment.new("comment")
end
tree << Const.new("C", "42") do |node|
tree << Const.new("C") do |node|
node.comments << Comment.new("comment")
end
tree << AttrAccessor.new(:a1) do |node|
Expand Down Expand Up @@ -135,7 +135,7 @@ class Bar; end
class << self; end

# comment
C = 42
C = T.let(T.unsafe(nil), T.untyped)

# comment
attr_accessor :a1
Expand Down Expand Up @@ -270,19 +270,19 @@ def test_model_fully_qualified_names
cls1 << singleton_class
assert_equal("::Foo::Bar::<self>", singleton_class.fully_qualified_name)

const = Const.new("Foo", "42")
const = Const.new("Foo")
assert_equal("::Foo", const.fully_qualified_name)

mod << const
assert_equal("::Foo::Foo", const.fully_qualified_name)

const2 = Const.new("Foo::Bar", "42")
const2 = Const.new("Foo::Bar")
assert_equal("::Foo::Bar", const2.fully_qualified_name)

mod << const2
assert_equal("::Foo::Foo::Bar", const2.fully_qualified_name)

const3 = Const.new("::Foo::Bar", "42")
const3 = Const.new("::Foo::Bar")
assert_equal("::Foo::Bar", const3.fully_qualified_name)

mod << const3
Expand Down Expand Up @@ -344,13 +344,13 @@ def test_model_nodes_as_strings
cls << singleton_class
assert_equal("::Foo::Bar::<self>", singleton_class.to_s)

const = Const.new("Foo", "42")
const = Const.new("Foo")
assert_equal("::Foo", const.to_s)

mod << const
assert_equal("::Foo::Foo", const.to_s)

const2 = Const.new("Foo::Bar", "42")
const2 = Const.new("Foo::Bar")
assert_equal("::Foo::Bar", const2.to_s)

mod << const2
Expand Down
Loading
Loading