Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 2 additions & 6 deletions compiler/src/dotty/tools/dotc/cc/Capability.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ package cc
import core.*
import Types.*, Symbols.*, Contexts.*, Decorators.*
import util.{SimpleIdentitySet, EqHashMap}
import typer.ErrorReporting.Addenda
import util.common.alwaysTrue
import scala.collection.mutable
import CCState.*
Expand Down Expand Up @@ -787,12 +786,9 @@ object Capabilities:
&& prefixAllowsAddHidden
&& vs.addHidden(x.hiddenSet, y)
case x: ResultCap =>
val result = y match
y match
case y: ResultCap => vs.unify(x, y)
case _ => y.derivesFromShared
if !result then
TypeComparer.addErrorNote(CaptureSet.ExistentialSubsumesFailure(x, y))
result
case GlobalCap =>
y match
case GlobalCap => true
Expand Down Expand Up @@ -900,7 +896,7 @@ object Capabilities:
case _ => c1

def showAsCapability(using Context) =
i"capability ${ctx.printer.toTextCapability(this).show}"
i"${ctx.printer.toTextCapability(this).show}"

def toText(printer: Printer): Text = printer.toTextCapability(this)
end Capability
Expand Down
9 changes: 7 additions & 2 deletions compiler/src/dotty/tools/dotc/cc/CaptureOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import CaptureSet.VarState
import Capabilities.*
import StdNames.nme
import config.Feature
import dotty.tools.dotc.core.NameKinds.TryOwnerName

/** Attachment key for capturing type trees */
private val Captures: Key[CaptureSet] = Key()
Expand Down Expand Up @@ -624,9 +625,13 @@ extension (sym: Symbol)
|| sym.info.hasAnnotation(defn.ConsumeAnnot)

def qualString(prefix: String)(using Context): String =
if !sym.exists then "" else i" $prefix ${sym.ownerString}"

def ownerString(using Context): String =
if !sym.exists then ""
else if sym.isAnonymousFunction then i" $prefix enclosing function"
else i" $prefix $sym"
else if sym.isAnonymousFunction then i"an enclosing function"
else if sym.name.is(TryOwnerName) then i"an enclosing try expression"
else sym.show

extension (tp: AnnotatedType)
/** Is this a boxed capturing type? */
Expand Down
138 changes: 87 additions & 51 deletions compiler/src/dotty/tools/dotc/cc/CaptureSet.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,11 @@ import annotation.threadUnsafe
import annotation.constructorOnly
import annotation.internal.sharable
import reporting.trace
import reporting.Message.Note
import printing.{Showable, Printer}
import printing.Texts.*
import util.{SimpleIdentitySet, Property, EqHashMap}
import typer.ErrorReporting.Addenda
import scala.collection.{mutable, immutable}
import TypeComparer.ErrorNote
import CCState.*
import TypeOps.AvoidMap
import compiletime.uninitialized
Expand Down Expand Up @@ -161,8 +160,8 @@ sealed abstract class CaptureSet extends Showable:

final def keepAlways: Boolean = this.isInstanceOf[EmptyWithProvenance]

def failWith(fail: TypeComparer.ErrorNote)(using Context): false =
TypeComparer.addErrorNote(fail)
def failWith(note: Note)(using Context): false =
TypeComparer.addErrorNote(note)
false

/** Try to include an element in this capture set.
Expand Down Expand Up @@ -1301,20 +1300,8 @@ object CaptureSet:
/** A TypeMap that is the identity on capabilities */
trait IdentityCaptRefMap extends TypeMap

/** A value of this class is produced and added as a note to ccState
* when a subsumes check decides that an existential variable `ex` cannot be
* instantiated to the other capability `other`.
*/
case class ExistentialSubsumesFailure(val ex: ResultCap, val other: Capability) extends ErrorNote:
def description(using Context): String =
def reason =
if other.isTerminalCapability then ""
else " since that capability is not a `Sharable` capability"
i"""the existential capture root in ${ex.originalBinder.resType}
|cannot subsume the capability $other$reason."""

/** Failure indicating that `elem` cannot be included in `cs` */
case class IncludeFailure(cs: CaptureSet, elem: Capability, levelError: Boolean = false) extends ErrorNote, Showable:
case class IncludeFailure(cs: CaptureSet, elem: Capability, levelError: Boolean = false) extends Note, Showable:
private var myTrace: List[CaptureSet] = cs :: Nil

