Skip to content

Commit ba8db28

Browse files
committed
Add unsafeUnbox as an escape hatch for unboxing *
1 parent f657399 commit ba8db28

File tree

5 files changed

+61
-28
lines changed

5 files changed

+61
-28
lines changed

compiler/src/dotty/tools/dotc/cc/CaptureOps.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,13 @@ extension (tp: Type)
9696
/** Is the boxedCaptureSet of this type nonempty? */
9797
def isBoxedCapturing(using Context) = !tp.boxedCaptureSet.isAlwaysEmpty
9898

99+
/** If this type is a boxed capturing type, its unboxed version */
100+
def unbox(using Context): Type = tp match
101+
case tp @ CapturingType(parent, refs) if tp.isBoxed =>
102+
CapturingType(parent, refs, boxed = false)
103+
case _ =>
104+
tp
105+
99106
/** Map capturing type to their parents. Capturing types accessible
100107
* via dealising are also stripped.
101108
*/
@@ -155,6 +162,7 @@ extension (sym: Symbol)
155162
case _ => false
156163
containsEnclTypeParam(sym.info.finalResultType)
157164
&& !sym.allowsRootCapture
165+
&& sym != defn.Caps_unsafeUnbox
158166

159167
extension (tp: AnnotatedType)
160168
/** Is this a boxed capturing type? */

compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -333,22 +333,29 @@ class CheckCaptures extends Recheck, SymTransformer:
333333
*/
334334
override def recheckApply(tree: Apply, pt: Type)(using Context): Type =
335335
includeCallCaptures(tree.symbol, tree.srcPos)
336-
super.recheckApply(tree, pt) match
337-
case appType @ CapturingType(appType1, refs) =>
338-
tree.fun match
339-
case Select(qual, _)
340-
if !tree.fun.symbol.isConstructor
341-
&& !qual.tpe.isBoxedCapturing
342-
&& !tree.args.exists(_.tpe.isBoxedCapturing)
343-
&& qual.tpe.captureSet.mightSubcapture(refs)
344-
&& tree.args.forall(_.tpe.captureSet.mightSubcapture(refs))
345-
=>
346-
val callCaptures = tree.args.foldLeft(qual.tpe.captureSet)((cs, arg) =>
347-
cs ++ arg.tpe.captureSet)
348-
appType.derivedCapturingType(appType1, callCaptures)
349-
.showing(i"narrow $tree: $appType, refs = $refs, qual = ${qual.tpe.captureSet} --> $result", capt)
350-
case _ => appType
351-
case appType => appType
336+
tree match
337+
case Apply(fn, arg :: Nil) if fn.symbol == defn.Caps_unsafeUnbox =>
338+
val argType0 = recheckStart(arg, pt).unbox
339+
val argType = super.recheckFinish(argType0, arg, pt)
340+
super.recheckFinish(argType, tree, pt)
341+
case _ =>
342+
super.recheckApply(tree, pt) match
343+
case appType @ CapturingType(appType1, refs) =>
344+
tree.fun match
345+
case Select(qual, _)
346+
if !tree.fun.symbol.isConstructor
347+
&& !qual.tpe.isBoxedCapturing
348+
&& !tree.args.exists(_.tpe.isBoxedCapturing)
349+
&& qual.tpe.captureSet.mightSubcapture(refs)
350+
&& tree.args.forall(_.tpe.captureSet.mightSubcapture(refs))
351+
=>
352+
val callCaptures = tree.args.foldLeft(qual.tpe.captureSet)((cs, arg) =>
353+
cs ++ arg.tpe.captureSet)
354+
appType.derivedCapturingType(appType1, callCaptures)
355+
.showing(i"narrow $tree: $appType, refs = $refs, qual = ${qual.tpe.captureSet} --> $result", capt)
356+
case _ => appType
357+
case appType => appType
358+
end recheckApply
352359

