Skip to content

Commit

Permalink
Revert "Polishings"
Browse files Browse the repository at this point in the history
This reverts commit 0a37cc1.
  • Loading branch information
odersky committed Feb 8, 2025
1 parent 2bfafe5 commit edfe896
Show file tree
Hide file tree
Showing 18 changed files with 77 additions and 133 deletions.
22 changes: 4 additions & 18 deletions compiler/src/dotty/tools/dotc/cc/CaptureOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -232,9 +232,7 @@ extension (tp: Type)
case tp @ ReachCapability(_) =>
tp.singletonCaptureSet
case ReadOnlyCapability(ref) =>
val refDcs = ref.deepCaptureSet(includeTypevars)
if refDcs.isConst then CaptureSet(refDcs.elems.map(_.readOnly))
else refDcs // this case should not happen for correct programs
ref.deepCaptureSet(includeTypevars)
case tp: SingletonCaptureRef if tp.isTrackableRef =>
tp.reach.singletonCaptureSet
case _ =>
Expand Down Expand Up @@ -281,19 +279,17 @@ extension (tp: Type)
case _ =>
tp

/** The first element of this path type. Note that class parameter references
* are of the form this.C but their pathroot is still this.C, not this.
*/
/** The first element of this path type */
final def pathRoot(using Context): Type = tp.dealias match
case tp1: TermRef if tp1.symbol.maybeOwner.isClass => tp1.prefix.pathRoot
case tp1: TypeRef if !tp1.symbol.is(Param) => tp1.prefix.pathRoot
case tp1 => tp1

/** The first element of a path type, but stop at references extending
* SharedCapability.
* SharableCapability
*/
final def pathRootOrShared(using Context): Type =
if tp.derivesFromSharedCapability then tp
if tp.derivesFrom(defn.Caps_SharedCapability) then tp
else tp.dealias match
case tp1: TermRef if tp1.symbol.maybeOwner.isClass => tp1.prefix.pathRoot
case tp1: TypeRef if !tp1.symbol.is(Param) => tp1.prefix.pathRoot
Expand Down Expand Up @@ -431,7 +427,6 @@ extension (tp: Type)

def derivesFromCapability(using Context): Boolean = derivesFromCapTrait(defn.Caps_Capability)
def derivesFromMutable(using Context): Boolean = derivesFromCapTrait(defn.Caps_Mutable)
def derivesFromSharedCapability(using Context): Boolean = derivesFromCapTrait(defn.Caps_SharedCapability)

/** Drop @retains annotations everywhere */
def dropAllRetains(using Context): Type = // TODO we should drop retains from inferred types before unpickling
Expand Down Expand Up @@ -471,11 +466,6 @@ extension (tp: Type)
* is the union of all capture sets that appear in covariant position in the
* type of `x`. If `x` and `y` are different variables then `{x*}` and `{y*}`
* are unrelated.
*
* Reach capabilities cannot wrap read-only capabilities or maybe capabilities.
* We have
* (x.rd).reach = x*.rd
* (x.rd)? = (x*)?
*/
def reach(using Context): CaptureRef = tp match
case tp @ AnnotatedType(tp1: CaptureRef, annot)
Expand All @@ -493,10 +483,6 @@ extension (tp: Type)
/** If `x` is a capture ref, its read-only capability `x.rd`, represented internally
* as `x @readOnlyCapability`. We have {x.rd} <: {x}. If `x` is a reach capability `y*`,
* then its read-only version is `x.rd*`.
*
* Read-only capabilities cannot wrap maybe capabilities
* but they can wrap reach capabilities. We have
* (x?).readOnly = (x.rd)?
*/
def readOnly(using Context): CaptureRef = tp match
case tp @ AnnotatedType(tp1: CaptureRef, annot)
Expand Down
14 changes: 5 additions & 9 deletions compiler/src/dotty/tools/dotc/cc/CaptureRef.scala
Original file line number Diff line number Diff line change
Expand Up @@ -100,25 +100,19 @@ trait CaptureRef extends TypeProxy, ValueType:
/** Is this reference the generic root capability `cap` or a Fresh.Cap instance? */
final def isCapOrFresh(using Context): Boolean = isCap || isFresh

