From a94efa4fdc1a46ec5e7a0cebd42834ffbed84f86 Mon Sep 17 00:00:00 2001 From: odersky Date: Wed, 19 Mar 2025 15:14:13 +0100 Subject: [PATCH 1/3] Fix override checking of alias vs abstract types # Conflicts: # compiler/src/dotty/tools/dotc/cc/Setup.scala --- .../dotty/tools/dotc/cc/CheckCaptures.scala | 41 ++++++++++++++++-- compiler/src/dotty/tools/dotc/cc/Setup.scala | 31 ++++++++------ compiler/src/dotty/tools/dotc/cc/root.scala | 6 ++- .../dotty/tools/dotc/core/TypeComparer.scala | 6 ++- .../dotc/transform/OverridingPairs.scala | 19 +++++---- .../dotty/tools/dotc/typer/RefChecks.scala | 42 +++++++++++++++---- .../captures/check-override-typebounds.scala | 16 +++++++ 7 files changed, 125 insertions(+), 36 deletions(-) create mode 100644 tests/pos-custom-args/captures/check-override-typebounds.scala diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 8b6ce6550cc3..39017712884d 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -1341,7 +1341,7 @@ class CheckCaptures extends Recheck, SymTransformer: else if !owner.exists then false else isPure(owner.info) && isPureContext(owner.owner, limit) - // Augment expeced capture set `erefs` by all references in actual capture + // Augment expected capture set `erefs` by all references in actual capture // set `arefs` that are outside some `C.this.type` reference in `erefs` for an enclosing // class `C`. If an added reference is not a ThisType itself, add it to the capture set // (i.e. use set) of the `C`. This makes sure that any outer reference implicitly subsumed @@ -1577,7 +1577,7 @@ class CheckCaptures extends Recheck, SymTransformer: /** Check subtype with box adaptation. * This function is passed to RefChecks to check the compatibility of overriding pairs. * @param sym symbol of the field definition that is being checked - */ + override def checkSubType(actual: Type, expected: Type)(using Context): Boolean = val expected1 = alignDependentFunction(addOuterRefs(expected, actual, tree.srcPos), actual.stripCapturing) val actual1 = @@ -1596,11 +1596,44 @@ class CheckCaptures extends Recheck, SymTransformer: case _ => adapted finally curEnv = saved actual1 frozen_<:< expected1 + */ /** Omit the check if one of {overriding,overridden} was nnot capture checked */ override def needsCheck(overriding: Symbol, overridden: Symbol)(using Context): Boolean = !setup.isPreCC(overriding) && !setup.isPreCC(overridden) + /** Perform box adaptation for override checking */ + override def adapt(member: Symbol, memberTp: Type, otherTp: Type)(using Context): Option[(Type, Type)] = + if member.isType then + memberTp match + case TypeAlias(_) => + otherTp match + case otherTp: RealTypeBounds + if otherTp.hi.isBoxedCapturing || otherTp.lo.isBoxedCapturing => + Some((memberTp, otherTp.unboxed)) + case _ => None + case _ => None + else + val expected1 = alignDependentFunction(addOuterRefs(otherTp, memberTp, tree.srcPos), memberTp.stripCapturing) + val actual1 = + val saved = curEnv + try + curEnv = Env(clazz, EnvKind.NestedInOwner, capturedVars(clazz), outer0 = curEnv) + val adapted = + adaptBoxed(memberTp, expected1, tree, covariant = true, alwaysConst = true) + memberTp match + case _: MethodType => + // We remove the capture set resulted from box adaptation for method types, + // since class methods are always treated as pure, and their captured variables + // are charged to the capture set of the class (which is already done during + // box adaptation). + adapted.stripCapturing + case _ => adapted + finally curEnv = saved + if (actual1 eq memberTp) && (expected1 eq otherTp) then None + else Some((actual1, expected1)) + end adapt + override def checkInheritedTraitParameters: Boolean = false /** Check that overrides don't change the @use or @consume status of their parameters */ @@ -1875,7 +1908,9 @@ class CheckCaptures extends Recheck, SymTransformer: def traverse(t: Tree)(using Context) = t match case tree: InferredTypeTree => case tree: New => - case tree: TypeTree => checkAppliedTypesIn(tree.withType(tree.nuType)) + case tree: TypeTree => + CCState.withCapAsRoot: + checkAppliedTypesIn(tree.withType(tree.nuType)) case _ => traverseChildren(t) checkApplied.traverse(unit) end postCheck diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index 6d52ad94613b..ecb88c7fce80 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -336,6 +336,20 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: def fail(msg: Message) = if !tptToCheck.isEmpty then report.error(msg, tptToCheck.srcPos) + /** If C derives from Capability and we have a C^cs in source, we leave it as is + * instead of expanding it to C^{cap.rd}^cs. We do this by stripping capability-generated + * universal capture sets from the parent of a CapturingType. + */ + def stripImpliedCaptureSet(tp: Type): Type = tp match + case tp @ CapturingType(parent, refs) + if (refs eq CaptureSet.universalImpliedByCapability) && !tp.isBoxedCapturing => + parent + case tp: AliasingBounds => + tp.derivedAlias(stripImpliedCaptureSet(tp.alias)) + case tp: RealTypeBounds => + tp.derivedTypeBounds(stripImpliedCaptureSet(tp.lo), stripImpliedCaptureSet(tp.hi)) + case _ => tp + object toCapturing extends DeepTypeMap, SetupTypeMap: override def toString = "transformExplicitType" @@ -367,16 +381,6 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: CapturingType(fntpe, cs, boxed = false) else fntpe - /** If C derives from Capability and we have a C^cs in source, we leave it as is - * instead of expanding it to C^{cap.rd}^cs. We do this by stripping capability-generated - * universal capture sets from the parent of a CapturingType. - */ - def stripImpliedCaptureSet(tp: Type): Type = tp match - case tp @ CapturingType(parent, refs) - if (refs eq CaptureSet.universalImpliedByCapability) && !tp.isBoxedCapturing => - parent - case _ => tp - /** Check that types extending SharedCapability don't have a `cap` in their capture set. * TODO This is not enough. * We need to also track that we cannot get exclusive capabilities in paths @@ -456,8 +460,11 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: toCapturing.keepFunAliases = false transform(tp1) else tp1 - if freshen then root.capToFresh(tp2).tap(addOwnerAsHidden(_, sym)) - else tp2 + val tp3 = + if sym.isType then stripImpliedCaptureSet(tp2) + else tp2 + if freshen then root.capToFresh(tp3).tap(addOwnerAsHidden(_, sym)) + else tp3 end transformExplicitType /** Substitute parameter symbols in `from` to paramRefs in corresponding diff --git a/compiler/src/dotty/tools/dotc/cc/root.scala b/compiler/src/dotty/tools/dotc/cc/root.scala index da4ee330ca3c..2da7f89d4a4e 100644 --- a/compiler/src/dotty/tools/dotc/cc/root.scala +++ b/compiler/src/dotty/tools/dotc/cc/root.scala @@ -162,7 +162,9 @@ object root: case _ if root.isCap => Some(Kind.Global) case _ => None - /** Map each occurrence of cap to a different Sep.Cap instance */ + /** Map each occurrence of cap to a different Fresh instance + * Exception: CapSet^ stays as it is. + */ class CapToFresh(owner: Symbol)(using Context) extends BiTypeMap, FollowAliasesMap: thisMap => @@ -171,6 +173,8 @@ object root: else t match case t: CaptureRef if t.isCap => Fresh.withOwner(owner) + case t @ CapturingType(parent: TypeRef, _) if parent.symbol == defn.Caps_CapSet => + t case t @ CapturingType(_, _) => mapOver(t) case t @ AnnotatedType(parent, ann) => diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index f8b0675bd204..ce946072bb85 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -426,7 +426,8 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling if (tp1.prefix.isStable) return tryLiftedToThis1 case _ => if isCaptureVarComparison then - return subCaptures(tp1.captureSet, tp2.captureSet).isOK + return CCState.withCapAsRoot: + subCaptures(tp1.captureSet, tp2.captureSet).isOK if (tp1 eq NothingType) || isBottom(tp1) then return true } @@ -575,7 +576,8 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling && (isBottom(tp1) || GADTusage(tp2.symbol)) if isCaptureVarComparison then - return subCaptures(tp1.captureSet, tp2.captureSet).isOK + return CCState.withCapAsRoot: + subCaptures(tp1.captureSet, tp2.captureSet).isOK isSubApproxHi(tp1, info2.lo) && (trustBounds || isSubApproxHi(tp1, info2.hi)) || compareGADT diff --git a/compiler/src/dotty/tools/dotc/transform/OverridingPairs.scala b/compiler/src/dotty/tools/dotc/transform/OverridingPairs.scala index a9a17f6db464..ca20ccd2aeab 100644 --- a/compiler/src/dotty/tools/dotc/transform/OverridingPairs.scala +++ b/compiler/src/dotty/tools/dotc/transform/OverridingPairs.scala @@ -8,7 +8,7 @@ import NameKinds.DefaultGetterName import NullOpsDecorator.* import collection.immutable.BitSet import scala.annotation.tailrec -import cc.isCaptureChecking +import cc.{isCaptureChecking, CCState} import scala.compiletime.uninitialized @@ -215,14 +215,15 @@ object OverridingPairs: if member.isType then // intersection of bounds to refined types must be nonempty memberTp.bounds.hi.hasSameKindAs(otherTp.bounds.hi) && ( - (memberTp frozen_<:< otherTp) - || !member.owner.derivesFrom(other.owner) - && { - // if member and other come from independent classes or traits, their - // bounds must have non-empty-intersection - val jointBounds = (memberTp.bounds & otherTp.bounds).bounds - jointBounds.lo frozen_<:< jointBounds.hi - } + CCState.withCapAsRoot: // If upper bound is CapSet^ any capture set of lower bound is OK + (memberTp frozen_<:< otherTp) + || !member.owner.derivesFrom(other.owner) + && { + // if member and other come from independent classes or traits, their + // bounds must have non-empty-intersection + val jointBounds = (memberTp.bounds & otherTp.bounds).bounds + jointBounds.lo frozen_<:< jointBounds.hi + } ) else member.name.is(DefaultGetterName) // default getters are not checked for compatibility diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index 17e475e50a49..1a4980b848ac 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -253,6 +253,13 @@ object RefChecks { */ def needsCheck(overriding: Symbol, overridden: Symbol)(using Context): Boolean = true + /** Adapt member type and other type so that they can be compared with `frozen_<:<`. + * @return optionally, if adaptation is necessary, the pair of adapted types (memberTp', otherTp') + * Note: we return an Option result to avoid a tuple allocation in the normal case + * where no adaptation is necessary. + */ + def adapt(member: Symbol, memberTp: Type, otherTp: Type)(using Context): Option[(Type, Type)] = None + protected def additionalChecks(overriding: Symbol, overridden: Symbol)(using Context): Unit = () private val subtypeChecker: (Type, Type) => Context ?=> Boolean = this.checkSubType @@ -429,18 +436,26 @@ object RefChecks { * of class `clazz` are met. */ def checkOverride(checkSubType: (Type, Type) => Context ?=> Boolean, member: Symbol, other: Symbol): Unit = - def memberTp(self: Type) = + def memberType(self: Type) = if (member.isClass) TypeAlias(member.typeRef.etaExpand) else self.memberInfo(member) - def otherTp(self: Type) = - self.memberInfo(other) + def otherType(self: Type) = + self.memberInfo(other) + + var memberTp = memberType(self) + var otherTp = otherType(self) + checker.adapt(member, memberTp, otherTp) match + case Some((mtp, otp)) => + memberTp = mtp + otherTp = otp + case None => refcheck.println(i"check override ${infoString(member)} overriding ${infoString(other)}") - def noErrorType = !memberTp(self).isErroneous && !otherTp(self).isErroneous + def noErrorType = !memberTp.isErroneous && !otherTp.isErroneous def overrideErrorMsg(core: Context ?=> String, compareTypes: Boolean = false): Message = - val (mtp, otp) = if compareTypes then (memberTp(self), otherTp(self)) else (NoType, NoType) + val (mtp, otp) = if compareTypes then (memberTp, otherTp) else (NoType, NoType) OverrideError(core, self, member, other, mtp, otp) def compatTypes(memberTp: Type, otherTp: Type): Boolean = @@ -469,7 +484,7 @@ object RefChecks { // with box adaptation, we simply ignore capture annotations here. // This should be safe since the compatibility under box adaptation is already // checked. - memberTp(self).matches(otherTp(self)) + memberTp.matches(otherTp) } def emitOverrideError(fullmsg: Message) = @@ -624,12 +639,21 @@ object RefChecks { overrideError("is not inline, cannot implement an inline method") else if (other.isScala2Macro && !member.isScala2Macro) // (1.11) overrideError("cannot be used here - only Scala-2 macros can override Scala-2 macros") - else if !compatTypes(memberTp(self), otherTp(self)) - && !compatTypes(memberTp(upwardsSelf), otherTp(upwardsSelf)) + else if !compatTypes(memberTp, otherTp) && !member.is(Tracked) // Tracked members need to be excluded since they are abstract type members with // singleton types. Concrete overrides usually have a wider type. // TODO: Should we exclude all refinements inherited from parents? + && { + var memberTpUp = memberType(upwardsSelf) + var otherTpUp = otherType(upwardsSelf) + checker.adapt(member, memberTpUp, otherTpUp) match + case Some((mtp, otp)) => + memberTpUp = mtp + otherTpUp = otp + case _ => + !compatTypes(memberTpUp, otherTpUp) + } then overrideError("has incompatible type", compareTypes = true) else if (member.targetName != other.targetName) @@ -637,7 +661,7 @@ object RefChecks { overrideError(i"needs to be declared with @targetName(${"\""}${other.targetName}${"\""}) so that external names match") else overrideError("cannot have a @targetName annotation since external names would be different") - else if intoOccurrences(memberTp(self)) != intoOccurrences(otherTp(self)) then + else if intoOccurrences(memberTp) != intoOccurrences(otherTp) then overrideError("has different occurrences of `into` modifiers", compareTypes = true) else if other.is(ParamAccessor) && !isInheritedAccessor(member, other) && !member.is(Tracked) // see remark on tracked members above diff --git a/tests/pos-custom-args/captures/check-override-typebounds.scala b/tests/pos-custom-args/captures/check-override-typebounds.scala new file mode 100644 index 000000000000..7a662c78b208 --- /dev/null +++ b/tests/pos-custom-args/captures/check-override-typebounds.scala @@ -0,0 +1,16 @@ +import language.experimental.captureChecking + +trait A: + type T <: caps.Capability + +class B extends A: + type T = C + +class C extends caps.Capability + + +trait A2: + type T[Cap^] + + def takesCap[Cap^](t: T[Cap]): Unit + From be6968ebb355757974235739cd602eb8b2a699ad Mon Sep 17 00:00:00 2001 From: odersky Date: Wed, 19 Mar 2025 18:46:39 +0100 Subject: [PATCH 2/3] Refactor: Drop isSubType parameter for override checking Simplifies previous too convoluted logic. --- .../dotty/tools/dotc/cc/CheckCaptures.scala | 75 ++++++++----------- .../src/dotty/tools/dotc/core/Types.scala | 5 +- .../dotc/transform/OverridingPairs.scala | 5 +- .../dotty/tools/dotc/typer/RefChecks.scala | 26 +++---- 4 files changed, 44 insertions(+), 67 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 39017712884d..f90ef977920f 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -1574,36 +1574,13 @@ class CheckCaptures extends Recheck, SymTransformer: */ def checkOverrides = new TreeTraverser: class OverridingPairsCheckerCC(clazz: ClassSymbol, self: Type, tree: Tree)(using Context) extends OverridingPairsChecker(clazz, self): - /** Check subtype with box adaptation. - * This function is passed to RefChecks to check the compatibility of overriding pairs. - * @param sym symbol of the field definition that is being checked - - override def checkSubType(actual: Type, expected: Type)(using Context): Boolean = - val expected1 = alignDependentFunction(addOuterRefs(expected, actual, tree.srcPos), actual.stripCapturing) - val actual1 = - val saved = curEnv - try - curEnv = Env(clazz, EnvKind.NestedInOwner, capturedVars(clazz), outer0 = curEnv) - val adapted = - adaptBoxed(actual, expected1, tree, covariant = true, alwaysConst = true) - actual match - case _: MethodType => - // We remove the capture set resulted from box adaptation for method types, - // since class methods are always treated as pure, and their captured variables - // are charged to the capture set of the class (which is already done during - // box adaptation). - adapted.stripCapturing - case _ => adapted - finally curEnv = saved - actual1 frozen_<:< expected1 - */ /** Omit the check if one of {overriding,overridden} was nnot capture checked */ override def needsCheck(overriding: Symbol, overridden: Symbol)(using Context): Boolean = !setup.isPreCC(overriding) && !setup.isPreCC(overridden) /** Perform box adaptation for override checking */ - override def adapt(member: Symbol, memberTp: Type, otherTp: Type)(using Context): Option[(Type, Type)] = + override def adaptOverridePair(member: Symbol, memberTp: Type, otherTp: Type)(using Context): Option[(Type, Type)] = if member.isType then memberTp match case TypeAlias(_) => @@ -1613,26 +1590,36 @@ class CheckCaptures extends Recheck, SymTransformer: Some((memberTp, otherTp.unboxed)) case _ => None case _ => None - else - val expected1 = alignDependentFunction(addOuterRefs(otherTp, memberTp, tree.srcPos), memberTp.stripCapturing) - val actual1 = - val saved = curEnv - try - curEnv = Env(clazz, EnvKind.NestedInOwner, capturedVars(clazz), outer0 = curEnv) - val adapted = - adaptBoxed(memberTp, expected1, tree, covariant = true, alwaysConst = true) - memberTp match - case _: MethodType => - // We remove the capture set resulted from box adaptation for method types, - // since class methods are always treated as pure, and their captured variables - // are charged to the capture set of the class (which is already done during - // box adaptation). - adapted.stripCapturing - case _ => adapted - finally curEnv = saved - if (actual1 eq memberTp) && (expected1 eq otherTp) then None - else Some((actual1, expected1)) - end adapt + else memberTp match + case memberTp @ ExprType(memberRes) => + adaptOverridePair(member, memberRes, otherTp) match + case Some((mres, otp)) => Some((memberTp.derivedExprType(mres), otp)) + case None => None + case _ => otherTp match + case otherTp @ ExprType(otherRes) => + adaptOverridePair(member, memberTp, otherRes) match + case Some((mtp, ores)) => Some((mtp, otherTp.derivedExprType(ores))) + case None => None + case _ => + val expected1 = alignDependentFunction(addOuterRefs(otherTp, memberTp, tree.srcPos), memberTp.stripCapturing) + val actual1 = + val saved = curEnv + try + curEnv = Env(clazz, EnvKind.NestedInOwner, capturedVars(clazz), outer0 = curEnv) + val adapted = + adaptBoxed(memberTp, expected1, tree, covariant = true, alwaysConst = true) + memberTp match + case _: MethodType => + // We remove the capture set resulted from box adaptation for method types, + // since class methods are always treated as pure, and their captured variables + // are charged to the capture set of the class (which is already done during + // box adaptation). + adapted.stripCapturing + case _ => adapted + finally curEnv = saved + if (actual1 eq memberTp) && (expected1 eq otherTp) then None + else Some((actual1, expected1)) + end adaptOverridePair override def checkInheritedTraitParameters: Boolean = false diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index df7700c73a17..c193e4a81510 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1167,10 +1167,9 @@ object Types extends TypeUtils { * * @param isSubType a function used for checking subtype relationships. */ - final def overrides(that: Type, matchLoosely: => Boolean, checkClassInfo: Boolean = true, - isSubType: (Type, Type) => Context ?=> Boolean = (tp1, tp2) => tp1 frozen_<:< tp2)(using Context): Boolean = { + final def overrides(that: Type, matchLoosely: => Boolean, checkClassInfo: Boolean = true)(using Context): Boolean = { !checkClassInfo && this.isInstanceOf[ClassInfo] - || isSubType(this.widenExpr, that.widenExpr) + || (this.widenExpr frozen_<:< that.widenExpr) || matchLoosely && { val this1 = this.widenNullaryMethod val that1 = that.widenNullaryMethod diff --git a/compiler/src/dotty/tools/dotc/transform/OverridingPairs.scala b/compiler/src/dotty/tools/dotc/transform/OverridingPairs.scala index ca20ccd2aeab..231ba9942a23 100644 --- a/compiler/src/dotty/tools/dotc/transform/OverridingPairs.scala +++ b/compiler/src/dotty/tools/dotc/transform/OverridingPairs.scala @@ -210,8 +210,7 @@ object OverridingPairs: * @param isSubType A function to be used for checking subtype relationships * between term fields. */ - def isOverridingPair(member: Symbol, memberTp: Type, other: Symbol, otherTp: Type, fallBack: => Boolean = false, - isSubType: (Type, Type) => Context ?=> Boolean = (tp1, tp2) => tp1 frozen_<:< tp2)(using Context): Boolean = + def isOverridingPair(member: Symbol, memberTp: Type, other: Symbol, otherTp: Type, fallBack: => Boolean = false)(using Context): Boolean = if member.isType then // intersection of bounds to refined types must be nonempty memberTp.bounds.hi.hasSameKindAs(otherTp.bounds.hi) && ( @@ -227,6 +226,6 @@ object OverridingPairs: ) else member.name.is(DefaultGetterName) // default getters are not checked for compatibility - || memberTp.overrides(otherTp, member.matchNullaryLoosely || other.matchNullaryLoosely || fallBack, isSubType = isSubType) + || memberTp.overrides(otherTp, member.matchNullaryLoosely || other.matchNullaryLoosely || fallBack) end OverridingPairs diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index 1a4980b848ac..d89fc83beddc 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -242,12 +242,6 @@ object RefChecks { && (inLinearizationOrder(sym1, sym2, parent) || parent.is(JavaDefined)) && !sym2.is(AbsOverride) - /** Checks the subtype relationship tp1 <:< tp2. - * It is passed to the `checkOverride` operation in `checkAll`, to be used for - * compatibility checking. - */ - def checkSubType(tp1: Type, tp2: Type)(using Context): Boolean = tp1 frozen_<:< tp2 - /** A hook that allows to omit override checks between `overriding` and `overridden`. * Overridden in capture checking to handle non-capture checked classes leniently. */ @@ -258,16 +252,14 @@ object RefChecks { * Note: we return an Option result to avoid a tuple allocation in the normal case * where no adaptation is necessary. */ - def adapt(member: Symbol, memberTp: Type, otherTp: Type)(using Context): Option[(Type, Type)] = None + def adaptOverridePair(member: Symbol, memberTp: Type, otherTp: Type)(using Context): Option[(Type, Type)] = None protected def additionalChecks(overriding: Symbol, overridden: Symbol)(using Context): Unit = () - private val subtypeChecker: (Type, Type) => Context ?=> Boolean = this.checkSubType - - def checkAll(checkOverride: ((Type, Type) => Context ?=> Boolean, Symbol, Symbol) => Unit) = + def checkAll(checkOverride: (Symbol, Symbol) => Unit) = while hasNext do if needsCheck(overriding, overridden) then - checkOverride(subtypeChecker, overriding, overridden) + checkOverride(overriding, overridden) additionalChecks(overriding, overridden) next() @@ -282,7 +274,7 @@ object RefChecks { if dcl.is(Deferred) then for other <- dcl.allOverriddenSymbols do if !other.is(Deferred) then - checkOverride(subtypeChecker, dcl, other) + checkOverride(dcl, other) end checkAll // Disabled for capture checking since traits can get different parameter refinements @@ -435,7 +427,7 @@ object RefChecks { /* Check that all conditions for overriding `other` by `member` * of class `clazz` are met. */ - def checkOverride(checkSubType: (Type, Type) => Context ?=> Boolean, member: Symbol, other: Symbol): Unit = + def checkOverride(member: Symbol, other: Symbol): Unit = def memberType(self: Type) = if (member.isClass) TypeAlias(member.typeRef.etaExpand) else self.memberInfo(member) @@ -444,7 +436,7 @@ object RefChecks { var memberTp = memberType(self) var otherTp = otherType(self) - checker.adapt(member, memberTp, otherTp) match + checker.adaptOverridePair(member, memberTp, otherTp) match case Some((mtp, otp)) => memberTp = mtp otherTp = otp @@ -463,8 +455,8 @@ object RefChecks { isOverridingPair(member, memberTp, other, otherTp, fallBack = warnOnMigration( overrideErrorMsg("no longer has compatible type"), - (if (member.owner == clazz) member else clazz).srcPos, version = `3.0`), - isSubType = checkSubType) + (if member.owner == clazz then member else clazz).srcPos, + version = `3.0`)) catch case ex: MissingType => // can happen when called with upwardsSelf as qualifier of memberTp and otherTp, // because in that case we might access types that are not members of the qualifier. @@ -647,7 +639,7 @@ object RefChecks { && { var memberTpUp = memberType(upwardsSelf) var otherTpUp = otherType(upwardsSelf) - checker.adapt(member, memberTpUp, otherTpUp) match + checker.adaptOverridePair(member, memberTpUp, otherTpUp) match case Some((mtp, otp)) => memberTpUp = mtp otherTpUp = otp From 93f1169c66392b0a6670ee895cde578188b21379 Mon Sep 17 00:00:00 2001 From: odersky Date: Wed, 19 Mar 2025 18:56:34 +0100 Subject: [PATCH 3/3] More targeted handling of overriding checks against CapSet^ --- .../src/dotty/tools/dotc/cc/CheckCaptures.scala | 15 ++++++++++++--- .../tools/dotc/transform/OverridingPairs.scala | 17 ++++++++--------- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index f90ef977920f..3cf0a8088c5f 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -1585,9 +1585,18 @@ class CheckCaptures extends Recheck, SymTransformer: memberTp match case TypeAlias(_) => otherTp match - case otherTp: RealTypeBounds - if otherTp.hi.isBoxedCapturing || otherTp.lo.isBoxedCapturing => - Some((memberTp, otherTp.unboxed)) + case otherTp: RealTypeBounds => + if otherTp.hi.isBoxedCapturing || otherTp.lo.isBoxedCapturing then + Some((memberTp, otherTp.unboxed)) + else otherTp.hi match + case hi @ CapturingType(parent: TypeRef, refs) + if parent.symbol == defn.Caps_CapSet && refs.isUniversal => + Some(( + memberTp, + otherTp.derivedTypeBounds( + otherTp.lo, + hi.derivedCapturingType(parent, root.Fresh().singletonCaptureSet)))) + case _ => None case _ => None case _ => None else memberTp match diff --git a/compiler/src/dotty/tools/dotc/transform/OverridingPairs.scala b/compiler/src/dotty/tools/dotc/transform/OverridingPairs.scala index 231ba9942a23..20b0c8534920 100644 --- a/compiler/src/dotty/tools/dotc/transform/OverridingPairs.scala +++ b/compiler/src/dotty/tools/dotc/transform/OverridingPairs.scala @@ -214,15 +214,14 @@ object OverridingPairs: if member.isType then // intersection of bounds to refined types must be nonempty memberTp.bounds.hi.hasSameKindAs(otherTp.bounds.hi) && ( - CCState.withCapAsRoot: // If upper bound is CapSet^ any capture set of lower bound is OK - (memberTp frozen_<:< otherTp) - || !member.owner.derivesFrom(other.owner) - && { - // if member and other come from independent classes or traits, their - // bounds must have non-empty-intersection - val jointBounds = (memberTp.bounds & otherTp.bounds).bounds - jointBounds.lo frozen_<:< jointBounds.hi - } + (memberTp frozen_<:< otherTp) + || !member.owner.derivesFrom(other.owner) + && { + // if member and other come from independent classes or traits, their + // bounds must have non-empty-intersection + val jointBounds = (memberTp.bounds & otherTp.bounds).bounds + jointBounds.lo frozen_<:< jointBounds.hi + } ) else member.name.is(DefaultGetterName) // default getters are not checked for compatibility