Skip to content

Commit cd67244

Browse files
committed
Use addenda mechanism to report level violations
1 parent f6da2ac commit cd67244

File tree

4 files changed

+50
-15
lines changed

4 files changed

+50
-15
lines changed

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

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,14 @@ import config.Printers.capt
1111
import util.Property.Key
1212
import tpd.*
1313
import config.Feature
14+
import collection.mutable
1415

1516
private val Captures: Key[CaptureSet] = Key()
1617
private val BoxedType: Key[BoxedTypeCache] = Key()
1718

19+
/** Attachment key for the nesting level cache */
20+
val ccState: Key[CCState] = Key()
21+
1822
/** Switch whether unpickled function types and byname types should be mapped to
1923
* impure types. With the new gradual typing using Fluid capture sets, this should
2024
* be no longer needed. Also, it has bad interactions with pickling tests.
@@ -32,6 +36,11 @@ def allowUniversalInBoxed(using Context) =
3236
/** An exception thrown if a @retains argument is not syntactically a CaptureRef */
3337
class IllegalCaptureRef(tpe: Type) extends Exception
3438

39+
class CCState:
40+
val nestingLevels: mutable.HashMap[Symbol, Int] = new mutable.HashMap
41+
val localRoots: mutable.HashMap[Symbol, CaptureRef] = new mutable.HashMap
42+
var levelError: Option[(CaptureRef, CaptureSet)] = None
43+
3544
extension (tree: Tree)
3645

3746
/** Map tree with CaptureRef type to its type, throw IllegalCaptureRef otherwise */
@@ -269,7 +278,7 @@ extension (sym: Symbol)
269278
def ccNestingLevel(using Context): Int =
270279
if sym.exists then
271280
val lowner = sym.levelOwner
272-
val cache = ctx.property(CheckCaptures.NestingLevels).get
281+
val cache = ctx.property(ccState).get.nestingLevels
273282
cache.getOrElseUpdate(lowner,
274283
if lowner.isRoot then 0 else lowner.owner.ccNestingLevel + 1)
275284
else -1
@@ -278,7 +287,7 @@ extension (sym: Symbol)
278287
* a capture checker is running.
279288
*/
280289
def ccNestingLevelOpt(using Context): Option[Int] =
281-
if ctx.property(CheckCaptures.NestingLevels).isDefined then
290+
if ctx.property(ccState).isDefined then
282291
Some(ccNestingLevel)
283292
else None
284293

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

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import reporting.trace
1313
import printing.{Showable, Printer}
1414
import printing.Texts.*
1515
import util.{SimpleIdentitySet, Property, optional}, optional.{break, ?}
16+
import typer.ErrorReporting.Addenda
1617
import util.common.alwaysTrue
1718
import scala.collection.mutable
1819
import config.Config.ccAllowUnsoundMaps
@@ -421,6 +422,8 @@ object CaptureSet:
421422

422423
var description: String = ""
423424

425+
private var triedElem: Option[CaptureRef] = None
426+
424427
/** Record current elements in given VarState provided it does not yet
425428
* contain an entry for this variable.
426429
*/
@@ -457,9 +460,15 @@ object CaptureSet:
457460
(CompareResult.OK /: deps) { (r, dep) =>
458461
r.andAlso(dep.tryInclude(newElems, this))
459462
}
460-
else widenCaptures(newElems) match
461-
case Some(newElems1) => tryInclude(newElems1, origin)
462-
case None => CompareResult.fail(this)
463+
else
464+
val res = widenCaptures(newElems) match
465+
case Some(newElems1) => tryInclude(newElems1, origin)
466+
case None => CompareResult.fail(this)
467+
if !res.isOK then recordLevelError()
468+
res
469+
470+
private def recordLevelError()(using Context): Unit =
471+
ctx.property(ccState).get.levelError = Some((triedElem.get, this))
463472

