Skip to content

Commit 6020422

Browse files
committed
Enhancement: Generalize handling of exceptions to all pure base classes
1 parent d0995ce commit 6020422

File tree

13 files changed

+55
-43
lines changed

13 files changed

+55
-43
lines changed

compiler/src/dotty/tools/dotc/cc/CaptureOps.scala

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -185,24 +185,27 @@ extension (tp: Type)
185185
case _ =>
186186
false
187187

188+
extension (cls: ClassSymbol)
189+
190+
def pureBaseClass(using Context): Option[Symbol] =
191+
cls.baseClasses.find(bc =>
192+
defn.pureBaseClasses.contains(bc)
193+
|| {
194+
val selfType = bc.givenSelfType
195+
selfType.exists && selfType.captureSet.isAlwaysEmpty
196+
})
197+
188198
extension (sym: Symbol)
189199

190200
/** A class is pure if:
191201
* - one its base types has an explicitly declared self type with an empty capture set
192202
* - or it is a value class
193-
* - or it is Nothing or Null
203+
* - or it is an exception
204+
* - or it is one of Nothing, Null, or String
194205
*/
195206
def isPureClass(using Context): Boolean = sym match
196207
case cls: ClassSymbol =>
197-
val AnyValClass = defn.AnyValClass
198-
cls.baseClasses.exists(bc =>
199-
bc == AnyValClass
200-
|| {
201-
val selfType = bc.givenSelfType
202-
selfType.exists && selfType.captureSet.isAlwaysEmpty
203-
})
204-
|| cls == defn.NothingClass
205-
|| cls == defn.NullClass
208+
cls.pureBaseClass.isDefined || defn.pureSimpleClasses.contains(cls)
206209
case _ =>
207210
false
208211

compiler/src/dotty/tools/dotc/cc/CaptureSet.scala

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -326,11 +326,6 @@ object CaptureSet:
326326
/** Used as a recursion brake */
327327
@sharable private[dotc] val Pending = Const(SimpleIdentitySet.empty)
328328

329-
/** The empty capture set with a description that says it's the elf type of an
330-
* exception class.
331-
*/
332-
val emptyOfException: CaptureSet.Const = Const(emptySet, "of an exception class")
333-
334329
def apply(elems: CaptureRef*)(using Context): CaptureSet.Const =
335330
if elems.isEmpty then empty
336331
else Const(SimpleIdentitySet(elems.map(_.normalizedRef)*))

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -516,8 +516,10 @@ class CheckCaptures extends Recheck, SymTransformer:
516516
for param <- cls.paramGetters do
517517
if !param.hasAnnotation(defn.ConstructorOnlyAnnot) then
518518
checkSubset(param.termRef.captureSet, thisSet, param.srcPos) // (3)
519-
if cls.derivesFrom(defn.ThrowableClass) then
520-
checkSubset(thisSet, CaptureSet.emptyOfException, tree.srcPos)
519+
for pureBase <- cls.pureBaseClass do
520+
checkSubset(thisSet,
521+
CaptureSet.empty.withDescription(i"of pure base class $pureBase"),
522+
tree.srcPos)
521523
super.recheckClassDef(tree, impl, cls)
522524
finally
523525
curEnv = saved

