From b94e6ea5a54d38470e5793c0084785e2d2f9a819 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 30 Nov 2016 14:34:17 +0100 Subject: 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. --- .../src/dotty/tools/dotc/core/Definitions.scala | 13 ++-- compiler/src/dotty/tools/dotc/core/StdNames.scala | 1 + .../src/dotty/tools/dotc/core/TypeErasure.scala | 17 ++++- .../src/dotty/tools/dotc/transform/Erasure.scala | 77 +++++++++++++++++----- 4 files changed, 86 insertions(+), 22 deletions(-) (limited to 'compiler') diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index e1a87d1f7..29b7bb60a 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -457,6 +457,9 @@ class Definitions { def PartialFunctionClass(implicit ctx: Context) = PartialFunctionType.symbol.asClass lazy val AbstractPartialFunctionType: TypeRef = ctx.requiredClassRef("scala.runtime.AbstractPartialFunction") def AbstractPartialFunctionClass(implicit ctx: Context) = AbstractPartialFunctionType.symbol.asClass + lazy val FunctionXXLType: TypeRef = ctx.requiredClassRef("scala.FunctionXXL") + def FunctionXXLClass(implicit ctx: Context) = FunctionXXLType.symbol.asClass + lazy val SymbolType: TypeRef = ctx.requiredClassRef("scala.Symbol") def SymbolClass(implicit ctx: Context) = SymbolType.symbol.asClass lazy val DynamicType: TypeRef = ctx.requiredClassRef("scala.Dynamic") @@ -645,6 +648,9 @@ class Definitions { private lazy val ImplementedFunctionType = mkArityArray("scala.Function", MaxImplementedFunctionArity, 0) def FunctionClassPerRun = new PerRun[Array[Symbol]](implicit ctx => ImplementedFunctionType.map(_.symbol.asClass)) + lazy val TupleType = mkArityArray("scala.Tuple", MaxTupleArity, 2) + lazy val ProductNType = mkArityArray("scala.Product", MaxTupleArity, 0) + def FunctionClass(n: Int)(implicit ctx: Context) = if (n < MaxImplementedFunctionArity) FunctionClassPerRun()(ctx)(n) else ctx.requiredClass("scala.Function" + n.toString) @@ -652,10 +658,7 @@ class Definitions { lazy val Function0_applyR = ImplementedFunctionType(0).symbol.requiredMethodRef(nme.apply) def Function0_apply(implicit ctx: Context) = Function0_applyR.symbol - lazy val TupleType = mkArityArray("scala.Tuple", MaxTupleArity, 2) - lazy val ProductNType = mkArityArray("scala.Product", MaxTupleArity, 0) - - def FunctionType(n: Int): TypeRef = + def FunctionType(n: Int)(implicit ctx: Context): TypeRef = if (n < MaxImplementedFunctionArity) ImplementedFunctionType(n) else FunctionClass(n).typeRef @@ -680,6 +683,8 @@ class Definitions { tp.derivesFrom(NothingClass) || tp.derivesFrom(NullClass) def isFunctionClass(cls: Symbol) = isVarArityClass(cls, tpnme.Function) + def isUnimplementedFunctionClass(cls: Symbol) = + isFunctionClass(cls) && cls.name.functionArity >= MaxImplementedFunctionArity def isAbstractFunctionClass(cls: Symbol) = isVarArityClass(cls, tpnme.AbstractFunction) def isTupleClass(cls: Symbol) = isVarArityClass(cls, tpnme.Tuple) def isProductClass(cls: Symbol) = isVarArityClass(cls, tpnme.Product) diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index c2a14b36f..741ff8b1f 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -265,6 +265,7 @@ object StdNames { val THIS: N = "_$this" val TRAIT_CONSTRUCTOR: N = "$init$" val U2EVT: N = "u2evt$" + val ALLARGS: N = "$allArgs" final val Nil: N = "Nil" final val Predef: N = "Predef" diff --git a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala index abbacee49..57397a8bc 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeErasure.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeErasure.scala @@ -7,6 +7,7 @@ import Uniques.unique import dotc.transform.ExplicitOuter._ import dotc.transform.ValueClasses._ import util.DotClass +import Definitions.MaxImplementedFunctionArity /** Erased types are: * @@ -38,7 +39,10 @@ object TypeErasure { case _: ErasedValueType => true case tp: TypeRef => - tp.symbol.isClass && tp.symbol != defn.AnyClass && tp.symbol != defn.ArrayClass + val sym = tp.symbol + sym.isClass && + sym != defn.AnyClass && sym != defn.ArrayClass && + !defn.isUnimplementedFunctionClass(sym) case _: TermRef => true case JavaArrayType(elem) => @@ -176,8 +180,13 @@ object TypeErasure { else if (sym.isAbstractType) TypeAlias(WildcardType) else if (sym.isConstructor) outer.addParam(sym.owner.asClass, erase(tp)(erasureCtx)) else erase.eraseInfo(tp, sym)(erasureCtx) match { - case einfo: MethodType if sym.isGetter && einfo.resultType.isRef(defn.UnitClass) => - MethodType(Nil, defn.BoxedUnitType) + case einfo: MethodType => + if (sym.isGetter && einfo.resultType.isRef(defn.UnitClass)) + MethodType(Nil, defn.BoxedUnitType) + else if (sym.isAnonymousFunction && einfo.paramTypes.length > MaxImplementedFunctionArity) + MethodType(nme.ALLARGS :: Nil, JavaArrayType(defn.ObjectType) :: Nil, einfo.resultType) + else + einfo case einfo => einfo } @@ -317,6 +326,7 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean * - For a term ref p.x, the type # x. * - For a typeref scala.Any, scala.AnyVal or scala.Singleton: |java.lang.Object| * - For a typeref scala.Unit, |scala.runtime.BoxedUnit|. + * - For a typeref scala.FunctionN, where N > MaxImplementedFunctionArity, scala.FunctionXXL * - For a typeref P.C where C refers to a class, # C. * - For a typeref P.C where C refers to an alias type, the erasure of C's alias. * - 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 if (!sym.isClass) this(tp.info) else if (semiEraseVCs && isDerivedValueClass(sym)) eraseDerivedValueClassRef(tp) else if (sym == defn.ArrayClass) apply(tp.appliedTo(TypeBounds.empty)) // i966 shows that we can hit a raw Array type. + else if (defn.isUnimplementedFunctionClass(sym)) defn.FunctionXXLType else eraseNormalClassRef(tp) case tp: RefinedType => val parent = tp.parent diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index 069176111..5dd2e512b 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -13,6 +13,7 @@ import core.StdNames._ import core.NameOps._ import core.Decorators._ import core.Constants._ +import core.Definitions._ import typer.NoChecking import typer.ProtoTypes._ import typer.ErrorReporting._ @@ -36,9 +37,17 @@ class Erasure extends Phase with DenotTransformer { thisTransformer => def transform(ref: SingleDenotation)(implicit ctx: Context): SingleDenotation = ref match { case ref: SymDenotation => + def isCompacted(sym: Symbol) = + sym.isAnonymousFunction && { + sym.info(ctx.withPhase(ctx.phase.next)) match { + case MethodType(nme.ALLARGS :: Nil, _) => true + case _ => false + } + } + assert(ctx.phase == this, s"transforming $ref at ${ctx.phase}") if (ref.symbol eq defn.ObjectClass) { - // Aftre erasure, all former Any members are now Object members + // After erasure, all former Any members are now Object members val ClassInfo(pre, _, ps, decls, selfInfo) = ref.info val extendedScope = decls.cloneScope for (decl <- defn.AnyClass.classInfo.decls) @@ -59,7 +68,10 @@ class Erasure extends Phase with DenotTransformer { thisTransformer => val oldInfo = ref.info val newInfo = transformInfo(ref.symbol, oldInfo) val oldFlags = ref.flags - val newFlags = ref.flags &~ Flags.HasDefaultParams // HasDefaultParams needs to be dropped because overriding might become overloading + val newFlags = + if (oldSymbol.is(Flags.TermParam) && isCompacted(oldSymbol.owner)) oldFlags &~ Flags.Param + else oldFlags &~ Flags.HasDefaultParams // HasDefaultParams needs to be dropped because overriding might become overloading + // TODO: define derivedSymDenotation? if ((oldSymbol eq newSymbol) && (oldOwner eq newOwner) && (oldInfo eq newInfo) && (oldFlags == newFlags)) ref else { @@ -331,8 +343,20 @@ object Erasure extends TypeTestsCasts{ * e.m -> e.[]m if `m` is an array operation other than `clone`. */ override def typedSelect(tree: untpd.Select, pt: Type)(implicit ctx: Context): Tree = { - val sym = tree.symbol - assert(sym.exists, tree.show) + val oldSym = tree.symbol + assert(oldSym.exists) + val oldOwner = oldSym.owner + val owner = + if ((oldOwner eq defn.AnyClass) || (oldOwner eq defn.AnyValClass)) { + assert(oldSym.isConstructor, s"${oldSym.showLocated}") + defn.ObjectClass + } + else if (defn.isUnimplementedFunctionClass(oldOwner)) + defn.FunctionXXLClass + else + oldOwner + val sym = if (owner eq oldOwner) oldSym else owner.info.decl(oldSym.name).symbol + assert(sym.exists, owner) def select(qual: Tree, sym: Symbol): Tree = { val name = tree.typeOpt match { @@ -366,11 +390,7 @@ object Erasure extends TypeTestsCasts{ def recur(qual: Tree): Tree = { val qualIsPrimitive = qual.tpe.widen.isPrimitiveValueType val symIsPrimitive = sym.owner.isPrimitiveValueClass - if ((sym.owner eq defn.AnyClass) || (sym.owner eq defn.AnyValClass)) { - assert(sym.isConstructor, s"${sym.showLocated}") - select(qual, defn.ObjectClass.info.decl(sym.name).symbol) - } - else if (qualIsPrimitive && !symIsPrimitive || qual.tpe.widenDealias.isErasedValueType) + if (qualIsPrimitive && !symIsPrimitive || qual.tpe.widenDealias.isErasedValueType) recur(box(qual)) else if (!qualIsPrimitive && symIsPrimitive) recur(unbox(qual, sym.owner.typeRef)) @@ -423,6 +443,9 @@ object Erasure extends TypeTestsCasts{ } } + /** Besides notmal typing, this method collects all arguments + * to a compacted function into a single argument of array type. + */ override def typedApply(tree: untpd.Apply, pt: Type)(implicit ctx: Context): Tree = { val Apply(fun, args) = tree if (fun.symbol == defn.dummyApply) @@ -434,7 +457,13 @@ object Erasure extends TypeTestsCasts{ fun1.tpe.widen match { case mt: MethodType => val outers = outer.args(fun.asInstanceOf[tpd.Tree]) // can't use fun1 here because its type is already erased - val args1 = (outers ::: args ++ protoArgs(pt)).zipWithConserve(mt.paramTypes)(typedExpr) + var args0 = outers ::: args ++ protoArgs(pt) + if (args0.length > MaxImplementedFunctionArity && mt.paramTypes.length == 1) { + val bunchedArgs = untpd.JavaSeqLiteral(args0, TypeTree(defn.ObjectType)) + .withType(defn.ArrayOf(defn.ObjectType)) + args0 = bunchedArgs :: Nil + } + val args1 = args0.zipWithConserve(mt.paramTypes)(typedExpr) untpd.cpy.Apply(tree)(fun1, args1) withType mt.resultType case _ => 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{ super.typedValDef(untpd.cpy.ValDef(vdef)( tpt = untpd.TypedSplice(TypeTree(sym.info).withPos(vdef.tpt.pos))), sym) + /** Besides normal typing, this function also compacts anonymous functions + * with more than `MaxImplementedFunctionArity` parameters to ise a single + * parameter of type `[]Object`. + */ override def typedDefDef(ddef: untpd.DefDef, sym: Symbol)(implicit ctx: Context) = { val restpe = if (sym.isConstructor) defn.UnitType else sym.info.resultType + var vparamss1 = (outer.paramDefs(sym) ::: ddef.vparamss.flatten) :: Nil + var rhs1 = ddef.rhs match { + case id @ Ident(nme.WILDCARD) => untpd.TypedSplice(id.withType(restpe)) + case _ => ddef.rhs + } + if (sym.isAnonymousFunction && vparamss1.head.length > MaxImplementedFunctionArity) { + val bunchedParam = ctx.newSymbol(sym, nme.ALLARGS, Flags.TermParam, JavaArrayType(defn.ObjectType)) + def selector(n: Int) = ref(bunchedParam) + .select(defn.Array_apply) + .appliedTo(Literal(Constant(n))) + val paramDefs = vparamss1.head.zipWithIndex.map { + case (paramDef, idx) => + assignType(untpd.cpy.ValDef(paramDef)(rhs = selector(idx)), paramDef.symbol) + } + vparamss1 = (tpd.ValDef(bunchedParam) :: Nil) :: Nil + rhs1 = untpd.Block(paramDefs, rhs1) + } val ddef1 = untpd.cpy.DefDef(ddef)( tparams = Nil, - vparamss = (outer.paramDefs(sym) ::: ddef.vparamss.flatten) :: Nil, + vparamss = vparamss1, tpt = untpd.TypedSplice(TypeTree(restpe).withPos(ddef.tpt.pos)), - rhs = ddef.rhs match { - case id @ Ident(nme.WILDCARD) => untpd.TypedSplice(id.withType(restpe)) - case _ => ddef.rhs - }) + rhs = rhs1) super.typedDefDef(ddef1, sym) } -- cgit v1.2.3