Skip to content

Commit a7ad939

Browse files
committed
Capture checker for functions
1 parent 9693cd8 commit a7ad939

File tree

81 files changed

+2089
-130
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

81 files changed

+2089
-130
lines changed

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package dotc
44
import core._
55
import Contexts._
66
import typer.{TyperPhase, RefChecks}
7+
import cc.CheckCaptures
78
import parsing.Parser
89
import Phases.Phase
910
import transform._
@@ -84,6 +85,8 @@ class Compiler {
8485
new ExplicitSelf, // Make references to non-trivial self types explicit as casts
8586
new ElimByName, // Expand by-name parameter references
8687
new StringInterpolatorOpt) :: // Optimizes raw and s string interpolators by rewriting them to string concatenations
88+
List(new PreRecheck) :: // Preparations for check captures phase, enabled under -Ycc
89+
List(new CheckCaptures) :: // Check captures, enabled under -Ycc
8790
List(new PruneErasedDefs, // Drop erased definitions from scopes and simplify erased expressions
8891
new UninitializedDefs, // Replaces `compiletime.uninitialized` by `_`
8992
new InlinePatterns, // Remove placeholders of inlined patterns
@@ -101,8 +104,6 @@ class Compiler {
101104
new TupleOptimizations, // Optimize generic operations on tuples
102105
new LetOverApply, // Lift blocks from receivers of applications
103106
new ArrayConstructors) :: // Intercept creation of (non-generic) arrays and intrinsify.
104-
List(new PreRecheck) :: // Preparations for recheck phase, enabled under -Yrecheck
105-
List(new TestRecheck) :: // Test rechecking, enabled under -Yrecheck
106107
List(new Erasure) :: // Rewrite types to JVM model, erasing all type parameters, abstract types and refinements.
107108
List(new ElimErasedValueType, // Expand erased value types to their underlying implmementation types
108109
new PureStats, // Remove pure stats from blocks

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,7 @@ import reporting.{Reporter, Suppression, Action}
2020
import reporting.Diagnostic
2121
import reporting.Diagnostic.Warning
2222
import rewrites.Rewrites
23-
2423
import profile.Profiler
25-
import printing.XprintMode
2624
import parsing.Parsers.Parser
2725
import parsing.JavaParsers.JavaParser
2826
import typer.ImplicitRunInfo
@@ -328,7 +326,7 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint
328326
val fusedPhase = ctx.base.fusedContaining(prevPhase)
329327
val echoHeader = f"[[syntax trees at end of $fusedPhase%25s]] // ${unit.source}"
330328
val tree = if ctx.isAfterTyper then unit.tpdTree else unit.untpdTree
331-
val treeString = tree.show(using ctx.withProperty(XprintMode, Some(())))
329+
val treeString = fusedPhase.show(tree)
332330

333331
last match {
334332
case SomePrintedTree(phase, lastTreeString) if lastTreeString == treeString =>

compiler/src/dotty/tools/dotc/ast/Desugar.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1752,6 +1752,9 @@ object desugar {
17521752
flatTree(pats1 map (makePatDef(tree, mods, _, rhs)))
17531753
case ext: ExtMethods =>
17541754
Block(List(ext), Literal(Constant(())).withSpan(ext.span))
1755+
case CapturingTypeTree(refs, parent) =>
1756+
val annot = New(scalaDot(tpnme.retains), List(refs))
1757+
Annotated(parent, annot)
17551758
}
17561759
desugared.withSpan(tree.span)
17571760
}
@@ -1890,6 +1893,8 @@ object desugar {
18901893
case _ => traverseChildren(tree)
18911894
}
18921895
}.traverse(expr)
1896+
case CapturingTypeTree(refs, parent) =>
1897+
collect(parent)
18931898
case _ =>
18941899
}
18951900
collect(tree)

