Skip to content

Improve rendering of "none of overloaded alternatives" error #15633

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 81 additions & 2 deletions compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down Expand Up @@ -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)
Expand Down
84 changes: 84 additions & 0 deletions compiler/src/dotty/tools/dotc/util/MarginString.scala
Original file line number Diff line number Diff line change
@@ -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

78 changes: 78 additions & 0 deletions compiler/src/dotty/tools/dotc/util/TypeRegistry.scala
Original file line number Diff line number Diff line change
@@ -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("<cnt>", 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 + "<cnt>"
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


1 change: 1 addition & 0 deletions sandbox/scalajs/src/hello.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ object HelloWorld extends MyTrait {
println(foo(4))
}
}

7 changes: 5 additions & 2 deletions tests/explicit-nulls/neg/byname-nullables.check
Original file line number Diff line number Diff line change
Expand Up @@ -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))
12 changes: 9 additions & 3 deletions tests/explicit-nulls/neg/i7883.check
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 5 additions & 2 deletions tests/neg/bad-unapplies.check
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
39 changes: 22 additions & 17 deletions tests/neg/i10901.check
Original file line number Diff line number Diff line change
@@ -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
| ^^^
Expand All @@ -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
Expand Down
7 changes: 5 additions & 2 deletions tests/neg/i11544.check
Original file line number Diff line number Diff line change
Expand Up @@ -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
|
| <none>(str: String, int: Int): Int
|
| <none>(arg: Int): Int
|
| both match arguments ((23 : Int))
|
| Note: Overloaded definitions introduced by refinements cannot be resolved
Expand Down
9 changes: 7 additions & 2 deletions tests/neg/i15000.check
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Loading