Skip to content

Commit 63344e7

Browse files
authored
Merge pull request #15877 from dotty-staging/cc-experiment
Add experimental capture checking
2 parents 3e20051 + 943d84a commit 63344e7

File tree

213 files changed

+8214
-526
lines changed

Some content is hidden

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

213 files changed

+8214
-526
lines changed

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

Lines changed: 5 additions & 0 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,10 @@ class Compiler {
7879
new SpecializeApplyMethods, // Adds specialized methods to FunctionN
7980
new TryCatchPatterns, // Compile cases in try/catch
8081
new PatternMatcher) :: // Compile pattern matches
82+
List(new TestRecheck.Pre) :: // Test only: run rechecker, enabled under -Yrecheck-test
83+
List(new TestRecheck) :: // Test only: run rechecker, enabled under -Yrecheck-test
84+
List(new CheckCaptures.Pre) :: // Preparations for check captures phase, enabled under -Ycc
85+
List(new CheckCaptures) :: // Check captures, enabled under -Ycc
8186
List(new ElimOpaque, // Turn opaque into normal aliases
8287
new sjs.ExplicitJSClasses, // Make all JS classes explicit (Scala.js only)
8388
new ExplicitOuter, // Add accessors to outer classes from nested ones.

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import reporting.{Suppression, Action, Profile, ActiveProfile, NoProfile}
1919
import reporting.Diagnostic
2020
import reporting.Diagnostic.Warning
2121
import rewrites.Rewrites
22-
2322
import profile.Profiler
2423
import printing.XprintMode
2524
import typer.ImplicitRunInfo
@@ -294,7 +293,7 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint
294293
val fusedPhase = ctx.phase.prevMega
295294
val echoHeader = f"[[syntax trees at end of $fusedPhase%25s]] // ${unit.source}"
296295
val tree = if ctx.isAfterTyper then unit.tpdTree else unit.untpdTree
297-
val treeString = tree.show(using ctx.withProperty(XprintMode, Some(())))
296+
val treeString = fusedPhase.show(tree)
298297

299298
last match {
300299
case SomePrintedTree(phase, lastTreeString) if lastTreeString == treeString =>

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

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -468,7 +468,7 @@ object desugar {
468468

469469
if mods.is(Trait) then
470470
for vparams <- originalVparamss; vparam <- vparams do
471-
if vparam.tpt.isInstanceOf[ByNameTypeTree] then
471+
if isByNameType(vparam.tpt) then
472472
report.error(em"implementation restriction: traits cannot have by name parameters", vparam.srcPos)
473473

474474
// Annotations on class _type_ parameters are set on the derived parameters
@@ -576,9 +576,8 @@ object desugar {
576576
appliedTypeTree(tycon, targs)
577577
}
578578

579-
def isRepeated(tree: Tree): Boolean = tree match {
579+
def isRepeated(tree: Tree): Boolean = stripByNameType(tree) match {
580580
case PostfixOp(_, Ident(tpnme.raw.STAR)) => true
581-
case ByNameTypeTree(tree1) => isRepeated(tree1)
582581
case _ => false
583582
}
584583

@@ -1810,6 +1809,16 @@ object desugar {
18101809
flatTree(pats1 map (makePatDef(tree, mods, _, rhs)))
18111810
case ext: ExtMethods =>
18121811
Block(List(ext), Literal(Constant(())).withSpan(ext.span))
1812+
case CapturingTypeTree(refs, parent) =>
1813+
// convert `{refs} T` to `T @retains refs`
1814+
// `{refs}-> T` to `-> (T @retainsByName refs)`
1815+
def annotate(annotName: TypeName, tp: Tree) =
1816+
Annotated(tp, New(scalaAnnotationDot(annotName), List(refs)))
1817+
parent match
1818+
case ByNameTypeTree(restpt) =>
1819+
cpy.ByNameTypeTree(parent)(annotate(tpnme.retainsByName, restpt))
1820+
case _ =>
1821+
annotate(tpnme.retains, parent)
18131822
}
18141823
desugared.withSpan(tree.span)
18151824
}
@@ -1946,6 +1955,8 @@ object desugar {
19461955
case _ => traverseChildren(tree)
19471956
}
19481957
}.traverse(expr)
1958+
case CapturingTypeTree(refs, parent) =>
1959+
collect(parent)
19491960
case _ =>
19501961
}
19511962
collect(tree)

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

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,8 +178,7 @@ trait TreeInfo[T >: Untyped <: Type] { self: Trees.Instance[T] =>
178178
}
179179

180180
/** Is tpt a vararg type of the form T* or => T*? */
181-
def isRepeatedParamType(tpt: Tree)(using Context): Boolean = tpt match {
182-
case ByNameTypeTree(tpt1) => isRepeatedParamType(tpt1)
181+
def isRepeatedParamType(tpt: Tree)(using Context): Boolean = stripByNameType(tpt) match {
183182
case tpt: TypeTree => tpt.typeOpt.isRepeatedParam
184183
case AppliedTypeTree(Select(_, tpnme.REPEATED_PARAM_CLASS), _) => true
185184
case _ => false
@@ -196,6 +195,18 @@ trait TreeInfo[T >: Untyped <: Type] { self: Trees.Instance[T] =>
196195
case arg => arg.typeOpt.widen.isRepeatedParam
197196
}
198197

198+
/** Is tree a type tree of the form `=> T` or (under -Ycc) `{refs}-> T`? */
199+
def isByNameType(tree: Tree)(using Context): Boolean =
200+
stripByNameType(tree) ne tree
201+
202+
/** Strip `=> T` to `T` and (under -Ycc) `{refs}-> T` to `T` */
203+
def stripByNameType(tree: Tree)(using Context): Tree = unsplice(tree) match
204+
case ByNameTypeTree(t1) => t1
205+
case untpd.CapturingTypeTree(_, parent) =>
206+
val parent1 = stripByNameType(parent)
207+
if parent1 eq parent then tree else parent1
208+
case _ => tree
209+
199210
/** All type and value parameter symbols of this DefDef */
200211
def allParamSyms(ddef: DefDef)(using Context): List[Symbol] =
201212
ddef.paramss.flatten.map(_.symbol)
@@ -388,6 +399,22 @@ trait UntypedTreeInfo extends TreeInfo[Untyped] { self: Trees.Instance[Untyped]
388399
case _ => None
389400
}
390401
}
402+
403+
/** Under -Ycc: A builder and extractor for `=> T`, which is an alias for `{*}-> T`.
404+
* Only trees of the form `=> T` are matched; trees written directly as `{*}-> T`
405+
* are ignored by the extractor.
406+
*/
407+
object ImpureByNameTypeTree:
408+
409+
def apply(tp: ByNameTypeTree)(using Context): untpd.CapturingTypeTree =
410+
untpd.CapturingTypeTree(
411+
Ident(nme.CAPTURE_ROOT).withSpan(tp.span.startPos) :: Nil, tp)
412+
413+
def unapply(tp: Tree)(using Context): Option[ByNameTypeTree] = tp match
414+
case untpd.CapturingTypeTree(id @ Ident(nme.CAPTURE_ROOT) :: Nil, bntp: ByNameTypeTree)
415+
if id.span == bntp.span.startPos => Some(bntp)
416+
case _ => None
417+
end ImpureByNameTypeTree
391418
}
392419

393420
trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] =>

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

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -253,16 +253,10 @@ object Trees {
253253
/** Tree's denotation can be derived from its type */
254254
abstract class DenotingTree[-T >: Untyped](implicit @constructorOnly src: SourceFile) extends Tree[T] {
255255
type ThisTree[-T >: Untyped] <: DenotingTree[T]
256-
override def denot(using Context): Denotation = typeOpt match {
256+
override def denot(using Context): Denotation = typeOpt.stripped match
257257
case tpe: NamedType => tpe.denot
258258
case tpe: ThisType => tpe.cls.denot
259-
case tpe: AnnotatedType => tpe.stripAnnots match {
260-
case tpe: NamedType => tpe.denot
261-
case tpe: ThisType => tpe.cls.denot
262-
case _ => NoDenotation
263-
}
264259
case _ => NoDenotation
265-
}
266260
}
267261

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

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -164,8 +164,8 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
164164
def Inlined(call: Tree, bindings: List[MemberDef], expansion: Tree)(using Context): Inlined =
165165
ta.assignType(untpd.Inlined(call, bindings, expansion), bindings, expansion)
166166

167-
def TypeTree(tp: Type)(using Context): TypeTree =
168-
untpd.TypeTree().withType(tp)
167+
def TypeTree(tp: Type, inferred: Boolean = false)(using Context): TypeTree =
168+
(if inferred then untpd.InferredTypeTree() else untpd.TypeTree()).withType(tp)
169169

170170
def SingletonTypeTree(ref: Tree)(using Context): SingletonTypeTree =
171171
ta.assignType(untpd.SingletonTypeTree(ref), ref)
@@ -203,8 +203,8 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
203203
ta.assignType(untpd.UnApply(fun, implicits, patterns), proto)
204204
}
205205

