Skip to content

Commit ce78e87

Browse files
committed
Drop CaptureSet.CompareResult
Handle everything using error notes
1 parent 97e5316 commit ce78e87

File tree

8 files changed

+165
-172
lines changed

8 files changed

+165
-172
lines changed

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

Lines changed: 1 addition & 1 deletion
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, VarState}
6+
import CaptureSet.VarState
77
import collection.mutable
88
import reporting.Message
99
import Contexts.Context

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ extension (tp: Type)
154154
* the two capture sets are combined.
155155
*/
156156
def capturing(cs: CaptureSet)(using Context): Type =
157-
if (cs.isAlwaysEmpty || cs.isConst && cs.subCaptures(tp.captureSet, VarState.Separate).isOK)
157+
if (cs.isAlwaysEmpty || cs.isConst && cs.subCaptures(tp.captureSet, VarState.Separate))
158158
&& !cs.keepAlways
159159
then tp
160160
else tp match

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

Lines changed: 93 additions & 115 deletions
Large diffs are not rendered by default.

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

Lines changed: 12 additions & 19 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, ExistentialSubsumesFailure}
21+
import CaptureSet.{withCaptureSetsExplained, CompareFailure, LevelError, ExistentialSubsumesFailure}
2222
import CCState.*
2323
import StdNames.nme
2424
import NameKinds.{DefaultGetterName, WildcardParamName, UniqueNameKind}
@@ -349,13 +349,14 @@ class CheckCaptures extends Recheck, SymTransformer:
349349

350350
/** Assert subcapturing `cs1 <: cs2` (available for debugging, otherwise unused) */
351351
def assertSub(cs1: CaptureSet, cs2: CaptureSet)(using Context) =
352-
assert(cs1.subCaptures(cs2).isOK, i"$cs1 is not a subset of $cs2")
352+
assert(cs1.subCaptures(cs2), i"$cs1 is not a subset of $cs2")
353353

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

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-
390382
/** Check subcapturing `{elem} <: cs`, report error on failure */
391383
def checkElem(elem: Capability, cs: CaptureSet, pos: SrcPos, provenance: => String = "")(using Context) =
392384
checkOK(
393-
convertResult(elem.singletonCaptureSet.subCaptures(cs)),
385+
TypeComparer.compareResult(elem.singletonCaptureSet.subCaptures(cs)),
394386
i"$elem cannot be referenced here; it is not",
395387
elem, cs, pos, provenance)
396388

397389
/** Check subcapturing `cs1 <: cs2`, report error on failure */
398390
def checkSubset(cs1: CaptureSet, cs2: CaptureSet, pos: SrcPos,
399391
provenance: => String = "", cs1description: String = "")(using Context) =
400392
checkOK(
401-
convertResult(cs1.subCaptures(cs2)),
393+
TypeComparer.compareResult(cs1.subCaptures(cs2)),
402394
if cs1.elems.size == 1 then i"reference ${cs1.elems.nth(0)}$cs1description is not"
403395
else i"references $cs1$cs1description are not all",
404396
cs1, cs2, pos, provenance)
@@ -1283,11 +1275,12 @@ class CheckCaptures extends Recheck, SymTransformer:
12831275
type BoxErrors = mutable.ListBuffer[Message] | Null
12841276

