diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index 60309d4d83bd..b86734afcc0a 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -525,8 +525,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { def makeRetaining(parent: Tree, refs: List[Tree], annotName: TypeName)(using Context): Annotated = Annotated(parent, New(scalaAnnotationDot(annotName), List(refs))) - def makeCapsOf(id: Ident)(using Context): Tree = - TypeApply(Select(scalaDot(nme.caps), nme.capsOf), id :: Nil) + def makeCapsOf(tp: Tree)(using Context): Tree = + TypeApply(Select(scalaDot(nme.caps), nme.capsOf), tp :: Nil) def makeCapsBound()(using Context): Tree = makeRetaining( diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index 29c6528e36de..80d853efa56f 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -194,7 +194,8 @@ extension (tp: Type) true case tp: TermRef => ((tp.prefix eq NoPrefix) - || tp.symbol.is(ParamAccessor) && tp.prefix.isThisTypeOf(tp.symbol.owner) + || tp.symbol.isField && !tp.symbol.isStatic && ( + tp.prefix.isThisTypeOf(tp.symbol.owner) || tp.prefix.isTrackableRef) || tp.isRootCapability ) && !tp.symbol.isOneOf(UnstableValueFlags) case tp: TypeRef => @@ -221,19 +222,22 @@ 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 + val dcs = CaptureSet.ofTypeDeeply(tp.widen.stripCapturing) + 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 /** A type capturing `ref` */ def capturing(ref: CaptureRef)(using Context): Type = @@ -273,6 +277,28 @@ 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 @ ReachCapability(tp2) => tp2.pathRoot + case _ => tp + + /** 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.isOneOf(Param | ParamAccessor) + case prefix => prefix.isParamPath + 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. @@ -468,29 +494,23 @@ extension (tp: Type) end CheckContraCaps object narrowCaps extends TypeMap: - /** Has the variance been flipped at this point? */ - private var isFlipped: Boolean = 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 => + 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 @@ -507,6 +527,14 @@ extension (tp: Type) case _ => tp + /** Add implied captures as defined by `CaptureSet.addImplied`. */ + def withImpliedCaptures(using Context): Type = + if tp.isValueType && !tp.isAlwaysPure then + val implied = CaptureSet.addImplied()(CaptureSet.empty, tp) + if !implied.isAlwaysEmpty then capt.println(i"Add implied $implied to $tp") + tp.capturing(implied) + else tp + def level(using Context): Level = tp match case tp: TermRef => tp.symbol.ccLevel @@ -639,8 +667,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/CaptureRef.scala b/compiler/src/dotty/tools/dotc/cc/CaptureRef.scala index f00c6869cd80..bbaf0c7d2fa0 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureRef.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureRef.scala @@ -61,18 +61,19 @@ trait CaptureRef extends TypeProxy, ValueType: case tp: TermParamRef => tp.underlying.derivesFrom(defn.Caps_Exists) case _ => false - /** Normalize reference so that it can be compared with `eq` for equality */ - final def normalizedRef(using Context): CaptureRef = this match - case tp @ AnnotatedType(parent: CaptureRef, annot) if tp.isTrackableRef => - tp.derivedAnnotatedType(parent.normalizedRef, annot) - case tp: TermRef if tp.isTrackableRef => - tp.symbol.termRef - case _ => this + // With the support of pathes, we don't need to normalize the `TermRef`s anymore. + // /** Normalize reference so that it can be compared with `eq` for equality */ + // final def normalizedRef(using Context): CaptureRef = this match + // case tp @ AnnotatedType(parent: CaptureRef, annot) if tp.isTrackableRef => + // tp.derivedAnnotatedType(parent.normalizedRef, annot) + // case tp: TermRef if tp.isTrackableRef => + // tp.symbol.termRef + // case _ => this /** The capture set consisting of exactly this reference */ final def singletonCaptureSet(using Context): CaptureSet.Const = if mySingletonCaptureSet == null then - mySingletonCaptureSet = CaptureSet(this.normalizedRef) + mySingletonCaptureSet = CaptureSet(this) mySingletonCaptureSet.uncheckedNN /** The capture set of the type underlying this reference */ @@ -97,27 +98,45 @@ trait CaptureRef extends TypeProxy, ValueType: * x subsumes y ==> x* subsumes y, x subsumes y? * x subsumes y ==> x* subsumes y*, x? subsumes y? * x: x1.type /\ x1 subsumes y ==> x subsumes y + * TODO: Document path cases */ final def subsumes(y: CaptureRef)(using Context): Boolean = + + def subsumingRefs(x: Type, y: Type): Boolean = x match + case x: CaptureRef => y match + case y: CaptureRef => x.subsumes(y) + case _ => false + case _ => false + + def viaInfo(info: Type)(test: Type => Boolean): Boolean = info.match + case info: SingletonCaptureRef => test(info) + case info: AndType => test(info.tp1) || test(info.tp2) + case info: OrType => test(info.tp1) && test(info.tp2) + case _ => false + (this eq y) || this.isRootCapability || y.match case y: TermRef => - (y.prefix eq this) - || y.info.match - case y1: SingletonCaptureRef => this.subsumes(y1) + y.prefix.match + case ypre: CaptureRef => + this.subsumes(ypre) + || this.match + case x @ TermRef(xpre: CaptureRef, _) if x.symbol == y.symbol => + subsumingRefs(xpre, ypre) && subsumingRefs(ypre, xpre) + case _ => + false case _ => false + || viaInfo(y.info)(subsumingRefs(this, _)) case MaybeCapability(y1) => this.stripMaybe.subsumes(y1) case _ => false || this.match case ReachCapability(x1) => x1.subsumes(y.stripReach) - case x: TermRef => - x.info match - case x1: SingletonCaptureRef => x1.subsumes(y) - case _ => false + case x: TermRef => viaInfo(x.info)(subsumingRefs(_, y)) case x: TermParamRef => subsumesExistentially(x, y) case x: TypeRef => assumedContainsOf(x).contains(y) case _ => false + end subsumes def assumedContainsOf(x: TypeRef)(using Context): SimpleIdentitySet[CaptureRef] = CaptureSet.assumedContains.getOrElse(x, SimpleIdentitySet.empty) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala index 44d5e2cf4b88..51d1328d2ad6 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala @@ -374,7 +374,7 @@ object CaptureSet: def apply(elems: CaptureRef*)(using Context): CaptureSet.Const = if elems.isEmpty then empty - else Const(SimpleIdentitySet(elems.map(_.normalizedRef.ensuring(_.isTrackableRef))*)) + else Const(SimpleIdentitySet(elems.map(_.ensuring(_.isTrackableRef))*)) def apply(elems: Refs)(using Context): CaptureSet.Const = if elems.isEmpty then empty else Const(elems) @@ -508,7 +508,11 @@ object CaptureSet: !noUniversal else elem match case elem: TermRef if level.isDefined => - elem.symbol.ccLevel <= level + elem.prefix match + case prefix: CaptureRef => + levelOK(prefix) + case _ => + elem.symbol.ccLevel <= level case elem: ThisType if level.isDefined => elem.cls.ccLevel.nextInner <= level case ReachCapability(elem1) => @@ -1060,10 +1064,43 @@ object CaptureSet: case ref: (TermRef | TermParamRef) if ref.isMaxCapability => if ref.isTrackableRef then ref.singletonCaptureSet else CaptureSet.universal - case ReachCapability(ref1) => deepCaptureSet(ref1.widen) + case ReachCapability(ref1) => ref1.widen.deepCaptureSet .showing(i"Deep capture set of $ref: ${ref1.widen} = $result", capt) case _ => ofType(ref.underlying, followResult = true) + /** Add captures implied by a type. This means: if we have a contravarint, boxed + * capability in a function parameter and the capability is either `cap`, or a + * reach capability, or a capture set variable, add the same capability to the enclosing + * function arrow. For instance `List[() ->{ops*} Unit] -> Unit` would become + * `List[() ->{ops*} Unit] ->{ops*} Unit`. This is needed to make + * the `delayedRunops*.scala` tests produce errors. + * TODO: Investigate whether we can roll this into a widening rule like + * + * List[() ->{cap} Unit] -> Unit <: List[() ->{ops*} Unit] ->{ops*} Unit + * + * but not + * + * List[() ->{cap} Unit] -> Unit <: List[() ->{ops*} Unit] -> Unit + * + * It would mean that a reach capability can no longer be a subtype of `cap`. + */ + class addImplied(using Context) extends TypeAccumulator[CaptureSet]: + var boundVars: Set[CaptureRef] = Set.empty + def isImplied(tp: CaptureRef) = + (tp.isRootCapability || tp.isReach || tp.derivesFrom(defn.Caps_CapSet)) + && !boundVars.contains(tp.stripReach) + def apply(cs: CaptureSet, t: Type) = t match + case t @ CapturingType(parent, cs1) => + val cs2 = this(cs, parent) + if variance <= 0 && t.isBoxed then cs2 ++ cs1.filter(isImplied) + else cs2 + case t: MethodOrPoly => + val saved = boundVars + boundVars ++= t.paramRefs.asInstanceOf[List[CaptureRef]] + try foldOver(cs, t) finally boundVars = saved + case _ => + foldOver(cs, t) + /** Capture set of a type */ def ofType(tp: Type, followResult: Boolean)(using Context): CaptureSet = def recur(tp: Type): CaptureSet = trace(i"ofType $tp, ${tp.getClass} $followResult", show = true): @@ -1111,17 +1148,25 @@ 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. */ def ofTypeDeeply(tp: Type)(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) + 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 @ 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 b05ab8542137..ecd016aadb9a 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -13,7 +13,7 @@ import Trees.* import typer.RefChecks.{checkAllOverrides, checkSelfAgainstParents, OverridingPairsChecker} import typer.Checking.{checkBounds, checkAppliedTypesIn} import typer.ErrorReporting.{Addenda, NothingToAdd, err} -import typer.ProtoTypes.{AnySelectionProto, LhsProto} +import typer.ProtoTypes.{LhsProto, WildcardSelectionProto} import util.{SimpleIdentitySet, EqHashMap, EqHashSet, SrcPos, Property} import transform.{Recheck, PreRecheck, CapturedVars} import Recheck.* @@ -122,10 +122,6 @@ object CheckCaptures: * This check is performed at Typer. */ def checkWellformed(parent: Tree, ann: Tree)(using Context): Unit = - parent.tpe match - case _: SingletonType => - report.error(em"Singleton type $parent cannot have capture set", parent.srcPos) - case _ => def check(elem: Tree, pos: SrcPos): Unit = elem.tpe match case ref: CaptureRef => if !ref.isTrackableRef then @@ -187,6 +183,9 @@ object CheckCaptures: /** 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 + class CheckCaptures extends Recheck, SymTransformer: thisPhase => @@ -329,20 +328,22 @@ 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. + /** The next environment enclosing `env` that needs to be charged + * with free references. + * Skips environments directly enclosing environments of kind ClosureResult. + * @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 = + var nextEnv = env.outer + if env.owner.isConstructor then + if included(nextEnv) then nextEnv = nextEnv.outer + if env.kind == EnvKind.ClosureResult then + // skip this one + nextEnvToCharge(nextEnv, included) + else + nextEnv /** A description where this environment comes from */ private def provenance(env: Env)(using Context): String = @@ -356,112 +357,89 @@ class CheckCaptures extends Recheck, SymTransformer: else i"\nof the enclosing ${owner.showLocated}" - /** Include `sym` in the capture sets of all enclosing environments nested in the * the environment in which `sym` is defined. */ def markFree(sym: Symbol, pos: SrcPos)(using Context): Unit = - if sym.exists then - val ref = sym.termRef - if ref.isTracked then - forallOuterEnvsUpTo(sym.enclosure): env => + markFree(sym, sym.termRef, pos) + + def markFree(sym: Symbol, ref: TermRef, pos: SrcPos)(using Context): Unit = + if sym.exists && ref.isTracked then + def recur(env: Env): Unit = + if env.isOpen && env.owner != sym.enclosure then capt.println(i"Mark $sym with cs ${ref.captureSet} free in ${env.owner}") checkElem(ref, env.captured, pos, provenance(env)) + recur(nextEnvToCharge(env, _.owner != sym.enclosure)) + recur(curEnv) /** Make sure (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 */ def markFree(cs: CaptureSet, pos: SrcPos)(using Context): Unit = - if !cs.isAlwaysEmpty then - forallOuterEnvsUpTo(ctx.owner.topLevelClass): env => - // Whether a symbol is defined inside the owner of the environment? - inline def isContainedInEnv(sym: Symbol) = - if env.kind == EnvKind.NestedInOwner then - sym.isProperlyContainedIn(env.owner) - else - sym.isContainedIn(env.owner) - // 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) = !isContainedInEnv(sym) + // 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 recur(cs: CaptureSet, env: Env)(using Context): 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 => - c.stripReach match - case ref: NamedType => - val refSym = ref.symbol - val refOwner = refSym.owner - val isVisible = isVisibleFromEnv(refOwner) - 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) + val isVisible = c.stripReach.pathRoot match + case ref: NamedType => isVisibleFromEnv(ref.symbol.owner, env) + case ref: ThisType => isVisibleFromEnv(ref.cls, env) case _ => false + c match + case ReachCapability(c1) if !isVisible && !c1.isParamPath => + // 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. + // 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. + val underlying = CaptureSet.ofTypeDeeply(c1.widen) + capt.println(i"Widen reach $c to $underlying in ${env.owner}") + recur(underlying, env) + case _ => + isVisible checkSubset(included, env.captured, pos, provenance(env)) capt.println(i"Include call or box capture $included from $cs in ${env.owner} --> ${env.captured}") + recur(included, nextEnvToCharge(env, !_.owner.isStaticOwner)) + recur(cs, curEnv) end markFree /** 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 _ => - - def markPrefixCalls(tree: Tree): Unit = tree match - case tree: GenericApply => - prefixCalls.add(tree) - markUnboxedArgs(tree) - markPrefixCalls(tree.fun) - case _ => - - markUnboxedArgs(call) - markPrefixCalls(call.fun) - val res = eval() - includeCallCaptures(meth, call.srcPos) - res - end handleCall + 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) 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 + includeCallCaptures(tree.symbol, tree.symbol.info, tree.srcPos) + else if !tree.symbol.isStatic then //debugShowEnvs() - markFree(tree.symbol, tree.srcPos) + 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) super.recheckIdent(tree, pt) + override def selectionProto(tree: Select, pt: Type)(using Context): Type = + val sym = tree.symbol + if !sym.isOneOf(UnstableValueFlags) && !sym.isStatic then PathSelectionProto(sym, pt) + else super.selectionProto(tree, pt) + /** A specialized implementation of the selection rule. * * E |- f: T{ m: R^Cr }^{f} @@ -488,21 +466,25 @@ class CheckCaptures extends Recheck, SymTransformer: case _ => denot val selType = recheckSelection(tree, qualType, name, disambiguate) - val selCs = selType.widen.captureSet - if selCs.isAlwaysEmpty - || selType.widen.isBoxedCapturing + val selWiden = selType.widen + + if pt == LhsProto || qualType.isBoxedCapturing - || pt == LhsProto + || selType.isTrackableRef + || selWiden.isBoxedCapturing + || selWiden.captureSet.isAlwaysEmpty then selType else val qualCs = qualType.captureSet - capt.println(i"pick one of $qualType, ${selType.widen}, $qualCs, $selCs in $tree") + val selCs = selType.captureSet + capt.println(i"pick one of $qualType, ${selType.widen}, $qualCs, $selCs ${selWiden.captureSet} in $tree") + if qualCs.mightSubcapture(selCs) && !selCs.mightSubcapture(qualCs) && !pt.stripCapturing.isInstanceOf[SingletonType] then - selType.widen.stripCapturing.capturing(qualCs) + selWiden.stripCapturing.capturing(qualCs) .showing(i"alternate type for select $tree: $selType --> $result, $qualCs / $selCs", capt) else selType @@ -542,15 +524,16 @@ class CheckCaptures extends Recheck, SymTransformer: tp.derivedCapturingType(forceBox(parent), refs) mapArgUsing(forceBox) else - handleCall(meth, tree, () => super.recheckApply(tree, pt)) + val res = super.recheckApply(tree, pt) + includeCallCaptures(meth, res, tree.srcPos) + res end recheckApply 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) + capt.println(i"charging deep capture set of $arg: ${argType} = ${argType.deepCaptureSet}") + markFree(argType.deepCaptureSet, arg.srcPos) argType /** A specialized implementation of the apply rule. @@ -561,27 +544,18 @@ class CheckCaptures extends Recheck, SymTransformer: * --------------------- * E |- f(a): Tr^C * - * If the function `f` does not have an `@unboxed` 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 - * unbox reach capabilities over its formal parameter. Therefore, the approximation - * would be `Cq \union dcs(Ca)` instead. + * If the type of the function `f` does not mention any formal parameters + * any capabilities of the result of the application must already be present in + * the application. So an upper approximation of the result capture set is Cq \union Ca, + * where `Ca` is the deep capture set of the argument. * If the approximation is known to subcapture the declared result Cr, we pick it for C - * otherwise we pick Cr. + * otherwise we pick Cr. ??? */ protected override def recheckApplication(tree: Apply, qualType: Type, funType: MethodType, argTypes: List[Type])(using Context): Type = 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 + val argCaptures = argTypes.map(_.deepCaptureSet) appType match case appType @ CapturingType(appType1, refs) if qualType.exists @@ -676,8 +650,10 @@ class CheckCaptures extends Recheck, SymTransformer: 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 res = Existential.toCap(super.recheckTypeApply(tree, pt)) + includeCallCaptures(meth, 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 @@ -1061,7 +1037,7 @@ class CheckCaptures extends Recheck, SymTransformer: if actualBoxed eq actual then // Only `addOuterRefs` when there is no box adaptation - expected1 = addOuterRefs(expected1, actual) + expected1 = addOuterRefs(expected1, actual, tree.srcPos) if isCompatible(actualBoxed, expected1) then if debugSuccesses then tree match case Ident(_) => @@ -1102,8 +1078,12 @@ class CheckCaptures extends Recheck, SymTransformer: * that are outside `Cls`. These are all accessed through `Cls.this`, * so we can assume they are already accounted for by `Ce` and adding * them explicitly to `Ce` changes nothing. + * - To make up for this, we also add these variables to the capture set of `Cls`, + * so that all instances of `Cls` will capture these outer references. + * So in a sense we use `{Cls.this}` as a placeholder for certain outer captures. + * that we needed to be subsumed by `Cls.this`. */ - private def addOuterRefs(expected: Type, actual: Type)(using Context): Type = + private def addOuterRefs(expected: Type, actual: Type, pos: SrcPos)(using Context): Type = def isPure(info: Type): Boolean = info match case info: PolyType => isPure(info.resType) @@ -1116,16 +1096,30 @@ class CheckCaptures extends Recheck, SymTransformer: else isPure(owner.info) && isPureContext(owner.owner, limit) // Augment expeced capture set `erefs` by all references in actual capture - // set `arefs` that are outside some `this.type` reference in `erefs` + // set `arefs` that are outside some `C.this.type` reference in `erefs` for an enclosing + // class `C`. If an added reference is not a ThisType itself, add it to the capture set + // (i.e. use set) of the `C`. This makes sure that any outer reference implicitly subsumed + // by `C.this` becomes a capture reference of every instance of `C`. def augment(erefs: CaptureSet, arefs: CaptureSet): CaptureSet = (erefs /: erefs.elems): (erefs, eref) => eref match case eref: ThisType if isPureContext(ctx.owner, eref.cls) => - erefs ++ arefs.filter { - case aref: TermRef => eref.cls.isProperlyContainedIn(aref.symbol.owner) - case aref: ThisType => eref.cls.isProperlyContainedIn(aref.cls) - case _ => false - } + 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) + && !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. + then + def provenance = + i""" of the enclosing class ${eref.cls}. + |The reference was included since we tried to establish that $arefs <: $erefs""" + checkElem(outerRef, capturedVars(eref.cls), pos, provenance) + + erefs ++ outerRefs case _ => erefs @@ -1272,6 +1266,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 => @@ -1292,7 +1292,7 @@ class CheckCaptures extends Recheck, SymTransformer: else val widened = improveCaptures(actual.widen.dealiasKeepAnnots, actual) val adapted = adaptBoxed( - widened.withReachCaptures(actual), expected, pos, + widened.withReachCaptures(actual).withImpliedCaptures, expected, pos, covariant = true, alwaysConst = false, boxErrors) if adapted eq widened then actual else adapted.showing(i"adapt boxed $actual vs $expected = $adapted", capt) @@ -1310,7 +1310,7 @@ class CheckCaptures extends Recheck, SymTransformer: * @param sym symbol of the field definition that is being checked */ override def checkSubType(actual: Type, expected: Type)(using Context): Boolean = - val expected1 = alignDependentFunction(addOuterRefs(expected, actual), actual.stripCapturing) + val expected1 = alignDependentFunction(addOuterRefs(expected, actual, srcPos), actual.stripCapturing) val actual1 = val saved = curEnv try @@ -1332,21 +1332,6 @@ class CheckCaptures extends Recheck, SymTransformer: !setup.isPreCC(overriding) && !setup.isPreCC(overridden) override def checkInheritedTraitParameters: Boolean = false - - /** Check that overrides don't change the @unbox 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 - report.error( - OverrideError( - i"has a parameter ${param1.name} with different @unbox 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 - ) end OverridingPairsCheckerCC def traverse(t: Tree)(using Context) = diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index 22e7899eeea1..ddd9e240be5d 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -518,6 +518,11 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: info match case mt: MethodOrPoly => val psyms = psymss.head + // TODO: the substitution does not work for param-dependent method types. + // For example, `(x: T, y: x.f.type) => Unit`. In this case, when we + // 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. mt.companion(mt.paramNames)( mt1 => if !paramSignatureChanges && !mt.isParamDependent && prevLambdas.isEmpty then @@ -746,7 +751,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 = diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index f95bb3cea351..68ca9f0dafc8 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -1057,7 +1057,6 @@ 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") diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index aba8c3bb31fd..60bac8221e1d 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1796,7 +1796,7 @@ object Types extends TypeUtils { /** Is this either not a method at all, or a parameterless method? */ final def isParameterless(using Context): Boolean = stripPoly match { - case mt: MethodType => false + case mt: MethodOrPoly => false case _ => true } diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index 8a173faa3cec..96f09a0d6214 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -1548,21 +1548,23 @@ object Parsers { case _ => None } - /** CaptureRef ::= ident [`*` | `^`] | `this` + /** CaptureRef ::= (ident | `this`) [`*` | `^`] */ def captureRef(): Tree = - if in.token == THIS then simpleRef() - else - val id = termIdent() - if isIdent(nme.raw.STAR) then - in.nextToken() - atSpan(startOffset(id)): - PostfixOp(id, Ident(nme.CC_REACH)) - else if isIdent(nme.UPARROW) then - in.nextToken() - atSpan(startOffset(id)): - makeCapsOf(cpy.Ident(id)(id.name.toTypeName)) - else id + val ref = singleton() + if isIdent(nme.raw.STAR) then + in.nextToken() + atSpan(startOffset(ref)): + PostfixOp(ref, Ident(nme.CC_REACH)) + else if isIdent(nme.UPARROW) then + in.nextToken() + def toTypeSel(r: Tree): Tree = r match + case id: Ident => cpy.Ident(id)(id.name.toTypeName) + case Select(qual, id) => Select(qual, id.toTypeName) + case _ => r + atSpan(startOffset(ref)): + makeCapsOf(toTypeSel(ref)) + else ref /** CaptureSet ::= `{` CaptureRef {`,` CaptureRef} `}` -- under captureChecking */ diff --git a/compiler/src/dotty/tools/dotc/transform/Recheck.scala b/compiler/src/dotty/tools/dotc/transform/Recheck.scala index 03f0001110d3..26a75215bab1 100644 --- a/compiler/src/dotty/tools/dotc/transform/Recheck.scala +++ b/compiler/src/dotty/tools/dotc/transform/Recheck.scala @@ -12,7 +12,7 @@ import DenotTransformers.{DenotTransformer, IdentityDenotTransformer, SymTransfo import NamerOps.linkConstructorParams import NullOpsDecorator.stripNull import typer.ErrorReporting.err -import typer.ProtoTypes.* +import typer.ProtoTypes.{AnySelectionProto, LhsProto} import typer.TypeAssigner.seqLitType import typer.ConstFold import typer.ErrorReporting.{Addenda, NothingToAdd} @@ -203,13 +203,12 @@ abstract class Recheck extends Phase, SymTransformer: tree.tpe def recheckSelect(tree: Select, pt: Type)(using Context): Type = - recheckSelection(tree, recheckSelectQualifier(tree), tree.name, pt) + recheckSelection(tree, + recheck(tree.qualifier, selectionProto(tree, pt)).widenIfUnstable, + tree.name, pt) - def recheckSelectQualifier(tree: Select)(using Context): Type = - val proto = - if tree.symbol == defn.Any_asInstanceOf then WildcardType - else AnySelectionProto - recheck(tree.qualifier, proto).widenIfUnstable + def selectionProto(tree: Select, pt: Type)(using Context): Type = + if tree.symbol == defn.Any_asInstanceOf then WildcardType else AnySelectionProto def recheckSelection(tree: Select, qualType: Type, name: Name, sharpen: Denotation => Denotation)(using Context): Type = @@ -308,7 +307,7 @@ abstract class Recheck extends Phase, SymTransformer: def recheckApply(tree: Apply, pt: Type)(using Context): Type = val (funtpe0, qualType) = tree.fun match case fun: Select => - val qualType = recheckSelectQualifier(fun) + val qualType = recheck(fun.qualifier, selectionProto(fun, WildcardType)).widenIfUnstable (recheckSelection(fun, qualType, fun.name, WildcardType), qualType) case _ => (recheck(tree.fun), NoType) diff --git a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala index a69a63d1ceef..53e0b456ed9a 100644 --- a/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala +++ b/compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala @@ -324,6 +324,8 @@ object ProtoTypes { case tp: UnapplyFunProto => new UnapplySelectionProto(name, nameSpan) case tp => SelectionProto(name, IgnoredProto(tp), typer, privateOK = true, nameSpan) + class WildcardSelectionProto extends SelectionProto(nme.WILDCARD, WildcardType, NoViewsAllowed, true, NoSpan) + /** A prototype for expressions [] that are in some unspecified selection operation * * [].?: ? @@ -332,9 +334,9 @@ object ProtoTypes { * operation is further selection. In this case, the expression need not be a value. * @see checkValue */ - @sharable object AnySelectionProto extends SelectionProto(nme.WILDCARD, WildcardType, NoViewsAllowed, true, NoSpan) + @sharable object AnySelectionProto extends WildcardSelectionProto - @sharable object SingletonTypeProto extends SelectionProto(nme.WILDCARD, WildcardType, NoViewsAllowed, true, NoSpan) + @sharable object SingletonTypeProto extends WildcardSelectionProto /** A prototype for selections in pattern constructors */ class UnapplySelectionProto(name: Name, nameSpan: Span) extends SelectionProto(name, WildcardType, NoViewsAllowed, true, nameSpan) diff --git a/library/src/scala/caps.scala b/library/src/scala/caps.scala index 9911ef920116..b56724422b25 100644 --- a/library/src/scala/caps.scala +++ b/library/src/scala/caps.scala @@ -52,11 +52,6 @@ import annotation.{experimental, compileTimeOnly, retainsCap} */ final class untrackedCaptures extends annotation.StaticAnnotation - /** 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 - object unsafe: extension [T](x: T) diff --git a/scala2-library-cc/src/scala/collection/SeqView.scala b/scala2-library-cc/src/scala/collection/SeqView.scala index 292dc61ddaa8..d1befe3f0988 100644 --- a/scala2-library-cc/src/scala/collection/SeqView.scala +++ b/scala2-library-cc/src/scala/collection/SeqView.scala @@ -212,7 +212,7 @@ object SeqView { override def sorted[B1 >: A](implicit ord1: Ordering[B1]): SeqView[A]^{this} = if (ord1 == Sorted.this.ord) outer.unsafeAssumePure else if (ord1.isReverseOf(Sorted.this.ord)) this - else new Sorted(elems, len, ord1) + else new Sorted(elems, len, ord1).asInstanceOf // !!! asInstanceOf needed after adding addImplied widening } @volatile private[this] var evaluated = false diff --git a/scala2-library-cc/src/scala/collection/View.scala b/scala2-library-cc/src/scala/collection/View.scala index 31c544a46beb..132934dbe3bd 100644 --- a/scala2-library-cc/src/scala/collection/View.scala +++ b/scala2-library-cc/src/scala/collection/View.scala @@ -150,7 +150,10 @@ object View extends IterableFactory[View] { object Filter { def apply[A](underlying: Iterable[A]^, p: A => Boolean, isFlipped: Boolean): Filter[A]^{underlying, p} = underlying match { - case filter: Filter[A] if filter.isFlipped == isFlipped => new Filter(filter.underlying, a => filter.p(a) && p(a), isFlipped) + case filter: Filter[A] if filter.isFlipped == isFlipped => + new Filter(filter.underlying, a => filter.p(a) && p(a), isFlipped) + .asInstanceOf[Filter[A]^{underlying, p}] + // !!! asInstanceOf needed once paths were added, see path-patmat-should-be-pos.scala for minimization case _ => new Filter(underlying, p, isFlipped) } } diff --git a/scala2-library-cc/src/scala/collection/immutable/LazyListIterable.scala b/scala2-library-cc/src/scala/collection/immutable/LazyListIterable.scala index 2f7b017a6729..ca55448991bd 100644 --- a/scala2-library-cc/src/scala/collection/immutable/LazyListIterable.scala +++ b/scala2-library-cc/src/scala/collection/immutable/LazyListIterable.scala @@ -384,7 +384,7 @@ final class LazyListIterable[+A] private(@untrackedCaptures lazyState: () => Laz * @param suffix The collection that gets appended to this lazy list * @return The lazy list containing elements of this lazy list and the iterable object. */ - def lazyAppendedAll[B >: A](suffix: => collection.IterableOnce[B]^): LazyListIterable[B]^{this, suffix} = + def lazyAppendedAll[B >: A](suffix: => collection.IterableOnce[B]^): LazyListIterable[B]^{this, suffix*} = newLL { if (isEmpty) suffix match { case lazyList: LazyListIterable[B] => lazyList.state // don't recompute the LazyListIterable @@ -497,7 +497,7 @@ final class LazyListIterable[+A] private(@untrackedCaptures lazyState: () => Laz * * $preservesLaziness */ - def prepended[B >: A](elem: B): LazyListIterable[B] = newLL(sCons(elem, this)) + def prepended[B >: A](elem: B): LazyListIterable[B]^{this} = newLL(sCons(elem, this)) /** @inheritdoc * @@ -1137,7 +1137,7 @@ object LazyListIterable extends IterableFactory[LazyListIterable] { /** Construct a LazyListIterable consisting of the concatenation of the given LazyListIterable and * another LazyListIterable. */ - def #:::[B >: A](prefix: LazyListIterable[B]^): LazyListIterable[B]^{prefix, l} = prefix lazyAppendedAll l + def #:::[B >: A](prefix: LazyListIterable[B]^): LazyListIterable[B]^{prefix, l*} = prefix lazyAppendedAll l object #:: { def unapply[A](s: LazyListIterable[A]^): Option[(A, LazyListIterable[A]^{s})] = @@ -1155,7 +1155,7 @@ object LazyListIterable extends IterableFactory[LazyListIterable] { /** Creates a State from an Iterator, with another State appended after the Iterator * is empty. */ - private def stateFromIteratorConcatSuffix[A](it: Iterator[A]^)(suffix: => State[A]^): State[A]^{it, suffix} = + private def stateFromIteratorConcatSuffix[A](it: Iterator[A]^)(suffix: => State[A]^): State[A]^{it, suffix*} = if (it.hasNext) sCons(it.next(), newLL(stateFromIteratorConcatSuffix(it)(suffix))) else suffix @@ -1366,7 +1366,9 @@ object LazyListIterable extends IterableFactory[LazyListIterable] { case SerializeEnd => initRead = true case a => init += a.asInstanceOf[A] } - val tail = in.readObject().asInstanceOf[LazyListIterable[A]] + val tail: LazyListIterable[A] = in.readObject().asInstanceOf[LazyListIterable[A]] + // Explicit type annotation needed so that tail.state below is dropped from capture set. + // Before paths were added, it was tail that was added, and the `asSeenFrom` to a pure type made it work. // scala/scala#10118: caution that no code path can evaluate `tail.state` // before the resulting LazyListIterable is returned val it = init.toList.iterator diff --git a/tests/neg-custom-args/captures/cc-selftype-unsound.scala b/tests/neg-custom-args/captures/cc-selftype-unsound.scala index 5aaaf5e8b8ca..07ce8c1d57d2 100644 --- a/tests/neg-custom-args/captures/cc-selftype-unsound.scala +++ b/tests/neg-custom-args/captures/cc-selftype-unsound.scala @@ -11,5 +11,5 @@ def magic(l: Logger^): Logger = Boxed[Logger^{this}](l) // error val x = new Foo val y = x.foo.unbox // y: Logger^{x} - val z: Logger = y // now the capability becomes pure + val z: Logger = y // error z diff --git a/tests/neg-custom-args/captures/class-contra.check b/tests/neg-custom-args/captures/class-contra.check index 9fc009ac3d48..808118bd1795 100644 --- a/tests/neg-custom-args/captures/class-contra.check +++ b/tests/neg-custom-args/captures/class-contra.check @@ -1,10 +1,7 @@ --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/class-contra.scala:12:39 --------------------------------- -12 | def fun(x: K{val f: T^{a}}) = x.setf(a) // error - | ^ - | Found: (a : T^{x, y}) - | Required: T^{} - | - | Note that a capability (K.this.f : T^) in a capture set appearing in contravariant position - | was mapped to (x.f : T^{a}) which is not a capability. Therefore, it was under-approximated to the empty set. +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/class-contra.scala:12:40 --------------------------------- +12 | def fun1(k: K{val f: T^{a}}) = k.setf(a) // error + | ^ + | Found: (a : T^{x, y}) + | Required: T^{k.f} | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/class-contra.scala b/tests/neg-custom-args/captures/class-contra.scala index 210fd4e331f1..8ef8e7485a18 100644 --- a/tests/neg-custom-args/captures/class-contra.scala +++ b/tests/neg-custom-args/captures/class-contra.scala @@ -9,5 +9,6 @@ class T def test(x: Cap, y: Cap) = val a: T^{x, y} = ??? - def fun(x: K{val f: T^{a}}) = x.setf(a) // error + def fun1(k: K{val f: T^{a}}) = k.setf(a) // error + def fun2(k: K{val f: a.type}) = k.setf(a) () \ No newline at end of file diff --git a/tests/neg-custom-args/captures/delayedRunops.check b/tests/neg-custom-args/captures/delayedRunops.check new file mode 100644 index 000000000000..179d4e62a0e4 --- /dev/null +++ b/tests/neg-custom-args/captures/delayedRunops.check @@ -0,0 +1,15 @@ +-- Error: tests/neg-custom-args/captures/delayedRunops.scala:15:13 ----------------------------------------------------- +15 | 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:21:13 ----------------------------------------------------- +21 | runOps(ops1) // 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/delayedRunops.scala:27:13 ----------------------------------------------------- +27 | 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..8fcaf87b3b53 --- /dev/null +++ b/tests/neg-custom-args/captures/delayedRunops.scala @@ -0,0 +1,27 @@ +import language.experimental.captureChecking + + // ok + def runOps(ops: List[() => Unit]): Unit = + ops.foreach(op => op()) + + // ok + def delayedRunOps(ops: List[() => Unit]): () ->{ops*} Unit = + () => 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/delayedRunops2.check b/tests/neg-custom-args/captures/delayedRunops2.check new file mode 100644 index 000000000000..a1f6be6f6e3b --- /dev/null +++ b/tests/neg-custom-args/captures/delayedRunops2.check @@ -0,0 +1,14 @@ +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/delayedRunops2.scala:10:35 ------------------------------- +10 | app[List[() ->{ops*} Unit], Unit](ops, runOps) // error + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | Found: () ->{ops*} Unit + | Required: () -> Unit + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/delayedRunops2.scala:18:36 ------------------------------- +18 | app2[List[() => Unit], Unit](ops, runOps: List[() => Unit] -> Unit) // error + | ^^^^^^ + | Found: (ops: List[box () ->? Unit]^?) ->? Unit + | Required: (ops: List[box () => Unit]) -> Unit + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/delayedRunops2.scala b/tests/neg-custom-args/captures/delayedRunops2.scala new file mode 100644 index 000000000000..ada95065764f --- /dev/null +++ b/tests/neg-custom-args/captures/delayedRunops2.scala @@ -0,0 +1,22 @@ +import language.experimental.captureChecking + +def runOps(ops: List[() => Unit]): Unit = + ops.foreach(op => op()) + +def app[T, U](x: T, op: T => U): () ->{op} U = + () => op(x) + +def unsafeRunOps(ops: List[() => Unit]): () ->{} Unit = + app[List[() ->{ops*} Unit], Unit](ops, runOps) // error + +def app2[T, U](x: T, op: T => U): () ->{op} U = + () => + def y: T = x + op(y) + +def unsafeRunOps2(ops: List[() => Unit]): () -> Unit = + app2[List[() => Unit], Unit](ops, runOps: List[() => Unit] -> Unit) // error + + + + diff --git a/tests/neg-custom-args/captures/delayedRunops3.check b/tests/neg-custom-args/captures/delayedRunops3.check new file mode 100644 index 000000000000..f2d08f4ea802 --- /dev/null +++ b/tests/neg-custom-args/captures/delayedRunops3.check @@ -0,0 +1,10 @@ +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/delayedRunops3.scala:10:41 ------------------------------- +10 | app[List[() ->{ops*} Unit], Unit](ops, runOps) // error + | ^^^^^^ + | Found: (ops: List[box () ->? Unit]^?) ->? Unit + | Required: (ops: List[box () ->{ops²*} Unit]) -> Unit + | + | where: ops is a reference to a value parameter + | ops² is a parameter in method unsafeRunOps + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/delayedRunops3.scala b/tests/neg-custom-args/captures/delayedRunops3.scala new file mode 100644 index 000000000000..fb727fa94487 --- /dev/null +++ b/tests/neg-custom-args/captures/delayedRunops3.scala @@ -0,0 +1,14 @@ +import language.experimental.captureChecking + +def runOps(ops: List[() => Unit]): Unit = + ops.foreach(op => op()) + +def app[T, U](x: T, op: T -> U): () ->{} U = + () => op(x) + +def unsafeRunOps(ops: List[() => Unit]): () ->{} Unit = + app[List[() ->{ops*} Unit], Unit](ops, runOps) // error + + + + diff --git a/tests/neg-custom-args/captures/delayedRunops4.check b/tests/neg-custom-args/captures/delayedRunops4.check new file mode 100644 index 000000000000..1cc7a3fc293a --- /dev/null +++ b/tests/neg-custom-args/captures/delayedRunops4.check @@ -0,0 +1,21 @@ +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/delayedRunops4.scala:11:4 -------------------------------- +11 | runOps[C]: List[() ->{C^} Unit] ->{C^} Unit) // error + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | Found: List[box () ->{C^} Unit] ->{C^} Unit + | Required: List[box () ->{C^} Unit] -> Unit + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/delayedRunops4.scala:15:43 ------------------------------- +15 | app[List[() ->{C^} Unit], Unit](ops, rops[C]) // error + | ^^^^^^^ + | Found: (ops: List[box () ->{C^} Unit]) ->{C^} Unit + | Required: (ops: List[box () ->{C^} Unit]) -> Unit + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/delayedRunops4.scala:18:39 ------------------------------- +18 | app[List[() ->{C^} Unit], Unit](ops, runOps) // error + | ^^^^^^ + | Found: (ops: List[box () ->? Unit]^?) ->? Unit + | Required: (ops: List[box () ->{C^} Unit]) -> Unit + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/delayedRunops4.scala b/tests/neg-custom-args/captures/delayedRunops4.scala new file mode 100644 index 000000000000..e2b7a1bd5bb3 --- /dev/null +++ b/tests/neg-custom-args/captures/delayedRunops4.scala @@ -0,0 +1,24 @@ +import language.experimental.captureChecking + +def runOps[C^](ops: List[() ->{C^} Unit]): Unit = + ops.foreach(op => op()) + +def app[T, U](x: T, op: T -> U): () ->{} U = + () => op(x) + +def unsafeRunOps[C^](ops: List[() ->{C^} Unit]): () ->{} Unit = + app[List[() ->{C^} Unit], Unit](ops, + runOps[C]: List[() ->{C^} Unit] ->{C^} Unit) // error + +def unsafeRunOps2[C^](ops: List[() ->{C^} Unit]): () ->{} Unit = + def rops[D^]: (ops: List[() ->{D^} Unit]) -> Unit = ??? + app[List[() ->{C^} Unit], Unit](ops, rops[C]) // error + +def unsafeRunOps3[C^](ops: List[() ->{C^} Unit]): () ->{} Unit = + app[List[() ->{C^} Unit], Unit](ops, runOps) // error + + + + + + diff --git a/tests/neg-custom-args/captures/effect-swaps-explicit.check b/tests/neg-custom-args/captures/effect-swaps-explicit.check deleted file mode 100644 index 264dfa663d39..000000000000 --- a/tests/neg-custom-args/captures/effect-swaps-explicit.check +++ /dev/null @@ -1,29 +0,0 @@ --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/effect-swaps-explicit.scala:64:8 ------------------------- -63 | Result: -64 | Future: // error, type mismatch - | ^ - | Found: Result.Ok[box Future[box T^?]^{fr, contextual$1}] - | Required: Result[Future[T], Nothing] -65 | fr.await.ok - |-------------------------------------------------------------------------------------------------------------------- - |Inline stack trace - |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |This location contains code that was inlined from effect-swaps-explicit.scala:41 -41 | boundary(Ok(body)) - | ^^^^^^^^ - -------------------------------------------------------------------------------------------------------------------- - | - | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/effect-swaps-explicit.scala:74:10 ------------------------ -74 | Future: fut ?=> // error: type mismatch - | ^ - | Found: Future[box T^?]^{fr, lbl} - | Required: Future[box T^?]^? -75 | fr.await.ok - | - | longer explanation available when compiling with `-explain` --- 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 diff --git a/tests/neg-custom-args/captures/effect-swaps-explicit.scala b/tests/neg-custom-args/captures/effect-swaps-explicit.scala deleted file mode 100644 index 7474e1711b34..000000000000 --- a/tests/neg-custom-args/captures/effect-swaps-explicit.scala +++ /dev/null @@ -1,76 +0,0 @@ -//> using options -source 3.4 -// (to make sure we use the sealed policy) -object boundary: - - final class Label[-T] // extends caps.Capability - - /** Abort current computation and instead return `value` as the value of - * the enclosing `boundary` call that created `label`. - */ - def break[T](value: T)(using label: Label[T]^): Nothing = ??? - - def apply[T](body: Label[T]^ ?=> T): T = ??? -end boundary - -import boundary.{Label, break} - -trait Async extends caps.Capability -object Async: - def blocking[T](body: Async ?=> T): T = ??? - -class Future[+T]: - this: Future[T]^ => - def await(using Async): T = ??? -object Future: - def apply[T](op: Async ?=> T)(using Async): Future[T]^{op} = ??? - -enum Result[+T, +E]: - case Ok[+T](value: T) extends Result[T, Nothing] - case Err[+E](error: E) extends Result[Nothing, E] - - -object Result: - extension [T, E](r: Result[T, E]^)(using Label[Err[E]]^) - - /** `_.ok` propagates Err to current Label */ - def ok: T = r match - case Ok(value) => value - case Err(value) => break[Err[E]](Err(value)) - - transparent inline def apply[T, E](inline body: Label[Result[T, E]]^ ?=> T): Result[T, E] = - boundary(Ok(body)) - - // same as apply, but not an inline method - def make[T, E](body: Label[Result[T, E]]^ ?=> T): Result[T, E] = - boundary(Ok(body)) - -end Result - -def test[T, E](using Async) = - import Result.* - Async.blocking: async ?=> - val good1: List[Future[Result[T, E]]] => Future[Result[List[T], E]] = frs => - Future: - Result: - frs.map(_.await.ok) // OK - - val good2: Result[Future[T], E] => Future[Result[T, E]] = rf => - Future: - Result: - rf.ok.await // OK, Future argument has type Result[T] - - def fail3(fr: Future[Result[T, E]]^) = - Result: - Future: // error, type mismatch - fr.await.ok - - def fail4[T, E](fr: Future[Result[T, E]]^) = - Result.make: //lbl ?=> // error, escaping label from Result - Future: fut ?=> - fr.await.ok - - def fail5[T, E](fr: Future[Result[T, E]]^) = - Result.make[Future[T], E]: lbl ?=> - Future: fut ?=> // error: type mismatch - fr.await.ok - diff --git a/tests/neg-custom-args/captures/effect-swaps.scala b/tests/neg-custom-args/captures/effect-swaps.scala index 4bafd6421af3..13123bca7397 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: // should be errorm but infers Result[Any, Any] Future: fut ?=> fr.await.ok diff --git a/tests/neg-custom-args/captures/explain-under-approx.check b/tests/neg-custom-args/captures/explain-under-approx.check index 2d2b05b4b95a..c186fc6adb11 100644 --- a/tests/neg-custom-args/captures/explain-under-approx.check +++ b/tests/neg-custom-args/captures/explain-under-approx.check @@ -1,20 +1,14 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/explain-under-approx.scala:12:10 ------------------------- 12 | col.add(Future(() => 25)) // error | ^^^^^^^^^^^^^^^^ - | Found: Future[Int]{val a: (async : Async^)}^{async} - | Required: Future[Int]^{} - | - | Note that a capability Collector.this.futs* in a capture set appearing in contravariant position - | was mapped to col.futs* which is not a capability. Therefore, it was under-approximated to the empty set. + | Found: Future[Int]{val a: (async : Async^)}^{async} + | Required: Future[Int]^{col.futs*} | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/explain-under-approx.scala:15:11 ------------------------- 15 | col1.add(Future(() => 25)) // error | ^^^^^^^^^^^^^^^^ - | Found: Future[Int]{val a: (async : Async^)}^{async} - | Required: Future[Int]^{} - | - | Note that a capability Collector.this.futs* in a capture set appearing in contravariant position - | was mapped to col1.futs* which is not a capability. Therefore, it was under-approximated to the empty set. + | Found: Future[Int]{val a: (async : Async^)}^{async} + | Required: Future[Int]^{col1.futs*} | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/filevar-multi-ios.scala b/tests/neg-custom-args/captures/filevar-multi-ios.scala new file mode 100644 index 000000000000..8ffc8d8e299c --- /dev/null +++ b/tests/neg-custom-args/captures/filevar-multi-ios.scala @@ -0,0 +1,41 @@ +import language.experimental.modularity +import compiletime.uninitialized + +class IO extends caps.Capability + +class File: + def write(x: String): Unit = ??? + +object test1: + + class Service(val io: IO, val io2: IO): + var file: File^{io} = uninitialized + var file2: File^{io2} = uninitialized + def log = file.write("log") + + def withFile[T](io: IO)(op: File^{io} => T): T = + op(new File) + + def test(io3: IO, io4: IO) = + withFile(io3): f => + val o = Service(io3, io4) + o.file = f // error + o.file2 = f // error + o.log + +object test2: + + class Service(tracked val io: IO, tracked val io2: IO): + var file: File^{io} = uninitialized + var file2: File^{io2} = uninitialized + def log = file.write("log") + + def withFile[T](io: IO)(op: File^{io} => T): T = + op(new File) + + def test(io3: IO, io4: IO) = + withFile(io3): f => + val o = Service(io3, io4) + o.file = f + o.file2 = f // error + o.log diff --git a/tests/neg-custom-args/captures/i15116.check b/tests/neg-custom-args/captures/i15116.check index df05324866e1..0a16af9f6704 100644 --- a/tests/neg-custom-args/captures/i15116.check +++ b/tests/neg-custom-args/captures/i15116.check @@ -18,13 +18,17 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i15116.scala:5:13 ---------------------------------------- 5 | val x = Foo(m) // error | ^^^^^^ - | Found: Foo{val m: String^{Baz.this}}^{Baz.this} + | Found: Foo{val m²: (Baz.this.m : String^)}^{Baz.this.m} | Required: Foo | + | where: m is a value in trait Baz + | m² is a value in class Foo + | + | | Note that the expected type Foo | is the previously inferred type of value x | which is also the type seen in separately compiled sources. - | The new inferred type Foo{val m: String^{Baz.this}}^{Baz.this} + | The new inferred type Foo{val m: (Baz.this.m : String^)}^{Baz.this.m} | must conform to this type. | | longer explanation available when compiling with `-explain` @@ -48,13 +52,17 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i15116.scala:9:13 ---------------------------------------- 9 | val x = Foo(m) // error | ^^^^^^ - | Found: Foo{val m: String^{Baz2.this}}^{Baz2.this} + | Found: Foo{val m²: (Baz2.this.m : String^)}^{Baz2.this.m} | Required: Foo | + | where: m is a value in trait Baz2 + | m² is a value in class Foo + | + | | Note that the expected type Foo | is the previously inferred type of value x | which is also the type seen in separately compiled sources. - | The new inferred type Foo{val m: String^{Baz2.this}}^{Baz2.this} + | The new inferred type Foo{val m: (Baz2.this.m : String^)}^{Baz2.this.m} | must conform to this type. | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/i15749a.scala b/tests/neg-custom-args/captures/i15749a.scala index 57fca27fae66..bba69fb239a8 100644 --- a/tests/neg-custom-args/captures/i15749a.scala +++ b/tests/neg-custom-args/captures/i15749a.scala @@ -1,5 +1,4 @@ import caps.cap -import caps.unbox class Unit object u extends Unit @@ -18,7 +17,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](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/i16114.check b/tests/neg-custom-args/captures/i16114.check new file mode 100644 index 000000000000..b4229efac303 --- /dev/null +++ b/tests/neg-custom-args/captures/i16114.check @@ -0,0 +1,25 @@ +-- Error: tests/neg-custom-args/captures/i16114.scala:18:8 ------------------------------------------------------------- +18 | fs // error + | ^^ + | (fs : Cap^) cannot be referenced here; it 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:8 ------------------------------------------------------------- +24 | io // error + | ^^ + | (io : Cap^) cannot be referenced here; it 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:34:19 ------------------------------------------------------------ +34 | expect[Cap^](io) // 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:38:8 ------------------------------------------------------------- +38 | io.use() // error + | ^^ + | (io : Cap^) cannot be referenced here; it 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:39:8 ------------------------------------------------------------- +39 | io // error + | ^^ + | (io : Cap^) cannot be referenced here; it 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..0901a3d0eb4a 100644 --- a/tests/neg-custom-args/captures/i16114.scala +++ b/tests/neg-custom-args/captures/i16114.scala @@ -1,5 +1,3 @@ -//> using options -source 3.4 -// (to make sure we use the sealed policy) trait Cap { def use(): Int; def close(): Unit } def mkCap(): Cap^ = ??? @@ -15,19 +13,19 @@ def withCap[T](op: Cap^ => T): T = { def main(fs: Cap^): Unit = { def badOp(io: Cap^): Unit ->{} Unit = { val op1: Unit ->{io} Unit = (x: Unit) => - expect[Cap^] { // error + expect[Cap^] { io.use() - fs // error (limitation) + fs // error } val op2: Unit ->{fs} Unit = (x: Unit) => - expect[Cap^] { // error + expect[Cap^] { fs.use() - io // error (limitation) + io // error } val op3: Unit ->{io} Unit = (x: Unit) => - expect[Cap^] { // error + expect[Cap^] { io.use() io } @@ -36,7 +34,7 @@ def main(fs: Cap^): Unit = { expect[Cap^](io) // error val op: Unit -> Unit = (x: Unit) => - expect[Cap^] { // error + expect[Cap^] { io.use() // error io // error } diff --git a/tests/neg-custom-args/captures/i21347.check b/tests/neg-custom-args/captures/i21347.check index c680a54d3efc..fbcd67d010eb 100644 --- a/tests/neg-custom-args/captures/i21347.check +++ b/tests/neg-custom-args/captures/i21347.check @@ -1,15 +1,5 @@ --- 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 -5 | op() --- Error: tests/neg-custom-args/captures/i21347.scala:8:14 ------------------------------------------------------------- +-- Error: tests/neg-custom-args/captures/i21347.scala:8:15 ------------------------------------------------------------- 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 -12 | op() + | ^ + | reference (f : () => Unit) 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/i21347.scala b/tests/neg-custom-args/captures/i21347.scala index 41887be6a78a..ca1480612517 100644 --- a/tests/neg-custom-args/captures/i21347.scala +++ b/tests/neg-custom-args/captures/i21347.scala @@ -1,12 +1,12 @@ import language.experimental.captureChecking def runOps[C^](ops: List[() ->{C^} Unit]): Unit = - ops.foreach: op => // error + ops.foreach: op => op() def boom(f: () => Unit): () -> Unit = () => runOps(f :: Nil) // error def runOpsAlt(ops: List[() => Unit]): Unit = - ops.foreach: op => // error - op() \ No newline at end of file + ops.foreach: op => + op() diff --git a/tests/neg-custom-args/captures/i21401.check b/tests/neg-custom-args/captures/i21401.check index e204540358ce..601e426ec0bd 100644 --- a/tests/neg-custom-args/captures/i21401.check +++ b/tests/neg-custom-args/captures/i21401.check @@ -1,3 +1,8 @@ +-- Error: tests/neg-custom-args/captures/i21401.scala:13:19 ------------------------------------------------------------ +13 | op1(Boxed[IO^](x)) // error + | ^ + | reference (x : IO^) is not included in the allowed capture set {} + | of an enclosing function literal with expected type Res -- 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` | ^^^^^^^^^^^^^^^^^^^^ @@ -8,7 +13,10 @@ | ^^^^^^^^^^^^^^^^^^^ | 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:18:21 ------------------------------------------------------------ -18 | val y: IO^{x*} = x.unbox // error - | ^^^^^^^ - | Local reach capability x* leaks into capture scope of method test2 +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i21401.scala:17:67 --------------------------------------- +17 | val x: Boxed[IO^] = leaked[Boxed[IO^], Boxed[IO^] -> Boxed[IO^]](x => x) // error after adding addImplied widening + | ^^^^^^ + | Found: (x: Boxed[box IO^?]^?) ->? Boxed[box IO^?]^? + | Required: (x: Boxed[box IO^]) -> Boxed[box IO^] + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/i21401.scala b/tests/neg-custom-args/captures/i21401.scala index 8284c601cd5f..48427426e5a8 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 y: IO^{x*} = x.unbox // error + val x: Boxed[IO^] = leaked[Boxed[IO^], Boxed[IO^] -> Boxed[IO^]](x => x) // error after adding addImplied widening + val y: IO^{x*} = x.unbox y.println("boom") diff --git a/tests/neg-custom-args/captures/i21442.check b/tests/neg-custom-args/captures/i21442.check deleted file mode 100644 index a3bbf65c5988..000000000000 --- a/tests/neg-custom-args/captures/i21442.check +++ /dev/null @@ -1,8 +0,0 @@ --- 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 --- Error: tests/neg-custom-args/captures/i21442.scala:17:14 ------------------------------------------------------------ -17 | val io = x1.unbox // error - | ^^^^^^^^ - | Local reach capability x1* leaks into capture scope of method bar diff --git a/tests/neg-custom-args/captures/i21614.check b/tests/neg-custom-args/captures/i21614.check index 14b468db4c8e..14e54c50ae04 100644 --- a/tests/neg-custom-args/captures/i21614.check +++ b/tests/neg-custom-args/captures/i21614.check @@ -1,12 +1,12 @@ --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i21614.scala:9:33 ---------------------------------------- -9 | files.map((f: F) => new Logger(f)) // error, Q: can we make this pass (see #19076)? +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i21614.scala:8:33 ---------------------------------------- +8 | files.map((f: F) => new Logger(f)) // error, Q: can we make this pass (see #19076)? | ^ | Found: (f : F^) | Required: File^ | | longer explanation available when compiling with `-explain` --- [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? +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i21614.scala:11:12 --------------------------------------- +11 | files.map(new Logger(_)) // error, Q: can we improve the error message? | ^^^^^^^^^^^^^ | Found: Logger{val f: (_$1 : File^{files*})}^ | Required: Logger{val f: File^?}^? diff --git a/tests/neg-custom-args/captures/i21614.scala b/tests/neg-custom-args/captures/i21614.scala index a5ed25d818a5..f1b0f43cc328 100644 --- a/tests/neg-custom-args/captures/i21614.scala +++ b/tests/neg-custom-args/captures/i21614.scala @@ -1,12 +1,11 @@ import language.experimental.captureChecking import caps.Capability -import caps.unbox 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^](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(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/i21646.scala b/tests/neg-custom-args/captures/i21646.scala new file mode 100644 index 000000000000..42c493a9ea80 --- /dev/null +++ b/tests/neg-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) // error + +@main def run = + val myFile: File = ??? + val r = Resource(myFile) // 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..3b6ef182997e 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](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/outer-var.check b/tests/neg-custom-args/captures/outer-var.check index 32351a179eab..2aa3ce936ddc 100644 --- a/tests/neg-custom-args/captures/outer-var.check +++ b/tests/neg-custom-args/captures/outer-var.check @@ -28,3 +28,13 @@ | since at least one of their capture sets contains the root capability `cap` | | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/outer-var.scala:16:65 ------------------------------------ +16 | var finalizeActions = collection.mutable.ListBuffer[() => Unit]() // error under addImplied widening + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + |Found: scala.collection.mutable.ListBuffer[box () => Unit]^ + |Required: box scala.collection.mutable.ListBuffer[box () ->? Unit]^ + | + |Note that scala.collection.mutable.ListBuffer[box () => Unit]^ cannot be box-converted to box scala.collection.mutable.ListBuffer[box () ->? Unit]^ + |since at least one of their capture sets contains the root capability `cap` + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/outer-var.scala b/tests/neg-custom-args/captures/outer-var.scala index e26cd631602a..861202b5626d 100644 --- a/tests/neg-custom-args/captures/outer-var.scala +++ b/tests/neg-custom-args/captures/outer-var.scala @@ -13,6 +13,6 @@ def test(p: Proc, q: () => Unit) = y = (q: Proc) // error y = q // OK, was error under sealed - var finalizeActions = collection.mutable.ListBuffer[() => Unit]() // OK, was error under sealed + var finalizeActions = collection.mutable.ListBuffer[() => Unit]() // error under addImplied widening diff --git a/tests/neg-custom-args/captures/outerRefsUses.scala b/tests/neg-custom-args/captures/outerRefsUses.scala new file mode 100644 index 000000000000..cd03c8c41efd --- /dev/null +++ b/tests/neg-custom-args/captures/outerRefsUses.scala @@ -0,0 +1,10 @@ +class IO +def test(io: IO^) = + class C: + def foo() = () => + val x: IO^{this} = io + () + val c = new C + val _: C^{io} = c // ok + val _: C = c // error + () diff --git a/tests/neg-custom-args/captures/path-box.scala b/tests/neg-custom-args/captures/path-box.scala new file mode 100644 index 000000000000..3213c236aaf5 --- /dev/null +++ b/tests/neg-custom-args/captures/path-box.scala @@ -0,0 +1,20 @@ +class A: + val m: A^ = ??? + val self: this.type = this + +case class Box[+T](value: T) + +def testBox1(a: A^): Box[A^{a}] = + Box(a.m) + +def testBox2(a: A^): Box[A^{a.m}] = + Box(a.m) + +def testBox3(a: A^): Box[A^{a.m}] = + Box(a) // error + +def testBox4(a: A^): Box[A^{a.m}] = + Box(a.m.m.m) + +def testBox5(a: A^): Box[A^{a.m}] = + Box(a.m.m.self) \ No newline at end of file diff --git a/tests/neg-custom-args/captures/path-connection.scala b/tests/neg-custom-args/captures/path-connection.scala new file mode 100644 index 000000000000..c65aa75b1ed2 --- /dev/null +++ b/tests/neg-custom-args/captures/path-connection.scala @@ -0,0 +1,48 @@ +import language.experimental.modularity + +trait Reader: + def read(): String + +trait Sender: + def send(msg: String): Unit + +class Connection extends Reader, Sender: + def read() = "hello" + def send(msg: String) = () + + val readOnly: Reader^ = new Reader: + def read() = Connection.this.read() + +class ReaderProxy(tracked val r: Reader^) extends Reader: + def read() = "(Proxy)" + r.read() + +class SenderProxy(tracked val s: Sender^) extends Sender: + def send(msg: String) = s.send("(Proxy) " + msg) + +// TODO: We have to put `c` in the different argument list to make it work. +// See the comments in `integrateRT`. +def testConnection(c: Connection^)( + handle1: Reader^{c.readOnly} => String, + handle2: Sender^{c} => Unit, + handle3: Reader^{c} => String, + ) = + val m1 = c.read() + c.send("hello") + + val m2 = c.readOnly.read() + + val m3a = handle1(c.readOnly) + val m3b = handle3(c.readOnly) + + val m4a = handle1(c) // error + val m4b = handle3(c) + + val m5a = handle1(new ReaderProxy(c.readOnly)) + val m5b = handle3(new ReaderProxy(c.readOnly)) + + val m6a = handle1(new ReaderProxy(c)) // error + val m6b = handle3(new ReaderProxy(c)) + + handle2(c) + + handle2(new SenderProxy(c)) \ No newline at end of file diff --git a/tests/neg-custom-args/captures/path-illigal.scala b/tests/neg-custom-args/captures/path-illigal.scala new file mode 100644 index 000000000000..f09db0087ef7 --- /dev/null +++ b/tests/neg-custom-args/captures/path-illigal.scala @@ -0,0 +1,7 @@ +class A: + val m: A^ = ??? + var n: A^ = ??? + +def test1(a: A^) = + val c1: A^{a.m} = a.m + val f1: A^{a.n} = a.n // error \ No newline at end of file diff --git a/tests/neg-custom-args/captures/path-patmat-should-be-pos.scala b/tests/neg-custom-args/captures/path-patmat-should-be-pos.scala new file mode 100644 index 000000000000..aca6102204a3 --- /dev/null +++ b/tests/neg-custom-args/captures/path-patmat-should-be-pos.scala @@ -0,0 +1,26 @@ +class It[A] + +class Filter[A](val underlying: It[A]^, val p: A => Boolean) extends It[A] +object Filter: + def apply[A](underlying: It[A]^, p: A => Boolean): Filter[A]^{underlying, p} = + underlying match + case filter: Filter[A]^ => + val x = new Filter(filter.underlying, a => filter.p(a) && p(a)) + x: Filter[A]^{underlying, p} // error + // !!! should work, it seems to be the case that the system does not recognize that + // underlying and filter are aliases. + + // On the other hand, the following works: + locally: + val filter: underlying.type & Filter[A] = ??? + val a: It[A]^{filter.underlying} = ??? + val b: It[A]^{underlying} = a + val x = new Filter(filter.underlying, a => filter.p(a) && p(a)) + x: Filter[A]^{underlying, p} + + locally: + val filter: underlying.type & Filter[A]^ = ??? + val a: It[A]^{filter.underlying} = ??? + val b: It[A]^{underlying} = a + val x = new Filter(filter.underlying, a => filter.p(a) && p(a)) + x: Filter[A]^{underlying, p} diff --git a/tests/neg-custom-args/captures/path-simple.scala b/tests/neg-custom-args/captures/path-simple.scala new file mode 100644 index 000000000000..93b6dacebe74 --- /dev/null +++ b/tests/neg-custom-args/captures/path-simple.scala @@ -0,0 +1,27 @@ + +class A: + val m: A^ = ??? + val self: this.type = this + +case class C(ca: A^) + +def test1(a: A^, b: A^) = + val c1: A^{a} = a.m + val c2: A^{a.m} = a.m + val c3: A^{b} = a.m // error + + val d1: A^{a} = a.self + val d2: A^{a.self} = a.self + val d3: A^{a.self} = a + + val e1: A^{a.m} = a.self.m + val e2: A^{a.self.m} = a.self.m + val e3: A^{a.self.m} = a.m + +def test2(a: A^) = + val b: a.type = a + val c1: C^{a} = new C(a) + val c2: C^{a} = new C(a.m) + val c3: C^{a.m} = new C(a.m) + val c4: C^{b} = new C(a) + val c5: C^{a} = new C(b) \ No newline at end of file diff --git a/tests/neg-custom-args/captures/reaches.check b/tests/neg-custom-args/captures/reaches.check index f00fea09ed8c..8ef0241ee025 100644 --- a/tests/neg-custom-args/captures/reaches.check +++ b/tests/neg-custom-args/captures/reaches.check @@ -1,12 +1,12 @@ --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reaches.scala:22:11 -------------------------------------- -22 | cur = (() => f.write()) :: Nil // error +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reaches.scala:21:11 -------------------------------------- +21 | cur = (() => f.write()) :: Nil // error | ^^^^^^^^^^^^^^^^^^^^^^^ | Found: List[box () ->{f} Unit] | Required: List[box () ->{xs*} Unit] | | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reaches.scala:33:7 --------------------------------------- -33 | (() => f.write()) :: Nil // error +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reaches.scala:32:7 --------------------------------------- +32 | (() => f.write()) :: Nil // error | ^^^^^^^^^^^^^^^^^^^^^^^ | Found: List[box () ->{f} Unit] | Required: box List[box () ->{xs*} Unit]^? @@ -15,38 +15,35 @@ | 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 +-- Error: tests/neg-custom-args/captures/reaches.scala:37:31 ----------------------------------------------------------- +37 | 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 +-- Error: tests/neg-custom-args/captures/reaches.scala:44:35 ----------------------------------------------------------- +44 | 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: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^ +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reaches.scala:52:27 -------------------------------------- +52 | val id: Id[Proc, Proc] = new Id[Proc, () -> Unit] // error + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | Found: Id[box () => Unit, () -> Unit]^ + | Required: Id[box () => Unit, box () => Unit] + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reaches.scala:61:27 -------------------------------------- +61 | val f1: File^{id*} = id(f) // error, since now id(f): File^ // error | ^^^^^ | Found: File^{f} | Required: File^{id*} | | longer explanation available when compiling with `-explain` --- 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 --- 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` +-- Error: tests/neg-custom-args/captures/reaches.scala:60:31 ----------------------------------------------------------- +60 | 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:61:18 ----------------------------------------------------------- +61 | 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..b10ccf5eaef3 100644 --- a/tests/neg-custom-args/captures/reaches.scala +++ b/tests/neg-custom-args/captures/reaches.scala @@ -1,4 +1,3 @@ -import caps.unbox class File: def write(): Unit = ??? @@ -11,7 +10,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(xs: List[Proc]): Unit = var cur: List[() ->{xs*} Unit] = xs while cur.nonEmpty do val next: () ->{xs*} Unit = cur.head @@ -21,7 +20,7 @@ def runAll0(@unbox xs: List[Proc]): Unit = usingFile: f => cur = (() => f.write()) :: Nil // error -def runAll1(@unbox xs: List[Proc]): Unit = +def runAll1(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 @@ -52,14 +51,14 @@ class Id[-A, +B >: A](): def test = val id: Id[Proc, Proc] = new Id[Proc, () -> Unit] // error usingFile: f => - id(() => f.write()) // error + id(() => f.write()) 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]: @@ -72,11 +71,3 @@ extension [A](x: A) def :: (xs: List[A]): List[A] = ??? object Nil extends List[Nothing] -def compose1[A, B, C](f: A => B, g: B => C): A ->{f, g} C = - z => g(f(z)) - -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] = - 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..0853cd227feb 100644 --- a/tests/neg-custom-args/captures/reaches2.check +++ b/tests/neg-custom-args/captures/reaches2.check @@ -1,10 +1,8 @@ --- Error: tests/neg-custom-args/captures/reaches2.scala:8:10 ----------------------------------------------------------- -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^? --- 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^? +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reaches2.scala:8:10 -------------------------------------- +8 | ps.map((x, y) => compose1(x, y)) // error + | ^^^^^^^^^^^^^^^^^^^^^^^ + |Found: (x$1: (box (x$0: A^?) ->? (ex$18: caps.Exists) -> A^?, box (x$0: A^?) ->? (ex$19: caps.Exists) -> A^?)^?) ->? + | box (x$0: A^?) ->? A^? + |Required: (x$1: (box A ->{ps*} A, box A ->{ps*} A)) -> box (x$0: A^?) ->? A^? + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/reaches2.scala b/tests/neg-custom-args/captures/reaches2.scala index f2447b8c8795..2fbc6dbf7c32 100644 --- a/tests/neg-custom-args/captures/reaches2.scala +++ b/tests/neg-custom-args/captures/reaches2.scala @@ -5,5 +5,5 @@ def compose1[A, B, C](f: A => B, g: B => C): A ->{f, g} C = z => g(f(z)) def mapCompose[A](ps: List[(A => A, A => A)]): List[A ->{ps*} A] = - ps.map((x, y) => compose1(x, y)) // error // error + ps.map((x, y) => compose1(x, y)) // error 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/singletons.scala b/tests/neg-custom-args/captures/singletons.scala index 194e6e850dcd..be0ee67ab1bc 100644 --- a/tests/neg-custom-args/captures/singletons.scala +++ b/tests/neg-custom-args/captures/singletons.scala @@ -1,6 +1,6 @@ val x = () => () -val y1: x.type = x // ok -val y2: x.type^{} = x // error: singleton type cannot have capture set -val y3: x.type^{x} = x // error: singleton type cannot have capture set // error -val y4: x.type^ = x // error: singleton type cannot have capture set +val y1: x.type = x +val y2: x.type^{} = x +val y3: x.type^{x} = x // error +val y4: x.type^ = x diff --git a/tests/neg-custom-args/captures/spread-problem.scala b/tests/neg-custom-args/captures/spread-problem.scala index 579c7817b9c1..891bbc2b2f9f 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](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/unbox-overrides.check b/tests/neg-custom-args/captures/unbox-overrides.check deleted file mode 100644 index b9a3be7bffbc..000000000000 --- a/tests/neg-custom-args/captures/unbox-overrides.check +++ /dev/null @@ -1,21 +0,0 @@ --- [E164] Declaration Error: tests/neg-custom-args/captures/unbox-overrides.scala:8:6 ---------------------------------- -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 - | - | 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 - | ^ - |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 - | - | 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 - | - | 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 deleted file mode 100644 index 5abb5013bfbe..000000000000 --- a/tests/neg-custom-args/captures/unbox-overrides.scala +++ /dev/null @@ -1,15 +0,0 @@ -import caps.unbox - -trait A[X]: - def foo(@unbox 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 - -trait B2: - def foo(x: C): C - def bar(@unbox x: C): C - -abstract class C extends A[C], B2 // error diff --git a/tests/neg-custom-args/captures/unsound-reach.check b/tests/neg-custom-args/captures/unsound-reach.check index 4a6793d204c5..f0e4c4deeb41 100644 --- a/tests/neg-custom-args/captures/unsound-reach.check +++ b/tests/neg-custom-args/captures/unsound-reach.check @@ -1,8 +1,3 @@ --- Error: tests/neg-custom-args/captures/unsound-reach.scala:18:21 ----------------------------------------------------- -18 | 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 | ^ diff --git a/tests/neg-custom-args/captures/unsound-reach.scala b/tests/neg-custom-args/captures/unsound-reach.scala index c3c31a7f32ff..2dcea24ff0f0 100644 --- a/tests/neg-custom-args/captures/unsound-reach.scala +++ b/tests/neg-custom-args/captures/unsound-reach.scala @@ -15,6 +15,6 @@ def bad(): Unit = var escaped: File^{backdoor*} = null withFile("hello.txt"): f => - boom.use(f): (f1: File^{backdoor*}) => // error + boom.use(f): (f1: File^{backdoor*}) => escaped = f1 diff --git a/tests/neg-custom-args/captures/uses.check b/tests/neg-custom-args/captures/uses.check new file mode 100644 index 000000000000..d8c41692f2cc --- /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 + | ^ + | 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:13:22 ----------------------------------------- +13 | val _: () -> Unit = f // error + | ^ + | Found: (f : () ->{x, y} Unit) + | Required: () -> 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} (ex$7: caps.Exists) -> () ->{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..c61d4baf2184 --- /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 + val _: D = d // error + + val f = () => println(D()) + val _: () ->{x, y} Unit = f // ok + val _: () -> Unit = f // error + + def g = () => + println(x) + () => println(y) + val _: () ->{x, y} () ->{y} Unit = g + val _: () -> () -> Unit = g // error + 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..dbe811ab99ec 100644 --- a/tests/neg-custom-args/captures/widen-reach.check +++ b/tests/neg-custom-args/captures/widen-reach.check @@ -1,11 +1,17 @@ --- Error: tests/neg-custom-args/captures/widen-reach.scala:13:26 ------------------------------------------------------- +-- [E007] Type Mismatch 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 --- Error: tests/neg-custom-args/captures/widen-reach.scala:14:30 ------------------------------------------------------- + | Found: IO^ ->{x*} IO^{x*} + | Required: IO^ -> (ex$6: caps.Exists) -> IO^{ex$6} + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch 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 + | Found: IO^ ->{x*} IO^{x*} + | Required: IO^ -> IO^{x*} + | + | longer explanation available when compiling with `-explain` -- [E164] Declaration Error: tests/neg-custom-args/captures/widen-reach.scala:9:6 -------------------------------------- 9 | val foo: IO^ -> IO^ = x => x // error | ^ diff --git a/tests/neg/i20503.scala b/tests/neg/i20503.scala deleted file mode 100644 index 3fb0573f6c2f..000000000000 --- a/tests/neg/i20503.scala +++ /dev/null @@ -1,18 +0,0 @@ -import language.experimental.captureChecking -import caps.unbox - -class List[+A]: - def head: A = ??? - def tail: List[A] = ??? - def map[B](f: A => B): List[B] = ??? - def foreach[U](f: A => U): Unit = ??? - def nonEmpty: Boolean = ??? - -def runOps(@unbox 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()) - -def main(): Unit = - val f: List[() => Unit] -> Unit = (ops: List[() => Unit]) => runOps(ops) // error - val _: List[() => Unit] -> Unit = runOps // error diff --git a/tests/neg/leak-problem-unboxed.scala b/tests/neg/leak-problem-unboxed.scala deleted file mode 100644 index 7de3d84bfcca..000000000000 --- a/tests/neg/leak-problem-unboxed.scala +++ /dev/null @@ -1,32 +0,0 @@ -import language.experimental.captureChecking -import caps.unbox - -// Some capabilities that should be used locally -trait Async: - // some method - def read(): Unit -def usingAsync[X](op: Async^ => X): X = ??? - -case class Box[+T](get: T) - -def useBoxedAsync(@unbox 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 test(): Unit = - - val f: Box[Async^] => Unit = (x: Box[Async^]) => useBoxedAsync(x) // error - val _: Box[Async^] => Unit = useBoxedAsync(_) // error - val _: Box[Async^] => Unit = useBoxedAsync // error - val _ = useBoxedAsync(_) // error - val _ = useBoxedAsync // error - - def boom(x: Async^): () ->{f} Unit = - () => f(Box(x)) - - val leaked = usingAsync[() ->{f} Unit](boom) - - leaked() // scope violation \ No newline at end of file diff --git a/tests/neg/leak-problem.scala b/tests/neg/leak-problem.scala index 354d54d86707..f124c16635de 100644 --- a/tests/neg/leak-problem.scala +++ b/tests/neg/leak-problem.scala @@ -10,21 +10,32 @@ case class Box[+T](get: T) def useBoxedAsync(x: Box[Async^]): Unit = val t0 = x - val t1 = t0.get // error + val t1 = t0.get // now ok t1.read() -def useBoxedAsync1(x: Box[Async^]): Unit = x.get.read() // error +def useBoxedAsync1(x: Box[Async^]): Unit = x.get.read() // now ok def test(): Unit = + val xs: Box[Async^] = ??? + val xsLambda = () => useBoxedAsync(xs) + val _: () ->{xs*} Unit = xsLambda + val _: () -> Unit = xsLambda // error + val useBoxedAsync2 = (x: Box[Async^]) => val t0 = x - val t1 = x.get // error + val t1 = x.get t1.read() + val xsLambda2 = () => useBoxedAsync2(xs) + val _: () ->{useBoxedAsync2, xs*} Unit = xsLambda2 // useBoxedAsync2 needed after adding addImplied widening + val _: () -> Unit = xsLambda2 // error + val f: Box[Async^] => Unit = (x: Box[Async^]) => useBoxedAsync(x) def boom(x: Async^): () ->{f} Unit = - () => f(Box(x)) + val ff = () => f(Box(x)) // error + val _: () ->{f, x} Unit = ff + ff val leaked = usingAsync[() ->{f} Unit](boom) diff --git a/tests/pos-custom-args/captures/boxed1.scala b/tests/pos-custom-args/captures/boxed1.scala index e2ff69c305d2..0aceadab6c81 100644 --- a/tests/pos-custom-args/captures/boxed1.scala +++ b/tests/pos-custom-args/captures/boxed1.scala @@ -8,4 +8,4 @@ def test(c: Cap) = val f = () => { c; 1 } val _: () ->{c} Int = f val g = () => Box(f) - val _: () -> Box[() ->{f} Int] = g + val _: () ->{g} Box[() ->{f} Int] = g diff --git a/tests/pos-custom-args/captures/dep-reach.scala b/tests/pos-custom-args/captures/dep-reach.scala index c81197aa738d..56343fbf8e53 100644 --- a/tests/pos-custom-args/captures/dep-reach.scala +++ b/tests/pos-custom-args/captures/dep-reach.scala @@ -1,10 +1,9 @@ -import caps.unbox 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(xs: Proc*): () ->{xs*} Unit = xs.head val a: () ->{c} Unit = () => () val b: () ->{d} Unit = () => () @@ -13,7 +12,7 @@ object Test: def g(c: C^, d: C^): () ->{c, d} Unit = - def foo(@unbox xs: Seq[() => Unit]): () ->{xs*} Unit = + def foo(xs: Seq[() => Unit]): () ->{xs*} Unit = xs.head val a: () ->{c} Unit = () => () diff --git a/tests/pos-custom-args/captures/filevar-expanded.scala b/tests/pos-custom-args/captures/filevar-expanded.scala index a883471e8d2e..58e7a0e67e0a 100644 --- a/tests/pos-custom-args/captures/filevar-expanded.scala +++ b/tests/pos-custom-args/captures/filevar-expanded.scala @@ -1,4 +1,5 @@ import language.experimental.captureChecking +import language.experimental.modularity import compiletime.uninitialized object test1: @@ -22,7 +23,7 @@ object test2: class File: def write(x: String): Unit = ??? - class Service(io: IO^): + class Service(tracked val io: IO^): var file: File^{io} = uninitialized def log = file.write("log") diff --git a/tests/pos-custom-args/captures/filevar.scala b/tests/pos-custom-args/captures/filevar.scala index 9ab34fe617b5..dc8d0b18908b 100644 --- a/tests/pos-custom-args/captures/filevar.scala +++ b/tests/pos-custom-args/captures/filevar.scala @@ -1,4 +1,5 @@ import language.experimental.captureChecking +import language.experimental.modularity import compiletime.uninitialized object test1: @@ -22,7 +23,7 @@ object test2: class File: def write(x: String): Unit = ??? - class Service(io: IO): + class Service(tracked val io: IO): var file: File^{io} = uninitialized def log = file.write("log") diff --git a/tests/pos/gears-probem.scala b/tests/pos-custom-args/captures/gears-problem.scala similarity index 100% rename from tests/pos/gears-probem.scala rename to tests/pos-custom-args/captures/gears-problem.scala diff --git a/tests/pos-custom-args/captures/i20503.scala b/tests/pos-custom-args/captures/i20503.scala new file mode 100644 index 000000000000..ec8764d3d6f9 --- /dev/null +++ b/tests/pos-custom-args/captures/i20503.scala @@ -0,0 +1,15 @@ +import language.experimental.captureChecking + +class List[+A]: + def head: A = ??? + def tail: List[A] = ??? + def map[B](f: A => B): List[B] = ??? + def foreach[U](f: A => U): Unit = ??? + def nonEmpty: Boolean = ??? + +def runOps(ops: List[() => Unit]): Unit = + ops.foreach(op => op()) + +def main(): Unit = + val f: List[() => Unit] => Unit = (ops: List[() => Unit]) => runOps(ops) + val _: List[() => Unit] => Unit = runOps diff --git a/tests/neg-custom-args/captures/i21442.scala b/tests/pos-custom-args/captures/i21442.scala similarity index 78% rename from tests/neg-custom-args/captures/i21442.scala rename to tests/pos-custom-args/captures/i21442.scala index c9fa7d152fae..fbc3d57ee333 100644 --- a/tests/neg-custom-args/captures/i21442.scala +++ b/tests/pos-custom-args/captures/i21442.scala @@ -6,7 +6,7 @@ case class Boxed[+T](unbox: T) // `foo` is a function that unboxes its parameter // and uses the capability boxed inside the parameter. def foo(x: Boxed[IO^]): Unit = - val io = x.unbox // error: local reach capability {x*} leaks + val io = x.unbox // now ok, was error: local reach capability {x*} leaks io.use() // `bar` is a function that does the same thing in a @@ -14,5 +14,5 @@ def foo(x: Boxed[IO^]): Unit = // But, no type error reported. def bar(x: Boxed[IO^]): Unit = val x1: Boxed[IO^] = x - val io = x1.unbox // error + val io = x1.unbox // now ok, was error io.use() diff --git a/tests/pos-custom-args/captures/list-encoding.scala b/tests/pos-custom-args/captures/list-encoding.scala index d959b523404b..8e94021c1285 100644 --- a/tests/pos-custom-args/captures/list-encoding.scala +++ b/tests/pos-custom-args/captures/list-encoding.scala @@ -7,7 +7,7 @@ type Op[T, C] = (v: T) => (s: C) => C type List[T] = - [C] -> (op: Op[T, C]) -> (s: C) ->{op} C + [C] -> (op: Op[T, C]) -> (s: C) ->{op*} C def nil[T]: List[T] = [C] => (op: Op[T, C]) => (s: C) => s diff --git a/tests/pos-custom-args/captures/path-use.scala b/tests/pos-custom-args/captures/path-use.scala new file mode 100644 index 000000000000..821bfc8ced21 --- /dev/null +++ b/tests/pos-custom-args/captures/path-use.scala @@ -0,0 +1,19 @@ +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 ff = () => println(c.f) + val _: () ->{c.f} Unit = ff + + val x = c.procs + val _: List[() ->{c.procs*} Unit] = x + + val g = () => println(c.procs.head) + val _: () ->{c.procs*} Unit = g diff --git a/tests/pos-custom-args/captures/reaches-mapcompose.scala b/tests/pos-custom-args/captures/reaches-mapcompose.scala new file mode 100644 index 000000000000..a01a8d30e67f --- /dev/null +++ b/tests/pos-custom-args/captures/reaches-mapcompose.scala @@ -0,0 +1,8 @@ +def compose1[A, B, C](f: A => B, g: B => C): A ->{f, g} C = + z => g(f(z)) + +def mapCompose[A](ps: List[(A => A, A => A)]): List[A ->{ps*} A] = + ps.map((x, y) => compose1(x, y)) // now ok + +def mapCompose2[A](ps: List[(A => A, A => A)]): List[A ->{ps*} A] = + ps.map((x, y) => compose1(x, y)) diff --git a/tests/pos-custom-args/captures/reaches.scala b/tests/pos-custom-args/captures/reaches.scala index ab0da9b67d18..f45dc5a430ec 100644 --- a/tests/pos-custom-args/captures/reaches.scala +++ b/tests/pos-custom-args/captures/reaches.scala @@ -1,4 +1,3 @@ -import caps.unbox class C def f(xs: List[C^]) = @@ -22,7 +21,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(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/unsafe-unbox.scala b/tests/pos-custom-args/captures/unsafe-unbox.scala index 1c523490e19d..451d107c520d 100644 --- a/tests/pos-custom-args/captures/unsafe-unbox.scala +++ b/tests/pos-custom-args/captures/unsafe-unbox.scala @@ -1,6 +1,7 @@ import annotation.unchecked.uncheckedCaptures def test = - val finalizeActionsInit = collection.mutable.ListBuffer[(() => Unit) @uncheckedCaptures]() + val finalizeActionsInit: (collection.mutable.ListBuffer[(() => Unit) @uncheckedCaptures]^) @uncheckedCaptures + = collection.mutable.ListBuffer() var finalizeActions = finalizeActionsInit val action = finalizeActions.remove(0) diff --git a/tests/pos/cc-poly-source-capability.scala b/tests/pos/cc-poly-source-capability.scala index 3b6c0bde1398..1c45c87ba352 100644 --- a/tests/pos/cc-poly-source-capability.scala +++ b/tests/pos/cc-poly-source-capability.scala @@ -1,10 +1,15 @@ import language.experimental.captureChecking import annotation.experimental import caps.{CapSet, Capability} -import caps.unbox @experimental object Test: + class Set[T] extends Pure: // Define sets as `Pure` needed after adding addImplied widening + def +[T](x: T): Set[T] = ??? + + object Set: + def empty[T]: Set[T] = ??? + class Async extends Capability def listener(async: Async): Listener^{async} = ??? @@ -18,7 +23,7 @@ import caps.unbox def allListeners: Set[Listener^{X^}] = listeners - def test1(async1: Async, @unbox others: List[Async]) = + def test1(async1: Async, others: List[Async]) = val src = Source[CapSet^{async1, others*}] val lst1 = listener(async1) val lsts = others.map(listener) @@ -29,5 +34,8 @@ import caps.unbox others.map(listener).foreach(src.register) val ls = src.allListeners val _: Set[Listener^{async1, others*}] = ls + // {ls, others*} would be added by addImplied here since sets are invariant + // But this is suppressed since Set is now declared to be pure. + diff --git a/tests/pos/cc-poly-source.scala b/tests/pos/cc-poly-source.scala index 4cfbbaa06936..b70fe90e4254 100644 --- a/tests/pos/cc-poly-source.scala +++ b/tests/pos/cc-poly-source.scala @@ -1,10 +1,15 @@ import language.experimental.captureChecking import annotation.experimental import caps.{CapSet, Capability} -import caps.unbox @experimental object Test: + class Set[T] extends Pure: // Define sets as `Pure` needed after adding addImplied widening + def +[T](x: T): Set[T] = ??? + + object Set: + def empty[T]: Set[T] = ??? + class Label //extends Capability class Listener @@ -25,7 +30,7 @@ import caps.unbox val ls = src.allListeners val _: Set[Listener^{lbl1, lbl2}] = ls - def test2(@unbox lbls: List[Label^]) = + def test2(lbls: List[Label^]) = def makeListener(lbl: Label^): Listener^{lbl} = ??? val listeners = lbls.map(makeListener) val src = Source[CapSet^{lbls*}] diff --git a/tests/pos/gears-probem-1.scala b/tests/pos/gears-probem-1.scala index ab71616b72fc..f5c7fdfd0a3c 100644 --- a/tests/pos/gears-probem-1.scala +++ b/tests/pos/gears-probem-1.scala @@ -1,5 +1,4 @@ import language.experimental.captureChecking -import caps.unbox trait Future[+T]: def await: T @@ -17,7 +16,7 @@ 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](fs: Seq[Future[T]^]) def awaitAll = val collector//: Collector[T]{val futures: Seq[Future[T]^{fs*}]} = Collector(fs) diff --git a/tests/pos/i13541.scala b/tests/pos/i13541.scala index 36ec09409b17..b93620badbdb 100644 --- a/tests/pos/i13541.scala +++ b/tests/pos/i13541.scala @@ -6,7 +6,7 @@ object Z: type Foo[B] = [A] =>> Bar[A, B] trait Bar[A, B] -given fooUnit[A: F]: Foo[Unit][A] = ??? +given fooUnit: [A: F] => Foo[Unit][A] = ??? //given bar[A: F]: Bar[A, Unit] = ??? def f[A: F](using Foo[Unit][A]): Nothing = ??? diff --git a/tests/pos/i18699.scala b/tests/pos/i18699.scala index 1937d7dca8c5..40c0c0ed791a 100644 --- a/tests/pos/i18699.scala +++ b/tests/pos/i18699.scala @@ -1,9 +1,8 @@ import language.experimental.captureChecking -import caps.unbox trait Cap: def use: Int = 42 -def test2(@unbox cs: List[Cap^]): Unit = +def test2(cs: List[Cap^]): Unit = val t0: Cap^{cs*} = cs.head // error var t1: Cap^{cs*} = cs.head // error diff --git a/tests/pos/i20342.scala b/tests/pos/i20342.scala index 250839680174..b6ed65428d48 100644 --- a/tests/pos/i20342.scala +++ b/tests/pos/i20342.scala @@ -1,8 +1,8 @@ class Repo[EC, E](using defaults: RepoDefaults[EC, E]) trait RepoDefaults[EC, E] object RepoDefaults: - inline given genImmutableRepo[E: DbCodec]: RepoDefaults[E, E] = ??? - inline given genRepo[EC: DbCodec, E: DbCodec]: RepoDefaults[EC, E] = ??? + inline given genImmutableRepo: [E: DbCodec] => RepoDefaults[E, E] = ??? + inline given genRepo: [EC: DbCodec, E: DbCodec] => RepoDefaults[EC, E] = ??? trait DbCodec[E] diff --git a/tests/pos/reach-capability.scala b/tests/pos/reach-capability.scala index 50ea479ec3c1..c444f14c1e22 100644 --- a/tests/pos/reach-capability.scala +++ b/tests/pos/reach-capability.scala @@ -1,7 +1,6 @@ import language.experimental.captureChecking import annotation.experimental import caps.Capability -import caps.unbox @experimental object Test2: @@ -12,7 +11,7 @@ import caps.unbox class Listener - def test2(@unbox lbls: List[Label]) = + def test2(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..19f7a50e5387 100644 --- a/tests/pos/reach-problem.scala +++ b/tests/pos/reach-problem.scala @@ -1,11 +1,10 @@ import language.experimental.captureChecking -import caps.unbox class Box[T](items: Seq[T^]): def getOne: T^{items*} = ??? object Box: - def getOne[T](@unbox items: Seq[T^]): T^{items*} = + def getOne[T](items: Seq[T^]): T^{items*} = val bx = Box(items) bx.getOne /*