Skip to content

Commit aeb5ff6

Browse files
committed
First version of capture checker.
A squashed version of the following commits: Handle byname parameters Don't force symbol completion when printing flags or annotations Check overrides and disallow non-local inferred capture types Handle `this` in capture sets Print capture variable dependencies under -Ydebug-cc Avoid spurious error message Avoid spurious error message "cannot be tracked since its capture set is empty". This arose in lazyref.scala for a DependentTypeTree in an anaonymois function. Dependent type trees map to normal TypeTrees, not InferredTypeTrees (and things go wrong if we try to change that). Drop TopType Consider bounds of type variables to be boxed More tests Avoid multiple maps when creating symbol infos Use a single BiTypeMap to map from inferred result and parameters to method info. This improves efficiency and debuggability by reducing the frequence of multiple stacked maps capture sets. Refactor with CompareResult#andAlso Refactoring: use isOK on CompareResult Reflect inferred parameter types in enclosing method type The variables in the inferred parameter type of an anonymous function need to also show up in the closure type itself, so that they can be constrained. Don't interpolate parameters of anonymous functions Here, we should wait until we get the info from the outside, which can be arbitrarily much later. Compute upper approximation of bimapped sets from both sides Fail when trying to add new elements to mapped sets It's the safe option. Print full origin trail of derived capture sets under -Ycc-debug Fix isEmpty condition in well-formedness check Make printing capture sets dependent on -Ycc-debug Recursion brake for upperApprox Fixes to upperApprox Make instantiteRT a BiTypeMap Otherwise we will not be able to do upper approximations of parameters. Interpolate only variables at negative polarity Interpolating covariant variables risks restricting capture sets to early. For instance, when a variable has the capture set of a called function in its capture set. When we have indirectly recursive calls it could be that the capture set of a called function is not yet fully formed. Interpolate type variables when symbols are completed Allow for possibility that variables are constant Only recomplete symbols if their info changes Add completions to Rechecker Complete val and def definitions lazily on first access. Now, recheckDefDef and recheckValDef are called the first time the new info of the defined symbol is needed, or, if the info is never needed, when the typer gets to the definitions. This only applied to definitions with inferred types. The others are handled in typer sequence, as before. The motivation of the change is that some modifications to inferred types of symbols can be made in subclasses without running into ordering problems. More fixes for subCapture New setting -Ycc-debug for more info on capture variables Fix subCapture in frozen state Previously, we still OKed two empty variables to be compared with subcapture in the frozen state. This should give an error. Direct comparisons of dependent function types Revert: Special treatment of dependent functions in TypeComparer change test Also treat explicit capturing type arguments as boxed Print subcapturing steps in -explain traces Don't decorate type variables with additional capture sets Boxed CapturingTypes Drop unsound capture suppression if expected type is boxed If expected type is boxed, the expression still contributes to the captured variables of its environment. Re-infer result types of anonymous functions Keep erased implicit args Special treatment of dependent functions in TypeComparer Fix addFunctionRefinements Always print refined function types as dependent functions. Makes it easier to see what goes on. Make CaptureSet ++ and ** simplify more Refine function types when reinferring so that they can be dependent Fix avoidance problem when typing blocks We should not pass en expected type when rechecking the expression of a block since that can add local references to global capture set variables. Also: tests for lists and pairs Print empty variables with "?" Fix printing untyped annotations Fix printing annotations in trees Drop redundant code Refactor map operations on capture sets Intoduce Bi-Mapped CaptureSets Report an error is a simply mapped capture set gets new elements that do not come from the original souurce. Introduce a new abstraction of bi-mapped sets that accept new elements and propagate them to the original source. Add map operation to SimpleIdentitySet Restrict tracked class parameters to vals Handle local classes and secondary constructors Fix CapturingType precedence when printing First stab at handling classes Bug fixes 1. Fix canBeTracked for TermRefs only TermRefs where prefix is NoPrefix or `this` can be tracked. The others have to be widened. 2. Fix rule for comparing capture refs on the left 3. Be more careful where comparisons are frozen Capture checker for functions
1 parent 3f8d6ef commit aeb5ff6

File tree

101 files changed

+3255
-227
lines changed

Some content is hidden

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

101 files changed

