Skip to content

Commit

Permalink
Fix restriction of numeral generic argument against non-free variable…
Browse files Browse the repository at this point in the history
… `Path` (#12784)
  • Loading branch information
HertzDevil authored Dec 5, 2022
1 parent 6b19d66 commit 33b1920
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 12 deletions.
30 changes: 27 additions & 3 deletions spec/compiler/semantic/restrictions_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -617,8 +617,7 @@ describe "Restrictions" do
CRYSTAL
end

# TODO: enable in #12784
pending "inserts constant before free variable with same name" do
it "inserts constant before free variable with same name" do
assert_type(<<-CRYSTAL) { tuple_of([char, bool]) }
class Foo(T); end
Expand All @@ -636,7 +635,7 @@ describe "Restrictions" do
CRYSTAL
end

pending "keeps constant before free variable with same name" do
it "keeps constant before free variable with same name" do
assert_type(<<-CRYSTAL) { tuple_of([char, bool]) }
class Foo(T); end
Expand Down Expand Up @@ -1140,6 +1139,31 @@ describe "Restrictions" do
"expected argument #2 to 'foo' to be StaticArray(UInt8, 10), not StaticArray(UInt8, 11)"
end

it "does not treat single path as free variable when given number (1) (#11859)" do
assert_error <<-CR, "expected argument #1 to 'Foo(1)#foo' to be Foo(1), not Foo(2)"
class Foo(T)
def foo(x : Foo(T))
end
end
Foo(1).new.foo(Foo(2).new)
CR
end

it "does not treat single path as free variable when given number (2) (#11859)" do
assert_error <<-CR, "expected argument #1 to 'foo' to be Foo(1), not Foo(2)"
X = 1
class Foo(T)
end
def foo(x : Foo(X))
end
foo(Foo(2).new)
CR
end

it "restricts aliased typedef type (#9474)" do
assert_type(%(
lib A
Expand Down
33 changes: 24 additions & 9 deletions src/compiler/crystal/semantic/restrictions.cr
Original file line number Diff line number Diff line change
Expand Up @@ -705,7 +705,20 @@ module Crystal
end

def restriction_of?(other : Generic, owner, self_free_vars = nil, other_free_vars = nil)
return true if self == other
# The two `Foo(X)`s below are not equal because only one of them is bound
# and the other one is unbound, so we compare the free variables too:
# (`X` is an alias or a numeric constant)
#
# ```
# def foo(x : Foo(X)) forall X
# end
#
# def foo(x : Foo(X))
# end
# ```
#
# See also the todo in `Path#restriction_of?(Path)`
return true if self == other && self_free_vars == other_free_vars
return false unless name == other.name && type_vars.size == other.type_vars.size

# Special case: NamedTuple against NamedTuple
Expand Down Expand Up @@ -1242,15 +1255,17 @@ module Crystal
end
when Path
if first_name = other_type_var.single_name?
# If the free variable is already set to another
# number, there's no match
existing = context.get_free_var(first_name)
if existing && existing != type_var
return nil
end
if context.has_def_free_var?(first_name)
# If the free variable is already set to another
# number, there's no match
existing = context.get_free_var(first_name)
if existing && existing != type_var
return nil
end

context.set_free_var(first_name, type_var)
return type_var
context.set_free_var(first_name, type_var)
return type_var
end
end
else
# Restriction is not possible (maybe return nil here?)
Expand Down

0 comments on commit 33b1920

Please sign in to comment.