/** Is this reference one of the generic root capabilities `cap` or `cap.rd` ? */
/** Is this reference one the generic root capabilities `cap` or `cap.rd` ? */
final def isRootCapability(using Context): Boolean = this match
case ReadOnlyCapability(tp1) => tp1.isCapOrFresh
case _ => isCapOrFresh

/** Is this reference a capability that does not derive from another capability?
* Includes read-only versions of maximal capabilities.
*/
/** Is this reference capability that does not derive from another capability ? */
final def isMaxCapability(using Context): Boolean = this match
case tp: TermRef => tp.isCap || tp.info.derivesFrom(defn.Caps_Exists)
case tp: TermParamRef => tp.underlying.derivesFrom(defn.Caps_Exists)
case Fresh.Cap(_) => true
case ReadOnlyCapability(tp1) => tp1.isMaxCapability
case _ => false

/** An exclusive capability is a capability that derives
* indirectly from a maximal capability without goinh through
* a read-only capability first.
*/
final def isExclusive(using Context): Boolean =
!isReadOnly && (isMaxCapability || captureSetOfInfo.isExclusive)

Expand Down Expand Up @@ -165,6 +159,8 @@ trait CaptureRef extends TypeProxy, ValueType:
* X: CapSet^c1...CapSet^c2, (CapSet^c1) subsumes y ==> X subsumes y
* Y: CapSet^c1...CapSet^c2, x subsumes (CapSet^c2) ==> x subsumes Y
* Contains[X, y] ==> X subsumes y
*
* TODO: Move to CaptureSet
*/
final def subsumes(y: CaptureRef)(using ctx: Context, vs: VarState = VarState.Separate): Boolean =

Expand Down Expand Up @@ -243,7 +239,7 @@ trait CaptureRef extends TypeProxy, ValueType:
end subsumes

/** This is a maximal capabaility that subsumes `y` in given context and VarState.
* @param canAddHidden If true we allow maximal capabilities to subsume all other capabilities.
* @param canAddHidden If true we allow maximal capabilties to subsume all other capabilities.
* We add those capabilities to the hidden set if this is Fresh.Cap
* If false we only accept `y` elements that are already in the
* hidden set of this Fresh.Cap. The idea is that in a VarState that
Expand Down
4 changes: 1 addition & 3 deletions compiler/src/dotty/tools/dotc/cc/CaptureSet.scala
Original file line number Diff line number Diff line change
Expand Up @@ -636,8 +636,7 @@ object CaptureSet:
*/
def solve()(using Context): Unit =
if !isConst then
val approx = upperApprox(empty)
.map(Fresh.FromCap(NoSymbol).inverse) // Fresh.Cap --> cap
val approx = upperApprox(empty).map(Fresh.FromCap(NoSymbol).inverse)
.showing(i"solve $this = $result", capt)
//println(i"solving var $this $approx ${approx.isConst} deps = ${deps.toList}")
val newElems = approx.elems -- elems
Expand Down Expand Up @@ -1140,7 +1139,6 @@ object CaptureSet:

/** A template for maps on capabilities where f(c) <: c and f(f(c)) = c */
private abstract class NarrowingCapabilityMap(using Context) extends BiTypeMap:

def mapRef(ref: CaptureRef): CaptureRef

def apply(t: Type) = t match
Expand Down
60 changes: 22 additions & 38 deletions compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala
Original file line number Diff line number Diff line change
Expand Up @@ -519,7 +519,8 @@ class CheckCaptures extends Recheck, SymTransformer:
def includeCallCaptures(sym: Symbol, resType: Type, tree: Tree)(using Context): Unit = resType match
case _: MethodOrPoly => // wait until method is fully applied
case _ =>
if sym.exists && curEnv.isOpen then markFree(capturedVars(sym), tree)
if sym.exists then
if curEnv.isOpen then markFree(capturedVars(sym), tree)