+3255
-227
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._
@@ -81,6 +82,8 @@ class Compiler {
8182
new SpecializeApplyMethods, // Adds specialized methods to FunctionN
8283
new TryCatchPatterns, // Compile cases in try/catch
8384
new PatternMatcher) :: // Compile pattern matches
85+
List(new PreRecheck) :: // Preparations for check captures phase, enabled under -Ycc
86+
List(new CheckCaptures) :: // Check captures, enabled under -Ycc
8487
List(new ElimOpaque, // Turn opaque into normal aliases
8588
new sjs.ExplicitJSClasses, // Make all JS classes explicit (Scala.js only)
8689
new ExplicitOuter, // Add accessors to outer classes from nested ones.
@@ -103,8 +106,6 @@ class Compiler {
103106
new TupleOptimizations, // Optimize generic operations on tuples
104107
new LetOverApply, // Lift blocks from receivers of applications
105108
new ArrayConstructors) :: // Intercept creation of (non-generic) arrays and intrinsify.
106-
List(new PreRecheck) :: // Preparations for recheck phase, enabled under -Yrecheck
107-
List(new TestRecheck) :: // Test rechecking, enabled under -Yrecheck
108109
List(new Erasure) :: // Rewrite types to JVM model, erasing all type parameters, abstract types and refinements.
109110
List(new ElimErasedValueType, // Expand erased value types to their underlying implmementation types
110111
new PureStats, // Remove pure stats from blocks

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}
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
@@ -286,7 +285,7 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint
286285
val fusedPhase = ctx.base.fusedContaining(prevPhase)
287286
val echoHeader = f"[[syntax trees at end of $fusedPhase%25s]] // ${unit.source}"
288287
val tree = if ctx.isAfterTyper then unit.tpdTree else unit.untpdTree
289-
val treeString = tree.show(using ctx.withProperty(XprintMode, Some(())))
288+
val treeString = fusedPhase.show(tree)
290289

291290
last match {
292291
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
@@ -1758,6 +1758,9 @@ object desugar {
17581758
flatTree(pats1 map (makePatDef(tree, mods, _, rhs)))
17591759
case ext: ExtMethods =>
17601760
Block(List(ext), Literal(Constant(())).withSpan(ext.span))
1761+
case CapturingTypeTree(refs, parent) =>
1762+
val annot = New(scalaDot(tpnme.retains), List(refs))
1763+
Annotated(parent, annot)
17611764
}
17621765
desugared.withSpan(tree.span)
17631766
}
@@ -1896,6 +1899,8 @@ object desugar {
18961899
case _ => traverseChildren(tree)
18971900
}
18981901
}.traverse(expr)
1902+
case CapturingTypeTree(refs, parent) =>
1903+
collect(parent)
18991904
case _ =>
19001905
}
19011906
collect(tree)

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

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

267261
/** 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
@@ -146,6 +146,9 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
146146
case Floating
147147
}
148148

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

@@ -649,6 +652,10 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
649652
case tree: Number if (digits == tree.digits) && (kind == tree.kind) => tree
650653
case _ => finalize(tree, untpd.Number(digits, kind))
651654
}
655+
def CapturingTypeTree(tree: Tree)(refs: List[Tree], parent: Tree)(using Context): Tree = tree match
656+
case tree: CapturingTypeTree if (refs eq tree.refs) && (parent eq tree.parent) => tree
657+
case _ => finalize(tree, untpd.CapturingTypeTree(refs, parent))
658+
652659
def TypedSplice(tree: Tree)(splice: tpd.Tree)(using Context): ProxyTree = tree match {
653660
case tree: TypedSplice if splice `eq` tree.splice => tree
654661
case _ => finalize(tree, untpd.TypedSplice(splice)(using ctx))
@@ -714,6 +721,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
714721
tree
715722
case MacroTree(expr) =>
716723
cpy.MacroTree(tree)(transform(expr))
724+
case CapturingTypeTree(refs, parent) =>
725+
cpy.CapturingTypeTree(tree)(transform(refs), transform(parent))
717726
case _ =>
718727
super.transformMoreCases(tree)
719728
}
@@ -775,6 +784,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
775784
this(x, splice)
776785
case MacroTree(expr) =>
777786
this(x, expr)
787+
case CapturingTypeTree(refs, parent) =>
788+
this(this(x, refs), parent)
778789
case _ =>
779790
super.foldMoreCases(x, tree)
780791
}
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)