From 44a8d6fdb3d001854383cdbc741ff085e99240ac Mon Sep 17 00:00:00 2001 From: odersky Date: Thu, 23 Oct 2025 13:06:55 +0300 Subject: [PATCH] Refactoring: Bundle mutability-related operations in separate object --- .../src/dotty/tools/dotc/cc/CaptureOps.scala | 82 ------ .../src/dotty/tools/dotc/cc/CaptureSet.scala | 3 - .../dotty/tools/dotc/cc/CheckCaptures.scala | 113 +-------- .../src/dotty/tools/dotc/cc/Mutability.scala | 236 ++++++++++++++++++ .../src/dotty/tools/dotc/cc/SepCheck.scala | 3 +- .../tools/dotc/printing/RefinedPrinter.scala | 1 + .../src/dotty/tools/dotc/typer/Checking.scala | 3 +- .../dotty/tools/dotc/typer/RefChecks.scala | 3 +- 8 files changed, 246 insertions(+), 198 deletions(-) create mode 100644 compiler/src/dotty/tools/dotc/cc/Mutability.scala diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index f9cedd53c66a..125569c16033 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -352,15 +352,6 @@ extension (tp: Type) case _ => false - /** Is this a type extending `Mutable` that has non-private update methods - * or mutable fields? - */ - def isMutableType(using Context): Boolean = - tp.derivesFrom(defn.Caps_Mutable) - && tp.membersBasedOnFlags(Mutable, EmptyFlags).exists: mbr => - if mbr.symbol.is(Method) then mbr.symbol.isUpdateMethod - else !mbr.symbol.is(Transparent) - /** Is this a reference to caps.cap? Note this is _not_ the GlobalCap capability. */ def isCapRef(using Context): Boolean = tp match case tp: TermRef => tp.name == nme.CAPTURE_ROOT && tp.symbol == defn.captureRoot @@ -499,23 +490,6 @@ extension (tp: Type) def classifier(using Context): ClassSymbol = tp.classSymbols.map(_.classifier).foldLeft(defn.AnyClass)(leastClassifier) - def exclusivity(using Context): Exclusivity = - if tp.derivesFrom(defn.Caps_Mutable) then - tp match - case tp: Capability if tp.isExclusive => Exclusivity.OK - case _ => tp.captureSet.exclusivity(tp) - else Exclusivity.OK - - def exclusivityInContext(using Context): Exclusivity = tp match - case tp: ThisType => - if tp.derivesFrom(defn.Caps_Mutable) - then ctx.owner.inExclusivePartOf(tp.cls) - else Exclusivity.OK - case tp @ TermRef(prefix: Capability, _) => - prefix.exclusivityInContext.andAlso(tp.exclusivity) - case _ => - tp.exclusivity - extension (tp: MethodType) /** A method marks an existential scope unless it is the prefix of a curried method */ def marksExistentialScope(using Context): Boolean = @@ -649,30 +623,6 @@ extension (sym: Symbol) sym.hasAnnotation(defn.ConsumeAnnot) || sym.info.hasAnnotation(defn.ConsumeAnnot) - /** An update method is either a method marked with `update` or - * a setter of a non-transparent var. - */ - def isUpdateMethod(using Context): Boolean = - sym.isAllOf(Mutable | Method) - && (!sym.isSetter || sym.field.is(Transparent)) - - def inExclusivePartOf(cls: Symbol)(using Context): Exclusivity = - import Exclusivity.* - val encl = sym.enclosingMethodOrClass.skipConstructor - if sym == cls then OK // we are directly in `cls` or in one of its constructors - else if encl.owner == cls then - if encl.isUpdateMethod then OK - else NotInUpdateMethod(encl, cls) - else if encl.isStatic then OutsideClass(cls) - else encl.owner.inExclusivePartOf(cls) - - def isReadOnlyMethod(using Context): Boolean = - sym.is(Method, butNot = Mutable | Accessor) && sym.owner.derivesFrom(defn.Caps_Mutable) - - def isInReadOnlyMethod(using Context): Boolean = - if sym.is(Method) && sym.owner.isClass then isReadOnlyMethod - else sym.owner.isInReadOnlyMethod - def qualString(prefix: String)(using Context): String = if !sym.exists then "" else if sym.isAnonymousFunction then i" $prefix enclosing function" @@ -803,35 +753,3 @@ abstract class DeepTypeAccumulator[T](using Context) extends TypeAccumulator[T]: foldOver(acc, t) end DeepTypeAccumulator -/** Either OK, or a reason why capture set cannot be exclusive */ -enum Exclusivity: - case OK - - /** Enclosing symbol `sym` is a method of class `cls`, but not an update method */ - case NotInUpdateMethod(sym: Symbol, cls: Symbol) - - /** Access to `this` from outside its class (not sure this can happen) */ - case OutsideClass(cls: Symbol) - - /** A prefix type `tp` has a read-only capture set */ - case ReadOnly(tp: Type) - - def isOK: Boolean = this == OK - - def andAlso(that: Exclusivity): Exclusivity = - if this == OK then that else this - - /** A textual description why `qualType` is not exclusive */ - def description(qualType: Type)(using Context): String = this.runtimeChecked match - case Exclusivity.ReadOnly(tp) => - if qualType eq tp then - i"its capture set ${qualType.captureSet} is read-only" - else - i"the capture set ${tp.captureSet} of its prefix $tp is read-only" - case Exclusivity.NotInUpdateMethod(sym: Symbol, cls: Symbol) => - i"the access is in $sym, which is not an update method" - case Exclusivity.OutsideClass(cls: Symbol) => - i"the access from is from ouside $cls" - -end Exclusivity - diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala index 1b66bb5dadd2..d3ad7004d55e 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala @@ -152,9 +152,6 @@ sealed abstract class CaptureSet extends Showable: final def isExclusive(using Context): Boolean = elems.exists(_.isExclusive) - def exclusivity(tp: Type)(using Context): Exclusivity = - if isExclusive then Exclusivity.OK else Exclusivity.ReadOnly(tp) - /** Similar to isExlusive, but also includes capture set variables * with unknown status. */ diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 7be3235bf758..87b75cfc9854 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -28,6 +28,7 @@ import NameKinds.{DefaultGetterName, WildcardParamName, UniqueNameKind} import reporting.{trace, Message, OverrideError} import Annotations.Annotation import Capabilities.* +import Mutability.* import util.common.alwaysTrue import scala.annotation.constructorOnly @@ -689,11 +690,7 @@ class CheckCaptures extends Recheck, SymTransformer: // read-only-use.scala, where the error on r3 goes unreported. markPathFree(sel, pt.pt, pt.select) case _ => - if ref.derivesFromMutable then - if pt.isValueType && !pt.isMutableType || ref.exclusivityInContext != Exclusivity.OK - then markFree(ref.readOnly, tree) - else markFree(ref, tree) - else markFree(ref, tree) + markFree(ref.adjustReadOnly(pt), tree) /** The expected type for the qualifier of a selection. If the selection * could be part of a capability path or is a a read-only method, we return @@ -773,12 +770,6 @@ class CheckCaptures extends Recheck, SymTransformer: selType }//.showing(i"recheck sel $tree, $qualType = $result") - def checkUpdate(qualType: Type, pos: SrcPos)(msg: => String)(using Context): Unit = - qualType.exclusivityInContext match - case Exclusivity.OK => - case err => - report.error(em"$msg\nsince ${err.description(qualType)}.", pos) - /** Recheck applications, with special handling of unsafeAssumePure. * More work is done in `recheckApplication`, `recheckArg` and `instantiate` below. */ @@ -1773,101 +1764,6 @@ class CheckCaptures extends Recheck, SymTransformer: case _ => widened case _ => widened - /** If actual is a capturing type T^C extending Mutable, and expected is an - * unboxed non-singleton value type not extending mutable, narrow the capture - * set `C` to `ro(C)`. - * The unboxed condition ensures that the expected type is not a type variable - * that's upper bounded by a read-only type. In this case it would not be sound - * to narrow to the read-only set, since that set can be propagated - * by the type variable instantiation. - */ - private def improveReadOnly(actual: Type, expected: Type)(using Context): Type = reporting.trace(i"improv ro $actual vs $expected"): - actual.dealiasKeepAnnots match - case actual @ CapturingType(parent, refs) => - val parent1 = improveReadOnly(parent, expected) - val refs1 = - if parent1.derivesFrom(defn.Caps_Mutable) - && expected.isValueType - && (!expected.derivesFromMutable || expected.captureSet.isAlwaysReadOnly) - && !expected.isSingleton - && actual.isBoxedCapturing == expected.isBoxedCapturing - then refs.readOnly - else refs - actual.derivedCapturingType(parent1, refs1) - case actual @ FunctionOrMethod(aargs, ares) => - expected.dealias.stripCapturing match - case FunctionOrMethod(eargs, eres) => - actual.derivedFunctionOrMethod(aargs, improveReadOnly(ares, eres)) - case _ => - actual - case actual @ AppliedType(atycon, aargs) => - def improveArgs(aargs: List[Type], eargs: List[Type], formals: List[ParamInfo]): List[Type] = - aargs match - case aargs @ (aarg :: aargs1) => - val aarg1 = - if formals.head.paramVariance.is(Covariant) - then improveReadOnly(aarg, eargs.head) - else aarg - aargs.derivedCons(aarg1, improveArgs(aargs1, eargs.tail, formals.tail)) - case Nil => - aargs - val expected1 = expected.dealias.stripCapturing - val esym = expected1.typeSymbol - expected1 match - case AppliedType(etycon, eargs) => - if atycon.typeSymbol == esym then - actual.derivedAppliedType(atycon, - improveArgs(aargs, eargs, etycon.typeParams)) - else if esym.isClass then - // This case is tricky: Try to lift actual to the base type with class `esym`, - // improve the resulting arguments, and figure out if anything can be - // deduced from that for the original arguments. - actual.baseType(esym) match - case base @ AppliedType(_, bargs) => - // If any of the base type arguments can be improved, check - // whether they are the same as an original argument, and in this - // case improve the original argument. - val iargs = improveArgs(bargs, eargs, etycon.typeParams) - if iargs ne bargs then - val updates = - for - (barg, iarg) <- bargs.lazyZip(iargs) - if barg ne iarg - aarg <- aargs.find(_ eq barg) - yield (aarg, iarg) - if updates.nonEmpty then AppliedType(atycon, aargs.map(updates.toMap)) - else actual - else actual - case _ => actual - else actual - case _ => - actual - case actual @ RefinedType(aparent, aname, ainfo) => - expected.dealias.stripCapturing match - case RefinedType(eparent, ename, einfo) if aname == ename => - actual.derivedRefinedType( - improveReadOnly(aparent, eparent), - aname, - improveReadOnly(ainfo, einfo)) - case _ => - actual - case actual @ AnnotatedType(parent, ann) => - actual.derivedAnnotatedType(improveReadOnly(parent, expected), ann) - case _ => - actual - end improveReadOnly - - def adaptReadOnly(improved: Type, original: Type, expected: Type, tree: Tree)(using Context): Type = improved match - case improved @ CapturingType(parent, refs) - if parent.derivesFrom(defn.Caps_Mutable) - && expected.isValueType - && refs.isExclusive - && !original.exclusivityInContext.isOK => - improved.derivedCapturingType(parent, refs.readOnly) - .showing(i"Adapted readonly $improved for $tree with original = $original in ${ctx.owner} --> $result", capt) - case _ => - improved - /* Currently not needed since it forms part of `adapt` private def improve(actual: Type, prefix: Type)(using Context): Type = val widened = actual.widen.dealiasKeepAnnots @@ -1904,12 +1800,11 @@ class CheckCaptures extends Recheck, SymTransformer: // since they obscures the capturing type. val widened = actual.widen.dealiasKeepAnnots.dropUseAndConsumeAnnots val improvedVAR = improveCaptures(widened, actual) - val improved = improveReadOnly(improvedVAR, expected) - val adaptedReadOnly = adaptReadOnly(improved, actual, expected, tree) + val adaptedReadOnly = adaptReadOnly(improvedVAR, actual, expected, tree) val adapted = adaptBoxed( adaptedReadOnly.withReachCaptures(actual), expected, tree, covariant = true, alwaysConst = false) - if adapted eq improvedVAR // no .rd improvement or adaptation, no box-adaptation + if adapted eq improvedVAR // no read-only-adaptation, no reaches added, no box-adaptation then actual // might as well use actual instead of improved widened else adapted.showing(i"adapt $actual vs $expected = $adapted", capt) end adapt diff --git a/compiler/src/dotty/tools/dotc/cc/Mutability.scala b/compiler/src/dotty/tools/dotc/cc/Mutability.scala new file mode 100644 index 000000000000..6367299960dd --- /dev/null +++ b/compiler/src/dotty/tools/dotc/cc/Mutability.scala @@ -0,0 +1,236 @@ +package dotty.tools +package dotc +package cc + +import core.* +import Symbols.*, Types.*, Flags.*, Contexts.*, Names.*, Decorators.* +import Capabilities.* +import util.SrcPos +import config.Printers.capt +import ast.tpd.Tree + +/** Handling mutability and read-only access + */ +object Mutability: + + /** Either OK, or a reason why capture set cannot be exclusive */ + private enum Exclusivity: + case OK + + /** Enclosing symbol `sym` is a method of class `cls`, but not an update method */ + case NotInUpdateMethod(sym: Symbol, cls: Symbol) + + /** Access to `this` from outside its class (not sure this can happen) */ + case OutsideClass(cls: Symbol) + + /** A prefix type `tp` has a read-only capture set */ + case ReadOnly(tp: Type) + + def isOK: Boolean = this == OK + + def andAlso(that: Exclusivity): Exclusivity = + if this == OK then that else this + + /** A textual description why `qualType` is not exclusive */ + def description(qualType: Type)(using Context): String = this.runtimeChecked match + case Exclusivity.ReadOnly(tp) => + if qualType eq tp then + i"its capture set ${qualType.captureSet} is read-only" + else + i"the capture set ${tp.captureSet} of its prefix $tp is read-only" + case Exclusivity.NotInUpdateMethod(sym: Symbol, cls: Symbol) => + i"the access is in $sym, which is not an update method" + case Exclusivity.OutsideClass(cls: Symbol) => + i"the access from is from ouside $cls" + + end Exclusivity + + extension (sym: Symbol) + /** An update method is either a method marked with `update` or + * a setter of a non-transparent var. + */ + def isUpdateMethod(using Context): Boolean = + sym.isAllOf(Mutable | Method) + && (!sym.isSetter || sym.field.is(Transparent)) + + /** A read-only methid is a real method (not an accessor) in a type extending + * Mutable that is not an update method. + */ + def isReadOnlyMethod(using Context): Boolean = + sym.is(Method, butNot = Mutable | Accessor) && sym.owner.derivesFrom(defn.Caps_Mutable) + + private def inExclusivePartOf(cls: Symbol)(using Context): Exclusivity = + import Exclusivity.* + val encl = sym.enclosingMethodOrClass.skipConstructor + if sym == cls then OK // we are directly in `cls` or in one of its constructors + else if encl.owner == cls then + if encl.isUpdateMethod then OK + else NotInUpdateMethod(encl, cls) + else if encl.isStatic then OutsideClass(cls) + else encl.owner.inExclusivePartOf(cls) + + extension (tp: Type) + /** Is this a type extending `Mutable` that has non-private update methods + * or mutable fields? + */ + def isMutableType(using Context): Boolean = + tp.derivesFrom(defn.Caps_Mutable) + && tp.membersBasedOnFlags(Mutable, EmptyFlags).exists: mbr => + if mbr.symbol.is(Method) then mbr.symbol.isUpdateMethod + else !mbr.symbol.is(Transparent) + + /** OK, except if `tp` extends `Mutable` but `tp`'s capture set is non-exclusive */ + private def exclusivity(using Context): Exclusivity = + if tp.derivesFrom(defn.Caps_Mutable) then + tp match + case tp: Capability if tp.isExclusive => Exclusivity.OK + case _ => tp.captureSet.exclusivity(tp) + else Exclusivity.OK + + /** Test conditions (1) and (2) listed in `adaptReadOnly` below */ + private def exclusivityInContext(using Context): Exclusivity = tp match + case tp: ThisType => + if tp.derivesFrom(defn.Caps_Mutable) + then ctx.owner.inExclusivePartOf(tp.cls) + else Exclusivity.OK + case tp @ TermRef(prefix: Capability, _) => + prefix.exclusivityInContext.andAlso(tp.exclusivity) + case _ => + tp.exclusivity + + extension (cs: CaptureSet) + private def exclusivity(tp: Type)(using Context): Exclusivity = + if cs.isExclusive then Exclusivity.OK else Exclusivity.ReadOnly(tp) + + extension (ref: TermRef | ThisType) + /** Map `ref` to `ref.readOnly` if its type extends Mutble, and one of the + * following is true: it appears in a non-exclusive context, or the expected + * type is a value type that is not a mutable type. + */ + def adjustReadOnly(pt: Type)(using Context): Capability = + if ref.derivesFromMutable + && (pt.isValueType && !pt.isMutableType + || ref.exclusivityInContext != Exclusivity.OK) + then ref.readOnly + else ref + + /** Check that we can call an update method of `qualType` or perform an assignment + * of a field of `qualType`. + */ + def checkUpdate(qualType: Type, pos: SrcPos)(msg: => String)(using Context): Unit = + qualType.exclusivityInContext match + case Exclusivity.OK => + case err => + report.error(em"$msg\nsince ${err.description(qualType)}.", pos) + + /** Perform step (3) of adaptReadOnly below. + * + * If actual is a capturing type T^C extending Mutable, and expected is an + * unboxed non-singleton value type not extending mutable, widen the capture + * set `C` to `ro(C).reader`. + * The unboxed condition ensures that the expected type is not a type variable + * that's upper bounded by a read-only type. In this case it would not be sound + * to widen to the read-only set, since that set can be propagated + * by the type variable instantiation. + */ + private def adaptReadOnlyToExpected(actual: Type, expected: Type)(using Context): Type = reporting.trace(i"improv ro $actual vs $expected"): + actual.dealiasKeepAnnots match + case actual @ CapturingType(parent, refs) => + val parent1 = adaptReadOnlyToExpected(parent, expected) + val refs1 = + if parent1.derivesFrom(defn.Caps_Mutable) + && expected.isValueType + && (!expected.derivesFromMutable || expected.captureSet.isAlwaysReadOnly) + && !expected.isSingleton + && actual.isBoxedCapturing == expected.isBoxedCapturing + then refs.readOnly + else refs + actual.derivedCapturingType(parent1, refs1) + case actual @ FunctionOrMethod(aargs, ares) => + expected.dealias.stripCapturing match + case FunctionOrMethod(eargs, eres) => + actual.derivedFunctionOrMethod(aargs, adaptReadOnlyToExpected(ares, eres)) + case _ => + actual + case actual @ AppliedType(atycon, aargs) => + def improveArgs(aargs: List[Type], eargs: List[Type], formals: List[ParamInfo]): List[Type] = + aargs match + case aargs @ (aarg :: aargs1) => + val aarg1 = + if formals.head.paramVariance.is(Covariant) + then adaptReadOnlyToExpected(aarg, eargs.head) + else aarg + aargs.derivedCons(aarg1, improveArgs(aargs1, eargs.tail, formals.tail)) + case Nil => + aargs + val expected1 = expected.dealias.stripCapturing + val esym = expected1.typeSymbol + expected1 match + case AppliedType(etycon, eargs) => + if atycon.typeSymbol == esym then + actual.derivedAppliedType(atycon, + improveArgs(aargs, eargs, etycon.typeParams)) + else if esym.isClass then + // This case is tricky: Try to lift actual to the base type with class `esym`, + // improve the resulting arguments, and figure out if anything can be + // deduced from that for the original arguments. + actual.baseType(esym) match + case base @ AppliedType(_, bargs) => + // If any of the base type arguments can be improved, check + // whether they are the same as an original argument, and in this + // case improve the original argument. + val iargs = improveArgs(bargs, eargs, etycon.typeParams) + if iargs ne bargs then + val updates = + for + (barg, iarg) <- bargs.lazyZip(iargs) + if barg ne iarg + aarg <- aargs.find(_ eq barg) + yield (aarg, iarg) + if updates.nonEmpty then AppliedType(atycon, aargs.map(updates.toMap)) + else actual + else actual + case _ => actual + else actual + case _ => + actual + case actual @ RefinedType(aparent, aname, ainfo) => + expected.dealias.stripCapturing match + case RefinedType(eparent, ename, einfo) if aname == ename => + actual.derivedRefinedType( + adaptReadOnlyToExpected(aparent, eparent), + aname, + adaptReadOnlyToExpected(ainfo, einfo)) + case _ => + actual + case actual @ AnnotatedType(parent, ann) => + actual.derivedAnnotatedType(adaptReadOnlyToExpected(parent, expected), ann) + case _ => + actual + end adaptReadOnlyToExpected + + /** Adapt type `actual` so that it represents a read-only access + * if needed. `actual` is the widened version of original with capture + * set improved by the VAR rule. The conditions for adaptation are + * as follows (see modularity.md, section "Read-Only Accesses" for context) + * 1. The `original` reference is a `this` of a type extending Mutable and + * the access is not from an update method of the class of `this`. + * 2. The `original` reference refers to a type extending Mutable and is a path + * where a prefix of that path has a read-only capture set. + * 3. The expected type corresponding to some part of `actual` that refers + * to a type extending Mutable is a value type that is not a mutable type. + * In that case this part is adapted to a read-only capture set. + */ + def adaptReadOnly(actual: Type, original: Type, expected: Type, tree: Tree)(using Context): Type = + adaptReadOnlyToExpected(actual, expected) match + case improved @ CapturingType(parent, refs) + if parent.derivesFrom(defn.Caps_Mutable) + && expected.isValueType + && refs.isExclusive + && !original.exclusivityInContext.isOK => + improved.derivedCapturingType(parent, refs.readOnly) + .showing(i"Adapted readonly $improved for $tree with original = $original in ${ctx.owner} --> $result", capt) + case improved => + improved + +end Mutability \ No newline at end of file diff --git a/compiler/src/dotty/tools/dotc/cc/SepCheck.scala b/compiler/src/dotty/tools/dotc/cc/SepCheck.scala index d951ca3a572d..c2626c7fb4b2 100644 --- a/compiler/src/dotty/tools/dotc/cc/SepCheck.scala +++ b/compiler/src/dotty/tools/dotc/cc/SepCheck.scala @@ -5,8 +5,7 @@ import ast.tpd import collection.mutable import core.* -import Symbols.*, Types.*, Flags.* -import Contexts.*, Names.*, Flags.*, Symbols.*, Decorators.* +import Symbols.*, Types.*, Flags.*, Contexts.*, Names.*, Decorators.* import CaptureSet.{Refs, emptyRefs, HiddenSet} import config.Printers.capt import StdNames.nme diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 3714878a42ee..b8de2c7a9115 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -31,6 +31,7 @@ import config.SourceVersion.* import dotty.tools.dotc.util.SourcePosition import dotty.tools.dotc.ast.untpd.{MemberDef, Modifiers, PackageDef, RefTree, Template, TypeDef, ValOrDefDef} import cc.* +import cc.Mutability.isUpdateMethod import dotty.tools.dotc.parsing.JavaParsers import dotty.tools.dotc.transform.TreeExtractors.BinaryOp diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index 9d8a30cb8c6d..7f52c871f9de 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -37,7 +37,8 @@ import config.Feature, Feature.{sourceVersion, modularity} import config.SourceVersion.* import config.MigrationVersion import printing.Formatting.hlAsKeyword -import cc.{isCaptureChecking, isRetainsLike, isUpdateMethod} +import cc.{isCaptureChecking, isRetainsLike} +import cc.Mutability.isUpdateMethod import collection.mutable import reporting.* diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index 0f1a251ccff8..0605228d3a6e 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -21,7 +21,8 @@ import config.MigrationVersion import config.Printers.refcheck import reporting.* import Constants.Constant -import cc.{stripCapturing, isUpdateMethod, CCState} +import cc.{stripCapturing, CCState} +import cc.Mutability.isUpdateMethod object RefChecks { import tpd.*