diff --git a/compiler/src/dotty/tools/dotc/cc/Capability.scala b/compiler/src/dotty/tools/dotc/cc/Capability.scala index 73471d0a7cae..0c2fe68fee23 100644 --- a/compiler/src/dotty/tools/dotc/cc/Capability.scala +++ b/compiler/src/dotty/tools/dotc/cc/Capability.scala @@ -191,7 +191,8 @@ object Capabilities: def acceptsLevelOf(ref: Capability)(using Context): Boolean = if ccConfig.useFreshLevels && !CCState.collapseFresh then val refOwner = ref.levelOwner - refOwner.isStaticOwner || ccOwner.isContainedIn(refOwner) + ccOwner.isContainedIn(refOwner) + || classifier.derivesFrom(defn.Caps_Unscoped) else ref.core match case ResultCap(_) | _: ParamRef => false case _ => true @@ -432,11 +433,18 @@ object Capabilities: core.isInstanceOf[RootCapability] /** Is the reference tracked? This is true if it can be tracked and the capture - * set of the underlying type is not always empty. + * set of the underlying type is not always empty. Also excluded are references + * that come from source files that were not capture checked and that have + * `Fluid` capture sets. */ final def isTracked(using Context): Boolean = this.core match case _: RootCapability => true - case tp: CoreCapability => tp.isTrackableRef && !captureSetOfInfo.isAlwaysEmpty + case tp: CoreCapability => + tp.isTrackableRef + && { + val cs = captureSetOfInfo + !cs.isAlwaysEmpty && cs != CaptureSet.Fluid + } /** An exclusive capability is a capability that derives * indirectly from a maximal capability without going through diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index b3cdd28245c2..6394b11971cb 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -117,7 +117,7 @@ extension (tp: Type) !tp.underlying.exists // might happen during construction of lambdas with annotations on parameters || ((tp.prefix eq NoPrefix) - || tp.symbol.isField && !tp.symbol.isStatic && tp.prefix.isTrackableRef + || tp.symbol.isField && tp.prefix.isTrackableRef ) && !tp.symbol.isOneOf(UnstableValueFlags) case tp: TypeRef => tp.symbol.isType && tp.derivesFrom(defn.Caps_CapSet) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala index ec730905c0cf..5a36b9a878d3 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala @@ -21,6 +21,7 @@ import compiletime.uninitialized import Capabilities.* import Names.Name import NameKinds.CapsetName +import StdNames.nme /** A class for capture sets. Capture sets can be constants or variables. * Capture sets support inclusion constraints <:< where <:< is subcapturing. @@ -1678,6 +1679,8 @@ object CaptureSet: // might happen during construction of lambdas, assume `{cap}` in this case so that // `ref` will not seem subsumed by other capabilities in a `++`. universal + case c: TermRef if isAssumedPure(c.symbol) => + CaptureSet.empty case c: CoreCapability => ofType(c.underlying, followResult = ccConfig.useSpanCapset) @@ -1723,7 +1726,7 @@ object CaptureSet: /** The deep capture set of a type is the union of all covariant occurrences of * capture sets. Nested existential sets are approximated with `cap`. */ - def ofTypeDeeply(tp: Type, includeTypevars: Boolean = false, includeBoxed: Boolean = true)(using Context): CaptureSet = + def ofTypeDeeply(tp: Type, includeTypevars: Boolean = false, includeBoxed: Boolean = true)(using Context): CaptureSet = { val collect = new DeepTypeAccumulator[CaptureSet]: def capturingCase(acc: CaptureSet, parent: Type, refs: CaptureSet, boxed: Boolean) = @@ -1736,6 +1739,15 @@ object CaptureSet: else this(acc, upperBound) collect(CaptureSet.empty, tp) + } + + /** Is `sym` assumed to be pure even though it would have a non-empty capture + * set by the normal rules? + */ + def isAssumedPure(sym: Symbol)(using Context): Boolean = + sym.name == nme.DOLLAR_VALUES // sym is an enum $values array, which for backwards + && sym.owner.is(Module) // compatible reasons is an array, but should really be an IArray. + && sym.owner.companionClass.is(Enum) type AssumedContains = immutable.Map[TypeRef, SimpleIdentitySet[Capability]] val AssumedContains: Property.Key[AssumedContains] = Property.Key() diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 21edd848cc9f..9790f366fb21 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -56,12 +56,12 @@ object CheckCaptures: * @param nestedClosure under deferredReaches: If this is an env of a method with an anonymous function or * anonymous class as RHS, the symbol of that function or class. NoSymbol in all other cases. */ - case class Env( - owner: Symbol, - kind: EnvKind, - captured: CaptureSet, - outer0: Env | Null, - nestedClosure: Symbol = NoSymbol)(using @constructorOnly ictx: Context): + class Env( + val owner: Symbol, + val kind: EnvKind, + val captured: CaptureSet, + outer0: Env | Null, + val nestedClosure: Symbol = NoSymbol)(using @constructorOnly ictx: Context) { assert(definesEnv(owner)) captured match @@ -71,16 +71,16 @@ object CheckCaptures: def outer = outer0.nn - def isOutermost = outer0 == null + def isRoot(using Context) = owner.is(Package) - def outersIterator: Iterator[Env] = new: + def outersIterator(using Context): Iterator[Env] = new: private var cur = Env.this - def hasNext = !cur.isOutermost + def hasNext = !cur.isRoot def next(): Env = val res = cur cur = cur.outer res - end Env + } def definesEnv(sym: Symbol)(using Context): Boolean = sym.isOneOf(MethodOrLazy) || sym.isClass @@ -389,6 +389,9 @@ class CheckCaptures extends Recheck, SymTransformer: /** If `tpt` is an inferred type, interpolate capture set variables appearing contra- * variantly in it. Also anchor Fresh instances with anchorCaps. + * Note: module vals don't have inferred types but still hold capture set variables. + * These capture set variables are interpolated after the associated module class + * has been rechecked. */ private def interpolateIfInferred(tpt: Tree, sym: Symbol)(using Context): Unit = if tpt.isInstanceOf[InferredTypeTree] then @@ -465,18 +468,17 @@ class CheckCaptures extends Recheck, SymTransformer: catch case ex: IllegalCaptureRef => report.error(em"Illegal capture reference: ${ex.getMessage}", sym.srcPos) CaptureSet.empty - case _ => - if sym.isTerm || !sym.owner.isStaticOwner || sym.is(Lazy) - then CaptureSet.Var(sym, nestedOK = false) - else CaptureSet.empty) + case _ if sym.is(Package) => CaptureSet.empty + case _ => CaptureSet.Var(sym, nestedOK = false) + ) // ---- Record Uses with MarkFree ---------------------------------------------------- /** The next environment enclosing `env` that needs to be charged * with free references. */ - def nextEnvToCharge(env: Env)(using Context): Env | Null = - if env.owner.isConstructor then env.outer.outer0 + def nextEnvToCharge(env: Env)(using Context): Env = + if env.owner.isConstructor then env.outer.outer else env.outer /** A description where this environment comes from */ @@ -495,9 +497,8 @@ class CheckCaptures extends Recheck, SymTransformer: * Does the given environment belong to a method that is (a) nested in a term * and (b) not the method of an anonymous function? */ - def isOfNestedMethod(env: Env | Null)(using Context) = + def isOfNestedMethod(env: Env)(using Context) = ccConfig.deferredReaches - && env != null && env.owner.is(Method) && env.owner.owner.isTerm && !env.owner.isAnonymousFunction @@ -587,7 +588,7 @@ class CheckCaptures extends Recheck, SymTransformer: tree.srcPos) def recur(cs: CaptureSet, env: Env, lastEnv: Env | Null): Unit = - if env.kind != EnvKind.Boxed && !env.owner.isStaticOwner && !cs.isAlwaysEmpty then + if env.kind != EnvKind.Boxed && !cs.isAlwaysEmpty then // Only captured references that are visible from the environment // should be included. val included = cs.filter: c => @@ -602,7 +603,7 @@ class CheckCaptures extends Recheck, SymTransformer: capt.println(i"Include call or box capture $included from $cs in ${env.owner} --> ${env.captured}") if !isOfNestedMethod(env) then val nextEnv = nextEnvToCharge(env) - if nextEnv != null && !nextEnv.owner.isStaticOwner then + if !nextEnv.isRoot then if nextEnv.owner != env.owner && env.owner.isReadOnlyMember && env.owner.owner.derivesFrom(defn.Caps_Stateful) @@ -731,7 +732,7 @@ class CheckCaptures extends Recheck, SymTransformer: // For fields it's not a problem since `c` would already have been // charged for the prefix `p` in `p.x`. markFree(sym.info.captureSet, tree) - if sym.exists && !sym.isStatic then + if sym.exists then markPathFree(sym.termRef, pt, tree) mapResultRoots(super.recheckIdent(tree, pt), tree.symbol) @@ -761,7 +762,7 @@ class CheckCaptures extends Recheck, SymTransformer: * a PathSelectionProto. */ override def selectionProto(tree: Select, pt: Type)(using Context): Type = - if tree.symbol.isStatic then super.selectionProto(tree, pt) + if tree.symbol.is(Package) then super.selectionProto(tree, pt) else PathSelectionProto(tree, pt) /** A specialized implementation of the selection rule. @@ -1261,22 +1262,6 @@ class CheckCaptures extends Recheck, SymTransformer: if runInConstructor && savedEnv.owner.isClass then markFree(declaredCaptures, tree, addUseInfo = false) - - if sym.owner.isStaticOwner - && !declaredCaptures.elems.isEmpty - && sym != defn.captureRoot - && !(sym.is(ModuleVal) && sym.owner.is(Package)) - // global modules that don't derive from capability can have captures - // only if their fields have them, and then the field was already reported. - then - def where = - if sym.effectiveOwner.is(Package) then "top-level definition" - else i"member of static ${sym.owner}" - report.warning( - em"""$sym has a non-empty capture set but will not be added as - |a capability to computed capture sets since it is globally accessible - |as a $where. Global values cannot be capabilities.""", - tree.namePos) end recheckValDef /** Recheck method definitions: @@ -1400,8 +1385,9 @@ class CheckCaptures extends Recheck, SymTransformer: // does not interfere with normal rechecking by constraining capture set variables. } // Test point (2) of doc comment above - if sym.owner.isClass && !sym.owner.isStaticOwner + if sym.owner.isClass && contributesFreshToClass(sym) + && !CaptureSet.isAssumedPure(sym) then todoAtPostCheck += { () => val cls = sym.owner.asClass @@ -1474,7 +1460,7 @@ class CheckCaptures extends Recheck, SymTransformer: def checkExplicitUses(sym: Symbol): Unit = capturedVars(sym) match case cs: CaptureSet.Var - if !cs.elems.isEmpty && !isExemptFromExplicitChecks(sym) => + if cs.elems.exists(!_.isTerminalCapability) && !isExemptFromExplicitChecks(sym) => val usesStr = if sym.isClass then "uses" else "uses_init" report.error( em"""Publicly visible $sym uses external capabilities $cs. @@ -1518,6 +1504,8 @@ class CheckCaptures extends Recheck, SymTransformer: finally checkExplicitUses(cls) checkExplicitUses(cls.primaryConstructor) + if cls.is(ModuleClass) then + interpolate(cls.sourceModule.info, cls.sourceModule) completed += cls curEnv = saved end recheckClassDef @@ -1838,11 +1826,11 @@ class CheckCaptures extends Recheck, SymTransformer: private def debugShowEnvs()(using Context): Unit = def showEnv(env: Env): String = i"Env(${env.owner}, ${env.kind}, ${env.captured})" val sb = StringBuilder() - @annotation.tailrec def walk(env: Env | Null): Unit = - if env != null then + @annotation.tailrec def walk(env: Env): Unit = + if !env.isRoot then sb ++= showEnv(env) sb ++= "\n" - walk(env.outer0) + walk(env.outer) sb ++= "===== Current Envs ======\n" walk(curEnv) sb ++= "===== End ======\n" @@ -2240,10 +2228,9 @@ class CheckCaptures extends Recheck, SymTransformer: def boxedOwner(env: Env): Symbol = if env.kind == EnvKind.Boxed then env.owner else if isOfNestedMethod(env) then env.owner.owner - else if env.owner.isStaticOwner then NoSymbol else val nextEnv = nextEnvToCharge(env) - if nextEnv == null then NoSymbol else boxedOwner(nextEnv) + if nextEnv.isRoot then NoSymbol else boxedOwner(nextEnv) def checkUseUnlessBoxed(c: Capability, croot: NamedType) = if !boxedOwner(env).isContainedIn(croot.symbol.owner) then diff --git a/compiler/src/dotty/tools/dotc/cc/Mutability.scala b/compiler/src/dotty/tools/dotc/cc/Mutability.scala index a579f69be175..b6f2328c7fd4 100644 --- a/compiler/src/dotty/tools/dotc/cc/Mutability.scala +++ b/compiler/src/dotty/tools/dotc/cc/Mutability.scala @@ -73,7 +73,7 @@ object Mutability: else if sym.owner == cls then if sym.isConstructor then OK else NotInUpdateMethod(sym, cls) - else if sym.isStatic then OutsideClass(cls) + else if sym.isRoot then OutsideClass(cls) else sym.owner.inExclusivePartOf(cls) extension (tp: Type) diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index ecc9f2fa87cc..7e5050b758fc 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -723,11 +723,10 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: val cinfo @ ClassInfo(prefix, _, ps, decls, selfInfo) = cls.classInfo // Compute new self type - def isInnerModule = cls.is(ModuleClass) && !cls.isStatic val selfInfo1 = - if (selfInfo ne NoType) && !isInnerModule then + if (selfInfo ne NoType) && !cls.is(ModuleClass) then // if selfInfo is explicitly given then use that one, except if - // self info applies to non-static modules, these still need to be inferred + // self info applies to a module class, these still need to be inferred selfInfo else if cls.isPureClass then // is cls is known to be pure, nothing needs to be added to self type @@ -735,16 +734,24 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: else if !cls.isEffectivelySealed && !cls.baseClassHasExplicitNonUniversalSelfType then // assume {cap} for completely unconstrained self types of publicly extensible classes CapturingType(cinfo.selfType, CaptureSet.universal) - else + else { // Infer the self type for the rest, which is all classes without explicit // self types (to which we also add nested module classes), provided they are // neither pure, nor are publicily extensible with an unconstrained self type. val cs = CaptureSet.ProperVar(cls, CaptureSet.emptyRefs, nestedOK = false, isRefining = false) + if cls.derivesFrom(defn.Caps_Capability) then // If cls is a capability class, we need to add a fresh capability to ensure // we cannot treat the class as pure. CaptureSet.fresh(cls, cls.thisType, Origin.InDecl(cls)).subCaptures(cs) - CapturingType(cinfo.selfType, cs) + + // Add capture set variable `cs`, burying it under any refinements + // that might contain infos of opaque type aliases + def addCs(tp: Type): Type = tp match + case tp @ RefinedType(parent, _, _) => tp.derivedRefinedType(parent = addCs(parent)) + case _ => CapturingType(tp, cs) + addCs(cinfo.selfType) + } // Compute new parent types val ps1 = inContext(ctx.withOwner(cls)): diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index 212842d9cef5..44c82cd8152c 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -1391,7 +1391,7 @@ object SymDenotations { * containing object. */ def opaqueAlias(using Context): Type = { - def recur(tp: Type): Type = tp match { + def recur(tp: Type): Type = tp.stripAnnots match { case RefinedType(parent, rname, TypeAlias(alias)) => if rname == name then alias.stripLazyRef else recur(parent) case _ => diff --git a/compiler/src/dotty/tools/dotc/core/SymUtils.scala b/compiler/src/dotty/tools/dotc/core/SymUtils.scala index 435320fec568..c9b92472f707 100644 --- a/compiler/src/dotty/tools/dotc/core/SymUtils.scala +++ b/compiler/src/dotty/tools/dotc/core/SymUtils.scala @@ -313,7 +313,7 @@ class SymUtils: } def isField(using Context): Boolean = - self.isTerm && !self.isOneOf(Method | PhantomSymbol | NonMember) + self.isTerm && !self.isOneOf(Method | PhantomSymbol | NonMember | Package) def isEnumCase(using Context): Boolean = self.isAllOf(EnumCase, butNot = JavaDefined) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 11a3ab7c4086..cecb3c790fbc 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -2486,7 +2486,7 @@ object Types extends TypeUtils { util.Stats.record("NamedType.computeDenot") def finish(d: Denotation) = { - if (d.exists) + if d.exists then // Avoid storing NoDenotations in the cache - we will not be able to recover from // them. The situation might arise that a type has NoDenotation in some later // phase but a defined denotation earlier (e.g. a TypeRef to an abstract type diff --git a/library/src/scala/collection/StringOps.scala b/library/src/scala/collection/StringOps.scala index ac5f91d237ce..b640917cfc99 100644 --- a/library/src/scala/collection/StringOps.scala +++ b/library/src/scala/collection/StringOps.scala @@ -160,7 +160,7 @@ object StringOps { } /** Avoid an allocation in [[collect]]. */ - private val fallback: Any => Any = _ => fallback + private val fallback: Any -> Any = _ => fallback } /** Provides extension methods for strings. diff --git a/library/src/scala/collection/mutable/LongMap.scala b/library/src/scala/collection/mutable/LongMap.scala index 73f6d1a95f8b..a1f04bde5c40 100644 --- a/library/src/scala/collection/mutable/LongMap.scala +++ b/library/src/scala/collection/mutable/LongMap.scala @@ -594,7 +594,7 @@ object LongMap { private final val VacantBit = 0x4000_0000 private final val MissVacant = 0xC000_0000 - private val exceptionDefault: Long => Nothing = (k: Long) => throw new NoSuchElementException(k.toString) + private val exceptionDefault: Long -> Nothing = (k: Long) => throw new NoSuchElementException(k.toString) /** A builder for instances of `LongMap`. * diff --git a/tests/neg-custom-args/captures/boundary.check b/tests/neg-custom-args/captures/boundary.check index a260dd418722..adb93f14c323 100644 --- a/tests/neg-custom-args/captures/boundary.check +++ b/tests/neg-custom-args/captures/boundary.check @@ -5,7 +5,7 @@ | Capability cap outlives its scope: it leaks into outer capture set 's1 which is owned by value local. | The leakage occurred when trying to match the following types: | - | Found: scala.util.boundary.Label[Object^'s1] + | Found: scala.util.boundary.Label[Object^'s1]^'s2 | Required: scala.util.boundary.Label[Object^]^² | | where: ^ and cap refer to the universal root capability diff --git a/tests/neg-custom-args/captures/box-unsoundness-global.scala b/tests/neg-custom-args/captures/box-unsoundness-global.scala new file mode 100644 index 000000000000..6d525985403d --- /dev/null +++ b/tests/neg-custom-args/captures/box-unsoundness-global.scala @@ -0,0 +1,13 @@ +class CanIO { def use(): Unit = () } +def use[X](x: X): (op: X -> Unit) -> Unit = op => op(x) +val io: CanIO^ = CanIO() +def test: Unit = + val f: (CanIO^ => Unit) -> Unit = use[CanIO^](io) // error + val _: (CanIO^ => Unit) -> Unit = f + + val g1 = () => f(x => x.use()) + + val a1 = f(x => x.use()) + val a2 = () => f(x => x.use()) + val g2: () -> Unit = a2 + // was UNSOUND: g uses the capability io but has an empty capture set diff --git a/tests/neg-custom-args/captures/capt-env-global.scala b/tests/neg-custom-args/captures/capt-env-global.scala new file mode 100644 index 000000000000..ba9d3dd10661 --- /dev/null +++ b/tests/neg-custom-args/captures/capt-env-global.scala @@ -0,0 +1,15 @@ +class C +type Cap = C^ + +class Pair[+A, +B](x: A, y: B): + def fst: A = x + def snd: B = y + +val c: Cap = C() + +def test() = + def f(x: Cap): Unit = if c == x then () + val p = Pair(f, f) + val g = () => p.fst == p.snd + val gc: () -> Boolean = g // error + diff --git a/tests/neg-custom-args/captures/cc-poly-2-global.scala b/tests/neg-custom-args/captures/cc-poly-2-global.scala new file mode 100644 index 000000000000..026e16e04035 --- /dev/null +++ b/tests/neg-custom-args/captures/cc-poly-2-global.scala @@ -0,0 +1,19 @@ +import language.experimental.captureChecking +import caps.{CapSet, SharedCapability} + +object Test: + + class C extends SharedCapability + class D + + def f[X^](x: D^{X}): D^{X} = x + + val c1: C = C() + val c2: C = C() + + def test() = + val d: D^ = D() + // f[Nothing](d) // already ruled out at typer + f[CapSet^{c1}](d) // error + val x = f(d) + val _: D^{c1} = x // error diff --git a/tests/neg-custom-args/captures/class-caps.check b/tests/neg-custom-args/captures/class-caps.check index dbdd8ffc3097..dbdd68270819 100644 --- a/tests/neg-custom-args/captures/class-caps.check +++ b/tests/neg-custom-args/captures/class-caps.check @@ -22,9 +22,15 @@ 30 | a + b | | longer explanation available when compiling with `-explain` --- Warning: tests/neg-custom-args/captures/class-caps.scala:35:6 ------------------------------------------------------- -35 | val console: Console^ = Console() // provide capability locally - | ^^^^^^^ - | value console has a non-empty capture set but will not be added as - | a capability to computed capture sets since it is globally accessible - | as a member of static object Test2. Global values cannot be capabilities. +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/class-caps.scala:37:46 ----------------------------------- +37 | def addWritesToConsole: (Int, Int) -> Int = (a, b) => // error + | ^ + | Found: (a: Int, b: Int) ->{Test2.console} Int + | Required: (Int, Int) -> Int + | + | Note that capability Test2.console is not included in capture set {}. + | +38 | log(s"adding a ($a) to b ($b)")(using console) +39 | a + b + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/class-caps.scala b/tests/neg-custom-args/captures/class-caps.scala index f49356c8aecb..94445d443164 100644 --- a/tests/neg-custom-args/captures/class-caps.scala +++ b/tests/neg-custom-args/captures/class-caps.scala @@ -34,6 +34,6 @@ object Test2: val console: Console^ = Console() // provide capability locally - def addWritesToConsole: (Int, Int) -> Int = (a, b) => // ok since `console` is static (maybe flag this?) + def addWritesToConsole: (Int, Int) -> Int = (a, b) => // error log(s"adding a ($a) to b ($b)")(using console) a + b diff --git a/tests/neg-custom-args/captures/class-constr-global.scala b/tests/neg-custom-args/captures/class-constr-global.scala new file mode 100644 index 000000000000..b5eccbc875a0 --- /dev/null +++ b/tests/neg-custom-args/captures/class-constr-global.scala @@ -0,0 +1,27 @@ +import annotation.{capability, constructorOnly} + +class Cap extends caps.SharedCapability + +class C(x: Cap, @constructorOnly y: Cap) + +val a: Cap = Cap() +val b: Cap = Cap() + +def test() = + val f = () => C(a, b) + val f_ok: () ->{a, b} C^{a} = f + val f_no1: () ->{a, b} C = f // error + val f_no2: () ->{a} C^{a} = f // error + val f_no3: () ->{a} C^{a} = f // error + + class D: + val xz = + println(a) + 1 + def yz = + println(b) + 2 + val d = () => new D() + val d_ok1: () ->{a, b} D^{a, b} = d // ok + val d_ok2: () ->{a} D^{b} = d // ok + val d_ok3: () -> D^{a, b} = d // error diff --git a/tests/neg-custom-args/captures/closure-result-typing-global.scala b/tests/neg-custom-args/captures/closure-result-typing-global.scala new file mode 100644 index 000000000000..44296972b082 --- /dev/null +++ b/tests/neg-custom-args/captures/closure-result-typing-global.scala @@ -0,0 +1,2 @@ +val c: Object^ = ??? +val x: () -> Object = () => c // error diff --git a/tests/neg-custom-args/captures/filevar-expanded-global.scala b/tests/neg-custom-args/captures/filevar-expanded-global.scala new file mode 100644 index 000000000000..074b00641f7d --- /dev/null +++ b/tests/neg-custom-args/captures/filevar-expanded-global.scala @@ -0,0 +1,40 @@ +import language.experimental.captureChecking +import language.experimental.modularity +import compiletime.uninitialized + +object test1: + class File: + def write(x: String): Unit = ??? + + class Service(f: File^): + def log = f.write("log") + + def withFile[T](op: (f: File^) => T): T = + op(new File) + + def test = + withFile: f => + val o = Service(f) + o.log + +object test2: + class IO + + class File: + def write(x: String): Unit = ??? + + class Service(tracked val io: IO^) extends caps.Stateful: + var file: File^{io} = uninitialized + def log = file.write("log") + + def withFile[T](io2: IO^)(op: (f: File^{io2}) => T): T = + op(new File) + + val io3: IO^ = IO() + + def test() = + withFile(io3): f => // error: separation failure + val o = Service(io3) + o.file = f // this is a bit dubious. It's legal since we treat class refinements + // as capture set variables that can be made to include refs coming from outside. + o.log diff --git a/tests/neg-custom-args/captures/ho-ref-global.scala b/tests/neg-custom-args/captures/ho-ref-global.scala new file mode 100644 index 000000000000..fc8247cdc01c --- /dev/null +++ b/tests/neg-custom-args/captures/ho-ref-global.scala @@ -0,0 +1,8 @@ +class Ref + +val a: Object^ = ??? +def test() = + val r: Ref^{a} = ??? + def mk1(op: (z: Ref^) -> Ref^{a} ->{z} Unit) = op(r) // error + def bad(x: Ref^)(y: Ref^{a}) = ??? + mk1(bad) diff --git a/tests/neg-custom-args/captures/i21620-global.scala b/tests/neg-custom-args/captures/i21620-global.scala new file mode 100644 index 000000000000..babe7a5314f9 --- /dev/null +++ b/tests/neg-custom-args/captures/i21620-global.scala @@ -0,0 +1,23 @@ +class C +val x: C^ = C() + +def test() = + val f = () => + def foo() = + x + () + println(s"hey: $x") + () => foo() + val _: () -> () ->{x} Unit = f // error + () + +def test2() = + def foo() = + x + () + val f = () => + // println() // uncomenting would give an error, but with + // a different way of handling curried functions should be OK + () => foo() + val _: () ->{} () ->{x} Unit = f // error, but could be OK + () diff --git a/tests/neg-custom-args/captures/indirect-avoid.check b/tests/neg-custom-args/captures/indirect-avoid.check index 9702a072125f..b548646f5c06 100644 --- a/tests/neg-custom-args/captures/indirect-avoid.check +++ b/tests/neg-custom-args/captures/indirect-avoid.check @@ -7,18 +7,6 @@ | Note that capability nll is not included in capture set {ll}. | | longer explanation available when compiling with `-explain` --- Warning: tests/neg-custom-args/captures/indirect-avoid.scala:24:6 --------------------------------------------------- -24 | val x1 = filterImpl1(LL()) - | ^^ - | value x1 has a non-empty capture set but will not be added as - | a capability to computed capture sets since it is globally accessible - | as a member of static object Test. Global values cannot be capabilities. --- Warning: tests/neg-custom-args/captures/indirect-avoid.scala:25:6 --------------------------------------------------- -25 | val _: LL^{cap.rd}= x1 - | ^ - | value _$1 has a non-empty capture set but will not be added as - | a capability to computed capture sets since it is globally accessible - | as a member of static object Test. Global values cannot be capabilities. -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/indirect-avoid.scala:26:14 ------------------------------- 26 | val _: LL = x1 // error | ^^ @@ -39,3 +27,11 @@ | Note that capability nll is not included in capture set {ll}. | | longer explanation available when compiling with `-explain` +-- Error: tests/neg-custom-args/captures/indirect-avoid.scala:24:8 ----------------------------------------------------- +24 | val x1 = filterImpl1(LL()) // error + | ^ + |value x1 needs an explicit type because it captures a root capability in its type Test.LL^{cap.rd}. + |Fields capturing a root capability need to be given an explicit type unless the capability is already + |subsumed by the computed capability of the enclosing class. + | + |where: cap is a fresh root capability created in value x1 when instantiating method filterImpl1's type (ll: Test.LL^): Test.LL^{cap².rd, ll} diff --git a/tests/neg-custom-args/captures/indirect-avoid.scala b/tests/neg-custom-args/captures/indirect-avoid.scala index 9b4b90475c5b..0f3eed7ca32f 100644 --- a/tests/neg-custom-args/captures/indirect-avoid.scala +++ b/tests/neg-custom-args/captures/indirect-avoid.scala @@ -21,7 +21,7 @@ object Test: val nll: LL^{cl} = ??? nll - val x1 = filterImpl1(LL()) + val x1 = filterImpl1(LL()) // error val _: LL^{cap.rd}= x1 val _: LL = x1 // error diff --git a/tests/neg-custom-args/captures/lazylist-global.check b/tests/neg-custom-args/captures/lazylist-global.check new file mode 100644 index 000000000000..5b068028133e --- /dev/null +++ b/tests/neg-custom-args/captures/lazylist-global.check @@ -0,0 +1,54 @@ +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/lazylist-global.scala:17:15 ------------------------------ +17 | def tail = xs() // error + | ^^^^ + | Found: lazylists.LazyList[T]^{LazyCons.this.xs} + | Required: lazylists.LazyList[T] + | + | Note that capability LazyCons.this.xs is not included in capture set {}. + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/lazylist-global.scala:39:29 ------------------------------ +39 | val ref1c: LazyList[Int] = ref1 // error + | ^^^^ + |Found: (ref1 : lazylists.LazyCons[Int]{val xs: () ->{lazylists.cap1} lazylists.LazyList[Int]^{}}^{lazylists.cap1}) + |Required: lazylists.LazyList[Int] + | + |Note that capability lazylists.cap1 is not included in capture set {}. + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/lazylist-global.scala:41:36 ------------------------------ +41 | val ref2c: LazyList[Int]^{ref1} = ref2 // error + | ^^^^ + | Found: (ref2 : lazylists.LazyList[Int]^{lazylists.cap2, ref1}) + | Required: lazylists.LazyList[Int]^{ref1} + | + | Note that capability lazylists.cap2 is not included in capture set {ref1}. + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/lazylist-global.scala:43:36 ------------------------------ +43 | val ref3c: LazyList[Int]^{cap2} = ref3 // error + | ^^^^ + | Found: (ref3 : lazylists.LazyList[Int]^{lazylists.cap2, ref1}) + | Required: lazylists.LazyList[Int]^{lazylists.cap2} + | + | Note that capability ref1 is not included in capture set {lazylists.cap2}. + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/lazylist-global.scala:45:42 ------------------------------ +45 | val ref4c: LazyList[Int]^{cap1, ref3} = ref4 // error + | ^^^^ + | Found: (ref4 : lazylists.LazyList[Int]^{lazylists.cap3, lazylists.cap1, lazylists.cap2}) + | Required: lazylists.LazyList[Int]^{lazylists.cap1, ref3} + | + | Note that capability lazylists.cap3 is not included in capture set {lazylists.cap1, ref3}. + | + | longer explanation available when compiling with `-explain` +-- [E164] Declaration Error: tests/neg-custom-args/captures/lazylist-global.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]^{cap} has incompatible type + | + | where: cap is a root capability associated with the result type of -> lazylists.LazyList[Nothing]^ + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/lazylist-global.scala b/tests/neg-custom-args/captures/lazylist-global.scala new file mode 100644 index 000000000000..08f81c53eb77 --- /dev/null +++ b/tests/neg-custom-args/captures/lazylist-global.scala @@ -0,0 +1,45 @@ +package lazylists + +abstract class LazyList[+T]: + this: LazyList[T]^ => + + def isEmpty: Boolean + def head: T + def tail: LazyList[T] + + def map[U](f: T => U): LazyList[U]^{f, this} = + if isEmpty then LazyNil + else LazyCons(f(head), () => tail.map(f)) + +class LazyCons[+T](val x: T, val xs: () => LazyList[T]^) extends LazyList[T]: + def isEmpty = false + def head = x + def tail = xs() // error + +object LazyNil extends LazyList[Nothing]: + def isEmpty = true + def head = ??? + def tail: LazyList[Nothing]^ = ??? // error overriding + +def map[A, B](xs: LazyList[A]^, f: A => B): LazyList[B]^{f, xs} = + xs.map(f) + +class CC +type Cap = CC^ + +val cap1: Cap = CC() +val cap2: Cap = CC() +val cap3: Cap = CC() + +def test() = + def f[T](x: LazyList[T]): LazyList[T] = if cap1 == cap1 then x else LazyNil + def g(x: Int) = if cap2 == cap2 then x else 0 + def h(x: Int) = if cap3 == cap3 then x else 0 + val ref1 = LazyCons(1, () => f(LazyNil)) + val ref1c: LazyList[Int] = ref1 // error + val ref2 = map(ref1, g) + val ref2c: LazyList[Int]^{ref1} = ref2 // error + val ref3 = ref1.map(g) + val ref3c: LazyList[Int]^{cap2} = ref3 // error + val ref4 = (if cap1 == cap2 then ref1 else ref2).map(h) + val ref4c: LazyList[Int]^{cap1, ref3} = ref4 // error diff --git a/tests/neg-custom-args/captures/lazyref-global.scala b/tests/neg-custom-args/captures/lazyref-global.scala new file mode 100644 index 000000000000..5e90069c2f8e --- /dev/null +++ b/tests/neg-custom-args/captures/lazyref-global.scala @@ -0,0 +1,33 @@ + +class CC +type Cap = CC^ + +class LazyRef[T](val elem: () => T): + def get: () => T = elem // error: separation failure + def map[U](f: T => U): LazyRef[U]^{f, this} = + new LazyRef(() => f(elem())) // error: separation failure + +def map[A, B](ref: LazyRef[A]^, f: A => B): LazyRef[B]^{f, ref} = + new LazyRef(() => f(ref.elem())) + +def mapc[A, B]: (ref: LazyRef[A]^, f: A => B) -> LazyRef[B]^{f, ref} = + (ref1, f1) => map[A, B](ref1, f1) + +val cap1: Cap = CC() +val cap2: Cap = CC() + +def test() = + def f(x: Int) = if cap1 == cap1 then x else 0 + def g(x: Int) = if cap2 == cap2 then x else 0 + val ref1 = LazyRef(() => f(0)) + val ref1c: LazyRef[Int] = ref1 // error + val ref2 = map(ref1, g) + val ref2c: LazyRef[Int]^{cap2} = ref2 // error + val ref3 = ref1.map(g) + val ref3c: LazyRef[Int]^{ref1} = ref3 // error + val ref4 = ( + if cap1 == cap2 + then ref1 + else ref2) + .map(g) // error: separation failure + val ref4c: LazyRef[Int]^{cap1} = ref4 // error diff --git a/tests/neg-custom-args/captures/mut-iterator4-global.scala b/tests/neg-custom-args/captures/mut-iterator4-global.scala new file mode 100644 index 000000000000..a3b042157fe5 --- /dev/null +++ b/tests/neg-custom-args/captures/mut-iterator4-global.scala @@ -0,0 +1,35 @@ +import caps.{cap, Stateful, SharedCapability} + +trait Iterator[T] extends Stateful: + def hasNext: Boolean + update def next(): T + + def map[U](f: T => U): Iterator[U]^{Iterator.this, f} = new Iterator: + def hasNext = Iterator.this.hasNext + update def next() = f(Iterator.this.next()) // error + +end Iterator + +def listIterator[T](xs: List[T]): Iterator[T]^ = new Iterator[T]: + private var current = xs + def hasNext = current.nonEmpty + update def next() = xs.runtimeChecked match + case x :: xs1 => + current = xs1 + x + +def mappedIterator[T, U](it: Iterator[T]^, f: T => U): Iterator[U]^{it, f} = new Iterator: // error + def hasNext = it.hasNext + update def next() = f(it.next()) + +class IO extends SharedCapability: + def write(x: Any): Unit = () + +val io: IO = IO() + +def test() = + def proc: Int => Int = i => { io.write(i); i * i } + listIterator(List(1, 2, 3)).map(proc) + val roit: Iterator[Int]^{cap.rd} = listIterator(List(1, 2, 3)) + val mapped = roit.map(proc) + mapped.next() diff --git a/tests/neg-custom-args/captures/nonsensical-for-pure.check b/tests/neg-custom-args/captures/nonsensical-for-pure.check index 2df943df4262..6860446c4177 100644 --- a/tests/neg-custom-args/captures/nonsensical-for-pure.check +++ b/tests/neg-custom-args/captures/nonsensical-for-pure.check @@ -2,9 +2,3 @@ 1 |val x: Int^ = 2 // error | ^^^^ | Int is a pure type, it makes no sense to add a capture set to it --- Warning: tests/neg-custom-args/captures/nonsensical-for-pure.scala:1:4 ---------------------------------------------- -1 |val x: Int^ = 2 // error - | ^ - | value x has a non-empty capture set but will not be added as - | a capability to computed capture sets since it is globally accessible - | as a top-level definition. Global values cannot be capabilities. diff --git a/tests/neg-custom-args/captures/outerRefsUses-global.scala b/tests/neg-custom-args/captures/outerRefsUses-global.scala new file mode 100644 index 000000000000..95bba41c4399 --- /dev/null +++ b/tests/neg-custom-args/captures/outerRefsUses-global.scala @@ -0,0 +1,11 @@ +class IO +val io: IO^ = IO() +def test() = + class C: + def foo() = () => + val x: IO^{this} = io + () + val c = new C + val _: C^{io} = c // ok + val _: C = c // error + () diff --git a/tests/neg-custom-args/captures/path-prefix-global.scala b/tests/neg-custom-args/captures/path-prefix-global.scala new file mode 100644 index 000000000000..c80aa937076e --- /dev/null +++ b/tests/neg-custom-args/captures/path-prefix-global.scala @@ -0,0 +1,46 @@ +import language.experimental.modularity +import language.experimental.captureChecking +import caps.SharedCapability + +class F: + val f: AnyRef^ = ??? + +case class B(tracked val a: A) extends F, SharedCapability + +class A extends F, SharedCapability: + val b: B { val a: A.this.type } = B(this) + +val a: A = A() + +def test() = + val x: a.b.type = a.b + val y: x.a.type = x.a + // x and y are two distinct singleton types with following properties: + // x =:= a.b + // y =:= x.a =:= a.b.a =:= a + + val cx: AnyRef^{x} = ??? + val cy: AnyRef^{y} = ??? + val caf: AnyRef^{a.f} = ??? + val cabf: AnyRef^{a.b.f} = ??? + val cxf: AnyRef^{x.f} = ??? + val cyf: AnyRef^{y.f} = ??? + + // x and y subsume to each other: + // * {x} <:< {y}: the underlying singleton of y is x.a, + // and the underlying singleton of x.a is a, + // which is a prefix for the underlying type of x (a.b), + // hence {x} <:< {y}; + // * {y} <:< {x}: by underlying singleton of y is x.a, whose prefix is x. + // Hence, {x} =:= {y}. + val x2y: AnyRef^{y} = cx + val y2x: AnyRef^{x} = cy + + val yf2af: AnyRef^{a.f} = cyf + val af2yf: AnyRef^{y.f} = caf + val xf2abf: AnyRef^{a.b.f} = cxf + val abf2xf: AnyRef^{x.f} = cabf + + // Since `x !=:= y`, {x.f} !=:= {y.f} + val yf2xf: AnyRef^{x.f} = cyf // error + val xf2yf: AnyRef^{y.f} = cxf // error diff --git a/tests/neg-custom-args/captures/reaches.check b/tests/neg-custom-args/captures/reaches.check index c759da0b68e9..67f47d05529b 100644 --- a/tests/neg-custom-args/captures/reaches.check +++ b/tests/neg-custom-args/captures/reaches.check @@ -32,7 +32,7 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reaches.scala:52:27 -------------------------------------- 52 | val id: Id[Proc, Proc] = new Id[Proc, () -> Unit] // error | ^^^^^^^^^^^^^^^^^^^^^^^^ - | Found: Id[() => Unit, () -> Unit] + | Found: Id[() => Unit, () -> Unit]^'s3 | Required: Id[() =>² Unit, () =>³ Unit] | | Note that capability cap is not included in capture set {cap²} @@ -46,7 +46,7 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reaches.scala:57:27 -------------------------------------- 57 | val id: File^ -> File^ = x => x // error | ^^^^^^ - | Found: (x: File^) ->'s3 File^² + | Found: (x: File^) ->'s4 File^² | Required: File^ -> File^³ | | Note that capability cap is not included in capture set {cap²} @@ -71,8 +71,8 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reaches.scala:85:10 -------------------------------------- 85 | ps.map((x, y) => compose1(x, y)) // error | ^^^^^^^^^^^^^^^^^^^^^^^ - |Found: (x$1: (A^ ->'s4 A^'s5, A^ ->'s6 A^'s7)^'s8) ->'s9 A^'s10 ->'s11 A^'s12 - |Required: ((A ->{ps*} A, A ->{ps*} A)) => A^'s13 ->'s14 A^'s15 + |Found: (x$1: (A^ ->'s5 A^'s6, A^ ->'s7 A^'s8)^'s9) ->'s10 A^'s11 ->'s12 A^'s13 + |Required: ((A ->{ps*} A, A ->{ps*} A)) => A^'s14 ->'s15 A^'s16 | |Note that capability ps* cannot be included in capture set {} of value x. | @@ -83,8 +83,8 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reaches.scala:88:10 -------------------------------------- 88 | ps.map((x, y) => compose1(x, y)) // error | ^^^^^^^^^^^^^^^^^^^^^^^ - |Found: (x$1: (A^ ->'s16 A^'s17, A^ ->'s18 A^'s19)^'s20) ->'s21 A^'s22 ->'s23 A^'s24 - |Required: ((A ->{C} A, A ->{C} A)) => A^'s25 ->'s26 A^'s27 + |Found: (x$1: (A^ ->'s17 A^'s18, A^ ->'s19 A^'s20)^'s21) ->'s22 A^'s23 ->'s24 A^'s25 + |Required: ((A ->{C} A, A ->{C} A)) => A^'s26 ->'s27 A^'s28 | |Note that capability C cannot be included in capture set {} of value x. | diff --git a/tests/neg-custom-args/captures/real-try.check b/tests/neg-custom-args/captures/real-try.check index 89f7487ac9c8..1e71fc964cd6 100644 --- a/tests/neg-custom-args/captures/real-try.check +++ b/tests/neg-custom-args/captures/real-try.check @@ -45,7 +45,7 @@ | it leaks into outer capture set 's5 of value b. | The leakage occurred when trying to match the following types: | - | Found: Cell[() ->{canThrow$4} Unit] - | Required: Cell[() ->'s5 Unit]^'s6 + | Found: Cell[() ->{canThrow$4} Unit]^'s6 + | Required: Cell[() ->'s5 Unit]^'s7 | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/scoped-caps-global.scala b/tests/neg-custom-args/captures/scoped-caps-global.scala new file mode 100644 index 000000000000..6ff371f55656 --- /dev/null +++ b/tests/neg-custom-args/captures/scoped-caps-global.scala @@ -0,0 +1,37 @@ +class A +class B +class S extends caps.SharedCapability + +val io: Object^ = ??? + +def test(): Unit = + val f: (x: A^) -> B^ = ??? + val g: A^ -> B^ = f // error + val _: (y: A^) -> B^ = f + val _: (x: A^) -> B^ = g // error + val _: A^ -> B^ = f // error + val _: A^ -> B^ = g + val _: A^ -> B^ = x => g(x) // error: g is no longer pure, since it contains the ^ of B + val _: (x: A^) -> B^ = x => f(x) // now OK, was error: existential in B cannot subsume `x` since `x` is not shared + + val h: S -> B^ = ??? + val _: (x: S) -> B^ = h // error: direct conversion fails + val _: (x: S) -> B^ = (x: S) => h(x) // eta expansion is ok + + val h2: S -> S = ??? + val _: (x: S) -> S = h2 // direct conversion OK for shared S + val _: (x: S) -> S = (x: S) => h2(x) // eta conversion is also OK + + val j: (x: S) -> B^ = ??? + val _: (x: S) -> B^ = j + val _: (x: S) -> B^ = x => j(x) + val _: S -> B^ = j // error + val _: S -> B^ = x => j(x) // error + + val g2: A^ => B^ = ??? + val _: A^ => B^ = x => + val y = g2(x) // now ok since g2(x): B^{g2*} + val _: B^{g2*} = y + y + val g3: A^ => B^ = ??? + val _: A^{io} => B^ = x => g3(x) // now g3(x): B^{g3*} diff --git a/tests/neg-custom-args/captures/sep-pairs-global.scala b/tests/neg-custom-args/captures/sep-pairs-global.scala new file mode 100644 index 000000000000..48c6aaf72918 --- /dev/null +++ b/tests/neg-custom-args/captures/sep-pairs-global.scala @@ -0,0 +1,48 @@ +import caps.Mutable +import caps.cap + +class Ref extends Mutable: + var x = 0 + def get: Int = x + update def put(y: Int): Unit = x = y + +class Pair[+X, +Y](val fst: X, val snd: Y) + +def mkPair[X](x: X): Pair[X, X] = Pair(x, x) + +def bad: Pair[Ref^, Ref^] = // error: overlap at r1*, r0 + val r0 = Ref() + val r1: Pair[Ref^, Ref^] = mkPair(r0) // error: overlap at r0 + r1 + +class SamePair[+X](val fst: X, val snd: X) + +def twoRefs(): Pair[Ref^, Ref^] = + val r1 = Ref() + val r2 = Ref() + Pair(r1, r2) + +def twoRefs2(): SamePair[Ref^] = + val r1 = Ref() + val r2 = Ref() + val r3: SamePair[Ref^] = SamePair(r1, r1) // ok + r3 + +def twoRefsBad(): Pair[Ref^, Ref^] = + Pair(Ref(), Ref()) // ok now + +val io: Object^ = ??? + +def test(): Unit = + val two = twoRefs() + val fst: Ref^{two.fst*} = two.fst + val snd: Ref^{two.snd*} = two.snd + val twoCopy: Pair[Ref^, Ref^] = Pair(fst, snd) // ok + + val same = twoRefs2() + val fstSame = same.fst + val sndSame = same.snd + val sameToPair: Pair[Ref^, Ref^] = Pair(fstSame, sndSame) // error + + + diff --git a/tests/neg-custom-args/captures/unscoped-classifier-global.scala b/tests/neg-custom-args/captures/unscoped-classifier-global.scala new file mode 100644 index 000000000000..bf71745710e6 --- /dev/null +++ b/tests/neg-custom-args/captures/unscoped-classifier-global.scala @@ -0,0 +1,23 @@ +import caps.* + +trait Async extends Control + +class A(a: Async) extends caps.Unscoped // error but msg could be better + +class B extends caps.Unscoped: + val a: Async^ = new Async {} // ok (should this be an error instead?) + +class C(f: () => Unit) extends caps.Unscoped // error but msg could be better + +class D extends caps.Unscoped: + val f: () => Unit = ??? // ok (should this be an error instead?) + +val g: () => Unit = () => () + +def test() = + class E extends caps.Unscoped: + def gg() = g() // error but msg could be better + + val b = B() + val d = D() // ok (?) + val _: D^{cap.only[Unscoped]} = d diff --git a/tests/neg-custom-args/captures/unscoped-classifier.check b/tests/neg-custom-args/captures/unscoped-classifier.check index b891c64907bd..6df15e71c769 100644 --- a/tests/neg-custom-args/captures/unscoped-classifier.check +++ b/tests/neg-custom-args/captures/unscoped-classifier.check @@ -17,15 +17,3 @@ | method gg should be declared an update method to allow this. | | where: => refers to a fresh root capability in the type of parameter g --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/unscoped-classifier.scala:19:11 -------------------------- -19 | val b = B() // error but msg could be better - | ^^^ - |Found: B^ - |Required: B^'s1 - | - |Note that capability cap is not classified as trait Unscoped, therefore it - |cannot be included in capture set 's1 of value b of Unscoped elements. - | - |where: ^ and cap refer to a fresh root capability classified as Nothing created in value b when constructing instance B - | - | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/unscoped-classifier.scala b/tests/neg-custom-args/captures/unscoped-classifier.scala index 12493963b827..4fd94efa56ec 100644 --- a/tests/neg-custom-args/captures/unscoped-classifier.scala +++ b/tests/neg-custom-args/captures/unscoped-classifier.scala @@ -16,6 +16,6 @@ def test(g: () => Unit) = class E extends caps.Unscoped: def gg() = g() // error but msg could be better - val b = B() // error but msg could be better + val b = B() val d = D() // ok (?) val _: D^{cap.only[Unscoped]} = d diff --git a/tests/neg-custom-args/captures/unsound-reach-4.check b/tests/neg-custom-args/captures/unsound-reach-4.check index 6e4271cf0057..344679727343 100644 --- a/tests/neg-custom-args/captures/unsound-reach-4.check +++ b/tests/neg-custom-args/captures/unsound-reach-4.check @@ -8,7 +8,7 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/unsound-reach-4.scala:20:29 ------------------------------ 20 | val backdoor: Foo[File^] = new Bar // error (follow-on, since the parent Foo[File^] of bar is illegal). | ^^^^^^^ - | Found: Bar + | Found: Bar^'s1 | Required: Foo[File^] | | Note that capability cap is not included in capture set {cap²} diff --git a/tests/neg-custom-args/captures/unsound-reach.check b/tests/neg-custom-args/captures/unsound-reach.check index e426cec0c0ac..4cf788de0f24 100644 --- a/tests/neg-custom-args/captures/unsound-reach.check +++ b/tests/neg-custom-args/captures/unsound-reach.check @@ -8,7 +8,7 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/unsound-reach.scala:18:31 -------------------------------- 18 | val backdoor: Foo[File^] = new Bar // error | ^^^^^^^ - | Found: Bar + | Found: Bar^'s1 | Required: Foo[File^] | | Note that capability cap is not included in capture set {cap²} @@ -21,7 +21,7 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/unsound-reach.scala:27:32 -------------------------------- 27 | val backdoor: Foo2[File^] = new Bar2 // error | ^^^^^^^^ - | Found: Bar2 + | Found: Bar2^'s2 | Required: Foo2[File^] | | Note that capability cap is not included in capture set {cap²} diff --git a/tests/neg-custom-args/captures/vars-simple-global.scala b/tests/neg-custom-args/captures/vars-simple-global.scala new file mode 100644 index 000000000000..4134e34aa56a --- /dev/null +++ b/tests/neg-custom-args/captures/vars-simple-global.scala @@ -0,0 +1,21 @@ +class CC +type Cap = CC^ + +val cap1: Cap = CC() +val cap2: Cap = CC() + +def test() = + var a: String ->{cap1, cap2} String = ??? + var b: List[String ->{cap1, cap2} String] = Nil + def f(x: String): String = if cap1 == cap1 then "" else "a" + a = f // ok + val x = List(f) + b = x // ok + b = List(f) // ok + + def scope(cap3: Cap) = + def g(x: String): String = if cap3 == cap3 then "" else "a" + a = (g: String => String) // error + a = g // error + b = List(g) // error + diff --git a/tests/neg-custom-args/captures/widen-reach-global.scala b/tests/neg-custom-args/captures/widen-reach-global.scala new file mode 100644 index 000000000000..2e397670c0a6 --- /dev/null +++ b/tests/neg-custom-args/captures/widen-reach-global.scala @@ -0,0 +1,16 @@ +import language.experimental.captureChecking + +trait IO + +trait Foo[+T]: + val foo: IO^ -> T + +trait Bar extends Foo[IO^]: // error + val foo: IO^ -> IO^ = x => x // error + +val x: Foo[IO^] = ??? + +def test(): Unit = + val y1: Foo[IO^{x*}] = x + val y2: IO^ -> IO^ = y1.foo // error + val y3: IO^ -> IO^{x*} = y1.foo // error \ No newline at end of file diff --git a/tests/neg-custom-args/captures/withFile.check b/tests/neg-custom-args/captures/withFile.check new file mode 100644 index 000000000000..0eda97de2804 --- /dev/null +++ b/tests/neg-custom-args/captures/withFile.check @@ -0,0 +1,64 @@ +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/withFile.scala:14:38 ------------------------------------- +14 | private val later1 = usingLogFile { f => () => f.write() } // error + | ^^^^^^^^^^^^^^^^^^^^ + |Capability f outlives its scope: it leaks into outer capture set 's1 which is owned by value later1. + |The leakage occurred when trying to match the following types: + | + |Found: (f: Test2.File^'s2) ->'s3 () ->{f} Unit + |Required: Test2.File^ => () ->'s1 Unit + | + |where: => refers to a fresh root capability created in value later1 when checking argument to parameter op of method usingLogFile + | ^ refers to the universal root capability + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/withFile.scala:15:38 ------------------------------------- +15 | private val later2 = usingLogFile { f => Box(f) } // error + | ^^^^^^^^^^^ + |Capability cap outlives its scope: it leaks into outer capture set 's4 which is owned by value later2. + |The leakage occurred when trying to match the following types: + | + |Found: (f: Test2.File^'s5) ->'s6 Test2.Box[Test2.File^'s7]^'s8 + |Required: Test2.File^ => Test2.Box[Test2.File^'s4]^'s9 + | + |where: => refers to a fresh root capability created in value later2 when checking argument to parameter op of method usingLogFile + | ^ refers to the universal root capability + | cap is a root capability associated with the result type of (f: Test2.File^): Test2.Box[Test2.File^'s4]^'s9 + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/withFile.scala:17:4 -------------------------------------- +17 | f => () => f.write() // error + | ^^^^^^^^^^^^^^^^^^^^ + |Found: (f: Test2.File^'s10) ->'s11 () ->{f} Unit + |Required: Test2.File^ => () =>² Unit + | + |Note that capability f is not included in capture set {cap} + |because (f : Test2.File^'s10) is not visible from cap in value later3. + | + |where: => refers to a fresh root capability created in value later3 when checking argument to parameter op of method usingLogFile + | =>² and cap refer to a fresh root capability created in value later3 + | ^ refers to the universal root capability + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/withFile.scala:19:4 -------------------------------------- +19 | f => Box(f) // error + | ^^^^^^^^^^^ + |Found: (f: Test2.File^'s12) ->'s13 Test2.Box[Test2.File^'s14]^'s15 + |Required: Test2.File^ => Test2.Box[Test2.File^²] + | + |Note that capability cap is not included in capture set {cap²} + |because cap is not visible from cap² in value later4. + | + |where: => refers to a fresh root capability created in value later4 when checking argument to parameter op of method usingLogFile + | ^ refers to the universal root capability + | ^² and cap² refer to a fresh root capability created in value later4 + | cap is a root capability associated with the result type of (f: Test2.File^'s12): Test2.Box[Test2.File^'s14]^'s15 + | + | longer explanation available when compiling with `-explain` +-- Error: tests/neg-custom-args/captures/withFile.scala:16:20 ---------------------------------------------------------- +16 | private val later3 = usingLogFile[() => Unit]: // error + | ^ + | value later3 needs an explicit type because it captures a root capability in its type () => Unit. + | Fields capturing a root capability need to be given an explicit type unless the capability is already + | subsumed by the computed capability of the enclosing class. + | + | where: => refers to a fresh root capability in the type of value later3 diff --git a/tests/neg-custom-args/captures/withFile.scala b/tests/neg-custom-args/captures/withFile.scala index 1eb458f8db83..b1b38001495b 100644 --- a/tests/neg-custom-args/captures/withFile.scala +++ b/tests/neg-custom-args/captures/withFile.scala @@ -13,7 +13,7 @@ object Test2: private val later1 = usingLogFile { f => () => f.write() } // error private val later2 = usingLogFile { f => Box(f) } // error - private val later3 = usingLogFile[() => Unit]: + private val later3 = usingLogFile[() => Unit]: // error f => () => f.write() // error private val later4 = usingLogFile[Box[File^]]: f => Box(f) // error diff --git a/tests/neg/mixed_cc.check b/tests/neg/mixed_cc.check new file mode 100644 index 000000000000..1876af06e2fc --- /dev/null +++ b/tests/neg/mixed_cc.check @@ -0,0 +1,4 @@ +-- Error: tests/neg/mixed_cc/B.scala:4:7 ------------------------------------------------------------------------------- +4 |val x: Object^{A.B} = ??? // error + | ^^^^^^^^^^^^ + | mixed_cc.A.B.type cannot be tracked since its capture set is empty diff --git a/tests/neg/mixed_cc/A.scala b/tests/neg/mixed_cc/A.scala new file mode 100644 index 000000000000..34443b892c7b --- /dev/null +++ b/tests/neg/mixed_cc/A.scala @@ -0,0 +1,4 @@ +package mixed_cc + +object A: + object B diff --git a/tests/neg/mixed_cc/B.scala b/tests/neg/mixed_cc/B.scala new file mode 100644 index 000000000000..d7461d5522a4 --- /dev/null +++ b/tests/neg/mixed_cc/B.scala @@ -0,0 +1,4 @@ +package mixed_cc +import language.experimental.captureChecking + +val x: Object^{A.B} = ??? // error \ No newline at end of file diff --git a/tests/new/test.scala b/tests/new/test.scala index cc8a16ab267a..00174769a0f0 100644 --- a/tests/new/test.scala +++ b/tests/new/test.scala @@ -1,453 +1 @@ -/* - * Scala (https://www.scala-lang.org) - * - * Copyright EPFL and Lightbend, Inc. dba Akka - * - * Licensed under Apache License 2.0 - * (http://www.apache.org/licenses/LICENSE-2.0). - * - * See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - */ - -package scala -package collection - -import scala.language.`2.13` -import language.experimental.captureChecking - -import scala.annotation.{retains, nowarn} -import scala.collection.generic.DefaultSerializable -import scala.collection.mutable.StringBuilder -import scala.util.hashing.MurmurHash3 - -/** Base Map type. */ -trait Map[K, +V] - extends Iterable[(K, V)] - with MapOps[K, V, Map, Map[K, V]] - with MapFactoryDefaults[K, V, Map, Iterable] - with Equals - with caps.Pure { - - def mapFactory: scala.collection.MapFactory[Map] = Map - - def canEqual(that: Any): Boolean = true - - /** - * Equality of maps is implemented using the lookup method [[get]]. This method returns `true` if - * - the argument `o` is a `Map`, - * - the two maps have the same [[size]], and - * - for every `(key, value)` pair in this map, `other.get(key) == Some(value)`. - * - * The implementation of `equals` checks the [[canEqual]] method, so subclasses of `Map` can narrow down the equality - * to specific map types. The `Map` implementations in the standard library can all be compared, their `canEqual` - * methods return `true`. - * - * Note: The `equals` method only respects the equality laws (symmetry, transitivity) if the two maps use the same - * key equivalence function in their lookup operation. For example, the key equivalence operation in a - * [[scala.collection.immutable.TreeMap]] is defined by its ordering. Comparing a `TreeMap` with a `HashMap` leads - * to unexpected results if `ordering.equiv(k1, k2)` (used for lookup in `TreeMap`) is different from `k1 == k2` - * (used for lookup in `HashMap`). - * - * {{{ - * scala> import scala.collection.immutable._ - * scala> val ord: Ordering[String] = _ compareToIgnoreCase _ - * - * scala> TreeMap("A" -> 1)(ord) == HashMap("a" -> 1) - * val res0: Boolean = false - * - * scala> HashMap("a" -> 1) == TreeMap("A" -> 1)(ord) - * val res1: Boolean = true - * }}} - * - * - * @param o The map to which this map is compared - * @return `true` if the two maps are equal according to the description - */ - override def equals(o: Any): Boolean = - (this eq o.asInstanceOf[AnyRef]) || (o match { - case map: Map[K @unchecked, _] if map.canEqual(this) => - (this.size == map.size) && { - try this.forall(kv => map.getOrElse(kv._1, Map.DefaultSentinelFn()) == kv._2) - catch { case _: ClassCastException => false } // PR #9565 / scala/bug#12228 - } - case _ => - false - }) - - override def hashCode(): Int = MurmurHash3.mapHash(this) - - // These two methods are not in MapOps so that MapView is not forced to implement them - @deprecated("Use - or removed on an immutable Map", "2.13.0") - def - (key: K): Map[K, V] - @deprecated("Use -- or removedAll on an immutable Map", "2.13.0") - def - (key1: K, key2: K, keys: K*): Map[K, V] - - @nowarn("""cat=deprecation&origin=scala\.collection\.Iterable\.stringPrefix""") - override protected def stringPrefix: String = "Map" - - override def toString(): String = super[Iterable].toString() // Because `Function1` overrides `toString` too -} - -/** Base Map implementation type - * - * @tparam K Type of keys - * @tparam V Type of values - * @tparam CC type constructor of the map (e.g. `HashMap`). Operations returning a collection - * with a different type of entries `(L, W)` (e.g. `map`) return a `CC[L, W]`. - * @tparam C type of the map (e.g. `HashMap[Int, String]`). Operations returning a collection - * with the same type of element (e.g. `drop`, `filter`) return a `C`. - * @define coll map - * @define Coll `Map` - */ -// Note: the upper bound constraint on CC is useful only to -// erase CC to IterableOps instead of Object -transparent trait MapOps[K, +V, +CC[_, _] <: IterableOps[?, AnyConstr, ?], +C] - extends IterableOps[(K, V), Iterable, C] - with PartialFunction[K, V] { self: MapOps[K, V, CC, C]^ => - - override def view: MapView[K, V]^{this} = new MapView.Id(this) - - /** Returns a [[Stepper]] for the keys of this map. See method [[stepper]]. */ - def keyStepper[S <: Stepper[?]](implicit shape: StepperShape[K, S]): S = { - import convert.impl._ - val s = shape.shape match { - case StepperShape.IntShape => new IntIteratorStepper (keysIterator.asInstanceOf[Iterator[Int]]) - case StepperShape.LongShape => new LongIteratorStepper (keysIterator.asInstanceOf[Iterator[Long]]) - case StepperShape.DoubleShape => new DoubleIteratorStepper(keysIterator.asInstanceOf[Iterator[Double]]) - case _ => shape.seqUnbox(new AnyIteratorStepper(keysIterator)) - } - s.asInstanceOf[S] - } - - /** Returns a [[Stepper]] for the values of this map. See method [[stepper]]. */ - def valueStepper[S <: Stepper[?]](implicit shape: StepperShape[V, S]): S = { - import convert.impl._ - val s = shape.shape match { - case StepperShape.IntShape => new IntIteratorStepper (valuesIterator.asInstanceOf[Iterator[Int]]) - case StepperShape.LongShape => new LongIteratorStepper (valuesIterator.asInstanceOf[Iterator[Long]]) - case StepperShape.DoubleShape => new DoubleIteratorStepper(valuesIterator.asInstanceOf[Iterator[Double]]) - case _ => shape.seqUnbox(new AnyIteratorStepper(valuesIterator)) - } - s.asInstanceOf[S] - } - - /** Similar to `fromIterable`, but returns a Map collection type. - * Note that the return type is now `CC[K2, V2]`. - */ - @`inline` protected final def mapFromIterable[K2, V2](it: Iterable[(K2, V2)]^): CC[K2, V2]^{it} = mapFactory.from(it) - - /** The companion object of this map, providing various factory methods. - * - * @note When implementing a custom collection type and refining `CC` to the new type, this - * method needs to be overridden to return a factory for the new type (the compiler will - * issue an error otherwise). - */ - def mapFactory: MapFactory[CC] - - /** Optionally returns the value associated with a key. - * - * @param key the key value - * @return an option value containing the value associated with `key` in this map, - * or `None` if none exists. - */ - def get(key: K): Option[V] - - /** Returns the value associated with a key, or a default value if the key is not contained in the map. - * @param key the key. - * @param default a computation that yields a default value in case no binding for `key` is - * found in the map. - * @tparam V1 the result type of the default computation. - * @return the value associated with `key` if it exists, - * otherwise the result of the `default` computation. - */ - def getOrElse[V1 >: V](key: K, default: => V1): V1 = get(key) match { - case Some(v) => v - case None => default - } - - /** Retrieves the value which is associated with the given key. This - * method invokes the `default` method of the map if there is no mapping - * from the given key to a value. Unless overridden, the `default` method throws a - * `NoSuchElementException`. - * - * @param key the key - * @return the value associated with the given key, or the result of the - * map's `default` method, if none exists. - */ - @throws[NoSuchElementException] - def apply(key: K): V = get(key) match { - case None => default(key) - case Some(value) => value - } - - override /*PartialFunction*/ def applyOrElse[K1 <: K, V1 >: V](x: K1, default: K1 => V1): V1 = getOrElse(x, default(x)) - - /** A set representing the keys contained by this map. - * - * For efficiency the resulting set may be a view (maintaining a reference to the map and reflecting modifications - * to the map), but it may also be a strict collection without reference to the map. - * - * - To ensure an independent strict collection, use `m.keysIterator.toSet` - * - To obtain a view on the keys, use `scala.collection.View.fromIteratorProvider(m.keysIterator)` - * - * @return a set representing the keys contained by this map - */ - def keySet: Set[K] = - // If we know one of the strict implementations inside this library, simply return LazyKeySet - import MapOps.LazyKeySet - (this: @unchecked) match - case s: SeqMap[K, V] => new LazyKeySet(s) - case s: SortedMap[K, V] => new LazyKeySet(s) - case s: immutable.MapOps[K, V, immutable.Map, immutable.Map[K, V]] => new LazyKeySet(s) - case s: mutable.MapOps[K, V, mutable.Map, mutable.Map[K, V]] => new LazyKeySet(s) - case _ => new KeySet - - /** The implementation class of the set returned by `keySet`. - */ - protected class KeySet extends AbstractSet[K] with GenKeySet with DefaultSerializable { - // If you need a generic, capturing KeySet, create a View from keysIterator - def diff(that: Set[K]): Set[K] = fromSpecific(allKeys.filterNot(that)) - } - - /** A generic trait that is reused by keyset implementations. - * Note that this version of KeySet copies all the keys into an interval val. - * See [[MapOps.LazyKeySet]] for a version that lazily captures the map. - */ - protected trait GenKeySet @retains[MapOps.this.type] () { // todo change @retains to uses_init when we bootstrap with 3.8.1 - this: Set[K] => - // CC note: this is unavoidable to make the KeySet pure. - private[MapOps] val allKeys = MapOps.this.keysIterator.toList - // We restore the lazy behavior in LazyKeySet - def iterator: Iterator[K] = - allKeys.iterator - def contains(key: K): Boolean = allKeys.contains(key) - override def size: Int = allKeys.size - override def knownSize: Int = allKeys.knownSize - override def isEmpty: Boolean = allKeys.isEmpty - } - - /** An [[Iterable]] collection of the keys contained by this map. - * - * For efficiency the resulting collection may be a view (maintaining a reference to the map and reflecting - * modifications to the map), but it may also be a strict collection without reference to the map. - * - * - To ensure an independent strict collection, use `m.keysIterator.toSet` - * - To obtain a view on the keys, use `scala.collection.View.fromIteratorProvider(m.keysIterator)` - * - * @return an [[Iterable]] collection of the keys contained by this map - */ - @deprecatedOverriding("This method should be an alias for keySet", since="2.13.13") - def keys: Iterable[K]^{this} = this.keySet - - /** Collects all values of this map in an iterable collection. - * - * @return the values of this map as an iterable. - */ - def values: Iterable[V]^{this} = new AbstractIterable[V] with DefaultSerializable { - override def knownSize: Int = MapOps.this.knownSize - override def iterator: Iterator[V]^{self} = valuesIterator - } - - /** An [[Iterator]] of the keys contained by this map. - * - * @return an [[Iterator]] of the keys contained by this map - */ - def keysIterator: Iterator[K]^{this} = new AbstractIterator[K] { - val iter = MapOps.this.iterator - def hasNext = iter.hasNext - def next() = iter.next()._1 - } - - /** Creates an iterator for all values in this map. - * - * @return an iterator over all values that are associated with some key in this map. - */ - def valuesIterator: Iterator[V]^{this} = new AbstractIterator[V] { - val iter = MapOps.this.iterator - def hasNext = iter.hasNext - def next() = iter.next()._2 - } - - /** Applies `f` to each key/value pair for its side effects - * Note: [U] parameter needed to help scalac's type inference. - */ - def foreachEntry[U](f: (K, V) => U): Unit = { - val it = iterator - while (it.hasNext) { - val next = it.next() - f(next._1, next._2) - } - } - - /** Filters this map by retaining only keys satisfying a predicate. - * @param p the predicate used to test keys - * @return an immutable map consisting only of those key value pairs of this map where the key satisfies - * the predicate `p`. The resulting map wraps the original map without copying any elements. - */ - @deprecated("Use .view.filterKeys(f). A future version will include a strict version of this method (for now, .view.filterKeys(p).toMap).", "2.13.0") - def filterKeys(p: K => Boolean): MapView[K, V]^{this, p} = new MapView.FilterKeys(this, p) - - /** Transforms this map by applying a function to every retrieved value. - * @param f the function used to transform values of this map. - * @return a map view which maps every key of this map - * to `f(this(key))`. The resulting map wraps the original map without copying any elements. - */ - @deprecated("Use .view.mapValues(f). A future version will include a strict version of this method (for now, .view.mapValues(f).toMap).", "2.13.0") - def mapValues[W](f: V => W): MapView[K, W]^{this, f} = new MapView.MapValues(this, f) - - /** Defines the default value computation for the map, - * returned when a key is not found. - * - * The method implemented here throws an exception, - * but it may be overridden by subclasses. - * - * @param key the given key value for which a binding is missing. - * @throws NoSuchElementException if no default value is defined - */ - @throws[NoSuchElementException] - def default(key: K): V = - throw new NoSuchElementException("key not found: " + key) - - /** Tests whether this map contains a binding for a key. - * - * @param key the key - * @return `true` if there is a binding for `key` in this map, `false` otherwise. - */ - def contains(key: K): Boolean = get(key).isDefined - - - /** Tests whether this map contains a binding for a key. This method, - * which implements an abstract method of trait `PartialFunction`, - * is equivalent to `contains`. - * - * @param key the key - * @return `true` if there is a binding for `key` in this map, `false` otherwise. - */ - def isDefinedAt(key: K): Boolean = contains(key) - - /** Builds a new map by applying a function to all elements of this $coll. - * - * @param f the function to apply to each element. - * @return a new $coll resulting from applying the given function - * `f` to each element of this $coll and collecting the results. - */ - def map[K2, V2](f: ((K, V)) => (K2, V2)): CC[K2, V2]^{this, f} = mapFactory.from(new View.Map(this, f)) - - /** Builds a new collection by applying a partial function to all elements of this $coll - * on which the function is defined. - * - * @param pf the partial function which filters and maps the $coll. - * @tparam K2 the key type of the returned $coll. - * @tparam V2 the value type of the returned $coll. - * @return a new $coll resulting from applying the given partial function - * `pf` to each element on which it is defined and collecting the results. - * The order of the elements is preserved. - */ - def collect[K2, V2](pf: PartialFunction[(K, V), (K2, V2)]^): CC[K2, V2]^{this, pf} = - mapFactory.from(new View.Collect(this, pf)) - - /** Builds a new map by applying a function to all elements of this $coll - * and using the elements of the resulting collections. - * - * @param f the function to apply to each element. - * @return a new $coll resulting from applying the given collection-valued function - * `f` to each element of this $coll and concatenating the results. - */ - def flatMap[K2, V2](f: ((K, V)) => IterableOnce[(K2, V2)]^): CC[K2, V2]^{this, f} = mapFactory.from(new View.FlatMap(this, f)) - - /** Returns a new $coll containing the elements from the left hand operand followed by the elements from the - * right hand operand. The element type of the $coll is the most specific superclass encompassing - * the element types of the two operands. - * - * @param suffix the iterable to append. - * @return a new $coll which contains all elements - * of this $coll followed by all elements of `suffix`. - */ - def concat[V2 >: V](suffix: collection.IterableOnce[(K, V2)]^): CC[K, V2]^{this, suffix} = mapFactory.from(suffix match { - case it: Iterable[(K, V2)] => new View.Concat(this, it) - case _ => iterator.concat(suffix.iterator) - }) - - // Not final because subclasses refine the result type, e.g. in SortedMap, the result type is - // SortedMap's CC, while Map's CC is fixed to Map - /** Alias for `concat`. */ - /*@`inline` final*/ def ++ [V2 >: V](xs: collection.IterableOnce[(K, V2)]^): CC[K, V2]^{this, xs} = concat(xs) - - override def addString(sb: StringBuilder, start: String, sep: String, end: String): sb.type = - iterator.map { case (k, v) => s"$k -> $v" }.addString(sb, start, sep, end) - - @deprecated("Consider requiring an immutable Map or fall back to Map.concat.", "2.13.0") - def + [V1 >: V](kv: (K, V1)): CC[K, V1]^{this} = - mapFactory.from(new View.Appended(this, kv)) - - @deprecated("Use ++ with an explicit collection argument instead of + with varargs", "2.13.0") - def + [V1 >: V](elem1: (K, V1), elem2: (K, V1), elems: (K, V1)*): CC[K, V1]^{this} = - mapFactory.from(new View.Concat(new View.Appended(new View.Appended(this, elem1), elem2), elems)) - - @deprecated("Consider requiring an immutable Map.", "2.13.0") - @`inline` def -- (keys: IterableOnce[K]^): C^{this, keys} = { - lazy val keysSet = keys.iterator.to(immutable.Set) - fromSpecific(this.view.filterKeys(k => !keysSet.contains(k))) - } - - @deprecated("Use ++ instead of ++: for collections of type Iterable", "2.13.0") - def ++: [V1 >: V](that: IterableOnce[(K,V1)]^): CC[K,V1]^{this, that} = { - val thatIterable: Iterable[(K, V1)]^{that} = that match { - case that: Iterable[(K, V1)] => that - case that => View.from(that) - } - mapFactory.from(new View.Concat(thatIterable, this)) - } -} - -object MapOps { - /** Specializes `WithFilter` for Map collection types by adding overloads to transformation - * operations that can return a Map. - * - * @define coll map collection - */ - @SerialVersionUID(3L) - class WithFilter[K, +V, +IterableCC[_], +CC[_, _] <: IterableOps[?, AnyConstr, ?]]( - self: (MapOps[K, V, CC, ?] & IterableOps[(K, V), IterableCC, ?])^, - p: ((K, V)) => Boolean - ) extends IterableOps.WithFilter[(K, V), IterableCC](self, p) with Serializable { - - def map[K2, V2](f: ((K, V)) => (K2, V2)): CC[K2, V2]^{this, f} = - self.mapFactory.from(new View.Map(filtered, f)) - - def flatMap[K2, V2](f: ((K, V)) => IterableOnce[(K2, V2)]^): CC[K2, V2]^{this, f} = - self.mapFactory.from(new View.FlatMap(filtered, f)) - - override def withFilter(q: ((K, V)) => Boolean): WithFilter[K, V, IterableCC, CC]^{this, q} = - new WithFilter[K, V, IterableCC, CC](self, (kv: (K, V)) => p(kv) && q(kv)) - - } - - - /** The implementation class of the set returned by `keySet`, for pure maps. - */ - private class LazyKeySet[K, +V, +CC[_, _] <: IterableOps[?, AnyConstr, ?], +C](mp: MapOps[K, V, CC, C]) extends AbstractSet[K] with DefaultSerializable { - def iterator: Iterator[K] = mp.keysIterator - def diff(that: Set[K]): Set[K] = LazyKeySet.this.fromSpecific(this.view.filterNot(that)) - def contains(key: K): Boolean = mp.contains(key) - override def size: Int = mp.size - override def knownSize: Int = mp.knownSize - override def isEmpty: Boolean = mp.isEmpty - } -} - -/** - * $factoryInfo - * @define coll map - * @define Coll `Map` - */ -@SerialVersionUID(3L) -object Map extends MapFactory.Delegate[Map](immutable.Map) { - private val DefaultSentinel: AnyRef = new AnyRef - private val DefaultSentinelFn: () -> AnyRef = () => DefaultSentinel -} - -/** Explicit instantiation of the `Map` trait to reduce class file size in subclasses. */ -abstract class AbstractMap[K, +V] extends AbstractIterable[(K, V)] with Map[K, V] - +object Test