From b860100f81754fe0535793a8c75921feab98cf29 Mon Sep 17 00:00:00 2001 From: odersky Date: Sun, 21 Jan 2024 11:50:35 +0100 Subject: [PATCH 1/8] Avoid forming intersections for dependent function types Avoid forming an intersection when selecting the apply method of a dependent function type. --- .../src/dotty/tools/dotc/core/Definitions.scala | 13 ++++++------- compiler/src/dotty/tools/dotc/core/Types.scala | 6 ++++-- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 4bc427ee0687..648254842e1b 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -1171,19 +1171,18 @@ class Definitions { } } - object RefinedFunctionOf { + object RefinedFunctionOf: + /** Matches a refined `PolyFunction`/`FunctionN[...]`/`ContextFunctionN[...]`. * Extracts the method type type and apply info. */ - def unapply(tpe: RefinedType)(using Context): Option[MethodOrPoly] = { + def unapply(tpe: RefinedType)(using Context): Option[MethodOrPoly] = tpe.refinedInfo match case mt: MethodOrPoly - if tpe.refinedName == nme.apply - && (tpe.parent.derivesFrom(defn.PolyFunctionClass) || isFunctionNType(tpe.parent)) => - Some(mt) + if tpe.refinedName == nme.apply && isFunctionType(tpe.parent) => Some(mt) case _ => None - } - } + + end RefinedFunctionOf object PolyFunctionOf { diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index b4ac059a4fc5..038b72ef25e0 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -845,7 +845,9 @@ object Types extends TypeUtils { safeIntersection = ctx.base.pendingMemberSearches.contains(name)) joint match case joint: SingleDenotation - if isRefinedMethod && rinfo <:< joint.info => + if isRefinedMethod + && (rinfo <:< joint.info + || name == nme.apply && defn.isFunctionType(tp.parent)) => // use `rinfo` to keep the right parameter names for named args. See i8516.scala. joint.derivedSingleDenotation(joint.symbol, rinfo, pre, isRefinedMethod) case _ => @@ -6479,7 +6481,7 @@ object Types extends TypeUtils { protected def needsRangeIfInvariant(refs: CaptureSet): Boolean = true override def mapCapturingType(tp: Type, parent: Type, refs: CaptureSet, v: Int): Type = - if v == 0 && needsRangeIfInvariant(refs) then + if v == 0 && needsRangeIfInvariant(refs) /* && false*/ then range(mapCapturingType(tp, parent, refs, -1), mapCapturingType(tp, parent, refs, 1)) else super.mapCapturingType(tp, parent, refs, v) From 11aabcf75d4aa9fb023537ed7d1c10879129d5af Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 23 Jan 2024 17:02:34 +0100 Subject: [PATCH 2/8] Introduce maybe capabilities x? Introduce maybe capabilities x? to handle the case where a capture set appears invariantly in its surrounding type. Maybe capabilities are similar to TypeBounds types, but restricted to capture sets. For instance, Array[C^{x?}] should be morally equivaelent to Array[_ >: C^{} <: C^{x}] but it has fewer issues with type inference. --- .../src/dotty/tools/dotc/cc/CaptureOps.scala | 39 +++++++++++++++--- .../src/dotty/tools/dotc/cc/CaptureSet.scala | 40 ++++++++++++++++--- .../dotty/tools/dotc/cc/CheckCaptures.scala | 5 --- .../dotty/tools/dotc/core/Definitions.scala | 1 + .../src/dotty/tools/dotc/core/TypeOps.scala | 12 ------ .../src/dotty/tools/dotc/core/Types.scala | 26 ++++++------ .../tools/dotc/printing/PlainPrinter.scala | 3 +- .../annotation/internal/maybeCapability.scala | 8 ++++ .../{reach.scala => reachCapability.scala} | 0 .../src/scala/collection/Factory.scala | 17 ++++---- tests/pos/invariant-cc.scala | 11 +++++ 11 files changed, 112 insertions(+), 50 deletions(-) create mode 100644 library/src/scala/annotation/internal/maybeCapability.scala rename library/src/scala/annotation/internal/{reach.scala => reachCapability.scala} (100%) create mode 100644 tests/pos/invariant-cc.scala diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index 8073540d6b24..8d2a5a0a927d 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -220,9 +220,31 @@ extension (tp: Type) * type of `x`. If `x` and `y` are different variables then `{x*}` and `{y*}` * are unrelated. */ - def reach(using Context): CaptureRef = - assert(tp.isTrackableRef) - AnnotatedType(tp, Annotation(defn.ReachCapabilityAnnot, util.Spans.NoSpan)) + def reach(using Context): CaptureRef = tp match + case tp: CaptureRef if tp.isTrackableRef => + if tp.isReach then tp else ReachCapability(tp) + + /** If `x` is a capture ref, its maybe capability `x?`, represented internally + * as `x @maybeCapability`. `x?` stands for a capability `x` that might or might + * not be part of a capture set. We have `{} <: {x?} <: {x}`. Maybe capabilities + * cannot be propagated between sets. If `a <: b` and `a` acquires `x?` then + * `x` is propagated to `b` as a conservative approximation. + * + * Maybe capabilities should only arise for caoture sets that appear in invariant + * position in their surrounding type. They are similar to TypeBunds types, but + * restricted to capture sets. For instance, + * + * Array[C^{x?}] + * + * should be morally equivaelent to + * + * Array[_ >: C^{} <: C^{x}] + * + * but it has fewer issues with type inference. + */ + def maybe(using Context): CaptureRef = tp match + case tp: CaptureRef if tp.isTrackableRef => + if tp.isMaybe then tp else MaybeCapability(tp) /** If `ref` is a trackable capture ref, and `tp` has only covariant occurrences of a * universal capture set, replace all these occurrences by `{ref*}`. This implements @@ -422,9 +444,14 @@ object ReachCapabilityApply: /** An extractor for `ref @annotation.internal.reachCapability`, which is used to express * the reach capability `ref*` as a type. */ -object ReachCapability: +class AnnotatedCapability(annot: Context ?=> ClassSymbol): + def apply(tp: Type)(using Context) = + AnnotatedType(tp, Annotation(annot, util.Spans.NoSpan)) def unapply(tree: AnnotatedType)(using Context): Option[SingletonCaptureRef] = tree match - case AnnotatedType(parent: SingletonCaptureRef, ann) - if ann.symbol == defn.ReachCapabilityAnnot => Some(parent) + case AnnotatedType(parent: SingletonCaptureRef, ann) if ann.symbol == annot => Some(parent) case _ => None +object ReachCapability extends AnnotatedCapability(defn.ReachCapabilityAnnot) +object MaybeCapability extends AnnotatedCapability(defn.MaybeCapabilityAnnot) + + diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala index 673c85479218..d1a5a07f6a0f 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala @@ -145,15 +145,16 @@ sealed abstract class CaptureSet extends Showable: /** x subsumes x * this subsumes this.f - * x subsumes y ==> x* subsumes y - * x subsumes y ==> x* subsumes y* + * x subsumes y ==> x* subsumes y, x subsumes y? + * x subsumes y ==> x* subsumes y*, x? subsumes y? */ extension (x: CaptureRef) private def subsumes(y: CaptureRef)(using Context): Boolean = (x eq y) || x.isRootCapability || y.match - case y: TermRef => !y.isReach && (y.prefix eq x) + case y: TermRef => y.prefix eq x + case MaybeCapability(y1) => x.stripMaybe.subsumes(y1) case _ => false || x.match case ReachCapability(x1) => x1.subsumes(y.stripReach) @@ -312,6 +313,8 @@ sealed abstract class CaptureSet extends Showable: def substParams(tl: BindingType, to: List[Type])(using Context) = map(Substituters.SubstParamsMap(tl, to)) + def maybe(using Context): CaptureSet = map(MaybeMap()) + /** Invoke handler if this set has (or later aquires) the root capability `cap` */ def disallowRootCapability(handler: () => Context ?=> Unit)(using Context): this.type = if isUniversal then handler() @@ -445,6 +448,8 @@ object CaptureSet: def isConst = isSolved def isAlwaysEmpty = false + def isMaybeSet = false // overridden in BiMapped + /** A handler to be invoked if the root reference `cap` is added to this set */ var rootAddedHandler: () => Context ?=> Unit = () => () @@ -490,9 +495,10 @@ object CaptureSet: if elem.isRootCapability then rootAddedHandler() newElemAddedHandler(elem) + val normElem = if isMaybeSet then elem else elem.stripMaybe // assert(id != 5 || elems.size != 3, this) val res = (CompareResult.OK /: deps): (r, dep) => - r.andAlso(dep.tryInclude(elem, this)) + r.andAlso(dep.tryInclude(normElem, this)) res.orElse: elems -= elem res.addToTrace(this) @@ -508,6 +514,8 @@ object CaptureSet: levelLimit.isContainedIn(elem.cls.levelOwner) case ReachCapability(elem1) => levelOK(elem1) + case MaybeCapability(elem1) => + levelOK(elem1) case _ => true @@ -760,6 +768,7 @@ object CaptureSet: if source eq origin then supApprox.map(bimap.inverse) else source.upperApprox(this).map(bimap) ** supApprox + override def isMaybeSet: Boolean = bimap.isInstanceOf[MaybeMap] override def toString = s"BiMapped$id($source, elems = $elems)" end BiMapped @@ -840,8 +849,7 @@ object CaptureSet: upper.isAlwaysEmpty || upper.isConst && upper.elems.size == 1 && upper.elems.contains(r1) if variance > 0 || isExact then upper else if variance < 0 then CaptureSet.empty - else if ctx.mode.is(Mode.Printing) then upper - else assert(false, i"trying to add $upper from $r via ${tm.getClass} in a non-variant setting") + else upper.maybe /** Apply `f` to each element in `xs`, and join result sets with `++` */ def mapRefs(xs: Refs, f: CaptureRef => CaptureSet)(using Context): CaptureSet = @@ -980,6 +988,26 @@ object CaptureSet: /** The current VarState, as passed by the implicit context */ def varState(using state: VarState): VarState = state + /** Maps `x` to `x?` */ + private class MaybeMap(using Context) extends BiTypeMap: + + def apply(t: Type) = t match + case t: CaptureRef if t.isTrackableRef => t.maybe + case _ => mapOver(t) + + override def toString = "Maybe" + + lazy val inverse = new BiTypeMap: + + def apply(t: Type) = t match + case t: CaptureRef if t.isMaybe => t.stripMaybe + case t => mapOver(t) + + def inverse = MaybeMap.this + + override def toString = "Maybe.inverse" + end MaybeMap + /* Not needed: def ofClass(cinfo: ClassInfo, argTypes: List[Type])(using Context): CaptureSet = CaptureSet.empty diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 9f6149dba548..131dcf972266 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -68,11 +68,6 @@ object CheckCaptures: */ final class SubstParamsMap(from: BindingType, to: List[Type])(using Context) extends ApproximatingTypeMap, IdempotentCaptRefMap: - /** This SubstParamsMap is exact if `to` only contains `CaptureRef`s. */ - private val isExactSubstitution: Boolean = to.forall(_.isTrackableRef) - - /** As long as this substitution is exact, there is no need to create `Range`s when mapping invariant positions. */ - override protected def needsRangeIfInvariant(refs: CaptureSet): Boolean = !isExactSubstitution def apply(tp: Type): Type = tp match diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 648254842e1b..2116e3eab7a7 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -1054,6 +1054,7 @@ class Definitions { @tu lazy val FunctionalInterfaceAnnot: ClassSymbol = requiredClass("java.lang.FunctionalInterface") @tu lazy val TargetNameAnnot: ClassSymbol = requiredClass("scala.annotation.targetName") @tu lazy val VarargsAnnot: ClassSymbol = requiredClass("scala.annotation.varargs") + @tu lazy val MaybeCapabilityAnnot = requiredClass("scala.annotation.internal.maybeCapability") @tu lazy val ReachCapabilityAnnot = requiredClass("scala.annotation.internal.reachCapability") @tu lazy val RequiresCapabilityAnnot: ClassSymbol = requiredClass("scala.annotation.internal.requiresCapability") @tu lazy val RetainsAnnot: ClassSymbol = requiredClass("scala.annotation.retains") diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index 2005aa702782..587c52688456 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -540,18 +540,6 @@ object TypeOps: val sym = tp.symbol forbidden.contains(sym) - /** We need to split the set into upper and lower approximations - * only if it contains a local element. The idea here is that at the - * time we perform an `avoid` all local elements are already accounted for - * and no further elements will be added afterwards. So we can just keep - * the set as it is. See comment by @linyxus on #16261. - */ - override def needsRangeIfInvariant(refs: CaptureSet): Boolean = - refs.elems.exists { - case ref: TermRef => toAvoid(ref) - case _ => false - } - override def apply(tp: Type): Type = tp match case tp: TypeVar if mapCtx.typerState.constraint.contains(tp) => val lo = TypeComparer.instanceType( diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 038b72ef25e0..b3f777a5a6bb 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -2200,7 +2200,11 @@ object Types extends TypeUtils { /** Is this a reach reference of the form `x*`? */ def isReach(using Context): Boolean = false // overridden in AnnotatedType + /** Is this a maybe reference of the form `x?`? */ + def isMaybe(using Context): Boolean = false // overridden in AnnotatedType + def stripReach(using Context): CaptureRef = this // overridden in AnnotatedType + def stripMaybe(using Context): CaptureRef = this // overridden in AnnotatedType /** Is this reference the generic root capability `cap` ? */ def isRootCapability(using Context): Boolean = false @@ -5620,14 +5624,21 @@ object Types extends TypeUtils { } override def isTrackableRef(using Context) = - isReach && parent.isTrackableRef + (isReach || isMaybe) && parent.isTrackableRef /** Is this a reach reference of the form `x*`? */ override def isReach(using Context): Boolean = annot.symbol == defn.ReachCapabilityAnnot - override def stripReach(using Context): SingletonCaptureRef = - (if isReach then parent else this).asInstanceOf[SingletonCaptureRef] + /** Is this a reach reference of the form `x*`? */ + override def isMaybe(using Context): Boolean = + annot.symbol == defn.MaybeCapabilityAnnot + + override def stripReach(using Context): CaptureRef = + if isReach then parent.asInstanceOf[CaptureRef] else this + + override def stripMaybe(using Context): CaptureRef = + if isMaybe then parent.asInstanceOf[CaptureRef] else this override def normalizedRef(using Context): CaptureRef = if isReach then AnnotatedType(stripReach.normalizedRef, annot) else this @@ -6477,15 +6488,6 @@ object Types extends TypeUtils { tp.derivedLambdaType(tp.paramNames, formals, restpe) } - /** Overridden in TypeOps.avoid and in CheckCaptures.substParamsMap */ - protected def needsRangeIfInvariant(refs: CaptureSet): Boolean = true - - override def mapCapturingType(tp: Type, parent: Type, refs: CaptureSet, v: Int): Type = - if v == 0 && needsRangeIfInvariant(refs) /* && false*/ then - range(mapCapturingType(tp, parent, refs, -1), mapCapturingType(tp, parent, refs, 1)) - else - super.mapCapturingType(tp, parent, refs, v) - protected def reapply(tp: Type): Type = apply(tp) } diff --git a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala index 83e6a3b204c3..8fc0c568e125 100644 --- a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -15,7 +15,7 @@ import util.SourcePosition import scala.util.control.NonFatal import scala.annotation.switch import config.{Config, Feature} -import cc.{CapturingType, RetainingType, CaptureSet, ReachCapability, isBoxed, levelOwner, retainedElems} +import cc.{CapturingType, RetainingType, CaptureSet, ReachCapability, MaybeCapability, isBoxed, levelOwner, retainedElems} class PlainPrinter(_ctx: Context) extends Printer { @@ -404,6 +404,7 @@ class PlainPrinter(_ctx: Context) extends Printer { case tp: TermRef if tp.symbol == defn.captureRoot => Str("cap") case tp: SingletonType => toTextRef(tp) case ReachCapability(tp1) => toTextRef(tp1) ~ "*" + case MaybeCapability(tp1) => toTextRef(tp1) ~ "?" case _ => toText(tp) protected def isOmittablePrefix(sym: Symbol): Boolean = diff --git a/library/src/scala/annotation/internal/maybeCapability.scala b/library/src/scala/annotation/internal/maybeCapability.scala new file mode 100644 index 000000000000..ebb8f6ec4833 --- /dev/null +++ b/library/src/scala/annotation/internal/maybeCapability.scala @@ -0,0 +1,8 @@ +package scala.annotation +package internal + +/** An annotation that marks a capture ref as a maybe capability. + * `x?` is encoded as `x.type @maybeCapability` + */ +class maybeCapability extends StaticAnnotation + diff --git a/library/src/scala/annotation/internal/reach.scala b/library/src/scala/annotation/internal/reachCapability.scala similarity index 100% rename from library/src/scala/annotation/internal/reach.scala rename to library/src/scala/annotation/internal/reachCapability.scala diff --git a/scala2-library-cc/src/scala/collection/Factory.scala b/scala2-library-cc/src/scala/collection/Factory.scala index c50fa395a0fb..e2c0bb978b17 100644 --- a/scala2-library-cc/src/scala/collection/Factory.scala +++ b/scala2-library-cc/src/scala/collection/Factory.scala @@ -160,8 +160,8 @@ trait IterableFactory[+CC[_]] extends Serializable, Pure { * @param elem the element computation * @return A $coll that contains the results of `n1 x n2` evaluations of `elem`. */ - def fill[A](n1: Int, n2: Int)(elem: => A): CC[CC[A] @uncheckedVariance]^{elem} = // !!! problem with checking rhs under cc - ??? // fill(n1)(fill(n2)(elem)) + def fill[A](n1: Int, n2: Int)(elem: => A): CC[(CC[A]^{elem}) @uncheckedVariance]^{elem} = + fill[CC[A]^{elem}](n1)(fill(n2)(elem)) // !!! explicit type argument required under cc /** Produces a three-dimensional $coll containing the results of some element computation a number of times. * @param n1 the number of elements in the 1st dimension @@ -170,8 +170,8 @@ trait IterableFactory[+CC[_]] extends Serializable, Pure { * @param elem the element computation * @return A $coll that contains the results of `n1 x n2 x n3` evaluations of `elem`. */ - def fill[A](n1: Int, n2: Int, n3: Int)(elem: => A): CC[CC[CC[A]] @uncheckedVariance]^{elem} = // !!! problem with checking rhs under cc - ??? // fill(n1)(fill(n2, n3)(elem)).unsafeAssumePure + def fill[A](n1: Int, n2: Int, n3: Int)(elem: => A): CC[(CC[CC[A]^{elem}]^{elem}) @uncheckedVariance]^{elem} = // !!! problem with checking rhs under cc + fill[CC[CC[A]^{elem}]^{elem}](n1)(fill(n2, n3)(elem)) // !!! explicit type argument required under cc /** Produces a four-dimensional $coll containing the results of some element computation a number of times. * @param n1 the number of elements in the 1st dimension @@ -181,8 +181,8 @@ trait IterableFactory[+CC[_]] extends Serializable, Pure { * @param elem the element computation * @return A $coll that contains the results of `n1 x n2 x n3 x n4` evaluations of `elem`. */ - def fill[A](n1: Int, n2: Int, n3: Int, n4: Int)(elem: => A): CC[CC[CC[CC[A]]] @uncheckedVariance]^{elem} = // !!! problem with checking rhs under cc - ??? // fill(n1)(fill(n2, n3, n4)(elem)) + def fill[A](n1: Int, n2: Int, n3: Int, n4: Int)(elem: => A): CC[(CC[CC[CC[A]^{elem}]^{elem}]^{elem}) @uncheckedVariance]^{elem} = // !!! problem with checking rhs under cc + fill[CC[CC[CC[A]^{elem}]^{elem}]^{elem}](n1)(fill(n2, n3, n4)(elem)) // !!! explicit type argument required under cc /** Produces a five-dimensional $coll containing the results of some element computation a number of times. * @param n1 the number of elements in the 1st dimension @@ -193,8 +193,9 @@ trait IterableFactory[+CC[_]] extends Serializable, Pure { * @param elem the element computation * @return A $coll that contains the results of `n1 x n2 x n3 x n4 x n5` evaluations of `elem`. */ - def fill[A](n1: Int, n2: Int, n3: Int, n4: Int, n5: Int)(elem: => A): CC[CC[CC[CC[CC[A]]]] @uncheckedVariance]^{elem} = // !!! problem with checking rhs under cc - ??? // fill(n1)(fill(n2, n3, n4, n5)(elem)) + def fill[A](n1: Int, n2: Int, n3: Int, n4: Int, n5: Int)(elem: => A): CC[(CC[CC[CC[CC[A]^{elem}]^{elem}]^{elem}]^{elem}) @uncheckedVariance]^{elem} = // !!! problem with checking rhs under cc + fill[CC[CC[CC[CC[A]^{elem}]^{elem}]^{elem}]^{elem}](n1)(fill(n2, n3, n4, n5)(elem)) // !!! explicit type argument required under cc + /** Produces a $coll containing values of a given function over a range of integer values starting from 0. * @param n The number of elements in the $coll diff --git a/tests/pos/invariant-cc.scala b/tests/pos/invariant-cc.scala new file mode 100644 index 000000000000..4412322152bf --- /dev/null +++ b/tests/pos/invariant-cc.scala @@ -0,0 +1,11 @@ +import language.experimental.captureChecking +import scala.annotation.unchecked.uncheckedVariance + +trait IterableFactory[+CC[_]] extends Pure: + + def fill[A](n: Int)(elem: => A): CC[A]^{elem} = ??? + def fill[A](n1: Int, n2: Int)(elem: => A): CC[(CC[A]^{elem}) @uncheckedVariance]^{elem} = + fill[CC[A]^{elem}](n1)(fill(n2)(elem)) // !!! explicit type argument required under cc + // fill(n1)(fill(n2)(elem)) // !!! does not currently work + + From 82405235a74c00daae297bcb8c1bf63b2b045ec2 Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 23 Jan 2024 18:43:24 +0100 Subject: [PATCH 3/8] Treat result of by-name closures as inferred --- compiler/src/dotty/tools/dotc/ast/tpd.scala | 10 +++---- .../dotty/tools/dotc/cc/CheckCaptures.scala | 1 - .../tools/dotc/transform/ElimByName.scala | 3 ++- tests/neg-custom-args/captures/byname.check | 26 ++++++++++++++----- tests/neg-custom-args/captures/byname.scala | 6 ++--- tests/pos/invariant-cc.scala | 11 ++++++-- 6 files changed, 38 insertions(+), 19 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index 44fa4e9b22fd..a9f786e4c478 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -117,14 +117,14 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { * where the closure's type is the target type of the expression (FunctionN, unless * otherwise specified). */ - def Closure(meth: TermSymbol, rhsFn: List[List[Tree]] => Tree, targs: List[Tree] = Nil, targetType: Type = NoType)(using Context): Block = { - val targetTpt = if (targetType.exists) TypeTree(targetType) else EmptyTree + def Closure(meth: TermSymbol, rhsFn: List[List[Tree]] => Tree, targs: List[Tree] = Nil, targetType: Type = NoType, inferred: Boolean = false)(using Context): Block = { + val targetTpt = if (targetType.exists) TypeTree(targetType, inferred) else EmptyTree val call = if (targs.isEmpty) Ident(TermRef(NoPrefix, meth)) else TypeApply(Ident(TermRef(NoPrefix, meth)), targs) - Block( - DefDef(meth, rhsFn) :: Nil, - Closure(Nil, call, targetTpt)) + var mdef = DefDef(meth, rhsFn) + if inferred then mdef = cpy.DefDef(mdef)(tpt = TypeTree(mdef.tpt.tpe, inferred)) + Block(mdef :: Nil, Closure(Nil, call, targetTpt)) } /** A closure whose anonymous function has the given method type */ diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 131dcf972266..4564bed6db01 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -68,7 +68,6 @@ object CheckCaptures: */ final class SubstParamsMap(from: BindingType, to: List[Type])(using Context) extends ApproximatingTypeMap, IdempotentCaptRefMap: - def apply(tp: Type): Type = tp match case tp: ParamRef => diff --git a/compiler/src/dotty/tools/dotc/transform/ElimByName.scala b/compiler/src/dotty/tools/dotc/transform/ElimByName.scala index eca3928569f1..b244736fdb82 100644 --- a/compiler/src/dotty/tools/dotc/transform/ElimByName.scala +++ b/compiler/src/dotty/tools/dotc/transform/ElimByName.scala @@ -102,7 +102,8 @@ class ElimByName extends MiniPhase, InfoTransformer: val meth = newAnonFun(ctx.owner, MethodType(Nil, argType), coord = arg.span) Closure(meth, _ => arg.changeOwnerAfter(ctx.owner, meth, thisPhase), - targetType = defn.ByNameFunction(argType) + targetType = defn.ByNameFunction(argType), + inferred = true ).withSpan(arg.span) private def isByNameRef(tree: Tree)(using Context): Boolean = diff --git a/tests/neg-custom-args/captures/byname.check b/tests/neg-custom-args/captures/byname.check index 61b83fc24688..226bee2cd0e5 100644 --- a/tests/neg-custom-args/captures/byname.check +++ b/tests/neg-custom-args/captures/byname.check @@ -1,12 +1,24 @@ --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/byname.scala:10:6 ---------------------------------------- -10 | h(f2()) // error - | ^^^^ - | Found: (x$0: Int) ->{cap1} Int - | Required: (x$0: Int) ->{cap2} Int - | - | longer explanation available when compiling with `-explain` -- Error: tests/neg-custom-args/captures/byname.scala:19:5 ------------------------------------------------------------- 19 | h(g()) // error | ^^^ | reference (cap2 : Cap^) is not included in the allowed capture set {cap1} | of an enclosing function literal with expected type () ?->{cap1} I +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/byname.scala:4:2 ----------------------------------------- + 4 | def f() = if cap1 == cap1 then g else g // error + | ^ + | Found: (x$0: Int) ->{cap2} Int + | Required: (x$0: Int) -> Int + | + | Note that the expected type Int => Int + | is the previously inferred result type of method test + | which is also the type seen in separately compiled sources. + | The new inferred type (x$0: Int) ->{cap2} Int + | must conform to this type. + 5 | def g(x: Int) = if cap2 == cap2 then 1 else x + 6 | def g2(x: Int) = if cap1 == cap1 then 1 else x + 7 | def f2() = if cap1 == cap1 then g2 else g2 + 8 | def h(ff: => Int ->{cap2} Int) = ff + 9 | h(f()) +10 | h(f2()) + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/byname.scala b/tests/neg-custom-args/captures/byname.scala index ac13174eb4f4..279122f54735 100644 --- a/tests/neg-custom-args/captures/byname.scala +++ b/tests/neg-custom-args/captures/byname.scala @@ -1,13 +1,13 @@ @annotation.capability class Cap def test(cap1: Cap, cap2: Cap) = - def f() = if cap1 == cap1 then g else g + def f() = if cap1 == cap1 then g else g // error def g(x: Int) = if cap2 == cap2 then 1 else x def g2(x: Int) = if cap1 == cap1 then 1 else x def f2() = if cap1 == cap1 then g2 else g2 def h(ff: => Int ->{cap2} Int) = ff - h(f()) // ok - h(f2()) // error + h(f()) + h(f2()) class I diff --git a/tests/pos/invariant-cc.scala b/tests/pos/invariant-cc.scala index 4412322152bf..347b0d2fda64 100644 --- a/tests/pos/invariant-cc.scala +++ b/tests/pos/invariant-cc.scala @@ -5,7 +5,14 @@ trait IterableFactory[+CC[_]] extends Pure: def fill[A](n: Int)(elem: => A): CC[A]^{elem} = ??? def fill[A](n1: Int, n2: Int)(elem: => A): CC[(CC[A]^{elem}) @uncheckedVariance]^{elem} = - fill[CC[A]^{elem}](n1)(fill(n2)(elem)) // !!! explicit type argument required under cc - // fill(n1)(fill(n2)(elem)) // !!! does not currently work + fill[CC[A]^{elem}](n1)(fill(n2)(elem)) + fill(n1)(fill(n2)(elem)) + + def fill2[A](n: Int)(elem: () => A): CC[A]^{elem} = ??? + def fill2[A](n1: Int, n2: Int)(elem: () => A): CC[(CC[A]^{elem}) @uncheckedVariance]^{elem} = + fill2[CC[A]^{elem}](n1)(() => fill2(n2)(elem)) + fill2(n1)(() => fill2(n2)(elem)) + + From 16cae115051add764766f399e975f37eb5d7ac5e Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 23 Jan 2024 19:07:19 +0100 Subject: [PATCH 4/8] Update scala2-library-cc to make use of inference fixes Most hacks using `asInstanceOf` or `???` are no longer necessary. --- .../src/scala/collection/Factory.scala | 31 +++++++++---------- .../src/scala/collection/Iterable.scala | 2 +- .../src/scala/collection/MapView.scala | 2 +- .../scala/collection/concurrent/TrieMap.scala | 5 +-- .../scala/collection/immutable/HashMap.scala | 2 +- .../scala/collection/immutable/HashSet.scala | 2 +- 6 files changed, 20 insertions(+), 24 deletions(-) diff --git a/scala2-library-cc/src/scala/collection/Factory.scala b/scala2-library-cc/src/scala/collection/Factory.scala index e2c0bb978b17..5392d7e2c642 100644 --- a/scala2-library-cc/src/scala/collection/Factory.scala +++ b/scala2-library-cc/src/scala/collection/Factory.scala @@ -161,7 +161,7 @@ trait IterableFactory[+CC[_]] extends Serializable, Pure { * @return A $coll that contains the results of `n1 x n2` evaluations of `elem`. */ def fill[A](n1: Int, n2: Int)(elem: => A): CC[(CC[A]^{elem}) @uncheckedVariance]^{elem} = - fill[CC[A]^{elem}](n1)(fill(n2)(elem)) // !!! explicit type argument required under cc + fill(n1)(fill(n2)(elem)) /** Produces a three-dimensional $coll containing the results of some element computation a number of times. * @param n1 the number of elements in the 1st dimension @@ -170,8 +170,8 @@ trait IterableFactory[+CC[_]] extends Serializable, Pure { * @param elem the element computation * @return A $coll that contains the results of `n1 x n2 x n3` evaluations of `elem`. */ - def fill[A](n1: Int, n2: Int, n3: Int)(elem: => A): CC[(CC[CC[A]^{elem}]^{elem}) @uncheckedVariance]^{elem} = // !!! problem with checking rhs under cc - fill[CC[CC[A]^{elem}]^{elem}](n1)(fill(n2, n3)(elem)) // !!! explicit type argument required under cc + def fill[A](n1: Int, n2: Int, n3: Int)(elem: => A): CC[(CC[CC[A]^{elem}]^{elem}) @uncheckedVariance]^{elem} = + fill(n1)(fill(n2, n3)(elem)) /** Produces a four-dimensional $coll containing the results of some element computation a number of times. * @param n1 the number of elements in the 1st dimension @@ -181,8 +181,8 @@ trait IterableFactory[+CC[_]] extends Serializable, Pure { * @param elem the element computation * @return A $coll that contains the results of `n1 x n2 x n3 x n4` evaluations of `elem`. */ - def fill[A](n1: Int, n2: Int, n3: Int, n4: Int)(elem: => A): CC[(CC[CC[CC[A]^{elem}]^{elem}]^{elem}) @uncheckedVariance]^{elem} = // !!! problem with checking rhs under cc - fill[CC[CC[CC[A]^{elem}]^{elem}]^{elem}](n1)(fill(n2, n3, n4)(elem)) // !!! explicit type argument required under cc + def fill[A](n1: Int, n2: Int, n3: Int, n4: Int)(elem: => A): CC[(CC[CC[CC[A]^{elem}]^{elem}]^{elem}) @uncheckedVariance]^{elem} = + fill(n1)(fill(n2, n3, n4)(elem)) /** Produces a five-dimensional $coll containing the results of some element computation a number of times. * @param n1 the number of elements in the 1st dimension @@ -193,9 +193,8 @@ trait IterableFactory[+CC[_]] extends Serializable, Pure { * @param elem the element computation * @return A $coll that contains the results of `n1 x n2 x n3 x n4 x n5` evaluations of `elem`. */ - def fill[A](n1: Int, n2: Int, n3: Int, n4: Int, n5: Int)(elem: => A): CC[(CC[CC[CC[CC[A]^{elem}]^{elem}]^{elem}]^{elem}) @uncheckedVariance]^{elem} = // !!! problem with checking rhs under cc - fill[CC[CC[CC[CC[A]^{elem}]^{elem}]^{elem}]^{elem}](n1)(fill(n2, n3, n4, n5)(elem)) // !!! explicit type argument required under cc - + def fill[A](n1: Int, n2: Int, n3: Int, n4: Int, n5: Int)(elem: => A): CC[(CC[CC[CC[CC[A]^{elem}]^{elem}]^{elem}]^{elem}) @uncheckedVariance]^{elem} = + fill(n1)(fill(n2, n3, n4, n5)(elem)) /** Produces a $coll containing values of a given function over a range of integer values starting from 0. * @param n The number of elements in the $coll @@ -211,8 +210,8 @@ trait IterableFactory[+CC[_]] extends Serializable, Pure { * @return A $coll consisting of elements `f(i1, i2)` * for `0 <= i1 < n1` and `0 <= i2 < n2`. */ - def tabulate[A](n1: Int, n2: Int)(f: (Int, Int) => A): CC[CC[A] @uncheckedVariance]^{f} = // !!! problem with checking rhs under cc - ??? // tabulate(n1)(i1 => tabulate(n2)(f(i1, _))) + def tabulate[A](n1: Int, n2: Int)(f: (Int, Int) => A): CC[(CC[A]^{f}) @uncheckedVariance]^{f} = + tabulate(n1)(i1 => tabulate(n2)(f(i1, _))) /** Produces a three-dimensional $coll containing values of a given function over ranges of integer values starting from 0. * @param n1 the number of elements in the 1st dimension @@ -222,8 +221,8 @@ trait IterableFactory[+CC[_]] extends Serializable, Pure { * @return A $coll consisting of elements `f(i1, i2, i3)` * for `0 <= i1 < n1`, `0 <= i2 < n2`, and `0 <= i3 < n3`. */ - def tabulate[A](n1: Int, n2: Int, n3: Int)(f: (Int, Int, Int) => A): CC[CC[CC[A]] @uncheckedVariance]^{f} = // !!! problem with checking rhs under cc - ??? // tabulate(n1)(i1 => tabulate(n2, n3)(f(i1, _, _))) + def tabulate[A](n1: Int, n2: Int, n3: Int)(f: (Int, Int, Int) => A): CC[(CC[CC[A]^{f}]^{f}) @uncheckedVariance]^{f} = + tabulate(n1)(i1 => tabulate(n2, n3)(f(i1, _, _))) /** Produces a four-dimensional $coll containing values of a given function over ranges of integer values starting from 0. * @param n1 the number of elements in the 1st dimension @@ -234,8 +233,8 @@ trait IterableFactory[+CC[_]] extends Serializable, Pure { * @return A $coll consisting of elements `f(i1, i2, i3, i4)` * for `0 <= i1 < n1`, `0 <= i2 < n2`, `0 <= i3 < n3`, and `0 <= i4 < n4`. */ - def tabulate[A](n1: Int, n2: Int, n3: Int, n4: Int)(f: (Int, Int, Int, Int) => A): CC[CC[CC[CC[A]]] @uncheckedVariance]^{f} = // !!! problem with checking rhs under cc - ??? // tabulate(n1)(i1 => tabulate(n2, n3, n4)(f(i1, _, _, _))) + def tabulate[A](n1: Int, n2: Int, n3: Int, n4: Int)(f: (Int, Int, Int, Int) => A): CC[(CC[CC[CC[A]^{f}]^{f}]^{f}) @uncheckedVariance]^{f} = + tabulate(n1)(i1 => tabulate(n2, n3, n4)(f(i1, _, _, _))) /** Produces a five-dimensional $coll containing values of a given function over ranges of integer values starting from 0. * @param n1 the number of elements in the 1st dimension @@ -247,8 +246,8 @@ trait IterableFactory[+CC[_]] extends Serializable, Pure { * @return A $coll consisting of elements `f(i1, i2, i3, i4, i5)` * for `0 <= i1 < n1`, `0 <= i2 < n2`, `0 <= i3 < n3`, `0 <= i4 < n4`, and `0 <= i5 < n5`. */ - def tabulate[A](n1: Int, n2: Int, n3: Int, n4: Int, n5: Int)(f: (Int, Int, Int, Int, Int) => A): CC[CC[CC[CC[CC[A]]]] @uncheckedVariance]^{f} = // !!! problem with checking rhs under cc - ??? // tabulate(n1)(i1 => tabulate(n2, n3, n4, n5)(f(i1, _, _, _, _))) + def tabulate[A](n1: Int, n2: Int, n3: Int, n4: Int, n5: Int)(f: (Int, Int, Int, Int, Int) => A): CC[(CC[CC[CC[CC[A]^{f}]^{f}]^{f}]^{f}) @uncheckedVariance]^{f} = + tabulate(n1)(i1 => tabulate(n2, n3, n4, n5)(f(i1, _, _, _, _))) /** Concatenates all argument collections into a single $coll. * diff --git a/scala2-library-cc/src/scala/collection/Iterable.scala b/scala2-library-cc/src/scala/collection/Iterable.scala index 1e6d8a24843b..5afc14f4ceef 100644 --- a/scala2-library-cc/src/scala/collection/Iterable.scala +++ b/scala2-library-cc/src/scala/collection/Iterable.scala @@ -397,7 +397,7 @@ trait IterableOps[+A, +CC[_], +C] extends Any with IterableOnce[A] with Iterable if (i != headSize) fail } - iterableFactory.from(bs.map(_.result())).asInstanceOf // !!! needed for cc + iterableFactory.from(bs.map(_.result())) } def filter(pred: A => Boolean): C^{this, pred} = fromSpecific(new View.Filter(this, pred, isFlipped = false)) diff --git a/scala2-library-cc/src/scala/collection/MapView.scala b/scala2-library-cc/src/scala/collection/MapView.scala index 5f6d1e4bdf2f..e6c5c91e1dd5 100644 --- a/scala2-library-cc/src/scala/collection/MapView.scala +++ b/scala2-library-cc/src/scala/collection/MapView.scala @@ -81,7 +81,7 @@ object MapView extends MapViewFactory { @SerialVersionUID(3L) object EmptyMapView extends AbstractMapView[Any, Nothing] { - // !!! cc problem: crash when we replace the line with + // !!! cc problem? type mismatch when we replace the line with // private val EmptyMapView: MapView[Any, Nothing] = new AbstractMapView[Any, Nothing] { override def get(key: Any): Option[Nothing] = None override def iterator: Iterator[Nothing] = Iterator.empty[Nothing] diff --git a/scala2-library-cc/src/scala/collection/concurrent/TrieMap.scala b/scala2-library-cc/src/scala/collection/concurrent/TrieMap.scala index 8a34eddf0bdb..3bcbc60c8744 100644 --- a/scala2-library-cc/src/scala/collection/concurrent/TrieMap.scala +++ b/scala2-library-cc/src/scala/collection/concurrent/TrieMap.scala @@ -1183,10 +1183,7 @@ private[collection] class TrieMapIterator[K, V](var level: Int, private var ct: stack(d) = arr1 stackpos(d) = -1 val it = newIterator(level + 1, ct, _mustInit = false) - val xss: Array[Array[BasicNode]] = it.stack.asInstanceOf - // !!! cc split into separate xss and asInstanceOf needed because cc gets confused with - // two-dimensinal invariant arrays - xss(0) = arr2 + it.stack(0) = arr2 it.stackpos(0) = -1 it.depth = 0 it.advance() // <-- fix it diff --git a/scala2-library-cc/src/scala/collection/immutable/HashMap.scala b/scala2-library-cc/src/scala/collection/immutable/HashMap.scala index 8faa37625d51..2d1179ef0ee6 100644 --- a/scala2-library-cc/src/scala/collection/immutable/HashMap.scala +++ b/scala2-library-cc/src/scala/collection/immutable/HashMap.scala @@ -2384,7 +2384,7 @@ private[immutable] final class HashMapBuilder[K, V] extends ReusableBuilder[(K, ) currentValueCursor += 1 } - }.asInstanceOf // !!! cc gets confused with representation of capture sets in invariant position + } case hm: collection.mutable.HashMap[K, V] => val iter = hm.nodeIterator while (iter.hasNext) { diff --git a/scala2-library-cc/src/scala/collection/immutable/HashSet.scala b/scala2-library-cc/src/scala/collection/immutable/HashSet.scala index 9a3676705201..b4b8f9fdf27c 100644 --- a/scala2-library-cc/src/scala/collection/immutable/HashSet.scala +++ b/scala2-library-cc/src/scala/collection/immutable/HashSet.scala @@ -2101,7 +2101,7 @@ private[collection] final class HashSetBuilder[A] extends ReusableBuilder[A, Has ) currentValueCursor += 1 } - }.asInstanceOf // !!! cc gets confused with representation of capture sets in invariant position + } case other => val it = other.iterator while(it.hasNext) addOne(it.next()) From 6e7f5be6286ed46c141bc133d134dd3287efa72f Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 23 Jan 2024 19:44:55 +0100 Subject: [PATCH 5/8] Make all closure results inferred types --- compiler/src/dotty/tools/dotc/ast/tpd.scala | 8 ++++---- compiler/src/dotty/tools/dotc/transform/ElimByName.scala | 3 +-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/tpd.scala b/compiler/src/dotty/tools/dotc/ast/tpd.scala index a9f786e4c478..9c798811be16 100644 --- a/compiler/src/dotty/tools/dotc/ast/tpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/tpd.scala @@ -117,13 +117,13 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo { * where the closure's type is the target type of the expression (FunctionN, unless * otherwise specified). */ - def Closure(meth: TermSymbol, rhsFn: List[List[Tree]] => Tree, targs: List[Tree] = Nil, targetType: Type = NoType, inferred: Boolean = false)(using Context): Block = { - val targetTpt = if (targetType.exists) TypeTree(targetType, inferred) else EmptyTree + def Closure(meth: TermSymbol, rhsFn: List[List[Tree]] => Tree, targs: List[Tree] = Nil, targetType: Type = NoType)(using Context): Block = { + val targetTpt = if (targetType.exists) TypeTree(targetType, inferred = true) else EmptyTree val call = if (targs.isEmpty) Ident(TermRef(NoPrefix, meth)) else TypeApply(Ident(TermRef(NoPrefix, meth)), targs) - var mdef = DefDef(meth, rhsFn) - if inferred then mdef = cpy.DefDef(mdef)(tpt = TypeTree(mdef.tpt.tpe, inferred)) + var mdef0 = DefDef(meth, rhsFn) + val mdef = cpy.DefDef(mdef0)(tpt = TypeTree(mdef0.tpt.tpe, inferred = true)) Block(mdef :: Nil, Closure(Nil, call, targetTpt)) } diff --git a/compiler/src/dotty/tools/dotc/transform/ElimByName.scala b/compiler/src/dotty/tools/dotc/transform/ElimByName.scala index b244736fdb82..eca3928569f1 100644 --- a/compiler/src/dotty/tools/dotc/transform/ElimByName.scala +++ b/compiler/src/dotty/tools/dotc/transform/ElimByName.scala @@ -102,8 +102,7 @@ class ElimByName extends MiniPhase, InfoTransformer: val meth = newAnonFun(ctx.owner, MethodType(Nil, argType), coord = arg.span) Closure(meth, _ => arg.changeOwnerAfter(ctx.owner, meth, thisPhase), - targetType = defn.ByNameFunction(argType), - inferred = true + targetType = defn.ByNameFunction(argType) ).withSpan(arg.span) private def isByNameRef(tree: Tree)(using Context): Boolean = From 2e0cf2ed98a3ad04c78462d2e484b5199ae568fb Mon Sep 17 00:00:00 2001 From: odersky Date: Fri, 26 Jan 2024 13:29:54 +0100 Subject: [PATCH 6/8] Make maybeCapability a compiler-generated symbol without source It's used only during the capture checking phase, cannot appear in source or Tasty. So it's best contained as a synthetic symbol created directly by the compiler. --- compiler/src/dotty/tools/dotc/cc/CaptureOps.scala | 2 +- compiler/src/dotty/tools/dotc/core/Definitions.scala | 10 +++++++--- compiler/src/dotty/tools/dotc/core/StdNames.scala | 1 + .../scala/annotation/internal/maybeCapability.scala | 8 -------- tests/neg/i19470.check | 2 +- 5 files changed, 10 insertions(+), 13 deletions(-) delete mode 100644 library/src/scala/annotation/internal/maybeCapability.scala diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index 8d2a5a0a927d..5a77c480e0b1 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -240,7 +240,7 @@ extension (tp: Type) * * Array[_ >: C^{} <: C^{x}] * - * but it has fewer issues with type inference. + * but it has fewer issues with type inference. */ def maybe(using Context): CaptureRef = tp match case tp: CaptureRef if tp.isTrackableRef => diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 2116e3eab7a7..a8026f4bf716 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -535,6 +535,10 @@ class Definitions { List(AnyType), EmptyScope) @tu lazy val SingletonType: TypeRef = SingletonClass.typeRef + @tu lazy val MaybeCapabilityAnnot: ClassSymbol = + completeClass(enterCompleteClassSymbol( + ScalaPackageClass, tpnme.maybeCapability, Final, List(StaticAnnotationClass.typeRef))) + @tu lazy val CollectionSeqType: TypeRef = requiredClassRef("scala.collection.Seq") @tu lazy val SeqType: TypeRef = requiredClassRef("scala.collection.immutable.Seq") @tu lazy val SeqModule: Symbol = requiredModule("scala.collection.immutable.Seq") @@ -993,7 +997,7 @@ class Definitions { // Annotation base classes @tu lazy val AnnotationClass: ClassSymbol = requiredClass("scala.annotation.Annotation") - // @tu lazy val StaticAnnotationClass: ClassSymbol = requiredClass("scala.annotation.StaticAnnotation") + @tu lazy val StaticAnnotationClass: ClassSymbol = requiredClass("scala.annotation.StaticAnnotation") @tu lazy val RefiningAnnotationClass: ClassSymbol = requiredClass("scala.annotation.RefiningAnnotation") @tu lazy val JavaAnnotationClass: ClassSymbol = requiredClass("java.lang.annotation.Annotation") @@ -1054,7 +1058,6 @@ class Definitions { @tu lazy val FunctionalInterfaceAnnot: ClassSymbol = requiredClass("java.lang.FunctionalInterface") @tu lazy val TargetNameAnnot: ClassSymbol = requiredClass("scala.annotation.targetName") @tu lazy val VarargsAnnot: ClassSymbol = requiredClass("scala.annotation.varargs") - @tu lazy val MaybeCapabilityAnnot = requiredClass("scala.annotation.internal.maybeCapability") @tu lazy val ReachCapabilityAnnot = requiredClass("scala.annotation.internal.reachCapability") @tu lazy val RequiresCapabilityAnnot: ClassSymbol = requiredClass("scala.annotation.internal.requiresCapability") @tu lazy val RetainsAnnot: ClassSymbol = requiredClass("scala.annotation.retains") @@ -2137,7 +2140,8 @@ class Definitions { AnyValClass, NullClass, NothingClass, - SingletonClass) + SingletonClass, + MaybeCapabilityAnnot) @tu lazy val syntheticCoreClasses: List[Symbol] = syntheticScalaClasses ++ List( EmptyPackageVal, diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index 253a45ffd7a8..662f717612e8 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -537,6 +537,7 @@ object StdNames { val ManifestFactory: N = "ManifestFactory" val manifestToTypeTag: N = "manifestToTypeTag" val map: N = "map" + val maybeCapability: N = "maybeCapability" val materializeClassTag: N = "materializeClassTag" val materializeWeakTypeTag: N = "materializeWeakTypeTag" val materializeTypeTag: N = "materializeTypeTag" diff --git a/library/src/scala/annotation/internal/maybeCapability.scala b/library/src/scala/annotation/internal/maybeCapability.scala deleted file mode 100644 index ebb8f6ec4833..000000000000 --- a/library/src/scala/annotation/internal/maybeCapability.scala +++ /dev/null @@ -1,8 +0,0 @@ -package scala.annotation -package internal - -/** An annotation that marks a capture ref as a maybe capability. - * `x?` is encoded as `x.type @maybeCapability` - */ -class maybeCapability extends StaticAnnotation - diff --git a/tests/neg/i19470.check b/tests/neg/i19470.check index 72f811f7ea3b..fdb336bef7e5 100644 --- a/tests/neg/i19470.check +++ b/tests/neg/i19470.check @@ -1,7 +1,7 @@ -- [E007] Type Mismatch Error: tests/neg/i19470.scala:9:12 ------------------------------------------------------------- 9 | List(foo(f())) // error | ^^^^^^^^ - | Found: Inv[? >: IO <: box IO^{f}] + | Found: Inv[box IO^{f?}] | Required: box Inv[box IO^?]^? | | longer explanation available when compiling with `-explain` From 4cd72b7cc71315b2b97aabae1ff873f4b59b00e6 Mon Sep 17 00:00:00 2001 From: odersky Date: Fri, 26 Jan 2024 19:32:29 +0100 Subject: [PATCH 7/8] Fix using source comment Detected manually, even though the test as a whole succeeded --- tests/neg/i19506.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/neg/i19506.scala b/tests/neg/i19506.scala index a2de4eca0e55..1b877a0bae7d 100644 --- a/tests/neg/i19506.scala +++ b/tests/neg/i19506.scala @@ -1,4 +1,4 @@ -//> using options "-source:3.4-migration" +//> using options -source 3.4-migration trait Reader[T] def read[T: Reader](s: String, trace: Boolean = false): T = ??? From 3ef25141b2d8b0acd9a563580b2ca3ec1affc77c Mon Sep 17 00:00:00 2001 From: odersky Date: Wed, 31 Jan 2024 19:12:39 +0100 Subject: [PATCH 8/8] Fix comments --- compiler/src/dotty/tools/dotc/cc/CaptureOps.scala | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index 5a77c480e0b1..501405a81e2b 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -230,13 +230,13 @@ extension (tp: Type) * cannot be propagated between sets. If `a <: b` and `a` acquires `x?` then * `x` is propagated to `b` as a conservative approximation. * - * Maybe capabilities should only arise for caoture sets that appear in invariant + * Maybe capabilities should only arise for capture sets that appear in invariant * position in their surrounding type. They are similar to TypeBunds types, but * restricted to capture sets. For instance, * * Array[C^{x?}] * - * should be morally equivaelent to + * should be morally equivalent to * * Array[_ >: C^{} <: C^{x}] * @@ -441,9 +441,6 @@ object ReachCapabilityApply: case Apply(reach, arg :: Nil) if reach.symbol == defn.Caps_reachCapability => Some(arg) case _ => None -/** An extractor for `ref @annotation.internal.reachCapability`, which is used to express - * the reach capability `ref*` as a type. - */ class AnnotatedCapability(annot: Context ?=> ClassSymbol): def apply(tp: Type)(using Context) = AnnotatedType(tp, Annotation(annot, util.Spans.NoSpan)) @@ -451,7 +448,14 @@ class AnnotatedCapability(annot: Context ?=> ClassSymbol): case AnnotatedType(parent: SingletonCaptureRef, ann) if ann.symbol == annot => Some(parent) case _ => None +/** An extractor for `ref @annotation.internal.reachCapability`, which is used to express + * the reach capability `ref*` as a type. + */ object ReachCapability extends AnnotatedCapability(defn.ReachCapabilityAnnot) + +/** An extractor for `ref @maybeCapability`, which is used to express + * the maybe capability `ref?` as a type. + */ object MaybeCapability extends AnnotatedCapability(defn.MaybeCapabilityAnnot)