Skip to content

Commit 9da35a4

Browse files
committed
Initial CheckCaptures
1 parent 86088da commit 9da35a4

File tree

2 files changed

+144
-1
lines changed

2 files changed

+144
-1
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package dotc
33

44
import core._
55
import Contexts._
6-
import typer.{FrontEnd, RefChecks}
6+
import typer.{FrontEnd, RefChecks, CheckCaptures}
77
import Phases.Phase
88
import transform._
99
import dotty.tools.backend.jvm.{CollectSuperCalls, GenBCode}
@@ -37,6 +37,7 @@ class Compiler {
3737
/** Phases dealing with the frontend up to trees ready for TASTY pickling */
3838
protected def frontendPhases: List[List[Phase]] =
3939
List(new FrontEnd) :: // Compiler frontend: scanner, parser, namer, typer
40+
List(new CheckCaptures) ::
4041
List(new YCheckPositions) :: // YCheck positions
4142
List(new sbt.ExtractDependencies) :: // Sends information on classes' dependencies to sbt via callbacks
4243
List(new semanticdb.ExtractSemanticDB) :: // Extract info into .semanticdb files
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
package dotty.tools
2+
package dotc
3+
package typer
4+
5+
import core._
6+
import Phases.*, DenotTransformers.*, SymDenotations.*
7+
import Contexts.*, Names.*, Flags.*, Symbols.*, Decorators.*
8+
import Types._
9+
import Symbols._
10+
import StdNames._
11+
import Decorators._
12+
import ProtoTypes._
13+
import Inferencing.isFullyDefined
14+
import config.Printers.capt
15+
import ast.{tpd, untpd, Trees}
16+
import NameKinds.{DocArtifactName, OuterSelectName, DefaultGetterName}
17+
import Trees._
18+
import scala.util.control.NonFatal
19+
import typer.ErrorReporting._
20+
import util.Spans.Span
21+
import util.{SimpleIdentitySet, SrcPos}
22+
import util.Chars.*
23+
import Nullables._
24+
import transform.*
25+
import scala.collection.mutable
26+
import reporting._
27+
import ProtoTypes._
28+
import dotty.tools.backend.jvm.DottyBackendInterface.symExtensions
29+
30+
class CheckCaptures extends Phase:
31+
import ast.tpd.*
32+
33+
def phaseName: String = "cc"
34+
override def isEnabled(using Context) = ctx.settings.Ycc.value
35+
36+
def newRefiner() = CaptureChecker()
37+
38+
def run(using Context): Unit =
39+
val unit = ctx.compilationUnit
40+
CaptureChecker().check(unit.tpdTree)
41+
42+
class CaptureChecker:
43+
import ast.tpd.*
44+
45+
private var myDeps: Dependencies = null
46+
47+
def deps(using Context): Dependencies =
48+
if myDeps == null then
49+
myDeps = new Dependencies(ctx.compilationUnit.tpdTree, ctx):
50+
def isExpr(sym: Symbol)(using Context): Boolean =
51+
sym.isRealClass || sym.isOneOf(MethodOrLazy)
52+
def enclosure(using Context) =
53+
def recur(owner: Symbol): Symbol =
54+
if isExpr(owner) || !owner.exists then owner else recur(owner.owner)
55+
recur(ctx.owner)
56+
myDeps
57+
58+
def check(tree: Tree)(using Context): Type = NoType
59+
60+
private def capturedVars(sym: Symbol)(using Context): CaptureSet =
61+
CaptureSet(deps.freeVars(sym).toList.map(_.termRef).filter(_.isTracked)*)
62+
63+
/*
64+
65+
override def typedClosure(tree: untpd.Closure, pt: Type)(using Context): Tree =
66+
super.typedClosure(tree, pt) match
67+
case tree1: Closure =>
68+
refinr.println(i"typing closure ${tree1.meth.symbol} with fvs ${capturedVars(tree1.meth.symbol)}")
69+
tree1.withType(tree1.tpe.capturing(capturedVars(tree1.meth.symbol)))
70+
case tree1 => tree1
71+
72+
override def typedApply(tree: untpd.Apply, pt: Type)(using Context): Tree =
73+
super.typedApply(tree, pt) match
74+
case tree1 @ Apply(fn, args) =>
75+
if tree.fun.symbol.isConstructor then
76+
//println(i"typing $tree1, ${capturedVars(tree1.tpe.classSymbol)}")
77+
tree1.withType(tree1.tpe.capturing(capturedVars(tree1.tpe.classSymbol)))
78+
else
79+
tree1
80+
case tree1 => tree1
81+
82+
*/
83+
84+
end CaptureChecker
85+
86+
inline val disallowGlobal = true
87+
88+
def checkWellFormed(whole: Type, pos: SrcPos)(using Context): Unit =
89+
def checkRelativeVariance(mt: MethodType) = new TypeTraverser:
90+
def traverse(tp: Type): Unit = tp match
91+
case CapturingType(parent, ref @ TermParamRef(`mt`, _)) =>
92+
if variance <= 0 then
93+
val direction = if variance < 0 then "contra" else "in"
94+
report.error(em"captured reference $ref appears ${direction}variantly in type $whole", pos)
95+
traverse(parent)
96+
case _ =>
97+
traverseChildren(tp)
98+
val checkVariance = new TypeTraverser:
99+
def traverse(tp: Type): Unit = tp match
100+
case mt: MethodType if mt.isResultDependent =>
101+
checkRelativeVariance(mt).traverse(mt)
102+
case _ =>
103+
traverseChildren(tp)
104+
checkVariance.traverse(whole)
105+
106+
object PostRefinerCheck extends TreeTraverser:
107+
def traverse(tree: Tree)(using Context) =
108+
tree match
109+
case tree1 @ TypeApply(fn, args) if disallowGlobal =>
110+
for arg <- args do
111+
//println(i"checking $arg in $tree: ${arg.tpe.captureSet}")
112+
for ref <- arg.tpe.captureSet.elems do
113+
val isGlobal = ref match
114+
case ref: TypeRef => ref.isRootCapability
115+
case ref: TermRef => ref.prefix != NoPrefix && ref.symbol.hasAnnotation(defn.AbilityAnnot)
116+
case _ => false
117+
val what = if ref.isRootCapability then "universal" else "global"
118+
if isGlobal then
119+
val notAllowed = i" is not allowed to capture the $what capability $ref"
120+
def msg = arg match
121+
case arg: InferredTypeTree =>
122+
i"""inferred type argument ${arg.tpe}$notAllowed
123+
|
124+
|The inferred arguments are: [$args%, %]"""
125+
case _ => s"type argument$notAllowed"
126+
report.error(msg, arg.srcPos)
127+
case tree: TypeTree =>
128+
// it's inferred, no need to check
129+
case _: TypTree | _: Closure =>
130+
checkWellFormed(tree.tpe, tree.srcPos)
131+
case tree: DefDef =>
132+
def check(tp: Type): Unit = tp match
133+
case tp: MethodOrPoly => check(tp.resType)
134+
case _ =>
135+
check(tree.symbol.info)
136+
case _ =>
137+
traverseChildren(tree)
138+
139+
def postRefinerCheck(tree: tpd.Tree)(using Context): Unit =
140+
PostRefinerCheck.traverse(tree)
141+
142+
end CheckCaptures

0 commit comments

Comments
 (0)