Skip to content

First version of capture checker based on rechecking #13309

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 29, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions compiler/src/dotty/tools/dotc/Compiler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package dotc
import core._
import Contexts._
import typer.{TyperPhase, RefChecks}
import cc.CheckCaptures
import parsing.Parser
import Phases.Phase
import transform._
Expand Down Expand Up @@ -78,6 +79,8 @@ class Compiler {
new RefChecks, // Various checks mostly related to abstract members and overriding
new TryCatchPatterns, // Compile cases in try/catch
new PatternMatcher) :: // Compile pattern matches
List(new PreRecheck) :: // Preparations for check captures phase, enabled under -Ycc
List(new CheckCaptures) :: // Check captures, enabled under -Ycc
List(new ElimOpaque, // Turn opaque into normal aliases
new sjs.ExplicitJSClasses, // Make all JS classes explicit (Scala.js only)
new ExplicitOuter, // Add accessors to outer classes from nested ones.
Expand All @@ -101,8 +104,6 @@ class Compiler {
new TupleOptimizations, // Optimize generic operations on tuples
new LetOverApply, // Lift blocks from receivers of applications
new ArrayConstructors) :: // Intercept creation of (non-generic) arrays and intrinsify.
List(new PreRecheck) :: // Preparations for recheck phase, enabled under -Yrecheck
List(new TestRecheck) :: // Test rechecking, enabled under -Yrecheck
List(new Erasure) :: // Rewrite types to JVM model, erasing all type parameters, abstract types and refinements.
List(new ElimErasedValueType, // Expand erased value types to their underlying implmementation types
new PureStats, // Remove pure stats from blocks
Expand Down
4 changes: 1 addition & 3 deletions compiler/src/dotty/tools/dotc/Run.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,7 @@ import reporting.{Reporter, Suppression, Action}
import reporting.Diagnostic
import reporting.Diagnostic.Warning
import rewrites.Rewrites

import profile.Profiler
import printing.XprintMode
import parsing.Parsers.Parser
import parsing.JavaParsers.JavaParser
import typer.ImplicitRunInfo
Expand Down Expand Up @@ -328,7 +326,7 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint
val fusedPhase = ctx.base.fusedContaining(prevPhase)
val echoHeader = f"[[syntax trees at end of $fusedPhase%25s]] // ${unit.source}"
val tree = if ctx.isAfterTyper then unit.tpdTree else unit.untpdTree
val treeString = tree.show(using ctx.withProperty(XprintMode, Some(())))
val treeString = fusedPhase.show(tree)

last match {
case SomePrintedTree(phase, lastTreeString) if lastTreeString == treeString =>
Expand Down
5 changes: 5 additions & 0 deletions compiler/src/dotty/tools/dotc/ast/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1752,6 +1752,9 @@ object desugar {
flatTree(pats1 map (makePatDef(tree, mods, _, rhs)))
case ext: ExtMethods =>
Block(List(ext), Literal(Constant(())).withSpan(ext.span))
case CapturingTypeTree(refs, parent) =>
val annot = New(scalaDot(tpnme.retains), List(refs))
Annotated(parent, annot)
}
desugared.withSpan(tree.span)
}
Expand Down Expand Up @@ -1890,6 +1893,8 @@ object desugar {
case _ => traverseChildren(tree)
}
}.traverse(expr)
case CapturingTypeTree(refs, parent) =>
collect(parent)
case _ =>
}
collect(tree)
Expand Down
8 changes: 1 addition & 7 deletions compiler/src/dotty/tools/dotc/ast/Trees.scala
Original file line number Diff line number Diff line change
Expand Up @@ -260,16 +260,10 @@ object Trees {
/** Tree's denotation can be derived from its type */
abstract class DenotingTree[-T >: Untyped](implicit @constructorOnly src: SourceFile) extends Tree[T] {
type ThisTree[-T >: Untyped] <: DenotingTree[T]
override def denot(using Context): Denotation = typeOpt match {
override def denot(using Context): Denotation = typeOpt.stripped match
case tpe: NamedType => tpe.denot
case tpe: ThisType => tpe.cls.denot
case tpe: AnnotatedType => tpe.stripAnnots match {
case tpe: NamedType => tpe.denot
case tpe: ThisType => tpe.cls.denot
case _ => NoDenotation
}
case _ => NoDenotation
}
}

/** Tree's denot/isType/isTerm properties come from a subtree
Expand Down
17 changes: 16 additions & 1 deletion compiler/src/dotty/tools/dotc/ast/untpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,9 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
case Floating
}

/** {x1, ..., xN} T (only relevant under -Ycc) */
case class CapturingTypeTree(refs: List[Tree], parent: Tree)(implicit @constructorOnly src: SourceFile) extends TypTree

/** Short-lived usage in typer, does not need copy/transform/fold infrastructure */
case class DependentTypeTree(tp: List[Symbol] => Type)(implicit @constructorOnly src: SourceFile) extends Tree

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

def TypeTree(tpe: Type)(using Context): TypedSplice = TypedSplice(TypeTree().withTypeUnchecked(tpe))
def TypeTree(tpe: Type)(using Context): TypedSplice =
TypedSplice(TypeTree().withTypeUnchecked(tpe))

def InferredTypeTree(tpe: Type)(using Context): TypedSplice =
TypedSplice(new InferredTypeTree().withTypeUnchecked(tpe))

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

Expand Down Expand Up @@ -646,6 +653,10 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
case tree: Number if (digits == tree.digits) && (kind == tree.kind) => tree
case _ => finalize(tree, untpd.Number(digits, kind))
}
def CapturingTypeTree(tree: Tree)(refs: List[Tree], parent: Tree)(using Context): Tree = tree match
case tree: CapturingTypeTree if (refs eq tree.refs) && (parent eq tree.parent) => tree
case _ => finalize(tree, untpd.CapturingTypeTree(refs, parent))

