Skip to content

Commit d2a5e8d

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 9241624 commit d2a5e8d

File tree

101 files changed

+3252
-230
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

+3252
-230
lines changed

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

+3-2
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._
@@ -79,6 +80,8 @@ class Compiler {
7980
new RefChecks, // Various checks mostly related to abstract members and overriding
8081
new TryCatchPatterns, // Compile cases in try/catch
8182
new PatternMatcher) :: // Compile pattern matches
83+
List(new PreRecheck) :: // Preparations for check captures phase, enabled under -Ycc
84+
List(new CheckCaptures) :: // Check captures, enabled under -Ycc
8285
List(new ElimOpaque, // Turn opaque into normal aliases
8386
new sjs.ExplicitJSClasses, // Make all JS classes explicit (Scala.js only)
8487
new ExplicitOuter, // Add accessors to outer classes from nested ones.
@@ -102,8 +105,6 @@ class Compiler {
102105
new TupleOptimizations, // Optimize generic operations on tuples
103106
new LetOverApply, // Lift blocks from receivers of applications
104107
new ArrayConstructors) :: // Intercept creation of (non-generic) arrays and intrinsify.
105-
List(new PreRecheck) :: // Preparations for recheck phase, enabled under -Yrecheck
106-
List(new TestRecheck) :: // Test rechecking, enabled under -Yrecheck
107108
List(new Erasure) :: // Rewrite types to JVM model, erasing all type parameters, abstract types and refinements.
108109
List(new ElimErasedValueType, // Expand erased value types to their underlying implmementation types
109110
new PureStats, // Remove pure stats from blocks

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

+1-3
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

+5
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

+1-7
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

+11
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

@@ -650,6 +653,10 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
650653
case tree: Number if (digits == tree.digits) && (kind == tree.kind) => tree
651654
case _ => finalize(tree, untpd.Number(digits, kind))
652655
}
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+
653660
def TypedSplice(tree: Tree)(splice: tpd.Tree)(using Context): ProxyTree = tree match {
654661
case tree: TypedSplice if splice `eq` tree.splice => tree
655662
case _ => finalize(tree, untpd.TypedSplice(splice)(using ctx))
@@ -715,6 +722,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
715722
tree
716723
case MacroTree(expr) =>
717724
cpy.MacroTree(tree)(transform(expr))
725+
case CapturingTypeTree(refs, parent) =>
726+
cpy.CapturingTypeTree(tree)(transform(refs), transform(parent))
718727
case _ =>
719728
super.transformMoreCases(tree)
720729
}
@@ -776,6 +785,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
776785
this(x, splice)
777786
case MacroTree(expr) =>
778787
this(x, expr)
788+
case CapturingTypeTree(refs, parent) =>
789+
this(this(x, refs), parent)
779790
case _ =>
780791
super.foldMoreCases(x, tree)
781792
}
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
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)