Skip to content

Commit 1f8229c

Browse files
Backport "Better error diagnostics under -explain-cyclic" to LTS (#21083)
Backports #20251 to the LTS branch. PR submitted by the release tooling. [skip ci]
2 parents bb73f5f + 900e74b commit 1f8229c

18 files changed

+175
-48
lines changed

Diff for: compiler/src/dotty/tools/dotc/core/SymDenotations.scala

+4-16
Original file line numberDiff line numberDiff line change
@@ -167,16 +167,11 @@ object SymDenotations {
167167
}
168168
}
169169
else
170-
val traceCycles = CyclicReference.isTraced
171-
try
172-
if traceCycles then
173-
CyclicReference.pushTrace("compute the signature of ", symbol, "")
170+
CyclicReference.trace("compute the signature of ", symbol):
174171
if myFlags.is(Touched) then
175172
throw CyclicReference(this)(using ctx.withOwner(symbol))
176173
myFlags |= Touched
177174
atPhase(validFor.firstPhaseId)(completer.complete(this))
178-
finally
179-
if traceCycles then CyclicReference.popTrace()
180175

181176
protected[dotc] def info_=(tp: Type): Unit = {
182177
/* // DEBUG
@@ -2980,12 +2975,9 @@ object SymDenotations {
29802975
def apply(clsd: ClassDenotation)(implicit onBehalf: BaseData, ctx: Context)
29812976
: (List[ClassSymbol], BaseClassSet) = {
29822977
assert(isValid)
2983-
val traceCycles = CyclicReference.isTraced
2984-
try
2985-
if traceCycles then
2986-
CyclicReference.pushTrace("compute the base classes of ", clsd.symbol, "")
2987-
if (cache != null) cache.uncheckedNN
2988-
else {
2978+
CyclicReference.trace("compute the base classes of ", clsd.symbol):
2979+
if cache != null then cache.uncheckedNN
2980+
else
29892981
if (locked) throw CyclicReference(clsd)
29902982
locked = true
29912983
provisional = false
@@ -2995,10 +2987,6 @@ object SymDenotations {
29952987
if (!provisional) cache = computed
29962988
else onBehalf.signalProvisional()
29972989
computed
2998-
}
2999-
finally
3000-
if traceCycles then CyclicReference.popTrace()
3001-
addDependent(onBehalf)
30022990
}
30032991

30042992
def sameGroup(p1: Phase, p2: Phase) = p1.sameParentsStartId == p2.sameParentsStartId

Diff for: compiler/src/dotty/tools/dotc/core/TypeErrors.scala

+15-4
Original file line numberDiff line numberDiff line change
@@ -198,20 +198,31 @@ object CyclicReference:
198198
cyclicErrors.println(elem.toString)
199199
ex
200200

201-
type TraceElement = (/*prefix:*/ String, Symbol, /*suffix:*/ String)
201+
type TraceElement = Context ?=> String
202202
type Trace = mutable.ArrayBuffer[TraceElement]
203203
val Trace = Property.Key[Trace]
204204

205-
def isTraced(using Context) =
205+
private def isTraced(using Context) =
206206
ctx.property(CyclicReference.Trace).isDefined
207207

208-
def pushTrace(info: TraceElement)(using Context): Unit =
208+
private def pushTrace(info: TraceElement)(using Context): Unit =
209209
for buf <- ctx.property(CyclicReference.Trace) do
210210
buf += info
211211

212-
def popTrace()(using Context): Unit =
212+
private def popTrace()(using Context): Unit =
213213
for buf <- ctx.property(CyclicReference.Trace) do
214214
buf.dropRightInPlace(1)
215+
216+
inline def trace[T](info: TraceElement)(inline op: => T)(using Context): T =
217+
val traceCycles = isTraced
218+
try
219+
if traceCycles then pushTrace(info)
220+
op
221+
finally
222+
if traceCycles then popTrace()
223+
224+
inline def trace[T](prefix: String, sym: Symbol)(inline op: => T)(using Context): T =
225+
trace((ctx: Context) ?=> i"$prefix$sym")(op)
215226
end CyclicReference
216227

