Skip to content

Commit 97e5316

Browse files
committed
Implement and use central error notes logic in TypeComparer
Needs some adaption for subCaptures tests for now.
1 parent 58cbdbc commit 97e5316

File tree

8 files changed

+105
-56
lines changed

8 files changed

+105
-56
lines changed

compiler/src/dotty/tools/dotc/cc/CCState.scala

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package dotc
33
package cc
44

55
import core.*
6-
import CaptureSet.{CompareResult, CompareFailure, VarState}
6+
import CaptureSet.{CompareResult, VarState}
77
import collection.mutable
88
import reporting.Message
99
import Contexts.Context
@@ -16,24 +16,6 @@ class CCState:
1616

1717
// ------ Error diagnostics -----------------------------
1818

19-
/** Error reprting notes produces since the last call to `test` */
20-
var notes: List[ErrorNote] = Nil
21-
22-
def addNote(note: ErrorNote): Unit =
23-
if !notes.exists(_.getClass == note.getClass) then
24-
notes = note :: notes
25-
26-
def test(op: => CompareResult): CompareResult =
27-
val saved = notes
28-
notes = Nil
29-
try op match
30-
case res: CompareFailure => res.withNotes(notes)
31-
case res => res
32-
finally notes = saved
33-
34-
def testOK(op: => Boolean): CompareResult =
35-
test(if op then CompareResult.OK else CompareResult.Fail(Nil))
36-
3719
/** Warnings relating to upper approximations of capture sets with
3820
* existentially bound variables.
3921
*/

compiler/src/dotty/tools/dotc/cc/Capability.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -558,7 +558,7 @@ object Capabilities:
558558
case y: ResultCap => vs.unify(x, y)
559559
case _ => y.derivesFromSharedCapability
560560
if !result then
561-
ccState.addNote(CaptureSet.ExistentialSubsumesFailure(x, y))
561+
TypeComparer.addErrorNote(CaptureSet.ExistentialSubsumesFailure(x, y))
562562
result
563563
case GlobalCap =>
564564
y match

