From 46eb5ea0b8ac3e80795e7f5030b128794feb692c Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Mon, 27 Oct 2014 17:18:48 +0100 Subject: Fix treatment of by name functions By-name functions like `(=> T) => T` were not treated correctly before. Witness the disabled `-Ycheck:gettersSetters` for transform/TreeCheckers in thge test suite. This commit changes the scheme how => T types are treated and fixes the problems with by-name functions. --- src/dotty/tools/dotc/TypeErasure.scala | 15 ++++- src/dotty/tools/dotc/transform/ElimByName.scala | 85 ++++++++++++------------ src/dotty/tools/dotc/transform/Erasure.scala | 8 ++- src/dotty/tools/dotc/transform/TreeChecker.scala | 2 +- test/dotc/tests.scala | 5 +- 5 files changed, 64 insertions(+), 51 deletions(-) diff --git a/src/dotty/tools/dotc/TypeErasure.scala b/src/dotty/tools/dotc/TypeErasure.scala index 851be7658..2a55d6732 100644 --- a/src/dotty/tools/dotc/TypeErasure.scala +++ b/src/dotty/tools/dotc/TypeErasure.scala @@ -111,6 +111,12 @@ object TypeErasure { erasure(tp) } + /** The erasure of a symbol's info. This is different of `erasure` in the way `ExprType`s are + * treated. `eraseInfo` maps them them to nullary method types, whereas `erasure` maps them + * to `Function0`. + */ + def eraseInfo(tp: Type)(implicit ctx: Context): Type = scalaErasureFn.eraseInfo(tp)(erasureCtx) + /** The erasure of a function result type. Differs from normal erasure in that * Unit is kept instead of being mapped to BoxedUnit. */ @@ -135,7 +141,7 @@ object TypeErasure { if ((sym eq defn.Any_asInstanceOf) || (sym eq defn.Any_isInstanceOf)) eraseParamBounds(sym.info.asInstanceOf[PolyType]) else if (sym.isAbstractType) TypeAlias(WildcardType) else if (sym.isConstructor) outer.addParam(sym.owner.asClass, erase(tp)(erasureCtx)) - else erase(tp)(erasureCtx) + else eraseInfo(tp)(erasureCtx) } def isUnboundedGeneric(tp: Type)(implicit ctx: Context) = !( @@ -263,7 +269,7 @@ class TypeErasure(isJava: Boolean, isSemi: Boolean, isConstructor: Boolean, wild case SuperType(thistpe, supertpe) => SuperType(this(thistpe), this(supertpe)) case ExprType(rt) => - MethodType(Nil, Nil, this(rt)) + defn.FunctionClass(0).typeRef case tp: TypeProxy => this(tp.underlying) case AndType(tp1, tp2) => @@ -312,6 +318,11 @@ class TypeErasure(isJava: Boolean, isSemi: Boolean, isConstructor: Boolean, wild else JavaArrayType(this(elemtp)) } + def eraseInfo(tp: Type)(implicit ctx: Context) = tp match { + case ExprType(rt) => MethodType(Nil, Nil, erasure(rt)) + case tp => erasure(tp) + } + private def eraseDerivedValueClassRef(tref: TypeRef)(implicit ctx: Context): Type = unsupported("eraseDerivedValueClass") diff --git a/src/dotty/tools/dotc/transform/ElimByName.scala b/src/dotty/tools/dotc/transform/ElimByName.scala index 1d0307398..1a37c17e4 100644 --- a/src/dotty/tools/dotc/transform/ElimByName.scala +++ b/src/dotty/tools/dotc/transform/ElimByName.scala @@ -2,32 +2,52 @@ package dotty.tools.dotc package transform import TreeTransforms._ -import core.DenotTransformers._ -import core.Symbols._ -import core.SymDenotations._ -import core.Contexts._ -import core.Types._ -import core.Flags._ -import core.Decorators._ +import core._ +import DenotTransformers._ +import Symbols._ +import SymDenotations._ +import Contexts._ +import Types._ +import Flags._ +import Decorators._ import SymUtils._ +import util.Attachment import core.StdNames.nme import ast.Trees._ -/** This phase eliminates ExprTypes `=> T` and replaces them by +object ElimByName { + val ByNameArg = new Attachment.Key[Unit] +} + +/** This phase eliminates ExprTypes `=> T` as types of function parameters, and replaces them by * nullary function types. More precisely: * - * For parameter types: + * For the types of parameter symbols: * * => T ==> () => T * - * For terms: + * Note that `=> T` types are not eliminated in MnethodTypes. This is done later at erasure. + * Terms are rewritten as follows: * * x ==> x.apply() if x is a parameter that had type => T + * + * Arguments to call-by-name parameters are translated as follows. First, the argument is + * rewritten by the rules + * * e.apply() ==> e if e.apply() is an argument to a call-by-name parameter * expr ==> () => expr if other expr is an argument to a call-by-name parameter + * + * This makes the argument compatible with a parameter type of () => T, which will be the + * formal parameter type at erasure. But to be -Ycheckable until then, any argument + * ARG rewritten by the rules above is again wrapped in an application ARG.apply(), + * labelled with an `ByNameParam` attachment. + * + * Erasure will later strip wrapped `.apply()` calls with ByNameParam attachments. + * */ class ElimByName extends MiniPhaseTransform with InfoTransformer { thisTransformer => import ast.tpd._ + import ElimByName._ override def phaseName: String = "elimByName" @@ -44,9 +64,9 @@ class ElimByName extends MiniPhaseTransform with InfoTransformer { thisTransform override def transformApply(tree: Apply)(implicit ctx: Context, info: TransformerInfo): Tree = ctx.traceIndented(s"transforming ${tree.show} at phase ${ctx.phase}", show = true) { - def transformArg(arg: Tree, formal: Type): Tree = formal match { - case _: ExprType => - arg match { + def transformArg(arg: Tree, formal: Type): Tree = formal.dealias match { + case formalExpr: ExprType => + val argFun = arg match { case Apply(Select(qual, nme.apply), Nil) if qual.tpe derivesFrom defn.FunctionClass(0) => qual case _ => @@ -54,24 +74,14 @@ class ElimByName extends MiniPhaseTransform with InfoTransformer { thisTransform ctx.owner, nme.ANON_FUN, Synthetic | Method, MethodType(Nil, Nil, arg.tpe.widen)) Closure(meth, _ => arg.changeOwner(ctx.owner, meth)) } + val argApplied = argFun.select(defn.Function0_apply).appliedToNone + argApplied.putAttachment(ByNameArg, ()) + argApplied case _ => arg } - /** Given that `info` is the possibly curried) method type of the - * tree's symbol, the method type that corresponds to the current application. - */ - def matchingMethType(info: Type, tree: Tree): Type = tree match { - case Apply(fn, _) => matchingMethType(info.resultType, fn) - case _ => info - } - - val origMethType = originalDenotation(tree.fun).info match { - case pt: PolyType => pt.resultType - case mt => mt - } - - val MethodType(_, formals) = matchingMethType(origMethType, tree.fun) + val MethodType(_, formals) = tree.fun.tpe.widen val args1 = tree.args.zipWithConserve(formals)(transformArg) cpy.Apply(tree)(tree.fun, args1) } @@ -99,22 +109,13 @@ class ElimByName extends MiniPhaseTransform with InfoTransformer { thisTransform case _ => tree } - def elimByNameParams(tp: Type)(implicit ctx: Context): Type = tp match { - case tp: PolyType => - tp.derivedPolyType(tp.paramNames, tp.paramBounds, elimByNameParams(tp.resultType)) - case tp: MethodType => - tp.derivedMethodType(tp.paramNames, tp.paramTypes mapConserve transformParamInfo, - elimByNameParams(tp.resultType)) - case _ => - tp - } + override def transformValDef(tree: ValDef)(implicit ctx: Context, info: TransformerInfo): Tree = + if (exprBecomesFunction(tree.symbol)) + cpy.ValDef(tree)(tpt = tree.tpt.withType(tree.symbol.info)) + else tree - def transformParamInfo(tp: Type)(implicit ctx: Context) = tp match { - case ExprType(rt) => defn.FunctionType(Nil, rt) + def transformInfo(tp: Type, sym: Symbol)(implicit ctx: Context): Type = tp match { + case ExprType(rt) if exprBecomesFunction(sym) => defn.FunctionType(Nil, rt) case _ => tp } - - def transformInfo(tp: Type, sym: Symbol)(implicit ctx: Context): Type = - if (exprBecomesFunction(sym)) transformParamInfo(tp) - else elimByNameParams(tp) } diff --git a/src/dotty/tools/dotc/transform/Erasure.scala b/src/dotty/tools/dotc/transform/Erasure.scala index 31397e08a..3de7ae36a 100644 --- a/src/dotty/tools/dotc/transform/Erasure.scala +++ b/src/dotty/tools/dotc/transform/Erasure.scala @@ -62,7 +62,7 @@ class Erasure extends Phase with DenotTransformer { thisTransformer => } } case ref => - ref.derivedSingleDenotation(ref.symbol, erasure(ref.info)) + ref.derivedSingleDenotation(ref.symbol, eraseInfo(ref.info)) } val eraser = new Erasure.Typer @@ -350,7 +350,11 @@ object Erasure extends TypeTestsCasts{ override def typedApply(tree: untpd.Apply, pt: Type)(implicit ctx: Context): Tree = { val Apply(fun, args) = tree - typedExpr(fun, FunProto(args, pt, this)) match { + if (tree.removeAttachment(ElimByName.ByNameArg).isDefined) { + val Select(qual, nme.apply) = fun + typedUnadapted(qual, pt) + } + else typedExpr(fun, FunProto(args, pt, this)) match { case fun1: Apply => // arguments passed in prototype were already passed fun1 case fun1 => diff --git a/src/dotty/tools/dotc/transform/TreeChecker.scala b/src/dotty/tools/dotc/transform/TreeChecker.scala index 4a7d280e5..e09a83a04 100644 --- a/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -196,7 +196,7 @@ class TreeChecker { !pt.isInstanceOf[FunProto]) assert(tree.tpe <:< pt, s"error at ${sourcePos(tree.pos)}\n" + - err.typeMismatchStr(tree.tpe, pt) + "tree = " + tree) + err.typeMismatchStr(tree.tpe, pt) + "\ntree = " + tree) tree } } diff --git a/test/dotc/tests.scala b/test/dotc/tests.scala index 4c6d004bf..519fa35ce 100644 --- a/test/dotc/tests.scala +++ b/test/dotc/tests.scala @@ -105,10 +105,7 @@ class tests extends CompilerTest { @Test def dotc_config = compileDir(dotcDir + "tools/dotc/config", twice) @Test def dotc_core = compileDir(dotcDir + "tools/dotc/core", twice)(allowDeepSubtypes) @Test def dotc_core_pickling = compileDir(dotcDir + "tools/dotc/core/pickling", twice)(allowDeepSubtypes) - - @Test def dotc_transform = compileDir(dotcDir + "tools/dotc/transform", twice)(defaultOptions ++ List("-Ycheck:pat,era,lam")) - //disabled, awaiting fix for call-by-name function types. - + @Test def dotc_transform = compileDir(dotcDir + "tools/dotc/transform", twice) @Test def dotc_parsing = compileDir(dotcDir + "tools/dotc/parsing", twice) @Test def dotc_printing = compileDir(dotcDir + "tools/dotc/printing", twice) @Test def dotc_reporting = compileDir(dotcDir + "tools/dotc/reporting", twice) -- cgit v1.2.3 From 474b2aeb608ebbdeb639ba081f413f49459b3eff Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Tue, 28 Oct 2014 15:18:24 +0100 Subject: Added a test for by name functions --- src/dotty/tools/dotc/transform/ElimByName.scala | 6 ++---- tests/pos/bynamefuns.scala | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 4 deletions(-) create mode 100644 tests/pos/bynamefuns.scala diff --git a/src/dotty/tools/dotc/transform/ElimByName.scala b/src/dotty/tools/dotc/transform/ElimByName.scala index 1a37c17e4..40fd48327 100644 --- a/src/dotty/tools/dotc/transform/ElimByName.scala +++ b/src/dotty/tools/dotc/transform/ElimByName.scala @@ -40,10 +40,8 @@ object ElimByName { * This makes the argument compatible with a parameter type of () => T, which will be the * formal parameter type at erasure. But to be -Ycheckable until then, any argument * ARG rewritten by the rules above is again wrapped in an application ARG.apply(), - * labelled with an `ByNameParam` attachment. - * - * Erasure will later strip wrapped `.apply()` calls with ByNameParam attachments. - * + * labelled with a `ByNameParam` attachment. Erasure will later strip wrapped + * `.apply()` calls with ByNameParam attachments. */ class ElimByName extends MiniPhaseTransform with InfoTransformer { thisTransformer => import ast.tpd._ diff --git a/tests/pos/bynamefuns.scala b/tests/pos/bynamefuns.scala new file mode 100644 index 000000000..5aa1df38d --- /dev/null +++ b/tests/pos/bynamefuns.scala @@ -0,0 +1,15 @@ +object Test { + + type LF = (=> Int) => Int + + def f(x: => Int) = x * x + + val x: LF = f + + def g = 3 + + f(11) + x(g) + x(11) + +} -- cgit v1.2.3