def TypedSplice(tree: Tree)(splice: tpd.Tree)(using Context): ProxyTree = tree match {
case tree: TypedSplice if splice `eq` tree.splice => tree
case _ => finalize(tree, untpd.TypedSplice(splice)(using ctx))
Expand Down Expand Up @@ -711,6 +722,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
tree
case MacroTree(expr) =>
cpy.MacroTree(tree)(transform(expr))
case CapturingTypeTree(refs, parent) =>
cpy.CapturingTypeTree(tree)(transform(refs), transform(parent))
case _ =>
super.transformMoreCases(tree)
}
Expand Down Expand Up @@ -772,6 +785,8 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
this(x, splice)
case MacroTree(expr) =>
this(x, expr)
case CapturingTypeTree(refs, parent) =>
this(this(x, refs), parent)
case _ =>
super.foldMoreCases(x, tree)
}
Expand Down
63 changes: 63 additions & 0 deletions compiler/src/dotty/tools/dotc/cc/CaptureAnnotation.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package dotty.tools
package dotc
package cc

import core.*
import Types.*, Symbols.*, Contexts.*, Annotations.*
import ast.Trees.*
import ast.{tpd, untpd}
import Decorators.*
import config.Printers.capt
import printing.Printer
import printing.Texts.Text


case class CaptureAnnotation(refs: CaptureSet, boxed: Boolean) extends Annotation:
import CaptureAnnotation.*
import tpd.*

override def tree(using Context) =
val elems = refs.elems.toList.map {
case cr: TermRef => ref(cr)
case cr: TermParamRef => untpd.Ident(cr.paramName).withType(cr)
case cr: ThisType => This(cr.cls)
}
val arg = repeated(elems, TypeTree(defn.AnyType))
New(symbol.typeRef, arg :: Nil)

override def symbol(using Context) = defn.RetainsAnnot

override def derivedAnnotation(tree: Tree)(using Context): Annotation =
unsupported("derivedAnnotation(Tree)")

def derivedAnnotation(refs: CaptureSet, boxed: Boolean)(using Context): Annotation =
if (this.refs eq refs) && (this.boxed == boxed) then this
else CaptureAnnotation(refs, boxed)

override def sameAnnotation(that: Annotation)(using Context): Boolean = that match
case CaptureAnnotation(refs2, boxed2) => refs == refs2 && boxed == boxed2
case _ => false

