summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVlad Ureche <vlad.ureche@gmail.com>2013-06-14 15:01:37 +0200
committerVlad Ureche <vlad.ureche@gmail.com>2013-06-25 11:46:24 +0200
commit228549356ce5dda0305dc2e9e08870859e4de06e (patch)
tree0e78e73438681de0d6db6cc5d1b11e0a8c90f199
parentd0df4c514f662312e2a9284fef2cf24a284aff28 (diff)
downloadscala-228549356ce5dda0305dc2e9e08870859e4de06e.tar.gz
scala-228549356ce5dda0305dc2e9e08870859e4de06e.tar.bz2
scala-228549356ce5dda0305dc2e9e08870859e4de06e.zip
SI-7344 Specialize methods in private scopes
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 The solution is limited by SI-7579: since the duplicator loses the sym annotations when duplicating, this expansion and rewiring can only take place in code that has not been subject to duplication. You can see the test case for an example. Review by @dragos, @paulp or @axel22.
-rw-r--r--src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala118
-rw-r--r--test/files/specialized/SI-7344.scala53
2 files changed, 147 insertions, 24 deletions
diff --git a/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala b/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala
index 5ef5d4031b..7e85647592 100644
--- a/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala
+++ b/src/compiler/scala/tools/nsc/transform/SpecializeTypes.scala
@@ -844,7 +844,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
}
}
@@ -1420,7 +1424,7 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers {
val env = unify(symbol.tpe, tree.tpe, emptyEnv, false)
debuglog("[specSym] checking for rerouting: %s with \n\tsym.tpe: %s, \n\ttree.tpe: %s \n\tenv: %s \n\tname: %s"
.format(tree, symbol.tpe, tree.tpe, env, specializedName(symbol, env)))
- if (!env.isEmpty) { // a method?
+ if (env.nonEmpty) { // a method?
val specCandidates = qual.tpe.member(specializedName(symbol, env))
val specMember = specCandidates suchThat { s =>
doesConform(symbol, tree.tpe, qual.tpe.memberType(s), env)
@@ -1438,6 +1442,34 @@ abstract class SpecializeTypes extends InfoTransform with TypingTransformers {
} else None
}
+ /** 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, specTree: Tree, baseTargs: List[Tree], env: TypeEnv) = {
+ val baseSym: Symbol = baseTree.symbol
+ val specSym: Symbol = specTree.symbol
+ val residualTargs = baseSym.info.typeParams zip baseTargs collect {
+ case (tvar, targ) if !env.contains(tvar) || !isPrimitiveValueClass(env(tvar).typeSymbol) => targ
+ }
+
+ if (specSym.info.typeParams.isEmpty && residualTargs.nonEmpty) {
+ log("!!! Type args to be applied, but symbol says no parameters: " + ((specSym.defString, residualTargs)))
+ baseTree
+ }
+ else {
+ ifDebug(assert(residualTargs.length == specSym.info.typeParams.length,
+ "residual: %s, tparams: %s, env: %s".format(residualTargs, specSym.info.typeParams, env))
+ )
+
+ val tree1 = gen.mkTypeApply(specTree, residualTargs)
+ debuglog("rewrote " + tree + " to " + tree1)
+ localTyper.typedOperator(atPos(tree.pos)(tree1))
+ }
+ }
+
curTree = tree
tree match {
case Apply(Select(New(tpt), nme.CONSTRUCTOR), args) =>
@@ -1470,43 +1502,44 @@ 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 Some(specMember) =>
debuglog("found " + specMember.fullName)
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) {
- log("!!! 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, gen.mkAttributedSelect(qual1, specMember), targs, env)
case None =>
treeCopy.TypeApply(tree, treeCopy.Select(sel, qual1, name), super.transformTrees(targs))
// See pos/exponential-spec.scala - can't call transform on the whole tree again.
// super.transform(tree)
}
+
+ // 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, Ident(specMember), targs, env)
+ case _ => super.transform(tree)
+ }
}
- transformTypeApply
case Select(Super(_, _), _) if illegalSpecializedInheritance(currentClass) =>
val pos = tree.pos
@@ -1657,7 +1690,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 = {
@@ -1682,6 +1719,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)
+}