464473
private def levelsOK(elems: Refs)(using Context): Boolean =
465474
!elems.exists(_.ccNestingLevel > ownLevel)
@@ -469,8 +478,13 @@ object CaptureSet:
469478
(SimpleIdentitySet[CaptureRef]() /: elems): (acc, elem) =>
470479
if elem.ccNestingLevel <= ownLevel then acc + elem
471480
else if elem.isRootCapability then break()
472-
else acc ++ widenCaptures(elem.captureSetOfInfo.elems).?
473-
val resStr = res match
481+
else
482+
val saved = triedElem
483+
triedElem = triedElem.orElse(Some(elem))
484+
val res = acc ++ widenCaptures(elem.captureSetOfInfo.elems).?
485+
triedElem = saved // reset only in case of success, leave as is on error
486+
res
487+
def resStr = res match
474488
case Some(refs) => i"${refs.toList}"
475489
case None => "FAIL"
476490
capt.println(i"widen captures ${elems.toList} for $this at $owner = $resStr")
@@ -974,4 +988,17 @@ object CaptureSet:
974988
println(i" ${cv.show.padTo(20, ' ')} :: ${cv.deps.toList}%, %")
975989
}
976990
else op
991+
992+
def levelErrors: Addenda = new Addenda:
993+
override def toAdd(using Context) =
994+
for
995+
state <- ctx.property(ccState).toList
996+
(ref, cs) <- state.levelError
997+
yield
998+
val level = ref.ccNestingLevel
999+
i"""
1000+
|
1001+
|Note that reference ${ref}, defined at level $level
1002+
|cannot be included in outer capture set $cs, defined at level ${cs.owner.nestingLevel} in ${cs.owner}"""
1003+
9771004
end CaptureSet

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

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import ast.{tpd, untpd, Trees}
1212
import Trees.*
1313
import typer.RefChecks.{checkAllOverrides, checkSelfAgainstParents, OverridingPairsChecker}
1414
import typer.Checking.{checkBounds, checkAppliedTypesIn}
15+
import typer.ErrorReporting.Addenda
1516
import util.{SimpleIdentitySet, EqHashMap, SrcPos, Property}
1617
import transform.SymUtils.*
1718
import transform.{Recheck, PreRecheck}
@@ -189,9 +190,6 @@ object CheckCaptures:
189190
/** Attachment key for bodies of closures, provided they are values */
190191
val ClosureBodyValue = Property.Key[Unit]
191192

192-
/** Attachment key for the nesting level cache */
193-
val NestingLevels = Property.Key[mutable.HashMap[Symbol, Int]]
194-
195193
class CheckCaptures extends Recheck, SymTransformer:
196194
thisPhase =>
197195

@@ -709,11 +707,11 @@ class CheckCaptures extends Recheck, SymTransformer:
709707
// - Adapt box status and environment capture sets by simulating box/unbox operations.
710708

711709
/** Massage `actual` and `expected` types using the methods below before checking conformance */
712-
override def checkConformsExpr(actual: Type, expected: Type, tree: Tree)(using Context): Unit =
710+
override def checkConformsExpr(actual: Type, expected: Type, tree: Tree, addenda: Addenda)(using Context): Unit =
713711
val expected1 = alignDependentFunction(addOuterRefs(expected, actual), actual.stripCapturing)
714712
val actual1 = adaptBoxed(actual, expected1, tree.srcPos)
715713
//println(i"check conforms $actual1 <<< $expected1")
716-
super.checkConformsExpr(actual1, expected1, tree)
714+
super.checkConformsExpr(actual1, expected1, tree, addenda ++ CaptureSet.levelErrors)
717715

718716
private def toDepFun(args: List[Type], resultType: Type, isContextual: Boolean)(using Context): Type =
719717
MethodType.companion(isContextual = isContextual)(args, resultType)
@@ -977,7 +975,7 @@ class CheckCaptures extends Recheck, SymTransformer:
977975
traverseChildren(t)
978976

979977
override def checkUnit(unit: CompilationUnit)(using Context): Unit =
980-
inContext(ctx.withProperty(NestingLevels, Some(new mutable.HashMap[Symbol, Int]))):
978+
inContext(ctx.withProperty(ccState, Some(new CCState))):
981979
Setup(preRecheckPhase, thisPhase, this)(ctx.compilationUnit.tpdTree)
982980
//println(i"SETUP:\n${Recheck.addRecheckedTypes.transform(ctx.compilationUnit.tpdTree)}")
983981
withCaptureSetsExplained:

compiler/src/dotty/tools/dotc/transform/Recheck.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import typer.ErrorReporting.err
1515
import typer.ProtoTypes.*
1616
import typer.TypeAssigner.seqLitType
1717
import typer.ConstFold
18+
import typer.ErrorReporting.{Addenda, NothingToAdd}
1819
import NamerOps.methodType
1920
import config.Printers.recheckr
2021
import util.Property
@@ -561,7 +562,7 @@ abstract class Recheck extends Phase, SymTransformer:
561562
case _ =>
562563
checkConformsExpr(tpe.widenExpr, pt.widenExpr, tree)
563564

564-
def checkConformsExpr(actual: Type, expected: Type, tree: Tree)(using Context): Unit =
565+
def checkConformsExpr(actual: Type, expected: Type, tree: Tree, addenda: Addenda = NothingToAdd)(using Context): Unit =
565566
//println(i"check conforms $actual <:< $expected")
566567

567568
def isCompatible(expected: Type): Boolean =
@@ -574,7 +575,7 @@ abstract class Recheck extends Phase, SymTransformer:
574575
}
575576
if !isCompatible(expected) then
576577
recheckr.println(i"conforms failed for ${tree}: $actual vs $expected")
577-
err.typeMismatch(tree.withType(actual), expected)
578+
err.typeMismatch(tree.withType(actual), expected, addenda)
578579
else if debugSuccesses then
579580
tree match
580581
case _: Ident =>

0 commit comments

Comments
 (0)