diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 74c243a20684..21567d694bc9 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -407,12 +407,12 @@ class CheckCaptures extends Recheck, SymTransformer: else i"references $cs1$cs1description are not all", cs1, cs2, pos, provenance) - /** If `sym` is a class or method nested inside a term, a capture set variable representing - * the captured variables of the environment associated with `sym`. + /** If `sym` is a method or a non-static inner class, a capture set variable + * representing the captured variables of the environment associated with `sym`. */ def capturedVars(sym: Symbol)(using Context): CaptureSet = myCapturedVars.getOrElseUpdate(sym, - if sym.ownersIterator.exists(_.isTerm) + if sym.isTerm || !sym.owner.isStaticOwner then CaptureSet.Var(sym.owner, level = ccState.symLevel(sym)) else CaptureSet.empty) @@ -420,12 +420,9 @@ class CheckCaptures extends Recheck, SymTransformer: /** The next environment enclosing `env` that needs to be charged * with free references. - * @param included Whether an environment is included in the range of - * environments to charge. Once `included` is false, no - * more environments need to be charged. */ - def nextEnvToCharge(env: Env, included: Env => Boolean)(using Context): Env = - if env.owner.isConstructor && included(env.outer) then env.outer.outer + def nextEnvToCharge(env: Env)(using Context): Env | Null = + if env.owner.isConstructor then env.outer.outer0 else env.outer /** A description where this environment comes from */ @@ -458,21 +455,27 @@ class CheckCaptures extends Recheck, SymTransformer: markFree(sym, sym.termRef, tree) def markFree(sym: Symbol, ref: Capability, tree: Tree)(using Context): Unit = - if sym.exists && ref.isTracked then markFree(ref.singletonCaptureSet, tree) + if sym.exists then markFree(ref, tree) + + def markFree(ref: Capability, tree: Tree)(using Context): Unit = + if ref.isTracked then markFree(ref.singletonCaptureSet, tree) /** Make sure the (projected) `cs` is a subset of the capture sets of all enclosing * environments. At each stage, only include references from `cs` that are outside * the environment's owner */ - def markFree(cs: CaptureSet, tree: Tree)(using Context): Unit = + def markFree(cs: CaptureSet, tree: Tree, addUseInfo: Boolean = true)(using Context): Unit = // 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) = sym.exists && { + val effectiveOwner = + if env.owner.isConstructor then env.owner.owner + else env.owner if env.kind == EnvKind.NestedInOwner then - !sym.isProperlyContainedIn(env.owner) + !sym.isProperlyContainedIn(effectiveOwner) else - !sym.isContainedIn(env.owner) + !sym.isContainedIn(effectiveOwner) } /** Avoid locally defined capability by charging the underlying type @@ -535,13 +538,15 @@ class CheckCaptures extends Recheck, SymTransformer: checkSubset(included, env.captured, tree.srcPos, provenance(env)) capt.println(i"Include call or box capture $included from $cs in ${env.owner} --> ${env.captured}") if !isOfNestedMethod(env) then - recur(included, nextEnvToCharge(env, !_.owner.isStaticOwner), env) + val nextEnv = nextEnvToCharge(env) + if nextEnv != null && !nextEnv.owner.isStaticOwner then + recur(included, nextEnv, env) // Under deferredReaches, don't propagate out of methods inside terms. // The use set of these methods will be charged when that method is called. if !cs.isAlwaysEmpty then recur(cs, curEnv, null) - useInfos += ((tree, cs, curEnv)) + if addUseInfo then useInfos += ((tree, cs, curEnv)) end markFree /** If capability `c` refers to a parameter that is not implicitly or explicitly @@ -626,25 +631,33 @@ class CheckCaptures extends Recheck, SymTransformer: // If ident refers to a parameterless method, charge its cv to the environment includeCallCaptures(sym, sym.info, tree) else if !sym.isStatic then - // Otherwise charge its symbol, but add all selections and also any `.rd` - // modifier implied by the expected type `pt`. - // Example: If we have `x` and the expected type says we select that with `.a.b` - // where `b` is a read-only method, we charge `x.a.b.rd` instead of `x`. - def addSelects(ref: TermRef, pt: Type): Capability = pt match - case pt: PathSelectionProto if ref.isTracked => - if pt.sym.isReadOnlyMethod then - ref.readOnly - else - // 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 - var pathRef: Capability = addSelects(sym.termRef, pt) - if pathRef.derivesFromMutable && pt.isValueType && !pt.isMutableType then - pathRef = pathRef.readOnly - markFree(sym, pathRef, tree) + markFree(sym, pathRef(sym.termRef, pt), tree) mapResultRoots(super.recheckIdent(tree, pt), tree.symbol) + override def recheckThis(tree: This, pt: Type)(using Context): Type = + markFree(pathRef(tree.tpe.asInstanceOf[ThisType], pt), tree) + super.recheckThis(tree, pt) + + /** Add all selections and also any `.rd modifier implied by the expected + * type `pt` to `base`. Example: + * If we have `x` and the expected type says we select that with `.a.b` + * where `b` is a read-only method, we charge `x.a.b.rd` instead of `x`. + */ + private def pathRef(base: TermRef | ThisType, pt: Type)(using Context): Capability = + def addSelects(ref: TermRef | ThisType, pt: Type): Capability = pt match + case pt: PathSelectionProto if ref.isTracked => + if pt.sym.isReadOnlyMethod then + ref.readOnly + else + // 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: Capability = addSelects(base, pt) + if ref.derivesFromMutable && pt.isValueType && !pt.isMutableType + then ref.readOnly + else ref + /** The expected type for the qualifier of a selection. If the selection * could be part of a capability path or is a a read-only method, we return * a PathSelectionProto. @@ -866,7 +879,7 @@ class CheckCaptures extends Recheck, SymTransformer: val (refined, cs) = addParamArgRefinements(core, initCs) refined.capturing(cs) - augmentConstructorType(resType, capturedVars(cls) ++ capturedVars(constr)) + augmentConstructorType(resType, capturedVars(cls)) .showing(i"constr type $mt with $argTypes%, % in $constr = $result", capt) end refineConstructorInstance @@ -975,6 +988,8 @@ class CheckCaptures extends Recheck, SymTransformer: * - Interpolate contravariant capture set variables in result type. */ override def recheckValDef(tree: ValDef, sym: Symbol)(using Context): Type = + val savedEnv = curEnv + val runInConstructor = !sym.isOneOf(Param | ParamAccessor | Lazy | NonMember) try if sym.is(Module) then sym.info // Modules are checked by checking the module class else @@ -993,6 +1008,8 @@ class CheckCaptures extends Recheck, SymTransformer: "" disallowBadRootsIn( tree.tpt.nuType, NoSymbol, i"Mutable $sym", "have type", addendum, sym.srcPos) + if runInConstructor then + pushConstructorEnv() checkInferredResult(super.recheckValDef(tree, sym), tree) finally if !sym.is(Param) then @@ -1002,6 +1019,22 @@ class CheckCaptures extends Recheck, SymTransformer: // function is compiled since we do not propagate expected types into blocks. interpolateIfInferred(tree.tpt, sym) + def declaredCaptures = tree.tpt.nuType.captureSet + if runInConstructor && savedEnv.owner.isClass then + curEnv = savedEnv + markFree(declaredCaptures, tree, addUseInfo = false) + + if sym.owner.isStaticOwner && !declaredCaptures.elems.isEmpty && sym != defn.captureRoot then + def where = + if sym.effectiveOwner.is(Package) then "top-level definition" + else i"member of static ${sym.owner}" + report.warning( + em"""$sym has a non-empty capture set but will not be added as + |a capability to computed capture sets since it is globally accessible + |as a $where. Global values cannot be capabilities.""", + tree.namePos) + end recheckValDef + /** Recheck method definitions: * - check body in a nested environment that tracks uses, in a nested level, * and in a nested context that knows abaout Contains parameters so that we @@ -1228,6 +1261,24 @@ class CheckCaptures extends Recheck, SymTransformer: recheckFinish(result, arg, pt) */ + /** If environment is owned by a class, run in a new environment owned by + * its primary constructor instead. + */ + def pushConstructorEnv()(using Context): Unit = + if curEnv.owner.isClass then + val constr = curEnv.owner.primaryConstructor + if constr.exists then + val constrSet = capturedVars(constr) + if capturedVars(constr) ne CaptureSet.empty then + curEnv = Env(constr, EnvKind.Regular, constrSet, curEnv) + + override def recheckStat(stat: Tree)(using Context): Unit = + val saved = curEnv + if !stat.isInstanceOf[MemberDef] then + pushConstructorEnv() + try recheck(stat) + finally curEnv = saved + /** The main recheck method does some box adapation for all nodes: * - If expected type `pt` is boxed and the tree is a lambda or a reference, * don't propagate free variables. @@ -2021,7 +2072,9 @@ class CheckCaptures extends Recheck, SymTransformer: if env.kind == EnvKind.Boxed then env.owner else if isOfNestedMethod(env) then env.owner.owner else if env.owner.isStaticOwner then NoSymbol - else boxedOwner(nextEnvToCharge(env, alwaysTrue)) + else + val nextEnv = nextEnvToCharge(env) + if nextEnv == null then NoSymbol else boxedOwner(nextEnv) def checkUseUnlessBoxed(c: Capability, croot: NamedType) = if !boxedOwner(env).isContainedIn(croot.symbol.owner) then diff --git a/compiler/src/dotty/tools/dotc/transform/Recheck.scala b/compiler/src/dotty/tools/dotc/transform/Recheck.scala index fff8f3e94762..d6fd49336e9d 100644 --- a/compiler/src/dotty/tools/dotc/transform/Recheck.scala +++ b/compiler/src/dotty/tools/dotc/transform/Recheck.scala @@ -248,6 +248,12 @@ abstract class Recheck extends Phase, SymTransformer: def recheckSelection(tree: Select, qualType: Type, name: Name, pt: Type)(using Context): Type = recheckSelection(tree, qualType, name, sharpen = identity[Denotation]) + def recheckThis(tree: This, pt: Type)(using Context): Type = + tree.tpe + + def recheckSuper(tree: Super, pt: Type)(using Context): Type = + tree.tpe + def recheckBind(tree: Bind, pt: Type)(using Context): Type = tree match case Bind(name, body) => recheck(body, pt) @@ -487,12 +493,15 @@ abstract class Recheck extends Phase, SymTransformer: recheckStats(tree.stats) NoType + def recheckStat(stat: Tree)(using Context): Unit = + recheck(stat) + def recheckStats(stats: List[Tree])(using Context): Unit = @tailrec def traverse(stats: List[Tree])(using Context): Unit = stats match case (imp: Import) :: rest => traverse(rest)(using ctx.importContext(imp, imp.symbol)) case stat :: rest => - recheck(stat) + recheckStat(stat) traverse(rest) case _ => traverse(stats) @@ -540,7 +549,9 @@ abstract class Recheck extends Phase, SymTransformer: def recheckUnnamed(tree: Tree, pt: Type): Type = tree match case tree: Apply => recheckApply(tree, pt) case tree: TypeApply => recheckTypeApply(tree, pt) - case _: New | _: This | _: Super | _: Literal => tree.tpe + case tree: This => recheckThis(tree, pt) + case tree: Super => recheckSuper(tree, pt) + case _: New | _: Literal => tree.tpe case tree: Typed => recheckTyped(tree) case tree: Assign => recheckAssign(tree) case tree: Block => recheckBlock(tree, pt) diff --git a/library/src/scala/collection/Iterator.scala b/library/src/scala/collection/Iterator.scala index 6a6132990f76..2b8b9d45d6e4 100644 --- a/library/src/scala/collection/Iterator.scala +++ b/library/src/scala/collection/Iterator.scala @@ -418,7 +418,7 @@ trait Iterator[+A] extends IterableOnce[A] with IterableOnceOps[A, Iterator, Ite @deprecated("Call scanRight on an Iterable instead.", "2.13.0") def scanRight[B](z: B)(op: (A, B) => B): Iterator[B]^{this, op} = ArrayBuffer.from(this).scanRight(z)(op).iterator - + /** Finds index of the first element satisfying some predicate after or at some start index. * * $mayNotTerminateInf @@ -494,9 +494,9 @@ trait Iterator[+A] extends IterableOnce[A] with IterableOnceOps[A, Iterator, Ite while (p(hd) == isFlipped) { if (!self.hasNext) return false hd = self.next() - } + } hdDefined = true - true + true } def next() = @@ -874,7 +874,7 @@ trait Iterator[+A] extends IterableOnce[A] with IterableOnceOps[A, Iterator, Ite */ def duplicate: (Iterator[A]^{this}, Iterator[A]^{this}) = { val gap = new scala.collection.mutable.Queue[A] - var ahead: Iterator[A] = null + var ahead: Iterator[A]^ = null class Partner extends AbstractIterator[A] { override def knownSize: Int = self.synchronized { val thisSize = self.knownSize @@ -890,7 +890,7 @@ trait Iterator[+A] extends IterableOnce[A] with IterableOnceOps[A, Iterator, Ite if (gap.isEmpty) ahead = this if (this eq ahead) { val e = self.next() - gap enqueue e + gap.enqueue(e) e } else gap.dequeue() } @@ -918,7 +918,7 @@ trait Iterator[+A] extends IterableOnce[A] with IterableOnceOps[A, Iterator, Ite */ def patch[B >: A](from: Int, patchElems: Iterator[B]^, replaced: Int): Iterator[B]^{this, patchElems} = new AbstractIterator[B] { - private[this] var origElems = self + private[this] var origElems: Iterator[B]^ = self // > 0 => that many more elems from `origElems` before switching to `patchElems` // 0 => need to drop elems from `origElems` and start using `patchElems` // -1 => have dropped elems from `origElems`, will be using `patchElems` until it's empty diff --git a/library/src/scala/collection/LazyZipOps.scala b/library/src/scala/collection/LazyZipOps.scala index 9a7dc3173926..5849de61448b 100644 --- a/library/src/scala/collection/LazyZipOps.scala +++ b/library/src/scala/collection/LazyZipOps.scala @@ -389,7 +389,7 @@ final class LazyZip4[+El1, +El2, +El3, +El4, C1] private[collection](src: C1, } private def toIterable: View[(El1, El2, El3, El4)]^{this} = new AbstractView[(El1, El2, El3, El4)] { - def iterator: AbstractIterator[(El1, El2, El3, El4)] = new AbstractIterator[(El1, El2, El3, El4)] { + def iterator: AbstractIterator[(El1, El2, El3, El4)]^{this} = new AbstractIterator[(El1, El2, El3, El4)] { private[this] val elems1 = coll1.iterator private[this] val elems2 = coll2.iterator private[this] val elems3 = coll3.iterator diff --git a/library/src/scala/collection/SeqView.scala b/library/src/scala/collection/SeqView.scala index 960de6f1d752..3928a20aef70 100644 --- a/library/src/scala/collection/SeqView.scala +++ b/library/src/scala/collection/SeqView.scala @@ -202,10 +202,10 @@ object SeqView { override def knownSize: Int = len override def isEmpty: Boolean = len == 0 override def to[C1](factory: Factory[A, C1]): C1 = _sorted.to(factory) - override def reverse: SeqView[A] = new ReverseSorted + override def reverse: SeqView[A]^{this} = new ReverseSorted // we know `_sorted` is either tiny or has efficient random access, // so this is acceptable for `reversed` - override protected def reversed: Iterable[A] = new ReverseSorted + override protected def reversed: Iterable[A]^{this} = new ReverseSorted override def sorted[B1 >: A](implicit ord1: Ordering[B1]): SeqView[A]^{this} = if (ord1 == this.ord) this diff --git a/library/src/scala/collection/Stepper.scala b/library/src/scala/collection/Stepper.scala index 7c4b5821aef9..7685c2304c7c 100644 --- a/library/src/scala/collection/Stepper.scala +++ b/library/src/scala/collection/Stepper.scala @@ -260,7 +260,7 @@ trait IntStepper extends Stepper[Int] { def spliterator[B >: Int]: Spliterator.OfInt^{this} = new IntStepper.IntStepperSpliterator(this) - def javaIterator[B >: Int]: PrimitiveIterator.OfInt = new PrimitiveIterator.OfInt { + def javaIterator[B >: Int]: PrimitiveIterator.OfInt^{this} = new PrimitiveIterator.OfInt { def hasNext: Boolean = hasStep def nextInt(): Int = nextStep() } @@ -298,7 +298,7 @@ trait DoubleStepper extends Stepper[Double] { def spliterator[B >: Double]: Spliterator.OfDouble^{this} = new DoubleStepper.DoubleStepperSpliterator(this) - def javaIterator[B >: Double]: PrimitiveIterator.OfDouble = new PrimitiveIterator.OfDouble { + def javaIterator[B >: Double]: PrimitiveIterator.OfDouble^{this} = new PrimitiveIterator.OfDouble { def hasNext: Boolean = hasStep def nextDouble(): Double = nextStep() } @@ -337,7 +337,7 @@ trait LongStepper extends Stepper[Long] { def spliterator[B >: Long]: Spliterator.OfLong^{this} = new LongStepper.LongStepperSpliterator(this) - def javaIterator[B >: Long]: PrimitiveIterator.OfLong = new PrimitiveIterator.OfLong { + def javaIterator[B >: Long]: PrimitiveIterator.OfLong^{this} = new PrimitiveIterator.OfLong { def hasNext: Boolean = hasStep def nextLong(): Long = nextStep() } diff --git a/library/src/scala/collection/View.scala b/library/src/scala/collection/View.scala index 6b862aece4ea..78a3f49b1f24 100644 --- a/library/src/scala/collection/View.scala +++ b/library/src/scala/collection/View.scala @@ -172,7 +172,7 @@ object View extends IterableFactory[View] { @SerialVersionUID(3L) class LeftPartitionMapped[A, A1, A2](underlying: SomeIterableOps[A]^, f: A => Either[A1, A2]) extends AbstractView[A1] { - def iterator: AbstractIterator[A1] = new AbstractIterator[A1] { + def iterator: AbstractIterator[A1]^{this} = new AbstractIterator[A1] { private[this] val self = underlying.iterator private[this] var hd: A1 = _ private[this] var hdDefined: Boolean = false @@ -197,7 +197,7 @@ object View extends IterableFactory[View] { @SerialVersionUID(3L) class RightPartitionMapped[A, A1, A2](underlying: SomeIterableOps[A]^, f: A => Either[A1, A2]) extends AbstractView[A2] { - def iterator: AbstractIterator[A2] = new AbstractIterator[A2] { + def iterator: AbstractIterator[A2]^{this} = new AbstractIterator[A2] { private[this] val self = underlying.iterator private[this] var hd: A2 = _ private[this] var hdDefined: Boolean = false diff --git a/library/src/scala/collection/convert/StreamExtensions.scala b/library/src/scala/collection/convert/StreamExtensions.scala index 5e70ed1b4fd0..28b762b425f0 100644 --- a/library/src/scala/collection/convert/StreamExtensions.scala +++ b/library/src/scala/collection/convert/StreamExtensions.scala @@ -30,6 +30,7 @@ import scala.jdk._ * [[scala.jdk.javaapi.StreamConverters]]. */ trait StreamExtensions { + this: StreamExtensions => // collections implicit class IterableHasSeqStream[A](cc: IterableOnce[A]) { diff --git a/library/src/scala/collection/mutable/HashTable.scala b/library/src/scala/collection/mutable/HashTable.scala index 738adce7dfaa..5d0d1f35bdca 100644 --- a/library/src/scala/collection/mutable/HashTable.scala +++ b/library/src/scala/collection/mutable/HashTable.scala @@ -211,7 +211,7 @@ private[collection] trait HashTable[A, B, Entry >: Null <: HashEntry[A, Entry]] /** An iterator returning all entries. */ - def entriesIterator: Iterator[Entry] = new AbstractIterator[Entry] { + def entriesIterator: Iterator[Entry]^{this} = new AbstractIterator[Entry] { val iterTable = table var idx = lastPopulatedIndex var es = iterTable(idx) diff --git a/tests/neg-custom-args/captures/class-caps.check b/tests/neg-custom-args/captures/class-caps.check new file mode 100644 index 000000000000..362eee3e6170 --- /dev/null +++ b/tests/neg-custom-args/captures/class-caps.check @@ -0,0 +1,28 @@ +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/class-caps.scala:18:46 ----------------------------------- +18 | def addWritesToConsole: (Int, Int) -> Int = (a, b) => // error + | ^ + | Found: (a: Int, b: Int) ->{Test.this.console} Int + | Required: (Int, Int) -> Int + | + | Note that capability Test.this.console is not included in capture set {}. +19 | log(s"adding a ($a) to b ($b)")(using console) +20 | a + b + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/class-caps.scala:28:46 ----------------------------------- +28 | def addWritesToConsole: (Int, Int) -> Int = (a, b) => // error + | ^ + | Found: (a: Int, b: Int) ->{Test1.this.console} Int + | Required: (Int, Int) -> Int + | + | Note that capability Test1.this.console is not included in capture set {}. +29 | log(s"adding a ($a) to b ($b)")(using console) +30 | a + b + | + | longer explanation available when compiling with `-explain` +-- Warning: tests/neg-custom-args/captures/class-caps.scala:35:6 ------------------------------------------------------- +35 | val console: Console^ = Console() // provide capability locally + | ^^^^^^^ + | value console has a non-empty capture set but will not be added as + | a capability to computed capture sets since it is globally accessible + | as a member of static object Test2. Global values cannot be capabilities. diff --git a/tests/neg-custom-args/captures/class-caps.scala b/tests/neg-custom-args/captures/class-caps.scala new file mode 100644 index 000000000000..f49356c8aecb --- /dev/null +++ b/tests/neg-custom-args/captures/class-caps.scala @@ -0,0 +1,39 @@ +//> using scala 3.nightly + +import scala.language.experimental.captureChecking +import scala.caps.* + +class Console() extends SharedCapability: + def println(s: String): Unit = System.out.println(s"console: $s") + +def log(s: String)(using c: Console): Unit = + summon[Console].println(s) + +class Test: + this: Test^ => + val addPure: (Int, Int) -> Int = (a, b) => a + b // same as before + + val console: Console^ = Console() // provide capability locally + + def addWritesToConsole: (Int, Int) -> Int = (a, b) => // error + log(s"adding a ($a) to b ($b)")(using console) + a + b + + +class Test1: + val addPure: (Int, Int) -> Int = (a, b) => a + b // same as before + + val console: Console^ = Console() // provide capability locally + + def addWritesToConsole: (Int, Int) -> Int = (a, b) => // error + log(s"adding a ($a) to b ($b)")(using console) + a + b + +object Test2: + val addPure: (Int, Int) -> Int = (a, b) => a + b // same as before + + val console: Console^ = Console() // provide capability locally + + def addWritesToConsole: (Int, Int) -> Int = (a, b) => // ok since `console` is static (maybe flag this?) + log(s"adding a ($a) to b ($b)")(using console) + a + b diff --git a/tests/neg-custom-args/captures/class-constr.scala b/tests/neg-custom-args/captures/class-constr.scala index e86263fb0714..ab150868bef1 100644 --- a/tests/neg-custom-args/captures/class-constr.scala +++ b/tests/neg-custom-args/captures/class-constr.scala @@ -19,6 +19,6 @@ def test(a: Cap, b: Cap) = println(b) 2 val d = () => new D() - val d_ok1: () ->{a, b} D^{a, b} = d - val d_ok2: () -> D^{a, b} = d // because of function shorthand - val d_ok3: () ->{a, b} D^{b} = d // error, but should work + val d_ok1: () ->{a, b} D^{a, b} = d // ok + val d_ok2: () ->{a} D^{b} = d // ok + val d_ok3: () -> D^{a, b} = d // error diff --git a/tests/neg-custom-args/captures/exception-definitions.check b/tests/neg-custom-args/captures/exception-definitions.check index be5ea4304bf5..9429628b4bde 100644 --- a/tests/neg-custom-args/captures/exception-definitions.check +++ b/tests/neg-custom-args/captures/exception-definitions.check @@ -2,11 +2,11 @@ 3 | self: Err^ => // error | ^^^^ | Err is a pure type, it makes no sense to add a capture set to it --- Error: tests/neg-custom-args/captures/exception-definitions.scala:7:12 ---------------------------------------------- +-- Error: tests/neg-custom-args/captures/exception-definitions.scala:7:8 ----------------------------------------------- 7 | val x = c // error - | ^ - | reference (c : Any^) is not included in the allowed capture set {} of the self type of class Err2 + | ^^^^^^^^^ + | reference (c : Object^) is not included in the allowed capture set {} of the self type of class Err2 -- Error: tests/neg-custom-args/captures/exception-definitions.scala:8:13 ---------------------------------------------- -8 | class Err3(c: Any^) extends Exception // error +8 | class Err3(c: Object^) extends Exception // error | ^ - | reference (Err3.this.c : Any^) is not included in the allowed capture set {} of the self type of class Err3 + | reference (Err3.this.c : Object^) is not included in the allowed capture set {} of the self type of class Err3 diff --git a/tests/neg-custom-args/captures/exception-definitions.scala b/tests/neg-custom-args/captures/exception-definitions.scala index fbc9f3fd1d33..4c3370464ffe 100644 --- a/tests/neg-custom-args/captures/exception-definitions.scala +++ b/tests/neg-custom-args/captures/exception-definitions.scala @@ -2,11 +2,11 @@ class Err extends Exception: self: Err^ => // error -def test(c: Any^) = +def test(c: Object^) = class Err2 extends Exception: val x = c // error - class Err3(c: Any^) extends Exception // error + class Err3(c: Object^) extends Exception // error -class Err4(c: Any^) extends AnyVal // was error, now ok +class Err4(c: Object^) extends AnyVal // was error, now ok diff --git a/tests/neg-custom-args/captures/method-uses.scala b/tests/neg-custom-args/captures/method-uses.scala index da8f226685c0..576e227e5d74 100644 --- a/tests/neg-custom-args/captures/method-uses.scala +++ b/tests/neg-custom-args/captures/method-uses.scala @@ -11,7 +11,7 @@ def test(xs: List[() => Unit]) = foo // error bar() // error - Foo() // OK, but could be error + Foo() // error def test2(xs: List[() => Unit]) = def foo = xs.head // error, ok under deferredReaches @@ -23,8 +23,8 @@ def test3(xs: List[() => Unit]): () ->{xs*} Unit = () => def test4(xs: List[() => Unit]) = () => xs.head // error, ok under deferredReaches def test5(xs: List[() => Unit]) = new: - println(xs.head) // error, ok under deferredReaches + println(xs.head) // error, ok under deferredReaches // error def test6(xs: List[() => Unit]) = - val x= new { println(xs.head) } // error + val x= new { println(xs.head) } // error // error x diff --git a/tests/neg-custom-args/captures/nonsensical-for-pure.check b/tests/neg-custom-args/captures/nonsensical-for-pure.check index 6860446c4177..2df943df4262 100644 --- a/tests/neg-custom-args/captures/nonsensical-for-pure.check +++ b/tests/neg-custom-args/captures/nonsensical-for-pure.check @@ -2,3 +2,9 @@ 1 |val x: Int^ = 2 // error | ^^^^ | Int is a pure type, it makes no sense to add a capture set to it +-- Warning: tests/neg-custom-args/captures/nonsensical-for-pure.scala:1:4 ---------------------------------------------- +1 |val x: Int^ = 2 // error + | ^ + | value x has a non-empty capture set but will not be added as + | a capability to computed capture sets since it is globally accessible + | as a top-level definition. Global values cannot be capabilities. diff --git a/tests/neg-custom-args/captures/real-try.check b/tests/neg-custom-args/captures/real-try.check index bca841e11094..3fde3af88fd9 100644 --- a/tests/neg-custom-args/captures/real-try.check +++ b/tests/neg-custom-args/captures/real-try.check @@ -43,7 +43,7 @@ -- Error: tests/neg-custom-args/captures/real-try.scala:32:10 ---------------------------------------------------------- 32 | val b = try // error | ^ - | The result of `try` cannot have type Cell[() => Unit]^'s2 since + | The result of `try` cannot have type Cell[() => Unit] since | the part () => Unit of that type captures the root capability `cap`. | This is often caused by a locally generated exception capability leaking as part of its result. | diff --git a/tests/neg-custom-args/captures/unsound-reach-4.check b/tests/neg-custom-args/captures/unsound-reach-4.check index 573e9a31d068..cddcc58e38b4 100644 --- a/tests/neg-custom-args/captures/unsound-reach-4.check +++ b/tests/neg-custom-args/captures/unsound-reach-4.check @@ -8,7 +8,7 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/unsound-reach-4.scala:20:29 ------------------------------ 20 | val backdoor: Foo[File^] = new Bar // error (follow-on, since the parent Foo[File^] of bar is illegal). | ^^^^^^^ - | Found: Bar^'s1 + | Found: Bar | Required: Foo[File^] | | Note that capability cap is not included in capture set {cap²} diff --git a/tests/neg-custom-args/captures/unsound-reach.check b/tests/neg-custom-args/captures/unsound-reach.check index 7f713586b6ec..0313ebe37b88 100644 --- a/tests/neg-custom-args/captures/unsound-reach.check +++ b/tests/neg-custom-args/captures/unsound-reach.check @@ -15,7 +15,7 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/unsound-reach.scala:18:31 -------------------------------- 18 | val backdoor: Foo[File^] = new Bar // error (follow-on, since the parent Foo[File^] of bar is illegal). | ^^^^^^^ - | Found: Bar^'s1 + | Found: Bar | Required: Foo[File^] | | Note that capability cap is not included in capture set {cap²} diff --git a/tests/neg-custom-args/captures/uses.check b/tests/neg-custom-args/captures/uses.check index 08c79dd8fb5f..5ada150ddf79 100644 --- a/tests/neg-custom-args/captures/uses.check +++ b/tests/neg-custom-args/captures/uses.check @@ -1,21 +1,21 @@ --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/uses.scala:8:17 ------------------------------------------ -8 | val _: D^{y} = d // error, should be ok - | ^ - | Found: (d : D^{x, y}) - | Required: D^{y} - | - | Note that capability x is not included in capture set {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}) + | Found: (d : D^{y}) | Required: D | - | Note that capability x is not included in capture set {}. + | Note that capability y is not included in capture set {}. | | 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} Unit) + | Required: () -> Unit + | + | Note that capability x is not included in capture set {}. + | + | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/uses.scala:18:34 ----------------------------------------- 18 | val _: () ->{x} () ->{y} Unit = g // error, should be ok | ^ diff --git a/tests/neg-custom-args/captures/uses.scala b/tests/neg-custom-args/captures/uses.scala index b872c7b03ec7..28d85b64687d 100644 --- a/tests/neg-custom-args/captures/uses.scala +++ b/tests/neg-custom-args/captures/uses.scala @@ -5,12 +5,12 @@ def test(x: C^, y: C^) = def foo() = println(y) } val d = D() - val _: D^{y} = d // error, should be ok + val _: D^{y} = d val _: D = d // error val f = () => println(D()) val _: () ->{x} Unit = f // ok - val _: () -> Unit = f // should be error + val _: () -> Unit = f // error def g = () => println(x) diff --git a/tests/pos-custom-args/captures/constr-uses.scala b/tests/pos-custom-args/captures/constr-uses.scala new file mode 100644 index 000000000000..cf32cfcfe4e3 --- /dev/null +++ b/tests/pos-custom-args/captures/constr-uses.scala @@ -0,0 +1,13 @@ +abstract class Test: + this: Test^ => + def outer: Int + + class Inner(x: Int): + def this() = this(outer) + + val i = Inner() + val _: Inner = i + + val f = () => Inner() + val _: () ->{this} Inner = f + diff --git a/tests/pos-custom-args/captures/i23421.scala b/tests/pos-custom-args/captures/i23421.scala index ef5e7564073e..87458e6faeac 100644 --- a/tests/pos-custom-args/captures/i23421.scala +++ b/tests/pos-custom-args/captures/i23421.scala @@ -12,5 +12,5 @@ object Collection: trait Foo: val thunks: Collection[() => Unit] // that's fine -object FooImpl1 extends Foo: +class FooImpl1 extends Foo: val thunks: Collection[() => Unit] = Collection.empty // was error, now ok diff --git a/tests/pos-custom-args/captures/nested-classes.scala b/tests/pos-custom-args/captures/nested-classes.scala index e9227889f10e..bb422ceabd8f 100644 --- a/tests/pos-custom-args/captures/nested-classes.scala +++ b/tests/pos-custom-args/captures/nested-classes.scala @@ -5,6 +5,7 @@ class IO extends caps.SharedCapability class Blah class Pkg(using io: IO): class Foo: + this: Foo^{Pkg.this} => def m(foo: Blah^{io}) = ??? class Pkg2(using io: IO): class Foo: diff --git a/tests/pos-custom-args/captures/nested-traits.scala b/tests/pos-custom-args/captures/nested-traits.scala new file mode 100644 index 000000000000..b532feedeeee --- /dev/null +++ b/tests/pos-custom-args/captures/nested-traits.scala @@ -0,0 +1,7 @@ +trait MapOps[K]: + this: MapOps[K]^ => + def keysIterator: Iterator[K] + + trait GenKeySet[K]: + this: collection.Set[K] => + private[MapOps] val allKeys = MapOps.this.keysIterator.toSet \ No newline at end of file diff --git a/tests/pos-custom-args/captures/secondary-constructors.scala b/tests/pos-custom-args/captures/secondary-constructors.scala new file mode 100644 index 000000000000..abc923a3a8a3 --- /dev/null +++ b/tests/pos-custom-args/captures/secondary-constructors.scala @@ -0,0 +1,13 @@ +package scala +package collection +package immutable + +final class LazyListIterable[+A](): + this: LazyListIterable[A]^ => + var _head: Any = 0 + private def this(head: A, tail: LazyListIterable[A]^) = + this() + _head = head + +object LazyListIterable: + @inline private def eagerCons[A](hd: A, tl: LazyListIterable[A]^): LazyListIterable[A]^{tl} = new LazyListIterable[A](hd, tl) \ No newline at end of file diff --git a/tests/run-custom-args/captures/colltest5/CollectionStrawManCC5_1.scala b/tests/run-custom-args/captures/colltest5/CollectionStrawManCC5_1.scala index ea0bdc240e0c..1143cdb30d5e 100644 --- a/tests/run-custom-args/captures/colltest5/CollectionStrawManCC5_1.scala +++ b/tests/run-custom-args/captures/colltest5/CollectionStrawManCC5_1.scala @@ -423,7 +423,7 @@ object CollectionStrawMan5 { def start: Int def end: Int def apply(i: Int): A - def iterator: Iterator[A] = new Iterator[A] { + def iterator: Iterator[A]^{this} = new Iterator[A] { private var current = start def hasNext = current < end def next(): A = {