217228
class UnpicklingError(denot: Denotation, where: String, cause: Throwable)(using Context) extends TypeError:

Diff for: compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala

+9-14
Original file line numberDiff line numberDiff line change
@@ -138,20 +138,15 @@ class TreeUnpickler(reader: TastyReader,
138138
if f == null then "" else s" in $f"
139139
def fail(ex: Throwable) = throw UnpicklingError(denot, where, ex)
140140
treeAtAddr(currentAddr) =
141-
val traceCycles = CyclicReference.isTraced
142-
try
143-
if traceCycles then
144-
CyclicReference.pushTrace("read the definition of ", denot.symbol, where)
145-
atPhaseBeforeTransforms {
146-
new TreeReader(reader).readIndexedDef()(
147-
using ctx.withOwner(owner).withModeBits(mode).withSource(source))
148-
}
149-
catch
150-
case ex: CyclicReference => throw ex
151-
case ex: AssertionError => fail(ex)
152-
case ex: Exception => fail(ex)
153-
finally
154-
if traceCycles then CyclicReference.popTrace()
141+
CyclicReference.trace(i"read the definition of ${denot.symbol}$where"):
142+
try
143+
atPhaseBeforeTransforms:
144+
new TreeReader(reader).readIndexedDef()(
145+
using ctx.withOwner(owner).withModeBits(mode).withSource(source))
146+
catch
147+
case ex: CyclicReference => throw ex
148+
case ex: AssertionError => fail(ex)
149+
case ex: Exception => fail(ex)
155150
}
156151

157152
class TreeReader(val reader: TastyReader) {

Diff for: compiler/src/dotty/tools/dotc/reporting/messages.scala

+2-1
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,8 @@ abstract class CyclicMsg(errorId: ErrorMessageID)(using Context) extends Message
9595
protected def context: String = ex.optTrace match
9696
case Some(trace) =>
9797
s"\n\nThe error occurred while trying to ${
98-
trace.map((prefix, sym, suffix) => i"$prefix$sym$suffix").mkString("\n which required to ")
98+
trace.map(identity) // map with identity will turn Context ?=> String elements to String elements
99+
.mkString("\n which required to ")
99100
}$debugInfo"
100101
case None =>
101102
"\n\n Run with -explain-cyclic for more details."

Diff for: compiler/src/dotty/tools/dotc/typer/Checking.scala

+1-6
Original file line numberDiff line numberDiff line change
@@ -336,10 +336,7 @@ object Checking {
336336
}
337337

