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
82 changes: 0 additions & 82 deletions compiler/src/dotty/tools/dotc/cc/CaptureOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -352,15 +352,6 @@ extension (tp: Type)
case _ =>
false

/** Is this a type extending `Mutable` that has non-private update methods
* or mutable fields?
*/
def isMutableType(using Context): Boolean =
tp.derivesFrom(defn.Caps_Mutable)
&& tp.membersBasedOnFlags(Mutable, EmptyFlags).exists: mbr =>
if mbr.symbol.is(Method) then mbr.symbol.isUpdateMethod
else !mbr.symbol.is(Transparent)

/** Is this a reference to caps.cap? Note this is _not_ the GlobalCap capability. */
def isCapRef(using Context): Boolean = tp match
case tp: TermRef => tp.name == nme.CAPTURE_ROOT && tp.symbol == defn.captureRoot
Expand Down Expand Up @@ -499,23 +490,6 @@ extension (tp: Type)
def classifier(using Context): ClassSymbol =
tp.classSymbols.map(_.classifier).foldLeft(defn.AnyClass)(leastClassifier)

def exclusivity(using Context): Exclusivity =
if tp.derivesFrom(defn.Caps_Mutable) then
tp match
case tp: Capability if tp.isExclusive => Exclusivity.OK
case _ => tp.captureSet.exclusivity(tp)
else Exclusivity.OK

def exclusivityInContext(using Context): Exclusivity = tp match
case tp: ThisType =>
if tp.derivesFrom(defn.Caps_Mutable)
then ctx.owner.inExclusivePartOf(tp.cls)
else Exclusivity.OK
case tp @ TermRef(prefix: Capability, _) =>
prefix.exclusivityInContext.andAlso(tp.exclusivity)
case _ =>
tp.exclusivity

extension (tp: MethodType)
/** A method marks an existential scope unless it is the prefix of a curried method */
def marksExistentialScope(using Context): Boolean =
Expand Down Expand Up @@ -649,30 +623,6 @@ extension (sym: Symbol)
sym.hasAnnotation(defn.ConsumeAnnot)
|| sym.info.hasAnnotation(defn.ConsumeAnnot)

/** An update method is either a method marked with `update` or
* a setter of a non-transparent var.
*/
def isUpdateMethod(using Context): Boolean =
sym.isAllOf(Mutable | Method)
&& (!sym.isSetter || sym.field.is(Transparent))

def inExclusivePartOf(cls: Symbol)(using Context): Exclusivity =
import Exclusivity.*
val encl = sym.enclosingMethodOrClass.skipConstructor
if sym == cls then OK // we are directly in `cls` or in one of its constructors
else if encl.owner == cls then
if encl.isUpdateMethod then OK
else NotInUpdateMethod(encl, cls)
else if encl.isStatic then OutsideClass(cls)
else encl.owner.inExclusivePartOf(cls)

def isReadOnlyMethod(using Context): Boolean =
sym.is(Method, butNot = Mutable | Accessor) && sym.owner.derivesFrom(defn.Caps_Mutable)

def isInReadOnlyMethod(using Context): Boolean =
if sym.is(Method) && sym.owner.isClass then isReadOnlyMethod
else sym.owner.isInReadOnlyMethod

def qualString(prefix: String)(using Context): String =
if !sym.exists then ""
else if sym.isAnonymousFunction then i" $prefix enclosing function"
Expand Down Expand Up @@ -803,35 +753,3 @@ abstract class DeepTypeAccumulator[T](using Context) extends TypeAccumulator[T]:
foldOver(acc, t)
end DeepTypeAccumulator

/** Either OK, or a reason why capture set cannot be exclusive */
enum Exclusivity:
case OK

/** Enclosing symbol `sym` is a method of class `cls`, but not an update method */
case NotInUpdateMethod(sym: Symbol, cls: Symbol)

/** Access to `this` from outside its class (not sure this can happen) */
case OutsideClass(cls: Symbol)

/** A prefix type `tp` has a read-only capture set */
case ReadOnly(tp: Type)

def isOK: Boolean = this == OK

