From 61d66c35017fe1797c0d80ae5ea60adc6164b20d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 10 Oct 2020 19:44:35 +0200 Subject: [PATCH 1/3] Avoid eta contractions when printing Don't eta contract if the result type of a lambda is an applied type that's treated specially. --- .../tools/dotc/printing/RefinedPrinter.scala | 23 ++++++++++++------- tests/neg/i9958.check | 4 ++++ tests/neg/i9958.scala | 1 + 3 files changed, 20 insertions(+), 8 deletions(-) create mode 100644 tests/neg/i9958.check create mode 100644 tests/neg/i9958.scala diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 620a5528e7f5..812c94bd0f2c 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -192,26 +192,33 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { } } - homogenize(tp) match { + def appliedText(tp: Type): Text = tp match case tp @ AppliedType(tycon, args) => val cls = tycon.typeSymbol - if (tycon.isRepeatedParam) toTextLocal(args.head) ~ "*" - else if (defn.isFunctionClass(cls)) toTextFunction(args, cls.name.isContextFunction, cls.name.isErasedFunction) - else if (tp.tupleArity >= 2 && !printDebug) toTextTuple(tp.tupleElementTypes) - else if (isInfixType(tp)) { + if tycon.isRepeatedParam then toTextLocal(args.head) ~ "*" + else if defn.isFunctionClass(cls) then toTextFunction(args, cls.name.isContextFunction, cls.name.isErasedFunction) + else if tp.tupleArity >= 2 && !printDebug then toTextTuple(tp.tupleElementTypes) + else if isInfixType(tp) then val l :: r :: Nil = args val opName = tyconName(tycon) toTextInfixType(tyconName(tycon), l, r) { simpleNameString(tycon.typeSymbol) } - } - else super.toText(tp) + else Str("") + case _ => + Str("") + homogenize(tp) match { + case tp: AppliedType => + val refined = appliedText(tp) + if refined.isEmpty then super.toText(tp) else refined // Since RefinedPrinter, unlike PlainPrinter, can output right-associative type-operators, we must override handling // of AndType and OrType to account for associativity case AndType(tp1, tp2) => toTextInfixType(tpnme.raw.AMP, tp1, tp2) { toText(tpnme.raw.AMP) } case OrType(tp1, tp2) => toTextInfixType(tpnme.raw.BAR, tp1, tp2) { toText(tpnme.raw.BAR) } - case EtaExpansion(tycon) if !printDebug => + case tp @ EtaExpansion(tycon) + if !printDebug && appliedText(tp.asInstanceOf[HKLambda].resType).isEmpty => + // don't eta contract if the application would be printed specially toText(tycon) case tp: RefinedType if defn.isFunctionType(tp) && !printDebug => toTextDependentFunction(tp.refinedInfo.asInstanceOf[MethodType]) diff --git a/tests/neg/i9958.check b/tests/neg/i9958.check new file mode 100644 index 000000000000..46b9f8b9fbea --- /dev/null +++ b/tests/neg/i9958.check @@ -0,0 +1,4 @@ +-- Error: tests/neg/i9958.scala:1:30 ----------------------------------------------------------------------------------- +1 |val x = summon[[X] =>> (X, X)] // error + | ^ + | no implicit argument of type [X] =>> (X, X) was found for parameter x of method summon in object DottyPredef diff --git a/tests/neg/i9958.scala b/tests/neg/i9958.scala new file mode 100644 index 000000000000..e85520d7fe83 --- /dev/null +++ b/tests/neg/i9958.scala @@ -0,0 +1 @@ +val x = summon[[X] =>> (X, X)] // error From 5e564163a2b242ff2bec9564eb02045703f32767 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sun, 11 Oct 2020 13:28:54 +0200 Subject: [PATCH 2/3] Fix #9971: Fix EtaExpansion unapply method The unapply method of EtaExpansion was missing several necessary conditions, which are now added: - parameters and arguments must have same length - bounds must conform --- .../tools/dotc/core/TypeApplications.scala | 41 ++++++++++++++----- tests/neg/i9958.check | 5 +++ tests/neg/i9958.scala | 8 ++++ 3 files changed, 44 insertions(+), 10 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeApplications.scala b/compiler/src/dotty/tools/dotc/core/TypeApplications.scala index 3732968aa884..e0509852d0a3 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeApplications.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeApplications.scala @@ -26,25 +26,46 @@ object TypeApplications { /** Extractor for * - * [v1 X1: B1, ..., vn Xn: Bn] -> C[X1, ..., Xn] + * [X1: B1, ..., Xn: Bn] -> C[X1, ..., Xn] * - * where v1, ..., vn and B1, ..., Bn are the variances and bounds of the type parameters - * of the class C. + * where B1, ..., Bn are bounds of the type parameters of the class C. * * @param tycon C */ - object EtaExpansion { - def apply(tycon: Type)(using Context): Type = { + object EtaExpansion: + + def apply(tycon: Type)(using Context): Type = assert(tycon.typeParams.nonEmpty, tycon) tycon.EtaExpand(tycon.typeParamSymbols) - } - def unapply(tp: Type)(using Context): Option[Type] = tp match { + /** Test that the parameter bounds in a hk type lambda `[X1,...,Xn] => C[X1, ..., Xn]` + * contain the bounds of the type parameters of `C`. This is necessary to be able to + * contract the hk lambda to `C`. + */ + private def weakerBounds(tp: HKTypeLambda, tparams: List[ParamInfo])(using Context): Boolean = + val onlyEmptyBounds = tp.typeParams.forall(_.paramInfo == TypeBounds.empty) + onlyEmptyBounds + // Note: this pre-test helps efficiency. It is also necessary since in some cases + // tparams is empty. This can happen when we change the owners of inlined local + // classes in mapSymbols. See pos/reference/delegates.scala for an example. + // In this case, we can still return true if we know that the hk lambda bounds + // are empty anyway. + || { + val paramRefs = tparams.map(_.paramRef) + tp.typeParams.corresponds(tparams) { (param1, param2) => + param2.paramInfo <:< param1.paramInfo.substParams(tp, paramRefs) + } + } + + def unapply(tp: Type)(using Context): Option[Type] = tp match case tp @ HKTypeLambda(tparams, AppliedType(fn: Type, args)) - if args.lazyZip(tparams).forall((arg, tparam) => arg == tparam.paramRef) => Some(fn) + if fn.typeSymbol.isClass + && tparams.hasSameLengthAs(args) + && args.lazyZip(tparams).forall((arg, tparam) => arg == tparam.paramRef) + && weakerBounds(tp, fn.typeParams) => Some(fn) case _ => None - } - } + + end EtaExpansion /** Adapt all arguments to possible higher-kinded type parameters using etaExpandIfHK */ diff --git a/tests/neg/i9958.check b/tests/neg/i9958.check index 46b9f8b9fbea..23fd7a6195e3 100644 --- a/tests/neg/i9958.check +++ b/tests/neg/i9958.check @@ -2,3 +2,8 @@ 1 |val x = summon[[X] =>> (X, X)] // error | ^ | no implicit argument of type [X] =>> (X, X) was found for parameter x of method summon in object DottyPredef +-- [E007] Type Mismatch Error: tests/neg/i9958.scala:8:10 -------------------------------------------------------------- +8 |def b = f(a) // error + | ^ + | Found: G[[A <: Int] =>> List[A]] + | Required: G[List] diff --git a/tests/neg/i9958.scala b/tests/neg/i9958.scala index e85520d7fe83..a5ddbed70709 100644 --- a/tests/neg/i9958.scala +++ b/tests/neg/i9958.scala @@ -1 +1,9 @@ val x = summon[[X] =>> (X, X)] // error +// #9971: +trait G[F[_]] + +def f(x: G[List]) = ??? + +def a: G[[A <: Int] =>> List[A]] = ??? +def b = f(a) // error + From 17509573f7485a2762259f06e6e69a98d33d3d65 Mon Sep 17 00:00:00 2001 From: odersky Date: Wed, 14 Oct 2020 12:11:40 +0200 Subject: [PATCH 3/3] Update compiler/src/dotty/tools/dotc/core/TypeApplications.scala Co-authored-by: Nicolas Stucki --- compiler/src/dotty/tools/dotc/core/TypeApplications.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/core/TypeApplications.scala b/compiler/src/dotty/tools/dotc/core/TypeApplications.scala index e0509852d0a3..449e2f6a309d 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeApplications.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeApplications.scala @@ -45,7 +45,7 @@ object TypeApplications { private def weakerBounds(tp: HKTypeLambda, tparams: List[ParamInfo])(using Context): Boolean = val onlyEmptyBounds = tp.typeParams.forall(_.paramInfo == TypeBounds.empty) onlyEmptyBounds - // Note: this pre-test helps efficiency. It is also necessary since in some cases + // Note: this pre-test helps efficiency. It is also necessary to workaround #9965 since in some cases // tparams is empty. This can happen when we change the owners of inlined local // classes in mapSymbols. See pos/reference/delegates.scala for an example. // In this case, we can still return true if we know that the hk lambda bounds