353360
/** Handle an application of method `sym` with type `mt` to arguments of types `argTypes`.
354361
* This means:

compiler/src/dotty/tools/dotc/core/Definitions.scala

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -943,23 +943,26 @@ class Definitions {
943943

944944
@tu lazy val RuntimeTuplesModule: Symbol = requiredModule("scala.runtime.Tuples")
945945
@tu lazy val RuntimeTuplesModuleClass: Symbol = RuntimeTuplesModule.moduleClass
946-
lazy val RuntimeTuples_consIterator: Symbol = RuntimeTuplesModule.requiredMethod("consIterator")
947-
lazy val RuntimeTuples_concatIterator: Symbol = RuntimeTuplesModule.requiredMethod("concatIterator")
948-
lazy val RuntimeTuples_apply: Symbol = RuntimeTuplesModule.requiredMethod("apply")
949-
lazy val RuntimeTuples_cons: Symbol = RuntimeTuplesModule.requiredMethod("cons")
950-
lazy val RuntimeTuples_size: Symbol = RuntimeTuplesModule.requiredMethod("size")
951-
lazy val RuntimeTuples_tail: Symbol = RuntimeTuplesModule.requiredMethod("tail")
952-
lazy val RuntimeTuples_concat: Symbol = RuntimeTuplesModule.requiredMethod("concat")
953-
lazy val RuntimeTuples_toArray: Symbol = RuntimeTuplesModule.requiredMethod("toArray")
954-
lazy val RuntimeTuples_productToArray: Symbol = RuntimeTuplesModule.requiredMethod("productToArray")
955-
lazy val RuntimeTuples_isInstanceOfTuple: Symbol = RuntimeTuplesModule.requiredMethod("isInstanceOfTuple")
956-
lazy val RuntimeTuples_isInstanceOfEmptyTuple: Symbol = RuntimeTuplesModule.requiredMethod("isInstanceOfEmptyTuple")
957-
lazy val RuntimeTuples_isInstanceOfNonEmptyTuple: Symbol = RuntimeTuplesModule.requiredMethod("isInstanceOfNonEmptyTuple")
946+
@tu lazy val RuntimeTuples_consIterator: Symbol = RuntimeTuplesModule.requiredMethod("consIterator")
947+
@tu lazy val RuntimeTuples_concatIterator: Symbol = RuntimeTuplesModule.requiredMethod("concatIterator")
948+
@tu lazy val RuntimeTuples_apply: Symbol = RuntimeTuplesModule.requiredMethod("apply")
949+
@tu lazy val RuntimeTuples_cons: Symbol = RuntimeTuplesModule.requiredMethod("cons")
950+
@tu lazy val RuntimeTuples_size: Symbol = RuntimeTuplesModule.requiredMethod("size")
951+
@tu lazy val RuntimeTuples_tail: Symbol = RuntimeTuplesModule.requiredMethod("tail")
952+
@tu lazy val RuntimeTuples_concat: Symbol = RuntimeTuplesModule.requiredMethod("concat")
953+
@tu lazy val RuntimeTuples_toArray: Symbol = RuntimeTuplesModule.requiredMethod("toArray")
954+
@tu lazy val RuntimeTuples_productToArray: Symbol = RuntimeTuplesModule.requiredMethod("productToArray")
955+
@tu lazy val RuntimeTuples_isInstanceOfTuple: Symbol = RuntimeTuplesModule.requiredMethod("isInstanceOfTuple")
956+
@tu lazy val RuntimeTuples_isInstanceOfEmptyTuple: Symbol = RuntimeTuplesModule.requiredMethod("isInstanceOfEmptyTuple")
957+
@tu lazy val RuntimeTuples_isInstanceOfNonEmptyTuple: Symbol = RuntimeTuplesModule.requiredMethod("isInstanceOfNonEmptyTuple")
958958

959959
@tu lazy val TupledFunctionTypeRef: TypeRef = requiredClassRef("scala.util.TupledFunction")
960960
def TupledFunctionClass(using Context): ClassSymbol = TupledFunctionTypeRef.symbol.asClass
961961
def RuntimeTupleFunctionsModule(using Context): Symbol = requiredModule("scala.runtime.TupledFunctions")
962962

963+
@tu lazy val CapsModule: Symbol = requiredModule("scala.caps")
964+
@tu lazy val Caps_unsafeUnbox: Symbol = CapsModule.requiredMethod("unsafeUnbox")
965+
963966
// Annotation base classes
964967
@tu lazy val AnnotationClass: ClassSymbol = requiredClass("scala.annotation.Annotation")
965968
@tu lazy val ClassfileAnnotationClass: ClassSymbol = requiredClass("scala.annotation.ClassfileAnnotation")

library/src/scala/caps.scala

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package scala
2+
3+
import annotation.experimental
4+
5+
// @experimental , suppress @experimental so we can use in compiler itself
6+
object caps:
7+
8+
/** If argument is of type `box cs T`, converts to type `cs T`. This
9+
* avoids the error that would be raised when unboxing `*`.
10+
*/
11+
extension [T](x: T) def unsafeUnbox: T = x
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import caps.*
2+
def test =
3+
var finalizeActions = collection.mutable.ListBuffer[() => Unit]()
4+
val action = finalizeActions.remove(0).unsafeUnbox

0 commit comments

Comments
 (0)