Skip to content

Commit ad0c52b

Browse files
committed
Implement Tuple specialisation
Of both construction (both `new` and `apply`) and of selection (`_1$mcI$sp`).
1 parent eeca5f1 commit ad0c52b

File tree

8 files changed

+95
-1
lines changed

8 files changed

+95
-1
lines changed

compiler/src/dotty/tools/dotc/Compiler.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ class Compiler {
9696
new InterceptedMethods, // Special handling of `==`, `|=`, `getClass` methods
9797
new Getters, // Replace non-private vals and vars with getter defs (fields are added later)
9898
new SpecializeFunctions, // Specialized Function{0,1,2} by replacing super with specialized super
99+
new SpecializeTuples, // Specializes Tuples by replacing tuple construction and selection trees
99100
new LiftTry, // Put try expressions that might execute on non-empty stacks into their own methods
100101
new CollectNullableFields, // Collect fields that can be nulled out after use in lazy initialization
101102
new ElimOuterSelect, // Expand outer selections

compiler/src/dotty/tools/dotc/core/Definitions.scala

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1329,6 +1329,8 @@ class Definitions {
13291329

13301330
@tu lazy val TupleType: Array[TypeRef | Null] = mkArityArray("scala.Tuple", MaxTupleArity, 1)
13311331

1332+
def SpecialisedTuple(base: Symbol, args: List[Type]): Symbol = base.owner.requiredClass(base.name.specializedName(args))
1333+
13321334
private class FunType(prefix: String):
13331335
private var classRefs: Array[TypeRef | Null] = new Array(22)
13341336
def apply(n: Int): TypeRef =
@@ -1587,6 +1589,20 @@ class Definitions {
15871589
def isFunctionType(tp: Type)(using Context): Boolean =
15881590
isNonRefinedFunction(tp.dropDependentRefinement)
15891591

1592+
private def withSpecMethods(cls: ClassSymbol, bases: List[Name], paramTypes: Set[TypeRef]) =
1593+
for base <- bases; tp <- paramTypes do
1594+
cls.enter(newSymbol(cls, base.specializedName(List(tp)), Method, ExprType(tp)))
1595+
cls
1596+
1597+
@tu lazy val Tuple1: ClassSymbol = withSpecMethods(requiredClass("scala.Tuple1"), List(nme._1), Tuple1SpecializedParamTypes)
1598+
@tu lazy val Tuple2: ClassSymbol = withSpecMethods(requiredClass("scala.Tuple2"), List(nme._1, nme._2), Tuple2SpecializedParamTypes)
1599+
1600+
@tu lazy val TupleSpecializedClasses: Set[Symbol] = Set(Tuple1, Tuple2)
1601+
@tu lazy val Tuple1SpecializedParamTypes: Set[TypeRef] = Set(IntType, LongType, DoubleType)
1602+
@tu lazy val Tuple2SpecializedParamTypes: Set[TypeRef] = Set(IntType, LongType, DoubleType, CharType, BooleanType)
1603+
@tu lazy val Tuple1SpecializedParamClasses: PerRun[Set[Symbol]] = new PerRun(Tuple1SpecializedParamTypes.map(_.symbol))
1604+
@tu lazy val Tuple2SpecializedParamClasses: PerRun[Set[Symbol]] = new PerRun(Tuple2SpecializedParamTypes.map(_.symbol))
1605+
15901606
// Specialized type parameters defined for scala.Function{0,1,2}.
15911607
@tu lazy val Function1SpecializedParamTypes: collection.Set[TypeRef] =
15921608
Set(IntType, LongType, FloatType, DoubleType)
@@ -1610,6 +1626,12 @@ class Definitions {
16101626
@tu lazy val Function2SpecializedReturnClasses: PerRun[collection.Set[Symbol]] =
16111627
new PerRun(Function2SpecializedReturnTypes.map(_.symbol))
16121628

1629+
def isSpecializableTuple(base: Symbol, args: List[Type])(using Context): Boolean =
1630+
args.length <= 2 && base.isClass && TupleSpecializedClasses.exists(base.asClass.derivesFrom) && args.match
1631+
case List(x) => Tuple1SpecializedParamClasses().contains(x.typeSymbol)
1632+
case List(x, y) => Tuple2SpecializedParamClasses().contains(x.typeSymbol) && Tuple2SpecializedParamClasses().contains(y.typeSymbol)
1633+
case _ => false
1634+
16131635
def isSpecializableFunction(cls: ClassSymbol, paramTypes: List[Type], retType: Type)(using Context): Boolean =
16141636
paramTypes.length <= 2
16151637
&& (cls.derivesFrom(FunctionClass(paramTypes.length)) || isByNameFunctionClass(cls))

compiler/src/dotty/tools/dotc/core/NameOps.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,15 @@ object NameOps {
278278
classTags.fold(nme.EMPTY)(_ ++ _) ++ nme.specializedTypeNames.suffix)
279279
}
280280

281+
def specializedName(args: List[Type])(using Context): N =
282+
val sb = new StringBuilder
283+
sb.append(name.toString)
284+
sb.append(nme.specializedTypeNames.prefix.toString)
285+
sb.append(nme.specializedTypeNames.separator)
286+
args.foreach { arg => sb.append(defn.typeTag(arg)) }
287+
sb.append(nme.specializedTypeNames.suffix)
288+
likeSpacedN(termName(sb.toString))
289+
281290
/** Use for specializing function names ONLY and use it if you are **not**
282291
* creating specialized name from type parameters. The order of names will
283292
* be:

compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1017,7 +1017,10 @@ class ClassfileParser(
10171017
else return unpickleTASTY(bytes)
10181018
}
10191019

1020-
if (scan(tpnme.ScalaATTR) && !scalaUnpickleWhitelist.contains(classRoot.name))
1020+
if scan(tpnme.ScalaATTR) && !scalaUnpickleWhitelist.contains(classRoot.name)
1021+
&& !(classRoot.name.startsWith("Tuple") && classRoot.name.endsWith("$sp"))
1022+
&& !(classRoot.name.startsWith("Product") && classRoot.name.endsWith("$sp"))
1023+
then
10211024
// To understand the situation, it's helpful to know that:
10221025
// - Scalac emits the `ScalaSig` attribute for classfiles with pickled information
10231026
// and the `Scala` attribute for everything else.
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package dotty.tools
2+
package dotc
3+
package transform
4+
5+
import ast.Trees.*, ast.tpd, core.*
6+
import Contexts.*, Types.*, Decorators.*, Symbols.*, DenotTransformers.*
7+
import SymDenotations.*, Scopes.*, StdNames.*, NameOps.*, Names.*
8+
import MegaPhase.MiniPhase
9+
10+
/** Specializes Tuples by replacing tuple construction and selection trees.
11+
*
12+
* Specifically:
13+
* 1. Replaces `(1, 1)` (which is `Tuple2.apply[Int, Int](1, 1)`) and
14+
* `new Tuple2[Int, Int](1, 1)` with `new Tuple2$mcII$sp(1, 1)`.
15+
* 2. Replaces `(_: Tuple2[Int, Int])._1` with `(_: Tuple2[Int, Int])._1$mcI$sp`
16+
*/
17+
class SpecializeTuples extends MiniPhase:
18+
import tpd.*
19+
20+
override def phaseName: String = SpecializeTuples.name
21+
override def description: String = SpecializeTuples.description
22+
override def isEnabled(using Context): Boolean = !ctx.settings.scalajs.value
23+
24+
override def transformApply(tree: Apply)(using Context): Tree = tree match
25+
case Apply(TypeApply(fun: NameTree, targs), args)
26+
if fun.name == nme.apply && fun.symbol.exists && defn.isSpecializableTuple(fun.symbol.owner.companionClass, targs.map(_.tpe)) =>
27+
Apply(Select(New(defn.SpecialisedTuple(fun.symbol.owner.companionClass, targs.map(_.tpe)).typeRef), nme.CONSTRUCTOR), args)
28+
case Apply(TypeApply(fun: NameTree, targs), args)
29+
if fun.name == nme.CONSTRUCTOR && fun.symbol.exists && defn.isSpecializableTuple(fun.symbol.owner, targs.map(_.tpe)) =>
30+
Apply(Select(New(defn.SpecialisedTuple(fun.symbol.owner, targs.map(_.tpe)).typeRef), nme.CONSTRUCTOR), args)
31+
case _ => tree
32+
end transformApply
33+
34+
override def transformSelect(tree: Select)(using Context): Tree = tree match
35+
case Select(qual, nme._1) if qual.tpe.widen.match
36+
case AppliedType(tycon, args) => defn.isSpecializableTuple(tycon.classSymbol, args)
37+
case _ => false
38+
=> Select(qual, nme._1.specializedName(qual.tpe.widen.argInfos.slice(0, 1)))
39+
case Select(qual, nme._2) if qual.tpe.widen.match
40+
case AppliedType(tycon, args) => defn.isSpecializableTuple(tycon.classSymbol, args)
41+
case _ => false
42+
=> Select(qual, nme._2.specializedName(qual.tpe.widen.argInfos.slice(1, 2)))
43+
case _ => tree
44+
end SpecializeTuples
45+
46+
object SpecializeTuples:
47+
val name: String = "specializeTuples"
48+
val description: String = "replaces tuple construction and selection trees"

tests/pos/i11114.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
class Foo {
2+
val x: (Int, Int) = (1, 1)
3+
val y: Int = x._1
4+
}

tests/run/i11114.check

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
class scala.Tuple2$mcII$sp
2+
class scala.Tuple2$mcII$sp
3+
class scala.Tuple2$mcII$sp

tests/run/i11114.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
@main def Test() =
2+
println((1, 1).getClass)
3+
println(Tuple2.apply(1, 1).getClass)
4+
println(new Tuple2(1, 1).getClass)

0 commit comments

Comments
 (0)