Skip to content

Commit 06f83ce

Browse files
authored
Merge pull request #13309 from dotty-staging/add-cc2
First version of capture checker based on rechecking
2 parents 8a9a37e + b00a8b5 commit 06f83ce

File tree

102 files changed

+3274
-234
lines changed

Some content is hidden

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

102 files changed

+3274
-234
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._
@@ -78,6 +79,8 @@ class Compiler {
7879
new RefChecks, // Various checks mostly related to abstract members and overriding
7980
new TryCatchPatterns, // Compile cases in try/catch
8081
new PatternMatcher) :: // Compile pattern matches
82+
List(new PreRecheck) :: // Preparations for check captures phase, enabled under -Ycc
83+
List(new CheckCaptures) :: // Check captures, enabled under -Ycc
8184
List(new ElimOpaque, // Turn opaque into normal aliases
8285
new sjs.ExplicitJSClasses, // Make all JS classes explicit (Scala.js only)
8386
new ExplicitOuter, // Add accessors to outer classes from nested ones.
@@ -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: 16 additions & 1 deletion
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

@@ -458,7 +461,11 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
458461
def AppliedTypeTree(tpt: Tree, arg: Tree)(implicit src: SourceFile): AppliedTypeTree =
459462
AppliedTypeTree(tpt, arg :: Nil)
460463

461-
def TypeTree(tpe: Type)(using Context): TypedSplice = TypedSplice(TypeTree().withTypeUnchecked(tpe))
464+
def TypeTree(tpe: Type)(using Context): TypedSplice =
465+
TypedSplice(TypeTree().withTypeUnchecked(tpe))
466+
467+
def InferredTypeTree(tpe: Type)(using Context): TypedSplice =
468+
TypedSplice(new InferredTypeTree().withTypeUnchecked(tpe))
462469

463470
def unitLiteral(implicit src: SourceFile): Literal = Literal(Constant(()))
464471

@@ -646,6 +653,10 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
646653
case tree: Number if (digits == tree.digits) && (kind == tree.kind) => tree
647654
case _ => finalize(tree, untpd.Number(digits, kind))
648655
}
656+
def CapturingTypeTree(tree: Tree)(refs: List[Tree], parent: Tree)(using Context): Tree = tree match
657+
case tree: CapturingTypeTree if (refs eq tree.refs) && (parent eq tree.parent) => tree
658+
case _ => finalize(tree, untpd.CapturingTypeTree(refs, parent))
659+
649660
def TypedSplice(tree: Tree)(splice: tpd.Tree)(using Context): ProxyTree = tree match {
650661
case tree: TypedSplice if splice `eq` tree.splice => tree
651662
case _ => finalize(tree, untpd.TypedSplice(splice)(using ctx))
@@ -711,6 +722,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
711722
tree
712723
case MacroTree(expr) =>
713724
cpy.MacroTree(tree)(transform(expr))
725+
case CapturingTypeTree(refs, parent) =>
726+
cpy.CapturingTypeTree(tree)(transform(refs), transform(parent))
714727
case _ =>
715728
super.transformMoreCases(tree)
716729
}
@@ -772,6 +785,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
772785
this(x, splice)
773786
case MacroTree(expr) =>
774787
this(x, expr)
788+
case CapturingTypeTree(refs, parent) =>
789+
this(this(x, refs), parent)
775790
case _ =>
776791
super.foldMoreCases(x, tree)
777792
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
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, boxed: Boolean) 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, boxed: Boolean)(using Context): Annotation =
34+
if (this.refs eq refs) && (this.boxed == boxed) then this
35+
else CaptureAnnotation(refs, boxed)
36+
37+
override def sameAnnotation(that: Annotation)(using Context): Boolean = that match
38+
case CaptureAnnotation(refs2, boxed2) => refs == refs2 && boxed == boxed2
39+
case _ => false
40+
41+
override def mapWith(tp: TypeMap)(using Context) =
42+
val elems = refs.elems.toList
43+
val elems1 = elems.mapConserve(tp)
44+
if elems1 eq elems then this
45+
else if elems1.forall(_.isInstanceOf[CaptureRef])
46+
then derivedAnnotation(CaptureSet(elems1.asInstanceOf[List[CaptureRef]]*), boxed)
47+
else EmptyAnnotation
48+
49+
override def refersToParamOf(tl: TermLambda)(using Context): Boolean =
50+
refs.elems.exists {
51+
case TermParamRef(tl1, _) => tl eq tl1
52+
case _ => false
53+
}
54+
55+
override def toText(printer: Printer): Text = refs.toText(printer)
56+
57+
override def hash: Int = (refs.hashCode << 1) | (if boxed then 1 else 0)
58+
59+
override def eql(that: Annotation) = that match
60+
case that: CaptureAnnotation => (this.refs eq that.refs) && (this.boxed == boxed)
61+
case _ => false
62+
63+
end CaptureAnnotation
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
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+
private val IsBoxed: Key[Unit] = Key()
15+
16+
def retainedElems(tree: Tree)(using Context): List[Tree] = tree match
17+
case Apply(_, Typed(SeqLiteral(elems, _), _) :: Nil) => elems
18+
case _ => Nil
19+
20+
extension (tree: Tree)
21+
22+
def toCaptureRef(using Context): CaptureRef = tree.tpe.asInstanceOf[CaptureRef]
23+
24+
def toCaptureSet(using Context): CaptureSet =
25+
tree.getAttachment(Captures) match
26+
case Some(refs) => refs
27+
case None =>
28+
val refs = CaptureSet(retainedElems(tree).map(_.toCaptureRef)*)
29+
.showing(i"toCaptureSet $tree --> $result", capt)
30+
tree.putAttachment(Captures, refs)
31+
refs
32+
33+
def isBoxedCapturing(using Context): Boolean =
34+
tree.hasAttachment(IsBoxed)
35+
36+
def setBoxedCapturing()(using Context): Unit =
37+
tree.putAttachment(IsBoxed, ())
38+
39+
extension (tp: Type)
40+
41+
def derivedCapturingType(parent: Type, refs: CaptureSet)(using Context): Type = tp match
42+
case CapturingType(p, r, b) =>
43+
if (parent eq p) && (refs eq r) then tp
44+
else CapturingType(parent, refs, b)
45+
46+
/** If this is type variable instantiated or upper bounded with a capturing type,
47+
* the capture set associated with that type. Extended to and-or types and
48+
* type proxies in the obvious way. If a term has a type with a boxed captureset,
49+
* that captureset counts towards the capture variables of the envirionment.
50+
*/
51+
def boxedCaptured(using Context): CaptureSet =
52+
def getBoxed(tp: Type): CaptureSet = tp match
53+
case CapturingType(_, refs, boxed) => if boxed then refs else CaptureSet.empty
54+
case tp: TypeProxy => getBoxed(tp.superType)
55+
case tp: AndType => getBoxed(tp.tp1) ++ getBoxed(tp.tp2)
56+
case tp: OrType => getBoxed(tp.tp1) ** getBoxed(tp.tp2)
57+
case _ => CaptureSet.empty
58+
getBoxed(tp)
59+
60+
def isBoxedCapturing(using Context) = !tp.boxedCaptured.isAlwaysEmpty
61+
62+
def canHaveInferredCapture(using Context): Boolean = tp match
63+
case tp: TypeRef if tp.symbol.isClass =>
64+
!tp.symbol.isValueClass && tp.symbol != defn.AnyClass
65+
case _: TypeVar | _: TypeParamRef =>
66+
false
67+
case tp: TypeProxy =>
68+
tp.superType.canHaveInferredCapture
69+
case tp: AndType =>
70+
tp.tp1.canHaveInferredCapture && tp.tp2.canHaveInferredCapture
71+
case tp: OrType =>
72+
tp.tp1.canHaveInferredCapture || tp.tp2.canHaveInferredCapture
73+
case _ =>
74+
false
75+
76+
def stripCapturing(using Context): Type = tp.dealiasKeepAnnots match
77+
case CapturingType(parent, _, _) =>
78+
parent.stripCapturing
79+
case atd @ AnnotatedType(parent, annot) =>
80+
atd.derivedAnnotatedType(parent.stripCapturing, annot)
81+
case _ =>
82+
tp

0 commit comments

Comments
 (0)