Skip to content

Commit d335a1a

Browse files
committed
Fix problems when changing between annotated can capturing types
Fix problems in denotation transformers when changing between annotated can capturing types
1 parent 6944aa0 commit d335a1a

File tree

7 files changed

+193
-45
lines changed

7 files changed

+193
-45
lines changed
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package dotty.tools
2+
package dotc
3+
package cc
4+
5+
import core.*
6+
import Types.*, Symbols.*, Contexts.*, Annotations.*
7+
import ast.Trees.*
8+
import ast.{tpd, untpd}
9+
import Decorators.*
10+
import config.Printers.capt
11+
12+
object CaptureOps:
13+
import tpd.*
14+
15+
extension (cs: CaptureSet)
16+
def toAnnotation(using Context): Annotation =
17+
val refs = cs.elems.toList.map {
18+
case cr: TermRef => ref(cr)
19+
case cr: TermParamRef => untpd.Ident(cr.paramName).withType(cr)
20+
case cr: ThisType => This(cr.cls)
21+
}
22+
val arg = repeated(refs, TypeTree(defn.AnyType))
23+
Annotation(defn.RetainsAnnot.typeRef, arg)
24+
25+
extension (tp: CapturingType)
26+
def toAnnotatedType(using Context): AnnotatedType =
27+
AnnotatedType(tp.parent, tp.refs.toAnnotation)
28+
29+
extension (annot: Annotation)
30+
def toCaptureSet(using Context): CaptureSet =
31+
assert(annot.symbol == defn.RetainsAnnot)
32+
annot.tree match
33+
case Apply(_, Typed(SeqLiteral(elems, _), _) :: Nil) =>
34+
CaptureSet(elems.map(_.tpe.asInstanceOf[CaptureRef])*)
35+
.showing(i"toCaptureSet $annot --> $result", capt)
36+
37+
extension (tp: AnnotatedType)
38+
def toCapturingType(using Context): Type =
39+
CapturingType(tp.parent, tp.annot.toCaptureSet)
40+
41+
extension (tp: Type)
42+
43+
/** If this is type variable instantiated or upper bounded with a capturing type,
44+
* the capture set associated with that type. Extended to and-or types and
45+
* type proxies in the obvious way. If a term has a type with a boxed captureset,
46+
* that captureset counts towards the capture variables of the envirionment.
47+
*/
48+
def boxedCaptured(using Context): CaptureSet =
49+
def getBoxed(tp: Type, enabled: Boolean): CaptureSet = tp match
50+
case tp: CapturingType if enabled => tp.refs
51+
case tp: TypeVar => getBoxed(tp.underlying, enabled = true)
52+
case tp: TypeRef if tp.symbol == defn.AnyClass && enabled => CaptureSet.universal
53+
case tp: TypeProxy => getBoxed(tp.superType, enabled)
54+
case tp: AndType => getBoxed(tp.tp1, enabled) ++ getBoxed(tp.tp2, enabled)
55+
case tp: OrType => getBoxed(tp.tp1, enabled) ** getBoxed(tp.tp2, enabled)
56+
case _ => CaptureSet.empty
57+
getBoxed(tp, enabled = false)
58+
59+
/** If this type appears as an expected type of a term, does it imply
60+
* that the term should be boxed? ^^^ Special treat Any?
61+
*/
62+
def needsBox(using Context): Boolean = tp match
63+
case _: TypeVar => true
64+
case tp: TypeRef =>
65+
tp.info match
66+
case TypeBounds(lo, _) => lo.needsBox
67+
case _ => false
68+
case tp: RefinedOrRecType => tp.parent.needsBox
69+
case tp: AnnotatedType => tp.parent.needsBox
70+
case tp: LazyRef => tp.ref.needsBox
71+
case tp: AndType => tp.tp1.needsBox || tp.tp2.needsBox
72+
case tp: OrType => tp.tp1.needsBox && tp.tp2.needsBox
73+
case _ => false
74+
75+
def canHaveInferredCapture(using Context): Boolean = tp match
76+
case tp: CapturingType =>
77+
false
78+
case tp: TypeRef =>
79+
if tp.symbol.isClass then
80+
!tp.symbol.isValueClass && tp.symbol != defn.AnyClass
81+
else
82+
tp.underlying.canHaveInferredCapture
83+
case tp: TypeProxy =>
84+
tp.underlying.canHaveInferredCapture
85+
case tp: AndType =>
86+
tp.tp1.canHaveInferredCapture && tp.tp2.canHaveInferredCapture
87+
case tp: OrType =>
88+
tp.tp1.canHaveInferredCapture || tp.tp2.canHaveInferredCapture
89+
case _ =>
90+
false
91+
92+
def addCaptureVars(using Context): Type =
93+
if ctx.settings.Ycc.value && canHaveInferredCapture then
94+
CapturingType(tp, CaptureSet.Var()) // ^^^ go deep
95+
else
96+
tp
97+
end CaptureOps

