Skip to content

Commit

Permalink
Merge pull request scala#15877 from dotty-staging/cc-experiment
Browse files Browse the repository at this point in the history
Add experimental capture checking
  • Loading branch information
odersky authored Aug 29, 2022
2 parents 3e20051 + 943d84a commit 63344e7
Show file tree
Hide file tree
Showing 213 changed files with 8,214 additions and 526 deletions.
5 changes: 5 additions & 0 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,10 @@ class Compiler {
new SpecializeApplyMethods, // Adds specialized methods to FunctionN
new TryCatchPatterns, // Compile cases in try/catch
new PatternMatcher) :: // Compile pattern matches
List(new TestRecheck.Pre) :: // Test only: run rechecker, enabled under -Yrecheck-test
List(new TestRecheck) :: // Test only: run rechecker, enabled under -Yrecheck-test
List(new CheckCaptures.Pre) :: // 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 Down
3 changes: 1 addition & 2 deletions compiler/src/dotty/tools/dotc/Run.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import reporting.{Suppression, Action, Profile, ActiveProfile, NoProfile}
import reporting.Diagnostic
import reporting.Diagnostic.Warning
import rewrites.Rewrites

import profile.Profiler
import printing.XprintMode
import typer.ImplicitRunInfo
Expand Down Expand Up @@ -294,7 +293,7 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint
val fusedPhase = ctx.phase.prevMega
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
17 changes: 14 additions & 3 deletions compiler/src/dotty/tools/dotc/ast/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -468,7 +468,7 @@ object desugar {

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

// Annotations on class _type_ parameters are set on the derived parameters
Expand Down Expand Up @@ -576,9 +576,8 @@ object desugar {
appliedTypeTree(tycon, targs)
}

def isRepeated(tree: Tree): Boolean = tree match {
def isRepeated(tree: Tree): Boolean = stripByNameType(tree) match {
case PostfixOp(_, Ident(tpnme.raw.STAR)) => true
case ByNameTypeTree(tree1) => isRepeated(tree1)
case _ => false
}

Expand Down Expand Up @@ -1810,6 +1809,16 @@ object desugar {
flatTree(pats1 map (makePatDef(tree, mods, _, rhs)))
case ext: ExtMethods =>
Block(List(ext), Literal(Constant(())).withSpan(ext.span))
case CapturingTypeTree(refs, parent) =>
// convert `{refs} T` to `T @retains refs`
// `{refs}-> T` to `-> (T @retainsByName refs)`
def annotate(annotName: TypeName, tp: Tree) =
Annotated(tp, New(scalaAnnotationDot(annotName), List(refs)))
parent match
case ByNameTypeTree(restpt) =>
cpy.ByNameTypeTree(parent)(annotate(tpnme.retainsByName, restpt))
case _ =>
annotate(tpnme.retains, parent)
}
desugared.withSpan(tree.span)
}
Expand Down Expand Up @@ -1946,6 +1955,8 @@ object desugar {
case _ => traverseChildren(tree)
}
}.traverse(expr)
case CapturingTypeTree(refs, parent) =>
collect(parent)
case _ =>
}
collect(tree)
Expand Down
31 changes: 29 additions & 2 deletions compiler/src/dotty/tools/dotc/ast/TreeInfo.scala
Original file line number Diff line number Diff line change
Expand Up @@ -178,8 +178,7 @@ trait TreeInfo[T >: Untyped <: Type] { self: Trees.Instance[T] =>
}

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

/** Is tree a type tree of the form `=> T` or (under -Ycc) `{refs}-> T`? */
def isByNameType(tree: Tree)(using Context): Boolean =
stripByNameType(tree) ne tree

/** Strip `=> T` to `T` and (under -Ycc) `{refs}-> T` to `T` */
def stripByNameType(tree: Tree)(using Context): Tree = unsplice(tree) match
case ByNameTypeTree(t1) => t1
case untpd.CapturingTypeTree(_, parent) =>
val parent1 = stripByNameType(parent)
if parent1 eq parent then tree else parent1
case _ => tree

/** All type and value parameter symbols of this DefDef */
def allParamSyms(ddef: DefDef)(using Context): List[Symbol] =
ddef.paramss.flatten.map(_.symbol)
Expand Down Expand Up @@ -388,6 +399,22 @@ trait UntypedTreeInfo extends TreeInfo[Untyped] { self: Trees.Instance[Untyped]
case _ => None
}
}

/** Under -Ycc: A builder and extractor for `=> T`, which is an alias for `{*}-> T`.
* Only trees of the form `=> T` are matched; trees written directly as `{*}-> T`
* are ignored by the extractor.
*/
object ImpureByNameTypeTree:

def apply(tp: ByNameTypeTree)(using Context): untpd.CapturingTypeTree =
untpd.CapturingTypeTree(
Ident(nme.CAPTURE_ROOT).withSpan(tp.span.startPos) :: Nil, tp)