def andAlso(that: Exclusivity): Exclusivity =
if this == OK then that else this

/** A textual description why `qualType` is not exclusive */
def description(qualType: Type)(using Context): String = this.runtimeChecked match
case Exclusivity.ReadOnly(tp) =>
if qualType eq tp then
i"its capture set ${qualType.captureSet} is read-only"
else
i"the capture set ${tp.captureSet} of its prefix $tp is read-only"
case Exclusivity.NotInUpdateMethod(sym: Symbol, cls: Symbol) =>
i"the access is in $sym, which is not an update method"
case Exclusivity.OutsideClass(cls: Symbol) =>
i"the access from is from ouside $cls"

end Exclusivity

3 changes: 0 additions & 3 deletions compiler/src/dotty/tools/dotc/cc/CaptureSet.scala
Original file line number Diff line number Diff line change
Expand Up @@ -152,9 +152,6 @@ sealed abstract class CaptureSet extends Showable:
final def isExclusive(using Context): Boolean =
elems.exists(_.isExclusive)

def exclusivity(tp: Type)(using Context): Exclusivity =
if isExclusive then Exclusivity.OK else Exclusivity.ReadOnly(tp)

/** Similar to isExlusive, but also includes capture set variables
* with unknown status.
*/
Expand Down
113 changes: 4 additions & 109 deletions compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import NameKinds.{DefaultGetterName, WildcardParamName, UniqueNameKind}
import reporting.{trace, Message, OverrideError}
import Annotations.Annotation
import Capabilities.*
import Mutability.*
import util.common.alwaysTrue
import scala.annotation.constructorOnly

Expand Down Expand Up @@ -689,11 +690,7 @@ class CheckCaptures extends Recheck, SymTransformer:
// read-only-use.scala, where the error on r3 goes unreported.
markPathFree(sel, pt.pt, pt.select)
case _ =>
if ref.derivesFromMutable then
if pt.isValueType && !pt.isMutableType || ref.exclusivityInContext != Exclusivity.OK
then markFree(ref.readOnly, tree)
else markFree(ref, tree)
else markFree(ref, tree)
markFree(ref.adjustReadOnly(pt), tree)

/** The expected type for the qualifier of a selection. If the selection
* could be part of a capability path or is a a read-only method, we return
Expand Down Expand Up @@ -773,12 +770,6 @@ class CheckCaptures extends Recheck, SymTransformer:
selType
}//.showing(i"recheck sel $tree, $qualType = $result")

def checkUpdate(qualType: Type, pos: SrcPos)(msg: => String)(using Context): Unit =
qualType.exclusivityInContext match
case Exclusivity.OK =>
case err =>
report.error(em"$msg\nsince ${err.description(qualType)}.", pos)

/** Recheck applications, with special handling of unsafeAssumePure.
* More work is done in `recheckApplication`, `recheckArg` and `instantiate` below.
*/
Expand Down Expand Up @@ -1773,101 +1764,6 @@ class CheckCaptures extends Recheck, SymTransformer:
case _ => widened
case _ => widened

