From b945a1bba85435cabea8e433d5d939da6c0601ca Mon Sep 17 00:00:00 2001 From: odersky Date: Wed, 9 Mar 2022 13:48:07 +0100 Subject: [PATCH] Adapt function arguments to n-ary prototype If a function argument is a synthetic term of the form x$1 => x$1 match case (a_1, ..., a_n) => e and the expected type is an n-ary function type, rewrite the argument to (a_1, ..., a_n) => e Fixes #14626. The example in #14626 now compiles without an implicit tupling conversion. Such a conversion was inserted before in 2.13, 3.0 and 3.1. --- .../src/dotty/tools/dotc/ast/Desugar.scala | 14 ++++ .../src/dotty/tools/dotc/typer/Typer.scala | 68 ++++++++++++------- tests/pos/i14626.scala | 3 + 3 files changed, 59 insertions(+), 26 deletions(-) create mode 100644 tests/pos/i14626.scala diff --git a/compiler/src/dotty/tools/dotc/ast/Desugar.scala b/compiler/src/dotty/tools/dotc/ast/Desugar.scala index 3578e4e0f10b..0f7eb0eda552 100644 --- a/compiler/src/dotty/tools/dotc/ast/Desugar.scala +++ b/compiler/src/dotty/tools/dotc/ast/Desugar.scala @@ -1431,6 +1431,20 @@ object desugar { Function(param :: Nil, Block(vdefs, body)) } + /** Convert a tuple pattern with given `elems` to a sequence of `ValDefs`, + * skipping elements that are not convertible. + */ + def patternsToParams(elems: List[Tree])(using Context): List[ValDef] = + def toParam(elem: Tree, tpt: Tree): Tree = + elem match + case Annotated(elem1, _) => toParam(elem1, tpt) + case Typed(elem1, tpt1) => toParam(elem1, tpt1) + case Ident(id: TermName) => ValDef(id, tpt, EmptyTree).withFlags(Param) + case _ => EmptyTree + elems.map(param => toParam(param, TypeTree()).withSpan(param.span)).collect { + case vd: ValDef => vd + } + def makeContextualFunction(formals: List[Tree], body: Tree, isErased: Boolean)(using Context): Function = { val mods = if (isErased) Given | Erased else Given val params = makeImplicitParameters(formals, mods) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index e5aefd8a13d9..dbc6f700157d 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1424,33 +1424,49 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer param.tpt.isEmpty || argType.widenExpr <:< typedAheadType(param.tpt).tpe } - val desugared = - if (protoFormals.length == 1 && params.length != 1 && ptIsCorrectProduct(protoFormals.head)) { - val isGenericTuple = - protoFormals.head.derivesFrom(defn.TupleClass) - && !defn.isTupleClass(protoFormals.head.typeSymbol) - desugar.makeTupledFunction(params, fnBody, isGenericTuple) - } - else { - val inferredParams: List[untpd.ValDef] = - for ((param, i) <- params.zipWithIndex) yield - if (!param.tpt.isEmpty) param - else - val formal = protoFormal(i) - val knownFormal = isFullyDefined(formal, ForceDegree.failBottom) - val paramType = - if knownFormal then formal - else inferredFromTarget(param, formal, calleeType, paramIndex) - .orElse(errorType(AnonymousFunctionMissingParamType(param, tree, formal), param.srcPos)) - val paramTpt = untpd.TypedSplice( - (if knownFormal then InferredTypeTree() else untpd.TypeTree()) - .withType(paramType.translateFromRepeated(toArray = false)) - .withSpan(param.span.endPos) - ) - cpy.ValDef(param)(tpt = paramTpt) - desugar.makeClosure(inferredParams, fnBody, resultTpt, isContextual, tree.span) - } + var desugared: untpd.Tree = EmptyTree + if protoFormals.length == 1 && params.length != 1 && ptIsCorrectProduct(protoFormals.head) then + val isGenericTuple = + protoFormals.head.derivesFrom(defn.TupleClass) + && !defn.isTupleClass(protoFormals.head.typeSymbol) + desugared = desugar.makeTupledFunction(params, fnBody, isGenericTuple) + else if protoFormals.length > 1 && params.length == 1 then + def isParamRef(scrut: untpd.Tree): Boolean = scrut match + case untpd.Annotated(scrut1, _) => isParamRef(scrut1) + case untpd.Ident(id) => id == params.head.name + fnBody match + case untpd.Match(scrut, untpd.CaseDef(untpd.Tuple(elems), untpd.EmptyTree, rhs) :: Nil) + if scrut.span.isSynthetic && isParamRef(scrut) && elems.hasSameLengthAs(protoFormals) => + // If `pt` is N-ary function type, convert synthetic lambda + // x$1 => x$1 match case (a1, ..., aN) => e + // to + // (a1, ..., aN) => e + val params1 = desugar.patternsToParams(elems) + if params1.hasSameLengthAs(elems) then + desugared = cpy.Function(tree)(params1, rhs) + case _ => + + if desugared.isEmpty then + val inferredParams: List[untpd.ValDef] = + for ((param, i) <- params.zipWithIndex) yield + if (!param.tpt.isEmpty) param + else + val formal = protoFormal(i) + val knownFormal = isFullyDefined(formal, ForceDegree.failBottom) + val paramType = + if knownFormal then formal + else inferredFromTarget(param, formal, calleeType, paramIndex) + .orElse(errorType(AnonymousFunctionMissingParamType(param, tree, formal), param.srcPos)) + val paramTpt = untpd.TypedSplice( + (if knownFormal then InferredTypeTree() else untpd.TypeTree()) + .withType(paramType.translateFromRepeated(toArray = false)) + .withSpan(param.span.endPos) + ) + cpy.ValDef(param)(tpt = paramTpt) + desugared = desugar.makeClosure(inferredParams, fnBody, resultTpt, isContextual, tree.span) + typed(desugared, pt) + .showing(i"desugared fun $tree --> $desugared with pt = $pt", typr) } def typedClosure(tree: untpd.Closure, pt: Type)(using Context): Tree = { diff --git a/tests/pos/i14626.scala b/tests/pos/i14626.scala new file mode 100644 index 000000000000..39756a15c42b --- /dev/null +++ b/tests/pos/i14626.scala @@ -0,0 +1,3 @@ +import language.`future` +def Test = + for (a, b) <- List("a","b","c").lazyZip(List(1,2,3)) do println(s"$a$b") \ No newline at end of file