Skip to content

Commit

Permalink
Drop @unbox
Browse files Browse the repository at this point in the history
Convert remaining occurrences in tests to @use
  • Loading branch information
odersky committed Oct 9, 2024
1 parent 1746091 commit 1391da7
Show file tree
Hide file tree
Showing 15 changed files with 44 additions and 126 deletions.
102 changes: 23 additions & 79 deletions compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala
Original file line number Diff line number Diff line change
Expand Up @@ -402,15 +402,16 @@ class CheckCaptures extends Recheck, SymTransformer:
&& (!ccConfig.useSealed || refSym.is(Param))
&& refOwner == env.owner
then
if refSym.hasAnnotation(defn.UnboxAnnot)
|| ref.info.hasAnnotation(defn.UseAnnot)
|| c.isUnderUse
then
if ref.info.hasAnnotation(defn.UseAnnot)|| c.isUnderUse 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.
// Reach capabilities are exempted if
// - they are under-use capabilties that were generated from a cap
// appearing under a @use, or
// - their reference has a type thar carries a @use annotation. In that
// case callers will charge the deep capture set of the argument.
val cs = CaptureSet.ofInfo(c)
cs.disallowRootCapability: () =>
def kind = if c.isReach then "reach capability" else "capture set variable"
Expand All @@ -428,48 +429,14 @@ class CheckCaptures extends Recheck, SymTransformer:
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)
includeCallCaptures(tree.symbol, tree.symbol.info, tree.srcPos)
else if !tree.symbol.isStatic then
//debugShowEnvs()
def addSelects(ref: TermRef, pt: Type): TermRef = pt match
Expand Down Expand Up @@ -574,16 +541,15 @@ 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)
accountForUses(arg, argType, formal)
if unboxedArgs.contains(arg) then
capt.println(i"charging deep capture set of $arg: ${argType} = ${argType.deepCaptureSet}")
markFree(argType.deepCaptureSet, arg.srcPos)
argType

class MapUses(deep: Boolean)(using Context) extends TypeMap:
Expand Down Expand Up @@ -620,27 +586,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 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.
*/
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(_.captureSet)
appType match
case appType @ CapturingType(appType1, refs)
if qualType.exists
Expand Down Expand Up @@ -735,8 +692,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
Expand Down Expand Up @@ -1423,21 +1382,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) =
Expand Down
3 changes: 1 addition & 2 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -552,7 +552,7 @@ class Definitions {
ScalaPackageClass, tpnme.maybeCapability, Final, List(StaticAnnotationClass.typeRef)))

/** A type `type <use>[+T] <: T` used locally in capture checking. At certain points
* `T @use` types are converted to `<use>[T]` types. These types are handled as
* `T @use` types are converted to `<use>[T]` types. These types are handled as
* compile-time applied types by TypeComparer.
*/
@tu lazy val UseType: TypeSymbol =
Expand Down Expand Up @@ -1068,7 +1068,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")
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
1 change: 0 additions & 1 deletion library/src/scala/caps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ import annotation.{experimental, compileTimeOnly, retainsCap}
/** 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
final class use extends annotation.StaticAnnotation

object unsafe:
Expand Down
2 changes: 1 addition & 1 deletion tests/neg-custom-args/captures/spread-problem.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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]^ @caps.use)*): Source[T]^{sources*} = ???

def raceTwo[T](src1: Source[T]^, src2: Source[T]^): Source[T]^{} =
race(Seq(src1, src2)*) // error
Expand Down
4 changes: 1 addition & 3 deletions tests/pos-custom-args/captures/Buffer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,8 @@ trait Buffer[A]:
def flatMapInPlace(f: A => IterableOnce[A]^): this.type = {
val g = f
val s = 10
// capture checking: we need the copy since we box/unbox on g* on the next line
// capture checking: we need the copy since we box/unbox on g* on the next line
// TODO: This looks fishy, need to investigate
// Alternative would be to mark `f` as @unbox. It's not inferred
// since `^ appears in a function result, not under a box.
val newElems = new Array[(IterableOnce[A]^{f})](s)
var i = 0
while i < s do
Expand Down
6 changes: 3 additions & 3 deletions tests/pos-custom-args/captures/dep-reach.scala
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import caps.unbox
import caps.use
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 @use *): () ->{xs*} Unit =
xs.head
val a: () ->{c} Unit = () => ()
val b: () ->{d} Unit = () => ()
Expand All @@ -13,7 +13,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) @use]): () ->{xs*} Unit =
xs.head

val a: () ->{c} Unit = () => ()
Expand Down
4 changes: 2 additions & 2 deletions tests/pos-custom-args/captures/reaches.scala
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import caps.unbox
import caps.use

class C
def f(xs: List[C^]) =
Expand All @@ -22,7 +22,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] @use): Unit =
var cur: List[() ->{xs*} Unit] = xs // OK, by revised VAR
while cur.nonEmpty do
val next: () ->{xs*} Unit = cur.head
Expand Down
22 changes: 0 additions & 22 deletions tests/pos/Buffer.scala

This file was deleted.

4 changes: 2 additions & 2 deletions tests/pos/cc-poly-source-capability.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import language.experimental.captureChecking
import annotation.experimental
import caps.{CapSet, Capability}
import caps.unbox
import caps.use

@experimental object Test:

Expand All @@ -18,7 +18,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 @use]) =
val src = Source[CapSet^{async1, others*}]
val lst1 = listener(async1)
val lsts = others.map(listener)
Expand Down
4 changes: 2 additions & 2 deletions tests/pos/cc-poly-source.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import language.experimental.captureChecking
import annotation.experimental
import caps.{CapSet, Capability}
import caps.unbox
import caps.use

@experimental object Test:

Expand All @@ -25,7 +25,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^ @use]) =
def makeListener(lbl: Label^): Listener^{lbl} = ???
val listeners = lbls.map(makeListener)
val src = Source[CapSet^{lbls*}]
Expand Down
4 changes: 2 additions & 2 deletions tests/pos/gears-probem-1.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import language.experimental.captureChecking
import caps.unbox
import caps.use

trait Future[+T]:
def await: T
Expand All @@ -17,7 +17,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]^ @use])
def awaitAll =
val collector//: Collector[T]{val futures: Seq[Future[T]^{fs*}]}
= Collector(fs)
Expand Down
4 changes: 2 additions & 2 deletions tests/pos/i18699.scala
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import language.experimental.captureChecking
import caps.unbox
import caps.use

trait Cap:
def use: Int = 42

def test2(@unbox cs: List[Cap^]): Unit =
def test2(cs: List[Cap^] @use): Unit =
val t0: Cap^{cs*} = cs.head // error
var t1: Cap^{cs*} = cs.head // error
4 changes: 2 additions & 2 deletions tests/pos/reach-capability.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import language.experimental.captureChecking
import annotation.experimental
import caps.Capability
import caps.unbox
import caps.use

@experimental object Test2:

Expand All @@ -12,7 +12,7 @@ import caps.unbox

class Listener

def test2(@unbox lbls: List[Label]) =
def test2(lbls: List[Label] @use) =
def makeListener(lbl: Label): Listener^{lbl} = ???
val listeners = lbls.map(makeListener) // should work

4 changes: 2 additions & 2 deletions tests/pos/reach-problem.scala
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import language.experimental.captureChecking
import caps.unbox
import caps.use

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^ @use]): T^{items*} =
val bx = Box(items)
bx.getOne
/*
Expand Down

0 comments on commit 1391da7

Please sign in to comment.