compiler/src/dotty/tools/dotc/Compiler.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ class Compiler {
8686
new StringInterpolatorOpt) :: // Optimizes raw and s string interpolators by rewriting them to string concatenations
8787
List(new PreRecheck) :: // Preparations for check captures phase, enabled under -Ycc
8888
List(new CheckCaptures) :: // Check captures, enabled under -Ycc
89+
List(new PostCapture) :: // Reset denotations transformed by CheckCaptures back to annotated types
8990
List(new PruneErasedDefs, // Drop erased definitions from scopes and simplify erased expressions
9091
new UninitializedDefs, // Replaces `compiletime.uninitialized` by `_`
9192
new InlinePatterns, // Remove placeholders of inlined patterns

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

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -351,12 +351,4 @@ object CaptureSet:
351351
empty
352352
recur(tp)
353353
.showing(i"capture set of $tp = $result", capt)
354-
355-
def fromAnnotation(annot: Annotation)(using Context): CaptureSet =
356-
import ast.Trees.*
357-
assert(annot.symbol == defn.RetainsAnnot)
358-
annot.tree match
359-
case Apply(_, Typed(SeqLiteral(elems, _), _) :: Nil) =>
360-
CaptureSet(elems.map(_.tpe.asInstanceOf[CaptureRef])*)
361-
362354
end CaptureSet
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package dotty.tools.dotc
2+
package transform
3+
4+
import core.*
5+
import Phases.Phase
6+
import DenotTransformers.InfoTransformer
7+
import Contexts.{Context, ctx}
8+
import Types.*, Symbols.*
9+
import Denotations.SingleDenotation
10+
import SymDenotations.SymDenotation
11+
import cc.CaptureOps.toAnnotation
12+
13+
/** A denotation transformer that comes after the CheckCaptures. It resets
14+
* all CapturingTypes in the info of derived SingletonDenotations to AnnotatedTypes.
15+
* SymDenotations are left unchaged. For them, the info is already reset in
16+
* Rechecl#updateInfo.
17+
*/
18+
class PostCapture extends Phase, InfoTransformer:
19+
thisPhase =>
20+
21+
def phaseName: String = "postCapture"
22+
23+
override def isEnabled(using Context) = prev.isEnabled
24+
override def changesBaseTypes: Boolean = true
25+
26+
override def transformInfo(tp: Type, sym: Symbol)(using Context): Type =
27+
val mapType = new TypeMap:
28+
def apply(t: Type) = t match
29+
case CapturingType(parent, cs) =>
30+
AnnotatedType(this(parent), toAnnotation(cs))
31+
case _ =>
32+
mapOver(t)
33+
mapType(tp)
34+
35+
override def transform(ref: SingleDenotation)(using Context): SingleDenotation =
36+
if ref.isInstanceOf[SymDenotation] then ref
37+
else super.transform(ref)
38+
39+
def run(using Context): Unit = ()
40+
41+
end PostCapture

compiler/src/dotty/tools/dotc/transform/Recheck.scala

Lines changed: 9 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -54,28 +54,16 @@ abstract class Recheck extends Phase, IdentityDenotTransformer:
5454
).installAfter(preRecheckPhase)
5555

5656
/** Hooks to be overridden */
57-
protected def reinfer(tp: Type)(using Context): Type = tp
58-
protected def transformType(tp: Type)(using Context): Type = tp
59-
60-
def reinferResult(info: Type)(using Context): Type = info match
61-
case info: MethodOrPoly =>
62-
info.derivedLambdaType(resType = reinferResult(info.resType))
63-
case info: ExprType =>
64-
info.derivedExprType(resType = reinferResult(info.resType))
65-
case _ =>
66-
reinfer(info)
57+
protected def transformType(tp: Type, inferred: Boolean)(using Context): Type = tp
6758

6859
def enterDef(stat: Tree)(using Context): Unit =
6960
val sym = stat.symbol
70-
val transInfo = transformType(sym.info)
71-
val newInfo = stat match
72-
case stat: ValOrDefDef if stat.tpt.isInstanceOf[InferredTypeTree] =>
73-
reinferResult(transInfo)
74-
case stat: Bind =>
75-
reinferResult(transInfo)
76-
case _ =>
77-
transInfo
78-
//println(i"update info $sym: ${sym.info} --> $transInfo --> $newInfo")
61+
val isInferred = stat match
62+
case stat: ValOrDefDef => stat.tpt.isInstanceOf[InferredTypeTree]
63+
case stat: Bind => true
64+
case _ => false
65+
val newInfo = transformType(sym.info, isInferred)
66+
//println(i"update info $sym: ${sym.info} --> $newInfo")
7967
sym.updateInfo(newInfo)
8068

