diff --git a/compiler/src/dotty/tools/dotc/core/TypeApplications.scala b/compiler/src/dotty/tools/dotc/core/TypeApplications.scala index 3732968aa884..449e2f6a309d 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 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 + // 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/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..23fd7a6195e3 --- /dev/null +++ b/tests/neg/i9958.check @@ -0,0 +1,9 @@ +-- 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 +-- [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 new file mode 100644 index 000000000000..a5ddbed70709 --- /dev/null +++ b/tests/neg/i9958.scala @@ -0,0 +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 +