Skip to content

Commit a12e924

Browse files
committed
Check outer class prefixes in type projections when pattern matching
A type pattern of the form A#B should be checked if the prefix A is not already the owner of B or a superclass. Fixes scala#16728
1 parent ea93524 commit a12e924

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
@@ -11,6 +11,7 @@ import util.Spans._
1111
import typer.Applications.*
1212
import SymUtils._
1313
import TypeUtils.*
14+
import Annotations.*
1415
import Flags._, Constants._
1516
import Decorators._
1617
import NameKinds.{PatMatStdBinderName, PatMatAltsName, PatMatResultName}
@@ -707,9 +708,9 @@ object PatternMatcher {
707708
// ----- Generating trees from plans ---------------
708709

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

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

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

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

776785
@tailrec
777786
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)