compiler/src/dotty/tools/dotc/cc/CaptureOps.scala

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,6 @@ def depFun(args: List[Type], resultType: Type, isContextual: Boolean, paramNames
4141
/** An exception thrown if a @retains argument is not syntactically a Capability */
4242
class IllegalCaptureRef(tpe: Type)(using Context) extends Exception(tpe.show)
4343

44-
/** A base trait for data producing addenda to error messages */
45-
trait ErrorNote
46-
4744
/** The currently valid CCState */
4845
def ccState(using Context): CCState =
4946
Phases.checkCapturesPhase.asInstanceOf[CheckCaptures].ccState1

compiler/src/dotty/tools/dotc/cc/CaptureSet.scala

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import util.{SimpleIdentitySet, Property}
1616
import typer.ErrorReporting.Addenda
1717
import util.common.alwaysTrue
1818
import scala.collection.{mutable, immutable}
19+
import TypeComparer.ErrorNote
1920
import CCState.*
2021
import TypeOps.AvoidMap
2122
import compiletime.uninitialized
@@ -242,7 +243,7 @@ sealed abstract class CaptureSet extends Showable:
242243
if result.isOK then
243244
addDependent(that)
244245
else
245-
result.levelError.foreach(ccState.addNote)
246+
result.levelError.foreach(TypeComparer.addErrorNote)
246247
varState.rollBack()
247248
result
248249
//.showing(i"subcaptures $this <:< $that = ${result.show}", capt)
@@ -960,6 +961,8 @@ object CaptureSet:
960961

961962
//assert(id != 4)
962963

964+
description = i"elements subsumed by $owningCap"
965+
963966
private def aliasRef: FreshCap | Null =
964967
if myElems.size == 1 then
965968
myElems.nth(0) match
@@ -1075,17 +1078,10 @@ object CaptureSet:
10751078
*/
10761079
case class ExistentialSubsumesFailure(val ex: ResultCap, val other: Capability) extends ErrorNote
10771080

1078-
trait CompareFailure:
1079-
private var myErrorNotes: List[ErrorNote] = Nil
1080-
def errorNotes: List[ErrorNote] = myErrorNotes
1081-
def withNotes(notes: List[ErrorNote]): this.type =
1082-
myErrorNotes = notes
1083-
this
1084-
10851081
enum CompareResult extends Showable:
10861082
case OK
1087-
case Fail(trace: List[CaptureSet]) extends CompareResult, CompareFailure
1088-
case LevelError(cs: CaptureSet, elem: Capability) extends CompareResult, CompareFailure, ErrorNote
1083+
case Fail(trace: List[CaptureSet]) extends CompareResult, ErrorNote
1084+
case LevelError(cs: CaptureSet, elem: Capability) extends CompareResult, ErrorNote
10891085

10901086
override def toText(printer: Printer): Text =
10911087
inContext(printer.printerContext):

compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import util.{SimpleIdentitySet, EqHashMap, EqHashSet, SrcPos, Property}
1818
import transform.{Recheck, PreRecheck, CapturedVars}
1919
import Recheck.*
2020
import scala.collection.mutable
21-
import CaptureSet.{withCaptureSetsExplained, CompareResult, CompareFailure, ExistentialSubsumesFailure}
21+
import CaptureSet.{withCaptureSetsExplained, CompareResult, ExistentialSubsumesFailure}
2222
import CCState.*
2323
import StdNames.nme
2424
import NameKinds.{DefaultGetterName, WildcardParamName, UniqueNameKind}
@@ -352,11 +352,12 @@ class CheckCaptures extends Recheck, SymTransformer:
352352
assert(cs1.subCaptures(cs2).isOK, i"$cs1 is not a subset of $cs2")
353353

354354
/** If `res` is not CompareResult.OK, report an error */
355-
def checkOK(res: CompareResult, prefix: => String, added: Capability | CaptureSet, target: CaptureSet, pos: SrcPos, provenance: => String = "")(using Context): Unit =
355+
def checkOK(res: TypeComparer.CompareResult, prefix: => String, added: Capability | CaptureSet, target: CaptureSet, pos: SrcPos, provenance: => String = "")(using Context): Unit =
356356
res match
357-
case res: CompareFailure =>
357+
case TypeComparer.CompareResult.Fail(notes) =>
358+
val ((res: CompareResult) :: Nil, otherNotes) = notes.partition(_.isInstanceOf[CompareResult]): @unchecked
358359
def msg(provisional: Boolean) =
359-
def toAdd: String = errorNotes(res.errorNotes).toAdd.mkString
360+
def toAdd: String = errorNotes(otherNotes).toAdd.mkString
360361
def descr: String =
361362
val d = res.blocking.description
362363
if d.isEmpty then provenance else ""
@@ -377,18 +378,27 @@ class CheckCaptures extends Recheck, SymTransformer:
377378
report.error(msg(provisional = false), pos)
378379
case _ =>
379380

381+
def convertResult(op: => CompareResult)(using Context): TypeComparer.CompareResult =
382+
TypeComparer.test:
383+
op match
384+
case res: TypeComparer.ErrorNote =>
385+
TypeComparer.addErrorNote(res)
386+
false
387+
case CompareResult.OK =>
388+
true
389+
380390
/** Check subcapturing `{elem} <: cs`, report error on failure */
381391
def checkElem(elem: Capability, cs: CaptureSet, pos: SrcPos, provenance: => String = "")(using Context) =
382392
checkOK(
383-
ccState.test(elem.singletonCaptureSet.subCaptures(cs)),
393+
convertResult(elem.singletonCaptureSet.subCaptures(cs)),
384394
i"$elem cannot be referenced here; it is not",
385395
elem, cs, pos, provenance)
386396

387397
/** Check subcapturing `cs1 <: cs2`, report error on failure */
388398
def checkSubset(cs1: CaptureSet, cs2: CaptureSet, pos: SrcPos,
389399
provenance: => String = "", cs1description: String = "")(using Context) =
390400
checkOK(
391-
ccState.test(cs1.subCaptures(cs2)),
401+
convertResult(cs1.subCaptures(cs2)),
392402
if cs1.elems.size == 1 then i"reference ${cs1.elems.nth(0)}$cs1description is not"
393403
else i"references $cs1$cs1description are not all",
394404
cs1, cs2, pos, provenance)
@@ -1272,7 +1282,7 @@ class CheckCaptures extends Recheck, SymTransformer:
12721282

12731283
type BoxErrors = mutable.ListBuffer[Message] | Null
12741284

1275-
private def errorNotes(notes: List[ErrorNote])(using Context): Addenda =
1285+
private def errorNotes(notes: List[TypeComparer.ErrorNote])(using Context): Addenda =
12761286
if notes.isEmpty then NothingToAdd
12771287
else new Addenda:
12781288
override def toAdd(using Context) = notes.map: note =>
@@ -1336,20 +1346,20 @@ class CheckCaptures extends Recheck, SymTransformer:
13361346
if actualBoxed eq actual then
13371347
// Only `addOuterRefs` when there is no box adaptation
13381348
expected1 = addOuterRefs(expected1, actual, tree.srcPos)
1339-
ccState.testOK(isCompatible(actualBoxed, expected1)) match
1340-
case CompareResult.OK =>
1341-
if debugSuccesses then tree match
1342-
case Ident(_) =>
1343-
println(i"SUCCESS $tree for $actual <:< $expected:\n${TypeComparer.explained(_.isSubType(actualBoxed, expected1))}")
1344-
case _ =>
1345-
actualBoxed
1346-
case fail: CompareFailure =>
1349+
TypeComparer.test(isCompatible(actualBoxed, expected1)) match
1350+
case TypeComparer.CompareResult.Fail(notes) =>
13471351
capt.println(i"conforms failed for ${tree}: $actual vs $expected")
13481352
err.typeMismatch(tree.withType(actualBoxed), expected1,
13491353
addApproxAddenda(
1350-
addenda ++ errorNotes(fail.errorNotes),
1354+
addenda ++ errorNotes(notes),
13511355
expected1))
13521356
actual
1357+
case /*OK*/ _ =>
1358+
if debugSuccesses then tree match
1359+
case Ident(_) =>
1360+
println(i"SUCCESS $tree for $actual <:< $expected:\n${TypeComparer.explained(_.isSubType(actualBoxed, expected1))}")
1361+
case _ =>
1362+
actualBoxed
13531363
end checkConformsExpr
13541364

13551365
/** Turn `expected` into a dependent function when `actual` is dependent. */

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

Lines changed: 66 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,17 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
5050
opaquesUsed = false
5151
recCount = 0
5252
needsGc = false
53+
maxErrorLevel = -1
54+
errorNotes = Nil
5355
if Config.checkTypeComparerReset then checkReset()
5456

5557
private var pendingSubTypes: util.MutableSet[(Type, Type)] | Null = null
5658
private var recCount = 0
5759
private var monitored = false
5860

61+
private var maxErrorLevel: Int = -1
62+
private var errorNotes: List[(Int, ErrorNote)] = Nil
63+
5964
private var needsGc = false
6065

6166
private var canCompareAtoms: Boolean = true // used for internal consistency checking
@@ -148,7 +153,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
148153
def testSubType(tp1: Type, tp2: Type): CompareResult =
149154
GADTused = false
150155
opaquesUsed = false
151-
if !topLevelSubType(tp1, tp2) then CompareResult.Fail
156+
if !topLevelSubType(tp1, tp2) then CompareResult.Fail(Nil)
152157
else if GADTused then CompareResult.OKwithGADTUsed
153158
else if opaquesUsed then CompareResult.OKwithOpaquesUsed // we cast on GADTused, so handles if both are used
154159
else CompareResult.OK
@@ -1584,10 +1589,9 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
15841589
ctx.gadtState.restore(savedGadt)
15851590
val savedSuccessCount = successCount
15861591
try
1587-
recCount += 1
1588-
if recCount >= Config.LogPendingSubTypesThreshold then monitored = true
1589-
val result = if monitored then monitoredIsSubType else firstTry
1590-
recCount -= 1
1592+
val result = inNestedLevel:
1593+
if recCount >= Config.LogPendingSubTypesThreshold then monitored = true
1594+
if monitored then monitoredIsSubType else firstTry
15911595
if !result then restore()
15921596
else if recCount == 0 && needsGc then
15931597
state.gc()
@@ -1602,6 +1606,32 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
16021606
throw ex
16031607
}
16041608

