Skip to content

Commit 467fc6a

Browse files
committed
Check overrides and disallow non-local inferred capture types
1 parent a3e9f6d commit 467fc6a

File tree

5 files changed

+66
-4
lines changed

5 files changed

+66
-4
lines changed

compiler/src/dotty/tools/dotc/typer/CheckCaptures.scala

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import NameKinds.{DocArtifactName, OuterSelectName, DefaultGetterName}
1515
import Trees._
1616
import scala.util.control.NonFatal
1717
import typer.ErrorReporting._
18+
import typer.RefChecks
1819
import util.Spans.Span
1920
import util.{SimpleIdentitySet, EqHashMap, SrcPos}
2021
import util.Chars.*
@@ -96,6 +97,21 @@ class CheckCaptures extends Recheck:
9697

9798
def newRechecker()(using Context) = CaptureChecker(ctx)
9899

100+
override def run(using Context): Unit =
101+
checkOverrides.traverse(ctx.compilationUnit.tpdTree)
102+
super.run
103+
104+
def checkOverrides = new TreeTraverser:
105+
def traverse(t: Tree)(using Context) =
106+
t match
107+
case t: Template =>
108+
// ^^^ TODO: Can we avoid doing overrides checks twice?
109+
// We need to do them here since only at this phase CaptureTypes are relevant
110+
// But maybe we can then elide the check during the RefChecks phase if -Ycc is set?
111+
RefChecks.checkAllOverrides(ctx.owner.asClass)
112+
case _ =>
113+
traverseChildren(t)
114+
99115
class CaptureChecker(ictx: Context) extends Rechecker(ictx):
100116
import ast.tpd.*
101117

@@ -404,6 +420,28 @@ class CheckCaptures extends Recheck:
404420
for arg <- args do
405421
//println(i"checking $arg in $tree: ${knownType(tree).captureSet}")
406422
checkNotGlobal(arg, args*)
423+
case t: ValOrDefDef if t.tpt.isInstanceOf[InferredTypeTree] =>
424+
val sym = t.symbol
425+
val isLocal =
426+
sym.ownersIterator.exists(_.isTerm)
427+
|| sym.accessBoundary(defn.RootClass).isContainedIn(sym.topLevelClass)
428+
429+
// The following classes of definitions need explicit capture types ...
430+
if !isLocal // ... since external capture types are not inferred
431+
|| sym.owner.is(Trait) // ... since we do OverridingPairs checking before capture inference
432+
|| sym.allOverriddenSymbols.nonEmpty // ... since we do override checking before capture inference
433+
then
434+
val inferred = knownType(t.tpt)
435+
def checkPure(tp: Type) = tp match
436+
case CapturingType(_, refs, _) if !refs.elems.isEmpty =>
437+
val resultStr = if t.isInstanceOf[DefDef] then " result" else ""
438+
report.error(
439+
em"""Non-local $sym cannot have an inferred$resultStr type
440+
|$inferred
441+
|with non-empty capture set $refs.
442+
|The type needs to be declared explicitly.""", t.srcPos)
443+
case _ =>
444+
inferred.foreachPart(checkPure, StopAt.Static)
407445
case _ =>
408446
traverseChildren(tree)
409447

compiler/src/dotty/tools/dotc/typer/RefChecks.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ object RefChecks {
224224
* TODO This still needs to be cleaned up; the current version is a straight port of what was there
225225
* before, but it looks too complicated and method bodies are far too large.
226226
*/
227-
private def checkAllOverrides(clazz: ClassSymbol)(using Context): Unit = {
227+
def checkAllOverrides(clazz: ClassSymbol)(using Context): Unit = {
228228
val self = clazz.thisType
229229
val upwardsSelf = upwardsThisType(clazz)
230230
var hasErrors = false

tests/pos-custom-args/captures/lazylist.scala renamed to tests/disabled/pos/lazylist.scala

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,25 @@ abstract class LazyList[+T]:
1111
if isEmpty then LazyNil
1212
else LazyCons(f(head), () => tail.map(f))
1313

14+
def concat[U >: T](that: {*} LazyList[U]): {this, that} LazyList[U]
15+
16+
// def flatMap[U](f: {*} T => LazyList[U]): {f, this} LazyList[U]
17+
1418
class LazyCons[+T](val x: T, val xs: {*} () => {*} LazyList[T]) extends LazyList[T]:
1519
def isEmpty = false
1620
def head = x
17-
def tail = xs()
21+
def tail: {*} LazyList[T] = xs()
22+
def concat[U >: T](that: {*} LazyList[U]): {this, that} LazyList[U] =
23+
LazyCons(x, () => xs().concat(that))
24+
// def flatMap[U](f: {*} T => LazyList[U]): {f, this} LazyList[U] =
25+
// f(x).concat(xs().flatMap(f))
1826

1927
object LazyNil extends LazyList[Nothing]:
2028
def isEmpty = true
2129
def head = ???
2230
def tail = ???
31+
def concat[U](that: {*} LazyList[U]): {that} LazyList[U] = that
32+
// def flatMap[U](f: {*} Nothing => LazyList[U]): LazyList[U] = LazyNil
2333

2434
def map[A, B](xs: {*} LazyList[A], f: {*} A => B): {f, xs} LazyList[B] =
2535
xs.map(f)

tests/neg-custom-args/captures/lazylist.check

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
-- [E163] Declaration Error: tests/neg-custom-args/captures/lazylist.scala:22:6 ----------------------------------------
2+
22 | def tail: {*} LazyList[Nothing] = ??? // error overriding
3+
| ^
4+
| error overriding method tail in class LazyList of type => lazylists.LazyList[Nothing];
5+
| method tail of type => {*} lazylists.LazyList[Nothing] has incompatible type
6+
7+
longer explanation available when compiling with `-explain`
18
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/lazylist.scala:35:29 -------------------------------------
29
35 | val ref1c: LazyList[Int] = ref1 // error
310
| ^^^^
@@ -26,3 +33,10 @@ longer explanation available when compiling with `-explain`
2633
| Required: {cap1, ref3, cap3} lazylists.LazyList[Int]
2734

2835
longer explanation available when compiling with `-explain`
36+
-- Error: tests/neg-custom-args/captures/lazylist.scala:17:6 -----------------------------------------------------------
37+
17 | def tail = xs() // error: cannot have an inferred type
38+
| ^^^^^^^^^^^^^^^
39+
| Non-local method tail cannot have an inferred result type
40+
| {*} lazylists.LazyList[T]
41+
| with non-empty capture set {*}.
42+
| The type needs to be declared explicitly.

tests/neg-custom-args/captures/lazylist.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@ abstract class LazyList[+T]:
1414
class LazyCons[+T](val x: T, val xs: {*} () => {*} LazyList[T]) extends LazyList[T]:
1515
def isEmpty = false
1616
def head = x
17-
def tail = xs()
17+
def tail = xs() // error: cannot have an inferred type
1818

1919
object LazyNil extends LazyList[Nothing]:
2020
def isEmpty = true
2121
def head = ???
22-
def tail = ???
22+
def tail: {*} LazyList[Nothing] = ??? // error overriding
2323

2424
def map[A, B](xs: {*} LazyList[A], f: {*} A => B): {f, xs} LazyList[B] =
2525
xs.map(f)

0 commit comments

Comments
 (0)