12851277
private def errorNotes(notes: List[TypeComparer.ErrorNote])(using Context): Addenda =
1286-
if notes.isEmpty then NothingToAdd
1278+
val printableNotes = notes.filter(_.isInstanceOf[LevelError | ExistentialSubsumesFailure])
1279+
if printableNotes.isEmpty then NothingToAdd
12871280
else new Addenda:
1288-
override def toAdd(using Context) = notes.map: note =>
1281+
override def toAdd(using Context) = printableNotes.map: note =>
12891282
val msg = note match
1290-
case CompareResult.LevelError(cs, ref) =>
1283+
case LevelError(cs, ref) =>
12911284
if ref.core.isCapOrFresh then
12921285
i"""the universal capability $ref
12931286
|cannot be included in capture set $cs"""
@@ -1346,7 +1339,7 @@ class CheckCaptures extends Recheck, SymTransformer:
13461339
if actualBoxed eq actual then
13471340
// Only `addOuterRefs` when there is no box adaptation
13481341
expected1 = addOuterRefs(expected1, actual, tree.srcPos)
1349-
TypeComparer.test(isCompatible(actualBoxed, expected1)) match
1342+
TypeComparer.compareResult(isCompatible(actualBoxed, expected1)) match
13501343
case TypeComparer.CompareResult.Fail(notes) =>
13511344
capt.println(i"conforms failed for ${tree}: $actual vs $expected")
13521345
err.typeMismatch(tree.withType(actualBoxed), expected1,
@@ -1522,7 +1515,7 @@ class CheckCaptures extends Recheck, SymTransformer:
15221515
val cs = actual.captureSet
15231516
if covariant then cs ++ leaked
15241517
else
1525-
if !leaked.subCaptures(cs).isOK then
1518+
if !leaked.subCaptures(cs) then
15261519
report.error(
15271520
em"""$expected cannot be box-converted to ${actual.capturing(leaked)}
15281521
|since the additional capture set $leaked resulting from box conversion is not allowed in $actual""", tree.srcPos)

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

Lines changed: 55 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
5959
private var monitored = false
6060

6161
private var maxErrorLevel: Int = -1
62-
private var errorNotes: List[(Int, ErrorNote)] = Nil
62+
protected var errorNotes: List[(Int, ErrorNote)] = Nil
6363

6464
private var needsGc = false
6565

@@ -433,7 +433,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
433433
case _ =>
434434
if isCaptureVarComparison then
435435
return CCState.withCapAsRoot:
436-
subCaptures(tp1.captureSet, tp2.captureSet).isOK
436+
subCaptures(tp1.captureSet, tp2.captureSet)
437437
if (tp1 eq NothingType) || isBottom(tp1) then
438438
return true
439439
}
@@ -541,7 +541,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
541541
case tp1 @ CapturingType(parent1, refs1) =>
542542
def compareCapturing =
543543
if tp2.isAny then true
544-
else if subCaptures(refs1, tp2.captureSet).isOK && sameBoxed(tp1, tp2, refs1)
544+
else if subCaptures(refs1, tp2.captureSet) && sameBoxed(tp1, tp2, refs1)
545545
|| !ctx.mode.is(Mode.CheckBoundsOrSelfType) && tp1.isAlwaysPure
546546
then
547547
val tp2a =
@@ -583,7 +583,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
583583

584584
if isCaptureVarComparison then
585585
return CCState.withCapAsRoot:
586-
subCaptures(tp1.captureSet, tp2.captureSet).isOK
586+
subCaptures(tp1.captureSet, tp2.captureSet)
587587

588588
isSubApproxHi(tp1, info2.lo) && (trustBounds || isSubApproxHi(tp1, info2.hi))
589589
|| compareGADT
@@ -668,12 +668,12 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
668668
&& isSubInfo(info1.resultType, info2.resultType.subst(info2, info1))
669669
case (info1 @ CapturingType(parent1, refs1), info2: Type)
670670
if info2.stripCapturing.isInstanceOf[MethodOrPoly] =>
671-
subCaptures(refs1, info2.captureSet).isOK && sameBoxed(info1, info2, refs1)
671+
subCaptures(refs1, info2.captureSet) && sameBoxed(info1, info2, refs1)
672672
&& isSubInfo(parent1, info2)
673673
case (info1: Type, CapturingType(parent2, refs2))
674674
if info1.stripCapturing.isInstanceOf[MethodOrPoly] =>
675675
val refs1 = info1.captureSet
676-
(refs1.isAlwaysEmpty || subCaptures(refs1, refs2).isOK) && sameBoxed(info1, info2, refs1)
676+
(refs1.isAlwaysEmpty || subCaptures(refs1, refs2)) && sameBoxed(info1, info2, refs1)
677677
&& isSubInfo(info1, parent2)
678678
case _ =>
679679
isSubType(info1, info2)
@@ -867,12 +867,12 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
867867
// Eamples where this arises is capt-capibility.scala and function-combinators.scala
868868
val singletonOK = tp1 match
869869
case tp1: SingletonType
870-
if subCaptures(tp1.underlying.captureSet, refs2, CaptureSet.VarState.Separate).isOK =>
870+
if subCaptures(tp1.underlying.captureSet, refs2, CaptureSet.VarState.Separate) =>
871871
recur(tp1.widen, tp2)
872872
case _ =>
873873
false
874874
singletonOK
875-
|| subCaptures(refs1, refs2).isOK
875+
|| subCaptures(refs1, refs2)
876876
&& sameBoxed(tp1, tp2, refs1)
877877
&& (recur(tp1.widen.stripCapturing, parent2)
878878
|| tp1.isInstanceOf[SingletonType] && recur(tp1, parent2)
@@ -2830,7 +2830,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
28302830
if frozenConstraint then CaptureSet.VarState.Closed() else CaptureSet.VarState()
28312831

28322832
protected def subCaptures(refs1: CaptureSet, refs2: CaptureSet,
2833-
vs: CaptureSet.VarState = makeVarState())(using Context): CaptureSet.CompareResult =
2833+
vs: CaptureSet.VarState = makeVarState())(using Context): Boolean =
28342834
try
28352835
refs1.subCaptures(refs2, vs)
28362836
catch case ex: AssertionError =>
@@ -2843,7 +2843,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
28432843
*/
28442844
protected def sameBoxed(tp1: Type, tp2: Type, refs1: CaptureSet)(using Context): Boolean =
28452845
(tp1.isBoxedCapturing == tp2.isBoxedCapturing)
2846-
|| refs1.subCaptures(CaptureSet.empty, makeVarState()).isOK
2846+
|| refs1.subCaptures(CaptureSet.empty, makeVarState())
28472847

