Skip to content

Commit 3381850

Browse files
authored
Various fixes to capture checking (#16199)
Fixes of issues detected when trying to compile dotc with -Ycc
2 parents d9301e0 + 9d86052 commit 3381850

30 files changed

+329
-100
lines changed

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

+1-2
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,7 @@ case class CaptureAnnotation(refs: CaptureSet, boxed: Boolean)(cls: Symbol) exte
3939

4040
override def symbol(using Context) = cls
4141

42-
override def derivedAnnotation(tree: Tree)(using Context): Annotation =
43-
unsupported(i"derivedAnnotation(Tree), $tree, $refs")
42+
override def derivedAnnotation(tree: Tree)(using Context): Annotation = this
4443

4544
def derivedAnnotation(refs: CaptureSet, boxed: Boolean)(using Context): Annotation =
4645
if (this.refs eq refs) && (this.boxed == boxed) then this

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

+15
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,19 @@ 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 capturing type, the version with boxed statues as given by `boxed`.
100+
* If it is a TermRef of a capturing type, and the box status flips, widen to a capturing
101+
* type that captures the TermRef.
102+
*/
103+
def forceBoxStatus(boxed: Boolean)(using Context): Type = tp.widenDealias match
104+
case tp @ CapturingType(parent, refs) if tp.isBoxed != boxed =>
105+
val refs1 = tp match
106+
case ref: CaptureRef if ref.isTracked => ref.singletonCaptureSet
107+
case _ => refs
108+
CapturingType(parent, refs1, boxed)
109+
case _ =>
110+
tp
111+
99112
/** Map capturing type to their parents. Capturing types accessible
100113
* via dealising are also stripped.
101114
*/
@@ -155,6 +168,8 @@ extension (sym: Symbol)
155168
case _ => false
156169
containsEnclTypeParam(sym.info.finalResultType)
157170
&& !sym.allowsRootCapture
171+
&& sym != defn.Caps_unsafeBox
172+
&& sym != defn.Caps_unsafeUnbox
158173

159174
extension (tp: AnnotatedType)
160175
/** Is this a boxed capturing type? */

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

+4-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,10 @@ object CapturingType:
4141
* returned separately by CaptureOps.isBoxed.
4242
*/
4343
def unapply(tp: AnnotatedType)(using Context): Option[(Type, CaptureSet)] =
44-
if ctx.phase == Phases.checkCapturesPhase && tp.annot.symbol == defn.RetainsAnnot then
44+
if ctx.phase == Phases.checkCapturesPhase
45+
&& tp.annot.symbol == defn.RetainsAnnot
46+
&& !ctx.mode.is(Mode.IgnoreCaptures)
47+
then
4548
EventuallyCapturingType.unapply(tp)
4649
else None
4750

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

+68-28
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ package cc
55
import core.*
66
import Phases.*, DenotTransformers.*, SymDenotations.*
77
import Contexts.*, Names.*, Flags.*, Symbols.*, Decorators.*
8-
import Types.*, StdNames.*
8+
import Types.*, StdNames.*, Denotations.*
99
import config.Printers.{capt, recheckr}
1010
import config.Config
1111
import ast.{tpd, untpd, Trees}
@@ -89,7 +89,7 @@ object CheckCaptures:
8989
elem.tpe match
9090
case ref: CaptureRef =>
9191
if !ref.canBeTracked then
92-
report.error(em"$elem cannot be tracked since it is not a parameter or a local variable", elem.srcPos)
92+
report.error(em"$elem cannot be tracked since it is not a parameter or local value", elem.srcPos)
9393
case tpe =>
9494
report.error(em"$elem: $tpe is not a legal element of a capture set", elem.srcPos)
9595

@@ -288,16 +288,34 @@ class CheckCaptures extends Recheck, SymTransformer:
288288
* outcome of a `mightSubcapture` test. It picks `{f}` if this might subcapture Cr
289289
* and Cr otherwise.
290290
*/
291-
override def recheckSelection(tree: Select, qualType: Type, name: Name)(using Context) = {
292-
val selType = super.recheckSelection(tree, qualType, name)
291+
override def recheckSelection(tree: Select, qualType: Type, name: Name, pt: Type)(using Context) = {
292+
def disambiguate(denot: Denotation): Denotation = denot match
293+
case MultiDenotation(denot1, denot2) =>
294+
// This case can arise when we try to merge multiple types that have different
295+
// capture sets on some part. For instance an asSeenFrom might produce
296+
// a bi-mapped capture set arising from a substition. Applying the same substitution
297+
// to the same type twice will nevertheless produce different capture setsw which can
298+
// lead to a failure in disambiguation since neither alternative is better than the
299+
// other in a frozen constraint. An example test case is disambiguate-select.scala.
300+
// We address the problem by disambiguating while ignoring all capture sets as a fallback.
301+
withMode(Mode.IgnoreCaptures) {
302+
disambiguate(denot1).meet(disambiguate(denot2), qualType)
303+
}
304+
case _ => denot
305+
306+
val selType = recheckSelection(tree, qualType, name, disambiguate)
293307
val selCs = selType.widen.captureSet
294308
if selCs.isAlwaysEmpty || selType.widen.isBoxedCapturing || qualType.isBoxedCapturing then
295309
selType
296310
else
297311
val qualCs = qualType.captureSet
298312
capt.println(i"intersect $qualType, ${selType.widen}, $qualCs, $selCs in $tree")
299-
if qualCs.mightSubcapture(selCs) then
313+
if qualCs.mightSubcapture(selCs)
314+
&& !selCs.mightSubcapture(qualCs)
315+
&& !pt.stripCapturing.isInstanceOf[SingletonType]
316+
then
300317
selType.widen.stripCapturing.capturing(qualCs)
318+
.showing(i"alternate type for select $tree: $selType --> $result, $qualCs / $selCs", capt)
301319
else
302320
selType
303321
}//.showing(i"recheck sel $tree, $qualType = $result")
@@ -314,23 +332,32 @@ class CheckCaptures extends Recheck, SymTransformer:
314332
* and Cr otherwise.
315333
*/
316334
override def recheckApply(tree: Apply, pt: Type)(using Context): Type =
317-
includeCallCaptures(tree.symbol, tree.srcPos)
318-
super.recheckApply(tree, pt) match
319-
case appType @ CapturingType(appType1, refs) =>
320-
tree.fun match
321-
case Select(qual, _)
322-
if !tree.fun.symbol.isConstructor
323-
&& !qual.tpe.isBoxedCapturing
324-
&& !tree.args.exists(_.tpe.isBoxedCapturing)
325-
&& qual.tpe.captureSet.mightSubcapture(refs)
326-
&& tree.args.forall(_.tpe.captureSet.mightSubcapture(refs))
327-
=>
328-
val callCaptures = tree.args.foldLeft(qual.tpe.captureSet)((cs, arg) =>
329-
cs ++ arg.tpe.captureSet)
330-
appType.derivedCapturingType(appType1, callCaptures)
331-
.showing(i"narrow $tree: $appType, refs = $refs, qual = ${qual.tpe.captureSet} --> $result", capt)
332-
case _ => appType
333-
case appType => appType
335+
val meth = tree.fun.symbol
336+
includeCallCaptures(meth, tree.srcPos)
337+
if meth == defn.Caps_unsafeBox || meth == defn.Caps_unsafeUnbox then
338+
val arg :: Nil = tree.args: @unchecked
339+
val argType0 = recheckStart(arg, pt)
340+
.forceBoxStatus(boxed = meth == defn.Caps_unsafeBox)
341+
val argType = super.recheckFinish(argType0, arg, pt)
342+
super.recheckFinish(argType, tree, pt)
343+
else
344+
super.recheckApply(tree, pt) match
345+
case appType @ CapturingType(appType1, refs) =>
346+
tree.fun match
347+
case Select(qual, _)
348+
if !tree.fun.symbol.isConstructor
349+
&& !qual.tpe.isBoxedCapturing
350+
&& !tree.args.exists(_.tpe.isBoxedCapturing)
351+
&& qual.tpe.captureSet.mightSubcapture(refs)
352+
&& tree.args.forall(_.tpe.captureSet.mightSubcapture(refs))
353+
=>
354+
val callCaptures = tree.args.foldLeft(qual.tpe.captureSet)((cs, arg) =>
355+
cs ++ arg.tpe.captureSet)
356+
appType.derivedCapturingType(appType1, callCaptures)
357+
.showing(i"narrow $tree: $appType, refs = $refs, qual = ${qual.tpe.captureSet} --> $result", capt)
358+
case _ => appType
359+
case appType => appType
360+
end recheckApply
334361

335362
/** Handle an application of method `sym` with type `mt` to arguments of types `argTypes`.
336363
* This means:
@@ -435,10 +462,25 @@ class CheckCaptures extends Recheck, SymTransformer:
435462
case _ =>
436463
super.recheckBlock(block, pt)
437464

465+
/** If `rhsProto` has `*` as its capture set, wrap `rhs` in a `unsafeBox`.
466+
* Used to infer `unsafeBox` for expressions that get assigned to variables
467+
* that have universal capture set.
468+
*/
469+
def maybeBox(rhs: Tree, rhsProto: Type)(using Context): Tree =
470+
if rhsProto.captureSet.isUniversal then
471+
ref(defn.Caps_unsafeBox).appliedToType(rhsProto).appliedTo(rhs)
472+
else rhs
473+
474+
override def recheckAssign(tree: Assign)(using Context): Type =
475+
val rhsProto = recheck(tree.lhs).widen
476+
recheck(maybeBox(tree.rhs, rhsProto), rhsProto)
477+
defn.UnitType
478+
438479
override def recheckValDef(tree: ValDef, sym: Symbol)(using Context): Unit =
439480
try
440481
if !sym.is(Module) then // Modules are checked by checking the module class
441-
super.recheckValDef(tree, sym)
482+
if sym.is(Mutable) then recheck(maybeBox(tree.rhs, sym.info), sym.info)
483+
else super.recheckValDef(tree, sym)
442484
finally
443485
if !sym.is(Param) then
444486
// Parameters with inferred types belong to anonymous methods. We need to wait
@@ -541,8 +583,6 @@ class CheckCaptures extends Recheck, SymTransformer:
541583
tpe
542584
case _: Try =>
543585
tpe
544-
case _: ValDef if tree.symbol.is(Mutable) =>
545-
tree.symbol.info
546586
case _ =>
547587
NoType
548588
def checkNotUniversal(tp: Type): Unit = tp.widenDealias match
@@ -671,7 +711,7 @@ class CheckCaptures extends Recheck, SymTransformer:
671711

672712
/** Destruct a capturing type `tp` to a tuple (cs, tp0, boxed),
673713
* where `tp0` is not a capturing type.
674-
*
714+
*
675715
* If `tp` is a nested capturing type, the return tuple always represents
676716
* the innermost capturing type. The outer capture annotations can be
677717
* reconstructed with the returned function.
@@ -727,7 +767,7 @@ class CheckCaptures extends Recheck, SymTransformer:
727767
val criticalSet = // the set which is not allowed to have `*`
728768
if covariant then cs1 // can't box with `*`
729769
else expected.captureSet // can't unbox with `*`
730-
if criticalSet.isUniversal then
770+
if criticalSet.isUniversal && expected.isValueType then
731771
// We can't box/unbox the universal capability. Leave `actual` as it is
732772
// so we get an error in checkConforms. This tends to give better error
733773
// messages than disallowing the root capability in `criticalSet`.
@@ -748,7 +788,6 @@ class CheckCaptures extends Recheck, SymTransformer:
748788
recon(CapturingType(parent1, cs1, actualIsBoxed))
749789
}
750790

751-
752791
var actualw = actual.widenDealias
753792
actual match
754793
case ref: CaptureRef if ref.isTracked =>
@@ -768,6 +807,7 @@ class CheckCaptures extends Recheck, SymTransformer:
768807
override def checkUnit(unit: CompilationUnit)(using Context): Unit =
769808
Setup(preRecheckPhase, thisPhase, recheckDef)
770809
.traverse(ctx.compilationUnit.tpdTree)
810+
//println(i"SETUP:\n${Recheck.addRecheckedTypes.transform(ctx.compilationUnit.tpdTree)}")
771811
withCaptureSetsExplained {
772812
super.checkUnit(unit)
773813
checkSelfTypes(unit.tpdTree)

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

+27-12
Original file line numberDiff line numberDiff line change
@@ -331,11 +331,12 @@ extends tpd.TreeTraverser:
331331
else expandAbbreviations(tp1)
332332

333333
/** Transform type of type tree, and remember the transformed type as the type the tree */
334-
private def transformTT(tree: TypeTree, boxed: Boolean)(using Context): Unit =
335-
tree.rememberType(
336-
if tree.isInstanceOf[InferredTypeTree]
337-
then transformInferredType(tree.tpe, boxed)
338-
else transformExplicitType(tree.tpe, boxed))
334+
private def transformTT(tree: TypeTree, boxed: Boolean, exact: Boolean)(using Context): Unit =
335+
if !tree.hasRememberedType then
336+
tree.rememberType(
337+
if tree.isInstanceOf[InferredTypeTree] && !exact
338+
then transformInferredType(tree.tpe, boxed)
339+
else transformExplicitType(tree.tpe, boxed))
339340

340341
/** Substitute parameter symbols in `from` to paramRefs in corresponding
341342
* method or poly types `to`. We use a single BiTypeMap to do everything.
@@ -376,20 +377,34 @@ extends tpd.TreeTraverser:
376377

377378
def traverse(tree: Tree)(using Context): Unit =
378379
tree match
379-
case tree: DefDef if isExcluded(tree.symbol) =>
380-
return
381-
case tree @ ValDef(_, tpt: TypeTree, _) if tree.symbol.is(Mutable) =>
382-
transformTT(tpt, boxed = true) // types of mutable variables are boxed
383-
traverse(tree.rhs)
380+
case tree: DefDef =>
381+
if isExcluded(tree.symbol) then
382+
return
383+
tree.tpt match
384+
case tpt: TypeTree if tree.symbol.allOverriddenSymbols.hasNext =>
385+
transformTT(tpt, boxed = false, exact = true)
386+
//println(i"TYPE of ${tree.symbol.showLocated} = ${tpt.knownType}")
387+
case _ =>
388+
traverseChildren(tree)
389+
case tree @ ValDef(_, tpt: TypeTree, _) =>
390+
val isVar = tree.symbol.is(Mutable)
391+
val overrides = tree.symbol.allOverriddenSymbols.hasNext
392+
//if overrides then println(i"transforming overriding ${tree.symbol}")
393+
if isVar || overrides then
394+
transformTT(tpt,
395+
boxed = isVar, // types of mutable variables are boxed
396+
exact = overrides // types of symbols that override a parent don't get a capture set
397+
)
398+
traverseChildren(tree)
384399
case tree @ TypeApply(fn, args) =>
385400
traverse(fn)
386401
for case arg: TypeTree <- args do
387-
transformTT(arg, boxed = true) // type arguments in type applications are boxed
402+
transformTT(arg, boxed = true, exact = false) // type arguments in type applications are boxed
388403
case _ =>
389404
traverseChildren(tree)
390405
tree match
391406
case tree: TypeTree =>
392-
transformTT(tree, boxed = false) // other types are not boxed
407+
transformTT(tree, boxed = false, exact = false) // other types are not boxed
393408
case tree: ValOrDefDef =>
394409
val sym = tree.symbol
395410

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

+17-12
Original file line numberDiff line numberDiff line change
@@ -943,23 +943,27 @@ 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_unsafeBox: Symbol = CapsModule.requiredMethod("unsafeBox")
965+
@tu lazy val Caps_unsafeUnbox: Symbol = CapsModule.requiredMethod("unsafeUnbox")
966+
963967
// Annotation base classes
964968
@tu lazy val AnnotationClass: ClassSymbol = requiredClass("scala.annotation.Annotation")
965969
@tu lazy val ClassfileAnnotationClass: ClassSymbol = requiredClass("scala.annotation.ClassfileAnnotation")
@@ -1020,6 +1024,7 @@ class Definitions {
10201024
@tu lazy val RequiresCapabilityAnnot: ClassSymbol = requiredClass("scala.annotation.internal.requiresCapability")
10211025
@tu lazy val RetainsAnnot: ClassSymbol = requiredClass("scala.annotation.retains")
10221026
@tu lazy val RetainsByNameAnnot: ClassSymbol = requiredClass("scala.annotation.retainsByName")
1027+
@tu lazy val RetainsUniversalAnnot: ClassSymbol = requiredClass("scala.annotation.retainsUniversal")
10231028

10241029
@tu lazy val JavaRepeatableAnnot: ClassSymbol = requiredClass("java.lang.annotation.Repeatable")
10251030

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

+8
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,14 @@ object Mode {
7878
/** Use Scala2 scheme for overloading and implicit resolution */
7979
val OldOverloadingResolution: Mode = newMode(15, "OldOverloadingResolution")
8080

81+
/** Treat CapturingTypes as plain AnnotatedTypes even in phase =Ycc.
82+
* Reuses the value of OldOverloadingResolution to save Mode bits.
83+
* This is OK since OldOverloadingResolution only affects implicit search, which
84+
* is done during phases Typer and Inlinig, and IgnoreCaptures only has an
85+
* effect during phase CheckCaptures.
86+
*/
87+
val IgnoreCaptures = OldOverloadingResolution
88+
8189
/** Allow hk applications of type lambdas to wildcard arguments;
8290
* used for checking that such applications do not normally arise
8391
*/

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

+5-2
Original file line numberDiff line numberDiff line change
@@ -1872,7 +1872,10 @@ object Types {
18721872
def dropRepeatedAnnot(using Context): Type = dropAnnot(defn.RepeatedAnnot)
18731873

18741874
def annotatedToRepeated(using Context): Type = this match {
1875-
case tp @ ExprType(tp1) => tp.derivedExprType(tp1.annotatedToRepeated)
1875+
case tp @ ExprType(tp1) =>
1876+
tp.derivedExprType(tp1.annotatedToRepeated)
1877+
case self @ AnnotatedType(tp, annot) if annot matches defn.RetainsByNameAnnot =>
1878+
self.derivedAnnotatedType(tp.annotatedToRepeated, annot)
18761879
case AnnotatedType(tp, annot) if annot matches defn.RepeatedAnnot =>
18771880
val typeSym = tp.typeSymbol.asClass
18781881
assert(typeSym == defn.SeqClass || typeSym == defn.ArrayClass)
@@ -2787,7 +2790,7 @@ object Types {
27872790
((prefix eq NoPrefix)
27882791
|| symbol.is(ParamAccessor) && (prefix eq symbol.owner.thisType)
27892792
|| isRootCapability
2790-
) && !symbol.is(Method)
2793+
) && !symbol.isOneOf(UnstableValueFlags)
27912794

27922795
override def isRootCapability(using Context): Boolean =
27932796
name == nme.CAPTURE_ROOT && symbol == defn.captureRoot

compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala

+2
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,8 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) {
272272
case tp: LazyRef if !printDebug =>
273273
try toText(tp.ref)
274274
catch case ex: Throwable => "..."
275+
case AnySelectionProto =>
276+
"a type that can be selected or applied"
275277
case tp: SelectionProto =>
276278
"?{ " ~ toText(tp.name) ~
277279
(Str(" ") provided !tp.name.toSimpleName.last.isLetterOrDigit) ~

0 commit comments

Comments
 (0)