Skip to content

Commit 3c1d0de

Browse files
committed
Infer more self types automatically
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 types 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.
1 parent 8039426 commit 3c1d0de

File tree

13 files changed

+77
-58
lines changed

13 files changed

+77
-58
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:
@@ -1178,7 +1174,7 @@ class CheckCaptures extends Recheck, SymTransformer:
11781174
/** Check that self types of subclasses conform to self types of super classes.
11791175
* (See comment below how this is achieved). The check assumes that classes
11801176
* without an explicit self type have the universal capture set `{cap}` on the
1181-
* self type. If a class without explicit self type is not `effectivelyFinal`
1177+
* self type. If a class without explicit self type is not `effectivelySealed`
11821178
* it is checked that the inferred self type is universal, in order to assure
11831179
* that joint and separate compilation give the same result.
11841180
*/
@@ -1208,23 +1204,20 @@ class CheckCaptures extends Recheck, SymTransformer:
12081204
checkSelfAgainstParents(root, root.baseClasses)
12091205
val selfType = root.asClass.classInfo.selfType
12101206
interpolator(startingVariance = -1).traverse(selfType)
1211-
if !root.isEffectivelySealed then
1212-
def matchesExplicitRefsInBaseClass(refs: CaptureSet, cls: ClassSymbol): Boolean =
1213-
cls.baseClasses.tail.exists { psym =>
1214-
val selfType = psym.asClass.givenSelfType
1215-
selfType.exists && selfType.captureSet.elems == refs.elems
1216-
}
1217-
selfType match
1218-
case CapturingType(_, refs: CaptureSet.Var)
1219-
if !refs.elems.exists(_.isRootCapability) && !matchesExplicitRefsInBaseClass(refs, root) =>
1220-
// Forbid inferred self types unless they are already implied by an explicit
1221-
// self type in a parent.
1222-
report.error(
1223-
em"""$root needs an explicitly declared self type since its
1224-
|inferred self type $selfType
1225-
|is not visible in other compilation units that define subclasses.""",
1226-
root.srcPos)
1227-
case _ =>
1207+
selfType match
1208+
case CapturingType(_, refs: CaptureSet.Var)
1209+
if !root.isEffectivelySealed
1210+
&& !refs.elems.exists(_.isRootCapability)
1211+
&& !root.matchesExplicitRefsInBaseClass(refs)
1212+
=>
1213+
// Forbid inferred self types unless they are already implied by an explicit
1214+
// self type in a parent.
1215+
report.error(
1216+
em"""$root needs an explicitly declared self type since its
1217+
|inferred self type $selfType
1218+
|is not visible in other compilation units that define subclasses.""",
1219+
root.srcPos)
1220+
case _ =>
12281221
parentTrees -= root
12291222
capt.println(i"checked $root with $selfType")
12301223
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
@@ -1197,7 +1197,14 @@ object SymDenotations {
11971197
* is defined in Scala 3 and is neither abstract nor open.
11981198
*/
11991199
final def isEffectivelySealed(using Context): Boolean =
1200-
isOneOf(FinalOrSealed) || isClass && !isOneOf(EffectivelyOpenFlags)
1200+
isOneOf(FinalOrSealed)
1201+
|| isClass && (!isOneOf(EffectivelyOpenFlags)
1202+
|| isLocalToCompilationUnit)
1203+
1204+
final def isLocalToCompilationUnit(using Context): Boolean =
1205+
is(Private)
1206+
|| owner.ownersIterator.exists(_.isTerm)
1207+
|| accessBoundary(defn.RootClass).isContainedIn(symbol.topLevelClass)
12011208

12021209
final def isTransparentClass(using Context): Boolean =
12031210
is(TransparentType)
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 =>

tests/pos-custom-args/captures/colltest.scala

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,18 @@ object CollectionStrawMan5 {
33

44
/** Base trait for generic collections */
55
trait Iterable[+A] extends IterableLike[A] {
6-
this: Iterable[A]^ =>
76
def iterator: Iterator[A]^{this}
87
def coll: Iterable[A]^{this} = this
98
}
109

1110
trait IterableLike[+A]:
12-
this: IterableLike[A]^ =>
1311
def coll: Iterable[A]^{this}
1412
def partition(p: A => Boolean): Unit =
1513
val pn = Partition(coll, p)
1614
()
1715

1816
/** Concrete collection type: View */
19-
trait View[+A] extends Iterable[A] with IterableLike[A] {
20-
this: View[A]^ =>
21-
}
17+
trait View[+A] extends Iterable[A] with IterableLike[A]
2218

2319
case class Partition[A](val underlying: Iterable[A]^, p: A => Boolean) {
2420
self: Partition[A]^{underlying, p} =>

tests/pos-custom-args/captures/future-traverse.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ trait BuildFrom[-From, -A, +C] {
55
def newBuilder(from: From): Builder[A, C]
66
}
77

8-
trait Future[+T] { this: Future[T]^ =>
8+
trait Future[+T] {
99
import Future.*
1010
def foldLeft[R](r: R): R = r
1111
def traverse[A, B, M[X] <: IterableOnce[X]](in: M[A]^, bf: BuildFrom[M[A]^, B, M[B]^]): Unit =

tests/pos-custom-args/captures/hk-param.scala

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,5 @@ trait Itable[+A] extends ItableOnce[A] with ILike[A, Itable^]
1212

1313
/** Iterator can be used only once */
1414
trait ItableOnce[+A] {
15-
this: ItableOnce[A]^ =>
1615
def iterator: Iterator[A]^{this}
1716
}

tests/pos-custom-args/captures/iterators.scala

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package cctest
22

33
trait IterableOnce[A]:
4-
this: IterableOnce[A]^ =>
54
def iterator: Iterator[A]^{this}
65

76
abstract class Iterator[T]:

tests/pos-custom-args/captures/lazylists.scala

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ class CC
22
type Cap = CC^
33

44
trait LazyList[+A]:
5-
this: LazyList[A]^ =>
6-
75
def isEmpty: Boolean
86
def head: A
97
def tail: LazyList[A]^{this}

tests/pos-custom-args/captures/selftypes.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,11 @@ class IM:
2020
def coll: IM^{this} = ???
2121
foo(coll)
2222

23+
// Demonstrates root mapping for self types, implicitly
24+
class IM2:
25+
def coll: IM2^{this} = ???
26+
foo2(coll)
27+
2328
def foo(im: IM^): Unit = ???
2429

30+
def foo2(im: IM2^): Unit = ???

tests/pos-custom-args/captures/steppers.scala

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11

2-
trait Stepper[+A]:
3-
this: Stepper[A]^ =>
2+
trait Stepper[+A]
43

54
object Stepper:
65
trait EfficientSplit
76

87
sealed trait StepperShape[-T, S <: Stepper[_]^] extends Pure
98

109
trait IterableOnce[+A] extends Any:
11-
this: IterableOnce[A]^ =>
1210
def stepper[S <: Stepper[_]^{this}](implicit shape: StepperShape[A, S]): S = ???
1311

1412
sealed abstract class ArraySeq[T] extends IterableOnce[T], Pure:

0 commit comments

Comments
 (0)