Skip to content

Commit d209a37

Browse files
committed
Merge pull request #4974 from szeiger/wip/patmat-outertest
More conservative optimization for unnecessary outer ref checks
2 parents 798462e + e7e1c77 commit d209a37

File tree

12 files changed

+204
-68
lines changed

12 files changed

+204
-68
lines changed

spec/08-pattern-matching.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -328,10 +328,12 @@ A type pattern $T$ is of one of the following forms:
328328

329329
* A reference to a class $C$, $p.C$, or `$T$#$C$`. This
330330
type pattern matches any non-null instance of the given class.
331-
Note that the prefix of the class, if it is given, is relevant for determining
331+
Note that the prefix of the class, if it exists, is relevant for determining
332332
class instances. For instance, the pattern $p.C$ matches only
333333
instances of classes $C$ which were created with the path $p$ as
334-
prefix.
334+
prefix. This also applies to prefixes which are not given syntactically.
335+
For example, if $C$ refers to a class defined in the nearest enclosing
336+
class and is thus equivalent to $this.C$, it is considered to have a prefix.
335337

336338
The bottom types `scala.Nothing` and `scala.Null` cannot
337339
be used as type patterns, because they would match nothing in any case.

src/compiler/scala/tools/nsc/transform/ExplicitOuter.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -486,7 +486,8 @@ abstract class ExplicitOuter extends InfoTransform
486486
// since we can't fix SI-4440 properly (we must drop the outer accessors of final classes when there's no immediate reference to them in sight)
487487
// at least don't crash... this duplicates maybeOmittable from constructors
488488
(acc.owner.isEffectivelyFinal && !acc.isOverridingSymbol)) {
489-
currentRun.reporting.uncheckedWarning(tree.pos, "The outer reference in this type test cannot be checked at run time.")
489+
if (!base.tpe.hasAnnotation(UncheckedClass))
490+
currentRun.reporting.uncheckedWarning(tree.pos, "The outer reference in this type test cannot be checked at run time.")
490491
transform(TRUE) // urgh... drop condition if there's no accessor (or if it may disappear after constructors)
491492
} else {
492493
// println("(base, acc)= "+(base, acc))

src/compiler/scala/tools/nsc/transform/patmat/MatchAnalysis.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,7 @@ trait MatchApproximation extends TreeAndTypeAnalysis with ScalaLogic with MatchT
351351
object condStrategy extends TypeTestTreeMaker.TypeTestCondStrategy {
352352
type Result = Prop
353353
def and(a: Result, b: Result) = And(a, b)
354-
def outerTest(testedBinder: Symbol, expectedTp: Type) = True // TODO OuterEqProp(testedBinder, expectedType)
354+
def withOuterTest(testedBinder: Symbol, expectedTp: Type) = True // TODO OuterEqProp(testedBinder, expectedType)
355355
def typeTest(b: Symbol, pt: Type) = { // a type test implies the tested path is non-null (null.isInstanceOf[T] is false for all T)
356356
val p = binderToUniqueTree(b); And(uniqueNonNullProp(p), uniqueTypeProp(p, uniqueTp(pt)))
357357
}

src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala

Lines changed: 28 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@ trait MatchTreeMaking extends MatchCodeGen with Debugging {
316316
trait TypeTestCondStrategy {
317317
type Result
318318

319-
def outerTest(testedBinder: Symbol, expectedTp: Type): Result
319+
def withOuterTest(orig: Result)(testedBinder: Symbol, expectedTp: Type): Result = orig
320320
// TODO: can probably always widen
321321
def typeTest(testedBinder: Symbol, expectedTp: Type): Result
322322
def nonNullTest(testedBinder: Symbol): Result
@@ -336,18 +336,34 @@ trait MatchTreeMaking extends MatchCodeGen with Debugging {
336336
def equalsTest(pat: Tree, testedBinder: Symbol) = codegen._equals(pat, testedBinder)
337337
def eqTest(pat: Tree, testedBinder: Symbol) = REF(testedBinder) OBJ_EQ pat
338338

339-
def outerTest(testedBinder: Symbol, expectedTp: Type): Tree = {
340-
val expectedOuter = expectedTp.prefix match {
341-
case ThisType(clazz) => This(clazz)
342-
case NoType => mkTRUE // fallback for SI-6183
343-
case pre => REF(pre.prefix, pre.termSymbol)
339+
override def withOuterTest(orig: Tree)(testedBinder: Symbol, expectedTp: Type): Tree = {
340+
val expectedPrefix = expectedTp.prefix
341+
val testedPrefix = testedBinder.info.prefix
342+
343+
// Check if a type is defined in a static location. Unlike `tp.isStatic` before `flatten`,
344+
// this also includes methods and (possibly nested) objects inside of methods.
345+
def definedInStaticLocation(tp: Type): Boolean = {
346+
def isStatic(tp: Type): Boolean =
347+
if (tp == NoType || tp.typeSymbol.isPackageClass || tp == NoPrefix) true
348+
else if (tp.typeSymbol.isModuleClass) isStatic(tp.prefix)
349+
else false
350+
tp.typeSymbol.owner == tp.prefix.typeSymbol && isStatic(tp.prefix)
344351
}
345352

346-
// ExplicitOuter replaces `Select(q, outerSym) OBJ_EQ expectedPrefix` by `Select(q, outerAccessor(outerSym.owner)) OBJ_EQ expectedPrefix`
347-
// if there's an outer accessor, otherwise the condition becomes `true` -- TODO: can we improve needsOuterTest so there's always an outerAccessor?
348-
val outer = expectedTp.typeSymbol.newMethod(vpmName.outer, newFlags = SYNTHETIC | ARTIFACT) setInfo expectedTp.prefix
349-
350-
(Select(codegen._asInstanceOf(testedBinder, expectedTp), outer)) OBJ_EQ expectedOuter
353+
if ((expectedPrefix eq NoPrefix)
354+
|| definedInStaticLocation(expectedTp)
355+
|| testedPrefix =:= expectedPrefix) orig
356+
else gen.mkAttributedQualifierIfPossible(expectedPrefix) match {
357+
case None => orig
358+
case Some(expectedOuterRef) =>
359+
// ExplicitOuter replaces `Select(q, outerSym) OBJ_EQ expectedPrefix`
360+
// by `Select(q, outerAccessor(outerSym.owner)) OBJ_EQ expectedPrefix`
361+
// if there's an outer accessor, otherwise the condition becomes `true`
362+
// TODO: centralize logic whether there's an outer accessor and use here?
363+
val synthOuterGetter = expectedTp.typeSymbol.newMethod(vpmName.outer, newFlags = SYNTHETIC | ARTIFACT) setInfo expectedPrefix
364+
val outerTest = (Select(codegen._asInstanceOf(testedBinder, expectedTp), synthOuterGetter)) OBJ_EQ expectedOuterRef
365+
and(orig, outerTest)
366+
}
351367
}
352368
}
353369

@@ -356,7 +372,6 @@ trait MatchTreeMaking extends MatchCodeGen with Debugging {
356372

357373
def typeTest(testedBinder: Symbol, expectedTp: Type): Result = true
358374

359-
def outerTest(testedBinder: Symbol, expectedTp: Type): Result = false
360375
def nonNullTest(testedBinder: Symbol): Result = false
361376
def equalsTest(pat: Tree, testedBinder: Symbol): Result = false
362377
def eqTest(pat: Tree, testedBinder: Symbol): Result = false
@@ -368,7 +383,6 @@ trait MatchTreeMaking extends MatchCodeGen with Debugging {
368383
type Result = Boolean
369384

370385
def typeTest(testedBinder: Symbol, expectedTp: Type): Result = testedBinder eq binder
371-
def outerTest(testedBinder: Symbol, expectedTp: Type): Result = false
372386
def nonNullTest(testedBinder: Symbol): Result = testedBinder eq binder
373387
def equalsTest(pat: Tree, testedBinder: Symbol): Result = false // could in principle analyse pat and see if it's statically known to be non-null
374388
def eqTest(pat: Tree, testedBinder: Symbol): Result = false // could in principle analyse pat and see if it's statically known to be non-null
@@ -405,12 +419,6 @@ trait MatchTreeMaking extends MatchCodeGen with Debugging {
405419
import TypeTestTreeMaker._
406420
debug.patmat("TTTM"+((prevBinder, extractorArgTypeTest, testedBinder, expectedTp, nextBinderTp)))
407421

408-
lazy val outerTestNeeded = (
409-
(expectedTp.prefix ne NoPrefix)
410-
&& !expectedTp.prefix.typeSymbol.isPackageClass
411-
&& needsOuterTest(expectedTp, testedBinder.info, matchOwner)
412-
)
413-
414422
// the logic to generate the run-time test that follows from the fact that
415423
// a `prevBinder` is expected to have type `expectedTp`
416424
// the actual tree-generation logic is factored out, since the analyses generate Cond(ition)s rather than Trees
@@ -429,12 +437,11 @@ trait MatchTreeMaking extends MatchCodeGen with Debugging {
429437
def isExpectedPrimitiveType = isAsExpected && isPrimitiveValueType(expectedTp)
430438
def isExpectedReferenceType = isAsExpected && (expectedTp <:< AnyRefTpe)
431439
def mkNullTest = nonNullTest(testedBinder)
432-
def mkOuterTest = outerTest(testedBinder, expectedTp)
433440
def mkTypeTest = typeTest(testedBinder, expectedWide)
434441

435442
def mkEqualsTest(lhs: Tree): cs.Result = equalsTest(lhs, testedBinder)
436443
def mkEqTest(lhs: Tree): cs.Result = eqTest(lhs, testedBinder)
437-
def addOuterTest(res: cs.Result): cs.Result = if (outerTestNeeded) and(res, mkOuterTest) else res
444+
def addOuterTest(res: cs.Result): cs.Result = withOuterTest(res)(testedBinder, expectedTp)
438445

439446
// If we conform to expected primitive type:
440447
// it cannot be null and cannot have an outer pointer. No further checking.

src/reflect/scala/reflect/internal/TreeGen.scala

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,30 @@ abstract class TreeGen {
117117
case _ => qual
118118
}
119119

120+
121+
122+
// val selType = testedBinder.info
123+
//
124+
// // See the test for SI-7214 for motivation for dealias. Later `treeCondStrategy#outerTest`
125+
// // generates an outer test based on `patType.prefix` with automatically dealises.
126+
// // Prefixes can have all kinds of shapes SI-9110
127+
// val patPre = expectedTp.dealiasWiden.prefix
128+
// val selPre = selType.dealiasWiden.prefix
129+
//
130+
// // Optimization: which prefixes can we disqualify from the need for an outer reference check?
131+
// // - classes in static owners do not get outer pointers
132+
// // - if the prefixes are statically known to be equal, the type system ensures an outer test is redundant
133+
// !((patPre eq NoPrefix) || (selPre eq NoPrefix)
134+
// || patPre.typeSymbol.isPackageClass
135+
// || selPre =:= patPre)
136+
137+
def mkAttributedQualifierIfPossible(prefix: Type): Option[Tree] = prefix match {
138+
case NoType | NoPrefix | ErrorType => None
139+
case TypeRef(_, sym, _) if sym.isModule || sym.isClass || sym.isType => None
140+
case pre => Some(mkAttributedQualifier(prefix))
141+
}
142+
143+
120144
/** Builds a reference to given symbol with given stable prefix. */
121145
def mkAttributedRef(pre: Type, sym: Symbol): RefTree = {
122146
val qual = mkAttributedQualifier(pre)

src/reflect/scala/reflect/internal/Types.scala

Lines changed: 0 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -3946,47 +3946,6 @@ trait Types
39463946
check(tp1, tp2) && check(tp2, tp1)
39473947
}
39483948

3949-
/** Does a pattern of type `patType` need an outer test when executed against
3950-
* selector type `selType` in context defined by `currentOwner`?
3951-
*/
3952-
def needsOuterTest(patType: Type, selType: Type, currentOwner: Symbol) = {
3953-
def createDummyClone(pre: Type): Type = {
3954-
val dummy = currentOwner.enclClass.newValue(nme.ANYname).setInfo(pre.widen)
3955-
singleType(ThisType(currentOwner.enclClass), dummy)
3956-
}
3957-
def maybeCreateDummyClone(pre: Type, sym: Symbol): Type = pre match {
3958-
case SingleType(pre1, sym1) =>
3959-
if (sym1.isModule && sym1.isStatic) {
3960-
if (sym.owner == sym1 || sym.isJavaDefined || sym.owner.sourceModule.isStaticModule) NoType
3961-
else pre
3962-
} else if (sym1.isModule && sym.owner == sym1.moduleClass) {
3963-
val pre2 = maybeCreateDummyClone(pre1, sym1)
3964-
if (pre2 eq NoType) pre2
3965-
else singleType(pre2, sym1)
3966-
} else {
3967-
createDummyClone(pre)
3968-
}
3969-
case ThisType(clazz) =>
3970-
if (clazz.isModuleClass)
3971-
maybeCreateDummyClone(clazz.typeOfThis, sym)
3972-
else if (sym.owner == clazz && (sym.hasFlag(PRIVATE) || sym.privateWithin == clazz))
3973-
NoType
3974-
else
3975-
createDummyClone(pre)
3976-
case _ =>
3977-
NoType
3978-
}
3979-
// See the test for SI-7214 for motivation for dealias. Later `treeCondStrategy#outerTest`
3980-
// generates an outer test based on `patType.prefix` with automatically dealises.
3981-
patType.dealias match {
3982-
case TypeRef(pre, sym, args) =>
3983-
val pre1 = maybeCreateDummyClone(pre, sym)
3984-
(pre1 ne NoType) && isPopulated(copyTypeRef(patType, pre1, sym, args), selType)
3985-
case _ =>
3986-
false
3987-
}
3988-
}
3989-
39903949
def normalizePlus(tp: Type): Type = {
39913950
if (isRawType(tp)) rawToExistential(tp)
39923951
else tp.normalize match {

test/files/neg/outer-ref-checks.check

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
outer-ref-checks.scala:5: warning: The outer reference in this type test cannot be checked at run time.
2+
final case class Inner(val s: String) // unchecked warning
3+
^
4+
outer-ref-checks.scala:8: warning: The outer reference in this type test cannot be checked at run time.
5+
case Inner(s) => // unchecked warning
6+
^
7+
outer-ref-checks.scala:18: warning: The outer reference in this type test cannot be checked at run time.
8+
case Inner(s) => // unchecked warning
9+
^
10+
outer-ref-checks.scala:19: warning: The outer reference in this type test cannot be checked at run time.
11+
case O.Inner(s) => // unchecked warning
12+
^
13+
outer-ref-checks.scala:41: warning: The outer reference in this type test cannot be checked at run time.
14+
case Inner(s) => // unchecked warning
15+
^
16+
outer-ref-checks.scala:46: warning: The outer reference in this type test cannot be checked at run time.
17+
case _: Inner => // unchecked warning
18+
^
19+
outer-ref-checks.scala:56: warning: The outer reference in this type test cannot be checked at run time.
20+
case _: (Inner @uncheckedVariance) => // unchecked warning
21+
^
22+
error: No warnings can be incurred under -Xfatal-warnings.
23+
7 warnings found
24+
one error found

test/files/neg/outer-ref-checks.flags

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
-Xfatal-warnings -unchecked

test/files/neg/outer-ref-checks.scala

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import scala.annotation.unchecked.uncheckedVariance
2+
3+
class Outer {
4+
// A final class gets no outer ref, so we expect to see warnings where an outer ref check should be performed
5+
final case class Inner(val s: String) // unchecked warning
6+
7+
def belongs(a: Any): Unit = a match {
8+
case Inner(s) => // unchecked warning
9+
case _ =>
10+
}
11+
12+
def belongsStaticSameOuter(a: Inner): Unit = a match {
13+
case Inner(s) => // no need for outer check
14+
// match is exhaustive, no default case needed
15+
}
16+
17+
def belongsOtherOuter(a: Outer#Inner): Unit = a match {
18+
case Inner(s) => // unchecked warning
19+
case O.Inner(s) => // unchecked warning
20+
case _ =>
21+
}
22+
}
23+
24+
object O extends Outer {
25+
def belongsStaticSameOuter2(a: Inner): Unit = a match {
26+
case Inner(s) => // no need for outer check
27+
// match is exhaustive, no default case needed
28+
}
29+
30+
def belongsStaticSameOuter3(a: Inner): Unit = a match {
31+
case _: Inner => // no need for outer check
32+
// match is exhaustive, no default case needed
33+
}
34+
35+
def belongsStaticSameOuter4(a: Inner): Unit = a match {
36+
case _: (Inner @uncheckedVariance) => // no need for outer check
37+
// match is exhaustive, no default case needed
38+
}
39+
40+
def belongsOtherOuter2(a: Outer#Inner): Unit = a match {
41+
case Inner(s) => // unchecked warning
42+
case _ =>
43+
}
44+
45+
def belongsOtherOuter3(a: Outer#Inner): Unit = a match {
46+
case _: Inner => // unchecked warning
47+
case _ =>
48+
}
49+
50+
def belongsOtherOuter4(a: Outer#Inner): Unit = a match {
51+
case _: (Inner @unchecked) => // warning supressed
52+
case _ =>
53+
}
54+
55+
def belongsOtherOuter5(a: Outer#Inner): Unit = a match {
56+
case _: (Inner @uncheckedVariance) => // unchecked warning
57+
case _ =>
58+
}
59+
60+
def nested: Unit = {
61+
final case class I(s: String)
62+
63+
def check1(a: Any): Unit = a match {
64+
case I(s) => // no need for outer check
65+
case _ =>
66+
}
67+
68+
def check2(a: I): Unit = a match {
69+
case I(s) => // no need for outer check
70+
// match is exhaustive, no default case needed
71+
}
72+
}
73+
}
74+
75+
class O2 {
76+
def nested: Unit = {
77+
final case class I(s: String)
78+
79+
def check1(a: Any): Unit = a match {
80+
case I(s) => // no need for outer check (is this correct?)
81+
case _ =>
82+
}
83+
84+
def check2(a: I): Unit = a match {
85+
case I(s) => // no need for outer check (is this correct?)
86+
// match is exhaustive, no default case needed
87+
}
88+
}
89+
}
90+
91+
package p {
92+
object T {
93+
case class C(x: Int)
94+
}
95+
}
96+
97+
object U {
98+
val T = p.T
99+
}
100+
101+
class Test {
102+
def m(a: Any) = a match {
103+
case U.T.C(1) => 1 // used to warn
104+
case _ => 1
105+
}
106+
}

test/files/neg/t7171.check

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
t7171.scala:2: warning: The outer reference in this type test cannot be checked at run time.
22
final case class A()
33
^
4+
t7171.scala:9: warning: The outer reference in this type test cannot be checked at run time.
5+
case _: A => true; case _ => false
6+
^
47
error: No warnings can be incurred under -Xfatal-warnings.
5-
one warning found
8+
two warnings found
69
one error found

test/files/neg/t7171b.check

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
t7171b.scala:2: warning: The outer reference in this type test cannot be checked at run time.
22
final case class A()
33
^
4+
t7171b.scala:8: warning: The outer reference in this type test cannot be checked at run time.
5+
case _: A => true; case _ => false
6+
^
7+
t7171b.scala:13: warning: The outer reference in this type test cannot be checked at run time.
8+
case _: A => true; case _ => false
9+
^
410
error: No warnings can be incurred under -Xfatal-warnings.
5-
one warning found
11+
three warnings found
612
one error found

test/files/run/t7171.check

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
t7171.scala:2: warning: The outer reference in this type test cannot be checked at run time.
22
final case class A()
33
^
4+
t7171.scala:9: warning: The outer reference in this type test cannot be checked at run time.
5+
case _: A => true; case _ => false
6+
^

0 commit comments

Comments
 (0)