Skip to content
Merged
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
29 changes: 21 additions & 8 deletions lib/steep/type_construction.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4487,7 +4487,7 @@ def to_instance_type(type, args: nil)
def try_tuple_type!(node, hint: nil)
if node.type == :array
if hint.nil? || hint.is_a?(AST::Types::Tuple)
node_range = node.loc.expression.yield_self {|l| l.begin_pos..l.end_pos }
node_range = node.loc.expression.to_range

typing.new_child(node_range) do |child_typing|
if pair = with_new_typing(child_typing).try_tuple_type(node, hint)
Expand All @@ -4507,15 +4507,28 @@ def try_tuple_type(array_node, hint)
element_types = [] #: Array[AST::Types::t]

array_node.children.each_with_index do |child, index|
return if child.type == :splat

child_hint =
if hint
hint.types[index]
if child.type == :splat
type, constr = constr.synthesize(child.children[0])
typing.add_typing(child, type, nil)
if converted_type = try_convert(type, :to_a)
if converted_type.is_a?(AST::Types::Tuple)
element_types.push(*converted_type.types)
else
# The converted_type may be an array, which cannot be used to construct a tuple type
return
end
else
element_types << type
end
else
child_hint =
if hint
hint.types[index]
end

type, constr = constr.synthesize(child, hint: child_hint)
element_types << type
type, constr = constr.synthesize(child, hint: child_hint)
element_types << type
end
end

constr.add_typing(array_node, type: AST::Types::Tuple.new(types: element_types))
Expand Down
55 changes: 55 additions & 0 deletions test/type_construction_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10490,4 +10490,59 @@ def foo
end
end
end

def test_masgn__splat_rhs1
with_checker(<<~RBS) do |checker|
class WithToA
def to_a: () -> [Integer, String, bool]
end
RBS
source = parse_ruby(<<~RUBY)
a, b, c = *WithToA.new
RUBY

with_standard_construction(checker, source) do |construction, typing|
type, _, context = construction.synthesize(source.node)
assert_no_error(typing)
assert_equal parse_type("::Integer"), context.type_env[:a]
assert_equal parse_type("::String"), context.type_env[:b]
assert_equal parse_type("bool"), context.type_env[:c]
end
end
end

def test_masgn__splat_rhs2
with_checker(<<~RBS) do |checker|
RBS
source = parse_ruby(<<~RUBY)
a, b = *123
RUBY

with_standard_construction(checker, source) do |construction, typing|
type, _, context = construction.synthesize(source.node)
assert_no_error(typing)
assert_equal parse_type("::Integer"), context.type_env[:a]
assert_equal parse_type("nil"), context.type_env[:b]
end
end
end

def test_masgn__splat_rhs3
with_checker(<<~RBS) do |checker|
class WithToA
def to_a: () -> Array[Integer]
end
RBS
source = parse_ruby(<<~RUBY)
a, b = *WithToA.new
RUBY

with_standard_construction(checker, source) do |construction, typing|
type, _, context = construction.synthesize(source.node)
assert_no_error(typing)
assert_equal parse_type("::Integer?"), context.type_env[:a]
assert_equal parse_type("::Integer?"), context.type_env[:b]
end
end
end
end