From caebd00a7c1964a97c571d113090b6b57a0ac84a Mon Sep 17 00:00:00 2001 From: Yichen Xu Date: Thu, 8 Dec 2022 02:14:46 +0100 Subject: [PATCH 01/10] parameterize checkAllOverrides with a subtype checker --- compiler/src/dotty/tools/dotc/core/Types.scala | 7 +++++-- .../dotty/tools/dotc/transform/OverridingPairs.scala | 11 +++++++---- compiler/src/dotty/tools/dotc/typer/RefChecks.scala | 10 ++++++++-- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 956236b955db..7e64a7d2f6c3 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1071,12 +1071,15 @@ object Types { * @param relaxedCheck if true type `Null` becomes a subtype of non-primitive value types in TypeComparer. * @param matchLoosely if true the types `=> T` and `()T` are seen as overriding each other. * @param checkClassInfo if true we check that ClassInfos are within bounds of abstract types + * + * @param isSubType a function used for checking subtype relationships. */ - final def overrides(that: Type, relaxedCheck: Boolean, matchLoosely: => Boolean, checkClassInfo: Boolean = true)(using Context): Boolean = { + final def overrides(that: Type, relaxedCheck: Boolean, matchLoosely: => Boolean, checkClassInfo: Boolean = true, + isSubType: Context ?=> (Type, Type) => Boolean = (tp1, tp2) => tp1 frozen_<:< tp2)(using Context): Boolean = { val overrideCtx = if relaxedCheck then ctx.relaxedOverrideContext else ctx inContext(overrideCtx) { !checkClassInfo && this.isInstanceOf[ClassInfo] - || (this.widenExpr frozen_<:< that.widenExpr) + || isSubType(this.widenExpr, 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 b27a75436d86..598b3f89740a 100644 --- a/compiler/src/dotty/tools/dotc/transform/OverridingPairs.scala +++ b/compiler/src/dotty/tools/dotc/transform/OverridingPairs.scala @@ -200,10 +200,13 @@ object OverridingPairs: /** Let `member` and `other` be members of some common class C with types * `memberTp` and `otherTp` in C. Are the two symbols considered an overriding * pair in C? We assume that names already match so we test only the types here. - * @param fallBack A function called if the initial test is false and - * `member` and `other` are term symbols. + * @param fallBack A function called if the initial test is false and + * `member` and `other` are term symbols. + * @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)(using Context): Boolean = + def isOverridingPair(member: Symbol, memberTp: Type, other: Symbol, otherTp: Type, fallBack: => Boolean = false, + isSubType: Context ?=> (Type, Type) => Boolean = (tp1, tp2) => tp1 frozen_<:< tp2)(using Context): Boolean = if member.isType then // intersection of bounds to refined types must be nonempty memberTp.bounds.hi.hasSameKindAs(otherTp.bounds.hi) && ( @@ -222,6 +225,6 @@ object OverridingPairs: val relaxedOverriding = ctx.explicitNulls && (member.is(JavaDefined) || other.is(JavaDefined)) member.name.is(DefaultGetterName) // default getters are not checked for compatibility || memberTp.overrides(otherTp, relaxedOverriding, - member.matchNullaryLoosely || other.matchNullaryLoosely || fallBack) + member.matchNullaryLoosely || other.matchNullaryLoosely || fallBack, isSubType = isSubType) end OverridingPairs diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index f7c7bfa2f8a1..fc4c6ddda365 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -290,8 +290,13 @@ object RefChecks { * TODO check that classes are not overridden * TODO This still needs to be cleaned up; the current version is a straight port of what was there * before, but it looks too complicated and method bodies are far too large. + * + * @param isSubType A function used for checking the subtype relationship between + * two types `tp1` and `tp2` when checking the compatibility + * between overriding pairs, with possible adaptations applied + * (e.g. box adaptation in capture checking). */ - def checkAllOverrides(clazz: ClassSymbol)(using Context): Unit = { + def checkAllOverrides(clazz: ClassSymbol, isSubType: Context ?=> Symbol => (Type, Type) => Boolean = _ => (tp1, tp2) => tp1 frozen_<:< tp2)(using Context): Unit = { val self = clazz.thisType val upwardsSelf = upwardsThisType(clazz) var hasErrors = false @@ -344,7 +349,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`)) + (if (member.owner == clazz) member else clazz).srcPos, version = `3.0`), + isSubType = isSubType(member)) 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. From 243037f05c93254ad700e19b3e7ab938cc814f0a Mon Sep 17 00:00:00 2001 From: Yichen Xu Date: Thu, 8 Dec 2022 02:30:55 +0100 Subject: [PATCH 02/10] apply box adaptation when checking the compatibility of overrides --- .../dotty/tools/dotc/cc/CheckCaptures.scala | 55 +++++++++++++------ .../dotty/tools/dotc/typer/RefChecks.scala | 11 +++- .../neg-custom-args/captures/lazylists2.check | 19 ++++--- .../neg-custom-args/captures/lazylists2.scala | 4 +- .../captures/override-adapt-box.scala | 14 +++++ .../captures/override-adapt-box-pos.scala | 19 +++++++ 6 files changed, 95 insertions(+), 27 deletions(-) create mode 100644 tests/neg-custom-args/captures/override-adapt-box.scala create mode 100644 tests/pos-custom-args/captures/override-adapt-box-pos.scala diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 74279197d45d..528f69ca7e97 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -141,25 +141,12 @@ class CheckCaptures extends Recheck, SymTransformer: override def run(using Context): Unit = if Feature.ccEnabled then - checkOverrides.traverse(ctx.compilationUnit.tpdTree) super.run override def transformSym(sym: SymDenotation)(using Context): SymDenotation = if Synthetics.needsTransform(sym) then Synthetics.transformFromCC(sym) else super.transformSym(sym) - /** Check overrides again, taking capture sets into account. - * TODO: Can we avoid doing overrides checks twice? - * We need to do them here since only at this phase CaptureTypes are relevant - * But maybe we can then elide the check during the RefChecks phase under captureChecking? - */ - def checkOverrides = new TreeTraverser: - def traverse(t: Tree)(using Context) = - t match - case t: Template => checkAllOverrides(ctx.owner.asClass) - case _ => - traverseChildren(t) - class CaptureChecker(ictx: Context) extends Rechecker(ictx): import ast.tpd.* @@ -668,8 +655,11 @@ class CheckCaptures extends Recheck, SymTransformer: case _ => expected - /** Adapt `actual` type to `expected` type by inserting boxing and unboxing conversions */ - def adaptBoxed(actual: Type, expected: Type, pos: SrcPos)(using Context): Type = + /** Adapt `actual` type to `expected` type by inserting boxing and unboxing conversions + * + * @param alwaysConst always make capture set variables constant after adaptation + */ + def adaptBoxed(actual: Type, expected: Type, pos: SrcPos, alwaysConst: Boolean = false)(using Context): Type = /** Adapt function type `actual`, which is `aargs -> ares` (possibly with dependencies) * to `expected` type. @@ -806,9 +796,9 @@ class CheckCaptures extends Recheck, SymTransformer: } if !insertBox then // unboxing markFree(criticalSet, pos) - recon(CapturingType(parent1, cs1, !actualIsBoxed)) + recon(CapturingType(parent1, if alwaysConst then CaptureSet(cs1.elems) else cs1, !actualIsBoxed)) else - recon(CapturingType(parent1, cs1, actualIsBoxed)) + recon(CapturingType(parent1, if alwaysConst then CaptureSet(cs1.elems) else cs1, actualIsBoxed)) } var actualw = actual.widenDealias @@ -827,7 +817,38 @@ class CheckCaptures extends Recheck, SymTransformer: else actual end adaptBoxed + /** Check overrides again, taking capture sets into account. + * TODO: Can we avoid doing overrides checks twice? + * We need to do them here since only at this phase CaptureTypes are relevant + * But maybe we can then elide the check during the RefChecks phase under captureChecking? + */ + def checkOverrides = new TreeTraverser: + /** 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 + */ + def checkSubtype(sym: Symbol)(tree: Tree, actual: Type, expected: Type)(using Context): Boolean = + val expected1 = alignDependentFunction(addOuterRefs(expected, actual), actual.stripCapturing) + val actual1 = + val saved = curEnv + try + curEnv = Env(sym, nestedInOwner = false, CaptureSet.Var(), isBoxed = false, outer0 = curEnv) + adaptBoxed(actual, expected1, tree.srcPos, alwaysConst = true) match + case tp @ CapturingType(parent, refs) => + CapturingType(parent, CaptureSet(refs.elems)) + case tp => tp + finally curEnv = saved + actual1 frozen_<:< expected1 + + def traverse(t: Tree)(using Context) = + t match + case t: Template => + checkAllOverrides(ctx.owner.asClass, isSubType = sym => (tp1, tp2) => checkSubtype(sym)(t, tp1, tp2)) + case _ => + traverseChildren(t) + override def checkUnit(unit: CompilationUnit)(using Context): Unit = + checkOverrides.traverse(unit.tpdTree) Setup(preRecheckPhase, thisPhase, recheckDef) .traverse(ctx.compilationUnit.tpdTree) //println(i"SETUP:\n${Recheck.addRecheckedTypes.transform(ctx.compilationUnit.tpdTree)}") diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index fc4c6ddda365..ade23c2d8e62 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -362,7 +362,16 @@ object RefChecks { * Type members are always assumed to match. */ def trueMatch: Boolean = - member.isType || memberTp(self).matches(otherTp(self)) + member.isType || withMode(Mode.IgnoreCaptures) { + // `matches` does not perform box adaptation so the result here would be + // spurious during capture checking. + // + // Instead of parameterizing `matches` with the function for subtype checking + // 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)) + } def emitOverrideError(fullmsg: Message) = if (!(hasErrors && member.is(Synthetic) && member.is(Module))) { diff --git a/tests/neg-custom-args/captures/lazylists2.check b/tests/neg-custom-args/captures/lazylists2.check index d26d373443e3..812170aabdfe 100644 --- a/tests/neg-custom-args/captures/lazylists2.check +++ b/tests/neg-custom-args/captures/lazylists2.check @@ -1,10 +1,3 @@ --- [E164] Declaration Error: tests/neg-custom-args/captures/lazylists2.scala:50:10 ------------------------------------- -50 | def tail: {xs, f} LazyList[B] = xs.tail.map(f) // error - | ^ - | error overriding method tail in trait LazyList of type -> {Mapped.this} LazyList[B]; - | method tail of type -> {xs, f} LazyList[B] has incompatible type - | - | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/lazylists2.scala:18:4 ------------------------------------ 18 | final class Mapped extends LazyList[B]: // error | ^ @@ -37,6 +30,18 @@ 41 | def tail: {this} LazyList[B] = xs.tail.map(f) // error | ^ |(f : A => B) cannot be referenced here; it is not included in the allowed capture set {xs} of the self type of class Mapped +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/lazylists2.scala:45:4 ------------------------------------ +45 | final class Mapped extends LazyList[B]: // error + | ^ + | Found: {f, xs} LazyList[? B] + | Required: {xs} LazyList[B] +46 | this: ({xs, f} Mapped) => +47 | def isEmpty = false +48 | def head: B = f(xs.head) +49 | def tail: {xs, f} LazyList[B] = xs.tail.map(f) +50 | new Mapped + | + | longer explanation available when compiling with `-explain` -- Error: tests/neg-custom-args/captures/lazylists2.scala:60:10 -------------------------------------------------------- 60 | class Mapped2 extends Mapped: // error | ^ diff --git a/tests/neg-custom-args/captures/lazylists2.scala b/tests/neg-custom-args/captures/lazylists2.scala index 7b661e931441..574fb5a1a488 100644 --- a/tests/neg-custom-args/captures/lazylists2.scala +++ b/tests/neg-custom-args/captures/lazylists2.scala @@ -42,12 +42,12 @@ extension [A](xs: {*} LazyList[A]) new Mapped def map4[B](f: A => B): {xs} LazyList[B] = - final class Mapped extends LazyList[B]: + final class Mapped extends LazyList[B]: // error this: ({xs, f} Mapped) => def isEmpty = false def head: B = f(xs.head) - def tail: {xs, f} LazyList[B] = xs.tail.map(f) // error + def tail: {xs, f} LazyList[B] = xs.tail.map(f) new Mapped def map5[B](f: A => B): LazyList[B] = diff --git a/tests/neg-custom-args/captures/override-adapt-box.scala b/tests/neg-custom-args/captures/override-adapt-box.scala new file mode 100644 index 000000000000..c11840734db9 --- /dev/null +++ b/tests/neg-custom-args/captures/override-adapt-box.scala @@ -0,0 +1,14 @@ +import language.experimental.captureChecking + +abstract class A[X] { + def foo(x: X): X +} + +class IO +class C + +def test(io: {*} IO) = { + class B extends A[{io} C] { // X =:= {io} C // error + override def foo(x: {io} C): {io} C = ??? // error + } +} diff --git a/tests/pos-custom-args/captures/override-adapt-box-pos.scala b/tests/pos-custom-args/captures/override-adapt-box-pos.scala new file mode 100644 index 000000000000..7496a138070d --- /dev/null +++ b/tests/pos-custom-args/captures/override-adapt-box-pos.scala @@ -0,0 +1,19 @@ +import language.experimental.captureChecking + +class IO + +abstract class A[X, Y] { + def foo(x: Unit): X + def bar(x: Int, y: {} IO): X + def baz(x: Y): X +} + +class C + +def test(io: {*} IO) = { + class B extends A[{io} C, {} C] { // X =:= {io} C + override def foo(x: Unit): {io} C = ??? + override def bar(x: Int, y: {} IO): {io} C = ??? + override def baz(x: {} C): {io} C = ??? + } +} From 302f670ff7c27970fd1bf6756755f5a611333b93 Mon Sep 17 00:00:00 2001 From: Yichen Xu Date: Thu, 8 Dec 2022 12:41:58 +0100 Subject: [PATCH 03/10] ignore capture annotations in abstract class checks --- .../src/dotty/tools/dotc/typer/RefChecks.scala | 4 ++-- .../captures/override-adapt-box.scala | 2 +- .../captures/override-adapt-box-pos-alt.scala | 17 +++++++++++++++++ 3 files changed, 20 insertions(+), 3 deletions(-) create mode 100644 tests/pos-custom-args/captures/override-adapt-box-pos-alt.scala diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index ade23c2d8e62..01d515bfc671 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -590,7 +590,7 @@ object RefChecks { clazz.nonPrivateMembersNamed(mbr.name) .filterWithPredicate( impl => isConcrete(impl.symbol) - && mbrDenot.matchesLoosely(impl, alwaysCompareTypes = true)) + && withMode(Mode.IgnoreCaptures)(mbrDenot.matchesLoosely(impl, alwaysCompareTypes = true))) .exists /** The term symbols in this class and its baseclasses that are @@ -737,7 +737,7 @@ object RefChecks { def checkNoAbstractDecls(bc: Symbol): Unit = { for (decl <- bc.info.decls) if (decl.is(Deferred)) { - val impl = decl.matchingMember(clazz.thisType) + val impl = withMode(Mode.IgnoreCaptures)(decl.matchingMember(clazz.thisType)) if (impl == NoSymbol || decl.owner.isSubClass(impl.owner)) && !ignoreDeferred(decl) then diff --git a/tests/neg-custom-args/captures/override-adapt-box.scala b/tests/neg-custom-args/captures/override-adapt-box.scala index c11840734db9..2ad091269195 100644 --- a/tests/neg-custom-args/captures/override-adapt-box.scala +++ b/tests/neg-custom-args/captures/override-adapt-box.scala @@ -8,7 +8,7 @@ class IO class C def test(io: {*} IO) = { - class B extends A[{io} C] { // X =:= {io} C // error + class B extends A[{io} C] { // X =:= {io} C override def foo(x: {io} C): {io} C = ??? // error } } diff --git a/tests/pos-custom-args/captures/override-adapt-box-pos-alt.scala b/tests/pos-custom-args/captures/override-adapt-box-pos-alt.scala new file mode 100644 index 000000000000..c7e4d38723d7 --- /dev/null +++ b/tests/pos-custom-args/captures/override-adapt-box-pos-alt.scala @@ -0,0 +1,17 @@ +import language.experimental.captureChecking + +class IO + +abstract class A[X] { + def foo(x: Unit): X + def bar(op: X => Int): Int +} + +class C + +def test(io: {*} IO) = { + class B extends A[{io} C] { // X =:= {io} C + def foo(x: Unit): {io} C = ??? + def bar(op: ({io} C) => Int): Int = 0 + } +} From 1467e5a346ddd5e39098631f1dbf8c11794a843d Mon Sep 17 00:00:00 2001 From: Yichen Xu Date: Mon, 12 Dec 2022 17:07:50 +0100 Subject: [PATCH 04/10] do not dealias FromJavaObject during box adaptation --- compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 528f69ca7e97..0d990e1925c4 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -736,7 +736,8 @@ class CheckCaptures extends Recheck, SymTransformer: else ((parent, cs, tp.isBoxed), reconstruct) case actual => - ((actual, CaptureSet(), false), reconstruct) + val res = if tp.isFromJavaObject then tp else actual + ((res, CaptureSet(), false), reconstruct) def adapt(actual: Type, expected: Type, covariant: Boolean): Type = trace(adaptInfo(actual, expected, covariant), recheckr, show = true) { if expected.isInstanceOf[WildcardType] then actual From 59484f4744d058f7ba41ea2c7a4c70949ec4f67c Mon Sep 17 00:00:00 2001 From: Yichen Xu Date: Mon, 12 Dec 2022 17:08:34 +0100 Subject: [PATCH 05/10] fix cc compiler to pass override checks --- tests/pos-with-compiler-cc/dotc/cc/CaptureAnnotation.scala | 3 ++- tests/pos-with-compiler-cc/dotc/cc/CaptureSet.scala | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/pos-with-compiler-cc/dotc/cc/CaptureAnnotation.scala b/tests/pos-with-compiler-cc/dotc/cc/CaptureAnnotation.scala index fd89159e2076..2e750865f407 100644 --- a/tests/pos-with-compiler-cc/dotc/cc/CaptureAnnotation.scala +++ b/tests/pos-with-compiler-cc/dotc/cc/CaptureAnnotation.scala @@ -10,6 +10,7 @@ import Decorators.* import config.Printers.capt import printing.Printer import printing.Texts.Text +import annotation.retains /** An annotation representing a capture set and whether it is boxed. * It simulates a normal @retains annotation except that it is more efficient, @@ -50,7 +51,7 @@ case class CaptureAnnotation(refs: CaptureSet, boxed: Boolean)(cls: Symbol) exte this.refs == refs && this.boxed == boxed && this.symbol == that.symbol case _ => false - override def mapWith(tm: TypeMap)(using Context) = + override def mapWith(tm: TypeMap @retains(caps.*))(using Context) = val elems = refs.elems.toList val elems1 = elems.mapConserve(tm) if elems1 eq elems then this diff --git a/tests/pos-with-compiler-cc/dotc/cc/CaptureSet.scala b/tests/pos-with-compiler-cc/dotc/cc/CaptureSet.scala index b52c54157481..c31bcb76c2c7 100644 --- a/tests/pos-with-compiler-cc/dotc/cc/CaptureSet.scala +++ b/tests/pos-with-compiler-cc/dotc/cc/CaptureSet.scala @@ -273,7 +273,7 @@ sealed abstract class CaptureSet extends Showable, caps.Pure: map(Substituters.SubstParamsMap(tl, to).detach) /** Invoke handler if this set has (or later aquires) the root capability `*` */ - def disallowRootCapability(handler: () => Context ?=> Unit)(using Context): this.type = + def disallowRootCapability(handler: () -> Context ?-> Unit)(using Context): this.type = if isUniversal then handler() this From ac5d92e5ca465c0dbafa1b15ee6d889260d9d46d Mon Sep 17 00:00:00 2001 From: Yichen Xu Date: Thu, 15 Dec 2022 13:04:54 +0100 Subject: [PATCH 06/10] refactor box adaptation for override checking - make OverridingPairsChecker extensible, with the method for comparing subtype being abstract, and extend it with the box-adapt-enabled subtyping in CC - delay override checking after checking captures - delegate captured references of methods to the class --- .../dotty/tools/dotc/cc/CheckCaptures.scala | 45 +++++++++-------- .../src/dotty/tools/dotc/core/Types.scala | 2 +- .../dotc/transform/OverridingPairs.scala | 2 +- .../dotty/tools/dotc/typer/RefChecks.scala | 46 ++++++++++-------- tests/neg-custom-args/captures/lazylist.check | 14 +++--- .../override-adapt-box-selftype.scala | 48 +++++++++++++++++++ .../captures/override-adapt-box.scala | 6 +-- 7 files changed, 111 insertions(+), 52 deletions(-) create mode 100644 tests/neg-custom-args/captures/override-adapt-box-selftype.scala diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 0d990e1925c4..16b3cd3016a3 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -10,7 +10,7 @@ import config.Printers.{capt, recheckr} import config.{Config, Feature} import ast.{tpd, untpd, Trees} import Trees.* -import typer.RefChecks.{checkAllOverrides, checkSelfAgainstParents} +import typer.RefChecks.{checkAllOverrides, checkSelfAgainstParents, OverridingPairsChecker} import typer.Checking.{checkBounds, checkAppliedTypesIn} import util.{SimpleIdentitySet, EqHashMap, SrcPos} import transform.SymUtils.* @@ -824,37 +824,44 @@ class CheckCaptures extends Recheck, SymTransformer: * But maybe we can then elide the check during the RefChecks phase under captureChecking? */ def checkOverrides = new TreeTraverser: - /** 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 - */ - def checkSubtype(sym: Symbol)(tree: Tree, actual: Type, expected: Type)(using Context): Boolean = - val expected1 = alignDependentFunction(addOuterRefs(expected, actual), actual.stripCapturing) - val actual1 = - val saved = curEnv - try - curEnv = Env(sym, nestedInOwner = false, CaptureSet.Var(), isBoxed = false, outer0 = curEnv) - adaptBoxed(actual, expected1, tree.srcPos, alwaysConst = true) match - case tp @ CapturingType(parent, refs) => - CapturingType(parent, CaptureSet(refs.elems)) - case tp => tp - finally curEnv = saved - actual1 frozen_<:< expected1 + class OverridingPairsCheckerCC(clazz: ClassSymbol, self: Type, srcPos: SrcPos)(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), actual.stripCapturing) + val actual1 = + val saved = curEnv + try + curEnv = Env(clazz, nestedInOwner = true, capturedVars(clazz), isBoxed = false, outer0 = curEnv) + val adapted = adaptBoxed(actual, expected1, srcPos, 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 + } def traverse(t: Tree)(using Context) = t match case t: Template => - checkAllOverrides(ctx.owner.asClass, isSubType = sym => (tp1, tp2) => checkSubtype(sym)(t, tp1, tp2)) + checkAllOverrides(ctx.owner.asClass, OverridingPairsCheckerCC(_, _, t)) case _ => traverseChildren(t) override def checkUnit(unit: CompilationUnit)(using Context): Unit = - checkOverrides.traverse(unit.tpdTree) Setup(preRecheckPhase, thisPhase, recheckDef) .traverse(ctx.compilationUnit.tpdTree) //println(i"SETUP:\n${Recheck.addRecheckedTypes.transform(ctx.compilationUnit.tpdTree)}") withCaptureSetsExplained { super.checkUnit(unit) + checkOverrides.traverse(unit.tpdTree) checkSelfTypes(unit.tpdTree) postCheck(unit.tpdTree) if ctx.settings.YccDebug.value then diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 7e64a7d2f6c3..f214922dd18e 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -1075,7 +1075,7 @@ object Types { * @param isSubType a function used for checking subtype relationships. */ final def overrides(that: Type, relaxedCheck: Boolean, matchLoosely: => Boolean, checkClassInfo: Boolean = true, - isSubType: Context ?=> (Type, Type) => Boolean = (tp1, tp2) => tp1 frozen_<:< tp2)(using Context): Boolean = { + isSubType: (Type, Type) => Context ?=> Boolean = (tp1, tp2) => tp1 frozen_<:< tp2)(using Context): Boolean = { val overrideCtx = if relaxedCheck then ctx.relaxedOverrideContext else ctx inContext(overrideCtx) { !checkClassInfo && this.isInstanceOf[ClassInfo] diff --git a/compiler/src/dotty/tools/dotc/transform/OverridingPairs.scala b/compiler/src/dotty/tools/dotc/transform/OverridingPairs.scala index 598b3f89740a..48dc7c818360 100644 --- a/compiler/src/dotty/tools/dotc/transform/OverridingPairs.scala +++ b/compiler/src/dotty/tools/dotc/transform/OverridingPairs.scala @@ -206,7 +206,7 @@ object OverridingPairs: * between term fields. */ def isOverridingPair(member: Symbol, memberTp: Type, other: Symbol, otherTp: Type, fallBack: => Boolean = false, - isSubType: Context ?=> (Type, Type) => Boolean = (tp1, tp2) => tp1 frozen_<:< tp2)(using Context): Boolean = + isSubType: (Type, Type) => Context ?=> Boolean = (tp1, tp2) => tp1 frozen_<:< tp2)(using Context): Boolean = if member.isType then // intersection of bounds to refined types must be nonempty memberTp.bounds.hi.hasSameKindAs(otherTp.bounds.hi) && ( diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index 01d515bfc671..c410fe076369 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -237,9 +237,14 @@ object RefChecks { && inLinearizationOrder(sym1, sym2, parent) && !sym2.is(AbsOverride) - def checkAll(checkOverride: (Symbol, Symbol) => Unit) = + // 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 + + def checkAll(checkOverride: ((Type, Type) => Context ?=> Boolean, Symbol, Symbol) => Unit) = while hasNext do - checkOverride(overriding, overridden) + checkOverride(checkSubType, overriding, overridden) next() // The OverridingPairs cursor does assume that concrete overrides abstract @@ -253,7 +258,7 @@ object RefChecks { if dcl.is(Deferred) then for other <- dcl.allOverriddenSymbols do if !other.is(Deferred) then - checkOverride(dcl, other) + checkOverride(checkSubType, dcl, other) end checkAll end OverridingPairsChecker @@ -291,12 +296,10 @@ object RefChecks { * TODO This still needs to be cleaned up; the current version is a straight port of what was there * before, but it looks too complicated and method bodies are far too large. * - * @param isSubType A function used for checking the subtype relationship between - * two types `tp1` and `tp2` when checking the compatibility - * between overriding pairs, with possible adaptations applied - * (e.g. box adaptation in capture checking). + * @param makeOverridePairsChecker A function for creating a OverridePairsChecker instance + * from the class symbol and the self type */ - def checkAllOverrides(clazz: ClassSymbol, isSubType: Context ?=> Symbol => (Type, Type) => Boolean = _ => (tp1, tp2) => tp1 frozen_<:< tp2)(using Context): Unit = { + def checkAllOverrides(clazz: ClassSymbol, makeOverridingPairsChecker: ((ClassSymbol, Type) => Context ?=> OverridingPairsChecker) | Null = null)(using Context): Unit = { val self = clazz.thisType val upwardsSelf = upwardsThisType(clazz) var hasErrors = false @@ -327,10 +330,17 @@ object RefChecks { def infoStringWithLocation(sym: Symbol) = err.infoString(sym, self, showLocation = true) + def isInheritedAccessor(mbr: Symbol, other: Symbol): Boolean = + mbr.is(ParamAccessor) + && { + val next = ParamForwarding.inheritedAccessor(mbr) + next == other || isInheritedAccessor(next, other) + } + /* Check that all conditions for overriding `other` by `member` - * of class `clazz` are met. - */ - def checkOverride(member: Symbol, other: Symbol): Unit = + * of class `clazz` are met. + */ + def checkOverride(checkSubType: (Type, Type) => Context ?=> Boolean, member: Symbol, other: Symbol): Unit = def memberTp(self: Type) = if (member.isClass) TypeAlias(member.typeRef.EtaExpand(member.typeParams)) else self.memberInfo(member) @@ -350,7 +360,7 @@ object RefChecks { fallBack = warnOnMigration( overrideErrorMsg("no longer has compatible type"), (if (member.owner == clazz) member else clazz).srcPos, version = `3.0`), - isSubType = isSubType(member)) + isSubType = checkSubType) 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. @@ -506,7 +516,7 @@ object RefChecks { else if (member.is(ModuleVal) && !other.isRealMethod && !other.isOneOf(DeferredOrLazy)) overrideError("may not override a concrete non-lazy value") else if (member.is(Lazy, butNot = Module) && !other.isRealMethod && !other.is(Lazy) && - !warnOnMigration(overrideErrorMsg("may not override a non-lazy value"), member.srcPos, version = `3.0`)) + !warnOnMigration(overrideErrorMsg("may not override a non-lazy value"), member.srcPos, version = `3.0`)) overrideError("may not override a non-lazy value") else if (other.is(Lazy) && !other.isRealMethod && !member.is(Lazy)) overrideError("must be declared lazy to override a lazy value") @@ -539,14 +549,8 @@ object RefChecks { overrideDeprecation("", member, other, "removed or renamed") end checkOverride - def isInheritedAccessor(mbr: Symbol, other: Symbol): Boolean = - mbr.is(ParamAccessor) - && { - val next = ParamForwarding.inheritedAccessor(mbr) - next == other || isInheritedAccessor(next, other) - } - - OverridingPairsChecker(clazz, self).checkAll(checkOverride) + val checker = if makeOverridingPairsChecker == null then OverridingPairsChecker(clazz, self) else makeOverridingPairsChecker(clazz, self) + checker.checkAll(checkOverride) printMixinOverrideErrors() // Verifying a concrete class has nothing unimplemented. diff --git a/tests/neg-custom-args/captures/lazylist.check b/tests/neg-custom-args/captures/lazylist.check index c594519496cd..471e2a038450 100644 --- a/tests/neg-custom-args/captures/lazylist.check +++ b/tests/neg-custom-args/captures/lazylist.check @@ -1,10 +1,3 @@ --- [E164] Declaration Error: tests/neg-custom-args/captures/lazylist.scala:22:6 ---------------------------------------- -22 | def tail: {*} LazyList[Nothing] = ??? // error overriding - | ^ - | error overriding method tail in class LazyList of type -> lazylists.LazyList[Nothing]; - | method tail of type -> {*} lazylists.LazyList[Nothing] has incompatible type - | - | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/lazylist.scala:17:15 ------------------------------------- 17 | def tail = xs() // error | ^^^^ @@ -40,3 +33,10 @@ | Required: {cap1, ref3, cap3} lazylists.LazyList[Int] | | longer explanation available when compiling with `-explain` +-- [E164] Declaration Error: tests/neg-custom-args/captures/lazylist.scala:22:6 ---------------------------------------- +22 | def tail: {*} LazyList[Nothing] = ??? // error overriding + | ^ + | error overriding method tail in class LazyList of type -> lazylists.LazyList[Nothing]; + | method tail of type -> {*} lazylists.LazyList[Nothing] has incompatible type + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/override-adapt-box-selftype.scala b/tests/neg-custom-args/captures/override-adapt-box-selftype.scala new file mode 100644 index 000000000000..a4dc92429192 --- /dev/null +++ b/tests/neg-custom-args/captures/override-adapt-box-selftype.scala @@ -0,0 +1,48 @@ +import language.experimental.captureChecking + +class IO +class C + +object Test1 { + abstract class A[X] { this: {} A[X] => + def foo(x: X): X + } + + def test(io: {*} IO) = { + class B extends A[{io} C] { // X =:= {io} C // error + override def foo(x: {io} C): {io} C = ??? + } + } +} + +def Test2(io: {*} IO, fs: {io} IO, ct: {*} IO) = { + abstract class A[X] { this: {io} A[X] => + def foo(x: X): X + } + + class B1 extends A[{io} C] { + override def foo(x: {io} C): {io} C = ??? + } + + class B2 extends A[{ct} C] { // error + override def foo(x: {ct} C): {ct} C = ??? + } + + class B3 extends A[{fs} C] { + override def foo(x: {fs} C): {fs} C = ??? + } +} + +def Test3(io: {*} IO, ct: {*} IO) = { + abstract class A[X] { this: {*} A[X] => + def foo(x: X): X + } + + class B1 extends A[{io} C] { + override def foo(x: {io} C): {io} C = ??? + } + + class B2 extends A[{io, ct} C] { + override def foo(x: {io, ct} C): {io, ct} C = ??? + } +} diff --git a/tests/neg-custom-args/captures/override-adapt-box.scala b/tests/neg-custom-args/captures/override-adapt-box.scala index 2ad091269195..64ba8743bf91 100644 --- a/tests/neg-custom-args/captures/override-adapt-box.scala +++ b/tests/neg-custom-args/captures/override-adapt-box.scala @@ -1,6 +1,6 @@ import language.experimental.captureChecking -abstract class A[X] { +abstract class A[X] { this: ({} A[X]) => def foo(x: X): X } @@ -8,7 +8,7 @@ class IO class C def test(io: {*} IO) = { - class B extends A[{io} C] { // X =:= {io} C - override def foo(x: {io} C): {io} C = ??? // error + class B extends A[{io} C] { // X =:= {io} C // error + override def foo(x: {io} C): {io} C = ??? } } From 7e74341ce101e81e0df39b98ca5e6dac8a8c35fe Mon Sep 17 00:00:00 2001 From: Yichen Xu Date: Tue, 20 Dec 2022 21:33:32 +0100 Subject: [PATCH 07/10] fix baseType caching during capture checking - disable caching during Setup - never cache capturing types --- compiler/src/dotty/tools/dotc/cc/CaptureOps.scala | 5 +++++ compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala | 3 +-- compiler/src/dotty/tools/dotc/cc/Setup.scala | 11 +++++++++++ .../src/dotty/tools/dotc/core/SymDenotations.scala | 10 ++++++---- 4 files changed, 23 insertions(+), 6 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index 0ede1825e611..e4533aa73ce0 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -166,6 +166,11 @@ extension (tp: Type) case CapturingType(_, _) => true case _ => false + def isEventuallyCapturingType(using Context): Boolean = + tp match + case EventuallyCapturingType(_, _) => true + case _ => false + /** Is type known to be always pure by its class structure, * so that adding a capture set to it would not make sense? */ diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 16b3cd3016a3..077d345d792d 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -856,8 +856,7 @@ class CheckCaptures extends Recheck, SymTransformer: traverseChildren(t) override def checkUnit(unit: CompilationUnit)(using Context): Unit = - Setup(preRecheckPhase, thisPhase, recheckDef) - .traverse(ctx.compilationUnit.tpdTree) + Setup(preRecheckPhase, thisPhase, recheckDef)(ctx.compilationUnit.tpdTree) //println(i"SETUP:\n${Recheck.addRecheckedTypes.transform(ctx.compilationUnit.tpdTree)}") withCaptureSetsExplained { super.checkUnit(unit) diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index b5dee7f4a13b..461c18ea0980 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -11,6 +11,7 @@ import ast.tpd import transform.Recheck.* import CaptureSet.IdentityCaptRefMap import Synthetics.isExcluded +import util.Property /** A tree traverser that prepares a compilation unit to be capture checked. * It does the following: @@ -484,4 +485,14 @@ extends tpd.TreeTraverser: capt.println(i"update info of ${tree.symbol} from $info to $newInfo") case _ => end traverse + + def apply(tree: Tree)(using Context): Unit = + traverse(tree)(using ctx.withProperty(Setup.IsDuringSetupKey, Some(()))) end Setup + +object Setup: + val IsDuringSetupKey = new Property.Key[Unit] + + def isDuringSetup(using Context): Boolean = + ctx.property(IsDuringSetupKey).isDefined + diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index e267bc51758f..2ad25339c0b4 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -24,7 +24,7 @@ import config.Config import reporting._ import collection.mutable import transform.TypeUtils._ -import cc.{CapturingType, derivedCapturingType} +import cc.{CapturingType, derivedCapturingType, Setup, EventuallyCapturingType, isEventuallyCapturingType} import scala.annotation.internal.sharable @@ -2140,6 +2140,7 @@ object SymDenotations { final def baseTypeOf(tp: Type)(using Context): Type = { val btrCache = baseTypeCache def inCache(tp: Type) = tp match + case EventuallyCapturingType(_, _) => false case tp: CachedType => btrCache.contains(tp) case _ => false def record(tp: CachedType, baseTp: Type) = { @@ -2147,7 +2148,7 @@ object SymDenotations { Stats.record("basetype cache entries") if (!baseTp.exists) Stats.record("basetype cache NoTypes") } - if (!tp.isProvisional) + if (!tp.isProvisional && !tp.isEventuallyCapturingType && !Setup.isDuringSetup) btrCache(tp) = baseTp else btrCache.remove(tp) // Remove any potential sentinel value @@ -2161,8 +2162,9 @@ object SymDenotations { def recur(tp: Type): Type = try { tp match { case tp: CachedType => - val baseTp = btrCache.lookup(tp) - if (baseTp != null) return ensureAcyclic(baseTp) + val baseTp: Type | Null = btrCache.lookup(tp) + if (baseTp != null) + return ensureAcyclic(baseTp) case _ => } if (Stats.monitored) { From c1753fe717a21c9ed2b29028bedb1dc6ac14c78c Mon Sep 17 00:00:00 2001 From: Yichen Xu Date: Tue, 20 Dec 2022 21:34:16 +0100 Subject: [PATCH 08/10] several fixes to CC compiler --- tests/pos-with-compiler-cc/dotc/core/GadtConstraint.scala | 2 +- tests/pos-with-compiler-cc/dotc/core/Types.scala | 8 ++++---- .../dotc/transform/TreeMapWithStages.scala | 2 +- .../dotc/transform/init/Semantic.scala | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/pos-with-compiler-cc/dotc/core/GadtConstraint.scala b/tests/pos-with-compiler-cc/dotc/core/GadtConstraint.scala index e8ac601fa368..46b7e07649b8 100644 --- a/tests/pos-with-compiler-cc/dotc/core/GadtConstraint.scala +++ b/tests/pos-with-compiler-cc/dotc/core/GadtConstraint.scala @@ -238,7 +238,7 @@ sealed trait GadtConstraint ( private def tvarOrError(sym: Symbol)(using Context): TypeVar = mapping(sym).ensuring(_ != null, i"not a constrainable symbol: $sym").uncheckedNN - private def containsNoInternalTypes(tp: Type, theAcc: TypeAccumulator[Boolean] | Null = null)(using Context): Boolean = tp match { + private def containsNoInternalTypes(tp: Type, theAcc: TypeAccumulator[Boolean] @retains(caps.*) | Null = null)(using Context): Boolean = tp match { case tpr: TypeParamRef => !reverseMapping.contains(tpr) case tv: TypeVar => !reverseMapping.contains(tv.origin) case tp => diff --git a/tests/pos-with-compiler-cc/dotc/core/Types.scala b/tests/pos-with-compiler-cc/dotc/core/Types.scala index 5e2b4431322e..f94e9ac6d645 100644 --- a/tests/pos-with-compiler-cc/dotc/core/Types.scala +++ b/tests/pos-with-compiler-cc/dotc/core/Types.scala @@ -115,7 +115,7 @@ object Types { private def testProvisional(using Context): Boolean = class ProAcc extends TypeAccumulator[Boolean]: override def apply(x: Boolean, t: Type) = x || test(t, this) - def test(t: Type, theAcc: TypeAccumulator[Boolean] | Null): Boolean = + def test(t: Type, theAcc: TypeAccumulator[Boolean] @retains(caps.*) | Null): Boolean = if t.mightBeProvisional then t.mightBeProvisional = t match case t: TypeRef => @@ -3773,7 +3773,7 @@ object Types { val status = (x & StatusMask) max (y & StatusMask) val provisional = (x | y) & Provisional (if status == TrueDeps then status else status | provisional).toByte - def compute(status: DependencyStatus, tp: Type, theAcc: TypeAccumulator[DependencyStatus] | Null): DependencyStatus = + def compute(status: DependencyStatus, tp: Type, theAcc: TypeAccumulator[DependencyStatus] @retains(caps.*) | Null): DependencyStatus = def applyPrefix(tp: NamedType) = if tp.isInstanceOf[SingletonType] && tp.currentSymbol.isStatic then status // Note: a type ref with static symbol can still be dependent since the symbol might be refined in the enclosing type. See pos/15331.scala. @@ -4351,7 +4351,7 @@ object Types { private var myEvalRunId: RunId = NoRunId private var myEvalued: Type = uninitialized - def isGround(acc: TypeAccumulator[Boolean])(using Context): Boolean = + def isGround(acc: TypeAccumulator[Boolean] @retains(caps.*))(using Context): Boolean = if myGround == 0 then myGround = if acc.foldOver(true, this) then 1 else -1 myGround > 0 @@ -5750,7 +5750,7 @@ object Types { } } - private def treeTypeMap = new TreeTypeMap(typeMap = this) + private def treeTypeMap = new TreeTypeMap(typeMap = this.detach) def mapOver(syms: List[Symbol]): List[Symbol] = mapSymbols(syms, treeTypeMap) diff --git a/tests/pos-with-compiler-cc/dotc/transform/TreeMapWithStages.scala b/tests/pos-with-compiler-cc/dotc/transform/TreeMapWithStages.scala index 289cc44f315d..c721c838d316 100644 --- a/tests/pos-with-compiler-cc/dotc/transform/TreeMapWithStages.scala +++ b/tests/pos-with-compiler-cc/dotc/transform/TreeMapWithStages.scala @@ -18,7 +18,7 @@ import scala.annotation.constructorOnly * and `l == -1` is code inside a top level splice (in an inline method). * @param levels a stacked map from symbols to the levels in which they were defined */ -abstract class TreeMapWithStages(@constructorOnly ictx: Context) extends TreeMapWithImplicits { +abstract class TreeMapWithStages(@constructorOnly ictx: Context) extends TreeMapWithImplicits { this: {} TreeMapWithStages => import tpd._ import TreeMapWithStages._ diff --git a/tests/pos-with-compiler-cc/dotc/transform/init/Semantic.scala b/tests/pos-with-compiler-cc/dotc/transform/init/Semantic.scala index 0c10db0b5f09..14873938bf78 100644 --- a/tests/pos-with-compiler-cc/dotc/transform/init/Semantic.scala +++ b/tests/pos-with-compiler-cc/dotc/transform/init/Semantic.scala @@ -515,7 +515,7 @@ object Semantic: object Reporter: class BufferedReporter extends Reporter: private val buf = new mutable.ArrayBuffer[Error] - def errors = buf.toList + def errors: List[Error] = buf.toList def report(err: Error) = buf += err class TryBufferedReporter(backup: Cache) extends BufferedReporter with TryReporter: From b9b47678466caed7f267204e06e4155d5aab4691 Mon Sep 17 00:00:00 2001 From: Yichen Xu Date: Tue, 20 Dec 2022 22:46:40 +0100 Subject: [PATCH 09/10] add a field for subtype checker to avoid creating closures repeatedly --- compiler/src/dotty/tools/dotc/typer/RefChecks.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index c410fe076369..cc39d5ebc2af 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -242,9 +242,11 @@ object RefChecks { // compatibility checking. def checkSubType(tp1: Type, tp2: Type)(using Context): Boolean = tp1 frozen_<:< tp2 + private val subtypeChecker: (Type, Type) => Context ?=> Boolean = this.checkSubType + def checkAll(checkOverride: ((Type, Type) => Context ?=> Boolean, Symbol, Symbol) => Unit) = while hasNext do - checkOverride(checkSubType, overriding, overridden) + checkOverride(subtypeChecker, overriding, overridden) next() // The OverridingPairs cursor does assume that concrete overrides abstract From 9dd4f528785a4d197db389cac509a7a13db33ad7 Mon Sep 17 00:00:00 2001 From: Yichen Xu Date: Mon, 9 Jan 2023 18:35:11 +0100 Subject: [PATCH 10/10] optimize the cachability check --- compiler/src/dotty/tools/dotc/cc/CapturingType.scala | 10 ++++++++++ .../src/dotty/tools/dotc/core/SymDenotations.scala | 3 +-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CapturingType.scala b/compiler/src/dotty/tools/dotc/cc/CapturingType.scala index e9862f1f20b8..a7c283f4cc3b 100644 --- a/compiler/src/dotty/tools/dotc/cc/CapturingType.scala +++ b/compiler/src/dotty/tools/dotc/cc/CapturingType.scala @@ -48,6 +48,16 @@ object CapturingType: EventuallyCapturingType.unapply(tp) else None + /** Check whether a type is uncachable when computing `baseType`. + * - Avoid caching all the types during the setup phase, since at that point + * the capture set variables are not fully installed yet. + * - Avoid caching capturing types when IgnoreCaptures mode is set, since the + * capture sets may be thrown away in the computed base type. + */ + def isUncachable(tp: Type)(using Context): Boolean = + ctx.phase == Phases.checkCapturesPhase && + (Setup.isDuringSetup || ctx.mode.is(Mode.IgnoreCaptures) && tp.isEventuallyCapturingType) + end CapturingType /** An extractor for types that will be capturing types at phase CheckCaptures. Also diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 2ad25339c0b4..7a54c01cada1 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -2140,7 +2140,6 @@ object SymDenotations { final def baseTypeOf(tp: Type)(using Context): Type = { val btrCache = baseTypeCache def inCache(tp: Type) = tp match - case EventuallyCapturingType(_, _) => false case tp: CachedType => btrCache.contains(tp) case _ => false def record(tp: CachedType, baseTp: Type) = { @@ -2148,7 +2147,7 @@ object SymDenotations { Stats.record("basetype cache entries") if (!baseTp.exists) Stats.record("basetype cache NoTypes") } - if (!tp.isProvisional && !tp.isEventuallyCapturingType && !Setup.isDuringSetup) + if (!tp.isProvisional && !CapturingType.isUncachable(tp)) btrCache(tp) = baseTp else btrCache.remove(tp) // Remove any potential sentinel value