diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala index 835e413463bd..cde0431e3e86 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala @@ -1120,6 +1120,7 @@ object CaptureSet: */ def ofTypeDeeply(tp: Type)(using Context): CaptureSet = val collect = new TypeAccumulator[CaptureSet]: + val seen = util.HashSet[Symbol]() def apply(cs: CaptureSet, t: Type) = if variance <= 0 then cs else t.dealias match @@ -1127,6 +1128,9 @@ object CaptureSet: this(cs, p) ++ cs1 case t @ AnnotatedType(parent, ann) => this(cs, parent) + case t: TypeRef if t.symbol.isAbstractOrParamType && !seen.contains(t.symbol) => + seen += t.symbol + this(cs, t.info.bounds.hi) case t @ FunctionOrMethod(args, res @ Existential(_, _)) if args.forall(_.isAlwaysPure) => this(cs, Existential.toCap(res)) diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 45561395263b..ba4445751117 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -655,7 +655,7 @@ class CheckCaptures extends Recheck, SymTransformer: for (getterName, argType) <- mt.paramNames.lazyZip(argTypes) do val getter = cls.info.member(getterName).suchThat(_.isRefiningParamAccessor).symbol if !getter.is(Private) && getter.hasTrackedParts then - refined = RefinedType(refined, getterName, argType) + refined = RefinedType(refined, getterName, argType.unboxed) allCaptures ++= argType.captureSet (refined, allCaptures) diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index c88e2219e5f1..c55ffe3f1418 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -73,11 +73,15 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: private def newFlagsFor(symd: SymDenotation)(using Context): FlagSet = object containsCovarRetains extends TypeAccumulator[Boolean]: + val seen = util.HashSet[Symbol]() def apply(x: Boolean, tp: Type): Boolean = if x then true else if tp.derivesFromCapability && variance >= 0 then true else tp match case AnnotatedType(_, ann) if ann.symbol.isRetains && variance >= 0 => true + case t: TypeRef if t.symbol.isAbstractOrParamType && !seen.contains(t.symbol) => + seen += t.symbol + apply(x, t.info.bounds.hi) case _ => foldOver(x, tp) def apply(tp: Type): Boolean = apply(false, tp) diff --git a/tests/neg-custom-args/captures/dcs-tvar.check b/tests/neg-custom-args/captures/dcs-tvar.check new file mode 100644 index 000000000000..d3caa720e88a --- /dev/null +++ b/tests/neg-custom-args/captures/dcs-tvar.check @@ -0,0 +1,10 @@ +-- Error: tests/neg-custom-args/captures/dcs-tvar.scala:6:15 ----------------------------------------------------------- +6 | () => runOps(xs) // error + | ^^ + | reference xs* is not included in the allowed capture set {} + | of an enclosing function literal with expected type () -> Unit +-- Error: tests/neg-custom-args/captures/dcs-tvar.scala:9:15 ----------------------------------------------------------- +9 | () => runOps(xs) // error + | ^^ + | reference xs* is not included in the allowed capture set {} + | of an enclosing function literal with expected type () -> Unit diff --git a/tests/neg-custom-args/captures/dcs-tvar.scala b/tests/neg-custom-args/captures/dcs-tvar.scala new file mode 100644 index 000000000000..381c08b4d351 --- /dev/null +++ b/tests/neg-custom-args/captures/dcs-tvar.scala @@ -0,0 +1,9 @@ +import caps.use + +def runOps(@use xs: List[() => Unit]): Unit = ??? + +def f[T <: List[() => Unit]](xs: T): () -> Unit = + () => runOps(xs) // error + +def g[T <: List[U], U <: () => Unit](xs: T): () -> Unit = + () => runOps(xs) // error diff --git a/tests/neg-custom-args/captures/i21646.scala b/tests/neg-custom-args/captures/i21646.scala index cf451ded3120..92aba9fda5d1 100644 --- a/tests/neg-custom-args/captures/i21646.scala +++ b/tests/neg-custom-args/captures/i21646.scala @@ -9,5 +9,5 @@ class Resource[T <: Capability](gen: T): @main def run = val myFile: File = ??? - val r = Resource(myFile) // error + val r = Resource(myFile) // now ok, was error () diff --git a/tests/neg-custom-args/captures/unsound-reach-6.scala b/tests/neg-custom-args/captures/unsound-reach-6.scala new file mode 100644 index 000000000000..b7306dca4190 --- /dev/null +++ b/tests/neg-custom-args/captures/unsound-reach-6.scala @@ -0,0 +1,22 @@ +class IO + +def f(xs: List[() => Unit]): () => Unit = () => + println(xs.head) // error + +def test(io: IO^)(ys: List[() ->{io} Unit]) = + val x = () => + val z = f(ys) + z() + val _: () -> Unit = x // !!! ys* gets lost + () + +def test(io: IO^) = + def ys: List[() ->{io} Unit] = ??? + val x = () => + val z = f(ys) + z() + val _: () -> Unit = x // !!! io gets lost + () + + + diff --git a/tests/neg-custom-args/captures/use-override.scala b/tests/neg-custom-args/captures/use-override.scala new file mode 100644 index 000000000000..febb59ca4208 --- /dev/null +++ b/tests/neg-custom-args/captures/use-override.scala @@ -0,0 +1,15 @@ +import caps.use + +def test(io: Object^, async: Object^) = + + trait A: + def f(@use x: List[() ->{io} Unit]): Unit + + class B extends A: + def f(@use x: List[() => Unit]): Unit = // error, would be unsound if allowed + x.foreach(_()) + + class C extends A: + def f(@use x: List[() ->{io, async} Unit]): Unit = // error, this one could be soundly allowed actually + x.foreach(_()) + diff --git a/tests/pos-custom-args/captures/i21646.scala b/tests/pos-custom-args/captures/i21646.scala new file mode 100644 index 000000000000..cf451ded3120 --- /dev/null +++ b/tests/pos-custom-args/captures/i21646.scala @@ -0,0 +1,13 @@ +import language.experimental.captureChecking +import caps.Capability + +trait File extends Capability + +class Resource[T <: Capability](gen: T): + def use[U](f: T => U): U = + f(gen) // OK, was error under unsealed + +@main def run = + val myFile: File = ??? + val r = Resource(myFile) // error + ()