summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAdriaan Moors <adriaan.moors@typesafe.com>2013-06-28 09:57:51 -0700
committerAdriaan Moors <adriaan.moors@typesafe.com>2013-06-28 09:57:51 -0700
commit397a057fd242833098562e48f12e11621b4ab7ae (patch)
tree16ddf1df3488aea70d80032b65c509ece1c1cd65
parentb5f70c844c77ac974118d07a9d53e3fd98a69e5f (diff)
parent32163215b183883f5c37ae0fd3342a4ae93a0090 (diff)
downloadscala-397a057fd242833098562e48f12e11621b4ab7ae.tar.gz
scala-397a057fd242833098562e48f12e11621b4ab7ae.tar.bz2
scala-397a057fd242833098562e48f12e11621b4ab7ae.zip
Merge pull request #2683 from VladUreche/issue/7344-master
SI-7344 Specialize methods in private scopes
-rw-r--r--src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala114
-rw-r--r--test/files/specialized/SI-7344.scala53
2 files changed, 144 insertions, 23 deletions
diff --git a/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala b/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala
index f43e42c027..d14fcb3eb1 100644
--- a/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala
+++ b/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala
@@ -852,7 +852,11 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers {
debuglog("%s expands to %s in %s".format(sym, specMember.name.decode, pp(env)))
info(specMember) = NormalizedMember(sym)
newOverload(sym, specMember, env)
- owner.info.decls.enter(specMember)
+ // if this is a class, we insert the normalized member in scope,
+ // if this is a method, there's no attached scope for it (EmptyScope)
+ val decls = owner.info.decls
+ if (decls != EmptyScope)
+ decls.enter(specMember)
specMember
}
}
@@ -1445,6 +1449,32 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers {
}
}
+ /** Computes residual type parameters after rewiring, like "String" in the following example:
+ * ```
+ * def specMe[@specialized T, U](t: T, u: U) = ???
+ * specMe[Int, String](1, "2") => specMe$mIc$sp[String](1, "2")
+ * ```
+ */
+ def computeResidualTypeVars(baseTree: Tree, specMember: Symbol, specTree: Tree, baseTargs: List[Tree], env: TypeEnv): Tree = {
+ val residualTargs = symbol.info.typeParams zip baseTargs collect {
+ case (tvar, targ) if !env.contains(tvar) || !isPrimitiveValueClass(env(tvar).typeSymbol) => targ
+ }
+ // See SI-5583. Don't know why it happens now if it didn't before.
+ if (specMember.info.typeParams.isEmpty && residualTargs.nonEmpty) {
+ devWarning("Type args to be applied, but symbol says no parameters: " + ((specMember.defString, residualTargs)))
+ baseTree
+ }
+ else {
+ ifDebug(assert(residualTargs.length == specMember.info.typeParams.length,
+ "residual: %s, tparams: %s, env: %s".format(residualTargs, specMember.info.typeParams, env))
+ )
+
+ val tree1 = gen.mkTypeApply(specTree, residualTargs)
+ debuglog("rewrote " + tree + " to " + tree1)
+ localTyper.typedOperator(atPos(tree.pos)(tree1)) // being polymorphic, it must be a method
+ }
+ }
+
curTree = tree
tree match {
case Apply(Select(New(tpt), nme.CONSTRUCTOR), args) =>
@@ -1470,12 +1500,16 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers {
}
transformSuperApply
+ // This rewires calls to specialized methods defined in a class (which have a receiver)
+ // class C {
+ // def foo[@specialized T](t: T): T = t
+ // C.this.foo(3) // TypeApply(Select(This(C), foo), List(Int)) => C.this.foo$mIc$sp(3)
+ // }
case TypeApply(sel @ Select(qual, name), targs)
- if (!specializedTypeVars(symbol.info).isEmpty && name != nme.CONSTRUCTOR) =>
- def transformTypeApply = {
+ if (specializedTypeVars(symbol.info).nonEmpty && name != nme.CONSTRUCTOR) =>
debuglog("checking typeapp for rerouting: " + tree + " with sym.tpe: " + symbol.tpe + " tree.tpe: " + tree.tpe)
val qual1 = transform(qual)
- // log(">>> TypeApply: " + tree + ", qual1: " + qual1)
+ log(">>> TypeApply: " + tree + ", qual1: " + qual1)
specSym(qual1) match {
case NoSymbol =>
// See pos/exponential-spec.scala - can't call transform on the whole tree again.
@@ -1485,26 +1519,23 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers {
ifDebug(assert(symbol.info.typeParams.length == targs.length, symbol.info.typeParams + " / " + targs))
val env = typeEnv(specMember)
- val residualTargs = symbol.info.typeParams zip targs collect {
- case (tvar, targ) if !env.contains(tvar) || !isPrimitiveValueClass(env(tvar).typeSymbol) => targ
- }
- // See SI-5583. Don't know why it happens now if it didn't before.
- if (specMember.info.typeParams.isEmpty && residualTargs.nonEmpty) {
- devWarning("Type args to be applied, but symbol says no parameters: " + ((specMember.defString, residualTargs)))
- localTyper.typed(sel)
- }
- else {
- ifDebug(assert(residualTargs.length == specMember.info.typeParams.length,
- "residual: %s, tparams: %s, env: %s".format(residualTargs, specMember.info.typeParams, env))
- )
-
- val tree1 = gen.mkTypeApply(Select(qual1, specMember), residualTargs)
- debuglog("rewrote " + tree + " to " + tree1)
- localTyper.typedOperator(atPos(tree.pos)(tree1)) // being polymorphic, it must be a method
- }
+ computeResidualTypeVars(tree, specMember, gen.mkAttributedSelect(qual1, specMember), targs, env)
}
+
+ // This rewires calls to specialized methods defined in the local scope. For example:
+ // def outerMethod = {
+ // def foo[@specialized T](t: T): T = t
+ // foo(3) // TypeApply(Ident(foo), List(Int)) => foo$mIc$sp(3)
+ // }
+ case TypeApply(sel @ Ident(name), targs) if name != nme.CONSTRUCTOR =>
+ val env = unify(symbol.tpe, tree.tpe, emptyEnv, false)
+ if (env.isEmpty) super.transform(tree)
+ else {
+ overloads(symbol) find (_ matchesEnv env) match {
+ case Some(Overload(specMember, _)) => computeResidualTypeVars(tree, specMember, Ident(specMember), targs, env)
+ case _ => super.transform(tree)
+ }
}
- transformTypeApply
case Select(Super(_, _), _) if illegalSpecializedInheritance(currentClass) =>
val pos = tree.pos
@@ -1621,7 +1652,11 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers {
localTyper.typed(deriveDefDef(tree)(rhs => rhs))
}
}
- transformDefDef
+ expandInnerNormalizedMembers(transformDefDef)
+
+ case ddef @ DefDef(_, _, _, _, _, _) =>
+ val tree1 = expandInnerNormalizedMembers(tree)
+ super.transform(tree1)
case ValDef(_, _, _, _) if symbol.hasFlag(SPECIALIZED) && !symbol.isParamAccessor =>
def transformValDef = {
@@ -1645,6 +1680,39 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers {
}
}
+ /**
+ * This performs method specialization inside a scope other than a {class, trait, object}: could be another method
+ * or a value. This specialization is much simpler, since there is no need to record the new members in the class
+ * signature, their signatures are only visible locally. It works according to the usual logic:
+ * - we use normalizeMember to create the specialized symbols
+ * - we leave DefDef stubs in the tree that are later filled in by tree duplication and adaptation
+ * @see duplicateBody
+ */
+ private def expandInnerNormalizedMembers(tree: Tree) = tree match {
+ case ddef @ DefDef(_, _, _, vparams :: Nil, _, rhs)
+ if ddef.symbol.owner.isMethod &&
+ specializedTypeVars(ddef.symbol.info).nonEmpty &&
+ !ddef.symbol.hasFlag(SPECIALIZED) =>
+
+ val sym = ddef.symbol
+ val owner = sym.owner
+ val norm = normalizeMember(owner, sym, emptyEnv)
+
+ if (norm.length > 1) {
+ // record the body for duplication
+ body(sym) = rhs
+ parameters(sym) = vparams.map(_.symbol)
+ // to avoid revisiting the member, we can set the SPECIALIZED
+ // flag. nobody has to see this anyway :)
+ sym.setFlag(SPECIALIZED)
+ // create empty bodies for specializations
+ localTyper.typed(Block(norm.tail.map(sym => DefDef(sym, { vparamss => EmptyTree })), ddef))
+ } else
+ tree
+ case _ =>
+ tree
+ }
+
/** Duplicate the body of the given method `tree` to the new symbol `source`.
*
* Knowing that the method can be invoked only in the `castmap` type environment,
diff --git a/test/files/specialized/SI-7344.scala b/test/files/specialized/SI-7344.scala
new file mode 100644
index 0000000000..1040460bd1
--- /dev/null
+++ b/test/files/specialized/SI-7344.scala
@@ -0,0 +1,53 @@
+/* Test for SI-7344, where specialized methods inside the bodies of other
+ * methods are not specialized, although they might as well be. The name
+ * for the specialized method should not be different depending on the
+ * outside method/class' specialization. */
+
+class Test[@specialized(Int, Double) X](val x: X) {
+
+ def checkSpecialization[Y](@specialized(Int, Double) y: Y): X = {
+
+ // checking the specialization using the method name, which we can
+ // extract from an exception's stack trace. We can match just the
+ // prefix, since the compiler will add a suffix to the method name
+ // during lambdalift, when it lifts the local methods outside.
+ def specMe[@specialized(Int, Double) T, N](t: T, n: N): Unit = checkNameStartsWith(n.toString)
+
+ // expected to specialize:
+ specMe("x", "specMe")
+ specMe(123, "specMe$mIc$sp")
+ specMe(1.3, new { override def toString = "specMe$mDc$sp" })
+
+ x
+ }
+
+ // name matching:
+ private[this] def checkNameStartsWith(prefix: String): Unit = {
+ val method = (new Exception).getStackTrace()(1).getMethodName()
+ assert(method.startsWith(prefix), method + ".startsWith(" + prefix + ") should be true")
+ }
+}
+
+object Test extends App {
+ val t1 = new Test("x")
+ val t2 = new Test(123)
+ val t3 = new Test(1.3)
+
+ // we want specialization to rewire these,
+ // that's why they're not in a for loop:
+ t1.checkSpecialization("x")
+
+ // Prevented by SI-7579:
+ // The duplicator loses the @specialized annotation,
+ // so our tree transformation doesn't know it needs to
+ // specialize specMe inside the duplicated (and specialized)
+ // variants of the `checkSpecialization` method
+ // t1.checkSpecialization(123)
+ // t1.checkSpecialization(1.3)
+ // t2.checkSpecialization("x")
+ // t2.checkSpecialization(123)
+ // t2.checkSpecialization(1.3)
+ // t3.checkSpecialization("x")
+ // t3.checkSpecialization(123)
+ // t3.checkSpecialization(1.3)
+}