Skip to content

Commit b94e6ea

Browse files
committed
Drop function 22 limit.
Functions with more than 22 parameters are now automatically converted to functions taking a single object array parameter. This has been achieved by tweaking erasure. Other things I have tried that did ot work out well: - Use a single function type in typer. The problem with this one which could not be circumvented was that existing higher-kinded code with e.g. Funcor assumes that Functon1 is a binary type constructor. - Have a late phase that converts to FunctonXXL instead of doing it in erasure. The problem with that one was that potentially every type could be affected, which was ill-suited to the architecture of a miniphase.
1 parent 3116142 commit b94e6ea

File tree

6 files changed

+159
-23
lines changed

6 files changed

+159
-23
lines changed

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

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,9 @@ class Definitions {
457457
def PartialFunctionClass(implicit ctx: Context) = PartialFunctionType.symbol.asClass
458458
lazy val AbstractPartialFunctionType: TypeRef = ctx.requiredClassRef("scala.runtime.AbstractPartialFunction")
459459
def AbstractPartialFunctionClass(implicit ctx: Context) = AbstractPartialFunctionType.symbol.asClass
460+
lazy val FunctionXXLType: TypeRef = ctx.requiredClassRef("scala.FunctionXXL")
461+
def FunctionXXLClass(implicit ctx: Context) = FunctionXXLType.symbol.asClass
462+
460463
lazy val SymbolType: TypeRef = ctx.requiredClassRef("scala.Symbol")
461464
def SymbolClass(implicit ctx: Context) = SymbolType.symbol.asClass
462465
lazy val DynamicType: TypeRef = ctx.requiredClassRef("scala.Dynamic")
@@ -645,17 +648,17 @@ class Definitions {
645648
private lazy val ImplementedFunctionType = mkArityArray("scala.Function", MaxImplementedFunctionArity, 0)
646649
def FunctionClassPerRun = new PerRun[Array[Symbol]](implicit ctx => ImplementedFunctionType.map(_.symbol.asClass))
647650

651+
lazy val TupleType = mkArityArray("scala.Tuple", MaxTupleArity, 2)
652+
lazy val ProductNType = mkArityArray("scala.Product", MaxTupleArity, 0)
653+
648654
def FunctionClass(n: Int)(implicit ctx: Context) =
649655
if (n < MaxImplementedFunctionArity) FunctionClassPerRun()(ctx)(n)
650656
else ctx.requiredClass("scala.Function" + n.toString)
651657

652658
lazy val Function0_applyR = ImplementedFunctionType(0).symbol.requiredMethodRef(nme.apply)
653659
def Function0_apply(implicit ctx: Context) = Function0_applyR.symbol
654660

655-
lazy val TupleType = mkArityArray("scala.Tuple", MaxTupleArity, 2)
656-
lazy val ProductNType = mkArityArray("scala.Product", MaxTupleArity, 0)
657-
658-
def FunctionType(n: Int): TypeRef =
661+
def FunctionType(n: Int)(implicit ctx: Context): TypeRef =
659662
if (n < MaxImplementedFunctionArity) ImplementedFunctionType(n)
660663
else FunctionClass(n).typeRef
661664

@@ -680,6 +683,8 @@ class Definitions {
680683
tp.derivesFrom(NothingClass) || tp.derivesFrom(NullClass)
681684

682685
def isFunctionClass(cls: Symbol) = isVarArityClass(cls, tpnme.Function)
686+
def isUnimplementedFunctionClass(cls: Symbol) =
687+
isFunctionClass(cls) && cls.name.functionArity >= MaxImplementedFunctionArity
683688
def isAbstractFunctionClass(cls: Symbol) = isVarArityClass(cls, tpnme.AbstractFunction)
684689
def isTupleClass(cls: Symbol) = isVarArityClass(cls, tpnme.Tuple)
685690
def isProductClass(cls: Symbol) = isVarArityClass(cls, tpnme.Product)

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,7 @@ object StdNames {
265265
val THIS: N = "_$this"
266266
val TRAIT_CONSTRUCTOR: N = "$init$"
267267
val U2EVT: N = "u2evt$"
268+
val ALLARGS: N = "$allArgs"
268269

269270
final val Nil: N = "Nil"
270271
final val Predef: N = "Predef"

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

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import Uniques.unique
77
import dotc.transform.ExplicitOuter._
88
import dotc.transform.ValueClasses._
99
import util.DotClass
10+
import Definitions.MaxImplementedFunctionArity
1011

1112
/** Erased types are:
1213
*
@@ -38,7 +39,10 @@ object TypeErasure {
3839
case _: ErasedValueType =>
3940
true
4041
case tp: TypeRef =>
41-
tp.symbol.isClass && tp.symbol != defn.AnyClass && tp.symbol != defn.ArrayClass
42+
val sym = tp.symbol
43+
sym.isClass &&
44+
sym != defn.AnyClass && sym != defn.ArrayClass &&
45+
!defn.isUnimplementedFunctionClass(sym)
4246
case _: TermRef =>
4347
true
4448
case JavaArrayType(elem) =>
@@ -176,8 +180,13 @@ object TypeErasure {
176180
else if (sym.isAbstractType) TypeAlias(WildcardType)
177181
else if (sym.isConstructor) outer.addParam(sym.owner.asClass, erase(tp)(erasureCtx))
178182
else erase.eraseInfo(tp, sym)(erasureCtx) match {
179-
case einfo: MethodType if sym.isGetter && einfo.resultType.isRef(defn.UnitClass) =>
180-
MethodType(Nil, defn.BoxedUnitType)
183+
case einfo: MethodType =>
184+
if (sym.isGetter && einfo.resultType.isRef(defn.UnitClass))
185+
MethodType(Nil, defn.BoxedUnitType)
186+
else if (sym.isAnonymousFunction && einfo.paramTypes.length > MaxImplementedFunctionArity)
187+
MethodType(nme.ALLARGS :: Nil, JavaArrayType(defn.ObjectType) :: Nil, einfo.resultType)
188+
else
189+
einfo
181190
case einfo =>
182191
einfo
183192
}
@@ -317,6 +326,7 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean
317326
* - For a term ref p.x, the type <noprefix> # x.
318327
* - For a typeref scala.Any, scala.AnyVal or scala.Singleton: |java.lang.Object|
319328
* - For a typeref scala.Unit, |scala.runtime.BoxedUnit|.
329+
* - For a typeref scala.FunctionN, where N > MaxImplementedFunctionArity, scala.FunctionXXL
320330
* - For a typeref P.C where C refers to a class, <noprefix> # C.
321331
* - For a typeref P.C where C refers to an alias type, the erasure of C's alias.
322332
* - For a typeref P.C where C refers to an abstract type, the erasure of C's upper bound.
@@ -345,6 +355,7 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean
345355
if (!sym.isClass) this(tp.info)
346356
else if (semiEraseVCs && isDerivedValueClass(sym)) eraseDerivedValueClassRef(tp)
347357
else if (sym == defn.ArrayClass) apply(tp.appliedTo(TypeBounds.empty)) // i966 shows that we can hit a raw Array type.
358+
else if (defn.isUnimplementedFunctionClass(sym)) defn.FunctionXXLType
348359
else eraseNormalClassRef(tp)
349360
case tp: RefinedType =>
350361
val parent = tp.parent

compiler/src/dotty/tools/dotc/transform/Erasure.scala

Lines changed: 62 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import core.StdNames._
1313
import core.NameOps._
1414
import core.Decorators._
1515
import core.Constants._
16+
import core.Definitions._
1617
import typer.NoChecking
1718
import typer.ProtoTypes._
1819
import typer.ErrorReporting._
@@ -36,9 +37,17 @@ class Erasure extends Phase with DenotTransformer { thisTransformer =>
3637

3738
def transform(ref: SingleDenotation)(implicit ctx: Context): SingleDenotation = ref match {
3839
case ref: SymDenotation =>
40+
def isCompacted(sym: Symbol) =
41+
sym.isAnonymousFunction && {
42+
sym.info(ctx.withPhase(ctx.phase.next)) match {
43+
case MethodType(nme.ALLARGS :: Nil, _) => true
44+
case _ => false
45+
}
46+
}
47+
3948
assert(ctx.phase == this, s"transforming $ref at ${ctx.phase}")
4049
if (ref.symbol eq defn.ObjectClass) {
41-
// Aftre erasure, all former Any members are now Object members
50+
// After erasure, all former Any members are now Object members
4251
val ClassInfo(pre, _, ps, decls, selfInfo) = ref.info
4352
val extendedScope = decls.cloneScope
4453
for (decl <- defn.AnyClass.classInfo.decls)
@@ -59,7 +68,10 @@ class Erasure extends Phase with DenotTransformer { thisTransformer =>
5968
val oldInfo = ref.info
6069
val newInfo = transformInfo(ref.symbol, oldInfo)
6170
val oldFlags = ref.flags
62-
val newFlags = ref.flags &~ Flags.HasDefaultParams // HasDefaultParams needs to be dropped because overriding might become overloading
71+
val newFlags =
72+
if (oldSymbol.is(Flags.TermParam) && isCompacted(oldSymbol.owner)) oldFlags &~ Flags.Param
73+
else oldFlags &~ Flags.HasDefaultParams // HasDefaultParams needs to be dropped because overriding might become overloading
74+
6375
// TODO: define derivedSymDenotation?
6476
if ((oldSymbol eq newSymbol) && (oldOwner eq newOwner) && (oldInfo eq newInfo) && (oldFlags == newFlags)) ref
6577
else {
@@ -331,8 +343,20 @@ object Erasure extends TypeTestsCasts{
331343
* e.m -> e.[]m if `m` is an array operation other than `clone`.
332344
*/
333345
override def typedSelect(tree: untpd.Select, pt: Type)(implicit ctx: Context): Tree = {
334-
val sym = tree.symbol
335-
assert(sym.exists, tree.show)
346+
val oldSym = tree.symbol
347+
assert(oldSym.exists)
348+
val oldOwner = oldSym.owner
349+
val owner =
350+
if ((oldOwner eq defn.AnyClass) || (oldOwner eq defn.AnyValClass)) {
351+
assert(oldSym.isConstructor, s"${oldSym.showLocated}")
352+
defn.ObjectClass
353+
}
354+
else if (defn.isUnimplementedFunctionClass(oldOwner))
355+
defn.FunctionXXLClass
356+
else
357+
oldOwner
358+
val sym = if (owner eq oldOwner) oldSym else owner.info.decl(oldSym.name).symbol
359+
assert(sym.exists, owner)
336360

337361
def select(qual: Tree, sym: Symbol): Tree = {
338362
val name = tree.typeOpt match {
@@ -366,11 +390,7 @@ object Erasure extends TypeTestsCasts{
366390
def recur(qual: Tree): Tree = {
367391
val qualIsPrimitive = qual.tpe.widen.isPrimitiveValueType
368392
val symIsPrimitive = sym.owner.isPrimitiveValueClass
369-
if ((sym.owner eq defn.AnyClass) || (sym.owner eq defn.AnyValClass)) {
370-
assert(sym.isConstructor, s"${sym.showLocated}")
371-
select(qual, defn.ObjectClass.info.decl(sym.name).symbol)
372-
}
373-
else if (qualIsPrimitive && !symIsPrimitive || qual.tpe.widenDealias.isErasedValueType)
393+
if (qualIsPrimitive && !symIsPrimitive || qual.tpe.widenDealias.isErasedValueType)
374394
recur(box(qual))
375395
else if (!qualIsPrimitive && symIsPrimitive)
376396
recur(unbox(qual, sym.owner.typeRef))
@@ -423,6 +443,9 @@ object Erasure extends TypeTestsCasts{
423443
}
424444
}
425445

446+
/** Besides notmal typing, this method collects all arguments
447+
* to a compacted function into a single argument of array type.
448+
*/
426449
override def typedApply(tree: untpd.Apply, pt: Type)(implicit ctx: Context): Tree = {
427450
val Apply(fun, args) = tree
428451
if (fun.symbol == defn.dummyApply)
@@ -434,7 +457,13 @@ object Erasure extends TypeTestsCasts{
434457
fun1.tpe.widen match {
435458
case mt: MethodType =>
436459
val outers = outer.args(fun.asInstanceOf[tpd.Tree]) // can't use fun1 here because its type is already erased
437-
val args1 = (outers ::: args ++ protoArgs(pt)).zipWithConserve(mt.paramTypes)(typedExpr)
460+
var args0 = outers ::: args ++ protoArgs(pt)
461+
if (args0.length > MaxImplementedFunctionArity && mt.paramTypes.length == 1) {
462+
val bunchedArgs = untpd.JavaSeqLiteral(args0, TypeTree(defn.ObjectType))
463+
.withType(defn.ArrayOf(defn.ObjectType))
464+
args0 = bunchedArgs :: Nil
465+
}
466+
val args1 = args0.zipWithConserve(mt.paramTypes)(typedExpr)
438467
untpd.cpy.Apply(tree)(fun1, args1) withType mt.resultType
439468
case _ =>
440469
throw new MatchError(i"tree $tree has unexpected type of function ${fun1.tpe.widen}, was ${fun.typeOpt.widen}")
@@ -470,18 +499,36 @@ object Erasure extends TypeTestsCasts{
470499
super.typedValDef(untpd.cpy.ValDef(vdef)(
471500
tpt = untpd.TypedSplice(TypeTree(sym.info).withPos(vdef.tpt.pos))), sym)
472501

502+
/** Besides normal typing, this function also compacts anonymous functions
503+
* with more than `MaxImplementedFunctionArity` parameters to ise a single
504+
* parameter of type `[]Object`.
505+
*/
473506
override def typedDefDef(ddef: untpd.DefDef, sym: Symbol)(implicit ctx: Context) = {
474507
val restpe =
475508
if (sym.isConstructor) defn.UnitType
476509
else sym.info.resultType
510+
var vparamss1 = (outer.paramDefs(sym) ::: ddef.vparamss.flatten) :: Nil
511+
var rhs1 = ddef.rhs match {
512+
case id @ Ident(nme.WILDCARD) => untpd.TypedSplice(id.withType(restpe))
513+
case _ => ddef.rhs
514+
}
515+
if (sym.isAnonymousFunction && vparamss1.head.length > MaxImplementedFunctionArity) {
516+
val bunchedParam = ctx.newSymbol(sym, nme.ALLARGS, Flags.TermParam, JavaArrayType(defn.ObjectType))
517+
def selector(n: Int) = ref(bunchedParam)
518+
.select(defn.Array_apply)
519+
.appliedTo(Literal(Constant(n)))
520+
val paramDefs = vparamss1.head.zipWithIndex.map {
521+
case (paramDef, idx) =>
522+
assignType(untpd.cpy.ValDef(paramDef)(rhs = selector(idx)), paramDef.symbol)
523+
}
524+
vparamss1 = (tpd.ValDef(bunchedParam) :: Nil) :: Nil
525+
rhs1 = untpd.Block(paramDefs, rhs1)
526+
}
477527
val ddef1 = untpd.cpy.DefDef(ddef)(
478528
tparams = Nil,
479-
vparamss = (outer.paramDefs(sym) ::: ddef.vparamss.flatten) :: Nil,
529+
vparamss = vparamss1,
480530
tpt = untpd.TypedSplice(TypeTree(restpe).withPos(ddef.tpt.pos)),
481-
rhs = ddef.rhs match {
482-
case id @ Ident(nme.WILDCARD) => untpd.TypedSplice(id.withType(restpe))
483-
case _ => ddef.rhs
484-
})
531+
rhs = rhs1)
485532
super.typedDefDef(ddef1, sym)
486533
}
487534

tests/pos/functionXXL.scala

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
object Test {
2+
3+
val f = (x1: Int,
4+
x2: Int,
5+
x3: Int,
6+
x4: Int,
7+
x5: Int,
8+
x6: Int,
9+
x7: Int,
10+
x8: Int,
11+
x9: Int,
12+
x10: Int,
13+
x11: Int,
14+
x12: Int,
15+
x13: Int,
16+
x14: Int,
17+
x15: Int,
18+
x16: Int,
19+
x17: Int,
20+
x18: Int,
21+
x19: Int,
22+
x20: Int,
23+
x21: Int,
24+
x22: Int,
25+
x23: Int,
26+
x24: Int,
27+
x25: Int,
28+
x26: Int) => 42
29+
30+
def main(args: Array[String]) = {
31+
val g = (x1: Int,
32+
x2: Int,
33+
x3: Int,
34+
x4: Int,
35+
x5: Int,
36+
x6: Int,
37+
x7: Int,
38+
x8: Int,
39+
x9: Int,
40+
x10: Int,
41+
x11: Int,
42+
x12: Int,
43+
x13: Int,
44+
x14: Int,
45+
x15: Int,
46+
x16: Int,
47+
x17: Int,
48+
x18: Int,
49+
x19: Int,
50+
x20: Int,
51+
x21: Int,
52+
x22: Int,
53+
x23: Int,
54+
x24: Int,
55+
x25: Int,
56+
x26: Int) => f(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10,
57+
x11, x12, x13, x14, x15, x16, x17, x18, x19, x20,
58+
x21, x22, x23, x24, x25, x26)
59+
60+
61+
62+
println(f(1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
63+
11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
64+
21, 22, 23, 24, 25, 26))
65+
66+
67+
println(g(1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
68+
11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
69+
21, 22, 23, 24, 25, 26))
70+
}
71+
72+
}

tests/pos/lazyValsSepComp.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,5 @@ import dotty.tools.dotc.core.Contexts._
1212
object Foo {
1313
val definitions: Definitions = null
1414
def defn = definitions
15-
def go = defn.FunctionType(0)
15+
def go = defn.FunctionClassPerRun
1616
}

0 commit comments

Comments
 (0)