compiler/src/dotty/tools/dotc/core/Definitions.scala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1349,6 +1349,15 @@ class Definitions {
13491349

13501350
@tu lazy val untestableClasses: Set[Symbol] = Set(NothingClass, NullClass, SingletonClass)
13511351

1352+
/** Base classes that are assumed to be pure for the purposes of capture checking.
1353+
* Every class inheriting from a pure baseclass is pure.
1354+
*/
1355+
@tu lazy val pureBaseClasses = Set(defn.AnyValClass, defn.ThrowableClass)
1356+
1357+
/** Non-inheritable lasses that are assumed to be pure for the purposes of capture checking,
1358+
*/
1359+
@tu lazy val pureSimpleClasses = Set(StringClass, NothingClass, NullClass)
1360+
13521361
@tu lazy val AbstractFunctionType: Array[TypeRef] = mkArityArray("scala.runtime.AbstractFunction", MaxImplementedFunctionArity, 0).asInstanceOf[Array[TypeRef]]
13531362
val AbstractFunctionClassPerRun: PerRun[Array[Symbol]] = new PerRun(AbstractFunctionType.map(_.symbol.asClass))
13541363
def AbstractFunctionClass(n: Int)(using Context): Symbol = AbstractFunctionClassPerRun()(using ctx)(n)
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import annotation.retains
22
class C
33
type Cap = C @retains(caps.*)
4+
class Str
45

56
def f(y: Cap, z: Cap) =
67
def g(): C @retains(y, z) = ???
7-
val ac: ((x: Cap) => String @retains(x) => String @retains(x)) = ???
8-
val dc: (({y, z} String) => {y, z} String) = ac(g()) // error
8+
val ac: ((x: Cap) => Str @retains(x) => Str @retains(x)) = ???
9+
val dc: (({y, z} Str) => {y, z} Str) = ac(g()) // error
Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import annotation.retains
22
class C
33
type Cap = C @retains(caps.*)
4+
class Str
45

56
def f(y: Cap, z: Cap) =
67
def g(): C @retains(y, z) = ???
7-
val ac: ((x: Cap) => Array[String @retains(x)]) = ???
8-
val dc = ac(g()) // error: Needs explicit type Array[? >: String <: {y, z} String]
8+
val ac: ((x: Cap) => Array[Str @retains(x)]) = ???
9+
val dc = ac(g()) // error: Needs explicit type Array[? >: Str <: {y, z} Str]
910
// This is a shortcoming of rechecking since the originally inferred
10-
// type is `Array[String]` and the actual type after rechecking
11-
// cannot be expressed as `Array[C String]` for any capture set C
11+
// type is `Array[Str]` and the actual type after rechecking
12+
// cannot be expressed as `Array[C Str]` for any capture set C

tests/neg-custom-args/captures/cc-this.check

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,7 @@
99
10 | class C2(val x: () => Int): // error
1010
| ^
1111
| reference (C2.this.x : () => Int) is not included in allowed capture set {} of the self type of class C2
12-
-- [E058] Type Mismatch Error: tests/neg-custom-args/captures/cc-this.scala:17:8 ---------------------------------------
12+
-- Error: tests/neg-custom-args/captures/cc-this.scala:17:8 ------------------------------------------------------------
1313
17 | class C4(val f: () => Int) extends C3 // error
14-
| ^
15-
| illegal inheritance: self type {C4.this.f} C4 of class C4 does not conform to self type C3
16-
| of parent class C3
17-
|
18-
| longer explanation available when compiling with `-explain`
14+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
15+
| reference (C4.this.f : () => Int) is not included in allowed capture set {} of pure base class class C3
Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11

2-
-- [E058] Type Mismatch Error: tests/neg-custom-args/captures/cc-this2/D_2.scala:2:6 -----------------------------------
2+
-- Error: tests/neg-custom-args/captures/cc-this2/D_2.scala:2:6 --------------------------------------------------------
33
2 |class D extends C: // error
4-
| ^
5-
| illegal inheritance: self type {*} D of class D does not conform to self type C
6-
| of parent class C
7-
|
8-
| longer explanation available when compiling with `-explain`
4+
|^
5+
|reference (scala.caps.* : Any) is not included in allowed capture set {} of pure base class class C
6+
3 | this: {*} D =>
Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
-- Error: tests/neg-custom-args/captures/exception-definitions.scala:2:6 -----------------------------------------------
22
2 |class Err extends Exception: // error
33
|^
4-
|reference (scala.caps.* : Any) is not included in allowed capture set {} of an exception class
4+
|reference (scala.caps.* : Any) is not included in allowed capture set {} of pure base class class Throwable
55
3 | self: {*} Err =>
6+
-- Error: tests/neg-custom-args/captures/exception-definitions.scala:10:6 ----------------------------------------------
7+
10 |class Err4(c: {*} Any) extends AnyVal // error
8+
|^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
9+
|reference (Err4.this.c : {*} Any) is not included in allowed capture set {} of pure base class class AnyVal
610
-- Error: tests/neg-custom-args/captures/exception-definitions.scala:7:12 ----------------------------------------------
711
7 | val x = c // error
812
| ^
9-
| (c : {*} Any) cannot be referenced here; it is not included in the allowed capture set {} of an exception class
13+
|(c : {*} Any) cannot be referenced here; it is not included in the allowed capture set {} of pure base class class Throwable
1014
-- Error: tests/neg-custom-args/captures/exception-definitions.scala:8:8 -----------------------------------------------
1115
8 | class Err3(c: {*} Any) extends Exception // error
1216
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
13-
| reference (Err3.this.c : {*} Any) is not included in allowed capture set {} of an exception class
17+
| reference (Err3.this.c : {*} Any) is not included in allowed capture set {} of pure base class class Throwable

tests/neg-custom-args/captures/exception-definitions.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,6 @@ def test(c: {*} Any) =
77
val x = c // error
88
class Err3(c: {*} Any) extends Exception // error
99

10+
class Err4(c: {*} Any) extends AnyVal // error
11+
1012

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
5 | val x = Foo(m) // error
1010
| ^^^^^^^^^^^^^^
1111
| Non-local value x cannot have an inferred type
12-
| {Baz.this} Foo{m: {Baz.this} String}
12+
| {Baz.this} Foo{m: {*} String}
1313
| with non-empty capture set {Baz.this}.
1414
| The type needs to be declared explicitly.
1515
-- Error: tests/neg-custom-args/captures/i15116.scala:7:6 --------------------------------------------------------------
@@ -23,6 +23,6 @@
2323
9 | val x = Foo(m) // error
2424
| ^^^^^^^^^^^^^^
2525
| Non-local value x cannot have an inferred type
26-
| {Baz2.this} Foo{m: {Baz2.this} String}
26+
| {Baz2.this} Foo{m: {*} String}
2727
| with non-empty capture set {Baz2.this}.
2828
| The type needs to be declared explicitly.

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/try.scala:23:49 ------------------------------------------
22
23 | val a = handle[Exception, CanThrow[Exception]] { // error
33
| ^
4-
| Found: ? ({*} CT[Exception]) -> CanThrow[? Exception]
4+
| Found: ? ({*} CT[Exception]) -> CanThrow[Exception]
55
| Required: CanThrow[Exception] => box {*} CT[Exception]
66
24 | (x: CanThrow[Exception]) => x
77
25 | }{

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
15 | val u = a // error
1010
| ^
1111
| Found: (a : box {*} String -> String)
12-
| Required: {*} (x$0: ? String) -> ? String
12+
| Required: {*} (x$0: String) -> String
1313
|
1414
| longer explanation available when compiling with `-explain`
1515
-- Error: tests/neg-custom-args/captures/vars.scala:16:2 ---------------------------------------------------------------
@@ -25,7 +25,7 @@
2525
-- Error: tests/neg-custom-args/captures/vars.scala:32:8 ---------------------------------------------------------------
2626
32 | local { cap3 => // error
2727
| ^
28-
| The expression's type box {*} (x$0: ? String) -> ? String is not allowed to capture the root capability `*`.
28+
| The expression's type box {*} (x$0: String) -> String is not allowed to capture the root capability `*`.
2929
| This usually means that a capability persists longer than its allowed lifetime.
3030
33 | def g(x: String): String = if cap3 == cap3 then "" else "a"
3131
34 | g

0 commit comments

Comments
 (0)