summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJason Zaugg <jzaugg@gmail.com>2014-02-22 20:33:43 +0100
committerJason Zaugg <jzaugg@gmail.com>2014-03-15 21:11:00 +0100
commitb7f359c718717262fa447f428dbd600ddd3b29bd (patch)
tree0d0f31673cd8404a0b94c46b57eba974627d6243
parent9c38e86a5526887f93a3f031b19a0e4fa31745d3 (diff)
downloadscala-b7f359c718717262fa447f428dbd600ddd3b29bd.tar.gz
scala-b7f359c718717262fa447f428dbd600ddd3b29bd.tar.bz2
scala-b7f359c718717262fa447f428dbd600ddd3b29bd.zip
SI-8329 Better hygiene for patmat partial functions
Don't enter synthetic parameters of `applyOrElse` et al into scope when typechecking the user-code; instead reference those symbolically. There is an exception to this principle. Currently we allow: val x: PartialFunction[A, B] = x => x match { ... } For this pattern of code, we use the given name `x` for the corresponding method parameter of `applyOrElse` and `isDefinedAt` and we actually need this to be in scope when we typecheck the scrutinee. This construct is tested in `run/virtpatmat_partial.scala`. A new parameter, `paramSynthetic`, differentiates this case from the more typical `val x: PF[A, B] = { case ... => ... ; ... } case, which uses a fresh name need not be in scope. (We could get away with it, as it is fresh, but I thought it better to exclude it.)
-rw-r--r--src/compiler/scala/tools/nsc/typechecker/Typers.scala25
-rw-r--r--test/files/pos/t8329.scala29
2 files changed, 41 insertions, 13 deletions
diff --git a/src/compiler/scala/tools/nsc/typechecker/Typers.scala b/src/compiler/scala/tools/nsc/typechecker/Typers.scala
index 9f3f257529..068ae8a798 100644
--- a/src/compiler/scala/tools/nsc/typechecker/Typers.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/Typers.scala
@@ -2479,7 +2479,8 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
* an alternative TODO: add partial function AST node or equivalent and get rid of this synthesis --> do everything in uncurry (or later)
* however, note that pattern matching codegen is designed to run *before* uncurry
*/
- def synthesizePartialFunction(paramName: TermName, paramPos: Position, tree: Tree, mode: Mode, pt: Type): Tree = {
+ def synthesizePartialFunction(paramName: TermName, paramPos: Position, paramSynthetic: Boolean,
+ tree: Tree, mode: Mode, pt: Type): Tree = {
assert(pt.typeSymbol == PartialFunctionClass, s"PartialFunction synthesis for match in $tree requires PartialFunction expected type, but got $pt.")
val targs = pt.dealiasWiden.typeArgs
@@ -2507,7 +2508,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
val casesTrue = cases map (c => deriveCaseDef(c)(x => atPos(x.pos.focus)(TRUE)).duplicate.asInstanceOf[CaseDef])
// must generate a new tree every time
- def selector: Tree = gen.mkUnchecked(
+ def selector(paramSym: Symbol): Tree = gen.mkUnchecked(
if (sel != EmptyTree) sel.duplicate
else atPos(tree.pos.focusStart)(
// SI-6925: subsume type of the selector to `argTp`
@@ -2518,7 +2519,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
// hence the cast, which will be erased in posterasure
// (the cast originally caused extremely weird types to show up
// in test/scaladoc/run/SI-5933.scala because `variantToSkolem` was missing `tpSym.initialize`)
- gen.mkCastPreservingAnnotations(Ident(paramName), argTp)
+ gen.mkCastPreservingAnnotations(Ident(paramSym), argTp)
))
def mkParam(methodSym: Symbol, tp: Type = argTp) =
@@ -2546,14 +2547,13 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
methodSym setInfo polyType(List(A1, B1), MethodType(paramSyms, B1.tpe))
val methodBodyTyper = newTyper(context.makeNewScope(context.tree, methodSym))
- // should use the DefDef for the context's tree, but it doesn't exist yet (we need the typer we're creating to create it)
- paramSyms foreach (methodBodyTyper.context.scope enter _)
+ if (!paramSynthetic) methodBodyTyper.context.scope enter x
// First, type without the default case; only the cases provided
// by the user are typed. The LUB of these becomes `B`, the lower
// bound of `B1`, which in turn is the result type of the default
// case
- val match0 = methodBodyTyper.typedMatch(selector, cases, mode, resTp)
+ val match0 = methodBodyTyper.typedMatch(selector(x), cases, mode, resTp)
val matchResTp = match0.tpe
B1 setInfo TypeBounds.lower(matchResTp) // patch info
@@ -2627,11 +2627,11 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
val paramSym = mkParam(methodSym)
val methodBodyTyper = newTyper(context.makeNewScope(context.tree, methodSym)) // should use the DefDef for the context's tree, but it doesn't exist yet (we need the typer we're creating to create it)
- methodBodyTyper.context.scope enter paramSym
+ if (!paramSynthetic) methodBodyTyper.context.scope enter paramSym
methodSym setInfo MethodType(List(paramSym), BooleanTpe)
val defaultCase = mkDefaultCase(FALSE)
- val match_ = methodBodyTyper.typedMatch(selector, casesTrue :+ defaultCase, mode, BooleanTpe)
+ val match_ = methodBodyTyper.typedMatch(selector(paramSym), casesTrue :+ defaultCase, mode, BooleanTpe)
DefDef(methodSym, methodBodyTyper.virtualizedMatch(match_, mode, BooleanTpe))
}
@@ -2645,10 +2645,9 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
methodSym setInfo MethodType(List(paramSym), AnyTpe)
val methodBodyTyper = newTyper(context.makeNewScope(context.tree, methodSym))
- // should use the DefDef for the context's tree, but it doesn't exist yet (we need the typer we're creating to create it)
- methodBodyTyper.context.scope enter paramSym
+ if (!paramSynthetic) methodBodyTyper.context.scope enter paramSym
- val match_ = methodBodyTyper.typedMatch(selector, cases, mode, resTp)
+ val match_ = methodBodyTyper.typedMatch(selector(paramSym), cases, mode, resTp)
val matchResTp = match_.tpe
methodSym setInfo MethodType(List(paramSym), matchResTp) // patch info
@@ -2920,7 +2919,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
val p = fun.vparams.head
if (p.tpt.tpe == null) p.tpt setType outerTyper.typedType(p.tpt).tpe
- outerTyper.synthesizePartialFunction(p.name, p.pos, fun.body, mode, pt)
+ outerTyper.synthesizePartialFunction(p.name, p.pos, paramSynthetic = false, fun.body, mode, pt)
// Use synthesizeSAMFunction to expand `(p1: T1, ..., pN: TN) => body`
// to an instance of the corresponding anonymous subclass of `pt`.
@@ -4207,7 +4206,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
val cases = tree.cases
if (selector == EmptyTree) {
if (pt.typeSymbol == PartialFunctionClass)
- synthesizePartialFunction(newTermName(context.unit.fresh.newName("x")), tree.pos, tree, mode, pt)
+ synthesizePartialFunction(newTermName(context.unit.fresh.newName("x")), tree.pos, paramSynthetic = true, tree, mode, pt)
else {
val arity = if (isFunctionType(pt)) pt.dealiasWiden.typeArgs.length - 1 else 1
val params = for (i <- List.range(0, arity)) yield
diff --git a/test/files/pos/t8329.scala b/test/files/pos/t8329.scala
new file mode 100644
index 0000000000..fcd5e50b37
--- /dev/null
+++ b/test/files/pos/t8329.scala
@@ -0,0 +1,29 @@
+object Test {
+ def pf(pf: PartialFunction[Any, Unit]) = ()
+ def f1(pf: Function[Any, Unit]) = ()
+
+ class A1; class B1
+ def test1(x: String, x1: String, default: String) = pf {
+ case _ if (
+ x.isEmpty
+ && default.isEmpty // was binding to synthetic param
+ && x1.isEmpty // was binding to synthetic param
+ ) =>
+ x.isEmpty
+ default.isEmpty // was binding to synthetic param
+ x1.isEmpty // was binding to synthetic param
+ new A1; new B1
+ }
+
+ def test2(x: String, x1: String, default: String) = f1 {
+ case _ if (
+ x.isEmpty
+ && default.isEmpty
+ && x1.isEmpty
+ ) =>
+ x.isEmpty
+ default.isEmpty
+ x1.isEmpty
+ new A1; new B1
+ }
+}