compiler/src/dotty/tools/dotc/ast/Trees.scala

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -260,16 +260,10 @@ object Trees {
260260
/** Tree's denotation can be derived from its type */
261261
abstract class DenotingTree[-T >: Untyped](implicit @constructorOnly src: SourceFile) extends Tree[T] {
262262
type ThisTree[-T >: Untyped] <: DenotingTree[T]
263-
override def denot(using Context): Denotation = typeOpt match {
263+
override def denot(using Context): Denotation = typeOpt.stripped match
264264
case tpe: NamedType => tpe.denot
265265
case tpe: ThisType => tpe.cls.denot
266-
case tpe: AnnotatedType => tpe.stripAnnots match {
267-
case tpe: NamedType => tpe.denot
268-
case tpe: ThisType => tpe.cls.denot
269-
case _ => NoDenotation
270-
}
271266
case _ => NoDenotation
272-
}
273267
}
274268

275269
/** Tree's denot/isType/isTerm properties come from a subtree

compiler/src/dotty/tools/dotc/ast/untpd.scala

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,9 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
147147
case Floating
148148
}
149149

150+
/** {x1, ..., xN} T (only relevant under -Ycc) */
151+
case class CapturingTypeTree(refs: List[Tree], parent: Tree)(implicit @constructorOnly src: SourceFile) extends TypTree
152+
150153
/** Short-lived usage in typer, does not need copy/transform/fold infrastructure */
151154
case class DependentTypeTree(tp: List[Symbol] => Type)(implicit @constructorOnly src: SourceFile) extends Tree
152155

@@ -646,6 +649,10 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
646649
case tree: Number if (digits == tree.digits) && (kind == tree.kind) => tree
647650
case _ => finalize(tree, untpd.Number(digits, kind))
648651
}
652+
def CapturingTypeTree(tree: Tree)(refs: List[Tree], parent: Tree)(using Context): Tree = tree match
653+
case tree: CapturingTypeTree if (refs eq tree.refs) && (parent eq tree.parent) => tree
654+
case _ => finalize(tree, untpd.CapturingTypeTree(refs, parent))
655+
649656
def TypedSplice(tree: Tree)(splice: tpd.Tree)(using Context): ProxyTree = tree match {
650657
case tree: TypedSplice if splice `eq` tree.splice => tree
651658
case _ => finalize(tree, untpd.TypedSplice(splice)(using ctx))
@@ -711,6 +718,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
711718
tree
712719
case MacroTree(expr) =>
713720
cpy.MacroTree(tree)(transform(expr))
721+
case CapturingTypeTree(refs, parent) =>
722+
cpy.CapturingTypeTree(tree)(transform(refs), transform(parent))
714723
case _ =>
715724
super.transformMoreCases(tree)
716725
}
@@ -772,6 +781,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
772781
this(x, splice)
773782
case MacroTree(expr) =>
774783
this(x, expr)
784+
case CapturingTypeTree(refs, parent) =>
785+
this(this(x, refs), parent)
775786
case _ =>
776787
super.foldMoreCases(x, tree)
777788
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
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+
import printing.Printer
12+
import printing.Texts.Text
13+
14+
15+
case class CaptureAnnotation(refs: CaptureSet) extends Annotation:
16+
import CaptureAnnotation.*
17+
import tpd.*
18+
19+
override def tree(using Context) =
20+
val elems = refs.elems.toList.map {
21+
case cr: TermRef => ref(cr)
22+
case cr: TermParamRef => untpd.Ident(cr.paramName).withType(cr)
23+
case cr: ThisType => This(cr.cls)
24+
}
25+
val arg = repeated(elems, TypeTree(defn.AnyType))
26+
New(symbol.typeRef, arg :: Nil)
27+
28+
override def symbol(using Context) = defn.RetainsAnnot
29+
30+
override def derivedAnnotation(tree: Tree)(using Context): Annotation =
31+
unsupported("derivedAnnotation(Tree)")
32+
33+
def derivedAnnotation(refs: CaptureSet)(using Context): Annotation =
34+
if this.refs eq refs then this else CaptureAnnotation(refs)
35+
36+
override def sameAnnotation(that: Annotation)(using Context): Boolean = that match
37+
case CaptureAnnotation(refs2) => refs == refs2
38+
case _ => false
39+
40+
override def mapWith(tp: TypeMap)(using Context) =
41+
val elems = refs.elems.toList
42+
val elems1 = elems.mapConserve(tp)
43+
if elems1 eq elems then this
44+
else if elems1.forall(_.isInstanceOf[CaptureRef])
45+
then CaptureAnnotation(CaptureSet(elems1.asInstanceOf[List[CaptureRef]]*))
46+
else EmptyAnnotation
47+
48+
override def refersToParamOf(tl: TermLambda)(using Context): Boolean =
49+
refs.elems.exists {
50+
case TermParamRef(tl1, _) => tl eq tl1
51+
case _ => false
52+
}
53+
54+
override def toText(printer: Printer): Text = refs.toText(printer)
55+
56+
override def hash: Int = refs.hashCode
57+
58+
override def eql(that: Annotation) = that match
59+
case that: CaptureAnnotation => this.refs eq that.refs
60+
case _ => false
61+
62+
end CaptureAnnotation
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package dotty.tools
2+
package dotc
3+
package cc
4+
5+
import core.*
6+
import Types.*, Symbols.*, Contexts.*, Annotations.*
7+
import ast.{tpd, untpd}
8+
import Decorators.*
9+
import config.Printers.capt
10+
import util.Property.Key
11+
import tpd.*
12+
13+
private val Captures: Key[CaptureSet] = Key()
14+
15+
def retainedElems(tree: Tree)(using Context): List[Tree] = tree match
16+
case Apply(_, Typed(SeqLiteral(elems, _), _) :: Nil) => elems
17+
case _ => Nil
18+
19+
extension (tree: Tree)
20+
21+
def toCaptureRef(using Context): CaptureRef = tree.tpe.asInstanceOf[CaptureRef]
22+
23+
def toCaptureSet(using Context): CaptureSet =
24+
tree.getAttachment(Captures) match
25+
case Some(refs) => refs
26+
case None =>
27+
val refs = CaptureSet(retainedElems(tree).map(_.toCaptureRef)*)
28+
.showing(i"toCaptureSet $tree --> $result", capt)
29+
tree.putAttachment(Captures, refs)
30+
refs
31+
32+
extension (tp: Type)
33+
34+
def derivedCapturingType(parent: Type, refs: CaptureSet)(using Context): Type = tp match
35+
case CapturingType(p, r) =>
36+
if (parent eq p) && (refs eq r) then tp
37+
else CapturingType(parent, refs)
38+
39+
/** If this is type variable instantiated or upper bounded with a capturing type,
40+
* the capture set associated with that type. Extended to and-or types and
41+
* type proxies in the obvious way. If a term has a type with a boxed captureset,
42+
* that captureset counts towards the capture variables of the envirionment.
43+
*/
44+
def boxedCaptured(using Context): CaptureSet =
45+
def getBoxed(tp: Type, enabled: Boolean): CaptureSet = tp match
46+
case CapturingType(_, refs) if enabled => refs
47+
case tp: TypeVar => getBoxed(tp.underlying, enabled = true)
48+
case tp: TypeRef if tp.symbol == defn.AnyClass && enabled => CaptureSet.universal
49+
case tp: TypeProxy => getBoxed(tp.superType, enabled)
50+
case tp: AndType => getBoxed(tp.tp1, enabled) ++ getBoxed(tp.tp2, enabled)
51+
case tp: OrType => getBoxed(tp.tp1, enabled) ** getBoxed(tp.tp2, enabled)
52+
case _ => CaptureSet.empty
53+
getBoxed(tp, enabled = false)
54+
55+
/** If this type appears as an expected type of a term, does it imply
56+
* that the term should be boxed?
57+
* ^^^ Special treat Any? - but the current status is more conservative in that
58+
* it counts free variables in expressions that have Any as expected type.
59+
*/
60+
def needsBox(using Context): Boolean = tp match
61+
case _: TypeVar => true
62+
case tp: TypeRef =>
63+
tp.info match
64+
case TypeBounds(lo, _) => lo.needsBox
65+
case _ => false
66+
case tp: RefinedOrRecType => tp.parent.needsBox
67+
case CapturingType(_, _) => false
68+
case tp: AnnotatedType => tp.parent.needsBox
69+
case tp: LazyRef => tp.ref.needsBox
70+
case tp: AndType => tp.tp1.needsBox || tp.tp2.needsBox
71+
case tp: OrType => tp.tp1.needsBox && tp.tp2.needsBox
72+
case _ => false
73+
74+
def canHaveInferredCapture(using Context): Boolean = tp match
75+
case tp: TypeRef if tp.symbol.isClass =>
76+
!tp.symbol.isValueClass && tp.symbol != defn.AnyClass
77+
case tp: TypeProxy =>
78+
tp.superType.canHaveInferredCapture
79+
case tp: AndType =>
80+
tp.tp1.canHaveInferredCapture && tp.tp2.canHaveInferredCapture
81+
case tp: OrType =>
82+
tp.tp1.canHaveInferredCapture || tp.tp2.canHaveInferredCapture
83+
case _ =>
84+
false

0 commit comments

Comments
 (0)