Skip to content

Commit afa2e30

Browse files
committed
Shorten traces for TypeMismatch errors under -explain
This is a partial fix of #18737. We still can't explain the differences concisely, but at least we shorten the comparison traces by showing only steps that contributed to the overall failure and by avoiding repetitions.
1 parent 48bb59c commit afa2e30

File tree

7 files changed

+130
-53
lines changed

7 files changed

+130
-53
lines changed

compiler/src/dotty/tools/dotc/core/TypeComparer.scala

+37-19
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
253253
//}
254254
assert(!ctx.settings.YnoDeepSubtypes.value)
255255
if (Config.traceDeepSubTypeRecursions && !this.isInstanceOf[ExplainingTypeComparer])
256-
report.log(explained(_.isSubType(tp1, tp2, approx)))
256+
report.log(explained(_.isSubType(tp1, tp2, approx), short = false))
257257
}
258258
// Eliminate LazyRefs before checking whether we have seen a type before
259259
val normalize = new TypeMap {
@@ -2972,7 +2972,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
29722972
}
29732973
}
29742974

2975-
protected def explainingTypeComparer = ExplainingTypeComparer(comparerContext)
2975+
protected def explainingTypeComparer(short: Boolean) = ExplainingTypeComparer(comparerContext, short)
29762976
protected def trackingTypeComparer = TrackingTypeComparer(comparerContext)
29772977

29782978
private def inSubComparer[T, Cmp <: TypeComparer](comparer: Cmp)(op: Cmp => T): T =
@@ -2982,8 +2982,8 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
29822982
finally myInstance = saved
29832983

29842984
/** The trace of comparison operations when performing `op` */
2985-
def explained[T](op: ExplainingTypeComparer => T, header: String = "Subtype trace:")(using Context): String =
2986-
val cmp = explainingTypeComparer
2985+
def explained[T](op: ExplainingTypeComparer => T, header: String = "Subtype trace:", short: Boolean)(using Context): String =
2986+
val cmp = explainingTypeComparer(short)
29872987
inSubComparer(cmp)(op)
29882988
cmp.lastTrace(header)
29892989

@@ -3152,8 +3152,8 @@ object TypeComparer {
31523152
def constrainPatternType(pat: Type, scrut: Type, forceInvariantRefinement: Boolean = false)(using Context): Boolean =
31533153
comparing(_.constrainPatternType(pat, scrut, forceInvariantRefinement))
31543154

3155-
def explained[T](op: ExplainingTypeComparer => T, header: String = "Subtype trace:")(using Context): String =
3156-
comparing(_.explained(op, header))
3155+
def explained[T](op: ExplainingTypeComparer => T, header: String = "Subtype trace:", short: Boolean = false)(using Context): String =
3156+
comparing(_.explained(op, header, short))
31573157

31583158
def tracked[T](op: TrackingTypeComparer => T)(using Context): T =
31593159
comparing(_.tracked(op))
@@ -3350,30 +3350,47 @@ class TrackingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
33503350
}
33513351
}
33523352

