Skip to content

Commit

Permalink
Better comparisons for type projections (#17092)
Browse files Browse the repository at this point in the history
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` 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
  • Loading branch information
odersky authored Mar 25, 2023
2 parents ee2bfdb + d17da7a commit dcf5f9d
Show file tree
Hide file tree
Showing 3 changed files with 25 additions and 4 deletions.
17 changes: 14 additions & 3 deletions compiler/src/dotty/tools/dotc/core/TypeComparer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 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 hasStablePrefix(tp: NamedType) =
tp.prefix.isStable

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 && hasStablePrefix(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 && hasStablePrefix(tp2) then
return false
case _ =>
}
val sym2 = tp2.symbol
Expand All @@ -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) ||
Expand Down
2 changes: 1 addition & 1 deletion tests/neg/i15525.scala
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def element22(
transmittable20.Type / transmittable21.Type
} = ???

def test22 = // error
def test22 =
Resolution(
element22(
Resolution(element0), Resolution(element0), // error // error
Expand Down
10 changes: 10 additions & 0 deletions tests/pos/i17064.scala
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit dcf5f9d

Please sign in to comment.