diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 7cdcf767f8a2..52ce035ed327 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -3286,7 +3286,7 @@ object Types { * `owningTree` and `owner` are used to determine whether a type-variable can be instantiated * at some given point. See `Inferencing#interpolateUndetVars`. */ - final class TypeVar(val origin: TypeParamRef, creatorState: TyperState, val bindingTree: untpd.Tree, val owner: Symbol) extends CachedProxyType with ValueType { + final class TypeVar(val origin: TypeParamRef, creatorState: TyperState, var bindingTree: untpd.Tree, val owner: Symbol) extends CachedProxyType with ValueType { /** The permanent instance type of the variable, or NoType is none is given yet */ private[this] var myInst: Type = NoType diff --git a/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala b/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala index be84427a83a0..2f39b07aa998 100644 --- a/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala +++ b/compiler/src/dotty/tools/dotc/typer/EtaExpansion.scala @@ -167,26 +167,45 @@ object EtaExpansion extends LiftImpure { * * { val xs = es; expr } * - * If xarity matches the number of parameters in `mt`, the eta-expansion is + * The result of the eta-expansion is either (1) * * { val xs = es; (x1, ..., xn) => expr(x1, ..., xn) } * - * Note that the function value's parameters are untyped, hence the type will - * be supplied by the environment (or if missing be supplied by the target - * method as a fallback). On the other hand, if `xarity` is different from - * the number of parameters in `mt`, then we cannot propagate parameter types - * from the expected type, and we fallback to using the method's original - * parameter types instead. + * or (2) * - * In either case, the result is an untyped tree, with `es` and `expr` as typed splices. + * { val xs = es; (x1: T1, ..., xn: Tn) => expr(x1, ..., xn) } + * + * or (3) + * + * { val xs = es; (x1: T1, ..., xn: Tn) => expr(x1, ..., xn) _ } + * + * where `T1, ..., Tn` are the paremeter types of the expanded method. + * + * Case (3) applies if the method is curried, i.e. its result type is again a method + * type. Case (2) applies if the expected arity of the function type `xarity` differs + * from the number of parameters in `mt`. Case (1) applies if `mt` is uncurried + * and its number of parameters equals `xarity`. In this case we can always infer + * the parameter types later from the callee even if parameter types could not be + * inferred from the expected type. Hence, we lose nothing by omitting parameter types + * in the eta expansion. On the other hand omitting these parameters keeps the possibility + * open that different parameters are inferred from the expected type, so we keep + * more options open. + * + * In each case, the result is an untyped tree, with `es` and `expr` as typed splices. + * + * F[V](x) ==> (x => F[X]) */ def etaExpand(tree: Tree, mt: MethodType, xarity: Int)(implicit ctx: Context): untpd.Tree = { import untpd._ assert(!ctx.isAfterTyper) val defs = new mutable.ListBuffer[tpd.Tree] val lifted: Tree = TypedSplice(liftApp(defs, tree)) + val isLastApplication = mt.resultType match { + case rt: MethodType => rt.isImplicitMethod + case _ => true + } val paramTypes: List[Tree] = - if (mt.paramInfos.length == xarity) mt.paramInfos map (_ => TypeTree()) + if (isLastApplication && mt.paramInfos.length == xarity) mt.paramInfos map (_ => TypeTree()) else mt.paramInfos map TypeTree val params = (mt.paramNames, paramTypes).zipped.map((name, tpe) => ValDef(name, tpe, EmptyTree).withFlags(Synthetic | Param).withPos(tree.pos.startPos)) @@ -194,10 +213,7 @@ object EtaExpansion extends LiftImpure { if (mt.paramInfos.nonEmpty && mt.paramInfos.last.isRepeatedParam) ids = ids.init :+ repeated(ids.last) var body: Tree = Apply(lifted, ids) - mt.resultType match { - case rt: MethodType if !rt.isImplicitMethod => body = PostfixOp(body, Ident(nme.WILDCARD)) - case _ => - } + if (!isLastApplication) body = PostfixOp(body, Ident(nme.WILDCARD)) val fn = untpd.Function(params, body) if (defs.nonEmpty) untpd.Block(defs.toList map (untpd.TypedSplice(_)), fn) else fn } diff --git a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala index 9b5d9d29c846..9219bcdd8979 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inferencing.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inferencing.scala @@ -166,7 +166,7 @@ object Inferencing { case Apply(fn, _) => boundVars(fn, acc) case TypeApply(fn, targs) => val tvars = targs.tpes.collect { - case tvar: TypeVar if !tvar.isInstantiated => tvar + case tvar: TypeVar if !tvar.isInstantiated && targs.contains(tvar.bindingTree) => tvar } boundVars(fn, acc ::: tvars) case Select(pre, _) => boundVars(pre, acc) @@ -394,6 +394,20 @@ trait Inferencing { this: Typer => } if (constraint.uninstVars exists qualifies) interpolate() } + + /** The uninstantiated type variables introduced somehwere in `tree` */ + def uninstBoundVars(tree: Tree)(implicit ctx: Context): List[TypeVar] = { + val buf = new mutable.ListBuffer[TypeVar] + tree.foreachSubTree { + case TypeApply(_, args) => + args.tpes.foreach { + case tv: TypeVar if !tv.isInstantiated && tree.contains(tv.bindingTree) => buf += tv + case _ => + } + case _ => + } + buf.toList + } } /** An enumeration controlling the degree of forcing in "is-dully-defined" checks. */ diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index fc56b72eef12..750bbcabb145 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1970,7 +1970,7 @@ class Typer extends Namer if (!tree.denot.isOverloaded) { // for overloaded trees: resolve overloading before simplifying if (tree.isDef) interpolateUndetVars(tree, tree.symbol, pt) - else if (!tree.tpe.widen.isInstanceOf[LambdaType]) interpolateUndetVars(tree, NoSymbol, pt) + else if (!tree.tpe.widen.isInstanceOf[MethodOrPoly]) interpolateUndetVars(tree, NoSymbol, pt) tree.overwriteType(tree.tpe.simplified) } adaptInterpolated(tree, pt) @@ -2227,8 +2227,18 @@ class Typer extends Namer if (arity >= 0 && !tree.symbol.isConstructor && !ctx.mode.is(Mode.Pattern) && - !(isSyntheticApply(tree) && !isExpandableApply)) - typed(etaExpand(tree, wtp, arity), pt) + !(isSyntheticApply(tree) && !isExpandableApply)) { + // Eta expansion interacts in tricky ways with type variable instantiation + // because it can extend the region where type variables are bound (and therefore may not + // be interpolated). To avoid premature interpolations, we need to extend the + // bindingTree of variables as we go along. Test case in pos/i3945.scala. + val boundtvs = uninstBoundVars(tree) + val uexpanded = etaExpand(tree, wtp, arity) + boundtvs.foreach(_.bindingTree = uexpanded) // make boundtvs point to uexpanded so that they are _not_ interpolated + val texpanded = typedUnadapted(uexpanded, pt) + boundtvs.foreach(_.bindingTree = texpanded) // make boundtvs point to texpanded so that they _can_ be interpolated + adapt(texpanded, pt) + } else if (wtp.paramInfos.isEmpty && isAutoApplied(tree.symbol)) adaptInterpolated(tpd.Apply(tree, Nil), pt) else if (wtp.isImplicitMethod) diff --git a/tests/pos/i3945.scala b/tests/pos/i3945.scala new file mode 100644 index 000000000000..2533a67ca8d9 --- /dev/null +++ b/tests/pos/i3945.scala @@ -0,0 +1,19 @@ +object Test { + def int[A](k: String => A)(s: String)(x: Int): A = ??? + + // composing directly: ok in scalac, now also in dotc + val c: (String => String) => (String) => (Int) => (Int) => String = (int[Int => String](_)).compose(int[String](_)) + + // unwrapping composition: ok in scalac, ok in dotc + val q: (String => Int => String) => (String) => (Int) => (Int => String) = int[Int => String] + val p: (String => String) => (String) => (Int) => String = int + val c2: (String => String) => (String) => (Int) => (Int) => String = q.compose(p) + + class B + class C extends B + implicit def iC: C => Unit = ??? + + // making sure A is not instantiated before implicit search + def f[A](k: String => A)(s: String)(x: Int)(implicit y: A => Unit): A = ??? + val r: (String => C) => (String) => (Int) => B = f +}