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

0 commit comments

Comments
 (0)