Skip to content

Commit 38559d7

Browse files
authored
Shorten traces for TypeMismatch errors under -explain (#18742)
This is a partial fix for #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.
2 parents 15033c7 + afa2e30 commit 38559d7

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 with CaptureSet.IdempotentCaptRefMap {
@@ -2959,7 +2959,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
29592959
}
29602960
}
29612961

2962-
protected def explainingTypeComparer = ExplainingTypeComparer(comparerContext)
2962+
protected def explainingTypeComparer(short: Boolean) = ExplainingTypeComparer(comparerContext, short)
29632963
protected def trackingTypeComparer = TrackingTypeComparer(comparerContext)
29642964

29652965
private def inSubComparer[T, Cmp <: TypeComparer](comparer: Cmp)(op: Cmp => T): T =
@@ -2969,8 +2969,8 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
29692969
finally myInstance = saved
29702970

29712971
/** The trace of comparison operations when performing `op` */
2972-
def explained[T](op: ExplainingTypeComparer => T, header: String = "Subtype trace:")(using Context): String =
2973-
val cmp = explainingTypeComparer
2972+
def explained[T](op: ExplainingTypeComparer => T, header: String = "Subtype trace:", short: Boolean)(using Context): String =
2973+
val cmp = explainingTypeComparer(short)
29742974
inSubComparer(cmp)(op)
29752975
cmp.lastTrace(header)
29762976

@@ -3139,8 +3139,8 @@ object TypeComparer {
31393139
def constrainPatternType(pat: Type, scrut: Type, forceInvariantRefinement: Boolean = false)(using Context): Boolean =
31403140
comparing(_.constrainPatternType(pat, scrut, forceInvariantRefinement))
31413141

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

31453145
def tracked[T](op: TrackingTypeComparer => T)(using Context): T =
31463146
comparing(_.tracked(op))
@@ -3337,30 +3337,47 @@ class TrackingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
33373337
}
33383338
}
33393339

3340-
/** A type comparer that can record traces of subtype operations */
3341-
class ExplainingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
3340+
/** A type comparer that can record traces of subtype operations
3341+
* @param short if true print only failing forward traces; never print succesful
3342+
* subtraces; never print backtraces starting with `<==`.
3343+
*/
3344+
class ExplainingTypeComparer(initctx: Context, short: Boolean) extends TypeComparer(initctx) {
33423345
import TypeComparer._
33433346

33443347
init(initctx)
33453348

3346-
override def explainingTypeComparer = this
3349+
override def explainingTypeComparer(short: Boolean) =
3350+
if short == this.short then this
3351+
else ExplainingTypeComparer(comparerContext, short)
33473352

33483353
private var indent = 0
33493354
private val b = new StringBuilder
3350-
3351-
private var skipped = false
3355+
private var lastForwardGoal: String | Null = null
33523356

33533357
override def traceIndented[T](str: String)(op: => T): T =
3354-
if (skipped) op
3355-
else {
3358+
val str1 = str.replace('\n', ' ')
3359+
if short && str1 == lastForwardGoal then
3360+
op // repeated goal, skip for clarity
3361+
else
3362+
lastForwardGoal = str1
3363+
val curLength = b.length
33563364
indent += 2
3357-
val str1 = str.replace('\n', ' ')
33583365
b.append("\n").append(" " * indent).append("==> ").append(str1)
33593366
val res = op
3360-
b.append("\n").append(" " * indent).append("<== ").append(str1).append(" = ").append(show(res))
3367+
if short then
3368+
if res == false then
3369+
if lastForwardGoal != null then // last was deepest goal that failed
3370+
b.append(" = false")
3371+
lastForwardGoal = null
3372+
else
3373+
b.length = curLength // don't show successful subtraces
3374+
else
3375+
b.append("\n").append(" " * indent).append("<== ").append(str1).append(" = ").append(show(res))
33613376
indent -= 2
33623377
res
3363-
}
3378+
3379+
private def traceIndentedIfNotShort[T](str: String)(op: => T): T =
3380+
if short then op else traceIndented(str)(op)
33643381

33653382
private def frozenNotice: String =
33663383
if frozenConstraint then " in frozen constraint" else ""
@@ -3371,7 +3388,8 @@ class ExplainingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
33713388
then s" ${tp1.getClass} ${tp2.getClass}"
33723389
else ""
33733390
val approx = approxState
3374-
traceIndented(s"${show(tp1)} <: ${show(tp2)}$moreInfo${approx.show}$frozenNotice") {
3391+
def approxStr = if short then "" else approx.show
3392+
traceIndented(s"${show(tp1)} <: ${show(tp2)}$moreInfo${approxStr}$frozenNotice") {
33753393
super.recur(tp1, tp2)
33763394
}
33773395

@@ -3381,12 +3399,12 @@ class ExplainingTypeComparer(initctx: Context) extends TypeComparer(initctx) {
33813399
}
33823400

33833401
override def lub(tp1: Type, tp2: Type, canConstrain: Boolean, isSoft: Boolean): Type =
3384-
traceIndented(s"lub(${show(tp1)}, ${show(tp2)}, canConstrain=$canConstrain, isSoft=$isSoft)") {
3402+
traceIndentedIfNotShort(s"lub(${show(tp1)}, ${show(tp2)}, canConstrain=$canConstrain, isSoft=$isSoft)") {
33853403
super.lub(tp1, tp2, canConstrain, isSoft)
33863404
}
33873405

33883406
override def glb(tp1: Type, tp2: Type): Type =
3389-
traceIndented(s"glb(${show(tp1)}, ${show(tp2)})") {
3407+
traceIndentedIfNotShort(s"glb(${show(tp1)}, ${show(tp2)})") {
33903408
super.glb(tp1, tp2)
33913409
}
33923410

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)