override def mapWith(tp: TypeMap)(using Context) =
val elems = refs.elems.toList
val elems1 = elems.mapConserve(tp)
if elems1 eq elems then this
else if elems1.forall(_.isInstanceOf[CaptureRef])
then derivedAnnotation(CaptureSet(elems1.asInstanceOf[List[CaptureRef]]*), boxed)
else EmptyAnnotation

override def refersToParamOf(tl: TermLambda)(using Context): Boolean =
refs.elems.exists {
case TermParamRef(tl1, _) => tl eq tl1
case _ => false
}

override def toText(printer: Printer): Text = refs.toText(printer)

override def hash: Int = (refs.hashCode << 1) | (if boxed then 1 else 0)

override def eql(that: Annotation) = that match
case that: CaptureAnnotation => (this.refs eq that.refs) && (this.boxed == boxed)
case _ => false

end CaptureAnnotation
82 changes: 82 additions & 0 deletions compiler/src/dotty/tools/dotc/cc/CaptureOps.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package dotty.tools
package dotc
package cc

import core.*
import Types.*, Symbols.*, Contexts.*, Annotations.*
import ast.{tpd, untpd}
import Decorators.*
import config.Printers.capt
import util.Property.Key
import tpd.*

private val Captures: Key[CaptureSet] = Key()
private val IsBoxed: Key[Unit] = Key()

def retainedElems(tree: Tree)(using Context): List[Tree] = tree match
case Apply(_, Typed(SeqLiteral(elems, _), _) :: Nil) => elems
case _ => Nil

extension (tree: Tree)

def toCaptureRef(using Context): CaptureRef = tree.tpe.asInstanceOf[CaptureRef]

def toCaptureSet(using Context): CaptureSet =
tree.getAttachment(Captures) match
case Some(refs) => refs
case None =>
val refs = CaptureSet(retainedElems(tree).map(_.toCaptureRef)*)
.showing(i"toCaptureSet $tree --> $result", capt)
tree.putAttachment(Captures, refs)
refs

def isBoxedCapturing(using Context): Boolean =
tree.hasAttachment(IsBoxed)

def setBoxedCapturing()(using Context): Unit =
tree.putAttachment(IsBoxed, ())

extension (tp: Type)

def derivedCapturingType(parent: Type, refs: CaptureSet)(using Context): Type = tp match
case CapturingType(p, r, b) =>
if (parent eq p) && (refs eq r) then tp
else CapturingType(parent, refs, b)

/** If this is type variable instantiated or upper bounded with a capturing type,
* the capture set associated with that type. Extended to and-or types and
* type proxies in the obvious way. If a term has a type with a boxed captureset,
* that captureset counts towards the capture variables of the envirionment.
*/
def boxedCaptured(using Context): CaptureSet =
def getBoxed(tp: Type): CaptureSet = tp match
case CapturingType(_, refs, boxed) => if boxed then refs else CaptureSet.empty
case tp: TypeProxy => getBoxed(tp.superType)
case tp: AndType => getBoxed(tp.tp1) ++ getBoxed(tp.tp2)
case tp: OrType => getBoxed(tp.tp1) ** getBoxed(tp.tp2)
case _ => CaptureSet.empty
getBoxed(tp)

def isBoxedCapturing(using Context) = !tp.boxedCaptured.isAlwaysEmpty

def canHaveInferredCapture(using Context): Boolean = tp match
case tp: TypeRef if tp.symbol.isClass =>
!tp.symbol.isValueClass && tp.symbol != defn.AnyClass
case _: TypeVar | _: TypeParamRef =>
false
case tp: TypeProxy =>
tp.superType.canHaveInferredCapture
case tp: AndType =>
tp.tp1.canHaveInferredCapture && tp.tp2.canHaveInferredCapture
case tp: OrType =>
tp.tp1.canHaveInferredCapture || tp.tp2.canHaveInferredCapture
case _ =>
false

def stripCapturing(using Context): Type = tp.dealiasKeepAnnots match
case CapturingType(parent, _, _) =>
parent.stripCapturing
case atd @ AnnotatedType(parent, annot) =>
atd.derivedAnnotatedType(parent.stripCapturing, annot)
case _ =>
tp
Loading