summaryrefslogtreecommitdiff
path: root/src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala
diff options
context:
space:
mode:
authorAdriaan Moors <adriaan.moors@typesafe.com>2016-01-29 17:56:21 -0800
committerStefan Zeiger <szeiger@novocode.com>2016-03-07 17:27:49 +0100
commite7e1c77092ec0d64a6ecbb8b920d1b4cca74837f (patch)
treefe490f451d0960b77045c621d7aa2b462288e119 /src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala
parenta926b840e1e756c0dcd936499583ed19d357b97b (diff)
downloadscala-e7e1c77092ec0d64a6ecbb8b920d1b4cca74837f.tar.gz
scala-e7e1c77092ec0d64a6ecbb8b920d1b4cca74837f.tar.bz2
scala-e7e1c77092ec0d64a6ecbb8b920d1b4cca74837f.zip
Improved outer ref checking in pattern matches:
The old algorithm omitted necessary outer ref checks in some places. This new one is more conservative. It only omits outer ref checks when the expected type and the scrutinee type match up, or when the expected type is defined in a static location. For this specific purpose the top level of a method or other code block (which is not a trait or class definition) is also considered static because it does not have a prefix. This change comes with a spec update to clarify the prefix rule for type patterns. The new wording makes it clear that the presence of a prefix is to be interpreted in a *semantic* way, i.e. the existence of a prefix determines the necessity for an outer ref check, no matter if the prefix is actually spelled out *syntactically*. Note that the old outer ref check implementation did not use the alternative interpretation of requiring prefixes to be given syntactically. It never created an outer ref check for a local class `C`, no matter if the pattern was `_: C` or `_: this.C`, thus violating both interpretations of the spec. There is now explicit support for unchecked matches (like `case _: (T @unchecked) =>`) to suppress warnings for unchecked outer refs. `@unchecked` worked before and was used for this purpose in `neg/t7721` but never actually existed as a feature. It was a result of a bug that prevented an outer ref check from being generated in the first place if *any* annotation was used on an expected type in a type pattern. This new version will still generate the outer ref check if an outer ref is available but suppress the warning otherwise. Other annotations on type patterns are ignored. New tests are in `neg/outer-ref-checks`. The expected results of tests `neg/t7171` and `neg/t7171b` have changed because the compiler now tries to generate additional outer ref checks that were not present before (which was a bug).
Diffstat (limited to 'src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala')
-rw-r--r--src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala49
1 files changed, 28 insertions, 21 deletions
diff --git a/src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala b/src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala
index c6e7f8fcda..8c59ced28f 100644
--- a/src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala
+++ b/src/compiler/scala/tools/nsc/transform/patmat/MatchTreeMaking.scala
@@ -316,7 +316,7 @@ trait MatchTreeMaking extends MatchCodeGen with Debugging {
trait TypeTestCondStrategy {
type Result
- def outerTest(testedBinder: Symbol, expectedTp: Type): Result
+ def withOuterTest(orig: Result)(testedBinder: Symbol, expectedTp: Type): Result = orig
// TODO: can probably always widen
def typeTest(testedBinder: Symbol, expectedTp: Type): Result
def nonNullTest(testedBinder: Symbol): Result
@@ -336,18 +336,34 @@ trait MatchTreeMaking extends MatchCodeGen with Debugging {
def equalsTest(pat: Tree, testedBinder: Symbol) = codegen._equals(pat, testedBinder)
def eqTest(pat: Tree, testedBinder: Symbol) = REF(testedBinder) OBJ_EQ pat
- def outerTest(testedBinder: Symbol, expectedTp: Type): Tree = {
- val expectedOuter = expectedTp.prefix match {
- case ThisType(clazz) => This(clazz)
- case NoType => mkTRUE // fallback for SI-6183
- case pre => REF(pre.prefix, pre.termSymbol)
+ override def withOuterTest(orig: Tree)(testedBinder: Symbol, expectedTp: Type): Tree = {
+ val expectedPrefix = expectedTp.prefix
+ val testedPrefix = testedBinder.info.prefix
+
+ // Check if a type is defined in a static location. Unlike `tp.isStatic` before `flatten`,
+ // this also includes methods and (possibly nested) objects inside of methods.
+ def definedInStaticLocation(tp: Type): Boolean = {
+ def isStatic(tp: Type): Boolean =
+ if (tp == NoType || tp.typeSymbol.isPackageClass || tp == NoPrefix) true
+ else if (tp.typeSymbol.isModuleClass) isStatic(tp.prefix)
+ else false
+ tp.typeSymbol.owner == tp.prefix.typeSymbol && isStatic(tp.prefix)
}
- // ExplicitOuter replaces `Select(q, outerSym) OBJ_EQ expectedPrefix` by `Select(q, outerAccessor(outerSym.owner)) OBJ_EQ expectedPrefix`
- // if there's an outer accessor, otherwise the condition becomes `true` -- TODO: can we improve needsOuterTest so there's always an outerAccessor?
- val outer = expectedTp.typeSymbol.newMethod(vpmName.outer, newFlags = SYNTHETIC | ARTIFACT) setInfo expectedTp.prefix
-
- (Select(codegen._asInstanceOf(testedBinder, expectedTp), outer)) OBJ_EQ expectedOuter
+ if ((expectedPrefix eq NoPrefix)
+ || definedInStaticLocation(expectedTp)
+ || testedPrefix =:= expectedPrefix) orig
+ else gen.mkAttributedQualifierIfPossible(expectedPrefix) match {
+ case None => orig
+ case Some(expectedOuterRef) =>
+ // ExplicitOuter replaces `Select(q, outerSym) OBJ_EQ expectedPrefix`
+ // by `Select(q, outerAccessor(outerSym.owner)) OBJ_EQ expectedPrefix`
+ // if there's an outer accessor, otherwise the condition becomes `true`
+ // TODO: centralize logic whether there's an outer accessor and use here?
+ val synthOuterGetter = expectedTp.typeSymbol.newMethod(vpmName.outer, newFlags = SYNTHETIC | ARTIFACT) setInfo expectedPrefix
+ val outerTest = (Select(codegen._asInstanceOf(testedBinder, expectedTp), synthOuterGetter)) OBJ_EQ expectedOuterRef
+ and(orig, outerTest)
+ }
}
}
@@ -356,7 +372,6 @@ trait MatchTreeMaking extends MatchCodeGen with Debugging {
def typeTest(testedBinder: Symbol, expectedTp: Type): Result = true
- def outerTest(testedBinder: Symbol, expectedTp: Type): Result = false
def nonNullTest(testedBinder: Symbol): Result = false
def equalsTest(pat: Tree, testedBinder: Symbol): Result = false
def eqTest(pat: Tree, testedBinder: Symbol): Result = false
@@ -368,7 +383,6 @@ trait MatchTreeMaking extends MatchCodeGen with Debugging {
type Result = Boolean
def typeTest(testedBinder: Symbol, expectedTp: Type): Result = testedBinder eq binder
- def outerTest(testedBinder: Symbol, expectedTp: Type): Result = false
def nonNullTest(testedBinder: Symbol): Result = testedBinder eq binder
def equalsTest(pat: Tree, testedBinder: Symbol): Result = false // could in principle analyse pat and see if it's statically known to be non-null
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 {
import TypeTestTreeMaker._
debug.patmat("TTTM"+((prevBinder, extractorArgTypeTest, testedBinder, expectedTp, nextBinderTp)))
- lazy val outerTestNeeded = (
- (expectedTp.prefix ne NoPrefix)
- && !expectedTp.prefix.typeSymbol.isPackageClass
- && needsOuterTest(expectedTp, testedBinder.info, matchOwner)
- )
-
// the logic to generate the run-time test that follows from the fact that
// a `prevBinder` is expected to have type `expectedTp`
// 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 {
def isExpectedPrimitiveType = isAsExpected && isPrimitiveValueType(expectedTp)
def isExpectedReferenceType = isAsExpected && (expectedTp <:< AnyRefTpe)
def mkNullTest = nonNullTest(testedBinder)
- def mkOuterTest = outerTest(testedBinder, expectedTp)
def mkTypeTest = typeTest(testedBinder, expectedWide)
def mkEqualsTest(lhs: Tree): cs.Result = equalsTest(lhs, testedBinder)
def mkEqTest(lhs: Tree): cs.Result = eqTest(lhs, testedBinder)
- def addOuterTest(res: cs.Result): cs.Result = if (outerTestNeeded) and(res, mkOuterTest) else res
+ def addOuterTest(res: cs.Result): cs.Result = withOuterTest(res)(testedBinder, expectedTp)
// If we conform to expected primitive type:
// it cannot be null and cannot have an outer pointer. No further checking.