28482848
// ----------- Diagnostics --------------------------------------------------
28492849

@@ -3254,16 +3254,40 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
32543254
* the same class as `note`.
32553255
*/
32563256
def addErrorNote(note: ErrorNote): Unit =
3257-
if errorNotes.forall(_._2.getClass != note.getClass) then
3257+
if errorNotes.forall(_._2.kind != note.kind) then
32583258
errorNotes = (recCount, note) :: errorNotes
32593259
assert(maxErrorLevel <= recCount)
32603260
maxErrorLevel = recCount
3261+
3262+
private[TypeComparer] inline
3263+
def isolated[T](inline op: Boolean, inline mapResult: Boolean => T)(using Context): T =
3264+
val savedNotes = errorNotes
3265+
val savedLevel = maxErrorLevel
3266+
errorNotes = Nil
3267+
maxErrorLevel = -1
3268+
try mapResult(op)
3269+
finally
3270+
errorNotes = savedNotes
3271+
maxErrorLevel = savedLevel
3272+
3273+
/** Run `op` on current type comparer, maping its Boolean result to
3274+
* a CompareResult with possible outcomes OK and Fail(...)`. In case
3275+
* of failure pass the accumulated errorNotes of this type comparer to
3276+
* in the Fail value.
3277+
*/
3278+
def compareResult(op: => Boolean)(using Context): CompareResult =
3279+
isolated(op, res =>
3280+
if res then CompareResult.OK else CompareResult.Fail(errorNotes.map(_._2)))
32613281
}
32623282