1609+
/** Run `op` in a recursion level (indicated by `recCount`) increased by one.
1610+
* This affects when monitoring starts and how error notes are propagated.
1611+
* On exit, error notes added at the current level are either
1612+
* - promoted to the next outer level (in case of failure),
1613+
* - cancelled (in case of success).
1614+
*/
1615+
inline def inNestedLevel(inline op: Boolean): Boolean =
1616+
recCount += 1
1617+
val result = op
1618+
recCount -= 1
1619+
if maxErrorLevel > recCount then
1620+
if result then
1621+
maxErrorLevel = -1
1622+
errorNotes = errorNotes.filterConserve: p =>
1623+
val (level, note) = p
1624+
if level <= recCount then
1625+
if level > maxErrorLevel then maxErrorLevel = level
1626+
true
1627+
else false
1628+
else
1629+
errorNotes = errorNotes.mapConserve: p =>
1630+
val (level, note) = p
1631+
if level > recCount then (recCount, note) else p
1632+
maxErrorLevel = recCount
1633+
result
1634+
16051635
private def nonExprBaseType(tp: Type, cls: Symbol)(using Context): Type =
16061636
if tp.isInstanceOf[ExprType] then NoType
16071637
else tp.baseType(cls)
@@ -3219,12 +3249,26 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
32193249