/** Under the sealed policy, disallow the root capability in type arguments.
* Type arguments come either from a TypeApply node or from an AppliedType
Expand Down Expand Up @@ -555,21 +556,16 @@ class CheckCaptures extends Recheck, SymTransformer:
if param.isUseParam then markFree(arg.nuType.deepCaptureSet, errTree)
end disallowCapInTypeArgs

/** Rechecking idents involves:
* - adding call captures for idents referring to methods
* - marking as free the identifier with any selections or .rd
* modifiers implied by the expected type
*/
override def recheckIdent(tree: Ident, pt: Type)(using Context): Type =
val sym = tree.symbol
if sym.is(Method) then
// If ident refers to a parameterless method, charge its cv to the environment
includeCallCaptures(sym, sym.info, tree)
else if !sym.isStatic then
// Otherwise charge its symbol, but add all selections and also any `.rd`
// modifier implied by the expected type `pt`.
// Example: If we have `x` and the expected type says we select that with `.a.b`
// where `b` is a read-only method, we charge `x.a.b.rd` instead of `x`.
// Otherwise charge its symbol, but add all selections implied by the e
// expected type `pt`.
// Example: If we have `x` and the expected type says we select that with `.a.b`,
// we charge `x.a.b` instead of `x`.
def addSelects(ref: TermRef, pt: Type): CaptureRef = pt match
case pt: PathSelectionProto if ref.isTracked =>
if pt.sym.isReadOnlyMethod then
Expand All @@ -586,8 +582,7 @@ class CheckCaptures extends Recheck, SymTransformer:
super.recheckIdent(tree, pt)

/** 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
* a PathSelectionProto.
* could be part of a capabaility path, we return a PathSelectionProto.
*/
override def selectionProto(tree: Select, pt: Type)(using Context): Type =
val sym = tree.symbol
Expand Down Expand Up @@ -621,9 +616,6 @@ class CheckCaptures extends Recheck, SymTransformer:
}
case _ => denot