206-
def ValDef(sym: TermSymbol, rhs: LazyTree = EmptyTree)(using Context): ValDef =
207-
ta.assignType(untpd.ValDef(sym.name, TypeTree(sym.info), rhs), sym)
206+
def ValDef(sym: TermSymbol, rhs: LazyTree = EmptyTree, inferred: Boolean = false)(using Context): ValDef =
207+
ta.assignType(untpd.ValDef(sym.name, TypeTree(sym.info, inferred), rhs), sym)
208208

209209
def SyntheticValDef(name: TermName, rhs: Tree, flags: FlagSet = EmptyFlags)(using Context): ValDef =
210210
ValDef(newSymbol(ctx.owner, name, Synthetic | flags, rhs.tpe.widen, coord = rhs.span), rhs)

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

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,13 +69,13 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
6969
case class InterpolatedString(id: TermName, segments: List[Tree])(implicit @constructorOnly src: SourceFile)
7070
extends TermTree
7171

72-
/** A function type */
72+
/** A function type or closure */
7373
case class Function(args: List[Tree], body: Tree)(implicit @constructorOnly src: SourceFile) extends Tree {
7474
override def isTerm: Boolean = body.isTerm
7575
override def isType: Boolean = body.isType
7676
}
7777

78-
/** A function type with `implicit`, `erased`, or `given` modifiers */
78+
/** A function type or closure with `implicit`, `erased`, or `given` modifiers */
7979
class FunctionWithMods(args: List[Tree], body: Tree, val mods: Modifiers)(implicit @constructorOnly src: SourceFile)
8080
extends Function(args, body)
8181

