From 2ac2315d1550b807fea3c0ec3fab74e8642a3d39 Mon Sep 17 00:00:00 2001 From: odersky Date: Sun, 12 Mar 2023 18:25:52 +0100 Subject: [PATCH 1/3] Better comparisons for type projections MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We already implemented in essence the rule suggested in lampepfl/dotty-feature-requests#14: ``` Γ ⊨ p : T ------------------------ (Sel-<:-Proj) Γ ⊨ p.A <: T#A ``` This rule is implemented in `isSubPrefix`. But we did not get to check that since we concluded prematurely that an alias type would never match. The alias type in question in i17064.scala was ```scala Outer#Inner ``` Since `Outer` is not a path, the asSeenFrom to recover the info of `Outer#Inner this got approximated with a `Nothing` argument and therefore the alias failed. It is important in this case that we could still succeed with a `isSubPrefix` test, which comes later. So we should not return `false` when the prefix is not a singleton. Fixes #17064 --- .../dotty/tools/dotc/core/TypeComparer.scala | 17 ++++++++++++++--- tests/neg/i15525.scala | 2 +- tests/pos/i17064.scala | 10 ++++++++++ 3 files changed, 25 insertions(+), 4 deletions(-) create mode 100644 tests/pos/i17064.scala diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 9dda18c72f26..07a573e9169d 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -283,17 +283,28 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling val ctx = comparerContext given Context = ctx // optimization for performance val info2 = tp2.info + + /** Does `tp2` have a singleton type or NoPrefix as a prefix? + * If that's not the case, following an alias via asSeenFrom could be lossy + * so we should not conclude `false` if comparing aliases fails. + * See pos/i17064.scala for a test case + */ + def hasPrecisePrefix(tp: NamedType) = + tp.prefix.isSingleton || tp.prefix == NoPrefix + info2 match case info2: TypeAlias => if recur(tp1, info2.alias) then return true - if tp2.asInstanceOf[TypeRef].canDropAlias then return false + if tp2.asInstanceOf[TypeRef].canDropAlias && hasPrecisePrefix(tp2) then + return false case _ => tp1 match case tp1: NamedType => tp1.info match { case info1: TypeAlias => if recur(info1.alias, tp2) then return true - if tp1.asInstanceOf[TypeRef].canDropAlias then return false + if tp1.asInstanceOf[TypeRef].canDropAlias && hasPrecisePrefix(tp2) then + return false case _ => } val sym2 = tp2.symbol @@ -302,7 +313,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling // For convenience we want X$ <:< X.type // This is safe because X$ self-type is X.type sym1 = sym1.companionModule - if ((sym1 ne NoSymbol) && (sym1 eq sym2)) + if (sym1 ne NoSymbol) && (sym1 eq sym2) then ctx.erasedTypes || sym1.isStaticOwner || isSubPrefix(tp1.prefix, tp2.prefix) || diff --git a/tests/neg/i15525.scala b/tests/neg/i15525.scala index b14c244b43c8..0813d7c82435 100644 --- a/tests/neg/i15525.scala +++ b/tests/neg/i15525.scala @@ -37,7 +37,7 @@ def element22( transmittable20.Type / transmittable21.Type } = ??? -def test22 = // error +def test22 = Resolution( element22( Resolution(element0), Resolution(element0), // error // error diff --git a/tests/pos/i17064.scala b/tests/pos/i17064.scala new file mode 100644 index 000000000000..e5a1f636f56c --- /dev/null +++ b/tests/pos/i17064.scala @@ -0,0 +1,10 @@ +class HiddenInner[+O<:Outer](val outer:O){ +} + +class Outer{ + type Inner = HiddenInner[this.type] +} + +val o : Outer = new Outer +def a : o.Inner = new o.Inner(o) +val b : Outer#Inner = a // DOES NOT COMPILE \ No newline at end of file From c8efe69ac26d2767e638641bd633533200a1eb87 Mon Sep 17 00:00:00 2001 From: odersky Date: Sat, 18 Mar 2023 16:28:08 +0100 Subject: [PATCH 2/3] Align tests in TypeComparer and AsSeenFrom --- compiler/src/dotty/tools/dotc/core/TypeComparer.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 07a573e9169d..45ccf80198e2 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -284,13 +284,13 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling given Context = ctx // optimization for performance val info2 = tp2.info - /** Does `tp2` have a singleton type or NoPrefix as a prefix? + /** Does `tp2` have a stable prefix or NoPrefix as a prefix? * If that's not the case, following an alias via asSeenFrom could be lossy * so we should not conclude `false` if comparing aliases fails. * See pos/i17064.scala for a test case */ def hasPrecisePrefix(tp: NamedType) = - tp.prefix.isSingleton || tp.prefix == NoPrefix + tp.prefix.isStable || tp.prefix == NoPrefix info2 match case info2: TypeAlias => From d17da7a733e9eda1e2618792609da07aa84dd50b Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Sat, 25 Mar 2023 14:57:05 +0100 Subject: [PATCH 3/3] Simplify hasPrecisePrefix check `NoPrefix.isStable` already returns true. --- compiler/src/dotty/tools/dotc/core/TypeComparer.scala | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index 45ccf80198e2..465978d329e6 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -284,18 +284,18 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling given Context = ctx // optimization for performance val info2 = tp2.info - /** Does `tp2` have a stable prefix or NoPrefix as a prefix? + /** Does `tp2` have a stable prefix? * If that's not the case, following an alias via asSeenFrom could be lossy * so we should not conclude `false` if comparing aliases fails. * See pos/i17064.scala for a test case */ - def hasPrecisePrefix(tp: NamedType) = - tp.prefix.isStable || tp.prefix == NoPrefix + def hasStablePrefix(tp: NamedType) = + tp.prefix.isStable info2 match case info2: TypeAlias => if recur(tp1, info2.alias) then return true - if tp2.asInstanceOf[TypeRef].canDropAlias && hasPrecisePrefix(tp2) then + if tp2.asInstanceOf[TypeRef].canDropAlias && hasStablePrefix(tp2) then return false case _ => tp1 match @@ -303,7 +303,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling tp1.info match { case info1: TypeAlias => if recur(info1.alias, tp2) then return true - if tp1.asInstanceOf[TypeRef].canDropAlias && hasPrecisePrefix(tp2) then + if tp1.asInstanceOf[TypeRef].canDropAlias && hasStablePrefix(tp2) then return false case _ => }