/** If actual is a capturing type T^C extending Mutable, and expected is an
* unboxed non-singleton value type not extending mutable, narrow the capture
* set `C` to `ro(C)`.
* The unboxed condition ensures that the expected type is not a type variable
* that's upper bounded by a read-only type. In this case it would not be sound
* to narrow to the read-only set, since that set can be propagated
* by the type variable instantiation.
*/
private def improveReadOnly(actual: Type, expected: Type)(using Context): Type = reporting.trace(i"improv ro $actual vs $expected"):
actual.dealiasKeepAnnots match
case actual @ CapturingType(parent, refs) =>
val parent1 = improveReadOnly(parent, expected)
val refs1 =
if parent1.derivesFrom(defn.Caps_Mutable)
&& expected.isValueType
&& (!expected.derivesFromMutable || expected.captureSet.isAlwaysReadOnly)
&& !expected.isSingleton
&& actual.isBoxedCapturing == expected.isBoxedCapturing
then refs.readOnly
else refs
actual.derivedCapturingType(parent1, refs1)
case actual @ FunctionOrMethod(aargs, ares) =>
expected.dealias.stripCapturing match
case FunctionOrMethod(eargs, eres) =>
actual.derivedFunctionOrMethod(aargs, improveReadOnly(ares, eres))
case _ =>
actual
case actual @ AppliedType(atycon, aargs) =>
def improveArgs(aargs: List[Type], eargs: List[Type], formals: List[ParamInfo]): List[Type] =
aargs match
case aargs @ (aarg :: aargs1) =>
val aarg1 =
if formals.head.paramVariance.is(Covariant)
then improveReadOnly(aarg, eargs.head)
else aarg
aargs.derivedCons(aarg1, improveArgs(aargs1, eargs.tail, formals.tail))
case Nil =>
aargs
val expected1 = expected.dealias.stripCapturing
val esym = expected1.typeSymbol
expected1 match
case AppliedType(etycon, eargs) =>
if atycon.typeSymbol == esym then
actual.derivedAppliedType(atycon,
improveArgs(aargs, eargs, etycon.typeParams))
else if esym.isClass then
// This case is tricky: Try to lift actual to the base type with class `esym`,
// improve the resulting arguments, and figure out if anything can be
// deduced from that for the original arguments.
actual.baseType(esym) match
case base @ AppliedType(_, bargs) =>
// If any of the base type arguments can be improved, check
// whether they are the same as an original argument, and in this
// case improve the original argument.
val iargs = improveArgs(bargs, eargs, etycon.typeParams)
if iargs ne bargs then
val updates =
for
(barg, iarg) <- bargs.lazyZip(iargs)
if barg ne iarg
aarg <- aargs.find(_ eq barg)
yield (aarg, iarg)
if updates.nonEmpty then AppliedType(atycon, aargs.map(updates.toMap))
else actual
else actual
case _ => actual
else actual
case _ =>
actual
case actual @ RefinedType(aparent, aname, ainfo) =>
expected.dealias.stripCapturing match
case RefinedType(eparent, ename, einfo) if aname == ename =>
actual.derivedRefinedType(
improveReadOnly(aparent, eparent),
aname,
improveReadOnly(ainfo, einfo))
case _ =>
actual
case actual @ AnnotatedType(parent, ann) =>
actual.derivedAnnotatedType(improveReadOnly(parent, expected), ann)
case _ =>
actual
end improveReadOnly

def adaptReadOnly(improved: Type, original: Type, expected: Type, tree: Tree)(using Context): Type = improved match
case improved @ CapturingType(parent, refs)
if parent.derivesFrom(defn.Caps_Mutable)
&& expected.isValueType
&& refs.isExclusive
&& !original.exclusivityInContext.isOK =>
improved.derivedCapturingType(parent, refs.readOnly)
.showing(i"Adapted readonly $improved for $tree with original = $original in ${ctx.owner} --> $result", capt)
case _ =>
improved

/* Currently not needed since it forms part of `adapt`
private def improve(actual: Type, prefix: Type)(using Context): Type =
val widened = actual.widen.dealiasKeepAnnots
Expand Down Expand Up @@ -1904,12 +1800,11 @@ class CheckCaptures extends Recheck, SymTransformer:
// since they obscures the capturing type.
val widened = actual.widen.dealiasKeepAnnots.dropUseAndConsumeAnnots
val improvedVAR = improveCaptures(widened, actual)
val improved = improveReadOnly(improvedVAR, expected)
val adaptedReadOnly = adaptReadOnly(improved, actual, expected, tree)
val adaptedReadOnly = adaptReadOnly(improvedVAR, actual, expected, tree)
val adapted = adaptBoxed(
adaptedReadOnly.withReachCaptures(actual), expected, tree,
covariant = true, alwaysConst = false)
if adapted eq improvedVAR // no .rd improvement or adaptation, no box-adaptation
if adapted eq improvedVAR // no read-only-adaptation, no reaches added, no box-adaptation
then actual // might as well use actual instead of improved widened
else adapted.showing(i"adapt $actual vs $expected = $adapted", capt)
end adapt
Expand Down
Loading
Loading