338338
if isInteresting(pre) then
339-
val traceCycles = CyclicReference.isTraced
340-
try
341-
if traceCycles then
342-
CyclicReference.pushTrace("explore ", tp.symbol, " for cyclic references")
339+
CyclicReference.trace(i"explore ${tp.symbol} for cyclic references"):
343340
val pre1 = this(pre, false, false)
344341
if locked.contains(tp)
345342
|| tp.symbol.infoOrCompleter.isInstanceOf[NoCompleter]
@@ -354,8 +351,6 @@ object Checking {
354351
finally
355352
locked -= tp
356353
tp.withPrefix(pre1)
357-
finally
358-
if traceCycles then CyclicReference.popTrace()
359354
else tp
360355
}
361356
catch {

Diff for: compiler/src/dotty/tools/dotc/typer/Implicits.scala

+9-4
Original file line numberDiff line numberDiff line change
@@ -1058,10 +1058,15 @@ trait Implicits:
10581058
(searchCtx.scope eq ctx.scope) && (searchCtx.owner eq ctx.owner.owner)
10591059
do ()
10601060

1061-
try ImplicitSearch(pt, argument, span)(using searchCtx).bestImplicit
1062-
catch case ce: CyclicReference =>
1063-
ce.inImplicitSearch = true
1064-
throw ce
1061+
def searchStr =
1062+
if argument.isEmpty then i"argument of type $pt"
1063+
else i"conversion from ${argument.tpe} to $pt"
1064+
1065+
CyclicReference.trace(i"searching for an implicit $searchStr"):
1066+
try ImplicitSearch(pt, argument, span)(using searchCtx).bestImplicit
1067+
catch case ce: CyclicReference =>
1068+
ce.inImplicitSearch = true
1069+
throw ce
10651070
else NoMatchingImplicitsFailure
10661071

10671072
val result =

Diff for: compiler/src/dotty/tools/dotc/typer/Namer.scala

+5-3
Original file line numberDiff line numberDiff line change
@@ -1440,7 +1440,8 @@ class Namer { typer: Typer =>
14401440

14411441
def process(stats: List[Tree])(using Context): Unit = stats match
14421442
case (stat: Export) :: stats1 =>
1443-
processExport(stat, NoSymbol)
1443+
CyclicReference.trace(i"elaborate the export clause $stat"):
1444+
processExport(stat, NoSymbol)
14441445
process(stats1)
14451446
case (stat: Import) :: stats1 =>
14461447
process(stats1)(using ctx.importContext(stat, symbolOfTree(stat)))
@@ -1949,8 +1950,9 @@ class Namer { typer: Typer =>
19491950
}
19501951

19511952
def typedAheadRhs(pt: Type) =
1952-
PrepareInlineable.dropInlineIfError(sym,
1953-
typedAheadExpr(mdef.rhs, pt)(using rhsCtx))
1953+
CyclicReference.trace(i"type the right hand side of $sym since no explicit type was given"):
1954+
PrepareInlineable.dropInlineIfError(sym,
1955+
typedAheadExpr(mdef.rhs, pt)(using rhsCtx))
19541956

19551957
def rhsType =
19561958
// For default getters, we use the corresponding parameter type as an

Diff for: tests/neg-macros/i14772.check

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
|
66
| The error occurred while trying to compute the signature of method $anonfun
77
| which required to compute the signature of method impl
8+
| which required to type the right hand side of method impl since no explicit type was given
89
|
910
| Run with both -explain-cyclic and -Ydebug-cyclic to see full stack trace.
1011
|

Diff for: tests/neg-macros/i16582.check

+2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66
| dotty.tools.dotc.core.CyclicReference: Recursive value o2 needs type
77
|
88
| The error occurred while trying to compute the signature of method test
9+
| which required to type the right hand side of method test since no explicit type was given
910
| which required to compute the signature of value o2
11+
| which required to type the right hand side of value o2 since no explicit type was given
1012
| which required to compute the signature of value o2
1113
|
1214
| Run with both -explain-cyclic and -Ydebug-cyclic to see full stack trace.

Diff for: tests/neg/cyclic.check

+4
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,13 @@
44
| Overloaded or recursive method f needs return type
55
|
66
| The error occurred while trying to compute the signature of method f
7+
| which required to type the right hand side of method f since no explicit type was given
78
| which required to compute the signature of method g
9+
| which required to type the right hand side of method g since no explicit type was given
810
| which required to compute the signature of method h
11+
| which required to type the right hand side of method h since no explicit type was given
912
| which required to compute the signature of method i
13+
| which required to type the right hand side of method i since no explicit type was given
1014
| which required to compute the signature of method f
1115
|
1216
| Run with both -explain-cyclic and -Ydebug-cyclic to see full stack trace.

Diff for: tests/neg/i11994.check

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
|
99
| The error occurred while trying to compute the signature of method foo
1010
| which required to compute the signature of type T
11+
| which required to searching for an implicit conversion from Tuple.type to ?{ meow: ? }
1112
| which required to compute the signature of method foo
1213
|
1314
| Run with both -explain-cyclic and -Ydebug-cyclic to see full stack trace.
@@ -21,6 +22,7 @@
2122
|
2223
| The error occurred while trying to compute the signature of given instance given_Unit
2324
| which required to compute the signature of type T
25+
| which required to searching for an implicit conversion from Tuple.type to ?{ meow: ? }
2426
| which required to compute the signature of given instance given_Unit
2527
|
2628
| Run with both -explain-cyclic and -Ydebug-cyclic to see full stack trace.

Diff for: tests/neg/i20245.check

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
2+
-- [E046] Cyclic Error: tests/neg/i20245/Typer_2.scala:16:57 -----------------------------------------------------------
3+
16 | private[typer] val unification = new Unification(using this) // error
4+
| ^
5+
| Cyclic reference involving class Context
6+
|
7+
| The error occurred while trying to compute the base classes of class Context
8+
| which required to compute the base classes of trait TyperOps
9+
| which required to compute the signature of trait TyperOps
10+
| which required to elaborate the export clause export unification.requireSubtype
11+
| which required to compute the signature of value unification
12+
| which required to type the right hand side of value unification since no explicit type was given
13+
| which required to compute the base classes of class Context
14+
|
15+
| Run with both -explain-cyclic and -Ydebug-cyclic to see full stack trace.
16+
|
17+
| longer explanation available when compiling with `-explain`

Diff for: tests/neg/i20245/Context_1.scala

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package effekt
2+
package context
3+
4+
import effekt.typer.TyperOps
5+
6+
7+
abstract class Context extends TyperOps {
8+
9+
// bring the context itself in scope
10+
implicit val context: Context = this
11+
12+
}

Diff for: tests/neg/i20245/Messages_1.scala

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package effekt
2+
package util
3+
4+
object messages {
5+
trait ErrorReporter {
6+
7+
}
8+
}

Diff for: tests/neg/i20245/Tree_1.scala

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package effekt
2+
package source
3+
4+
import effekt.context.Context
5+
6+
object Resolvable {
7+
8+
// There need to be two resolve extension methods for the error to show up
9+
// They also need to take an implicit Context
10+
extension (n: Int) {
11+
def resolve(using C: Context): Unit = ???
12+
}
13+
14+
extension (b: Boolean) {
15+
def resolve(using C: Context): Unit = ???
16+
}
17+
}
18+
export Resolvable.resolve

Diff for: tests/neg/i20245/Typer_1.scala

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package effekt
2+
package typer
3+
4+
import effekt.util.messages.ErrorReporter
5+
6+
import effekt.context.{ Context }
7+
8+
// This import is also NECESSARY for the cyclic error
9+
import effekt.source.{ resolve }
10+
11+
12+
trait TyperOps extends ErrorReporter { self: Context =>
13+
14+
// passing `this` as ErrorReporter here is also NECESSARY for the cyclic error
15+
private[typer] val unification = new Unification(using this)
16+
17+
// this export is NECESSARY for the cyclic error
18+
export unification.{ requireSubtype }
19+
20+
println(1)
21+
22+
// vvvvvvvv insert a line here, save, and run `compile` again vvvvvvvvvv
23+
}
24+
25+
26+
27+
28+

Diff for: tests/neg/i20245/Typer_2.scala

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
//> using options -explain-cyclic
2+
package effekt
3+
package typer
4+
5+
import effekt.util.messages.ErrorReporter
6+
7+
import effekt.context.{ Context }
8+
9+
// This import is also NECESSARY for the cyclic error
10+
import effekt.source.{ resolve }
11+
12+
13+
trait TyperOps extends ErrorReporter { self: Context =>
14+
15+
// passing `this` as ErrorReporter here is also NECESSARY for the cyclic error
16+
private[typer] val unification = new Unification(using this) // error
17+
18+
// this export is NECESSARY for the cyclic error
19+
export unification.{ requireSubtype }
20+
21+
// vvvvvvvv insert a line here, save, and run `compile` again vvvvvvvvvv
22+
}
23+
24+
25+
26+
27+

Diff for: tests/neg/i20245/Unification_1.scala

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package effekt
2+
package typer
3+
4+
import effekt.util.messages.ErrorReporter
5+
6+
7+
class Unification(using C: ErrorReporter) {
8+
9+
def requireSubtype(): Unit = ()
10+
11+
}

0 commit comments

Comments
 (0)