Skip to content

Commit b21867f

Browse files
committed
Enhancement: More lenient check for inferred self types
An inferred self type in a non-sealed class is now OK if it is the same as an explicit self type in a base class.
1 parent 4b98155 commit b21867f

File tree

3 files changed

+44
-15
lines changed

3 files changed

+44
-15
lines changed

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

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import config.Printers.{capt, recheckr}
1010
import config.{Config, Feature}
1111
import ast.{tpd, untpd, Trees}
1212
import Trees.*
13-
import typer.RefChecks.{checkAllOverrides, checkParents}
13+
import typer.RefChecks.{checkAllOverrides, checkSelfAgainstParents}
1414
import util.{SimpleIdentitySet, EqHashMap, SrcPos}
1515
import transform.SymUtils.*
1616
import transform.{Recheck, PreRecheck}
@@ -833,13 +833,21 @@ class CheckCaptures extends Recheck, SymTransformer:
833833
cls => !parentTrees(cls).exists(ptree => parentTrees.contains(ptree.tpe.classSymbol))
834834
}
835835
assert(roots.nonEmpty)
836-
for root <- roots do
837-
checkParents(root, parentTrees(root))
836+
for case root: ClassSymbol <- roots do
837+
checkSelfAgainstParents(root, root.baseClasses)
838838
val selfType = root.asClass.classInfo.selfType
839839
interpolator(startingVariance = -1).traverse(selfType)
840840
if !root.isEffectivelySealed then
841+
def matchesExplicitRefsInBaseClass(refs: CaptureSet, cls: ClassSymbol): Boolean =
842+
cls.baseClasses.tail.exists { psym =>
843+
val selfType = psym.asClass.givenSelfType
844+
selfType.exists && selfType.captureSet.elems == refs.elems
845+
}
841846
selfType match
842-
case CapturingType(_, refs: CaptureSet.Var) if !refs.isUniversal =>
847+
case CapturingType(_, refs: CaptureSet.Var)
848+
if !refs.isUniversal && !matchesExplicitRefsInBaseClass(refs, root) =>
849+
// Forbid inferred self types unless they are already implied by an explicit
850+
// self type in a parent.
843851
report.error(
844852
i"""$root needs an explicitly declared self type since its
845853
|inferred self type $selfType

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

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -91,24 +91,40 @@ object RefChecks {
9191
cls.thisType
9292
}
9393

94+
/** - Check that self type of `cls` conforms to self types of all `parents` as seen from
95+
* `cls.thisType`
96+
* - If self type of `cls` is explicit, check that it conforms to the self types
97+
* of all its class symbols.
98+
* @param deep If true and a self type of a parent is not given explicitly, recurse to
99+
* check against the parents of the parent. This is needed when capture checking,
100+
* since we assume (& check) that the capture set of an inferred self type
101+
* is the intersection of the capture sets of all its parents
102+
*/
103+
def checkSelfAgainstParents(cls: ClassSymbol, parents: List[Symbol])(using Context): Unit =
104+
val cinfo = cls.classInfo
105+
106+
def checkSelfConforms(other: ClassSymbol, category: String, relation: String) =
107+
val otherSelf = other.declaredSelfTypeAsSeenFrom(cls.thisType)
108+
if otherSelf.exists then
109+
if !(cinfo.selfType <:< otherSelf) then
110+
report.error(DoesNotConformToSelfType(category, cinfo.selfType, cls, otherSelf, relation, other),
111+
cls.srcPos)
112+
113+
for psym <- parents do
114+
checkSelfConforms(psym.asClass, "illegal inheritance", "parent")
115+
for reqd <- cls.asClass.givenSelfType.classSymbols do
116+
if reqd != cls then
117+
checkSelfConforms(reqd, "missing requirement", "required")
118+
end checkSelfAgainstParents
119+
94120
/** Check that self type of this class conforms to self types of parents
95121
* and required classes. Also check that only `enum` constructs extend
96122
* `java.lang.Enum` and no user-written class extends ContextFunctionN.
97123
*/
98124
def checkParents(cls: Symbol, parentTrees: List[Tree])(using Context): Unit = cls.info match {
99125
case cinfo: ClassInfo =>
100-
def checkSelfConforms(other: ClassSymbol, category: String, relation: String) = {
101-
val otherSelf = other.declaredSelfTypeAsSeenFrom(cls.thisType)
102-
if otherSelf.exists && !(cinfo.selfType <:< otherSelf) then
103-
report.error(DoesNotConformToSelfType(category, cinfo.selfType, cls, otherSelf, relation, other),
104-
cls.srcPos)
105-
}
106126
val psyms = cls.asClass.parentSyms
107-
for (psym <- psyms)
108-
checkSelfConforms(psym.asClass, "illegal inheritance", "parent")
109-
for reqd <- cinfo.cls.givenSelfType.classSymbols do
110-
if reqd != cls then
111-
checkSelfConforms(reqd, "missing requirement", "required")
127+
checkSelfAgainstParents(cls.asClass, psyms)
112128

113129
def isClassExtendingJavaEnum =
114130
!cls.isOneOf(Enum | Trait) && psyms.contains(defn.JavaEnumClass)

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import annotation.constructorOnly
12
trait A:
23
self: A =>
34
def foo: Int
@@ -8,3 +9,7 @@
89
class C extends B:
910
def foo = 1
1011
def derived = this
12+
13+
class D(@constructorOnly op: Int => Int) extends C:
14+
val x = 1//op(1)
15+

0 commit comments

Comments
 (0)