From 33b1920a553980487bd801120264d881ebddfa90 Mon Sep 17 00:00:00 2001 From: Quinton Miller Date: Tue, 6 Dec 2022 05:01:22 +0800 Subject: [PATCH] Fix restriction of numeral generic argument against non-free variable `Path` (#12784) --- spec/compiler/semantic/restrictions_spec.cr | 30 +++++++++++++++-- src/compiler/crystal/semantic/restrictions.cr | 33 ++++++++++++++----- 2 files changed, 51 insertions(+), 12 deletions(-) diff --git a/spec/compiler/semantic/restrictions_spec.cr b/spec/compiler/semantic/restrictions_spec.cr index baf69551da21..7201bb816f69 100644 --- a/spec/compiler/semantic/restrictions_spec.cr +++ b/spec/compiler/semantic/restrictions_spec.cr @@ -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 @@ -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 @@ -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 diff --git a/src/compiler/crystal/semantic/restrictions.cr b/src/compiler/crystal/semantic/restrictions.cr index 523030444d2e..c391dfdb4ee6 100644 --- a/src/compiler/crystal/semantic/restrictions.cr +++ b/src/compiler/crystal/semantic/restrictions.cr @@ -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 @@ -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?)