@@ -145,6 +145,9 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
145145
case Floating
146146
}
147147

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

@@ -213,6 +216,9 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
213216
case class Transparent()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Transparent)
214217

215218
case class Infix()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Infix)
219+
220+
/** Used under -Ycc to mark impure function types `A => B` in `FunctionWithMods` */
221+
case class Impure()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Impure)
216222
}
217223

218224
/** Modifiers and annotations for definitions
@@ -390,6 +396,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
390396
def JavaSeqLiteral(elems: List[Tree], elemtpt: Tree)(implicit src: SourceFile): JavaSeqLiteral = new JavaSeqLiteral(elems, elemtpt)
391397
def Inlined(call: tpd.Tree, bindings: List[MemberDef], expansion: Tree)(implicit src: SourceFile): Inlined = new Inlined(call, bindings, expansion)
392398
def TypeTree()(implicit src: SourceFile): TypeTree = new TypeTree()
399+
def InferredTypeTree()(implicit src: SourceFile): TypeTree = new InferredTypeTree()
393400
def SingletonTypeTree(ref: Tree)(implicit src: SourceFile): SingletonTypeTree = new SingletonTypeTree(ref)
394401
def RefinedTypeTree(tpt: Tree, refinements: List[Tree])(implicit src: SourceFile): RefinedTypeTree = new RefinedTypeTree(tpt, refinements)
395402
def AppliedTypeTree(tpt: Tree, args: List[Tree])(implicit src: SourceFile): AppliedTypeTree = new AppliedTypeTree(tpt, args)
@@ -647,6 +654,10 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
647654
case tree: Number if (digits == tree.digits) && (kind == tree.kind) => tree
648655
case _ => finalize(tree, untpd.Number(digits, kind))
649656
}
657+
def CapturingTypeTree(tree: Tree)(refs: List[Tree], parent: Tree)(using Context): Tree = tree match
658+
case tree: CapturingTypeTree if (refs eq tree.refs) && (parent eq tree.parent) => tree
659+
case _ => finalize(tree, untpd.CapturingTypeTree(refs, parent))
660+
650661
def TypedSplice(tree: Tree)(splice: tpd.Tree)(using Context): ProxyTree = tree match {
651662
case tree: TypedSplice if splice `eq` tree.splice => tree
652663
case _ => finalize(tree, untpd.TypedSplice(splice)(using ctx))
@@ -710,6 +721,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
710721
tree
711722
case MacroTree(expr) =>
712723
cpy.MacroTree(tree)(transform(expr))
724+
case CapturingTypeTree(refs, parent) =>
725+
cpy.CapturingTypeTree(tree)(transform(refs), transform(parent))
713726
case _ =>
714727
super.transformMoreCases(tree)
715728
}
@@ -769,6 +782,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
769782
this(x, splice)
770783
case MacroTree(expr) =>
771784
this(x, expr)
785+
case CapturingTypeTree(refs, parent) =>
786+
this(this(x, refs), parent)
772787
case _ =>
773788
super.foldMoreCases(x, tree)
774789
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package dotty.tools
2+
package dotc
3+
package cc
4+
5+
import core.*
6+
import Types.*, Symbols.*, Contexts.*
7+
8+
/** A one-element cache for the boxed version of an unboxed capturing type */
9+
class BoxedTypeCache:
10+
private var boxed: Type = compiletime.uninitialized
11+
private var unboxed: Type = NoType
12+
13+
def apply(tp: AnnotatedType)(using Context): Type =
14+
if tp ne unboxed then
15+
unboxed = tp
16+
val CapturingType(parent, refs) = tp: @unchecked
17+
boxed = CapturingType(parent, refs, boxed = true)
18+
boxed
19+
end BoxedTypeCache
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
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+
/** An annotation representing a capture set and whether it is boxed.
15+
* It simulates a normal @retains annotation except that it is more efficient,
16+
* supports variables as capture sets, and adds a `boxed` flag.
17+
* These annotations are created during capture checking. Before that
18+
* there are only regular @retains and @retainsByName annotations.
19+
* @param refs the capture set
20+
* @param boxed whether the type carrying the annotation is boxed
21+
* @param cls the underlying class (either annotation.retains or annotation.retainsByName)
22+
*/
23+
case class CaptureAnnotation(refs: CaptureSet, boxed: Boolean)(cls: Symbol) extends Annotation:
24+
import CaptureAnnotation.*
25+
import tpd.*
26+
27+
/** A cache for boxed version of a capturing type with this annotation */
28+
val boxedType = BoxedTypeCache()
29+
30+
/** Reconstitute annotation tree from capture set */
31+
override def tree(using Context) =
32+
val elems = refs.elems.toList.map {
33+
case cr: TermRef => ref(cr)
34+
case cr: TermParamRef => untpd.Ident(cr.paramName).withType(cr)
35+
case cr: ThisType => This(cr.cls)
36+
}
37+
val arg = repeated(elems, TypeTree(defn.AnyType))
38+
New(symbol.typeRef, arg :: Nil)
39+
40+
override def symbol(using Context) = cls
41+
42+
override def derivedAnnotation(tree: Tree)(using Context): Annotation =
43+
unsupported(i"derivedAnnotation(Tree), $tree, $refs")
44+
45+
def derivedAnnotation(refs: CaptureSet, boxed: Boolean)(using Context): Annotation =
46+
if (this.refs eq refs) && (this.boxed == boxed) then this
47+
else CaptureAnnotation(refs, boxed)(cls)
48+
49+
override def sameAnnotation(that: Annotation)(using Context): Boolean = that match
50+
case CaptureAnnotation(refs, boxed) =>
51+
this.refs == refs && this.boxed == boxed && this.symbol == that.symbol
52+
case _ => false
53+
54+
override def mapWith(tm: TypeMap)(using Context) =
55+
val elems = refs.elems.toList
56+
val elems1 = elems.mapConserve(tm)
57+
if elems1 eq elems then this
58+
else if elems1.forall(_.isInstanceOf[CaptureRef])
59+
then derivedAnnotation(CaptureSet(elems1.asInstanceOf[List[CaptureRef]]*), boxed)
60+
else EmptyAnnotation
61+
62+
override def refersToParamOf(tl: TermLambda)(using Context): Boolean =
63+
refs.elems.exists {
64+
case TermParamRef(tl1, _) => tl eq tl1
65+
case _ => false
66+
}
67+
68+
override def toText(printer: Printer): Text = refs.toText(printer)
69+
70+
override def hash: Int =
71+
(refs.hashCode << 1) | (if boxed then 1 else 0)
72+
73+
override def eql(that: Annotation) = that match
74+
case that: CaptureAnnotation => (this.refs eq that.refs) && (this.boxed == that.boxed)
75+
case _ => false
76+
77+
end CaptureAnnotation

0 commit comments

Comments
 (0)