8169
def constFold(tree: Tree, tp: Type)(using Context): Type =
@@ -234,9 +222,8 @@ abstract class Recheck extends Phase, IdentityDenotTransformer:
234222
val elemTypes = tree.elems.map(recheck(_, elemProto))
235223
seqLitType(tree, TypeComparer.lub(declaredElemType :: elemTypes))
236224

237-
def recheckTypeTree(tree: TypeTree)(using Context): Type = tree match
238-
case tree: InferredTypeTree => reinfer(tree.tpe)
239-
case _ => transformType(tree.tpe)
225+
def recheckTypeTree(tree: TypeTree)(using Context): Type =
226+
transformType(tree.tpe, tree.isInstanceOf[InferredTypeTree])
240227

241228
def recheckAnnotated(tree: Annotated)(using Context): Type =
242229
tree.tpe match

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

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import reporting._
2828
import ProtoTypes._
2929
import dotty.tools.backend.jvm.DottyBackendInterface.symExtensions
3030
import CaptureSet.CompareResult
31+
import cc.CaptureOps.toCaptureSet
3132

3233
object CheckCaptures:
3334
case class Env(owner: Symbol, captured: CaptureSet, isBoxed: Boolean, outer: Env):
@@ -85,17 +86,45 @@ class CheckCaptures extends Recheck:
8586
class CaptureChecker(ictx: Context) extends Rechecker(ictx):
8687
import ast.tpd.*
8788

88-
override def reinfer(tp: Type)(using Context): Type =
89-
CapturingType(tp, CaptureSet.Var()) // ^^^ go deep
90-
91-
override def transformType(tp: Type)(using Context): Type =
89+
override def transformType(tp: Type, inferred: Boolean)(using Context): Type =
9290
val mapType = new TypeMap:
93-
def apply(t: Type) = mapOver(t) match
91+
def apply(t: Type) = t match
9492
case AnnotatedType(parent, annot) if annot.symbol == defn.RetainsAnnot =>
95-
CapturingType(parent, CaptureSet.fromAnnotation(annot))
96-
case t1 =>
97-
t1
98-
mapType(tp)
93+
CapturingType(this(parent), annot.toCaptureSet)
94+
case t @ RefinedType(core, nme.apply, appInfo) if defn.isFunctionType(t) =>
95+
val core1 = this(core)
96+
val appInfo1 = this(appInfo)
97+
if appInfo1 ne appInfo then appInfo1.toFunctionType(isJava = false)
98+
else t.derivedRefinedType(core1, nme.apply, appInfo)
99+
case _ =>
100+
mapOver(t)
101+
102+
def reinfer(tp: Type): Type =
103+
val cleanType = new TypeMap:
104+
def apply(t: Type) = t match
105+
case AnnotatedType(parent, annot) if annot.symbol == defn.RetainsAnnot =>
106+
parent
107+
case _ =>
108+
mapOver(t)
109+
def addVars(tp: Type): Type =
110+
val tp1 = tp match
111+
case tp @ AppliedType(tycon, args) =>
112+
tp.derivedAppliedType(tycon, args.map(addVars))
113+
case _ => // ^^^ go deeper in other types as well
114+
tp
115+
CapturingType(tp1, CaptureSet.Var())
116+
tp match
117+
case tp: MethodOrPoly =>
118+
tp.derivedLambdaType(resType = reinfer(tp.resType))
119+
case tp: ExprType =>
120+
tp.derivedExprType(resType = reinfer(tp.resType))
121+
case _ =>
122+
val tp1 = cleanType(tp)
123+
addVars(tp1)
124+
125+
val tp1 = if inferred then reinfer(tp) else tp
126+
mapType(tp1)
127+
end transformType
99128

100129
private var curEnv: Env = Env(NoSymbol, CaptureSet.empty, false, null)
101130

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
class C
2-
type Cap = C retains *
3-
type Top = Any retains *
2+
type Cap = C @retains(*)
43

5-
type T = (x: Cap) => String retains x.type
4+
type T = (x: Cap) => String @retains(x)
65

7-
def f(y: Cap): String retains * =
8-
val a: T = (x: Cap) => ""
6+
val aa: ((x: Cap) => String @retains(x)) = (x: Cap) => ""
7+
8+
def f(y: Cap): String @retains(*) =
9+
val a: ((x: Cap) => String @retains(x)) = (x: Cap) => ""
910
val b = a(y)
10-
val c: String retains y.type = b
11+
val c: String @retains(y) = b
1112
c

0 commit comments

Comments
 (0)