Skip to content

Commit d4e80c4

Browse files
authored
Check outer class prefixes in type projections when pattern matching (#17136)
2 parents 0625d1c + a12e924 commit d4e80c4

File tree

5 files changed

+132
-32
lines changed

5 files changed

+132
-32
lines changed

compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala

Lines changed: 41 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import util.Spans._
1010
import typer.Applications.*
1111
import SymUtils._
1212
import TypeUtils.*
13+
import Annotations.*
1314
import Flags._, Constants._
1415
import Decorators._
1516
import NameKinds.{PatMatStdBinderName, PatMatAltsName, PatMatResultName}
@@ -708,9 +709,9 @@ object PatternMatcher {
708709
// ----- Generating trees from plans ---------------
709710

710711
/** The condition a test plan rewrites to */
711-
private def emitCondition(plan: TestPlan): Tree = {
712+
private def emitCondition(plan: TestPlan): Tree =
712713
val scrutinee = plan.scrutinee
713-
(plan.test: @unchecked) match {
714+
(plan.test: @unchecked) match
714715
case NonEmptyTest =>
715716
constToLiteral(
716717
scrutinee
@@ -738,41 +739,49 @@ object PatternMatcher {
738739
case TypeTest(tpt, trusted) =>
739740
val expectedTp = tpt.tpe
740741

741-
// An outer test is needed in a situation like `case x: y.Inner => ...`
742-
def outerTestNeeded: Boolean = {
743-
def go(expected: Type): Boolean = expected match {
744-
case tref @ TypeRef(pre: SingletonType, _) =>
745-
tref.symbol.isClass &&
746-
ExplicitOuter.needsOuterIfReferenced(tref.symbol.asClass)
747-
case AppliedType(tpe, _) => go(tpe)
748-
case _ =>
749-
false
750-
}
751-
// See the test for SI-7214 for motivation for dealias. Later `treeCondStrategy#outerTest`
752-
// generates an outer test based on `patType.prefix` with automatically dealises.
753-
go(expectedTp.dealias)
754-
}
742+
def typeTest(scrut: Tree, expected: Type): Tree =
743+
val ttest = scrut.select(defn.Any_typeTest).appliedToType(expected)
744+
if trusted then ttest.pushAttachment(TrustedTypeTestKey, ())
745+
ttest
755746

756-
def outerTest: Tree = thisPhase.transformFollowingDeep {
757-
val expectedOuter = singleton(expectedTp.normalizedPrefix)
758-
val expectedClass = expectedTp.dealias.classSymbol.asClass
759-
ExplicitOuter.ensureOuterAccessors(expectedClass)
760-
scrutinee.ensureConforms(expectedTp)
761-
.outerSelect(1, expectedClass.owner.typeRef)
762-
.select(defn.Object_eq)
763-
.appliedTo(expectedOuter)
764-
}
747+
/** An outer test is needed in a situation like `case x: y.Inner => ...
748+
* or like case x: O#Inner if the owner of Inner is not a subclass of O.
749+
* Outer tests are added here instead of in TypeTestsCasts since they
750+
* might cause outer accessors to be added to inner classes (via ensureOuterAccessors)
751+
* and therefore have to run before ExplicitOuter.
752+
*/
753+
def addOuterTest(tree: Tree, expected: Type): Tree = expected.dealias match
754+
case tref @ TypeRef(pre, _) =>
755+
tref.symbol match
756+
case expectedCls: ClassSymbol if ExplicitOuter.needsOuterIfReferenced(expectedCls) =>
757+
def selectOuter =
758+
ExplicitOuter.ensureOuterAccessors(expectedCls)
759+
scrutinee.ensureConforms(expected).outerSelect(1, expectedCls.owner.typeRef)
760+
if pre.isSingleton then
761+
val expectedOuter = singleton(pre)
762+
tree.and(selectOuter.select(defn.Object_eq).appliedTo(expectedOuter))
763+
else if !expectedCls.isStatic
764+
&& expectedCls.owner.isType
765+
&& !expectedCls.owner.derivesFrom(pre.classSymbol)
766+
then
767+
val testPre =
768+
if expected.hasAnnotation(defn.UncheckedAnnot) then
769+
AnnotatedType(pre, Annotation(defn.UncheckedAnnot, tree.span))
770+
else pre
771+
tree.and(typeTest(selectOuter, testPre))
772+
else tree
773+
case _ => tree
774+
case AppliedType(tycon, _) =>
775+
addOuterTest(tree, tycon)
776+
case _ =>
777+
tree
765778

766-
expectedTp.dealias match {
779+
expectedTp.dealias match
767780
case expectedTp: SingletonType =>
768781
scrutinee.isInstance(expectedTp) // will be translated to an equality test
769782
case _ =>
770-
val typeTest = scrutinee.select(defn.Any_typeTest).appliedToType(expectedTp)
771-
if (trusted) typeTest.pushAttachment(TrustedTypeTestKey, ())
772-
if (outerTestNeeded) typeTest.and(outerTest) else typeTest
773-
}
774-
}
775-
}
783+
addOuterTest(typeTest(scrutinee, expectedTp), expectedTp)
784+
end emitCondition
776785

777786
@tailrec
778787
private def canFallThrough(plan: Plan): Boolean = plan match {
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
-- Error: tests/neg-custom-args/fatal-warnings/i16728.scala:16:11 ------------------------------------------------------
2+
16 | case tx : C[Int]#X => // error
3+
| ^
4+
| the type test for C[Int] cannot be checked at runtime because its type arguments can't be determined from A
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
class A[T] {
2+
class X {
3+
def outer : A.this.type = A.this
4+
}
5+
}
6+
7+
class B extends A[Int]
8+
class C[T] extends A[T]
9+
10+
object Test {
11+
def main(args: Array[String]) : Unit = {
12+
val b0 = new B
13+
val b0x : A[?]#X = new b0.X
14+
15+
def test = b0x match {
16+
case tx : C[Int]#X => // error
17+
val c : C[Int] = tx.outer
18+
c
19+
case _ =>
20+
"no match"
21+
}
22+
23+
def test2 = b0x match {
24+
case tx : C[Int]#X @unchecked => // ok
25+
val c : C[Int] = tx.outer
26+
c
27+
case _ =>
28+
"no match"
29+
}
30+
31+
}
32+
}

tests/run/i16728.check

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
b1.X
2+
B#X
3+
ELSE

tests/run/i16728.scala

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
class A {
2+
class X {
3+
def outer : A.this.type = A.this
4+
}
5+
}
6+
7+
class B extends A
8+
class C extends A
9+
10+
object Test {
11+
def main(args: Array[String]) : Unit = {
12+
val b0 = new B
13+
val b1 = b0
14+
val b2 = new B
15+
16+
val c0 = new C
17+
val c1 = c0
18+
val c2 = new C
19+
20+
val b0x : A#X = new b0.X
21+
22+
val pathTypeMatch = b0x match {
23+
case _ : c2.X => "c2.X"
24+
case _ : c1.X => "c1.x"
25+
case _ : c0.X => "c0.X"
26+
case _ : b2.X => "b2.X"
27+
case _ : b1.X => "b1.X"
28+
case _ : b0.X => "b0.X"
29+
case _ => "ELSE"
30+
}
31+
32+
println(pathTypeMatch)
33+
34+
val projectionTypeMatch = b0x match {
35+
case _ : C#X => "C#X"
36+
case _ : B#X => "B#X"
37+
case _ : A#X => "A#X"
38+
case _ => "ELSE"
39+
}
40+
41+
println(projectionTypeMatch)
42+
43+
val failingTypeMatch = b0x match {
44+
case cx : C#X =>
45+
val c : C = cx.outer
46+
c
47+
case _ => "ELSE"
48+
}
49+
50+
println(failingTypeMatch)
51+
}
52+
}

0 commit comments

Comments
 (0)