def unapply(tp: Tree)(using Context): Option[ByNameTypeTree] = tp match
case untpd.CapturingTypeTree(id @ Ident(nme.CAPTURE_ROOT) :: Nil, bntp: ByNameTypeTree)
if id.span == bntp.span.startPos => Some(bntp)
case _ => None
end ImpureByNameTypeTree
}

trait TypedTreeInfo extends TreeInfo[Type] { self: Trees.Instance[Type] =>
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 @@ -253,16 +253,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
8 changes: 4 additions & 4 deletions compiler/src/dotty/tools/dotc/ast/tpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -164,8 +164,8 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
def Inlined(call: Tree, bindings: List[MemberDef], expansion: Tree)(using Context): Inlined =
ta.assignType(untpd.Inlined(call, bindings, expansion), bindings, expansion)

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

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

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

def SyntheticValDef(name: TermName, rhs: Tree, flags: FlagSet = EmptyFlags)(using Context): ValDef =
ValDef(newSymbol(ctx.owner, name, Synthetic | flags, rhs.tpe.widen, coord = rhs.span), rhs)
Expand Down
19 changes: 17 additions & 2 deletions compiler/src/dotty/tools/dotc/ast/untpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,13 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
case class InterpolatedString(id: TermName, segments: List[Tree])(implicit @constructorOnly src: SourceFile)
extends TermTree

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

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

Expand Down Expand Up @@ -145,6 +145,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 @@ -213,6 +216,9 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
case class Transparent()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Transparent)

case class Infix()(implicit @constructorOnly src: SourceFile) extends Mod(Flags.Infix)

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

/** Modifiers and annotations for definitions
Expand Down Expand Up @@ -390,6 +396,7 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
def JavaSeqLiteral(elems: List[Tree], elemtpt: Tree)(implicit src: SourceFile): JavaSeqLiteral = new JavaSeqLiteral(elems, elemtpt)
def Inlined(call: tpd.Tree, bindings: List[MemberDef], expansion: Tree)(implicit src: SourceFile): Inlined = new Inlined(call, bindings, expansion)
def TypeTree()(implicit src: SourceFile): TypeTree = new TypeTree()
def InferredTypeTree()(implicit src: SourceFile): TypeTree = new InferredTypeTree()
def SingletonTypeTree(ref: Tree)(implicit src: SourceFile): SingletonTypeTree = new SingletonTypeTree(ref)
def RefinedTypeTree(tpt: Tree, refinements: List[Tree])(implicit src: SourceFile): RefinedTypeTree = new RefinedTypeTree(tpt, refinements)
def AppliedTypeTree(tpt: Tree, args: List[Tree])(implicit src: SourceFile): AppliedTypeTree = new AppliedTypeTree(tpt, args)
Expand Down Expand Up @@ -647,6 +654,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 @@ -710,6 +721,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 @@ -769,6 +782,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
19 changes: 19 additions & 0 deletions compiler/src/dotty/tools/dotc/cc/BoxedTypeCache.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package dotty.tools
package dotc
package cc

import core.*
import Types.*, Symbols.*, Contexts.*

/** A one-element cache for the boxed version of an unboxed capturing type */
class BoxedTypeCache:
private var boxed: Type = compiletime.uninitialized
private var unboxed: Type = NoType

def apply(tp: AnnotatedType)(using Context): Type =
if tp ne unboxed then
unboxed = tp
val CapturingType(parent, refs) = tp: @unchecked
boxed = CapturingType(parent, refs, boxed = true)
boxed
end BoxedTypeCache
77 changes: 77 additions & 0 deletions compiler/src/dotty/tools/dotc/cc/CaptureAnnotation.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
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

/** An annotation representing a capture set and whether it is boxed.
* It simulates a normal @retains annotation except that it is more efficient,
* supports variables as capture sets, and adds a `boxed` flag.
* These annotations are created during capture checking. Before that
* there are only regular @retains and @retainsByName annotations.
* @param refs the capture set
* @param boxed whether the type carrying the annotation is boxed
* @param cls the underlying class (either annotation.retains or annotation.retainsByName)
*/
case class CaptureAnnotation(refs: CaptureSet, boxed: Boolean)(cls: Symbol) extends Annotation:
import CaptureAnnotation.*
import tpd.*

/** A cache for boxed version of a capturing type with this annotation */
val boxedType = BoxedTypeCache()

/** Reconstitute annotation tree from capture set */
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) = cls

override def derivedAnnotation(tree: Tree)(using Context): Annotation =
unsupported(i"derivedAnnotation(Tree), $tree, $refs")

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

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

override def mapWith(tm: TypeMap)(using Context) =
val elems = refs.elems.toList
val elems1 = elems.mapConserve(tm)
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 == that.boxed)
case _ => false

end CaptureAnnotation
Loading

0 comments on commit 63344e7

Please sign in to comment.