From aba158cb28ceb535c5ac58bed606fbd72f89a11c Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 12 Sep 2022 10:13:30 +0200 Subject: [PATCH] Improve error messages for overloading errors We need to show argument lists after the first one if they were used for overloading resolution. --- .../tools/dotc/typer/ErrorReporting.scala | 33 +++++++++++++------ .../dotty/tools/dotc/typer/ProtoTypes.scala | 10 +++++- tests/neg/i10901.check | 4 +-- tests/neg/i15000.check | 2 +- tests/neg/i15287.check | 7 ++++ tests/neg/i15287.scala | 4 +++ tests/run/i15287.scala | 11 +++++-- 7 files changed, 54 insertions(+), 17 deletions(-) create mode 100644 tests/neg/i15287.check create mode 100644 tests/neg/i15287.scala diff --git a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala index 3c92a217206d..20b251753687 100644 --- a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala +++ b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala @@ -69,15 +69,28 @@ object ErrorReporting { "\n(Note that variables need to be initialized to be defined)" else "" + /** Reveal arguments in FunProtos that are proteted by an IgnoredProto but were + * revealed during type inference. This gives clearer error messages for overloading + * resolution errors that need to show argument lists after the first. We do not + * reveal other kinds of ignored prototypes since these might be misleading because + * there might be a possible implicit conversion on the result. + */ + def revealDeepenedArgs(tp: Type): Type = tp match + case tp @ IgnoredProto(deepTp: FunProto) if tp.wasDeepened => deepTp + case _ => tp + def expectedTypeStr(tp: Type): String = tp match { case tp: PolyProto => - em"type arguments [${tp.targs.tpes}%, %] and ${expectedTypeStr(tp.resultType)}" + em"type arguments [${tp.targs.tpes}%, %] and ${expectedTypeStr(revealDeepenedArgs(tp.resultType))}" case tp: FunProto => - val result = tp.resultType match { - case _: WildcardType | _: IgnoredProto => "" - case tp => em" and expected result type $tp" - } - em"arguments (${tp.typedArgs().tpes}%, %)$result" + def argStr(tp: FunProto): String = + val result = revealDeepenedArgs(tp.resultType) match { + case restp: FunProto => argStr(restp) + case _: WildcardType | _: IgnoredProto => "" + case tp => em" and expected result type $tp" + } + em"(${tp.typedArgs().tpes}%, %)$result" + s"arguments ${argStr(tp)}" case _ => em"expected type $tp" } @@ -125,25 +138,25 @@ object ErrorReporting { def typeMismatch(tree: Tree, pt: Type, implicitFailure: SearchFailureType = NoMatchingImplicits): Tree = { val normTp = normalize(tree.tpe, pt) val normPt = normalize(pt, pt) - + def contextFunctionCount(tp: Type): Int = tp.stripped match case defn.ContextFunctionType(_, restp, _) => 1 + contextFunctionCount(restp) case _ => 0 def strippedTpCount = contextFunctionCount(tree.tpe) - contextFunctionCount(normTp) def strippedPtCount = contextFunctionCount(pt) - contextFunctionCount(normPt) - + val (treeTp, expectedTp) = if normTp <:< normPt || strippedTpCount != strippedPtCount then (tree.tpe, pt) else (normTp, normPt) // use normalized types if that also shows an error, and both sides stripped // the same number of context functions. Use original types otherwise. - + def missingElse = tree match case If(_, _, elsep @ Literal(Constant(()))) if elsep.span.isSynthetic => "\nMaybe you are missing an else part for the conditional?" case _ => "" - + errorTree(tree, TypeMismatch(treeTp, expectedTp, Some(tree), implicitFailure.whyNoConversion, missingElse)) } diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index 71b500dc04a9..028812588607 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -131,10 +131,18 @@ object ProtoTypes { /** A class marking ignored prototypes that can be revealed by `deepenProto` */ abstract case class IgnoredProto(ignored: Type) extends CachedGroundType with MatchAlways: + private var myWasDeepened = false override def revealIgnored = ignored - override def deepenProto(using Context): Type = ignored + override def deepenProto(using Context): Type = + myWasDeepened = true + ignored override def deepenProtoTrans(using Context): Type = ignored.deepenProtoTrans + /** Did someone look inside via deepenProto? Used for error deagniostics + * to give a more extensive expected type. + */ + def wasDeepened: Boolean = myWasDeepened + override def computeHash(bs: Hashable.Binders): Int = doHash(bs, ignored) override def eql(that: Type): Boolean = that match diff --git a/tests/neg/i10901.check b/tests/neg/i10901.check index 7f506628798e..26270ced338b 100644 --- a/tests/neg/i10901.check +++ b/tests/neg/i10901.check @@ -15,7 +15,7 @@ | (x: T1) | (y: BugExp4Point2D.ColumnType[T2]) | (implicit evidence$5: Numeric[T1], evidence$6: Numeric[T2]): BugExp4Point2D.Point2D[T1, T2] - | both match arguments ((x : BugExp4Point2D.IntT.type)) + | both match arguments ((x : BugExp4Point2D.IntT.type))((y : BugExp4Point2D.DoubleT.type)) -- [E008] Not Found Error: tests/neg/i10901.scala:48:38 ---------------------------------------------------------------- 48 | val pos4: Point2D[Int,Double] = x ยบ 201.1 // error | ^^^ @@ -29,7 +29,7 @@ | (x: BugExp4Point2D.ColumnType[T1]) | (y: T2)(implicit evidence$9: Numeric[T1], evidence$10: Numeric[T2]): BugExp4Point2D.Point2D[T1, T2] | [T1, T2](x: T1)(y: T2)(implicit evidence$3: Numeric[T1], evidence$4: Numeric[T2]): BugExp4Point2D.Point2D[T1, T2] - | both match arguments ((x : BugExp4Point2D.IntT.type)) + | both match arguments ((x : BugExp4Point2D.IntT.type))((201.1d : Double)) -- [E008] Not Found Error: tests/neg/i10901.scala:62:16 ---------------------------------------------------------------- 62 | val y = "abc".foo // error | ^^^^^^^^^ diff --git a/tests/neg/i15000.check b/tests/neg/i15000.check index c63866993103..1a1e8e1b973b 100644 --- a/tests/neg/i15000.check +++ b/tests/neg/i15000.check @@ -21,4 +21,4 @@ | Ambiguous overload. The overloaded alternatives of method apply in object ExtensionMethodReproduction with types | (c: ExtensionMethodReproduction.C)(x: Int, y: Int): String | (c: ExtensionMethodReproduction.C)(x: Int, y: String): String - | both match arguments (ExtensionMethodReproduction.c.type) + | both match arguments (ExtensionMethodReproduction.c.type)((ExtensionMethodReproduction.x : Int), ) diff --git a/tests/neg/i15287.check b/tests/neg/i15287.check new file mode 100644 index 000000000000..558916cea437 --- /dev/null +++ b/tests/neg/i15287.check @@ -0,0 +1,7 @@ +-- [E134] Type Error: tests/neg/i15287.scala:4:19 ---------------------------------------------------------------------- +4 |@main def Test() = f('c')(2) // error + | ^ + | None of the overloaded alternatives of method f with types + | (x: Char)(min: Boolean, max: Int): String + | (x: Char)(someParam: String): String + | match arguments (('c' : Char))((2 : Int)) diff --git a/tests/neg/i15287.scala b/tests/neg/i15287.scala new file mode 100644 index 000000000000..10dc587b8a17 --- /dev/null +++ b/tests/neg/i15287.scala @@ -0,0 +1,4 @@ +def f(x: Char)(someParam: String): String = "a" +def f(x: Char)(min: Boolean, max: Int = 4): String = "b" + +@main def Test() = f('c')(2) // error diff --git a/tests/run/i15287.scala b/tests/run/i15287.scala index 538c9334f9a5..1231b0955695 100644 --- a/tests/run/i15287.scala +++ b/tests/run/i15287.scala @@ -1,4 +1,9 @@ -def f(x: Char)(someParam: String): String = "a" -def f(x: Char)(min: Boolean, max: Int = 4): String = "b" +extension (x: Char) + def f(someParam: String): String = "a" + def f(min: Boolean, max: Int = 4): String = "b" + +@main def Test() = + f('c')(false) + 'c'.f(true) + 'c'.f("a") -@main def Test() = f('c')(false)