32633283
object TypeComparer {
32643284

32653285
/** A base trait for data producing addenda to error messages */
3266-
trait ErrorNote
3286+
trait ErrorNote:
3287+
/** A disciminating kind. An error note is not added if it has the same kind
3288+
* as an already existing error note.
3289+
*/
3290+
def kind: Class[?] = getClass
32673291

32683292
/** A richer compare result, returned by `testSubType` and `test`. */
32693293
enum CompareResult:
@@ -3280,7 +3304,6 @@ object TypeComparer {
32803304
else res match
32813305
case ClassInfo(_, cls, _, _, _) => cls.showLocated
32823306
case bounds: TypeBounds => i"type bounds [$bounds]"
3283-
case CaptureSet.CompareResult.OK => "OK"
32843307
case res: printing.Showable => res.show
32853308
case _ => String.valueOf(res).nn
32863309

@@ -3435,7 +3458,7 @@ object TypeComparer {
34353458
def reduceMatchWith[T](op: MatchReducer => T)(using Context): T =
34363459
comparing(_.reduceMatchWith(op))
34373460

3438-
def subCaptures(refs1: CaptureSet, refs2: CaptureSet, vs: CaptureSet.VarState)(using Context): CaptureSet.CompareResult =
3461+
def subCaptures(refs1: CaptureSet, refs2: CaptureSet, vs: CaptureSet.VarState)(using Context): Boolean =
34393462
comparing(_.subCaptures(refs1, refs2, vs))
34403463

34413464
def inNestedLevel(op: => Boolean)(using Context): Boolean =
@@ -3444,15 +3467,16 @@ object TypeComparer {
34443467
def addErrorNote(note: ErrorNote)(using Context): Unit =
34453468
comparer.addErrorNote(note)
34463469

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))
3470+
def updateErrorNotes(f: PartialFunction[ErrorNote, ErrorNote])(using Context): Unit =
3471+
comparer.errorNotes = comparer.errorNotes.mapConserve: p =>
3472+
val (level, note) = p
3473+
if f.isDefinedAt(note) then (level, f(note)) else p
3474+
3475+
def compareResult(op: => Boolean)(using Context): CompareResult =
3476+
comparing(_.compareResult(op))
3477+
3478+
inline def noNotes(inline op: Boolean)(using Context): Boolean =
3479+
comparer.isolated(op, x => x)
34563480
}
34573481

34583482
object MatchReducer:
@@ -3857,9 +3881,11 @@ class ExplainingTypeComparer(initctx: Context, short: Boolean) extends TypeCompa
38573881
private val b = new StringBuilder
38583882
private var lastForwardGoal: String | Null = null
38593883

3860-
private def appendFailure(x: String) =
3884+
private def appendFailure(notes: List[ErrorNote]) =
38613885
if lastForwardGoal != null then // last was deepest goal that failed
3862-
b.append(s" = $x")
3886+
b.append(s" = false")
3887+
for case note: printing.Showable <- notes do
3888+
b.append(i": $note")
38633889
lastForwardGoal = null
38643890

38653891
override def traceIndented[T](str: String)(op: => T): T =
@@ -3875,9 +3901,9 @@ class ExplainingTypeComparer(initctx: Context, short: Boolean) extends TypeCompa
38753901
if short then
38763902
res match
38773903
case false =>
3878-
appendFailure("false")
3879-
case res: CaptureSet.CompareResult if res != CaptureSet.CompareResult.OK =>
3880-
appendFailure(show(res))
3904+
appendFailure(errorNotes.map(_._2))
3905+
case CompareResult.Fail(notes) =>
3906+
appendFailure(notes)
38813907
case _ =>
38823908
b.length = curLength // don't show successful subtraces
38833909
else
@@ -3927,7 +3953,7 @@ class ExplainingTypeComparer(initctx: Context, short: Boolean) extends TypeCompa
39273953
super.gadtAddBound(sym, b, isUpper)
39283954
}
39293955

3930-
override def subCaptures(refs1: CaptureSet, refs2: CaptureSet, vs: CaptureSet.VarState)(using Context): CaptureSet.CompareResult =
3956+
override def subCaptures(refs1: CaptureSet, refs2: CaptureSet, vs: CaptureSet.VarState)(using Context): Boolean =
39313957
traceIndented(i"subcaptures $refs1 <:< $refs2 in ${vs.toString}") {
39323958
super.subCaptures(refs1, refs2, vs)
39333959
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import typer.Inferencing.*
1919
import typer.IfBottom
2020
import reporting.TestingReporter
2121
import cc.{CapturingType, derivedCapturingType, CaptureSet, captureSet, isBoxed, isBoxedCapturing}
22-
import CaptureSet.{CompareResult, IdentityCaptRefMap, VarState}
22+
import CaptureSet.{IdentityCaptRefMap, VarState}
2323

2424
import scala.annotation.internal.sharable
2525
import scala.annotation.threadUnsafe
@@ -161,7 +161,7 @@ object TypeOps:
161161
TypeComparer.lub(simplify(l, theMap), simplify(r, theMap), isSoft = tp.isSoft)
162162
case tp @ CapturingType(parent, refs) =>
163163
if !ctx.mode.is(Mode.Type)
164-
&& refs.subCaptures(parent.captureSet, VarState.Separate).isOK
164+
&& refs.subCaptures(parent.captureSet, VarState.Separate)
165165
&& (tp.isBoxed || !parent.isBoxedCapturing)
166166
// fuse types with same boxed status and outer boxed with any type
167167
then

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ import reporting.{trace, Message}
3939
import java.lang.ref.WeakReference
4040
import compiletime.uninitialized
4141
import cc.*
42-
import CaptureSet.{CompareResult, IdentityCaptRefMap}
42+
import CaptureSet.IdentityCaptRefMap
4343
import Capabilities.*
4444

4545
import scala.annotation.internal.sharable

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

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,6 @@
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-
|
139
| longer explanation available when compiling with `-explain`
1410
-- Error: tests/neg-custom-args/captures/capt-depfun.scala:11:24 -------------------------------------------------------
1511
11 | val dc: ((Str^{y, z}) => Str^{y, z}) = ac(g()) // error // error: separatioon

0 commit comments

Comments
 (0)