32203250
def reduceMatchWith[T](op: MatchReducer => T)(using Context): T =
32213251
inSubComparer(matchReducer)(op)
3252+
3253+
/** Add given ErrorNote note, provided there is not yet an error note with
3254+
* the same class as `note`.
3255+
*/
3256+
def addErrorNote(note: ErrorNote): Unit =
3257+
if errorNotes.forall(_._2.getClass != note.getClass) then
3258+
errorNotes = (recCount, note) :: errorNotes
3259+
assert(maxErrorLevel <= recCount)
3260+
maxErrorLevel = recCount
32223261
}
32233262

32243263
object TypeComparer {
32253264

3265+
/** A base trait for data producing addenda to error messages */
3266+
trait ErrorNote
3267+
3268+
/** A richer compare result, returned by `testSubType` and `test`. */
32263269
enum CompareResult:
3227-
case OK, Fail, OKwithGADTUsed, OKwithOpaquesUsed
3270+
case OK, OKwithGADTUsed, OKwithOpaquesUsed
3271+
case Fail(errorNotes: List[ErrorNote])
32283272

32293273
/** Class for unification variables used in `natValue`. */
32303274
private class AnyConstantType extends UncachedGroundType with ValueType {
@@ -3393,6 +3437,22 @@ object TypeComparer {
33933437

33943438
def subCaptures(refs1: CaptureSet, refs2: CaptureSet, vs: CaptureSet.VarState)(using Context): CaptureSet.CompareResult =
33953439
comparing(_.subCaptures(refs1, refs2, vs))
3440+
3441+
def inNestedLevel(op: => Boolean)(using Context): Boolean =
3442+
comparer.inNestedLevel(op)
3443+
3444+
def addErrorNote(note: ErrorNote)(using Context): Unit =
3445+
comparer.addErrorNote(note)
3446+
3447+
/** Run `op` on current type comparer, maping its Boolean result to
3448+
* a CompareResult with possible outcomes OK and Fail(...)`. In case
3449+
* of failure pass the accumulated errorNotes of this type comparer to
3450+
* in the Fail value.
3451+
*/
3452+
def test(op: => Boolean)(using Context): CompareResult =
3453+
comparing: comparer =>
3454+
if op then CompareResult.OK
3455+
else CompareResult.Fail(comparer.errorNotes.map(_._2))
33963456
}
33973457

33983458
object MatchReducer:

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4581,7 +4581,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
45814581
tree
45824582
}
45834583
else TypeComparer.testSubType(tree.tpe.widenExpr, pt) match
4584-
case CompareResult.Fail =>
4584+
case CompareResult.Fail(_) =>
45854585
wtp match
45864586
case wtp: MethodType => missingArgs(wtp)
45874587
case _ =>

tests/neg-custom-args/captures/capt-depfun.check

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66
|
77
| where: => refers to a fresh root capability in the type of value dc
88
|
9+
|
10+
| Note that the existential capture root in Str^{x} => Str^{x}
11+
| cannot subsume the capability <cap of (x: C^): Str^{x} => Str^{x}>
12+
|
913
| longer explanation available when compiling with `-explain`
1014
-- Error: tests/neg-custom-args/captures/capt-depfun.scala:11:24 -------------------------------------------------------
1115
11 | val dc: ((Str^{y, z}) => Str^{y, z}) = ac(g()) // error // error: separatioon

0 commit comments

Comments
 (0)