// Don't allow update methods to be called unless the qualifier captures
// contain an exclusive referenece. TODO This should probabkly rolled into
// qualifier logic once we have it.
if tree.symbol.isUpdateMethod && !qualType.captureSet.isExclusive then
report.error(
em"""cannot call update ${tree.symbol} from $qualType,
Expand Down Expand Up @@ -659,8 +651,8 @@ class CheckCaptures extends Recheck, SymTransformer:
selType
}//.showing(i"recheck sel $tree, $qualType = $result")

/** Hook for massaging a function before it is applied. Copies all @use and @consume
* annotations on method parameter symbols to the corresponding paramInfo types.
/** Hook for massaging a function before it is applied. Copies all @use annotations
* on method parameter symbols to the corresponding paramInfo types.
*/
override def prepareFunction(funtpe: MethodType, meth: Symbol)(using Context): MethodType =
val paramInfosWithUses =
Expand Down Expand Up @@ -690,8 +682,7 @@ class CheckCaptures extends Recheck, SymTransformer:
includeCallCaptures(meth, res, tree)
res

/** Recheck argument against a "freshened" version of `formal` where toplevel `cap`
* occurrences are replaced by `Fresh.Cap`. Also, if formal parameter carries a `@use`,
/** Recheck argument, and, if formal parameter carries a `@use`,
* charge the deep capture set of the actual argument to the environment.
*/
protected override def recheckArg(arg: Tree, formal: Type)(using Context): Type =
Expand Down Expand Up @@ -782,21 +773,16 @@ class CheckCaptures extends Recheck, SymTransformer:

/** First half of result pair:
* Refine the type of a constructor call `new C(t_1, ..., t_n)`
* to C{val x_1: @refineOverride T_1, ..., x_m: @refineOverride T_m}
* where x_1, ..., x_m are the tracked parameters of C and
* T_1, ..., T_m are the types of the corresponding arguments. The @refineOveride
* annotations avoid problematic intersections of capture sets when those
* parameters are selected.
* to C{val x_1: T_1, ..., x_m: T_m} where x_1, ..., x_m are the tracked
* parameters of C and T_1, ..., T_m are the types of the corresponding arguments.
*
* Second half: union of initial capture set and all capture sets of arguments
* to tracked parameters. The initial capture set `initCs` is augmented with
* - Fresh.Cap if `core` extends Mutable
* - Fresh.Cap.rd if `core` extends Capability
* to tracked parameters.
*/
def addParamArgRefinements(core: Type, initCs: CaptureSet): (Type, CaptureSet) =
var refined: Type = core
var allCaptures: CaptureSet =
if core.derivesFromMutable then initCs ++ CaptureSet.fresh()
if core.derivesFromMutable then CaptureSet.fresh()
else if core.derivesFromCapability then initCs ++ Fresh.Cap().readOnly.singletonCaptureSet
else initCs
for (getterName, argType) <- mt.paramNames.lazyZip(argTypes) do
Expand Down Expand Up @@ -1502,7 +1488,7 @@ class CheckCaptures extends Recheck, SymTransformer:
/** 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
* The unboxed condition ensures that the expected 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.
Expand All @@ -1528,9 +1514,9 @@ class CheckCaptures extends Recheck, SymTransformer:
actual
else
val improvedVAR = improveCaptures(actual.widen.dealiasKeepAnnots, actual)
val improved = improveReadOnly(improvedVAR, expected)
val improvedRO = improveReadOnly(improvedVAR, expected)
val adapted = adaptBoxed(
improved.withReachCaptures(actual), expected, tree,
improvedRO.withReachCaptures(actual), expected, tree,
covariant = true, alwaysConst = false, boxErrors)
if adapted eq improvedVAR // no .rd improvement, no box-adaptation
then actual // might as well use actual instead of improved widened
Expand Down Expand Up @@ -1577,19 +1563,17 @@ class CheckCaptures extends Recheck, SymTransformer:

/** Check that overrides don't change the @use or @consume status of their parameters */
override def additionalChecks(member: Symbol, other: Symbol)(using Context): Unit =
def fail(msg: String) =
report.error(
OverrideError(msg, self, member, other, self.memberInfo(member), self.memberInfo(other)),
if member.owner == clazz then member.srcPos else clazz.srcPos)
for
(params1, params2) <- member.rawParamss.lazyZip(other.rawParamss)
(param1, param2) <- params1.lazyZip(params2)
do
def checkAnnot(cls: ClassSymbol) =
if param1.hasAnnotation(cls) != param2.hasAnnotation(cls) then
report.error(
OverrideError(
i"has a parameter ${param1.name} with different @${cls.name} status than the corresponding parameter in the overridden definition",
self, member, other, self.memberInfo(member), self.memberInfo(other)
),
if member.owner == clazz then member.srcPos else clazz.srcPos)

fail(i"has a parameter ${param1.name} with different @${cls.name} status than the corresponding parameter in the overridden definition")
checkAnnot(defn.UseAnnot)
checkAnnot(defn.ConsumeAnnot)
end OverridingPairsCheckerCC
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/cc/Existential.scala
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ object Existential:
tp1.derivedAnnotatedType(toCap(parent), ann)
case _ => tp

/** Map existentials at the top-level and in all nested result types to `Fresh.Cap`
/** Map existentials at the top-level and in all nested result types to `cap`
*/
def toCapDeeply(tp: Type)(using Context): Type = tp.dealiasKeepAnnots match
case Existential(boundVar, unpacked) =>
Expand Down
15 changes: 3 additions & 12 deletions compiler/src/dotty/tools/dotc/cc/Fresh.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,11 @@ import util.SimpleIdentitySet.empty
import CaptureSet.{Refs, emptySet, NarrowingCapabilityMap}
import dotty.tools.dotc.util.SimpleIdentitySet

/** A module for handling Fresh types. Fresh.Cap instances are top type that keep
* track of what they hide when capabilities get widened by subsumption to fresh.
* The module implements operations to convert between regular caps.cap and
* Fresh.Cap instances. Fresh.Cap is encoded as `caps.cap @freshCapability(...)` where
* `freshCapability(...)` is a special kind of annotation of type `Fresh.Annot`
* that contains a hidden set.
*/
/** Handling fresh in CC:
*/
object Fresh:

/** The annotation of a Fresh.Cap instance */
case class Annot(hidden: CaptureSet.HiddenSet) extends Annotation:
override def symbol(using Context) = defn.FreshCapabilityAnnot
override def tree(using Context) = New(symbol.typeRef, Nil)
Expand All @@ -37,17 +32,13 @@ object Fresh:
case _ => false
end Annot

/** The initial elements (either 0 or 1) of a hidden set created for given `owner`.
* If owner `x` is a trackable this is `x*` if reach` is true, or `x` otherwise.
*/
private def ownerToHidden(owner: Symbol, reach: Boolean)(using Context): Refs =
val ref = owner.termRef
if reach then
if ref.isTrackableRef then SimpleIdentitySet(ref.reach) else emptySet
else
if ref.isTracked then SimpleIdentitySet(ref) else emptySet

/** An extractor for "fresh" capabilities */
object Cap:

def apply(initialHidden: Refs = emptySet)(using Context): CaptureRef =
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/cc/SepCheck.scala
Original file line number Diff line number Diff line change
Expand Up @@ -411,7 +411,7 @@ class SepChecker(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
def currentOwner = role.dclSym.orElse(ctx.owner)
for hiddenRef <- prune(refsToCheck, tpe, role) do
val proot = hiddenRef.pathRootOrShared
if !proot.widen.derivesFromSharedCapability then
if !proot.widen.derivesFrom(defn.Caps_SharedCapability) then
proot match
case ref: TermRef =>
val refSym = ref.symbol
Expand Down Expand Up @@ -448,7 +448,7 @@ class SepChecker(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser:
role match
case _: TypeRole.Argument | _: TypeRole.Qualifier =>
for ref <- refsToCheck do
if !ref.pathRootOrShared.derivesFromSharedCapability then
if !ref.pathRootOrShared.derivesFrom(defn.Caps_SharedCapability) then
consumed.put(ref, pos)
case _ =>
end checkConsumedRefs
Expand Down
10 changes: 1 addition & 9 deletions compiler/src/dotty/tools/dotc/cc/Setup.scala
Original file line number Diff line number Diff line change
Expand Up @@ -345,18 +345,10 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
parent
case _ => tp

/** Check that types extending SharedCapability don't have a `cap` in their capture set.
* TODO This is not enough.
* We need to also track that we cannot get exclusive capabilities in paths
* where some prefix derives from SharedCapability. Also, can we just
* exclude `cap`, or do we have to extend this to all exclusive capabilties?
* The problem is that we know what is exclusive in general only after capture
* checking, not before.
*/
def checkSharedOK(tp: Type): tp.type =
tp match
case CapturingType(parent, refs)
if refs.isUniversal && parent.derivesFromSharedCapability =>
if refs.isUniversal && parent.derivesFrom(defn.Caps_SharedCapability) =>
fail(em"$tp extends SharedCapability, so it cannot capture `cap`")
case _ =>
tp
Expand Down
2 changes: 2 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1115,6 +1115,8 @@ class Definitions {
@tu lazy val SilentAnnots: Set[Symbol] =
Set(InlineParamAnnot, ErasedParamAnnot, RefineOverrideAnnot)

@tu lazy val ccParamOnlyAnnotations: Set[Symbol] = Set(UseAnnot, ConsumeAnnot)

// A list of annotations that are commonly used to indicate that a field/method argument or return
// type is not null. These annotations are used by the nullification logic in JavaNullInterop to
// improve the precision of type nullification.
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/typer/Checking.scala
Original file line number Diff line number Diff line change
Expand Up @@ -606,7 +606,7 @@ object Checking {
if sym.isWrappedToplevelDef && !sym.isType && sym.flags.is(Infix, butNot = Extension) then
fail(ModifierNotAllowedForDefinition(Flags.Infix, s"A top-level ${sym.showKind} cannot be infix."))
if sym.isUpdateMethod && !sym.owner.derivesFrom(defn.Caps_Mutable) then
fail(em"Update methods can only be used as members of classes extending the `Mutable` trait")
fail(em"Update methods can only be used as members of classes deriving from the `Mutable` trait")
checkApplicable(Erased,
!sym.is(Lazy, butNot = Given)
&& !sym.isMutableVarOrAccessor
Expand Down
Loading

0 comments on commit edfe896

Please sign in to comment.