diff --git a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala index 215fe9bfa687..6785d8033fb4 100644 --- a/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala +++ b/compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala @@ -15,6 +15,10 @@ import reporting._ import collection.mutable import scala.util.matching.Regex +import dotty.tools.dotc.core.Names.TypeName +import dotty.tools.dotc.util.MarginString +import MarginString.Joiner +import dotty.tools.dotc.util.TypeRegistry object ErrorReporting { @@ -91,9 +95,84 @@ object ErrorReporting { em"$kind $tpe" } - def overloadedAltsStr(alts: List[SingleDenotation]): String = + def overloadedAltsStr(alts: List[SingleDenotation])(using ctx: Context): String = { + val denots = Vector.newBuilder[String] + val registry = TypeRegistry() + + inline def renderParamList(names: List[String], types: List[Type]) = + val params = names.zip(types).map { case (name, tpe) => + registry.getFullName(tpe) + MarginString.Chunk(s"$name: ${registry.getShortName(tpe).applied}") + } + + MarginString.Group( + params.toVector, + Joiner.rightBreakable(", "), + prefix = Some(Joiner.rightBreakable("(")), + suffix = Some(Joiner.rightBreakable(")")) + ) + end renderParamList + + alts.foreach { alt => + val info = alt.info + val name = alt.name.show + + val paramNamess = info.paramNamess.map(_.map(_.show)) + val paramTypes = info.paramInfoss + val resultType = info.finalResultType + + val methodPrefix = " " + name + + val typeParamsSection = + info match + case pt: PolyType => + val typeParams = pt.paramNames + if typeParams.isEmpty then "" + else + typeParams.map(_.show).mkString("[", ", ", "]") + case _ => "" + + + val paramLists = MarginString.Group( + paramNamess.zip(paramTypes).map(t => renderParamList(t._1, t._2)).toVector, + Joiner.nonBreakable("") + ).lines(60).map(MarginString.Chunk.apply) + + MarginString.Group( + paramLists, + Joiner.nonBreakable("\n " + (" " * methodPrefix.size)), + prefix = Some(Joiner.nonBreakable(methodPrefix + typeParamsSection)), + suffix = Some(Joiner.nonBreakable(": " + registry.getShortName(resultType).applied)) + ) + .lines(0) + .foreach(denots += _) + } + + val allMentionedTypes = registry.allShortNames + .toVector + .filter((tpe, shortName) => registry.getFullName(tpe) != shortName.ref) + .sortBy(_._2.ref) + .map(_._1) + + val maxShortLength = registry.allShortNames.values.maxByOption(_.ref.length).map(_.ref.length).getOrElse(0) + + denots += "" + + if allMentionedTypes.nonEmpty then + denots += "where" + + import printing.Highlighting.* + + allMentionedTypes.foreach {tpe => + val shortName = registry.getShortName(tpe) + val longName = registry.getFullName(tpe) + val paddedName = shortName.ref.padTo(maxShortLength, ' ') + denots += s" ${Cyan(paddedName).show} is $longName" + } + em"overloaded alternatives of ${denotStr(alts.head)} with types\n" + - em" ${alts map (_.info)}%\n %" + denots.result().mkString("\n") + } def denotStr(denot: Denotation): String = if (denot.isOverloaded) overloadedAltsStr(denot.alternatives) diff --git a/compiler/src/dotty/tools/dotc/util/MarginString.scala b/compiler/src/dotty/tools/dotc/util/MarginString.scala new file mode 100644 index 000000000000..208a57082771 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/util/MarginString.scala @@ -0,0 +1,84 @@ +package dotty.tools.dotc.util + +import MarginString.Joiner + +enum MarginString: + case Chunk(s: String) + case Group(strings: Vector[MarginString], + joiner: Joiner, + prefix: Option[Joiner] = None, + suffix: Option[Joiner] = None) + + def lines(maxWidth: Int) = MarginString.lines(this, maxWidth) + +object MarginString: + case class Joiner private (s: String, breakLeft: Boolean, breakRight: Boolean) + object Joiner: + def apply(s: String) = new Joiner(s, true, true) + def nonBreakable(s: String) = new Joiner(s, false, false) + def leftBreakable(s: String) = new Joiner(s, breakLeft = true, false) + def rightBreakable(s: String) = new Joiner(s, false, breakRight = true) + + def lines(str: MarginString, maxLength: Int): Vector[String] = + val nonBreakable = { + val chunks = List.newBuilder[String] + val sb = StringBuilder() + + tokens(str).foreach { + case str: String => sb.append(str) + case Breakpoint => + chunks += sb.result() + sb.clear() + } + + if sb.nonEmpty then chunks += sb.result() + + chunks.result() + } + + val lineBreaks = Vector.newBuilder[String] + val curLine = StringBuilder() + + nonBreakable.foreach { str => + if curLine.size >= maxLength then + lineBreaks += curLine.result() + curLine.clear() + + curLine.append(str) + } + if curLine.nonEmpty then lineBreaks += curLine.result() + + lineBreaks.result() + end lines + + private case object Breakpoint + + private def tokens(str: MarginString): Vector[String | Breakpoint.type] = + def go(ms: MarginString): Vector[String | Breakpoint.type] = + ms match + case Chunk(s) => Vector(s) + case g: Group => + import g.* + val b = Vector.newBuilder[String | Breakpoint.type] + + def renderJoiner(joiner: Joiner) = + if joiner.breakLeft then + b += Breakpoint + b += joiner.s + if joiner.breakRight then + b += Breakpoint + + + prefix.foreach(renderJoiner) + + strings.zipWithIndex.foreach {case (c, idx) => + b ++= go(c) + if idx != strings.size - 1 then renderJoiner(joiner) + } + + suffix.foreach(renderJoiner) + + b.result() + go(str) + end tokens + diff --git a/compiler/src/dotty/tools/dotc/util/TypeRegistry.scala b/compiler/src/dotty/tools/dotc/util/TypeRegistry.scala new file mode 100644 index 000000000000..5c4b1acf7d06 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/util/TypeRegistry.scala @@ -0,0 +1,78 @@ +package dotty.tools.dotc.util + +import collection.mutable.Map as MMap + +import dotty.tools.dotc.core.Types.* +import dotty.tools.dotc.core.Contexts.Context + +case class ShortTypeName(ref: String, applied: String, canonical: String) + +final class TypeRegistry: + def getShortName(tpe: Type)(using Context) = computeShortName(tpe) + + def getFullName(tpe: Type)(using Context) = + fullNames.getOrElseUpdate(tpe, { + val concreteType = concrete(tpe) + + fullNames.getOrElseUpdate(concreteType, { + computeFullName(concreteType) + }) + }) + + + def allShortNames: Map[Type, ShortTypeName] = shortNames.toMap + + def allTypes: scala.collection.Set[Type] = shortNames.keySet + + private val fullNames = MMap.empty[Type, String] + private val shortNames = MMap.empty[Type, ShortTypeName] + private val resolvedNames = MMap.empty[ShortTypeName, String] + private val conflicts = MMap.empty[ShortTypeName, Int] + + private def concrete(tpe: Type) = + tpe match + case at: AppliedType => at.tycon + case _ => tpe + + private def nameIt(canonical: String, template: String, tpe: Type): String = + val cnt = shortNames.count((t, r) => r.canonical == canonical && tpe != t) + val idx = cnt.toChar match + case 0 => "" + case 1 => "¹" + case 2 => "²" + case 3 => "³" + case 4 => "⁴" + case 5 => "⁵" + case 6 => "⁶" + case 7 => "⁷" + case 8 => "⁸" + case 9 => "⁹" + + template.replaceAll("", idx).nn + + private def computeShortName(tpe: Type)(using Context): ShortTypeName = + shortNames.getOrElseUpdate(concrete(tpe), { + val short = + tpe match + case n: NamedType => + val nm = n.name.lastPart.show + val nmTemplate = nm + "" + val value = nameIt(nm, nmTemplate, n) + ShortTypeName(value, value, nm) + + case at: AppliedType => + val ref = computeShortName(at.tycon).ref + val applied = ref + at.args.map(computeShortName(_).applied).mkString("[", ", ", "]") + + ShortTypeName(ref, applied, ref) + + case _ => ShortTypeName(tpe.show, tpe.show, tpe.show) + + short + + }) + + private def computeFullName(tpe: Type)(using Context) = + tpe.show + + diff --git a/sandbox/scalajs/src/hello.scala b/sandbox/scalajs/src/hello.scala index 5a2b6a2952c7..20282ecfd51d 100644 --- a/sandbox/scalajs/src/hello.scala +++ b/sandbox/scalajs/src/hello.scala @@ -13,3 +13,4 @@ object HelloWorld extends MyTrait { println(foo(4)) } } + diff --git a/tests/explicit-nulls/neg/byname-nullables.check b/tests/explicit-nulls/neg/byname-nullables.check index 887e0267b6eb..dd492873ed4d 100644 --- a/tests/explicit-nulls/neg/byname-nullables.check +++ b/tests/explicit-nulls/neg/byname-nullables.check @@ -25,6 +25,9 @@ 81 | if x != null then f(byName(x), 1) // error: none of the overloaded methods match argument types | ^ | None of the overloaded alternatives of method f in object Test7 with types - | (x: => String, y: Int): String - | (x: String, y: String): String + | + | f(x: => String, y: Int): String + | + | f(x: String, y: String): String + | | match arguments (String | Null, (1 : Int)) diff --git a/tests/explicit-nulls/neg/i7883.check b/tests/explicit-nulls/neg/i7883.check index e37285332359..ec346be688db 100644 --- a/tests/explicit-nulls/neg/i7883.check +++ b/tests/explicit-nulls/neg/i7883.check @@ -2,9 +2,15 @@ 6 | case r(hd, tl) => Some((hd, tl)) // error // error // error | ^ | None of the overloaded alternatives of method unapplySeq in class Regex with types - | (m: scala.util.matching.Regex.Match): Option[List[String]] - | (c: Char): Option[List[Char]] - | (s: CharSequence): Option[List[String]] + | + | unapplySeq(m: Match): Option[List[String]] + | + | unapplySeq(c: Char): Option[List[String]] + | + | unapplySeq(s: CharSequence): Option[List[String]] + | + | where + | Match is scala.util.matching.Regex.Match | match arguments (String | Null) -- [E006] Not Found Error: tests/explicit-nulls/neg/i7883.scala:6:30 --------------------------------------------------- 6 | case r(hd, tl) => Some((hd, tl)) // error // error // error diff --git a/tests/neg/bad-unapplies.check b/tests/neg/bad-unapplies.check index 44633ca6950a..8c61665989e3 100644 --- a/tests/neg/bad-unapplies.check +++ b/tests/neg/bad-unapplies.check @@ -2,8 +2,11 @@ 22 | case A("2") => // error (cannot resolve overloading) | ^ | Ambiguous overload. The overloaded alternatives of method unapply in object A with types - | (x: B): Option[String] - | (x: A): Option[String] + | + | unapply(x: B): Option[String] + | + | unapply(x: A): Option[String] + | | both match arguments (C) | | longer explanation available when compiling with `-explain` diff --git a/tests/neg/i10901.check b/tests/neg/i10901.check index 7f506628798e..f4c96d2953ec 100644 --- a/tests/neg/i10901.check +++ b/tests/neg/i10901.check @@ -1,21 +1,22 @@ -- [E008] Not Found Error: tests/neg/i10901.scala:45:38 ---------------------------------------------------------------- 45 | val pos1: Point2D[Int,Double] = x º y // error | ^^^ - | value º is not a member of object BugExp4Point2D.IntT. - | An extension method was tried, but could not be fully constructed: + | value º is not a member of object BugExp4Point2D.IntT. + | An extension method was tried, but could not be fully constructed: | - | º(x) failed with + | º(x) failed with | - | Ambiguous overload. The overloaded alternatives of method º in object dsl with types - | [T1, T2] - | (x: BugExp4Point2D.ColumnType[T1]) - | (y: BugExp4Point2D.ColumnType[T2]) - | (implicit evidence$7: Numeric[T1], evidence$8: Numeric[T2]): BugExp4Point2D.Point2D[T1, T2] - | [T1, T2] - | (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)) + | Ambiguous overload. The overloaded alternatives of method º in object dsl with types + | + | º[T1, T2](x: ColumnType[T1])(y: ColumnType[T1])(evidence$7: Numeric[T1], + | evidence$8: Numeric[T1]): Point2D[T1, T2] + | + | º[T1, T2](x: T1)(y: ColumnType[T1])(evidence$5: Numeric[T1], evidence$6: Numeric[T1]): Point2D[T1, T2] + | + | where + | ColumnType is BugExp4Point2D.ColumnType + | Point2D is BugExp4Point2D.Point2D + | both match arguments ((x : BugExp4Point2D.IntT.type)) -- [E008] Not Found Error: tests/neg/i10901.scala:48:38 ---------------------------------------------------------------- 48 | val pos4: Point2D[Int,Double] = x º 201.1 // error | ^^^ @@ -25,10 +26,14 @@ | º(x) failed with | | Ambiguous overload. The overloaded alternatives of method º in object dsl with types - | [T1, T2] - | (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] + | + | º[T1, T2](x: ColumnType[T1])(y: T2)(evidence$9: Numeric[T1], evidence$10: Numeric[T1]): Point2D[T1, T2] + | + | º[T1, T2](x: T1)(y: T2)(evidence$3: Numeric[T1], evidence$4: Numeric[T1]): Point2D[T1, T2] + | + | where + | ColumnType is BugExp4Point2D.ColumnType + | Point2D is BugExp4Point2D.Point2D | both match arguments ((x : BugExp4Point2D.IntT.type)) -- [E008] Not Found Error: tests/neg/i10901.scala:62:16 ---------------------------------------------------------------- 62 | val y = "abc".foo // error diff --git a/tests/neg/i11544.check b/tests/neg/i11544.check index 15469f0c37a5..8c4ee09d850c 100644 --- a/tests/neg/i11544.check +++ b/tests/neg/i11544.check @@ -2,8 +2,11 @@ 6 | val n = m1.foo(23) // error | ^^^^^^ | Ambiguous overload. The overloaded alternatives of method (str: String, int: Int): Int with types - | (str: String, int: Int): Int - | (arg: Int): Int + | + | (str: String, int: Int): Int + | + | (arg: Int): Int + | | both match arguments ((23 : Int)) | | Note: Overloaded definitions introduced by refinements cannot be resolved diff --git a/tests/neg/i15000.check b/tests/neg/i15000.check index c63866993103..4f3267258cb9 100644 --- a/tests/neg/i15000.check +++ b/tests/neg/i15000.check @@ -19,6 +19,11 @@ | apply(ExtensionMethodReproduction.c) failed with | | 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 + | + | apply(c: C)(x: Int, y: Int): String + | + | apply(c: C)(x: Int, y: String): String + | + | where + | C is ExtensionMethodReproduction.C | both match arguments (ExtensionMethodReproduction.c.type) diff --git a/tests/neg/i6183.check b/tests/neg/i6183.check index 70c1afaae621..24756b45a932 100644 --- a/tests/neg/i6183.check +++ b/tests/neg/i6183.check @@ -7,15 +7,21 @@ | render(42) failed with | | Ambiguous overload. The overloaded alternatives of method render in object Test with types - | [B](b: B)(using x$2: DummyImplicit): Char - | [A](a: A): String + | + | render[B](b: B)(x$2: DummyImplicit): Char + | + | render[A](a: A): String + | | both match arguments ((42 : Int)) -- [E051] Reference Error: tests/neg/i6183.scala:7:9 ------------------------------------------------------------------- 7 | Test.render(42) // error | ^^^^^^^^^^^ | Ambiguous overload. The overloaded alternatives of method render in object Test with types - | [B](b: B)(using x$2: DummyImplicit): Char - | [A](a: A): String + | + | render[B](b: B)(x$2: DummyImplicit): Char + | + | render[A](a: A): String + | | both match arguments ((42 : Int)) | | longer explanation available when compiling with `-explain` diff --git a/tests/neg/i8736.check b/tests/neg/i8736.check index e7a0d62cb4af..ad211d46c21b 100644 --- a/tests/neg/i8736.check +++ b/tests/neg/i8736.check @@ -16,9 +16,13 @@ 31 | def res3: Boolean = rec.get("z") // error: ambiguous | ^^^^^^^ | Ambiguous overload. The overloaded alternatives of method (k: ("k" : String)): String with types - | (k: ("k" : String)): String - | (k: ("v" : String)): Int - | (k: ("z" : String)): Boolean + | + | (k: ("k" : String)): String + | + | (k: ("v" : String)): Int + | + | (k: ("z" : String)): Boolean + | | all match arguments (("z" : String)) | | Note: Overloaded definitions introduced by refinements cannot be resolved diff --git a/tests/neg/indent-colons.check b/tests/neg/indent-colons.check index 06bd7a31b079..177282c17b80 100644 --- a/tests/neg/indent-colons.check +++ b/tests/neg/indent-colons.check @@ -26,14 +26,23 @@ 23 | val x = 1.+ : // error | ^^^ | None of the overloaded alternatives of method + in class Int with types - | (x: Double): Double - | (x: Float): Float - | (x: Long): Long - | (x: Int): Int - | (x: Char): Int - | (x: Short): Int - | (x: Byte): Int - | (x: String): String + | + | +(x: Double): Double + | + | +(x: Float): Float + | + | +(x: Long): Long + | + | +(x: Int): Int + | + | +(x: Char): Int + | + | +(x: Short): Int + | + | +(x: Byte): Int + | + | +(x: String): String + | | match expected type (2 : Int) -- [E006] Not Found Error: tests/neg/indent-colons.scala:32:7 ---------------------------------------------------------- 32 | if file.isEmpty // error diff --git a/tests/neg/overload-error-message.check b/tests/neg/overload-error-message.check new file mode 100644 index 000000000000..453256ec1c69 --- /dev/null +++ b/tests/neg/overload-error-message.check @@ -0,0 +1,28 @@ +-- [E134] Type Error: tests/neg/overload-error-message.scala:45:2 ------------------------------------------------------ +45 | method(new World, new World, 25, new World, new HelloWorldFactory) // error + | ^^^^^^ + |None of the overloaded alternatives of method method with types + | + | method(p1: String, p1_1: HelloWorldFactory, p2: World, p3: World, + | p4: HelloWorldFactory): Int + | + | method(p1: World, p1_1: HelloWorldFactory, p2: World, p3: World, p4: HelloWorldFactory): Int + | + | method(p1: World, p2: World¹, p3: World, p4: HelloWorldFactory, p5: HelloWorldFactory, + | p6: HelloWorldFactory, p7: HelloWorldFactory): Int + | + |where + | HelloWorldFactory is bla.longname.otherlongname.HelloWorldFactory + | World is hello.World + | World¹ is bye.World + |match arguments (hello.World, hello.World, (25 : Int), hello.World, bla.longname.otherlongname.HelloWorldFactory) +-- [E134] Type Error: tests/neg/overload-error-message.scala:46:2 ------------------------------------------------------ +46 | generic(25, false) // error + | ^^^^^^^ + | None of the overloaded alternatives of method generic with types + | + | generic[T1, T2](bla: Int)(x$2: Numeric[T1], x$3: Numeric[T1]): Nothing + | + | generic[T1, T2](bla: Int, a: String, h: List[Int])(x$4: Numeric[T1], x$5: Numeric[T1]): Nothing + | + | match arguments ((25 : Int), (false : Boolean)) diff --git a/tests/neg/overload-error-message.scala b/tests/neg/overload-error-message.scala new file mode 100644 index 000000000000..770ba069a899 --- /dev/null +++ b/tests/neg/overload-error-message.scala @@ -0,0 +1,47 @@ +object hello: + trait World + +object bye: + trait World + +object bla: + object longname: + object otherlongname: + trait HelloWorldFactory +import bla.longname.otherlongname.HelloWorldFactory + +def method( + p1: hello.World, + p2: bye.World, + p3: hello.World, + p4: HelloWorldFactory, + p5: HelloWorldFactory, + p6: HelloWorldFactory, + p7: HelloWorldFactory +) = 1 + +def method( + p1: hello.World, + p1_1: HelloWorldFactory, + p2: hello.World, + p3: hello.World, + p4: HelloWorldFactory +) = 1 + +def method( + p1: String, + p1_1: HelloWorldFactory, + p2: hello.World, + p3: hello.World, + p4: HelloWorldFactory +) = 1 + +def generic[T1, T2](bla: Int, a: String, h: List[Int])(using Numeric[T1], Numeric[T2]) = ??? +def generic[T1, T2](bla: Int)(using Numeric[T1], Numeric[T2]) = ??? + + +@main def test = + import hello.World + method(new World, new World, 25, new World, new HelloWorldFactory) // error + generic(25, false) // error +