Skip to content

Commit 9b5815a

Browse files
authored
CC: Infer more self types automatically (#19425)
Fixes #19398 Clean up the logic how we infer self types, and add a new clause: > If we have an externally extensible class that itself does not have a declared self type itself and also not in any of its base classes, assume {cap} as the self type. Previously we would install a capture set but then check after the fact that that capture set is indeed {cap}. So it's less verbose to just assume that from the start.
2 parents b5ecaa0 + 8e0cac4 commit 9b5815a

26 files changed

+80
-88
lines changed

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

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -236,19 +236,19 @@ extension (tp: Type)
236236
* (2) all covariant occurrences of cap replaced by `x*`, provided there
237237
* are no occurrences in `T` at other variances. (1) is standard, whereas
238238
* (2) is new.
239-
*
239+
*
240240
* For (2), multiple-flipped covariant occurrences of cap won't be replaced.
241241
* In other words,
242242
*
243243
* - For xs: List[File^] ==> List[File^{xs*}], the cap is replaced;
244244
* - while f: [R] -> (op: File^ => R) -> R remains unchanged.
245-
*
245+
*
246246
* Without this restriction, the signature of functions like withFile:
247-
*
247+
*
248248
* (path: String) -> [R] -> (op: File^ => R) -> R
249249
*
250250
* could be refined to
251-
*
251+
*
252252
* (path: String) -> [R] -> (op: File^{withFile*} => R) -> R
253253
*
254254
* which is clearly unsound.
@@ -315,6 +315,15 @@ extension (cls: ClassSymbol)
315315
// and err on the side of impure.
316316
&& selfType.exists && selfType.captureSet.isAlwaysEmpty
317317

318+
def baseClassHasExplicitSelfType(using Context): Boolean =
319+
cls.baseClasses.exists: bc =>
320+
bc.is(CaptureChecked) && bc.givenSelfType.exists
321+
322+
def matchesExplicitRefsInBaseClass(refs: CaptureSet)(using Context): Boolean =
323+
cls.baseClasses.tail.exists: bc =>
324+
val selfType = bc.givenSelfType
325+
bc.is(CaptureChecked) && selfType.exists && selfType.captureSet.elems == refs.elems
326+
318327
extension (sym: Symbol)
319328

320329
/** A class is pure if:

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

Lines changed: 16 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -672,17 +672,13 @@ class CheckCaptures extends Recheck, SymTransformer:
672672
def checkInferredResult(tp: Type, tree: ValOrDefDef)(using Context): Type =
673673
val sym = tree.symbol
674674

675-
def isLocal =
676-
sym.owner.ownersIterator.exists(_.isTerm)
677-
|| sym.accessBoundary(defn.RootClass).isContainedIn(sym.topLevelClass)
678-
679675
def canUseInferred = // If canUseInferred is false, all capturing types in the type of `sym` need to be given explicitly
680676
sym.is(Private) // private symbols can always have inferred types
681677
|| sym.name.is(DefaultGetterName) // default getters are exempted since otherwise it would be
682678
// too annoying. This is a hole since a defualt getter's result type
683679
// might leak into a type variable.
684680
|| // non-local symbols cannot have inferred types since external capture types are not inferred
685-
isLocal // local symbols still need explicit types if
681+
sym.isLocalToCompilationUnit // local symbols still need explicit types if
686682
&& !sym.owner.is(Trait) // they are defined in a trait, since we do OverridingPairs checking before capture inference
687683

688684
def addenda(expected: Type) = new Addenda:
@@ -1182,7 +1178,7 @@ class CheckCaptures extends Recheck, SymTransformer:
11821178
/** Check that self types of subclasses conform to self types of super classes.
11831179
* (See comment below how this is achieved). The check assumes that classes
11841180
* without an explicit self type have the universal capture set `{cap}` on the
1185-
* self type. If a class without explicit self type is not `effectivelyFinal`
1181+
* self type. If a class without explicit self type is not `effectivelySealed`
11861182
* it is checked that the inferred self type is universal, in order to assure
11871183
* that joint and separate compilation give the same result.
11881184
*/
@@ -1212,23 +1208,20 @@ class CheckCaptures extends Recheck, SymTransformer:
12121208
checkSelfAgainstParents(root, root.baseClasses)
12131209
val selfType = root.asClass.classInfo.selfType
12141210
interpolator(startingVariance = -1).traverse(selfType)
1215-
if !root.isEffectivelySealed then
1216-
def matchesExplicitRefsInBaseClass(refs: CaptureSet, cls: ClassSymbol): Boolean =
1217-
cls.baseClasses.tail.exists { psym =>
1218-
val selfType = psym.asClass.givenSelfType
1219-
selfType.exists && selfType.captureSet.elems == refs.elems
1220-
}
1221-
selfType match
1222-
case CapturingType(_, refs: CaptureSet.Var)
1223-
if !refs.elems.exists(_.isRootCapability) && !matchesExplicitRefsInBaseClass(refs, root) =>
1224-
// Forbid inferred self types unless they are already implied by an explicit
1225-
// self type in a parent.
1226-
report.error(
1227-
em"""$root needs an explicitly declared self type since its
1228-
|inferred self type $selfType
1229-
|is not visible in other compilation units that define subclasses.""",
1230-
root.srcPos)
1231-
case _ =>
1211+
selfType match
1212+
case CapturingType(_, refs: CaptureSet.Var)
1213+
if !root.isEffectivelySealed
1214+
&& !refs.elems.exists(_.isRootCapability)
1215+
&& !root.matchesExplicitRefsInBaseClass(refs)
1216+
=>
1217+
// Forbid inferred self types unless they are already implied by an explicit
1218+
// self type in a parent.
1219+
report.error(
1220+
em"""$root needs an explicitly declared self type since its
1221+
|inferred self type $selfType
1222+
|is not visible in other compilation units that define subclasses.""",
1223+
root.srcPos)
1224+
case _ =>
12321225
parentTrees -= root
12331226
capt.println(i"checked $root with $selfType")
12341227
end checkSelfTypes

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

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -517,21 +517,34 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
517517
tree.symbol match
518518
case cls: ClassSymbol =>
519519
val cinfo @ ClassInfo(prefix, _, ps, decls, selfInfo) = cls.classInfo
520-
if ((selfInfo eq NoType) || cls.is(ModuleClass) && !cls.isStatic)
521-
&& !cls.isPureClass
522-
then
523-
// add capture set to self type of nested classes if no self type is given explicitly.
524-
val newSelfType = CapturingType(cinfo.selfType, CaptureSet.Var(cls))
525-
val ps1 = inContext(ctx.withOwner(cls)):
526-
ps.mapConserve(transformExplicitType(_))
527-
val newInfo = ClassInfo(prefix, cls, ps1, decls, newSelfType)
520+
def innerModule = cls.is(ModuleClass) && !cls.isStatic
521+
val selfInfo1 =
522+
if (selfInfo ne NoType) && !innerModule then
523+
// if selfInfo is explicitly given then use that one, except if
524+
// self info applies to non-static modules, these still need to be inferred
525+
selfInfo
526+
else if cls.isPureClass then
527+
// is cls is known to be pure, nothing needs to be added to self type
528+
selfInfo
529+
else if !cls.isEffectivelySealed && !cls.baseClassHasExplicitSelfType then
530+
// assume {cap} for completely unconstrained self types of publicly extensible classes
531+
CapturingType(cinfo.selfType, CaptureSet.universal)
532+
else
533+
// Infer the self type for the rest, which is all classes without explicit
534+
// self types (to which we also add nested module classes), provided they are
535+
// neither pure, nor are publicily extensible with an unconstrained self type.
536+
CapturingType(cinfo.selfType, CaptureSet.Var(cls))
537+
val ps1 = inContext(ctx.withOwner(cls)):
538+
ps.mapConserve(transformExplicitType(_))
539+
if (selfInfo1 ne selfInfo) || (ps1 ne ps) then
540+
val newInfo = ClassInfo(prefix, cls, ps1, decls, selfInfo1)
528541
updateInfo(cls, newInfo)
529542
capt.println(i"update class info of $cls with parents $ps selfinfo $selfInfo to $newInfo")
530543
cls.thisType.asInstanceOf[ThisType].invalidateCaches()
531544
if cls.is(ModuleClass) then
532545
// if it's a module, the capture set of the module reference is the capture set of the self type
533546
val modul = cls.sourceModule
534-
updateInfo(modul, CapturingType(modul.info, newSelfType.captureSet))
547+
updateInfo(modul, CapturingType(modul.info, selfInfo1.asInstanceOf[Type].captureSet))
535548
modul.termRef.invalidateCaches()
536549
case _ =>
537550
case _ =>

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1202,7 +1202,14 @@ object SymDenotations {
12021202
* is defined in Scala 3 and is neither abstract nor open.
12031203
*/
12041204
final def isEffectivelySealed(using Context): Boolean =
1205-
isOneOf(FinalOrSealed) || isClass && !isOneOf(EffectivelyOpenFlags)
1205+
isOneOf(FinalOrSealed)
1206+
|| isClass && (!isOneOf(EffectivelyOpenFlags)
1207+
|| isLocalToCompilationUnit)
1208+
1209+
final def isLocalToCompilationUnit(using Context): Boolean =
1210+
is(Private)
1211+
|| owner.ownersIterator.exists(_.isTerm)
1212+
|| accessBoundary(defn.RootClass).isContainedIn(symbol.topLevelClass)
12061213

12071214
final def isTransparentClass(using Context): Boolean =
12081215
is(TransparentType)

scala2-library-cc/src/scala/collection/IndexedSeqView.scala

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@ object IndexedSeqView {
5555

5656
@SerialVersionUID(3L)
5757
private[collection] class IndexedSeqViewIterator[A](self: IndexedSeqView[A]^) extends AbstractIterator[A] with Serializable {
58-
this: IndexedSeqViewIterator[A]^ =>
5958
private[this] var current = 0
6059
private[this] var remainder = self.length
6160
override def knownSize: Int = remainder
@@ -90,7 +89,6 @@ object IndexedSeqView {
9089
}
9190
@SerialVersionUID(3L)
9291
private[collection] class IndexedSeqViewReverseIterator[A](self: IndexedSeqView[A]^) extends AbstractIterator[A] with Serializable {
93-
this: IndexedSeqViewReverseIterator[A]^ =>
9492
private[this] var remainder = self.length
9593
private[this] var pos = remainder - 1
9694
@inline private[this] def _hasNext: Boolean = remainder > 0

scala2-library-cc/src/scala/collection/Iterable.scala

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
* See the NOTICE file distributed with this work for
1010
* additional information regarding copyright ownership.
1111
*/
12-
1312
package scala
1413
package collection
1514

@@ -29,7 +28,6 @@ import language.experimental.captureChecking
2928
trait Iterable[+A] extends IterableOnce[A]
3029
with IterableOps[A, Iterable, Iterable[A]]
3130
with IterableFactoryDefaults[A, Iterable] {
32-
this: Iterable[A]^ =>
3331

3432
// The collection itself
3533
@deprecated("toIterable is internal and will be made protected; its name is similar to `toList` or `toSeq`, but it doesn't copy non-immutable collections", "2.13.7")
@@ -134,7 +132,6 @@ trait Iterable[+A] extends IterableOnce[A]
134132
* and may be nondeterministic.
135133
*/
136134
trait IterableOps[+A, +CC[_], +C] extends Any with IterableOnce[A] with IterableOnceOps[A, CC, C] {
137-
this: IterableOps[A, CC, C]^ =>
138135

139136
/**
140137
* @return This collection as an `Iterable[A]`. No new collection will be built if `this` is already an `Iterable[A]`.

scala2-library-cc/src/scala/collection/IterableOnce.scala

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ import language.experimental.captureChecking
4343
* @define coll collection
4444
*/
4545
trait IterableOnce[+A] extends Any {
46-
this: IterableOnce[A]^ =>
4746

4847
/** Iterator can be used only once */
4948
def iterator: Iterator[A]^{this}

scala2-library-cc/src/scala/collection/Iterator.scala

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1302,5 +1302,4 @@ object Iterator extends IterableFactory[Iterator] {
13021302
}
13031303

13041304
/** Explicit instantiation of the `Iterator` trait to reduce class file size in subclasses. */
1305-
abstract class AbstractIterator[+A] extends Iterator[A]:
1306-
this: Iterator[A]^ =>
1305+
abstract class AbstractIterator[+A] extends Iterator[A]

scala2-library-cc/src/scala/collection/Map.scala

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,6 @@ trait Map[K, +V]
104104
trait MapOps[K, +V, +CC[_, _] <: IterableOps[_, AnyConstr, _], +C]
105105
extends IterableOps[(K, V), Iterable, C]
106106
with PartialFunction[K, V] {
107-
this: MapOps[K, V, CC, C]^ =>
108107

109108
override def view: MapView[K, V]^{this} = new MapView.Id(this)
110109

scala2-library-cc/src/scala/collection/MapView.scala

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import caps.unsafe.unsafeAssumePure
2121
trait MapView[K, +V]
2222
extends MapOps[K, V, ({ type l[X, Y] = View[(X, Y)] })#l, View[(K, V)]]
2323
with View[(K, V)] {
24-
this: MapView[K, V]^ =>
2524

2625
override def view: MapView[K, V]^{this} = this
2726

@@ -191,6 +190,5 @@ trait MapViewFactory extends collection.MapFactory[({ type l[X, Y] = View[(X, Y)
191190

192191
/** Explicit instantiation of the `MapView` trait to reduce class file size in subclasses. */
193192
@SerialVersionUID(3L)
194-
abstract class AbstractMapView[K, +V] extends AbstractView[(K, V)] with MapView[K, V]:
195-
this: AbstractMapView[K, V]^ =>
193+
abstract class AbstractMapView[K, +V] extends AbstractView[(K, V)] with MapView[K, V]
196194

scala2-library-cc/src/scala/collection/Stepper.scala

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ import scala.collection.Stepper.EfficientSplit
3939
* @tparam A the element type of the Stepper
4040
*/
4141
trait Stepper[@specialized(Double, Int, Long) +A] {
42-
this: Stepper[A]^ =>
4342

4443
/** Check if there's an element available. */
4544
def hasStep: Boolean
@@ -186,7 +185,6 @@ object Stepper {
186185

187186
/** A Stepper for arbitrary element types. See [[Stepper]]. */
188187
trait AnyStepper[+A] extends Stepper[A] {
189-
this: AnyStepper[A]^ =>
190188

191189
def trySplit(): AnyStepper[A]
192190

@@ -258,7 +256,6 @@ object AnyStepper {
258256

259257
/** A Stepper for Ints. See [[Stepper]]. */
260258
trait IntStepper extends Stepper[Int] {
261-
this: IntStepper^ =>
262259

263260
def trySplit(): IntStepper
264261

@@ -298,7 +295,6 @@ object IntStepper {
298295

299296
/** A Stepper for Doubles. See [[Stepper]]. */
300297
trait DoubleStepper extends Stepper[Double] {
301-
this: DoubleStepper^ =>
302298
def trySplit(): DoubleStepper
303299

304300
def spliterator[B >: Double]: Spliterator.OfDouble^{this} = new DoubleStepper.DoubleStepperSpliterator(this)
@@ -338,7 +334,6 @@ object DoubleStepper {
338334

339335
/** A Stepper for Longs. See [[Stepper]]. */
340336
trait LongStepper extends Stepper[Long] {
341-
this: LongStepper^ =>
342337

343338
def trySplit(): LongStepper^{this}
344339

scala2-library-cc/src/scala/collection/View.scala

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import language.experimental.captureChecking
2424
* @define Coll `View`
2525
*/
2626
trait View[+A] extends Iterable[A] with IterableOps[A, View, View[A]] with IterableFactoryDefaults[A, View] with Serializable {
27-
this: View[A]^ =>
2827

2928
override def view: View[A]^{this} = this
3029

scala2-library-cc/src/scala/collection/WithFilter.scala

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ import language.experimental.captureChecking
2323
*/
2424
@SerialVersionUID(3L)
2525
abstract class WithFilter[+A, +CC[_]] extends Serializable {
26-
this: WithFilter[A, CC]^ =>
2726

2827
/** Builds a new collection by applying a function to all elements of the
2928
* `filtered` outer $coll.

scala2-library-cc/src/scala/collection/immutable/Iterable.scala

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,6 @@ import language.experimental.captureChecking
2525
trait Iterable[+A] extends collection.Iterable[A]
2626
with collection.IterableOps[A, Iterable, Iterable[A]]
2727
with IterableFactoryDefaults[A, Iterable] {
28-
this: Iterable[A]^ =>
29-
3028
override def iterableFactory: IterableFactory[Iterable] = Iterable
3129
}
3230

scala2-library-cc/src/scala/collection/immutable/LazyListIterable.scala

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,6 @@ final class LazyListIterable[+A] private(private[this] var lazyState: () => Lazy
251251
with IterableOps[A, LazyListIterable, LazyListIterable[A]]
252252
with IterableFactoryDefaults[A, LazyListIterable]
253253
with Serializable {
254-
this: LazyListIterable[A]^ =>
255254
import LazyListIterable._
256255

257256
@volatile private[this] var stateEvaluated: Boolean = false
@@ -964,7 +963,6 @@ object LazyListIterable extends IterableFactory[LazyListIterable] {
964963
private[this] val _empty = newLL(State.Empty).force
965964

966965
private sealed trait State[+A] extends Serializable {
967-
this: State[A]^ =>
968966
def head: A
969967
def tail: LazyListIterable[A]^
970968
}
@@ -1252,7 +1250,6 @@ object LazyListIterable extends IterableFactory[LazyListIterable] {
12521250

12531251
private class SlidingIterator[A](private[this] var lazyList: LazyListIterable[A]^, size: Int, step: Int)
12541252
extends AbstractIterator[LazyListIterable[A]] {
1255-
this: SlidingIterator[A]^ =>
12561253
private val minLen = size - step max 0
12571254
private var first = true
12581255

@@ -1273,7 +1270,6 @@ object LazyListIterable extends IterableFactory[LazyListIterable] {
12731270

12741271
private final class WithFilter[A] private[LazyListIterable](lazyList: LazyListIterable[A]^, p: A => Boolean)
12751272
extends collection.WithFilter[A, LazyListIterable] {
1276-
this: WithFilter[A]^ =>
12771273
private[this] val filtered = lazyList.filter(p)
12781274
def map[B](f: A => B): LazyListIterable[B]^{this, f} = filtered.map(f)
12791275
def flatMap[B](f: A => IterableOnce[B]^): LazyListIterable[B]^{this, f} = filtered.flatMap(f)
@@ -1320,7 +1316,6 @@ object LazyListIterable extends IterableFactory[LazyListIterable] {
13201316

13211317
private object LazyBuilder {
13221318
final class DeferredState[A] {
1323-
this: DeferredState[A]^ =>
13241319
private[this] var _state: (() => State[A]^) @uncheckedCaptures = _
13251320

13261321
def eval(): State[A]^ = {

scala2-library-cc/src/scala/collection/mutable/CheckedIndexedSeqView.scala

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ package mutable
1616
import language.experimental.captureChecking
1717

1818
private[mutable] trait CheckedIndexedSeqView[+A] extends IndexedSeqView[A] {
19-
this: CheckedIndexedSeqView[A]^ =>
2019

2120
protected val mutationCount: () => Int
2221

scala2-library-cc/src/scala/collection/mutable/Iterable.scala

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ trait Iterable[A]
1919
extends collection.Iterable[A]
2020
with collection.IterableOps[A, Iterable, Iterable[A]]
2121
with IterableFactoryDefaults[A, Iterable] {
22-
this: Iterable[A]^ =>
2322

2423
override def iterableFactory: IterableFactory[Iterable] = Iterable
2524
}
@@ -33,5 +32,4 @@ trait Iterable[A]
3332
object Iterable extends IterableFactory.Delegate[Iterable](ArrayBuffer)
3433

3534
/** Explicit instantiation of the `Iterable` trait to reduce class file size in subclasses. */
36-
abstract class AbstractIterable[A] extends scala.collection.AbstractIterable[A] with Iterable[A]:
37-
this: AbstractIterable[A]^ =>
35+
abstract class AbstractIterable[A] extends scala.collection.AbstractIterable[A] with Iterable[A]
Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
-- Error: tests/neg-custom-args/captures/cc-this4.scala:1:11 -----------------------------------------------------------
2-
1 |open class C: // error
3-
| ^
4-
| class C needs an explicitly declared self type since its
5-
| inferred self type C^{}
6-
| is not visible in other compilation units that define subclasses.
1+
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/cc-this4.scala:2:13 --------------------------------------
2+
2 | val x: C = this // error
3+
| ^^^^
4+
| Found: (C.this : C^)
5+
| Required: C
6+
|
7+
| longer explanation available when compiling with `-explain`

tests/neg-custom-args/captures/cc-this4.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
open class C: // error
2-
val x: C = this
1+
open class C:
2+
val x: C = this // error
33

44
open class D:
55
this: D =>

0 commit comments

Comments
 (0)