3353-
/** A type comparer that can record traces of subtype operations */
3354-
class ExplainingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
3353+
/** A type comparer that can record traces of subtype operations
3354+
* @param short if true print only failing forward traces; never print succesful
3355+
* subtraces; never print backtraces starting with `<==`.
3356+
*/
3357+
class ExplainingTypeComparer(initctx: Context, short: Boolean) extends TypeComparer(initctx) {
33553358
import TypeComparer._
33563359

33573360
init(initctx)
33583361

3359-
override def explainingTypeComparer = this
3362+
override def explainingTypeComparer(short: Boolean) =
3363+
if short == this.short then this
3364+
else ExplainingTypeComparer(comparerContext, short)
33603365

33613366
private var indent = 0
33623367
private val b = new StringBuilder
3363-
3364-
private var skipped = false
3368+
private var lastForwardGoal: String | Null = null
33653369

33663370
override def traceIndented[T](str: String)(op: => T): T =
3367-
if (skipped) op
3368-
else {
3371+
val str1 = str.replace('\n', ' ')
3372+
if short && str1 == lastForwardGoal then
3373+
op // repeated goal, skip for clarity
3374+
else
3375+
lastForwardGoal = str1
3376+
val curLength = b.length
33693377
indent += 2
3370-
val str1 = str.replace('\n', ' ')
33713378
b.append("\n").append(" " * indent).append("==> ").append(str1)
33723379
val res = op
3373-
b.append("\n").append(" " * indent).append("<== ").append(str1).append(" = ").append(show(res))
3380+
if short then
3381+
if res == false then
3382+
if lastForwardGoal != null then // last was deepest goal that failed
3383+
b.append(" = false")
3384+
lastForwardGoal = null
3385+
else
3386+
b.length = curLength // don't show successful subtraces
3387+
else
3388+
b.append("\n").append(" " * indent).append("<== ").append(str1).append(" = ").append(show(res))
33743389
indent -= 2
33753390
res
3376-
}
3391+
3392+
private def traceIndentedIfNotShort[T](str: String)(op: => T): T =
3393+
if short then op else traceIndented(str)(op)
33773394

33783395
private def frozenNotice: String =
33793396
if frozenConstraint then " in frozen constraint" else ""
@@ -3384,7 +3401,8 @@ class ExplainingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
33843401
then s" ${tp1.getClass} ${tp2.getClass}"
33853402
else ""
33863403
val approx = approxState
3387-
traceIndented(s"${show(tp1)} <: ${show(tp2)}$moreInfo${approx.show}$frozenNotice") {
3404+
def approxStr = if short then "" else approx.show
3405+
traceIndented(s"${show(tp1)} <: ${show(tp2)}$moreInfo${approxStr}$frozenNotice") {
33883406
super.recur(tp1, tp2)
33893407
}
33903408

@@ -3394,12 +3412,12 @@ class ExplainingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
33943412
}
33953413

33963414
override def lub(tp1: Type, tp2: Type, canConstrain: Boolean, isSoft: Boolean): Type =
3397-
traceIndented(s"lub(${show(tp1)}, ${show(tp2)}, canConstrain=$canConstrain, isSoft=$isSoft)") {
3415+
traceIndentedIfNotShort(s"lub(${show(tp1)}, ${show(tp2)}, canConstrain=$canConstrain, isSoft=$isSoft)") {
33983416
super.lub(tp1, tp2, canConstrain, isSoft)
33993417
}
34003418

34013419
override def glb(tp1: Type, tp2: Type): Type =
3402-
traceIndented(s"glb(${show(tp1)}, ${show(tp2)})") {
3420+
traceIndentedIfNotShort(s"glb(${show(tp1)}, ${show(tp2)})") {
34033421
super.glb(tp1, tp2)
34043422
}
34053423

compiler/src/dotty/tools/dotc/typer/ErrorReporting.scala

+2-2
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ object ErrorReporting {
204204
| $found
205205
|conforms to
206206
| $expected
207-
|but the comparison trace ended with `false`:
207+
|but none of the attempts shown below succeeded:
208208
|"""
209209
val c = ctx.typerState.constraint
210210
val constraintText =
@@ -213,7 +213,7 @@ object ErrorReporting {
213213
else
214214
i"""a constraint with:
215215
|$c"""
216-
i"""${TypeComparer.explained(_.isSubType(found, expected), header)}
216+
i"""${TypeComparer.explained(_.isSubType(found, expected), header, short = !ctx.settings.Ydebug.value)}
217217
|
218218
|The tests were made under $constraintText"""
219219

tests/neg/hidden-type-errors.check

+2-5
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,9 @@
1212
| String
1313
| conforms to
1414
| Int
15-
| but the comparison trace ended with `false`:
15+
| but none of the attempts shown below succeeded:
1616
|
17-
| ==> String <: Int
18-
| ==> String <: Int
19-
| <== String <: Int = false
20-
| <== String <: Int = false
17+
| ==> String <: Int = false
2118
|
2219
| The tests were made under the empty constraint
2320
---------------------------------------------------------------------------------------------------------------------

tests/neg/i11637.check

+4-18
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,13 @@
99
| test2.FunctorImpl
1010
| conforms to
1111
| [Generic2[T <: String] <: Set[T]] =>> Any
12-
| but the comparison trace ended with `false`:
12+
| but none of the attempts shown below succeeded:
1313
|
1414
| ==> test2.FunctorImpl <: [Generic2[T <: String] <: Set[T]] =>> Any
1515
| ==> type bounds [[T <: String] <: Set[T]] <: type bounds [[T] <: Iterable[T]]
1616
| ==> [T <: String] =>> Set[T] <: Iterable
1717
| ==> type bounds [] <: type bounds [ <: String]
18-
| ==> Any <: String
19-
| ==> Any <: String
20-
| <== Any <: String = false
21-
| <== Any <: String = false
22-
| <== type bounds [] <: type bounds [ <: String] = false
23-
| <== [T <: String] =>> Set[T] <: Iterable = false
24-
| <== type bounds [[T <: String] <: Set[T]] <: type bounds [[T] <: Iterable[T]] = false
25-
| <== test2.FunctorImpl <: [Generic2[T <: String] <: Set[T]] =>> Any = false
18+
| ==> Any <: String = false
2619
|
2720
| The tests were made under the empty constraint
2821
--------------------------------------------------------------------------------------------------------------------
@@ -37,20 +30,13 @@
3730
| test2.FunctorImpl
3831
| conforms to
3932
| [Generic2[T <: String] <: Set[T]] =>> Any
40-
| but the comparison trace ended with `false`:
33+
| but none of the attempts shown below succeeded:
4134
|
4235
| ==> test2.FunctorImpl <: [Generic2[T <: String] <: Set[T]] =>> Any
4336
| ==> type bounds [[T <: String] <: Set[T]] <: type bounds [[T] <: Iterable[T]]
4437
| ==> [T <: String] =>> Set[T] <: Iterable
4538
| ==> type bounds [] <: type bounds [ <: String]
46-
| ==> Any <: String
47-
| ==> Any <: String
48-
| <== Any <: String = false
49-
| <== Any <: String = false
50-
| <== type bounds [] <: type bounds [ <: String] = false
51-
| <== [T <: String] =>> Set[T] <: Iterable = false
52-
| <== type bounds [[T <: String] <: Set[T]] <: type bounds [[T] <: Iterable[T]] = false
53-
| <== test2.FunctorImpl <: [Generic2[T <: String] <: Set[T]] =>> Any = false
39+
| ==> Any <: String = false
5440
|
5541
| The tests were made under the empty constraint
5642
--------------------------------------------------------------------------------------------------------------------

tests/neg/i15575.check

+4-9
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,10 @@
99
| Any
1010
| conforms to
1111
| T & Any
12-
| but the comparison trace ended with `false`:
12+
| but none of the attempts shown below succeeded:
1313
|
1414
| ==> Any <: T & Any
15-
| ==> Any <: T
16-
| <== Any <: T = false
17-
| <== Any <: T & Any = false
15+
| ==> Any <: T = false
1816
|
1917
| The tests were made under the empty constraint
2018
---------------------------------------------------------------------------------------------------------------------
@@ -29,12 +27,9 @@
2927
| CharSequence
3028
| conforms to
3129
| String
32-
| but the comparison trace ended with `false`:
30+
| but none of the attempts shown below succeeded:
3331
|
34-
| ==> CharSequence <: String
35-
| ==> CharSequence <: String
36-
| <== CharSequence <: String = false
37-
| <== CharSequence <: String = false
32+
| ==> CharSequence <: String = false
3833
|
3934
| The tests were made under the empty constraint
4035
---------------------------------------------------------------------------------------------------------------------

tests/neg/i18737.check

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
-- [E007] Type Mismatch Error: tests/neg/i18737.scala:3:36 -------------------------------------------------------------
2+
3 |def test2(v: String & Long) = test1(v) // error
3+
| ^
4+
| Found: (v : String & Long)
5+
| Required: String & Integer & List[String]
6+
|---------------------------------------------------------------------------------------------------------------------
7+
| Explanation (enabled by `-explain`)
8+
|- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
9+
|
10+
| Tree: v
11+
| I tried to show that
12+
| (v : String & Long)
13+
| conforms to
14+
| String & Integer & List[String]
15+
| but none of the attempts shown below succeeded:
16+
|
17+
| ==> (v : String & Long) <: String & Integer & List[String]
18+
| ==> (v : String & Long) <: String & Integer
19+
| ==> (v : String & Long) <: Integer
20+
| ==> String & Long <: Integer
21+
| ==> String <: Integer = false
22+
| ==> Long <: Integer = false
23+
|
24+
| The tests were made under the empty constraint
25+
---------------------------------------------------------------------------------------------------------------------
26+
-- [E007] Type Mismatch Error: tests/neg/i18737.scala:6:36 -------------------------------------------------------------
27+
6 |def test4(v: String | Long) = test3(v) // error
28+
| ^
29+
| Found: (v : String | Long)
30+
| Required: String | Integer | List[String]
31+
|---------------------------------------------------------------------------------------------------------------------
32+
| Explanation (enabled by `-explain`)
33+
|- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
34+
|
35+
| Tree: v
36+
| I tried to show that
37+
| (v : String | Long)
38+
| conforms to
39+
| String | Integer | List[String]
40+
| but none of the attempts shown below succeeded:
41+
|
42+
| ==> (v : String | Long) <: String | Integer | List[String]
43+
| ==> String | Long <: String | Integer | List[String]
44+
| ==> Long <: String | Integer | List[String]
45+
| ==> Long <: String | Integer
46+
| ==> Long <: String = false
47+
| ==> Long <: Integer = false
48+
| ==> Long <: List[String] = false
49+
| ==> (v : String | Long) <: String | Integer
50+
| ==> String | Long <: String | Integer
51+
| ==> Long <: String | Integer
52+
| ==> Long <: String = false
53+
| ==> Long <: Integer = false
54+
| ==> (v : String | Long) <: String
55+
| ==> String | Long <: String
56+
| ==> Long <: String = false
57+
| ==> (v : String | Long) <: Integer
58+
| ==> String | Long <: Integer
59+
| ==> String <: Integer = false
60+
| ==> String | Long <: String | Integer
61+
| ==> Long <: String | Integer
62+
| ==> Long <: String = false
63+
| ==> Long <: Integer = false
64+
| ==> (v : String | Long) <: List[String]
65+
| ==> String | Long <: List[String]
66+
| ==> String <: List[String] = false
67+
| ==> String | Long <: String | Integer | List[String]
68+
| ==> Long <: String | Integer | List[String]
69+
| ==> Long <: String | Integer
70+
| ==> Long <: String = false
71+
| ==> Long <: Integer = false
72+
| ==> Long <: List[String] = false
73+
|
74+
| The tests were made under the empty constraint
75+
---------------------------------------------------------------------------------------------------------------------

tests/neg/i18737.scala

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
//> using options -explain
2+
def test1(v: String & Integer & List[String]) = ()
3+
def test2(v: String & Long) = test1(v) // error
4+
5+
def test3(v: String | Integer | List[String]) = ()
6+
def test4(v: String | Long) = test3(v) // error

0 commit comments

Comments
 (0)