Skip to content

Commit

Permalink
Showing 3 changed files with 86 additions and 4 deletions.
15 changes: 15 additions & 0 deletions spec/compiler/type_inference/splat_spec.cr
Original file line number Diff line number Diff line change
@@ -471,6 +471,21 @@ describe "Type inference: splat" do
)) { tuple_of([int32.metaclass, tuple_of([char, string]).metaclass, bool.metaclass]) }
end

it "errors if using two splat indices on restriction" do
assert_error %(
class Foo(*T)
end
def method(x : Foo(A, *B, *C))
{A, B, C}
end
foo = Foo(Int32, Char, String, Bool).new
method(foo)
),
"can't specify more than one splat in restriction"
end

it "matches with splat" do
assert_type(%(
def foo(&block : *{Int32, Int32} -> U)
30 changes: 30 additions & 0 deletions spec/compiler/type_inference/tuple_spec.cr
Original file line number Diff line number Diff line change
@@ -219,4 +219,34 @@ describe "Type inference: tuples" do
Tuple(Nil, Int32).types
)) { nil_type.metaclass }
end

it "matches tuple with splat (#2932)" do
assert_type(%(
def foo(x : Tuple(*T))
T
end
foo({1, 'a'})
)) { tuple_of([int32, char]).metaclass }
end

it "matches tuple with splat (2) (#2932)" do
assert_type(%(
def foo(x : Tuple(A, *B, C))
{A, B, C}
end
foo({1, 'a', true, 1.5})
)) { tuple_of([int32.metaclass, tuple_of([char, bool]).metaclass, float64.metaclass]) }
end

it "errors if using two splat indices on restriction" do
assert_error %(
def foo(x : Tuple(*A, *B))
end
foo({1, 'a'})
),
"can't specify more than one splat in restriction"
end
end
45 changes: 41 additions & 4 deletions src/compiler/crystal/semantic/restrictions.cr
Original file line number Diff line number Diff line change
@@ -494,8 +494,12 @@ module Crystal
end

i = 0
found_splat = false
other.type_vars.each do |type_var|
if type_var.is_a?(Splat)
type_var.raise "can't specify more than one splat in restriction" if found_splat
found_splat = true

count = types.size - (other.type_vars.size - 1)
return nil unless count >= 0

@@ -588,11 +592,44 @@ module Crystal
return super unless generic_class == self.generic_class

generic_class = generic_class.as(TupleType)
return nil unless other.type_vars.size == tuple_types.size

tuple_types.zip(other.type_vars) do |tuple_type, type_var|
restricted = tuple_type.restrict(type_var, context)
return nil unless restricted == tuple_type
# Consider the case of a splat in the type vars
splat_index = other.type_vars.index &.is_a?(Splat)
if splat_index
found_splat = false
i = 0
other.type_vars.each do |type_var|
if type_var.is_a?(Splat)
type_var.raise "can't specify more than one splat in restriction" if found_splat
found_splat = true

count = tuple_types.size - (other.type_vars.size - 1)
return nil unless count >= 0

arg_types = tuple_types[i, count]
arg_types_tuple = context.type_lookup.program.tuple_of(arg_types)

restricted = arg_types_tuple.restrict(type_var.exp, context)
return nil unless restricted == arg_types_tuple

i += count
else
arg_type = tuple_types[i]
restricted = arg_type.restrict(type_var, context)
return unless restricted == arg_type

i += 1
end
end

return self
else
return nil unless other.type_vars.size == tuple_types.size

tuple_types.zip(other.type_vars) do |tuple_type, type_var|
restricted = tuple_type.restrict(type_var, context)
return nil unless restricted == tuple_type
end
end

self

0 comments on commit 7649b33

Please sign in to comment.