def trace: List[CaptureSet] = myTrace
Expand All @@ -1323,36 +1310,77 @@ object CaptureSet:
res.myTrace = cs1 :: this.myTrace
res

def description(using Context): String =
def why =
val reasons = cs.elems.toList.collect:
case c: FreshCap if !c.acceptsLevelOf(elem) =>
i"$elem${elem.levelOwner.qualString("in")} is not visible from $c${c.ccOwner.qualString("in")}"
case c: FreshCap if !elem.tryClassifyAs(c.hiddenSet.classifier) =>
i"$c is classified as ${c.hiddenSet.classifier} but $elem is not"
if reasons.isEmpty then ""
else reasons.mkString("\nbecause ", "\nand ", "")
cs match
case cs: Var =>
if !cs.levelOK(elem) then
val levelStr = elem match
case ref: TermRef => i", defined in ${ref.symbol.maybeOwner}\n"
case _ => " "
i"""${elem.showAsCapability}${levelStr}cannot be included in outer capture set $cs"""
else if !elem.tryClassifyAs(cs.classifier) then
i"""${elem.showAsCapability} is not classified as ${cs.classifier}, therefore it
|cannot be included in capture set $cs of ${cs.classifier.name} elements"""
else if cs.isBadRoot(elem) then
elem match
case elem: FreshCap =>
i"""local ${elem.showAsCapability} created in ${elem.ccOwner}
|cannot be included in outer capture set $cs"""
case _ =>
i"universal ${elem.showAsCapability} cannot be included in capture set $cs"
else
i"${elem.showAsCapability} cannot be included in capture set $cs"
case _ =>
i"${elem.showAsCapability} is not included in capture set $cs$why"
override def showAsPrefix(using Context) = cs match
case cs: Var =>
!cs.levelOK(elem)
|| cs.isBadRoot(elem) && elem.isInstanceOf[FreshCap]
case _ =>
false

/** An include failure F1 covers another include failure F2 unless F2
* strictly subsumes F1, which means they describe the same capture sets
* and the element in F2 is more specific than the element in F1.
*/
override def covers(other: Note)(using Context) = other match
case other @ IncludeFailure(cs1, elem1, _) =>
val strictlySubsumes =
cs.elems == cs1.elems
&& elem1.singletonCaptureSet.mightSubcapture(elem.singletonCaptureSet)
!strictlySubsumes
case _ => false

def trailing(msg: String)(using Context): String =
i"""
|
|Note that $msg."""

def leading(msg: String)(using Context): String =
i"""$msg.
|The leakage occurred when trying to match the following types:
|
|"""

def render(using Context): String = cs match
case cs: Var =>
def ownerStr =
if !cs.description.isEmpty then "" else cs.owner.qualString("which is owned by")
if !cs.levelOK(elem) then
val outlivesStr = elem match
case ref: TermRef => i"${ref.symbol.maybeOwner.qualString("defined in")} outlives its scope:\n"
case _ => " outlives its scope: "
leading:
i"""Capability ${elem.showAsCapability}${outlivesStr}it leaks into outer capture set $cs$ownerStr"""
else if !elem.tryClassifyAs(cs.classifier) then
trailing:
i"""capability ${elem.showAsCapability} is not classified as ${cs.classifier}, therefore it
|cannot be included in capture set $cs of ${cs.classifier.name} elements"""
else if cs.isBadRoot(elem) then
elem match
case elem: FreshCap =>
leading:
i"""Local capability ${elem.showAsCapability} created in ${elem.ccOwner} outlives its scope:
|It leaks into outer capture set $cs$ownerStr"""
case _ =>
trailing:
i"universal capability ${elem.showAsCapability} cannot be included in capture set $cs"
else
trailing:
i"capability ${elem.showAsCapability} cannot be included in capture set $cs"
case _ =>
def why =
val reasons = cs.elems.toList.collect:
case c: FreshCap if !c.acceptsLevelOf(elem) =>
i"$elem${elem.levelOwner.qualString("in")} is not visible from $c${c.ccOwner.qualString("in")}"
case c: FreshCap if !elem.tryClassifyAs(c.hiddenSet.classifier) =>
i"$c is classified as ${c.hiddenSet.classifier} but ${elem.showAsCapability} is not"
case c: ResultCap if !c.subsumes(elem) =>
val toAdd = if elem.isTerminalCapability then "" else " since that capability is not a SharedCapability"
i"$c, which is existentially bound in ${c.originalBinder.resType}, cannot subsume ${elem.showAsCapability}$toAdd"
if reasons.isEmpty then ""
else reasons.mkString("\nbecause ", "\nand ", "")

