Skip to content

Fix the visibility check in markFree #20221

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 26 additions & 3 deletions compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala
Original file line number Diff line number Diff line change
Expand Up @@ -358,9 +358,17 @@ class CheckCaptures extends Recheck, SymTransformer:
def markFree(cs: CaptureSet, pos: SrcPos)(using Context): Unit =
if !cs.isAlwaysEmpty then
forallOuterEnvsUpTo(ctx.owner.topLevelClass): env =>
def isVisibleFromEnv(sym: Symbol) =
(env.kind == EnvKind.NestedInOwner || env.owner != sym)
&& env.owner.isContainedIn(sym)
// 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)
// Only captured references that are visible from the environment
// should be included.
val included = cs.filter:
case ref: TermRef => isVisibleFromEnv(ref.symbol.owner)
case ref: ThisType => isVisibleFromEnv(ref.cls)
Expand All @@ -378,6 +386,7 @@ class CheckCaptures extends Recheck, SymTransformer:
// there won't be an apply; need to include call captures now
includeCallCaptures(tree.symbol, tree.srcPos)
else
//debugShowEnvs()
markFree(tree.symbol, tree.srcPos)
super.recheckIdent(tree, pt)

Expand Down Expand Up @@ -946,6 +955,19 @@ class CheckCaptures extends Recheck, SymTransformer:
expected
end addOuterRefs

/** A debugging method for showing the envrionments during capture checking. */
private def debugShowEnvs()(using Context): Unit =
def showEnv(env: Env): String = i"Env(${env.owner}, ${env.kind}, ${env.captured})"
val sb = StringBuilder()
@annotation.tailrec def walk(env: Env | Null): Unit =
if env != null then
sb ++= showEnv(env)
sb ++= "\n"
walk(env.outer0)
sb ++= "===== Current Envs ======\n"
walk(curEnv)
sb ++= "===== End ======\n"
println(sb.result())

/** Adapt `actual` type to `expected` type by inserting boxing and unboxing conversions
*
Expand Down Expand Up @@ -1085,6 +1107,7 @@ class CheckCaptures extends Recheck, SymTransformer:
pos)
}
if !insertBox then // unboxing
//debugShowEnvs()
markFree(criticalSet, pos)
adaptedType(!boxed)
else
Expand Down
8 changes: 6 additions & 2 deletions compiler/src/dotty/tools/dotc/cc/Setup.scala
Original file line number Diff line number Diff line change
Expand Up @@ -407,8 +407,12 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:

case tree @ TypeApply(fn, args) =>
traverse(fn)
for case arg: TypeTree <- args do
transformTT(arg, boxed = true, exact = false) // type arguments in type applications are boxed
fn match
case Select(qual, nme.asInstanceOf_) =>
// No need to box type arguments of an asInstanceOf call. See #20224.
case _ =>
for case arg: TypeTree <- args do
transformTT(arg, boxed = true, exact = false) // type arguments in type applications are boxed

case tree: TypeDef if tree.symbol.isClass =>
inContext(ctx.withOwner(tree.symbol)):
Expand Down
15 changes: 15 additions & 0 deletions tests/neg-custom-args/captures/i16725.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import language.experimental.captureChecking
@annotation.capability
class IO:
def brewCoffee(): Unit = ???
def usingIO[T](op: IO => T): T = ???

type Wrapper[T] = [R] -> (f: T => R) -> R
def mk[T](x: T): Wrapper[T] = [R] => f => f(x)
def useWrappedIO(wrapper: Wrapper[IO]): () -> Unit =
() =>
wrapper: io => // error
io.brewCoffee()
def main(): Unit =
val escaped = usingIO(io => useWrappedIO(mk(io)))
escaped() // boom
8 changes: 8 additions & 0 deletions tests/neg-custom-args/captures/i20169.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
case class Box[T](x: T):
def foreach(f: T => Unit): Unit = f(x)

def runOps(ops: Box[() => Unit]): () -> Unit =
val applyFn: (() => Unit) -> Unit = f => f()
val fn: () -> Unit = () =>
ops.foreach(applyFn) // error
fn