From cd28a05fa16b5b2cf3569f0ab0a8c9c685e41bf1 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 5 Dec 2013 16:51:31 +0100 Subject: Fixes to named and default arguments. Now come with test cases. --- src/dotty/tools/dotc/ast/Desugar.scala | 22 ++++---- src/dotty/tools/dotc/core/NameOps.scala | 7 ++- src/dotty/tools/dotc/typer/Applications.scala | 77 +++++++++++++-------------- src/dotty/tools/dotc/typer/EtaExpansion.scala | 13 ++++- 4 files changed, 64 insertions(+), 55 deletions(-) (limited to 'src/dotty/tools') diff --git a/src/dotty/tools/dotc/ast/Desugar.scala b/src/dotty/tools/dotc/ast/Desugar.scala index 84e321158..cd3de4fd4 100644 --- a/src/dotty/tools/dotc/ast/Desugar.scala +++ b/src/dotty/tools/dotc/ast/Desugar.scala @@ -23,7 +23,7 @@ object desugar { /** var x: Int = expr * ==> * def x: Int = expr - * def x_=($1: like (var x: Int = expr)): Unit = () + * def x_=($1: ): Unit = () */ def valDef(vdef: ValDef)(implicit ctx: Context): Tree = { val ValDef(mods, name, tpt, rhs) = vdef @@ -33,7 +33,7 @@ object desugar { // todo: copy of vdef as getter needed? // val getter = ValDef(mods, name, tpt, rhs) withPos vdef.pos ? // right now vdef maps via expandedTree to a thicket which concerns itself. - // I don't see a problem with that but if there is one we can avoid it by making a copy here. + // I don't see a problem with that but if there is one we can avoid it by making a copy here. val setterParam = makeSyntheticParameter(tpt = TypeTree()) val setterRhs = if (vdef.rhs.isEmpty) EmptyTree else unitLiteral val setter = cpy.DefDef(vdef, @@ -87,13 +87,11 @@ object desugar { cpy.DefDef(meth, mods, name, tparams1, vparamss1, tpt, rhs) } - /** The first n parameters in a possibly curried list of parameter sections */ - def take(vparamss: List[List[ValDef]], n: Int): List[List[ValDef]] = vparamss match { + /** The longest prefix of parameter lists in vparamss whose total length does not exceed `n` */ + def takeUpTo(vparamss: List[List[ValDef]], n: Int): List[List[ValDef]] = vparamss match { case vparams :: vparamss1 => val len = vparams.length - if (n == 0) Nil - else if (n < len) (vparams take n) :: Nil - else vparams :: take(vparamss1, n - len) + if (n >= len) vparams :: takeUpTo(vparamss1, n - len) else Nil case _ => Nil } @@ -101,25 +99,25 @@ object desugar { def normalizedVparamss = vparamss map (_ map (vparam => cpy.ValDef(vparam, vparam.mods, vparam.name, vparam.tpt, EmptyTree))) - def defaultGetters(vparamss: List[List[ValDef]], n: Int = 0): List[DefDef] = vparamss match { + def defaultGetters(vparamss: List[List[ValDef]], n: Int): List[DefDef] = vparamss match { case (vparam :: vparams) :: vparamss1 => def defaultGetter: DefDef = DefDef( mods = vparam.mods & AccessFlags, - name = meth.name.defaultGetterName(n + 1), + name = meth.name.defaultGetterName(n), tparams = meth.tparams, - vparamss = take(normalizedVparamss, n), + vparamss = takeUpTo(normalizedVparamss, n), tpt = TypeTree(), rhs = vparam.rhs) val rest = defaultGetters(vparams :: vparamss1, n + 1) if (vparam.rhs.isEmpty) rest else defaultGetter :: rest case Nil :: vparamss1 => - defaultGetters(vparamss1) + defaultGetters(vparamss1, n) case nil => Nil } - val defGetters = defaultGetters(vparamss) + val defGetters = defaultGetters(vparamss, 0) if (defGetters.isEmpty) meth1 else { val mods1 = meth1.mods | DefaultParameterized diff --git a/src/dotty/tools/dotc/core/NameOps.scala b/src/dotty/tools/dotc/core/NameOps.scala index c9665577d..41384e346 100644 --- a/src/dotty/tools/dotc/core/NameOps.scala +++ b/src/dotty/tools/dotc/core/NameOps.scala @@ -203,10 +203,13 @@ object NameOps { } } - /** Nominally, name$default$N, encoded for */ + /** Nominally, name$default$N, encoded for + * @param Post the parameters position. + * @note Default getter name suffixes start at 1, so `pos` has to be adjusted by +1 + */ def defaultGetterName(pos: Int): TermName = { val prefix = if (name.isConstructorName) DEFAULT_GETTER_INIT else name - prefix ++ DEFAULT_GETTER ++ pos.toString + prefix ++ DEFAULT_GETTER ++ (pos + 1).toString } /** Nominally, name from name$default$N, CONSTRUCTOR for */ diff --git a/src/dotty/tools/dotc/typer/Applications.scala b/src/dotty/tools/dotc/typer/Applications.scala index ed3759b20..3de835736 100644 --- a/src/dotty/tools/dotc/typer/Applications.scala +++ b/src/dotty/tools/dotc/typer/Applications.scala @@ -137,49 +137,48 @@ trait Applications extends Compatibility { self: Typer => /** Re-order arguments to correctly align named arguments */ def reorder[T >: Untyped](args: List[Trees.Tree[T]]): List[Trees.Tree[T]] = { - var namedToArg: Map[Name, Trees.Tree[T]] = - (for (NamedArg(name, arg1) <- args) yield (name, arg1)).toMap - - def badNamedArg(arg: untpd.Tree): Unit = { - val NamedArg(name, _) = arg - def msg = - if (methodType.paramNames contains name) - s"parameter $name of $methString is already instantiated" - else - s"$methString does not have a parameter $name" - fail(msg, arg.asInstanceOf[Arg]) - } - def recur(pnames: List[Name], args: List[Trees.Tree[T]]): List[Trees.Tree[T]] = pnames match { - case pname :: pnames1 => - namedToArg get pname match { - case Some(arg) => // there is a named argument for this parameter; pick it - namedToArg -= pname - arg :: recur(pnames1, args) - case None => - args match { - case (arg @ NamedArg(aname, _)) :: args1 => - if (namedToArg contains aname) // argument is missing, pass an empty tree - genericEmptyTree :: recur(pnames1, args) - else { // name not (or no longer) available for named arg - badNamedArg(arg) - recur(pnames1, args1) - } - case arg :: args1 => - arg :: recur(pnames1, args1) // unnamed argument; pick it - case Nil => // no more args, continue to pick up any preceding named args - recur(pnames1, args) + /** @param pnames The list of parameter names that are missing arguments + * @param args The list of arguments that are not yet passed, or that are waiting to be dropped + * @param nameToArg A map from as yet unseen names to named arguments + * @param todrop A set of names that have aready be passed as named arguments + * + * For a well-typed application we have the invariants + * + * 1. `(args diff toDrop)` can be reordered to match `pnames` + * 2. For every `(name -> arg)` in `nameToArg`, `arg` is an element of `args` + */ + def recur(pnames: List[Name], args: List[Trees.Tree[T]], + nameToArg: Map[Name, Trees.NamedArg[T]], toDrop: Set[Name]): List[Trees.Tree[T]] = pnames match { + case pname :: pnames1 if nameToArg contains pname => + // there is a named argument for this parameter; pick it + nameToArg(pname) :: recur(pnames1, args, nameToArg - pname, toDrop + pname) + case _ => + def pnamesRest = if (pnames.isEmpty) pnames else pnames.tail + args match { + case (arg @ NamedArg(aname, _)) :: args1 => + if (toDrop contains aname) // argument is already passed + recur(pnames, args1, nameToArg, toDrop - aname) + else if (nameToArg contains aname) // argument is missing, pass an empty tree + genericEmptyTree :: recur(pnamesRest, args, nameToArg, toDrop) + else { // name not (or no longer) available for named arg + def msg = + if (methodType.paramNames contains aname) + s"parameter $aname of $methString is already instantiated" + else + s"$methString does not have a parameter $aname" + fail(msg, arg.asInstanceOf[Arg]) + arg :: recur(pnamesRest, args1, nameToArg, toDrop) } + case arg :: args1 => + arg :: recur(pnamesRest, args1, nameToArg, toDrop) // unnamed argument; pick it + case Nil => // no more args, continue to pick up any preceding named args + if (pnames.isEmpty) Nil + else recur(pnamesRest, args, nameToArg, toDrop) } - case nil => // supernumerary arguments, can only be default args. - if (hasNamedArg(args)) { - val (namedArgs, otherArgs) = args partition isNamedArg - namedArgs foreach badNamedArg - otherArgs - } else args } - - recur(methodType.paramNames, args) + val nameAssocs = for (arg @ NamedArg(name, _) <- args) yield (name, arg) + recur(methodType.paramNames, args, nameAssocs.toMap, Set()) } /** Splice new method reference into existing application */ diff --git a/src/dotty/tools/dotc/typer/EtaExpansion.scala b/src/dotty/tools/dotc/typer/EtaExpansion.scala index 2eb7486eb..8781eea4b 100644 --- a/src/dotty/tools/dotc/typer/EtaExpansion.scala +++ b/src/dotty/tools/dotc/typer/EtaExpansion.scala @@ -42,6 +42,15 @@ object EtaExpansion { tree } + /** Lift a function argument, stripping any NamedArg wrapper */ + def liftArg(defs: mutable.ListBuffer[Tree], arg: Tree, prefix: String = "")(implicit ctx: Context): Tree = { + val arg1 = arg match { + case NamedArg(_, arg1) => arg1 + case arg => arg + } + lift(defs, arg1, prefix) + } + /** Lift arguments that are not-idempotent into ValDefs in buffer `defs` * and replace by the idents of so created ValDefs. */ @@ -50,10 +59,10 @@ object EtaExpansion { case MethodType(paramNames, paramTypes) => (args, paramNames, paramTypes).zipped map { (arg, name, tp) => if (tp isRef defn.ByNameParamClass) arg - else lift(defs, arg, if (name contains '$') "" else name.toString) + else liftArg(defs, arg, if (name contains '$') "" else name.toString) } case _ => - args map (lift(defs, _)) + args map (liftArg(defs, _)) } /** Lift out function prefix and all arguments from application -- cgit v1.2.3