trailing:
i"capability ${elem.showAsCapability} is not included in capture set $cs$why"

override def toText(printer: Printer): Text =
inContext(printer.printerContext):
Expand All @@ -1370,11 +1398,19 @@ object CaptureSet:
* @param lo the lower type of the orginal type comparison, or NoType if not known
* @param hi the upper type of the orginal type comparison, or NoType if not known
*/
case class MutAdaptFailure(cs: CaptureSet, lo: Type = NoType, hi: Type = NoType) extends ErrorNote:
def description(using Context): String =
case class MutAdaptFailure(cs: CaptureSet, lo: Type = NoType, hi: Type = NoType) extends Note:

def render(using Context): String =
def ofType(tp: Type) = if tp.exists then i"of the mutable type $tp" else "of a mutable type"
i"""$cs is an exclusive capture set ${ofType(hi)},
|it cannot subsume a read-only capture set ${ofType(lo)}"""
i"""
|
|Note that $cs is an exclusive capture set ${ofType(hi)},
|it cannot subsume a read-only capture set ${ofType(lo)}."""

// Show only one failure of this kind
override def covers(other: Note)(using Context) =
other.isInstanceOf[MutAdaptFailure]
end MutAdaptFailure

/** A VarState serves as a snapshot mechanism that can undo
* additions of elements or super sets if an operation fails
Expand Down
52 changes: 22 additions & 30 deletions compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,19 @@ import typer.ForceDegree
import typer.Inferencing.isFullyDefined
import typer.RefChecks.{checkAllOverrides, checkSelfAgainstParents, OverridingPairsChecker}
import typer.Checking.{checkBounds, checkAppliedTypesIn}
import typer.ErrorReporting.{Addenda, NothingToAdd, err}
import typer.ErrorReporting.err
import typer.ProtoTypes.{LhsProto, WildcardSelectionProto, SelectionProto}
import util.{SimpleIdentitySet, EqHashMap, EqHashSet, SrcPos, Property}
import util.chaining.tap
import transform.{Recheck, PreRecheck, CapturedVars}
import Recheck.*
import scala.collection.mutable
import CaptureSet.{withCaptureSetsExplained, IncludeFailure, ExistentialSubsumesFailure, MutAdaptFailure}
import CaptureSet.{withCaptureSetsExplained, IncludeFailure, MutAdaptFailure}
import CCState.*
import StdNames.nme
import NameKinds.{DefaultGetterName, WildcardParamName, UniqueNameKind}
import reporting.{trace, Message, OverrideError}
import reporting.Message.Note
import Annotations.Annotation
import Capabilities.*
import Mutability.*
Expand Down Expand Up @@ -200,9 +201,6 @@ object CheckCaptures:
&& !sym.isOneOf(DeferredOrTermParamOrAccessor)
&& !sym.hasAnnotation(defn.UntrackedCapturesAnnot)

private def ownerStr(owner: Symbol)(using Context): String =
if owner.isAnonymousFunction then "enclosing function" else owner.show

trait CheckerAPI:
/** Complete symbol info of a val or a def */
def completeDef(tree: ValOrDefDef, sym: Symbol, completer: LazyType)(using Context): Type
Expand Down Expand Up @@ -399,7 +397,7 @@ class CheckCaptures extends Recheck, SymTransformer:
case (fail: IncludeFailure) :: _ => fail.cs
case _ => target
def msg(provisional: Boolean) =
def toAdd: String = errorNotes(otherNotes).toAdd.mkString
def toAdd: String = otherNotes.map(_.render).mkString
def descr: String =
val d = realTarget.description
if d.isEmpty then provenance else ""
Expand Down Expand Up @@ -1208,7 +1206,7 @@ class CheckCaptures extends Recheck, SymTransformer:
// too annoying. This is a hole since a defualt getter's result type
// might leak into a type variable.

def fail(tree: Tree, expected: Type, addenda: Addenda): Unit =
def fail(tree: Tree, expected: Type, notes: List[Note]): Unit =
def maybeResult = if sym.is(Method) then " result" else ""
report.error(
em"""$sym needs an explicit$maybeResult type because the inferred type does not conform to
Expand All @@ -1218,7 +1216,7 @@ class CheckCaptures extends Recheck, SymTransformer:
| Externally visible type: $expected""",
tree.srcPos)

