diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index a9272b73e605..bc4eb92234eb 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -27,32 +27,25 @@ object ccConfig: */ inline val allowUnsoundMaps = false - /** If true, when computing the memberinfo of a refined type created - * by addCaptureRefinements take the refineInfo directly without intersecting - * with the parent info. - */ - inline val optimizedRefinements = false - /** If enabled, use a special path in recheckClosure for closures - * that are eta expansions. This can improve some error messages but - * currently leads to unsoundess for handling reach capabilities. - * TODO: The unsoundness needs followin up. + * that are eta expansions. This can improve some error messages. */ - inline val handleEtaExpansionsSpecially = false + inline val handleEtaExpansionsSpecially = true - /** If true, use existential capture set variables */ - def useExistentials(using Context) = - Feature.sourceVersion.stable.isAtLeast(SourceVersion.`3.5`) + /** Don't require @use for reach capabilities that are accessed + * only in a nested closure. This is unsound without additional + * mitigation measures, as shown by unsound-reach-5.scala. + */ + inline val deferredReaches = false /** If true, use "sealed" as encapsulation mechanism, meaning that we * check that type variable instantiations don't have `cap` in any of * their capture sets. This is an alternative of the original restriction - * that `cap` can't be boxed or unboxed. It is used in 3.3 and 3.4 but - * dropped again in 3.5. + * that `cap` can't be boxed or unboxed. It is dropped in 3.5 but used + * again in 3.6. */ def useSealed(using Context) = - Feature.sourceVersion.stable == SourceVersion.`3.3` - || Feature.sourceVersion.stable == SourceVersion.`3.4` + Feature.sourceVersion.stable != SourceVersion.`3.5` end ccConfig @@ -134,10 +127,6 @@ end CCState def ccState(using Context) = Phases.checkCapturesPhase.asInstanceOf[CheckCaptures].ccState1 -class NoCommonRoot(rs: Symbol*)(using Context) extends Exception( - i"No common capture root nested in ${rs.mkString(" and ")}" -) - extension (tree: Tree) /** Map tree with CaptureRef type to its type, @@ -221,19 +210,25 @@ extension (tp: Type) case tp: SingletonCaptureRef => tp.captureSetOfInfo case _ => CaptureSet.ofType(tp, followResult = false) - /** The deep capture set of a type. - * For singleton capabilities `x` and reach capabilities `x*`, this is `{x*}`, provided - * the underlying capture set resulting from traversing the type is non-empty. - * For other types this is the union of all covariant capture sets embedded - * in the type, as computed by `CaptureSet.ofTypeDeeply`. + /** The deep capture set of a type. This is by default the union of all + * covariant capture sets embedded in the widened type, as computed by + * `CaptureSet.ofTypeDeeply`. If that set is nonempty, and the type is + * a singleton capability `x` or a reach capability `x*`, the deep capture + * set can be narrowed to`{x*}`. */ - def deepCaptureSet(using Context): CaptureSet = - val dcs = CaptureSet.ofTypeDeeply(tp) - if dcs.isAlwaysEmpty then dcs + def deepCaptureSet(includeTypevars: Boolean)(using Context): CaptureSet = + val dcs = CaptureSet.ofTypeDeeply(tp.widen.stripCapturing, includeTypevars) + if dcs.isAlwaysEmpty then tp.captureSet else tp match - case tp @ ReachCapability(_) => tp.singletonCaptureSet - case tp: SingletonCaptureRef => tp.reach.singletonCaptureSet - case _ => dcs + case tp @ ReachCapability(_) => + tp.singletonCaptureSet + case tp: SingletonCaptureRef if tp.isTrackableRef => + tp.reach.singletonCaptureSet + case _ => + tp.captureSet ++ dcs + + def deepCaptureSet(using Context): CaptureSet = + deepCaptureSet(includeTypevars = false) /** A type capturing `ref` */ def capturing(ref: CaptureRef)(using Context): Type = @@ -273,6 +268,29 @@ extension (tp: Type) case _ => tp + /** The first element of this path type */ + final def pathRoot(using Context): Type = tp.dealias match + case tp1: NamedType if tp1.symbol.owner.isClass => tp1.prefix.pathRoot + case tp1 => tp1 + + /** If this part starts with `C.this`, the class `C`. + * Otherwise, if it starts with a reference `r`, `r`'s owner. + * Otherwise NoSymbol. + */ + final def pathOwner(using Context): Symbol = pathRoot match + case tp1: NamedType => tp1.symbol.owner + case tp1: ThisType => tp1.cls + case _ => NoSymbol + + final def isParamPath(using Context): Boolean = tp.dealias match + case tp1: NamedType => + tp1.prefix match + case _: ThisType | NoPrefix => + tp1.symbol.is(Param) || tp1.symbol.is(ParamAccessor) + case prefix => prefix.isParamPath + case _: ParamRef => true + case _ => false + /** If this is a unboxed capturing type with nonempty capture set, its boxed version. * Or, if type is a TypeBounds of capturing types, the version where the bounds are boxed. * The identity for all other types. @@ -456,53 +474,35 @@ extension (tp: Type) * occurrences of cap are allowed in instance types of type variables. */ def withReachCaptures(ref: Type)(using Context): Type = - class CheckContraCaps extends TypeTraverser: - var ok = true - def traverse(t: Type): Unit = - if ok then - t.dealias match - case CapturingType(_, cs) if cs.isUniversal && variance <= 0 => - ok = false - case _ => - traverseChildren(t) - end CheckContraCaps - object narrowCaps extends TypeMap: - /** Has the variance been flipped at this point? */ - private var isFlipped: Boolean = false - + var change = false def apply(t: Type) = - val saved = isFlipped - try - if variance <= 0 then isFlipped = true - t.dealias match - case t1 @ CapturingType(p, cs) if cs.isUniversal && !isFlipped => - t1.derivedCapturingType(apply(p), ref.reach.singletonCaptureSet) - case t1 @ FunctionOrMethod(args, res @ Existential(_, _)) - if args.forall(_.isAlwaysPure) => - // Also map existentials in results to reach capabilities if all - // preceding arguments are known to be always pure - apply(t1.derivedFunctionOrMethod(args, Existential.toCap(res))) - case Existential(_, _) => - t - case _ => t match - case t @ CapturingType(p, cs) => - t.derivedCapturingType(apply(p), cs) // don't map capture set variables - case t => - mapOver(t) - finally isFlipped = saved + if variance <= 0 then t + else t.dealiasKeepAnnots match + case t @ CapturingType(p, cs) if cs.isUniversal => + change = true + t.derivedCapturingType(apply(p), ref.reach.singletonCaptureSet) + case t @ AnnotatedType(parent, ann) => + // Don't map annotations, which includes capture sets + t.derivedAnnotatedType(this(parent), ann) + case t @ FunctionOrMethod(args, res @ Existential(_, _)) + if args.forall(_.isAlwaysPure) => + // Also map existentials in results to reach capabilities if all + // preceding arguments are known to be always pure + apply(t.derivedFunctionOrMethod(args, Existential.toCap(res))) + case Existential(_, _) => + t + case _ => + mapOver(t) end narrowCaps ref match case ref: CaptureRef if ref.isTrackableRef => - val checker = new CheckContraCaps - if !ccConfig.useExistentials then checker.traverse(tp) - if checker.ok then - val tp1 = narrowCaps(tp) - if tp1 ne tp then capt.println(i"narrow $tp of $ref to $tp1") + val tp1 = narrowCaps(tp) + if narrowCaps.change then + capt.println(i"narrow $tp of $ref to $tp1") tp1 else - capt.println(i"cannot narrow $tp of $ref") tp case _ => tp @@ -588,17 +588,14 @@ extension (sym: Symbol) case _ => false containsEnclTypeParam(sym.info.finalResultType) && !sym.allowsRootCapture - && sym != defn.Caps_unsafeBox - && sym != defn.Caps_unsafeUnbox && !defn.isPolymorphicAfterErasure(sym) && !defn.isTypeTestOrCast(sym) + /** It's a parameter accessor that is not annotated @constructorOnly or @uncheckedCaptures */ def isRefiningParamAccessor(using Context): Boolean = sym.is(ParamAccessor) && { - val param = sym.owner.primaryConstructor.paramSymss - .nestedFind(_.name == sym.name) - .getOrElse(NoSymbol) + val param = sym.owner.primaryConstructor.paramNamed(sym.name) !param.hasAnnotation(defn.ConstructorOnlyAnnot) && !param.hasAnnotation(defn.UntrackedCapturesAnnot) } @@ -606,6 +603,18 @@ extension (sym: Symbol) def hasTrackedParts(using Context): Boolean = !CaptureSet.ofTypeDeeply(sym.info).isAlwaysEmpty + /** `sym` is annotated @use or it is a type parameter with a matching + * @use-annotated term parameter that contains `sym` in its deep capture set. + */ + def isUseParam(using Context): Boolean = + sym.hasAnnotation(defn.UseAnnot) + || sym.is(TypeParam) + && sym.owner.rawParamss.nestedExists: param => + param.is(TermParam) && param.hasAnnotation(defn.UseAnnot) + && param.info.deepCaptureSet.elems.exists: + case c: TypeRef => c.symbol == sym + case _ => false + extension (tp: AnnotatedType) /** Is this a boxed capturing type? */ def isBoxed(using Context): Boolean = tp.annot match @@ -639,8 +648,8 @@ object CapsOfApply: class AnnotatedCapability(annot: Context ?=> ClassSymbol): def apply(tp: Type)(using Context) = AnnotatedType(tp, Annotation(annot, util.Spans.NoSpan)) - def unapply(tree: AnnotatedType)(using Context): Option[SingletonCaptureRef] = tree match - case AnnotatedType(parent: SingletonCaptureRef, ann) if ann.symbol == annot => Some(parent) + def unapply(tree: AnnotatedType)(using Context): Option[CaptureRef] = tree match + case AnnotatedType(parent: CaptureRef, ann) if ann.symbol == annot => Some(parent) case _ => None /** An extractor for `ref @annotation.internal.reachCapability`, which is used to express diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala index 81b4287961ba..5be4f6a2d1cd 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala @@ -185,10 +185,12 @@ sealed abstract class CaptureSet extends Showable: /** A more optimistic version of subCaptures used to choose one of two typing rules * for selections and applications. `cs1 mightSubcapture cs2` if `cs2` might account for - * every element currently known to be in `cs1`. + * every element currently known to be in `cs1`, and the same is not true in reverse + * when we compare elements of cs2 vs cs1. */ def mightSubcapture(that: CaptureSet)(using Context): Boolean = elems.forall(that.mightAccountFor) + && !that.elems.forall(this.mightAccountFor) /** The subcapturing test. * @param frozen if true, no new variables or dependent sets are allowed to @@ -1064,8 +1066,9 @@ object CaptureSet: case ref: (TermRef | TermParamRef) if ref.isMaxCapability => if ref.isTrackableRef then ref.singletonCaptureSet else CaptureSet.universal - case ReachCapability(ref1) => deepCaptureSet(ref1.widen) - .showing(i"Deep capture set of $ref: ${ref1.widen} = $result", capt) + case ReachCapability(ref1) => + ref1.widen.deepCaptureSet(includeTypevars = true) + .showing(i"Deep capture set of $ref: ${ref1.widen} = ${result}", capt) case _ => ofType(ref.underlying, followResult = true) /** Capture set of a type */ @@ -1115,17 +1118,33 @@ object CaptureSet: /** The deep capture set of a type is the union of all covariant occurrences of * capture sets. Nested existential sets are approximated with `cap`. + * NOTE: The traversal logic needs to be in sync with narrowCaps in CaptureOps, which + * replaces caps with reach capabilties. The one exception to this is invariant + * arguments. This have to be included to be conservative in dcs but must be + * excluded in narrowCaps. */ - def ofTypeDeeply(tp: Type)(using Context): CaptureSet = + def ofTypeDeeply(tp: Type, includeTypevars: Boolean = false)(using Context): CaptureSet = val collect = new TypeAccumulator[CaptureSet]: - def apply(cs: CaptureSet, t: Type) = t.dealias match - case t @ CapturingType(p, cs1) => - val cs2 = apply(cs, p) - if variance > 0 then cs2 ++ cs1 else cs2 - case t @ Existential(_, _) => - apply(cs, Existential.toCap(t)) - case _ => - foldOver(cs, t) + val seen = util.HashSet[Symbol]() + def apply(cs: CaptureSet, t: Type) = + if variance < 0 then cs + else t.dealias match + case t @ CapturingType(p, cs1) => + this(cs, p) ++ cs1 + case t @ AnnotatedType(parent, ann) => + this(cs, parent) + case t: TypeRef if t.symbol.isAbstractOrParamType && !seen.contains(t.symbol) => + seen += t.symbol + val upper = t.info.bounds.hi + if includeTypevars && upper.isExactlyAny then CaptureSet.universal + else this(cs, upper) + case t @ FunctionOrMethod(args, res @ Existential(_, _)) + if args.forall(_.isAlwaysPure) => + this(cs, Existential.toCap(res)) + case t @ Existential(_, _) => + cs + case _ => + foldOver(cs, t) collect(CaptureSet.empty, tp) type AssumedContains = immutable.Map[TypeRef, SimpleIdentitySet[CaptureRef]] diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 2a3cd50d0e0c..23e7d8f8ecf8 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -36,20 +36,22 @@ object CheckCaptures: case NestedInOwner // environment is a temporary one nested in the owner's environment, // and does not have a different actual owner symbol // (this happens when doing box adaptation). - case ClosureResult // environment is for the result of a closure case Boxed // environment is inside a box (in which case references are not counted) /** A class describing environments. - * @param owner the current owner - * @param kind the environment's kind - * @param captured the capture set containing all references to tracked free variables outside of boxes - * @param outer0 the next enclosing environment + * @param owner the current owner + * @param kind the environment's kind + * @param captured the capture set containing all references to tracked free variables outside of boxes + * @param outer0 the next enclosing environment + * @param nestedClosure under deferredReaches: If this is an env of a method with an anonymous function or + * anonymous class as RHS, the symbol of that function or class. NoSymbol in all other cases. */ case class Env( owner: Symbol, kind: EnvKind, captured: CaptureSet, - outer0: Env | Null): + outer0: Env | Null, + nestedClosure: Symbol = NoSymbol): def outer = outer0.nn @@ -88,6 +90,9 @@ object CheckCaptures: mapOver(tp) end SubstParamsMap + /** Used for substituting parameters in a special case: when all actual arguments + * are mutually distinct capabilities. + */ final class SubstParamsBiMap(from: LambdaType, to: List[Type])(using Context) extends BiTypeMap: thisMap => @@ -121,6 +126,9 @@ object CheckCaptures: def inverse = thisMap end SubstParamsBiMap + /** A prototype that indicates selection with an immutable value */ + class PathSelectionProto(val sym: Symbol, val pt: Type)(using Context) extends WildcardSelectionProto + /** Check that a @retains annotation only mentions references that can be tracked. * This check is performed at Typer. */ @@ -144,9 +152,9 @@ object CheckCaptures: case ReachCapabilityApply(arg) => check(arg, elem.srcPos) case _ => check(elem, elem.srcPos) - /** Report an error if some part of `tp` contains the root capability in its capture set - * or if it refers to an unsealed type parameter that could possibly be instantiated with - * cap in a way that's visible at the type. + /** Under the sealed policy, report an error if some part of `tp` contains the + * root capability in its capture set or if it refers to a type parameter that + * could possibly be instantiated with cap in a way that's visible at the type. */ private def disallowRootCapabilitiesIn(tp: Type, carrier: Symbol, what: String, have: String, addendum: String, pos: SrcPos)(using Context) = val check = new TypeTraverser: @@ -174,8 +182,7 @@ object CheckCaptures: def part = if t eq tp then "" else i"the part $t of " report.error( em"""$what cannot $have $tp since - |${part}that type captures the root capability `cap`. - |$addendum""", + |${part}that type captures the root capability `cap`.$addendum""", pos) traverse(parent) case t => @@ -183,11 +190,38 @@ object CheckCaptures: if ccConfig.useSealed then check.traverse(tp) end disallowRootCapabilitiesIn - /** Attachment key for bodies of closures, provided they are values */ - val ClosureBodyValue = Property.Key[Unit] - - /** A prototype that indicates selection with an immutable value */ - class PathSelectionProto(val sym: Symbol, val pt: Type)(using Context) extends WildcardSelectionProto + /** If we are not under the sealed policy, and a tree is an application that unboxes + * its result or is a try, check that the tree's type does not have covariant universal + * capabilities. + */ + private def checkNotUniversalInUnboxedResult(tpe: Type, tree: Tree)(using Context): Unit = + def needsUniversalCheck = tree match + case _: RefTree | _: Apply | _: TypeApply => tree.symbol.unboxesResult + case _: Try => true + case _ => false + + object checkNotUniversal extends TypeTraverser: + def traverse(tp: Type) = + tp.dealias match + case wtp @ CapturingType(parent, refs) => + if variance > 0 then + refs.disallowRootCapability: () => + def part = if wtp eq tpe.widen then "" else i" in its part $wtp" + report.error( + em"""The expression's type ${tpe.widen} is not allowed to capture the root capability `cap`$part. + |This usually means that a capability persists longer than its allowed lifetime.""", + tree.srcPos) + if !wtp.isBoxed then traverse(parent) + case tp => + traverseChildren(tp) + + if !ccConfig.useSealed + && !tpe.hasAnnotation(defn.UncheckedCapturesAnnot) + && needsUniversalCheck + && tpe.widen.isValueType + then + checkNotUniversal.traverse(tpe.widen) + end checkNotUniversalInUnboxedResult class CheckCaptures extends Recheck, SymTransformer: thisPhase => @@ -211,6 +245,22 @@ class CheckCaptures extends Recheck, SymTransformer: class CaptureChecker(ictx: Context) extends Rechecker(ictx): + /** The current environment */ + private val rootEnv: Env = inContext(ictx): + Env(defn.RootClass, EnvKind.Regular, CaptureSet.empty, null) + private var curEnv = rootEnv + + /** Currently checked closures and their expected types, used for error reporting */ + private var openClosures: List[(Symbol, Type)] = Nil + + private val myCapturedVars: util.EqHashMap[Symbol, CaptureSet] = EqHashMap() + + /** A list of actions to perform at postCheck. The reason to defer these actions + * is that it is sometimes better for type inference to not constrain too early + * with a checkConformsExpr. + */ + private val todoAtPostCheck = new mutable.ListBuffer[() => Unit] + override def keepType(tree: Tree) = super.keepType(tree) || tree.isInstanceOf[Try] // type of `try` needs tp be checked for * escapes @@ -242,10 +292,11 @@ class CheckCaptures extends Recheck, SymTransformer: report.warning(msg, tpt.srcPos) ccState.approxWarnings.clear() - /** Assert subcapturing `cs1 <: cs2` */ + /** Assert subcapturing `cs1 <: cs2` (available for debugging, otherwise unused) */ def assertSub(cs1: CaptureSet, cs2: CaptureSet)(using Context) = assert(cs1.subCaptures(cs2, frozen = false).isOK, i"$cs1 is not a subset of $cs2") + /** If `res` is not CompareResult.OK, report an error */ def checkOK(res: CompareResult, prefix: => String, pos: SrcPos, provenance: => String = "")(using Context): Unit = if !res.isOK then def toAdd: String = CaptureSet.levelErrors.toAdd.mkString @@ -270,60 +321,6 @@ class CheckCaptures extends Recheck, SymTransformer: else i"references $cs1$cs1description are not all", pos, provenance) - def showRef(ref: CaptureRef)(using Context): String = - ctx.printer.toTextCaptureRef(ref).show - - // Uses 4-space indent as a trial - private def checkReachCapsIsolated(tpe: Type, pos: SrcPos)(using Context): Unit = - - object checker extends TypeTraverser: - var refVariances: Map[Boolean, Int] = Map.empty - var seenReach: CaptureRef | Null = null - def traverse(tp: Type) = - tp.dealias match - case CapturingType(parent, refs) => - traverse(parent) - for ref <- refs.elems do - if ref.isReach && !ref.stripReach.isInstanceOf[TermParamRef] - || ref.isRootCapability - then - val isReach = ref.isReach - def register() = - refVariances = refVariances.updated(isReach, variance) - seenReach = ref - refVariances.get(isReach) match - case None => register() - case Some(v) => if v != 0 && variance == 0 then register() - case _ => - traverseChildren(tp) - - checker.traverse(tpe) - if checker.refVariances.size == 2 - && checker.refVariances(true) >= 0 - && checker.refVariances(false) <= 0 - then - report.error( - em"""Reach capability ${showRef(checker.seenReach.nn)} and universal capability cap cannot both - |appear in the type $tpe of this expression""", - pos) - end checkReachCapsIsolated - - /** The current environment */ - private val rootEnv: Env = inContext(ictx): - Env(defn.RootClass, EnvKind.Regular, CaptureSet.empty, null) - private var curEnv = rootEnv - - /** Currently checked closures and their expected types, used for error reporting */ - private var openClosures: List[(Symbol, Type)] = Nil - - private val myCapturedVars: util.EqHashMap[Symbol, CaptureSet] = EqHashMap() - - /** A list of actions to perform at postCheck. The reason to defer these actions - * is that it is sometimes better for type inference to not constrain too early - * with a checkConformsExpr. - */ - private var todoAtPostCheck = new mutable.ListBuffer[() => Unit] - /** If `sym` is a class or method nested inside a term, a capture set variable representing * the captured variables of the environment associated with `sym`. */ @@ -333,20 +330,17 @@ class CheckCaptures extends Recheck, SymTransformer: then CaptureSet.Var(sym.owner, level = sym.ccLevel) else CaptureSet.empty) - /** For all nested environments up to `limit` or a closed environment perform `op`, - * but skip environmenrts directly enclosing environments of kind ClosureResult. +// ---- Record Uses with MarkFree ---------------------------------------------------- + + /** The next environment enclosing `env` that needs to be charged + * with free references. + * @param included Whether an environment is included in the range of + * environments to charge. Once `included` is false, no + * more environments need to be charged. */ - def forallOuterEnvsUpTo(limit: Symbol)(op: Env => Unit)(using Context): Unit = - def recur(env: Env, skip: Boolean): Unit = - if env.isOpen && env.owner != limit then - if !skip then op(env) - if !env.isOutermost then - var nextEnv = env.outer - if env.owner.isConstructor then - if nextEnv.owner != limit && !nextEnv.isOutermost then - nextEnv = nextEnv.outer - recur(nextEnv, skip = env.kind == EnvKind.ClosureResult) - recur(curEnv, skip = false) + def nextEnvToCharge(env: Env, included: Env => Boolean)(using Context): Env = + if env.owner.isConstructor && included(env.outer) then env.outer.outer + else env.outer /** A description where this environment comes from */ private def provenance(env: Env)(using Context): String = @@ -360,6 +354,14 @@ class CheckCaptures extends Recheck, SymTransformer: else i"\nof the enclosing ${owner.showLocated}" + /** Does the given environment belong to a method that is (a) nested in a term + * and (b) not the method of an anonympus function? + */ + def isOfNestedMethod(env: Env | Null)(using Context) = + env != null + && env.owner.is(Method) + && env.owner.owner.isTerm + && !env.owner.isAnonymousFunction /** Include `sym` in the capture sets of all enclosing environments nested in the * the environment in which `sym` is defined. @@ -368,12 +370,9 @@ class CheckCaptures extends Recheck, SymTransformer: markFree(sym, sym.termRef, pos) def markFree(sym: Symbol, ref: TermRef, pos: SrcPos)(using Context): Unit = - if sym.exists && ref.isTracked then - forallOuterEnvsUpTo(sym.enclosure): env => - capt.println(i"Mark $sym with cs ${ref.captureSet} free in ${env.owner}") - checkElem(ref, env.captured, pos, provenance(env)) + if sym.exists && ref.isTracked then markFree(ref.captureSet, pos) - /** Make sure (projected) `cs` is a subset of the capture sets of all enclosing + /** Make sure the (projected) `cs` is a subset of the capture sets of all enclosing * environments. At each stage, only include references from `cs` that are outside * the environment's owner */ @@ -381,111 +380,157 @@ class CheckCaptures extends Recheck, SymTransformer: // A captured reference with the symbol `sym` is visible from the environment // if `sym` is not defined inside the owner of the environment. inline def isVisibleFromEnv(sym: Symbol, env: Env) = - if env.kind == EnvKind.NestedInOwner then - !sym.isProperlyContainedIn(env.owner) - else - !sym.isContainedIn(env.owner) - - def checkSubsetEnv(cs: CaptureSet, env: Env)(using Context): Unit = - // Only captured references that are visible from the environment - // should be included. - val included = cs.filter: c => - c.stripReach match - case ref: NamedType => - val refSym = ref.symbol - val refOwner = refSym.owner - val isVisible = isVisibleFromEnv(refOwner, env) - if isVisible && !ref.isRootCapability then - ref match - case ref: TermRef if ref.prefix `ne` NoPrefix => - // If c is a path of a class defined outside the environment, - // we check the capture set of its info. - checkSubsetEnv(ref.captureSetOfInfo, env) - case _ => - if !isVisible - && (c.isReach || ref.isType) - && (!ccConfig.useSealed || refSym.is(Param)) - && refOwner == env.owner - then - if refSym.hasAnnotation(defn.UnboxAnnot) then - capt.println(i"exempt: $ref in $refOwner") - else - // Reach capabilities that go out of scope have to be approximated - // by their underlying capture set, which cannot be universal. - // Reach capabilities of @unboxed parameters are exempted. - val cs = CaptureSet.ofInfo(c) - cs.disallowRootCapability: () => - report.error(em"Local reach capability $c leaks into capture scope of ${env.ownerString}", pos) - checkSubset(cs, env.captured, pos, provenance(env)) - isVisible - case ref: ThisType => isVisibleFromEnv(ref.cls, env) - case _ => false - checkSubset(included, env.captured, pos, provenance(env)) - capt.println(i"Include call or box capture $included from $cs in ${env.owner} --> ${env.captured}") - - if !cs.isAlwaysEmpty then - forallOuterEnvsUpTo(ctx.owner.topLevelClass): env => - checkSubsetEnv(cs, env) - end markFree + sym.exists && { + if env.kind == EnvKind.NestedInOwner then + !sym.isProperlyContainedIn(env.owner) + else + !sym.isContainedIn(env.owner) + } - /** Include references captured by the called method in the current environment stack */ - def includeCallCaptures(sym: Symbol, pos: SrcPos)(using Context): Unit = - if sym.exists && curEnv.isOpen then markFree(capturedVars(sym), pos) - - private val prefixCalls = util.EqHashSet[GenericApply]() - private val unboxedArgs = util.EqHashSet[Tree]() - - def handleCall(meth: Symbol, call: GenericApply, eval: () => Type)(using Context): Type = - if prefixCalls.remove(call) then return eval() - - val unboxedParamNames = - meth.rawParamss.flatMap: params => - params.collect: - case param if param.hasAnnotation(defn.UnboxAnnot) => - param.name - .toSet - - def markUnboxedArgs(call: GenericApply): Unit = call.fun.tpe.widen match - case MethodType(pnames) => - for (pname, arg) <- pnames.lazyZip(call.args) do - if unboxedParamNames.contains(pname) then - unboxedArgs.add(arg) - case _ => + /** If captureRef `c` refers to a parameter that is not @use declared, report an error. + * Exception under deferredReaches: If use comes from a nested closure, accept it. + */ + def checkUseDeclared(c: CaptureRef, env: Env, lastEnv: Env | Null) = + if lastEnv != null && env.nestedClosure.exists && env.nestedClosure == lastEnv.owner then + assert(ccConfig.deferredReaches) // access is from a nested closure under deferredReaches, so it's OK + else c.pathRoot match + case ref: NamedType if !ref.symbol.isUseParam => + val what = if ref.isType then "Capture set parameter" else "Local reach capability" + report.error( + em"""$what $c leaks into capture scope of ${env.ownerString}. + |To allow this, the ${ref.symbol} should be declared with a @use annotation""", pos) + case _ => - def markPrefixCalls(tree: Tree): Unit = tree match - case tree: GenericApply => - prefixCalls.add(tree) - markUnboxedArgs(tree) - markPrefixCalls(tree.fun) + /** Avoid locally defined capability by charging the underlying type + * (which may not be cap). This scheme applies only under the deferredReaches setting. + */ + def avoidLocalCapability(c: CaptureRef, env: Env, lastEnv: Env | Null): Unit = + if c.isParamPath then + c match + case ReachCapability(_) | _: TypeRef => + checkUseDeclared(c, env, lastEnv) + case _ => + else + val underlying = c match + case ReachCapability(c1) => + CaptureSet.ofTypeDeeply(c1.widen) + case _ => + CaptureSet.ofType(c.widen, followResult = false) + capt.println(i"Widen reach $c to $underlying in ${env.owner}") + underlying.disallowRootCapability: () => + report.error(em"Local capability $c in ${env.ownerString} cannot have `cap` as underlying capture set", pos) + recur(underlying, env, lastEnv) + + /** Avoid locally defined capability if it is a reach capability or capture set + * parameter. This is the default. + */ + def avoidLocalReachCapability(c: CaptureRef, env: Env): Unit = c match + case ReachCapability(c1) => + if c1.isParamPath then + checkUseDeclared(c, env, null) + else + // When a reach capabilty x* where `x` is not a parameter goes out + // of scope, we need to continue with `x`'s underlying deep capture set. + // It is an error if that set contains cap. + // The same is not an issue for normal capabilities since in a local + // definition `val x = e`, the capabilities of `e` have already been charged. + // Note: It's not true that the underlying capture set of a reach capability + // is always cap. Reach capabilities over paths depend on the prefix, which + // might turn a cap into something else. + // The path-use.scala neg test contains an example. + val underlying = CaptureSet.ofTypeDeeply(c1.widen) + capt.println(i"Widen reach $c to $underlying in ${env.owner}") + underlying.disallowRootCapability: () => + report.error(em"Local reach capability $c leaks into capture scope of ${env.ownerString}", pos) + recur(underlying, env, null) + case c: TypeRef if c.isParamPath => + checkUseDeclared(c, env, null) case _ => - markUnboxedArgs(call) - markPrefixCalls(call.fun) - val res = eval() - includeCallCaptures(meth, call.srcPos) - res - end handleCall + def recur(cs: CaptureSet, env: Env, lastEnv: Env | Null): Unit = + if env.isOpen && !env.owner.isStaticOwner && !cs.isAlwaysEmpty then + // Only captured references that are visible from the environment + // should be included. + val included = cs.filter: c => + val isVisible = isVisibleFromEnv(c.pathOwner, env) + if !isVisible then + if ccConfig.deferredReaches + then avoidLocalCapability(c, env, lastEnv) + else avoidLocalReachCapability(c, env) + isVisible + checkSubset(included, env.captured, pos, provenance(env)) + capt.println(i"Include call or box capture $included from $cs in ${env.owner} --> ${env.captured}") + if !isOfNestedMethod(env) then + recur(included, nextEnvToCharge(env, !_.owner.isStaticOwner), env) + // Don't propagate out of methods inside terms. The use set of these methods + // will be charged when that method is called. + + recur(cs, curEnv, null) + end markFree + + /** Include references captured by the called method in the current environment stack */ + def includeCallCaptures(sym: Symbol, resType: Type, pos: SrcPos)(using Context): Unit = resType match + case _: MethodOrPoly => // wait until method is fully applied + case _ => + if sym.exists && curEnv.isOpen then markFree(capturedVars(sym), pos) + + /** Under the sealed policy, disallow the root capability in type arguments. + * Type arguments come either from a TypeApply node or from an AppliedType + * which represents a trait parent in a template. + * Also, if a corresponding formal type parameter is declared or implied @use, + * charge the deep capture set of the argument to the environent. + * @param fn the type application, of type TypeApply or TypeTree + * @param sym the constructor symbol (could be a method or a val or a class) + * @param args the type arguments + */ + def disallowCapInTypeArgs(fn: Tree, sym: Symbol, args: List[Tree])(using Context): Unit = + def isExempt = sym.isTypeTestOrCast || sym == defn.Compiletime_erasedValue + if ccConfig.useSealed && !isExempt then + val paramNames = atPhase(thisPhase.prev): + fn.tpe.widenDealias match + case tl: TypeLambda => tl.paramNames + case ref: AppliedType if ref.typeSymbol.isClass => ref.typeSymbol.typeParams.map(_.name) + case t => + println(i"parent type: $t") + args.map(_ => EmptyTypeName) + + for case (arg: TypeTree, pname) <- args.lazyZip(paramNames) do + def where = if sym.exists then i" in an argument of $sym" else "" + val (addendum, pos) = + if arg.isInferred + then ("\nThis is often caused by a local capability$where\nleaking as part of its result.", fn.srcPos) + else if arg.span.exists then ("", arg.srcPos) + else ("", fn.srcPos) + disallowRootCapabilitiesIn(arg.knownType, NoSymbol, + i"Type variable $pname of $sym", "be instantiated to", addendum, pos) + + val param = fn.symbol.paramNamed(pname) + if param.isUseParam then markFree(arg.knownType.deepCaptureSet, pos) + end disallowCapInTypeArgs override def recheckIdent(tree: Ident, pt: Type)(using Context): Type = - if tree.symbol.is(Method) then - if tree.symbol.info.isParameterless then - // there won't be an apply; need to include call captures now - includeCallCaptures(tree.symbol, tree.srcPos) - else if !tree.symbol.isStatic then - //debugShowEnvs() + 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.srcPos) + else if !sym.isStatic then + // 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): TermRef = pt match case pt: PathSelectionProto if ref.isTracked => // if `ref` is not tracked then the selection could not give anything new // class SerializationProxy in stdlib-cc/../LazyListIterable.scala has an example where this matters. addSelects(ref.select(pt.sym).asInstanceOf[TermRef], pt.pt) case _ => ref - val ref = tree.symbol.termRef - val pathRef = addSelects(ref, pt) - //if pathRef ne ref then - // println(i"add selects $ref --> $pathRef") - markFree(tree.symbol, if false then ref else pathRef, tree.srcPos) + val pathRef = addSelects(sym.termRef, pt) + markFree(sym, pathRef, tree.srcPos) super.recheckIdent(tree, pt) + /** The expected type for the qualifier of a selection. If the selection + * 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 if !sym.isOneOf(UnstableValueFlags) && !sym.isStatic then PathSelectionProto(sym, pt) @@ -498,8 +543,8 @@ class CheckCaptures extends Recheck, SymTransformer: * E |- f.m: R^C * * The implementation picks as `C` one of `{f}` or `Cr`, depending on the - * outcome of a `mightSubcapture` test. It picks `{f}` if this might subcapture Cr - * and Cr otherwise. + * outcome of a `mightSubcapture` test. It picks `{f}` if it might subcapture Cr + * and picks Cr otherwise. */ override def recheckSelection(tree: Select, qualType: Type, name: Name, pt: Type)(using Context) = { def disambiguate(denot: Denotation): Denotation = denot match @@ -519,10 +564,14 @@ class CheckCaptures extends Recheck, SymTransformer: val selType = recheckSelection(tree, qualType, name, disambiguate) val selWiden = selType.widen + // Don't apply the rule + // - on the LHS of assignments, or + // - if the qualifier or selection type is boxed, or + // - the selection is either a trackable capture ref or a pure type if pt == LhsProto || qualType.isBoxedCapturing - || selType.isTrackableRef || selWiden.isBoxedCapturing + || selType.isTrackableRef || selWiden.captureSet.isAlwaysEmpty then selType @@ -532,7 +581,7 @@ class CheckCaptures extends Recheck, SymTransformer: capt.println(i"pick one of $qualType, ${selType.widen}, $qualCs, $selCs ${selWiden.captureSet} in $tree") if qualCs.mightSubcapture(selCs) - && !selCs.mightSubcapture(qualCs) + //&& !selCs.mightSubcapture(qualCs) && !pt.stripCapturing.isInstanceOf[SingletonType] then selWiden.stripCapturing.capturing(qualCs) @@ -541,16 +590,22 @@ 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 annotations + * on method parameter symbols to the corresponding paramInfo types. + */ + override def prepareFunction(funtpe: MethodType, meth: Symbol)(using Context): MethodType = + val paramInfosWithUses = funtpe.paramInfos.zipWithConserve(funtpe.paramNames): (formal, pname) => + val param = meth.paramNamed(pname) + param.getAnnotation(defn.UseAnnot) match + case Some(ann) => AnnotatedType(formal, ann) + case _ => formal + funtpe.derivedLambdaType(paramInfos = paramInfosWithUses) + + /** Recheck applications, with special handling of unsafeAssumePure. + * More work is done in `recheckApplication`, `recheckArg` and `instantiate` below. + */ override def recheckApply(tree: Apply, pt: Type)(using Context): Type = val meth = tree.fun.symbol - - // Unsafe box/unbox handling, only for versions < 3.3 - def mapArgUsing(f: Type => Type) = - val arg :: Nil = tree.args: @unchecked - val argType0 = f(recheckStart(arg, pt)) - val argType = super.recheckFinish(argType0, arg, pt) - super.recheckFinish(argType, tree, pt) - if meth == defn.Caps_unsafeAssumePure then val arg :: Nil = tree.args: @unchecked val argType0 = recheck(arg, pt.capturing(CaptureSet.universal)) @@ -559,34 +614,26 @@ class CheckCaptures extends Recheck, SymTransformer: else argType0.widen.stripCapturing capt.println(i"rechecking $arg with $pt: $argType") super.recheckFinish(argType, tree, pt) - else if meth == defn.Caps_unsafeBox then - mapArgUsing(_.forceBoxStatus(true)) - else if meth == defn.Caps_unsafeUnbox then - mapArgUsing(_.forceBoxStatus(false)) - else if meth == defn.Caps_unsafeBoxFunArg then - def forceBox(tp: Type): Type = tp.strippedDealias match - case defn.FunctionOf(paramtpe :: Nil, restpe, isContextual) => - defn.FunctionOf(paramtpe.forceBoxStatus(true) :: Nil, restpe, isContextual) - case tp @ RefinedType(parent, rname, rinfo: MethodType) => - tp.derivedRefinedType(parent, rname, - rinfo.derivedLambdaType( - paramInfos = rinfo.paramInfos.map(_.forceBoxStatus(true)))) - case tp @ CapturingType(parent, refs) => - tp.derivedCapturingType(forceBox(parent), refs) - mapArgUsing(forceBox) else - handleCall(meth, tree, () => super.recheckApply(tree, pt)) - end recheckApply + val res = super.recheckApply(tree, pt) + includeCallCaptures(meth, res, tree.srcPos) + res - protected override - def recheckArg(arg: Tree, formal: Type)(using Context): Type = + /** 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 = val argType = recheck(arg, formal) - if unboxedArgs.contains(arg) then - capt.println(i"charging deep capture set of $arg: ${argType} = ${argType.deepCaptureSet}") - markFree(argType.deepCaptureSet, arg.srcPos) + formal match + case AnnotatedType(formal1, ann) if ann.symbol == defn.UseAnnot => + // The UseAnnot is added to `formal` by `prepareFunction` + capt.println(i"charging deep capture set of $arg: ${argType} = ${argType.deepCaptureSet}") + markFree(argType.deepCaptureSet, arg.srcPos) + case _ => argType - /** A specialized implementation of the apply rule. + /** Map existential captures in result to `cap` and implement the following + * rele: * * E |- q: Tq^Cq * E |- q.f: Ta^Ca ->Cf Tr^Cr @@ -594,16 +641,16 @@ class CheckCaptures extends Recheck, SymTransformer: * --------------------- * E |- f(a): Tr^C * - * If the function `f` does not have an `@unboxed` parameter, then + * If the function `f` does not have an `@use` parameter, then * any unboxing it does would be charged to the environment of the function * so they have to appear in Cq. Since any capabilities of the result of the * application must already be present in the application, an upper * approximation of the result capture set is Cq \union Ca, where `Ca` * is the capture set of the argument. - * If the function `f` does have an `@unboxed` parameter, then it could in addition + * If the function `f` does have an `@use` parameter, then it could in addition * unbox reach capabilities over its formal parameter. Therefore, the approximation - * would be `Cq \union dcs(Ca)` instead. - * If the approximation is known to subcapture the declared result Cr, we pick it for C + * would be `Cq \union dcs(Ta)` instead. + * If the approximation might subcapture the declared result Cr, we pick it for C * otherwise we pick Cr. */ protected override @@ -611,10 +658,10 @@ class CheckCaptures extends Recheck, SymTransformer: val appType = Existential.toCap(super.recheckApplication(tree, qualType, funType, argTypes)) val qualCaptures = qualType.captureSet val argCaptures = - for (arg, argType) <- tree.args.lazyZip(argTypes) yield - if unboxedArgs.remove(arg) // need to ensure the remove happens, that's why argCaptures is computed even if not needed. - then argType.deepCaptureSet - else argType.captureSet + for (argType, formal) <- argTypes.lazyZip(funType.paramInfos) yield + formal match + case AnnotatedType(_, ann) if ann.symbol == defn.UseAnnot => argType.deepCaptureSet + case _ => argType.captureSet appType match case appType @ CapturingType(appType1, refs) if qualType.exists @@ -632,12 +679,9 @@ class CheckCaptures extends Recheck, SymTransformer: case Nil => true /** Handle an application of method `sym` with type `mt` to arguments of types `argTypes`. - * This means: + * This means * - Instantiate result type with actual arguments - * - If call is to a constructor: - * - remember types of arguments corresponding to tracked - * parameters in refinements. - * - add capture set of instantiated class to capture set of result type. + * - if `sym` is a constructor, refine its type with `refineInstanceType` * If all argument types are mutually different trackable capture references, use a BiTypeMap, * since that is more precise. Otherwise use a normal idempotent map, which might lose information * in the case where the result type contains captureset variables that are further @@ -651,70 +695,77 @@ class CheckCaptures extends Recheck, SymTransformer: SubstParamsBiMap(mt, argTypes)(mt.resType) else SubstParamsMap(mt, argTypes)(mt.resType) - - if sym.isConstructor then - val cls = sym.owner.asClass - - /** First half of result pair: - * Refine the type of a constructor call `new C(t_1, ..., t_n)` - * 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 all capture sets of arguments to tracked parameters. - */ - def addParamArgRefinements(core: Type, initCs: CaptureSet): (Type, CaptureSet) = - var refined: Type = core - var allCaptures: CaptureSet = - if core.derivesFromCapability then defn.universalCSImpliedByCapability else initCs - for (getterName, argType) <- mt.paramNames.lazyZip(argTypes) do - val getter = cls.info.member(getterName).suchThat(_.isRefiningParamAccessor).symbol - if !getter.is(Private) && getter.hasTrackedParts then - refined = RefinedType(refined, getterName, argType) - allCaptures ++= argType.captureSet - (refined, allCaptures) - - /** Augment result type of constructor with refinements and captures. - * @param core The result type of the constructor - * @param initCs The initial capture set to add, not yet counting capture sets from arguments - */ - def augmentConstructorType(core: Type, initCs: CaptureSet): Type = core match - case core: MethodType => - // more parameters to follow; augment result type - core.derivedLambdaType(resType = augmentConstructorType(core.resType, initCs)) - case CapturingType(parent, refs) => - // can happen for curried constructors if instantiate of a previous step - // added capture set to result. - augmentConstructorType(parent, initCs ++ refs) - case _ => - val (refined, cs) = addParamArgRefinements(core, initCs) - refined.capturing(cs) - - augmentConstructorType(ownType, capturedVars(cls) ++ capturedVars(sym)) - .showing(i"constr type $mt with $argTypes%, % in $cls = $result", capt) + if sym.isConstructor then refineConstructorInstance(ownType, mt, argTypes, sym) else ownType - end instantiate + /** Refine the type returned from a constructor as follows: + * - remember types of arguments corresponding to tracked parameters in refinements. + * - add capture set of instantiated class and capture set of constructor to capture set of result type. + * Note: This scheme does not distinguish whether a capture is made by the constructor + * only or by a method in the class. Both captures go into the result type. We + * could be more precise by distinguishing the two capture sets. + */ + private def refineConstructorInstance(resType: Type, mt: MethodType, argTypes: List[Type], constr: Symbol)(using Context): Type = + val cls = constr.owner.asClass + + /** First half of result pair: + * Refine the type of a constructor call `new C(t_1, ..., t_n)` + * 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. + */ + def addParamArgRefinements(core: Type, initCs: CaptureSet): (Type, CaptureSet) = + var refined: Type = core + var allCaptures: CaptureSet = + if core.derivesFromCapability then defn.universalCSImpliedByCapability else initCs + for (getterName, argType) <- mt.paramNames.lazyZip(argTypes) do + val getter = cls.info.member(getterName).suchThat(_.isRefiningParamAccessor).symbol + if !getter.is(Private) && getter.hasTrackedParts then + refined = RefinedType(refined, getterName, argType.unboxed) // Yichen you might want to check this + allCaptures ++= argType.captureSet + (refined, allCaptures) + + /** Augment result type of constructor with refinements and captures. + * @param core The result type of the constructor + * @param initCs The initial capture set to add, not yet counting capture sets from arguments + */ + def augmentConstructorType(core: Type, initCs: CaptureSet): Type = core match + case core: MethodType => + // more parameters to follow; augment result type + core.derivedLambdaType(resType = augmentConstructorType(core.resType, initCs)) + case CapturingType(parent, refs) => + // can happen for curried constructors if instantiate of a previous step + // added capture set to result. + augmentConstructorType(parent, initCs ++ refs) + case _ => + val (refined, cs) = addParamArgRefinements(core, initCs) + refined.capturing(cs) + + augmentConstructorType(resType, capturedVars(cls) ++ capturedVars(constr)) + .showing(i"constr type $mt with $argTypes%, % in $constr = $result", capt) + end refineConstructorInstance + + /** Recheck type applications: + * - Map existential captures in result to `cap` + * - include captures of called methods in environment + * - don't allow cap to appear covariantly in type arguments + * - special handling of `contains[A, B]` calls + */ override def recheckTypeApply(tree: TypeApply, pt: Type)(using Context): Type = - val meth = tree.symbol - if ccConfig.useSealed then - val TypeApply(fn, args) = tree - val polyType = atPhase(thisPhase.prev): - fn.tpe.widen.asInstanceOf[TypeLambda] - def isExempt(sym: Symbol) = - sym.isTypeTestOrCast || sym == defn.Compiletime_erasedValue - for case (arg: TypeTree, formal, pname) <- args.lazyZip(polyType.paramRefs).lazyZip((polyType.paramNames)) do - if !isExempt(meth) then - def where = if meth.exists then i" in an argument of $meth" else "" - disallowRootCapabilitiesIn(arg.knownType, NoSymbol, - i"Sealed type variable $pname", "be instantiated to", - i"This is often caused by a local capability$where\nleaking as part of its result.", - tree.srcPos) - try handleCall(meth, tree, () => Existential.toCap(super.recheckTypeApply(tree, pt))) - finally checkContains(tree) + val meth = tree.fun match + case fun @ Select(qual, nme.apply) => qual.symbol.orElse(fun.symbol) + case fun => fun.symbol + disallowCapInTypeArgs(tree.fun, meth, tree.args) + val res = Existential.toCap(super.recheckTypeApply(tree, pt)) + includeCallCaptures(tree.symbol, res, tree.srcPos) + checkContains(tree) + res end recheckTypeApply /** Faced with a tree of form `caps.contansImpl[CS, r.type]`, check that `R` is a tracked - * capability and assert that `{r} <:CS`. + * capability and assert that `{r} <: CS`. */ def checkContains(tree: TypeApply)(using Context): Unit = tree match case ContainsImpl(csArg, refArg) => @@ -731,34 +782,34 @@ class CheckCaptures extends Recheck, SymTransformer: override def recheckBlock(tree: Block, pt: Type)(using Context): Type = inNestedLevel(super.recheckBlock(tree, pt)) + /** Recheck Closure node: add the captured vars of the anonymoys function + * to the result type. See also `recheckClosureBlock` which rechecks the + * block containing the anonymous function and the Closure node. + */ override def recheckClosure(tree: Closure, pt: Type, forceDependent: Boolean)(using Context): Type = val cs = capturedVars(tree.meth.symbol) capt.println(i"typing closure $tree with cvs $cs") super.recheckClosure(tree, pt, forceDependent).capturing(cs) .showing(i"rechecked closure $tree / $pt = $result", capt) + /** Recheck a lambda of the form + * { def $anonfun(...) = ...; closure($anonfun, ...)} + */ override def recheckClosureBlock(mdef: DefDef, expr: Closure, pt: Type)(using Context): Type = - mdef.rhs match - case rhs @ closure(_, _, _) => - // In a curried closure `x => y => e` don't leak capabilities retained by - // the second closure `y => e` into the first one. This is an approximation - // of the CC rule which says that a closure contributes captures to its - // environment only if a let-bound reference to the closure is used. - mdef.rhs.putAttachment(ClosureBodyValue, ()) - case _ => - openClosures = (mdef.symbol, pt) :: openClosures try // Constrain closure's parameters and result from the expected type before // rechecking the body. val res = recheckClosure(expr, pt, forceDependent = true) if !(isEtaExpansion(mdef) && ccConfig.handleEtaExpansionsSpecially) then - // If closure is an eta expanded method reference it's better to not constrain + // Check whether the closure's results conforms to the expected type + // This constrains parameter types of the closure which can give better + // error messages. + // But if the closure is an eta expanded method reference it's better to not constrain // its internals early since that would give error messages in generated code - // which are less intelligible. - // Example is the line `a = x` in neg-custom-args/captures/vars.scala. - // For all other closures, early constraints are preferred since they - // give more localized error messages. + // which are less intelligible. An example is the line `a = x` in + // neg-custom-args/captures/vars.scala. That's why this code is conditioned. + // to apply only to closures that are not eta expansions. val res1 = Existential.toCapDeeply(res) val pt1 = Existential.toCapDeeply(pt) // We need to open existentials here in order not to get vars mixed up in them @@ -771,42 +822,18 @@ class CheckCaptures extends Recheck, SymTransformer: openClosures = openClosures.tail end recheckClosureBlock + /** Elements of a SeqLiteral instantiate a Seq or Array parameter, so they + * should be boxed. + */ override def seqLiteralElemProto(tree: SeqLiteral, pt: Type, declared: Type)(using Context) = super.seqLiteralElemProto(tree, pt, declared).boxed - /** Maps mutable variables to the symbols that capture them (in the - * CheckCaptures sense, i.e. symbol is referred to from a different method - * than the one it is defined in). - */ - private val capturedBy = util.HashMap[Symbol, Symbol]() - - /** Maps anonymous functions appearing as function arguments to - * the function that is called. - */ - private val anonFunCallee = util.HashMap[Symbol, Symbol]() - - /** Populates `capturedBy` and `anonFunCallee`. Called by `checkUnit`. + /** Recheck val and var definitions: + * - disallow cap in the type of mutable vars. + * - for externally visible definitions: check that their inferred type + * does not refine what was known before capture checking. + * - Interpolate contravariant capture set variables in result type. */ - private def collectCapturedMutVars(using Context) = new TreeTraverser: - def traverse(tree: Tree)(using Context) = tree match - case id: Ident => - val sym = id.symbol - if sym.is(Mutable, butNot = Method) && sym.owner.isTerm then - val enclMeth = ctx.owner.enclosingMethod - if sym.enclosingMethod != enclMeth then - capturedBy(sym) = enclMeth - case Apply(fn, args) => - for case closureDef(mdef) <- args do - anonFunCallee(mdef.symbol) = fn.symbol - traverseChildren(tree) - case Inlined(_, bindings, expansion) => - traverse(bindings) - traverse(expansion) - case mdef: DefDef => - if !mdef.symbol.isInlineMethod then traverseChildren(tree) - case _ => - traverseChildren(tree) - override def recheckValDef(tree: ValDef, sym: Symbol)(using Context): Type = try if sym.is(Module) then sym.info // Modules are checked by checking the module class @@ -821,7 +848,7 @@ class CheckCaptures extends Recheck, SymTransformer: case _ => "" s"an anonymous function$location" else encl.show - (NoSymbol, i"\nNote that $sym does not count as local since it is captured by $enclStr") + (NoSymbol, i"\n\nNote that $sym does not count as local since it is captured by $enclStr") case _ => (sym, "") disallowRootCapabilitiesIn( @@ -835,13 +862,34 @@ class CheckCaptures extends Recheck, SymTransformer: // function is compiled since we do not propagate expected types into blocks. interpolateVarsIn(tree.tpt) + /** Recheck method definitions: + * - check body in a nested environment that tracks uses, in a nested level, + * and in a nested context that knows abaout Contains parameters so that we + * can assume they are true. + * - for externally visible definitions: check that their inferred type + * does not refine what was known before capture checking. + * - Interpolate contravariant capture set variables in result type unless + * def is anonymous. + */ override def recheckDefDef(tree: DefDef, sym: Symbol)(using Context): Type = if Synthetics.isExcluded(sym) then sym.info else + // Under the deferredReaches setting: If rhs ends in a closure or + // anonymous class, the corresponding symbol + def nestedClosure(rhs: Tree)(using Context): Symbol = + if !ccConfig.deferredReaches then NoSymbol + else rhs match + case Closure(_, meth, _) => meth.symbol + case Apply(fn, _) if fn.symbol.isConstructor && fn.symbol.owner.isAnonymousClass => fn.symbol.owner + case Block(_, expr) => nestedClosure(expr) + case Inlined(_, _, expansion) => nestedClosure(expansion) + case Typed(expr, _) => nestedClosure(expr) + case _ => NoSymbol + val saved = curEnv val localSet = capturedVars(sym) if !localSet.isAlwaysEmpty then - curEnv = Env(sym, EnvKind.Regular, localSet, curEnv) + curEnv = Env(sym, EnvKind.Regular, localSet, curEnv, nestedClosure(tree.rhs)) // ctx with AssumedContains entries for each Contains parameter val bodyCtx = @@ -852,7 +900,7 @@ class CheckCaptures extends Recheck, SymTransformer: if ac.isEmpty then ctx else ctx.withProperty(CaptureSet.AssumedContains, Some(ac)) - inNestedLevel: // TODO: needed here? + inNestedLevel: // TODO: nestedLevel needed here? try checkInferredResult(super.recheckDefDef(tree, sym)(using bodyCtx), tree) finally if !sym.isAnonymousFunction then @@ -872,13 +920,14 @@ class CheckCaptures extends Recheck, SymTransformer: val sym = tree.symbol def canUseInferred = // If canUseInferred is false, all capturing types in the type of `sym` need to be given explicitly - sym.is(Private) // private symbols can always have inferred types - || sym.name.is(DefaultGetterName) // default getters are exempted since otherwise it would be + sym.isLocalToCompilationUnit // Symbols that can't be seen outside the compilation unit can always have inferred types + || sym.privateWithin == defn.EmptyPackageClass + // We make an exception for private symbols in a toplevel file in the empty package + // these could theoretically be accessed from other files in the empty package, but + // it would be too annoying to require explicit types. + || sym.name.is(DefaultGetterName) // Default getters are exempted since otherwise it would be // too annoying. This is a hole since a defualt getter's result type // might leak into a type variable. - || // non-local symbols cannot have inferred types since external capture types are not inferred - sym.isLocalToCompilationUnit // local symbols still need explicit types if - && !sym.owner.is(Trait) // they are defined in a trait, since we do OverridingPairs checking before capture inference def addenda(expected: Type) = new Addenda: override def toAdd(using Context) = @@ -895,23 +944,60 @@ class CheckCaptures extends Recheck, SymTransformer: case tpt: InferredTypeTree if !canUseInferred => val expected = tpt.tpe.dropAllRetains todoAtPostCheck += (() => checkConformsExpr(tp, expected, tree.rhs, addenda(expected))) + // The check that inferred <: expected is done after recheck so that it + // does not interfere with normal rechecking by constraining capture set variables. case _ => tp end checkInferredResult - /** Class-specific capture set relations: + /** The set of symbols that were rechecked via a completer */ + private val completed = new mutable.HashSet[Symbol] + + /** The normal rechecking if `sym` was already completed before */ + override def skipRecheck(sym: Symbol)(using Context): Boolean = + completed.contains(sym) + + /** Check a ValDef or DefDef as an action performed in a completer. Since + * these checks can appear out of order, we need to first create the correct + * environment for checking the definition. + */ + def completeDef(tree: ValOrDefDef, sym: Symbol)(using Context): Type = + val saved = curEnv + try + // Setup environment to reflect the new owner. + val envForOwner: Map[Symbol, Env] = curEnv.outersIterator + .takeWhile(e => !capturedVars(e.owner).isAlwaysEmpty) // no refs can leak beyond this point + .map(e => (e.owner, e)) + .toMap + def restoreEnvFor(sym: Symbol): Env = + val localSet = capturedVars(sym) + if localSet.isAlwaysEmpty then rootEnv + else envForOwner.get(sym) match + case Some(e) => e + case None => Env(sym, EnvKind.Regular, localSet, restoreEnvFor(sym.owner)) + curEnv = restoreEnvFor(sym.owner) + capt.println(i"Complete $sym in ${curEnv.outersIterator.toList.map(_.owner)}") + try recheckDef(tree, sym) + finally completed += sym + finally + curEnv = saved + + /** Recheck classDef by enforcing the following class-specific capture set relations: * 1. The capture set of a class includes the capture sets of its parents. * 2. The capture set of the self type of a class includes the capture set of the class. * 3. The capture set of the self type of a class includes the capture set of every class parameter, - * unless the parameter is marked @constructorOnly. + * unless the parameter is marked @constructorOnly or @untrackedCaptures. * 4. If the class extends a pure base class, the capture set of the self type must be empty. + * Also, check that trait parents represented as applied types don't have cap in their + * type arguments. Other generic parents are represented as TypeApplys, where the same check + * is already done in the TypeApply. */ override def recheckClassDef(tree: TypeDef, impl: Template, cls: ClassSymbol)(using Context): Type = - val saved = curEnv val localSet = capturedVars(cls) for parent <- impl.parents do // (1) checkSubset(capturedVars(parent.tpe.classSymbol), localSet, parent.srcPos, i"\nof the references allowed to be captured by $cls") + val saved = curEnv if !localSet.isAlwaysEmpty then curEnv = Env(cls, EnvKind.Regular, localSet, curEnv) try @@ -922,15 +1008,20 @@ class CheckCaptures extends Recheck, SymTransformer: && !param.hasAnnotation(defn.UntrackedCapturesAnnot) then checkSubset(param.termRef.captureSet, thisSet, param.srcPos) // (3) for pureBase <- cls.pureBaseClass do // (4) - def selfType = impl.body + def selfTypeTree = impl.body .collect: case TypeDef(tpnme.SELF, rhs) => rhs .headOption - .getOrElse(tree) - .orElse(tree) + .getOrElse(tree) // Use class tree if self type tree is missing ... + .orElse(tree) // ... or empty. checkSubset(thisSet, CaptureSet.empty.withDescription(i"of pure base class $pureBase"), - selfType.srcPos, cs1description = " captured by this self type") + selfTypeTree.srcPos, cs1description = " captured by this self type") + for case tpt: TypeTree <- impl.parents do + tpt.tpe match + case AppliedType(fn, args) => + disallowCapInTypeArgs(tpt, fn.typeSymbol, args.map(TypeTree(_))) + case _ => inNestedLevelUnless(cls.is(Module)): super.recheckClassDef(tree, impl, cls) finally @@ -950,12 +1041,15 @@ class CheckCaptures extends Recheck, SymTransformer: case _ => super.recheckTyped(tree) + /** Under the sealed policy and with saferExceptions, disallow cap in the + * result type of a try + */ override def recheckTry(tree: Try, pt: Type)(using Context): Type = val tp = super.recheckTry(tree, pt) if ccConfig.useSealed && Feature.enabled(Feature.saferExceptions) then disallowRootCapabilitiesIn(tp, ctx.owner, - "result of `try`", "have type", - "This is often caused by a locally generated exception capability leaking as part of its result.", + "The result of `try`", "have type", + "\nThis is often caused by a locally generated exception capability leaking as part of its result.", tree.srcPos) tp @@ -975,18 +1069,18 @@ class CheckCaptures extends Recheck, SymTransformer: recheckFinish(result, arg, pt) */ - /** If expected type `pt` is boxed and the tree is a function or a reference, - * don't propagate free variables. - * Otherwise, if the result type is boxed, simulate an unboxing by - * adding all references in the boxed capture set to the current environment. + /** The main recheck method does some box adapation for all nodes: + * - If expected type `pt` is boxed and the tree is a lambda or a reference, + * don't propagate free variables. + * - If the expected type is not boxed but the result type is boxed, + * simulate an unboxing by adding all references in the boxed capture set + * of the result type to the current environment. */ override def recheck(tree: Tree, pt: Type = WildcardType)(using Context): Type = val saved = curEnv tree match case _: RefTree | closureDef(_) if pt.isBoxedCapturing => curEnv = Env(curEnv.owner, EnvKind.Boxed, CaptureSet.Var(curEnv.owner, level = currentLevel), curEnv) - case _ if tree.hasAttachment(ClosureBodyValue) => - curEnv = Env(curEnv.owner, EnvKind.ClosureResult, CaptureSet.Var(curEnv.owner, level = currentLevel), curEnv) case _ => val res = try @@ -995,44 +1089,14 @@ class CheckCaptures extends Recheck, SymTransformer: else trace.force(i"rechecking $tree with pt = $pt", recheckr, show = true): super.recheck(tree, pt) - catch case ex: NoCommonRoot => - report.error(ex.getMessage.nn) - tree.tpe finally curEnv = saved - if tree.isTerm then - if !ccConfig.useExistentials then - checkReachCapsIsolated(res.widen, tree.srcPos) - if !pt.isBoxedCapturing && pt != LhsProto then - markFree(res.boxedCaptureSet, tree.srcPos) + if tree.isTerm && !pt.isBoxedCapturing && pt != LhsProto then + markFree(res.boxedCaptureSet, tree.srcPos) res + /** Under the old unsealed policy: check that cap is ot unboxed */ override def recheckFinish(tpe: Type, tree: Tree, pt: Type)(using Context): Type = - def needsUniversalCheck = tree match - case _: RefTree | _: Apply | _: TypeApply => tree.symbol.unboxesResult - case _: Try => true - case _ => false - - object checkNotUniversal extends TypeTraverser: - def traverse(tp: Type) = - tp.dealias match - case wtp @ CapturingType(parent, refs) => - if variance > 0 then - refs.disallowRootCapability: () => - def part = if wtp eq tpe.widen then "" else i" in its part $wtp" - report.error( - em"""The expression's type ${tpe.widen} is not allowed to capture the root capability `cap`$part. - |This usually means that a capability persists longer than its allowed lifetime.""", - tree.srcPos) - if !wtp.isBoxed then traverse(parent) - case tp => - traverseChildren(tp) - - if !ccConfig.useSealed - && !tpe.hasAnnotation(defn.UncheckedCapturesAnnot) - && needsUniversalCheck - && tpe.widen.isValueType - then - checkNotUniversal.traverse(tpe.widen) + checkNotUniversalInUnboxedResult(tpe, tree) super.recheckFinish(tpe, tree, pt) end recheckFinish @@ -1060,6 +1124,10 @@ class CheckCaptures extends Recheck, SymTransformer: | |Note that ${msg.toString}""" + /** Addendas for error messages that show where we have under-approximated by + * mapping a a capture ref in contravariant position to the empty set because + * the original result type of the map was not itself a capture ref. + */ private def addApproxAddenda(using Context) = new TypeAccumulator[Addenda]: def apply(add: Addenda, t: Type) = t match @@ -1161,22 +1229,12 @@ class CheckCaptures extends Recheck, SymTransformer: (erefs /: erefs.elems): (erefs, eref) => eref match case eref: ThisType if isPureContext(ctx.owner, eref.cls) => - - def pathRoot(aref: Type): Type = aref match - case aref: NamedType if aref.symbol.owner.isClass => pathRoot(aref.prefix) - case _ => aref - - def isOuterRef(aref: Type): Boolean = pathRoot(aref) match - case aref: NamedType => eref.cls.isProperlyContainedIn(aref.symbol.owner) - case aref: ThisType => eref.cls.isProperlyContainedIn(aref.cls) - case _ => false - - val outerRefs = arefs.filter(isOuterRef) - + val outerRefs = arefs.filter: aref => + eref.cls.isProperlyContainedIn(aref.pathOwner) // Include implicitly added outer references in the capture set of the class of `eref`. for outerRef <- outerRefs.elems do if !erefs.elems.contains(outerRef) - && !pathRoot(outerRef).isInstanceOf[ThisType] + && !outerRef.pathRoot.isInstanceOf[ThisType] // we don't need to add outer ThisTypes as these are anyway added as path // prefixes at the use site. And this exemption is required since capture sets // of non-local classes are always empty, so we can't add an outer this to them. @@ -1250,6 +1308,7 @@ class CheckCaptures extends Recheck, SymTransformer: def adaptStr = i"adapting $actual ${if covariant then "~~>" else "<~~"} $expected" + // Get existentials and wildcards out of the way actual match case actual @ Existential(_, actualUnpacked) => return Existential.derivedExistentialType(actual): @@ -1283,11 +1342,10 @@ class CheckCaptures extends Recheck, SymTransformer: else if !leaked.subCaptures(cs, frozen = false).isOK then report.error( - em"""$expected cannot be box-converted to $actual + em"""$expected cannot be box-converted to ${actual.capturing(leaked)} |since the additional capture set $leaked resulted from box conversion is not allowed in $actual""", pos) cs - // Compute the adapted type def adaptedType(resultBoxed: Boolean) = if (adaptedShape eq actualShape) && leaked.isAlwaysEmpty && actualIsBoxed == resultBoxed then actual @@ -1296,35 +1354,43 @@ class CheckCaptures extends Recheck, SymTransformer: .forceBoxStatus(resultBoxed) if needsAdaptation then - val criticalSet = // the set which is not allowed to have `cap` - if covariant then captures // can't box with `cap` - else expected.captureSet // can't unbox with `cap` + val criticalSet = // the set with which we box or unbox + if covariant then captures // covariant: we box with captures of actual type plus captures leaked by inner adapation + else expected.captureSet // contravarant: we unbox with captures of epected type def msg = em"""$actual cannot be box-converted to $expected |since at least one of their capture sets contains the root capability `cap`""" def allowUniversalInBoxed = ccConfig.useSealed || expected.hasAnnotation(defn.UncheckedCapturesAnnot) || actual.widen.hasAnnotation(defn.UncheckedCapturesAnnot) - if criticalSet.isUnboxable && expected.isValueType && !allowUniversalInBoxed then - // We can't box/unbox the universal capability. Leave `actual` as it is - // so we get an error in checkConforms. Add the error message generated - // from boxing as an addendum. This tends to give better error - // messages than disallowing the root capability in `criticalSet`. - if boxErrors != null then boxErrors += msg - if ctx.settings.YccDebug.value then - println(i"cannot box/unbox $actual vs $expected") - actual - else - if !allowUniversalInBoxed then - // Disallow future addition of `cap` to `criticalSet`. - criticalSet.disallowRootCapability: () => - report.error(msg, pos) - if !insertBox then // unboxing - //debugShowEnvs() - markFree(criticalSet, pos) - adaptedType(!actualIsBoxed) - else - adaptedType(actualIsBoxed) + if !allowUniversalInBoxed then + if criticalSet.isUnboxable && expected.isValueType then + // We can't box/unbox the universal capability. Leave `actual` as it is + // so we get an error in checkConforms. Add the error message generated + // from boxing as an addendum. This tends to give better error + // messages than disallowing the root capability in `criticalSet`. + if boxErrors != null then boxErrors += msg + if ctx.settings.YccDebug.value then + println(i"cannot box/unbox $actual vs $expected") + return actual + // Disallow future addition of `cap` to `criticalSet`. + criticalSet.disallowRootCapability: () => + report.error(msg, pos) + + if !insertBox then // we are unboxing + //debugShowEnvs() + markFree(criticalSet, pos) + end if + + // Compute the adapted type. + // The result is boxed if actual is boxed and we don't need to adapt, + // or if actual is unboxed and we do need to adapt. + val resultIsBoxed = actualIsBoxed != needsAdaptation + if (adaptedShape eq actualShape) && leaked.isAlwaysEmpty && actualIsBoxed == resultIsBoxed + then actual + else adaptedShape + .capturing(if alwaysConst then CaptureSet(captures.elems) else captures) + .forceBoxStatus(resultIsBoxed) } end recur @@ -1333,6 +1399,12 @@ class CheckCaptures extends Recheck, SymTransformer: /** If actual is a tracked CaptureRef `a` and widened is a capturing type T^C, * improve `T^C` to `T^{a}`, following the VAR rule of CC. + * TODO: We probably should do this also for other top-level occurrences of captures + * E.g. + * class Foo { def a: C^{io}; val def: C^{async} } + * val foo: Foo^{io, async} + * Then + * foo: Foo { def a: C^{foo}; def b: C^{foo} }^{foo} */ private def improveCaptures(widened: Type, actual: Type)(using Context): Type = actual match case ref: CaptureRef if ref.isTracked => @@ -1343,9 +1415,10 @@ class CheckCaptures extends Recheck, SymTransformer: case _ => widened case _ => widened - /** Adapt `actual` type to `expected` type by inserting boxing and unboxing conversions - * - * @param alwaysConst always make capture set variables constant after adaptation + /** Adapt `actual` type to `expected` type. This involves: + * - narrow toplevel captures of `x`'s underlying type to `{x}` according to CC's VAR rule + * - narrow nested captures of `x`'s underlying type to `{x*}` + * - do box adaptation */ def adapt(actual: Type, expected: Type, pos: SrcPos, boxErrors: BoxErrors)(using Context): Type = if expected == LhsProto || expected.isSingleton && actual.isSingleton then @@ -1359,6 +1432,8 @@ class CheckCaptures extends Recheck, SymTransformer: else adapted.showing(i"adapt boxed $actual vs $expected = $adapted", capt) end adapt +// ---- Unit-level rechecking ------------------------------------------- + /** Check overrides again, taking capture sets into account. * TODO: Can we avoid doing overrides checks twice? * We need to do them here since only at this phase CaptureTypes are relevant @@ -1389,21 +1464,22 @@ class CheckCaptures extends Recheck, SymTransformer: finally curEnv = saved actual1 frozen_<:< expected1 + /** Omit the check if one of {overriding,overridden} was nnot capture checked */ override def needsCheck(overriding: Symbol, overridden: Symbol)(using Context): Boolean = !setup.isPreCC(overriding) && !setup.isPreCC(overridden) override def checkInheritedTraitParameters: Boolean = false - /** Check that overrides don't change the @unbox status of their parameters */ + /** Check that overrides don't change the @use status of their parameters */ override def additionalChecks(member: Symbol, other: Symbol)(using Context): Unit = for (params1, params2) <- member.rawParamss.lazyZip(other.rawParamss) (param1, param2) <- params1.lazyZip(params2) do - if param1.hasAnnotation(defn.UnboxAnnot) != param2.hasAnnotation(defn.UnboxAnnot) then + if param1.hasAnnotation(defn.UseAnnot) != param2.hasAnnotation(defn.UseAnnot) then report.error( OverrideError( - i"has a parameter ${param1.name} with different @unbox status than the corresponding parameter in the overridden definition", + i"has a parameter ${param1.name} with different @use 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 @@ -1416,36 +1492,43 @@ class CheckCaptures extends Recheck, SymTransformer: checkAllOverrides(ctx.owner.asClass, OverridingPairsCheckerCC(_, _, t)) case _ => traverseChildren(t) + end checkOverrides - private val completed = new mutable.HashSet[Symbol] + /** Used for error reporting: + * Maps mutable variables to the symbols that capture them (in the + * CheckCaptures sense, i.e. symbol is referred to from a different method + * than the one it is defined in). + */ + private val capturedBy = util.HashMap[Symbol, Symbol]() - override def skipRecheck(sym: Symbol)(using Context): Boolean = - completed.contains(sym) + /** Used for error reporting: + * Maps anonymous functions appearing as function arguments to + * the function that is called. + */ + private val anonFunCallee = util.HashMap[Symbol, Symbol]() - /** Check a ValDef or DefDef as an action performed in a completer. Since - * these checks can appear out of order, we need to firsty create the correct - * environment for checking the definition. + /** Used for error reporting: + * Populates `capturedBy` and `anonFunCallee`. Called by `checkUnit`. */ - def completeDef(tree: ValOrDefDef, sym: Symbol)(using Context): Type = - val saved = curEnv - try - // Setup environment to reflect the new owner. - val envForOwner: Map[Symbol, Env] = curEnv.outersIterator - .takeWhile(e => !capturedVars(e.owner).isAlwaysEmpty) // no refs can leak beyind this point - .map(e => (e.owner, e)) - .toMap - def restoreEnvFor(sym: Symbol): Env = - val localSet = capturedVars(sym) - if localSet.isAlwaysEmpty then rootEnv - else envForOwner.get(sym) match - case Some(e) => e - case None => Env(sym, EnvKind.Regular, localSet, restoreEnvFor(sym.owner)) - curEnv = restoreEnvFor(sym.owner) - capt.println(i"Complete $sym in ${curEnv.outersIterator.toList.map(_.owner)}") - try recheckDef(tree, sym) - finally completed += sym - finally - curEnv = saved + private def collectCapturedMutVars(using Context) = new TreeTraverser: + def traverse(tree: Tree)(using Context) = tree match + case id: Ident => + val sym = id.symbol + if sym.is(Mutable, butNot = Method) && sym.owner.isTerm then + val enclMeth = ctx.owner.enclosingMethod + if sym.enclosingMethod != enclMeth then + capturedBy(sym) = enclMeth + case Apply(fn, args) => + for case closureDef(mdef) <- args do + anonFunCallee(mdef.symbol) = fn.symbol + traverseChildren(tree) + case Inlined(_, bindings, expansion) => + traverse(bindings) + traverse(expansion) + case mdef: DefDef => + if !mdef.symbol.isInlineMethod then traverseChildren(tree) + case _ => + traverseChildren(tree) private val setup: SetupAPI = thisPhase.prev.asInstanceOf[Setup] @@ -1466,6 +1549,9 @@ class CheckCaptures extends Recheck, SymTransformer: postCheckWF(unit.tpdTree) if ctx.settings.YccDebug.value then show(unit.tpdTree) // this does not print tree, but makes its variables visible for dependency printing + end checkUnit + +// ----- Checks to do after the rechecking traversal -------------------------- /** Check that self types of subclasses conform to self types of super classes. * (See comment below how this is achieved). The check assumes that classes @@ -1593,6 +1679,10 @@ class CheckCaptures extends Recheck, SymTransformer: checker.traverse(tree.knownType) end healTypeParam + /** Under the unsealed policy: Arrays are like vars, check that their element types + * do not contains `cap` (in fact it would work also to check on array creation + * like we do under sealed). + */ def checkArraysAreSealedIn(tp: Type, pos: SrcPos)(using Context): Unit = val check = new TypeTraverser: def traverse(t: Type): Unit = @@ -1602,8 +1692,7 @@ class CheckCaptures extends Recheck, SymTransformer: && !arg.typeSymbol.name.is(WildcardParamName) then CheckCaptures.disallowRootCapabilitiesIn(arg, NoSymbol, - "Array", "have element type", - "Since arrays are mutable, they have to be treated like variables,\nso their element type must be sealed.", + "Array", "have element type", "", pos) traverseChildren(t) case defn.RefinedFunctionOf(rinfo: MethodType) => @@ -1635,7 +1724,7 @@ class CheckCaptures extends Recheck, SymTransformer: checkBounds(normArgs, tl) args.lazyZip(tl.paramNames).foreach(healTypeParam(_, _, fun.symbol)) case _ => - case tree: TypeTree => + case tree: TypeTree if !ccConfig.useSealed => checkArraysAreSealedIn(tree.tpe, tree.srcPos) case _ => end check diff --git a/compiler/src/dotty/tools/dotc/cc/Existential.scala b/compiler/src/dotty/tools/dotc/cc/Existential.scala index 732510789e28..ea979e0b9f7f 100644 --- a/compiler/src/dotty/tools/dotc/cc/Existential.scala +++ b/compiler/src/dotty/tools/dotc/cc/Existential.scala @@ -333,12 +333,11 @@ object Existential: override def toString = "Wrap.inverse" end Wrap - if ccConfig.useExistentials then - val wrapped = apply(Wrap(_)(tp)) - if needsWrap then wrapped else tp - else tp + val wrapped = apply(Wrap(_)(tp)) + if needsWrap then wrapped else tp end mapCap + /** Map `cap` in function results to fresh existentials */ def mapCapInResults(fail: Message => Unit)(using Context): TypeMap = new: def mapFunOrMethod(tp: Type, args: List[Type], res: Type): Type = diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index 7cd12b999618..dfb9ec70bdba 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -13,7 +13,7 @@ import ast.tpd, tpd.* import transform.{PreRecheck, Recheck}, Recheck.* import CaptureSet.{IdentityCaptRefMap, IdempotentCaptRefMap} import Synthetics.isExcluded -import util.Property +import util.{Property, SimpleIdentitySet} import reporting.Message import printing.{Printer, Texts}, Texts.{Text, Str} import collection.mutable @@ -22,9 +22,23 @@ import dotty.tools.dotc.util.NoSourcePosition /** Operations accessed from CheckCaptures */ trait SetupAPI: + + /** The operation to recheck a ValDef or DefDef */ type DefRecheck = (tpd.ValOrDefDef, Symbol) => Context ?=> Type + + /** Setup procedure to run for each compilation unit + * @param tree the typed tree of the unit to check + * @param recheckDef the recheck method to run on completion of symbols with + * inferred (result-) types + */ def setupUnit(tree: Tree, recheckDef: DefRecheck)(using Context): Unit + + /** Symbol is a term member of a class that was not capture checked + * The info of these symbols is made fluid. + */ def isPreCC(sym: Symbol)(using Context): Boolean + + /** Check to do after the capture checking traversal */ def postCheck()(using Context): Unit object Setup: @@ -42,7 +56,9 @@ object Setup: end Setup import Setup.* -/** A tree traverser that prepares a compilation unit to be capture checked. +/** Phase that sets up everthing for capture checking. + * + * A tree traverser that prepares a compilation unit to be capture checked. * It does the following: * - For every inferred type, drop any retains annotations, * add capture sets to all its parts, add refinements to class types and function types. @@ -64,16 +80,28 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: override def isRunnable(using Context) = super.isRunnable && Feature.ccEnabledSomewhere + /** A set containing symbols whose denotation is in train of being updated + * Used to suppress transforming the denotations of these symbols. + */ private val toBeUpdated = new mutable.HashSet[Symbol] + /** Drops `private` from the flags of `symd` provided it is + * a parameter accessor that's not `constructorOnly` or `uncheckedCaptured` + * and that contains at least one @retains in co- or in-variant position. + * The @retains mught be implicit for a type deriving from `Capability`. + */ private def newFlagsFor(symd: SymDenotation)(using Context): FlagSet = object containsCovarRetains extends TypeAccumulator[Boolean]: + val seen = util.HashSet[Symbol]() def apply(x: Boolean, tp: Type): Boolean = if x then true else if tp.derivesFromCapability && variance >= 0 then true else tp match case AnnotatedType(_, ann) if ann.symbol.isRetains && variance >= 0 => true + case t: TypeRef if t.symbol.isAbstractOrParamType && !seen.contains(t.symbol) => + seen += t.symbol + apply(x, t.info.bounds.hi) case _ => foldOver(x, tp) def apply(tp: Type): Boolean = apply(false, tp) @@ -85,30 +113,17 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: else symd.flags end newFlagsFor + /** Symbol is a term member of a class that was not capture checked + * The info of these symbols is made fluid. + */ def isPreCC(sym: Symbol)(using Context): Boolean = sym.isTerm && sym.maybeOwner.isClass && !sym.is(Module) && !sym.owner.is(CaptureChecked) && !defn.isFunctionSymbol(sym.owner) - private def fluidify(using Context) = new TypeMap with IdempotentCaptRefMap: - def apply(t: Type): Type = t match - case t: MethodType => - mapOver(t) - case t: TypeLambda => - t.derivedLambdaType(resType = this(t.resType)) - case CapturingType(_, _) => - t - case _ => - val t1 = t match - case t @ defn.RefinedFunctionOf(rinfo: MethodType) => - t.derivedRefinedType(t.parent, t.refinedName, this(rinfo)) - case _ => - mapOver(t) - if variance > 0 then t1 - else decorate(t1, addedSet = Function.const(CaptureSet.Fluid)) - - /** - Reset `private` flags of parameter accessors so that we can refine them + /** The symbol transformer of this phase. + * - Resets `private` flags of parameter accessors so that we can refine them * in Setup if they have non-empty capture sets. * - Special handling of some symbols defined for case classes. * Enabled only until recheck is finished, and provided some compilation unit @@ -118,7 +133,8 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: if !pastRecheck && Feature.ccEnabledSomewhere then val sym = symd.symbol def mappedInfo = - if toBeUpdated.contains(sym) then symd.info + if toBeUpdated.contains(sym) + then symd.info // don't transform symbols that will anyway be updated else transformExplicitType(symd.info) if Synthetics.needsTransform(symd) then Synthetics.transform(symd, mappedInfo) @@ -167,7 +183,8 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: case tp: MethodOrPoly => tp // don't box results of methods outside refinements case _ => recur(tp) - /** Perform the following transformation steps everywhere in a type: + /** Transform the type of an InferredTypeTree by performing the following transformation + * steps everywhere in the type: * 1. Drop retains annotations * 2. Turn plain function types into dependent function types, so that * we can refer to their parameters in capture sets. Currently this is @@ -186,8 +203,8 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: override def toString = "map inferred" /** Refine a possibly applied class type C where the class has tracked parameters - * x_1: T_1, ..., x_n: T_n to C { val x_1: CV_1 T_1, ..., val x_n: CV_n T_n } - * where CV_1, ..., CV_n are fresh capture sets. + * x_1: T_1, ..., x_n: T_n to C { val x_1: T_1^{CV_1}, ..., val x_n: T_n^{CV_n} } + * where CV_1, ..., CV_n are fresh capture set variables. */ def addCaptureRefinements(tp: Type): Type = tp match case _: TypeRef | _: AppliedType if refine && tp.typeParams.isEmpty => @@ -271,14 +288,22 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: try val tp1 = mapInferred(refine = true)(tp) val tp2 = Existential.mapCapInResults(_ => assert(false))(tp1) - if tp2 ne tp then capt.println(i"expanded implicit in ${ctx.owner}: $tp --> $tp1 --> $tp2") + if tp2 ne tp then capt.println(i"expanded inferred in ${ctx.owner}: $tp --> $tp1 --> $tp2") tp2 catch case ex: AssertionError => println(i"error while mapping inferred $tp") throw ex end transformInferredType - private def transformExplicitType(tp: Type, tptToCheck: Option[Tree] = None)(using Context): Type = + /** Transform an explicitly given type by performing the following transformation + * steps everywhere in the type: + * 1. Expand throws aliases to contextual function type with CanThrow parameters + * 2. Map types with retains annotations to CapturingTypes + * 3. Add universal capture sets to types deriving from Capability + * 4. Map `cap` in function result types to existentially bound variables. + * 5. Schedule deferred well-formed tests for types with retains annotations. + */ + private def transformExplicitType(tp: Type, tptToCheck: Tree = EmptyTree)(using Context): Type = val toCapturing = new DeepTypeMap: override def toString = "expand aliases" @@ -302,11 +327,15 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: case _ => res ) val fntpe = defn.PolyFunctionOf(mt) - if !encl.isEmpty && resDecomposed.isEmpty then + if !encl.isEmpty then val cs = CaptureSet(encl.map(_.paramRefs.head)*) CapturingType(fntpe, cs, boxed = false) else fntpe + /** If C derives from Capability and we have a C^cs in source, we leave it as is + * instead of expanding it to C^{cap}^cs. We do this by stripping capability-generated + * universal capture sets from the parent of a CapturingType. + */ def stripImpliedCaptureSet(tp: Type): Type = tp match case tp @ CapturingType(parent, refs) if (refs eq defn.universalCSImpliedByCapability) && !tp.isBoxedCapturing => @@ -322,12 +351,12 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: val parent1 = this(parent) if ann.symbol.isRetains then val parent2 = stripImpliedCaptureSet(parent1) - for tpt <- tptToCheck do - checkWellformedLater(parent2, ann.tree, tpt) + if !tptToCheck.isEmpty then + checkWellformedLater(parent2, ann.tree, tptToCheck) try CapturingType(parent2, ann.tree.toCaptureSet) catch case ex: IllegalCaptureRef => - report.error(em"Illegal capture reference: ${ex.getMessage.nn}", tptToCheck.fold(NoSourcePosition)(_.srcPos)) + report.error(em"Illegal capture reference: ${ex.getMessage.nn}", tptToCheck.srcPos) parent2 else t.derivedAnnotatedType(parent1, ann) @@ -341,7 +370,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: end toCapturing def fail(msg: Message) = - for tree <- tptToCheck do report.error(msg, tree.srcPos) + if !tptToCheck.isEmpty then report.error(msg, tptToCheck.srcPos) val tp1 = toCapturing(tp) val tp2 = Existential.mapCapInResults(fail)(tp1) @@ -349,13 +378,13 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: tp2 end transformExplicitType - /** Transform type of type tree, and remember the transformed type as the type the tree */ + /** Transform type of tree, and remember the transformed type as the type the tree */ private def transformTT(tree: TypeTree, boxed: Boolean)(using Context): Unit = if !tree.hasRememberedType then val transformed = if tree.isInferred then transformInferredType(tree.tpe) - else transformExplicitType(tree.tpe, tptToCheck = Some(tree)) + else transformExplicitType(tree.tpe, tptToCheck = tree) tree.rememberType(if boxed then box(transformed) else transformed) /** Substitute parameter symbols in `from` to paramRefs in corresponding @@ -401,27 +430,32 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: toBeUpdated += sym sym.updateInfo(thisPhase, info, newFlagsFor(sym)) toBeUpdated -= sym - sym.namedType match - case ref: CaptureRef if ref.isTrackableRef => ref.invalidateCaches() // TODO: needed? - case _ => + /** The info of `sym` at the CheckCaptures phase */ extension (sym: Symbol) def nextInfo(using Context): Type = atPhase(thisPhase.next)(sym.info) + /** A traverser that adds knownTypes and updates symbol infos */ def setupTraverser(recheckDef: DefRecheck) = new TreeTraverserWithPreciseImportContexts: + /** Transform the type of a val or var or the result type of a def */ def transformResultType(tpt: TypeTree, sym: Symbol)(using Context): Unit = + // First step: Transform the type and record it as knownType of tpt. try transformTT(tpt, boxed = sym.is(Mutable, butNot = Method) && !ccConfig.useSealed && !sym.hasAnnotation(defn.UncheckedCapturesAnnot), - // types of mutable variables are boxed in pre 3.3 code + // Under the sealed policy, we disallow root capabilities in the type of mutable + // variables, no need to box them here. ) catch case ex: IllegalCaptureRef => capt.println(i"fail while transforming result type $tpt of $sym") throw ex + + // Second step: Add descriptions for all capture set variables created in + // step one stating that they belong to `sym`. val addDescription = new TypeTraverser: def traverse(tp: Type) = tp match case tp @ CapturingType(parent, refs) => @@ -431,6 +465,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: case _ => traverseChildren(tp) addDescription.traverse(tpt.knownType) + end transformResultType def traverse(tree: Tree)(using Context): Unit = tree match @@ -445,7 +480,6 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: paramss.foreach(traverse) transformResultType(tpt, meth) traverse(tree.rhs) - //println(i"TYPE of ${tree.symbol.showLocated} = ${tpt.knownType}") case tree @ ValDef(_, tpt: TypeTree, _) => val sym = tree.symbol @@ -453,7 +487,6 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: val defCtx = if sym.isOneOf(TermParamOrAccessor) then ctx else ctx.withOwner(sym) inContext(defCtx): transformResultType(tpt, sym) - capt.println(i"mapped $tree = ${tpt.knownType}") traverse(tree.rhs) case tree @ TypeApply(fn, args) => @@ -479,32 +512,36 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: case _ => traverseChildren(tree) postProcess(tree) + checkProperUse(tree) end traverse + /** Processing done on node `tree` after its children are traversed */ def postProcess(tree: Tree)(using Context): Unit = tree match case tree: TypeTree => transformTT(tree, boxed = false) case tree: ValOrDefDef => + // Make sure denotation of tree's symbol is correct val sym = tree.symbol - /** The return type of a constructor instantiated with local type and value - * parameters. Constructors have `unit` result type, that's why we can't - * get this type by reading the result type tree, and have to construct it - * explicitly. - */ + // The return type of a constructor instantiated with local type and value + // parameters. Constructor defs have `Unit` as result type tree, that's why + // we can't get this type by reading the result type tree, and have to construct + // it explicitly. def constrReturnType(info: Type, psymss: List[List[Symbol]]): Type = info match case info: MethodOrPoly => constrReturnType(info.instantiate(psymss.head.map(_.namedType)), psymss.tail) case _ => info - /** The local result type, which is the known type of the result type tree, - * with special treatment for constructors. - */ + // The local result type, which is the known type of the result type tree, + // with special treatment for constructors. def localReturnType = if sym.isConstructor then constrReturnType(sym.info, sym.paramSymss) else tree.tpt.knownType + // A test whether parameter signature might change. This returns true if one of + // the parameters has a remembered type. The idea here is that we store a remembered + // type only if the transformed type is different from the original. def paramSignatureChanges = tree.match case tree: DefDef => tree.paramss.nestedExists: @@ -518,7 +555,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: tree.tpt.hasRememberedType && !sym.isConstructor || paramSignatureChanges // Replace an existing symbol info with inferred types where capture sets of - // TypeParamRefs and TermParamRefs put in correspondence by BiTypeMaps with the + // TypeParamRefs and TermParamRefs are put in correspondence by BiTypeMaps with the // capture sets of the types of the method's parameter symbols and result type. def integrateRT( info: Type, // symbol info to replace @@ -535,13 +572,16 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: // substitute `x.f.type`, `x` becomes a `TermParamRef`. But the new method // type is still under initialization and `paramInfos` is still `null`, // so the new `NamedType` will not have a denoation. + def adaptedInfo(psym: Symbol, info: mt.PInfo): mt.PInfo = mt.companion match + case mtc: MethodTypeCompanion => mtc.adaptParamInfo(psym, info).asInstanceOf[mt.PInfo] + case _ => info mt.companion(mt.paramNames)( mt1 => if !paramSignatureChanges && !mt.isParamDependent && prevLambdas.isEmpty then mt.paramInfos else val subst = SubstParams(psyms :: prevPsymss, mt1 :: prevLambdas) - psyms.map(psym => subst(psym.nextInfo).asInstanceOf[mt.PInfo]), + psyms.map(psym => adaptedInfo(psym, subst(psym.nextInfo).asInstanceOf[mt.PInfo])), mt1 => integrateRT(mt.resType, psymss.tail, resType, psyms :: prevPsymss, mt1 :: prevLambdas) ) @@ -552,6 +592,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: if prevLambdas.isEmpty then resType else SubstParams(prevPsymss, prevLambdas)(resType) + // If there's a change in the signature, update the info of `sym` if sym.exists && signatureChanges then val newInfo = Existential.mapCapInResults(report.error(_, tree.srcPos)): @@ -568,9 +609,9 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: // the expected type and only afterwards we recheck the definition newInfo else new LazyType: + // infos of other methods are determined from their definitions, which + // are checked on demand def complete(denot: SymDenotation)(using Context) = - // infos of other methods are determined from their definitions which - // are checked on demand assert(ctx.phase == thisPhase.next, i"$sym") capt.println(i"forcing $sym, printing = ${ctx.mode.is(Mode.Printing)}") //if ctx.mode.is(Mode.Printing) then new Error().printStackTrace() @@ -586,9 +627,11 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: case cls: ClassSymbol => inNestedLevelUnless(cls.is(Module)): val cinfo @ ClassInfo(prefix, _, ps, decls, selfInfo) = cls.classInfo - def innerModule = cls.is(ModuleClass) && !cls.isStatic + + // Compute new self type + def isInnerModule = cls.is(ModuleClass) && !cls.isStatic val selfInfo1 = - if (selfInfo ne NoType) && !innerModule then + if (selfInfo ne NoType) && !isInnerModule then // if selfInfo is explicitly given then use that one, except if // self info applies to non-static modules, these still need to be inferred selfInfo @@ -603,8 +646,12 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: // self types (to which we also add nested module classes), provided they are // neither pure, nor are publicily extensible with an unconstrained self type. CapturingType(cinfo.selfType, CaptureSet.Var(cls, level = currentLevel)) + + // Compute new parent types val ps1 = inContext(ctx.withOwner(cls)): ps.mapConserve(transformExplicitType(_)) + + // Install new types and if it is a module class also update module object if (selfInfo1 ne selfInfo) || (ps1 ne ps) then val newInfo = ClassInfo(prefix, cls, ps1, decls, selfInfo1) updateInfo(cls, newInfo) @@ -618,9 +665,25 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: case _ => case _ => end postProcess + + /** Check that @use annotations only appear on parameters and not on anonymous function parameters */ + def checkProperUse(tree: Tree)(using Context): Unit = tree match + case tree: MemberDef => + def useAllowed(sym: Symbol) = + (sym.is(Param) || sym.is(ParamAccessor)) && !sym.owner.isAnonymousFunction + for ann <- tree.symbol.annotations do + if ann.symbol == defn.UseAnnot && !useAllowed(tree.symbol) then + report.error(i"Only parameters of methods can have @use annotations", tree.srcPos) + case _ => + end checkProperUse end setupTraverser - /** Checks whether an abstract type could be impure. See also: [[needsVariable]]. */ +// --------------- Adding capture set variables ---------------------------------- + + /** Checks whether an abstract type or its bound could be impure. If that's the case, + * the abstract type gets a capture set variable in `decorate`. + * See also: [[needsVariable]]. + */ private def instanceCanBeImpure(tp: Type)(using Context): Boolean = { tp.dealiasKeepAnnots match case CapturingType(_, refs) => @@ -643,7 +706,13 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: false }.showing(i"instance can be impure $tp = $result", capt) - /** Should a capture set variable be added on type `tp`? */ + /** Should a capture set variable be added on type `tp`? + * Notable exceptions are: + * - If type refers to a class which is Pure, or it is Any, it does not need a variable. + * - If type is an abstract or parameter type we decide according to `instanceCanBeImpure` + * - If type is a capturing type that has already a capture set variable or has + * the universal capture set, it does not need a variable. + */ def needsVariable(tp: Type)(using Context): Boolean = { tp.typeParams.isEmpty && tp.match case tp: (TypeRef | AppliedType) => @@ -675,6 +744,61 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: false }.showing(i"can have inferred capture $tp = $result", captDebug) + /** Add a capture set variable or set to `tp` if necessary. + * Dealias `tp` (but keep annotations and opaque types) if doing + * so ends up adding a capture set. + * @param tp the type to add a capture set to + * @param added A function producing the added capture set from a set of initial elements. + */ + def decorate(tp: Type, added: CaptureSet.Refs => CaptureSet)(using Context): Type = + if tp.typeSymbol == defn.FromJavaObjectSymbol then + // For capture checking, we assume Object from Java is the same as Any + tp + else + def maybeAdd(target: Type, fallback: Type) = + if needsVariable(target) then + target match + case CapturingType(_, CaptureSet.Fluid) => + target + case CapturingType(target1, cs1) if cs1.isConst => + CapturingType(target1, added(cs1.elems)) + case _ => + CapturingType(target, added(SimpleIdentitySet.empty)) + else fallback + val dealiased = tp.dealiasKeepAnnotsAndOpaques + if dealiased ne tp then + val transformed = transformInferredType(dealiased) + maybeAdd(transformed, if transformed ne dealiased then transformed else tp) + else maybeAdd(tp, tp) + + /** Add a capture set variable to `tp` if necessary. */ + private def addVar(tp: Type, owner: Symbol)(using Context): Type = + decorate(tp, CaptureSet.Var(owner, _, level = currentLevel)) + + /** A map that adds capture sets at all contra- and invariant positions + * in a type where a capture set would be needed. This is used to make types + * that were not capture checked compatible with types that are capture checked. + * We don't need to add in covariant positions since pure types are + * anyway compatible with capturing types. + */ + private def fluidify(using Context) = new TypeMap with IdempotentCaptRefMap: + def apply(t: Type): Type = t match + case t: MethodType => + mapOver(t) + case t: TypeLambda => + t.derivedLambdaType(resType = this(t.resType)) + case CapturingType(_, _) => + t + case _ => + val t1 = t match + case t @ defn.RefinedFunctionOf(rinfo: MethodType) => + // Just refine the apply method, don't bother with the parent + t.derivedRefinedType(t.parent, t.refinedName, this(rinfo)) + case _ => + mapOver(t) + if variance > 0 then t1 + else decorate(t1, Function.const(CaptureSet.Fluid)) + /** Pull out an embedded capture set from a part of `tp` */ def normalizeCaptures(tp: Type)(using Context): Type = tp match case tp @ RefinedType(parent @ CapturingType(parent1, refs), rname, rinfo) => @@ -706,32 +830,9 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: case _ => tp - /** Add a capture set variable to `tp` if necessary, or maybe pull out - * an embedded capture set variable from a part of `tp`. + /** Run setup on a compilation unit with given `tree`. + * @param recheckDef the function to run for completing a val or def */ - def decorate(tp: Type, addedSet: Type => CaptureSet)(using Context): Type = - if tp.typeSymbol == defn.FromJavaObjectSymbol then - // For capture checking, we assume Object from Java is the same as Any - tp - else - def maybeAdd(target: Type, fallback: Type) = - if needsVariable(target) then CapturingType(target, addedSet(target)) - else fallback - val dealiased = tp.dealiasKeepAnnotsAndOpaques - if dealiased ne tp then - val transformed = transformInferredType(dealiased) - maybeAdd(transformed, if transformed ne dealiased then transformed else tp) - else maybeAdd(tp, tp) - - /** Add a capture set variable to `tp` if necessary, or maybe pull out - * an embedded capture set variable from a part of `tp`. - */ - private def addVar(tp: Type, owner: Symbol)(using Context): Type = - decorate(tp, - addedSet = _.dealias.match - case CapturingType(_, refs) => CaptureSet.Var(owner, refs.elems, level = currentLevel) - case _ => CaptureSet.Var(owner, level = currentLevel)) - def setupUnit(tree: Tree, recheckDef: DefRecheck)(using Context): Unit = setupTraverser(recheckDef).traverse(tree)(using ctx.withPhase(thisPhase)) @@ -746,8 +847,11 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: * This check is performed after capture sets are computed in phase cc. * Note: We need to perform the check on the original annotation rather than its * capture set since the conversion to a capture set already eliminates redundant elements. + * @param parent the parent of the capturing type + * @param ann the original retains annotation + * @param tpt the tree for which an error or warning should be reported */ - private def checkWellformedPost(parent: Type, ann: Tree, tpt: Tree)(using Context): Unit = + private def checkWellformed(parent: Type, ann: Tree, tpt: Tree)(using Context): Unit = capt.println(i"checkWF post $parent ${ann.retainedElems} in $tpt") var retained = ann.retainedElems.toArray for i <- 0 until retained.length do @@ -763,7 +867,8 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: report.warning(em"redundant capture: $dom already accounts for $ref", pos) if ref.captureSetOfInfo.elems.isEmpty && !ref.derivesFrom(defn.Caps_Capability) then - report.error(em"$ref cannot be tracked since its capture set is empty", pos) + val deepStr = if ref.isReach then " deep" else "" + report.error(em"$ref cannot be tracked since its$deepStr capture set is empty", pos) check(parent.captureSet, parent) val others = @@ -775,14 +880,18 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: check(remaining, remaining) end for end for - end checkWellformedPost + end checkWellformed - /** Check well formed at post check time */ + /** Check well formed at post check time. We need to wait until after + * recheck because we find out only then whether capture sets are empty or + * capture references are redundant. + */ private def checkWellformedLater(parent: Type, ann: Tree, tpt: Tree)(using Context): Unit = if !tpt.span.isZeroExtent && enclosingInlineds.isEmpty then todoAtPostCheck += (ctx1 => - checkWellformedPost(parent, ann, tpt)(using ctx1.withOwner(ctx.owner))) + checkWellformed(parent, ann, tpt)(using ctx1.withOwner(ctx.owner))) + /** Run all well formedness tests that were deferred to post check */ def postCheck()(using Context): Unit = for chk <- todoAtPostCheck do chk(ctx) todoAtPostCheck.clear() diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 0195a4ddbf34..2890bdf306be 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -999,9 +999,6 @@ class Definitions { @tu lazy val Caps_Exists: ClassSymbol = requiredClass("scala.caps.Exists") @tu lazy val CapsUnsafeModule: Symbol = requiredModule("scala.caps.unsafe") @tu lazy val Caps_unsafeAssumePure: Symbol = CapsUnsafeModule.requiredMethod("unsafeAssumePure") - @tu lazy val Caps_unsafeBox: Symbol = CapsUnsafeModule.requiredMethod("unsafeBox") - @tu lazy val Caps_unsafeUnbox: Symbol = CapsUnsafeModule.requiredMethod("unsafeUnbox") - @tu lazy val Caps_unsafeBoxFunArg: Symbol = CapsUnsafeModule.requiredMethod("unsafeBoxFunArg") @tu lazy val Caps_ContainsTrait: TypeSymbol = CapsModule.requiredType("Contains") @tu lazy val Caps_containsImpl: TermSymbol = CapsModule.requiredMethod("containsImpl") @@ -1057,12 +1054,12 @@ class Definitions { @tu lazy val ExperimentalAnnot: ClassSymbol = requiredClass("scala.annotation.experimental") @tu lazy val ThrowsAnnot: ClassSymbol = requiredClass("scala.throws") @tu lazy val TransientAnnot: ClassSymbol = requiredClass("scala.transient") - @tu lazy val UnboxAnnot: ClassSymbol = requiredClass("scala.caps.unbox") @tu lazy val UncheckedAnnot: ClassSymbol = requiredClass("scala.unchecked") @tu lazy val UncheckedStableAnnot: ClassSymbol = requiredClass("scala.annotation.unchecked.uncheckedStable") @tu lazy val UncheckedVarianceAnnot: ClassSymbol = requiredClass("scala.annotation.unchecked.uncheckedVariance") @tu lazy val UncheckedCapturesAnnot: ClassSymbol = requiredClass("scala.annotation.unchecked.uncheckedCaptures") @tu lazy val UntrackedCapturesAnnot: ClassSymbol = requiredClass("scala.caps.untrackedCaptures") + @tu lazy val UseAnnot: ClassSymbol = requiredClass("scala.caps.use") @tu lazy val VolatileAnnot: ClassSymbol = requiredClass("scala.volatile") @tu lazy val BeanGetterMetaAnnot: ClassSymbol = requiredClass("scala.annotation.meta.beanGetter") @tu lazy val BeanSetterMetaAnnot: ClassSymbol = requiredClass("scala.annotation.meta.beanSetter") diff --git a/compiler/src/dotty/tools/dotc/core/SymUtils.scala b/compiler/src/dotty/tools/dotc/core/SymUtils.scala index 3a97a0053dbd..1a762737d52f 100644 --- a/compiler/src/dotty/tools/dotc/core/SymUtils.scala +++ b/compiler/src/dotty/tools/dotc/core/SymUtils.scala @@ -271,6 +271,9 @@ class SymUtils: self.owner.info.decl(fieldName).suchThat(!_.is(Method)).symbol } + def paramNamed(name: Name)(using Context): Symbol = + self.rawParamss.nestedFind(_.name == name).getOrElse(NoSymbol) + /** Is this symbol a constant expression final val? * * This is the case if all of the following are true: diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index e497c541166c..95c3b025b3ce 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -38,8 +38,7 @@ import config.Printers.{core, typr, matchTypes} import reporting.{trace, Message} import java.lang.ref.WeakReference import compiletime.uninitialized -import cc.{CapturingType, CaptureRef, CaptureSet, SingletonCaptureRef, isTrackableRef, - derivedCapturingType, isBoxedCapturing, isCaptureChecking, isRetains, isRetainsLike} +import cc.* import CaptureSet.{CompareResult, IdempotentCaptRefMap, IdentityCaptRefMap} import scala.annotation.internal.sharable @@ -863,23 +862,19 @@ object Types extends TypeUtils { } else val isRefinedMethod = rinfo.isInstanceOf[MethodOrPoly] - rinfo match - case CapturingType(_, refs: CaptureSet.RefiningVar) if ccConfig.optimizedRefinements => - pdenot.asSingleDenotation.derivedSingleDenotation(pdenot.symbol, rinfo) + val joint = pdenot.meet( + new JointRefDenotation(NoSymbol, rinfo, Period.allInRun(ctx.runId), pre, isRefinedMethod), + pre, + safeIntersection = ctx.base.pendingMemberSearches.contains(name)) + joint match + case joint: SingleDenotation + if isRefinedMethod + && (rinfo <:< joint.info + || name == nme.apply && defn.isFunctionType(tp.parent)) => + // use `rinfo` to keep the right parameter names for named args. See i8516.scala. + joint.derivedSingleDenotation(joint.symbol, rinfo, pre, isRefinedMethod) case _ => - val joint = pdenot.meet( - new JointRefDenotation(NoSymbol, rinfo, Period.allInRun(ctx.runId), pre, isRefinedMethod), - pre, - safeIntersection = ctx.base.pendingMemberSearches.contains(name)) - joint match - case joint: SingleDenotation - if isRefinedMethod - && (rinfo <:< joint.info - || name == nme.apply && defn.isFunctionType(tp.parent)) => - // use `rinfo` to keep the right parameter names for named args. See i8516.scala. - joint.derivedSingleDenotation(joint.symbol, rinfo, pre, isRefinedMethod) - case _ => - joint + joint } def goApplied(tp: AppliedType, tycon: HKTypeLambda) = @@ -4074,6 +4069,10 @@ object Types extends TypeUtils { range(defn.NothingType, atVariance(1)(apply(tp.underlying))) case CapturingType(_, _) => mapOver(tp) + case ReachCapability(tp1) => + apply(tp1) match + case tp1a: CaptureRef if tp1a.isTrackableRef => tp1a.reach + case _ => defn.captureRoot.termRef case AnnotatedType(parent, ann) if ann.refersToParamOf(thisLambdaType) => val parent1 = mapOver(parent) if ann.symbol.isRetainsLike then @@ -4175,24 +4174,28 @@ object Types extends TypeUtils { * - wrap types of parameters that have an @allowConversions annotation with Into[_] */ def fromSymbols(params: List[Symbol], resultType: Type)(using Context): MethodType = + apply(params.map(_.name.asTermName))( + tl => params.map(p => tl.integrate(params, adaptParamInfo(p))), + tl => tl.integrate(params, resultType)) + + /** Adapt info of parameter symbol to be integhrated into corresponding MethodType + * using the scheme described in `fromSymbols`. + */ + def adaptParamInfo(param: Symbol, pinfo: Type)(using Context): Type = def addAnnotation(tp: Type, cls: ClassSymbol, param: Symbol): Type = tp match case ExprType(resType) => ExprType(addAnnotation(resType, cls, param)) case _ => AnnotatedType(tp, Annotation(cls, param.span)) - - def paramInfo(param: Symbol) = - var paramType = param.info - .annotatedToRepeated - .mapIntoAnnot(defn.IntoAnnot, defn.IntoParamAnnot) - if param.is(Inline) then - paramType = addAnnotation(paramType, defn.InlineParamAnnot, param) - if param.is(Erased) then - paramType = addAnnotation(paramType, defn.ErasedParamAnnot, param) - paramType - - apply(params.map(_.name.asTermName))( - tl => params.map(p => tl.integrate(params, paramInfo(p))), - tl => tl.integrate(params, resultType)) - end fromSymbols + var paramType = pinfo + .annotatedToRepeated + .mapIntoAnnot(defn.IntoAnnot, defn.IntoParamAnnot) + if param.is(Inline) then + paramType = addAnnotation(paramType, defn.InlineParamAnnot, param) + if param.is(Erased) then + paramType = addAnnotation(paramType, defn.ErasedParamAnnot, param) + paramType + + def adaptParamInfo(param: Symbol)(using Context): Type = + adaptParamInfo(param, param.info) def apply(paramNames: List[TermName])(paramInfosExp: MethodType => List[Type], resultTypeExp: MethodType => Type)(using Context): MethodType = checkValid(unique(new CachedMethodType(paramNames)(paramInfosExp, resultTypeExp, self))) diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 3caba59a091f..a399b8173b5d 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -30,7 +30,7 @@ import config.SourceVersion.* import dotty.tools.dotc.util.SourcePosition import dotty.tools.dotc.ast.untpd.{MemberDef, Modifiers, PackageDef, RefTree, Template, TypeDef, ValOrDefDef} -import cc.{CaptureSet, CapturingType, toCaptureSet, IllegalCaptureRef, isRetains, ReachCapability, MaybeCapability} +import cc.* import dotty.tools.dotc.parsing.JavaParsers class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { @@ -285,6 +285,9 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { if !printDebug && appliedText(tp.asInstanceOf[HKLambda].resType).isEmpty => // don't eta contract if the application would be printed specially toText(tycon) + case Existential(boundVar, unpacked) + if !printDebug && !ctx.settings.YccDebug.value && !unpacked.existsPart(_ == boundVar) => + toText(unpacked) case tp: RefinedType if defn.isFunctionType(tp) && !printDebug => toTextMethodAsFunction(tp.refinedInfo, isPure = Feature.pureFunsEnabled && !tp.typeSymbol.name.isImpureFunction, diff --git a/compiler/src/dotty/tools/dotc/transform/ElimByName.scala b/compiler/src/dotty/tools/dotc/transform/ElimByName.scala index eca3928569f1..0bf2dc107ce9 100644 --- a/compiler/src/dotty/tools/dotc/transform/ElimByName.scala +++ b/compiler/src/dotty/tools/dotc/transform/ElimByName.scala @@ -103,6 +103,9 @@ class ElimByName extends MiniPhase, InfoTransformer: Closure(meth, _ => arg.changeOwnerAfter(ctx.owner, meth, thisPhase), targetType = defn.ByNameFunction(argType) + // Note: this will forget any captures on the original by-name type + // But that's not a problem since we treat these closures specially + // anyway during recheck. ).withSpan(arg.span) private def isByNameRef(tree: Tree)(using Context): Boolean = diff --git a/compiler/src/dotty/tools/dotc/transform/Recheck.scala b/compiler/src/dotty/tools/dotc/transform/Recheck.scala index be1d9d8bee54..d3173cef252d 100644 --- a/compiler/src/dotty/tools/dotc/transform/Recheck.scala +++ b/compiler/src/dotty/tools/dotc/transform/Recheck.scala @@ -99,7 +99,7 @@ object Recheck: * - in function and method parameter types * - under annotations */ - def normalizeByName(tp: Type)(using Context): Type = tp.dealias match + def normalizeByName(tp: Type)(using Context): Type = tp.dealiasKeepAnnots match case tp: ExprType => mapExprType(tp) case tp: PolyType => @@ -291,7 +291,7 @@ abstract class Recheck extends Phase, SymTransformer: protected def instantiate(mt: MethodType, argTypes: List[Type], sym: Symbol)(using Context): Type = mt.instantiate(argTypes) - /** A hook to massage the type of an applied method; currently not overridden */ + /** A hook to massage the type of an applied method */ protected def prepareFunction(funtpe: MethodType, meth: Symbol)(using Context): MethodType = funtpe protected def recheckArg(arg: Tree, formal: Type)(using Context): Type = @@ -336,7 +336,7 @@ abstract class Recheck extends Phase, SymTransformer: assert(formals.isEmpty) Nil val argTypes = recheckArgs(tree.args, formals, fntpe.paramRefs) - recheckApplication(tree, qualType, fntpe1, argTypes) + recheckApplication(tree, qualType, fntpe, argTypes) //.showing(i"typed app $tree : $fntpe with ${tree.args}%, % : $argTypes%, % = $result") case tp => assert(false, i"unexpected type of ${tree.fun}: $tp") @@ -387,6 +387,10 @@ abstract class Recheck extends Phase, SymTransformer: def recheckClosure(tree: Closure, pt: Type, forceDependent: Boolean = false)(using Context): Type = if tree.tpt.isEmpty then tree.meth.tpe.widen.toFunctionType(tree.meth.symbol.is(JavaDefined), alwaysDependent = forceDependent) + else if defn.isByNameFunction(tree.tpt.tpe) then + val mt @ MethodType(Nil) = tree.meth.tpe.widen: @unchecked + val cmt = ContextualMethodType(Nil, Nil, mt.resultType) + cmt.toFunctionType(alwaysDependent = forceDependent) else recheck(tree.tpt) diff --git a/docs/_docs/internals/cc/use-design.md b/docs/_docs/internals/cc/use-design.md new file mode 100644 index 000000000000..4732f45a84ab --- /dev/null +++ b/docs/_docs/internals/cc/use-design.md @@ -0,0 +1,71 @@ + +Possible design: + + 1. Have @use annotation on type parameters and value parameters of regular methods + (not anonymous functions). + 2. In markFree, keep track whether a capture set variable or reach capability + is used directly in the method where it is defined, or in a nested context + (either unbound nested closure or unbound anonymous class). + 3. Disallow charging a reach capability `xs*` to the environment of the method where + `xs` is a parameter unless `xs` is declared `@use`. + 4. Analogously, disallow charging a capture set variable `C^` to the environment of the method where `C^` is a parameter unless `C^` is declared `@use`. + 5. When passing an argument to a `@use`d term parameter, charge the `dcs` of the argument type to the environments via markFree. + 6. When instantiating a `@use`d type parameter, charge the capture set of the argument + to the environments via markFree. + +It follows that we cannot refer to methods with @use term parameters as values. Indeed, +their eta expansion would produce an anonymous function that includes a reach capability of +its parameter in its use set, violating (3). + +Example: + +```scala + def runOps(@use ops: List[() => Unit]): Unit = ops.foreach(_()) +``` +Then `runOps` expands to +```scala +(xs: List[() => Unit]) => runOps(xs) +``` +Note that `xs` does not carry a `@use` since this is disallowed by (1) for anonymous functions. By (5), we charge the deep capture set of `xs`, which is `xs*` to the environment. By (3), this is actually disallowed. + +Now, if we express this with explicit capture set parameters we get: +```scala + def runOpsPoly[@use C^](ops: List[() ->{C^} Unit]): Unit = ops.foreach[C^](_()) +``` +Then `runOpsPoly` expands to `runOpsPoly[cs]` for some inferred capture set `cs`. And this expands to: +```scala +(xs: List[() ->{cs} Unit]) => runOpsPoly[cs](xs) +``` +Since `cs` is passed to the `@use` parameter of `runOpsPoly` it is charged +to the environment of the function body, so the type of the previous expression is +```scala +List[() ->{cs} Unit]) ->{cs} Unit +``` + +We can also use explicit capture set parameters to eta expand the first `runOps` manually: + +```scala +[C^] => (xs: List[() ->{C^} Unit]) => runOps(xs) + : [C^] -> List[() ->{C^} Unit] ->[C^] Unit +``` +Except that this currently runs afoul of the implementation restriction that polymorphic functions cannot wrap capturing functions. But that's a restriction we need to lift anyway. + +## `@use` inference + + - `@use` is implied for a term parameter `x` of a method if `x`'s type contains a boxed cap and `x` or `x*` is not referred to in the result type of the method. + + - `@use` is implied for a capture set parameter `C` of a method if `C` is not referred to in the result type of the method. + +If `@use` is implied, one can override to no use by giving an explicit use annotation +`@use(false)` instead. Example: +```scala + def f(@use(false) xs: List[() => Unit]): Int = xs.length +``` + +This works since `@use` is defined like this: +```scala +class use(cond: Boolean = true) extends StaticAnnotation +``` + + + diff --git a/library/src/scala/caps.scala b/library/src/scala/caps.scala index 9911ef920116..53c4ae7dc0dd 100644 --- a/library/src/scala/caps.scala +++ b/library/src/scala/caps.scala @@ -55,7 +55,7 @@ import annotation.{experimental, compileTimeOnly, retainsCap} /** This should go into annotations. For now it is here, so that we * can experiment with it quickly between minor releases */ - final class unbox extends annotation.StaticAnnotation + final class use extends annotation.StaticAnnotation object unsafe: @@ -66,20 +66,4 @@ import annotation.{experimental, compileTimeOnly, retainsCap} */ def unsafeAssumePure: T = x - /** If argument is of type `cs T`, converts to type `box cs T`. This - * avoids the error that would be raised when boxing `cap`. - */ - def unsafeBox: T = x - - /** If argument is of type `box cs T`, converts to type `cs T`. This - * avoids the error that would be raised when unboxing `cap`. - */ - def unsafeUnbox: T = x - - extension [T, U](f: T => U) - /** If argument is of type `box cs T`, converts to type `cs T`. This - * avoids the error that would be raised when unboxing `cap`. - */ - def unsafeBoxFunArg: T => U = f - end unsafe diff --git a/scala2-library-cc/src/scala/collection/Iterator.scala b/scala2-library-cc/src/scala/collection/Iterator.scala index 4d1b0ed4ff95..91a22caa288c 100644 --- a/scala2-library-cc/src/scala/collection/Iterator.scala +++ b/scala2-library-cc/src/scala/collection/Iterator.scala @@ -17,7 +17,7 @@ import scala.annotation.tailrec import scala.annotation.unchecked.uncheckedVariance import scala.runtime.Statics import language.experimental.captureChecking - +import caps.unsafe.unsafeAssumePure /** Iterators are data structures that allow to iterate over a sequence * of elements. They have a `hasNext` method for checking @@ -1008,7 +1008,7 @@ object Iterator extends IterableFactory[Iterator] { def newBuilder[A]: Builder[A, Iterator[A]] = new ImmutableBuilder[A, Iterator[A]](empty[A]) { override def addOne(elem: A): this.type = { elems = elems ++ single(elem); this } - }.asInstanceOf // !!! CC unsafe op + }.unsafeAssumePure /** Creates iterator that produces the results of some element computation a number of times. * @@ -1159,8 +1159,8 @@ object Iterator extends IterableFactory[Iterator] { // If we advanced the current iterator to a ConcatIterator, merge it into this one @tailrec def merge(): Unit = if (current.isInstanceOf[ConcatIterator[_]]) { - val c = current.asInstanceOf[ConcatIterator[A]] - current = c.current.asInstanceOf // !!! CC unsafe op + val c: ConcatIterator[A] = current.asInstanceOf + current = c.current.unsafeAssumePure // !!! CC unsafe op currentHasNextChecked = c.currentHasNextChecked if (c.tail != null) { if (last == null) last = c.last diff --git a/scala2-library-cc/src/scala/collection/SeqView.scala b/scala2-library-cc/src/scala/collection/SeqView.scala index 292dc61ddaa8..30207fca46ff 100644 --- a/scala2-library-cc/src/scala/collection/SeqView.scala +++ b/scala2-library-cc/src/scala/collection/SeqView.scala @@ -16,7 +16,6 @@ package collection import scala.annotation.nowarn import language.experimental.captureChecking import caps.unsafe.unsafeAssumePure -import scala.annotation.unchecked.uncheckedCaptures /** !!! Scala 2 difference: Need intermediate trait SeqViewOps to collect the * necessary functionality over which SeqViews are defined, and at the same diff --git a/tests/neg-custom-args/captures/bad-uses-2.scala b/tests/neg-custom-args/captures/bad-uses-2.scala new file mode 100644 index 000000000000..8dd121b2b134 --- /dev/null +++ b/tests/neg-custom-args/captures/bad-uses-2.scala @@ -0,0 +1,7 @@ +import caps.use +class Test: + @use def F = ??? // error + @use val x = ??? // error + @use type T // error + def foo[@use T](@use c: T): Unit = ??? // OK + diff --git a/tests/neg-custom-args/captures/bad-uses.scala b/tests/neg-custom-args/captures/bad-uses.scala new file mode 100644 index 000000000000..c21976ebb3cf --- /dev/null +++ b/tests/neg-custom-args/captures/bad-uses.scala @@ -0,0 +1,3 @@ +import caps.use +class Test: + val bar = (@use c: Test) => () // error diff --git a/tests/neg-custom-args/captures/box-adapt-cases.scala b/tests/neg-custom-args/captures/box-adapt-cases.scala index 681d699842ed..d9ec0f80a548 100644 --- a/tests/neg-custom-args/captures/box-adapt-cases.scala +++ b/tests/neg-custom-args/captures/box-adapt-cases.scala @@ -4,7 +4,7 @@ def test1(): Unit = { type Id[X] = [T] -> (op: X => T) -> T val x: Id[Cap^] = ??? - x(cap => cap.use()) // error, OK under sealed + x(cap => cap.use()) } def test2(io: Cap^): Unit = { diff --git a/tests/neg-custom-args/captures/byname.check b/tests/neg-custom-args/captures/byname.check index c9530f6aad50..1c113591922d 100644 --- a/tests/neg-custom-args/captures/byname.check +++ b/tests/neg-custom-args/captures/byname.check @@ -1,3 +1,10 @@ +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/byname.scala:10:6 ---------------------------------------- +10 | h(f2()) // error + | ^^^^ + | Found: (x$0: Int) ->{cap1} Int + | Required: (x$0: Int) ->? Int + | + | longer explanation available when compiling with `-explain` -- Error: tests/neg-custom-args/captures/byname.scala:19:5 ------------------------------------------------------------- 19 | h(g()) // error | ^^^ @@ -8,22 +15,3 @@ | ^^^ | reference (cap2 : Cap^) is not included in the allowed capture set {cap1} | of an enclosing function literal with expected type () ->{cap1} I --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/byname.scala:4:2 ----------------------------------------- - 4 | def f() = if cap1 == cap1 then g else g // error - | ^ - | Found: ((x$0: Int) ->{cap2} Int)^{} - | Required: Int -> Int - | - | Note that the expected type Int ->{} Int - | is the previously inferred result type of method test - | which is also the type seen in separately compiled sources. - | The new inferred type ((x$0: Int) ->{cap2} Int)^{} - | must conform to this type. - 5 | def g(x: Int) = if cap2 == cap2 then 1 else x - 6 | def g2(x: Int) = if cap1 == cap1 then 1 else x - 7 | def f2() = if cap1 == cap1 then g2 else g2 - 8 | def h(ff: => Int ->{cap2} Int) = ff - 9 | h(f()) -10 | h(f2()) - | - | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/byname.scala b/tests/neg-custom-args/captures/byname.scala index 75ad527dbd2d..dd8fcf1b8818 100644 --- a/tests/neg-custom-args/captures/byname.scala +++ b/tests/neg-custom-args/captures/byname.scala @@ -1,13 +1,13 @@ class Cap extends caps.Capability def test(cap1: Cap, cap2: Cap) = - def f() = if cap1 == cap1 then g else g // error + def f() = if cap1 == cap1 then g else g def g(x: Int) = if cap2 == cap2 then 1 else x def g2(x: Int) = if cap1 == cap1 then 1 else x def f2() = if cap1 == cap1 then g2 else g2 def h(ff: => Int ->{cap2} Int) = ff h(f()) - h(f2()) + h(f2()) // error class I diff --git a/tests/neg-custom-args/captures/capt-test.scala b/tests/neg-custom-args/captures/capt-test.scala index b202a14d0940..80ee1aba84e1 100644 --- a/tests/neg-custom-args/captures/capt-test.scala +++ b/tests/neg-custom-args/captures/capt-test.scala @@ -20,8 +20,8 @@ def handle[E <: Exception, R <: Top](op: (CT[E] @retains(caps.cap)) => R)(handl catch case ex: E => handler(ex) def test: Unit = - val b = handle[Exception, () => Nothing] { + val b = handle[Exception, () => Nothing] { // error (x: CanThrow[Exception]) => () => raise(new Exception)(using x) - } { // error + } { (ex: Exception) => ??? } diff --git a/tests/neg-custom-args/captures/capt1.check b/tests/neg-custom-args/captures/capt1.check index 3d0ed538b2e5..f63c55ca48c4 100644 --- a/tests/neg-custom-args/captures/capt1.check +++ b/tests/neg-custom-args/captures/capt1.check @@ -1,12 +1,12 @@ -- Error: tests/neg-custom-args/captures/capt1.scala:6:11 -------------------------------------------------------------- 6 | () => if x == null then y else y // error | ^ - | (x : C^) cannot be referenced here; it is not included in the allowed capture set {} + | reference (x : C^) is not included in the allowed capture set {} | of an enclosing function literal with expected type () -> C -- Error: tests/neg-custom-args/captures/capt1.scala:9:11 -------------------------------------------------------------- 9 | () => if x == null then y else y // error | ^ - | (x : C^) cannot be referenced here; it is not included in the allowed capture set {} + | reference (x : C^) is not included in the allowed capture set {} | of an enclosing function literal with expected type Matchable -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/capt1.scala:16:2 ----------------------------------------- 16 | def f(y: Int) = if x == null then y else y // error @@ -33,22 +33,18 @@ 29 | def m() = if x == null then y else y | | longer explanation available when compiling with `-explain` --- Error: tests/neg-custom-args/captures/capt1.scala:34:12 ------------------------------------------------------------- +-- Error: tests/neg-custom-args/captures/capt1.scala:34:16 ------------------------------------------------------------- 34 | val z2 = h[() -> Cap](() => x) // error // error - | ^^^^^^^^^^^^ - | Sealed type variable X cannot be instantiated to () -> box C^ since - | the part box C^ of that type captures the root capability `cap`. - | This is often caused by a local capability in an argument of method h - | leaking as part of its result. + | ^^^^^^^^^ + | Type variable X of method h cannot be instantiated to () -> box C^ since + | the part box C^ of that type captures the root capability `cap`. -- Error: tests/neg-custom-args/captures/capt1.scala:34:30 ------------------------------------------------------------- 34 | val z2 = h[() -> Cap](() => x) // error // error | ^ - | (x : C^) cannot be referenced here; it is not included in the allowed capture set {} - | of an enclosing function literal with expected type () -> box C^ --- Error: tests/neg-custom-args/captures/capt1.scala:36:12 ------------------------------------------------------------- + | reference (x : C^) is not included in the allowed capture set {} + | of an enclosing function literal with expected type () -> box C^ +-- Error: tests/neg-custom-args/captures/capt1.scala:36:13 ------------------------------------------------------------- 36 | val z3 = h[(() -> Cap) @retains(x)](() => x)(() => C()) // error - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ - | Sealed type variable X cannot be instantiated to box () ->{x} Cap since - | the part Cap of that type captures the root capability `cap`. - | This is often caused by a local capability in an argument of method h - | leaking as part of its result. + | ^^^^^^^^^^^^^^^^^^^^^^^ + | Type variable X of method h cannot be instantiated to box () ->{x} Cap since + | the part Cap of that type captures the root capability `cap`. diff --git a/tests/neg-custom-args/captures/capt1.scala b/tests/neg-custom-args/captures/capt1.scala index cad0bad4ba56..8da7e633ca51 100644 --- a/tests/neg-custom-args/captures/capt1.scala +++ b/tests/neg-custom-args/captures/capt1.scala @@ -1,5 +1,5 @@ -//> using options -source 3.4 -// (to make sure we use the sealed policy) + + import annotation.retains class C def f(x: C @retains(caps.cap), y: C): () -> C = diff --git a/tests/neg-custom-args/captures/cc-this5.check b/tests/neg-custom-args/captures/cc-this5.check index 8affe7005e2e..21b5b36e0574 100644 --- a/tests/neg-custom-args/captures/cc-this5.check +++ b/tests/neg-custom-args/captures/cc-this5.check @@ -1,7 +1,7 @@ -- Error: tests/neg-custom-args/captures/cc-this5.scala:16:20 ---------------------------------------------------------- 16 | def f = println(c) // error | ^ - | (c : Cap^) cannot be referenced here; it is not included in the allowed capture set {} + | reference (c : Cap^) is not included in the allowed capture set {} | of the enclosing class A -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/cc-this5.scala:21:15 ------------------------------------- 21 | val x: A = this // error diff --git a/tests/pos-custom-args/captures/curried-closures.scala b/tests/neg-custom-args/captures/curried-closures.scala similarity index 91% rename from tests/pos-custom-args/captures/curried-closures.scala rename to tests/neg-custom-args/captures/curried-closures.scala index 262dd4b66b92..426f0df85022 100644 --- a/tests/pos-custom-args/captures/curried-closures.scala +++ b/tests/neg-custom-args/captures/curried-closures.scala @@ -30,5 +30,5 @@ def Test4(g: OutputStream^) = val _: (f: OutputStream^) ->{} Int ->{f} Unit = later val later2 = () => (y: Int) => xs.foreach(x => g.write(x + y)) - val _: () ->{} Int ->{g} Unit = later2 + val _: () ->{} Int ->{g} Unit = later2 // error, inferred type is () ->{later2} Int ->{g} Unit diff --git a/tests/neg-custom-args/captures/dcs-tvar.check b/tests/neg-custom-args/captures/dcs-tvar.check new file mode 100644 index 000000000000..d3caa720e88a --- /dev/null +++ b/tests/neg-custom-args/captures/dcs-tvar.check @@ -0,0 +1,10 @@ +-- Error: tests/neg-custom-args/captures/dcs-tvar.scala:6:15 ----------------------------------------------------------- +6 | () => runOps(xs) // error + | ^^ + | reference xs* is not included in the allowed capture set {} + | of an enclosing function literal with expected type () -> Unit +-- Error: tests/neg-custom-args/captures/dcs-tvar.scala:9:15 ----------------------------------------------------------- +9 | () => runOps(xs) // error + | ^^ + | reference xs* is not included in the allowed capture set {} + | of an enclosing function literal with expected type () -> Unit diff --git a/tests/neg-custom-args/captures/dcs-tvar.scala b/tests/neg-custom-args/captures/dcs-tvar.scala new file mode 100644 index 000000000000..381c08b4d351 --- /dev/null +++ b/tests/neg-custom-args/captures/dcs-tvar.scala @@ -0,0 +1,9 @@ +import caps.use + +def runOps(@use xs: List[() => Unit]): Unit = ??? + +def f[T <: List[() => Unit]](xs: T): () -> Unit = + () => runOps(xs) // error + +def g[T <: List[U], U <: () => Unit](xs: T): () -> Unit = + () => runOps(xs) // error diff --git a/tests/neg-custom-args/captures/delayedRunops.check b/tests/neg-custom-args/captures/delayedRunops.check new file mode 100644 index 000000000000..68da4672acf5 --- /dev/null +++ b/tests/neg-custom-args/captures/delayedRunops.check @@ -0,0 +1,14 @@ +-- Error: tests/neg-custom-args/captures/delayedRunops.scala:16:13 ----------------------------------------------------- +16 | runOps(ops1) // error + | ^^^^ + | reference ops* is not included in the allowed capture set {} + | of an enclosing function literal with expected type () -> Unit +-- Error: tests/neg-custom-args/captures/delayedRunops.scala:22:13 ----------------------------------------------------- +22 | runOps(ops1) // error + | ^^^^ + | Local reach capability ops1* leaks into capture scope of enclosing function +-- Error: tests/neg-custom-args/captures/delayedRunops.scala:28:13 ----------------------------------------------------- +28 | runOps(ops1) // error + | ^^^^ + | reference ops* is not included in the allowed capture set {} + | of an enclosing function literal with expected type () -> Unit diff --git a/tests/neg-custom-args/captures/delayedRunops.scala b/tests/neg-custom-args/captures/delayedRunops.scala new file mode 100644 index 000000000000..191118fa19c9 --- /dev/null +++ b/tests/neg-custom-args/captures/delayedRunops.scala @@ -0,0 +1,28 @@ +import language.experimental.captureChecking +import caps.use + + // ok + def runOps(@use ops: List[() => Unit]): Unit = + ops.foreach(op => op()) + + // ok + def delayedRunOps(@use ops: List[() => Unit]): () ->{ops*} Unit = // @use should not be necessary in the future + () => runOps(ops) + + // unsound: impure operation pretended pure + def delayedRunOps1(ops: List[() => Unit]): () ->{} Unit = + () => + val ops1 = ops + runOps(ops1) // error + + // unsound: impure operation pretended pure + def delayedRunOps2(ops: List[() => Unit]): () ->{} Unit = + () => + val ops1: List[() => Unit] = ops + runOps(ops1) // error + + // unsound: impure operation pretended pure + def delayedRunOps3(ops: List[() => Unit]): () ->{} Unit = + () => + val ops1: List[() ->{ops*} Unit] = ops + runOps(ops1) // error diff --git a/tests/neg-custom-args/captures/depfun-reach.check b/tests/neg-custom-args/captures/depfun-reach.check new file mode 100644 index 000000000000..c1d7d05dc8d6 --- /dev/null +++ b/tests/neg-custom-args/captures/depfun-reach.check @@ -0,0 +1,14 @@ +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/depfun-reach.scala:13:4 ---------------------------------- +13 | op // error + | ^^ + | Found: (xs: List[(X, box () ->{io} Unit)]) ->{op} List[box () ->{xs*} Unit] + | Required: (xs: List[(X, box () ->{io} Unit)]) => List[() -> Unit] + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/depfun-reach.scala:20:60 --------------------------------- +20 | val b: (xs: List[() ->{io} Unit]) => List[() ->{} Unit] = a // error + | ^ + | Found: (xs: List[box () ->{io} Unit]) ->{a} List[box () ->{xs*} Unit] + | Required: (xs: List[box () ->{io} Unit]) => List[() -> Unit] + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/depfun-reach.scala b/tests/neg-custom-args/captures/depfun-reach.scala new file mode 100644 index 000000000000..94b10f7dbcdb --- /dev/null +++ b/tests/neg-custom-args/captures/depfun-reach.scala @@ -0,0 +1,20 @@ +import language.experimental.captureChecking +import caps.cap + +def test(io: Object^, async: Object^) = + def compose(op: List[(() ->{cap} Unit, () ->{cap} Unit)]): List[() ->{op*} Unit] = + List(() => op.foreach((f,g) => { f(); g() })) + + def compose1(op: List[(() ->{async} Unit, () ->{io} Unit)]): List[() ->{op*} Unit] = + compose(op) + + def foo[X](op: (xs: List[(X, () ->{io} Unit)]) => List[() ->{xs*} Unit]) + : (xs: List[(X, () ->{io} Unit)]) => List[() ->{} Unit] = + op // error + + def boom(op: List[(() ->{async} Unit, () ->{io} Unit)]): List[() ->{} Unit] = + foo(compose1)(op) + +def test2(io: Object^) = + val a: (xs: List[() ->{io} Unit]) => List[() ->{xs*} Unit] = ??? + val b: (xs: List[() ->{io} Unit]) => List[() ->{} Unit] = a // error diff --git a/tests/neg-custom-args/captures/effect-swaps-explicit.check b/tests/neg-custom-args/captures/effect-swaps-explicit.check index 264dfa663d39..47559ab97568 100644 --- a/tests/neg-custom-args/captures/effect-swaps-explicit.check +++ b/tests/neg-custom-args/captures/effect-swaps-explicit.check @@ -25,5 +25,5 @@ -- Error: tests/neg-custom-args/captures/effect-swaps-explicit.scala:68:15 --------------------------------------------- 68 | Result.make: //lbl ?=> // error, escaping label from Result | ^^^^^^^^^^^ - |local reference contextual$9 from (using contextual$9: boundary.Label[Result[box Future[box T^?]^{fr, contextual$9, contextual$9}, box E^?]]^): - | box Future[box T^?]^{fr, contextual$9, contextual$9} leaks into outer capture set of type parameter T of method make in object Result + |local reference contextual$9 from (using contextual$9: boundary.Label[Result[box Future[box T^?]^{fr, contextual$9}, box E^?]]^): + | box Future[box T^?]^{fr, contextual$9} leaks into outer capture set of type parameter T of method make in object Result diff --git a/tests/neg-custom-args/captures/effect-swaps-explicit.scala b/tests/neg-custom-args/captures/effect-swaps-explicit.scala index 7474e1711b34..e440271ccf88 100644 --- a/tests/neg-custom-args/captures/effect-swaps-explicit.scala +++ b/tests/neg-custom-args/captures/effect-swaps-explicit.scala @@ -1,5 +1,5 @@ -//> using options -source 3.4 -// (to make sure we use the sealed policy) + + object boundary: final class Label[-T] // extends caps.Capability diff --git a/tests/neg-custom-args/captures/effect-swaps.check b/tests/neg-custom-args/captures/effect-swaps.check index ef5a95d333bf..b74c165fd6b6 100644 --- a/tests/neg-custom-args/captures/effect-swaps.check +++ b/tests/neg-custom-args/captures/effect-swaps.check @@ -22,3 +22,8 @@ 73 | fr.await.ok | | longer explanation available when compiling with `-explain` +-- Error: tests/neg-custom-args/captures/effect-swaps.scala:66:15 ------------------------------------------------------ +66 | Result.make: // error: local reference leaks + | ^^^^^^^^^^^ + |local reference contextual$9 from (using contextual$9: boundary.Label[Result[box Future[box T^?]^{fr, contextual$9}, box E^?]]^): + | box Future[box T^?]^{fr, contextual$9} leaks into outer capture set of type parameter T of method make in object Result diff --git a/tests/neg-custom-args/captures/effect-swaps.scala b/tests/neg-custom-args/captures/effect-swaps.scala index 4bafd6421af3..99c781b963c5 100644 --- a/tests/neg-custom-args/captures/effect-swaps.scala +++ b/tests/neg-custom-args/captures/effect-swaps.scala @@ -63,7 +63,7 @@ def test[T, E](using Async) = fr.await.ok def fail4[T, E](fr: Future[Result[T, E]]^) = - Result.make: // should be errorm but inders Result[Any, Any] + Result.make: // error: local reference leaks Future: fut ?=> fr.await.ok diff --git a/tests/neg-custom-args/captures/eta.check b/tests/neg-custom-args/captures/eta.check index 9850e54a7fdf..b7669e9b68ea 100644 --- a/tests/neg-custom-args/captures/eta.check +++ b/tests/neg-custom-args/captures/eta.check @@ -8,5 +8,5 @@ -- Error: tests/neg-custom-args/captures/eta.scala:6:20 ---------------------------------------------------------------- 6 | bar( () => f ) // error | ^ - | (f : Proc^) cannot be referenced here; it is not included in the allowed capture set {} + | reference (f : Proc^) is not included in the allowed capture set {} | of an enclosing function literal with expected type () -> box () ->? Unit diff --git a/tests/neg-custom-args/captures/exception-definitions.check b/tests/neg-custom-args/captures/exception-definitions.check index 7f915ebd9833..3f2b15f312b9 100644 --- a/tests/neg-custom-args/captures/exception-definitions.check +++ b/tests/neg-custom-args/captures/exception-definitions.check @@ -5,7 +5,7 @@ -- Error: tests/neg-custom-args/captures/exception-definitions.scala:7:12 ---------------------------------------------- 7 | val x = c // error | ^ - |(c : Any^) cannot be referenced here; it is not included in the allowed capture set {} of the self type of class Err2 + | reference (c : Any^) is not included in the allowed capture set {} of the self type of class Err2 -- Error: tests/neg-custom-args/captures/exception-definitions.scala:8:13 ---------------------------------------------- 8 | class Err3(c: Any^) extends Exception // error | ^ diff --git a/tests/neg-custom-args/captures/filevar.scala b/tests/neg-custom-args/captures/filevar.scala index e54f161ef124..5b83f1d29380 100644 --- a/tests/neg-custom-args/captures/filevar.scala +++ b/tests/neg-custom-args/captures/filevar.scala @@ -5,8 +5,8 @@ class File: def write(x: String): Unit = ??? class Service: - var file: File^ = uninitialized // OK, was error under sealed - def log = file.write("log") // error, was OK under sealed + var file: File^ = uninitialized // error, was OK under unsealed + def log = file.write("log") // OK, was error under unsealed def withFile[T](op: (l: caps.Capability) ?-> (f: File^{l}) => T): T = op(using caps.cap)(new File) diff --git a/tests/pos/gears-probem-1.scala b/tests/neg-custom-args/captures/gears-problem-1.scala similarity index 79% rename from tests/pos/gears-probem-1.scala rename to tests/neg-custom-args/captures/gears-problem-1.scala index ab71616b72fc..515aecd468f6 100644 --- a/tests/pos/gears-probem-1.scala +++ b/tests/neg-custom-args/captures/gears-problem-1.scala @@ -1,5 +1,5 @@ import language.experimental.captureChecking -import caps.unbox +import caps.use trait Future[+T]: def await: T @@ -17,9 +17,9 @@ class Result[+T, +E]: case class Err[+E](e: E) extends Result[Nothing, E] case class Ok[+T](x: T) extends Result[T, Nothing] -extension [T](@unbox fs: Seq[Future[T]^]) +extension [T](@use fs: Seq[Future[T]^]) def awaitAll = val collector//: Collector[T]{val futures: Seq[Future[T]^{fs*}]} = Collector(fs) // val ch = collector.results // also errors - val fut: Future[T]^{fs*} = collector.results.read().get // found ...^{caps.cap} \ No newline at end of file + val fut: Future[T]^{fs*} = collector.results.read().get // error diff --git a/tests/neg-custom-args/captures/gears-problem.check b/tests/neg-custom-args/captures/gears-problem.check new file mode 100644 index 000000000000..eb37feb6d568 --- /dev/null +++ b/tests/neg-custom-args/captures/gears-problem.check @@ -0,0 +1,15 @@ +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/gears-problem.scala:19:62 -------------------------------- +19 | val fut: Future[T]^{fs*} = collector.results.read().right.get // error + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | Found: Future[T]^{collector.futures*} + | Required: Future[T]^{fs*} + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/gears-problem.scala:24:34 -------------------------------- +24 | val fut2: Future[T]^{fs*} = r.get // error + | ^^^^^ + | Found: Future[box T^?]^{collector.futures*} + | Required: Future[T]^{fs*} + | + | longer explanation available when compiling with `-explain` +there were 4 deprecation warnings; re-run with -deprecation for details diff --git a/tests/pos/gears-probem.scala b/tests/neg-custom-args/captures/gears-problem.scala similarity index 70% rename from tests/pos/gears-probem.scala rename to tests/neg-custom-args/captures/gears-problem.scala index 2e445c985de2..8dcdaabb2100 100644 --- a/tests/pos/gears-probem.scala +++ b/tests/neg-custom-args/captures/gears-problem.scala @@ -1,4 +1,5 @@ import language.experimental.captureChecking +import caps.use trait Future[+T]: def await: T @@ -10,9 +11,14 @@ class Collector[T](val futures: Seq[Future[T]^]): val results: Channel[Future[T]^{futures*}] = ??? end Collector -extension [T](fs: Seq[Future[T]^]) +extension [T](@use fs: Seq[Future[T]^]) def awaitAll = val collector: Collector[T]{val futures: Seq[Future[T]^{fs*}]} = Collector(fs) // val ch = collector.results // also errors - val fut: Future[T]^{fs*} = collector.results.read().right.get // found ...^{caps.cap} \ No newline at end of file + val fut: Future[T]^{fs*} = collector.results.read().right.get // error + + val ch = collector.results + val item = ch.read() + val r = item.right + val fut2: Future[T]^{fs*} = r.get // error \ No newline at end of file diff --git a/tests/neg-custom-args/captures/hk-param.scala b/tests/neg-custom-args/captures/hk-param.scala new file mode 100644 index 000000000000..bfd4f82a0697 --- /dev/null +++ b/tests/neg-custom-args/captures/hk-param.scala @@ -0,0 +1,16 @@ +/** Concrete collection type: View */ +trait View[+A] extends Itable[A], ILike[A, [X] =>> View[X]^]: // error + override def fromIterable[B](c: Itable[B]^): View[B]^{c} = ??? + +trait IPolyTransforms[+A, +C[A]] extends Any: + def fromIterable[B](coll: Itable[B]^): C[B] + +trait ILike[+A, +C[X] <: Itable[X]^] extends IPolyTransforms[A, C] + +/** Base trait for generic collections */ +trait Itable[+A] extends ItableOnce[A] with ILike[A, Itable^] // error + +/** Iterator can be used only once */ +trait ItableOnce[+A] { + def iterator: Iterator[A]^{this} +} diff --git a/tests/neg-custom-args/captures/i15749.scala b/tests/neg-custom-args/captures/i15749.scala index c5b59042085a..9a4e8b0b81ae 100644 --- a/tests/neg-custom-args/captures/i15749.scala +++ b/tests/neg-custom-args/captures/i15749.scala @@ -1,3 +1,5 @@ +//> using options -source 3.5 +// (to make sure we use the unsealed policy) class Unit object unit extends Unit diff --git a/tests/neg-custom-args/captures/i15749a.scala b/tests/neg-custom-args/captures/i15749a.scala index 57fca27fae66..d3c1fce13322 100644 --- a/tests/neg-custom-args/captures/i15749a.scala +++ b/tests/neg-custom-args/captures/i15749a.scala @@ -1,5 +1,5 @@ import caps.cap -import caps.unbox +import caps.use class Unit object u extends Unit @@ -18,7 +18,7 @@ def test = def force[A](thunk: Unit ->{cap} A): A = thunk(u) - def forceWrapper[A](@unbox mx: Wrapper[Unit ->{cap} A]): Wrapper[A] = + def forceWrapper[A](@use mx: Wrapper[Unit ->{cap} A]): Wrapper[A] = // Γ ⊢ mx: Wrapper[□ {cap} Unit => A] // `force` should be typed as ∀(□ {cap} Unit -> A) A, but it can not strictMap[Unit ->{mx*} A, A](mx)(t => force[A](t)) // error // should work diff --git a/tests/neg-custom-args/captures/i15772.check b/tests/neg-custom-args/captures/i15772.check index 58582423b101..67685d5663b8 100644 --- a/tests/neg-custom-args/captures/i15772.check +++ b/tests/neg-custom-args/captures/i15772.check @@ -1,7 +1,7 @@ -- Error: tests/neg-custom-args/captures/i15772.scala:19:26 ------------------------------------------------------------ 19 | val c : C^{x} = new C(x) // error | ^ - | (x : C^) cannot be referenced here; it is not included in the allowed capture set {} + | reference (x : C^) is not included in the allowed capture set {} | of an enclosing function literal with expected type () -> Int -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i15772.scala:20:46 --------------------------------------- 20 | val boxed1 : ((C^) => Unit) -> Unit = box1(c) // error @@ -13,7 +13,7 @@ -- Error: tests/neg-custom-args/captures/i15772.scala:26:26 ------------------------------------------------------------ 26 | val c : C^{x} = new C(x) // error | ^ - | (x : C^) cannot be referenced here; it is not included in the allowed capture set {} + | reference (x : C^) is not included in the allowed capture set {} | of an enclosing function literal with expected type () -> Int -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i15772.scala:27:35 --------------------------------------- 27 | val boxed2 : Observe[C^] = box2(c) // error @@ -25,11 +25,11 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i15772.scala:33:34 --------------------------------------- 33 | val boxed2 : Observe[C]^ = box2(c) // error | ^ - | Found: C^ - | Required: box C{val arg: C^?}^ + | Found: box C^ + | Required: box C{val arg: C^?}^? | - | Note that C^ cannot be box-converted to box C{val arg: C^?}^ - | since at least one of their capture sets contains the root capability `cap` + | Note that the universal capability `cap` + | cannot be included in capture set ? | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i15772.scala:44:2 ---------------------------------------- diff --git a/tests/neg-custom-args/captures/i15922.scala b/tests/neg-custom-args/captures/i15922.scala index 89bf91493fcd..848a22fe5341 100644 --- a/tests/neg-custom-args/captures/i15922.scala +++ b/tests/neg-custom-args/captures/i15922.scala @@ -1,5 +1,5 @@ -//> using options -source 3.4 -// (to force sealed encapsulation checking) + + trait Cap { def use(): Int } type Id[X] = [T] -> (op: X => T) -> T def mkId[X](x: X): Id[X] = [T] => (op: X => T) => op(x) diff --git a/tests/neg-custom-args/captures/i15923-cases.scala b/tests/neg-custom-args/captures/i15923-cases.scala deleted file mode 100644 index 83cfa554e8b9..000000000000 --- a/tests/neg-custom-args/captures/i15923-cases.scala +++ /dev/null @@ -1,7 +0,0 @@ -trait Cap { def use(): Int } -type Id[X] = [T] -> (op: X => T) -> T -def mkId[X](x: X): Id[X] = [T] => (op: X => T) => op(x) - -def foo(x: Id[Cap^]) = { - x(_.use()) // error, was OK under sealed policy -} diff --git a/tests/neg-custom-args/captures/i16114.check b/tests/neg-custom-args/captures/i16114.check new file mode 100644 index 000000000000..745ccea1f905 --- /dev/null +++ b/tests/neg-custom-args/captures/i16114.check @@ -0,0 +1,45 @@ +-- Error: tests/neg-custom-args/captures/i16114.scala:18:13 ------------------------------------------------------------ +18 | expect[Cap^] { // error + | ^^^^ + | Type variable T of method expect cannot be instantiated to box Cap^ since + | that type captures the root capability `cap`. +-- Error: tests/neg-custom-args/captures/i16114.scala:20:8 ------------------------------------------------------------- +20 | fs // error (limitation) + | ^^ + | reference (fs : Cap^) is not included in the allowed capture set {io} + | of an enclosing function literal with expected type Unit ->{io} Unit +-- Error: tests/neg-custom-args/captures/i16114.scala:24:13 ------------------------------------------------------------ +24 | expect[Cap^] { // error + | ^^^^ + | Type variable T of method expect cannot be instantiated to box Cap^ since + | that type captures the root capability `cap`. +-- Error: tests/neg-custom-args/captures/i16114.scala:26:8 ------------------------------------------------------------- +26 | io // error (limitation) + | ^^ + | reference (io : Cap^) is not included in the allowed capture set {fs} + | of an enclosing function literal with expected type Unit ->{fs} Unit +-- Error: tests/neg-custom-args/captures/i16114.scala:30:13 ------------------------------------------------------------ +30 | expect[Cap^] { // error + | ^^^^ + | Type variable T of method expect cannot be instantiated to box Cap^ since + | that type captures the root capability `cap`. +-- Error: tests/neg-custom-args/captures/i16114.scala:36:13 ------------------------------------------------------------ +36 | expect[Cap^](io) // error + | ^^^^ + | Type variable T of method expect cannot be instantiated to box Cap^ since + | that type captures the root capability `cap`. +-- Error: tests/neg-custom-args/captures/i16114.scala:39:13 ------------------------------------------------------------ +39 | expect[Cap^] { // error + | ^^^^ + | Type variable T of method expect cannot be instantiated to box Cap^ since + | that type captures the root capability `cap`. +-- Error: tests/neg-custom-args/captures/i16114.scala:40:8 ------------------------------------------------------------- +40 | io.use() // error + | ^^ + | reference (io : Cap^) is not included in the allowed capture set {} + | of an enclosing function literal with expected type Unit -> Unit +-- Error: tests/neg-custom-args/captures/i16114.scala:41:8 ------------------------------------------------------------- +41 | io // error + | ^^ + | reference (io : Cap^) is not included in the allowed capture set {} + | of an enclosing function literal with expected type Unit -> Unit diff --git a/tests/neg-custom-args/captures/i16114.scala b/tests/neg-custom-args/captures/i16114.scala index ec04fe9c9827..801ea3b11a3d 100644 --- a/tests/neg-custom-args/captures/i16114.scala +++ b/tests/neg-custom-args/captures/i16114.scala @@ -1,5 +1,5 @@ -//> using options -source 3.4 -// (to make sure we use the sealed policy) + + trait Cap { def use(): Int; def close(): Unit } def mkCap(): Cap^ = ??? diff --git a/tests/neg-custom-args/captures/i19330-alt2.scala b/tests/neg-custom-args/captures/i19330-alt2.scala index 86634b45dbe3..3e52e3c65634 100644 --- a/tests/neg-custom-args/captures/i19330-alt2.scala +++ b/tests/neg-custom-args/captures/i19330-alt2.scala @@ -1,5 +1,5 @@ -//> using options -source 3.4 -// (to make sure we use the sealed policy) + + import language.experimental.captureChecking trait Logger diff --git a/tests/neg-custom-args/captures/i19330.scala b/tests/neg-custom-args/captures/i19330.scala index 5fbdc00db311..715b670860cd 100644 --- a/tests/neg-custom-args/captures/i19330.scala +++ b/tests/neg-custom-args/captures/i19330.scala @@ -1,5 +1,5 @@ -//> using options -source 3.4 -// (to force sealed encapsulation checking) + + import language.experimental.captureChecking trait Logger diff --git a/tests/neg-custom-args/captures/i21347.check b/tests/neg-custom-args/captures/i21347.check index c680a54d3efc..e1845d9778d5 100644 --- a/tests/neg-custom-args/captures/i21347.check +++ b/tests/neg-custom-args/captures/i21347.check @@ -1,15 +1,12 @@ -- Error: tests/neg-custom-args/captures/i21347.scala:4:15 ------------------------------------------------------------- 4 | ops.foreach: op => // error | ^ - | Local reach capability C leaks into capture scope of method runOps + | Capture set parameter C leaks into capture scope of method runOps. + | To allow this, the type C should be declared with a @use annotation 5 | op() --- Error: tests/neg-custom-args/captures/i21347.scala:8:14 ------------------------------------------------------------- -8 | () => runOps(f :: Nil) // error - | ^^^^^^^^^^^^^^^^ - | reference (caps.cap : caps.Capability) is not included in the allowed capture set {} - | of an enclosing function literal with expected type () -> Unit -- Error: tests/neg-custom-args/captures/i21347.scala:11:15 ------------------------------------------------------------ 11 | ops.foreach: op => // error | ^ - | Local reach capability ops* leaks into capture scope of method runOpsAlt + | Local reach capability ops* leaks into capture scope of method runOpsAlt. + | To allow this, the parameter ops should be declared with a @use annotation 12 | op() diff --git a/tests/neg-custom-args/captures/i21347.scala b/tests/neg-custom-args/captures/i21347.scala index 41887be6a78a..54fe859caedd 100644 --- a/tests/neg-custom-args/captures/i21347.scala +++ b/tests/neg-custom-args/captures/i21347.scala @@ -5,7 +5,7 @@ def runOps[C^](ops: List[() ->{C^} Unit]): Unit = op() def boom(f: () => Unit): () -> Unit = - () => runOps(f :: Nil) // error + () => runOps(f :: Nil) // now ok def runOpsAlt(ops: List[() => Unit]): Unit = ops.foreach: op => // error diff --git a/tests/neg-custom-args/captures/i21401.check b/tests/neg-custom-args/captures/i21401.check index e204540358ce..679c451949bd 100644 --- a/tests/neg-custom-args/captures/i21401.check +++ b/tests/neg-custom-args/captures/i21401.check @@ -1,13 +1,28 @@ --- Error: tests/neg-custom-args/captures/i21401.scala:15:22 ------------------------------------------------------------ -15 | val a = usingIO[IO^](x => x) // error: The expression's type IO^ is not allowed to capture the root capability `cap` - | ^^^^^^^^^^^^^^^^^^^^ - | The expression's type box IO^ is not allowed to capture the root capability `cap`. - | This usually means that a capability persists longer than its allowed lifetime. --- Error: tests/neg-custom-args/captures/i21401.scala:16:70 ------------------------------------------------------------ -16 | val leaked: [R, X <: Boxed[IO^] -> R] -> (op: X) -> R = usingIO[Res](mkRes) // error: The expression's type Res is not allowed to capture the root capability `cap` in its part box IO^ - | ^^^^^^^^^^^^^^^^^^^ - | The expression's type Res is not allowed to capture the root capability `cap` in its part box IO^. - | This usually means that a capability persists longer than its allowed lifetime. +-- Error: tests/neg-custom-args/captures/i21401.scala:13:14 ------------------------------------------------------------ +13 | op1(Boxed[IO^](x)) // error + | ^^^ + | Type variable T of object Boxed cannot be instantiated to box IO^ since + | that type captures the root capability `cap`. +-- Error: tests/neg-custom-args/captures/i21401.scala:15:18 ------------------------------------------------------------ +15 | val a = usingIO[IO^](x => x) // error + | ^^^ + | Type variable R of method usingIO cannot be instantiated to box IO^ since + | that type captures the root capability `cap`. +-- Error: tests/neg-custom-args/captures/i21401.scala:16:66 ------------------------------------------------------------ +16 | val leaked: [R, X <: Boxed[IO^] -> R] -> (op: X) -> R = usingIO[Res](mkRes) // error + | ^^^ + | Type variable R of method usingIO cannot be instantiated to Res since + | the part box IO^ of that type captures the root capability `cap`. +-- Error: tests/neg-custom-args/captures/i21401.scala:17:29 ------------------------------------------------------------ +17 | val x: Boxed[IO^] = leaked[Boxed[IO^], Boxed[IO^] -> Boxed[IO^]](x => x) // error // error + | ^^^^^^^^^^ + | Type variable R of value leaked cannot be instantiated to Boxed[box IO^] since + | the part box IO^ of that type captures the root capability `cap`. +-- Error: tests/neg-custom-args/captures/i21401.scala:17:52 ------------------------------------------------------------ +17 | val x: Boxed[IO^] = leaked[Boxed[IO^], Boxed[IO^] -> Boxed[IO^]](x => x) // error // error + | ^^^^^^^^^^^^^^^^^^^^^^^^ + |Type variable X of value leaked cannot be instantiated to Boxed[box IO^] -> (ex$18: caps.Exists) -> Boxed[box IO^{ex$18}] since + |the part box IO^{ex$18} of that type captures the root capability `cap`. -- Error: tests/neg-custom-args/captures/i21401.scala:18:21 ------------------------------------------------------------ 18 | val y: IO^{x*} = x.unbox // error | ^^^^^^^ diff --git a/tests/neg-custom-args/captures/i21401.scala b/tests/neg-custom-args/captures/i21401.scala index 8284c601cd5f..0b5479376a0a 100644 --- a/tests/neg-custom-args/captures/i21401.scala +++ b/tests/neg-custom-args/captures/i21401.scala @@ -10,10 +10,10 @@ type Res = [R, X <: Boxed[IO^] -> R] -> (op: X) -> R def mkRes(x: IO^): Res = [R, X <: Boxed[IO^] -> R] => (op: X) => val op1: Boxed[IO^] -> R = op - op1(Boxed[IO^](x)) + op1(Boxed[IO^](x)) // error def test2() = - val a = usingIO[IO^](x => x) // error: The expression's type IO^ is not allowed to capture the root capability `cap` - val leaked: [R, X <: Boxed[IO^] -> R] -> (op: X) -> R = usingIO[Res](mkRes) // error: The expression's type Res is not allowed to capture the root capability `cap` in its part box IO^ - val x: Boxed[IO^] = leaked[Boxed[IO^], Boxed[IO^] -> Boxed[IO^]](x => x) + val a = usingIO[IO^](x => x) // error + val leaked: [R, X <: Boxed[IO^] -> R] -> (op: X) -> R = usingIO[Res](mkRes) // error + val x: Boxed[IO^] = leaked[Boxed[IO^], Boxed[IO^] -> Boxed[IO^]](x => x) // error // error val y: IO^{x*} = x.unbox // error y.println("boom") diff --git a/tests/neg-custom-args/captures/i21442.check b/tests/neg-custom-args/captures/i21442.check index a3bbf65c5988..30becfea0215 100644 --- a/tests/neg-custom-args/captures/i21442.check +++ b/tests/neg-custom-args/captures/i21442.check @@ -1,7 +1,8 @@ -- Error: tests/neg-custom-args/captures/i21442.scala:9:13 ------------------------------------------------------------- 9 | val io = x.unbox // error: local reach capability {x*} leaks | ^^^^^^^ - | Local reach capability x* leaks into capture scope of method foo + | Local reach capability x* leaks into capture scope of method foo. + | To allow this, the parameter x should be declared with a @use annotation -- Error: tests/neg-custom-args/captures/i21442.scala:17:14 ------------------------------------------------------------ 17 | val io = x1.unbox // error | ^^^^^^^^ diff --git a/tests/neg-custom-args/captures/i21614.check b/tests/neg-custom-args/captures/i21614.check index 14b468db4c8e..ced3ab7fd59a 100644 --- a/tests/neg-custom-args/captures/i21614.check +++ b/tests/neg-custom-args/captures/i21614.check @@ -8,10 +8,10 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i21614.scala:12:12 --------------------------------------- 12 | files.map(new Logger(_)) // error, Q: can we improve the error message? | ^^^^^^^^^^^^^ - | Found: Logger{val f: (_$1 : File^{files*})}^ - | Required: Logger{val f: File^?}^? + | Found: (_$1: box File^{files*}) ->{files*} (ex$13: caps.Exists) -> box Logger{val f: File^{_$1}}^{ex$13} + | Required: (_$1: box File^{files*}) -> box Logger{val f: File^?}^? | - | Note that the universal capability `cap` - | cannot be included in capture set ? + | Note that the universal capability `cap` + | cannot be included in capture set ? | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/i21614.scala b/tests/neg-custom-args/captures/i21614.scala index a5ed25d818a5..69aef446e9e2 100644 --- a/tests/neg-custom-args/captures/i21614.scala +++ b/tests/neg-custom-args/captures/i21614.scala @@ -1,12 +1,12 @@ import language.experimental.captureChecking import caps.Capability -import caps.unbox +import caps.use trait File extends Capability class Logger(f: File^) extends Capability // <- will work if we remove the extends clause -def mkLoggers1[F <: File^](@unbox files: List[F]): List[Logger^] = +def mkLoggers1[F <: File^](@use files: List[F]): List[Logger^] = files.map((f: F) => new Logger(f)) // error, Q: can we make this pass (see #19076)? -def mkLoggers2(@unbox files: List[File^]): List[Logger^] = +def mkLoggers2(@use files: List[File^]): List[Logger^] = files.map(new Logger(_)) // error, Q: can we improve the error message? diff --git a/tests/neg-custom-args/captures/i21620.check b/tests/neg-custom-args/captures/i21620.check index 3a09ba978574..d5be3bab9e73 100644 --- a/tests/neg-custom-args/captures/i21620.check +++ b/tests/neg-custom-args/captures/i21620.check @@ -4,10 +4,23 @@ | A pure expression does nothing in statement position | | longer explanation available when compiling with `-explain` +-- [E129] Potential Issue Warning: tests/neg-custom-args/captures/i21620.scala:14:4 ------------------------------------ +14 | x + | ^ + | A pure expression does nothing in statement position + | + | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i21620.scala:9:31 ---------------------------------------- 9 | val _: () -> () ->{x} Unit = f // error | ^ - | Found: () ->{f} () ->{x} Unit + | Found: (f : () ->{x} () ->{x} Unit) | Required: () -> () ->{x} Unit | | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i21620.scala:20:33 --------------------------------------- +20 | val _: () ->{} () ->{x} Unit = f // error, but could be OK + | ^ + | Found: (f : () ->{x} () ->{x} Unit) + | Required: () -> () ->{x} Unit + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/i21620.scala b/tests/neg-custom-args/captures/i21620.scala index a21a41a10863..3b7ba5a9fd5a 100644 --- a/tests/neg-custom-args/captures/i21620.scala +++ b/tests/neg-custom-args/captures/i21620.scala @@ -8,3 +8,14 @@ def test(x: C^) = () => foo() val _: () -> () ->{x} Unit = f // error () + +def test2(x: C^) = + def foo() = + x + () + val f = () => + // println() // uncomenting would give an error, but with + // a different way of handling curried functions should be OK + () => foo() + val _: () ->{} () ->{x} Unit = f // error, but could be OK + () diff --git a/tests/neg-custom-args/captures/i21920.check b/tests/neg-custom-args/captures/i21920.check new file mode 100644 index 000000000000..8efa24426d01 --- /dev/null +++ b/tests/neg-custom-args/captures/i21920.check @@ -0,0 +1,10 @@ +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i21920.scala:34:34 --------------------------------------- +34 | val cell: Cell[File] = File.open(f => Cell(Seq(f))) // error + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | Found: Cell[box File^{f, f²}]{val head: () ?->? IterableOnce[box File^{f, f²}]^?}^? + | Required: Cell[File] + | + | where: f is a reference to a value parameter + | f² is a reference to a value parameter + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/i21920.scala b/tests/neg-custom-args/captures/i21920.scala new file mode 100644 index 000000000000..7ea5a63969b1 --- /dev/null +++ b/tests/neg-custom-args/captures/i21920.scala @@ -0,0 +1,36 @@ +import language.experimental.captureChecking + +trait Iterator[+A] extends IterableOnce[A]: + self: Iterator[A]^ => + def next(): A + +trait IterableOnce[+A] extends Any: + def iterator: Iterator[A]^{this} + +final class Cell[A](head: => IterableOnce[A]^): + def headIterator: Iterator[A]^{this} = head.iterator + +class File private (): + private var closed = false + + def close() = closed = true + + def read() = + assert(!closed, "File closed") + 1 + +object File: + def open[T](f: File^ => T): T = + val file = File() + try + f(file) + finally + file.close() + +object Seq: + def apply[A](xs: A*): IterableOnce[A] = ??? + +@main def Main() = + val cell: Cell[File] = File.open(f => Cell(Seq(f))) // error + val file = cell.headIterator.next() + file.read() diff --git a/tests/neg-custom-args/captures/lazylist.check b/tests/neg-custom-args/captures/lazylist.check index f0fbd1a025b5..65fed0c4ec7e 100644 --- a/tests/neg-custom-args/captures/lazylist.check +++ b/tests/neg-custom-args/captures/lazylist.check @@ -8,8 +8,8 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/lazylist.scala:35:29 ------------------------------------- 35 | val ref1c: LazyList[Int] = ref1 // error | ^^^^ - | Found: lazylists.LazyCons[Int]{val xs: () ->{cap1} lazylists.LazyList[Int]^?}^{ref1} - | Required: lazylists.LazyList[Int] + | Found: (ref1 : lazylists.LazyCons[Int]{val xs: () ->{cap1} lazylists.LazyList[Int]^?}^{cap1}) + | Required: lazylists.LazyList[Int] | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/lazylist.scala:37:36 ------------------------------------- @@ -29,7 +29,7 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/lazylist.scala:41:42 ------------------------------------- 41 | val ref4c: LazyList[Int]^{cap1, ref3} = ref4 // error | ^^^^ - | Found: (ref4 : lazylists.LazyList[Int]^{cap3, ref2, ref1}) + | Found: (ref4 : lazylists.LazyList[Int]^{cap3, ref1, ref2}) | Required: lazylists.LazyList[Int]^{cap1, ref3} | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/lazylists-exceptions.check b/tests/neg-custom-args/captures/lazylists-exceptions.check index 4a8738118609..111719a81f07 100644 --- a/tests/neg-custom-args/captures/lazylists-exceptions.check +++ b/tests/neg-custom-args/captures/lazylists-exceptions.check @@ -1,8 +1,9 @@ -- Error: tests/neg-custom-args/captures/lazylists-exceptions.scala:36:2 ----------------------------------------------- 36 | try // error | ^ - | The expression's type LazyList[Int]^ is not allowed to capture the root capability `cap`. - | This usually means that a capability persists longer than its allowed lifetime. + | The result of `try` cannot have type LazyList[Int]^ since + | that type captures the root capability `cap`. + | This is often caused by a locally generated exception capability leaking as part of its result. 37 | tabulate(10) { i => 38 | if i > 9 then throw Ex1() 39 | i * i diff --git a/tests/neg-custom-args/captures/lazylists2.check b/tests/neg-custom-args/captures/lazylists2.check index 13b1da6eaf1c..d1883cc54718 100644 --- a/tests/neg-custom-args/captures/lazylists2.check +++ b/tests/neg-custom-args/captures/lazylists2.check @@ -25,11 +25,11 @@ -- Error: tests/neg-custom-args/captures/lazylists2.scala:40:20 -------------------------------------------------------- 40 | def head: B = f(xs.head) // error | ^ - |(f : A => B) cannot be referenced here; it is not included in the allowed capture set {xs} of the self type of class Mapped + | reference (f : A => B) is not included in the allowed capture set {xs} of the self type of class Mapped -- Error: tests/neg-custom-args/captures/lazylists2.scala:41:48 -------------------------------------------------------- 41 | def tail: LazyList[B]^{this}= xs.tail.map(f) // error | ^ - |(f : A => B) cannot be referenced here; it is not included in the allowed capture set {xs} of the self type of class Mapped + | reference (f : A => B) is not included in the allowed capture set {xs} of the self type of class Mapped -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/lazylists2.scala:45:4 ------------------------------------ 45 | final class Mapped extends LazyList[B]: // error | ^ diff --git a/tests/neg-custom-args/captures/leak-problem-2.scala b/tests/neg-custom-args/captures/leak-problem-2.scala index 08a3a6c2d9ca..8ca298dbdd1e 100644 --- a/tests/neg-custom-args/captures/leak-problem-2.scala +++ b/tests/neg-custom-args/captures/leak-problem-2.scala @@ -2,7 +2,7 @@ import language.experimental.captureChecking trait Source[+T] -def race[T](@caps.unbox sources: Seq[Source[T]^]): Source[T]^{sources*} = ??? +def race[T](@caps.use sources: Seq[Source[T]^]): Source[T]^{sources*} = ??? def raceTwo[T](src1: Source[T]^, src2: Source[T]^): Source[T]^{} = race(Seq(src1, src2)) // error diff --git a/tests/neg-custom-args/captures/leaked-curried.check b/tests/neg-custom-args/captures/leaked-curried.check index 3f0a9800a4ec..be11aedd74ae 100644 --- a/tests/neg-custom-args/captures/leaked-curried.check +++ b/tests/neg-custom-args/captures/leaked-curried.check @@ -1,8 +1,10 @@ -- Error: tests/neg-custom-args/captures/leaked-curried.scala:14:20 ---------------------------------------------------- 14 | () => () => io // error | ^^ - |(io : Cap^) cannot be referenced here; it is not included in the allowed capture set {} of the self type of class Fuzz + | reference (io : Cap^) is not included in the allowed capture set {} + | of an enclosing function literal with expected type () -> () ->{io} (ex$7: caps.Exists) -> Cap^{ex$7} -- Error: tests/neg-custom-args/captures/leaked-curried.scala:17:20 ---------------------------------------------------- 17 | () => () => io // error | ^^ - |(io : Cap^) cannot be referenced here; it is not included in the allowed capture set {} of the self type of class Foo + | reference (io : Cap^) is not included in the allowed capture set {} + | of an enclosing function literal with expected type () -> () ->{io} (ex$15: caps.Exists) -> Cap^{ex$15} diff --git a/tests/neg-custom-args/captures/levels.check b/tests/neg-custom-args/captures/levels.check index ddfa7c051211..b99adefd4b2f 100644 --- a/tests/neg-custom-args/captures/levels.check +++ b/tests/neg-custom-args/captures/levels.check @@ -1,14 +1,15 @@ --- Error: tests/neg-custom-args/captures/levels.scala:19:13 ------------------------------------------------------------ -19 | val _ = Ref[String => String]((x: String) => x) // error - | ^^^^^^^^^^^^^^^^^^^^^ - | Sealed type variable T cannot be instantiated to box String => String since - | that type captures the root capability `cap`. - | This is often caused by a local capability in an argument of constructor Ref - | leaking as part of its result. --- Error: tests/neg-custom-args/captures/levels.scala:24:11 ------------------------------------------------------------ -24 | r.setV(g) // error +-- Error: tests/neg-custom-args/captures/levels.scala:17:21 ------------------------------------------------------------ +17 | val _ = Ref[String => String]((x: String) => x) // error + | ^^^^^^^^^^^^^^^^ + | Type variable T of constructor Ref cannot be instantiated to box String => String since + | that type captures the root capability `cap`. +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/levels.scala:22:11 --------------------------------------- +22 | r.setV(g) // error | ^ - | reference (cap3 : CC^) is not included in the allowed capture set ? of value r + | Found: box (x: String) ->{cap3} String + | Required: box (x$0: String) ->? String | | Note that reference (cap3 : CC^), defined in method scope | cannot be included in outer capture set ? of value r + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/levels.scala b/tests/neg-custom-args/captures/levels.scala index 4709fd80d9b8..b28e87f03ef7 100644 --- a/tests/neg-custom-args/captures/levels.scala +++ b/tests/neg-custom-args/captures/levels.scala @@ -1,5 +1,3 @@ -//> using options -source 3.4 -// (to make sure we use the sealed policy) class CC def test1(cap1: CC^) = diff --git a/tests/neg-custom-args/captures/method-uses.scala b/tests/neg-custom-args/captures/method-uses.scala new file mode 100644 index 000000000000..69acef6a99a8 --- /dev/null +++ b/tests/neg-custom-args/captures/method-uses.scala @@ -0,0 +1,30 @@ +def test(xs: List[() => Unit]) = + xs.head // error + + def foo = + xs.head // ok + def bar() = + xs.head // ok + + class Foo: + println(xs.head) // error, but could be OK + + foo // error + bar() // error + Foo() // OK, but could be error + +def test2(xs: List[() => Unit]) = + def foo = xs.head // ok + () + +def test3(xs: List[() => Unit]): () ->{xs*} Unit = () => + println(xs.head) // error, ok under deferredReaches + + def test4(xs: List[() => Unit]) = () => xs.head // error, ok under deferredReaches + + def test5(xs: List[() => Unit]) = new: + println(xs.head) // error, ok under deferredReaches + + def test6(xs: List[() => Unit]) = + val x= new { println(xs.head) } // error + x diff --git a/tests/neg-custom-args/captures/outer-var.check b/tests/neg-custom-args/captures/outer-var.check index 32351a179eab..72af842728a1 100644 --- a/tests/neg-custom-args/captures/outer-var.check +++ b/tests/neg-custom-args/captures/outer-var.check @@ -1,8 +1,8 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/outer-var.scala:11:8 ------------------------------------- 11 | x = q // error | ^ - | Found: box () ->{q} Unit - | Required: box () ->{p, q²} Unit + | Found: (q : Proc) + | Required: () ->{p, q²} Unit | | where: q is a parameter in method inner | q² is a parameter in method test @@ -12,19 +12,31 @@ 12 | x = (q: Proc) // error | ^^^^^^^ | Found: Proc - | Required: box () ->{p, q} Unit - | - | Note that () => Unit cannot be box-converted to box () ->{p, q} Unit - | since at least one of their capture sets contains the root capability `cap` + | Required: () ->{p, q} Unit | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/outer-var.scala:13:9 ------------------------------------- 13 | y = (q: Proc) // error | ^^^^^^^ | Found: Proc - | Required: box () => Unit + | Required: () ->{p} Unit + | + | Note that the universal capability `cap` + | cannot be included in capture set {p} of variable y + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/outer-var.scala:14:8 ------------------------------------- +14 | y = q // error, was OK under unsealed + | ^ + | Found: (q : Proc) + | Required: () ->{p} Unit | - | Note that () => Unit cannot be box-converted to box () => Unit - | since at least one of their capture sets contains the root capability `cap` + | Note that reference (q : Proc), defined in method inner + | cannot be included in outer capture set {p} of variable y | | longer explanation available when compiling with `-explain` +-- Error: tests/neg-custom-args/captures/outer-var.scala:16:57 --------------------------------------------------------- +16 | var finalizeActions = collection.mutable.ListBuffer[() => Unit]() // error, was OK under unsealed + | ^^^^^^^^^^ + | Type variable A of object ListBuffer cannot be instantiated to box () => Unit since + | that type captures the root capability `cap`. diff --git a/tests/neg-custom-args/captures/outer-var.scala b/tests/neg-custom-args/captures/outer-var.scala index e26cd631602a..f869bfbfc387 100644 --- a/tests/neg-custom-args/captures/outer-var.scala +++ b/tests/neg-custom-args/captures/outer-var.scala @@ -11,8 +11,8 @@ def test(p: Proc, q: () => Unit) = x = q // error x = (q: Proc) // error y = (q: Proc) // error - y = q // OK, was error under sealed + y = q // error, was OK under unsealed - var finalizeActions = collection.mutable.ListBuffer[() => Unit]() // OK, was error under sealed + var finalizeActions = collection.mutable.ListBuffer[() => Unit]() // error, was OK under unsealed diff --git a/tests/neg-custom-args/captures/path-use.check b/tests/neg-custom-args/captures/path-use.check new file mode 100644 index 000000000000..e09ee232dd17 --- /dev/null +++ b/tests/neg-custom-args/captures/path-use.check @@ -0,0 +1,4 @@ +-- Error: tests/neg-custom-args/captures/path-use.scala:18:32 ---------------------------------------------------------- +18 | val g = () => println(c.procs.head) // error, local reach capability c.procs* leaks + | ^^^^^^^^^^^^ + | Local reach capability c.procs* leaks into capture scope of method test diff --git a/tests/neg-custom-args/captures/path-use.scala b/tests/neg-custom-args/captures/path-use.scala new file mode 100644 index 000000000000..31feb4c0adf4 --- /dev/null +++ b/tests/neg-custom-args/captures/path-use.scala @@ -0,0 +1,25 @@ +import language.experimental.namedTuples + +class IO + +class C(val f: IO^): + val procs: List[Proc] = ??? + +type Proc = () => Unit + +def test(io: IO^) = + val c = C(io) + val f = () => println(c.f) + val _: () ->{c.f} Unit = f + + val x = c.procs + val _: List[() ->{c.procs*} Unit] = x + + val g = () => println(c.procs.head) // error, local reach capability c.procs* leaks + val _: () ->{c.procs*} Unit = g + + val cc: C { val f: IO^{io}; val procs: List[() ->{io} Unit] }^{io} = + ??? + + val gg = () => println(cc.procs.head) // OK, since cc.procs* has {io} as underlying capture set + val _: () ->{io} Unit = gg diff --git a/tests/neg-custom-args/captures/reaches.check b/tests/neg-custom-args/captures/reaches.check index f00fea09ed8c..7c00fa7299fe 100644 --- a/tests/neg-custom-args/captures/reaches.check +++ b/tests/neg-custom-args/captures/reaches.check @@ -15,22 +15,27 @@ | cannot be included in outer capture set {xs*} of value cur | | longer explanation available when compiling with `-explain` --- Error: tests/neg-custom-args/captures/reaches.scala:38:31 ----------------------------------------------------------- -38 | val next: () => Unit = cur.head // error - | ^^^^^^^^ - | The expression's type box () => Unit is not allowed to capture the root capability `cap`. - | This usually means that a capability persists longer than its allowed lifetime. --- Error: tests/neg-custom-args/captures/reaches.scala:45:35 ----------------------------------------------------------- -45 | val next: () => Unit = cur.get.head // error - | ^^^^^^^^^^^^ - | The expression's type box () => Unit is not allowed to capture the root capability `cap`. - | This usually means that a capability persists longer than its allowed lifetime. +-- Error: tests/neg-custom-args/captures/reaches.scala:36:6 ------------------------------------------------------------ +36 | var cur: List[Proc] = xs // error + | ^ + | Mutable variable cur cannot have type List[box () => Unit] since + | the part box () => Unit of that type captures the root capability `cap`. +-- Error: tests/neg-custom-args/captures/reaches.scala:43:16 ----------------------------------------------------------- +43 | val cur = Ref[List[Proc]](xs) // error + | ^^^^^^^^^^ + | Type variable T of constructor Ref cannot be instantiated to List[box () => Unit] since + | the part box () => Unit of that type captures the root capability `cap`. +-- Error: tests/neg-custom-args/captures/reaches.scala:53:51 ----------------------------------------------------------- +53 | val id: Id[Proc, Proc] = new Id[Proc, () -> Unit] // error + | ^ + | Type variable A of constructor Id cannot be instantiated to box () => Unit since + | that type captures the root capability `cap`. -- Error: tests/neg-custom-args/captures/reaches.scala:55:6 ------------------------------------------------------------ 55 | id(() => f.write()) // error | ^^^^^^^^^^^^^^^^^^^ | Local reach capability id* leaks into capture scope of method test -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reaches.scala:62:27 -------------------------------------- -62 | val f1: File^{id*} = id(f) // error, since now id(f): File^ +62 | val f1: File^{id*} = id(f) // error, since now id(f): File^ // error | ^^^^^ | Found: File^{f} | Required: File^{id*} @@ -39,14 +44,18 @@ -- Error: tests/neg-custom-args/captures/reaches.scala:79:10 ----------------------------------------------------------- 79 | ps.map((x, y) => compose1(x, y)) // error // error | ^ - | Local reach capability ps* leaks into capture scope of method mapCompose + | Local reach capability ps* leaks into capture scope of method mapCompose. + | To allow this, the parameter ps should be declared with a @use annotation -- Error: tests/neg-custom-args/captures/reaches.scala:79:13 ----------------------------------------------------------- 79 | ps.map((x, y) => compose1(x, y)) // error // error | ^ - | Local reach capability ps* leaks into capture scope of method mapCompose --- [E057] Type Mismatch Error: tests/neg-custom-args/captures/reaches.scala:53:51 -------------------------------------- -53 | val id: Id[Proc, Proc] = new Id[Proc, () -> Unit] // error - | ^ - | Type argument () -> Unit does not conform to lower bound () => Unit - | - | longer explanation available when compiling with `-explain` + | Local reach capability ps* leaks into capture scope of method mapCompose. + | To allow this, the parameter ps should be declared with a @use annotation +-- Error: tests/neg-custom-args/captures/reaches.scala:61:31 ----------------------------------------------------------- +61 | val leaked = usingFile[File^{id*}]: f => // error + | ^^^ + | id* cannot be tracked since its deep capture set is empty +-- Error: tests/neg-custom-args/captures/reaches.scala:62:18 ----------------------------------------------------------- +62 | val f1: File^{id*} = id(f) // error, since now id(f): File^ // error + | ^^^ + | id* cannot be tracked since its deep capture set is empty diff --git a/tests/neg-custom-args/captures/reaches.scala b/tests/neg-custom-args/captures/reaches.scala index c33ba80a668b..a9773b76f445 100644 --- a/tests/neg-custom-args/captures/reaches.scala +++ b/tests/neg-custom-args/captures/reaches.scala @@ -1,4 +1,4 @@ -import caps.unbox +import caps.use class File: def write(): Unit = ??? @@ -11,7 +11,7 @@ class Ref[T](init: T): def get: T = x def set(y: T) = { x = y } -def runAll0(@unbox xs: List[Proc]): Unit = +def runAll0(@use xs: List[Proc]): Unit = var cur: List[() ->{xs*} Unit] = xs while cur.nonEmpty do val next: () ->{xs*} Unit = cur.head @@ -21,7 +21,7 @@ def runAll0(@unbox xs: List[Proc]): Unit = usingFile: f => cur = (() => f.write()) :: Nil // error -def runAll1(@unbox xs: List[Proc]): Unit = +def runAll1(@use xs: List[Proc]): Unit = val cur = Ref[List[() ->{xs*} Unit]](xs) // OK, by revised VAR while cur.get.nonEmpty do val next: () ->{xs*} Unit = cur.get.head @@ -33,16 +33,16 @@ def runAll1(@unbox xs: List[Proc]): Unit = (() => f.write()) :: Nil // error def runAll2(xs: List[Proc]): Unit = - var cur: List[Proc] = xs + var cur: List[Proc] = xs // error while cur.nonEmpty do - val next: () => Unit = cur.head // error + val next: () => Unit = cur.head next() cur = cur.tail def runAll3(xs: List[Proc]): Unit = - val cur = Ref[List[Proc]](xs) + val cur = Ref[List[Proc]](xs) // error while cur.get.nonEmpty do - val next: () => Unit = cur.get.head // error + val next: () => Unit = cur.get.head next() cur.set(cur.get.tail: List[Proc]) @@ -58,8 +58,8 @@ def attack2 = val id: File^ -> File^ = x => x // val id: File^ -> EX C.File^C - val leaked = usingFile[File^{id*}]: f => - val f1: File^{id*} = id(f) // error, since now id(f): File^ + val leaked = usingFile[File^{id*}]: f => // error + val f1: File^{id*} = id(f) // error, since now id(f): File^ // error f1 class List[+A]: @@ -78,5 +78,5 @@ def compose1[A, B, C](f: A => B, g: B => C): A ->{f, g} C = def mapCompose[A](ps: List[(A => A, A => A)]): List[A ->{ps*} A] = ps.map((x, y) => compose1(x, y)) // error // error -def mapCompose2[A](@unbox ps: List[(A => A, A => A)]): List[A ->{ps*} A] = +def mapCompose2[A](@use ps: List[(A => A, A => A)]): List[A ->{ps*} A] = ps.map((x, y) => compose1(x, y)) diff --git a/tests/neg-custom-args/captures/reaches2.check b/tests/neg-custom-args/captures/reaches2.check index 03860ee4a01b..1e921ee92072 100644 --- a/tests/neg-custom-args/captures/reaches2.check +++ b/tests/neg-custom-args/captures/reaches2.check @@ -2,9 +2,9 @@ 8 | ps.map((x, y) => compose1(x, y)) // error // error | ^ |reference ps* is not included in the allowed capture set {} - |of an enclosing function literal with expected type ((box A ->{ps*} A, box A ->{ps*} A)) -> box (x$0: A^?) ->? (ex$15: caps.Exists) -> A^? + |of an enclosing function literal with expected type ((box A ->{ps*} A, box A ->{ps*} A)) -> box (x$0: A^?) ->? A^? -- Error: tests/neg-custom-args/captures/reaches2.scala:8:13 ----------------------------------------------------------- 8 | ps.map((x, y) => compose1(x, y)) // error // error | ^ |reference ps* is not included in the allowed capture set {} - |of an enclosing function literal with expected type ((box A ->{ps*} A, box A ->{ps*} A)) -> box (x$0: A^?) ->? (ex$15: caps.Exists) -> A^? + |of an enclosing function literal with expected type ((box A ->{ps*} A, box A ->{ps*} A)) -> box (x$0: A^?) ->? A^? diff --git a/tests/neg-custom-args/captures/real-try.check b/tests/neg-custom-args/captures/real-try.check index 6df092885384..7a4b12ac08f6 100644 --- a/tests/neg-custom-args/captures/real-try.check +++ b/tests/neg-custom-args/captures/real-try.check @@ -1,13 +1,13 @@ --- [E190] Potential Issue Warning: tests/neg-custom-args/captures/real-try.scala:38:4 ---------------------------------- -38 | b.x - | ^^^ - | Discarded non-Unit value of type () -> Unit. Add `: Unit` to discard silently. +-- [E190] Potential Issue Warning: tests/neg-custom-args/captures/real-try.scala:38:2 ---------------------------------- +38 | b + | ^ + | Discarded non-Unit value of type Cell[() -> Unit]. Add `: Unit` to discard silently. | | longer explanation available when compiling with `-explain` -- Error: tests/neg-custom-args/captures/real-try.scala:14:2 ----------------------------------------------------------- 14 | try // error | ^ - | result of `try` cannot have type () => Unit since + | The result of `try` cannot have type () => Unit since | that type captures the root capability `cap`. | This is often caused by a locally generated exception capability leaking as part of its result. 15 | () => foo(1) @@ -17,7 +17,7 @@ -- Error: tests/neg-custom-args/captures/real-try.scala:20:10 ---------------------------------------------------------- 20 | val x = try // error | ^ - | result of `try` cannot have type () => Unit since + | The result of `try` cannot have type () => Unit since | that type captures the root capability `cap`. | This is often caused by a locally generated exception capability leaking as part of its result. 21 | () => foo(1) @@ -27,7 +27,7 @@ -- Error: tests/neg-custom-args/captures/real-try.scala:26:10 ---------------------------------------------------------- 26 | val y = try // error | ^ - | result of `try` cannot have type () => Cell[Unit]^? since + | The result of `try` cannot have type () => Cell[Unit]^? since | that type captures the root capability `cap`. | This is often caused by a locally generated exception capability leaking as part of its result. 27 | () => Cell(foo(1)) @@ -37,7 +37,7 @@ -- Error: tests/neg-custom-args/captures/real-try.scala:32:10 ---------------------------------------------------------- 32 | val b = try // error | ^ - | result of `try` cannot have type Cell[box () => Unit]^? since + | The result of `try` cannot have type Cell[box () => Unit]^? since | the part box () => Unit of that type captures the root capability `cap`. | This is often caused by a locally generated exception capability leaking as part of its result. 33 | Cell(() => foo(1)) diff --git a/tests/neg-custom-args/captures/real-try.scala b/tests/neg-custom-args/captures/real-try.scala index 51f1a0fdea5a..32e976f654a9 100644 --- a/tests/neg-custom-args/captures/real-try.scala +++ b/tests/neg-custom-args/captures/real-try.scala @@ -1,5 +1,5 @@ -//> using options -source 3.4 -// (to make sure we use the sealed policy) + + import language.experimental.saferExceptions class Ex1 extends Exception("Ex1") @@ -35,4 +35,4 @@ def test(): Unit = case _: Ex1 => ??? case _: Ex2 => ??? - b.x + b diff --git a/tests/neg-custom-args/captures/refine-reach-shallow.scala b/tests/neg-custom-args/captures/refine-reach-shallow.scala index 525d33fdb7c5..f78c99f919af 100644 --- a/tests/neg-custom-args/captures/refine-reach-shallow.scala +++ b/tests/neg-custom-args/captures/refine-reach-shallow.scala @@ -5,14 +5,15 @@ def test1(): Unit = val g: IO^ => IO^{f*} = f // error def test2(): Unit = val f: [R] -> (IO^ => R) -> R = ??? - val g: [R] -> (IO^{f*} => R) -> R = f // error + val ff = f + val g: [R] -> (IO^{f*} => R) -> R = f // error // error def test3(): Unit = val f: [R] -> (IO^ -> R) -> R = ??? - val g: [R] -> (IO^{f*} -> R) -> R = f // error + val g: [R] -> (IO^{f*} -> R) -> R = f // error // error def test4(): Unit = val xs: List[IO^] = ??? val ys: List[IO^{xs*}] = xs // ok def test5(): Unit = val f: [R] -> (IO^ -> R) -> IO^ = ??? - val g: [R] -> (IO^ -> R) -> IO^{f*} = f // error - val h: [R] -> (IO^{f*} -> R) -> IO^ = f // error + val g: [R] -> (IO^ -> R) -> IO^{f*} = f // error // error + val h: [R] -> (IO^{f*} -> R) -> IO^ = f // error // error diff --git a/tests/neg-custom-args/captures/refine-withFile.scala b/tests/neg-custom-args/captures/refine-withFile.scala index 823b62711d05..e7958ab66fc8 100644 --- a/tests/neg-custom-args/captures/refine-withFile.scala +++ b/tests/neg-custom-args/captures/refine-withFile.scala @@ -4,5 +4,5 @@ trait File val useFile: [R] -> (path: String) -> (op: File^ -> R) -> R = ??? def main(): Unit = val f: [R] -> (path: String) -> (op: File^ -> R) -> R = useFile - val g: [R] -> (path: String) -> (op: File^{f*} -> R) -> R = f // error - val leaked = g[File^{f*}]("test")(f => f) // boom + val g: [R] -> (path: String) -> (op: File^{f*} -> R) -> R = f // error // error + val leaked = g[File^{f*}]("test")(f => f) // error diff --git a/tests/neg-custom-args/captures/spread-problem.scala b/tests/neg-custom-args/captures/spread-problem.scala index 579c7817b9c1..75f3a615cde8 100644 --- a/tests/neg-custom-args/captures/spread-problem.scala +++ b/tests/neg-custom-args/captures/spread-problem.scala @@ -2,7 +2,7 @@ import language.experimental.captureChecking trait Source[+T] -def race[T](@caps.unbox sources: (Source[T]^)*): Source[T]^{sources*} = ??? +def race[T](@caps.use sources: (Source[T]^)*): Source[T]^{sources*} = ??? def raceTwo[T](src1: Source[T]^, src2: Source[T]^): Source[T]^{} = race(Seq(src1, src2)*) // error diff --git a/tests/neg-custom-args/captures/try.check b/tests/neg-custom-args/captures/try.check index 77a5fc06e05a..72604451472c 100644 --- a/tests/neg-custom-args/captures/try.check +++ b/tests/neg-custom-args/captures/try.check @@ -1,17 +1,13 @@ --- Error: tests/neg-custom-args/captures/try.scala:25:3 ---------------------------------------------------------------- -23 | val a = handle[Exception, CanThrow[Exception]] { -24 | (x: CanThrow[Exception]) => x -25 | }{ // error (but could be better) - | ^ - | The expression's type box CT[Exception]^ is not allowed to capture the root capability `cap`. - | This usually means that a capability persists longer than its allowed lifetime. -26 | (ex: Exception) => ??? -27 | } +-- Error: tests/neg-custom-args/captures/try.scala:23:28 --------------------------------------------------------------- +23 | val a = handle[Exception, CanThrow[Exception]] { // error + | ^^^^^^^^^^^^^^^^^^^ + | Type variable R of method handle cannot be instantiated to box CT[Exception]^ since + | that type captures the root capability `cap`. -- Error: tests/neg-custom-args/captures/try.scala:30:65 --------------------------------------------------------------- 30 | (x: CanThrow[Exception]) => () => raise(new Exception)(using x) // error | ^ - | (x : CanThrow[Exception]) cannot be referenced here; it is not included in the allowed capture set {} - | of an enclosing function literal with expected type () ->? Nothing + | reference (x : CanThrow[Exception]) is not included in the allowed capture set {} + | of an enclosing function literal with expected type () ->? Nothing -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/try.scala:52:2 ------------------------------------------- 47 |val global: () -> Int = handle { 48 | (x: CanThrow[Exception]) => diff --git a/tests/neg-custom-args/captures/try.scala b/tests/neg-custom-args/captures/try.scala index 45a1b346a512..a85a18f69caa 100644 --- a/tests/neg-custom-args/captures/try.scala +++ b/tests/neg-custom-args/captures/try.scala @@ -20,9 +20,9 @@ def handle[E <: Exception, R <: Top](op: CT[E]^ => R)(handler: E => R): R = catch case ex: E => handler(ex) def test = - val a = handle[Exception, CanThrow[Exception]] { + val a = handle[Exception, CanThrow[Exception]] { // error (x: CanThrow[Exception]) => x - }{ // error (but could be better) + }{ (ex: Exception) => ??? } diff --git a/tests/neg-custom-args/captures/unbox-overrides.check b/tests/neg-custom-args/captures/unbox-overrides.check index b9a3be7bffbc..dbffc164b5c5 100644 --- a/tests/neg-custom-args/captures/unbox-overrides.check +++ b/tests/neg-custom-args/captures/unbox-overrides.check @@ -2,20 +2,20 @@ 8 | def foo(x: C): C // error | ^ |error overriding method foo in trait A of type (x: C): C; - | method foo of type (x: C): C has a parameter x with different @unbox status than the corresponding parameter in the overridden definition + | method foo of type (x: C): C has a parameter x with different @use status than the corresponding parameter in the overridden definition | | longer explanation available when compiling with `-explain` -- [E164] Declaration Error: tests/neg-custom-args/captures/unbox-overrides.scala:9:6 ---------------------------------- -9 | def bar(@unbox x: C): C // error +9 | def bar(@use x: C): C // error | ^ |error overriding method bar in trait A of type (x: C): C; - | method bar of type (x: C): C has a parameter x with different @unbox status than the corresponding parameter in the overridden definition + | method bar of type (x: C): C has a parameter x with different @use status than the corresponding parameter in the overridden definition | | longer explanation available when compiling with `-explain` -- [E164] Declaration Error: tests/neg-custom-args/captures/unbox-overrides.scala:15:15 -------------------------------- 15 |abstract class C extends A[C], B2 // error | ^ |error overriding method foo in trait A of type (x: C): C; - | method foo in trait B2 of type (x: C): C has a parameter x with different @unbox status than the corresponding parameter in the overridden definition + | method foo in trait B2 of type (x: C): C has a parameter x with different @use status than the corresponding parameter in the overridden definition | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/unbox-overrides.scala b/tests/neg-custom-args/captures/unbox-overrides.scala index 5abb5013bfbe..6164ca274eaf 100644 --- a/tests/neg-custom-args/captures/unbox-overrides.scala +++ b/tests/neg-custom-args/captures/unbox-overrides.scala @@ -1,15 +1,15 @@ -import caps.unbox +import caps.use trait A[X]: - def foo(@unbox x: X): X + def foo(@use x: X): X def bar(x: X): X trait B extends A[C]: def foo(x: C): C // error - def bar(@unbox x: C): C // error + def bar(@use x: C): C // error trait B2: def foo(x: C): C - def bar(@unbox x: C): C + def bar(@use x: C): C abstract class C extends A[C], B2 // error diff --git a/tests/neg-custom-args/captures/unbox.scala b/tests/neg-custom-args/captures/unbox.scala index 33702a954068..28feb5f89aff 100644 --- a/tests/neg-custom-args/captures/unbox.scala +++ b/tests/neg-custom-args/captures/unbox.scala @@ -1,4 +1,4 @@ -import language.`3.2` +import language.`3.5` type Proc = () => Unit val xs: List[Proc] = ??? diff --git a/tests/neg-custom-args/captures/unsound-reach-2.scala b/tests/neg-custom-args/captures/unsound-reach-2.scala index 5bea18bdccba..c7dfa117a2fe 100644 --- a/tests/neg-custom-args/captures/unsound-reach-2.scala +++ b/tests/neg-custom-args/captures/unsound-reach-2.scala @@ -1,5 +1,3 @@ -//> using options -source 3.4 -// (to make sure we use the sealed policy) import language.experimental.captureChecking trait Consumer[-T]: def apply(x: T): Unit @@ -11,7 +9,7 @@ def withFile[R](path: String)(op: Consumer[File]): R = ??? trait Foo[+X]: def use(x: File^)(op: Consumer[X]): Unit -class Bar extends Foo[File^]: +class Bar extends Foo[File^]: // error def use(x: File^)(op: Consumer[File^]): Unit = op.apply(x) def bad(): Unit = diff --git a/tests/neg-custom-args/captures/unsound-reach-3.scala b/tests/neg-custom-args/captures/unsound-reach-3.scala index 0063216e957e..c5cdfca9d87a 100644 --- a/tests/neg-custom-args/captures/unsound-reach-3.scala +++ b/tests/neg-custom-args/captures/unsound-reach-3.scala @@ -1,5 +1,5 @@ -//> using options -source 3.4 -// (to make sure we use the sealed policy) + + import language.experimental.captureChecking trait File: def close(): Unit @@ -8,7 +8,7 @@ def withFile[R](path: String)(op: File^ => R): R = ??? trait Foo[+X]: def use(x: File^): X -class Bar extends Foo[File^]: +class Bar extends Foo[File^]: // error def use(x: File^): File^ = x def bad(): Unit = @@ -16,8 +16,8 @@ def bad(): Unit = val boom: Foo[File^{backdoor*}] = backdoor var escaped: File^{backdoor*} = null - withFile("hello.txt"): f => // error - escaped = boom.use(f) + withFile("hello.txt"): f => + escaped = boom.use(f) // error // boom.use: (x: File^) -> File^{backdoor*}, it is a selection so reach capabilities are allowed // f: File^, so there is no reach capabilities diff --git a/tests/neg-custom-args/captures/unsound-reach-4.check b/tests/neg-custom-args/captures/unsound-reach-4.check index d359b298555e..ca95bf42ba59 100644 --- a/tests/neg-custom-args/captures/unsound-reach-4.check +++ b/tests/neg-custom-args/captures/unsound-reach-4.check @@ -1,6 +1,9 @@ --- Error: tests/neg-custom-args/captures/unsound-reach-4.scala:21:25 --------------------------------------------------- -21 | withFile("hello.txt"): f => // error - | ^ - | Reach capability backdoor* and universal capability cap cannot both - | appear in the type (f: File^) ->{backdoor*} Unit of this expression -22 | escaped = boom.use(f) +-- Error: tests/neg-custom-args/captures/unsound-reach-4.scala:13:18 --------------------------------------------------- +13 |class Bar extends Foo[File^]: // error + | ^^^^^^^^^^ + | Type variable X of trait Foo cannot be instantiated to File^ since + | that type captures the root capability `cap`. +-- Error: tests/neg-custom-args/captures/unsound-reach-4.scala:22:22 --------------------------------------------------- +22 | escaped = boom.use(f) // error + | ^^^^^^^^^^^ + | Local reach capability backdoor* leaks into capture scope of method bad diff --git a/tests/neg-custom-args/captures/unsound-reach-4.scala b/tests/neg-custom-args/captures/unsound-reach-4.scala index bc66085614f2..88fbc2f5c1de 100644 --- a/tests/neg-custom-args/captures/unsound-reach-4.scala +++ b/tests/neg-custom-args/captures/unsound-reach-4.scala @@ -1,5 +1,5 @@ -//> using options -source 3.4 -// (to make sure we use the sealed policy) + + import language.experimental.captureChecking trait File: def close(): Unit @@ -10,7 +10,7 @@ type F = File^ trait Foo[+X]: def use(x: F): X -class Bar extends Foo[File^]: +class Bar extends Foo[File^]: // error def use(x: F): File^ = x def bad(): Unit = @@ -18,5 +18,5 @@ def bad(): Unit = val boom: Foo[File^{backdoor*}] = backdoor var escaped: File^{backdoor*} = null - withFile("hello.txt"): f => // error - escaped = boom.use(f) + withFile("hello.txt"): f => + escaped = boom.use(f) // error diff --git a/tests/neg-custom-args/captures/unsound-reach-5.scala b/tests/neg-custom-args/captures/unsound-reach-5.scala new file mode 100644 index 000000000000..806de5093ecd --- /dev/null +++ b/tests/neg-custom-args/captures/unsound-reach-5.scala @@ -0,0 +1,22 @@ +class IO + +def f(xs: List[() => Unit]): () ->{xs*} Unit = () => + println(xs.head) // error + +def test(io: IO^)(ys: List[() ->{io} Unit]) = + val x = () => + val z = f(ys) + z() + val _: () -> Unit = x // !!! ys* gets lost + () + +def test(io: IO^) = + def ys: List[() ->{io} Unit] = ??? + val x = () => + val z = f(ys) + z() + val _: () -> Unit = x // !!! io gets lost + () + + + diff --git a/tests/neg-custom-args/captures/unsound-reach-6.scala b/tests/neg-custom-args/captures/unsound-reach-6.scala new file mode 100644 index 000000000000..b7306dca4190 --- /dev/null +++ b/tests/neg-custom-args/captures/unsound-reach-6.scala @@ -0,0 +1,22 @@ +class IO + +def f(xs: List[() => Unit]): () => Unit = () => + println(xs.head) // error + +def test(io: IO^)(ys: List[() ->{io} Unit]) = + val x = () => + val z = f(ys) + z() + val _: () -> Unit = x // !!! ys* gets lost + () + +def test(io: IO^) = + def ys: List[() ->{io} Unit] = ??? + val x = () => + val z = f(ys) + z() + val _: () -> Unit = x // !!! io gets lost + () + + + diff --git a/tests/neg-custom-args/captures/unsound-reach-7.scala b/tests/neg-custom-args/captures/unsound-reach-7.scala new file mode 100644 index 000000000000..df345cdf7f6d --- /dev/null +++ b/tests/neg-custom-args/captures/unsound-reach-7.scala @@ -0,0 +1,15 @@ +import language.experimental.captureChecking +import caps.{cap, use} + +trait IO +trait Async + +def main(io: IO^, async: Async^) = + def bad[X](ops: List[(X, () ->{io} Unit)])(f: () ->{ops*} Unit): () ->{io} Unit = f // error + def runOps(@use ops: List[(() => Unit, () => Unit)]): () ->{ops*} Unit = + () => ops.foreach((f1, f2) => { f1(); f2() }) + def delayOps(@use ops: List[(() ->{async} Unit, () ->{io} Unit)]): () ->{io} Unit = + val runner: () ->{ops*} Unit = runOps(ops) + val badRunner: () ->{io} Unit = bad[() ->{async} Unit](ops)(runner) + // it uses both async and io, but we losed track of async. + badRunner \ No newline at end of file diff --git a/tests/neg-custom-args/captures/unsound-reach.check b/tests/neg-custom-args/captures/unsound-reach.check index 4a6793d204c5..69794f569edb 100644 --- a/tests/neg-custom-args/captures/unsound-reach.check +++ b/tests/neg-custom-args/captures/unsound-reach.check @@ -1,12 +1,15 @@ --- Error: tests/neg-custom-args/captures/unsound-reach.scala:18:21 ----------------------------------------------------- -18 | boom.use(f): (f1: File^{backdoor*}) => // error +-- Error: tests/neg-custom-args/captures/unsound-reach.scala:9:18 ------------------------------------------------------ +9 |class Bar extends Foo[File^]: // error + | ^^^^^^^^^^ + | Type variable X of trait Foo cannot be instantiated to File^ since + | that type captures the root capability `cap`. +-- Error: tests/neg-custom-args/captures/unsound-reach.scala:14:19 ----------------------------------------------------- +14 |class Bar2 extends Foo2[File^]: // error + | ^ + | Type variable X of constructor Foo2 cannot be instantiated to box File^ since + | that type captures the root capability `cap`. +-- Error: tests/neg-custom-args/captures/unsound-reach.scala:23:21 ----------------------------------------------------- +23 | boom.use(f): (f1: File^{backdoor*}) => // error | ^ | Local reach capability backdoor* leaks into capture scope of method bad -19 | escaped = f1 --- [E164] Declaration Error: tests/neg-custom-args/captures/unsound-reach.scala:10:8 ----------------------------------- -10 | def use(x: File^)(op: File^ => Unit): Unit = op(x) // error, was OK using sealed checking - | ^ - | error overriding method use in trait Foo of type (x: File^)(op: box File^ => Unit): Unit; - | method use of type (x: File^)(op: File^ => Unit): Unit has incompatible type - | - | longer explanation available when compiling with `-explain` +24 | escaped = f1 diff --git a/tests/neg-custom-args/captures/unsound-reach.scala b/tests/neg-custom-args/captures/unsound-reach.scala index c3c31a7f32ff..3fb666c7c1fc 100644 --- a/tests/neg-custom-args/captures/unsound-reach.scala +++ b/tests/neg-custom-args/captures/unsound-reach.scala @@ -6,8 +6,13 @@ def withFile[R](path: String)(op: File^ => R): R = ??? trait Foo[+X]: def use(x: File^)(op: X => Unit): Unit -class Bar extends Foo[File^]: - def use(x: File^)(op: File^ => Unit): Unit = op(x) // error, was OK using sealed checking +class Bar extends Foo[File^]: // error + def use(x: File^)(op: File^ => Unit): Unit = op(x) // OK using sealed checking + +abstract class Foo2[+X](): + def use(x: File^)(op: X => Unit): Unit +class Bar2 extends Foo2[File^]: // error + def use(x: File^)(op: File^ => Unit): Unit = op(x) // OK using sealed checking def bad(): Unit = val backdoor: Foo[File^] = new Bar diff --git a/tests/neg-custom-args/captures/use-capset.check b/tests/neg-custom-args/captures/use-capset.check new file mode 100644 index 000000000000..cb330daf67f8 --- /dev/null +++ b/tests/neg-custom-args/captures/use-capset.check @@ -0,0 +1,19 @@ +-- Error: tests/neg-custom-args/captures/use-capset.scala:7:50 --------------------------------------------------------- +7 |private def g[C^] = (xs: List[Object^{C^}]) => xs.head // error + | ^^^^^^^ + | Capture set parameter C leaks into capture scope of method g. + | To allow this, the type C should be declared with a @use annotation +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/use-capset.scala:13:22 ----------------------------------- +13 | val _: () -> Unit = h // error: should be ->{io} + | ^ + | Found: (h : () ->{io} Unit) + | Required: () -> Unit + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/use-capset.scala:15:50 ----------------------------------- +15 | val _: () -> List[Object^{io}] -> Object^{io} = h2 // error, should be ->{io} + | ^^ + | Found: (h2 : () ->? (x$0: List[box Object^]^{}) ->{io} Object^{io}) + | Required: () -> List[box Object^{io}] -> Object^{io} + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/use-capset.scala b/tests/neg-custom-args/captures/use-capset.scala new file mode 100644 index 000000000000..6010e955f867 --- /dev/null +++ b/tests/neg-custom-args/captures/use-capset.scala @@ -0,0 +1,16 @@ +import caps.{use, CapSet} + + + +def f[C^](@use xs: List[Object^{C^}]): Unit = ??? + +private def g[C^] = (xs: List[Object^{C^}]) => xs.head // error + +private def g2[@use C^] = (xs: List[Object^{C^}]) => xs.head // ok + +def test(io: Object^)(@use xs: List[Object^{io}]): Unit = + val h = () => f(xs) + val _: () -> Unit = h // error: should be ->{io} + val h2 = () => g[CapSet^{io}] + val _: () -> List[Object^{io}] -> Object^{io} = h2 // error, should be ->{io} + diff --git a/tests/neg-custom-args/captures/use-override.scala b/tests/neg-custom-args/captures/use-override.scala new file mode 100644 index 000000000000..febb59ca4208 --- /dev/null +++ b/tests/neg-custom-args/captures/use-override.scala @@ -0,0 +1,15 @@ +import caps.use + +def test(io: Object^, async: Object^) = + + trait A: + def f(@use x: List[() ->{io} Unit]): Unit + + class B extends A: + def f(@use x: List[() => Unit]): Unit = // error, would be unsound if allowed + x.foreach(_()) + + class C extends A: + def f(@use x: List[() ->{io, async} Unit]): Unit = // error, this one could be soundly allowed actually + x.foreach(_()) + diff --git a/tests/neg-custom-args/captures/uses.check b/tests/neg-custom-args/captures/uses.check new file mode 100644 index 000000000000..d201d79133cf --- /dev/null +++ b/tests/neg-custom-args/captures/uses.check @@ -0,0 +1,28 @@ +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/uses.scala:8:17 ------------------------------------------ +8 | val _: D^{y} = d // error, should be ok + | ^ + | Found: (d : D^{x, y}) + | Required: D^{y} + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/uses.scala:9:13 ------------------------------------------ +9 | val _: D = d // error + | ^ + | Found: (d : D^{x, y}) + | Required: D + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/uses.scala:18:34 ----------------------------------------- +18 | val _: () ->{x} () ->{y} Unit = g // error, should be ok + | ^ + | Found: () ->{x, y} () ->{y} Unit + | Required: () ->{x} () ->{y} Unit + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/uses.scala:19:28 ----------------------------------------- +19 | val _: () -> () -> Unit = g // error + | ^ + | Found: () ->{x, y} () ->{y} Unit + | Required: () -> () -> Unit + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/uses.scala b/tests/neg-custom-args/captures/uses.scala new file mode 100644 index 000000000000..b872c7b03ec7 --- /dev/null +++ b/tests/neg-custom-args/captures/uses.scala @@ -0,0 +1,20 @@ +class C +def test(x: C^, y: C^) = + class D { + println(x) + def foo() = println(y) + } + val d = D() + val _: D^{y} = d // error, should be ok + val _: D = d // error + + val f = () => println(D()) + val _: () ->{x} Unit = f // ok + val _: () -> Unit = f // should be error + + def g = () => + println(x) + () => println(y) + val _: () ->{x} () ->{y} Unit = g // error, should be ok + val _: () -> () -> Unit = g // error + diff --git a/tests/neg-custom-args/captures/vars-simple.check b/tests/neg-custom-args/captures/vars-simple.check index e9671f775c22..2bc014e9a4e7 100644 --- a/tests/neg-custom-args/captures/vars-simple.check +++ b/tests/neg-custom-args/captures/vars-simple.check @@ -2,17 +2,16 @@ 15 | a = (g: String => String) // error | ^^^^^^^^^^^^^^^^^^^ | Found: String => String - | Required: box String ->{cap1, cap2} String - | - | Note that String => String cannot be box-converted to box String ->{cap1, cap2} String - | since at least one of their capture sets contains the root capability `cap` + | Required: String ->{cap1, cap2} String | | longer explanation available when compiling with `-explain` --- Error: tests/neg-custom-args/captures/vars-simple.scala:16:8 -------------------------------------------------------- +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/vars-simple.scala:16:8 ----------------------------------- 16 | a = g // error | ^ - | reference (cap3 : Cap) is not included in the allowed capture set {cap1, cap2} - | of an enclosing function literal with expected type box String ->{cap1, cap2} String + | Found: (x: String) ->{cap3} String + | Required: (x: String) ->{cap1, cap2} String + | + | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/vars-simple.scala:17:12 ---------------------------------- 17 | b = List(g) // error | ^^^^^^^ diff --git a/tests/neg-custom-args/captures/vars.check b/tests/neg-custom-args/captures/vars.check index 0d3c2e0f2e11..e4b1e71a2000 100644 --- a/tests/neg-custom-args/captures/vars.check +++ b/tests/neg-custom-args/captures/vars.check @@ -5,13 +5,16 @@ | | Note that reference (cap3 : Cap), defined in method scope | cannot be included in outer capture set {cap1} of variable a --- Error: tests/neg-custom-args/captures/vars.scala:25:8 --------------------------------------------------------------- +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/vars.scala:25:8 ------------------------------------------ 25 | a = g // error | ^ - | reference (cap3 : Cap) is not included in the allowed capture set {cap1} of variable a + | Found: (x: String) ->{cap3} String + | Required: (x$0: String) ->{cap1} String | | Note that reference (cap3 : Cap), defined in method scope | cannot be included in outer capture set {cap1} of variable a + | + | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/vars.scala:27:12 ----------------------------------------- 27 | b = List(g) // error | ^^^^^^^ diff --git a/tests/neg-custom-args/captures/vars.scala b/tests/neg-custom-args/captures/vars.scala index 5eb1e3fedda9..eb9719cd2adf 100644 --- a/tests/neg-custom-args/captures/vars.scala +++ b/tests/neg-custom-args/captures/vars.scala @@ -1,5 +1,5 @@ -//> using options -source 3.4 -// (to make sure we use the sealed policy) + + class CC type Cap = CC^ diff --git a/tests/neg-custom-args/captures/wf-reach-1.check b/tests/neg-custom-args/captures/wf-reach-1.check new file mode 100644 index 000000000000..6a3ac9771a11 --- /dev/null +++ b/tests/neg-custom-args/captures/wf-reach-1.check @@ -0,0 +1,4 @@ +-- Error: tests/neg-custom-args/captures/wf-reach-1.scala:2:17 --------------------------------------------------------- +2 | val y: Object^{x*} = ??? // error + | ^^ + | x* cannot be tracked since its deep capture set is empty diff --git a/tests/neg-custom-args/captures/wf-reach-1.scala b/tests/neg-custom-args/captures/wf-reach-1.scala new file mode 100644 index 000000000000..c8901c7ae4a8 --- /dev/null +++ b/tests/neg-custom-args/captures/wf-reach-1.scala @@ -0,0 +1,2 @@ +def test(x: List[() -> Unit]) = + val y: Object^{x*} = ??? // error diff --git a/tests/neg-custom-args/captures/widen-reach.check b/tests/neg-custom-args/captures/widen-reach.check index 06d21ff445d8..9fe1f2bd5de6 100644 --- a/tests/neg-custom-args/captures/widen-reach.check +++ b/tests/neg-custom-args/captures/widen-reach.check @@ -1,15 +1,15 @@ +-- Error: tests/neg-custom-args/captures/widen-reach.scala:8:18 -------------------------------------------------------- +8 |trait Bar extends Foo[IO^]: // error + | ^^^^^^^^ + | Type variable T of trait Foo cannot be instantiated to IO^ since + | that type captures the root capability `cap`. -- Error: tests/neg-custom-args/captures/widen-reach.scala:13:26 ------------------------------------------------------- 13 | val y2: IO^ -> IO^ = y1.foo // error | ^^^^^^ - | Local reach capability x* leaks into capture scope of method test + | Local reach capability x* leaks into capture scope of method test. + | To allow this, the parameter x should be declared with a @use annotation -- Error: tests/neg-custom-args/captures/widen-reach.scala:14:30 ------------------------------------------------------- 14 | val y3: IO^ -> IO^{x*} = y1.foo // error | ^^^^^^ - | Local reach capability x* leaks into capture scope of method test --- [E164] Declaration Error: tests/neg-custom-args/captures/widen-reach.scala:9:6 -------------------------------------- -9 | val foo: IO^ -> IO^ = x => x // error - | ^ - | error overriding value foo in trait Foo of type IO^ -> box IO^; - | value foo of type IO^ -> (ex$3: caps.Exists) -> IO^{ex$3} has incompatible type - | - | longer explanation available when compiling with `-explain` + | Local reach capability x* leaks into capture scope of method test. + | To allow this, the parameter x should be declared with a @use annotation diff --git a/tests/neg-custom-args/captures/widen-reach.scala b/tests/neg-custom-args/captures/widen-reach.scala index fa5eee1232df..9a9305640473 100644 --- a/tests/neg-custom-args/captures/widen-reach.scala +++ b/tests/neg-custom-args/captures/widen-reach.scala @@ -5,8 +5,8 @@ trait IO trait Foo[+T]: val foo: IO^ -> T -trait Bar extends Foo[IO^]: - val foo: IO^ -> IO^ = x => x // error +trait Bar extends Foo[IO^]: // error + val foo: IO^ -> IO^ = x => x def test(x: Foo[IO^]): Unit = val y1: Foo[IO^{x*}] = x diff --git a/tests/neg/i20503.scala b/tests/neg/i20503.scala index 3fb0573f6c2f..828b6ce71137 100644 --- a/tests/neg/i20503.scala +++ b/tests/neg/i20503.scala @@ -1,5 +1,5 @@ import language.experimental.captureChecking -import caps.unbox +import caps.use class List[+A]: def head: A = ??? @@ -8,7 +8,7 @@ class List[+A]: def foreach[U](f: A => U): Unit = ??? def nonEmpty: Boolean = ??? -def runOps(@unbox ops: List[() => Unit]): Unit = +def runOps(@use ops: List[() => Unit]): Unit = // See i20156, due to limitation in expressiveness of current system, // we could map over the list of impure elements. OK with existentials. ops.foreach(op => op()) diff --git a/tests/neg/leak-problem-unboxed.scala b/tests/neg/leak-problem-unboxed.scala index 7de3d84bfcca..aedd7c889112 100644 --- a/tests/neg/leak-problem-unboxed.scala +++ b/tests/neg/leak-problem-unboxed.scala @@ -1,5 +1,5 @@ import language.experimental.captureChecking -import caps.unbox +import caps.use // Some capabilities that should be used locally trait Async: @@ -9,12 +9,12 @@ def usingAsync[X](op: Async^ => X): X = ??? case class Box[+T](get: T) -def useBoxedAsync(@unbox x: Box[Async^]): Unit = +def useBoxedAsync(@use x: Box[Async^]): Unit = val t0 = x val t1 = t0.get // ok t1.read() -def useBoxedAsync1(@unbox x: Box[Async^]): Unit = x.get.read() // ok +def useBoxedAsync1(@use x: Box[Async^]): Unit = x.get.read() // ok def test(): Unit = diff --git a/tests/neg/leak-problem.scala b/tests/neg/leak-problem.scala index 354d54d86707..c842280c0587 100644 --- a/tests/neg/leak-problem.scala +++ b/tests/neg/leak-problem.scala @@ -1,4 +1,5 @@ import language.experimental.captureChecking +import caps.use // Some capabilities that should be used locally trait Async: @@ -16,12 +17,28 @@ def useBoxedAsync(x: Box[Async^]): Unit = def useBoxedAsync1(x: Box[Async^]): Unit = x.get.read() // error def test(): Unit = + def useBoxedAsync(@use x: Box[Async^]): Unit = + val t0 = x + val t1 = t0.get + t1.read() + + def useBoxedAsync1(@use x: Box[Async^]): Unit = x.get.read() + + val xs: Box[Async^] = ??? + val xsLambda = () => useBoxedAsync(xs) // error + val _: () ->{xs*} Unit = xsLambda + val _: () -> Unit = xsLambda // error + val useBoxedAsync2 = (x: Box[Async^]) => val t0 = x val t1 = x.get // error t1.read() - val f: Box[Async^] => Unit = (x: Box[Async^]) => useBoxedAsync(x) + val xsLambda2 = () => useBoxedAsync2(xs) + val _: () ->{xs*} Unit = xsLambda2 + val _: () -> Unit = xsLambda2 + + val f: Box[Async^] => Unit = (x: Box[Async^]) => useBoxedAsync(x) // error def boom(x: Async^): () ->{f} Unit = () => f(Box(x)) diff --git a/tests/pending/pos/function-contravariance.scala b/tests/pending/pos/function-contravariance.scala new file mode 100644 index 000000000000..4df88f890bed --- /dev/null +++ b/tests/pending/pos/function-contravariance.scala @@ -0,0 +1,10 @@ +import language.experimental.namedTuples + +class A: + type T + +class B extends A + +val f: (x: A) => x.T = ??? +val g: (x: B) => x.T = f // OK +val h: (x: A) => x.T = g // error diff --git a/tests/pos-custom-args/captures/Buffer.scala b/tests/pos-custom-args/captures/Buffer.scala index 2412e5b388ca..9ecd51ffa62a 100644 --- a/tests/pos-custom-args/captures/Buffer.scala +++ b/tests/pos-custom-args/captures/Buffer.scala @@ -10,7 +10,7 @@ trait Buffer[A]: val s = 10 // capture checking: we need the copy since we box/unbox on g* on the next line // TODO: This looks fishy, need to investigate - // Alternative would be to mark `f` as @unbox. It's not inferred + // Alternative would be to mark `f` as @use. It's not inferred // since `^ appears in a function result, not under a box. val newElems = new Array[(IterableOnce[A]^{f})](s) var i = 0 diff --git a/tests/pos-custom-args/captures/boxed-use.scala b/tests/pos-custom-args/captures/boxed-use.scala new file mode 100644 index 000000000000..5dbdab7a6935 --- /dev/null +++ b/tests/pos-custom-args/captures/boxed-use.scala @@ -0,0 +1,17 @@ +class Box[A](val elem: A) +class CoBox[+A](val elem: A) + +def applyAll[A](fs: Box[A => Unit], x: A): Box[() ->{fs*} Unit] = + Box(() => fs.elem(x)) + +def applyAllCo[A](fs: CoBox[A => Unit], x: A): CoBox[() ->{fs*} Unit] = + CoBox(() => fs.elem(x)) + +// Same with inferred result types +def test = + def applyAll[A](fs: Box[A => Unit], x: A) = + Box(() => fs.elem(x)) + + def applyAllCo[A](fs: CoBox[A => Unit], x: A) = + CoBox(() => fs.elem(x)) + diff --git a/tests/pos-custom-args/captures/dep-reach.scala b/tests/pos-custom-args/captures/dep-reach.scala index c81197aa738d..1ee6fc3d17f9 100644 --- a/tests/pos-custom-args/captures/dep-reach.scala +++ b/tests/pos-custom-args/captures/dep-reach.scala @@ -1,10 +1,10 @@ -import caps.unbox +import caps.use object Test: class C type Proc = () => Unit def f(c: C^, d: C^): () ->{c, d} Unit = - def foo(@unbox xs: Proc*): () ->{xs*} Unit = + def foo(@use xs: Proc*): () ->{xs*} Unit = xs.head val a: () ->{c} Unit = () => () val b: () ->{d} Unit = () => () @@ -13,7 +13,7 @@ object Test: def g(c: C^, d: C^): () ->{c, d} Unit = - def foo(@unbox xs: Seq[() => Unit]): () ->{xs*} Unit = + def foo(@use xs: Seq[() => Unit]): () ->{xs*} Unit = xs.head val a: () ->{c} Unit = () => () diff --git a/tests/pos-custom-args/captures/erased-methods.scala b/tests/pos-custom-args/captures/erased-methods.scala new file mode 100644 index 000000000000..911c779e08e5 --- /dev/null +++ b/tests/pos-custom-args/captures/erased-methods.scala @@ -0,0 +1,20 @@ +import language.experimental.saferExceptions +import language.experimental.erasedDefinitions +import language.experimental.captureChecking + +class Ex1 extends Exception("Ex1") +class Ex2 extends Exception("Ex2") +class Ex3 extends Exception("Ex3") + +def foo8a(i: Int) = + (erased xx1: CanThrow[Ex2]^) ?=> throw new Ex2 + +def foo9a(i: Int) + : (erased x$0: CanThrow[Ex3]^) + ?=> (erased x$1: CanThrow[Ex2]^) + ?=> (erased x$2: CanThrow[Ex1]^) + ?=> Unit + = (erased x$1: CanThrow[Ex3]^) + ?=> (erased x$2: CanThrow[Ex2]^) + ?=> (erased x$3: CanThrow[Ex1]^) + ?=> throw new Ex3 diff --git a/tests/pos-custom-args/captures/gears-problem-poly.scala b/tests/pos-custom-args/captures/gears-problem-poly.scala new file mode 100644 index 000000000000..fdbcf37a35a6 --- /dev/null +++ b/tests/pos-custom-args/captures/gears-problem-poly.scala @@ -0,0 +1,29 @@ +import language.experimental.captureChecking +import caps.{use, CapSet} + +trait Future[+T]: + def await: T + +trait Channel[+T]: + def read(): Ok[T] + +class Collector[T, C^](val futures: Seq[Future[T]^{C^}]): + val results: Channel[Future[T]^{C^}] = ??? +end Collector + +class Result[+T, +E]: + def get: T = ??? + +case class Err[+E](e: E) extends Result[Nothing, E] +case class Ok[+T](x: T) extends Result[T, Nothing] + +extension [T, C^](@use fs: Seq[Future[T]^{C^}]) + def awaitAllPoly = + val collector = Collector(fs) + val fut: Future[T]^{C^} = collector.results.read().get + +extension [T](@use fs: Seq[Future[T]^]) + def awaitAll = fs.awaitAllPoly + +def awaitExplicit[T](@use fs: Seq[Future[T]^]): Unit = + awaitAllPoly[T, CapSet^{fs*}](fs) diff --git a/tests/pos-custom-args/captures/hk-param.scala b/tests/pos-custom-args/captures/hk-param.scala index df4335069bbb..325a2b55a480 100644 --- a/tests/pos-custom-args/captures/hk-param.scala +++ b/tests/pos-custom-args/captures/hk-param.scala @@ -1,3 +1,5 @@ +//> using options -source 3.5 +// (to make sure we use the unsealed policy) /** Concrete collection type: View */ trait View[+A] extends Itable[A], ILike[A, [X] =>> View[X]^]: override def fromIterable[B](c: Itable[B]^): View[B]^{c} = ??? diff --git a/tests/pos-custom-args/captures/i15923-cases.scala b/tests/pos-custom-args/captures/i15923-cases.scala index 4b5a36f208ec..c48ca430c440 100644 --- a/tests/pos-custom-args/captures/i15923-cases.scala +++ b/tests/pos-custom-args/captures/i15923-cases.scala @@ -2,6 +2,10 @@ trait Cap { def use(): Int } type Id[X] = [T] -> (op: X => T) -> T def mkId[X](x: X): Id[X] = [T] => (op: X => T) => op(x) +def foo(x: Id[Cap^]) = { + x(_.use()) // OK under sealed policy +} + def bar(io: Cap^, x: Id[Cap^{io}]) = { x(_.use()) } diff --git a/tests/pos-custom-args/captures/i21620.scala b/tests/pos-custom-args/captures/i21620.scala deleted file mode 100644 index b2c382aa4c75..000000000000 --- a/tests/pos-custom-args/captures/i21620.scala +++ /dev/null @@ -1,11 +0,0 @@ -class C -def test(x: C^) = - def foo() = - x - () - val f = () => - // println() // uncomenting would give an error, but with - // a different way of handling curried functions should be OK - () => foo() - val _: () -> () ->{x} Unit = f - () diff --git a/tests/pos-custom-args/captures/i21646.scala b/tests/pos-custom-args/captures/i21646.scala new file mode 100644 index 000000000000..92aba9fda5d1 --- /dev/null +++ b/tests/pos-custom-args/captures/i21646.scala @@ -0,0 +1,13 @@ +import language.experimental.captureChecking +import caps.Capability + +trait File extends Capability + +class Resource[T <: Capability](gen: T): + def use[U](f: T => U): U = + f(gen) // OK, was error under unsealed + +@main def run = + val myFile: File = ??? + val r = Resource(myFile) // now ok, was error + () diff --git a/tests/pos-custom-args/captures/levels.scala b/tests/pos-custom-args/captures/levels.scala index cabd537442a5..4d9d759e86db 100644 --- a/tests/pos-custom-args/captures/levels.scala +++ b/tests/pos-custom-args/captures/levels.scala @@ -1,3 +1,5 @@ +//> using options -source 3.5 +// (to make sure we use the unsealed policy) class CC def test1(cap1: CC^) = @@ -14,10 +16,10 @@ def test2(cap1: CC^) = def setV(x: T): Unit = v = x def getV: T = v - val _ = Ref[String => String]((x: String) => x) // ok + val _ = Ref[String => String]((x: String) => x) val r = Ref((x: String) => x) def scope(cap3: CC^) = def g(x: String): String = if cap3 == cap3 then "" else "a" - r.setV(g) // error + r.setV(g) () diff --git a/tests/pos-custom-args/captures/path-use.scala b/tests/pos-custom-args/captures/path-use.scala index 5eb2b60fd218..629fa04315a7 100644 --- a/tests/pos-custom-args/captures/path-use.scala +++ b/tests/pos-custom-args/captures/path-use.scala @@ -1,4 +1,5 @@ import language.experimental.namedTuples +import caps.use class IO @@ -8,12 +9,14 @@ class C(val f: IO^): type Proc = () => Unit def test(io: IO^) = - val c = C(io) - val f = () => println(c.f) - val _: () ->{c.f} Unit = f + def test1(@use c: C { val f: IO^{io}}^{io}) = + val f = () => println(c.f) + val _: () ->{c.f} Unit = f - val x = c.procs - val _: List[() ->{c.procs*} Unit] = x + val x = c.procs + val _: List[() ->{c.procs*} Unit] = x + + val g = () => println(c.procs.head) + val _: () ->{c.procs*} Unit = g + test1(C(io)) - val g = () => println(c.procs.head) - val _: () ->{c.procs*} Unit = g diff --git a/tests/pos-custom-args/captures/reaches.scala b/tests/pos-custom-args/captures/reaches.scala index ab0da9b67d18..cbe88e60020b 100644 --- a/tests/pos-custom-args/captures/reaches.scala +++ b/tests/pos-custom-args/captures/reaches.scala @@ -1,4 +1,4 @@ -import caps.unbox +import caps.use class C def f(xs: List[C^]) = @@ -22,7 +22,7 @@ extension [A](x: A) def :: (xs: List[A]): List[A] = ??? object Nil extends List[Nothing] -def runAll(@unbox xs: List[Proc]): Unit = +def runAll(@use xs: List[Proc]): Unit = var cur: List[() ->{xs*} Unit] = xs // OK, by revised VAR while cur.nonEmpty do val next: () ->{xs*} Unit = cur.head diff --git a/tests/pos-custom-args/captures/vars1.scala b/tests/pos-custom-args/captures/vars1.scala index 451b8988364f..c8d887e38463 100644 --- a/tests/pos-custom-args/captures/vars1.scala +++ b/tests/pos-custom-args/captures/vars1.scala @@ -8,7 +8,7 @@ object Test: var defaultIncompleteHandler: ErrorHandler = ??? @uncheckedCaptures var incompleteHandler: ErrorHandler = defaultIncompleteHandler - private val x = incompleteHandler.unsafeUnbox + private val x = incompleteHandler val _ : ErrorHandler = x val _ = x(1, "a") diff --git a/tests/pos/Buffer.scala b/tests/pos/Buffer.scala index 2412e5b388ca..9ecd51ffa62a 100644 --- a/tests/pos/Buffer.scala +++ b/tests/pos/Buffer.scala @@ -10,7 +10,7 @@ trait Buffer[A]: val s = 10 // capture checking: we need the copy since we box/unbox on g* on the next line // TODO: This looks fishy, need to investigate - // Alternative would be to mark `f` as @unbox. It's not inferred + // Alternative would be to mark `f` as @use. It's not inferred // since `^ appears in a function result, not under a box. val newElems = new Array[(IterableOnce[A]^{f})](s) var i = 0 diff --git a/tests/pos/cc-poly-source-capability.scala b/tests/pos/cc-poly-source-capability.scala index 3b6c0bde1398..363f261dadc1 100644 --- a/tests/pos/cc-poly-source-capability.scala +++ b/tests/pos/cc-poly-source-capability.scala @@ -1,7 +1,7 @@ import language.experimental.captureChecking import annotation.experimental import caps.{CapSet, Capability} -import caps.unbox +import caps.use @experimental object Test: @@ -18,7 +18,7 @@ import caps.unbox def allListeners: Set[Listener^{X^}] = listeners - def test1(async1: Async, @unbox others: List[Async]) = + def test1(async1: Async, @use others: List[Async]) = val src = Source[CapSet^{async1, others*}] val lst1 = listener(async1) val lsts = others.map(listener) diff --git a/tests/pos/cc-poly-source.scala b/tests/pos/cc-poly-source.scala index 4cfbbaa06936..2de5c6d67340 100644 --- a/tests/pos/cc-poly-source.scala +++ b/tests/pos/cc-poly-source.scala @@ -1,7 +1,7 @@ import language.experimental.captureChecking import annotation.experimental import caps.{CapSet, Capability} -import caps.unbox +import caps.use @experimental object Test: @@ -25,7 +25,7 @@ import caps.unbox val ls = src.allListeners val _: Set[Listener^{lbl1, lbl2}] = ls - def test2(@unbox lbls: List[Label^]) = + def test2(@use lbls: List[Label^]) = def makeListener(lbl: Label^): Listener^{lbl} = ??? val listeners = lbls.map(makeListener) val src = Source[CapSet^{lbls*}] diff --git a/tests/pos/i18699.scala b/tests/pos/i18699.scala index 1937d7dca8c5..5a0a9357f774 100644 --- a/tests/pos/i18699.scala +++ b/tests/pos/i18699.scala @@ -1,9 +1,9 @@ import language.experimental.captureChecking -import caps.unbox +import caps.use trait Cap: def use: Int = 42 -def test2(@unbox cs: List[Cap^]): Unit = +def test2(@use cs: List[Cap^]): Unit = val t0: Cap^{cs*} = cs.head // error var t1: Cap^{cs*} = cs.head // error diff --git a/tests/pos/reach-capability.scala b/tests/pos/reach-capability.scala index 50ea479ec3c1..08e82a1dabe9 100644 --- a/tests/pos/reach-capability.scala +++ b/tests/pos/reach-capability.scala @@ -1,7 +1,7 @@ import language.experimental.captureChecking import annotation.experimental import caps.Capability -import caps.unbox +import caps.use @experimental object Test2: @@ -12,7 +12,7 @@ import caps.unbox class Listener - def test2(@unbox lbls: List[Label]) = + def test2(@use lbls: List[Label]) = def makeListener(lbl: Label): Listener^{lbl} = ??? val listeners = lbls.map(makeListener) // should work diff --git a/tests/pos/reach-problem.scala b/tests/pos/reach-problem.scala index d6b7b79011a6..f36a4af14ad2 100644 --- a/tests/pos/reach-problem.scala +++ b/tests/pos/reach-problem.scala @@ -1,11 +1,11 @@ import language.experimental.captureChecking -import caps.unbox +import caps.use class Box[T](items: Seq[T^]): def getOne: T^{items*} = ??? object Box: - def getOne[T](@unbox items: Seq[T^]): T^{items*} = + def getOne[T](@use items: Seq[T^]): T^{items*} = val bx = Box(items) bx.getOne /*