Skip to content

Commit

Permalink
Follow upper bounds of type variables when computing dcs
Browse files Browse the repository at this point in the history
  • Loading branch information
odersky committed Nov 2, 2024
1 parent 128a2d7 commit 735506b
Show file tree
Hide file tree
Showing 9 changed files with 79 additions and 2 deletions.
4 changes: 4 additions & 0 deletions compiler/src/dotty/tools/dotc/cc/CaptureSet.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1120,13 +1120,17 @@ 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
case t @ CapturingType(p, cs1) =>
this(cs, p) ++ cs1
case t @ AnnotatedType(parent, ann) =>
this(cs, parent)
case t: TypeRef if t.symbol.isAbstractOrParamType && !seen.contains(t.symbol) =>
seen += t.symbol
this(cs, t.info.bounds.hi)
case t @ FunctionOrMethod(args, res @ Existential(_, _))
if args.forall(_.isAlwaysPure) =>
this(cs, Existential.toCap(res))
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
4 changes: 4 additions & 0 deletions compiler/src/dotty/tools/dotc/cc/Setup.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
10 changes: 10 additions & 0 deletions tests/neg-custom-args/captures/dcs-tvar.check
Original file line number Diff line number Diff line change
@@ -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
9 changes: 9 additions & 0 deletions tests/neg-custom-args/captures/dcs-tvar.scala
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion tests/neg-custom-args/captures/i21646.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
()
22 changes: 22 additions & 0 deletions tests/neg-custom-args/captures/unsound-reach-6.scala
Original file line number Diff line number Diff line change
@@ -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
()



15 changes: 15 additions & 0 deletions tests/neg-custom-args/captures/use-override.scala
Original file line number Diff line number Diff line change
@@ -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(_())

13 changes: 13 additions & 0 deletions tests/pos-custom-args/captures/i21646.scala
Original file line number Diff line number Diff line change
@@ -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
()

0 comments on commit 735506b

Please sign in to comment.