def addenda(expected: Type) = Addenda:
def addendum(expected: Type) = Note:
def result = if tree.isInstanceOf[ValDef] then"" else " result"
i"""
|
Expand All @@ -1237,7 +1235,7 @@ class CheckCaptures extends Recheck, SymTransformer:
val expected = tpt.tpe.dropAllRetains
todoAtPostCheck += { () =>
withCapAsRoot:
testAdapted(tp, expected, tree.rhs, addenda(expected))(fail)
testAdapted(tp, expected, tree.rhs, addendum(expected) :: Nil)(fail)
// The check that inferred <: expected is done after recheck so that it
// does not interfere with normal rechecking by constraining capture set variables.
}
Expand Down Expand Up @@ -1444,34 +1442,27 @@ class CheckCaptures extends Recheck, SymTransformer:

type BoxErrors = mutable.ListBuffer[Message] | Null

private def errorNotes(notes: List[TypeComparer.ErrorNote])(using Context): Addenda =
if notes.isEmpty then NothingToAdd
else new Addenda:
override def toAdd(using Context) = notes.map: note =>
i"""
|
|Note that ${note.description}."""

/** Addendas for error messages that show where we have under-approximated by
* mapping a a capability in contravariant position to the empty set because
* mapping of a capability in contravariant position to the empty set because
* the original result type of the map was not itself a capability.
*/
private def addApproxAddenda(using Context) =
new TypeAccumulator[Addenda]:
def apply(add: Addenda, t: Type) = t match
private def addApproxAddenda(using Context): TypeAccumulator[List[Note]] =
new TypeAccumulator:
def apply(notes: List[Note], t: Type) = t match
case CapturingType(t, CaptureSet.EmptyWithProvenance(ref, mapped)) =>
/* val (origCore, kind) = original match
case tp @ AnnotatedType(parent, ann) if ann.hasSymbol(defn.ReachCapabilityAnnot) =>
(parent, " deep")
case _ =>
(original, "")*/
add ++ Addenda:
Note:
i"""
|
|Note that a capability $ref in a capture set appearing in contravariant position
|was mapped to $mapped which is not a capability. Therefore, it was under-approximated to the empty set."""
:: notes
case _ =>
foldOver(add, t)
foldOver(notes, t)

/** Massage `actual` and `expected` types before checking conformance.
* Massaging is done by the methods following this one:
Expand All @@ -1480,8 +1471,8 @@ class CheckCaptures extends Recheck, SymTransformer:
* If the resulting types are not compatible, try again with an actual type
* where local capture roots are instantiated to root variables.
*/
override def checkConformsExpr(actual: Type, expected: Type, tree: Tree, addenda: Addenda)(using Context): Type =
try testAdapted(actual, expected, tree, addenda)(err.typeMismatch)
override def checkConformsExpr(actual: Type, expected: Type, tree: Tree, notes: List[Note])(using Context): Type =
try testAdapted(actual, expected, tree, notes: List[Note])(err.typeMismatch)
catch case ex: AssertionError =>
println(i"error while checking $tree: $actual against $expected")
throw ex
Expand All @@ -1496,8 +1487,8 @@ class CheckCaptures extends Recheck, SymTransformer:
case _ => NoType
case _ => NoType

inline def testAdapted(actual: Type, expected: Type, tree: Tree, addenda: Addenda)
(fail: (Tree, Type, Addenda) => Unit)(using Context): Type =
inline def testAdapted(actual: Type, expected: Type, tree: Tree, notes: List[Note])
(fail: (Tree, Type, List[Note]) => Unit)(using Context): Type =

var expected1 = alignDependentFunction(expected, actual.stripCapturing)
val falseDeps = expected1 ne expected
Expand Down Expand Up @@ -1544,11 +1535,12 @@ class CheckCaptures extends Recheck, SymTransformer:
}

TypeComparer.compareResult(tryCurrentType || tryWidenNamed) match
case TypeComparer.CompareResult.Fail(notes) =>
case TypeComparer.CompareResult.Fail(cmpNotes) =>
capt.println(i"conforms failed for ${tree}: $actual vs $expected")
if falseDeps then expected1 = unalignFunction(expected1)
fail(tree.withType(actualBoxed), expected1,
addApproxAddenda(addenda ++ errorNotes(notes), expected1))
val toAdd0 = notes ++ cmpNotes
val toAdd1 = addApproxAddenda(toAdd0, expected1)
fail(tree.withType(actualBoxed), expected1, toAdd1)
actual
case /*OK*/ _ =>
if debugSuccesses then tree match
Expand Down
Loading
Loading