From e0256bdc0d8cb473b670a350d8263c8ee9cafe32 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Mon, 14 Jul 2025 16:24:26 +0200 Subject: [PATCH 001/101] Enhance pattern matching with capturing types; fix stdlib-cc --- .../tools/dotc/transform/PatternMatcher.scala | 7 ++++++- .../src/scala/collection/IndexedSeqView.scala | 2 +- .../mutable/CheckedIndexedSeqView.scala | 2 +- tests/neg-custom-args/captures/match.scala | 21 +++++++++++++++++++ 4 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 tests/neg-custom-args/captures/match.scala diff --git a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala index 8bf88a0027c4..b4d766a1bd24 100644 --- a/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala +++ b/compiler/src/dotty/tools/dotc/transform/PatternMatcher.scala @@ -14,9 +14,11 @@ import Flags.*, Constants.* import Decorators.* import NameKinds.{PatMatStdBinderName, PatMatAltsName, PatMatResultName} import config.Printers.patmatch +import config.Feature import reporting.* import ast.* import util.Property.* +import cc.{CapturingType, Capabilities} import scala.annotation.tailrec import scala.collection.mutable @@ -427,8 +429,11 @@ object PatternMatcher { && !hasExplicitTypeArgs(extractor) case _ => false } + val castTp = if Feature.ccEnabled + then CapturingType(tpt.tpe, scrutinee.termRef.singletonCaptureSet) + else tpt.tpe TestPlan(TypeTest(tpt, isTrusted), scrutinee, tree.span, - letAbstract(ref(scrutinee).cast(tpt.tpe)) { casted => + letAbstract(ref(scrutinee).cast(castTp)) { casted => nonNull += casted patternPlan(casted, pat, onSuccess) }) diff --git a/scala2-library-cc/src/scala/collection/IndexedSeqView.scala b/scala2-library-cc/src/scala/collection/IndexedSeqView.scala index 78f8abb8e327..07698bc09951 100644 --- a/scala2-library-cc/src/scala/collection/IndexedSeqView.scala +++ b/scala2-library-cc/src/scala/collection/IndexedSeqView.scala @@ -160,7 +160,7 @@ object IndexedSeqView { @SerialVersionUID(3L) class Reverse[A](underlying: SomeIndexedSeqOps[A]^) extends SeqView.Reverse[A](underlying) with IndexedSeqView[A] { - override def reverse: IndexedSeqView[A] = underlying match { + override def reverse: IndexedSeqView[A]^{underlying} = underlying match { case x: IndexedSeqView[A] => x case _ => super.reverse } diff --git a/scala2-library-cc/src/scala/collection/mutable/CheckedIndexedSeqView.scala b/scala2-library-cc/src/scala/collection/mutable/CheckedIndexedSeqView.scala index 1c3f669f5358..89e3dfb78d8e 100644 --- a/scala2-library-cc/src/scala/collection/mutable/CheckedIndexedSeqView.scala +++ b/scala2-library-cc/src/scala/collection/mutable/CheckedIndexedSeqView.scala @@ -101,7 +101,7 @@ private[mutable] object CheckedIndexedSeqView { @SerialVersionUID(3L) class Reverse[A](underlying: SomeIndexedSeqOps[A]^)(protected val mutationCount: () ->{cap.rd} Int) extends IndexedSeqView.Reverse[A](underlying) with CheckedIndexedSeqView[A] { - override def reverse: IndexedSeqView[A] = underlying match { + override def reverse: IndexedSeqView[A]^{underlying} = underlying match { case x: IndexedSeqView[A] => x case _ => super.reverse } diff --git a/tests/neg-custom-args/captures/match.scala b/tests/neg-custom-args/captures/match.scala new file mode 100644 index 000000000000..9f7c9c6d8e74 --- /dev/null +++ b/tests/neg-custom-args/captures/match.scala @@ -0,0 +1,21 @@ +import language.experimental.captureChecking + +trait A + +case class B(x: AnyRef^) extends A + +def test = + val x: AnyRef^ = new AnyRef + val a: A^{x} = B(x) + + val y1: A = a match + case b: B => b // error: (b: B) becomes B^{x} implicitly + + val y2: A^{x} = a match + case b: B => b // ok + + val x3: AnyRef = a match + case B(x2: AnyRef) => x2 // error: we lose some information about field x, but it still cannot be pure + + val x4: AnyRef = a match + case b: B => b.x // error From 484e97a68c778b10e260cfbf4797ddca9b77dc58 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Fri, 15 Aug 2025 13:57:17 +0200 Subject: [PATCH 002/101] Try to bypass separation check in case body --- scala2-library-cc/src/scala/collection/View.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scala2-library-cc/src/scala/collection/View.scala b/scala2-library-cc/src/scala/collection/View.scala index 72a073836e77..d93ccaa89f20 100644 --- a/scala2-library-cc/src/scala/collection/View.scala +++ b/scala2-library-cc/src/scala/collection/View.scala @@ -152,8 +152,9 @@ object View extends IterableFactory[View] { def apply[A](underlying: Iterable[A]^, p: A => Boolean, isFlipped: Boolean): Filter[A]^{underlying, p} = underlying match { case filter: Filter[A] if filter.isFlipped == isFlipped => - new Filter(filter.underlying, a => filter.p(a) && p(a), isFlipped) - .asInstanceOf[Filter[A]^{underlying, p}] + unsafeAssumeSeparate: + new Filter(filter.underlying, a => filter.p(a) && p(a), isFlipped) + .asInstanceOf[Filter[A]^{underlying, p}] // !!! asInstanceOf needed once paths were added, see path-patmat-should-be-pos.scala for minimization //case filter: Filter[A]^{underlying} if filter.isFlipped == isFlipped => // unsafeAssumeSeparate: From d140083bb8ba3605499aa8fbd3215d7dba4c0184 Mon Sep 17 00:00:00 2001 From: noti0na1 Date: Fri, 15 Aug 2025 15:02:28 +0200 Subject: [PATCH 003/101] Update test --- scala2-library-cc/src/scala/collection/View.scala | 3 --- tests/neg-custom-args/captures/match.scala | 5 ++++- .../captures/colltest5/CollectionStrawManCC5_1.scala | 7 +++---- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/scala2-library-cc/src/scala/collection/View.scala b/scala2-library-cc/src/scala/collection/View.scala index d93ccaa89f20..b8de97159f06 100644 --- a/scala2-library-cc/src/scala/collection/View.scala +++ b/scala2-library-cc/src/scala/collection/View.scala @@ -156,12 +156,9 @@ object View extends IterableFactory[View] { new Filter(filter.underlying, a => filter.p(a) && p(a), isFlipped) .asInstanceOf[Filter[A]^{underlying, p}] // !!! asInstanceOf needed once paths were added, see path-patmat-should-be-pos.scala for minimization - //case filter: Filter[A]^{underlying} if filter.isFlipped == isFlipped => - // unsafeAssumeSeparate: // See filter-iterable.scala for a test where a variant of Filter // works without the unsafeAssumeSeparate. But it requires significant // changes compared to the version here. See also Filter in colltest5.CollectionStrawManCC5_1. - // new Filter(filter.underlying, a => filter.p(a) && p(a), isFlipped) case _ => new Filter(underlying, p, isFlipped) } } diff --git a/tests/neg-custom-args/captures/match.scala b/tests/neg-custom-args/captures/match.scala index 9f7c9c6d8e74..cf0c0f1a3377 100644 --- a/tests/neg-custom-args/captures/match.scala +++ b/tests/neg-custom-args/captures/match.scala @@ -12,7 +12,10 @@ def test = case b: B => b // error: (b: B) becomes B^{x} implicitly val y2: A^{x} = a match - case b: B => b // ok + case b: B => + val bb: B^{b} = b + val aa: A^{a} = bb + b // ok val x3: AnyRef = a match case B(x2: AnyRef) => x2 // error: we lose some information about field x, but it still cannot be pure diff --git a/tests/run-custom-args/captures/colltest5/CollectionStrawManCC5_1.scala b/tests/run-custom-args/captures/colltest5/CollectionStrawManCC5_1.scala index ffb68c8d0d60..ea0bdc240e0c 100644 --- a/tests/run-custom-args/captures/colltest5/CollectionStrawManCC5_1.scala +++ b/tests/run-custom-args/captures/colltest5/CollectionStrawManCC5_1.scala @@ -457,13 +457,12 @@ object CollectionStrawMan5 { def apply[A](underlying: Iterable[A]^, pp: A => Boolean, isFlipped: Boolean): Filter[A]^{underlying, pp} = underlying match case filter: Filter[A] => - new Filter(filter.underlying, a => filter.p(a) && pp(a)) - .asInstanceOf[Filter[A]^{underlying, pp}] - //unsafeAssumeSeparate: + unsafeAssumeSeparate: + new Filter(filter.underlying, a => filter.p(a) && pp(a)) + .asInstanceOf[Filter[A]^{underlying, pp}] // See filter-iterable.scala for a test where a variant of Filter // works without the unsafeAssumeSeparate. But it requires significant // changes compared to the version here. - //new Filter(filter.underlying, a => filter.p(a) && pp(a)) case _ => new Filter(underlying, pp) case class Partition[A](val underlying: Iterable[A]^, p: A => Boolean) { From 42a55e2da3326e33e7617278a7e349544bb531b4 Mon Sep 17 00:00:00 2001 From: Hamza Remmal Date: Fri, 15 Aug 2025 16:09:27 +0200 Subject: [PATCH 004/101] chore: render `@consume` at the `consume` modifier --- scaladoc/src/dotty/tools/scaladoc/api.scala | 1 + .../dotty/tools/scaladoc/tasty/BasicSupport.scala | 2 +- .../tools/scaladoc/tasty/ClassLikeSupport.scala | 14 ++++++++------ 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/scaladoc/src/dotty/tools/scaladoc/api.scala b/scaladoc/src/dotty/tools/scaladoc/api.scala index 41ccd8fb2280..8106bd349e34 100644 --- a/scaladoc/src/dotty/tools/scaladoc/api.scala +++ b/scaladoc/src/dotty/tools/scaladoc/api.scala @@ -45,6 +45,7 @@ enum Modifier(val name: String, val prefix: Boolean): case Infix extends Modifier("infix", true) case AbsOverride extends Modifier("abstract override", true) case Update extends Modifier("update", true) + case Consume extends Modifier("consume", true) case class ExtensionTarget(name: String, typeParams: Seq[TypeParameter], argsLists: Seq[TermParameterList], signature: Signature, dri: DRI, position: Long) case class ImplicitConversion(from: DRI, to: DRI) diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/BasicSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/BasicSupport.scala index 06fe658b850e..c96b6899501d 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/BasicSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/BasicSupport.scala @@ -57,7 +57,7 @@ trait BasicSupport: ) val fqNameAllowlist = if ccEnabled then - fqNameAllowlist0 + CaptureDefs.useAnnotFullName + CaptureDefs.consumeAnnotFullName + fqNameAllowlist0 + CaptureDefs.useAnnotFullName else fqNameAllowlist0 val documentedSymbol = summon[Quotes].reflect.Symbol.requiredClass("java.lang.annotation.Documented") val annotations = sym.annotations.filter { a => diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala index 1a64625d491c..20af3b710878 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala @@ -23,11 +23,12 @@ trait ClassLikeSupport: extension (symbol: Symbol) { def getExtraModifiers(): Seq[Modifier] = - val mods = SymOps.getExtraModifiers(symbol)() - if ccEnabled && symbol.flags.is(Flags.Mutable)then - mods :+ Modifier.Update - else - mods + var mods = SymOps.getExtraModifiers(symbol)() + if ccEnabled && symbol.flags.is(Flags.Mutable) then + mods :+= Modifier.Update + if ccEnabled && symbol.hasAnnotation(cc.CaptureDefs.ConsumeAnnot) then + mods :+= Modifier.Consume + mods } private def bareClasslikeKind(using Quotes)(symbol: reflect.Symbol): Kind = @@ -452,12 +453,13 @@ trait ClassLikeSupport: ) = val symbol = argument.symbol val inlinePrefix = if symbol.flags.is(Flags.Inline) then "inline " else "" + val comsumePrefix = if self.ccEnabled && symbol.hasAnnotation(cc.CaptureDefs.ConsumeAnnot) then "consume " else "" val name = symbol.normalizedName val nameIfNotSynthetic = Option.when(!symbol.flags.is(Flags.Synthetic))(name) val defaultValue = Option.when(symbol.flags.is(Flags.HasDefault))(Plain(" = ...")) api.TermParameter( symbol.getAnnotations(), - inlinePrefix + prefix(symbol), + comsumePrefix + inlinePrefix + prefix(symbol), nameIfNotSynthetic, symbol.dri, argument.tpt.asSignature(classDef, symbol.owner) :++ defaultValue, From 5e2a3ab72c62f8c549e780a7b294f82521b769d3 Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 11 Aug 2025 17:58:06 +0200 Subject: [PATCH 005/101] Reorganize CC docs - Split doc page into individual sub-pages - Add new material on classifiers - Revise material on scoped caps TODO - Revise material on reach capabilities - Add material on how to use the capture checker - Add material on separation checking - Check everything for completeness and accuracy Also: Add two test files named `reference-cc.scala` in neg and pos that let us try out the code on the doc pages. TODO: Complete these files so that all relevant code is there. --- .../experimental/cc-checked-exceptions.md | 92 +++ .../reference/experimental/cc-classes.md | 237 +++++++ .../reference/experimental/cc-classifiers.md | 113 ++++ .../reference/experimental/cc-how-to-use.md | 23 + .../reference/experimental/cc-internals.md | 89 +++ .../reference/experimental/cc-polymorphism.md | 99 +++ .../reference/experimental/cc-scoped-caps.md | 124 ++++ docs/_docs/reference/experimental/cc.md | 630 +----------------- .../captures/reference-cc.check | 43 ++ .../captures/reference-cc.scala | 59 ++ .../captures/reference-cc.scala | 8 + 11 files changed, 912 insertions(+), 605 deletions(-) create mode 100644 docs/_docs/reference/experimental/cc-checked-exceptions.md create mode 100644 docs/_docs/reference/experimental/cc-classes.md create mode 100644 docs/_docs/reference/experimental/cc-classifiers.md create mode 100644 docs/_docs/reference/experimental/cc-how-to-use.md create mode 100644 docs/_docs/reference/experimental/cc-internals.md create mode 100644 docs/_docs/reference/experimental/cc-polymorphism.md create mode 100644 docs/_docs/reference/experimental/cc-scoped-caps.md create mode 100644 tests/neg-custom-args/captures/reference-cc.check create mode 100644 tests/neg-custom-args/captures/reference-cc.scala create mode 100644 tests/pos-custom-args/captures/reference-cc.scala diff --git a/docs/_docs/reference/experimental/cc-checked-exceptions.md b/docs/_docs/reference/experimental/cc-checked-exceptions.md new file mode 100644 index 000000000000..cb6fb0d52d23 --- /dev/null +++ b/docs/_docs/reference/experimental/cc-checked-exceptions.md @@ -0,0 +1,92 @@ +--- +layout: doc-page +title: "Checked Exceptions" +nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/cc-checked-exceptions.html +--- + +## Checked Exceptions + +Scala enables checked exceptions through a language import. Here is an example, +taken from the [safer exceptions page](./canthrow.md), and also described in a +[paper](https://infoscience.epfl.ch/record/290885) presented at the + 2021 Scala Symposium. +```scala +import language.experimental.saferExceptions + +class LimitExceeded extends Exception + +val limit = 10e+10 +def f(x: Double): Double throws LimitExceeded = + if x < limit then x * x else throw LimitExceeded() +``` +The new `throws` clause expands into an implicit parameter that provides +a `CanThrow` capability. Hence, function `f` could equivalently be written +like this: +```scala +def f(x: Double)(using CanThrow[LimitExceeded]): Double = ... +``` +If the implicit parameter is missing, an error is reported. For instance, the function definition +```scala +def g(x: Double): Double = + if x < limit then x * x else throw LimitExceeded() +``` +is rejected with this error message: +``` + | if x < limit then x * x else throw LimitExceeded() + | ^^^^^^^^^^^^^^^^^^^^^ + |The capability to throw exception LimitExceeded is missing. + |The capability can be provided by one of the following: + | - Adding a using clause `(using CanThrow[LimitExceeded])` to the definition of the enclosing method + | - Adding `throws LimitExceeded` clause after the result type of the enclosing method + | - Wrapping this piece of code with a `try` block that catches LimitExceeded +``` +`CanThrow` capabilities are required by `throw` expressions and are created +by `try` expressions. For instance, the expression +```scala +try xs.map(f).sum +catch case ex: LimitExceeded => -1 +``` +would be expanded by the compiler to something like the following: +```scala +try + erased given ctl: CanThrow[LimitExceeded] = compiletime.erasedValue + xs.map(f).sum +catch case ex: LimitExceeded => -1 +``` +(The `ctl` capability is only used for type checking but need not show up in the generated code, so it can be declared as +erased.) + +As with other capability based schemes, one needs to guard against capabilities +that are captured in results. For instance, here is a problematic use case: +```scala +def escaped(xs: Double*): (() => Double) throws LimitExceeded = + try () => xs.map(f).sum + catch case ex: LimitExceeded => () => -1 +val crasher = escaped(1, 2, 10e+11) +crasher() +``` +This code needs to be rejected since otherwise the call to `crasher()` would cause +an unhandled `LimitExceeded` exception to be thrown. + +Under the language import `language.experimental.captureChecking`, the code is indeed rejected + + + +To integrate exception and capture checking, only two changes are needed: + + - `CanThrow` is declared as a class extending `Control`, so all references to `CanThrow` instances are tracked. + - Escape checking is extended to `try` expressions. The result type of a `try` is not allowed to + capture capabilities defined in the body of the `try`. + +--- + +Next: [How to Use the Capture Checker](./cc-how-to-use.md) \ No newline at end of file diff --git a/docs/_docs/reference/experimental/cc-classes.md b/docs/_docs/reference/experimental/cc-classes.md new file mode 100644 index 000000000000..79f7f1d806c3 --- /dev/null +++ b/docs/_docs/reference/experimental/cc-classes.md @@ -0,0 +1,237 @@ +--- +layout: doc-page +title: "Capture Checking of Classes" +nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/cc-classes.html +--- + +## Capture Checking of Classes + +The principles for capture checking closures also apply to classes. For instance, consider: +```scala +class Logger(using fs: FileSystem): + def log(s: String): Unit = ... summon[FileSystem] ... + +def test(xfs: FileSystem): Logger^{xfs} = + Logger(xfs) +``` +Here, class `Logger` retains the capability `fs` as a (private) field. Hence, the result +of `test` is of type `Logger^{xfs}` + +Sometimes, a tracked capability is meant to be used only in the constructor of a class, but +is not intended to be retained as a field. This fact can be communicated to the capture +checker by declaring the parameter as `@constructorOnly`. Example: +```scala +import annotation.constructorOnly + +class NullLogger(using @constructorOnly fs: FileSystem): + ... +def test2(using fs: FileSystem): NullLogger = NullLogger() // OK +``` + +The captured references of a class include _local capabilities_ and _argument capabilities_. Local capabilities are capabilities defined outside the class and referenced from its body. Argument capabilities are passed as parameters to the primary constructor of the class. Local capabilities are inherited: +the local capabilities of a superclass are also local capabilities of its subclasses. Example: + +```scala +class Cap extends caps.SharedCapability + +def test(a: Cap, b: Cap, c: Cap) = + class Super(y: Cap): + def f = a + class Sub(x: Cap) extends Super(x) + def g = b + Sub(c) +``` +Here class `Super` has local capability `a`, which gets inherited by class +`Sub` and is combined with `Sub`'s own local capability `b`. Class `Sub` also has an argument capability corresponding to its parameter `x`. This capability gets instantiated to `c` in the final constructor call `Sub(c)`. Hence, +the capture set of that call is `{a, b, c}`. + +The capture set of the type of `this` of a class is inferred by the capture checker, unless the type is explicitly declared with a self type annotation like this one: +```scala +class C: + self: D^{a, b} => ... +``` +The inference observes the following constraints: + + - The type of `this` of a class `C` includes all captured references of `C`. + - The type of `this` of a class `C` is a subtype of the type of `this` + of each parent class of `C`. + - The type of `this` must observe all constraints where `this` is used. + +For instance, in +```scala +class Cap extends caps.SharedCapability +def test(c: Cap) = + class A: + val x: A = this + def f = println(c) // error +``` +we know that the type of `this` must be pure, since `this` is the right hand side of a `val` with type `A`. However, in the last line we find that the capture set of the class, and with it the capture set of `this`, would include `c`. This leads to a contradiction, and hence to a checking error: +``` + | def f = println(c) // error + | ^ + | reference (c : Cap) is not included in the allowed capture set {} + | of the enclosing class A +``` + +## Capture Tunnelling + +Consider the following simple definition of a `Pair` class: +```scala +class Pair[+A, +B](x: A, y: B): + def fst: A = x + def snd: B = y +``` +What happens if `Pair` is instantiated like this (assuming `ct` and `fs` are two capabilities in scope)? +```scala +def x: Int ->{ct} String +def y: Logger^{fs} +def p = Pair(x, y) +``` +The last line will be typed as follows: +```scala +def p: Pair[Int ->{ct} String, Logger^{fs}] = Pair(x, y) +``` +This might seem surprising. The `Pair(x, y)` value does capture capabilities `ct` and `fs`. Why don't they show up in its type at the outside? + +The answer is capture tunnelling. Once a type variable is instantiated to a capturing type, the +capture is not propagated beyond this point. On the other hand, if the type variable is instantiated +again on access, the capture information "pops out" again. For instance, even though `p` is technically pure because its capture set is empty, writing `p.fst` would record a reference to the captured capability `ct`. So if this access was put in a closure, the capability would again form part of the outer capture set. E.g. +```scala +() => p.fst : () -> Int ->{ct} String +``` +In other words, references to capabilities "tunnel through" in generic instantiations from creation to access; they do not affect the capture set of the enclosing generic data constructor applications. +This principle plays an important part in making capture checking concise and practical. + + +## A Larger Example + +As a larger example, we present an implementation of lazy lists and some use cases. For simplicity, +our lists are lazy only in their tail part. This corresponds to what the Scala-2 type `Stream` did, whereas Scala 3's `LazyList` type computes strictly less since it is also lazy in the first argument. + +Here is the base trait `LzyList` for our version of lazy lists: +```scala +trait LzyList[+A]: + def isEmpty: Boolean + def head: A + def tail: LzyList[A]^{this} +``` +Note that `tail` carries a capture annotation. It says that the tail of a lazy list can +potentially capture the same references as the lazy list as a whole. + +The empty case of a `LzyList` is written as usual: +```scala +object LzyNil extends LzyList[Nothing]: + def isEmpty = true + def head = ??? + def tail = ??? +``` +Here is a formulation of the class for lazy cons nodes: +```scala +import scala.compiletime.uninitialized + +final class LzyCons[+A](hd: A, tl: () => LzyList[A]^) extends LzyList[A]: + private var forced = false + private var cache: LzyList[A]^{this} = uninitialized + private def force = + if !forced then { cache = tl(); forced = true } + cache + + def isEmpty = false + def head = hd + def tail: LzyList[A]^{this} = force +end LzyCons +``` +The `LzyCons` class takes two parameters: A head `hd` and a tail `tl`, which is a function +returning a `LzyList`. Both the function and its result can capture arbitrary capabilities. +The result of applying the function is memoized after the first dereference of `tail` in +the private mutable field `cache`. Note that the typing of the assignment `cache = tl()` relies on the monotonicity rule for `{this}` capture sets. + +Here is an extension method to define an infix cons operator `#:` for lazy lists. It is analogous +to `::` but instead of a strict list it produces a lazy list without evaluating its right operand. +```scala +extension [A](x: A) + def #:(xs1: => LzyList[A]^): LzyList[A]^{xs1} = + LzyCons(x, () => xs1) +``` +Note that `#:` takes an impure call-by-name parameter `xs1` as its right argument. The result +of `#:` is a lazy list that captures that argument. + +As an example usage of `#:`, here is a method `tabulate` that creates a lazy list +of given length with a generator function `gen`. The generator function is allowed +to have side effects. +```scala +def tabulate[A](n: Int)(gen: Int => A): LzyList[A]^{gen} = + def recur(i: Int): LzyList[A]^{gen} = + if i == n then LzyNil + else gen(i) #: recur(i + 1) + recur(0) +``` +Here is a use of `tabulate`: +```scala +class LimitExceeded extends Exception +def squares(n: Int)(using ct: CanThrow[LimitExceeded]) = + tabulate(10): i => + if i > 9 then throw LimitExceeded() + i * i +``` +The inferred result type of `squares` is `LzyList[Int]^{ct}`, i.e it is a lazy list of +`Int`s that can throw the `LimitExceeded` exception when it is elaborated by calling `tail` +one or more times. + +Here are some further extension methods for mapping, filtering, and concatenating lazy lists: +```scala +extension [A](xs: LzyList[A]^) + def map[B](f: A => B): LzyList[B]^{xs, f} = + if xs.isEmpty then LzyNil + else f(xs.head) #: xs.tail.map(f) + + def filter(p: A => Boolean): LzyList[A]^{xs, p} = + if xs.isEmpty then LzyNil + else if p(xs.head) then xs.head #: xs.tail.filter(p) + else xs.tail.filter(p) + + def concat(ys: LzyList[A]^): LzyList[A]^{xs, ys} = + if xs.isEmpty then ys + else xs.head #: xs.tail.concat(ys) + + def drop(n: Int): LzyList[A]^{xs} = + if n == 0 then xs else xs.tail.drop(n - 1) +``` +Their capture annotations are all as one would expect: + + - Mapping a lazy list produces a lazy list that captures the original list as well + as the (possibly impure) mapping function. + - Filtering a lazy list produces a lazy list that captures the original list as well + as the (possibly impure) filtering predicate. + - Concatenating two lazy lists produces a lazy list that captures both arguments. + - Dropping elements from a lazy list gives a safe approximation where the original list is captured in the result. In fact, it's only some suffix of the list that is retained at run time, but our modelling identifies lazy lists and their suffixes, so this additional knowledge would not be useful. + +Of course the function passed to `map` or `filter` could also be pure. After all, `A -> B` is a subtype of `(A -> B)^{cap}` which is the same as `A => B`. In that case, the pure function +argument will _not_ show up in the result type of `map` or `filter`. For instance: +```scala +val xs = squares(10) +val ys: LzyList[Int]^{xs} = xs.map(_ + 1) +``` +The type of the mapped list `ys` has only `xs` in its capture set. The actual function +argument does not show up since it is pure. Likewise, if the lazy list +`xs` was pure, it would not show up in any of the method results. +This demonstrates that capability-based +effect systems with capture checking are naturally _effect polymorphic_. + +This concludes our example. It's worth mentioning that an equivalent program defining and using standard, strict lists would require no capture annotations whatsoever. It would compile exactly as written now in standard Scala 3, yet one gets the capture checking for free. Essentially, `=>` already means "can capture anything" and since in a strict list side effecting operations are not retained in the result, there are no additional captures to record. A strict list could of course capture side-effecting closures in its elements but then tunnelling applies, since +these elements are represented by a type variable. This means we don't need to annotate anything there either. + +Another possibility would be a variant of lazy lists that requires all functions passed to `map`, `filter` and other operations like it to be pure. E.g. `map` on such a list would be defined like this: +```scala +extension [A](xs: LzyList[A]) + def map[B](f: A -> B): LzyList[B] = ... +``` +That variant would not require any capture annotations either. + +To summarize, there are two "sweet spots" of data structure design: strict lists in +side-effecting or resource-aware code and lazy lists in purely functional code. +Both are already correctly capture-typed without requiring any explicit annotations. Capture annotations only come into play where the semantics gets more complicated because we deal with delayed effects such as in impure lazy lists or side-effecting iterators over strict lists. This property is probably one of the greatest plus points of our approach to capture checking compared to previous techniques which tend to be more noisy. + +---- + +Next: [Capability Polymorphism](./cc-polymorphism.md) diff --git a/docs/_docs/reference/experimental/cc-classifiers.md b/docs/_docs/reference/experimental/cc-classifiers.md new file mode 100644 index 000000000000..4232853bd28d --- /dev/null +++ b/docs/_docs/reference/experimental/cc-classifiers.md @@ -0,0 +1,113 @@ +--- +layout: doc-page +title: "Capability Classifiers" +nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/cc-classifiers.html +--- + +## Capability Classifiers + +Capabilities are extremely versatile. They can express concepts from many different domains. Exceptions, continuations, I/O, mutation, information flow, security permissions, are just some examples, the list goes on. + +Sometimes it is important to restrict, or: _classify_ what kind of capabilities are expected or returned in a context. For instance, we might want to allow only control capabilities such as `CanThrow`s or boundary `Label`s but no +other capabilities. Or might want to allow mutation, but no other side effects. This is achieved by having a capability class extend a _classifier_. + +For instance, the `scala.caps` package defines a classifier trait called `Control`, +like this: +```scala + trait Control extends SharedCapability, Classifier +``` +The Gears library then defines a capability class `Async` which extends `Control`. + +```scala + trait Async extends Control +``` +Unlike normal inheritance, classifiers also restrict the capture set of a capability. For instance, say we have a function +```scala + def f(using async: Async^) = body +``` +(the `^` is as usual redundant here since `Async` is a capability trait). +Then we have the guarantee that any actual `async` argument can only capture +capabilities that have types extending `Control`. No other capabilities such as mutation or I/O are allowed. + +A class or trait becomes a classifier by extending directly the marker trait +`caps.Classifier`. So with the definitions above, `Control` is a classifier trait, but `Async` is not, since it extends `Classifier` only indirectly, through `Control`. + +Classifiers are unique: a class cannot extend directly or transitively at the same time two unrelated classifier traits. So if a class transitively extends two classifier traits `C1` and `C2` then one of them must be a subtrait of the other. + +### Predefined Classifiers + +The `caps` object defines the `Classifier` trait itself and some traits that extend it: +```scala +trait Classifier + +sealed trait Capability + +trait SharedCapability extends Capability Classifier +trait Control extends SharedCapability, Classifier + +trait ExclusiveCapability extends Capability, Classifier +trait Mutable extends ExclusiveCapability, Classifier +``` +Here is a graph showing the hierarchy of predefined classifier traits: +``` + Capability + / \ + / \ + / \ + / \ + SharedCapability ExclusiveCapability + | | + | | + | | + | | + Control Mutable +``` +At the top of the hierarchy, we distinguish between _shared_ and _exclusive_ capabilities in two classifier traits `SharedCapability` and `ExclusiveCapability`. All capability classes we have seen so far are shared. +`ExclusiveCapability` is a base trait for capabilities that allow only un-aliased access to the data they represent, with the rules governed by [separation checking](../separation-checking.html). Separation checking is currently an optional extension of capture checking, enabled by a different language import. Since `Capability` is a sealed trait, all capability classes are either shared or exclusive. + +`Control` capabilities are shared. This means they cannot directly or indirectly capture exclusive capabilities such as capabilities that control access to mutable state. Typical `Control` capabilities are: + + - `Label`s that enable to return from a `boundary`, + - `CanThrow` capabilities that enable throwing exceptions, or + - `Async` capabilities that allow to suspend. + +These are all expressed by having their capability classes extend `Control`. + +### Classifier Restriction + +Consider the following problem: The `Try.apply` method takes in its `body` parameter a computation, and runs it while catching any exceptions or `boundary.break` aborts. The exception or break will be re-issued when calling +the `get` method of a `Try` object. What should a capability-aware signature of `Try` be? + +The body passed to `Try.apply` can have arbitrary effects, so it can retain arbitrary capabilities. Yet the resulting `Try` object will retain only those capabilities of `body` which are classified as `Control`. So the signature of `Try.apply` should look like this: +```scala +object Try: + def apply[T](body: => T): Try[T]^{body.only[Control]} +``` +Note a new form of capability in the result's capture set: `body.only[Control]`. This is called a _restricted capability_. The general form of a restricted capability is +`c.only[A]` where + + - `c` is a regular, unrestricted capability + - `A` is a classifier trait. + + When substituting a capability set for the underlying capability, we drop all capabilities that are known to be unrelated to the classifier. For instance, say we have an actual body `{ expr }` that uses a capture set `{io, async}` where + + - `io` is an `IO` capability, where IO is assumed to extend `SharedCapability` but not `Control`. + - `async` is a `Control` capability. + +Then the result of `Try { expr }` would have type `Try^{async}`. We drop `io` since +`io`'s type is a `Capability` class that does not extend `Control`. + +If `expr` would use an additional capability `proc: () => Unit`, then `proc` would also show up in the result capture set. Since `proc` is fully effect-polymorphic, we can't exclude that it retains `Control` capabilities, so we have to keep it in the restricted capture set. These elements are shown together in the following example: +```scala +class IO extends caps.SharedCapability +class Async extends caps.Control + +def test(io: IO, async: Async, proc: () => Unit) = + val r = Try: + // code accessing `io`, `async`, and `proc` and returning an `Int. + val _: Try[Int]^{async, proc} = r +``` + +--- + +Next: [Checked exceptions](./cc-checked-exceptions.md) \ No newline at end of file diff --git a/docs/_docs/reference/experimental/cc-how-to-use.md b/docs/_docs/reference/experimental/cc-how-to-use.md new file mode 100644 index 000000000000..641d45cc4dfd --- /dev/null +++ b/docs/_docs/reference/experimental/cc-how-to-use.md @@ -0,0 +1,23 @@ +--- +layout: doc-page +title: "How to Use the Capture Checker" +nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/cc-how-to-use.html +--- + +## Enabling Capture Checking + +TODO + +## Compilation Options + +The following options are relevant for capture checking. + + - **-Vprint:cc** Prints the program with capturing types as inferred by capture checking. + - **-Ycc-verbose** Prints capabilities and capturing types in more detail. + - **-Ycc-debug** Gives more detailed, implementation-oriented information about capture checking, as described in the next section. + + The implementation supporting capture checking with these options is currently in branch `cc-experiment` on dotty.epfl.ch. + +--- + +To find out more about the internal workings of the capture checker: [Capture Checking Internals](./cc-internals.md) \ No newline at end of file diff --git a/docs/_docs/reference/experimental/cc-internals.md b/docs/_docs/reference/experimental/cc-internals.md new file mode 100644 index 000000000000..20c18fc5ea7f --- /dev/null +++ b/docs/_docs/reference/experimental/cc-internals.md @@ -0,0 +1,89 @@ +--- +layout: doc-page +title: "Capture Checking Internals" +nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/cc-internals.html +--- + + + + +## Capture Checking Internals + +The capture checker is architected as a propagation constraint solver, which runs as a separate phase after type-checking and some initial transformations. + +Constraint variables stand for unknown capture sets. A constraint variable is introduced + + - for every part of a previously inferred type, + - for the accessed references of every method, class, anonymous function, or by-name argument, + - for the parameters passed in a class constructor call. + +Capture sets in explicitly written types are treated as constants (before capture checking, such sets are simply ignored). + +The capture checker essentially rechecks the program with the usual typing rules. Every time a subtype requirement between capturing types is checked, this translates to a subcapturing test on capture sets. If the two sets are constant, this is simply a yes/no question, where a no will produce an error message. + +If the lower set `C₁` of a comparison `C₁ <: C₂` is a variable, the set `C₂` is recorded +as a _superset_ of `C₁`. If the upper set `C₂` is a variable, the elements of `C₁` are _propagated_ to `C₂`. Propagation of an element `x` to a set `C` means that `x` is included as an element in `C`, and it is also propagated +to all known supersets of `C`. If such a superset is a constant, it is checked that `x` is included in it. If that's not the case, the original comparison `C₁ <: C₂` has no solution and an error is reported. + +The type checker also performs various maps on types, for instance when substituting actual argument types for formal parameter types in dependent functions, or mapping +member types with "as-seen-from" in a selection. Maps keep track of the variance +of positions in a type. The variance is initially covariant, it flips to +contravariant in function parameter positions, and can be either covariant, +contravariant, or nonvariant in type arguments, depending on the variance of +the type parameter. + +When capture checking, the same maps are also performed on capture sets. If a capture set is a constant, its elements (which are capabilities) are mapped as regular types. If the result of such a map is not a capability, the result is approximated according to the variance of the type. A covariant approximation replaces a type by its capture set. +A contravariant approximation replaces it with the empty capture set. A nonvariant +approximation replaces the enclosing capturing type with a range of possible types +that gets propagated and resolved further out. + +When a mapping `m` is performed on a capture set variable `C`, a new variable `Cm` is created that contains the mapped elements and that is linked with `C`. If `C` subsequently acquires further elements through propagation, these are also propagated to `Cm` after being transformed by the `m` mapping. `Cm` also gets the same supersets as `C`, mapped again using `m`. + +One interesting aspect of the capture checker concerns the implementation of capture tunnelling. The [foundational theory](https://infoscience.epfl.ch/record/290885) on which capture checking is based makes tunnelling explicit through so-called _box_ and +_unbox_ operations. Boxing hides a capture set and unboxing recovers it. The capture checker inserts virtual box and unbox operations based on actual and expected types similar to the way the type checker inserts implicit conversions. When capture set variables are first introduced, any capture set in a capturing type that is an instance of a type parameter instance is marked as "boxed". A boxing operation is +inserted if the expected type of an expression is a capturing type with +a boxed capture set variable. The effect of the insertion is that any references +to capabilities in the boxed expression are forgotten, which means that capture +propagation is stopped. Dually, if the actual type of an expression has +a boxed variable as capture set, an unbox operation is inserted, which adds all +elements of the capture set to the environment. + +Boxing and unboxing has no runtime effect, so the insertion of these operations is only simulated; the only visible effect is the retraction and insertion +of variables in the capture sets representing the environment of the currently checked expression. + +The `-Ycc-debug` option provides some insight into the workings of the capture checker. +When it is turned on, boxed sets are marked explicitly and capture set variables are printed with an ID and some information about their provenance. For instance, the string `{f, xs}33M5V` indicates a capture set +variable that is known to hold elements `f` and `xs`. The variable's ID is `33`. The `M` +indicates that the variable was created through a mapping from a variable with ID `5`. The latter is a regular variable, as indicated + by `V`. + +Generally, the string following the capture set consists of alternating numbers and letters where each number gives a variable ID and each letter gives the provenance of the variable. Possible letters are + + - `V` : a regular variable, + - `M` : a variable resulting from a _mapping_ of the variable indicated by the string to the right, + - `B` : similar to `M` but where the mapping is a _bijection_, + - `F` : a variable resulting from _filtering_ the elements of the variable indicated by the string to the right, + - `I` : a variable resulting from an _intersection_ of two capture sets, + - `D` : a variable resulting from the set _difference_ of two capture sets. + - `R` : a regular variable that _refines_ a class parameter, so that the capture + set of a constructor argument is known in the class instance type. + +At the end of a compilation run, `-Ycc-debug` will print all variable dependencies of variables referred to in previous output. Here is an example: +``` +Capture set dependencies: + {}2V :: + {}3V :: + {}4V :: + {f, xs}5V :: {f, xs}31M5V, {f, xs}32M5V + {f, xs}31M5V :: {xs, f} + {f, xs}32M5V :: +``` +This section lists all variables that appeared in previous diagnostics and their dependencies, recursively. For instance, we learn that + + - variables 2, 3, 4 are empty and have no dependencies, + - variable `5` has two dependencies: variables `31` and `32` which both result from mapping variable `5`, + - variable `31` has a constant fixed superset `{xs, f}` + - variable `32` has no dependencies. + + + diff --git a/docs/_docs/reference/experimental/cc-polymorphism.md b/docs/_docs/reference/experimental/cc-polymorphism.md new file mode 100644 index 000000000000..6d7a139826e3 --- /dev/null +++ b/docs/_docs/reference/experimental/cc-polymorphism.md @@ -0,0 +1,99 @@ +--- +layout: doc-page +title: "Capability Polymorphism" +nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/cc-polymorphism.html +--- + +## Capability Polymorphism + +It is sometimes convenient to write operations that are parameterized with a capture set of capabilities. For instance consider a type of event sources +`Source` on which `Listener`s can be registered. Listeners can hold certain capabilities, which show up as a parameter to `Source`: +```scala +class Source[X^]: + private var listeners: Set[Listener^{X}] = Set.empty + def register(x: Listener^{X}): Unit = + listeners += x + + def allListeners: Set[Listener^{X}] = listeners +``` +The type variable `X^` can be instantiated with a set of capabilities. It can occur in capture sets in its scope. For instance, in the example above +we see a variable `listeners` that has as type a `Set` of `Listeners` capturing `X`. The `register` method takes a listener of this type +and assigns it to the variable. + +Capture-set variables `X^` without user-annotated bounds by default range over the interval `>: {} <: {caps.cap}` which is the universe of capture sets instead of regular types. + +Under the hood, such capture-set variables are represented as regular type variables within the special interval + `>: CapSet <: CapSet^`. +For instance, `Source` from above could be equivalently +defined as follows: +```scala +class Source[X >: CapSet <: CapSet^]: + ... +``` +`CapSet` is a sealed trait in the `caps` object. It cannot be instantiated or inherited, so its only +purpose is to identify type variables which are capture sets. In non-capture-checked +usage contexts, the type system will treat `CapSet^{a}` and `CapSet^{a,b}` as the type `CapSet`, whereas +with capture checking enabled, it will take the annotated capture sets into account, +so that `CapSet^{a}` and `CapSet^{a,b}` are distinct. +This representation based on `CapSet` is subject to change and +its direct use is discouraged. + +Capture-set variables can be inferred like regular type variables. When they should be instantiated +explicitly one supplies a concrete capture set. For instance: +```scala +class Async extends caps.SharedCapability + +def listener(async: Async): Listener^{async} = ??? + +def test1(async1: Async, others: List[Async]) = + val src = Source[{async1, others*}] + ... +``` +Here, `src` is created as a `Source` on which listeners can be registered that refer to the `async` capability or to any of the capabilities in list `others`. So we can continue the example code above as follows: +```scala + src.register(listener(async1)) + others.map(listener).foreach(src.register) + val ls: Set[Listener^{async, others*}] = src.allListeners +``` +A common use-case for explicit capture parameters is describing changes to the captures of mutable fields, such as concatenating +effectful iterators: +```scala +class ConcatIterator[A, C^](var iterators: mutable.List[IterableOnce[A]^{C}]): + def concat(it: IterableOnce[A]^): ConcatIterator[A, {C, it}]^{this, it} = + iterators ++= it // ^ + this // track contents of `it` in the result +``` +In such a scenario, we also should ensure that any pre-existing alias of a `ConcatIterator` object should become +inaccessible after invoking its `concat` method. This is achieved with mutation and separation tracking which are +currently in development. + +## Capability Members + +Just as parametrization by types can be equally expressed with type members, we could +also define the `Source[X^]` class above could using a _capability member_: +```scala +class Source: + type X^ + private var listeners: Set[Listener^{this.X}] = Set.empty + ... // as before +``` +Here, we can refer to capability members using paths in capture sets (such as `{this.X}`). Similarly to type members, +capability members can be upper- and lower-bounded with capture sets: +```scala +trait Thread: + type Cap^ + def run(block: () ->{this.Cap} -> Unit): Unit + +trait GPUThread extends Thread: + type Cap^ >: {cudaMalloc, cudaFree} <: {caps.cap} +``` +Since `caps.cap` is the top element for subcapturing, we could have also left out the +upper bound: `type Cap^ >: {cudaMalloc, cudaFree}`. + +---- + +[More Advanced Use Cases](cc-advanced.md) + +---- + +Next: [Scoped Caps](./cc-scoped-caps.md) \ No newline at end of file diff --git a/docs/_docs/reference/experimental/cc-scoped-caps.md b/docs/_docs/reference/experimental/cc-scoped-caps.md new file mode 100644 index 000000000000..952f430a54d5 --- /dev/null +++ b/docs/_docs/reference/experimental/cc-scoped-caps.md @@ -0,0 +1,124 @@ +--- +layout: doc-page +title: "Scoped Caps" +nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/cc-scoped-caps.html +--- + +## Scoped Caps + +When discussing escape checking, we referred to a scoping discipline. That is, capture sets can contain only capabilities that are visible at the point where the set is defined. But that raises the question where a universal capability `cap` is defined? In fact, what is written as the top type `cap` can mean different capabilities, depending on scope. Usually a `cap` refers to a universal capability defined in the scope where the `cap` appears. + +Special rules apply to `cap`s in method and function parameters and results. For example, take this method: + +```scala + def makeLogger(fs: FileSystem^): Logger^ = new Logger(fs) +``` +This creates a `Logger` that captures `fs`. +We could have been more specific in specifying `Logger^{fs}` as the return type of makeLogger, but the current definition is also valid, and might be preferable if we want to hide details what the returned logger captures. If we write it as above then certainly the implied `cap` in the return type of `Logger` should be able subsume the capability `fs`. This means that this `cap` has to be defined in a scope in which +`fs` is visible. + +In logic, the usual way to achieve this scoping is with an existential binder. We can express the type of `makeLogger` like this: +```scala +makeLogger: (fs: ∃cap₁.FileSystem^{cap₁}): ∃cap₂. Logger^{cap₂} +``` +In words: `makeLogger` takes a parameter `fs` of type `Filesystem` capturing _some_ universal capability `cap` and returns a `Logger` capturing some other (possibly different) universal `cap`. + +We can also turn the existential in the function parameter to a universal "forall" +in the function itself. In that alternative notation, the type of makeLogger would read like this: +```scala +makeLogger: ∀cap₁.(fs: FileSystem^{cap₁}): ∃cap₂. Logger^{cap₂} +``` +There's a connection with capture polymorphism here. `cap`s in function parameters behave like additional +capture parameters that can be instantiated at the call site to arbitrary capabilities. + +The conventions for method types carry over to function types. A function type +```scala + (x: T) -> U^ +``` +is interpreted as having an existentially bound `cap` in the result, like this +```scala + (x: T) -> ∃cap.U^{cap} +``` +The same rules hold for the other kinds of function arrows, `=>`, `?->`, and `?=>`. So `cap` can in this case +subsume the function parameter `x` since it is locally bound in the function result. + +However, the expansion of `cap` into an existentially bound variable only applies to functions that use +the dependent function style syntax, with explicitly named parameters. Parametric functions such as +`A => B^` or `(A₍, ..., Aₖ) -> B^` don't bind their result cap in an existential quantifier. +For instance, the function +```scala + (x: A) -> B -> C^ +``` +is interpreted as +```scala + (x: A) -> ∃cap.B -> C^{cap} +``` +In other words, existential quantifiers are only inserted in results of function arrows that follow an explicitly named parameter list. + +To summarize: + + - If a function result type follows a named parameter list and contains covariant occurrences of `cap`, + we replace these occurrences with a fresh existential variable which + is bound by a quantifier scoping over the result type. + - If a function parameter type contains covariant occurrences of `cap`, we replace these occurrences with + a fresh existential variable scoping over the parameter type. + - Occurrences of `cap` elsewhere are not translated. They can be seen as representing an existential in the + scope of the definition in which they appear. + +**Examples:** + + - `A => B` is an alias type that expands to `A ->{cap} B`, therefore + `(x: T) -> A => B` expands to `(x: T) -> ∃cap.(A ->{cap} B)`. + + - `(x: T) -> Iterator[A => B]` expands to `() -> ∃cap.Iterator[A ->{cap} B]` + + +## Reach Capabilities + +Say you have a method `f` that takes an impure function argument which gets stored in a `var`: +```scala +def f(op: A => B) + var x: A ->{op} B = op + ... +``` +This is legal even though `var`s cannot have types with `cap` or existential capabilities. The trick is that the type of the variable `x` +is not `A => B` (this would be rejected), but is the "narrowed" type +`A ->{op} B`. In other words, all capabilities retained by values of `x` +are all also referred to by `op`, which justifies the replacement of `cap` by `op`. + +A more complicated situation is if we want to store successive values +held in a list. Example: +```scala +def f(ops: List[A => B]) + var xs = ops + var x: ??? = xs.head + while xs.nonEmpty do + xs = xs.tail + x = xs.head + ... +``` +Here, `x` cannot be given a type with an `ops` capability. In fact, `ops` is pure, i.e. it's capture set is empty, so it cannot be used as the name of a capability. What we would like to express is that `x` refers to +any operation "reachable" through `ops`. This can be expressed using a +_reach capability_ `ops*`. +```scala +def f(ops: List[A => B]) + var xs = ops + var x: A ->{ops*} B = xs.head + ... +``` +Reach capabilities take the form `x*` where `x` is syntactically a regular capability. If `x: T` then `x*` stands for any capability that appears covariantly in `T` and that is accessed through `x`. The least supertype of this capability is the set of all capabilities appearing covariantly in `T`. + +--- + +Next: [Capability Classifiers](./cc-classifiers.md) \ No newline at end of file diff --git a/docs/_docs/reference/experimental/cc.md b/docs/_docs/reference/experimental/cc.md index 0fdcf143ae72..84a4e3c52ca1 100644 --- a/docs/_docs/reference/experimental/cc.md +++ b/docs/_docs/reference/experimental/cc.md @@ -11,6 +11,18 @@ import language.experimental.captureChecking At present, capture checking is still highly experimental and unstable, and it evolves quickly. Before trying it out, make sure you have the latest version of Scala. +The rest of this page explains the basics capture checking. Further topics are described in the following pages: + + - [Capture Checking of Classes](./cc-classes.md) + - [Capability Polymorphism](./cc-polymorphism.md) + - [Scoped Caps](./cc-scoped-caps.md) + - [Capability Classifiers](./cc-classifiers.md) + - [Checked Exceptions](./cc-checked-exceptions.md) + - [Separation Checking](./cc-separation-checking.md) + - [How to Use the Capture Checker](./cc-how-to-use.md) + - [Capture Checking Internals](./cc-internals.md) + + To get an idea what capture checking can do, let's start with a small example: ```scala def usingLogFile[T](op: FileOutputStream => T): T = @@ -79,13 +91,15 @@ try-with-resources patterns, it can also be a key part to the solutions of many The following sections explain in detail how capture checking works in Scala 3. +Overvow -## Overview -The capture checker extension introduces a new kind of types and it enforces some rules for working with these types. ## Capabilities and Capturing Types +The capture checker extension introduces a new kind of types and it enforces some rules for working with these types. + + Capture checking is done in terms of _capturing types_ of the form `T^{c₁, ..., cᵢ}`. Here `T` is a type, and `{c₁, ..., cᵢ}` is a _capture set_ consisting of references to capabilities `c₁, ..., cᵢ`. @@ -140,7 +154,7 @@ This type is a shorthand for `(A -> B)^{c, d}`, i.e. the function type `A -> B` The impure function type `A => B` is treated as an alias for `A ->{cap} B`. That is, impure functions are functions that can capture anything. A capture annotation `^` binds more strongly than a function arrow. So -`A -> B^{c}` is read as `A -> (B^{c})` and `A -> B^` is read as `A -> (B^{cap})`. +`A -> B^{c}` is read as `A -> (B^{c})` and `A -> B^` is read as `A -> (B^)`. Analogous conventions apply to context function types. `A ?=> B` is an impure context function, with `A ?-> B` as its pure complement. @@ -218,13 +232,13 @@ This widening is called _avoidance_; it is not specific to capture checking but ## Capability Classes -Classes like `CanThrow` or `FileSystem` have the property that their values are always intended to be capabilities. We can make this intention explicit and save boilerplate by letting these classes extend the `Capability` class defined in object `cap`. +Classes like `CanThrow` or `FileSystem` have the property that their values are always intended to be capabilities. We can make this intention explicit and save boilerplate by letting these classes extend the `SharedCapability` class defined in object `cap`. -The capture set of a `Capability` subclass type is always `{cap}`. This means we could equivalently express the `FileSystem` and `Logger` classes as follows: +The capture set of type extending `SharedCapability` is always `{cap}`. This means we could equivalently express the `FileSystem` and `Logger` classes as follows: ```scala -import caps.Capability +import caps.SharedCapability -class FileSystem extends Capability +class FileSystem extends SharedCapability class Logger(using FileSystem): def log(s: String): Unit = ??? @@ -233,137 +247,12 @@ def test(using fs: FileSystem) = val l: Logger^{fs} = Logger() ... ``` -In this version, `FileSystem` is a capability class, which means that the `{cap}` capture set is implied on the parameters of `Logger` and `test`. Writing the capture set explicitly produces a warning: -```scala -class Logger(using FileSystem^{cap}): - ^^^^^^^^^^^^^^ - redundant capture: FileSystem already accounts for cap -``` -Another, unrelated change in the version of the last example here is that the `FileSystem` capability is now passed as an implicit parameter. It is quite natural to model capabilities with implicit parameters since it greatly reduces the wiring overhead once multiple capabilities are in play. - -## Capture Checking of Closures - -If a closure refers to capabilities in its body, it captures these capabilities in its type. For instance, consider: -```scala -def test(fs: FileSystem): String ->{fs} Unit = - (x: String) => Logger(fs).log(x) -``` -Here, the body of `test` is a lambda that refers to the capability `fs`, which means that `fs` is retained in the lambda. -Consequently, the type of the lambda is `String ->{fs} Unit`. - -**Note:** Function values are always written with `=>` (or `?=>` for context functions). There is no syntactic -distinction for pure _vs_ impure function values. The distinction is only made in their types. - -A closure also captures all capabilities that are captured by the functions -it calls. For instance, in -```scala -def test(fs: FileSystem) = - def f() = g() - def g() = (x: String) => Logger(fs).log(x) - f -``` -the result of `test` has type `String ->{fs} Unit` even though function `f` itself does not refer to `fs`. - -## Capture Checking of Classes - -The principles for capture checking closures also apply to classes. For instance, consider: -```scala -class Logger(using fs: FileSystem): - def log(s: String): Unit = ... summon[FileSystem] ... - -def test(xfs: FileSystem): Logger^{xfs} = - Logger(xfs) -``` -Here, class `Logger` retains the capability `fs` as a (private) field. Hence, the result -of `test` is of type `Logger^{xfs}` - -Sometimes, a tracked capability is meant to be used only in the constructor of a class, but -is not intended to be retained as a field. This fact can be communicated to the capture -checker by declaring the parameter as `@constructorOnly`. Example: -```scala -import annotation.constructorOnly - -class NullLogger(using @constructorOnly fs: FileSystem): - ... -def test2(using fs: FileSystem): NullLogger = NullLogger() // OK -``` - -The captured references of a class include _local capabilities_ and _argument capabilities_. Local capabilities are capabilities defined outside the class and referenced from its body. Argument capabilities are passed as parameters to the primary constructor of the class. Local capabilities are inherited: -the local capabilities of a superclass are also local capabilities of its subclasses. Example: - -```scala -class Cap extends caps.Capability - -def test(a: Cap, b: Cap, c: Cap) = - class Super(y: Cap): - def f = a - class Sub(x: Cap) extends Super(x) - def g = b - Sub(c) -``` -Here class `Super` has local capability `a`, which gets inherited by class -`Sub` and is combined with `Sub`'s own local capability `b`. Class `Sub` also has an argument capability corresponding to its parameter `x`. This capability gets instantiated to `c` in the final constructor call `Sub(c)`. Hence, -the capture set of that call is `{a, b, c}`. - -The capture set of the type of `this` of a class is inferred by the capture checker, unless the type is explicitly declared with a self type annotation like this one: -```scala -class C: - self: D^{a, b} => ... -``` -The inference observes the following constraints: - - - The type of `this` of a class `C` includes all captured references of `C`. - - The type of `this` of a class `C` is a subtype of the type of `this` - of each parent class of `C`. - - The type of `this` must observe all constraints where `this` is used. - -For instance, in -```scala -class Cap extends caps.Capability -def test(c: Cap) = - class A: - val x: A = this - def f = println(c) // error -``` -we know that the type of `this` must be pure, since `this` is the right hand side of a `val` with type `A`. However, in the last line we find that the capture set of the class, and with it the capture set of `this`, would include `c`. This leads to a contradiction, and hence to a checking error: -``` -16 | def f = println(c) // error - | ^ - |(c : Cap) cannot be referenced here; it is not included in the allowed capture set {} -``` - -## Capture Tunnelling - -Consider the following simple definition of a `Pair` class: -```scala -class Pair[+A, +B](x: A, y: B): - def fst: A = x - def snd: B = y -``` -What happens if `Pair` is instantiated like this (assuming `ct` and `fs` are two capabilities in scope)? -```scala -def x: Int ->{ct} String -def y: Logger^{fs} -def p = Pair(x, y) -``` -The last line will be typed as follows: -```scala -def p: Pair[Int ->{ct} String, Logger^{fs}] = Pair(x, y) -``` -This might seem surprising. The `Pair(x, y)` value does capture capabilities `ct` and `fs`. Why don't they show up in its type at the outside? +In this version, `FileSystem` is a capability class, which means that the `{cap}` capture set is implied on the parameters of `Logger` and `test`. -The answer is capture tunnelling. Once a type variable is instantiated to a capturing type, the -capture is not propagated beyond this point. On the other hand, if the type variable is instantiated -again on access, the capture information "pops out" again. For instance, even though `p` is technically pure because its capture set is empty, writing `p.fst` would record a reference to the captured capability `ct`. So if this access was put in a closure, the capability would again form part of the outer capture set. E.g. -```scala -() => p.fst : () -> Int ->{ct} String -``` -In other words, references to capabilities "tunnel through" in generic instantiations from creation to access; they do not affect the capture set of the enclosing generic data constructor applications. -This principle plays an important part in making capture checking concise and practical. +Another, unrelated change in the version of the last example here is that the `FileSystem` capability is now passed as an implicit parameter. It is quite natural to model capabilities with implicit parameters since it greatly reduces the wiring overhead once multiple capabilities are in play. ## Escape Checking - Capabilities follow the usual scoping discipline, which means that capture sets can contain only capabilities that are visible at the point where the set is defined. @@ -413,475 +302,6 @@ Looking at object graphs, we observe a monotonicity property: The capture set of - In a class `C` with a field `f`, the capture set `{this}` covers the capture set `{this.f}` as well as the capture set of any application of `this.f` to pure arguments. -## Checked Exceptions - -Scala enables checked exceptions through a language import. Here is an example, -taken from the [safer exceptions page](./canthrow.md), and also described in a -[paper](https://infoscience.epfl.ch/record/290885) presented at the - 2021 Scala Symposium. -```scala -import language.experimental.saferExceptions - -class LimitExceeded extends Exception - -val limit = 10e+10 -def f(x: Double): Double throws LimitExceeded = - if x < limit then x * x else throw LimitExceeded() -``` -The new `throws` clause expands into an implicit parameter that provides -a `CanThrow` capability. Hence, function `f` could equivalently be written -like this: -```scala -def f(x: Double)(using CanThrow[LimitExceeded]): Double = ... -``` -If the implicit parameter is missing, an error is reported. For instance, the function definition -```scala -def g(x: Double): Double = - if x < limit then x * x else throw LimitExceeded() -``` -is rejected with this error message: -``` - | if x < limit then x * x else throw LimitExceeded() - | ^^^^^^^^^^^^^^^^^^^^^ - |The capability to throw exception LimitExceeded is missing. - |The capability can be provided by one of the following: - | - Adding a using clause `(using CanThrow[LimitExceeded])` to the definition of the enclosing method - | - Adding `throws LimitExceeded` clause after the result type of the enclosing method - | - Wrapping this piece of code with a `try` block that catches LimitExceeded -``` -`CanThrow` capabilities are required by `throw` expressions and are created -by `try` expressions. For instance, the expression -```scala -try xs.map(f).sum -catch case ex: LimitExceeded => -1 -``` -would be expanded by the compiler to something like the following: -```scala -try - erased given ctl: CanThrow[LimitExceeded] = compiletime.erasedValue - xs.map(f).sum -catch case ex: LimitExceeded => -1 -``` -(The `ctl` capability is only used for type checking but need not show up in the generated code, so it can be declared as -erased.) - -As with other capability based schemes, one needs to guard against capabilities -that are captured in results. For instance, here is a problematic use case: -```scala -def escaped(xs: Double*): (() => Double) throws LimitExceeded = - try () => xs.map(f).sum - catch case ex: LimitExceeded => () => -1 -val crasher = escaped(1, 2, 10e+11) -crasher() -``` -This code needs to be rejected since otherwise the call to `crasher()` would cause -an unhandled `LimitExceeded` exception to be thrown. - -Under the language import `language.experimental.captureChecking`, the code is indeed rejected -``` -14 | try () => xs.map(f).sum - | ^ - |The expression's type () => Double is not allowed to capture the root capability `cap`. - |This usually means that a capability persists longer than its allowed lifetime. -15 | catch case ex: LimitExceeded => () => -1 -``` -To integrate exception and capture checking, only two changes are needed: - - - `CanThrow` is declared as a class extending `Capability`, so all references to `CanThrow` instances are tracked. - - Escape checking is extended to `try` expressions. The result type of a `try` is not allowed to - capture the universal capability. - -## A Larger Example - -As a larger example, we present an implementation of lazy lists and some use cases. For simplicity, -our lists are lazy only in their tail part. This corresponds to what the Scala-2 type `Stream` did, whereas Scala 3's `LazyList` type computes strictly less since it is also lazy in the first argument. - -Here is the base trait `LzyList` for our version of lazy lists: -```scala -trait LzyList[+A]: - def isEmpty: Boolean - def head: A - def tail: LzyList[A]^{this} -``` -Note that `tail` carries a capture annotation. It says that the tail of a lazy list can -potentially capture the same references as the lazy list as a whole. - -The empty case of a `LzyList` is written as usual: -```scala -object LzyNil extends LzyList[Nothing]: - def isEmpty = true - def head = ??? - def tail = ??? -``` -Here is a formulation of the class for lazy cons nodes: -```scala -import scala.compiletime.uninitialized - -final class LzyCons[+A](hd: A, tl: () => LzyList[A]^) extends LzyList[A]: - private var forced = false - private var cache: LzyList[A]^{this} = uninitialized - private def force = - if !forced then { cache = tl(); forced = true } - cache - - def isEmpty = false - def head = hd - def tail: LzyList[A]^{this} = force -end LzyCons -``` -The `LzyCons` class takes two parameters: A head `hd` and a tail `tl`, which is a function -returning a `LzyList`. Both the function and its result can capture arbitrary capabilities. -The result of applying the function is memoized after the first dereference of `tail` in -the private mutable field `cache`. Note that the typing of the assignment `cache = tl()` relies on the monotonicity rule for `{this}` capture sets. - -Here is an extension method to define an infix cons operator `#:` for lazy lists. It is analogous -to `::` but instead of a strict list it produces a lazy list without evaluating its right operand. -```scala -extension [A](x: A) - def #:(xs1: => LzyList[A]^): LzyList[A]^{xs1} = - LzyCons(x, () => xs1) -``` -Note that `#:` takes an impure call-by-name parameter `xs1` as its right argument. The result -of `#:` is a lazy list that captures that argument. - -As an example usage of `#:`, here is a method `tabulate` that creates a lazy list -of given length with a generator function `gen`. The generator function is allowed -to have side effects. -```scala -def tabulate[A](n: Int)(gen: Int => A) = - def recur(i: Int): LzyList[A]^{gen} = - if i == n then LzyNil - else gen(i) #: recur(i + 1) - recur(0) -``` -Here is a use of `tabulate`: -```scala -class LimitExceeded extends Exception -def squares(n: Int)(using ct: CanThrow[LimitExceeded]) = - tabulate(10): i => - if i > 9 then throw LimitExceeded() - i * i -``` -The inferred result type of `squares` is `LzyList[Int]^{ct}`, i.e it is a lazy list of -`Int`s that can throw the `LimitExceeded` exception when it is elaborated by calling `tail` -one or more times. - -Here are some further extension methods for mapping, filtering, and concatenating lazy lists: -```scala -extension [A](xs: LzyList[A]^) - def map[B](f: A => B): LzyList[B]^{xs, f} = - if xs.isEmpty then LzyNil - else f(xs.head) #: xs.tail.map(f) - - def filter(p: A => Boolean): LzyList[A]^{xs, p} = - if xs.isEmpty then LzyNil - else if p(xs.head) then xs.head #: xs.tail.filter(p) - else xs.tail.filter(p) - - def concat(ys: LzyList[A]^): LzyList[A]^{xs, ys} = - if xs.isEmpty then ys - else xs.head #: xs.tail.concat(ys) - - def drop(n: Int): LzyList[A]^{xs} = - if n == 0 then xs else xs.tail.drop(n - 1) -``` -Their capture annotations are all as one would expect: - - - Mapping a lazy list produces a lazy list that captures the original list as well - as the (possibly impure) mapping function. - - Filtering a lazy list produces a lazy list that captures the original list as well - as the (possibly impure) filtering predicate. - - Concatenating two lazy lists produces a lazy list that captures both arguments. - - Dropping elements from a lazy list gives a safe approximation where the original list is captured in the result. In fact, it's only some suffix of the list that is retained at run time, but our modelling identifies lazy lists and their suffixes, so this additional knowledge would not be useful. - -Of course the function passed to `map` or `filter` could also be pure. After all, `A -> B` is a subtype of `(A -> B)^{cap}` which is the same as `A => B`. In that case, the pure function -argument will _not_ show up in the result type of `map` or `filter`. For instance: -```scala -val xs = squares(10) -val ys: LzyList[Int]^{xs} = xs.map(_ + 1) -``` -The type of the mapped list `ys` has only `xs` in its capture set. The actual function -argument does not show up since it is pure. Likewise, if the lazy list -`xs` was pure, it would not show up in any of the method results. -This demonstrates that capability-based -effect systems with capture checking are naturally _effect polymorphic_. - -This concludes our example. It's worth mentioning that an equivalent program defining and using standard, strict lists would require no capture annotations whatsoever. It would compile exactly as written now in standard Scala 3, yet one gets the capture checking for free. Essentially, `=>` already means "can capture anything" and since in a strict list side effecting operations are not retained in the result, there are no additional captures to record. A strict list could of course capture side-effecting closures in its elements but then tunnelling applies, since -these elements are represented by a type variable. This means we don't need to annotate anything there either. - -Another possibility would be a variant of lazy lists that requires all functions passed to `map`, `filter` and other operations like it to be pure. E.g. `map` on such a list would be defined like this: -```scala -extension [A](xs: LzyList[A]) - def map[B](f: A -> B): LzyList[B] = ... -``` -That variant would not require any capture annotations either. - -To summarize, there are two "sweet spots" of data structure design: strict lists in -side-effecting or resource-aware code and lazy lists in purely functional code. -Both are already correctly capture-typed without requiring any explicit annotations. Capture annotations only come into play where the semantics gets more complicated because we deal with delayed effects such as in impure lazy lists or side-effecting iterators over strict lists. This property is probably one of the greatest plus points of our approach to capture checking compared to previous techniques which tend to be more noisy. - -## Existential Capabilities - -In fact, what is written as the top type `cap` can mean different capabilities, depending on scope. For instance, consider the function type -`() -> Iterator[T]^`. This is taken to mean -```scala - () -> Exists x. Iterator[T]^x -``` -In other words, it means an unknown type bound `x` by an "existential" in the scope of the function result. A `cap` in a function result is therefore different from a `cap` at the top-level or in a function parameter. - -Internally, an existential type is represented as a kind of dependent function type. The type above would be modelled as -```scala - () -> (x: Exists) -> Iterator[T]^x -``` -Here, `Exists` is a sealed trait in the `caps` object that serves to mark -dependent functions as representations of existentials. It should be noted -that this is strictly an internal representation. It is explained here because it can show up in error messages. It is generally not recommended to use this syntax in source code. Instead one should rely on the automatic expansion of `^` and `cap` to existentials, which can be -influenced by introducing the right alias types. The rules for this expansion are as follows: - - - If a function result type contains covariant occurrences of `cap`, - we replace these occurrences with a fresh existential variable which - is bound by a quantifier scoping over the result type. - - We might want to do the same expansion in function arguments, but right now this is not done. - - Occurrences of `cap` elsewhere are not translated. They can be seen as representing an existential at the top-level scope. - -**Examples:** - - - `A => B` is an alias type that expands to `(A -> B)^`, therefore - `() -> A => B` expands to `() -> Exists c. A ->{c} B`. - - - `() -> Iterator[A => B]` expands to `() -> Exists c. Iterator[A ->{c} B]` - - - `A -> B^` expands to `A -> Exists c.B^{c}`. - - - If we define `type Fun[T] = A -> T`, then `() -> Fun[B^]` expands to `() -> Exists c.Fun[B^{c}]`, which dealiases to `() -> Exists c.A -> B^{c}`. This demonstrates how aliases can be used to force existential binders to be in some specific outer scope. - - - If we define - ```scala - type F = A -> Fun[B^] - ``` - then the type alias expands to - ```scala - type F = A -> Exists c.A -> B^{c} - ``` - -**Typing Rules:** - - - When we typecheck the body of a function or method, any covariant occurrences of `cap` in the result type are bound with a fresh existential. - - Conversely, when we typecheck the application of a function or method, - with an existential result type `Exists ex.T`, the result of the application is `T` where every occurrence of the existentially bound - variable `ex` is replaced by `cap`. - -## Reach Capabilities - -Say you have a method `f` that takes an impure function argument which gets stored in a `var`: -```scala -def f(op: A => B) - var x: A ->{op} B = op - ... -``` -This is legal even though `var`s cannot have types with `cap` or existential capabilities. The trick is that the type of the variable `x` -is not `A => B` (this would be rejected), but is the "narrowed" type -`A ->{op} B`. In other words, all capabilities retained by values of `x` -are all also referred to by `op`, which justifies the replacement of `cap` by `op`. - -A more complicated situation is if we want to store successive values -held in a list. Example: -```scala -def f(ops: List[A => B]) - var xs = ops - var x: ??? = xs.head - while xs.nonEmpty do - xs = xs.tail - x = xs.head - ... -``` -Here, `x` cannot be given a type with an `ops` capability. In fact, `ops` is pure, i.e. it's capture set is empty, so it cannot be used as the name of a capability. What we would like to express is that `x` refers to -any operation "reachable" through `ops`. This can be expressed using a -_reach capability_ `ops*`. -```scala -def f(ops: List[A => B]) - var xs = ops - var x: A ->{ops*} B = xs.head - ... -``` -Reach capabilities take the form `x*` where `x` is syntactically a regular capability. If `x: T` then `x*` stands for any capability that appears covariantly in `T` and that is accessed through `x`. The least supertype of this capability is the set of all capabilities appearing covariantly in `T`. - -## Capability Polymorphism - -It is sometimes convenient to write operations that are parameterized with a capture set of capabilities. For instance consider a type of event sources -`Source` on which `Listener`s can be registered. Listeners can hold certain capabilities, which show up as a parameter to `Source`: -```scala -class Source[X^]: - private var listeners: Set[Listener^{X}] = Set.empty - def register(x: Listener^{X}): Unit = - listeners += x - - def allListeners: Set[Listener^{X}] = listeners -``` -The type variable `X^` can be instantiated with a set of capabilities. It can occur in capture sets in its scope. For instance, in the example above -we see a variable `listeners` that has as type a `Set` of `Listeners` capturing `X`. The `register` method takes a listener of this type -and assigns it to the variable. - -Capture-set variables `X^` without user-annotated bounds by default range over the interval `>: {} <: {caps.cap}` which is the universe of capture sets instead of regular types. - -Under the hood, such capture-set variables are represented as regular type variables within the special interval - `>: CapSet <: CapSet^`. -For instance, `Source` from above could be equivalently -defined as follows: -```scala -class Source[X >: CapSet <: CapSet^]: - ... -``` -`CapSet` is a sealed trait in the `caps` object. It cannot be instantiated or inherited, so its only -purpose is to identify type variables which are capture sets. In non-capture-checked -usage contexts, the type system will treat `CapSet^{a}` and `CapSet^{a,b}` as the type `CapSet`, whereas -with capture checking enabled, it will take the annotated capture sets into account, -so that `CapSet^{a}` and `CapSet^{a,b}` are distinct. -This representation based on `CapSet` is subject to change and -its direct use is discouraged. - -Capture-set variables can be inferred like regular type variables. When they should be instantiated -explicitly one supplies a concrete capture set. For instance: -```scala -class Async extends caps.Capability - -def listener(async: Async): Listener^{async} = ??? - -def test1(async1: Async, others: List[Async]) = - val src = Source[{async1, others*}] - ... -``` -Here, `src` is created as a `Source` on which listeners can be registered that refer to the `async` capability or to any of the capabilities in list `others`. So we can continue the example code above as follows: -```scala - src.register(listener(async1)) - others.map(listener).foreach(src.register) - val ls: Set[Listener^{async, others*}] = src.allListeners -``` -A common use-case for explicit capture parameters is describing changes to the captures of mutable fields, such as concatenating -effectful iterators: -```scala -class ConcatIterator[A, C^](var iterators: mutable.List[IterableOnce[A]^{C}]): - def concat(it: IterableOnce[A]^): ConcatIterator[A, {C, it}]^{this, it} = - iterators ++= it // ^ - this // track contents of `it` in the result -``` -In such a scenario, we also should ensure that any pre-existing alias of a `ConcatIterator` object should become -inaccessible after invoking its `concat` method. This is achieved with mutation and separation tracking which are -currently in development. - - -## Capability Members - -Just as parametrization by types can be equally expressed with type members, we could -also define the `Source[X^]` class above could using a _capability member_: -```scala -class Source: - type X^ - private var listeners: Set[Listener^{this.X}] = Set.empty - ... // as before -``` -Here, we can refer to capability members using paths in capture sets (such as `{this.X}`). Similarly to type members, -capability members can be upper- and lower-bounded with capture sets: -```scala -trait Thread: - type Cap^ - def run(block: () ->{this.Cap} -> Unit): Unit - -trait GPUThread extends Thread: - type Cap^ >: {cudaMalloc, cudaFree} <: {caps.cap} -``` -Since `caps.cap` is the top element for subcapturing, we could have also left out the -upper bound: `type Cap^ >: {cudaMalloc, cudaFree}`. - - -[More Advanced Use Cases](cc-advanced.md) - -## Compilation Options - -The following options are relevant for capture checking. - - - **-Vprint:cc** Prints the program with capturing types as inferred by capture checking. - - **-Ycc-debug** Gives more detailed, implementation-oriented information about capture checking, as described in the next section. - - The implementation supporting capture checking with these options is currently in branch `cc-experiment` on dotty.epfl.ch. - -## Capture Checking Internals - -The capture checker is architected as a propagation constraint solver, which runs as a separate phase after type-checking and some initial transformations. - -Constraint variables stand for unknown capture sets. A constraint variable is introduced - - - for every part of a previously inferred type, - - for the accessed references of every method, class, anonymous function, or by-name argument, - - for the parameters passed in a class constructor call. - -Capture sets in explicitly written types are treated as constants (before capture checking, such sets are simply ignored). - -The capture checker essentially rechecks the program with the usual typing rules. Every time a subtype requirement between capturing types is checked, this translates to a subcapturing test on capture sets. If the two sets are constant, this is simply a yes/no question, where a no will produce an error message. - -If the lower set `C₁` of a comparison `C₁ <: C₂` is a variable, the set `C₂` is recorded -as a _superset_ of `C₁`. If the upper set `C₂` is a variable, the elements of `C₁` are _propagated_ to `C₂`. Propagation of an element `x` to a set `C` means that `x` is included as an element in `C`, and it is also propagated -to all known supersets of `C`. If such a superset is a constant, it is checked that `x` is included in it. If that's not the case, the original comparison `C₁ <: C₂` has no solution and an error is reported. - -The type checker also performs various maps on types, for instance when substituting actual argument types for formal parameter types in dependent functions, or mapping -member types with "as-seen-from" in a selection. Maps keep track of the variance -of positions in a type. The variance is initially covariant, it flips to -contravariant in function parameter positions, and can be either covariant, -contravariant, or nonvariant in type arguments, depending on the variance of -the type parameter. - -When capture checking, the same maps are also performed on capture sets. If a capture set is a constant, its elements (which are capabilities) are mapped as regular types. If the result of such a map is not a capability, the result is approximated according to the variance of the type. A covariant approximation replaces a type by its capture set. -A contravariant approximation replaces it with the empty capture set. A nonvariant -approximation replaces the enclosing capturing type with a range of possible types -that gets propagated and resolved further out. - -When a mapping `m` is performed on a capture set variable `C`, a new variable `Cm` is created that contains the mapped elements and that is linked with `C`. If `C` subsequently acquires further elements through propagation, these are also propagated to `Cm` after being transformed by the `m` mapping. `Cm` also gets the same supersets as `C`, mapped again using `m`. - -One interesting aspect of the capture checker concerns the implementation of capture tunnelling. The [foundational theory](https://infoscience.epfl.ch/record/290885) on which capture checking is based makes tunnelling explicit through so-called _box_ and -_unbox_ operations. Boxing hides a capture set and unboxing recovers it. The capture checker inserts virtual box and unbox operations based on actual and expected types similar to the way the type checker inserts implicit conversions. When capture set variables are first introduced, any capture set in a capturing type that is an instance of a type parameter instance is marked as "boxed". A boxing operation is -inserted if the expected type of an expression is a capturing type with -a boxed capture set variable. The effect of the insertion is that any references -to capabilities in the boxed expression are forgotten, which means that capture -propagation is stopped. Dually, if the actual type of an expression has -a boxed variable as capture set, an unbox operation is inserted, which adds all -elements of the capture set to the environment. - -Boxing and unboxing has no runtime effect, so the insertion of these operations is only simulated; the only visible effect is the retraction and insertion -of variables in the capture sets representing the environment of the currently checked expression. - -The `-Ycc-debug` option provides some insight into the workings of the capture checker. -When it is turned on, boxed sets are marked explicitly and capture set variables are printed with an ID and some information about their provenance. For instance, the string `{f, xs}33M5V` indicates a capture set -variable that is known to hold elements `f` and `xs`. The variable's ID is `33`. The `M` -indicates that the variable was created through a mapping from a variable with ID `5`. The latter is a regular variable, as indicated - by `V`. - -Generally, the string following the capture set consists of alternating numbers and letters where each number gives a variable ID and each letter gives the provenance of the variable. Possible letters are - - - `V` : a regular variable, - - `M` : a variable resulting from a _mapping_ of the variable indicated by the string to the right, - - `B` : similar to `M` but where the mapping is a _bijection_, - - `F` : a variable resulting from _filtering_ the elements of the variable indicated by the string to the right, - - `I` : a variable resulting from an _intersection_ of two capture sets, - - `D` : a variable resulting from the set _difference_ of two capture sets. - - `R` : a regular variable that _refines_ a class parameter, so that the capture - set of a constructor argument is known in the class instance type. - -At the end of a compilation run, `-Ycc-debug` will print all variable dependencies of variables referred to in previous output. Here is an example: -``` -Capture set dependencies: - {}2V :: - {}3V :: - {}4V :: - {f, xs}5V :: {f, xs}31M5V, {f, xs}32M5V - {f, xs}31M5V :: {xs, f} - {f, xs}32M5V :: -``` -This section lists all variables that appeared in previous diagnostics and their dependencies, recursively. For instance, we learn that - - - variables 2, 3, 4 are empty and have no dependencies, - - variable `5` has two dependencies: variables `31` and `32` which both result from mapping variable `5`, - - variable `31` has a constant fixed superset `{xs, f}` - - variable `32` has no dependencies. - - +---- +Next: [Capture Checking of Classes](./cc-classes.md) \ No newline at end of file diff --git a/tests/neg-custom-args/captures/reference-cc.check b/tests/neg-custom-args/captures/reference-cc.check new file mode 100644 index 000000000000..a76238e5fade --- /dev/null +++ b/tests/neg-custom-args/captures/reference-cc.check @@ -0,0 +1,43 @@ +-- Error: tests/neg-custom-args/captures/reference-cc.scala:42:20 ------------------------------------------------------ +42 | def f = println(c) // error + | ^ + | reference (c : Cap) is not included in the allowed capture set {} + | of the enclosing class A +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reference-cc.scala:44:27 --------------------------------- +44 | val later = usingLogFile { file => () => file.write(0) } // error + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + |Found: (file: java.io.FileOutputStream^'s1) ->'s2 () ->{file} Unit + |Required: java.io.FileOutputStream^ => () ->'s3 Unit + | + |where: => refers to a fresh root capability created in value later when checking argument to parameter op of method usingLogFile + | ^ refers to the universal root capability + | + |Note that capability file cannot be included in outer capture set 's3. + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reference-cc.scala:47:25 --------------------------------- +47 | val xs = usingLogFile: f => // error + | ^ + |Found: (f: java.io.FileOutputStream^'s4) ->'s5 LzyList[Int]^{f} + |Required: java.io.FileOutputStream^ => LzyList[Int]^'s6 + | + |where: => refers to a fresh root capability created in value xs when checking argument to parameter op of method usingLogFile + | ^ refers to the universal root capability + | + |Note that capability f cannot be included in outer capture set 's6. +48 | LzyList(1, 2, 3).map { x => f.write(x); x * x } + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reference-cc.scala:58:8 ---------------------------------- +58 | try () => xs.map(f).sum // error TODO improve error message + | ^^^^^^^^^^^^^^^^^^^ + |Found: () => Double + |Required: () =>² Double + | + |where: => refers to a fresh root capability classified as Control in the type of given instance canThrow$1 + | =>² refers to a fresh root capability created in anonymous function of type (using erased x$1: CanThrow[LimitExceeded]): () => Double when instantiating expected result type () ->{cap} Double of function literal + | + |Note that capability cap is not included in capture set {cap} + |because cap in method try$1 is not visible from cap in enclosing function. + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/reference-cc.scala b/tests/neg-custom-args/captures/reference-cc.scala new file mode 100644 index 000000000000..c94ad207bb04 --- /dev/null +++ b/tests/neg-custom-args/captures/reference-cc.scala @@ -0,0 +1,59 @@ +import language.experimental.captureChecking +import scala.compiletime.uninitialized +import java.io.* +import language.experimental.saferExceptions + + +def usingLogFile[T](op: FileOutputStream^ => T): T = + val logFile = FileOutputStream("log") + val result = op(logFile) + logFile.close() + result + +trait LzyList[+A]: + def isEmpty: Boolean + def head: A + def tail: LzyList[A]^{this} + def map[B](f: A => B): LzyList[B]^{f, this} = ??? +object LzyList: + def apply[A](xs: A*): LzyList[A] = ??? + +object LzyNil extends LzyList[Nothing]: + def isEmpty = true + def head = ??? + def tail = ??? + +final class LzyCons[+A](hd: A, tl: () => LzyList[A]^) extends LzyList[A]: + private var forced = false + private var cache: LzyList[A]^{this} = uninitialized + private def force = + if !forced then { cache = tl(); forced = true } + cache + + def isEmpty = false + def head = hd + def tail: LzyList[A]^{this} = force +end LzyCons + +class Cap extends caps.SharedCapability +def test(c: Cap) = + class A: + val x: A = this + def f = println(c) // error + + val later = usingLogFile { file => () => file.write(0) } // error + later() // crash + + val xs = usingLogFile: f => // error + LzyList(1, 2, 3).map { x => f.write(x); x * x } + +class LimitExceeded extends Exception + +def testException = + val limit = 10e+10 + def f(x: Double): Double throws LimitExceeded = + if x < limit then x * x else throw LimitExceeded() + + def escaped(xs: Double*): (() => Double) throws LimitExceeded = + try () => xs.map(f).sum // error TODO improve error message + catch case ex: LimitExceeded => () => -1 diff --git a/tests/pos-custom-args/captures/reference-cc.scala b/tests/pos-custom-args/captures/reference-cc.scala new file mode 100644 index 000000000000..ea0dbf6860aa --- /dev/null +++ b/tests/pos-custom-args/captures/reference-cc.scala @@ -0,0 +1,8 @@ +import language.experimental.captureChecking +import java.io.* + +def usingLogFile[T](op: FileOutputStream^ => T): T = + val logFile = FileOutputStream("log") + val result = op(logFile) + logFile.close() + result \ No newline at end of file From 589f2a020986d69569fc427e80aa72a76f785df2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20Bra=C4=8Devac?= Date: Mon, 11 Aug 2025 22:03:07 +0200 Subject: [PATCH 006/101] Fix links in experimental cc docs Files should also be listed in sidebar.yml. --- docs/_docs/reference/experimental/cc-advanced.md | 2 +- .../reference/experimental/cc-classifiers.md | 2 +- .../experimental/cc-separation-checking.md | 1 + docs/_docs/reference/experimental/cc.md | 3 --- docs/sidebar.yml | 16 ++++++++++++++++ 5 files changed, 19 insertions(+), 5 deletions(-) create mode 100644 docs/_docs/reference/experimental/cc-separation-checking.md diff --git a/docs/_docs/reference/experimental/cc-advanced.md b/docs/_docs/reference/experimental/cc-advanced.md index 52d1956caf49..8e46f700bc70 100644 --- a/docs/_docs/reference/experimental/cc-advanced.md +++ b/docs/_docs/reference/experimental/cc-advanced.md @@ -75,4 +75,4 @@ By leveraging capability polymorphism, capability members, and path-dependent ca * `Label`s store the free capabilities `C` of the `block` passed to `boundary` in their capability member `Fv`. * When suspending on a given label, the suspension handler can capture at most the capabilities that occur freely at the `boundary` that introduced the label. That prevents mentioning nested bound labels. -[Back to Capture Checking](cc.md) \ No newline at end of file +[Back to Capability Polymorphism](cc-polymorphism.md) \ No newline at end of file diff --git a/docs/_docs/reference/experimental/cc-classifiers.md b/docs/_docs/reference/experimental/cc-classifiers.md index 4232853bd28d..22a440724c92 100644 --- a/docs/_docs/reference/experimental/cc-classifiers.md +++ b/docs/_docs/reference/experimental/cc-classifiers.md @@ -63,7 +63,7 @@ Here is a graph showing the hierarchy of predefined classifier traits: Control Mutable ``` At the top of the hierarchy, we distinguish between _shared_ and _exclusive_ capabilities in two classifier traits `SharedCapability` and `ExclusiveCapability`. All capability classes we have seen so far are shared. -`ExclusiveCapability` is a base trait for capabilities that allow only un-aliased access to the data they represent, with the rules governed by [separation checking](../separation-checking.html). Separation checking is currently an optional extension of capture checking, enabled by a different language import. Since `Capability` is a sealed trait, all capability classes are either shared or exclusive. +`ExclusiveCapability` is a base trait for capabilities that allow only un-aliased access to the data they represent, with the rules governed by [separation checking](cc-separation-checking.md). Separation checking is currently an optional extension of capture checking, enabled by a different language import. Since `Capability` is a sealed trait, all capability classes are either shared or exclusive. `Control` capabilities are shared. This means they cannot directly or indirectly capture exclusive capabilities such as capabilities that control access to mutable state. Typical `Control` capabilities are: diff --git a/docs/_docs/reference/experimental/cc-separation-checking.md b/docs/_docs/reference/experimental/cc-separation-checking.md new file mode 100644 index 000000000000..f87f5c14cbbd --- /dev/null +++ b/docs/_docs/reference/experimental/cc-separation-checking.md @@ -0,0 +1 @@ +# TODO \ No newline at end of file diff --git a/docs/_docs/reference/experimental/cc.md b/docs/_docs/reference/experimental/cc.md index 84a4e3c52ca1..61d986f433e2 100644 --- a/docs/_docs/reference/experimental/cc.md +++ b/docs/_docs/reference/experimental/cc.md @@ -91,9 +91,6 @@ try-with-resources patterns, it can also be a key part to the solutions of many The following sections explain in detail how capture checking works in Scala 3. -Overvow - - ## Capabilities and Capturing Types diff --git a/docs/sidebar.yml b/docs/sidebar.yml index 440cfa450849..8016db45a1bb 100644 --- a/docs/sidebar.yml +++ b/docs/sidebar.yml @@ -163,8 +163,24 @@ subsection: - page: reference/experimental/main-annotation.md - page: reference/experimental/into.md - page: reference/experimental/cc.md + - page: reference/experimental/cc-classes.md + hidden: true + - page: reference/experimental/cc-polymorphism.md + hidden: true - page: reference/experimental/cc-advanced.md hidden: true + - page: reference/experimental/cc-scoped-caps.md + hidden: true + - page: reference/experimental/cc-classifiers.md + hidden: true + - page: reference/experimental/cc-checked-exceptions.md + hidden: true + - page: reference/experimental/cc-separation-checking.md + hidden: true + - page: reference/experimental/cc-how-to-use.md + hidden: true + - page: reference/experimental/cc-internals.md + hidden: true - page: reference/experimental/purefuns.md - page: reference/experimental/tupled-function.md - page: reference/experimental/modularity.md From 0440a21189cb1a09f50b33d6ac4da5a1b77ac889 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20Bra=C4=8Devac?= Date: Mon, 11 Aug 2025 22:20:29 +0200 Subject: [PATCH 007/101] cc docs: remove manual links and improve sidebar Replaced hand-written intra-doc links with automatic doctool linking. Sidebar now reflects the subsection hierarchy. --- .../experimental/cc-checked-exceptions.md | 4 --- .../reference/experimental/cc-classes.md | 4 --- .../reference/experimental/cc-classifiers.md | 4 --- .../reference/experimental/cc-how-to-use.md | 4 --- .../reference/experimental/cc-polymorphism.md | 4 --- .../reference/experimental/cc-scoped-caps.md | 4 --- .../experimental/cc-separation-checking.md | 6 ++++ docs/_docs/reference/experimental/cc.md | 4 --- docs/sidebar.yml | 32 ++++++++----------- 9 files changed, 19 insertions(+), 47 deletions(-) diff --git a/docs/_docs/reference/experimental/cc-checked-exceptions.md b/docs/_docs/reference/experimental/cc-checked-exceptions.md index cb6fb0d52d23..26664d56bc57 100644 --- a/docs/_docs/reference/experimental/cc-checked-exceptions.md +++ b/docs/_docs/reference/experimental/cc-checked-exceptions.md @@ -86,7 +86,3 @@ To integrate exception and capture checking, only two changes are needed: - `CanThrow` is declared as a class extending `Control`, so all references to `CanThrow` instances are tracked. - Escape checking is extended to `try` expressions. The result type of a `try` is not allowed to capture capabilities defined in the body of the `try`. - ---- - -Next: [How to Use the Capture Checker](./cc-how-to-use.md) \ No newline at end of file diff --git a/docs/_docs/reference/experimental/cc-classes.md b/docs/_docs/reference/experimental/cc-classes.md index 79f7f1d806c3..3f9308bb1139 100644 --- a/docs/_docs/reference/experimental/cc-classes.md +++ b/docs/_docs/reference/experimental/cc-classes.md @@ -231,7 +231,3 @@ That variant would not require any capture annotations either. To summarize, there are two "sweet spots" of data structure design: strict lists in side-effecting or resource-aware code and lazy lists in purely functional code. Both are already correctly capture-typed without requiring any explicit annotations. Capture annotations only come into play where the semantics gets more complicated because we deal with delayed effects such as in impure lazy lists or side-effecting iterators over strict lists. This property is probably one of the greatest plus points of our approach to capture checking compared to previous techniques which tend to be more noisy. - ----- - -Next: [Capability Polymorphism](./cc-polymorphism.md) diff --git a/docs/_docs/reference/experimental/cc-classifiers.md b/docs/_docs/reference/experimental/cc-classifiers.md index 22a440724c92..c8461ba0753e 100644 --- a/docs/_docs/reference/experimental/cc-classifiers.md +++ b/docs/_docs/reference/experimental/cc-classifiers.md @@ -107,7 +107,3 @@ def test(io: IO, async: Async, proc: () => Unit) = // code accessing `io`, `async`, and `proc` and returning an `Int. val _: Try[Int]^{async, proc} = r ``` - ---- - -Next: [Checked exceptions](./cc-checked-exceptions.md) \ No newline at end of file diff --git a/docs/_docs/reference/experimental/cc-how-to-use.md b/docs/_docs/reference/experimental/cc-how-to-use.md index 641d45cc4dfd..753c723ab4ca 100644 --- a/docs/_docs/reference/experimental/cc-how-to-use.md +++ b/docs/_docs/reference/experimental/cc-how-to-use.md @@ -17,7 +17,3 @@ The following options are relevant for capture checking. - **-Ycc-debug** Gives more detailed, implementation-oriented information about capture checking, as described in the next section. The implementation supporting capture checking with these options is currently in branch `cc-experiment` on dotty.epfl.ch. - ---- - -To find out more about the internal workings of the capture checker: [Capture Checking Internals](./cc-internals.md) \ No newline at end of file diff --git a/docs/_docs/reference/experimental/cc-polymorphism.md b/docs/_docs/reference/experimental/cc-polymorphism.md index 6d7a139826e3..a564ce90db61 100644 --- a/docs/_docs/reference/experimental/cc-polymorphism.md +++ b/docs/_docs/reference/experimental/cc-polymorphism.md @@ -93,7 +93,3 @@ upper bound: `type Cap^ >: {cudaMalloc, cudaFree}`. ---- [More Advanced Use Cases](cc-advanced.md) - ----- - -Next: [Scoped Caps](./cc-scoped-caps.md) \ No newline at end of file diff --git a/docs/_docs/reference/experimental/cc-scoped-caps.md b/docs/_docs/reference/experimental/cc-scoped-caps.md index 952f430a54d5..fec2286827c3 100644 --- a/docs/_docs/reference/experimental/cc-scoped-caps.md +++ b/docs/_docs/reference/experimental/cc-scoped-caps.md @@ -118,7 +118,3 @@ def f(ops: List[A => B]) ... ``` Reach capabilities take the form `x*` where `x` is syntactically a regular capability. If `x: T` then `x*` stands for any capability that appears covariantly in `T` and that is accessed through `x`. The least supertype of this capability is the set of all capabilities appearing covariantly in `T`. - ---- - -Next: [Capability Classifiers](./cc-classifiers.md) \ No newline at end of file diff --git a/docs/_docs/reference/experimental/cc-separation-checking.md b/docs/_docs/reference/experimental/cc-separation-checking.md index f87f5c14cbbd..c61e8c465252 100644 --- a/docs/_docs/reference/experimental/cc-separation-checking.md +++ b/docs/_docs/reference/experimental/cc-separation-checking.md @@ -1 +1,7 @@ +--- +layout: doc-page +title: "Separation Checking" +nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/cc-separation-checking.html +--- + # TODO \ No newline at end of file diff --git a/docs/_docs/reference/experimental/cc.md b/docs/_docs/reference/experimental/cc.md index 61d986f433e2..1fb486250a8b 100644 --- a/docs/_docs/reference/experimental/cc.md +++ b/docs/_docs/reference/experimental/cc.md @@ -298,7 +298,3 @@ where `loophole` is defined. Looking at object graphs, we observe a monotonicity property: The capture set of an object `x` covers the capture sets of all objects reachable through `x`. This property is reflected in the type system by the following _monotonicity rule_: - In a class `C` with a field `f`, the capture set `{this}` covers the capture set `{this.f}` as well as the capture set of any application of `this.f` to pure arguments. - ----- - -Next: [Capture Checking of Classes](./cc-classes.md) \ No newline at end of file diff --git a/docs/sidebar.yml b/docs/sidebar.yml index 8016db45a1bb..6a33a02fdd6b 100644 --- a/docs/sidebar.yml +++ b/docs/sidebar.yml @@ -162,25 +162,19 @@ subsection: - page: reference/experimental/explicit-nulls.md - page: reference/experimental/main-annotation.md - page: reference/experimental/into.md - - page: reference/experimental/cc.md - - page: reference/experimental/cc-classes.md - hidden: true - - page: reference/experimental/cc-polymorphism.md - hidden: true - - page: reference/experimental/cc-advanced.md - hidden: true - - page: reference/experimental/cc-scoped-caps.md - hidden: true - - page: reference/experimental/cc-classifiers.md - hidden: true - - page: reference/experimental/cc-checked-exceptions.md - hidden: true - - page: reference/experimental/cc-separation-checking.md - hidden: true - - page: reference/experimental/cc-how-to-use.md - hidden: true - - page: reference/experimental/cc-internals.md - hidden: true + - title: Capture checking + index: reference/experimental/cc.md + subsection: + - page: reference/experimental/cc-classes.md + - page: reference/experimental/cc-polymorphism.md + - page: reference/experimental/cc-advanced.md + hidden: true + - page: reference/experimental/cc-scoped-caps.md + - page: reference/experimental/cc-classifiers.md + - page: reference/experimental/cc-checked-exceptions.md + - page: reference/experimental/cc-separation-checking.md + - page: reference/experimental/cc-how-to-use.md + - page: reference/experimental/cc-internals.md - page: reference/experimental/purefuns.md - page: reference/experimental/tupled-function.md - page: reference/experimental/modularity.md From 9c952302f37ca6fe4aab3228f29807d3db31e926 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20Bra=C4=8Devac?= Date: Mon, 11 Aug 2025 22:54:30 +0200 Subject: [PATCH 008/101] Move cc docs into separate folder Also remove redundant headings --- .../advanced.md} | 6 ++--- .../experimental/{ => capture-checking}/cc.md | 18 +++++++-------- .../checked-exceptions.md} | 6 ++--- .../classes.md} | 3 +-- .../classifiers.md} | 6 ++--- .../how-to-use.md} | 2 +- .../internals.md} | 7 +----- .../polymorphism.md} | 6 ++--- .../scoped-caps.md} | 4 +--- .../separation-checking.md} | 4 ++-- docs/_docs/reference/experimental/overview.md | 2 +- docs/sidebar.yml | 22 +++++++++---------- 12 files changed, 36 insertions(+), 50 deletions(-) rename docs/_docs/reference/experimental/{cc-advanced.md => capture-checking/advanced.md} (96%) rename docs/_docs/reference/experimental/{ => capture-checking}/cc.md (97%) rename docs/_docs/reference/experimental/{cc-checked-exceptions.md => capture-checking/checked-exceptions.md} (96%) rename docs/_docs/reference/experimental/{cc-classes.md => capture-checking/classes.md} (99%) rename docs/_docs/reference/experimental/{cc-classifiers.md => capture-checking/classifiers.md} (94%) rename docs/_docs/reference/experimental/{cc-how-to-use.md => capture-checking/how-to-use.md} (94%) rename docs/_docs/reference/experimental/{cc-internals.md => capture-checking/internals.md} (99%) rename docs/_docs/reference/experimental/{cc-polymorphism.md => capture-checking/polymorphism.md} (97%) rename docs/_docs/reference/experimental/{cc-scoped-caps.md => capture-checking/scoped-caps.md} (99%) rename docs/_docs/reference/experimental/{cc-separation-checking.md => capture-checking/separation-checking.md} (68%) diff --git a/docs/_docs/reference/experimental/cc-advanced.md b/docs/_docs/reference/experimental/capture-checking/advanced.md similarity index 96% rename from docs/_docs/reference/experimental/cc-advanced.md rename to docs/_docs/reference/experimental/capture-checking/advanced.md index 8e46f700bc70..9c19cefc1f8e 100644 --- a/docs/_docs/reference/experimental/cc-advanced.md +++ b/docs/_docs/reference/experimental/capture-checking/advanced.md @@ -1,7 +1,7 @@ --- layout: doc-page -title: "Capture Checking -- Advanced Use Cases" -nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/cc-advanced.html +title: "Capability Polymorphism -- Advanced Use Cases" +nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/capture-checking/advanced.html --- @@ -75,4 +75,4 @@ By leveraging capability polymorphism, capability members, and path-dependent ca * `Label`s store the free capabilities `C` of the `block` passed to `boundary` in their capability member `Fv`. * When suspending on a given label, the suspension handler can capture at most the capabilities that occur freely at the `boundary` that introduced the label. That prevents mentioning nested bound labels. -[Back to Capability Polymorphism](cc-polymorphism.md) \ No newline at end of file +[Back to Capability Polymorphism](polymorphism.md) \ No newline at end of file diff --git a/docs/_docs/reference/experimental/cc.md b/docs/_docs/reference/experimental/capture-checking/cc.md similarity index 97% rename from docs/_docs/reference/experimental/cc.md rename to docs/_docs/reference/experimental/capture-checking/cc.md index 1fb486250a8b..d2e459d06068 100644 --- a/docs/_docs/reference/experimental/cc.md +++ b/docs/_docs/reference/experimental/capture-checking/cc.md @@ -1,7 +1,7 @@ --- layout: doc-page title: "Capture Checking" -nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/cc.html +nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/capture-checking/cc.html --- Capture checking is a research project that modifies the Scala type system to track references to capabilities in values. It can be enabled by the language import @@ -13,14 +13,14 @@ Before trying it out, make sure you have the latest version of Scala. The rest of this page explains the basics capture checking. Further topics are described in the following pages: - - [Capture Checking of Classes](./cc-classes.md) - - [Capability Polymorphism](./cc-polymorphism.md) - - [Scoped Caps](./cc-scoped-caps.md) - - [Capability Classifiers](./cc-classifiers.md) - - [Checked Exceptions](./cc-checked-exceptions.md) - - [Separation Checking](./cc-separation-checking.md) - - [How to Use the Capture Checker](./cc-how-to-use.md) - - [Capture Checking Internals](./cc-internals.md) + - [Capture Checking of Classes](./classes.md) + - [Capability Polymorphism](./polymorphism.md) + - [Scoped Caps](./scoped-caps.md) + - [Capability Classifiers](./classifiers.md) + - [Checked Exceptions](./checked-exceptions.md) + - [Separation Checking](./separation-checking.md) + - [How to Use the Capture Checker](./how-to-use.md) + - [Capture Checking Internals](./internals.md) To get an idea what capture checking can do, let's start with a small example: diff --git a/docs/_docs/reference/experimental/cc-checked-exceptions.md b/docs/_docs/reference/experimental/capture-checking/checked-exceptions.md similarity index 96% rename from docs/_docs/reference/experimental/cc-checked-exceptions.md rename to docs/_docs/reference/experimental/capture-checking/checked-exceptions.md index 26664d56bc57..879c99e56040 100644 --- a/docs/_docs/reference/experimental/cc-checked-exceptions.md +++ b/docs/_docs/reference/experimental/capture-checking/checked-exceptions.md @@ -1,13 +1,11 @@ --- layout: doc-page title: "Checked Exceptions" -nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/cc-checked-exceptions.html +nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/capture-checking/checked-exceptions.html --- -## Checked Exceptions - Scala enables checked exceptions through a language import. Here is an example, -taken from the [safer exceptions page](./canthrow.md), and also described in a +taken from the [safer exceptions page](../canthrow.md), and also described in a [paper](https://infoscience.epfl.ch/record/290885) presented at the 2021 Scala Symposium. ```scala diff --git a/docs/_docs/reference/experimental/cc-classes.md b/docs/_docs/reference/experimental/capture-checking/classes.md similarity index 99% rename from docs/_docs/reference/experimental/cc-classes.md rename to docs/_docs/reference/experimental/capture-checking/classes.md index 3f9308bb1139..9bc39095a8b5 100644 --- a/docs/_docs/reference/experimental/cc-classes.md +++ b/docs/_docs/reference/experimental/capture-checking/classes.md @@ -1,10 +1,9 @@ --- layout: doc-page title: "Capture Checking of Classes" -nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/cc-classes.html +nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/capture-checking/classes.html --- -## Capture Checking of Classes The principles for capture checking closures also apply to classes. For instance, consider: ```scala diff --git a/docs/_docs/reference/experimental/cc-classifiers.md b/docs/_docs/reference/experimental/capture-checking/classifiers.md similarity index 94% rename from docs/_docs/reference/experimental/cc-classifiers.md rename to docs/_docs/reference/experimental/capture-checking/classifiers.md index c8461ba0753e..7c56ca52dcbf 100644 --- a/docs/_docs/reference/experimental/cc-classifiers.md +++ b/docs/_docs/reference/experimental/capture-checking/classifiers.md @@ -1,11 +1,9 @@ --- layout: doc-page title: "Capability Classifiers" -nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/cc-classifiers.html +nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/capture-checking/classifiers.html --- -## Capability Classifiers - Capabilities are extremely versatile. They can express concepts from many different domains. Exceptions, continuations, I/O, mutation, information flow, security permissions, are just some examples, the list goes on. Sometimes it is important to restrict, or: _classify_ what kind of capabilities are expected or returned in a context. For instance, we might want to allow only control capabilities such as `CanThrow`s or boundary `Label`s but no @@ -63,7 +61,7 @@ Here is a graph showing the hierarchy of predefined classifier traits: Control Mutable ``` At the top of the hierarchy, we distinguish between _shared_ and _exclusive_ capabilities in two classifier traits `SharedCapability` and `ExclusiveCapability`. All capability classes we have seen so far are shared. -`ExclusiveCapability` is a base trait for capabilities that allow only un-aliased access to the data they represent, with the rules governed by [separation checking](cc-separation-checking.md). Separation checking is currently an optional extension of capture checking, enabled by a different language import. Since `Capability` is a sealed trait, all capability classes are either shared or exclusive. +`ExclusiveCapability` is a base trait for capabilities that allow only un-aliased access to the data they represent, with the rules governed by [separation checking](separation-checking.md). Separation checking is currently an optional extension of capture checking, enabled by a different language import. Since `Capability` is a sealed trait, all capability classes are either shared or exclusive. `Control` capabilities are shared. This means they cannot directly or indirectly capture exclusive capabilities such as capabilities that control access to mutable state. Typical `Control` capabilities are: diff --git a/docs/_docs/reference/experimental/cc-how-to-use.md b/docs/_docs/reference/experimental/capture-checking/how-to-use.md similarity index 94% rename from docs/_docs/reference/experimental/cc-how-to-use.md rename to docs/_docs/reference/experimental/capture-checking/how-to-use.md index 753c723ab4ca..d08125dc3fc8 100644 --- a/docs/_docs/reference/experimental/cc-how-to-use.md +++ b/docs/_docs/reference/experimental/capture-checking/how-to-use.md @@ -1,7 +1,7 @@ --- layout: doc-page title: "How to Use the Capture Checker" -nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/cc-how-to-use.html +nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/capture-checking/how-to-use.html --- ## Enabling Capture Checking diff --git a/docs/_docs/reference/experimental/cc-internals.md b/docs/_docs/reference/experimental/capture-checking/internals.md similarity index 99% rename from docs/_docs/reference/experimental/cc-internals.md rename to docs/_docs/reference/experimental/capture-checking/internals.md index 20c18fc5ea7f..6aff9b208fd9 100644 --- a/docs/_docs/reference/experimental/cc-internals.md +++ b/docs/_docs/reference/experimental/capture-checking/internals.md @@ -1,14 +1,9 @@ --- layout: doc-page title: "Capture Checking Internals" -nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/cc-internals.html +nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/capture-checking/internals.html --- - - - -## Capture Checking Internals - The capture checker is architected as a propagation constraint solver, which runs as a separate phase after type-checking and some initial transformations. Constraint variables stand for unknown capture sets. A constraint variable is introduced diff --git a/docs/_docs/reference/experimental/cc-polymorphism.md b/docs/_docs/reference/experimental/capture-checking/polymorphism.md similarity index 97% rename from docs/_docs/reference/experimental/cc-polymorphism.md rename to docs/_docs/reference/experimental/capture-checking/polymorphism.md index a564ce90db61..349418971403 100644 --- a/docs/_docs/reference/experimental/cc-polymorphism.md +++ b/docs/_docs/reference/experimental/capture-checking/polymorphism.md @@ -1,11 +1,9 @@ --- layout: doc-page title: "Capability Polymorphism" -nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/cc-polymorphism.html +nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/capture-checking/polymorphism.html --- -## Capability Polymorphism - It is sometimes convenient to write operations that are parameterized with a capture set of capabilities. For instance consider a type of event sources `Source` on which `Listener`s can be registered. Listeners can hold certain capabilities, which show up as a parameter to `Source`: ```scala @@ -92,4 +90,4 @@ upper bound: `type Cap^ >: {cudaMalloc, cudaFree}`. ---- -[More Advanced Use Cases](cc-advanced.md) +[More Advanced Use Cases](advanced.md) diff --git a/docs/_docs/reference/experimental/cc-scoped-caps.md b/docs/_docs/reference/experimental/capture-checking/scoped-caps.md similarity index 99% rename from docs/_docs/reference/experimental/cc-scoped-caps.md rename to docs/_docs/reference/experimental/capture-checking/scoped-caps.md index fec2286827c3..40be7bbe7a9c 100644 --- a/docs/_docs/reference/experimental/cc-scoped-caps.md +++ b/docs/_docs/reference/experimental/capture-checking/scoped-caps.md @@ -1,11 +1,9 @@ --- layout: doc-page title: "Scoped Caps" -nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/cc-scoped-caps.html +nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/capture-checking/scoped-caps.html --- -## Scoped Caps - When discussing escape checking, we referred to a scoping discipline. That is, capture sets can contain only capabilities that are visible at the point where the set is defined. But that raises the question where a universal capability `cap` is defined? In fact, what is written as the top type `cap` can mean different capabilities, depending on scope. Usually a `cap` refers to a universal capability defined in the scope where the `cap` appears. Special rules apply to `cap`s in method and function parameters and results. For example, take this method: diff --git a/docs/_docs/reference/experimental/cc-separation-checking.md b/docs/_docs/reference/experimental/capture-checking/separation-checking.md similarity index 68% rename from docs/_docs/reference/experimental/cc-separation-checking.md rename to docs/_docs/reference/experimental/capture-checking/separation-checking.md index c61e8c465252..8b0b2850d611 100644 --- a/docs/_docs/reference/experimental/cc-separation-checking.md +++ b/docs/_docs/reference/experimental/capture-checking/separation-checking.md @@ -1,7 +1,7 @@ --- layout: doc-page title: "Separation Checking" -nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/cc-separation-checking.html +nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/capture-checking/separation-checking.html --- -# TODO \ No newline at end of file +## TODO \ No newline at end of file diff --git a/docs/_docs/reference/experimental/overview.md b/docs/_docs/reference/experimental/overview.md index f70cf32b9c24..9242056d8405 100644 --- a/docs/_docs/reference/experimental/overview.md +++ b/docs/_docs/reference/experimental/overview.md @@ -32,4 +32,4 @@ Hence, dependent projects also have to be experimental. Some experimental language features that are still in research and development can be enabled with special compiler options. These include * [`-Yexplicit-nulls`](./explicit-nulls.md). Enable support for tracking null references in the type system. -* [`-Ycc`](./cc.md). Enable support for capture checking. +* [`-Ycc`](./capture-checking/cc.md). Enable support for capture checking. diff --git a/docs/sidebar.yml b/docs/sidebar.yml index 6a33a02fdd6b..06e811ca62b8 100644 --- a/docs/sidebar.yml +++ b/docs/sidebar.yml @@ -162,19 +162,19 @@ subsection: - page: reference/experimental/explicit-nulls.md - page: reference/experimental/main-annotation.md - page: reference/experimental/into.md - - title: Capture checking - index: reference/experimental/cc.md + - title: Capture Checking + index: reference/experimental/capture-checking/cc.md subsection: - - page: reference/experimental/cc-classes.md - - page: reference/experimental/cc-polymorphism.md - - page: reference/experimental/cc-advanced.md + - page: reference/experimental/capture-checking/classes.md + - page: reference/experimental/capture-checking/polymorphism.md + - page: reference/experimental/capture-checking/advanced.md hidden: true - - page: reference/experimental/cc-scoped-caps.md - - page: reference/experimental/cc-classifiers.md - - page: reference/experimental/cc-checked-exceptions.md - - page: reference/experimental/cc-separation-checking.md - - page: reference/experimental/cc-how-to-use.md - - page: reference/experimental/cc-internals.md + - page: reference/experimental/capture-checking/scoped-caps.md + - page: reference/experimental/capture-checking/classifiers.md + - page: reference/experimental/capture-checking/checked-exceptions.md + - page: reference/experimental/capture-checking/separation-checking.md + - page: reference/experimental/capture-checking/how-to-use.md + - page: reference/experimental/capture-checking/internals.md - page: reference/experimental/purefuns.md - page: reference/experimental/tupled-function.md - page: reference/experimental/modularity.md From cd577d4ed7d8604984b5b6e22013c90283f487f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20Bra=C4=8Devac?= Date: Mon, 11 Aug 2025 23:11:25 +0200 Subject: [PATCH 009/101] Tweaks to cc docs The first paragraph of a doc page is rendered as some sort of synopsis/subtitle, which looked awkward with the current text. This change adds introduction headings, which also improves the navigation on the right-hand side of a page. --- docs/_docs/reference/experimental/capture-checking/cc.md | 6 +++++- .../experimental/capture-checking/checked-exceptions.md | 2 ++ .../reference/experimental/capture-checking/classes.md | 1 + .../reference/experimental/capture-checking/classifiers.md | 2 ++ .../reference/experimental/capture-checking/polymorphism.md | 2 ++ .../reference/experimental/capture-checking/scoped-caps.md | 2 ++ 6 files changed, 14 insertions(+), 1 deletion(-) diff --git a/docs/_docs/reference/experimental/capture-checking/cc.md b/docs/_docs/reference/experimental/capture-checking/cc.md index d2e459d06068..dec31d0c82a4 100644 --- a/docs/_docs/reference/experimental/capture-checking/cc.md +++ b/docs/_docs/reference/experimental/capture-checking/cc.md @@ -4,7 +4,11 @@ title: "Capture Checking" nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/capture-checking/cc.html --- -Capture checking is a research project that modifies the Scala type system to track references to capabilities in values. It can be enabled by the language import +Capture checking is a research project that modifies the Scala type system to track references to capabilities in values. + +## Introduction + +Capture checking can be enabled by the language import ```scala import language.experimental.captureChecking ``` diff --git a/docs/_docs/reference/experimental/capture-checking/checked-exceptions.md b/docs/_docs/reference/experimental/capture-checking/checked-exceptions.md index 879c99e56040..2c78ea457a93 100644 --- a/docs/_docs/reference/experimental/capture-checking/checked-exceptions.md +++ b/docs/_docs/reference/experimental/capture-checking/checked-exceptions.md @@ -4,6 +4,8 @@ title: "Checked Exceptions" nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/capture-checking/checked-exceptions.html --- +## Introduction + Scala enables checked exceptions through a language import. Here is an example, taken from the [safer exceptions page](../canthrow.md), and also described in a [paper](https://infoscience.epfl.ch/record/290885) presented at the diff --git a/docs/_docs/reference/experimental/capture-checking/classes.md b/docs/_docs/reference/experimental/capture-checking/classes.md index 9bc39095a8b5..9f9269915233 100644 --- a/docs/_docs/reference/experimental/capture-checking/classes.md +++ b/docs/_docs/reference/experimental/capture-checking/classes.md @@ -4,6 +4,7 @@ title: "Capture Checking of Classes" nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/capture-checking/classes.html --- +## Introduction The principles for capture checking closures also apply to classes. For instance, consider: ```scala diff --git a/docs/_docs/reference/experimental/capture-checking/classifiers.md b/docs/_docs/reference/experimental/capture-checking/classifiers.md index 7c56ca52dcbf..5de21e4e469f 100644 --- a/docs/_docs/reference/experimental/capture-checking/classifiers.md +++ b/docs/_docs/reference/experimental/capture-checking/classifiers.md @@ -4,6 +4,8 @@ title: "Capability Classifiers" nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/capture-checking/classifiers.html --- +## Introduction + Capabilities are extremely versatile. They can express concepts from many different domains. Exceptions, continuations, I/O, mutation, information flow, security permissions, are just some examples, the list goes on. Sometimes it is important to restrict, or: _classify_ what kind of capabilities are expected or returned in a context. For instance, we might want to allow only control capabilities such as `CanThrow`s or boundary `Label`s but no diff --git a/docs/_docs/reference/experimental/capture-checking/polymorphism.md b/docs/_docs/reference/experimental/capture-checking/polymorphism.md index 349418971403..8e168de03f2c 100644 --- a/docs/_docs/reference/experimental/capture-checking/polymorphism.md +++ b/docs/_docs/reference/experimental/capture-checking/polymorphism.md @@ -4,6 +4,8 @@ title: "Capability Polymorphism" nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/capture-checking/polymorphism.html --- +## Introduction + It is sometimes convenient to write operations that are parameterized with a capture set of capabilities. For instance consider a type of event sources `Source` on which `Listener`s can be registered. Listeners can hold certain capabilities, which show up as a parameter to `Source`: ```scala diff --git a/docs/_docs/reference/experimental/capture-checking/scoped-caps.md b/docs/_docs/reference/experimental/capture-checking/scoped-caps.md index 40be7bbe7a9c..455a916e1692 100644 --- a/docs/_docs/reference/experimental/capture-checking/scoped-caps.md +++ b/docs/_docs/reference/experimental/capture-checking/scoped-caps.md @@ -4,6 +4,8 @@ title: "Scoped Caps" nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/capture-checking/scoped-caps.html --- +## Introduction + When discussing escape checking, we referred to a scoping discipline. That is, capture sets can contain only capabilities that are visible at the point where the set is defined. But that raises the question where a universal capability `cap` is defined? In fact, what is written as the top type `cap` can mean different capabilities, depending on scope. Usually a `cap` refers to a universal capability defined in the scope where the `cap` appears. Special rules apply to `cap`s in method and function parameters and results. For example, take this method: From c5b780df8da441efb9174c4767098391888b8c92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20Bra=C4=8Devac?= Date: Tue, 12 Aug 2025 08:32:48 +0200 Subject: [PATCH 010/101] Update cc in `referenceReplacements/sidebar.yml` This has to be in sync with `docs/sidebar.yml` --- .../resources/referenceReplacements/sidebar.yml | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/project/resources/referenceReplacements/sidebar.yml b/project/resources/referenceReplacements/sidebar.yml index 4e40642858c9..a31bb231360f 100644 --- a/project/resources/referenceReplacements/sidebar.yml +++ b/project/resources/referenceReplacements/sidebar.yml @@ -149,9 +149,19 @@ subsection: - page: reference/experimental/numeric-literals.md - page: reference/experimental/explicit-nulls.md - page: reference/experimental/main-annotation.md - - page: reference/experimental/cc.md - - page: reference/experimental/cc-advanced.md - hidden: true + - title: Capture Checking + index: reference/experimental/capture-checking/cc.md + subsection: + - page: reference/experimental/capture-checking/classes.md + - page: reference/experimental/capture-checking/polymorphism.md + - page: reference/experimental/capture-checking/advanced.md + hidden: true + - page: reference/experimental/capture-checking/scoped-caps.md + - page: reference/experimental/capture-checking/classifiers.md + - page: reference/experimental/capture-checking/checked-exceptions.md + - page: reference/experimental/capture-checking/separation-checking.md + - page: reference/experimental/capture-checking/how-to-use.md + - page: reference/experimental/capture-checking/internals.md - page: reference/experimental/tupled-function.md - page: reference/syntax.md - title: Language Versions From 164e2ef525f7475cebe703a13ca17ec68562b446 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20Bra=C4=8Devac?= Date: Tue, 12 Aug 2025 16:11:09 +0200 Subject: [PATCH 011/101] Pass over cc.md --- docs/_docs/reference/experimental/capture-checking/cc.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/_docs/reference/experimental/capture-checking/cc.md b/docs/_docs/reference/experimental/capture-checking/cc.md index dec31d0c82a4..7cca14c83eb3 100644 --- a/docs/_docs/reference/experimental/capture-checking/cc.md +++ b/docs/_docs/reference/experimental/capture-checking/cc.md @@ -15,7 +15,7 @@ import language.experimental.captureChecking At present, capture checking is still highly experimental and unstable, and it evolves quickly. Before trying it out, make sure you have the latest version of Scala. -The rest of this page explains the basics capture checking. Further topics are described in the following pages: +The rest of this page explains the basics of capture checking. Further topics are described in the following pages: - [Capture Checking of Classes](./classes.md) - [Capability Polymorphism](./polymorphism.md) @@ -90,7 +90,7 @@ try-with-resources patterns, it can also be a key part to the solutions of many - How to address the problem of effect polymorphism in general. - How to solve the "what color is your function?" problem of mixing synchronous and asynchronous computations. - - How to do region-based allocation, safely, + - How to do region-based allocation, safely. - How to reason about capabilities associated with memory locations. The following sections explain in detail how capture checking works in Scala 3. @@ -194,8 +194,8 @@ analogous _subcapturing_ relation applies to capture sets. If `C₁` and `C₂` Subtyping extends as follows to capturing types: - - Pure types are subtypes of capturing types. That is, `T <: C T`, for any type `T`, capturing set `C`. - - For capturing types, smaller capturing sets produce subtypes: `C₁ T₁ <: C₂ T₂` if + - Pure types are subtypes of capturing types. That is, `T <: T ^ C`, for any type `T`, capturing set `C`. + - For capturing types, smaller capturing sets produce subtypes: `T₁ ^ C₁ <: T₂ ^ C₂` if `C₁ <: C₂` and `T₁ <: T₂`. A subcapturing relation `C₁ <: C₂` holds if `C₂` _accounts for_ every element `c` in `C₁`. This means one of the following three conditions must be true: From c00b05314c1bd473c1b691bb59eaccde033e74ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20Bra=C4=8Devac?= Date: Tue, 12 Aug 2025 17:59:54 +0200 Subject: [PATCH 012/101] cc docs: pass until polymorphism --- .../reference/experimental/capture-checking/advanced.md | 3 ++- .../experimental/capture-checking/polymorphism.md | 9 +++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/docs/_docs/reference/experimental/capture-checking/advanced.md b/docs/_docs/reference/experimental/capture-checking/advanced.md index 9c19cefc1f8e..4cc7aba82c8c 100644 --- a/docs/_docs/reference/experimental/capture-checking/advanced.md +++ b/docs/_docs/reference/experimental/capture-checking/advanced.md @@ -1,9 +1,10 @@ --- layout: doc-page -title: "Capability Polymorphism -- Advanced Use Cases" +title: "Capability Polymorphism" nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/capture-checking/advanced.html --- +Advanced use cases. ## Access Control Analogously to type parameters, we can lower- and upper-bound capability parameters where the bounds consist of concrete capture sets: diff --git a/docs/_docs/reference/experimental/capture-checking/polymorphism.md b/docs/_docs/reference/experimental/capture-checking/polymorphism.md index 8e168de03f2c..8853b54c82a2 100644 --- a/docs/_docs/reference/experimental/capture-checking/polymorphism.md +++ b/docs/_docs/reference/experimental/capture-checking/polymorphism.md @@ -64,13 +64,12 @@ class ConcatIterator[A, C^](var iterators: mutable.List[IterableOnce[A]^{C}]): this // track contents of `it` in the result ``` In such a scenario, we also should ensure that any pre-existing alias of a `ConcatIterator` object should become -inaccessible after invoking its `concat` method. This is achieved with mutation and separation tracking which are -currently in development. +inaccessible after invoking its `concat` method. This is achieved with [mutation and separation tracking](separation-checking.md) which are currently in development. ## Capability Members Just as parametrization by types can be equally expressed with type members, we could -also define the `Source[X^]` class above could using a _capability member_: +also define the `Source[X^]` class above using a _capability member_: ```scala class Source: type X^ @@ -90,6 +89,4 @@ trait GPUThread extends Thread: Since `caps.cap` is the top element for subcapturing, we could have also left out the upper bound: `type Cap^ >: {cudaMalloc, cudaFree}`. ----- - -[More Advanced Use Cases](advanced.md) +**Advanced uses:** We discuss more advanced uses cases for capability members [here](advanced.md). From d45a3ca7fa0d8a5df13c0faf6c07534ea2f5ba64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20Bra=C4=8Devac?= Date: Tue, 12 Aug 2025 18:38:46 +0200 Subject: [PATCH 013/101] Factor out cc basics into separate subsection & let the root document be an index, consistent with the style of other chapters of the reference. There is also no need to manually write the ToC with an index page. --- .../experimental/capture-checking/basics.md | 290 +++++++++++++++++ .../experimental/capture-checking/cc.md | 301 +----------------- docs/sidebar.yml | 1 + .../referenceReplacements/sidebar.yml | 1 + 4 files changed, 294 insertions(+), 299 deletions(-) create mode 100644 docs/_docs/reference/experimental/capture-checking/basics.md diff --git a/docs/_docs/reference/experimental/capture-checking/basics.md b/docs/_docs/reference/experimental/capture-checking/basics.md new file mode 100644 index 000000000000..a0f440bbc283 --- /dev/null +++ b/docs/_docs/reference/experimental/capture-checking/basics.md @@ -0,0 +1,290 @@ +--- +layout: doc-page +title: "Capture Checking Basics" +nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/capture-checking/basics.html +--- + +## Introduction + +Capture checking can be enabled by the language import +```scala +import language.experimental.captureChecking +``` +At present, capture checking is still highly experimental and unstable, and it evolves quickly. +Before trying it out, make sure you have the latest version of Scala. + +To get an idea what capture checking can do, let's start with a small example: +```scala +def usingLogFile[T](op: FileOutputStream => T): T = + val logFile = FileOutputStream("log") + val result = op(logFile) + logFile.close() + result +``` +The `usingLogFile` method invokes a given operation with a fresh log file as parameter. Once the operation has ended, the log file is closed and the +operation's result is returned. This is a typical _try-with-resources_ pattern, similar to many other such patterns which are often supported by special language constructs in other languages. + +The problem is that `usingLogFile`'s implementation is not entirely safe. One can +undermine it by passing an operation that performs the logging at some later point +after it has terminated. For instance: +```scala +val later = usingLogFile { file => () => file.write(0) } +later() // crash +``` +When `later` is executed it tries to write to a file that is already closed, which +results in an uncaught `IOException`. + +Capture checking gives us the mechanism to prevent such errors _statically_. To +prevent unsafe usages of `usingLogFile`, we can declare it like this: +```scala +def usingLogFile[T](op: FileOutputStream^ => T): T = + // same body as before +``` +The only thing that's changed is that the `FileOutputStream` parameter of `op` is now +followed by `^`. We'll see that this turns the parameter into a _capability_ whose lifetime is tracked. + +If we now try to define the problematic value `later`, we get a static error: +``` + | val later = usingLogFile { f => () => f.write(0) } + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | Found: (f: java.io.FileOutputStream^'s1) ->'s2 () ->{f} Unit + | Required: java.io.FileOutputStream^ => () ->'s3 Unit + | + | Note that capability f cannot be included in outer capture set 's3. +``` +In this case, it was easy to see that the `logFile` capability escapes in the closure passed to `usingLogFile`. But capture checking also works for more complex cases. +For instance, capture checking is able to distinguish between the following safe code: +```scala +val xs = usingLogFile { f => + List(1, 2, 3).map { x => f.write(x); x * x } +} +``` +and the following unsafe one: +```scala +val xs = usingLogFile { f => + LzyList(1, 2, 3).map { x => f.write(x); x * x } +} +``` +An error would be issued in the second case, but not the first one (this assumes a capture-aware +formulation `LzyList` of lazily evaluated lists, which we will present later in this page). + +It turns out that capture checking has very broad applications. Besides the various +try-with-resources patterns, it can also be a key part to the solutions of many other long standing problems in programming languages. Among them: + + - How to have a simple and flexible system for checked exceptions. We show later + how capture checking enables a clean and fully safe system for checked exceptions in Scala. + - How to address the problem of effect polymorphism in general. + - How to solve the "what color is your function?" problem of mixing synchronous + and asynchronous computations. + - How to do region-based allocation, safely. + - How to reason about capabilities associated with memory locations. + +The following sections explain in detail how capture checking works in Scala 3. + + +## Capabilities and Capturing Types + +The capture checker extension introduces a new kind of types and it enforces some rules for working with these types. + + +Capture checking is done in terms of _capturing types_ of the form +`T^{c₁, ..., cᵢ}`. Here `T` is a type, and `{c₁, ..., cᵢ}` is a _capture set_ consisting of references to capabilities `c₁, ..., cᵢ`. + +An _object capability_ is syntactically a method- or class-parameter, a local variable, or the `this` of an enclosing class. The type of a capability +must be a capturing type with a non-empty capture set. We also say that +variables that are capabilities are _tracked_. + +In a sense, every +capability gets its authority from some other, more sweeping capability which it captures. The recursion stops with a _universal capability_, written `cap`, from which all other capabilities are ultimately derived. +If `T` is a type, then `T^` is a shorthand for `T^{cap}`, meaning `T` can capture arbitrary capabilities. + +Here is an example: +```scala +class FileSystem + +class Logger(fs: FileSystem^): + def log(s: String): Unit = ... // Write to a log file, using `fs` + +def test(fs: FileSystem^) = + val l: Logger^{fs} = Logger(fs) + l.log("hello world!") + val xs: LzyList[Int]^{l} = + LzyList.from(1) + .map { i => + l.log(s"computing elem # $i") + i * i + } + xs +``` +Here, the `test` method takes a `FileSystem` as a parameter. `fs` is a capability since its type has a non-empty capture set. The capability is passed to the `Logger` constructor +and retained as a field in class `Logger`. Hence, the local variable `l` has type +`Logger^{fs}`: it is a `Logger` which retains the `fs` capability. + +The second variable defined in `test` is `xs`, a lazy list that is obtained from +`LzyList.from(1)` by logging and mapping consecutive numbers. Since the list is lazy, +it needs to retain the reference to the logger `l` for its computations. Hence, the +type of the list is `LzyList[Int]^{l}`. On the other hand, since `xs` only logs but does +not do other file operations, it retains the `fs` capability only indirectly. That's why +`fs` does not show up in the capture set of `xs`. + +Capturing types come with a subtype relation where types with "smaller" capture sets are subtypes of types with larger sets (the _subcapturing_ relation is defined in more detail below). If a type `T` does not have a capture set, it is called _pure_, and is a subtype of +any capturing type that adds a capture set to `T`. + +## Function Types + +The usual function type `A => B` now stands for a function that can capture arbitrary capabilities. We call such functions +_impure_. By contrast, the new single arrow function type `A -> B` stands for a function that cannot capture any capabilities, or otherwise said, is _pure_. +One can add a capture set after the arrow of an otherwise pure function. +For instance, `A ->{c, d} B` would be a function that can capture capabilities `c` and `d`, but no others. +This type is a shorthand for `(A -> B)^{c, d}`, i.e. the function type `A -> B` with possible captures `{c, d}`. + +The impure function type `A => B` is treated as an alias for `A ->{cap} B`. That is, impure functions are functions that can capture anything. + +A capture annotation `^` binds more strongly than a function arrow. So +`A -> B^{c}` is read as `A -> (B^{c})` and `A -> B^` is read as `A -> (B^)`. + +Analogous conventions apply to context function types. `A ?=> B` is an impure context function, with `A ?-> B` as its pure complement. + +**Note 1:** The identifiers `->` and `?->` are now treated as soft keywords when used as infix type operators. They are +still available as regular identifiers for terms. For instance, the mapping syntax `Map("x" -> 1, "y" -> 2)` is still supported since it only applies to terms. + +**Note 2:** The distinctions between pure vs impure function types do not apply to methods. In fact, since methods are not values they never capture anything directly. References to +capabilities in a method are instead counted in the capture set of the enclosing object. + +## By-Name Parameter Types + +A convention analogous to function types also extends to by-name parameters. In +```scala +def f(x: => Int): Int +``` +the actual argument can refer to arbitrary capabilities. So the following would be OK: +```scala +f(if p(y) then throw Ex() else 1) +``` +On the other hand, if `f` was defined like this +```scala +def f(x: -> Int): Int +``` +the actual argument to `f` could not refer to any capabilities, so the call above would be rejected. +One can also allow specific capabilities like this: +```scala +def f(x: ->{c} Int): Int +``` +Here, the actual argument to `f` is allowed to use the `c` capability but no others. + +## Subtyping and Subcapturing + +Capturing influences subtyping. As usual we write `T₁ <: T₂` to express that the type +`T₁` is a subtype of the type `T₂`, or equivalently, that `T₁` conforms to `T₂`. An +analogous _subcapturing_ relation applies to capture sets. If `C₁` and `C₂` are capture sets, we write `C₁ <: C₂` to express that `C₁` _is covered by_ `C₂`, or, swapping the operands, that `C₂` _covers_ `C₁`. + +Subtyping extends as follows to capturing types: + + - Pure types are subtypes of capturing types. That is, `T <: T ^ C`, for any type `T`, capturing set `C`. + - For capturing types, smaller capturing sets produce subtypes: `T₁ ^ C₁ <: T₂ ^ C₂` if + `C₁ <: C₂` and `T₁ <: T₂`. + +A subcapturing relation `C₁ <: C₂` holds if `C₂` _accounts for_ every element `c` in `C₁`. This means one of the following three conditions must be true: + + - `c ∈ C₂`, + - `c` refers to a parameter of some class `Cls` and `C₂` contains `Cls.this`, + - `c`'s type has capturing set `C` and `C₂` accounts for every element of `C` (that is, `C <: C₂`). + + +**Example 1.** Given +```scala +fs: FileSystem^ +ct: CanThrow[Exception]^ +l : Logger^{fs} +``` +we have +``` +{l} <: {fs} <: {cap} +{fs} <: {fs, ct} <: {cap} +{ct} <: {fs, ct} <: {cap} +``` +The set consisting of the root capability `{cap}` covers every other capture set. This is +a consequence of the fact that, ultimately, every capability is created from `cap`. + +**Example 2.** Consider again the FileSystem/Logger example from before. `LzyList[Int]` is a proper subtype of `LzyList[Int]^{l}`. So if the `test` method in that example +was declared with a result type `LzyList[Int]`, we'd get a type error. Here is the error message: +``` +11 |def test(using fs: FileSystem^): LzyList[Int] = { + | ^ + | Found: LzyList[Int]^{fs} + | Required: LzyList[Int] +``` +Why does it say `LzyList[Int]^{fs}` and not `LzyList[Int]^{l}`, which is, after all, the type of the returned value `xs`? The reason is that `l` is a local variable in the body of `test`, so it cannot be referred to in a type outside that body. What happens instead is that the type is _widened_ to the smallest supertype that does not mention `l`. Since `l` has capture set `fs`, we have that `{fs}` covers `{l}`, and `{fs}` is acceptable in a result type of `test`, so `{fs}` is the result of that widening. +This widening is called _avoidance_; it is not specific to capture checking but applies to all variable references in Scala types. + +## Capability Classes + +Classes like `CanThrow` or `FileSystem` have the property that their values are always intended to be capabilities. We can make this intention explicit and save boilerplate by letting these classes extend the `SharedCapability` class defined in object `cap`. + +The capture set of type extending `SharedCapability` is always `{cap}`. This means we could equivalently express the `FileSystem` and `Logger` classes as follows: +```scala +import caps.SharedCapability + +class FileSystem extends SharedCapability + +class Logger(using FileSystem): + def log(s: String): Unit = ??? + +def test(using fs: FileSystem) = + val l: Logger^{fs} = Logger() + ... +``` +In this version, `FileSystem` is a capability class, which means that the `{cap}` capture set is implied on the parameters of `Logger` and `test`. + +Another, unrelated change in the version of the last example here is that the `FileSystem` capability is now passed as an implicit parameter. It is quite natural to model capabilities with implicit parameters since it greatly reduces the wiring overhead once multiple capabilities are in play. + +## Escape Checking + +Capabilities follow the usual scoping discipline, which means that capture sets +can contain only capabilities that are visible at the point where the set is defined. + +We now reconstruct how this principle produced the error in the introductory example, where +`usingLogFile` was declared like this: +```scala +def usingLogFile[T](op: FileOutputStream^ => T): T = ... +``` +The error message was: +``` + | val later = usingLogFile { f => () => f.write(0) } + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | Found: (f: java.io.FileOutputStream^'s1) ->'s2 () ->{f} Unit + | Required: java.io.FileOutputStream^ => () ->'s3 Unit + | + | Note that capability f cannot be included in outer capture set 's3. +``` +This error message was produced by the following logic: + + - The `f` parameter has type `FileOutputStream^`, which makes it a capability. + - Therefore, the type of the expression `() => f.write(0)` is `() ->{f} Unit`. + - This makes the type of the whole closure passed to `usingLogFile` the dependent function type + `(f: FileOutputStream^'s1) ->'s2 () ->{f} Unit`, + for some as yet uncomputed capture sets `'s1` and `'s2`. + - The expected type of the closure is a simple, parametric, impure function type `FileOutputStream^ => T`, + for some instantiation of the type variable `T`. + - Matching with the found type, `T` must have the shape `() ->'s3 Unit`, for + some capture set `'s3` defined at the level of value `later`. + - That capture set cannot include the capability `f` since `f` is locally bound. + This causes the error. + +An analogous restriction applies to the type of a mutable variable. +Another way one could try to undermine capture checking would be to +assign a closure with a local capability to a global variable. Maybe +like this: +```scala +var loophole: () => Unit = () => () +usingLogFile { f => + loophole = () => f.write(0) +} +loophole() +``` +But this will not compile either, since the capture set of the mutable variable `loophole` cannot refer to variable `f`, which is not visible +where `loophole` is defined. + +Looking at object graphs, we observe a monotonicity property: The capture set of an object `x` covers the capture sets of all objects reachable through `x`. This property is reflected in the type system by the following _monotonicity rule_: + + - In a class `C` with a field `f`, the capture set `{this}` covers the capture set `{this.f}` as well as the capture set of any application of `this.f` to pure arguments. diff --git a/docs/_docs/reference/experimental/capture-checking/cc.md b/docs/_docs/reference/experimental/capture-checking/cc.md index 7cca14c83eb3..e8fa8fb761ad 100644 --- a/docs/_docs/reference/experimental/capture-checking/cc.md +++ b/docs/_docs/reference/experimental/capture-checking/cc.md @@ -1,304 +1,7 @@ --- -layout: doc-page +layout: index title: "Capture Checking" nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/capture-checking/cc.html --- -Capture checking is a research project that modifies the Scala type system to track references to capabilities in values. - -## Introduction - -Capture checking can be enabled by the language import -```scala -import language.experimental.captureChecking -``` -At present, capture checking is still highly experimental and unstable, and it evolves quickly. -Before trying it out, make sure you have the latest version of Scala. - -The rest of this page explains the basics of capture checking. Further topics are described in the following pages: - - - [Capture Checking of Classes](./classes.md) - - [Capability Polymorphism](./polymorphism.md) - - [Scoped Caps](./scoped-caps.md) - - [Capability Classifiers](./classifiers.md) - - [Checked Exceptions](./checked-exceptions.md) - - [Separation Checking](./separation-checking.md) - - [How to Use the Capture Checker](./how-to-use.md) - - [Capture Checking Internals](./internals.md) - - -To get an idea what capture checking can do, let's start with a small example: -```scala -def usingLogFile[T](op: FileOutputStream => T): T = - val logFile = FileOutputStream("log") - val result = op(logFile) - logFile.close() - result -``` -The `usingLogFile` method invokes a given operation with a fresh log file as parameter. Once the operation has ended, the log file is closed and the -operation's result is returned. This is a typical _try-with-resources_ pattern, similar to many other such patterns which are often supported by special language constructs in other languages. - -The problem is that `usingLogFile`'s implementation is not entirely safe. One can -undermine it by passing an operation that performs the logging at some later point -after it has terminated. For instance: -```scala -val later = usingLogFile { file => () => file.write(0) } -later() // crash -``` -When `later` is executed it tries to write to a file that is already closed, which -results in an uncaught `IOException`. - -Capture checking gives us the mechanism to prevent such errors _statically_. To -prevent unsafe usages of `usingLogFile`, we can declare it like this: -```scala -def usingLogFile[T](op: FileOutputStream^ => T): T = - // same body as before -``` -The only thing that's changed is that the `FileOutputStream` parameter of `op` is now -followed by `^`. We'll see that this turns the parameter into a _capability_ whose lifetime is tracked. - -If we now try to define the problematic value `later`, we get a static error: -``` - | val later = usingLogFile { f => () => f.write(0) } - | ^^^^^^^^^^^^^^^^^^^^^^^^^ - | Found: (f: java.io.FileOutputStream^'s1) ->'s2 () ->{f} Unit - | Required: java.io.FileOutputStream^ => () ->'s3 Unit - | - | Note that capability f cannot be included in outer capture set 's3. -``` -In this case, it was easy to see that the `logFile` capability escapes in the closure passed to `usingLogFile`. But capture checking also works for more complex cases. -For instance, capture checking is able to distinguish between the following safe code: -```scala -val xs = usingLogFile { f => - List(1, 2, 3).map { x => f.write(x); x * x } -} -``` -and the following unsafe one: -```scala -val xs = usingLogFile { f => - LzyList(1, 2, 3).map { x => f.write(x); x * x } -} -``` -An error would be issued in the second case, but not the first one (this assumes a capture-aware -formulation `LzyList` of lazily evaluated lists, which we will present later in this page). - -It turns out that capture checking has very broad applications. Besides the various -try-with-resources patterns, it can also be a key part to the solutions of many other long standing problems in programming languages. Among them: - - - How to have a simple and flexible system for checked exceptions. We show later - how capture checking enables a clean and fully safe system for checked exceptions in Scala. - - How to address the problem of effect polymorphism in general. - - How to solve the "what color is your function?" problem of mixing synchronous - and asynchronous computations. - - How to do region-based allocation, safely. - - How to reason about capabilities associated with memory locations. - -The following sections explain in detail how capture checking works in Scala 3. - - -## Capabilities and Capturing Types - -The capture checker extension introduces a new kind of types and it enforces some rules for working with these types. - - -Capture checking is done in terms of _capturing types_ of the form -`T^{c₁, ..., cᵢ}`. Here `T` is a type, and `{c₁, ..., cᵢ}` is a _capture set_ consisting of references to capabilities `c₁, ..., cᵢ`. - -An _object capability_ is syntactically a method- or class-parameter, a local variable, or the `this` of an enclosing class. The type of a capability -must be a capturing type with a non-empty capture set. We also say that -variables that are capabilities are _tracked_. - -In a sense, every -capability gets its authority from some other, more sweeping capability which it captures. The recursion stops with a _universal capability_, written `cap`, from which all other capabilities are ultimately derived. -If `T` is a type, then `T^` is a shorthand for `T^{cap}`, meaning `T` can capture arbitrary capabilities. - -Here is an example: -```scala -class FileSystem - -class Logger(fs: FileSystem^): - def log(s: String): Unit = ... // Write to a log file, using `fs` - -def test(fs: FileSystem^) = - val l: Logger^{fs} = Logger(fs) - l.log("hello world!") - val xs: LzyList[Int]^{l} = - LzyList.from(1) - .map { i => - l.log(s"computing elem # $i") - i * i - } - xs -``` -Here, the `test` method takes a `FileSystem` as a parameter. `fs` is a capability since its type has a non-empty capture set. The capability is passed to the `Logger` constructor -and retained as a field in class `Logger`. Hence, the local variable `l` has type -`Logger^{fs}`: it is a `Logger` which retains the `fs` capability. - -The second variable defined in `test` is `xs`, a lazy list that is obtained from -`LzyList.from(1)` by logging and mapping consecutive numbers. Since the list is lazy, -it needs to retain the reference to the logger `l` for its computations. Hence, the -type of the list is `LzyList[Int]^{l}`. On the other hand, since `xs` only logs but does -not do other file operations, it retains the `fs` capability only indirectly. That's why -`fs` does not show up in the capture set of `xs`. - -Capturing types come with a subtype relation where types with "smaller" capture sets are subtypes of types with larger sets (the _subcapturing_ relation is defined in more detail below). If a type `T` does not have a capture set, it is called _pure_, and is a subtype of -any capturing type that adds a capture set to `T`. - -## Function Types - -The usual function type `A => B` now stands for a function that can capture arbitrary capabilities. We call such functions -_impure_. By contrast, the new single arrow function type `A -> B` stands for a function that cannot capture any capabilities, or otherwise said, is _pure_. -One can add a capture set after the arrow of an otherwise pure function. -For instance, `A ->{c, d} B` would be a function that can capture capabilities `c` and `d`, but no others. -This type is a shorthand for `(A -> B)^{c, d}`, i.e. the function type `A -> B` with possible captures `{c, d}`. - -The impure function type `A => B` is treated as an alias for `A ->{cap} B`. That is, impure functions are functions that can capture anything. - -A capture annotation `^` binds more strongly than a function arrow. So -`A -> B^{c}` is read as `A -> (B^{c})` and `A -> B^` is read as `A -> (B^)`. - -Analogous conventions apply to context function types. `A ?=> B` is an impure context function, with `A ?-> B` as its pure complement. - -**Note 1:** The identifiers `->` and `?->` are now treated as soft keywords when used as infix type operators. They are -still available as regular identifiers for terms. For instance, the mapping syntax `Map("x" -> 1, "y" -> 2)` is still supported since it only applies to terms. - -**Note 2:** The distinctions between pure vs impure function types do not apply to methods. In fact, since methods are not values they never capture anything directly. References to -capabilities in a method are instead counted in the capture set of the enclosing object. - -## By-Name Parameter Types - -A convention analogous to function types also extends to by-name parameters. In -```scala -def f(x: => Int): Int -``` -the actual argument can refer to arbitrary capabilities. So the following would be OK: -```scala -f(if p(y) then throw Ex() else 1) -``` -On the other hand, if `f` was defined like this -```scala -def f(x: -> Int): Int -``` -the actual argument to `f` could not refer to any capabilities, so the call above would be rejected. -One can also allow specific capabilities like this: -```scala -def f(x: ->{c} Int): Int -``` -Here, the actual argument to `f` is allowed to use the `c` capability but no others. - -## Subtyping and Subcapturing - -Capturing influences subtyping. As usual we write `T₁ <: T₂` to express that the type -`T₁` is a subtype of the type `T₂`, or equivalently, that `T₁` conforms to `T₂`. An -analogous _subcapturing_ relation applies to capture sets. If `C₁` and `C₂` are capture sets, we write `C₁ <: C₂` to express that `C₁` _is covered by_ `C₂`, or, swapping the operands, that `C₂` _covers_ `C₁`. - -Subtyping extends as follows to capturing types: - - - Pure types are subtypes of capturing types. That is, `T <: T ^ C`, for any type `T`, capturing set `C`. - - For capturing types, smaller capturing sets produce subtypes: `T₁ ^ C₁ <: T₂ ^ C₂` if - `C₁ <: C₂` and `T₁ <: T₂`. - -A subcapturing relation `C₁ <: C₂` holds if `C₂` _accounts for_ every element `c` in `C₁`. This means one of the following three conditions must be true: - - - `c ∈ C₂`, - - `c` refers to a parameter of some class `Cls` and `C₂` contains `Cls.this`, - - `c`'s type has capturing set `C` and `C₂` accounts for every element of `C` (that is, `C <: C₂`). - - -**Example 1.** Given -```scala -fs: FileSystem^ -ct: CanThrow[Exception]^ -l : Logger^{fs} -``` -we have -``` -{l} <: {fs} <: {cap} -{fs} <: {fs, ct} <: {cap} -{ct} <: {fs, ct} <: {cap} -``` -The set consisting of the root capability `{cap}` covers every other capture set. This is -a consequence of the fact that, ultimately, every capability is created from `cap`. - -**Example 2.** Consider again the FileSystem/Logger example from before. `LzyList[Int]` is a proper subtype of `LzyList[Int]^{l}`. So if the `test` method in that example -was declared with a result type `LzyList[Int]`, we'd get a type error. Here is the error message: -``` -11 |def test(using fs: FileSystem^): LzyList[Int] = { - | ^ - | Found: LzyList[Int]^{fs} - | Required: LzyList[Int] -``` -Why does it say `LzyList[Int]^{fs}` and not `LzyList[Int]^{l}`, which is, after all, the type of the returned value `xs`? The reason is that `l` is a local variable in the body of `test`, so it cannot be referred to in a type outside that body. What happens instead is that the type is _widened_ to the smallest supertype that does not mention `l`. Since `l` has capture set `fs`, we have that `{fs}` covers `{l}`, and `{fs}` is acceptable in a result type of `test`, so `{fs}` is the result of that widening. -This widening is called _avoidance_; it is not specific to capture checking but applies to all variable references in Scala types. - -## Capability Classes - -Classes like `CanThrow` or `FileSystem` have the property that their values are always intended to be capabilities. We can make this intention explicit and save boilerplate by letting these classes extend the `SharedCapability` class defined in object `cap`. - -The capture set of type extending `SharedCapability` is always `{cap}`. This means we could equivalently express the `FileSystem` and `Logger` classes as follows: -```scala -import caps.SharedCapability - -class FileSystem extends SharedCapability - -class Logger(using FileSystem): - def log(s: String): Unit = ??? - -def test(using fs: FileSystem) = - val l: Logger^{fs} = Logger() - ... -``` -In this version, `FileSystem` is a capability class, which means that the `{cap}` capture set is implied on the parameters of `Logger` and `test`. - -Another, unrelated change in the version of the last example here is that the `FileSystem` capability is now passed as an implicit parameter. It is quite natural to model capabilities with implicit parameters since it greatly reduces the wiring overhead once multiple capabilities are in play. - -## Escape Checking - -Capabilities follow the usual scoping discipline, which means that capture sets -can contain only capabilities that are visible at the point where the set is defined. - -We now reconstruct how this principle produced the error in the introductory example, where -`usingLogFile` was declared like this: -```scala -def usingLogFile[T](op: FileOutputStream^ => T): T = ... -``` -The error message was: -``` - | val later = usingLogFile { f => () => f.write(0) } - | ^^^^^^^^^^^^^^^^^^^^^^^^^ - | Found: (f: java.io.FileOutputStream^'s1) ->'s2 () ->{f} Unit - | Required: java.io.FileOutputStream^ => () ->'s3 Unit - | - | Note that capability f cannot be included in outer capture set 's3. -``` -This error message was produced by the following logic: - - - The `f` parameter has type `FileOutputStream^`, which makes it a capability. - - Therefore, the type of the expression `() => f.write(0)` is `() ->{f} Unit`. - - This makes the type of the whole closure passed to `usingLogFile` the dependent function type - `(f: FileOutputStream^'s1) ->'s2 () ->{f} Unit`, - for some as yet uncomputed capture sets `'s1` and `'s2`. - - The expected type of the closure is a simple, parametric, impure function type `FileOutputStream^ => T`, - for some instantiation of the type variable `T`. - - Matching with the found type, `T` must have the shape `() ->'s3 Unit`, for - some capture set `'s3` defined at the level of value `later`. - - That capture set cannot include the capability `f` since `f` is locally bound. - This causes the error. - -An analogous restriction applies to the type of a mutable variable. -Another way one could try to undermine capture checking would be to -assign a closure with a local capability to a global variable. Maybe -like this: -```scala -var loophole: () => Unit = () => () -usingLogFile { f => - loophole = () => f.write(0) -} -loophole() -``` -But this will not compile either, since the capture set of the mutable variable `loophole` cannot refer to variable `f`, which is not visible -where `loophole` is defined. - -Looking at object graphs, we observe a monotonicity property: The capture set of an object `x` covers the capture sets of all objects reachable through `x`. This property is reflected in the type system by the following _monotonicity rule_: - - - In a class `C` with a field `f`, the capture set `{this}` covers the capture set `{this.f}` as well as the capture set of any application of `this.f` to pure arguments. +Capture checking is a research project that modifies the Scala type system to track references to capabilities in values. \ No newline at end of file diff --git a/docs/sidebar.yml b/docs/sidebar.yml index 06e811ca62b8..144ad293eaea 100644 --- a/docs/sidebar.yml +++ b/docs/sidebar.yml @@ -165,6 +165,7 @@ subsection: - title: Capture Checking index: reference/experimental/capture-checking/cc.md subsection: + - page: reference/experimental/capture-checking/basics.md - page: reference/experimental/capture-checking/classes.md - page: reference/experimental/capture-checking/polymorphism.md - page: reference/experimental/capture-checking/advanced.md diff --git a/project/resources/referenceReplacements/sidebar.yml b/project/resources/referenceReplacements/sidebar.yml index a31bb231360f..2f766e17e715 100644 --- a/project/resources/referenceReplacements/sidebar.yml +++ b/project/resources/referenceReplacements/sidebar.yml @@ -152,6 +152,7 @@ subsection: - title: Capture Checking index: reference/experimental/capture-checking/cc.md subsection: + - page: reference/experimental/capture-checking/basics.md - page: reference/experimental/capture-checking/classes.md - page: reference/experimental/capture-checking/polymorphism.md - page: reference/experimental/capture-checking/advanced.md From 8fb53251d006fb67abc02ed7516223e84dca6c35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20Bra=C4=8Devac?= Date: Tue, 12 Aug 2025 19:16:35 +0200 Subject: [PATCH 014/101] cc doc: add missing links --- .../reference/experimental/capture-checking/classifiers.md | 2 +- .../reference/experimental/capture-checking/scoped-caps.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/_docs/reference/experimental/capture-checking/classifiers.md b/docs/_docs/reference/experimental/capture-checking/classifiers.md index 5de21e4e469f..bbba42c37da9 100644 --- a/docs/_docs/reference/experimental/capture-checking/classifiers.md +++ b/docs/_docs/reference/experimental/capture-checking/classifiers.md @@ -16,7 +16,7 @@ like this: ```scala trait Control extends SharedCapability, Classifier ``` -The Gears library then defines a capability class `Async` which extends `Control`. +The [Gears library](https://lampepfl.github.io/gears/) then defines a capability class `Async` which extends `Control`. ```scala trait Async extends Control diff --git a/docs/_docs/reference/experimental/capture-checking/scoped-caps.md b/docs/_docs/reference/experimental/capture-checking/scoped-caps.md index 455a916e1692..0d6e4890a65c 100644 --- a/docs/_docs/reference/experimental/capture-checking/scoped-caps.md +++ b/docs/_docs/reference/experimental/capture-checking/scoped-caps.md @@ -28,7 +28,7 @@ in the function itself. In that alternative notation, the type of makeLogger wou ```scala makeLogger: ∀cap₁.(fs: FileSystem^{cap₁}): ∃cap₂. Logger^{cap₂} ``` -There's a connection with capture polymorphism here. `cap`s in function parameters behave like additional +There's a connection with [capture polymorphism](polymorphism.md) here. `cap`s in function parameters behave like additional capture parameters that can be instantiated at the call site to arbitrary capabilities. The conventions for method types carry over to function types. A function type From 04a496bf2fbcffaf4c48d23e05d38f8704c9e984 Mon Sep 17 00:00:00 2001 From: odersky Date: Thu, 14 Aug 2025 13:50:56 +0200 Subject: [PATCH 015/101] WIP add separation checking --- .../_docs/internals/exclusive-capabilities.md | 30 +-- .../capture-checking/scoped-caps.md | 4 +- .../capture-checking/separation-checking.md | 243 +++++++++++++++++- 3 files changed, 256 insertions(+), 21 deletions(-) diff --git a/docs/_docs/internals/exclusive-capabilities.md b/docs/_docs/internals/exclusive-capabilities.md index 938eb5ccdd55..07d3de3f124a 100644 --- a/docs/_docs/internals/exclusive-capabilities.md +++ b/docs/_docs/internals/exclusive-capabilities.md @@ -6,21 +6,19 @@ Language design draft ## Capability Kinds A capability is called - - _exclusive_ if it is `cap` or it has an exclusive capability in its capture set. - - _shared_ otherwise. - -There is a new top capability `shared` which can be used as a capability for deriving shared capture sets. Other shared capabilities are created as read-only versions of exclusive capabilities. + - _shared_ if it is classified as a `SharedCapability` + - _exclusive_ otherwise. ## Update Methods We introduce a new trait ```scala -trait Mutable +trait Mutable extends ExclusiveCapability, Classifier ``` It is used as a base trait for types that define _update methods_ using a new soft modifier `update`. -`update` can only be used in classes or objects extending `Mutable`. An update method is allowed to access exclusive capabilities in the method's environment. By contrast, a normal method in a type extending `Mutable` may access exclusive capabilities only if they are defined locally or passed to it in parameters. +`update` can only be used in classes or objects extending `Mutable`. An update method is allowed to access exclusive capabilities in the method's environment. By contrast, a normal method in a type extending `Mutable` may access exclusive capabilities only if they are defined locally or passed to it in parameters. **Example:** ```scala @@ -115,11 +113,6 @@ a method that accesses exclusive capabilities. If `x` is an exclusive capability of a type extending `Mutable`, `x.rd` is its associated, shared _read-only_ capability. -`shared` can be understood as the read-only capability corresponding to `cap`. -```scala - shared = cap.rd -``` - A _top capability_ is either `cap` or `shared`. @@ -134,7 +127,7 @@ The meaning of `^` and `=>` is the same as before: **Implicitly added capture sets** -A reference to a type extending any of the traits `Capability` or `Mutable` gets an implicit capture set `{shared}` in case no explicit capture set is given. +A reference to a type extending trait `Mutable` gets an implicit capture set `{cap.rd}` provided no explicit capture set is given. For instance, a matrix multiplication method can be expressed as follows: @@ -148,7 +141,7 @@ def mul(a: Matrix, b: Matrix, c: Matrix^): Unit = ``` Here, `a` and `b` are implicitly read-only, and `c`'s type has capture set `cap`. I.e. with explicit capture sets this would read: ```scala -def mul(a: Matrix^{shared}, b: Matrix^{shared}, c: Matrix^{cap}): Unit +def mul(a: Matrix^{cap.rd}, b: Matrix^{cap.rd}, c: Matrix^{cap}): Unit ``` Separation checking will then make sure that `a` and `b` must be different from `c`. @@ -173,8 +166,6 @@ The read-only function `ro` maps capture sets to read-only capture sets. It is d - `ro(x) = x` if `x` is shared - `ro(x) = x.rd` if `x` is exclusive - - ## Subcapturing Subcapturing has to take the mode of capture sets into account. We let `m` stand for arbitrary modes. @@ -271,7 +262,7 @@ A reference `p.m` to an update method or class `m` of a mutable type is allowed If `e` is an expression of a type `T^cs` extending `Mutable` and the expected type is a value type that is not a mutable type, then the type of `e` is mapped to `T^ro(cs)`. - + + \ No newline at end of file diff --git a/docs/_docs/reference/experimental/capture-checking/separation-checking.md b/docs/_docs/reference/experimental/capture-checking/separation-checking.md index 8b0b2850d611..78c3010cbb1c 100644 --- a/docs/_docs/reference/experimental/capture-checking/separation-checking.md +++ b/docs/_docs/reference/experimental/capture-checking/separation-checking.md @@ -4,4 +4,245 @@ title: "Separation Checking" nightlyOf: https://docs.scala-lang.org/scala3/reference/experimental/capture-checking/separation-checking.html --- -## TODO \ No newline at end of file +## Introduction + +Separation checking is an extension of capture checking that enforces unique, un-aliased access to capabilities. It is enabled by a language import +```scala +import language.experimental.separationChecking +``` +(or the corresponding setting `-language:experimental.separationChecking`). +The import has to be given in addition to the `language.experimental.captureChecking` import that enables capture checking. +The reason for the second language import is that separation checking is less mature than capture checking proper, so we are less sure +we got the balance of safety and expressivity right for it at the present time. + +The purpose of separation checking is to ensure that certain accesses to capabilities are not aliased. As an example, consider matrix multiplication. A method that multiplies matrices `a`, and `b` into `c`, could be declared like this: +```scala +def multiply(a: Matrix, b: Matrix, c: Matrix): Unit +``` +But that signature alone does not tell us which matrices are supposed to be inputs and which one is the output. Nor does it guarantee that an input matrix is not re-used as the output, which would lead to wrong results. + +With separation checking, we can declare `Matrix` as a `Mutable` type, like this: +```scala +class Matrix(nrows: Int, ncols: Int) extends Mutable: + update def setElem(i: Int, j: Int, x: Double): Unit = ... + def getElem(i: Int, j: Int): Double = ... +``` +We further declare that the `setElem` method in a `Matrix` is an `update` method, which means it has a side effect. + +Separation checking also gives a special interpretation to the following modified signature of `multiply`: +```scala +def multiply(a: Matrix, b: Matrix, c: Matrix^): Unit +``` +In fact, only a single character was added: `c`'s type now carries a universal capability. This signature enforces at the same time two desirable properties: + + - Matrices `a`, and `b` are read-only; `multiply` will not call their update method. By contrast, matrix `c` can be updated. + - Matrices `a` and `b` are different from matrix `c`, but `a` and `b` could refer to the same matrix. + +So, effectively, anything that can be updated must be unaliased. + +The idea behind separation checking is simple: We now interpret each occurrence of `cap` as a separate top capability. This includes derived syntaxes like `A^` and `B => C`. We further keep track during capture checking which capabilities are subsumed by each `cap`. If capture checking widens a capability `x` to the universal capability, we say `x` is _hidden_ by the universal. The rule then is that any capability hidden by a universal capability `capᵢ` cannot be referenced independently or hidden in another cap in code that can see `capᵢ`. + +Here's an example: +```scala +val x: C^ = y + ... x ... // ok + ... y ... // error +``` +This principle ensures that capabilities such as `x` that have `cap` as underlying capture set are un-aliased or "fresh". Any previously existing aliases such as `y` in the code above are inaccessible as long as `x` is also visible. + +## Capability Kinds + +A capability is called + - _shared_ if it is [classified](../classifiers.md) as a `SharedCapability` + - _exclusive_ otherwise. + +## The Mutable Trait + +We introduce a new trait +```scala +trait Mutable extends ExclusiveCapability, Classifier +``` +It is used as a [classifier](../classifiers.md) trait for types that define _update methods_ using +a new soft modifier `update`. + +**Example:** +```scala +class Ref(init: Int) extends Mutable: + private var current = init + def get: Int = current + update def put(x: Int): Unit = current = x +``` +`update` can only be used in classes or objects extending `Mutable`. An update method is allowed to access exclusive capabilities in the method's environment. By contrast, a normal method in a type extending `Mutable` may access exclusive capabilities only if they are defined locally or passed to it in parameters. + +In class `Ref`, the `put` should be declared as an update method since it accesses the exclusive write capability of the variable `current` in its environment. + +`update` can also be used on an inner class of a class or object extending `Mutable`. It gives all code in the class the right +to access exclusive capabilities in the class environment. Normal classes +can only access exclusive capabilities defined in the class or passed to it in parameters. + +```scala +object Registry extends Mutable: + var count = 0 + update class Counter: + update def next: Int = + count += 1 + count +``` +Normal method members of `Mutable` classes cannot call update methods. This is indicated since accesses in the callee are recorded in the caller. So if the callee captures exclusive capabilities so does the caller. + +An update method cannot implement or override a normal method, whereas normal methods may implement or override update methods. Since methods such as `toString` or `==` inherited from Object are normal methods, it follows that none of these methods may be implemented as an update method. + +The `apply` method of a function type is also a normal method, hence `Mutable` classes may not implement a function type with an update method as the `apply` method. + +## Mutable Types + +A type is called a _mutable_ if it extends `Mutable` and it has an update method or an update class as non-private member or constructor. + +When we create an instance of a mutable type we always add `cap` to its capture set. For instance, if class `Ref` is declared as shown previously then `new Ref(1)` has type `Ref[Int]^`. + +**Restriction:** A non-mutable type cannot be downcast by a pattern match to a mutable type. + +**Definition:** A class is _read_only_ if the following conditions are met: + + 1. It does not extend any exclusive capabilities from its environment. + 2. It does not take parameters with exclusive capabilities. + 3. It does not contain mutable fields, or fields that take exclusive capabilities. + +**Restriction:** If a class or trait extends `Mutable` all its parent classes or traits must either extend `Mutable` or be read-only. + +The idea is that when we upcast a reference to a type extending `Mutable` to a type that does not extend `Mutable`, we cannot possibly call a method on this reference that uses an exclusive capability. Indeed, by the previous restriction this class must be a read-only class, which means that none of the code implemented +in the class can access exclusive capabilities on its own. And we +also cannot override any of the methods of this class with a method +accessing exclusive capabilities, since such a method would have +to be an update method and update methods are not allowed to override regular methods. + + + +**Example:** + +Consider trait `IterableOnce` from the standard library. + +```scala +trait IterableOnce[+T] extends Mutable: + def iterator: Iterator[T]^{this} + update def foreach(op: T => Unit): Unit + update def exists(op: T => Boolean): Boolean + ... +``` +The trait is a mutable type with many update methods, among them `foreach` and `exists`. These need to be classified as `update` because their implementation in the subtrait `Iterator` uses the update method `next`. +```scala +trait Iterator[T] extends IterableOnce[T]: + def iterator = this + def hasNext: Boolean + update def next(): T + update def foreach(op: T => Unit): Unit = ... + update def exists(op; T => Boolean): Boolean = ... + ... +``` +But there are other implementations of `IterableOnce` that are not mutable types (even though they do indirectly extend the `Mutable` trait). Notably, collection classes implement `IterableOnce` by creating a fresh +`iterator` each time one is required. The mutation via `next()` is then restricted to the state of that iterator, whereas the underlying collection is unaffected. These implementations would implement each `update` method in `IterableOnce` by a normal method without the `update` modifier. + +```scala +trait Iterable[T] extends IterableOnce[T]: + def iterator = new Iterator[T] { ... } + def foreach(op: T => Unit) = iterator.foreach(op) + def exists(op: T => Boolean) = iterator.exists(op) +``` +Here, `Iterable` is not a mutable type since it has no update method as member. +All inherited update methods are (re-)implemented by normal methods. + +**Note:** One might think that we don't need a base trait `Mutable` since in any case +a mutable type is defined by the presence of update methods, not by what it extends. In fact the importance of `Mutable` is that it defines _the other methods_ as read-only methods that _cannot_ access exclusive capabilities. For types not extending `Mutable`, this is not the case. For instance, the `apply` method of a function type is not an update method and the type itself does not extend `Mutable`. But `apply` may well be implemented by +a method that accesses exclusive capabilities. + +## Read-only Capabilities + +If `x` is an exclusive capability of a type extending `Mutable`, `x.rd` is its associated _read-only_ capability. It counts as a shared capability. A read-only capability does not permit access to the mutable fields of a matrix. + +A read-only capability can be seen as a classified capability +using a classifier trait `Read` that extends `Mutable`. I.e. +`x.rd` can be seen as being essentially the same as `x.only[Read]`. +Currently, this precise equivalence is still waiting to be implemented. + +**Implicitly added capture sets** + +A reference to a type extending trait `Mutable` gets an implicit capture set `{cap.rd}` provided no explicit capture set is given. This is different from +other capability traits which implicitly add `{cap}`. + +For instance, consider the matrix multiplication method mentioned previously: +```scala +def multiply(a: Matrix, b: Matrix, c: Matrix^): Unit +``` +It is expanded to +```scala +def multiply(a: Matrix^{cap.rd}, b: Matrix^{cap.rd}, c: Matrix^{cap}): Unit +``` +Here, `a` and `b` are implicitly read-only, and `c`'s type has capture set `cap`. I.e. with explicit capture sets this would read: +```scala +def mul(a: Matrix^{cap.rd}, b: Matrix^{cap.rd}, c: Matrix^{cap}): Unit +``` +Separation checking will then make sure that `a` and `b` must be different from `c`. + +## Accesses to Mutable Types + +An access `p.m` to an update method or class `m` in a mutable type is permitted only if the type of the prefix `M` retains exclusive capabilities. If `M` is pure or its capture set has only shared +capabilities then the access is not permitted. + +A _read-only access_ is a reference `x` to a type extending `Mutable` that is either + + - widened to a value type that is not a mutable type, or + - immediately followed by a selection with a member that is a normal method or class (not an update method or class). + +A read-only access charges the read-only capability `x.rd` to its environment. Other accesses charge the full capability `x`. + +**Example:** + +Consider a reference `x` and two closures `f` and `g`. + +```scala +val x = Ref() +val f = () => x.get // f: () ->{x.rd} Unit +val g = () => x.set(1) // g: () ->{x} Unit +``` + +`f` accesses a regular method, so it charges only `x.rd` to its environment which shows up in its capture set. Bu contrast, `g` +accesses an update method of `x`,so its capture set is `{x}`. + +A reference to a mutable type with an exclusive capture set can be widened to a reference with a read-only set. For instance, the following is OK: +```scala +val a: Ref^ = Ref() +val b1: Ref^{a.rd} = a +val b2: Ref^{cap.rd} = a +``` + +## Read-Only Capsets + +If we consider subtyping and subcapturing, we observe what looks like a contradiction: `x.rd` is seen as a restricted capability, so `{x.rd}` should subcapture `{x}`. Yet, we have seen in the example above that sometimes it goes the other way: `a`'s capture set is either `{a}` or `{cap}`, yet `a` can be used to define `b1` and `b2`, with capture sets `{a.rd}` and `{cap.rd}`, respectively. + +The contradiction can be explained by noting that we use a capture set in two different roles. + +First, and as always, a capture set defines _retained capabilities_ that may or may be not used by a value. More capabilities give larger types, and the empty capture set is the smallest set according to that ordering. That makes sense: If a higher-order function like `map` is willing to accept a function `A => B` that can have arbitrary effects it's certainly OK to pass a pure function of type `A -> B` to it. + +But for mutations, we use a capture set in a second role, in which it defines a set of _access permissions_. If we have a `Ref^`, we can access all its methods, but if we have a `Ref^{cap.rd}`, we can access only regular methods, not update methods. From that viewpoint a mutable type with exclusive capabilities lets you do more than a mutable type with just read-only capabilities. So by the Liskov substitution principle, sets with exclusive capabilities subcapture sets with only read-only capabilities. + +The contradiction can be solved by distinguishing these two roles. For access permissions, we express read-only sets with an additional _qualifier_ `RD`. That qualifier is used only in the formal theory and the implementation, it currently cannot be expressed in source. +We add an implicit read-only qualifier `RD` to all capture sets on mutable types that consist only of shared or read-only capabilities. +So when we write +```scala +val b1: Ref^{a.rd} = a +``` +we really mean +```scala +val b1: Ref^{a.rd}.RD = a +``` + +The subcapturing theory for sets is then as before, with the following additional rules: + + - `C <: C.RD` + - `C₁.RD <: C₂.RD` if `C₍ <: C₂` + - `{x, ...}.RD = {x.rd, ...}.RD` + - `{x.rd, ...} <: {x, ...}` + + + + From 17b2315838bc4751f6e79848cb66a1a54d241ea2 Mon Sep 17 00:00:00 2001 From: odersky Date: Thu, 14 Aug 2025 21:55:08 +0200 Subject: [PATCH 016/101] Finish separation checking section --- .../capture-checking/separation-checking.md | 173 +++++++++++++++++- 1 file changed, 163 insertions(+), 10 deletions(-) diff --git a/docs/_docs/reference/experimental/capture-checking/separation-checking.md b/docs/_docs/reference/experimental/capture-checking/separation-checking.md index 78c3010cbb1c..3391bf9e0135 100644 --- a/docs/_docs/reference/experimental/capture-checking/separation-checking.md +++ b/docs/_docs/reference/experimental/capture-checking/separation-checking.md @@ -40,15 +40,7 @@ In fact, only a single character was added: `c`'s type now carries a universal c So, effectively, anything that can be updated must be unaliased. -The idea behind separation checking is simple: We now interpret each occurrence of `cap` as a separate top capability. This includes derived syntaxes like `A^` and `B => C`. We further keep track during capture checking which capabilities are subsumed by each `cap`. If capture checking widens a capability `x` to the universal capability, we say `x` is _hidden_ by the universal. The rule then is that any capability hidden by a universal capability `capᵢ` cannot be referenced independently or hidden in another cap in code that can see `capᵢ`. -Here's an example: -```scala -val x: C^ = y - ... x ... // ok - ... y ... // error -``` -This principle ensures that capabilities such as `x` that have `cap` as underlying capture set are un-aliased or "fresh". Any previously existing aliases such as `y` in the code above are inaccessible as long as `x` is also visible. ## Capability Kinds @@ -200,7 +192,7 @@ A read-only access charges the read-only capability `x.rd` to its environment. O Consider a reference `x` and two closures `f` and `g`. ```scala -val x = Ref() +val x = Ref(1) val f = () => x.get // f: () ->{x.rd} Unit val g = () => x.set(1) // g: () ->{x} Unit ``` @@ -210,7 +202,7 @@ accesses an update method of `x`,so its capture set is `{x}`. A reference to a mutable type with an exclusive capture set can be widened to a reference with a read-only set. For instance, the following is OK: ```scala -val a: Ref^ = Ref() +val a: Ref^ = Ref(1) val b1: Ref^{a.rd} = a val b2: Ref^{cap.rd} = a ``` @@ -243,6 +235,167 @@ The subcapturing theory for sets is then as before, with the following additiona - `{x, ...}.RD = {x.rd, ...}.RD` - `{x.rd, ...} <: {x, ...}` +## Separation Checking + +The idea behind separation checking is simple: We now interpret each occurrence of `cap` as a separate top capability. This includes derived syntaxes like `A^` and `B => C`. We further keep track during capture checking which capabilities are subsumed by each `cap`. If capture checking widens a capability `x` to the universal capability, we say `x` is _hidden_ by the universal. The rule then is that any capability hidden by a universal capability `capᵢ` cannot be referenced independently or hidden in another cap in code that can see `capᵢ`. + +Here's an example: +```scala +val x: C^ = y + ... x ... // ok + ... y ... // error +``` +This principle ensures that capabilities such as `x` that have `cap` as underlying capture set are un-aliased or "fresh". Any previously existing aliases such as `y` in the code above are inaccessible as long as `x` is also visible. + +The idea behind separation checking is simple: We now interpret each occurrence of `cap` as a separate top capability. This includes derived syntaxes like `A^` and `B => C`. We further keep track during capture checking which capabilities are subsumed by each `cap`. If capture checking widens a capability `x` to the universal capability, we say `x` is _hidden_ by the universal. The rule then is that any capability hidden by a universal capability `capᵢ` cannot be referenced independently or hidden in another cap in code that can see `capᵢ`. + +**Definitions:** + + - The _transitive capture set_ `tcs(c)` of a capability `c` with underlying capture set `C` is `c` itself, plus the transitive capture set of `C`. + + - The _transitive capture set_ `tcs(C)` of a capture set C is the union + of `tcs(c)` for all elements `c` of `C`. + + - Two capture sets _interfere_ if one contains an exclusive capability `x` and the other also contains `x` or contains the read-only capability `x.rd`. Conversely, two capture sets are _separated_ if they don't interfere.] + +Separation checks are applied in the following scenarios: + +### Checking Applications + +When checking a function application `f(e_1, ..., e_n)`, we instantiate each `cap` in a formal parameter of `f` to a fresh top capability and compare the argument types with these instantiated parameter types. We then check that the hidden set of each instantiated top capability for an argument `eᵢ` is separated from the capture sets of all the other arguments as well as from the capture sets of the function prefix and the function result. For instance a +call to +```scala +multiply(a, b, a) +``` +would be rejected since `a` appears in the hidden set of the last parameter of multiply, which has type `Matrix^` and also appears in the capture set of the +first parameter. + +We do not report a separation error between two sets if a formal parameter's capture set explicitly names a conflicting parameter. For instance, consider a method `seq` to apply two effectful function arguments in sequence. It can be declared as follows: +```scala +def seq(f: () => Unit; g: () ->{cap, f} Unit): Unit = + f(); g() +``` +Here, the `g` parameter explicitly mentions `f` in its potential capture set. This means that the `cap` in the same capture set would not need to hide the first argument, since it already appears explicitly in the same set. Consequently, we can pass the same function twice to `compose` without violating the separation criteria: +```scala +val r = Ref(1) +val plusOne = r.set(r.get + 1) +seq(plusOne, plusOne) +``` +Without the explicit mention of parameter `f` in the capture set of parameter `g` of `seq` we'd get a separation error, since the capture sets of both arguments contain `r` and are therefore not separated. + +### Checking Statement Sequences + +When a capability `x` is used at some point in a statement sequence, check that `x` does not appear in the hidden set of a previous definition. + +Example: +```scala +val a: Ref^ = Ref(1) +val b: Ref^ = a +val x = a.get // error +``` +Here, the last line violates the separation criterion since it uses in `a.get` the capability `a`, which is hidden by the definition of `b`. +Note that this check only applies when there are explicit top capabilities in play. One could very well write +```scala +val a: Ref^ = Ref(1) +val b: Ref^{a} = a +val x = a.get // ok +``` +One can also drop the explicit type of `b` and leave it to be inferred. That would +not cause a separation error either. +```scala +val a: Ref^ = Ref(0 +val b = a +val x = a.get // ok +``` + +### Checking Types + +When a type contains top capabilities we check that their hidden sets don't interfere with other parts of the same type. + +Example: +```scala +val b: (Ref^, Ref^) = (a, a) // error +val c: (Ref^, Ref^{a}) = (a, a) // error +val d: (Ref^{a}, Ref^{a}) = (a, a) // ok +``` +Here, the definition of `b` is in error since the hidden sets of the two `^` in its type both contain `a`. Likewise, the definition of `c` is in error since the hidden set of the `^` in its type contains `a`, which is also part of a capture set somewhere else in the type. On the other hand, the definition of `d` is legal since there are no hidden sets to check. + +### Checking Return Types + +When a `cap` appears in the return type of a function it means a "fresh" top capability, different from what is known at the call site. Separation checking makes sure this is the case. For instance, the following is OK: +```scala +def newRef(): Ref^ = Ref(1) +``` +And so is this: +```scala +def newRef(): Ref^ = + val a = Ref(1) + a +``` +But the next line would cause a separation error: +```scala +val a = Ref(1) +def newRef(): Ref^ = a // error +``` +Here the rule is that the hidden set of a fresh cap in a return type cannot reference capabilities defined outside of the function. What about parameters? +Here's another illegal version: +```scala +def incr(a: Ref^): Ref^ = + a.set(a.get + 1) + a +``` +These needs to be rejected because otherwise we could have set up the following bad example: +```scala +val a = Ref(1) +val b: Ref^ = incr(a) +``` +Here, `b` aliases `a` but does not hide it. If we referred to `a` afterwards we would be surprised to see that the reference has now a value of 2. +Therefore, parameters cannot appear in the hidden sets of fresh result caps either, at least not in general. An exception to this rule is described in the next section. + +### Consume Parameters +Returning parameters in fresh result caps is safe if the actual argument to the parameter is not used afterwards. We can signal and enforce this pattern by adding a `@consume` annotation to a parameter. With that annotation, the following variant of `incr` is legal: +```scala +def incr(@consume a: Ref^): Ref^ = + a.set(a.get + 1) + a +``` +Here, we increment the value of a reference and then return the same reference while enforcing the condition that the original reference cannot be used afterwards. Then the following is legal: +```scala +val a1 = Ref(1) +val a2 = incr(a1) +val a3 = incr(a2) +println(a3) +``` +Each reference `aᵢ` is unused after it is passed to `incr`. But the following continuation of that sequence would be in error: +```scala +val a4 = println(a2) // error +val a5 = incr(a1) // error +``` +In both of these assignments we use a capability that was consumed in an argument +of a previous application. +Consume parameters enforce linear access to resources. This can be very useful. As an example, consider Scala's buffers such as `ListBuffer` or `ArrayBuffer`. We can treat these buffers as if they were purely functional, if we can enforce linear access. + +We can define a function `linearAdd` that adds elements to buffers without violating referential transparency: +```scala +def linearAdd[T](@consume buf: Buffer[T]^, elem: T): Buffer[T]^ = + buf += elem +``` +`linearAdd` returns a fresh buffer resulting from appending `elem` to `buf`. It overwrites `buf`, but that's OK since the `@consume` annotation on `buf `makes the argument is not used after the call. + +### Consume Methods + +Buffers in Scala's standard library use a single-argument method `+=` instead of a two argument global function. We can enforce linearity in this case by adding the `@consume` annotation to the method itself. +```scala +class Buffer[T] extends Mutable: + @consume update def +=(x: T): Buffer[T]^ = this // ok +``` +Then we can write +```scala +val b = Buffer[Int]() += 1 += 2 +val c = b += 3 +// b cannot be used from here +``` +and this code is equivalent (but a lot more efficient) to functional append with `+`. From 426edbd974aac5e23303eabf49c728f2eee50e4c Mon Sep 17 00:00:00 2001 From: odersky Date: Fri, 15 Aug 2025 11:24:02 +0200 Subject: [PATCH 017/101] Fixes to separation checking --- .../capture-checking/separation-checking.md | 47 +++++++++---------- 1 file changed, 21 insertions(+), 26 deletions(-) diff --git a/docs/_docs/reference/experimental/capture-checking/separation-checking.md b/docs/_docs/reference/experimental/capture-checking/separation-checking.md index 3391bf9e0135..1a4c9ead1776 100644 --- a/docs/_docs/reference/experimental/capture-checking/separation-checking.md +++ b/docs/_docs/reference/experimental/capture-checking/separation-checking.md @@ -29,13 +29,13 @@ class Matrix(nrows: Int, ncols: Int) extends Mutable: ``` We further declare that the `setElem` method in a `Matrix` is an `update` method, which means it has a side effect. -Separation checking also gives a special interpretation to the following modified signature of `multiply`: +Separation checking gives a special interpretation to the following modified signature of `multiply`: ```scala def multiply(a: Matrix, b: Matrix, c: Matrix^): Unit ``` In fact, only a single character was added: `c`'s type now carries a universal capability. This signature enforces at the same time two desirable properties: - - Matrices `a`, and `b` are read-only; `multiply` will not call their update method. By contrast, matrix `c` can be updated. + - Matrices `a`, and `b` are read-only; `multiply` will not call their update method. By contrast, the `c` matrix can be updated. - Matrices `a` and `b` are different from matrix `c`, but `a` and `b` could refer to the same matrix. So, effectively, anything that can be updated must be unaliased. @@ -62,11 +62,11 @@ a new soft modifier `update`. class Ref(init: Int) extends Mutable: private var current = init def get: Int = current - update def put(x: Int): Unit = current = x + update def set(x: Int): Unit = current = x ``` `update` can only be used in classes or objects extending `Mutable`. An update method is allowed to access exclusive capabilities in the method's environment. By contrast, a normal method in a type extending `Mutable` may access exclusive capabilities only if they are defined locally or passed to it in parameters. -In class `Ref`, the `put` should be declared as an update method since it accesses the exclusive write capability of the variable `current` in its environment. +In class `Ref`, the `set` should be declared as an update method since it accesses the exclusive write capability of the variable `current` in its environment. `update` can also be used on an inner class of a class or object extending `Mutable`. It gives all code in the class the right to access exclusive capabilities in the class environment. Normal classes @@ -84,7 +84,7 @@ Normal method members of `Mutable` classes cannot call update methods. This is i An update method cannot implement or override a normal method, whereas normal methods may implement or override update methods. Since methods such as `toString` or `==` inherited from Object are normal methods, it follows that none of these methods may be implemented as an update method. -The `apply` method of a function type is also a normal method, hence `Mutable` classes may not implement a function type with an update method as the `apply` method. +The `apply` method of a function type is also a normal method, hence `Mutable` classes may not implement a function type using an update method as the `apply` method. ## Mutable Types @@ -154,7 +154,7 @@ If `x` is an exclusive capability of a type extending `Mutable`, `x.rd` is its a A read-only capability can be seen as a classified capability using a classifier trait `Read` that extends `Mutable`. I.e. `x.rd` can be seen as being essentially the same as `x.only[Read]`. -Currently, this precise equivalence is still waiting to be implemented. +(Currently, this precise equivalence is still waiting to be implemented.) **Implicitly added capture sets** @@ -165,10 +165,6 @@ For instance, consider the matrix multiplication method mentioned previously: ```scala def multiply(a: Matrix, b: Matrix, c: Matrix^): Unit ``` -It is expanded to -```scala -def multiply(a: Matrix^{cap.rd}, b: Matrix^{cap.rd}, c: Matrix^{cap}): Unit -``` Here, `a` and `b` are implicitly read-only, and `c`'s type has capture set `cap`. I.e. with explicit capture sets this would read: ```scala def mul(a: Matrix^{cap.rd}, b: Matrix^{cap.rd}, c: Matrix^{cap}): Unit @@ -177,7 +173,7 @@ Separation checking will then make sure that `a` and `b` must be different from ## Accesses to Mutable Types -An access `p.m` to an update method or class `m` in a mutable type is permitted only if the type of the prefix `M` retains exclusive capabilities. If `M` is pure or its capture set has only shared +An access `p.m` to an update method or class `m` in a mutable type is permitted only if the type `M` of the prefix `p` retains exclusive capabilities. If `M` is pure or its capture set has only shared capabilities then the access is not permitted. A _read-only access_ is a reference `x` to a type extending `Mutable` that is either @@ -197,8 +193,8 @@ val f = () => x.get // f: () ->{x.rd} Unit val g = () => x.set(1) // g: () ->{x} Unit ``` -`f` accesses a regular method, so it charges only `x.rd` to its environment which shows up in its capture set. Bu contrast, `g` -accesses an update method of `x`,so its capture set is `{x}`. +`f` accesses a regular method, so it charges only `x.rd` to its environment which shows up in its capture set. By contrast, `g` +accesses an update method of `x`, so its capture set is `{x}`. A reference to a mutable type with an exclusive capture set can be widened to a reference with a read-only set. For instance, the following is OK: ```scala @@ -237,7 +233,7 @@ The subcapturing theory for sets is then as before, with the following additiona ## Separation Checking -The idea behind separation checking is simple: We now interpret each occurrence of `cap` as a separate top capability. This includes derived syntaxes like `A^` and `B => C`. We further keep track during capture checking which capabilities are subsumed by each `cap`. If capture checking widens a capability `x` to the universal capability, we say `x` is _hidden_ by the universal. The rule then is that any capability hidden by a universal capability `capᵢ` cannot be referenced independently or hidden in another cap in code that can see `capᵢ`. +The idea behind separation checking is simple: We now interpret each occurrence of `cap` as a separate top capability. This includes derived syntaxes like `A^` and `B => C`. We further keep track during capture checking which capabilities are subsumed by each `cap`. If capture checking widens a capability `x` to a top capability `capᵢ`, we say `x` is _hidden_ by `capᵢ`. The rule then is that any capability hidden by a top capability `capᵢ` cannot be referenced independently or hidden in another `capⱼ` in code that can see `capᵢ`. Here's an example: ```scala @@ -247,7 +243,7 @@ val x: C^ = y ``` This principle ensures that capabilities such as `x` that have `cap` as underlying capture set are un-aliased or "fresh". Any previously existing aliases such as `y` in the code above are inaccessible as long as `x` is also visible. -The idea behind separation checking is simple: We now interpret each occurrence of `cap` as a separate top capability. This includes derived syntaxes like `A^` and `B => C`. We further keep track during capture checking which capabilities are subsumed by each `cap`. If capture checking widens a capability `x` to the universal capability, we say `x` is _hidden_ by the universal. The rule then is that any capability hidden by a universal capability `capᵢ` cannot be referenced independently or hidden in another cap in code that can see `capᵢ`. +Separation checking applies only to exclusive capabilities and their read-only versions. Any capability extending `SharedCapability` in its type is exempted; the following definitions and rules do not apply to them. **Definitions:** @@ -256,7 +252,7 @@ The idea behind separation checking is simple: We now interpret each occurrence - The _transitive capture set_ `tcs(C)` of a capture set C is the union of `tcs(c)` for all elements `c` of `C`. - - Two capture sets _interfere_ if one contains an exclusive capability `x` and the other also contains `x` or contains the read-only capability `x.rd`. Conversely, two capture sets are _separated_ if they don't interfere.] + - Two capture sets _interfere_ if one contains an exclusive capability `x` and the other also contains `x` or contains the read-only capability `x.rd`. Conversely, two capture sets are _separated_ if their transitive capture sets don't interfere. Separation checks are applied in the following scenarios: @@ -281,11 +277,11 @@ val r = Ref(1) val plusOne = r.set(r.get + 1) seq(plusOne, plusOne) ``` -Without the explicit mention of parameter `f` in the capture set of parameter `g` of `seq` we'd get a separation error, since the capture sets of both arguments contain `r` and are therefore not separated. +Without the explicit mention of parameter `f` in the capture set of parameter `g` of `seq` we'd get a separation error, since the transitive capture sets of both arguments contain `r` and are therefore not separated. ### Checking Statement Sequences -When a capability `x` is used at some point in a statement sequence, check that `x` does not appear in the hidden set of a previous definition. +When a capability `x` is used at some point in a statement sequence, we check that `{x}` is separated from the hidden sets of all previous definitions. Example: ```scala @@ -318,7 +314,7 @@ val b: (Ref^, Ref^) = (a, a) // error val c: (Ref^, Ref^{a}) = (a, a) // error val d: (Ref^{a}, Ref^{a}) = (a, a) // ok ``` -Here, the definition of `b` is in error since the hidden sets of the two `^` in its type both contain `a`. Likewise, the definition of `c` is in error since the hidden set of the `^` in its type contains `a`, which is also part of a capture set somewhere else in the type. On the other hand, the definition of `d` is legal since there are no hidden sets to check. +Here, the definition of `b` is in error since the hidden sets of the two `^`s in its type both contain `a`. Likewise, the definition of `c` is in error since the hidden set of the `^` in its type contains `a`, which is also part of a capture set somewhere else in the type. On the other hand, the definition of `d` is legal since there are no hidden sets to check. ### Checking Return Types @@ -332,13 +328,12 @@ def newRef(): Ref^ = val a = Ref(1) a ``` -But the next line would cause a separation error: +But the next definitions would cause a separation error: ```scala val a = Ref(1) def newRef(): Ref^ = a // error ``` -Here the rule is that the hidden set of a fresh cap in a return type cannot reference capabilities defined outside of the function. What about parameters? -Here's another illegal version: +The rule is that the hidden set of a fresh cap in a return type cannot reference exclusive or read-only capabilities defined outside of the function. What about parameters? Here's another illegal version: ```scala def incr(a: Ref^): Ref^ = a.set(a.get + 1) @@ -377,16 +372,16 @@ of a previous application. Consume parameters enforce linear access to resources. This can be very useful. As an example, consider Scala's buffers such as `ListBuffer` or `ArrayBuffer`. We can treat these buffers as if they were purely functional, if we can enforce linear access. -We can define a function `linearAdd` that adds elements to buffers without violating referential transparency: +For instance, we can define a function `linearAdd` that adds elements to buffers in-place without violating referential transparency: ```scala def linearAdd[T](@consume buf: Buffer[T]^, elem: T): Buffer[T]^ = buf += elem ``` -`linearAdd` returns a fresh buffer resulting from appending `elem` to `buf`. It overwrites `buf`, but that's OK since the `@consume` annotation on `buf `makes the argument is not used after the call. +`linearAdd` returns a fresh buffer resulting from appending `elem` to `buf`. It overwrites `buf`, but that's OK since the `@consume` annotation on `buf` ensures that the argument is not used after the call. ### Consume Methods -Buffers in Scala's standard library use a single-argument method `+=` instead of a two argument global function. We can enforce linearity in this case by adding the `@consume` annotation to the method itself. +Buffers in Scala's standard library use a single-argument method `+=` instead of a two argument global function like `linearAdd`. We can enforce linearity in this case by adding the `@consume` annotation to the method itself. ```scala class Buffer[T] extends Mutable: @consume update def +=(x: T): Buffer[T]^ = this // ok @@ -397,5 +392,5 @@ val b = Buffer[Int]() += 1 += 2 val c = b += 3 // b cannot be used from here ``` -and this code is equivalent (but a lot more efficient) to functional append with `+`. +This code is equivalent to functional append with `+`, and is at the same time more efficient since it re-uses the storage of the argument buffer. From 57cf1dbeffd3b8cbfd676adf0564041fac09810f Mon Sep 17 00:00:00 2001 From: odersky Date: Fri, 15 Aug 2025 15:56:19 +0200 Subject: [PATCH 018/101] Make consume a soft modifier Internally, it is still represented as an annotation --- compiler/src/dotty/tools/dotc/ast/untpd.scala | 3 ++ .../src/dotty/tools/dotc/cc/SepCheck.scala | 24 ++++----- compiler/src/dotty/tools/dotc/cc/Setup.scala | 4 +- .../dotty/tools/dotc/core/Definitions.scala | 2 +- .../src/dotty/tools/dotc/core/StdNames.scala | 1 + .../dotty/tools/dotc/parsing/Parsers.scala | 23 +++++--- .../dotty/tools/dotc/parsing/Scanners.scala | 3 +- .../tools/dotc/printing/PlainPrinter.scala | 10 ++-- .../tools/dotc/printing/RefinedPrinter.scala | 9 ++-- .../src/dotty/tools/dotc/typer/Namer.scala | 5 ++ docs/_docs/internals/syntax.md | 8 +-- library/src/scala/caps/package.scala | 15 ++---- .../neg-custom-args/captures/bad-uses-2.scala | 11 ++-- .../captures/box-adapt-contra.scala | 4 +- .../captures/cc-annot-value-classes2.scala | 6 +-- .../captures/cc-this-shared.scala | 4 +- tests/neg-custom-args/captures/cc-this.scala | 4 +- .../captures/consume-overrides.scala | 8 +-- .../captures/delayedRunops.check | 24 ++++----- .../captures/delayedRunops.scala | 4 +- tests/neg-custom-args/captures/i19330.check | 2 +- tests/neg-custom-args/captures/i21442.check | 2 +- tests/neg-custom-args/captures/i22005.scala | 2 +- tests/neg-custom-args/captures/i23431.check | 2 +- .../captures/linear-buffer-2.check | 8 +-- .../captures/linear-buffer-2.scala | 16 +++--- .../captures/linear-buffer.check | 14 ++--- .../captures/linear-buffer.scala | 16 +++--- .../captures/localReaches.check | 4 +- .../captures/localReaches.scala | 2 +- .../captures/non-local-consume.scala | 14 ++--- tests/neg-custom-args/captures/reaches.check | 52 +++++++++---------- tests/neg-custom-args/captures/reaches.scala | 4 +- .../captures/sep-consume.check | 6 +-- .../captures/sep-consume.scala | 12 ++--- .../captures/sep-lazyListState.check | 2 +- tests/neg-custom-args/captures/sep-use.scala | 10 ++-- tests/neg-custom-args/captures/sep-use2.check | 10 ++-- tests/neg-custom-args/captures/sep-use2.scala | 14 ++--- .../neg-custom-args/captures/sepchecks2.check | 4 +- .../neg-custom-args/captures/sepchecks2.scala | 8 +-- .../neg-custom-args/captures/sepchecks3.scala | 4 +- .../neg-custom-args/captures/sepchecks4.check | 4 +- .../neg-custom-args/captures/sepchecks4.scala | 2 +- .../neg-custom-args/captures/sepchecks5.check | 10 ++-- .../neg-custom-args/captures/sepchecks5.scala | 6 +-- .../captures/unsound-reach-3.scala | 6 +-- .../captures/unsound-reach-4.check | 2 +- .../captures/unsound-reach-4.scala | 4 +- .../captures/unsound-reach-6.check | 6 +-- .../captures/unsound-reach-6.scala | 8 +-- tests/neg/localReaches.scala | 2 +- tests/new/test.scala | 6 ++- .../captures/capt-depfun.scala | 4 +- .../captures/capt-depfun2.scala | 4 +- tests/pos-custom-args/captures/cc-this.scala | 4 +- tests/pos-custom-args/captures/lazyref.scala | 4 +- tests/pos-custom-args/captures/reaches.scala | 7 +-- .../pos-custom-args/captures/sep-pairs.scala | 8 +-- tests/pos-custom-args/captures/skolems2.scala | 6 +-- .../stdlibExperimentalDefinitions.scala | 2 +- 61 files changed, 247 insertions(+), 228 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/ast/untpd.scala b/compiler/src/dotty/tools/dotc/ast/untpd.scala index 96c8c4c4f845..76a584ef146a 100644 --- a/compiler/src/dotty/tools/dotc/ast/untpd.scala +++ b/compiler/src/dotty/tools/dotc/ast/untpd.scala @@ -559,6 +559,9 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo { def makeOnlyAnnot(qid: Tree)(using Context) = New(AppliedTypeTree(ref(defn.OnlyCapabilityAnnot.typeRef), qid :: Nil), Nil :: Nil) + def makeConsumeAnnot()(using Context): Tree = + New(ref(defn.ConsumeAnnot.typeRef), Nil :: Nil) + def makeConstructor(tparams: List[TypeDef], vparamss: List[List[ValDef]], rhs: Tree = EmptyTree)(using Context): DefDef = DefDef(nme.CONSTRUCTOR, joinParams(tparams, vparamss), TypeTree(), rhs) diff --git a/compiler/src/dotty/tools/dotc/cc/SepCheck.scala b/compiler/src/dotty/tools/dotc/cc/SepCheck.scala index f5589caf3872..a3f3d40a42f6 100644 --- a/compiler/src/dotty/tools/dotc/cc/SepCheck.scala +++ b/compiler/src/dotty/tools/dotc/cc/SepCheck.scala @@ -22,9 +22,9 @@ import Capabilities.* * * - Hidden sets of arguments must not be referred to in the same application * - Hidden sets of (result-) types must not be referred to alter in the same scope. - * - Returned hidden sets can only refer to @consume parameters. + * - Returned hidden sets can only refer to consume parameters. * - If returned hidden sets refer to an encloding this, the reference must be - * from a @consume method. + * from a consume method. * - Consumed entities cannot be used subsequently. * - Entitites cannot be consumed in a loop. */ @@ -422,7 +422,7 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser: def consumeError(ref: Capability, loc: SrcPos, pos: SrcPos)(using Context): Unit = report.error( em"""Separation failure: Illegal access to $ref, which was passed to a - |@consume parameter or was used as a prefix to a @consume method on line ${loc.line + 1} + |consume parameter or was used as a prefix to a consume method on line ${loc.line + 1} |and therefore is no longer available.""", pos) @@ -433,7 +433,7 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser: def consumeInLoopError(ref: Capability, pos: SrcPos)(using Context): Unit = report.error( em"""Separation failure: $ref appears in a loop, therefore it cannot - |be passed to a @consume parameter or be used as a prefix of a @consume method call.""", + |be passed to a consume parameter or be used as a prefix of a consume method call.""", pos) // ------------ Checks ----------------------------------------------------- @@ -587,16 +587,16 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser: /** Check validity of consumed references `refsToCheck`. The references are consumed * because they are hidden in a Fresh result type or they are referred - * to in an argument to a @consume parameter or in a prefix of a @consume method -- + * to in an argument to a consume parameter or in a prefix of a consume method -- * which one applies is determined by the role parameter. * * This entails the following checks: * - The reference must be defined in the same as method or class as * the access. * - If the reference is to a term parameter, that parameter must be - * marked as @consume as well. + * marked as consume as well. * - If the reference is to a this type of the enclosing class, the - * access must be in a @consume method. + * access must be in a consume method. * * References that extend caps.Sharable are excluded from checking. * As a side effect, add all checked references with the given position `pos` @@ -631,7 +631,7 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser: then report.error( em"""Separation failure: $descr non-local this of class ${ref.cls}. - |The access must be in a @consume method to allow this.""", + |The access must be in a consume method to allow this.""", pos) case _ => @@ -643,7 +643,7 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser: val (pluralS, singleS) = if badParams.tail.isEmpty then ("", "s") else ("s", "") report.error( em"""Separation failure: $descr parameter$pluralS ${paramsStr(badParams.toList)}. - |The parameter$pluralS need$singleS to be annotated with @consume to allow this.""", + |The parameter$pluralS need$singleS to be annotated with consume to allow this.""", pos) role match @@ -769,7 +769,7 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser: /** If `tpe` appears as a (result-) type of a definition, treat its * hidden set minus its explicitly declared footprint as consumed. - * If `tpe` appears as an argument to a @consume parameter, treat + * If `tpe` appears as an argument to a consume parameter, treat * its footprint as consumed. */ def checkLegalRefs() = role match @@ -786,7 +786,7 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser: case TypeRole.Argument(arg) => if tpe.hasAnnotation(defn.ConsumeAnnot) then val capts = captures(arg).footprint - checkConsumedRefs(capts, tpe, role, i"argument to @consume parameter with type ${arg.nuType} refers to", pos) + checkConsumedRefs(capts, tpe, role, i"argument to consume parameter with type ${arg.nuType} refers to", pos) case _ => if !tpe.hasAnnotation(defn.UntrackedCapturesAnnot) then @@ -910,7 +910,7 @@ class SepCheck(checker: CheckCaptures.CheckerAPI) extends tpd.TreeTraverser: checkConsumedRefs( captures(qual).footprint, qual.nuType, TypeRole.Qualifier(qual, tree.symbol), - i"call prefix of @consume ${tree.symbol} refers to", qual.srcPos) + i"call prefix of consume ${tree.symbol} refers to", qual.srcPos) case tree: GenericApply => traverseChildren(tree) tree.tpe match diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index ec312631a61b..aa9b9f399634 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -758,8 +758,8 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI: && !(sym.is(Method) && sym.owner.isClass) then report.error( - em"""@consume cannot be used here. Only member methods and their term parameters - |can have @consume annotations.""", + em"""consume cannot be used here. Only member methods and their term parameters + |can have a consume modifier.""", tree.srcPos) else if annotCls == defn.UseAnnot then if !ccConfig.allowUse then diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 79fa0e94e06a..35febde2d307 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -1076,8 +1076,8 @@ class Definitions { @tu lazy val UncheckedCapturesAnnot: ClassSymbol = requiredClass("scala.annotation.unchecked.uncheckedCaptures") @tu lazy val UntrackedCapturesAnnot: ClassSymbol = requiredClass("scala.caps.unsafe.untrackedCaptures") @tu lazy val UseAnnot: ClassSymbol = requiredClass("scala.caps.use") - @tu lazy val ConsumeAnnot: ClassSymbol = requiredClass("scala.caps.consume") @tu lazy val ReserveAnnot: ClassSymbol = requiredClass("scala.caps.reserve") + @tu lazy val ConsumeAnnot: ClassSymbol = requiredClass("scala.caps.internal.consume") @tu lazy val RefineOverrideAnnot: ClassSymbol = requiredClass("scala.caps.internal.refineOverride") @tu lazy val VolatileAnnot: ClassSymbol = requiredClass("scala.volatile") @tu lazy val LanguageFeatureMetaAnnot: ClassSymbol = requiredClass("scala.annotation.meta.languageFeature") diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index 0bde164186b7..a061220b9b69 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -454,6 +454,7 @@ object StdNames { val compiletime : N = "compiletime" val compose: N = "compose" val conforms_ : N = "$conforms" + val consume: N = "consume" val contents: N = "contents" val copy: N = "copy" val create: N = "create" diff --git a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala index f6dd3c2396d4..530a2c29a94c 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Parsers.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Parsers.scala @@ -35,6 +35,7 @@ import config.SourceVersion.* import config.SourceVersion import dotty.tools.dotc.config.MigrationVersion import dotty.tools.dotc.util.chaining.* +import dotty.tools.dotc.config.Feature.ccEnabled object Parsers { @@ -216,6 +217,8 @@ object Parsers { def isPureArrow: Boolean = isPureArrow(nme.PUREARROW) || isPureArrow(nme.PURECTXARROW) def isErased = isIdent(nme.erased) && in.erasedEnabled && in.isSoftModifierInParamModifierPosition + def isConsume = + isIdent(nme.consume) && ccEnabled //\&& in.isSoftModifierInParamModifierPosition def isSimpleLiteral = simpleLiteralTokens.contains(in.token) || isIdent(nme.raw.MINUS) && numericLitTokens.contains(in.lookahead.token) @@ -3325,11 +3328,15 @@ object Parsers { private def addModifier(mods: Modifiers): Modifiers = { val tok = in.token val name = in.name - val mod = atSpan(in.skipToken()) { modOfToken(tok, name) } - - if mods.isOneOf(mod.flags) then - syntaxError(RepeatedModifier(mod.flags.flagsString, source, mod.span), mod.span) - addMod(mods, mod) + if isConsume then + val consumeAnnot = atSpan(in.skipToken())(makeConsumeAnnot()) + mods.withAddedAnnotation(consumeAnnot) + else + val mod = atSpan(in.skipToken()): + modOfToken(tok, name) + if mods.isOneOf(mod.flags) then + syntaxError(RepeatedModifier(mod.flags.flagsString, source, mod.span), mod.span) + addMod(mods, mod) } def addFlag(mods: Modifiers, flag: FlagSet): Modifiers = @@ -3555,7 +3562,8 @@ object Parsers { * UsingParamClause ::= ‘(’ ‘using’ (DefTermParams | ContextTypes) ‘)’ * DefImplicitClause ::= [nl] ‘(’ ‘implicit’ DefTermParams ‘)’ * DefTermParams ::= DefTermParam {‘,’ DefTermParam} - * DefTermParam ::= {Annotation} [‘erased’] [‘inline’] Param + * DefTermParam ::= {Annotation} TermParamMods Param + * TermParamMods ::= [‘erased‘] [‘inline’] | [‘consume‘] * * Param ::= id `:' ParamType [`=' Expr] * @@ -3592,7 +3600,7 @@ object Parsers { def param(): ValDef = { val start = in.offset var mods = impliedMods.withAnnotations(annotations()) - if isErased then + if isConsume || isErased then mods = addModifier(mods) if paramOwner.isClass then mods = addFlag(modifiers(start = mods), ParamAccessor) @@ -3664,6 +3672,7 @@ object Parsers { var mods = EmptyModifiers if in.lookahead.isColon then (mods, true) + else if isConsume then (mods, true) else if isErased then mods = addModifier(mods) val paramsAreNamed = diff --git a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala index ca2f5da3df3c..52e03de60dea 100644 --- a/compiler/src/dotty/tools/dotc/parsing/Scanners.scala +++ b/compiler/src/dotty/tools/dotc/parsing/Scanners.scala @@ -1228,7 +1228,8 @@ object Scanners { && (softModifierNames.contains(name) || name == nme.erased && erasedEnabled || name == nme.tracked && trackedEnabled - || name == nme.update && Feature.ccEnabled) + || name == nme.update && Feature.ccEnabled + || name == nme.consume && Feature.ccEnabled) def isSoftModifierInModifierPosition: Boolean = isSoftModifier && inModifierPosition() diff --git a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala index cda3e7d4b6f2..4a23f00f3c53 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 ast.tpd +import ast.{tpd, untpd} import cc.* import CaptureSet.Mutability import Capabilities.* @@ -716,8 +716,12 @@ class PlainPrinter(_ctx: Context) extends Printer { case _ => literalText(String.valueOf(const.value)) } - /** Usual target for `Annotation#toText`, overridden in RefinedPrinter */ - def annotText(annot: Annotation): Text = s"@${annot.symbol.name}" + /** Usual target for `Annotation#toText`, overridden in RefinedPrinter, which also + * looks at trees. + */ + override def annotText(annot: Annotation): Text = annotText(annot.symbol) + + protected def annotText(sym: Symbol): Text = s"@${sym.name}" def toText(annot: Annotation): Text = annot.toText(this) diff --git a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala index 1e58268c2e38..30934b615649 100644 --- a/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/RefinedPrinter.scala @@ -1140,6 +1140,8 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { /** Textual representation of an instance creation expression without the leading `new` */ protected def constrText(tree: untpd.Tree): Text = toTextLocal(tree).stripPrefix(keywordStr("new ")) // DD + override def annotText(annot: Annotation): Text = annotText(annot.symbol, annot.tree) + protected def annotText(sym: Symbol, tree: untpd.Tree): Text = def recur(t: untpd.Tree): Text = t match case Apply(fn, Nil) => recur(fn) @@ -1152,9 +1154,12 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { case New(tpt) => recur(tpt) case _ => val annotSym = sym.orElse(tree.symbol.enclosingClass) - s"@${if annotSym.exists then annotSym.name.toString else t.show}" + if annotSym.exists then annotText(annotSym) else s"@${t.show}" recur(tree) + protected override def annotText(sym: Symbol): Text = + if sym == defn.ConsumeAnnot then "consume" else super.annotText(sym) + protected def modText(mods: untpd.Modifiers, sym: Symbol, kw: String, isType: Boolean): Text = { // DD val suppressKw = if (enclDefIsClass) mods.isAllOf(LocalParam) else mods.is(Param) var flagMask = @@ -1175,8 +1180,6 @@ class RefinedPrinter(_ctx: Context) extends PlainPrinter(_ctx) { Text(annotTexts, " ") ~~ flagsText ~~ (Str(kw) provided !suppressKw) } - override def annotText(annot: Annotation): Text = annotText(annot.symbol, annot.tree) - def optText(name: Name)(encl: Text => Text): Text = if (name.isEmpty) "" else encl(toText(name)) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index e86414cc2183..4e74b558b5db 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -995,6 +995,10 @@ class Namer { typer: Typer => end if } + private def normalizeFlags(denot: SymDenotation)(using Context): Unit = + if denot.is(Method) && denot.hasAnnotation(defn.ConsumeAnnot) then + denot.setFlag(Mutable) + /** Intentionally left without `using Context` parameter. We need * to pick up the context at the point where the completer was created. */ @@ -1004,6 +1008,7 @@ class Namer { typer: Typer => addInlineInfo(sym) denot.info = typeSig(sym) invalidateIfClashingSynthetic(denot) + normalizeFlags(denot) Checking.checkWellFormed(sym) denot.info = avoidPrivateLeaks(sym) } diff --git a/docs/_docs/internals/syntax.md b/docs/_docs/internals/syntax.md index 9a5d3d4b2776..5ce45a291091 100644 --- a/docs/_docs/internals/syntax.md +++ b/docs/_docs/internals/syntax.md @@ -141,7 +141,7 @@ type val var while with yield ### Soft keywords ``` -as derives end erased extension infix inline mut opaque open throws tracked transparent using | * + - +as consume derives end erased extension infix inline opaque open throws tracked transparent update using | * + - ``` See the [separate section on soft keywords](../reference/soft-modifier.md) for additional @@ -406,7 +406,8 @@ UsingParamClause ::= [nl] ‘(’ ‘using’ (DefTermParams | FunArgTypes) DefImplicitClause ::= [nl] ‘(’ ‘implicit’ DefTermParams ‘)’ DefTermParams ::= DefTermParam {‘,’ DefTermParam} -DefTermParam ::= {Annotation} [`erased`] [‘inline’] Param ValDef(mods, id, tpe, expr) -- point of mods at id. +DefTermParam ::= {Annotation} TermParamMods Param ValDef(mods, id, tpe, expr) -- point of mods at id. +TermParamMods ::= [‘erased‘] [‘inline’] | [‘consume‘] Param ::= id ‘:’ ParamType [‘=’ Expr] ``` @@ -430,7 +431,8 @@ LocalModifier ::= ‘abstract’ | ‘infix’ | ‘erased’ | ‘tracked’ - | ‘mut’ -- under captureChecking + | ‘update’ -- under captureChecking + | ‘consume’ AccessModifier ::= (‘private’ | ‘protected’) [AccessQualifier] AccessQualifier ::= ‘[’ id ‘]’ diff --git a/library/src/scala/caps/package.scala b/library/src/scala/caps/package.scala index ff029fbbae9c..45d1e18e741a 100644 --- a/library/src/scala/caps/package.scala +++ b/library/src/scala/caps/package.scala @@ -123,16 +123,6 @@ final class reserve extends annotation.StaticAnnotation @experimental final class use extends annotation.StaticAnnotation -/** An annotations on parameters and update methods. - * On a parameter it states that any capabilties passed in the argument - * are no longer available afterwards, unless they are of class `SharableCapabilitty`. - * On an update method, it states that the `this` of the enclosing class is - * consumed, which means that any capabilities of the method prefix are - * no longer available afterwards. - */ -@experimental -final class consume extends annotation.StaticAnnotation - /** A trait that used to allow expressing existential types. Replaced by * root.Result instances. */ @@ -143,6 +133,11 @@ sealed trait Exists extends Capability @experimental object internal: + /** An annotation to reflect that a parameter or method carries the `consume` + * soft modifier. + */ + final class consume extends annotation.StaticAnnotation + /** An internal annotation placed on a refinement created by capture checking. * Refinements with this annotation unconditionally override any * info from the parent type, so no intersection needs to be formed. diff --git a/tests/neg-custom-args/captures/bad-uses-2.scala b/tests/neg-custom-args/captures/bad-uses-2.scala index d8bc3eca93e6..7a4f26b506a9 100644 --- a/tests/neg-custom-args/captures/bad-uses-2.scala +++ b/tests/neg-custom-args/captures/bad-uses-2.scala @@ -1,13 +1,12 @@ -import caps.{use, consume} +import caps.{use, Mutable} class TestUse: @use def F = ??? // error @use val x = ??? // error @use type T // error def foo[@use T](@use c: T): Unit = ??? // error // error -class TestConsume: - @consume def F = ??? // ok - @consume val x = ??? // error - @consume type T // error - def foo[@consume T](@use c: T): Unit = ??? // error // error +class TestConsume extends Mutable: + consume def F = ??? // ok + consume val x = ??? // error + consume type T // error diff --git a/tests/neg-custom-args/captures/box-adapt-contra.scala b/tests/neg-custom-args/captures/box-adapt-contra.scala index 9c18a9a2c50a..660c82ac8410 100644 --- a/tests/neg-custom-args/captures/box-adapt-contra.scala +++ b/tests/neg-custom-args/captures/box-adapt-contra.scala @@ -1,5 +1,5 @@ -import caps.consume + trait Cap @@ -8,7 +8,7 @@ def useCap[X](x: X): (X -> Unit) -> Unit = ??? def test1(c: Cap^): Unit = val f: (Cap^{c} -> Unit) -> Unit = useCap[Cap^{c}](c) // error -def test2(@consume c: Cap^, d: Cap^): Unit = +def test2(consume c: Cap^, d: Cap^): Unit = def useCap1[X](x: X): (X => Unit) -> Unit = ??? val f1: (Cap^{c} => Unit) ->{c} Unit = useCap1[Cap^{c}](c) // error, was ok when cap was a root diff --git a/tests/neg-custom-args/captures/cc-annot-value-classes2.scala b/tests/neg-custom-args/captures/cc-annot-value-classes2.scala index 5821f9664f6b..f8449b49a444 100644 --- a/tests/neg-custom-args/captures/cc-annot-value-classes2.scala +++ b/tests/neg-custom-args/captures/cc-annot-value-classes2.scala @@ -1,13 +1,13 @@ import language.experimental.captureChecking import caps.* trait Ref extends Mutable -def kill(@consume x: Ref^): Unit = () +def kill(consume x: Ref^): Unit = () class C1: - def myKill(@consume x: Ref^): Unit = kill(x) // ok + def myKill(consume x: Ref^): Unit = kill(x) // ok class C2(val dummy: Int) extends AnyVal: - def myKill(@consume x: Ref^): Unit = kill(x) // ok, too + def myKill(consume x: Ref^): Unit = kill(x) // ok, too class C3: def myKill(x: Ref^): Unit = kill(x) // error diff --git a/tests/neg-custom-args/captures/cc-this-shared.scala b/tests/neg-custom-args/captures/cc-this-shared.scala index 769bae9b5ee6..cd4b6f93af07 100644 --- a/tests/neg-custom-args/captures/cc-this-shared.scala +++ b/tests/neg-custom-args/captures/cc-this-shared.scala @@ -1,4 +1,4 @@ -import caps.consume + class Cap extends caps.SharedCapability @@ -19,7 +19,7 @@ def test(using Cap) = class C4(val f: () => Int) extends C3 // error // The following is a variation of pos/cc-this.scala -def test2(using @consume cc: Cap) = +def test2(using consume cc: Cap) = class C(val x: () => Int): val y: C^ = this diff --git a/tests/neg-custom-args/captures/cc-this.scala b/tests/neg-custom-args/captures/cc-this.scala index d90c0084a14f..3d9f8ca5cf53 100644 --- a/tests/neg-custom-args/captures/cc-this.scala +++ b/tests/neg-custom-args/captures/cc-this.scala @@ -1,4 +1,4 @@ -import caps.consume + class Cap extends caps.ExclusiveCapability @@ -19,7 +19,7 @@ def test(using Cap) = class C4(val f: () => Int) extends C3 // error // The following is a variation of pos/cc-this.scala -def test2(using @consume cc: Cap) = +def test2(using consume cc: Cap) = class C(val x: () => Int): val y: C^ = this diff --git a/tests/neg-custom-args/captures/consume-overrides.scala b/tests/neg-custom-args/captures/consume-overrides.scala index 78f013349a31..f3cd371b0cd7 100644 --- a/tests/neg-custom-args/captures/consume-overrides.scala +++ b/tests/neg-custom-args/captures/consume-overrides.scala @@ -1,15 +1,15 @@ -import caps.consume + trait A[X]: - def foo(@consume x: X): X + def foo(consume x: X): X def bar(x: X): X trait B extends A[C]: def foo(x: C): C // error - def bar(@consume x: C): C // error + def bar(consume x: C): C // error trait B2: def foo(x: C): C - def bar(@consume x: C): C + def bar(consume x: C): C abstract class C extends A[C], B2 // error diff --git a/tests/neg-custom-args/captures/delayedRunops.check b/tests/neg-custom-args/captures/delayedRunops.check index df578f4c6146..05d02af6b5a7 100644 --- a/tests/neg-custom-args/captures/delayedRunops.check +++ b/tests/neg-custom-args/captures/delayedRunops.check @@ -1,31 +1,31 @@ --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/delayedRunops.scala:14:4 --------------------------------- -14 | () => // error +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/delayedRunops.scala:12:4 --------------------------------- +12 | () => // error | ^ | Found: () ->{ops*} Unit | Required: () -> Unit | | Note that capability ops* is not included in capture set {}. -15 | val ops1 = ops -16 | runOps(ops1) +13 | val ops1 = ops +14 | runOps(ops1) | | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/delayedRunops.scala:26:4 --------------------------------- -26 | () => // error +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/delayedRunops.scala:24:4 --------------------------------- +24 | () => // error | ^ | Found: () ->{ops*} Unit | Required: () -> Unit | | Note that capability ops* is not included in capture set {}. -27 | val ops1: List[() ->{ops*} Unit] = ops -28 | runOps(ops1) +25 | val ops1: List[() ->{ops*} Unit] = ops +26 | runOps(ops1) | | longer explanation available when compiling with `-explain` --- Error: tests/neg-custom-args/captures/delayedRunops.scala:22:6 ------------------------------------------------------ -22 | runOps(ops1) // error +-- Error: tests/neg-custom-args/captures/delayedRunops.scala:20:6 ------------------------------------------------------ +20 | runOps(ops1) // error | ^^^^^^ | Local reach capability ops* leaks into capture scope of method delayedRunOps2. | You could try to abstract the capabilities referred to by ops* in a capset variable. --- Error: tests/neg-custom-args/captures/delayedRunops.scala:21:16 ----------------------------------------------------- -21 | val ops1: List[() => Unit] = ops // error +-- Error: tests/neg-custom-args/captures/delayedRunops.scala:19:16 ----------------------------------------------------- +19 | val ops1: List[() => Unit] = ops // error | ^^^^^^^^^^^^^^^^ | Separation failure: value ops1's type List[() => Unit] hides non-local parameter ops diff --git a/tests/neg-custom-args/captures/delayedRunops.scala b/tests/neg-custom-args/captures/delayedRunops.scala index a9ad226d4da1..4b89a14537c5 100644 --- a/tests/neg-custom-args/captures/delayedRunops.scala +++ b/tests/neg-custom-args/captures/delayedRunops.scala @@ -1,7 +1,5 @@ import language.experimental.captureChecking -import caps.{use, consume} - def runOps[C^](ops: List[() ->{C} Unit]): Unit = ops.foreach(op => op()) @@ -16,7 +14,7 @@ import caps.{use, consume} runOps(ops1) // unsound: impure operation pretended pure - def delayedRunOps2(@consume ops: List[() => Unit]): () ->{} Unit = + def delayedRunOps2(consume ops: List[() => Unit]): () ->{} Unit = () => val ops1: List[() => Unit] = ops // error runOps(ops1) // error diff --git a/tests/neg-custom-args/captures/i19330.check b/tests/neg-custom-args/captures/i19330.check index e142d6dbddb0..72947fb1bcf6 100644 --- a/tests/neg-custom-args/captures/i19330.check +++ b/tests/neg-custom-args/captures/i19330.check @@ -31,4 +31,4 @@ 16 | val t: () => Logger^ = () => l // error | ^^^^^^^^^^^^^ | Separation failure: value t's type () => Logger^ hides parameter l. - | The parameter needs to be annotated with @consume to allow this. + | The parameter needs to be annotated with consume to allow this. diff --git a/tests/neg-custom-args/captures/i21442.check b/tests/neg-custom-args/captures/i21442.check index aa0a8e53ff7f..0f64325fad98 100644 --- a/tests/neg-custom-args/captures/i21442.check +++ b/tests/neg-custom-args/captures/i21442.check @@ -12,4 +12,4 @@ 17 | val x1: Boxed[IO^] = x // error | ^^^^^^^^^^ | Separation failure: value x1's type Boxed[IO^] hides parameter x. - | The parameter needs to be annotated with @consume to allow this. + | The parameter needs to be annotated with consume to allow this. diff --git a/tests/neg-custom-args/captures/i22005.scala b/tests/neg-custom-args/captures/i22005.scala index fc7d4183f8c3..8bf17661fc82 100644 --- a/tests/neg-custom-args/captures/i22005.scala +++ b/tests/neg-custom-args/captures/i22005.scala @@ -7,4 +7,4 @@ class File(io: IO^) class Handler[C^]: def f(file: File^): File^{C} = file // error - def g(@consume file: File^{C}): File^ = file // ok + def g(consume file: File^{C}): File^ = file // ok diff --git a/tests/neg-custom-args/captures/i23431.check b/tests/neg-custom-args/captures/i23431.check index 624a3b63cb09..722fd6d6e313 100644 --- a/tests/neg-custom-args/captures/i23431.check +++ b/tests/neg-custom-args/captures/i23431.check @@ -48,4 +48,4 @@ 6 | var myIO: IO^ = io1 // error: separation | ^^^ | Separation failure: variable myIO's type IO^ hides parameter io1. - | The parameter needs to be annotated with @consume to allow this. + | The parameter needs to be annotated with consume to allow this. diff --git a/tests/neg-custom-args/captures/linear-buffer-2.check b/tests/neg-custom-args/captures/linear-buffer-2.check index be8f0623418e..6d9493bddb02 100644 --- a/tests/neg-custom-args/captures/linear-buffer-2.check +++ b/tests/neg-custom-args/captures/linear-buffer-2.check @@ -10,7 +10,7 @@ 20 | val buf3 = buf1.append(4) // error | ^^^^ | Separation failure: Illegal access to (buf1 : Buffer[Int]^), which was passed to a - | @consume parameter or was used as a prefix to a @consume method on line 18 + | consume parameter or was used as a prefix to a consume method on line 18 | and therefore is no longer available. | | where: ^ refers to a fresh root capability classified as Mutable in the type of value buf1 @@ -18,7 +18,7 @@ 28 | val buf3 = buf1.append(4) // error | ^^^^ | Separation failure: Illegal access to (buf1 : Buffer[Int]^), which was passed to a - | @consume parameter or was used as a prefix to a @consume method on line 25 + | consume parameter or was used as a prefix to a consume method on line 25 | and therefore is no longer available. | | where: ^ refers to a fresh root capability classified as Mutable in the type of value buf1 @@ -26,7 +26,7 @@ 38 | val buf3 = buf1.append(4) // error | ^^^^ | Separation failure: Illegal access to (buf1 : Buffer[Int]^), which was passed to a - | @consume parameter or was used as a prefix to a @consume method on line 33 + | consume parameter or was used as a prefix to a consume method on line 33 | and therefore is no longer available. | | where: ^ refers to a fresh root capability classified as Mutable in the type of value buf1 @@ -34,6 +34,6 @@ 42 | buf.append(1) // error | ^^^ | Separation failure: (buf : Buffer[Int]^) appears in a loop, therefore it cannot - | be passed to a @consume parameter or be used as a prefix of a @consume method call. + | be passed to a consume parameter or be used as a prefix of a consume method call. | | where: ^ refers to a fresh root capability classified as Mutable in the type of parameter buf diff --git a/tests/neg-custom-args/captures/linear-buffer-2.scala b/tests/neg-custom-args/captures/linear-buffer-2.scala index 78561351838b..d2ed1544fadf 100644 --- a/tests/neg-custom-args/captures/linear-buffer-2.scala +++ b/tests/neg-custom-args/captures/linear-buffer-2.scala @@ -1,25 +1,25 @@ -import caps.{cap, consume, Mutable} +import caps.{cap, Mutable} import language.experimental.captureChecking class Buffer[T] extends Mutable: - @consume update def append(x: T): Buffer[T]^ = this // ok + consume def append(x: T): Buffer[T]^ = this // ok -def app[T](@consume buf: Buffer[T]^, elem: T): Buffer[T]^ = +def app[T](consume buf: Buffer[T]^, elem: T): Buffer[T]^ = buf.append(elem) -def Test(@consume buf: Buffer[Int]^) = +def Test(consume buf: Buffer[Int]^) = val buf1: Buffer[Int]^ = buf.append(1) val buf2 = buf1.append(2) // OK val buf3 = buf.append(3) // error -def Test2(@consume buf: Buffer[Int]^) = +def Test2(consume buf: Buffer[Int]^) = val buf1: Buffer[Int]^ = buf.append(1) val buf2 = if ??? then buf1.append(2) // OK else buf1.append(3) // OK val buf3 = buf1.append(4) // error -def Test3(@consume buf: Buffer[Int]^) = +def Test3(consume buf: Buffer[Int]^) = val buf1: Buffer[Int]^ = buf.append(1) val buf2 = (??? : Int) match case 1 => buf1.append(2) // OK @@ -27,7 +27,7 @@ def Test3(@consume buf: Buffer[Int]^) = case _ => buf1.append(3) val buf3 = buf1.append(4) // error -def Test4(@consume buf: Buffer[Int]^) = +def Test4(consume buf: Buffer[Int]^) = val buf1: Buffer[Int]^ = buf.append(1) val buf2 = (??? : Int) match case 1 => buf1.append(2) // OK @@ -37,6 +37,6 @@ def Test4(@consume buf: Buffer[Int]^) = case 5 => buf1.append(5) val buf3 = buf1.append(4) // error -def Test5(@consume buf: Buffer[Int]^) = +def Test5(consume buf: Buffer[Int]^) = while true do buf.append(1) // error diff --git a/tests/neg-custom-args/captures/linear-buffer.check b/tests/neg-custom-args/captures/linear-buffer.check index 4aaf1794aac1..78ddb54b5535 100644 --- a/tests/neg-custom-args/captures/linear-buffer.check +++ b/tests/neg-custom-args/captures/linear-buffer.check @@ -2,17 +2,17 @@ 5 | update def append(x: T): BadBuffer[T]^ = this // error | ^^^^^^^^^^^^^ | Separation failure: method append's result type BadBuffer[T]^ hides non-local this of class class BadBuffer. - | The access must be in a @consume method to allow this. + | The access must be in a consume method to allow this. -- Error: tests/neg-custom-args/captures/linear-buffer.scala:7:13 ------------------------------------------------------ 7 | def bar: BadBuffer[T]^ = this // error | ^^^^^^^^^^^^^ | Separation failure: method bar's result type BadBuffer[T]^ hides non-local this of class class BadBuffer. - | The access must be in a @consume method to allow this. + | The access must be in a consume method to allow this. -- Error: tests/neg-custom-args/captures/linear-buffer.scala:19:17 ----------------------------------------------------- 19 | val buf3 = app(buf, 3) // error | ^^^ | Separation failure: Illegal access to (buf : Buffer[Int]^), which was passed to a - | @consume parameter or was used as a prefix to a @consume method on line 17 + | consume parameter or was used as a prefix to a consume method on line 17 | and therefore is no longer available. | | where: ^ refers to a fresh root capability classified as Mutable in the type of parameter buf @@ -20,7 +20,7 @@ 26 | val buf3 = app(buf1, 4) // error | ^^^^ | Separation failure: Illegal access to (buf1 : Buffer[Int]^), which was passed to a - | @consume parameter or was used as a prefix to a @consume method on line 24 + | consume parameter or was used as a prefix to a consume method on line 24 | and therefore is no longer available. | | where: ^ refers to a fresh root capability classified as Mutable in the type of value buf1 @@ -28,7 +28,7 @@ 34 | val buf3 = app(buf1, 4) // error | ^^^^ | Separation failure: Illegal access to (buf1 : Buffer[Int]^), which was passed to a - | @consume parameter or was used as a prefix to a @consume method on line 31 + | consume parameter or was used as a prefix to a consume method on line 31 | and therefore is no longer available. | | where: ^ refers to a fresh root capability classified as Mutable in the type of value buf1 @@ -36,7 +36,7 @@ 44 | val buf3 = app(buf1, 4) // error | ^^^^ | Separation failure: Illegal access to (buf1 : Buffer[Int]^), which was passed to a - | @consume parameter or was used as a prefix to a @consume method on line 39 + | consume parameter or was used as a prefix to a consume method on line 39 | and therefore is no longer available. | | where: ^ refers to a fresh root capability classified as Mutable in the type of value buf1 @@ -44,6 +44,6 @@ 48 | app(buf, 1) // error | ^^^ | Separation failure: (buf : Buffer[Int]^) appears in a loop, therefore it cannot - | be passed to a @consume parameter or be used as a prefix of a @consume method call. + | be passed to a consume parameter or be used as a prefix of a consume method call. | | where: ^ refers to a fresh root capability classified as Mutable in the type of parameter buf diff --git a/tests/neg-custom-args/captures/linear-buffer.scala b/tests/neg-custom-args/captures/linear-buffer.scala index 5c6d991579aa..27e2efeeed1f 100644 --- a/tests/neg-custom-args/captures/linear-buffer.scala +++ b/tests/neg-custom-args/captures/linear-buffer.scala @@ -1,4 +1,4 @@ -import caps.{cap, consume, Mutable} +import caps.{cap, Mutable} import language.experimental.captureChecking class BadBuffer[T] extends Mutable: @@ -8,24 +8,24 @@ class BadBuffer[T] extends Mutable: bar class Buffer[T] extends Mutable: - @consume update def append(x: T): Buffer[T]^ = this // ok + consume def append(x: T): Buffer[T]^ = this // ok -def app[T](@consume buf: Buffer[T]^, elem: T): Buffer[T]^ = +def app[T](consume buf: Buffer[T]^, elem: T): Buffer[T]^ = buf.append(elem) -def Test(@consume buf: Buffer[Int]^) = +def Test(consume buf: Buffer[Int]^) = val buf1: Buffer[Int]^ = app(buf, 1) val buf2 = app(buf1, 2) // OK val buf3 = app(buf, 3) // error -def Test2(@consume buf: Buffer[Int]^) = +def Test2(consume buf: Buffer[Int]^) = val buf1: Buffer[Int]^ = app(buf, 1) val buf2 = if ??? then app(buf1, 2) // OK else app(buf1, 3) // OK val buf3 = app(buf1, 4) // error -def Test3(@consume buf: Buffer[Int]^) = +def Test3(consume buf: Buffer[Int]^) = val buf1: Buffer[Int]^ = app(buf, 1) val buf2 = (??? : Int) match case 1 => app(buf1, 2) // OK @@ -33,7 +33,7 @@ def Test3(@consume buf: Buffer[Int]^) = case _ => app(buf1, 3) val buf3 = app(buf1, 4) // error -def Test4(@consume buf: Buffer[Int]^) = +def Test4(consume buf: Buffer[Int]^) = val buf1: Buffer[Int]^ = app(buf, 1) val buf2 = (??? : Int) match case 1 => app(buf1, 2) // OK @@ -43,6 +43,6 @@ def Test4(@consume buf: Buffer[Int]^) = case 5 => app(buf1, 5) val buf3 = app(buf1, 4) // error -def Test5(@consume buf: Buffer[Int]^) = +def Test5(consume buf: Buffer[Int]^) = while true do app(buf, 1) // error diff --git a/tests/neg-custom-args/captures/localReaches.check b/tests/neg-custom-args/captures/localReaches.check index 1df4886e61d5..5acb12457323 100644 --- a/tests/neg-custom-args/captures/localReaches.check +++ b/tests/neg-custom-args/captures/localReaches.check @@ -12,9 +12,9 @@ 14 | val xs: List[() => Unit] = op :: Nil // error | ^^^^^^^^^^^^^^^^ | Separation failure: value xs's type List[() => Unit] hides parameter op. - | The parameter needs to be annotated with @consume to allow this. + | The parameter needs to be annotated with consume to allow this. -- Error: tests/neg-custom-args/captures/localReaches.scala:22:10 ------------------------------------------------------ 22 | val xs: List[() => Unit] = ops // error | ^^^^^^^^^^^^^^^^ | Separation failure: value xs's type List[() => Unit] hides parameter ops. - | The parameter needs to be annotated with @consume to allow this. + | The parameter needs to be annotated with consume to allow this. diff --git a/tests/neg-custom-args/captures/localReaches.scala b/tests/neg-custom-args/captures/localReaches.scala index 9795a6dd099c..d88e3f4bb992 100644 --- a/tests/neg-custom-args/captures/localReaches.scala +++ b/tests/neg-custom-args/captures/localReaches.scala @@ -1,6 +1,6 @@ import language.experimental.captureChecking // no separation checking -import caps.consume + def localReach() = val xs: List[() => Unit] = ??? diff --git a/tests/neg-custom-args/captures/non-local-consume.scala b/tests/neg-custom-args/captures/non-local-consume.scala index 0e46146eb7e8..81915a54246f 100644 --- a/tests/neg-custom-args/captures/non-local-consume.scala +++ b/tests/neg-custom-args/captures/non-local-consume.scala @@ -1,29 +1,29 @@ -import caps.{cap, consume, Mutable} +import caps.{cap, Mutable} import language.experimental.captureChecking class Buffer extends Mutable -def f1(@consume buf: Buffer^): Buffer^ = +def f1(consume buf: Buffer^): Buffer^ = val buf1: Buffer^ = buf // OK buf1 -def f2(@consume buf: Buffer^): Buffer^ = +def f2(consume buf: Buffer^): Buffer^ = def g(): Buffer^ = buf // error g() -def f3(@consume buf: Buffer^): Buffer^ = +def f3(consume buf: Buffer^): Buffer^ = val buf1 = buf def g(): Buffer^ = buf1 // error g() -def f4(@consume buf: Buffer^): Buffer^ = +def f4(consume buf: Buffer^): Buffer^ = val buf1: Buffer^ = buf def g(): Buffer^ = buf1 // error g() -def f5(@consume buf: Buffer^): Unit = +def f5(consume buf: Buffer^): Unit = val buf1: Buffer^ = buf def g(): Unit = cc(buf1) // error g() -def cc(@consume buf: Buffer^): Unit = () +def cc(consume buf: Buffer^): Unit = () diff --git a/tests/neg-custom-args/captures/reaches.check b/tests/neg-custom-args/captures/reaches.check index bcfbc33c8a9f..cb88ef40431e 100644 --- a/tests/neg-custom-args/captures/reaches.check +++ b/tests/neg-custom-args/captures/reaches.check @@ -1,5 +1,5 @@ --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reaches.scala:22:13 -------------------------------------- -22 | usingFile: f => // error +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reaches.scala:20:13 -------------------------------------- +20 | usingFile: f => // error | ^ |Found: (f: File^'s1) ->'s2 Unit |Required: File^ => Unit @@ -12,8 +12,8 @@ 23 | cur = (() => f.write()) :: Nil | | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reaches.scala:32:13 -------------------------------------- -32 | usingFile: f => // error +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reaches.scala:30:13 -------------------------------------- +30 | usingFile: f => // error | ^ |Found: (f: File^'s3) ->'s4 Unit |Required: File^ => Unit @@ -27,15 +27,15 @@ 34 | (() => f.write()) :: Nil | | longer explanation available when compiling with `-explain` --- Error: tests/neg-custom-args/captures/reaches.scala:44:16 ----------------------------------------------------------- -44 | val cur = Ref[List[Proc]](xs) // error +-- Error: tests/neg-custom-args/captures/reaches.scala:42:16 ----------------------------------------------------------- +42 | val cur = Ref[List[Proc]](xs) // error | ^^^^^^^^^^ | Type variable T of constructor Ref cannot be instantiated to List[() => Unit] since | the part () => Unit of that type captures the root capability `cap`. | | where: => refers to the universal root capability --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reaches.scala:46:35 -------------------------------------- -46 | val next: () => Unit = cur.get.head // error +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reaches.scala:44:35 -------------------------------------- +44 | val next: () => Unit = cur.get.head // error | ^^^^^^^^^^^^ | Found: () => Unit | Required: () =>² Unit @@ -49,8 +49,8 @@ | cap² is a fresh root capability in the type of value next | | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reaches.scala:48:20 -------------------------------------- -48 | cur.set(cur.get.tail: List[Proc]) // error +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reaches.scala:46:20 -------------------------------------- +46 | cur.set(cur.get.tail: List[Proc]) // error | ^^^^^^^^^^^^ | Found: List[() => Unit] | Required: List[() =>² Unit] @@ -64,15 +64,15 @@ | cap² is a fresh root capability created in method runAll3 | | longer explanation available when compiling with `-explain` --- Error: tests/neg-custom-args/captures/reaches.scala:54:51 ----------------------------------------------------------- -54 | val id: Id[Proc, Proc] = new Id[Proc, () -> Unit] // error +-- Error: tests/neg-custom-args/captures/reaches.scala:52:51 ----------------------------------------------------------- +52 | val id: Id[Proc, Proc] = new Id[Proc, () -> Unit] // error | ^ | Type variable A of constructor Id cannot be instantiated to () => Unit since | that type captures the root capability `cap`. | | where: => refers to the universal root capability --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reaches.scala:59:27 -------------------------------------- -59 | val id: File^ -> File^ = x => x // error +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reaches.scala:57:27 -------------------------------------- +57 | val id: File^ -> File^ = x => x // error | ^^^^^^ | Found: (x: File^) ->'s5 File^² | Required: File^ -> File^³ @@ -87,8 +87,8 @@ | cap² is a fresh root capability in the type of value id | | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reaches.scala:69:38 -------------------------------------- -69 | val leaked = usingFile[File^{id*}]: f => // error +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reaches.scala:67:38 -------------------------------------- +67 | val leaked = usingFile[File^{id*}]: f => // error | ^ |Found: (f: File^'s6) ->'s7 File^{id*} |Required: File^ => File^{id*} @@ -102,8 +102,8 @@ 71 | f1 | | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reaches.scala:87:10 -------------------------------------- -87 | ps.map((x, y) => compose1(x, y)) // error +-- [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^ ->'s8 A^'s9, A^ ->'s10 A^'s11)^'s12) ->'s13 A^'s14 ->'s15 A^'s16 |Required: ((A ->{ps*} A, A ->{ps*} A)) => A^'s17 ->'s18 A^'s19 @@ -114,8 +114,8 @@ | ^ refers to the universal root capability | | longer explanation available when compiling with `-explain` --- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reaches.scala:90:10 -------------------------------------- -90 | ps.map((x, y) => compose1(x, y)) // error +-- [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^ ->'s20 A^'s21, A^ ->'s22 A^'s23)^'s24) ->'s25 A^'s26 ->'s27 A^'s28 |Required: ((A ->{C} A, A ->{C} A)) => A^'s29 ->'s30 A^'s31 @@ -126,16 +126,16 @@ | ^ refers to the universal root capability | | longer explanation available when compiling with `-explain` --- Error: tests/neg-custom-args/captures/reaches.scala:39:31 ----------------------------------------------------------- -39 | val next: () => Unit = cur.head // error, use +-- Error: tests/neg-custom-args/captures/reaches.scala:37:31 ----------------------------------------------------------- +37 | val next: () => Unit = cur.head // error, use | ^^^^^^^^ | Local reach capability xs* leaks into capture scope of method runAll2. | You could try to abstract the capabilities referred to by xs* in a capset variable. --- Error: tests/neg-custom-args/captures/reaches.scala:62:36 ----------------------------------------------------------- -62 | val leaked = usingFile[File^{id*}]: f => // error: separation +-- Error: tests/neg-custom-args/captures/reaches.scala:60:36 ----------------------------------------------------------- +60 | val leaked = usingFile[File^{id*}]: f => // error: separation | ^ | Local cap created in type of parameter x leaks into capture scope of enclosing function | | where: cap is a fresh root capability created in value id of parameter parameter x of method $anonfun -63 | val f1: File^{id*} = id(f) -64 | f1 +61 | val f1: File^{id*} = id(f) +62 | f1 diff --git a/tests/neg-custom-args/captures/reaches.scala b/tests/neg-custom-args/captures/reaches.scala index d59bc4a7994b..daf326f17f98 100644 --- a/tests/neg-custom-args/captures/reaches.scala +++ b/tests/neg-custom-args/captures/reaches.scala @@ -1,5 +1,3 @@ -import caps.use; -import caps.consume class File: def write(): Unit = ??? @@ -33,7 +31,7 @@ def runAll1[C^](xs: List[() ->{C} Unit]): Unit = cur.set: (() => f.write()) :: Nil -def runAll2(@consume xs: List[Proc]): Unit = +def runAll2(consume xs: List[Proc]): Unit = var cur: List[Proc] = xs while cur.nonEmpty do val next: () => Unit = cur.head // error, use diff --git a/tests/neg-custom-args/captures/sep-consume.check b/tests/neg-custom-args/captures/sep-consume.check index e44d3235afd6..5967947783ae 100644 --- a/tests/neg-custom-args/captures/sep-consume.check +++ b/tests/neg-custom-args/captures/sep-consume.check @@ -2,7 +2,7 @@ 19 | x.put(42) // error | ^ | Separation failure: Illegal access to (x : Ref^), which was passed to a - | @consume parameter or was used as a prefix to a @consume method on line 18 + | consume parameter or was used as a prefix to a consume method on line 18 | and therefore is no longer available. | | where: ^ refers to a fresh root capability classified as Mutable in the type of parameter x @@ -10,11 +10,11 @@ 21 | par(rx, () => x.put(42)) // error | ^ | Separation failure: Illegal access to (x : Ref^), which was passed to a - | @consume parameter or was used as a prefix to a @consume method on line 18 + | consume parameter or was used as a prefix to a consume method on line 18 | and therefore is no longer available. | | where: ^ refers to a fresh root capability classified as Mutable in the type of parameter x -- Error: tests/neg-custom-args/captures/sep-consume.scala:26:16 ------------------------------------------------------- 26 | def foo = bad(f) // error | ^ - | Separation failure: argument to @consume parameter with type (f : () ->{x.rd} Unit) refers to non-local value f + | Separation failure: argument to consume parameter with type (f : () ->{x.rd} Unit) refers to non-local value f diff --git a/tests/neg-custom-args/captures/sep-consume.scala b/tests/neg-custom-args/captures/sep-consume.scala index 7b633b814e88..3caa2713dd95 100644 --- a/tests/neg-custom-args/captures/sep-consume.scala +++ b/tests/neg-custom-args/captures/sep-consume.scala @@ -11,9 +11,9 @@ case class Pair[+A, +B](fst: A, snd: B) // require f and g to be non-interfering def par(f: () => Unit, g: () => Unit): Unit = ??? -def bad(@consume op: () ->{cap.rd} Unit): () => Unit = op +def bad(consume op: () ->{cap.rd} Unit): () => Unit = op -def test2(@consume x: Ref^): Unit = +def test2(consume x: Ref^): Unit = val f: () ->{x.rd} Unit = () => x.get val rx: () => Unit = bad(f) // hides x.rd in the resulting `cap` x.put(42) // error @@ -21,22 +21,22 @@ def test2(@consume x: Ref^): Unit = par(rx, () => x.put(42)) // error par(rx, () => x.get) // ok rd/rd -def test3(@consume x: Ref^): Unit = +def test3(consume x: Ref^): Unit = val f: () ->{x.rd} Unit = () => x.get def foo = bad(f) // error foo() foo() /* -def test4(@consume @use p: Pair[Ref^, Ref^]): Unit = +def test4(consume @use p: Pair[Ref^, Ref^]): Unit = val x: Ref^{p.fst*} = p.fst val y: Ref^{p.snd*} = p.snd badp(Pair(x, y)) println(p.fst.get) -def badp(@consume p: Pair[Ref^, Ref^]): Unit = () +def badp(consume p: Pair[Ref^, Ref^]): Unit = () -def test5(@consume @use p: Pair[Ref^, Ref^]): Unit = +def test5(consume @use p: Pair[Ref^, Ref^]): Unit = badp(p) // ok println(p.fst.get) */ diff --git a/tests/neg-custom-args/captures/sep-lazyListState.check b/tests/neg-custom-args/captures/sep-lazyListState.check index 519d143983bf..5806f9082d58 100644 --- a/tests/neg-custom-args/captures/sep-lazyListState.check +++ b/tests/neg-custom-args/captures/sep-lazyListState.check @@ -2,4 +2,4 @@ 13 | def tail: LazyListIterable[A]^ = tl // error | ^^^^^^^^^^^^^^^^^^^^ | Separation failure: method tail's result type LazyListIterable[A]^ hides non-local this of class class Cons. - | The access must be in a @consume method to allow this. + | The access must be in a consume method to allow this. diff --git a/tests/neg-custom-args/captures/sep-use.scala b/tests/neg-custom-args/captures/sep-use.scala index e89adb0f060e..ec661b15fedc 100644 --- a/tests/neg-custom-args/captures/sep-use.scala +++ b/tests/neg-custom-args/captures/sep-use.scala @@ -1,25 +1,25 @@ -import caps.{cap, consume} +import caps.cap -def test1(@consume io: Object^): Unit = +def test1(consume io: Object^): Unit = val x: () => Unit = () => println(io) println(io) // error println(x) // ok -def test2(@consume io: Object^): Unit = +def test2(consume io: Object^): Unit = def x: () => Unit = () => println(io) // error println(io) // error println(x) // ok -def test3(@consume io: Object^): Unit = +def test3(consume io: Object^): Unit = def xx: (y: Int) => Unit = _ => println(io) // error println(io) // error println(xx(2)) // ok -def test4(@consume io: Object^): Unit = +def test4(consume io: Object^): Unit = def xxx(y: Int): Object^ = io // error println(io) // error diff --git a/tests/neg-custom-args/captures/sep-use2.check b/tests/neg-custom-args/captures/sep-use2.check index d6379639f0bc..8fec0e3bdd83 100644 --- a/tests/neg-custom-args/captures/sep-use2.check +++ b/tests/neg-custom-args/captures/sep-use2.check @@ -14,7 +14,7 @@ 12 | val x4: Object^ = // error | ^^^^^^^ | Separation failure: value x4's type Object^ hides parameter f. - | The parameter needs to be annotated with @consume to allow this. + | The parameter needs to be annotated with consume to allow this. -- Error: tests/neg-custom-args/captures/sep-use2.scala:16:10 ---------------------------------------------------------- 16 | def cc: Object^ = c // error | ^^^^^^^ @@ -61,10 +61,10 @@ |where: ^ refers to a fresh root capability in the type of parameter c | ^² refers to a fresh root capability created in value x4 when checking argument to parameter v1 of method apply -- Error: tests/neg-custom-args/captures/sep-use2.scala:23:10 ---------------------------------------------------------- -23 | val x4: Object^ = // error: ^ hides f*, needs @consume +23 | val x4: Object^ = // error: ^ hides f*, needs consume | ^^^^^^^ | Separation failure: value x4's type Object^ hides parameter f. - | The parameter needs to be annotated with @consume to allow this. + | The parameter needs to be annotated with consume to allow this. -- Error: tests/neg-custom-args/captures/sep-use2.scala:28:8 ----------------------------------------------------------- 28 | { f(c) } // error | ^ @@ -83,7 +83,7 @@ |where: ^ refers to a fresh root capability in the type of parameter c | ^² refers to a fresh root capability created in value x4 when checking argument to parameter v1 of method apply -- Error: tests/neg-custom-args/captures/sep-use2.scala:27:10 ---------------------------------------------------------- -27 | val x4: Object^ = // error: ^ hides f* which refers to c, so c needs @consume +27 | val x4: Object^ = // error: ^ hides f* which refers to c, so c needs consume | ^^^^^^^ | Separation failure: value x4's type Object^ hides parameter c. - | The parameter needs to be annotated with @consume to allow this. + | The parameter needs to be annotated with consume to allow this. diff --git a/tests/neg-custom-args/captures/sep-use2.scala b/tests/neg-custom-args/captures/sep-use2.scala index 088984fab0c6..9ce8a1ade72f 100644 --- a/tests/neg-custom-args/captures/sep-use2.scala +++ b/tests/neg-custom-args/captures/sep-use2.scala @@ -1,7 +1,7 @@ -import caps.consume -def test1(@consume c: Object^, f: (x: Object^) => Object^) = + +def test1(consume c: Object^, f: (x: Object^) => Object^) = def cc: Object^ = c // error val x1 = { f(cc) } // ok @@ -12,19 +12,19 @@ def test1(@consume c: Object^, f: (x: Object^) => Object^) = val x4: Object^ = // error { f(c) } // error -def test2(@consume c: Object^, f: (x: Object^) ->{c} Object^) = +def test2(consume c: Object^, f: (x: Object^) ->{c} Object^) = def cc: Object^ = c // error val x1 = { f(cc) } // error val x4: Object^ = // ^ hides just c, since the Object^ in the result of `f` is existential { f(c) } // error // error -def test3(@consume c: Object^, f: Object^ ->{c} Object^) = - val x4: Object^ = // error: ^ hides f*, needs @consume +def test3(consume c: Object^, f: Object^ ->{c} Object^) = + val x4: Object^ = // error: ^ hides f*, needs consume { f(c) } // error -def test4(c: Object^, @consume f: Object^ ->{c} Object^) = - val x4: Object^ = // error: ^ hides f* which refers to c, so c needs @consume +def test4(c: Object^, consume f: Object^ ->{c} Object^) = + val x4: Object^ = // error: ^ hides f* which refers to c, so c needs consume { f(c) } // error diff --git a/tests/neg-custom-args/captures/sepchecks2.check b/tests/neg-custom-args/captures/sepchecks2.check index e7b8dcbf038f..5529c2a6d1c8 100644 --- a/tests/neg-custom-args/captures/sepchecks2.check +++ b/tests/neg-custom-args/captures/sepchecks2.check @@ -55,9 +55,9 @@ 30 | val x: (Object^, Object^{c}) = (d, c) // error | ^^^^^^^^^^^^^^^^^^^^^ | Separation failure: value x's type (Object^, Object^{c}) hides parameter d. - | The parameter needs to be annotated with @consume to allow this. + | The parameter needs to be annotated with consume to allow this. -- Error: tests/neg-custom-args/captures/sepchecks2.scala:33:9 --------------------------------------------------------- 33 | val x: (Object^, Object^) = (c, d) // error | ^^^^^^^^^^^^^^^^^^ | Separation failure: value x's type (Object^, Object^) hides parameters c and d. - | The parameters need to be annotated with @consume to allow this. + | The parameters need to be annotated with consume to allow this. diff --git a/tests/neg-custom-args/captures/sepchecks2.scala b/tests/neg-custom-args/captures/sepchecks2.scala index 34ca3a56a243..d3310fc8b3ca 100644 --- a/tests/neg-custom-args/captures/sepchecks2.scala +++ b/tests/neg-custom-args/captures/sepchecks2.scala @@ -1,11 +1,11 @@ -import caps.consume + def foo(xs: List[() => Unit], y: Object^) = ??? def bar(x: (Object^, Object^)): Unit = ??? -def Test(@consume c: Object^) = +def Test(consume c: Object^) = val xs: List[() => Unit] = (() => println(c)) :: Nil println(c) // error @@ -14,10 +14,10 @@ def Test2(c: Object^, d: Object^): Unit = val x1: (Object^, Object^) = (c, c) // error val x2: (Object^, Object^{d}) = (d, d) // error -def Test3(@consume c: Object^, @consume d: Object^) = +def Test3(consume c: Object^, consume d: Object^) = val x: (Object^, Object^) = (c, d) // ok -def Test4(@consume c: Object^, @consume d: Object^) = +def Test4(consume c: Object^, consume d: Object^) = val x: (Object^, Object^{c}) = (d, c) // ok def Test5(c: Object^, d: Object^): Unit = diff --git a/tests/neg-custom-args/captures/sepchecks3.scala b/tests/neg-custom-args/captures/sepchecks3.scala index 8cc7d705b42b..abfd263b0ff8 100644 --- a/tests/neg-custom-args/captures/sepchecks3.scala +++ b/tests/neg-custom-args/captures/sepchecks3.scala @@ -1,5 +1,5 @@ -import caps.consume + def foo(xs: List[() => Unit], y: Object^) = ??? @@ -7,6 +7,6 @@ def bar(x: (Object^, Object^)): Unit = ??? def Test(c: Object^): Object^ = c // error -def Test2(@consume c: Object^): Object^ = c // ok +def Test2(consume c: Object^): Object^ = c // ok def Test3(c: Object^): List[Object^] = c :: Nil // error diff --git a/tests/neg-custom-args/captures/sepchecks4.check b/tests/neg-custom-args/captures/sepchecks4.check index 7b587363d1cd..64ebb04239d5 100644 --- a/tests/neg-custom-args/captures/sepchecks4.check +++ b/tests/neg-custom-args/captures/sepchecks4.check @@ -2,12 +2,12 @@ 8 | val x: () => Unit = () => println(io) // error | ^^^^^^^^^^ | Separation failure: value x's type () => Unit hides parameter io. - | The parameter needs to be annotated with @consume to allow this. + | The parameter needs to be annotated with consume to allow this. -- Error: tests/neg-custom-args/captures/sepchecks4.scala:7:25 --------------------------------------------------------- 7 |def bad(io: Object^): () => Unit = // error | ^^^^^^^^^^ | Separation failure: method bad's result type () => Unit hides parameter io. - | The parameter needs to be annotated with @consume to allow this. + | The parameter needs to be annotated with consume to allow this. -- Error: tests/neg-custom-args/captures/sepchecks4.scala:12:6 --------------------------------------------------------- 12 | par(() => println(io))(() => println(io)) // error // (1) | ^^^^^^^^^^^^^^^^^ diff --git a/tests/neg-custom-args/captures/sepchecks4.scala b/tests/neg-custom-args/captures/sepchecks4.scala index d44b31ca02dc..285c5405e215 100644 --- a/tests/neg-custom-args/captures/sepchecks4.scala +++ b/tests/neg-custom-args/captures/sepchecks4.scala @@ -12,5 +12,5 @@ def test(io: Object^): Unit = par(() => println(io))(() => println(io)) // error // (1) val f = bad(io) - par(f)(() => println(io)) // no error, but it is equivalent to (1) and should failimport caps.consume + par(f)(() => println(io)) // no error, but it is equivalent to (1) and should fail diff --git a/tests/neg-custom-args/captures/sepchecks5.check b/tests/neg-custom-args/captures/sepchecks5.check index 63ec3bc73431..9f76ef21f46d 100644 --- a/tests/neg-custom-args/captures/sepchecks5.check +++ b/tests/neg-custom-args/captures/sepchecks5.check @@ -1,18 +1,18 @@ -- Error: tests/neg-custom-args/captures/sepchecks5.scala:12:37 -------------------------------------------------------- 12 |def bad(io: Object^): () => Unit = f(io) // error | ^^ - | Separation failure: argument to @consume parameter with type (io : Object^) refers to parameter io. - | The parameter needs to be annotated with @consume to allow this. + | Separation failure: argument to consume parameter with type (io : Object^) refers to parameter io. + | The parameter needs to be annotated with consume to allow this. -- Error: tests/neg-custom-args/captures/sepchecks5.scala:19:13 -------------------------------------------------------- 19 | val f2 = g(io) // error | ^^ - | Separation failure: argument to @consume parameter with type (io : Object^) refers to parameter io. - | The parameter needs to be annotated with @consume to allow this. + | Separation failure: argument to consume parameter with type (io : Object^) refers to parameter io. + | The parameter needs to be annotated with consume to allow this. -- Error: tests/neg-custom-args/captures/sepchecks5.scala:20:24 -------------------------------------------------------- 20 | par(f2)(() => println(io)) // error | ^^ | Separation failure: Illegal access to (io : Object^), which was passed to a - | @consume parameter or was used as a prefix to a @consume method on line 19 + | consume parameter or was used as a prefix to a consume method on line 19 | and therefore is no longer available. | | where: ^ refers to a fresh root capability in the type of parameter io diff --git a/tests/neg-custom-args/captures/sepchecks5.scala b/tests/neg-custom-args/captures/sepchecks5.scala index 9a16051e64bd..82b7f17108f0 100644 --- a/tests/neg-custom-args/captures/sepchecks5.scala +++ b/tests/neg-custom-args/captures/sepchecks5.scala @@ -1,13 +1,13 @@ -import caps.{cap, consume} +import caps.cap import language.future import language.experimental.captureChecking def par(op1: () => Unit)(op2: () => Unit): Unit = () -def f(@consume io: Object^): () => Unit = +def f(consume io: Object^): () => Unit = () => println(io) -def g(@consume io: Object^): () => Unit = f(io) // ok +def g(consume io: Object^): () => Unit = f(io) // ok def bad(io: Object^): () => Unit = f(io) // error diff --git a/tests/neg-custom-args/captures/unsound-reach-3.scala b/tests/neg-custom-args/captures/unsound-reach-3.scala index 46ed469a308e..56b39097b1ca 100644 --- a/tests/neg-custom-args/captures/unsound-reach-3.scala +++ b/tests/neg-custom-args/captures/unsound-reach-3.scala @@ -1,6 +1,6 @@ import language.experimental.captureChecking -import caps.consume + trait File: def close(): Unit @@ -8,9 +8,9 @@ trait File: def withFile[R](path: String)(op: File^ => R): R = ??? trait Foo[+X]: - def use(@consume x: File^): X + def use(consume x: File^): X class Bar extends Foo[File^]: // error - def use(@consume x: File^): File^ = x // error override + def use(consume x: File^): File^ = x // error override def bad(): Unit = val backdoor: Foo[File^] = new Bar // error (follow-on, since the parent Foo[File^] of bar is illegal). diff --git a/tests/neg-custom-args/captures/unsound-reach-4.check b/tests/neg-custom-args/captures/unsound-reach-4.check index 56e1a0cc1548..573e9a31d068 100644 --- a/tests/neg-custom-args/captures/unsound-reach-4.check +++ b/tests/neg-custom-args/captures/unsound-reach-4.check @@ -20,7 +20,7 @@ | | longer explanation available when compiling with `-explain` -- [E164] Declaration Error: tests/neg-custom-args/captures/unsound-reach-4.scala:17:6 --------------------------------- -17 | def use(@consume x: F): File^ = x // error @consume override +17 | def use(consume x: F): File^ = x // error consume override | ^ | error overriding method use in trait Foo of type (x: File^): File^; | method use of type (@consume x: File^): File^² has incompatible type diff --git a/tests/neg-custom-args/captures/unsound-reach-4.scala b/tests/neg-custom-args/captures/unsound-reach-4.scala index 715dfcc90ef8..c6ad721213c2 100644 --- a/tests/neg-custom-args/captures/unsound-reach-4.scala +++ b/tests/neg-custom-args/captures/unsound-reach-4.scala @@ -2,7 +2,7 @@ import language.experimental.captureChecking; -import caps.consume + trait File: def close(): Unit @@ -14,7 +14,7 @@ type F = File^ trait Foo[+X]: def use(x: F): X class Bar extends Foo[File^]: // error - def use(@consume x: F): File^ = x // error @consume override + def use(consume x: F): File^ = x // error consume override def bad(): Unit = val backdoor: Foo[File^] = new Bar // error (follow-on, since the parent Foo[File^] of bar is illegal). diff --git a/tests/neg-custom-args/captures/unsound-reach-6.check b/tests/neg-custom-args/captures/unsound-reach-6.check index c6f45c15e62b..b9e9b287dace 100644 --- a/tests/neg-custom-args/captures/unsound-reach-6.check +++ b/tests/neg-custom-args/captures/unsound-reach-6.check @@ -22,11 +22,11 @@ | Local reach capability xs* leaks into capture scope of method f. | You could try to abstract the capabilities referred to by xs* in a capset variable. -- Error: tests/neg-custom-args/captures/unsound-reach-6.scala:11:14 --------------------------------------------------- -11 | val z = f(ys) // error @consume failure +11 | val z = f(ys) // error consume failure | ^^ | Local reach capability ys* leaks into capture scope of method test. | You could try to abstract the capabilities referred to by ys* in a capset variable. -- Error: tests/neg-custom-args/captures/unsound-reach-6.scala:19:14 --------------------------------------------------- -19 | val z = f(ys) // error @consume failure +19 | val z = f(ys) // error consume failure | ^^ - |Separation failure: argument to @consume parameter with type List[() ->{io} Unit] refers to non-local parameter io + |Separation failure: argument to consume parameter with type List[() ->{io} Unit] refers to non-local parameter io diff --git a/tests/neg-custom-args/captures/unsound-reach-6.scala b/tests/neg-custom-args/captures/unsound-reach-6.scala index 4643950d78d0..0d737828d466 100644 --- a/tests/neg-custom-args/captures/unsound-reach-6.scala +++ b/tests/neg-custom-args/captures/unsound-reach-6.scala @@ -1,14 +1,14 @@ -import caps.consume + class IO -def f(@consume xs: List[() => Unit]): () => Unit = () => +def f(consume xs: List[() => Unit]): () => Unit = () => println(xs.head) // error def test(io: IO^)(ys: List[() ->{io} Unit]) = val x = () => - val z = f(ys) // error @consume failure + val z = f(ys) // error consume failure z() val _: () -> Unit = x // error () @@ -16,7 +16,7 @@ def test(io: IO^)(ys: List[() ->{io} Unit]) = def test(io: IO^) = def ys: List[() ->{io} Unit] = ??? val x = () => - val z = f(ys) // error @consume failure + val z = f(ys) // error consume failure z() val _: () -> Unit = x // error () diff --git a/tests/neg/localReaches.scala b/tests/neg/localReaches.scala index d0935e2d491f..5bf1b9a26303 100644 --- a/tests/neg/localReaches.scala +++ b/tests/neg/localReaches.scala @@ -1,6 +1,6 @@ import language.experimental.captureChecking // no separation checking -import caps.consume + def localReach() = val xs: List[() => Unit] = ??? diff --git a/tests/new/test.scala b/tests/new/test.scala index 2904592cc6b2..23ac0de47b6f 100644 --- a/tests/new/test.scala +++ b/tests/new/test.scala @@ -1 +1,5 @@ -def test[T] = println(T) +class Cap extends caps.ExclusiveCapability + +def test(consume x: Cap) = ??? + +def impl(using consume x: Cap) = ??? diff --git a/tests/pos-custom-args/captures/capt-depfun.scala b/tests/pos-custom-args/captures/capt-depfun.scala index fb6866459aba..359c49e9110f 100644 --- a/tests/pos-custom-args/captures/capt-depfun.scala +++ b/tests/pos-custom-args/captures/capt-depfun.scala @@ -1,5 +1,5 @@ import annotation.retains -import caps.consume + class C type Cap = C @retains[caps.cap.type] @@ -11,7 +11,7 @@ class STR(s: String) val aa: ((x: Cap) -> STR @retains[x.type]) = (x: Cap) => STR("") -def f(@consume y: Cap, z: Cap): STR @retains[caps.cap.type] = +def f(consume y: Cap, z: Cap): STR @retains[caps.cap.type] = val a: ((x: Cap) -> STR @retains[x.type]) = (x: Cap) => STR("") val b = a(y) val c: STR @retains[y.type] = b diff --git a/tests/pos-custom-args/captures/capt-depfun2.scala b/tests/pos-custom-args/captures/capt-depfun2.scala index 101ea6bb94e1..4451deb27c76 100644 --- a/tests/pos-custom-args/captures/capt-depfun2.scala +++ b/tests/pos-custom-args/captures/capt-depfun2.scala @@ -1,10 +1,10 @@ import annotation.retains -import caps.consume + class C type Cap = C @retains[caps.cap.type] class STR -def f(@consume y: Cap, @consume z: Cap) = +def f(consume y: Cap, consume z: Cap) = def g(): C @retains[y.type | z.type] = ??? val ac: ((x: Cap) -> Array[STR @retains[x.type]]) = ??? val dc: Array[? >: STR <: STR^]^{y, z} = ac(g()) // needs to be inferred diff --git a/tests/pos-custom-args/captures/cc-this.scala b/tests/pos-custom-args/captures/cc-this.scala index 6aa21a7a6c70..719a48130c6c 100644 --- a/tests/pos-custom-args/captures/cc-this.scala +++ b/tests/pos-custom-args/captures/cc-this.scala @@ -1,12 +1,12 @@ -import caps.consume + import caps.unsafe.unsafeAssumeSeparate class Cap extends caps.SharedCapability def eff(using Cap): Unit = () -def test(using @consume cc: Cap) = +def test(using consume cc: Cap) = class C(val x: () => Int): val y: C^ = this diff --git a/tests/pos-custom-args/captures/lazyref.scala b/tests/pos-custom-args/captures/lazyref.scala index 6b5d62c743b6..0723aef95271 100644 --- a/tests/pos-custom-args/captures/lazyref.scala +++ b/tests/pos-custom-args/captures/lazyref.scala @@ -1,5 +1,5 @@ -import caps.consume + class Cap extends caps.SharedCapability @@ -14,7 +14,7 @@ def map[A, B](ref: LazyRef[A]^, f: A => B): LazyRef[B]^{f, ref} = def mapc[A, B]: (ref: LazyRef[A]^, f: A => B) => LazyRef[B]^{f, ref} = (ref1, f1) => map[A, B](ref1, f1) -def test(@consume cap1: Cap, @consume cap2: Cap) = +def test(consume cap1: Cap, consume cap2: Cap) = 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)) diff --git a/tests/pos-custom-args/captures/reaches.scala b/tests/pos-custom-args/captures/reaches.scala index eae4c3e8a73c..348346ad1c3f 100644 --- a/tests/pos-custom-args/captures/reaches.scala +++ b/tests/pos-custom-args/captures/reaches.scala @@ -1,6 +1,3 @@ - -import caps.{use, consume} - class C def f(xs: List[C^]) = val y = xs @@ -37,7 +34,7 @@ def cons(x: Proc, xs: List[Proc]): List[() ->{x, xs*} Unit] = val y = x :: xs y -def addOneProc(@consume xs: List[Proc]): List[Proc] = +def addOneProc(consume xs: List[Proc]): List[Proc] = val x: Proc = () => println("hello") val result: List[() ->{x, xs*} Unit] = x :: xs result // OK, we can widen () ->{x, xs*} Unit to cap here. @@ -45,7 +42,7 @@ def addOneProc(@consume xs: List[Proc]): List[Proc] = def compose1[A, B, C](f: A => B, g: B => C): A ->{f, g} C = z => g(f(z)) -def compose2[A, B, C](@consume f: A => B, @consume g: B => C): A => C = +def compose2[A, B, C](consume f: A => B, consume g: B => C): A => C = z => g(f(z)) //def mapCompose[A](ps: List[(A => A, A => A)]): List[A ->{ps*} A] = diff --git a/tests/pos-custom-args/captures/sep-pairs.scala b/tests/pos-custom-args/captures/sep-pairs.scala index 17cb8eb9aa6c..ad22df412e0d 100644 --- a/tests/pos-custom-args/captures/sep-pairs.scala +++ b/tests/pos-custom-args/captures/sep-pairs.scala @@ -1,5 +1,5 @@ import caps.Mutable -import caps.{cap, consume, use} +import caps.cap class Ref extends Mutable: var x = 0 @@ -14,19 +14,19 @@ def mkPair: Pair[Ref^, Ref^] = val p_exact: Pair[Ref^{r1}, Ref^{r2}] = Pair(r1, r2) p_exact -def copyPair[C^, D^](@consume p: Pair[Ref^{C}, Ref^{D}]): Pair[Ref^{C}, Ref^{D}] = +def copyPair[C^, D^](consume p: Pair[Ref^{C}, Ref^{D}]): Pair[Ref^{C}, Ref^{D}] = val x: Ref^{C} = p.fst val y: Ref^{D} = p.snd Pair[Ref^{C}, Ref^{D}](x, y) /* TODO: The following variants don't work -def copyPair1[C^, D^](@consume p: Pair[Ref^{C}, Ref^{D}]): Pair[Ref^{C}, Ref^{D}] = +def copyPair1[C^, D^](consume p: Pair[Ref^{C}, Ref^{D}]): Pair[Ref^{C}, Ref^{D}] = val x: Ref^{C} = p.fst val y: Ref^{D} = p.snd Pair(x, y) -def copyPair2[C^, D^](@consume p: Pair[Ref^{C}, Ref^{D}]): Pair[Ref^, Ref^] = +def copyPair2[C^, D^](consume p: Pair[Ref^{C}, Ref^{D}]): Pair[Ref^, Ref^] = val x: Ref^{C} = p.fst val y: Ref^{D} = p.snd Pair(x, y) diff --git a/tests/pos-custom-args/captures/skolems2.scala b/tests/pos-custom-args/captures/skolems2.scala index 53fbe935e362..754f80b9a36f 100644 --- a/tests/pos-custom-args/captures/skolems2.scala +++ b/tests/pos-custom-args/captures/skolems2.scala @@ -1,8 +1,8 @@ -import caps.consume + import caps.unsafe.unsafeAssumeSeparate -def Test(@consume c: Object^, @consume f: Object^ => Object^) = +def Test(consume c: Object^, consume f: Object^ => Object^) = def cc: Object^ = unsafeAssumeSeparate(c) val x1 = { f(cc) } @@ -13,7 +13,7 @@ def Test(@consume c: Object^, @consume f: Object^ => Object^) = val x4: Object^ = { unsafeAssumeSeparate(f)(cc) } -def Test2(@consume c: Object^, @consume f: Object^ => Object^) = +def Test2(consume c: Object^, consume f: Object^ => Object^) = def cc(): Object^ = unsafeAssumeSeparate(c) val x1 = { f(cc()) } diff --git a/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala b/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala index 9effb37d5278..c276af431b52 100644 --- a/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala +++ b/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala @@ -39,7 +39,7 @@ val experimentalDefinitionInLibrary = Set( "scala.caps.ExclusiveCapability", "scala.caps.Mutable", "scala.caps.Read", - "scala.caps.consume", + "scala.caps.Control", "scala.caps.internal", "scala.caps.internal$", "scala.caps.cap", From f21323e0a2d256c6f16c0d9ca75b0e4f626490d8 Mon Sep 17 00:00:00 2001 From: odersky Date: Fri, 15 Aug 2025 16:01:45 +0200 Subject: [PATCH 019/101] Some unrelated tests --- .../captures/scoped-caps2.check | 82 +++++++++++++++++++ .../captures/scoped-caps2.scala | 17 ++++ 2 files changed, 99 insertions(+) create mode 100644 tests/neg-custom-args/captures/scoped-caps2.check create mode 100644 tests/neg-custom-args/captures/scoped-caps2.scala diff --git a/tests/neg-custom-args/captures/scoped-caps2.check b/tests/neg-custom-args/captures/scoped-caps2.check new file mode 100644 index 000000000000..e0603af7dbc0 --- /dev/null +++ b/tests/neg-custom-args/captures/scoped-caps2.check @@ -0,0 +1,82 @@ +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/scoped-caps2.scala:5:23 ---------------------------------- +5 | val _: (x: C) => C = b // error + | ^ + | Found: (b : C => C) + | Required: (x: C^) =>² C^² + | + | where: => refers to a fresh root capability in the type of value b + | =>² refers to a fresh root capability in the type of value _$1 + | ^ refers to the universal root capability + | ^² refers to a root capability associated with the result type of (x: C^): C^² + | + | Note that the existential capture root in C^ + | cannot subsume the capability cap.. + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/scoped-caps2.scala:6:18 ---------------------------------- +6 | val _: C => C = a // error + | ^ + | Found: (a : (x: C) => C) + | Required: C^ =>² C^² + | + | where: => refers to a fresh root capability in the type of value a + | =>² refers to a fresh root capability in the type of value _$2 + | ^ refers to the universal root capability + | ^² refers to a fresh root capability classified as SharedCapability in the type of value _$2 + | + | Note that capability cap is not included in capture set {cap}. + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/scoped-caps2.scala:8:18 ---------------------------------- +8 | val _: C => C = (x: C) => a(x) // error + | ^^^^^^^^^^^^^^ + | Found: (x: C^) ->{a} C^² + | Required: C^ => C^³ + | + | where: => refers to a fresh root capability in the type of value _$4 + | ^ refers to the universal root capability + | ^² refers to a root capability associated with the result type of (x: C^): C^² + | ^³ refers to a fresh root capability classified as SharedCapability in the type of value _$4 + | + | Note that capability is not included in capture set {cap} + | because is not visible from cap in value _$4. + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/scoped-caps2.scala:14:18 --------------------------------- +14 | val _: C -> C = a // error + | ^ + | Found: (a : (x: C) -> C) + | Required: C^ -> C^² + | + | where: ^ refers to the universal root capability + | ^² refers to a fresh root capability classified as SharedCapability in the type of value _$6 + | + | Note that capability is not included in capture set {cap} + | because is not visible from cap in value _$6. + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/scoped-caps2.scala:15:23 --------------------------------- +15 | val _: (x: C) -> C = (x: C) => b(x) // error + | ^^^^^^^^^^^^^^ + | Found: (x: C^) ->{b} C^² + | Required: (x: C^) -> C^³ + | + | where: ^ refers to the universal root capability + | ^² refers to a root capability associated with the result type of (x: C^): C^² + | ^³ refers to a root capability associated with the result type of (x: C^): C^³ + | + | Note that capability b is not included in capture set {}. + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/scoped-caps2.scala:16:29 --------------------------------- +16 | val _: C -> C = (x: C) => a(x) // error + | ^^^^ + | Found: C^{x} + | Required: C^ + | + | where: ^ refers to a fresh root capability classified as SharedCapability in the type of value _$8 + | + | Note that capability x is not included in capture set {cap} + | because (x : C) in enclosing function is not visible from cap in value _$8. + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/scoped-caps2.scala b/tests/neg-custom-args/captures/scoped-caps2.scala new file mode 100644 index 000000000000..527debb57378 --- /dev/null +++ b/tests/neg-custom-args/captures/scoped-caps2.scala @@ -0,0 +1,17 @@ +class C extends caps.SharedCapability +def test1(c: C) = + val a: (x: C) => C = ??? + val b: C => C = ??? + val _: (x: C) => C = b // error + val _: C => C = a // error + val _: (x: C) => C = (x: C) => b(x) // OK + val _: C => C = (x: C) => a(x) // error + +def test2(c: C) = + val a: (x: C) -> C = ??? + val b: C -> C = ??? + val _: (x: C) -> C = b // OK + val _: C -> C = a // error + val _: (x: C) -> C = (x: C) => b(x) // error + val _: C -> C = (x: C) => a(x) // error + From 633272f0dcb13c186c5a5ac5c628a9266d9004da Mon Sep 17 00:00:00 2001 From: odersky Date: Fri, 15 Aug 2025 16:08:44 +0200 Subject: [PATCH 020/101] Update doc page to consume as a modifier --- .../capture-checking/separation-checking.md | 14 +++++++------- .../stdlibExperimentalDefinitions.scala | 1 - 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/docs/_docs/reference/experimental/capture-checking/separation-checking.md b/docs/_docs/reference/experimental/capture-checking/separation-checking.md index 1a4c9ead1776..6bb0fd3bf47c 100644 --- a/docs/_docs/reference/experimental/capture-checking/separation-checking.md +++ b/docs/_docs/reference/experimental/capture-checking/separation-checking.md @@ -349,9 +349,9 @@ Therefore, parameters cannot appear in the hidden sets of fresh result caps eith ### Consume Parameters -Returning parameters in fresh result caps is safe if the actual argument to the parameter is not used afterwards. We can signal and enforce this pattern by adding a `@consume` annotation to a parameter. With that annotation, the following variant of `incr` is legal: +Returning parameters in fresh result caps is safe if the actual argument to the parameter is not used afterwards. We can signal and enforce this pattern by adding a `consume` modifier to a parameter. With that new soft modifier, the following variant of `incr` is legal: ```scala -def incr(@consume a: Ref^): Ref^ = +def incr(consume a: Ref^): Ref^ = a.set(a.get + 1) a ``` @@ -374,19 +374,19 @@ Consume parameters enforce linear access to resources. This can be very useful. For instance, we can define a function `linearAdd` that adds elements to buffers in-place without violating referential transparency: ```scala -def linearAdd[T](@consume buf: Buffer[T]^, elem: T): Buffer[T]^ = +def linearAdd[T](consume buf: Buffer[T]^, elem: T): Buffer[T]^ = buf += elem ``` -`linearAdd` returns a fresh buffer resulting from appending `elem` to `buf`. It overwrites `buf`, but that's OK since the `@consume` annotation on `buf` ensures that the argument is not used after the call. +`linearAdd` returns a fresh buffer resulting from appending `elem` to `buf`. It overwrites `buf`, but that's OK since the `consume` modifier on `buf` ensures that the argument is not used after the call. ### Consume Methods -Buffers in Scala's standard library use a single-argument method `+=` instead of a two argument global function like `linearAdd`. We can enforce linearity in this case by adding the `@consume` annotation to the method itself. +Buffers in Scala's standard library use a single-argument method `+=` instead of a two argument global function like `linearAdd`. We can enforce linearity in this case by adding the `consume` modifier to the method itself. ```scala class Buffer[T] extends Mutable: - @consume update def +=(x: T): Buffer[T]^ = this // ok + consume def +=(x: T): Buffer[T]^ = this // ok ``` -Then we can write +`consume` on a method implies `update`, so there's no need to label `+=` separately as an update method. Then we can write ```scala val b = Buffer[Int]() += 1 += 2 val c = b += 3 diff --git a/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala b/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala index c276af431b52..a546d5def0d9 100644 --- a/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala +++ b/tests/run-tasty-inspector/stdlibExperimentalDefinitions.scala @@ -39,7 +39,6 @@ val experimentalDefinitionInLibrary = Set( "scala.caps.ExclusiveCapability", "scala.caps.Mutable", "scala.caps.Read", - "scala.caps.Control", "scala.caps.internal", "scala.caps.internal$", "scala.caps.cap", From 4b8db822395ee10137a163497ffe342db6be8b1b Mon Sep 17 00:00:00 2001 From: odersky Date: Fri, 15 Aug 2025 17:12:19 +0200 Subject: [PATCH 021/101] Fix check files on rebase --- tests/neg-custom-args/captures/reaches.check | 10 +-- .../captures/reference-cc.check | 18 ++-- .../captures/scoped-caps2.check | 83 +++++++++++-------- 3 files changed, 62 insertions(+), 49 deletions(-) diff --git a/tests/neg-custom-args/captures/reaches.check b/tests/neg-custom-args/captures/reaches.check index cb88ef40431e..70896596593e 100644 --- a/tests/neg-custom-args/captures/reaches.check +++ b/tests/neg-custom-args/captures/reaches.check @@ -9,7 +9,7 @@ |where: => refers to a fresh root capability created in method runAll0 when checking argument to parameter f of method usingFile | ^ refers to the universal root capability | cap is a fresh root capability created in anonymous function of type (f: File^'s1): Unit of parameter parameter f² of method $anonfun -23 | cur = (() => f.write()) :: Nil +21 | cur = (() => f.write()) :: Nil | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reaches.scala:30:13 -------------------------------------- @@ -23,8 +23,8 @@ |where: => refers to a fresh root capability created in method runAll1 when checking argument to parameter f of method usingFile | ^ refers to the universal root capability | cap is a fresh root capability created in anonymous function of type (f: File^'s3): Unit of parameter parameter f² of method $anonfun -33 | cur.set: -34 | (() => f.write()) :: Nil +31 | cur.set: +32 | (() => f.write()) :: Nil | | longer explanation available when compiling with `-explain` -- Error: tests/neg-custom-args/captures/reaches.scala:42:16 ----------------------------------------------------------- @@ -98,8 +98,8 @@ |where: => refers to a fresh root capability created in value leaked when checking argument to parameter f of method usingFile | ^ refers to the universal root capability | cap is a fresh root capability created in anonymous function of type (f: File^'s6): File^{id*} of parameter parameter f² of method $anonfun -70 | val f1: File^{id*} = id(f) -71 | f1 +68 | val f1: File^{id*} = id(f) +69 | f1 | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reaches.scala:85:10 -------------------------------------- diff --git a/tests/neg-custom-args/captures/reference-cc.check b/tests/neg-custom-args/captures/reference-cc.check index a76238e5fade..dea3e1a5b5e1 100644 --- a/tests/neg-custom-args/captures/reference-cc.check +++ b/tests/neg-custom-args/captures/reference-cc.check @@ -9,11 +9,11 @@ |Found: (file: java.io.FileOutputStream^'s1) ->'s2 () ->{file} Unit |Required: java.io.FileOutputStream^ => () ->'s3 Unit | + |Note that capability file cannot be included in outer capture set 's3. + | |where: => refers to a fresh root capability created in value later when checking argument to parameter op of method usingLogFile | ^ refers to the universal root capability | - |Note that capability file cannot be included in outer capture set 's3. - | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/reference-cc.scala:47:25 --------------------------------- 47 | val xs = usingLogFile: f => // error @@ -21,10 +21,10 @@ |Found: (f: java.io.FileOutputStream^'s4) ->'s5 LzyList[Int]^{f} |Required: java.io.FileOutputStream^ => LzyList[Int]^'s6 | + |Note that capability f cannot be included in outer capture set 's6. + | |where: => refers to a fresh root capability created in value xs when checking argument to parameter op of method usingLogFile | ^ refers to the universal root capability - | - |Note that capability f cannot be included in outer capture set 's6. 48 | LzyList(1, 2, 3).map { x => f.write(x); x * x } | | longer explanation available when compiling with `-explain` @@ -34,10 +34,12 @@ |Found: () => Double |Required: () =>² Double | - |where: => refers to a fresh root capability classified as Control in the type of given instance canThrow$1 - | =>² refers to a fresh root capability created in anonymous function of type (using erased x$1: CanThrow[LimitExceeded]): () => Double when instantiating expected result type () ->{cap} Double of function literal + |Note that capability cap is not included in capture set {cap²} + |because cap in method try$1 is not visible from cap² in enclosing function. | - |Note that capability cap is not included in capture set {cap} - |because cap in method try$1 is not visible from cap in enclosing function. + |where: => refers to a fresh root capability classified as Control in the type of given instance canThrow$1 + | =>² refers to a fresh root capability created in anonymous function of type (using erased x$1: CanThrow[LimitExceeded]): () => Double when instantiating expected result type () ->{cap³} Double of function literal + | cap is a fresh root capability classified as Control in the type of given instance canThrow$1 + | cap² is a fresh root capability created in anonymous function of type (using erased x$1: CanThrow[LimitExceeded]): () => Double when instantiating expected result type () ->{cap³} Double of function literal | | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/scoped-caps2.check b/tests/neg-custom-args/captures/scoped-caps2.check index e0603af7dbc0..1a885b4689d3 100644 --- a/tests/neg-custom-args/captures/scoped-caps2.check +++ b/tests/neg-custom-args/captures/scoped-caps2.check @@ -1,58 +1,68 @@ -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/scoped-caps2.scala:5:23 ---------------------------------- 5 | val _: (x: C) => C = b // error | ^ - | Found: (b : C => C) - | Required: (x: C^) =>² C^² + | Found: (b : C => C) + | Required: (x: C^) =>² C^² | - | where: => refers to a fresh root capability in the type of value b - | =>² refers to a fresh root capability in the type of value _$1 - | ^ refers to the universal root capability - | ^² refers to a root capability associated with the result type of (x: C^): C^² + | Note that the existential capture root in C^ + | cannot subsume the capability cap.. | - | Note that the existential capture root in C^ - | cannot subsume the capability cap.. + | Note that capability cap is not included in capture set {cap²}. + | + | where: => refers to a fresh root capability in the type of value b + | =>² refers to a fresh root capability in the type of value _$1 + | ^ refers to the universal root capability + | ^² refers to a root capability associated with the result type of (x: C^): C^² + | cap is a fresh root capability classified as SharedCapability in the type of value b + | cap² is a fresh root capability in the type of value b | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/scoped-caps2.scala:6:18 ---------------------------------- 6 | val _: C => C = a // error | ^ - | Found: (a : (x: C) => C) - | Required: C^ =>² C^² + | Found: (a : (x: C) => C) + | Required: C^ =>² C^² | - | where: => refers to a fresh root capability in the type of value a - | =>² refers to a fresh root capability in the type of value _$2 - | ^ refers to the universal root capability - | ^² refers to a fresh root capability classified as SharedCapability in the type of value _$2 + | Note that capability cap is not included in capture set {cap²}. | - | Note that capability cap is not included in capture set {cap}. + | where: => refers to a fresh root capability in the type of value a + | =>² refers to a fresh root capability in the type of value _$2 + | ^ refers to the universal root capability + | ^² refers to a fresh root capability classified as SharedCapability in the type of value _$2 + | cap is a fresh root capability in the type of value a + | cap² is a fresh root capability in the type of value _$2 | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/scoped-caps2.scala:8:18 ---------------------------------- 8 | val _: C => C = (x: C) => a(x) // error | ^^^^^^^^^^^^^^ - | Found: (x: C^) ->{a} C^² - | Required: C^ => C^³ + | Found: (x: C^) ->{a} C^² + | Required: C^ => C^³ | - | where: => refers to a fresh root capability in the type of value _$4 - | ^ refers to the universal root capability - | ^² refers to a root capability associated with the result type of (x: C^): C^² - | ^³ refers to a fresh root capability classified as SharedCapability in the type of value _$4 + | Note that capability cap is not included in capture set {cap²} + | because cap is not visible from cap² in value _$4. | - | Note that capability is not included in capture set {cap} - | because is not visible from cap in value _$4. + | where: => refers to a fresh root capability in the type of value _$4 + | ^ refers to the universal root capability + | ^² refers to a root capability associated with the result type of (x: C^): C^² + | ^³ refers to a fresh root capability classified as SharedCapability in the type of value _$4 + | cap is a root capability associated with the result type of (x: C^): C^² + | cap² is a fresh root capability classified as SharedCapability in the type of value _$4 | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/scoped-caps2.scala:14:18 --------------------------------- 14 | val _: C -> C = a // error | ^ - | Found: (a : (x: C) -> C) - | Required: C^ -> C^² + | Found: (a : (x: C) -> C) + | Required: C^ -> C^² | - | where: ^ refers to the universal root capability - | ^² refers to a fresh root capability classified as SharedCapability in the type of value _$6 + | Note that capability cap is not included in capture set {cap²} + | because cap is not visible from cap² in value _$6. | - | Note that capability is not included in capture set {cap} - | because is not visible from cap in value _$6. + | where: ^ refers to the universal root capability + | ^² refers to a fresh root capability classified as SharedCapability in the type of value _$6 + | cap is a root capability associated with the result type of (x: C^): C^³ + | cap² is a fresh root capability classified as SharedCapability in the type of value _$6 | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/scoped-caps2.scala:15:23 --------------------------------- @@ -61,22 +71,23 @@ | Found: (x: C^) ->{b} C^² | Required: (x: C^) -> C^³ | + | Note that capability b is not included in capture set {}. + | | where: ^ refers to the universal root capability | ^² refers to a root capability associated with the result type of (x: C^): C^² | ^³ refers to a root capability associated with the result type of (x: C^): C^³ | - | Note that capability b is not included in capture set {}. - | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/scoped-caps2.scala:16:29 --------------------------------- 16 | val _: C -> C = (x: C) => a(x) // error | ^^^^ - | Found: C^{x} - | Required: C^ + | Found: C^{x} + | Required: C^ | - | where: ^ refers to a fresh root capability classified as SharedCapability in the type of value _$8 + | Note that capability x is not included in capture set {cap} + | because (x : C) in enclosing function is not visible from cap in value _$8. | - | Note that capability x is not included in capture set {cap} - | because (x : C) in enclosing function is not visible from cap in value _$8. + | where: ^ refers to a fresh root capability classified as SharedCapability in the type of value _$8 + | cap is a fresh root capability classified as SharedCapability in the type of value _$8 | | longer explanation available when compiling with `-explain` From 49ddd51d5359a7aee12d85c9dbd925e6c9e669c8 Mon Sep 17 00:00:00 2001 From: Hamza Remmal Date: Fri, 15 Aug 2025 16:51:20 +0200 Subject: [PATCH 022/101] chore: `scaladoc` takes into account changes to `consume` --- scaladoc/src/dotty/tools/scaladoc/cc/CaptureOps.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scaladoc/src/dotty/tools/scaladoc/cc/CaptureOps.scala b/scaladoc/src/dotty/tools/scaladoc/cc/CaptureOps.scala index 626162db55ec..3867e1e37ac7 100644 --- a/scaladoc/src/dotty/tools/scaladoc/cc/CaptureOps.scala +++ b/scaladoc/src/dotty/tools/scaladoc/cc/CaptureOps.scala @@ -27,7 +27,7 @@ object CaptureDefs: def UseAnnot(using qctx: Quotes) = qctx.reflect.Symbol.requiredClass("scala.caps.use") def ConsumeAnnot(using qctx: Quotes) = - qctx.reflect.Symbol.requiredClass("scala.caps.consume") + qctx.reflect.Symbol.requiredClass("scala.caps.internal.consume") def ReachCapabilityAnnot(using qctx: Quotes) = qctx.reflect.Symbol.requiredClass("scala.annotation.internal.reachCapability") def RootCapabilityAnnot(using qctx: Quotes) = From 705fe5f21ec6ece4f06fc322cc815e3ca84088c7 Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Sat, 16 Aug 2025 02:12:34 +0200 Subject: [PATCH 023/101] Scaladoc fix: don't drop caps on parameters --- scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala index 0ec9a150235f..4546d05adf6d 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/TypesSupport.scala @@ -535,6 +535,7 @@ trait TypesSupport: private def isCapturedInContext(using Quotes)(ref: reflect.TypeRepr)(using elideThis: reflect.ClassDef): Boolean = import reflect._ ref match + case t if t.isCaptureRoot => true case ReachCapability(c) => isCapturedInContext(c) case ReadOnlyCapability(c) => isCapturedInContext(c) case ThisType(tr) => !elideThis.symbol.typeRef.isPureClass(elideThis) /* is the current class pure? */ From fe8937d1501822b338fd4f955b3d7a4f22a2e662 Mon Sep 17 00:00:00 2001 From: Hamza Remmal Date: Sat, 16 Aug 2025 12:59:13 +0200 Subject: [PATCH 024/101] chore: do not render `consume update` in `scaladoc` --- .../src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala b/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala index 19f7c067a79e..0d122bfe37e5 100644 --- a/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala +++ b/scaladoc/src/dotty/tools/scaladoc/tasty/ClassLikeSupport.scala @@ -25,9 +25,10 @@ trait ClassLikeSupport: def getExtraModifiers(): Seq[Modifier] = var mods = SymOps.getExtraModifiers(symbol)() if ccEnabled && symbol.flags.is(Flags.Mutable) then - mods :+= Modifier.Update - if ccEnabled && symbol.hasAnnotation(cc.CaptureDefs.ConsumeAnnot) then - mods :+= Modifier.Consume + if symbol.hasAnnotation(cc.CaptureDefs.ConsumeAnnot) then + mods :+= Modifier.Consume + else + mods :+= Modifier.Update mods } From abe1746d8e8966655736355b9a5233463ea77e64 Mon Sep 17 00:00:00 2001 From: Hamza Remmal Date: Sat, 16 Aug 2025 15:06:56 +0200 Subject: [PATCH 025/101] chore: add release settings to the new build --- project/Build.scala | 102 +++++++++++++------------------------------- 1 file changed, 29 insertions(+), 73 deletions(-) diff --git a/project/Build.scala b/project/Build.scala index 9acb1c7d55aa..f9e678eab13f 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1471,6 +1471,7 @@ object Build { /* Configuration of the org.scala-lang:scala3-sbt-bridge:*.**.**-bootstrapped project */ lazy val `scala3-sbt-bridge-bootstrapped` = project.in(file("sbt-bridge")) .dependsOn(`scala3-compiler-bootstrapped-new`) // TODO: Would this actually evict the reference compiler in scala-tool? + .settings(publishSettings) .settings( name := "scala3-sbt-bridge-bootstrapped", moduleName := "scala3-sbt-bridge", @@ -1542,6 +1543,7 @@ object Build { // when compiling a project that depends on scala3-staging (see sbt-test/sbt-dotty/quoted-example-project), // but we always need it to be present on the JVM classpath at runtime. .dependsOn(`scala3-compiler-bootstrapped-new` % "provided; compile->runtime; test->test") + .settings(publishSettings) .settings( name := "scala3-staging", moduleName := "scala3-staging", @@ -1599,6 +1601,7 @@ object Build { // when compiling a project that depends on scala3-tasty-inspector (see sbt-test/sbt-dotty/tasty-inspector-example-project), // but we always need it to be present on the JVM classpath at runtime. .dependsOn(`scala3-compiler-bootstrapped-new` % "provided; compile->runtime; test->test") + .settings(publishSettings) .settings( name := "scala3-tasty-inspector", moduleName := "scala3-tasty-inspector", @@ -1737,6 +1740,7 @@ object Build { /* Configuration of the org.scala-lang:scala-library:*.**.**-bootstrapped project */ lazy val `scala-library-bootstrapped` = project.in(file("library")) .enablePlugins(ScalaLibraryPlugin) + .settings(publishSettings) .settings( name := "scala-library-bootstrapped", moduleName := "scala-library", @@ -1809,6 +1813,7 @@ object Build { /* Configuration of the org.scala-lang:scala3-library_3:*.**.**-bootstrapped project */ lazy val `scala3-library-bootstrapped-new` = project.in(file("library")) .dependsOn(`scala-library-bootstrapped`) + .settings(publishSettings) .settings( name := "scala3-library-bootstrapped", moduleName := "scala3-library", @@ -1851,6 +1856,7 @@ object Build { // We add a dependency to the JVM library to have the classfile available // (as they are not part of this artifact) .dependsOn(`scala3-library-bootstrapped-new`) + .settings(publishSettings) .settings( name := "scala-library-sjs", organization := "org.scala-js", @@ -1961,6 +1967,7 @@ object Build { /* Configuration of the org.scala-lang:scala3-library_sjs1_3:*.**.**-bootstrapped project */ lazy val `scala3-library-sjs` = project.in(file("library-js")) .dependsOn(`scala-library-sjs`) + .settings(publishSettings) .settings( name := "scala3-library-sjs", moduleName := "scala3-library_sjs1", @@ -2064,6 +2071,7 @@ object Build { /* Configuration of the org.scala-lang:tasty-core_3:*.**.**-bootstrapped project */ lazy val `tasty-core-bootstrapped-new` = project.in(file("tasty")) .dependsOn(`scala3-library-bootstrapped-new`) + .settings(publishSettings) .settings( name := "tasty-core-bootstrapped", moduleName := "tasty-core", @@ -2256,6 +2264,7 @@ object Build { /* Configuration of the org.scala-lang:scala3-compiler_3:*.**.**-bootstrapped project */ lazy val `scala3-compiler-bootstrapped-new` = project.in(file("compiler")) .dependsOn(`scala3-interfaces`, `tasty-core-bootstrapped-new`, `scala3-library-bootstrapped-new`) + .settings(publishSettings) .settings( name := "scala3-compiler-bootstrapped", moduleName := "scala3-compiler", @@ -2390,6 +2399,7 @@ object Build { /* Configuration of the org.scala-lang:scaladoc_3:*.**.**-bootstrapped project */ lazy val `scaladoc-new` = project.in(file("scaladoc")) .dependsOn(`scala3-compiler-bootstrapped-new`, `scala3-tasty-inspector-new`) + .settings(publishSettings) .settings( name := "scaladoc", moduleName := "scaladoc", @@ -3527,10 +3537,24 @@ object Build { publishMavenStyle := true, isSnapshot := version.value.contains("SNAPSHOT"), publishTo := { - val centralSnapshots = "https://central.sonatype.com/repository/maven-snapshots/" - if (isSnapshot.value) Some("central-snapshots" at centralSnapshots) - else localStaging.value + if (sys.env.get("NEWNIGHTLY").contains("yes")) { + Some(sys.env("MAVEN_REPOSITORY_REALM") at sys.env("MAVEN_REPOSITORY_URL")) + } else if (isSnapshot.value) { + Some("central-snapshots" at "https://central.sonatype.com/repository/maven-snapshots/") + } else + localStaging.value }, + credentials ++= ( + if (sys.env.get("NEWNIGHTLY").contains("yes")) { + for { + username <- sys.env.get("MAVEN_REPOSITORY_USER") + token <- sys.env.get("MAVEN_REPOSITORY_TOKEN") + } yield Credentials(sys.env("MAVEN_REPOSITORY_REALM"), sys.env("MAVEN_REPOSITORY_HOST"), username, token) + } + else + // The old build credentials are configured differently + None + ).toList, publishConfiguration ~= (_.withOverwrite(true)), publishLocalConfiguration ~= (_.withOverwrite(true)), projectID ~= {id => @@ -3539,76 +3563,8 @@ object Build { }, Test / publishArtifact := false, homepage := Some(url(dottyGithubUrl)), - licenses += (("Apache-2.0", - url("https://www.apache.org/licenses/LICENSE-2.0"))), - scmInfo := Some( - ScmInfo( - url(dottyGithubUrl), - "scm:git:git@github.com:scala/scala3.git" - ) - ), - developers := List( - Developer( - id = "odersky", - name = "Martin Odersky", - email = "martin.odersky@epfl.ch", - url = url("https://github.com/odersky") - ), - Developer( - id = "DarkDimius", - name = "Dmitry Petrashko", - email = "me@d-d.me", - url = url("https://d-d.me") - ), - Developer( - id = "smarter", - name = "Guillaume Martres", - email = "smarter@ubuntu.com", - url = url("http://guillaume.martres.me") - ), - Developer( - id = "felixmulder", - name = "Felix Mulder", - email = "felix.mulder@gmail.com", - url = url("http://felixmulder.com") - ), - Developer( - id = "liufengyun", - name = "Liu Fengyun", - email = "liu@fengy.me", - url = url("https://fengy.me") - ), - Developer( - id = "nicolasstucki", - name = "Nicolas Stucki", - email = "nicolas.stucki@gmail.com", - url = url("https://github.com/nicolasstucki") - ), - Developer( - id = "OlivierBlanvillain", - name = "Olivier Blanvillain", - email = "olivier.blanvillain@gmail.com", - url = url("https://github.com/OlivierBlanvillain") - ), - Developer( - id = "biboudis", - name = "Aggelos Biboudis", - email = "aggelos.biboudis@epfl.ch", - url = url("http://biboudis.github.io") - ), - Developer( - id = "allanrenucci", - name = "Allan Renucci", - email = "allan.renucci@gmail.com", - url = url("https://github.com/allanrenucci") - ), - Developer( - id = "Duhemm", - name = "Martin Duhem", - email = "martin.duhem@gmail.com", - url = url("https://github.com/Duhemm") - ) - ) + licenses += (("Apache-2.0", url("https://www.apache.org/licenses/LICENSE-2.0"))), + scmInfo := Some(ScmInfo(url(dottyGithubUrl), "scm:git:git@github.com:scala/scala3.git")), ) lazy val commonDistSettings = Seq( From dc37512c8c013e473bdd318b9caff09e50006852 Mon Sep 17 00:00:00 2001 From: Hamza Remmal Date: Sat, 16 Aug 2025 15:27:07 +0200 Subject: [PATCH 026/101] chore: add workflow to release nightly of the new build --- .github/workflows/release-maven-artifacts.yml | 23 +++++++++++++++++++ .github/workflows/release-nightly.yml | 19 +++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 .github/workflows/release-maven-artifacts.yml create mode 100644 .github/workflows/release-nightly.yml diff --git a/.github/workflows/release-maven-artifacts.yml b/.github/workflows/release-maven-artifacts.yml new file mode 100644 index 000000000000..86d1fbb23029 --- /dev/null +++ b/.github/workflows/release-maven-artifacts.yml @@ -0,0 +1,23 @@ +name: Release Artifacts to Maven + +on: + workflow_call: + inputs: + environment: + required: true + type: string + +jobs: + release-maven-artifacts: + runs-on: ubuntu-latest + environment: ${{ inputs.environment }} + steps: + - uses: actions/checkout@v5 + - uses: actions/setup-java@v4 + with: + distribution: 'adopt' + java-version: 17 + cache: 'sbt' + - uses: sbt/setup-sbt@v1 + - name: Publish Artifacts to the Maven Repository + run : sbt scala3-bootstrapped-new/publish diff --git a/.github/workflows/release-nightly.yml b/.github/workflows/release-nightly.yml new file mode 100644 index 000000000000..f1a594bf64b4 --- /dev/null +++ b/.github/workflows/release-nightly.yml @@ -0,0 +1,19 @@ +name: Nightly Release of Scala 3 +run-name: Nightly Release of Scala 3 (${{ github.sha }}) + +on: + workflow_dispatch: + schedule: + - cron: '0 3 * * *' # Every day at 3 AM + +jobs: + + ## TODO: Add the compilation pipeline here + + ## TODO: Add the test pipeline here + + release-maven-artifacts: + # TODO: Add a dependency to the test ad compilation pipeline before releasing + uses: ./.github/workflows/release-maven-artifacts.yml + with: + environment: release-nightly From 03d0a7880e5a59ac5ce814dd555480884519f95f Mon Sep 17 00:00:00 2001 From: Hamza Remmal Date: Sat, 16 Aug 2025 16:06:56 +0200 Subject: [PATCH 027/101] chore: use the variables in the workflow --- .github/workflows/release-maven-artifacts.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/release-maven-artifacts.yml b/.github/workflows/release-maven-artifacts.yml index 86d1fbb23029..dd774602d262 100644 --- a/.github/workflows/release-maven-artifacts.yml +++ b/.github/workflows/release-maven-artifacts.yml @@ -21,3 +21,11 @@ jobs: - uses: sbt/setup-sbt@v1 - name: Publish Artifacts to the Maven Repository run : sbt scala3-bootstrapped-new/publish + env: + MAVEN_REPOSITORY_USER : ${{ secrets.MAVEN_REPOSITORY_USER }} + MAVEN_REPOSITORY_TOKEN: ${{ secrets.MAVEN_REPOSITORY_TOKEN }} + MAVEN_REPOSITORY_HOST : ${{ vars.MAVEN_REPOSITORY_HOST }} + MAVEN_REPOSITORY_REALM: ${{ vars.MAVEN_REPOSITORY_REALM }} + MAVEN_REPOSITORY_URL : ${{ vars.MAVEN_REPOSITORY_URL }} + NEWNIGHTLY : ${{ vars.NEWNIGHTLY }} + NIGHTLYBUILD : ${{ vars.NIGHTLYBUILD }} From f2ee8a78f0701acee1cfcf5d4c48f0f61055a47d Mon Sep 17 00:00:00 2001 From: Hamza Remmal Date: Sat, 16 Aug 2025 16:40:27 +0200 Subject: [PATCH 028/101] chore: allow access to secrets --- .github/workflows/release-nightly.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release-nightly.yml b/.github/workflows/release-nightly.yml index f1a594bf64b4..5e8dc2af0534 100644 --- a/.github/workflows/release-nightly.yml +++ b/.github/workflows/release-nightly.yml @@ -17,3 +17,4 @@ jobs: uses: ./.github/workflows/release-maven-artifacts.yml with: environment: release-nightly + secrets: inherit From aae5acb3eaa032d44d55a588c93cd36f59debf1d Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Sat, 16 Aug 2025 20:00:30 +0100 Subject: [PATCH 029/101] Add capture checking annotation to Scala 3 Standard Library (#23688) ## List of changes - Capture checking enabled for all files except listed in the next section - `boundary` is now capture-checked, preventing `Label` from being leaked outside of its corresponding `boundary`. Requires #23507. - ` ## Files missing capture checking - [ ] `Tuple.scala`, waiting for #23695 to reach the next base version - [ ] `Mirror.scala`, which requires auto-generated Mirrors to be compatible with extending one of the `caps.Capability` subtraits. Currently the plan is to simply ban case classes. --------- Co-authored-by: Hamza Remmal --- .../src/dotty/tools/dotc/cc/Synthetics.scala | 2 +- .../src/dotty/tools/dotc/core/StdNames.scala | 1 + .../tools/dotc/transform/DropBreaks.scala | 15 +++++- .../backend/jvm/LabelBytecodeTests.scala | 2 +- library/src/scala/CanEqual.scala | 2 + library/src/scala/CanThrow.scala | 2 + library/src/scala/Conversion.scala | 16 +++--- library/src/scala/IArray.scala | 54 ++++++++++--------- library/src/scala/NamedTuple.scala | 2 + library/src/scala/PolyFunction.scala | 2 + library/src/scala/Precise.scala | 3 ++ library/src/scala/Selectable.scala | 1 + library/src/scala/Tuple.scala | 1 + .../scala/annotation/MacroAnnotation.scala | 2 + .../scala/annotation/RefiningAnnotation.scala | 2 + library/src/scala/annotation/alpha.scala | 2 + library/src/scala/annotation/capability.scala | 2 + .../scala/annotation/constructorOnly.scala | 2 + .../src/scala/annotation/experimental.scala | 2 + library/src/scala/annotation/init.scala | 2 + .../src/scala/annotation/internal/$into.scala | 3 ++ .../src/scala/annotation/internal/Alias.scala | 2 + .../internal/AnnotationDefault.scala | 2 + .../internal/AssignedNonLocally.scala | 2 + .../src/scala/annotation/internal/Body.scala | 2 + .../annotation/internal/CaptureChecked.scala | 2 + .../src/scala/annotation/internal/Child.scala | 2 + .../internal/ContextResultCount.scala | 2 + .../annotation/internal/ErasedParam.scala | 2 + .../annotation/internal/InlineParam.scala | 2 + .../internal/MappedAlternative.scala | 2 + .../internal/ProvisionalSuperClass.scala | 2 + .../scala/annotation/internal/Repeated.scala | 2 + .../annotation/internal/RuntimeChecked.scala | 2 + .../annotation/internal/SourceFile.scala | 2 + .../annotation/internal/WithPureFuns.scala | 2 + .../annotation/internal/WitnessNames.scala | 2 + .../scala/annotation/internal/preview.scala | 1 + .../annotation/internal/reachCapability.scala | 2 + .../internal/readOnlyCapability.scala | 2 + .../internal/requiresCapability.scala | 2 + .../scala/annotation/internal/sharable.scala | 2 + .../scala/annotation/internal/unshared.scala | 2 + .../src/scala/annotation/publicInBinary.scala | 2 + library/src/scala/annotation/retains.scala | 2 + .../src/scala/annotation/retainsByName.scala | 2 + library/src/scala/annotation/static.scala | 2 + library/src/scala/annotation/targetName.scala | 2 + .../src/scala/annotation/threadUnsafe.scala | 2 + .../scala/annotation/transparentTrait.scala | 2 + .../unchecked/uncheckedCaptures.scala | 2 + library/src/scala/annotation/unroll.scala | 2 + library/src/scala/caps/Pure.scala | 2 + library/src/scala/caps/package.scala | 2 + library/src/scala/compiletime/ops/any.scala | 2 + .../src/scala/compiletime/ops/boolean.scala | 2 + .../src/scala/compiletime/ops/double.scala | 2 + library/src/scala/compiletime/ops/float.scala | 2 + library/src/scala/compiletime/ops/int.scala | 2 + library/src/scala/compiletime/ops/long.scala | 2 + .../src/scala/compiletime/ops/string.scala | 2 + library/src/scala/compiletime/package.scala | 2 + .../src/scala/compiletime/testing/Error.scala | 2 + .../scala/compiletime/testing/ErrorKind.scala | 2 + .../scala/compiletime/testing/package.scala | 2 + library/src/scala/deriving/Mirror.scala | 4 +- library/src/scala/main.scala | 2 + library/src/scala/quoted/Expr.scala | 2 + library/src/scala/quoted/ExprMap.scala | 2 + library/src/scala/quoted/Exprs.scala | 2 + library/src/scala/quoted/FromExpr.scala | 2 + library/src/scala/quoted/Quotes.scala | 2 + library/src/scala/quoted/ToExpr.scala | 2 + library/src/scala/quoted/Type.scala | 2 + library/src/scala/quoted/Varargs.scala | 2 + library/src/scala/quoted/runtime/Expr.scala | 2 + .../src/scala/quoted/runtime/Patterns.scala | 2 + .../scala/quoted/runtime/QuoteMatching.scala | 2 + .../scala/quoted/runtime/QuoteUnpickler.scala | 2 + .../scala/quoted/runtime/SplicedType.scala | 2 + .../quoted/runtime/StopMacroExpansion.scala | 2 + library/src/scala/reflect/Enum.scala | 2 + library/src/scala/reflect/Selectable.scala | 2 + library/src/scala/reflect/TypeTest.scala | 2 + library/src/scala/reflect/Typeable.scala | 2 + library/src/scala/runtime/$throws.scala | 3 ++ library/src/scala/runtime/Arrays.scala | 2 + library/src/scala/runtime/EnumValue.scala | 2 + library/src/scala/runtime/FunctionXXL.scala | 2 + library/src/scala/runtime/LazyVals.scala | 3 +- library/src/scala/runtime/MatchCase.scala | 2 + library/src/scala/runtime/Scala3RunTime.scala | 2 + library/src/scala/runtime/TupleMirror.scala | 2 + library/src/scala/runtime/TupleXXL.scala | 2 + .../src/scala/runtime/TupledFunctions.scala | 2 + library/src/scala/runtime/Tuples.scala | 2 + library/src/scala/runtime/TypeBox.scala | 2 + .../src/scala/runtime/coverage/Invoker.scala | 2 + .../scala/runtime/stdLibPatches/Predef.scala | 2 + .../runtime/stdLibPatches/language.scala | 2 + .../src/scala/util/CommandLineParser.scala | 8 +-- library/src/scala/util/FromDigits.scala | 1 + library/src/scala/util/NotGiven.scala | 2 + library/src/scala/util/TupledFunction.scala | 4 +- library/src/scala/util/boundary.scala | 21 ++++++-- .../scala/util/control/NonLocalReturns.scala | 4 +- tests/neg-custom-args/captures/boundary.check | 50 +++++++++++++++++ tests/neg-custom-args/captures/boundary.scala | 8 +++ tests/neg-macros/i19842-a.check | 2 +- tests/neg-macros/i19842-b.check | 2 +- tests/neg-macros/i23008.check | 4 +- tests/run/assert-stack.check | 4 +- 112 files changed, 337 insertions(+), 54 deletions(-) create mode 100644 tests/neg-custom-args/captures/boundary.check create mode 100644 tests/neg-custom-args/captures/boundary.scala diff --git a/compiler/src/dotty/tools/dotc/cc/Synthetics.scala b/compiler/src/dotty/tools/dotc/cc/Synthetics.scala index bb2228932cb8..a32cb76fc127 100644 --- a/compiler/src/dotty/tools/dotc/cc/Synthetics.scala +++ b/compiler/src/dotty/tools/dotc/cc/Synthetics.scala @@ -168,4 +168,4 @@ object Synthetics: transformCompareCaptures) end transform -end Synthetics \ No newline at end of file +end Synthetics diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index a061220b9b69..18873dfa83af 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -531,6 +531,7 @@ object StdNames { val isEmpty: N = "isEmpty" val isInstanceOf_ : N = "isInstanceOf" val isInstanceOfPM: N = "$isInstanceOf$" + val isSameLabelAs : N = "isSameLabelAs" val java: N = "java" val key: N = "key" val label: N = "label" diff --git a/compiler/src/dotty/tools/dotc/transform/DropBreaks.scala b/compiler/src/dotty/tools/dotc/transform/DropBreaks.scala index 5f26a6af6c3c..d5f555763bb4 100644 --- a/compiler/src/dotty/tools/dotc/transform/DropBreaks.scala +++ b/compiler/src/dotty/tools/dotc/transform/DropBreaks.scala @@ -27,6 +27,8 @@ object DropBreaks: /** The number of other references to associated label */ var otherRefs: Int = 0 + override def toString() = s"LabelUsage($goto, $enclMeth, returnRefs = $returnRefs, otherRefs = $otherRefs)" + private val LabelUsages = new Property.Key[Map[Symbol, LabelUsage]] private val ShadowedLabels = new Property.Key[Set[Symbol]] @@ -63,13 +65,22 @@ class DropBreaks extends MiniPhase: */ def unapply(expr: Tree)(using Context): Option[(Symbol, Symbol)] = stripTyped(expr) match case If( - Apply(Select(Select(ex: Ident, label), eq), (lbl @ Ident(local)) :: Nil), + Apply(Select(ex: Ident, isSameLabelAs), (lbl @ Ident(local)) :: Nil), Select(ex2: Ident, value), Apply(throww, (ex3: Ident) :: Nil)) - if label == nme.label && eq == nme.eq && local == nme.local && value == nme.value + if isSameLabelAs == nme.isSameLabelAs && local == nme.local && value == nme.value && throww.symbol == defn.throwMethod && ex.symbol == ex2.symbol && ex.symbol == ex3.symbol => Some((ex.symbol, lbl.symbol)) + case If( + Apply(Select(ex: Ident, isSameLabelAs), (lbl @ Ident(local)) :: Nil), + Literal(_), // in the case where the value is constant folded + Apply(throww, (ex3: Ident) :: Nil)) + if isSameLabelAs == nme.isSameLabelAs && local == nme.local + && throww.symbol == defn.throwMethod + && ex.symbol == ex3.symbol + && expr.tpe.isSingleton => + Some((ex.symbol, lbl.symbol)) case _ => None end GuardedThrow diff --git a/compiler/test/dotty/tools/backend/jvm/LabelBytecodeTests.scala b/compiler/test/dotty/tools/backend/jvm/LabelBytecodeTests.scala index aea567b87f91..6a420e2bb56e 100644 --- a/compiler/test/dotty/tools/backend/jvm/LabelBytecodeTests.scala +++ b/compiler/test/dotty/tools/backend/jvm/LabelBytecodeTests.scala @@ -21,7 +21,7 @@ class LabelBytecodeTests extends DottyBytecodeTest { """val local = boundary.Label[Long]() |try break(5L)(using local) |catch case ex: boundary.Break[Long] @unchecked => - | if ex.label eq local then ex.value + | if ex.isSameLabelAs(local) then ex.value | else throw ex """.stripMargin, "Long", diff --git a/library/src/scala/CanEqual.scala b/library/src/scala/CanEqual.scala index 8c331bb21b43..12cfdfc6f5fd 100644 --- a/library/src/scala/CanEqual.scala +++ b/library/src/scala/CanEqual.scala @@ -1,5 +1,7 @@ package scala +import language.experimental.captureChecking + import annotation.implicitNotFound import scala.collection.{Seq, Set, Map} diff --git a/library/src/scala/CanThrow.scala b/library/src/scala/CanThrow.scala index d55d631235e7..0852ce804cd7 100644 --- a/library/src/scala/CanThrow.scala +++ b/library/src/scala/CanThrow.scala @@ -2,6 +2,8 @@ package scala import language.experimental.erasedDefinitions import annotation.{implicitNotFound, experimental, capability} +import language.experimental.captureChecking + /** A capability class that allows to throw exception `E`. When used with the * experimental.saferExceptions feature, a `throw Ex()` expression will require * a given of class `CanThrow[Ex]` to be available. diff --git a/library/src/scala/Conversion.scala b/library/src/scala/Conversion.scala index cbae3448dab9..282566063b6e 100644 --- a/library/src/scala/Conversion.scala +++ b/library/src/scala/Conversion.scala @@ -1,5 +1,7 @@ package scala +import language.experimental.captureChecking + /** A class for implicit values that can serve as implicit conversions. * The implicit resolution algorithm will act as if there existed * the additional implicit definition: @@ -23,12 +25,13 @@ package scala */ @java.lang.FunctionalInterface abstract class Conversion[-T, +U] extends Function1[T, U]: - /** Convert value `x` of type `T` to type `U` */ - def apply(x: T): U + self => + /** Convert value `x` of type `T` to type `U` */ + def apply(x: T): U - extension (x: T) - /** `x.convert` converts a value `x` of type `T` to type `U` */ - def convert = this(x) + extension (x: T) + /** `x.convert` converts a value `x` of type `T` to type `U` */ + def convert = this(x) object Conversion: import annotation.experimental @@ -44,5 +47,4 @@ object Conversion: /** Unwrap an `into` */ extension [T](x: into[T]) @experimental def underlying: T = x - -end Conversion \ No newline at end of file +end Conversion diff --git a/library/src/scala/IArray.scala b/library/src/scala/IArray.scala index 887ca517ef2b..12eac3985626 100644 --- a/library/src/scala/IArray.scala +++ b/library/src/scala/IArray.scala @@ -1,6 +1,8 @@ package scala import reflect.ClassTag +import language.experimental.captureChecking + import scala.collection.{LazyZip2, SeqView, Searching, Stepper, StepperShape} import scala.collection.immutable.ArraySeq import scala.collection.mutable.{ArrayBuilder, Builder} @@ -92,12 +94,12 @@ object IArray: /** Builds a new array by applying a function to all elements of this array * and using the elements of the resulting collections. */ - extension [T](arr: IArray[T]) def flatMap[U: ClassTag](f: T => IterableOnce[U]): IArray[U] = + extension [T](arr: IArray[T]) def flatMap[U: ClassTag](f: T => IterableOnce[U]^): IArray[U] = genericArrayOps(arr).flatMap(f) /** Flattens a two-dimensional array by concatenating all its rows * into a single array. */ - extension [T](arr: IArray[T]) def flatten[U](using asIterable: T => Iterable[U], ct: ClassTag[U]): IArray[U] = + extension [T](arr: IArray[T]) def flatten[U](using asIterable: T => Iterable[U]^, ct: ClassTag[U]): IArray[U] = genericArrayOps(arr).flatten /** Folds the elements of this array using the specified associative binary operator. */ @@ -222,7 +224,7 @@ object IArray: genericArrayOps(arr).sortWith(f) /** Sorts this array according to an Ordering. */ - extension [T](arr: IArray[T]) def sorted(using math.Ordering[T]): IArray[T] = + extension [T](arr: IArray[T]) def sorted(using math.Ordering[T]^): IArray[T] = genericArrayOps(arr).sorted /** Splits this array into a prefix/suffix pair according to a predicate. */ @@ -260,42 +262,42 @@ object IArray: def ++[U >: T: ClassTag](suffix: IterableOnce[U]): IArray[U] = genericArrayOps(arr) ++ suffix def :+ [U >: T: ClassTag](x: U): IArray[U] = genericArrayOps(arr) :+ x def :++ [U >: T: ClassTag](suffix: IArray[U]): IArray[U] = genericArrayOps(arr) :++ suffix - def :++ [U >: T: ClassTag](suffix: IterableOnce[U]): IArray[U] = genericArrayOps(arr) :++ suffix + def :++ [U >: T: ClassTag](suffix: IterableOnce[U]^): IArray[U] = genericArrayOps(arr) :++ suffix def appended[U >: T: ClassTag](x: U): IArray[U] = genericArrayOps(arr).appended(x) def appendedAll[U >: T: ClassTag](suffix: IArray[U]): IArray[U] = genericArrayOps(arr).appendedAll(suffix) - def appendedAll[U >: T: ClassTag](suffix: IterableOnce[U]): IArray[U] = genericArrayOps(arr).appendedAll(suffix) - def collect[U: ClassTag](pf: PartialFunction[T, U]): IArray[U] = genericArrayOps(arr).collect(pf) - def collectFirst[U](f: PartialFunction[T, U]): Option[U] = genericArrayOps(arr).collectFirst(f) + def appendedAll[U >: T: ClassTag](suffix: IterableOnce[U]^): IArray[U] = genericArrayOps(arr).appendedAll(suffix) + def collect[U: ClassTag](pf: PartialFunction[T, U]^): IArray[U] = genericArrayOps(arr).collect(pf) + def collectFirst[U](f: PartialFunction[T, U]^): Option[U] = genericArrayOps(arr).collectFirst(f) def combinations(n: Int): Iterator[IArray[T]] = genericArrayOps(arr).combinations(n) def concat[U >: T: ClassTag](suffix: IArray[U]): IArray[U] = genericArrayOps(arr).concat(suffix) - def concat[U >: T: ClassTag](suffix: IterableOnce[U]): IArray[U] = genericArrayOps(arr).concat(suffix) + def concat[U >: T: ClassTag](suffix: IterableOnce[U]^): IArray[U] = genericArrayOps(arr).concat(suffix) def diff[U >: T](that: IArray[U]): IArray[T] = genericArrayOps(arr).diff(that.toSeq) - def diff[U >: T](that: Seq[U]): IArray[T] = genericArrayOps(arr).diff(that) + def diff[U >: T](that: Seq[U]^): IArray[T] = genericArrayOps(arr).diff(that) def distinct: IArray[T] = genericArrayOps(arr).distinct def distinctBy[U](f: T => U): IArray[T] = genericArrayOps(arr).distinctBy(f) def startsWith[U >: T](that: IArray[U]): Boolean = genericArrayOps(arr).startsWith(that, 0) def startsWith[U >: T](that: IArray[U], offset: Int): Boolean = genericArrayOps(arr).startsWith(that, offset) - def startsWith[U >: T](that: IterableOnce[U]): Boolean = genericArrayOps(arr).startsWith(that, 0) - def startsWith[U >: T](that: IterableOnce[U], offset: Int): Boolean = genericArrayOps(arr).startsWith(that, offset) + def startsWith[U >: T](that: IterableOnce[U]^): Boolean = genericArrayOps(arr).startsWith(that, 0) + def startsWith[U >: T](that: IterableOnce[U]^, offset: Int): Boolean = genericArrayOps(arr).startsWith(that, offset) def endsWith[U >: T](that: IArray[U]): Boolean = genericArrayOps(arr).endsWith(that) - def endsWith[U >: T](that: Iterable[U]): Boolean = genericArrayOps(arr).endsWith(that) + def endsWith[U >: T](that: Iterable[U]^): Boolean = genericArrayOps(arr).endsWith(that) def groupBy[K](f: T => K): Map[K, IArray[T]] = genericArrayOps(arr).groupBy(f) def groupMap[K, U: ClassTag](key: T => K)(f: T => U): Map[K, IArray[U]] = genericArrayOps(arr).groupMap(key)(f) def grouped(size: Int): Iterator[IArray[T]] = genericArrayOps(arr).grouped(size) def inits: Iterator[IArray[T]] = genericArrayOps(arr).inits def intersect[U >: T](that: IArray[U]): IArray[T] = genericArrayOps(arr).intersect(that) - def intersect[U >: T](that: Seq[U]): IArray[T] = genericArrayOps(arr).intersect(that) + def intersect[U >: T](that: Seq[U]^): IArray[T] = genericArrayOps(arr).intersect(that) def lazyZip[U](that: IArray[U]): LazyZip2[T, U, IArray[T]] = genericArrayOps(arr).lazyZip[U](that).asInstanceOf[LazyZip2[T, U, IArray[T]]] - def lazyZip[U](that: Iterable[U]): LazyZip2[T, U, IArray[T]] = genericArrayOps(arr).lazyZip[U](that).asInstanceOf[LazyZip2[T, U, IArray[T]]] + def lazyZip[U](that: Iterable[U]^): LazyZip2[T, U, IArray[T]] = genericArrayOps(arr).lazyZip[U](that).asInstanceOf[LazyZip2[T, U, IArray[T]]] def lengthCompare(len: Int): Int = genericArrayOps(arr).lengthCompare(len) def padTo[U >: T: ClassTag](len: Int, elem: U): IArray[U] = genericArrayOps(arr).padTo(len, elem) def partitionMap[T1: ClassTag, T2: ClassTag](f: T => Either[T1, T2]): (IArray[T1], IArray[T2]) = genericArrayOps(arr).partitionMap(f) def patch[U >: T: ClassTag](from: Int, other: IterableOnce[U], replaced: Int): IArray[U] = genericArrayOps(arr).patch(from, other, replaced) def permutations: Iterator[IArray[T]] = genericArrayOps(arr).permutations def prepended[U >: T: ClassTag](x: U): IArray[U] = genericArrayOps(arr).prepended(x) - def prependedAll[U >: T: ClassTag](prefix: IterableOnce[U]): IArray[U] = genericArrayOps(arr).prependedAll(prefix) + def prependedAll[U >: T: ClassTag](prefix: IterableOnce[U]^): IArray[U] = genericArrayOps(arr).prependedAll(prefix) def reverseIterator: Iterator[T] = genericArrayOps(arr).reverseIterator - def search[U >: T](elem: U)(using Ordering[U]): Searching.SearchResult = arr.toSeq.search(elem) + def search[U >: T](elem: U)(using Ordering[U]^): Searching.SearchResult = arr.toSeq.search(elem) def search[U >: T](elem: U, from: Int, to: Int)(using Ordering[U]): Searching.SearchResult = arr.toSeq.search(elem, from, to) def sizeCompare(that: IArray[Any]): Int = arr.toSeq.sizeCompare(that) def sizeCompare(that: Iterable[?]): Int = arr.toSeq.sizeCompare(that) @@ -312,14 +314,14 @@ object IArray: def unzip3[T1, T2, T3](using asTriple: T => (T1, T2, T3), ct1: ClassTag[T1], ct2: ClassTag[T2], ct3: ClassTag[T3]): (IArray[T1], IArray[T2], IArray[T3]) = genericArrayOps(arr).unzip3 def updated[U >: T: ClassTag](index: Int, elem: U): IArray[U] = genericArrayOps(arr).updated(index, elem) def view: SeqView[T] = genericArrayOps(arr).view - def withFilter(p: T => Boolean): WithFilter[T] = new WithFilter(p, arr) + def withFilter(p: T => Boolean): WithFilter[T]^{p} = new WithFilter(p, arr) def zip[U](that: IArray[U]): IArray[(T, U)] = genericArrayOps(arr).zip(that) - def zip[U](that: IterableOnce[U]): IArray[(T, U)] = genericArrayOps(arr).zip(that) + def zip[U](that: IterableOnce[U]^): IArray[(T, U)] = genericArrayOps(arr).zip(that) def zipAll[T1 >: T, U](that: IArray[U], thisElem: T1, thatElem: U): IArray[(T1, U)] = genericArrayOps(arr).zipAll(that, thisElem, thatElem) - def zipAll[T1 >: T, U](that: Iterable[U], thisElem: T1, thatElem: U): IArray[(T1, U)] = genericArrayOps(arr).zipAll(that, thisElem, thatElem) + def zipAll[T1 >: T, U](that: Iterable[U]^, thisElem: T1, thatElem: U): IArray[(T1, U)] = genericArrayOps(arr).zipAll(that, thisElem, thatElem) def zipWithIndex: IArray[(T, Int)] = genericArrayOps(arr).zipWithIndex - extension [T, U >: T: ClassTag](prefix: IterableOnce[T]) + extension [T, U >: T: ClassTag](prefix: IterableOnce[T]^) def ++:(arr: IArray[U]): IArray[U] = genericArrayOps(arr).prependedAll(prefix) extension [T, U >: T: ClassTag](prefix: IArray[T]) @@ -444,7 +446,7 @@ object IArray: * @param it the iterable collection * @return an array consisting of elements of the iterable collection */ - def from[A : ClassTag](it: IterableOnce[A]): IArray[A] = + def from[A : ClassTag](it: IterableOnce[A]^): IArray[A] = unsafeFromArray(Array.from(it)) def newBuilder[T](using t: ClassTag[T]): Builder[T, IArray[T]] = @@ -606,8 +608,8 @@ object IArray: * @param ys an array of AnyRef * @return true if corresponding elements are equal */ - def equals(xs: IArray[AnyRef], ys: IArray[AnyRef]): Boolean = - Array.equals(xs.asInstanceOf[Array[AnyRef]], ys.asInstanceOf[Array[AnyRef]]) + def equals(xs: IArray[AnyRef^], ys: IArray[AnyRef^]): Boolean = + Array.equals(xs.asInstanceOf[Array[AnyRef^{xs}]], ys.asInstanceOf[Array[AnyRef^{ys}]]) /** Returns a decomposition of the array into a sequence. This supports * a pattern match like `{ case IArray(x,y,z) => println('3 elements')}`. @@ -660,7 +662,7 @@ object IArray: * @return a new array resulting from applying the given collection-valued function * `f` to each element of this array and concatenating the results. */ - def flatMap[U: ClassTag](f: T => IterableOnce[U]): IArray[U] = { + def flatMap[U: ClassTag](f: T => IterableOnce[U]^): IArray[U] = { val b = IArray.newBuilder[U] var i = 0 while(i < xs.length) { @@ -671,11 +673,11 @@ object IArray: b.result() } - def flatMap[BS, U](f: T => BS)(using asIterable: BS => Iterable[U], m: ClassTag[U]): IArray[U] = + def flatMap[BS, U](f: T => BS)(using asIterable: BS => Iterable[U]^, m: ClassTag[U]): IArray[U] = flatMap[U](x => asIterable(f(x))) /** Creates a new non-strict filter which combines this filter with the given predicate. */ - def withFilter(q: T => Boolean): WithFilter[T] = new WithFilter[T](a => p(a) && q(a), xs) + def withFilter(q: T => Boolean): WithFilter[T]^{p, q} = new WithFilter[T](a => p(a) && q(a), xs) end WithFilter diff --git a/library/src/scala/NamedTuple.scala b/library/src/scala/NamedTuple.scala index c63ad29c56e9..f88f7760365b 100644 --- a/library/src/scala/NamedTuple.scala +++ b/library/src/scala/NamedTuple.scala @@ -1,6 +1,8 @@ package scala import compiletime.ops.boolean.* +import language.experimental.captureChecking + object NamedTuple: /** The type to which named tuples get mapped to. For instance, diff --git a/library/src/scala/PolyFunction.scala b/library/src/scala/PolyFunction.scala index 44278b6cffde..f37b1f9eab45 100644 --- a/library/src/scala/PolyFunction.scala +++ b/library/src/scala/PolyFunction.scala @@ -1,5 +1,7 @@ package scala +import language.experimental.captureChecking + /* (EXPERIMENTAL) NEW DOCUMENTATION: This should be used when we stabilize erased definitions or other generalize other function types. * * Marker trait for many (poly) function types. diff --git a/library/src/scala/Precise.scala b/library/src/scala/Precise.scala index f8a8dd6b47f4..838327c79409 100644 --- a/library/src/scala/Precise.scala +++ b/library/src/scala/Precise.scala @@ -1,4 +1,7 @@ package scala + +import language.experimental.captureChecking + import annotation.experimental import language.experimental.erasedDefinitions diff --git a/library/src/scala/Selectable.scala b/library/src/scala/Selectable.scala index 74004a350679..801ab1ea4bc7 100644 --- a/library/src/scala/Selectable.scala +++ b/library/src/scala/Selectable.scala @@ -1,5 +1,6 @@ package scala +import language.experimental.captureChecking /** A marker trait for objects that support structural selection via * `selectDynamic` and `applyDynamic` diff --git a/library/src/scala/Tuple.scala b/library/src/scala/Tuple.scala index 57d1572772e2..6d6f234a817a 100644 --- a/library/src/scala/Tuple.scala +++ b/library/src/scala/Tuple.scala @@ -1,5 +1,6 @@ package scala +// import language.experimental.captureChecking import annotation.showAsInfix import compiletime.* import compiletime.ops.int.* diff --git a/library/src/scala/annotation/MacroAnnotation.scala b/library/src/scala/annotation/MacroAnnotation.scala index ffaa0cf88464..7f2bd56c56c1 100644 --- a/library/src/scala/annotation/MacroAnnotation.scala +++ b/library/src/scala/annotation/MacroAnnotation.scala @@ -2,6 +2,8 @@ package scala package annotation +import language.experimental.captureChecking + import scala.quoted.* /** Base trait for macro annotation implementation. diff --git a/library/src/scala/annotation/RefiningAnnotation.scala b/library/src/scala/annotation/RefiningAnnotation.scala index 65761d5234d4..6fb2960b9685 100644 --- a/library/src/scala/annotation/RefiningAnnotation.scala +++ b/library/src/scala/annotation/RefiningAnnotation.scala @@ -1,5 +1,7 @@ package scala.annotation +import language.experimental.captureChecking + /** A base trait for annotations that yield proper subtypes of the types they annotate. * Refining annotations are more "sticky" than normal ones. They are conceptually kept * around when normal refinements would also not be stripped away. diff --git a/library/src/scala/annotation/alpha.scala b/library/src/scala/annotation/alpha.scala index ac2a11c5b650..932713ce2031 100644 --- a/library/src/scala/annotation/alpha.scala +++ b/library/src/scala/annotation/alpha.scala @@ -1,5 +1,7 @@ package scala.annotation +import language.experimental.captureChecking + /** An annotation that defines an external name for a definition. * If an `alpha(extname)` annotation is given for a method or some other * definition, its implementation will use the name `extname` instead of diff --git a/library/src/scala/annotation/capability.scala b/library/src/scala/annotation/capability.scala index d3453e3c8168..63428b509b77 100644 --- a/library/src/scala/annotation/capability.scala +++ b/library/src/scala/annotation/capability.scala @@ -1,6 +1,8 @@ package scala.annotation import annotation.experimental +import language.experimental.captureChecking + /** Marks an annotated class as a capability. * If the annotation is present and -Ycc is set, any (possibly aliased * or refined) instance of the class type is implicitly augmented with diff --git a/library/src/scala/annotation/constructorOnly.scala b/library/src/scala/annotation/constructorOnly.scala index 8fefc3b012dd..4959b7c503b6 100644 --- a/library/src/scala/annotation/constructorOnly.scala +++ b/library/src/scala/annotation/constructorOnly.scala @@ -8,6 +8,8 @@ package scala.annotation +import language.experimental.captureChecking + import scala.annotation.meta.* /** An annotation that goes on parameters of classes or traits. It asserts diff --git a/library/src/scala/annotation/experimental.scala b/library/src/scala/annotation/experimental.scala index 634cfe12db7f..ee91a6408b4b 100644 --- a/library/src/scala/annotation/experimental.scala +++ b/library/src/scala/annotation/experimental.scala @@ -1,5 +1,7 @@ package scala.annotation +import language.experimental.captureChecking + /** An annotation that can be used to mark a definition as experimental. * * @see [[https://dotty.epfl.ch/docs/reference/other-new-features/experimental-defs]] diff --git a/library/src/scala/annotation/init.scala b/library/src/scala/annotation/init.scala index 7dea418664e6..c2b12041e1db 100644 --- a/library/src/scala/annotation/init.scala +++ b/library/src/scala/annotation/init.scala @@ -1,5 +1,7 @@ package scala.annotation +import language.experimental.captureChecking + /** Annotations to control the behavior of the compiler check for safe initialization of static obects. * * Programmers usually do not need to use any annotations. They are intended for complex initialization diff --git a/library/src/scala/annotation/internal/$into.scala b/library/src/scala/annotation/internal/$into.scala index bad0e399d389..565aad3f2aa7 100644 --- a/library/src/scala/annotation/internal/$into.scala +++ b/library/src/scala/annotation/internal/$into.scala @@ -1,4 +1,7 @@ package scala.annotation.internal + +import language.experimental.captureChecking + import annotation.experimental /** An internal annotation on (part of) a parameter type that serves as a marker where diff --git a/library/src/scala/annotation/internal/Alias.scala b/library/src/scala/annotation/internal/Alias.scala index 5c8f7d4b9ffa..3d3de9f27c00 100644 --- a/library/src/scala/annotation/internal/Alias.scala +++ b/library/src/scala/annotation/internal/Alias.scala @@ -1,5 +1,7 @@ package scala.annotation.internal +import language.experimental.captureChecking + import scala.annotation.Annotation /** An annotation to record a Scala2 pickled alias. diff --git a/library/src/scala/annotation/internal/AnnotationDefault.scala b/library/src/scala/annotation/internal/AnnotationDefault.scala index 5280d091c1ec..5ea16e60a76c 100644 --- a/library/src/scala/annotation/internal/AnnotationDefault.scala +++ b/library/src/scala/annotation/internal/AnnotationDefault.scala @@ -1,5 +1,7 @@ package scala.annotation.internal +import language.experimental.captureChecking + import scala.annotation.Annotation /** An annotation to tag Java annotation default values */ diff --git a/library/src/scala/annotation/internal/AssignedNonLocally.scala b/library/src/scala/annotation/internal/AssignedNonLocally.scala index 8095ba91cfee..075f2f4453c8 100644 --- a/library/src/scala/annotation/internal/AssignedNonLocally.scala +++ b/library/src/scala/annotation/internal/AssignedNonLocally.scala @@ -1,6 +1,8 @@ package scala.annotation package internal +import language.experimental.captureChecking + /** An annotation to indicate that a private `var` was assigned with a prefix * other than the `this` type of its owner. */ diff --git a/library/src/scala/annotation/internal/Body.scala b/library/src/scala/annotation/internal/Body.scala index b6aa0c0fb616..682c26388cdf 100644 --- a/library/src/scala/annotation/internal/Body.scala +++ b/library/src/scala/annotation/internal/Body.scala @@ -1,5 +1,7 @@ package scala.annotation.internal +import language.experimental.captureChecking + import scala.annotation.Annotation /** The class associated with a `BodyAnnotation`, which indicates diff --git a/library/src/scala/annotation/internal/CaptureChecked.scala b/library/src/scala/annotation/internal/CaptureChecked.scala index 0bc6d628a542..a41c6364d46d 100644 --- a/library/src/scala/annotation/internal/CaptureChecked.scala +++ b/library/src/scala/annotation/internal/CaptureChecked.scala @@ -1,6 +1,8 @@ package scala.annotation package internal +import language.experimental.captureChecking + /** A marker annotation on a toplevel class that indicates * that the class was typed with the captureChecking language import. */ diff --git a/library/src/scala/annotation/internal/Child.scala b/library/src/scala/annotation/internal/Child.scala index b0d380e2460d..a6ec1f13b699 100644 --- a/library/src/scala/annotation/internal/Child.scala +++ b/library/src/scala/annotation/internal/Child.scala @@ -1,6 +1,8 @@ package scala.annotation package internal +import language.experimental.captureChecking + /** An annotation to indicate a child class or object of the annotated class. * E.g. if we have * diff --git a/library/src/scala/annotation/internal/ContextResultCount.scala b/library/src/scala/annotation/internal/ContextResultCount.scala index ecaedb65bf4f..8469a1171cfc 100644 --- a/library/src/scala/annotation/internal/ContextResultCount.scala +++ b/library/src/scala/annotation/internal/ContextResultCount.scala @@ -1,6 +1,8 @@ package scala.annotation package internal +import language.experimental.captureChecking + /** An annotation that's automatically added for methods * that have one or more nested context closures as their right hand side. * The parameter `n` is an Int Literal that tells how many nested closures diff --git a/library/src/scala/annotation/internal/ErasedParam.scala b/library/src/scala/annotation/internal/ErasedParam.scala index a5ed192820c3..e1482292464a 100644 --- a/library/src/scala/annotation/internal/ErasedParam.scala +++ b/library/src/scala/annotation/internal/ErasedParam.scala @@ -1,5 +1,7 @@ package scala.annotation package internal +import language.experimental.captureChecking + /** An annotation produced by Namer to indicate an erased parameter */ final class ErasedParam() extends Annotation diff --git a/library/src/scala/annotation/internal/InlineParam.scala b/library/src/scala/annotation/internal/InlineParam.scala index 0b3649e89da9..a95fd8363eee 100644 --- a/library/src/scala/annotation/internal/InlineParam.scala +++ b/library/src/scala/annotation/internal/InlineParam.scala @@ -1,5 +1,7 @@ package scala.annotation.internal +import language.experimental.captureChecking + import scala.annotation.Annotation /** An annotation produced by Namer to indicate an inline parameter */ diff --git a/library/src/scala/annotation/internal/MappedAlternative.scala b/library/src/scala/annotation/internal/MappedAlternative.scala index 19bd84df633c..72a545a2898d 100644 --- a/library/src/scala/annotation/internal/MappedAlternative.scala +++ b/library/src/scala/annotation/internal/MappedAlternative.scala @@ -1,6 +1,8 @@ package scala.annotation package internal +import language.experimental.captureChecking + /** An annotation added by overloading resoluton to mapped symbols that * explore deeper into the types of the opverloaded alternatives. * Its tree is a TypeTree with two parameters which are both needed to diff --git a/library/src/scala/annotation/internal/ProvisionalSuperClass.scala b/library/src/scala/annotation/internal/ProvisionalSuperClass.scala index 147b7b087c5a..96b8d42c2240 100644 --- a/library/src/scala/annotation/internal/ProvisionalSuperClass.scala +++ b/library/src/scala/annotation/internal/ProvisionalSuperClass.scala @@ -1,6 +1,8 @@ package scala.annotation package internal +import language.experimental.captureChecking + /** An annotation to record a provisional super class */ class ProvisionalSuperClass extends StaticAnnotation diff --git a/library/src/scala/annotation/internal/Repeated.scala b/library/src/scala/annotation/internal/Repeated.scala index 75eb3bc25f1a..fd01f0512b67 100644 --- a/library/src/scala/annotation/internal/Repeated.scala +++ b/library/src/scala/annotation/internal/Repeated.scala @@ -1,5 +1,7 @@ package scala.annotation.internal +import language.experimental.captureChecking + import scala.annotation.Annotation /** An annotation produced by desugaring to indicate that a diff --git a/library/src/scala/annotation/internal/RuntimeChecked.scala b/library/src/scala/annotation/internal/RuntimeChecked.scala index ed83936dc473..6612b1a904fc 100644 --- a/library/src/scala/annotation/internal/RuntimeChecked.scala +++ b/library/src/scala/annotation/internal/RuntimeChecked.scala @@ -1,5 +1,7 @@ package scala.annotation.internal +import language.experimental.captureChecking + import scala.annotation.Annotation import scala.annotation.experimental diff --git a/library/src/scala/annotation/internal/SourceFile.scala b/library/src/scala/annotation/internal/SourceFile.scala index b203869cf383..16003869dcc7 100644 --- a/library/src/scala/annotation/internal/SourceFile.scala +++ b/library/src/scala/annotation/internal/SourceFile.scala @@ -1,5 +1,7 @@ package scala.annotation.internal +import language.experimental.captureChecking + import scala.annotation.Annotation /** An annotation to record a Scala2 pickled alias. diff --git a/library/src/scala/annotation/internal/WithPureFuns.scala b/library/src/scala/annotation/internal/WithPureFuns.scala index 979dd7c405cc..9b28959423e5 100644 --- a/library/src/scala/annotation/internal/WithPureFuns.scala +++ b/library/src/scala/annotation/internal/WithPureFuns.scala @@ -1,6 +1,8 @@ package scala.annotation package internal +import language.experimental.captureChecking + /** A marker annotation on a toplevel class that indicates * that the class was typed with the pureFunctions language import. */ diff --git a/library/src/scala/annotation/internal/WitnessNames.scala b/library/src/scala/annotation/internal/WitnessNames.scala index 3921c2083617..e2a17958f354 100644 --- a/library/src/scala/annotation/internal/WitnessNames.scala +++ b/library/src/scala/annotation/internal/WitnessNames.scala @@ -1,6 +1,8 @@ package scala.annotation package internal +import language.experimental.captureChecking + /** An annotation that is used for marking type definitions that should get * context bound companions. The scheme is as follows: * diff --git a/library/src/scala/annotation/internal/preview.scala b/library/src/scala/annotation/internal/preview.scala index a6e797d78e97..2bae60d0eee6 100644 --- a/library/src/scala/annotation/internal/preview.scala +++ b/library/src/scala/annotation/internal/preview.scala @@ -1,6 +1,7 @@ package scala.annotation package internal +import language.experimental.captureChecking /** An annotation that can be used to mark a definition as preview. * diff --git a/library/src/scala/annotation/internal/reachCapability.scala b/library/src/scala/annotation/internal/reachCapability.scala index 9f5e344adbe9..cddc19011129 100644 --- a/library/src/scala/annotation/internal/reachCapability.scala +++ b/library/src/scala/annotation/internal/reachCapability.scala @@ -1,6 +1,8 @@ package scala.annotation package internal +import language.experimental.captureChecking + /** An annotation that marks a capture ref as a reach capability. * `x*` is encoded as `x.type @reachCapability` */ diff --git a/library/src/scala/annotation/internal/readOnlyCapability.scala b/library/src/scala/annotation/internal/readOnlyCapability.scala index 8e939aea6bb9..516543dba053 100644 --- a/library/src/scala/annotation/internal/readOnlyCapability.scala +++ b/library/src/scala/annotation/internal/readOnlyCapability.scala @@ -1,6 +1,8 @@ package scala.annotation package internal +import language.experimental.captureChecking + /** An annotation that marks a capture ref as a read-only capability. * `x.rd` is encoded as `x.type @readOnlyCapability` */ diff --git a/library/src/scala/annotation/internal/requiresCapability.scala b/library/src/scala/annotation/internal/requiresCapability.scala index 56fca22d2982..94653a8b0890 100644 --- a/library/src/scala/annotation/internal/requiresCapability.scala +++ b/library/src/scala/annotation/internal/requiresCapability.scala @@ -1,6 +1,8 @@ package scala.annotation.internal import annotation.StaticAnnotation +import language.experimental.captureChecking + /** An annotation to record a required capaility in the type of a throws */ class requiresCapability(capability: Any) extends StaticAnnotation diff --git a/library/src/scala/annotation/internal/sharable.scala b/library/src/scala/annotation/internal/sharable.scala index e2d6dcc61beb..862c57a383a3 100644 --- a/library/src/scala/annotation/internal/sharable.scala +++ b/library/src/scala/annotation/internal/sharable.scala @@ -1,5 +1,7 @@ package scala.annotation.internal +import language.experimental.captureChecking + import scala.annotation.Annotation /** An annotation indicating to `-Ycheck:reentrant` that a class or val can be safely shared. diff --git a/library/src/scala/annotation/internal/unshared.scala b/library/src/scala/annotation/internal/unshared.scala index 89796eba09e3..052e57a18454 100644 --- a/library/src/scala/annotation/internal/unshared.scala +++ b/library/src/scala/annotation/internal/unshared.scala @@ -1,5 +1,7 @@ package scala.annotation.internal +import language.experimental.captureChecking + import scala.annotation.Annotation /** An annotation indicating to `-Ycheck:reentrant` that an object will not be accessed from multiple threads. diff --git a/library/src/scala/annotation/publicInBinary.scala b/library/src/scala/annotation/publicInBinary.scala index a517f085dc7a..133e7f8d06b5 100644 --- a/library/src/scala/annotation/publicInBinary.scala +++ b/library/src/scala/annotation/publicInBinary.scala @@ -1,5 +1,7 @@ package scala.annotation +import language.experimental.captureChecking + /** A binary API is a definition that is annotated with `@publicInBinary`. * This annotation can be placed on `def`, `val`, `lazy val`, `var`, class constructors, `object`, and `given` definitions. * A binary API will be publicly available in the bytecode. Tools like TASTy MiMa will take this into account to check diff --git a/library/src/scala/annotation/retains.scala b/library/src/scala/annotation/retains.scala index 05896b117796..f526a17c053e 100644 --- a/library/src/scala/annotation/retains.scala +++ b/library/src/scala/annotation/retains.scala @@ -1,5 +1,7 @@ package scala.annotation +import language.experimental.captureChecking + /** An annotation that indicates capture of a set of references under capture checking. * * T @retains[x.type | y.type | z.type] diff --git a/library/src/scala/annotation/retainsByName.scala b/library/src/scala/annotation/retainsByName.scala index acc8ec664af3..b01f890f86b9 100644 --- a/library/src/scala/annotation/retainsByName.scala +++ b/library/src/scala/annotation/retainsByName.scala @@ -1,5 +1,7 @@ package scala.annotation +import language.experimental.captureChecking + /** An annotation that indicates capture of an enclosing by-name type */ @experimental class retainsByName[Elems] extends annotation.StaticAnnotation diff --git a/library/src/scala/annotation/static.scala b/library/src/scala/annotation/static.scala index 7cee5fc160d5..dc40ab9f3ac2 100644 --- a/library/src/scala/annotation/static.scala +++ b/library/src/scala/annotation/static.scala @@ -1,5 +1,7 @@ package scala.annotation +import language.experimental.captureChecking + import scala.annotation.meta.* /** https://github.com/scala/scala.github.com/pull/491 */ diff --git a/library/src/scala/annotation/targetName.scala b/library/src/scala/annotation/targetName.scala index 56e1361c73d7..17945fa86fb7 100644 --- a/library/src/scala/annotation/targetName.scala +++ b/library/src/scala/annotation/targetName.scala @@ -1,5 +1,7 @@ package scala.annotation +import language.experimental.captureChecking + /** An annotation that defines an external name for a definition. * If an `targetName(extname)` annotation is given for a method or some other * definition, its implementation will use the name `extname` instead of diff --git a/library/src/scala/annotation/threadUnsafe.scala b/library/src/scala/annotation/threadUnsafe.scala index 36fecec941c2..dd87e74f3048 100644 --- a/library/src/scala/annotation/threadUnsafe.scala +++ b/library/src/scala/annotation/threadUnsafe.scala @@ -1,5 +1,7 @@ package scala.annotation +import language.experimental.captureChecking + /** This annotation can only be used on a field which defines a lazy val. * When this annotation is used, the initialization of the lazy val will use a * faster mechanism which is not thread-safe. diff --git a/library/src/scala/annotation/transparentTrait.scala b/library/src/scala/annotation/transparentTrait.scala index ae6cb5bb1188..b82234708881 100644 --- a/library/src/scala/annotation/transparentTrait.scala +++ b/library/src/scala/annotation/transparentTrait.scala @@ -1,5 +1,7 @@ package scala.annotation +import language.experimental.captureChecking + /** An annotation that can be used from Scala 2 to mark a trait as transparent. * Scala 3 code would use the modifier `transparent` instead. Transparent traits * are not inferred when combined with other types in an intersection. diff --git a/library/src/scala/annotation/unchecked/uncheckedCaptures.scala b/library/src/scala/annotation/unchecked/uncheckedCaptures.scala index 477ac6d742f7..e494cef28a0e 100644 --- a/library/src/scala/annotation/unchecked/uncheckedCaptures.scala +++ b/library/src/scala/annotation/unchecked/uncheckedCaptures.scala @@ -1,6 +1,8 @@ package scala.annotation package unchecked +import language.experimental.captureChecking + /** An annotation for mutable variables that are allowed to capture * the root capability `cap`. Allowing this is not capture safe since * it can cause leakage of capabilities from local scopes by assigning diff --git a/library/src/scala/annotation/unroll.scala b/library/src/scala/annotation/unroll.scala index c37b7903d605..dcc279f93798 100644 --- a/library/src/scala/annotation/unroll.scala +++ b/library/src/scala/annotation/unroll.scala @@ -1,5 +1,7 @@ package scala.annotation +import language.experimental.captureChecking + @experimental("under review as part of SIP-61") /**The `@unroll` annotation is reserved for parameters of classes and methods. * diff --git a/library/src/scala/caps/Pure.scala b/library/src/scala/caps/Pure.scala index dfb8127bcc7f..11d0e3f039e9 100644 --- a/library/src/scala/caps/Pure.scala +++ b/library/src/scala/caps/Pure.scala @@ -1,6 +1,8 @@ package scala package caps +import language.experimental.captureChecking + /** A marker trait that declares that all inheriting classes are "pure" in the * sense that their values retain no capabilities including capabilities needed * to perform effects. This has formal meaning only under capture checking. diff --git a/library/src/scala/caps/package.scala b/library/src/scala/caps/package.scala index 45d1e18e741a..2cd75efdf197 100644 --- a/library/src/scala/caps/package.scala +++ b/library/src/scala/caps/package.scala @@ -1,6 +1,8 @@ package scala package caps +import language.experimental.captureChecking + import annotation.{experimental, compileTimeOnly, retainsCap} /** diff --git a/library/src/scala/compiletime/ops/any.scala b/library/src/scala/compiletime/ops/any.scala index b3c1930f9715..26e38d234b78 100644 --- a/library/src/scala/compiletime/ops/any.scala +++ b/library/src/scala/compiletime/ops/any.scala @@ -1,6 +1,8 @@ package scala.compiletime package ops +import language.experimental.captureChecking + object any: /** Equality comparison of two singleton types. * ```scala diff --git a/library/src/scala/compiletime/ops/boolean.scala b/library/src/scala/compiletime/ops/boolean.scala index 3e1b5650a519..0ef6785e2fcd 100644 --- a/library/src/scala/compiletime/ops/boolean.scala +++ b/library/src/scala/compiletime/ops/boolean.scala @@ -1,6 +1,8 @@ package scala.compiletime package ops +import language.experimental.captureChecking + object boolean: /** Negation of a `Boolean` singleton type. diff --git a/library/src/scala/compiletime/ops/double.scala b/library/src/scala/compiletime/ops/double.scala index 4bb4527f14a9..789df9c6462c 100644 --- a/library/src/scala/compiletime/ops/double.scala +++ b/library/src/scala/compiletime/ops/double.scala @@ -1,6 +1,8 @@ package scala.compiletime package ops +import language.experimental.captureChecking + object double: /** Addition of two `Double` singleton types. * ```scala diff --git a/library/src/scala/compiletime/ops/float.scala b/library/src/scala/compiletime/ops/float.scala index bd9b5c75f1f1..70a06522f526 100644 --- a/library/src/scala/compiletime/ops/float.scala +++ b/library/src/scala/compiletime/ops/float.scala @@ -1,6 +1,8 @@ package scala.compiletime package ops +import language.experimental.captureChecking + object float: /** Addition of two `Float` singleton types. * ```scala diff --git a/library/src/scala/compiletime/ops/int.scala b/library/src/scala/compiletime/ops/int.scala index b8ec370421e2..b74ee054da6d 100644 --- a/library/src/scala/compiletime/ops/int.scala +++ b/library/src/scala/compiletime/ops/int.scala @@ -1,6 +1,8 @@ package scala.compiletime package ops +import language.experimental.captureChecking + object int: /** Successor of a natural number where zero is the type 0 and successors are reduced as if the definition was: * diff --git a/library/src/scala/compiletime/ops/long.scala b/library/src/scala/compiletime/ops/long.scala index 3bda31e4979c..7188dffb5af8 100644 --- a/library/src/scala/compiletime/ops/long.scala +++ b/library/src/scala/compiletime/ops/long.scala @@ -1,6 +1,8 @@ package scala.compiletime package ops +import language.experimental.captureChecking + object long: /** Successor of a natural number where zero is the type 0 and successors are reduced as if the definition was: * diff --git a/library/src/scala/compiletime/ops/string.scala b/library/src/scala/compiletime/ops/string.scala index 90515c62e55b..9ef5a3fb7b79 100644 --- a/library/src/scala/compiletime/ops/string.scala +++ b/library/src/scala/compiletime/ops/string.scala @@ -1,6 +1,8 @@ package scala.compiletime package ops +import language.experimental.captureChecking + object string: /** Concatenation of two `String` singleton types. * ```scala diff --git a/library/src/scala/compiletime/package.scala b/library/src/scala/compiletime/package.scala index 1a161ebd4a03..3821b9dbd2e7 100644 --- a/library/src/scala/compiletime/package.scala +++ b/library/src/scala/compiletime/package.scala @@ -1,6 +1,8 @@ package scala package compiletime +import language.experimental.captureChecking + import annotation.{compileTimeOnly, experimental} /** Use this method when you have a type, do not have a value for it but want to diff --git a/library/src/scala/compiletime/testing/Error.scala b/library/src/scala/compiletime/testing/Error.scala index fc6ca0bcaabc..65ad302771db 100644 --- a/library/src/scala/compiletime/testing/Error.scala +++ b/library/src/scala/compiletime/testing/Error.scala @@ -1,5 +1,7 @@ package scala.compiletime.testing +import language.experimental.captureChecking + /** Represents a compile-time error. * * @see scala.compiletime.testing.typeCheckErrors diff --git a/library/src/scala/compiletime/testing/ErrorKind.scala b/library/src/scala/compiletime/testing/ErrorKind.scala index c7e38ae22d83..1e45e3250f94 100644 --- a/library/src/scala/compiletime/testing/ErrorKind.scala +++ b/library/src/scala/compiletime/testing/ErrorKind.scala @@ -1,5 +1,7 @@ package scala.compiletime.testing +import language.experimental.captureChecking + /** An error can be either a parse-time or a typecheck-time */ sealed trait ErrorKind // This should be an enum but currently, Dotty lib fails to // compile with an obscure error. diff --git a/library/src/scala/compiletime/testing/package.scala b/library/src/scala/compiletime/testing/package.scala index ebe629244fb3..b2c4cbedf9df 100644 --- a/library/src/scala/compiletime/testing/package.scala +++ b/library/src/scala/compiletime/testing/package.scala @@ -1,6 +1,8 @@ package scala.compiletime package testing +import language.experimental.captureChecking + /** Whether the code type checks in the current context? * * An inline definition with a call to `typeChecks` should be transparent. diff --git a/library/src/scala/deriving/Mirror.scala b/library/src/scala/deriving/Mirror.scala index a7477cf0fb2d..85351e9b7f23 100644 --- a/library/src/scala/deriving/Mirror.scala +++ b/library/src/scala/deriving/Mirror.scala @@ -1,5 +1,7 @@ package scala.deriving +// import language.experimental.captureChecking + /** Mirrors allows typelevel access to enums, case classes and objects, and their sealed parents. */ sealed trait Mirror { @@ -23,7 +25,7 @@ object Mirror { } /** The Mirror for a product type */ - trait Product extends Mirror { + trait Product extends Mirror { self => /** Create a new instance of type `T` with elements taken from product `p`. */ def fromProduct(p: scala.Product): MirroredMonoType diff --git a/library/src/scala/main.scala b/library/src/scala/main.scala index 057fdad4c2fb..f0494bf975e0 100644 --- a/library/src/scala/main.scala +++ b/library/src/scala/main.scala @@ -8,6 +8,8 @@ package scala +import language.experimental.captureChecking + /** An annotation that designates a main function */ class main extends scala.annotation.Annotation {} diff --git a/library/src/scala/quoted/Expr.scala b/library/src/scala/quoted/Expr.scala index bfb26023e2c8..9d297ed27d0e 100644 --- a/library/src/scala/quoted/Expr.scala +++ b/library/src/scala/quoted/Expr.scala @@ -1,5 +1,7 @@ package scala.quoted +import language.experimental.captureChecking + /** Quoted expression of type `T`. * * `Expr` has extension methods that are defined in `scala.quoted.Quotes`. diff --git a/library/src/scala/quoted/ExprMap.scala b/library/src/scala/quoted/ExprMap.scala index fbe5dee2b342..8aa513eec4ac 100644 --- a/library/src/scala/quoted/ExprMap.scala +++ b/library/src/scala/quoted/ExprMap.scala @@ -1,5 +1,7 @@ package scala.quoted +import language.experimental.captureChecking + trait ExprMap: /** Map an expression `e` with a type `T` */ diff --git a/library/src/scala/quoted/Exprs.scala b/library/src/scala/quoted/Exprs.scala index 48b0200236bf..2cc2e2944761 100644 --- a/library/src/scala/quoted/Exprs.scala +++ b/library/src/scala/quoted/Exprs.scala @@ -1,5 +1,7 @@ package scala.quoted +import language.experimental.captureChecking + object Exprs: /** Matches literal sequence of literal constant value expressions and return a sequence of values. diff --git a/library/src/scala/quoted/FromExpr.scala b/library/src/scala/quoted/FromExpr.scala index bcd10e4f09ed..2d9e3ebc5ab6 100644 --- a/library/src/scala/quoted/FromExpr.scala +++ b/library/src/scala/quoted/FromExpr.scala @@ -1,5 +1,7 @@ package scala.quoted +import language.experimental.captureChecking + /** A type class for types that can convert a `quoted.Expr[T]` to a `T`. * * - Converts expression containing literal values to their values: diff --git a/library/src/scala/quoted/Quotes.scala b/library/src/scala/quoted/Quotes.scala index b4ea133e47eb..61902c1842f0 100644 --- a/library/src/scala/quoted/Quotes.scala +++ b/library/src/scala/quoted/Quotes.scala @@ -1,5 +1,7 @@ package scala.quoted +import language.experimental.captureChecking + import scala.annotation.{experimental, implicitNotFound, unused} import scala.reflect.TypeTest diff --git a/library/src/scala/quoted/ToExpr.scala b/library/src/scala/quoted/ToExpr.scala index 6c167c353d87..fbe590c660de 100644 --- a/library/src/scala/quoted/ToExpr.scala +++ b/library/src/scala/quoted/ToExpr.scala @@ -1,5 +1,7 @@ package scala.quoted +import language.experimental.captureChecking + import scala.reflect.ClassTag /** A type class for types that can convert a value of `T` into `quoted.Expr[T]` diff --git a/library/src/scala/quoted/Type.scala b/library/src/scala/quoted/Type.scala index b035bdd6e52f..2d3a0b4fcabb 100644 --- a/library/src/scala/quoted/Type.scala +++ b/library/src/scala/quoted/Type.scala @@ -1,5 +1,7 @@ package scala.quoted +import language.experimental.captureChecking + import scala.annotation.{compileTimeOnly, experimental} /** Type (or type constructor) `T` needed contextually when using `T` in a quoted expression `'{... T ...}` */ diff --git a/library/src/scala/quoted/Varargs.scala b/library/src/scala/quoted/Varargs.scala index 61937fc8fecb..3ffd0b64120d 100644 --- a/library/src/scala/quoted/Varargs.scala +++ b/library/src/scala/quoted/Varargs.scala @@ -1,5 +1,7 @@ package scala.quoted +import language.experimental.captureChecking + /** Expression representation of literal sequence of expressions. * * `Varargs` can be used to create the an expression `args` that will be used as varargs `'{ f($args: _*) }` diff --git a/library/src/scala/quoted/runtime/Expr.scala b/library/src/scala/quoted/runtime/Expr.scala index b95f225c13b3..e3a54a64a06a 100644 --- a/library/src/scala/quoted/runtime/Expr.scala +++ b/library/src/scala/quoted/runtime/Expr.scala @@ -1,6 +1,8 @@ package scala.quoted package runtime +import language.experimental.captureChecking + import scala.annotation.{Annotation, compileTimeOnly} @compileTimeOnly("Illegal reference to `scala.quoted.runtime.Expr`") diff --git a/library/src/scala/quoted/runtime/Patterns.scala b/library/src/scala/quoted/runtime/Patterns.scala index f8e172d30f62..cdbbb224424e 100644 --- a/library/src/scala/quoted/runtime/Patterns.scala +++ b/library/src/scala/quoted/runtime/Patterns.scala @@ -1,5 +1,7 @@ package scala.quoted.runtime +import language.experimental.captureChecking + import scala.annotation.{Annotation, compileTimeOnly} import scala.annotation.experimental diff --git a/library/src/scala/quoted/runtime/QuoteMatching.scala b/library/src/scala/quoted/runtime/QuoteMatching.scala index c95ffe87b5dc..90205266d01a 100644 --- a/library/src/scala/quoted/runtime/QuoteMatching.scala +++ b/library/src/scala/quoted/runtime/QuoteMatching.scala @@ -1,5 +1,7 @@ package scala.quoted.runtime +import language.experimental.captureChecking + import scala.quoted.{Expr, Type} /** Part of the Quotes interface that needs to be implemented by the compiler but is not visible to users */ diff --git a/library/src/scala/quoted/runtime/QuoteUnpickler.scala b/library/src/scala/quoted/runtime/QuoteUnpickler.scala index 63e62658cbb4..aa83ceea2e28 100644 --- a/library/src/scala/quoted/runtime/QuoteUnpickler.scala +++ b/library/src/scala/quoted/runtime/QuoteUnpickler.scala @@ -1,5 +1,7 @@ package scala.quoted.runtime +import language.experimental.captureChecking + import scala.quoted.{Quotes, Expr, Type} /** Part of the Quotes interface that needs to be implemented by the compiler but is not visible to users */ diff --git a/library/src/scala/quoted/runtime/SplicedType.scala b/library/src/scala/quoted/runtime/SplicedType.scala index 1a7b74740d33..0a4242dffa6c 100644 --- a/library/src/scala/quoted/runtime/SplicedType.scala +++ b/library/src/scala/quoted/runtime/SplicedType.scala @@ -1,5 +1,7 @@ package scala.quoted.runtime +import language.experimental.captureChecking + import scala.annotation.{Annotation, compileTimeOnly} /** Artifact of pickled type splices diff --git a/library/src/scala/quoted/runtime/StopMacroExpansion.scala b/library/src/scala/quoted/runtime/StopMacroExpansion.scala index b4d570167621..678c8f93ee66 100644 --- a/library/src/scala/quoted/runtime/StopMacroExpansion.scala +++ b/library/src/scala/quoted/runtime/StopMacroExpansion.scala @@ -1,5 +1,7 @@ package scala.quoted.runtime +import language.experimental.captureChecking + /** Throwable used to abort the expansion of a macro after an error was reported */ class StopMacroExpansion extends Throwable: diff --git a/library/src/scala/reflect/Enum.scala b/library/src/scala/reflect/Enum.scala index 8fb9135f6478..3be8965c97a6 100644 --- a/library/src/scala/reflect/Enum.scala +++ b/library/src/scala/reflect/Enum.scala @@ -1,5 +1,7 @@ package scala.reflect +import language.experimental.captureChecking + /** A base trait of all Scala enum definitions */ transparent trait Enum extends Any, Product, Serializable: diff --git a/library/src/scala/reflect/Selectable.scala b/library/src/scala/reflect/Selectable.scala index ac8e502128bb..52b584bf815e 100644 --- a/library/src/scala/reflect/Selectable.scala +++ b/library/src/scala/reflect/Selectable.scala @@ -1,5 +1,7 @@ package scala.reflect +import language.experimental.captureChecking + /** A class that implements structural selections using Java reflection. * * It can be used as a supertrait of a class or be made available diff --git a/library/src/scala/reflect/TypeTest.scala b/library/src/scala/reflect/TypeTest.scala index 23dea5ec9ced..b122d3b92bc2 100644 --- a/library/src/scala/reflect/TypeTest.scala +++ b/library/src/scala/reflect/TypeTest.scala @@ -1,5 +1,7 @@ package scala.reflect +import language.experimental.captureChecking + /** A `TypeTest[S, T]` contains the logic needed to know at runtime if a value of * type `S` is an instance of `T`. * diff --git a/library/src/scala/reflect/Typeable.scala b/library/src/scala/reflect/Typeable.scala index 3214de77123e..a298d91041c6 100644 --- a/library/src/scala/reflect/Typeable.scala +++ b/library/src/scala/reflect/Typeable.scala @@ -1,5 +1,7 @@ package scala.reflect +import language.experimental.captureChecking + /** A shorhand for `TypeTest[Any, T]`. A `Typeable[T] contains the logic needed to * know at runtime if a value can be downcasted to `T`. * diff --git a/library/src/scala/runtime/$throws.scala b/library/src/scala/runtime/$throws.scala index af35f7402ca3..f02b5b205f47 100644 --- a/library/src/scala/runtime/$throws.scala +++ b/library/src/scala/runtime/$throws.scala @@ -1,4 +1,7 @@ package scala.runtime + +import language.experimental.captureChecking + import annotation.experimental /** A helper type to allow syntax like diff --git a/library/src/scala/runtime/Arrays.scala b/library/src/scala/runtime/Arrays.scala index 085b36c08a1f..2e8b62d76a56 100644 --- a/library/src/scala/runtime/Arrays.scala +++ b/library/src/scala/runtime/Arrays.scala @@ -1,5 +1,7 @@ package scala.runtime +import language.experimental.captureChecking + import scala.annotation.unused import scala.reflect.ClassTag diff --git a/library/src/scala/runtime/EnumValue.scala b/library/src/scala/runtime/EnumValue.scala index 1cd13605279e..62b3af3584ba 100644 --- a/library/src/scala/runtime/EnumValue.scala +++ b/library/src/scala/runtime/EnumValue.scala @@ -1,5 +1,7 @@ package scala.runtime +import language.experimental.captureChecking + transparent trait EnumValue extends Product, Serializable: override def canEqual(that: Any) = this eq that.asInstanceOf[AnyRef] override def productArity: Int = 0 diff --git a/library/src/scala/runtime/FunctionXXL.scala b/library/src/scala/runtime/FunctionXXL.scala index 250e03577e08..be23632e7eff 100644 --- a/library/src/scala/runtime/FunctionXXL.scala +++ b/library/src/scala/runtime/FunctionXXL.scala @@ -1,5 +1,7 @@ package scala.runtime +import language.experimental.captureChecking + /** A function with all parameters grouped in an array. */ trait FunctionXXL { diff --git a/library/src/scala/runtime/LazyVals.scala b/library/src/scala/runtime/LazyVals.scala index deea2201414a..a92712c71a05 100644 --- a/library/src/scala/runtime/LazyVals.scala +++ b/library/src/scala/runtime/LazyVals.scala @@ -1,5 +1,6 @@ package scala.runtime +import language.experimental.captureChecking import java.util.concurrent.CountDownLatch import scala.annotation.* @@ -9,7 +10,7 @@ import scala.annotation.* */ object LazyVals { @nowarn - private val unsafe: sun.misc.Unsafe = { + private val unsafe: sun.misc.Unsafe^ = { // do not let unsafe leak def throwInitializationException() = throw new ExceptionInInitializerError( new IllegalStateException("Can't find instance of sun.misc.Unsafe") diff --git a/library/src/scala/runtime/MatchCase.scala b/library/src/scala/runtime/MatchCase.scala index 2f1a27328bbc..f163413df2a3 100644 --- a/library/src/scala/runtime/MatchCase.scala +++ b/library/src/scala/runtime/MatchCase.scala @@ -1,5 +1,7 @@ package scala.runtime +import language.experimental.captureChecking + /** A type constructor for a case in a match type. */ final abstract class MatchCase[Pat, +Body] diff --git a/library/src/scala/runtime/Scala3RunTime.scala b/library/src/scala/runtime/Scala3RunTime.scala index e503b2d7ec7e..685947f293e0 100644 --- a/library/src/scala/runtime/Scala3RunTime.scala +++ b/library/src/scala/runtime/Scala3RunTime.scala @@ -1,5 +1,7 @@ package scala.runtime +import language.experimental.captureChecking + object Scala3RunTime: // Called by inline def assert's. Extracted to minimize the bytecode size at call site. diff --git a/library/src/scala/runtime/TupleMirror.scala b/library/src/scala/runtime/TupleMirror.scala index e6981df54134..2abe4a5004d5 100644 --- a/library/src/scala/runtime/TupleMirror.scala +++ b/library/src/scala/runtime/TupleMirror.scala @@ -1,5 +1,7 @@ package scala.runtime +import language.experimental.captureChecking + /** A concrete subclass of `scala.deriving.Mirror.Product`, enabling reduction of bytecode size. * as we do not need to synthesize an anonymous Mirror class at every callsite. */ diff --git a/library/src/scala/runtime/TupleXXL.scala b/library/src/scala/runtime/TupleXXL.scala index b4a81d60f817..2da0cbb15396 100644 --- a/library/src/scala/runtime/TupleXXL.scala +++ b/library/src/scala/runtime/TupleXXL.scala @@ -1,5 +1,7 @@ package scala.runtime +import language.experimental.captureChecking + final class TupleXXL private (es: IArray[Object]) extends Product { assert(es.length > 22) diff --git a/library/src/scala/runtime/TupledFunctions.scala b/library/src/scala/runtime/TupledFunctions.scala index 52b39dedbf76..48a3a63f48a4 100644 --- a/library/src/scala/runtime/TupledFunctions.scala +++ b/library/src/scala/runtime/TupledFunctions.scala @@ -1,5 +1,7 @@ package scala.runtime +import language.experimental.captureChecking + import scala.util.TupledFunction import scala.annotation.experimental diff --git a/library/src/scala/runtime/Tuples.scala b/library/src/scala/runtime/Tuples.scala index 66dc486d2a1d..d9a078a5456a 100644 --- a/library/src/scala/runtime/Tuples.scala +++ b/library/src/scala/runtime/Tuples.scala @@ -1,5 +1,7 @@ package scala.runtime +import language.experimental.captureChecking + object Tuples { inline val MaxSpecialized = 22 diff --git a/library/src/scala/runtime/TypeBox.scala b/library/src/scala/runtime/TypeBox.scala index ec2e42eeafc7..629242460564 100644 --- a/library/src/scala/runtime/TypeBox.scala +++ b/library/src/scala/runtime/TypeBox.scala @@ -1,5 +1,7 @@ package scala.runtime +import language.experimental.captureChecking + /** A type for skolems that are generated during capture conversion. Capture conversion * narrows the type of a tree whose type has wildcard arguments. A typical situation * is a tree `t` of type `C[_ >: L <: U]` and an expected type `C[X]` where `X` is an diff --git a/library/src/scala/runtime/coverage/Invoker.scala b/library/src/scala/runtime/coverage/Invoker.scala index 98f71076b2f6..e8156c57095d 100644 --- a/library/src/scala/runtime/coverage/Invoker.scala +++ b/library/src/scala/runtime/coverage/Invoker.scala @@ -1,5 +1,7 @@ package scala.runtime.coverage +import language.experimental.captureChecking + import scala.annotation.internal.sharable import scala.annotation.nowarn import scala.collection.concurrent.TrieMap diff --git a/library/src/scala/runtime/stdLibPatches/Predef.scala b/library/src/scala/runtime/stdLibPatches/Predef.scala index 3ea13255ac73..ed4cf207c925 100644 --- a/library/src/scala/runtime/stdLibPatches/Predef.scala +++ b/library/src/scala/runtime/stdLibPatches/Predef.scala @@ -1,5 +1,7 @@ package scala.runtime.stdLibPatches +import scala.language.experimental.captureChecking + import scala.annotation.experimental import scala.annotation.internal.RuntimeChecked diff --git a/library/src/scala/runtime/stdLibPatches/language.scala b/library/src/scala/runtime/stdLibPatches/language.scala index c16f9eec7fe7..202afbbe5064 100644 --- a/library/src/scala/runtime/stdLibPatches/language.scala +++ b/library/src/scala/runtime/stdLibPatches/language.scala @@ -1,5 +1,7 @@ package scala.runtime.stdLibPatches +import scala.language.experimental.captureChecking + import scala.annotation.compileTimeOnly /** Scala 3 additions and replacements to the `scala.language` object. diff --git a/library/src/scala/util/CommandLineParser.scala b/library/src/scala/util/CommandLineParser.scala index fd239ef231c5..a43148c3699b 100644 --- a/library/src/scala/util/CommandLineParser.scala +++ b/library/src/scala/util/CommandLineParser.scala @@ -1,5 +1,7 @@ package scala.util +import language.experimental.captureChecking + /** A utility object to support command line parsing for @main methods */ object CommandLineParser { @@ -12,7 +14,7 @@ object CommandLineParser { /** Parse command line argument `s`, which has index `n`, as a value of type `T` * @throws ParseError if argument cannot be converted to type `T`. */ - def parseString[T](str: String, n: Int)(using fs: FromString[T]): T = { + def parseString[T](str: String, n: Int)(using fs: FromString[T]^): T = { try fs.fromString(str) catch { case ex: IllegalArgumentException => throw ParseError(n, ex.toString) @@ -22,14 +24,14 @@ object CommandLineParser { /** Parse `n`'th argument in `args` (counting from 0) as a value of type `T` * @throws ParseError if argument does not exist or cannot be converted to type `T`. */ - def parseArgument[T](args: Array[String], n: Int)(using fs: FromString[T]): T = + def parseArgument[T](args: Array[String], n: Int)(using fs: FromString[T]^): T = if n < args.length then parseString(args(n), n) else throw ParseError(n, "more arguments expected") /** Parse all arguments from `n`'th one (counting from 0) as a list of values of type `T` * @throws ParseError if some of the arguments cannot be converted to type `T`. */ - def parseRemainingArguments[T](args: Array[String], n: Int)(using fs: FromString[T]): List[T] = + def parseRemainingArguments[T](args: Array[String], n: Int)(using fs: FromString[T]^): List[T] = if n < args.length then parseString(args(n), n) :: parseRemainingArguments(args, n + 1) else Nil diff --git a/library/src/scala/util/FromDigits.scala b/library/src/scala/util/FromDigits.scala index 44532649868d..73b5a79b7546 100644 --- a/library/src/scala/util/FromDigits.scala +++ b/library/src/scala/util/FromDigits.scala @@ -3,6 +3,7 @@ import scala.math.{BigInt} import quoted.* import annotation.internal.sharable +import language.experimental.captureChecking /** A type class for types that admit numeric literals. */ diff --git a/library/src/scala/util/NotGiven.scala b/library/src/scala/util/NotGiven.scala index 973e709042cb..4896a1c44c80 100644 --- a/library/src/scala/util/NotGiven.scala +++ b/library/src/scala/util/NotGiven.scala @@ -1,5 +1,7 @@ package scala.util +import language.experimental.captureChecking + /** A special class used to implement negation in implicit search. * * Consider the problem of using implicit `i1` for a query type `D` if an implicit diff --git a/library/src/scala/util/TupledFunction.scala b/library/src/scala/util/TupledFunction.scala index 99df6b5b7cae..b5a6bfd7f65b 100644 --- a/library/src/scala/util/TupledFunction.scala +++ b/library/src/scala/util/TupledFunction.scala @@ -1,5 +1,7 @@ package scala.util +import language.experimental.captureChecking + import scala.annotation.implicitNotFound import scala.annotation.experimental @@ -16,7 +18,7 @@ sealed trait TupledFunction[F, G]: @experimental private[scala] object TupledFunction: - def apply[F, G](tupledImpl: F => G, untupledImpl: G => F): TupledFunction[F, G] = + def apply[F, G](tupledImpl: F => G, untupledImpl: G => F): TupledFunction[F, G]^{tupledImpl, untupledImpl} = new TupledFunction[F, G]: def tupled(f: F): G = tupledImpl(f) def untupled(g: G): F = untupledImpl(g) diff --git a/library/src/scala/util/boundary.scala b/library/src/scala/util/boundary.scala index 3039fc70be90..75df7f9b2121 100644 --- a/library/src/scala/util/boundary.scala +++ b/library/src/scala/util/boundary.scala @@ -1,4 +1,6 @@ package scala.util + +import language.experimental.captureChecking import scala.annotation.implicitNotFound /** A boundary that can be exited by `break` calls. @@ -30,15 +32,26 @@ object boundary: /** User code should call `break.apply` instead of throwing this exception * directly. + * + * Note that it is **capability unsafe** to access `label` from a `Break`. + * This field will be marked private in a future release. */ - final class Break[T] private[boundary](val label: Label[T], val value: T) + final class Break[T] private[boundary](val label: Label[T]^{}, val value: T) extends RuntimeException( - /*message*/ null, /*cause*/ null, /*enableSuppression=*/ false, /*writableStackTrace*/ false) + /*message*/ null, /*cause*/ null, /*enableSuppression=*/ false, /*writableStackTrace*/ false): + /** Compare the given [[Label]] to the one this [[Break]] was constructed with. */ + def isSameLabelAs(other: Label[T]) = label eq other + + object Break: + import caps.unsafe.unsafeAssumePure + def apply[T](label: Label[T], value: T) = + // SAFETY: labels cannot leak from [[Break]], and is only used for equality comparison. + new Break(label.unsafeAssumePure, value) /** Labels are targets indicating which boundary will be exited by a `break`. */ @implicitNotFound("explain=A Label is generated from an enclosing `scala.util.boundary` call.\nMaybe that boundary is missing?") - final class Label[-T] + final class Label[-T] extends caps.Control /** Abort current computation and instead return `value` as the value of * the enclosing `boundary` call that created `label`. @@ -60,7 +73,7 @@ object boundary: val local = Label[T]() try body(using local) catch case ex: Break[T] @unchecked => - if ex.label eq local then ex.value + if ex.isSameLabelAs(local) then ex.value else throw ex end boundary diff --git a/library/src/scala/util/control/NonLocalReturns.scala b/library/src/scala/util/control/NonLocalReturns.scala index f59b4c22a4c2..aa446ce33004 100644 --- a/library/src/scala/util/control/NonLocalReturns.scala +++ b/library/src/scala/util/control/NonLocalReturns.scala @@ -1,5 +1,7 @@ package scala.util.control +import language.experimental.captureChecking + import scala.compiletime.uninitialized /** Library implementation of nonlocal return. @@ -22,7 +24,7 @@ import scala.compiletime.uninitialized @deprecated("Use scala.util.boundary instead", "3.3") object NonLocalReturns { @deprecated("Use scala.util.boundary.Break instead", "3.3") - class ReturnThrowable[T] extends ControlThrowable { + class ReturnThrowable[T] extends ControlThrowable, caps.Control { private var myResult: T = uninitialized def throwReturn(result: T): Nothing = { myResult = result diff --git a/tests/neg-custom-args/captures/boundary.check b/tests/neg-custom-args/captures/boundary.check new file mode 100644 index 000000000000..dd0b6716adc7 --- /dev/null +++ b/tests/neg-custom-args/captures/boundary.check @@ -0,0 +1,50 @@ +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/boundary.scala:5:4 --------------------------------------- + 4 | boundary[AnyRef^]: + 5 | l1 ?=> // error // error + | ^ + |Found: scala.util.boundary.Label[Object^'s1]^{cap.rd} + |Required: scala.util.boundary.Label[Object^]^² + | + |Note that capability cap² cannot be included in outer capture set 's1. + | + |where: ^ refers to the universal root capability + | ^² refers to a fresh root capability classified as Control in the type of value local + | cap is a fresh root capability classified as Control created in value local when constructing Capability instance scala.util.boundary.Label[Object^'s1] + | cap² is the universal root capability + 6 | boundary[Unit]: l2 ?=> + 7 | boundary.break(l2)(using l1) + 8 | ??? + |-------------------------------------------------------------------------------------------------------------------- + |Inline stack trace + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + |This location contains code that was inlined from boundary.scala:73 +73 | val local = Label[T]() + | ^^^^^^^^^^ + -------------------------------------------------------------------------------------------------------------------- + | + | longer explanation available when compiling with `-explain` +-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/boundary.scala:5:4 --------------------------------------- + 4 | boundary[AnyRef^]: + 5 | l1 ?=> // error // error + | ^ + | Found: scala.util.boundary.Break[AnyRef^] @unchecked + | Required: scala.util.boundary.Break[Object^²] @unchecked + | + | Note that capability cap is not included in capture set {cap²}. + | + | where: ^ refers to the universal root capability + | ^² refers to a fresh root capability created in package + | cap is a fresh root capability created in package + | cap² is the universal root capability + 6 | boundary[Unit]: l2 ?=> + 7 | boundary.break(l2)(using l1) + 8 | ??? + |-------------------------------------------------------------------------------------------------------------------- + |Inline stack trace + |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + |This location contains code that was inlined from boundary.scala:75 +75 | catch case ex: Break[T] @unchecked => + | ^ + -------------------------------------------------------------------------------------------------------------------- + | + | longer explanation available when compiling with `-explain` diff --git a/tests/neg-custom-args/captures/boundary.scala b/tests/neg-custom-args/captures/boundary.scala new file mode 100644 index 000000000000..9792a0e49fa3 --- /dev/null +++ b/tests/neg-custom-args/captures/boundary.scala @@ -0,0 +1,8 @@ +import scala.util.boundary + +object test: + boundary[AnyRef^]: + l1 ?=> // error // error + boundary[Unit]: l2 ?=> + boundary.break(l2)(using l1) + ??? diff --git a/tests/neg-macros/i19842-a.check b/tests/neg-macros/i19842-a.check index 4d92739e00ff..33588b7278c2 100644 --- a/tests/neg-macros/i19842-a.check +++ b/tests/neg-macros/i19842-a.check @@ -7,7 +7,7 @@ |Parents in symbol: [class Object, trait Serializer] |Parents in tree: [trait Serializer] | - | at scala.runtime.Scala3RunTime$.assertFailed(Scala3RunTime.scala:8) + | at scala.runtime.Scala3RunTime$.assertFailed(Scala3RunTime.scala:10) | at dotty.tools.dotc.transform.TreeChecker$.checkParents(TreeChecker.scala:210) | at scala.quoted.runtime.impl.QuotesImpl$reflect$ClassDef$.module(QuotesImpl.scala:286) | at scala.quoted.runtime.impl.QuotesImpl$reflect$ClassDef$.module(QuotesImpl.scala:285) diff --git a/tests/neg-macros/i19842-b.check b/tests/neg-macros/i19842-b.check index 5a2fe6f39d48..29500e10fb25 100644 --- a/tests/neg-macros/i19842-b.check +++ b/tests/neg-macros/i19842-b.check @@ -7,7 +7,7 @@ |Parents in symbol: [class Object, trait Serializer] |Parents in tree: [class Object, trait Serializer, trait Foo] | - | at scala.runtime.Scala3RunTime$.assertFailed(Scala3RunTime.scala:8) + | at scala.runtime.Scala3RunTime$.assertFailed(Scala3RunTime.scala:10) | at dotty.tools.dotc.transform.TreeChecker$.checkParents(TreeChecker.scala:210) | at scala.quoted.runtime.impl.QuotesImpl$reflect$ClassDef$.module(QuotesImpl.scala:286) | at scala.quoted.runtime.impl.QuotesImpl$reflect$ClassDef$.module(QuotesImpl.scala:285) diff --git a/tests/neg-macros/i23008.check b/tests/neg-macros/i23008.check index 5193fb0ad7c4..c44f7cccb52a 100644 --- a/tests/neg-macros/i23008.check +++ b/tests/neg-macros/i23008.check @@ -7,9 +7,9 @@ | at scala.Predef$.require(Predef.scala:337) | at scala.quoted.runtime.impl.QuotesImpl$reflect$StringConstant$.apply(QuotesImpl.scala:2540) | at scala.quoted.runtime.impl.QuotesImpl$reflect$StringConstant$.apply(QuotesImpl.scala:2539) + | at scala.quoted.ToExpr$StringToExpr.apply(ToExpr.scala:82) | at scala.quoted.ToExpr$StringToExpr.apply(ToExpr.scala:80) - | at scala.quoted.ToExpr$StringToExpr.apply(ToExpr.scala:78) - | at scala.quoted.Expr$.apply(Expr.scala:70) + | at scala.quoted.Expr$.apply(Expr.scala:72) | at Macros$.buildStringCode(Macro_1.scala:9) | |--------------------------------------------------------------------------------------------------------------------- diff --git a/tests/run/assert-stack.check b/tests/run/assert-stack.check index c2c97450fd57..aec36d2a4f6e 100644 --- a/tests/run/assert-stack.check +++ b/tests/run/assert-stack.check @@ -1,6 +1,6 @@ -scala.runtime.Scala3RunTime$.assertFailed(Scala3RunTime.scala:8) +scala.runtime.Scala3RunTime$.assertFailed(Scala3RunTime.scala:10) Test$.main(assert-stack.scala:7) -scala.runtime.Scala3RunTime$.assertFailed(Scala3RunTime.scala:11) +scala.runtime.Scala3RunTime$.assertFailed(Scala3RunTime.scala:13) Test$.main(assert-stack.scala:12) From ac2ffaa11405c164bca8feb7fe0e6ff7e60de163 Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Thu, 7 Aug 2025 16:53:51 +0200 Subject: [PATCH 030/101] Extend TODO with Scala 2 --- TODO.md | 644 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 644 insertions(+) create mode 100644 TODO.md diff --git a/TODO.md b/TODO.md new file mode 100644 index 000000000000..d8e7e698c72e --- /dev/null +++ b/TODO.md @@ -0,0 +1,644 @@ +- [ ] library/src/scala/AnyValCompanion.scala +- [ ] library/src/scala/App.scala +- [ ] library/src/scala/Array.scala +- [ ] library/src/scala/Boolean.scala +- [ ] library/src/scala/Byte.scala +- [x] library/src/scala/CanEqual.scala +- [x] library/src/scala/CanThrow.scala +- [ ] library/src/scala/Char.scala +- [ ] library/src/scala/Console.scala +- [x] library/src/scala/Conversion.scala +- [ ] library/src/scala/DelayedInit.scala +- [ ] library/src/scala/Double.scala +- [ ] library/src/scala/DummyImplicit.scala +- [ ] library/src/scala/Dynamic.scala +- [ ] library/src/scala/Enumeration.scala +- [ ] library/src/scala/Equals.scala +- [ ] library/src/scala/Float.scala +- [ ] library/src/scala/Function.scala +- [ ] library/src/scala/Function0.scala +- [ ] library/src/scala/Function1.scala +- [ ] library/src/scala/Function10.scala +- [ ] library/src/scala/Function11.scala +- [ ] library/src/scala/Function12.scala +- [ ] library/src/scala/Function13.scala +- [ ] library/src/scala/Function14.scala +- [ ] library/src/scala/Function15.scala +- [ ] library/src/scala/Function16.scala +- [ ] library/src/scala/Function17.scala +- [ ] library/src/scala/Function18.scala +- [ ] library/src/scala/Function19.scala +- [ ] library/src/scala/Function2.scala +- [ ] library/src/scala/Function20.scala +- [ ] library/src/scala/Function21.scala +- [ ] library/src/scala/Function22.scala +- [ ] library/src/scala/Function3.scala +- [ ] library/src/scala/Function4.scala +- [ ] library/src/scala/Function5.scala +- [ ] library/src/scala/Function6.scala +- [ ] library/src/scala/Function7.scala +- [ ] library/src/scala/Function8.scala +- [ ] library/src/scala/Function9.scala +- [x] library/src/scala/IArray.scala +- [ ] library/src/scala/Int.scala +- [ ] library/src/scala/Long.scala +- [ ] library/src/scala/MatchError.scala +- [x] library/src/scala/NamedTuple.scala +- [ ] library/src/scala/NotImplementedError.scala +- [ ] library/src/scala/Option.scala +- [ ] library/src/scala/PartialFunction.scala +- [x] library/src/scala/PolyFunction.scala +- [x] library/src/scala/Precise.scala +- [ ] library/src/scala/Predef.scala +- [ ] library/src/scala/Product.scala +- [ ] library/src/scala/Product1.scala +- [ ] library/src/scala/Product10.scala +- [ ] library/src/scala/Product11.scala +- [ ] library/src/scala/Product12.scala +- [ ] library/src/scala/Product13.scala +- [ ] library/src/scala/Product14.scala +- [ ] library/src/scala/Product15.scala +- [ ] library/src/scala/Product16.scala +- [ ] library/src/scala/Product17.scala +- [ ] library/src/scala/Product18.scala +- [ ] library/src/scala/Product19.scala +- [ ] library/src/scala/Product2.scala +- [ ] library/src/scala/Product20.scala +- [ ] library/src/scala/Product21.scala +- [ ] library/src/scala/Product22.scala +- [ ] library/src/scala/Product3.scala +- [ ] library/src/scala/Product4.scala +- [ ] library/src/scala/Product5.scala +- [ ] library/src/scala/Product6.scala +- [ ] library/src/scala/Product7.scala +- [ ] library/src/scala/Product8.scala +- [ ] library/src/scala/Product9.scala +- [ ] library/src/scala/Proxy.scala +- [x] library/src/scala/Pure.scala +- [x] library/src/scala/Selectable.scala +- [ ] library/src/scala/SerialVersionUID.scala +- [ ] library/src/scala/Short.scala +- [ ] library/src/scala/Specializable.scala +- [ ] library/src/scala/StringContext.scala +- [ ] library/src/scala/Symbol.scala +- [x] library/src/scala/Tuple.scala +- [ ] library/src/scala/Tuple1.scala +- [ ] library/src/scala/Tuple10.scala +- [ ] library/src/scala/Tuple11.scala +- [ ] library/src/scala/Tuple12.scala +- [ ] library/src/scala/Tuple13.scala +- [ ] library/src/scala/Tuple14.scala +- [ ] library/src/scala/Tuple15.scala +- [ ] library/src/scala/Tuple16.scala +- [ ] library/src/scala/Tuple17.scala +- [ ] library/src/scala/Tuple18.scala +- [ ] library/src/scala/Tuple19.scala +- [ ] library/src/scala/Tuple2.scala +- [ ] library/src/scala/Tuple20.scala +- [ ] library/src/scala/Tuple21.scala +- [ ] library/src/scala/Tuple22.scala +- [ ] library/src/scala/Tuple3.scala +- [ ] library/src/scala/Tuple4.scala +- [ ] library/src/scala/Tuple5.scala +- [ ] library/src/scala/Tuple6.scala +- [ ] library/src/scala/Tuple7.scala +- [ ] library/src/scala/Tuple8.scala +- [ ] library/src/scala/Tuple9.scala +- [ ] library/src/scala/UninitializedError.scala +- [ ] library/src/scala/UninitializedFieldError.scala +- [ ] library/src/scala/Unit.scala +- [ ] library/src/scala/ValueOf.scala +- [ ] library/src/scala/annotation/Annotation.scala +- [ ] library/src/scala/annotation/ClassfileAnnotation.scala +- [ ] library/src/scala/annotation/ConstantAnnotation.scala +- [x] library/src/scala/annotation/MacroAnnotation.scala +- [x] library/src/scala/annotation/RefiningAnnotation.scala +- [ ] library/src/scala/annotation/StaticAnnotation.scala +- [ ] library/src/scala/annotation/TypeConstraint.scala +- [x] library/src/scala/annotation/alpha.scala +- [x] library/src/scala/annotation/capability.scala +- [ ] library/src/scala/annotation/compileTimeOnly.scala +- [x] library/src/scala/annotation/constructorOnly.scala +- [ ] library/src/scala/annotation/elidable.scala +- [x] library/src/scala/annotation/experimental.scala +- [ ] library/src/scala/annotation/implicitAmbiguous.scala +- [ ] library/src/scala/annotation/implicitNotFound.scala +- [x] library/src/scala/annotation/init.scala +- [x] library/src/scala/annotation/internal/$into.scala +- [x] library/src/scala/annotation/internal/Alias.scala +- [x] library/src/scala/annotation/internal/AnnotationDefault.scala +- [x] library/src/scala/annotation/internal/AssignedNonLocally.scala +- [x] library/src/scala/annotation/internal/Body.scala +- [x] library/src/scala/annotation/internal/CaptureChecked.scala +- [x] library/src/scala/annotation/internal/Child.scala +- [x] library/src/scala/annotation/internal/ContextResultCount.scala +- [x] library/src/scala/annotation/internal/ErasedParam.scala +- [x] library/src/scala/annotation/internal/InlineParam.scala +- [x] library/src/scala/annotation/internal/MappedAlternative.scala +- [x] library/src/scala/annotation/internal/ProvisionalSuperClass.scala +- [x] library/src/scala/annotation/internal/Repeated.scala +- [x] library/src/scala/annotation/internal/RuntimeChecked.scala +- [x] library/src/scala/annotation/internal/SourceFile.scala +- [x] library/src/scala/annotation/internal/WithPureFuns.scala +- [x] library/src/scala/annotation/internal/WitnessNames.scala +- [ ] library/src/scala/annotation/internal/onlyCapability.scala +- [x] library/src/scala/annotation/internal/preview.scala +- [x] library/src/scala/annotation/internal/reachCapability.scala +- [x] library/src/scala/annotation/internal/readOnlyCapability.scala +- [x] library/src/scala/annotation/internal/requiresCapability.scala +- [x] library/src/scala/annotation/internal/sharable.scala +- [x] library/src/scala/annotation/internal/unshared.scala +- [ ] library/src/scala/annotation/meta/beanGetter.scala +- [ ] library/src/scala/annotation/meta/beanSetter.scala +- [ ] library/src/scala/annotation/meta/companionClass.scala +- [ ] library/src/scala/annotation/meta/companionMethod.scala +- [ ] library/src/scala/annotation/meta/companionObject.scala +- [ ] library/src/scala/annotation/meta/defaultArg.scala +- [ ] library/src/scala/annotation/meta/field.scala +- [ ] library/src/scala/annotation/meta/getter.scala +- [ ] library/src/scala/annotation/meta/languageFeature.scala +- [ ] library/src/scala/annotation/meta/package.scala +- [ ] library/src/scala/annotation/meta/param.scala +- [ ] library/src/scala/annotation/meta/setter.scala +- [ ] library/src/scala/annotation/meta/superArg.scala +- [ ] library/src/scala/annotation/migration.scala +- [ ] library/src/scala/annotation/nowarn.scala +- [x] library/src/scala/annotation/publicInBinary.scala +- [x] library/src/scala/annotation/retains.scala +- [x] library/src/scala/annotation/retainsByName.scala +- [ ] library/src/scala/annotation/showAsInfix.scala +- [ ] library/src/scala/annotation/stableNull.scala +- [x] library/src/scala/annotation/static.scala +- [ ] library/src/scala/annotation/strictfp.scala +- [ ] library/src/scala/annotation/switch.scala +- [ ] library/src/scala/annotation/tailrec.scala +- [x] library/src/scala/annotation/targetName.scala +- [x] library/src/scala/annotation/threadUnsafe.scala +- [x] library/src/scala/annotation/transparentTrait.scala +- [ ] library/src/scala/annotation/unchecked/uncheckedCapabilityLeaks.scala +- [x] library/src/scala/annotation/unchecked/uncheckedCaptures.scala +- [ ] library/src/scala/annotation/unchecked/uncheckedStable.scala +- [ ] library/src/scala/annotation/unchecked/uncheckedVariance.scala +- [x] library/src/scala/annotation/unroll.scala +- [ ] library/src/scala/annotation/unspecialized.scala +- [ ] library/src/scala/annotation/unused.scala +- [ ] library/src/scala/annotation/varargs.scala +- [ ] library/src/scala/beans/BeanProperty.scala +- [ ] library/src/scala/beans/BooleanBeanProperty.scala +- [x] library/src/scala/caps/package.scala +- [ ] library/src/scala/collection/ArrayOps.scala +- [ ] library/src/scala/collection/BitSet.scala +- [ ] library/src/scala/collection/BufferedIterator.scala +- [ ] library/src/scala/collection/BuildFrom.scala +- [ ] library/src/scala/collection/DefaultMap.scala +- [ ] library/src/scala/collection/Factory.scala +- [ ] library/src/scala/collection/Hashing.scala +- [ ] library/src/scala/collection/IndexedSeq.scala +- [ ] library/src/scala/collection/IndexedSeqView.scala +- [ ] library/src/scala/collection/Iterable.scala +- [ ] library/src/scala/collection/IterableOnce.scala +- [ ] library/src/scala/collection/Iterator.scala +- [ ] library/src/scala/collection/JavaConverters.scala +- [ ] library/src/scala/collection/LazyZipOps.scala +- [ ] library/src/scala/collection/LinearSeq.scala +- [ ] library/src/scala/collection/Map.scala +- [ ] library/src/scala/collection/MapView.scala +- [ ] library/src/scala/collection/Searching.scala +- [ ] library/src/scala/collection/Seq.scala +- [ ] library/src/scala/collection/SeqMap.scala +- [ ] library/src/scala/collection/SeqView.scala +- [ ] library/src/scala/collection/Set.scala +- [ ] library/src/scala/collection/SortedMap.scala +- [ ] library/src/scala/collection/SortedOps.scala +- [ ] library/src/scala/collection/SortedSet.scala +- [ ] library/src/scala/collection/Stepper.scala +- [ ] library/src/scala/collection/StepperShape.scala +- [ ] library/src/scala/collection/StrictOptimizedIterableOps.scala +- [ ] library/src/scala/collection/StrictOptimizedMapOps.scala +- [ ] library/src/scala/collection/StrictOptimizedSeqOps.scala +- [ ] library/src/scala/collection/StrictOptimizedSetOps.scala +- [ ] library/src/scala/collection/StrictOptimizedSortedMapOps.scala +- [ ] library/src/scala/collection/StrictOptimizedSortedSetOps.scala +- [ ] library/src/scala/collection/StringOps.scala +- [ ] library/src/scala/collection/StringParsers.scala +- [ ] library/src/scala/collection/View.scala +- [ ] library/src/scala/collection/WithFilter.scala +- [ ] library/src/scala/collection/concurrent/Map.scala +- [ ] library/src/scala/collection/concurrent/TrieMap.scala +- [ ] library/src/scala/collection/convert/AsJavaConverters.scala +- [ ] library/src/scala/collection/convert/AsJavaExtensions.scala +- [ ] library/src/scala/collection/convert/AsScalaConverters.scala +- [ ] library/src/scala/collection/convert/AsScalaExtensions.scala +- [ ] library/src/scala/collection/convert/ImplicitConversions.scala +- [ ] library/src/scala/collection/convert/JavaCollectionWrappers.scala +- [ ] library/src/scala/collection/convert/StreamExtensions.scala +- [ ] library/src/scala/collection/convert/impl/ArrayStepper.scala +- [ ] library/src/scala/collection/convert/impl/BinaryTreeStepper.scala +- [ ] library/src/scala/collection/convert/impl/BitSetStepper.scala +- [ ] library/src/scala/collection/convert/impl/ChampStepper.scala +- [ ] library/src/scala/collection/convert/impl/InOrderStepperBase.scala +- [ ] library/src/scala/collection/convert/impl/IndexedSeqStepper.scala +- [ ] library/src/scala/collection/convert/impl/IndexedStepperBase.scala +- [ ] library/src/scala/collection/convert/impl/IteratorStepper.scala +- [ ] library/src/scala/collection/convert/impl/NumericRangeStepper.scala +- [ ] library/src/scala/collection/convert/impl/RangeStepper.scala +- [ ] library/src/scala/collection/convert/impl/StringStepper.scala +- [ ] library/src/scala/collection/convert/impl/TableStepper.scala +- [ ] library/src/scala/collection/convert/impl/VectorStepper.scala +- [ ] library/src/scala/collection/generic/BitOperations.scala +- [ ] library/src/scala/collection/generic/CommonErrors.scala +- [ ] library/src/scala/collection/generic/DefaultSerializationProxy.scala +- [ ] library/src/scala/collection/generic/IsIterable.scala +- [ ] library/src/scala/collection/generic/IsIterableOnce.scala +- [ ] library/src/scala/collection/generic/IsMap.scala +- [ ] library/src/scala/collection/generic/IsSeq.scala +- [ ] library/src/scala/collection/generic/Subtractable.scala +- [ ] library/src/scala/collection/generic/package.scala +- [ ] library/src/scala/collection/immutable/ArraySeq.scala +- [ ] library/src/scala/collection/immutable/BitSet.scala +- [ ] library/src/scala/collection/immutable/ChampCommon.scala +- [ ] library/src/scala/collection/immutable/HashMap.scala +- [ ] library/src/scala/collection/immutable/HashSet.scala +- [ ] library/src/scala/collection/immutable/IntMap.scala +- [ ] library/src/scala/collection/immutable/Iterable.scala +- [ ] library/src/scala/collection/immutable/LazyList.scala +- [ ] library/src/scala/collection/immutable/List.scala +- [ ] library/src/scala/collection/immutable/ListMap.scala +- [ ] library/src/scala/collection/immutable/ListSet.scala +- [ ] library/src/scala/collection/immutable/LongMap.scala +- [ ] library/src/scala/collection/immutable/Map.scala +- [ ] library/src/scala/collection/immutable/NumericRange.scala +- [ ] library/src/scala/collection/immutable/Queue.scala +- [ ] library/src/scala/collection/immutable/Range.scala +- [ ] library/src/scala/collection/immutable/RedBlackTree.scala +- [ ] library/src/scala/collection/immutable/Seq.scala +- [ ] library/src/scala/collection/immutable/SeqMap.scala +- [ ] library/src/scala/collection/immutable/Set.scala +- [ ] library/src/scala/collection/immutable/SortedMap.scala +- [ ] library/src/scala/collection/immutable/SortedSet.scala +- [ ] library/src/scala/collection/immutable/Stream.scala +- [ ] library/src/scala/collection/immutable/StrictOptimizedSeqOps.scala +- [ ] library/src/scala/collection/immutable/TreeMap.scala +- [ ] library/src/scala/collection/immutable/TreeSeqMap.scala +- [ ] library/src/scala/collection/immutable/TreeSet.scala +- [ ] library/src/scala/collection/immutable/Vector.scala +- [ ] library/src/scala/collection/immutable/VectorMap.scala +- [ ] library/src/scala/collection/immutable/WrappedString.scala +- [ ] library/src/scala/collection/immutable/package.scala +- [ ] library/src/scala/collection/mutable/AnyRefMap.scala +- [ ] library/src/scala/collection/mutable/ArrayBuffer.scala +- [ ] library/src/scala/collection/mutable/ArrayBuilder.scala +- [ ] library/src/scala/collection/mutable/ArrayDeque.scala +- [ ] library/src/scala/collection/mutable/ArraySeq.scala +- [ ] library/src/scala/collection/mutable/BitSet.scala +- [ ] library/src/scala/collection/mutable/Buffer.scala +- [ ] library/src/scala/collection/mutable/Builder.scala +- [ ] library/src/scala/collection/mutable/CheckedIndexedSeqView.scala +- [ ] library/src/scala/collection/mutable/Cloneable.scala +- [ ] library/src/scala/collection/mutable/CollisionProofHashMap.scala +- [ ] library/src/scala/collection/mutable/Growable.scala +- [ ] library/src/scala/collection/mutable/GrowableBuilder.scala +- [ ] library/src/scala/collection/mutable/HashMap.scala +- [ ] library/src/scala/collection/mutable/HashSet.scala +- [ ] library/src/scala/collection/mutable/HashTable.scala +- [ ] library/src/scala/collection/mutable/ImmutableBuilder.scala +- [ ] library/src/scala/collection/mutable/IndexedSeq.scala +- [ ] library/src/scala/collection/mutable/Iterable.scala +- [ ] library/src/scala/collection/mutable/LinkedHashMap.scala +- [ ] library/src/scala/collection/mutable/LinkedHashSet.scala +- [ ] library/src/scala/collection/mutable/ListBuffer.scala +- [ ] library/src/scala/collection/mutable/ListMap.scala +- [ ] library/src/scala/collection/mutable/LongMap.scala +- [ ] library/src/scala/collection/mutable/Map.scala +- [ ] library/src/scala/collection/mutable/MultiMap.scala +- [ ] library/src/scala/collection/mutable/MutationTracker.scala +- [ ] library/src/scala/collection/mutable/OpenHashMap.scala +- [ ] library/src/scala/collection/mutable/PriorityQueue.scala +- [ ] library/src/scala/collection/mutable/Queue.scala +- [ ] library/src/scala/collection/mutable/RedBlackTree.scala +- [ ] library/src/scala/collection/mutable/ReusableBuilder.scala +- [ ] library/src/scala/collection/mutable/Seq.scala +- [ ] library/src/scala/collection/mutable/SeqMap.scala +- [ ] library/src/scala/collection/mutable/Set.scala +- [ ] library/src/scala/collection/mutable/Shrinkable.scala +- [ ] library/src/scala/collection/mutable/SortedMap.scala +- [ ] library/src/scala/collection/mutable/SortedSet.scala +- [ ] library/src/scala/collection/mutable/Stack.scala +- [ ] library/src/scala/collection/mutable/StringBuilder.scala +- [ ] library/src/scala/collection/mutable/TreeMap.scala +- [ ] library/src/scala/collection/mutable/TreeSet.scala +- [ ] library/src/scala/collection/mutable/UnrolledBuffer.scala +- [ ] library/src/scala/collection/mutable/WeakHashMap.scala +- [ ] library/src/scala/collection/mutable/package.scala +- [ ] library/src/scala/collection/package.scala +- [ ] library/src/scala/compat/Platform.scala +- [ ] library/src/scala/compiletime/Erased.scala +- [x] library/src/scala/compiletime/ops/any.scala +- [x] library/src/scala/compiletime/ops/boolean.scala +- [x] library/src/scala/compiletime/ops/double.scala +- [x] library/src/scala/compiletime/ops/float.scala +- [x] library/src/scala/compiletime/ops/int.scala +- [x] library/src/scala/compiletime/ops/long.scala +- [x] library/src/scala/compiletime/ops/string.scala +- [x] library/src/scala/compiletime/package.scala +- [x] library/src/scala/compiletime/testing/Error.scala +- [x] library/src/scala/compiletime/testing/ErrorKind.scala +- [x] library/src/scala/compiletime/testing/package.scala +- [ ] library/src/scala/concurrent/Awaitable.scala +- [ ] library/src/scala/concurrent/BatchingExecutor.scala +- [ ] library/src/scala/concurrent/BlockContext.scala +- [ ] library/src/scala/concurrent/Channel.scala +- [ ] library/src/scala/concurrent/DelayedLazyVal.scala +- [ ] library/src/scala/concurrent/ExecutionContext.scala +- [ ] library/src/scala/concurrent/Future.scala +- [ ] library/src/scala/concurrent/JavaConversions.scala +- [ ] library/src/scala/concurrent/Promise.scala +- [ ] library/src/scala/concurrent/SyncChannel.scala +- [ ] library/src/scala/concurrent/SyncVar.scala +- [ ] library/src/scala/concurrent/duration/Deadline.scala +- [ ] library/src/scala/concurrent/duration/Duration.scala +- [ ] library/src/scala/concurrent/duration/DurationConversions.scala +- [ ] library/src/scala/concurrent/duration/package.scala +- [ ] library/src/scala/concurrent/impl/ExecutionContextImpl.scala +- [ ] library/src/scala/concurrent/impl/FutureConvertersImpl.scala +- [ ] library/src/scala/concurrent/impl/Promise.scala +- [ ] library/src/scala/concurrent/package.scala +- [ ] library/src/scala/deprecated.scala +- [ ] library/src/scala/deprecatedInheritance.scala +- [ ] library/src/scala/deprecatedName.scala +- [ ] library/src/scala/deprecatedOverriding.scala +- [ ] library/src/scala/inline.scala +- [ ] library/src/scala/io/AnsiColor.scala +- [ ] library/src/scala/io/BufferedSource.scala +- [ ] library/src/scala/io/Codec.scala +- [ ] library/src/scala/io/Position.scala +- [ ] library/src/scala/io/Source.scala +- [ ] library/src/scala/io/StdIn.scala +- [ ] library/src/scala/jdk/Accumulator.scala +- [ ] library/src/scala/jdk/AnyAccumulator.scala +- [ ] library/src/scala/jdk/CollectionConverters.scala +- [ ] library/src/scala/jdk/DoubleAccumulator.scala +- [ ] library/src/scala/jdk/DurationConverters.scala +- [ ] library/src/scala/jdk/FunctionConverters.scala +- [ ] library/src/scala/jdk/FunctionExtensions.scala +- [ ] library/src/scala/jdk/FunctionWrappers.scala +- [ ] library/src/scala/jdk/FutureConverters.scala +- [ ] library/src/scala/jdk/IntAccumulator.scala +- [ ] library/src/scala/jdk/LongAccumulator.scala +- [ ] library/src/scala/jdk/OptionConverters.scala +- [ ] library/src/scala/jdk/OptionShape.scala +- [ ] library/src/scala/jdk/StreamConverters.scala +- [ ] library/src/scala/jdk/javaapi/CollectionConverters.scala +- [ ] library/src/scala/jdk/javaapi/DurationConverters.scala +- [ ] library/src/scala/jdk/javaapi/FunctionConverters.scala +- [ ] library/src/scala/jdk/javaapi/FutureConverters.scala +- [ ] library/src/scala/jdk/javaapi/OptionConverters.scala +- [ ] library/src/scala/jdk/javaapi/StreamConverters.scala +- [ ] library/src/scala/jdk/package.scala +- [ ] library/src/scala/language.scala +- [ ] library/src/scala/languageFeature.scala +- [x] library/src/scala/main.scala +- [ ] library/src/scala/math/BigDecimal.scala +- [ ] library/src/scala/math/BigInt.scala +- [ ] library/src/scala/math/Equiv.scala +- [ ] library/src/scala/math/Fractional.scala +- [ ] library/src/scala/math/Integral.scala +- [ ] library/src/scala/math/Numeric.scala +- [ ] library/src/scala/math/Ordered.scala +- [ ] library/src/scala/math/Ordering.scala +- [ ] library/src/scala/math/PartialOrdering.scala +- [ ] library/src/scala/math/PartiallyOrdered.scala +- [ ] library/src/scala/math/ScalaNumericConversions.scala +- [ ] library/src/scala/math/package.scala +- [ ] library/src/scala/native.scala +- [ ] library/src/scala/noinline.scala +- [ ] library/src/scala/package.scala +- [x] library/src/scala/quoted/Expr.scala +- [x] library/src/scala/quoted/ExprMap.scala +- [x] library/src/scala/quoted/Exprs.scala +- [x] library/src/scala/quoted/FromExpr.scala +- [x] library/src/scala/quoted/Quotes.scala +- [x] library/src/scala/quoted/ToExpr.scala +- [x] library/src/scala/quoted/Type.scala +- [x] library/src/scala/quoted/Varargs.scala +- [x] library/src/scala/quoted/runtime/Expr.scala +- [x] library/src/scala/quoted/runtime/Patterns.scala +- [x] library/src/scala/quoted/runtime/QuoteMatching.scala +- [x] library/src/scala/quoted/runtime/QuoteUnpickler.scala +- [x] library/src/scala/quoted/runtime/SplicedType.scala +- [x] library/src/scala/quoted/runtime/StopMacroExpansion.scala +- [ ] library/src/scala/ref/PhantomReference.scala +- [ ] library/src/scala/ref/Reference.scala +- [ ] library/src/scala/ref/ReferenceQueue.scala +- [ ] library/src/scala/ref/ReferenceWrapper.scala +- [ ] library/src/scala/ref/SoftReference.scala +- [ ] library/src/scala/ref/WeakReference.scala +- [ ] library/src/scala/reflect/ClassManifestDeprecatedApis.scala +- [ ] library/src/scala/reflect/ClassTag.scala +- [x] library/src/scala/reflect/Enum.scala +- [ ] library/src/scala/reflect/Manifest.scala +- [ ] library/src/scala/reflect/NameTransformer.scala +- [ ] library/src/scala/reflect/NoManifest.scala +- [ ] library/src/scala/reflect/OptManifest.scala +- [x] library/src/scala/reflect/Selectable.scala +- [x] library/src/scala/reflect/TypeTest.scala +- [x] library/src/scala/reflect/Typeable.scala +- [ ] library/src/scala/reflect/macros/internal/macroImpl.scala +- [ ] library/src/scala/reflect/package.scala +- [x] library/src/scala/runtime/$throws.scala +- [ ] library/src/scala/runtime/AbstractFunction0.scala +- [ ] library/src/scala/runtime/AbstractFunction1.scala +- [ ] library/src/scala/runtime/AbstractFunction10.scala +- [ ] library/src/scala/runtime/AbstractFunction11.scala +- [ ] library/src/scala/runtime/AbstractFunction12.scala +- [ ] library/src/scala/runtime/AbstractFunction13.scala +- [ ] library/src/scala/runtime/AbstractFunction14.scala +- [ ] library/src/scala/runtime/AbstractFunction15.scala +- [ ] library/src/scala/runtime/AbstractFunction16.scala +- [ ] library/src/scala/runtime/AbstractFunction17.scala +- [ ] library/src/scala/runtime/AbstractFunction18.scala +- [ ] library/src/scala/runtime/AbstractFunction19.scala +- [ ] library/src/scala/runtime/AbstractFunction2.scala +- [ ] library/src/scala/runtime/AbstractFunction20.scala +- [ ] library/src/scala/runtime/AbstractFunction21.scala +- [ ] library/src/scala/runtime/AbstractFunction22.scala +- [ ] library/src/scala/runtime/AbstractFunction3.scala +- [ ] library/src/scala/runtime/AbstractFunction4.scala +- [ ] library/src/scala/runtime/AbstractFunction5.scala +- [ ] library/src/scala/runtime/AbstractFunction6.scala +- [ ] library/src/scala/runtime/AbstractFunction7.scala +- [ ] library/src/scala/runtime/AbstractFunction8.scala +- [ ] library/src/scala/runtime/AbstractFunction9.scala +- [ ] library/src/scala/runtime/AbstractPartialFunction.scala +- [ ] library/src/scala/runtime/ArrayCharSequence.scala +- [x] library/src/scala/runtime/Arrays.scala +- [ ] library/src/scala/runtime/ClassValueCompat.scala +- [x] library/src/scala/runtime/EnumValue.scala +- [x] library/src/scala/runtime/FunctionXXL.scala +- [ ] library/src/scala/runtime/LambdaDeserialize.scala +- [ ] library/src/scala/runtime/LambdaDeserializer.scala +- [ ] library/src/scala/runtime/LazyRef.scala +- [x] library/src/scala/runtime/LazyVals.scala +- [x] library/src/scala/runtime/MatchCase.scala +- [ ] library/src/scala/runtime/MethodCache.scala +- [ ] library/src/scala/runtime/ModuleSerializationProxy.scala +- [ ] library/src/scala/runtime/NonLocalReturnControl.scala +- [ ] library/src/scala/runtime/Nothing$.scala +- [ ] library/src/scala/runtime/Null$.scala +- [ ] library/src/scala/runtime/PStatics.scala +- [ ] library/src/scala/runtime/RichBoolean.scala +- [ ] library/src/scala/runtime/RichByte.scala +- [ ] library/src/scala/runtime/RichChar.scala +- [ ] library/src/scala/runtime/RichDouble.scala +- [ ] library/src/scala/runtime/RichFloat.scala +- [ ] library/src/scala/runtime/RichInt.scala +- [ ] library/src/scala/runtime/RichLong.scala +- [ ] library/src/scala/runtime/RichShort.scala +- [x] library/src/scala/runtime/Scala3RunTime.scala +- [ ] library/src/scala/runtime/ScalaNumberProxy.scala +- [ ] library/src/scala/runtime/ScalaRunTime.scala +- [ ] library/src/scala/runtime/StructuralCallSite.scala +- [ ] library/src/scala/runtime/Tuple2Zipped.scala +- [ ] library/src/scala/runtime/Tuple3Zipped.scala +- [x] library/src/scala/runtime/TupleMirror.scala +- [x] library/src/scala/runtime/TupleXXL.scala +- [x] library/src/scala/runtime/TupledFunctions.scala +- [x] library/src/scala/runtime/Tuples.scala +- [x] library/src/scala/runtime/TypeBox.scala +- [x] library/src/scala/runtime/coverage/Invoker.scala +- [ ] library/src/scala/runtime/java8/JFunction0$mcB$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction0$mcC$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction0$mcD$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction0$mcF$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction0$mcI$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction0$mcJ$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction0$mcS$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction0$mcV$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction0$mcZ$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction1$mcDD$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction1$mcDF$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction1$mcDI$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction1$mcDJ$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction1$mcFD$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction1$mcFF$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction1$mcFI$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction1$mcFJ$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction1$mcID$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction1$mcIF$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction1$mcII$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction1$mcIJ$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction1$mcJD$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction1$mcJF$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction1$mcJI$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction1$mcJJ$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction1$mcVD$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction1$mcVF$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction1$mcVI$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction1$mcVJ$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction1$mcZD$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction1$mcZF$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction1$mcZI$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction1$mcZJ$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction2$mcDDD$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction2$mcDDI$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction2$mcDDJ$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction2$mcDID$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction2$mcDII$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction2$mcDIJ$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction2$mcDJD$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction2$mcDJI$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction2$mcDJJ$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction2$mcFDD$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction2$mcFDI$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction2$mcFDJ$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction2$mcFID$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction2$mcFII$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction2$mcFIJ$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction2$mcFJD$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction2$mcFJI$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction2$mcFJJ$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction2$mcIDD$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction2$mcIDI$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction2$mcIDJ$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction2$mcIID$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction2$mcIII$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction2$mcIIJ$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction2$mcIJD$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction2$mcIJI$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction2$mcIJJ$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction2$mcJDD$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction2$mcJDI$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction2$mcJDJ$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction2$mcJID$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction2$mcJII$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction2$mcJIJ$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction2$mcJJD$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction2$mcJJI$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction2$mcJJJ$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction2$mcVDD$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction2$mcVDI$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction2$mcVDJ$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction2$mcVID$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction2$mcVII$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction2$mcVIJ$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction2$mcVJD$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction2$mcVJI$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction2$mcVJJ$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction2$mcZDD$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction2$mcZDI$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction2$mcZDJ$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction2$mcZID$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction2$mcZII$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction2$mcZIJ$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction2$mcZJD$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction2$mcZJI$sp.scala +- [ ] library/src/scala/runtime/java8/JFunction2$mcZJJ$sp.scala +- [x] library/src/scala/runtime/stdLibPatches/Predef.scala +- [x] library/src/scala/runtime/stdLibPatches/language.scala +- [ ] library/src/scala/specialized.scala +- [ ] library/src/scala/sys/BooleanProp.scala +- [ ] library/src/scala/sys/Prop.scala +- [ ] library/src/scala/sys/PropImpl.scala +- [ ] library/src/scala/sys/ShutdownHookThread.scala +- [ ] library/src/scala/sys/SystemProperties.scala +- [ ] library/src/scala/sys/package.scala +- [ ] library/src/scala/sys/process/BasicIO.scala +- [ ] library/src/scala/sys/process/Parser.scala +- [ ] library/src/scala/sys/process/Process.scala +- [ ] library/src/scala/sys/process/ProcessBuilder.scala +- [ ] library/src/scala/sys/process/ProcessBuilderImpl.scala +- [ ] library/src/scala/sys/process/ProcessIO.scala +- [ ] library/src/scala/sys/process/ProcessImpl.scala +- [ ] library/src/scala/sys/process/ProcessLogger.scala +- [ ] library/src/scala/sys/process/package.scala +- [ ] library/src/scala/throws.scala +- [ ] library/src/scala/transient.scala +- [ ] library/src/scala/typeConstraints.scala +- [ ] library/src/scala/unchecked.scala +- [ ] library/src/scala/util/ChainingOps.scala +- [x] library/src/scala/util/CommandLineParser.scala +- [ ] library/src/scala/util/DynamicVariable.scala +- [ ] library/src/scala/util/Either.scala +- [x] library/src/scala/util/FromDigits.scala +- [x] library/src/scala/util/NotGiven.scala +- [ ] library/src/scala/util/Properties.scala +- [ ] library/src/scala/util/Random.scala +- [ ] library/src/scala/util/Sorting.scala +- [ ] library/src/scala/util/Try.scala +- [x] library/src/scala/util/TupledFunction.scala +- [ ] library/src/scala/util/Using.scala +- [x] library/src/scala/util/boundary.scala +- [ ] library/src/scala/util/control/Breaks.scala +- [ ] library/src/scala/util/control/ControlThrowable.scala +- [ ] library/src/scala/util/control/Exception.scala +- [ ] library/src/scala/util/control/NoStackTrace.scala +- [ ] library/src/scala/util/control/NonFatal.scala +- [x] library/src/scala/util/control/NonLocalReturns.scala +- [ ] library/src/scala/util/control/TailCalls.scala +- [ ] library/src/scala/util/hashing/ByteswapHashing.scala +- [ ] library/src/scala/util/hashing/Hashing.scala +- [ ] library/src/scala/util/hashing/MurmurHash3.scala +- [ ] library/src/scala/util/hashing/package.scala +- [ ] library/src/scala/util/matching/Regex.scala +- [ ] library/src/scala/util/package.scala +- [ ] library/src/scala/volatile.scala From 40f875ffa5263ca904f8584e9614c28230a388e7 Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Thu, 7 Aug 2025 16:58:12 +0200 Subject: [PATCH 031/101] Add capture checking to scala.Array --- TODO.md | 2 +- library/src/scala/Array.scala | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/TODO.md b/TODO.md index d8e7e698c72e..aa197bbcef2a 100644 --- a/TODO.md +++ b/TODO.md @@ -1,6 +1,6 @@ - [ ] library/src/scala/AnyValCompanion.scala - [ ] library/src/scala/App.scala -- [ ] library/src/scala/Array.scala +- [x] library/src/scala/Array.scala - [ ] library/src/scala/Boolean.scala - [ ] library/src/scala/Byte.scala - [x] library/src/scala/CanEqual.scala diff --git a/library/src/scala/Array.scala b/library/src/scala/Array.scala index 8daa1859cf44..a1924d61846d 100644 --- a/library/src/scala/Array.scala +++ b/library/src/scala/Array.scala @@ -13,6 +13,7 @@ package scala import scala.language.`2.13` +import language.experimental.captureChecking //import scala.collection.generic._ import scala.collection.{Factory, immutable, mutable} @@ -71,7 +72,7 @@ object Array { * @param it the iterable collection * @return an array consisting of elements of the iterable collection */ - def from[A : ClassTag](it: IterableOnce[A]): Array[A] = it match { + def from[A : ClassTag](it: IterableOnce[A]^): Array[A] = it match { case it: Iterable[A] => it.toArray[A] case _ => it.iterator.toArray[A] } From 0e37625337df4b45e12ad22203a109e4b34880da Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Mon, 11 Aug 2025 14:55:33 +0200 Subject: [PATCH 032/101] Copy generator implementations from scala 2.13.17 --- project/GenerateAnyVals.scala | 514 +++++++++++++++++++++++ project/GenerateFunctionConverters.scala | 433 +++++++++++++++++++ project/genprod.scala | 479 +++++++++++++++++++++ 3 files changed, 1426 insertions(+) create mode 100644 project/GenerateAnyVals.scala create mode 100644 project/GenerateFunctionConverters.scala create mode 100644 project/genprod.scala diff --git a/project/GenerateAnyVals.scala b/project/GenerateAnyVals.scala new file mode 100644 index 000000000000..df9917c21f31 --- /dev/null +++ b/project/GenerateAnyVals.scala @@ -0,0 +1,514 @@ +package scala.build + +/** Code generation of the AnyVal types and their companions. */ +trait GenerateAnyValReps { + self: GenerateAnyVals => + + sealed abstract class AnyValNum(name: String, repr: Option[String], javaEquiv: String) + extends AnyValRep(name,repr,javaEquiv) { + + case class Op(op : String, doc : String) + + private def companionCoercions(deprecated: Boolean, tos: AnyValRep*): List[String] = + tos.toList.flatMap { to => + val code = s"implicit def @javaequiv@2${to.javaEquiv}(x: @name@): ${to.name} = x.to${to.name}" + if (deprecated) + List(s"""@deprecated("Implicit conversion from @name@ to ${to.name} is dangerous because it loses precision. Write `.to${to.name}` instead.", "2.13.1")""", code) + else + List(code) + } + + def coercionComment = +"""/** Language mandated coercions from @name@ to "wider" types. */ +import scala.language.implicitConversions""" + + def implicitCoercions: List[String] = { + val coercions = this match { + case B => companionCoercions(deprecated = false, S, I, L, F, D) + case S | C => companionCoercions(deprecated = false, I, L, F, D) + case I => companionCoercions(deprecated = true, F) ++ companionCoercions(deprecated = false, L, D) + case L => companionCoercions(deprecated = true, F, D) + case F => companionCoercions(deprecated = false, D) + case _ => Nil + } + if (coercions.isEmpty) Nil + else coercionComment.linesIterator.toList ++ coercions + } + + def isCardinal: Boolean = isIntegerType(this) + def unaryOps = { + val ops = List( + Op("+", "/** Returns this value, unmodified. */"), + Op("-", "/** Returns the negation of this value. */")) + + if(isCardinal) + Op("~", "/**\n" + + " * Returns the bitwise negation of this value.\n" + + " * @example {{{\n" + + " * ~5 == -6\n" + + " * // in binary: ~00000101 ==\n" + + " * // 11111010\n" + + " * }}}\n" + + " */") :: ops + else ops + } + + def bitwiseOps = + if (isCardinal) + List( + Op("|", "/**\n" + + " * Returns the bitwise OR of this value and `x`.\n" + + " * @example {{{\n" + + " * (0xf0 | 0xaa) == 0xfa\n" + + " * // in binary: 11110000\n" + + " * // | 10101010\n" + + " * // --------\n" + + " * // 11111010\n" + + " * }}}\n" + + " */"), + Op("&", "/**\n" + + " * Returns the bitwise AND of this value and `x`.\n" + + " * @example {{{\n" + + " * (0xf0 & 0xaa) == 0xa0\n" + + " * // in binary: 11110000\n" + + " * // & 10101010\n" + + " * // --------\n" + + " * // 10100000\n" + + " * }}}\n" + + " */"), + Op("^", "/**\n" + + " * Returns the bitwise XOR of this value and `x`.\n" + + " * @example {{{\n" + + " * (0xf0 ^ 0xaa) == 0x5a\n" + + " * // in binary: 11110000\n" + + " * // ^ 10101010\n" + + " * // --------\n" + + " * // 01011010\n" + + " * }}}\n" + + " */")) + else Nil + + def shiftOps = + if (isCardinal) + List( + Op("<<", "/**\n" + + " * Returns this value bit-shifted left by the specified number of bits,\n" + + " * filling in the new right bits with zeroes.\n" + + " * @example {{{ 6 << 3 == 48 // in binary: 0110 << 3 == 0110000 }}}\n" + + " */"), + + Op(">>>", "/**\n" + + " * Returns this value bit-shifted right by the specified number of bits,\n" + + " * filling the new left bits with zeroes.\n" + + " * @example {{{ 21 >>> 3 == 2 // in binary: 010101 >>> 3 == 010 }}}\n" + + " * @example {{{\n" + + " * -21 >>> 3 == 536870909\n" + + " * // in binary: 11111111 11111111 11111111 11101011 >>> 3 ==\n" + + " * // 00011111 11111111 11111111 11111101\n" + + " * }}}\n" + + " */"), + + Op(">>", "/**\n" + + " * Returns this value bit-shifted right by the specified number of bits,\n" + + " * filling in the left bits with the same value as the left-most bit of this.\n" + + " * The effect of this is to retain the sign of the value.\n" + + " * @example {{{\n" + + " * -21 >> 3 == -3\n" + + " * // in binary: 11111111 11111111 11111111 11101011 >> 3 ==\n" + + " * // 11111111 11111111 11111111 11111101\n" + + " * }}}\n" + + " */")) + else Nil + + def comparisonOps = List( + Op("==", "/** Returns `true` if this value is equal to x, `false` otherwise. */"), + Op("!=", "/** Returns `true` if this value is not equal to x, `false` otherwise. */"), + Op("<", "/** Returns `true` if this value is less than x, `false` otherwise. */"), + Op("<=", "/** Returns `true` if this value is less than or equal to x, `false` otherwise. */"), + Op(">", "/** Returns `true` if this value is greater than x, `false` otherwise. */"), + Op(">=", "/** Returns `true` if this value is greater than or equal to x, `false` otherwise. */")) + + def otherOps = List( + Op("+", "/** Returns the sum of this value and `x`. */"), + Op("-", "/** Returns the difference of this value and `x`. */"), + Op("*", "/** Returns the product of this value and `x`. */"), + Op("/", "/** Returns the quotient of this value and `x`. */"), + Op("%", "/** Returns the remainder of the division of this value by `x`. */")) + + // Given two numeric value types S and T , the operation type of S and T is defined as follows: + // If both S and T are subrange types then the operation type of S and T is Int. + // Otherwise the operation type of S and T is the larger of the two types wrt ranking. + // Given two numeric values v and w the operation type of v and w is the operation type + // of their run-time types. + def opType(that: AnyValNum): AnyValNum = { + val rank = IndexedSeq(I, L, F, D) + (rank indexOf this, rank indexOf that) match { + case (-1, -1) => I + case (r1, r2) => rank apply (r1 max r2) + } + } + + def mkCoercions = numeric map (x => "def to%s: %s".format(x, x)) + def mkUnaryOps = unaryOps map (x => "%s\n def unary_%s : %s".format(x.doc, x.op, this opType I)) + def mkStringOps = List( + "@deprecated(\"Adding a number and a String is deprecated. Use the string interpolation `s\\\"$num$str\\\"`\", \"2.13.0\")\n def +(x: String): String" + ) + def mkShiftOps = ( + for (op <- shiftOps ; arg <- List(I, L)) yield { + val doc = op.doc + (if (this == L || arg == I) "" else "\n @deprecated(\"shifting a value by a `Long` argument is deprecated (except when the value is a `Long`).\\nCall `toInt` on the argument to maintain the current behavior and avoid the deprecation warning.\", \"2.12.7\")") + "%s\n def %s(x: %s): %s".format(doc, op.op, arg, this opType I) + } + ) + + def clumps: List[List[String]] = { + val xs1 = List(mkCoercions, mkUnaryOps, mkStringOps, mkShiftOps) map (xs => if (xs.isEmpty) xs else xs :+ "") + val xs2 = List( + mkBinOpsGroup(comparisonOps, numeric, _ => Z), + mkBinOpsGroup(bitwiseOps, cardinal, this opType _), + mkBinOpsGroup(otherOps, numeric, this opType _) + ) + xs1 ++ xs2 + } + def classLines = (clumps :+ commonClassLines).foldLeft(List[String]()) { + case (res, Nil) => res + case (res, lines) => + val xs = lines map { + case "" => "" + case s => interpolate(s) + } + res ++ xs + } + def objectLines = { + val comp = if (isCardinal) cardinalCompanion else floatingCompanion + interpolate(comp + allCompanions + "\n" + nonUnitCompanions).trim.linesIterator.toList ++ (implicitCoercions map interpolate) + } + + /** Makes a set of binary operations based on the given set of ops, args, and resultFn. + * + * @param ops list of function names e.g. List(">>", "%") + * @param args list of types which should appear as arguments + * @param resultFn function which calculates return type based on arg type + * @return list of function definitions + */ + def mkBinOpsGroup(ops: List[Op], args: List[AnyValNum], resultFn: AnyValNum => AnyValRep): List[String] = ( + ops flatMap (op => + args.map(arg => + "%s\n def %s(x: %s): %s".format(op.doc, op.op, arg, resultFn(arg))) :+ "" + ) + ).toList + } + + sealed abstract class AnyValRep(val name: String, val repr: Option[String], val javaEquiv: String) { + def classLines: List[String] + def objectLines: List[String] + def commonClassLines = List( + "// Provide a more specific return type for Scaladoc", + "override def getClass(): Class[@name@] = ???" + ) + + def lcname = name.toLowerCase + def boxedSimpleName = this match { + case C => "Character" + case I => "Integer" + case _ => name + } + def boxedName = this match { + case U => "scala.runtime.BoxedUnit" + case _ => "java.lang." + boxedSimpleName + } + def zeroRep = this match { + case L => "0L" + case F => "0.0f" + case D => "0.0d" + case _ => "0" + } + + def representation = repr.map(", a " + _).getOrElse("") + + def indent(s: String) = if (s == "") "" else " " + s + def indentN(s: String) = s.linesIterator map indent mkString "\n" + + def boxUnboxInterpolations = Map( + "@boxRunTimeDoc@" -> """ + * Runtime implementation determined by `scala.runtime.BoxesRunTime.boxTo%s`. See [[https://github.com/scala/scala src/library/scala/runtime/BoxesRunTime.java]]. + *""".format(boxedSimpleName), + "@unboxRunTimeDoc@" -> """ + * Runtime implementation determined by `scala.runtime.BoxesRunTime.unboxTo%s`. See [[https://github.com/scala/scala src/library/scala/runtime/BoxesRunTime.java]]. + *""".format(name), + "@unboxDoc@" -> "the %s resulting from calling %sValue() on `x`".format(name, lcname), + "@boxImpl@" -> "???", + "@unboxImpl@" -> "???" + ) + def interpolations = Map( + "@article@" -> (if (this == I) "an" else "a"), + "@name@" -> name, + "@representation@" -> representation, + "@javaequiv@" -> javaEquiv, + "@boxed@" -> boxedName, + "@lcname@" -> lcname, + "@zero@" -> zeroRep + ) ++ boxUnboxInterpolations + + def interpolate(s: String): String = interpolations.foldLeft(s) { + case (str, (key, value)) => str.replaceAll(key, value) + } + def classDoc = interpolate(classDocTemplate) + def objectDoc = "" + def mkImports = "" + + def mkClass = assemble("final abstract class " + name + " private extends AnyVal", classLines) + def mkObject = assemble("object " + name + " extends AnyValCompanion", objectLines) + def make() = List[String]( + headerTemplate, + mkImports, + classDoc, + mkClass, + objectDoc, + mkObject + ) mkString "" + + def assemble(decl: String, lines: List[String]): String = { + val body = if (lines.isEmpty) " { }\n\n" else lines map indent mkString (" {\n", "\n", "\n}\n") + + decl + body + "\n" + } + override def toString = name + } +} + +trait GenerateAnyValTemplates { + def headerTemplate = """/* + * 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. + */ + +// DO NOT EDIT, CHANGES WILL BE LOST +// This auto-generated code can be modified in "project/GenerateAnyVals.scala". +// Afterwards, running "sbt generateSources" regenerates this source file. + +package scala + +""" + + def classDocTemplate = (""" +/** `@name@`@representation@ (equivalent to Java's `@javaequiv@` primitive type) is a + * subtype of [[scala.AnyVal]]. Instances of `@name@` are not + * represented by an object in the underlying runtime system. + * + * There is an implicit conversion from [[scala.@name@]] => [[scala.runtime.Rich@name@]] + * which provides useful non-primitive operations. + */ +""".trim + "\n") + + def allCompanions = """ +/** Transform a value type into a boxed reference type. + *@boxRunTimeDoc@ + * @param x the @name@ to be boxed + * @return a @boxed@ offering `x` as its underlying value. + */ +def box(x: @name@): @boxed@ = @boxImpl@ + +/** Transform a boxed type into a value type. Note that this + * method is not typesafe: it accepts any Object, but will throw + * an exception if the argument is not a @boxed@. + *@unboxRunTimeDoc@ + * @param x the @boxed@ to be unboxed. + * @throws ClassCastException if the argument is not a @boxed@ + * @return @unboxDoc@ + */ +def unbox(x: java.lang.Object): @name@ = @unboxImpl@ + +/** The String representation of the scala.@name@ companion object. */ +override def toString = "object scala.@name@" +""" + + def nonUnitCompanions = "" // todo + + def cardinalCompanion = """ +/** The smallest value representable as @article@ @name@. */ +final val MinValue = @boxed@.MIN_VALUE + +/** The largest value representable as @article@ @name@. */ +final val MaxValue = @boxed@.MAX_VALUE +""" + + def floatingCompanion = """ +/** The smallest positive value greater than @zero@ which is + * representable as a @name@. + */ +final val MinPositiveValue = @boxed@.MIN_VALUE +final val NaN = @boxed@.NaN +final val PositiveInfinity = @boxed@.POSITIVE_INFINITY +final val NegativeInfinity = @boxed@.NEGATIVE_INFINITY + +/** The negative number with the greatest (finite) absolute value which is representable + * by a @name@. Note that it differs from [[java.lang.@name@.MIN_VALUE]], which + * is the smallest positive value representable by a @name@. In Scala that number + * is called @name@.MinPositiveValue. + */ +final val MinValue = -@boxed@.MAX_VALUE + +/** The largest finite positive number representable as a @name@. */ +final val MaxValue = @boxed@.MAX_VALUE +""" +} + +class GenerateAnyVals extends GenerateAnyValReps with GenerateAnyValTemplates { + object B extends AnyValNum("Byte", Some("8-bit signed integer"), "byte") + object S extends AnyValNum("Short", Some("16-bit signed integer"), "short") + object C extends AnyValNum("Char", Some("16-bit unsigned integer"), "char") + object I extends AnyValNum("Int", Some("32-bit signed integer"), "int") + object L extends AnyValNum("Long", Some("64-bit signed integer"), "long") + object F extends AnyValNum("Float", Some("32-bit IEEE-754 floating point number"), "float") + object D extends AnyValNum("Double", Some("64-bit IEEE-754 floating point number"), "double") + object Z extends AnyValRep("Boolean", None, "boolean") { + def classLines = """ +/** Negates a Boolean expression. + * + * - `!a` results in `false` if and only if `a` evaluates to `true` and + * - `!a` results in `true` if and only if `a` evaluates to `false`. + * + * @return the negated expression + */ +def unary_! : Boolean + +/** Compares two Boolean expressions and returns `true` if they evaluate to the same value. + * + * `a == b` returns `true` if and only if + * - `a` and `b` are `true` or + * - `a` and `b` are `false`. + */ +def ==(x: Boolean): Boolean + +/** + * Compares two Boolean expressions and returns `true` if they evaluate to a different value. + * + * `a != b` returns `true` if and only if + * - `a` is `true` and `b` is `false` or + * - `a` is `false` and `b` is `true`. + */ +def !=(x: Boolean): Boolean + +/** Compares two Boolean expressions and returns `true` if one or both of them evaluate to true. + * + * `a || b` returns `true` if and only if + * - `a` is `true` or + * - `b` is `true` or + * - `a` and `b` are `true`. + * + * @note This method uses 'short-circuit' evaluation and + * behaves as if it was declared as `def ||(x: => Boolean): Boolean`. + * If `a` evaluates to `true`, `true` is returned without evaluating `b`. + */ +def ||(x: Boolean): Boolean + +/** Compares two Boolean expressions and returns `true` if both of them evaluate to true. + * + * `a && b` returns `true` if and only if + * - `a` and `b` are `true`. + * + * @note This method uses 'short-circuit' evaluation and + * behaves as if it was declared as `def &&(x: => Boolean): Boolean`. + * If `a` evaluates to `false`, `false` is returned without evaluating `b`. + */ +def &&(x: Boolean): Boolean + +// Compiler won't build with these seemingly more accurate signatures +// def ||(x: => Boolean): Boolean +// def &&(x: => Boolean): Boolean + +/** Compares two Boolean expressions and returns `true` if one or both of them evaluate to true. + * + * `a | b` returns `true` if and only if + * - `a` is `true` or + * - `b` is `true` or + * - `a` and `b` are `true`. + * + * @note This method evaluates both `a` and `b`, even if the result is already determined after evaluating `a`. + */ +def |(x: Boolean): Boolean + +/** Compares two Boolean expressions and returns `true` if both of them evaluate to true. + * + * `a & b` returns `true` if and only if + * - `a` and `b` are `true`. + * + * @note This method evaluates both `a` and `b`, even if the result is already determined after evaluating `a`. + */ +def &(x: Boolean): Boolean + +/** Compares two Boolean expressions and returns `true` if they evaluate to a different value. + * + * `a ^ b` returns `true` if and only if + * - `a` is `true` and `b` is `false` or + * - `a` is `false` and `b` is `true`. + */ +def ^(x: Boolean): Boolean + +// Provide a more specific return type for Scaladoc +override def getClass(): Class[Boolean] = ??? + """.trim.linesIterator.toList + + def objectLines = interpolate(allCompanions + "\n" + nonUnitCompanions).linesIterator.toList + } + object U extends AnyValRep("Unit", None, "void") { + override def classDoc = """ +/** `Unit` is a subtype of [[scala.AnyVal]]. There is only one value of type + * `Unit`, `()`, and it is not represented by any object in the underlying + * runtime system. A method with return type `Unit` is analogous to a Java + * method which is declared `void`. + */ +""" + def classLines = List( + "// Provide a more specific return type for Scaladoc", + "override def getClass(): Class[Unit] = ???" + ) + def objectLines = interpolate(allCompanions).linesIterator.toList + + private def nono = "`Unit` companion object is not allowed in source; instead, use `()` for the unit value" + override def mkObject = s"""@scala.annotation.compileTimeOnly("$nono")\n${super.mkObject}""" + + override def boxUnboxInterpolations = Map( + "@boxRunTimeDoc@" -> """ + * This method is not intended for use in source code. + * The runtime representation of this value is platform specific. + *""", + "@unboxRunTimeDoc@" -> """ + * This method is not intended for use in source code. + * The result of successfully unboxing a value is `()`. + *""", + "@unboxDoc@" -> "the Unit value ()", + "@boxImpl@" -> "scala.runtime.BoxedUnit.UNIT", + "@unboxImpl@" -> "x.asInstanceOf[scala.runtime.BoxedUnit]" + ) + } + + def isSubrangeType = Set(B, S, C) + def isIntegerType = Set(B, S, C, I, L) + def isFloatingType = Set(F, D) + def isWideType = Set(L, D) + + def cardinal = numeric filter isIntegerType + def numeric = List(B, S, C, I, L, F, D) + def values = List(U, Z) ++ numeric + + def make() = values map (x => (x.name, x.make())) +} + +object GenerateAnyVals { + def run(outDir: java.io.File): Unit = { + val av = new GenerateAnyVals + + av.make() foreach { case (name, code ) => + val file = new java.io.File(outDir, name + ".scala") + sbt.IO.write(file, code, java.nio.charset.StandardCharsets.UTF_8, false) + } + } +} diff --git a/project/GenerateFunctionConverters.scala b/project/GenerateFunctionConverters.scala new file mode 100644 index 000000000000..31ef68cdb3c0 --- /dev/null +++ b/project/GenerateFunctionConverters.scala @@ -0,0 +1,433 @@ +/* + * 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. + */ + +object GenerateFunctionConverters { + case class Artifact(name: String, content: String) + + val copyright = + s"""/* + | * 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. + | */ + | + |// GENERATED CODE: DO NOT EDIT. + |""".stripMargin + + val packaging = "package scala.jdk" + + import scala.tools.nsc._ + val settings = new Settings(msg => sys.error(msg)) + def us(cl: ClassLoader): List[String] = cl match { + case ucl: java.net.URLClassLoader => ucl.getURLs.map(u => new java.io.File(u.toURI).getAbsolutePath).toList ::: us(ucl.getParent) + case _ => Nil + } + settings.classpath.value = us(settings.getClass.getClassLoader).mkString(java.io.File.pathSeparator) + val compiler = new Global(settings) + val run = new compiler.Run + + import compiler._, definitions._ + locally { + // make sure `java.lang.Double` prints as `java.lang.Double`, not just `Double` (which resolves to `scala.Double`) + val f = classOf[scala.reflect.internal.Definitions#DefinitionsClass].getDeclaredField("UnqualifiedOwners") + f.setAccessible(true) + f.set(definitions, definitions.UnqualifiedOwners.filter(_.fullNameString != "java.lang")) + } + + def primitiveBox(tp: Type): Type = tp.typeSymbol match { + case UnitClass => BoxedUnitClass.tpe + case ByteClass => BoxedByteClass.tpe + case ShortClass => BoxedShortClass.tpe + case CharClass => BoxedCharacterClass.tpe + case IntClass => BoxedIntClass.tpe + case LongClass => BoxedLongClass.tpe + case FloatClass => BoxedFloatClass.tpe + case DoubleClass => BoxedDoubleClass.tpe + case BooleanClass => BoxedBooleanClass.tpe + case _ => tp + } + + implicit class IndentMe(v: Vector[String]) { + def indent: Vector[String] = v.map(" " + _) + } + + implicit class FlattenMe(v: Vector[Vector[String]]) { + def mkVec(join: String = ""): Vector[String] = { + val vb = Vector.newBuilder[String] + var first = true + v.foreach{ vi => + if (!first) vb += join + first = false + vb ++= vi + } + vb.result() + } + } + + implicit class DoubleFlattenMe(v: Vector[Vector[Vector[String]]]) { + def mkVecVec(join: String = ""): Vector[String] = { + val vb = Vector.newBuilder[String] + var first = true + v.foreach{ vi => + if (!first) { vb += join; vb += join } + first = false + var ifirst = true + vi.foreach{ vj => + if (!ifirst) vb += join + ifirst = false + vb ++= vj + } + } + vb.result() + } + } + + implicit class SplitMyLinesAndStuff(s: String) { + // work around scala/bug#11125 + def toVec = Predef.augmentString(s).lines.toVector + def nonBlank = s.trim.length > 0 + } + + implicit class TreeToText(t: Tree) { + // work around scala/bug#11125 + def text = Predef.augmentString(showCode(t).replace("$", "")).lines.toVector + } + + case class Prioritized(lines: Vector[String], priority: Int) { + def withPriority(i: Int) = copy(priority = i) + } + + case class SamConversionCode( + base: String, + wrappedAsScala: Vector[String], + asScalaAnyVal: Vector[String], + implicitToScala: Vector[String], + asScalaDef: Vector[String], + wrappedAsJava: Vector[String], + asJavaAnyVal: Vector[String], + implicitToJava: Prioritized, + asJavaDef: Vector[String] + ) { + def impls: Vector[Vector[String]] = Vector(wrappedAsScala, asScalaAnyVal, wrappedAsJava, asJavaAnyVal) + def defs: Vector[Vector[String]] = Vector(asScalaDef, asJavaDef) + def withPriority(i: Int): SamConversionCode = copy(implicitToJava = implicitToJava.withPriority(i)) + } + object SamConversionCode { + def apply(scc: SamConversionCode*): (Vector[String], Vector[String], Vector[Vector[String]]) = { + val sccDepthSet = scc.map(_.implicitToJava.priority).toSet + val codes = + { + if (sccDepthSet != (0 to sccDepthSet.max).toSet) { + val sccDepthMap = sccDepthSet.toList.sorted.zipWithIndex.toMap + scc.map(x => x.withPriority(sccDepthMap(x.implicitToJava.priority))) + } + else scc + }.toVector.sortBy(_.base) + def priorityName(n: Int, pure: Boolean = false): String = { + val pre = + if (pure) s"Priority${n}FunctionExtensions" + else s"trait ${priorityName(n, pure = true)}" + if (!pure && n < (sccDepthSet.size-1)) s"$pre extends ${priorityName(n+1, pure = true)}" else pre + } + val impls = + "object FunctionWrappers {" +: { + codes.map(_.impls).mkVecVec().indent + } :+ "}" + val explicitDefs = codes.map(_.defs).mkVecVec() + val traits = codes.groupBy(_.implicitToJava.priority).toVector.sortBy(- _._1).map{ case (k,vs) => + s"import language.implicitConversions" +: + "" +: + s"${priorityName(k)} {" +: + s" import FunctionWrappers._" +: + s" " +: + { + vs.map(_.implicitToJava.lines).mkVec().indent ++ + ( + if (k == 0) Vector.fill(3)(" ") ++ codes.map(_.implicitToScala).mkVec().indent + else Vector() + ) + } :+ + s"}" + } + (impls, explicitDefs, traits) + } + } + + private def buildWrappersViaReflection: Seq[SamConversionCode] = { + + val pack: Symbol = rootMirror.getPackageIfDefined("java.util.function") + + case class Jfn(iface: Symbol, sam: Symbol) { + lazy val genericCount = iface.typeParams.length + lazy val name = sam.name.toTermName + lazy val title = iface.name.encoded + lazy val params = sam.info.params + lazy val sig = sam typeSignatureIn iface.info + lazy val pTypes = sig.params.map(_.info) + lazy val rType = sig.resultType + def arity = params.length + } + + val sams = pack.info.decls. + map(d => (d, d.typeSignature.members.filter(_.isAbstract).toList)). + collect{ case (d, m :: Nil) if d.isAbstract => Jfn(d, m) } + + def generate(jfn: Jfn): SamConversionCode = { + def mkRef(tp: Type): Tree = if (tp.typeSymbol.isTypeParameter) Ident(tp.typeSymbol.name.toTypeName) else tq"$tp" + + // Types for the Java SAM and the corresponding Scala function, plus all type parameters + val scalaType = gen.mkAttributedRef(FunctionClass(jfn.arity)) + val javaType = gen.mkAttributedRef(jfn.iface) + val tnParams: List[TypeName] = jfn.iface.typeParams.map(_.name.toTypeName) + val tdParams: List[TypeDef] = tnParams.map(TypeDef(NoMods, _, Nil, EmptyTree)) + val javaTargs: List[Tree] = tdParams.map(_.name).map(Ident(_)) + val scalaTargTps = jfn.pTypes :+ jfn.rType + val scalaTargBoxedTps = scalaTargTps.map(primitiveBox) + val scalaTargs: List[Tree] = scalaTargTps.map(mkRef) + val scalaTargsBoxed: List[Tree] = scalaTargBoxedTps.map(mkRef) + val boxComment = + if (scalaTargTps.map(_.typeSymbol) != scalaTargBoxedTps.map(_.typeSymbol)) + Literal(Constant("primitiveComment")) + else + Literal(Constant("noComment")) + + // Conversion wrappers have three or four components that we need to name + // (1) The wrapper class that wraps a Java SAM as Scala function, or vice versa (ClassN) + // (2) A value class that provides .asJava or .asScala to request the conversion (ValCN) + // (3) A name for an explicit conversion method (DefN) + // (4) An implicit conversion method name (ImpN) that invokes the value class + + // Names for Java conversions to Scala + val j2sClassN = TypeName("FromJava" + jfn.title) + val j2sCompanionN = j2sClassN.toTermName + val j2sValCN = TypeName("Rich" + jfn.title + "As" + scalaType.name.encoded) + val j2sDefN = TermName("asScalaFrom" + jfn.title) + val j2sImpN = TermName("enrichAsScalaFrom" + jfn.title) + + // Names for Scala conversions to Java + val s2jAsJavaTitle = TermName("asJava" + jfn.title) + val s2jClassN = TypeName("AsJava" + jfn.title) + val s2jCompanionN = s2jClassN.toTermName + val s2jValCN = TypeName("Rich" + scalaType.name.encoded + "As" + jfn.title) + val s2jDefN = TermName("asJava" + jfn.title) + val s2jImpN = TermName("enrichAsJava" + jfn.title) + + // Argument lists for the function / SAM + val vParams = (jfn.params zip jfn.pTypes).map{ case (p,t) => + ValDef(NoMods, p.name.toTermName, if (t.typeSymbol.isTypeParameter) Ident(t.typeSymbol.name) else gen.mkAttributedRef(t.typeSymbol), EmptyTree) + } + val vParamRefs = vParams.map(_.name).map(Ident(_)) + + val j2sClassTree = + q"""case class $j2sClassN[..$tdParams](jf: $javaType[..$javaTargs]) extends $scalaType[..$scalaTargs] { + def apply(..$vParams) = jf.${jfn.name}(..$vParamRefs) + }""" + + val j2sValCTree = + q"""class $j2sValCN[..$tdParams](private val underlying: $javaType[..$javaTargs]) extends AnyVal { + @inline def asScala: $scalaType[..$scalaTargs] = underlying match { + case $s2jCompanionN(sf) => sf.asInstanceOf[$scalaType[..$scalaTargs]] + case _ => new $j2sClassN[..$tnParams](underlying) + } + }""" + + val j2sDefTree = + q"""@deprecated($boxComment) @inline def $j2sDefN[..$tdParams](jf: $javaType[..$javaTargs]): $scalaType[..$scalaTargsBoxed] = jf match { + case $s2jCompanionN(f) => f.asInstanceOf[$scalaType[..$scalaTargsBoxed]] + case _ => new $j2sClassN[..$tnParams](jf).asInstanceOf[$scalaType[..$scalaTargsBoxed]] + }""" + + val j2sImpTree = + q"""@inline implicit def $j2sImpN[..$tdParams](jf: $javaType[..$javaTargs]): $j2sValCN[..$tnParams] = new $j2sValCN[..$tnParams](jf)""" + + val s2jClassTree = + q"""case class $s2jClassN[..$tdParams](sf: $scalaType[..$scalaTargs]) extends $javaType[..$javaTargs] { + def ${jfn.name}(..$vParams) = sf.apply(..$vParamRefs) + }""" + + val s2jDefTree = + q"""@deprecated($boxComment) @inline def $s2jDefN[..$tdParams](sf: $scalaType[..$scalaTargsBoxed]): $javaType[..$javaTargs] = (sf: AnyRef) match { + case $j2sCompanionN(f) => f.asInstanceOf[$javaType[..$javaTargs]] + case _ => new $s2jClassN[..$tnParams](sf.asInstanceOf[$scalaType[..$scalaTargs]]) + }""" + + // This is especially tricky because functions are contravariant in their arguments + // Need to prevent e.g. Any => String from "downcasting" itself to Int => String; we want the more exact conversion + val (s2jImpTree, priority) = + if (jfn.pTypes.forall(! _.isFinalType) && jfn.sig == jfn.sam.typeSignature) + ( + q"""@inline implicit def $s2jImpN[..$tdParams](sf: $scalaType[..$scalaTargs]): $s2jValCN[..$tnParams] = new $s2jValCN[..$tnParams](sf)""", + tdParams.length + ) + else { + // Some types are not generic or are re-used; we had better catch those. + // Making up new type names, so switch everything to TypeName or TypeDef + // Instead of foo[A](f: (Int, A) => Long): Fuu[A] = new Foo[A](f) + // we want foo[X, A](f: (X, A) => Long)(implicit evX: Int =:= X): Fuu[A] = new Foo[A](f.asInstanceOf[(Int, A) => Long]) + // Instead of bar[A](f: A => A): Brr[A] = new Foo[A](f) + // we want bar[A, B](f: A => B)(implicit evB: A =:= B): Brr[A] = new Foo[A](f.asInstanceOf[A => B]) + val An = "A(\\d+)".r + val numberedA = collection.mutable.Set.empty[Int] + val evidences = collection.mutable.ArrayBuffer.empty[(TypeName, TypeName)] + numberedA ++= scalaTargs.map(_.toString).collect{ case An(digits) if (digits.length < 10) => digits.toInt } + val scalafnTnames = (jfn.pTypes :+ jfn.rType).zipWithIndex.map{ + case (pt, i) if (i < jfn.pTypes.length && pt.isFinalType) || (!pt.isFinalType && jfn.pTypes.take(i).exists(_ == pt)) => + val j = Iterator.from(i).dropWhile(numberedA).next + val genericName = TypeName(s"A$j") + numberedA += j + evidences += ((genericName, pt.typeSymbol.name.toTypeName)) + genericName + case (pt, _) => pt.typeSymbol.name.toTypeName + } + val scalafnTdefs = scalafnTnames. + map(TypeDef(NoMods, _, Nil, EmptyTree)). + dropRight(if (jfn.rType.isFinalType) 1 else 0) + val evs = evidences.map{ case (generic, specific) => ValDef(NoMods, TermName("ev"+generic.toString), tq"$generic =:= $specific", EmptyTree) } + val tree = + q"""@inline implicit def $s2jImpN[..$scalafnTdefs](sf: $scalaType[..$scalafnTnames])(implicit ..$evs): $s2jValCN[..$tnParams] = + new $s2jValCN[..$tnParams](sf.asInstanceOf[$scalaType[..$scalaTargs]]) + """ + (tree, tdParams.length) + } + + val s2jValFullNameAsJavaMethodTree = + if (priority > 0) + q"""@inline def $s2jAsJavaTitle: $javaType[..$javaTargs] = underlying match { + case $j2sCompanionN(sf) => sf.asInstanceOf[$javaType[..$javaTargs]] + case _ => new $s2jClassN[..$tnParams](underlying) + }""" + else EmptyTree + + val s2jValCTree = + q"""class $s2jValCN[..$tdParams](private val underlying: $scalaType[..$scalaTargs]) extends AnyVal { + @inline def asJava: $javaType[..$javaTargs] = underlying match { + case $j2sCompanionN(jf) => jf.asInstanceOf[$javaType[..$javaTargs]] + case _ => new $s2jClassN[..$tnParams](underlying) + } + $s2jValFullNameAsJavaMethodTree + }""" + + SamConversionCode( + base = jfn.title, + wrappedAsScala = j2sClassTree.text, + asScalaAnyVal = j2sValCTree.text, + implicitToScala = j2sImpTree.text, + asScalaDef = j2sDefTree.text, + wrappedAsJava = s2jClassTree.text, + asJavaAnyVal = s2jValCTree.text, + implicitToJava = Prioritized(s2jImpTree.text, priority), + asJavaDef = s2jDefTree.text + ) + } + + sams.toSeq.map(generate) + } + + def sourceFile(subPack: String, body: String): String = + s"""$copyright + | + |$packaging$subPack + | + |$body + |""".stripMargin + + def sameText(f: java.io.File, text: String): Boolean = { + val x = scala.io.Source.fromFile(f) + val lines = try { x.getLines().toVector } finally { x.close } + // work around scala/bug#11125 + lines.iterator.filter(_.nonBlank) == Predef.augmentString(text).lines.filter(_.nonBlank) + } + + def write(outDir: java.io.File, artifact: Artifact): Unit = { + val f = scala.tools.nsc.io.Path(outDir.getAbsolutePath) / artifact.name + if (!f.exists || !sameText(f.jfile, artifact.content)) { + f.parent.createDirectory(force = true) + f.toFile writeAll artifact.content + } + } + + def run(outDir: java.io.File): Unit = { + val (impls, explicitDefs, defss) = SamConversionCode(buildWrappersViaReflection: _*) + val javaFunConvsNoComments = + s"""/** This object contains methods that convert between Scala and Java function types. + | * + | * The explicit conversion methods defined here are intended to be used in Java code. For Scala + | * code, it is recommended to use the extension methods defined in [[scala.jdk.FunctionConverters]]. + | * + | * For details how the function converters work, see [[scala.jdk.FunctionConverters]]. + | * + | */ + |object FunctionConverters { + | import scala.jdk.FunctionWrappers._ + | + |${explicitDefs.indent.mkString("\n")} + |}""".stripMargin + + // cannot generate comments with quasiquotes + val javaFunConvs = javaFunConvsNoComments.replace(""" @deprecated("primitiveComment") """, + s""" /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the + | * primitive type `scala.X` to improve compatibility when using it in Java code (the + | * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to + | * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add + | * `import scala.jdk.FunctionConverters._` and use the extension methods instead. + | */ + |""".stripMargin + " ").replace("""@deprecated("noComment") """, "") + + val scalaFunConvs = + """/** This object provides extension methods that convert between Scala and Java function types. + | * + | * When writing Java code, use the explicit conversion methods defined in + | * [[javaapi.FunctionConverters]] instead. + | * + | * Using the `.asJava` extension method on a Scala function produces the most specific possible + | * Java function type: + | * + | * {{{ + | * scala> import scala.jdk.FunctionConverters._ + | * scala> val f = (x: Int) => x + 1 + | * + | * scala> val jf1 = f.asJava + | * jf1: java.util.function.IntUnaryOperator = ... + | * }}} + | * + | * More generic Java function types can be created using the corresponding `asJavaXYZ` extension + | * method: + | * + | * {{{ + | * scala> val jf2 = f.asJavaFunction + | * jf2: java.util.function.Function[Int,Int] = ... + | * + | * scala> val jf3 = f.asJavaUnaryOperator + | * jf3: java.util.function.UnaryOperator[Int] = ... + | * }}} + | * + | * Converting a Java function to Scala is done using the `asScala` extension method: + | * + | * {{{ + | * scala> List(1,2,3).map(jf2.asScala) + | * res1: List[Int] = List(2, 3, 4) + | * }}} + | */ + |object FunctionConverters extends Priority0FunctionExtensions""".stripMargin + + write(outDir, Artifact("jdk/javaapi/FunctionConverters.scala", sourceFile(".javaapi", javaFunConvs))) + write(outDir, Artifact("jdk/FunctionConverters.scala", sourceFile("", scalaFunConvs))) + write(outDir, Artifact("jdk/FunctionWrappers.scala", sourceFile("", impls.mkString("\n")))) + write(outDir, Artifact("jdk/FunctionExtensions.scala", sourceFile("", defss.map(_.mkString("\n")).mkString("\n\n\n\n")))) + } +} diff --git a/project/genprod.scala b/project/genprod.scala new file mode 100644 index 000000000000..ec144322a083 --- /dev/null +++ b/project/genprod.scala @@ -0,0 +1,479 @@ +/* + * 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. + */ + +/** + * This program generates the ProductN, TupleN, FunctionN, and AbstractFunctionN, + * where 0 <= N <= MaxArity. Usage: sbt generateSources + */ +object genprod { + final val MaxArity = 22 + def arities = (1 to MaxArity).toList + + class Group(val name: String) { + def className(i: Int) = name + i + def fileName(i: Int) = className(i) + ".scala" + } + + def productFiles = arities map Product.make + def tupleFiles = arities map Tuple.make + def functionFiles = (0 :: arities) map Function.make + def absFunctionFiles = (0 :: arities) map AbstractFunction.make + def allfiles = productFiles ::: tupleFiles ::: functionFiles ::: absFunctionFiles + + trait Arity extends Group { + def i: Int // arity + + def typeArgsString(xs: Seq[String]) = xs.mkString("[", ", ", "]") + def typeArgsToTupleSyntacticSugarString(xs: Seq[String]) = xs.mkString("(", ", ", ")") + + def to = (1 to i).toList + def s = if (i == 1) "" else "s" + def className = name + i + def classAnnotation = "" + def fileName = className + ".scala" + def targs = to map ("T" + _) + def vdefs = to map ("v" + _) + def xdefs = to map ("x" + _) + def mdefs = to map ("_" + _) + def invariantArgs = typeArgsString(targs) + def covariantArgs = typeArgsString(targs map (covariantSpecs + "+" + _)) + def covariantSpecs = "" + def contravariantSpecs = "" + def contraCoArgs = typeArgsString((targs map (contravariantSpecs + "-" + _)) ::: List(covariantSpecs + "+R")) + def constructorArgs = (targs).map( _.toLowerCase ) mkString ", " + def fields = (mdefs, targs).zipped.map(_ + ": " + _) mkString ", " + def funArgs = (vdefs, targs).zipped.map(_ + ": " + _) mkString ", " + + def genprodString = " See scala.Function0 for timestamp." + def moreMethods = "" + def companionObject = "" + def packageDef = "scala" + def imports = "" + + def header = """/* + * 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. + */ + +// GENERATED CODE: DO NOT EDIT.%s + +package %s +%s +""".trim.format(genprodString, packageDef, imports) + } + + def run(outDir: java.io.File): Unit = { + val out = outDir.getAbsolutePath + def writeFile(node: scala.xml.Node): Unit = { + import scala.tools.nsc.io._ + val f = Path(out) / node.attributes("name").toString + f.parent.createDirectory(force = true) + f.toFile writeAll node.text + } + allfiles foreach writeFile + } +} + +import genprod._ + + +/* zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz + F U N C T I O N +zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz */ + +object FunctionZero extends Function(0) { + override def genprodString = "\n// genprod generated these sources at: " + java.time.Instant.now() + override def covariantSpecs = "@specialized(Specializable.Primitives) " + override def descriptiveComment = " " + functionNTemplate.format("greeting", "anonfun0", +raw""" + * val name = "world" + * val greeting = () => s"hello, $$name" + * + * val anonfun0 = new Function0[String] { + * def apply(): String = s"hello, $$name" + * } + * assert(greeting() == anonfun0()) + * """) + override def moreMethods = "" +} + +object FunctionOne extends Function(1) { + override def classAnnotation = "@annotation.implicitNotFound(msg = \"No implicit view available from ${T1} => ${R}.\")\n" + override def contravariantSpecs = "@specialized(Specializable.Arg) " + override def covariantSpecs = "@specialized(Specializable.Return) " + + override def descriptiveComment = " " + functionNTemplate.format("succ", "anonfun1", +""" + * val succ = (x: Int) => x + 1 + * val anonfun1 = new Function1[Int, Int] { + * def apply(x: Int): Int = x + 1 + * } + * assert(succ(0) == anonfun1(0)) + * """) + """ + * + * Note that the difference between `Function1` and [[scala.PartialFunction]] + * is that the latter can specify inputs which it will not handle.""" + + override def moreMethods = """ + /** Composes two instances of Function1 in a new Function1, with this function applied last. + * + * @tparam A the type to which function `g` can be applied + * @param g a function A => T1 + * @return a new function `f` such that `f(x) == apply(g(x))` + */ + @annotation.unspecialized def compose[A](g: A => T1): A => R = { x => apply(g(x)) } + + /** Composes two instances of Function1 in a new Function1, with this function applied first. + * + * @tparam A the result type of function `g` + * @param g a function R => A + * @return a new function `f` such that `f(x) == g(apply(x))` + */ + @annotation.unspecialized def andThen[A](g: R => A): T1 => A = { x => g(apply(x)) } +""" + override def companionObject = +""" +object Function1 { + + implicit final class UnliftOps[A, B] private[Function1](private val f: A => Option[B]) extends AnyVal { + /** Converts an optional function to a partial function. + * + * @example Unlike [[Function.unlift]], this [[UnliftOps.unlift]] method can be used in extractors. + * {{{ + * val of: Int => Option[String] = { i => + * if (i == 2) { + * Some("matched by an optional function") + * } else { + * None + * } + * } + * + * util.Random.nextInt(4) match { + * case of.unlift(m) => // Convert an optional function to a pattern + * println(m) + * case _ => + * println("Not matched") + * } + * }}} + */ + def unlift: PartialFunction[A, B] = Function.unlift(f) + } + +} +""" +} + +object FunctionTwo extends Function(2) { + override def contravariantSpecs = "@specialized(Specializable.Args) " + override def covariantSpecs = "@specialized(Specializable.Return) " + + override def descriptiveComment = " " + functionNTemplate.format("max", "anonfun2", +""" + * val max = (x: Int, y: Int) => if (x < y) y else x + * + * val anonfun2 = new Function2[Int, Int, Int] { + * def apply(x: Int, y: Int): Int = if (x < y) y else x + * } + * assert(max(0, 1) == anonfun2(0, 1)) + * """) +} + +object Function { + def make(i: Int) = apply(i)() + def apply(i: Int) = i match { + case 0 => FunctionZero + case 1 => FunctionOne + case 2 => FunctionTwo + case _ => new Function(i) + } +} + +class Function(val i: Int) extends Group("Function") with Arity { + def descriptiveComment = "" + def functionNTemplate = +""" + * In the following example, the definition of `%s` is + * shorthand, conceptually, for the anonymous class definition + * `%s`, although the implementation details of how the + * function value is constructed may differ: + * + * {{{ + * object Main extends App {%s} + * }}}""" + + def toStr = "\"" + ("" format i) + "\"" + def apply() = { +{header} +{companionObject} +/** A function of {i} parameter{s}. + *{descriptiveComment} + */ +{classAnnotation}trait {className}{contraCoArgs} extends AnyRef {{ self => + /** Apply the body of this function to the argument{s}. + * @return the result of function application. + */ + def apply({funArgs}): R +{moreMethods} + override def toString(): String = {toStr} +}} + +} + + private def commaXs = xdefs.mkString("(", ", ", ")") + + // (x1: T1) => (x2: T2) => (x3: T3) => (x4: T4) => apply(x1,x2,x3,x4) + def shortCurry = { + val body = "apply" + commaXs + (xdefs, targs).zipped.map("(%s: %s) => ".format(_, _)).mkString("", "", body) + } + + // (x1: T1) => ((x2: T2, x3: T3, x4: T4, x5: T5, x6: T6, x7: T7) => self.apply(x1,x2,x3,x4,x5,x6,x7)).curried + def longCurry = ((xdefs, targs).zipped.map(_ + ": " + _) drop 1).mkString( + "(x1: T1) => ((", + ", ", + ") => self.apply%s).curried".format(commaXs) + ) + + // f(x1,x2,x3,x4,x5,x6) == (f.curried)(x1)(x2)(x3)(x4)(x5)(x6) + def curryComment = { +""" /** Creates a curried version of this function. + * + * @return a function `f` such that `f%s == apply%s` + */""".format(xdefs.map("(" + _ + ")").mkString, commaXs) + } + + def tupleMethod = { + def comment = +""" /** Creates a tupled version of this function: instead of %d arguments, + * it accepts a single [[scala.Tuple%d]] argument. + * + * @return a function `f` such that `f(%s) == f(Tuple%d%s) == apply%s` + */ +""".format(i, i, commaXs, i, commaXs, commaXs) + def body = "case (%s) => apply%s".format(commaXs, commaXs) + + comment + "\n @annotation.unspecialized def tupled: (%s) => R = {\n %s\n }".format( + typeArgsToTupleSyntacticSugarString(targs), body) + } + + def curryMethod = { + val body = if (i < 5) shortCurry else longCurry + + curryComment + + "\n @annotation.unspecialized def curried: %s => R = {\n %s\n }\n".format( + targs mkString " => ", body + ) + } + + override def moreMethods = curryMethod + tupleMethod +} // object Function + + +/* zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz + T U P L E +zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz */ + +object Tuple { + val zipImports = "" + + def make(i: Int) = apply(i)() + def apply(i: Int) = i match { + case 1 => TupleOne + case 2 => TupleTwo + case 3 => TupleThree + case _ => new Tuple(i) + } +} + +object TupleOne extends Tuple(1) +{ + override def covariantSpecs = "@specialized(Int, Long, Double) " +} + +object TupleTwo extends Tuple(2) +{ + override def imports = Tuple.zipImports + override def covariantSpecs = "@specialized(Int, Long, Double, Char, Boolean/*, AnyRef*/) " + override def moreMethods = """ + /** Swaps the elements of this `Tuple`. + * @return a new Tuple where the first element is the second element of this Tuple and the + * second element is the first element of this Tuple. + */ + def swap: Tuple2[T2,T1] = Tuple2(_2, _1) +""" +} + +object TupleThree extends Tuple(3) { + override def imports = Tuple.zipImports +} + +class Tuple(val i: Int) extends Group("Tuple") with Arity { + private def idiomatic = + if (i < 2) "" + else " Note that it is more idiomatic to create a %s via `(%s)`".format(className, constructorArgs) + + private def params = ( + 1 to i map (x => " * @param _%d Element %d of this Tuple%d".format(x, x, i)) + ) mkString "\n" + + // prettifies it a little if it's overlong + def mkToString() = { + def str(xs: List[String]) = xs.mkString(""" + "," + """) + if (i <= MaxArity / 2) str(mdefs) + else { + val s1 = str(mdefs take (i / 2)) + val s2 = str(mdefs drop (i / 2)) + s1 + " +\n \",\" + " + s2 + } + } + + def apply() = { +{header} + +/** A tuple of {i} elements; the canonical representation of a [[scala.{Product.className(i)}]]. + * + * @constructor Create a new tuple with {i} elements.{idiomatic} +{params} + */ +final case class {className}{covariantArgs}({fields}) + extends {Product.className(i)}{invariantArgs} +{{ + override def toString(): String = "(" + {mkToString} + ")" + {moreMethods} +}} +} +} // object Tuple + + +/* zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz + P R O D U C T +zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz */ + +object Product extends Group("Product") +{ + def make(i: Int) = apply(i)() + def apply(i: Int) = i match { + case 1 => ProductOne + case 2 => ProductTwo + case _ => new Product(i) + } +} + +object ProductOne extends Product(1) +{ + override def covariantSpecs = "@specialized(Int, Long, Double) " +} + +object ProductTwo extends Product(2) +{ + override def covariantSpecs = "@specialized(Int, Long, Double) " +} + +class Product(val i: Int) extends Group("Product") with Arity { + val productElementComment = s""" + /** Returns the n-th projection of this product if 0 <= n < productArity, + * otherwise throws an `IndexOutOfBoundsException`. + * + * @param n number of the projection to be returned + * @return same as `._(n+1)`, for example `productElement(0)` is the same as `._1`. + * @throws IndexOutOfBoundsException if the `n` is out of range(n < 0 || n >= $i). + */ +""" + + def cases = { + val xs = for ((x, i) <- mdefs.zipWithIndex) yield "case %d => %s".format(i, x) + val default = "case _ => throw new IndexOutOfBoundsException(s\"$n is out of bounds (min 0, max " + (i-1) +")\")" + "\n" + ((xs ::: List(default)).map(" " + _ + "\n").mkString) + } + def proj = { + (mdefs,targs).zipped.map( (_,_) ).zipWithIndex.map { case ((method,typeName),index) => + """| /** A projection of element %d of this Product. + | * @return A projection of element %d. + | */ + | def %s: %s + |""".stripMargin.format(index + 1, index + 1, method, typeName) + }.mkString + } + + def apply() = { +{header} +object {className} {{ + def unapply{invariantArgs}(x: {className}{invariantArgs}): Option[{className}{invariantArgs}] = + Some(x) +}} + +/** {className} is a Cartesian product of {i} component{s}. + */ +trait {className}{covariantArgs} extends Any with Product {{ + /** The arity of this product. + * @return {i} + */ + override def productArity: Int = {i} + + {productElementComment} + @throws(classOf[IndexOutOfBoundsException]) + override def productElement(n: Int): Any = n match {{ {cases} }} + +{proj} +{moreMethods} +}} +} + +} + +/** Abstract functions **/ + +object AbstractFunctionZero extends AbstractFunction(0) { + override def covariantSpecs = FunctionZero.covariantSpecs +} + +object AbstractFunctionOne extends AbstractFunction(1) { + override def covariantSpecs = FunctionOne.covariantSpecs + override def contravariantSpecs = FunctionOne.contravariantSpecs +} + +object AbstractFunctionTwo extends AbstractFunction(2) { + override def covariantSpecs = FunctionTwo.covariantSpecs + override def contravariantSpecs = FunctionTwo.contravariantSpecs +} + +class AbstractFunction(val i: Int) extends Group("AbstractFunction") with Arity +{ + override def packageDef = "scala.runtime" + + val superTypeArgs = typeArgsString(targs ::: List("R")) + + def apply() = { +{header} +abstract class {className}{contraCoArgs} extends Function{i}{superTypeArgs} {{ +{moreMethods} +}} +} + +} +object AbstractFunction +{ + def make(i: Int) = apply(i)() + def apply(i: Int) = i match { + case 0 => AbstractFunctionZero + case 1 => AbstractFunctionOne + case 2 => AbstractFunctionTwo + case _ => new AbstractFunction(i) + } +} From 00dc9d3c9f1e2a35f43a72d7a3cb6748175ef0ca Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Mon, 11 Aug 2025 14:53:35 +0200 Subject: [PATCH 033/101] Add Scala 2.13 import to generated files to match current source --- library/src/scala/Function0.scala | 2 +- project/GenerateAnyVals.scala | 6 ++++-- project/GenerateFunctionConverters.scala | 9 +++++++-- ...od.scala => GenerateLibraryNTemplates.scala} | 17 ++++++++++++----- project/ScalaLibraryPlugin.scala | 14 +++++++++++++- 5 files changed, 37 insertions(+), 11 deletions(-) rename project/{genprod.scala => GenerateLibraryNTemplates.scala} (97%) diff --git a/library/src/scala/Function0.scala b/library/src/scala/Function0.scala index af9327e6e88c..b750d7b582b7 100644 --- a/library/src/scala/Function0.scala +++ b/library/src/scala/Function0.scala @@ -11,7 +11,7 @@ */ // GENERATED CODE: DO NOT EDIT. -// genprod generated these sources at: 2022-01-17T20:47:12.170348200Z +// genprod generated these sources at: 2025-08-11T12:52:13.642685846Z package scala diff --git a/project/GenerateAnyVals.scala b/project/GenerateAnyVals.scala index df9917c21f31..a79322a23d9b 100644 --- a/project/GenerateAnyVals.scala +++ b/project/GenerateAnyVals.scala @@ -1,4 +1,4 @@ -package scala.build +package dotty.tools /** Code generation of the AnyVal types and their companions. */ trait GenerateAnyValReps { @@ -295,6 +295,8 @@ trait GenerateAnyValTemplates { package scala +import scala.language.`2.13` + """ def classDocTemplate = (""" @@ -465,7 +467,7 @@ override def getClass(): Class[Boolean] = ??? * runtime system. A method with return type `Unit` is analogous to a Java * method which is declared `void`. */ -""" +""".trim + "\n" def classLines = List( "// Provide a more specific return type for Scaladoc", "override def getClass(): Class[Unit] = ???" diff --git a/project/GenerateFunctionConverters.scala b/project/GenerateFunctionConverters.scala index 31ef68cdb3c0..a73bbc8d3b5a 100644 --- a/project/GenerateFunctionConverters.scala +++ b/project/GenerateFunctionConverters.scala @@ -1,3 +1,5 @@ +package dotty.tools + /* * Scala (https://www.scala-lang.org) * @@ -338,13 +340,16 @@ object GenerateFunctionConverters { sams.toSeq.map(generate) } - def sourceFile(subPack: String, body: String): String = + def sourceFile(subPack: String, body: String): String = { + val body1 = if (body.startsWith("import "))body else "\n" + body s"""$copyright | |$packaging$subPack | - |$body + |import scala.language.`2.13` + |$body1 |""".stripMargin + } def sameText(f: java.io.File, text: String): Boolean = { val x = scala.io.Source.fromFile(f) diff --git a/project/genprod.scala b/project/GenerateLibraryNTemplates.scala similarity index 97% rename from project/genprod.scala rename to project/GenerateLibraryNTemplates.scala index ec144322a083..67e42b58fcdf 100644 --- a/project/genprod.scala +++ b/project/GenerateLibraryNTemplates.scala @@ -1,3 +1,5 @@ +package dotty.tools + /* * Scala (https://www.scala-lang.org) * @@ -14,7 +16,7 @@ * This program generates the ProductN, TupleN, FunctionN, and AbstractFunctionN, * where 0 <= N <= MaxArity. Usage: sbt generateSources */ -object genprod { +object GenerateLibraryNTemplates { final val MaxArity = 22 def arities = (1 to MaxArity).toList @@ -74,8 +76,10 @@ object genprod { // GENERATED CODE: DO NOT EDIT.%s package %s + +import scala.language.`2.13` %s -""".trim.format(genprodString, packageDef, imports) +""".format(genprodString, packageDef, imports).trim } def run(outDir: java.io.File): Unit = { @@ -90,7 +94,7 @@ package %s } } -import genprod._ +import GenerateLibraryNTemplates._ /* zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz @@ -131,7 +135,7 @@ object FunctionOne extends Function(1) { * is that the latter can specify inputs which it will not handle.""" override def moreMethods = """ - /** Composes two instances of Function1 in a new Function1, with this function applied last. + /** Composes two instances of `Function1` in a new `Function1`, with this function applied last. * * @tparam A the type to which function `g` can be applied * @param g a function A => T1 @@ -139,7 +143,7 @@ object FunctionOne extends Function(1) { */ @annotation.unspecialized def compose[A](g: A => T1): A => R = { x => apply(g(x)) } - /** Composes two instances of Function1 in a new Function1, with this function applied first. + /** Composes two instances of `Function1` in a new `Function1`, with this function applied first. * * @tparam A the result type of function `g` * @param g a function R => A @@ -413,6 +417,7 @@ class Product(val i: Int) extends Group("Product") with Arity { def apply() = { {header} + object {className} {{ def unapply{invariantArgs}(x: {className}{invariantArgs}): Option[{className}{invariantArgs}] = Some(x) @@ -461,6 +466,7 @@ class AbstractFunction(val i: Int) extends Group("AbstractFunction") with Arity def apply() = { {header} + abstract class {className}{contraCoArgs} extends Function{i}{superTypeArgs} {{ {moreMethods} }} @@ -477,3 +483,4 @@ object AbstractFunction case _ => new AbstractFunction(i) } } + diff --git a/project/ScalaLibraryPlugin.scala b/project/ScalaLibraryPlugin.scala index 1d77a2f873b3..66972e68495c 100644 --- a/project/ScalaLibraryPlugin.scala +++ b/project/ScalaLibraryPlugin.scala @@ -110,7 +110,19 @@ object ScalaLibraryPlugin extends AutoPlugin { previous .withAnalysis(analysis.copy(stamps = stamps)) // update the analysis with the correct stamps .withHasModified(true) // mark it as updated for sbt to update its caches - } + }, + + // Generate (Product|TupleN|Function|AbstractFunction)*.scala files and scaladoc stubs for all AnyVal sources. + // They should really go into a managedSources dir instead of overwriting sources checked into git but scaladoc + // source links (could be fixed by shipping these sources with the scaladoc bundles) and scala-js source maps + // rely on them being on github. + commands += Command.command("generateSources") { state => + val dir = ((ThisBuild / baseDirectory).value / "library" / "src" / "scala").getAbsoluteFile + dotty.tools.GenerateLibraryNTemplates.run(dir) + dotty.tools.GenerateAnyVals.run(dir) + dotty.tools.GenerateFunctionConverters.run(dir) + state + }, ) private lazy val filesToCopy = Set( From 637213236f19822a7b2212b6f9a1bf5e85ead2a4 Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Mon, 11 Aug 2025 17:17:32 +0200 Subject: [PATCH 034/101] Rename cardinal to integer --- project/GenerateAnyVals.scala | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/project/GenerateAnyVals.scala b/project/GenerateAnyVals.scala index a79322a23d9b..8e899ce542da 100644 --- a/project/GenerateAnyVals.scala +++ b/project/GenerateAnyVals.scala @@ -35,13 +35,13 @@ import scala.language.implicitConversions""" else coercionComment.linesIterator.toList ++ coercions } - def isCardinal: Boolean = isIntegerType(this) + def isInteger: Boolean = isIntegerType(this) def unaryOps = { val ops = List( Op("+", "/** Returns this value, unmodified. */"), Op("-", "/** Returns the negation of this value. */")) - if(isCardinal) + if(isInteger) Op("~", "/**\n" + " * Returns the bitwise negation of this value.\n" + " * @example {{{\n" + @@ -54,7 +54,7 @@ import scala.language.implicitConversions""" } def bitwiseOps = - if (isCardinal) + if (isInteger) List( Op("|", "/**\n" + " * Returns the bitwise OR of this value and `x`.\n" + @@ -89,7 +89,7 @@ import scala.language.implicitConversions""" else Nil def shiftOps = - if (isCardinal) + if (isInteger) List( Op("<<", "/**\n" + " * Returns this value bit-shifted left by the specified number of bits,\n" + @@ -164,7 +164,7 @@ import scala.language.implicitConversions""" val xs1 = List(mkCoercions, mkUnaryOps, mkStringOps, mkShiftOps) map (xs => if (xs.isEmpty) xs else xs :+ "") val xs2 = List( mkBinOpsGroup(comparisonOps, numeric, _ => Z), - mkBinOpsGroup(bitwiseOps, cardinal, this opType _), + mkBinOpsGroup(bitwiseOps, integer, this opType _), mkBinOpsGroup(otherOps, numeric, this opType _) ) xs1 ++ xs2 @@ -179,7 +179,7 @@ import scala.language.implicitConversions""" res ++ xs } def objectLines = { - val comp = if (isCardinal) cardinalCompanion else floatingCompanion + val comp = if (isInteger) integerCompanion else floatingCompanion interpolate(comp + allCompanions + "\n" + nonUnitCompanions).trim.linesIterator.toList ++ (implicitCoercions map interpolate) } @@ -333,7 +333,7 @@ override def toString = "object scala.@name@" def nonUnitCompanions = "" // todo - def cardinalCompanion = """ + def integerCompanion = """ /** The smallest value representable as @article@ @name@. */ final val MinValue = @boxed@.MIN_VALUE @@ -497,7 +497,7 @@ override def getClass(): Class[Boolean] = ??? def isFloatingType = Set(F, D) def isWideType = Set(L, D) - def cardinal = numeric filter isIntegerType + def integer = numeric filter isIntegerType def numeric = List(B, S, C, I, L, F, D) def values = List(U, Z) ++ numeric From 88ee08cd9a7d017a43fae10439dba495c8a9a15f Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Mon, 11 Aug 2025 17:28:02 +0200 Subject: [PATCH 035/101] Make opType documentation and code clearer --- project/GenerateAnyVals.scala | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/project/GenerateAnyVals.scala b/project/GenerateAnyVals.scala index 8e899ce542da..52dc6e39eef5 100644 --- a/project/GenerateAnyVals.scala +++ b/project/GenerateAnyVals.scala @@ -36,12 +36,13 @@ import scala.language.implicitConversions""" } def isInteger: Boolean = isIntegerType(this) + def unaryOps = { val ops = List( Op("+", "/** Returns this value, unmodified. */"), Op("-", "/** Returns the negation of this value. */")) - if(isInteger) + if (isInteger) Op("~", "/**\n" + " * Returns the bitwise negation of this value.\n" + " * @example {{{\n" + @@ -135,16 +136,19 @@ import scala.language.implicitConversions""" Op("/", "/** Returns the quotient of this value and `x`. */"), Op("%", "/** Returns the remainder of the division of this value by `x`. */")) - // Given two numeric value types S and T , the operation type of S and T is defined as follows: + // Given two numeric value types S and T, the operation type of S and T is defined as follows: // If both S and T are subrange types then the operation type of S and T is Int. - // Otherwise the operation type of S and T is the larger of the two types wrt ranking. + // Otherwise the operation type of S and T is the larger of the two types w.r.t. the ordering + // of numeric types defined in `fullTypes` below. // Given two numeric values v and w the operation type of v and w is the operation type // of their run-time types. def opType(that: AnyValNum): AnyValNum = { - val rank = IndexedSeq(I, L, F, D) - (rank indexOf this, rank indexOf that) match { - case (-1, -1) => I - case (r1, r2) => rank apply (r1 max r2) + val fullTypes = IndexedSeq(I, L, F, D) + (fullTypes.indexOf(this), fullTypes.indexOf(that)) match { + case (-1, -1) => I // both are subrange types + case (-1, _) => that // one is subrange + case (_, -1) => this + case (r1, r2) => fullTypes(r1.max(r2)) // use the larger type } } From 907016619b1d0ad91be31cc6a18b8cb2b59f5e21 Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Mon, 11 Aug 2025 17:50:10 +0200 Subject: [PATCH 036/101] Typo and style changes --- library/src/scala/Boolean.scala | 4 +-- library/src/scala/Byte.scala | 4 +-- library/src/scala/Char.scala | 4 +-- library/src/scala/Double.scala | 4 +-- library/src/scala/Float.scala | 4 +-- library/src/scala/Function0.scala | 2 +- library/src/scala/Int.scala | 4 +-- library/src/scala/Long.scala | 4 +-- library/src/scala/Short.scala | 4 +-- library/src/scala/Unit.scala | 4 +-- project/GenerateAnyVals.scala | 42 ++++++++++++++++++------------- 11 files changed, 43 insertions(+), 37 deletions(-) diff --git a/library/src/scala/Boolean.scala b/library/src/scala/Boolean.scala index 5ff37694d133..5df9184b5e30 100644 --- a/library/src/scala/Boolean.scala +++ b/library/src/scala/Boolean.scala @@ -114,7 +114,7 @@ final abstract class Boolean private extends AnyVal { object Boolean extends AnyValCompanion { - /** Transform a value type into a boxed reference type. + /** Transforms a value type into a boxed reference type. * * Runtime implementation determined by `scala.runtime.BoxesRunTime.boxToBoolean`. See [[https://github.com/scala/scala src/library/scala/runtime/BoxesRunTime.java]]. * @@ -123,7 +123,7 @@ object Boolean extends AnyValCompanion { */ def box(x: Boolean): java.lang.Boolean = ??? - /** Transform a boxed type into a value type. Note that this + /** Transforms a boxed type into a value type. Note that this * method is not typesafe: it accepts any Object, but will throw * an exception if the argument is not a java.lang.Boolean. * diff --git a/library/src/scala/Byte.scala b/library/src/scala/Byte.scala index c5891be37e36..c1f0e81f746b 100644 --- a/library/src/scala/Byte.scala +++ b/library/src/scala/Byte.scala @@ -455,7 +455,7 @@ object Byte extends AnyValCompanion { /** The largest value representable as a Byte. */ final val MaxValue = java.lang.Byte.MAX_VALUE - /** Transform a value type into a boxed reference type. + /** Transforms a value type into a boxed reference type. * * Runtime implementation determined by `scala.runtime.BoxesRunTime.boxToByte`. See [[https://github.com/scala/scala src/library/scala/runtime/BoxesRunTime.java]]. * @@ -464,7 +464,7 @@ object Byte extends AnyValCompanion { */ def box(x: Byte): java.lang.Byte = ??? - /** Transform a boxed type into a value type. Note that this + /** Transforms a boxed type into a value type. Note that this * method is not typesafe: it accepts any Object, but will throw * an exception if the argument is not a java.lang.Byte. * diff --git a/library/src/scala/Char.scala b/library/src/scala/Char.scala index 500ffcb05412..06a6e186901b 100644 --- a/library/src/scala/Char.scala +++ b/library/src/scala/Char.scala @@ -455,7 +455,7 @@ object Char extends AnyValCompanion { /** The largest value representable as a Char. */ final val MaxValue = java.lang.Character.MAX_VALUE - /** Transform a value type into a boxed reference type. + /** Transforms a value type into a boxed reference type. * * Runtime implementation determined by `scala.runtime.BoxesRunTime.boxToCharacter`. See [[https://github.com/scala/scala src/library/scala/runtime/BoxesRunTime.java]]. * @@ -464,7 +464,7 @@ object Char extends AnyValCompanion { */ def box(x: Char): java.lang.Character = ??? - /** Transform a boxed type into a value type. Note that this + /** Transforms a boxed type into a value type. Note that this * method is not typesafe: it accepts any Object, but will throw * an exception if the argument is not a java.lang.Character. * diff --git a/library/src/scala/Double.scala b/library/src/scala/Double.scala index 08a91bf8c603..57e99999198b 100644 --- a/library/src/scala/Double.scala +++ b/library/src/scala/Double.scala @@ -230,7 +230,7 @@ object Double extends AnyValCompanion { /** The largest finite positive number representable as a Double. */ final val MaxValue = java.lang.Double.MAX_VALUE - /** Transform a value type into a boxed reference type. + /** Transforms a value type into a boxed reference type. * * Runtime implementation determined by `scala.runtime.BoxesRunTime.boxToDouble`. See [[https://github.com/scala/scala src/library/scala/runtime/BoxesRunTime.java]]. * @@ -239,7 +239,7 @@ object Double extends AnyValCompanion { */ def box(x: Double): java.lang.Double = ??? - /** Transform a boxed type into a value type. Note that this + /** Transforms a boxed type into a value type. Note that this * method is not typesafe: it accepts any Object, but will throw * an exception if the argument is not a java.lang.Double. * diff --git a/library/src/scala/Float.scala b/library/src/scala/Float.scala index fae92d99e882..1f8b00886258 100644 --- a/library/src/scala/Float.scala +++ b/library/src/scala/Float.scala @@ -230,7 +230,7 @@ object Float extends AnyValCompanion { /** The largest finite positive number representable as a Float. */ final val MaxValue = java.lang.Float.MAX_VALUE - /** Transform a value type into a boxed reference type. + /** Transforms a value type into a boxed reference type. * * Runtime implementation determined by `scala.runtime.BoxesRunTime.boxToFloat`. See [[https://github.com/scala/scala src/library/scala/runtime/BoxesRunTime.java]]. * @@ -239,7 +239,7 @@ object Float extends AnyValCompanion { */ def box(x: Float): java.lang.Float = ??? - /** Transform a boxed type into a value type. Note that this + /** Transforms a boxed type into a value type. Note that this * method is not typesafe: it accepts any Object, but will throw * an exception if the argument is not a java.lang.Float. * diff --git a/library/src/scala/Function0.scala b/library/src/scala/Function0.scala index b750d7b582b7..403d85c703c3 100644 --- a/library/src/scala/Function0.scala +++ b/library/src/scala/Function0.scala @@ -11,7 +11,7 @@ */ // GENERATED CODE: DO NOT EDIT. -// genprod generated these sources at: 2025-08-11T12:52:13.642685846Z +// genprod generated these sources at: 2025-08-11T16:42:35.454923303Z package scala diff --git a/library/src/scala/Int.scala b/library/src/scala/Int.scala index 335e33233541..a3fff7e991da 100644 --- a/library/src/scala/Int.scala +++ b/library/src/scala/Int.scala @@ -455,7 +455,7 @@ object Int extends AnyValCompanion { /** The largest value representable as an Int. */ final val MaxValue = java.lang.Integer.MAX_VALUE - /** Transform a value type into a boxed reference type. + /** Transforms a value type into a boxed reference type. * * Runtime implementation determined by `scala.runtime.BoxesRunTime.boxToInteger`. See [[https://github.com/scala/scala src/library/scala/runtime/BoxesRunTime.java]]. * @@ -464,7 +464,7 @@ object Int extends AnyValCompanion { */ def box(x: Int): java.lang.Integer = ??? - /** Transform a boxed type into a value type. Note that this + /** Transforms a boxed type into a value type. Note that this * method is not typesafe: it accepts any Object, but will throw * an exception if the argument is not a java.lang.Integer. * diff --git a/library/src/scala/Long.scala b/library/src/scala/Long.scala index e19b7e706f96..2561c1ec74c2 100644 --- a/library/src/scala/Long.scala +++ b/library/src/scala/Long.scala @@ -452,7 +452,7 @@ object Long extends AnyValCompanion { /** The largest value representable as a Long. */ final val MaxValue = java.lang.Long.MAX_VALUE - /** Transform a value type into a boxed reference type. + /** Transforms a value type into a boxed reference type. * * Runtime implementation determined by `scala.runtime.BoxesRunTime.boxToLong`. See [[https://github.com/scala/scala src/library/scala/runtime/BoxesRunTime.java]]. * @@ -461,7 +461,7 @@ object Long extends AnyValCompanion { */ def box(x: Long): java.lang.Long = ??? - /** Transform a boxed type into a value type. Note that this + /** Transforms a boxed type into a value type. Note that this * method is not typesafe: it accepts any Object, but will throw * an exception if the argument is not a java.lang.Long. * diff --git a/library/src/scala/Short.scala b/library/src/scala/Short.scala index fc58ad8640e7..03b0929ea5ce 100644 --- a/library/src/scala/Short.scala +++ b/library/src/scala/Short.scala @@ -455,7 +455,7 @@ object Short extends AnyValCompanion { /** The largest value representable as a Short. */ final val MaxValue = java.lang.Short.MAX_VALUE - /** Transform a value type into a boxed reference type. + /** Transforms a value type into a boxed reference type. * * Runtime implementation determined by `scala.runtime.BoxesRunTime.boxToShort`. See [[https://github.com/scala/scala src/library/scala/runtime/BoxesRunTime.java]]. * @@ -464,7 +464,7 @@ object Short extends AnyValCompanion { */ def box(x: Short): java.lang.Short = ??? - /** Transform a boxed type into a value type. Note that this + /** Transforms a boxed type into a value type. Note that this * method is not typesafe: it accepts any Object, but will throw * an exception if the argument is not a java.lang.Short. * diff --git a/library/src/scala/Unit.scala b/library/src/scala/Unit.scala index 1799f678e3fa..e7ad25073f50 100644 --- a/library/src/scala/Unit.scala +++ b/library/src/scala/Unit.scala @@ -31,7 +31,7 @@ final abstract class Unit private extends AnyVal { @scala.annotation.compileTimeOnly("`Unit` companion object is not allowed in source; instead, use `()` for the unit value") object Unit extends AnyValCompanion { - /** Transform a value type into a boxed reference type. + /** Transforms a value type into a boxed reference type. * * This method is not intended for use in source code. * The runtime representation of this value is platform specific. @@ -41,7 +41,7 @@ object Unit extends AnyValCompanion { */ def box(x: Unit): scala.runtime.BoxedUnit = scala.runtime.BoxedUnit.UNIT - /** Transform a boxed type into a value type. Note that this + /** Transforms a boxed type into a value type. Note that this * method is not typesafe: it accepts any Object, but will throw * an exception if the argument is not a scala.runtime.BoxedUnit. * diff --git a/project/GenerateAnyVals.scala b/project/GenerateAnyVals.scala index 52dc6e39eef5..12b68fc3e29e 100644 --- a/project/GenerateAnyVals.scala +++ b/project/GenerateAnyVals.scala @@ -152,11 +152,14 @@ import scala.language.implicitConversions""" } } - def mkCoercions = numeric map (x => "def to%s: %s".format(x, x)) - def mkUnaryOps = unaryOps map (x => "%s\n def unary_%s : %s".format(x.doc, x.op, this opType I)) + def mkCoercions = numeric.map(x => "def to%s: %s".format(x, x)) + + def mkUnaryOps = unaryOps.map(x => "%s\n def unary_%s : %s".format(x.doc, x.op, this opType I)) + def mkStringOps = List( "@deprecated(\"Adding a number and a String is deprecated. Use the string interpolation `s\\\"$num$str\\\"`\", \"2.13.0\")\n def +(x: String): String" ) + def mkShiftOps = ( for (op <- shiftOps ; arg <- List(I, L)) yield { val doc = op.doc + (if (this == L || arg == I) "" else "\n @deprecated(\"shifting a value by a `Long` argument is deprecated (except when the value is a `Long`).\\nCall `toInt` on the argument to maintain the current behavior and avoid the deprecation warning.\", \"2.12.7\")") @@ -165,7 +168,9 @@ import scala.language.implicitConversions""" ) def clumps: List[List[String]] = { - val xs1 = List(mkCoercions, mkUnaryOps, mkStringOps, mkShiftOps) map (xs => if (xs.isEmpty) xs else xs :+ "") + val xs1 = List(mkCoercions, mkUnaryOps, mkStringOps, mkShiftOps) + .filter(!_.isEmpty) + .map(_ :+ "") // add a trailing empty line val xs2 = List( mkBinOpsGroup(comparisonOps, numeric, _ => Z), mkBinOpsGroup(bitwiseOps, integer, this opType _), @@ -173,15 +178,16 @@ import scala.language.implicitConversions""" ) xs1 ++ xs2 } - def classLines = (clumps :+ commonClassLines).foldLeft(List[String]()) { - case (res, Nil) => res - case (res, lines) => - val xs = lines map { - case "" => "" - case s => interpolate(s) - } - res ++ xs + + def classLines = { + val groups = (clumps :+ commonClassLines).filter(!_.isEmpty) + val interpolated = groups.map(_.map { + case "" => "" + case s => interpolate(s) + }) + interpolated.flatten } + def objectLines = { val comp = if (isInteger) integerCompanion else floatingCompanion interpolate(comp + allCompanions + "\n" + nonUnitCompanions).trim.linesIterator.toList ++ (implicitCoercions map interpolate) @@ -260,9 +266,9 @@ import scala.language.implicitConversions""" def objectDoc = "" def mkImports = "" - def mkClass = assemble("final abstract class " + name + " private extends AnyVal", classLines) - def mkObject = assemble("object " + name + " extends AnyValCompanion", objectLines) - def make() = List[String]( + def mkClass = assemble("final abstract class " + name + " private extends AnyVal", classLines) + def mkObject = assemble("object " + name + " extends AnyValCompanion", objectLines) + def make() = List[String]( headerTemplate, mkImports, classDoc, @@ -272,10 +278,10 @@ import scala.language.implicitConversions""" ) mkString "" def assemble(decl: String, lines: List[String]): String = { - val body = if (lines.isEmpty) " { }\n\n" else lines map indent mkString (" {\n", "\n", "\n}\n") - + val body = if (lines.isEmpty) " { }\n\n" else lines.map(indent).mkString(" {\n", "\n", "\n}\n") decl + body + "\n" } + override def toString = name } } @@ -314,14 +320,14 @@ import scala.language.`2.13` """.trim + "\n") def allCompanions = """ -/** Transform a value type into a boxed reference type. +/** Transforms a value type into a boxed reference type. *@boxRunTimeDoc@ * @param x the @name@ to be boxed * @return a @boxed@ offering `x` as its underlying value. */ def box(x: @name@): @boxed@ = @boxImpl@ -/** Transform a boxed type into a value type. Note that this +/** Transforms a boxed type into a value type. Note that this * method is not typesafe: it accepts any Object, but will throw * an exception if the argument is not a @boxed@. *@unboxRunTimeDoc@ From 0c31c4a4c6c182971ca6ae589fac06eeec1e5d66 Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Mon, 11 Aug 2025 18:09:12 +0200 Subject: [PATCH 037/101] Add documentation and simplify some implicit classes --- library/src/scala/Function0.scala | 2 +- project/GenerateFunctionConverters.scala | 39 +++++++++++++++--------- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/library/src/scala/Function0.scala b/library/src/scala/Function0.scala index 403d85c703c3..68df7f546c51 100644 --- a/library/src/scala/Function0.scala +++ b/library/src/scala/Function0.scala @@ -11,7 +11,7 @@ */ // GENERATED CODE: DO NOT EDIT. -// genprod generated these sources at: 2025-08-11T16:42:35.454923303Z +// genprod generated these sources at: 2025-08-11T16:42:57.162284084Z package scala diff --git a/project/GenerateFunctionConverters.scala b/project/GenerateFunctionConverters.scala index a73bbc8d3b5a..e0eabb8266ad 100644 --- a/project/GenerateFunctionConverters.scala +++ b/project/GenerateFunctionConverters.scala @@ -13,6 +13,8 @@ package dotty.tools */ object GenerateFunctionConverters { + import scala.tools.nsc._ + case class Artifact(name: String, content: String) val copyright = @@ -33,7 +35,6 @@ object GenerateFunctionConverters { val packaging = "package scala.jdk" - import scala.tools.nsc._ val settings = new Settings(msg => sys.error(msg)) def us(cl: ClassLoader): List[String] = cl match { case ucl: java.net.URLClassLoader => ucl.getURLs.map(u => new java.io.File(u.toURI).getAbsolutePath).toList ::: us(ucl.getParent) @@ -44,6 +45,7 @@ object GenerateFunctionConverters { val run = new compiler.Run import compiler._, definitions._ + locally { // make sure `java.lang.Double` prints as `java.lang.Double`, not just `Double` (which resolves to `scala.Double`) val f = classOf[scala.reflect.internal.Definitions#DefinitionsClass].getDeclaredField("UnqualifiedOwners") @@ -65,43 +67,50 @@ object GenerateFunctionConverters { } implicit class IndentMe(v: Vector[String]) { + /** Adds indentation to every line in the vector. */ def indent: Vector[String] = v.map(" " + _) } - implicit class FlattenMe(v: Vector[Vector[String]]) { - def mkVec(join: String = ""): Vector[String] = { - val vb = Vector.newBuilder[String] + implicit class WriteToBuilder(v: Vector[Vector[String]]) { + /** Writes the vector into a builder, with `join` inserted between each group. */ + def writeTo(builder: collection.mutable.Builder[String, Vector[String]], join: String = "") = { var first = true - v.foreach{ vi => - if (!first) vb += join + for (vi <- v) { + if (!first) builder += join first = false - vb ++= vi + builder ++= vi } + } + } + + implicit class FlattenMe(v: Vector[Vector[String]]) { + /** Joins the given `Vector[Vector[String]]`, with `join` inserted in between. */ + def mkVec(join: String = ""): Vector[String] = { + val vb = Vector.newBuilder[String] + v.writeTo(vb) vb.result() } } implicit class DoubleFlattenMe(v: Vector[Vector[Vector[String]]]) { + /** Like `mkVec`, but done twice, with `join` inserted twice between outer blocks. */ def mkVecVec(join: String = ""): Vector[String] = { val vb = Vector.newBuilder[String] var first = true - v.foreach{ vi => + for (vi <- v) { if (!first) { vb += join; vb += join } first = false - var ifirst = true - vi.foreach{ vj => - if (!ifirst) vb += join - ifirst = false - vb ++= vj - } + vi.writeTo(vb, join) } vb.result() } } - implicit class SplitMyLinesAndStuff(s: String) { + implicit class StringExtensions(s: String) { // work around scala/bug#11125 + /** Splits the string into a `Vector` of lines. */ def toVec = Predef.augmentString(s).lines.toVector + /** Is the string a blank line? */ def nonBlank = s.trim.length > 0 } From fe0c8ca0adabc94b76042f7f38d5d0afd18962b7 Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Mon, 11 Aug 2025 18:30:42 +0200 Subject: [PATCH 038/101] Simplify some generation logic of GenerateFunctionConverters --- library/src/scala/Function0.scala | 2 +- project/GenerateFunctionConverters.scala | 21 ++++++++++++--------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/library/src/scala/Function0.scala b/library/src/scala/Function0.scala index 68df7f546c51..39f639793a5f 100644 --- a/library/src/scala/Function0.scala +++ b/library/src/scala/Function0.scala @@ -11,7 +11,7 @@ */ // GENERATED CODE: DO NOT EDIT. -// genprod generated these sources at: 2025-08-11T16:42:57.162284084Z +// genprod generated these sources at: 2025-08-11T16:43:12.987908263Z package scala diff --git a/project/GenerateFunctionConverters.scala b/project/GenerateFunctionConverters.scala index e0eabb8266ad..5bdc04175969 100644 --- a/project/GenerateFunctionConverters.scala +++ b/project/GenerateFunctionConverters.scala @@ -119,10 +119,12 @@ object GenerateFunctionConverters { def text = Predef.augmentString(showCode(t).replace("$", "")).lines.toVector } + /** Blocks of code with priority (lower priority has higher precedence in implicit search). */ case class Prioritized(lines: Vector[String], priority: Int) { def withPriority(i: Int) = copy(priority = i) } + /** Contains code for conversion between functions and SAMs (single abstract methods). */ case class SamConversionCode( base: String, wrappedAsScala: Vector[String], @@ -140,20 +142,21 @@ object GenerateFunctionConverters { } object SamConversionCode { def apply(scc: SamConversionCode*): (Vector[String], Vector[String], Vector[Vector[String]]) = { - val sccDepthSet = scc.map(_.implicitToJava.priority).toSet + val sccDepthSet = scc.map(_.implicitToJava.priority).to[collection.SortedSet] val codes = { - if (sccDepthSet != (0 to sccDepthSet.max).toSet) { - val sccDepthMap = sccDepthSet.toList.sorted.zipWithIndex.toMap + val size = sccDepthSet.size + // normalize the elements to a fix [0..size-1] range + if (sccDepthSet.firstKey != 0 || sccDepthSet.lastKey != size - 1) { + val sccDepthMap = sccDepthSet.iterator.zipWithIndex.toMap scc.map(x => x.withPriority(sccDepthMap(x.implicitToJava.priority))) } else scc }.toVector.sortBy(_.base) - def priorityName(n: Int, pure: Boolean = false): String = { - val pre = - if (pure) s"Priority${n}FunctionExtensions" - else s"trait ${priorityName(n, pure = true)}" - if (!pure && n < (sccDepthSet.size-1)) s"$pre extends ${priorityName(n+1, pure = true)}" else pre + def priorityName(n: Int): String = s"Priority${n}FunctionExtensions" + def priorityHeader(n: Int): String = { + val pre = s"trait ${priorityName(n)}" + if (n < sccDepthSet.size - 1) s"$pre extends ${priorityName(n+1)}" else pre } val impls = "object FunctionWrappers {" +: { @@ -163,7 +166,7 @@ object GenerateFunctionConverters { val traits = codes.groupBy(_.implicitToJava.priority).toVector.sortBy(- _._1).map{ case (k,vs) => s"import language.implicitConversions" +: "" +: - s"${priorityName(k)} {" +: + s"${priorityHeader(k)} {" +: s" import FunctionWrappers._" +: s" " +: { From f00abf8d279e86ff3371b3efc7f8eeec18201903 Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Mon, 11 Aug 2025 18:37:12 +0200 Subject: [PATCH 039/101] Style changes in GenerateLibraryNTemplates --- project/GenerateLibraryNTemplates.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/project/GenerateLibraryNTemplates.scala b/project/GenerateLibraryNTemplates.scala index 67e42b58fcdf..94c026a98d0f 100644 --- a/project/GenerateLibraryNTemplates.scala +++ b/project/GenerateLibraryNTemplates.scala @@ -201,10 +201,10 @@ object FunctionTwo extends Function(2) { object Function { def make(i: Int) = apply(i)() def apply(i: Int) = i match { - case 0 => FunctionZero - case 1 => FunctionOne - case 2 => FunctionTwo - case _ => new Function(i) + case 0 => FunctionZero + case 1 => FunctionOne + case 2 => FunctionTwo + case _ => new Function(i) } } From 2152a4d2e2e37181c17b91284d945acdbe4c0ab7 Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Mon, 11 Aug 2025 17:08:36 +0200 Subject: [PATCH 040/101] Drop self type for FunctionN --- library/src/scala/Function0.scala | 4 ++-- library/src/scala/Function1.scala | 2 +- library/src/scala/Function10.scala | 4 ++-- library/src/scala/Function11.scala | 4 ++-- library/src/scala/Function12.scala | 4 ++-- library/src/scala/Function13.scala | 4 ++-- library/src/scala/Function14.scala | 4 ++-- library/src/scala/Function15.scala | 4 ++-- library/src/scala/Function16.scala | 4 ++-- library/src/scala/Function17.scala | 4 ++-- library/src/scala/Function18.scala | 4 ++-- library/src/scala/Function19.scala | 4 ++-- library/src/scala/Function2.scala | 2 +- library/src/scala/Function20.scala | 4 ++-- library/src/scala/Function21.scala | 4 ++-- library/src/scala/Function22.scala | 4 ++-- library/src/scala/Function3.scala | 2 +- library/src/scala/Function4.scala | 2 +- library/src/scala/Function5.scala | 4 ++-- library/src/scala/Function6.scala | 4 ++-- library/src/scala/Function7.scala | 4 ++-- library/src/scala/Function8.scala | 4 ++-- library/src/scala/Function9.scala | 4 ++-- project/GenerateLibraryNTemplates.scala | 8 +++++--- 24 files changed, 47 insertions(+), 45 deletions(-) diff --git a/library/src/scala/Function0.scala b/library/src/scala/Function0.scala index 39f639793a5f..0dd571885a43 100644 --- a/library/src/scala/Function0.scala +++ b/library/src/scala/Function0.scala @@ -11,7 +11,7 @@ */ // GENERATED CODE: DO NOT EDIT. -// genprod generated these sources at: 2025-08-11T16:43:12.987908263Z +// genprod generated these sources at: 2025-08-11T16:44:44.712777821Z package scala @@ -36,7 +36,7 @@ import scala.language.`2.13` * } * }}} */ -trait Function0[@specialized(Specializable.Primitives) +R] extends AnyRef { self => +trait Function0[@specialized(Specializable.Primitives) +R] extends AnyRef { /** Apply the body of this function to the arguments. * @return the result of function application. */ diff --git a/library/src/scala/Function1.scala b/library/src/scala/Function1.scala index b31ed60766d5..ece9d778aa7b 100644 --- a/library/src/scala/Function1.scala +++ b/library/src/scala/Function1.scala @@ -65,7 +65,7 @@ object Function1 { * is that the latter can specify inputs which it will not handle. */ @annotation.implicitNotFound(msg = "No implicit view available from ${T1} => ${R}.") -trait Function1[@specialized(Specializable.Arg) -T1, @specialized(Specializable.Return) +R] extends AnyRef { self => +trait Function1[@specialized(Specializable.Arg) -T1, @specialized(Specializable.Return) +R] extends AnyRef { /** Apply the body of this function to the argument. * @return the result of function application. */ diff --git a/library/src/scala/Function10.scala b/library/src/scala/Function10.scala index c362ea877732..823544865d54 100644 --- a/library/src/scala/Function10.scala +++ b/library/src/scala/Function10.scala @@ -19,7 +19,7 @@ import scala.language.`2.13` /** A function of 10 parameters. * */ -trait Function10[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, +R] extends AnyRef { self => +trait Function10[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, +R] extends AnyRef { /** Apply the body of this function to the arguments. * @return the result of function application. */ @@ -29,7 +29,7 @@ trait Function10[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, +R] extends * @return a function `f` such that `f(x1)(x2)(x3)(x4)(x5)(x6)(x7)(x8)(x9)(x10) == apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10)` */ @annotation.unspecialized def curried: T1 => T2 => T3 => T4 => T5 => T6 => T7 => T8 => T9 => T10 => R = { - (x1: T1) => ((x2: T2, x3: T3, x4: T4, x5: T5, x6: T6, x7: T7, x8: T8, x9: T9, x10: T10) => self.apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10)).curried + (x1: T1) => ((x2: T2, x3: T3, x4: T4, x5: T5, x6: T6, x7: T7, x8: T8, x9: T9, x10: T10) => this.apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10)).curried } /** Creates a tupled version of this function: instead of 10 arguments, * it accepts a single [[scala.Tuple10]] argument. diff --git a/library/src/scala/Function11.scala b/library/src/scala/Function11.scala index 1706470355c3..bd4b3fb394ad 100644 --- a/library/src/scala/Function11.scala +++ b/library/src/scala/Function11.scala @@ -19,7 +19,7 @@ import scala.language.`2.13` /** A function of 11 parameters. * */ -trait Function11[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, +R] extends AnyRef { self => +trait Function11[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, +R] extends AnyRef { /** Apply the body of this function to the arguments. * @return the result of function application. */ @@ -29,7 +29,7 @@ trait Function11[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, +R] ex * @return a function `f` such that `f(x1)(x2)(x3)(x4)(x5)(x6)(x7)(x8)(x9)(x10)(x11) == apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11)` */ @annotation.unspecialized def curried: T1 => T2 => T3 => T4 => T5 => T6 => T7 => T8 => T9 => T10 => T11 => R = { - (x1: T1) => ((x2: T2, x3: T3, x4: T4, x5: T5, x6: T6, x7: T7, x8: T8, x9: T9, x10: T10, x11: T11) => self.apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11)).curried + (x1: T1) => ((x2: T2, x3: T3, x4: T4, x5: T5, x6: T6, x7: T7, x8: T8, x9: T9, x10: T10, x11: T11) => this.apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11)).curried } /** Creates a tupled version of this function: instead of 11 arguments, * it accepts a single [[scala.Tuple11]] argument. diff --git a/library/src/scala/Function12.scala b/library/src/scala/Function12.scala index 5ccaa7722095..42c5ba6aa645 100644 --- a/library/src/scala/Function12.scala +++ b/library/src/scala/Function12.scala @@ -19,7 +19,7 @@ import scala.language.`2.13` /** A function of 12 parameters. * */ -trait Function12[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, +R] extends AnyRef { self => +trait Function12[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, +R] extends AnyRef { /** Apply the body of this function to the arguments. * @return the result of function application. */ @@ -29,7 +29,7 @@ trait Function12[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, * @return a function `f` such that `f(x1)(x2)(x3)(x4)(x5)(x6)(x7)(x8)(x9)(x10)(x11)(x12) == apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12)` */ @annotation.unspecialized def curried: T1 => T2 => T3 => T4 => T5 => T6 => T7 => T8 => T9 => T10 => T11 => T12 => R = { - (x1: T1) => ((x2: T2, x3: T3, x4: T4, x5: T5, x6: T6, x7: T7, x8: T8, x9: T9, x10: T10, x11: T11, x12: T12) => self.apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12)).curried + (x1: T1) => ((x2: T2, x3: T3, x4: T4, x5: T5, x6: T6, x7: T7, x8: T8, x9: T9, x10: T10, x11: T11, x12: T12) => this.apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12)).curried } /** Creates a tupled version of this function: instead of 12 arguments, * it accepts a single [[scala.Tuple12]] argument. diff --git a/library/src/scala/Function13.scala b/library/src/scala/Function13.scala index a92b6710bbfb..074c69b779c1 100644 --- a/library/src/scala/Function13.scala +++ b/library/src/scala/Function13.scala @@ -19,7 +19,7 @@ import scala.language.`2.13` /** A function of 13 parameters. * */ -trait Function13[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, -T13, +R] extends AnyRef { self => +trait Function13[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, -T13, +R] extends AnyRef { /** Apply the body of this function to the arguments. * @return the result of function application. */ @@ -29,7 +29,7 @@ trait Function13[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, * @return a function `f` such that `f(x1)(x2)(x3)(x4)(x5)(x6)(x7)(x8)(x9)(x10)(x11)(x12)(x13) == apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13)` */ @annotation.unspecialized def curried: T1 => T2 => T3 => T4 => T5 => T6 => T7 => T8 => T9 => T10 => T11 => T12 => T13 => R = { - (x1: T1) => ((x2: T2, x3: T3, x4: T4, x5: T5, x6: T6, x7: T7, x8: T8, x9: T9, x10: T10, x11: T11, x12: T12, x13: T13) => self.apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13)).curried + (x1: T1) => ((x2: T2, x3: T3, x4: T4, x5: T5, x6: T6, x7: T7, x8: T8, x9: T9, x10: T10, x11: T11, x12: T12, x13: T13) => this.apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13)).curried } /** Creates a tupled version of this function: instead of 13 arguments, * it accepts a single [[scala.Tuple13]] argument. diff --git a/library/src/scala/Function14.scala b/library/src/scala/Function14.scala index 687a3e693766..06332ea72f66 100644 --- a/library/src/scala/Function14.scala +++ b/library/src/scala/Function14.scala @@ -19,7 +19,7 @@ import scala.language.`2.13` /** A function of 14 parameters. * */ -trait Function14[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, -T13, -T14, +R] extends AnyRef { self => +trait Function14[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, -T13, -T14, +R] extends AnyRef { /** Apply the body of this function to the arguments. * @return the result of function application. */ @@ -29,7 +29,7 @@ trait Function14[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, * @return a function `f` such that `f(x1)(x2)(x3)(x4)(x5)(x6)(x7)(x8)(x9)(x10)(x11)(x12)(x13)(x14) == apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14)` */ @annotation.unspecialized def curried: T1 => T2 => T3 => T4 => T5 => T6 => T7 => T8 => T9 => T10 => T11 => T12 => T13 => T14 => R = { - (x1: T1) => ((x2: T2, x3: T3, x4: T4, x5: T5, x6: T6, x7: T7, x8: T8, x9: T9, x10: T10, x11: T11, x12: T12, x13: T13, x14: T14) => self.apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14)).curried + (x1: T1) => ((x2: T2, x3: T3, x4: T4, x5: T5, x6: T6, x7: T7, x8: T8, x9: T9, x10: T10, x11: T11, x12: T12, x13: T13, x14: T14) => this.apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14)).curried } /** Creates a tupled version of this function: instead of 14 arguments, * it accepts a single [[scala.Tuple14]] argument. diff --git a/library/src/scala/Function15.scala b/library/src/scala/Function15.scala index c45cae4e1a97..5b4c022bef76 100644 --- a/library/src/scala/Function15.scala +++ b/library/src/scala/Function15.scala @@ -19,7 +19,7 @@ import scala.language.`2.13` /** A function of 15 parameters. * */ -trait Function15[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, -T13, -T14, -T15, +R] extends AnyRef { self => +trait Function15[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, -T13, -T14, -T15, +R] extends AnyRef { /** Apply the body of this function to the arguments. * @return the result of function application. */ @@ -29,7 +29,7 @@ trait Function15[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, * @return a function `f` such that `f(x1)(x2)(x3)(x4)(x5)(x6)(x7)(x8)(x9)(x10)(x11)(x12)(x13)(x14)(x15) == apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15)` */ @annotation.unspecialized def curried: T1 => T2 => T3 => T4 => T5 => T6 => T7 => T8 => T9 => T10 => T11 => T12 => T13 => T14 => T15 => R = { - (x1: T1) => ((x2: T2, x3: T3, x4: T4, x5: T5, x6: T6, x7: T7, x8: T8, x9: T9, x10: T10, x11: T11, x12: T12, x13: T13, x14: T14, x15: T15) => self.apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15)).curried + (x1: T1) => ((x2: T2, x3: T3, x4: T4, x5: T5, x6: T6, x7: T7, x8: T8, x9: T9, x10: T10, x11: T11, x12: T12, x13: T13, x14: T14, x15: T15) => this.apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15)).curried } /** Creates a tupled version of this function: instead of 15 arguments, * it accepts a single [[scala.Tuple15]] argument. diff --git a/library/src/scala/Function16.scala b/library/src/scala/Function16.scala index 8795ccfbd546..937ee43ef768 100644 --- a/library/src/scala/Function16.scala +++ b/library/src/scala/Function16.scala @@ -19,7 +19,7 @@ import scala.language.`2.13` /** A function of 16 parameters. * */ -trait Function16[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, -T13, -T14, -T15, -T16, +R] extends AnyRef { self => +trait Function16[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, -T13, -T14, -T15, -T16, +R] extends AnyRef { /** Apply the body of this function to the arguments. * @return the result of function application. */ @@ -29,7 +29,7 @@ trait Function16[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, * @return a function `f` such that `f(x1)(x2)(x3)(x4)(x5)(x6)(x7)(x8)(x9)(x10)(x11)(x12)(x13)(x14)(x15)(x16) == apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16)` */ @annotation.unspecialized def curried: T1 => T2 => T3 => T4 => T5 => T6 => T7 => T8 => T9 => T10 => T11 => T12 => T13 => T14 => T15 => T16 => R = { - (x1: T1) => ((x2: T2, x3: T3, x4: T4, x5: T5, x6: T6, x7: T7, x8: T8, x9: T9, x10: T10, x11: T11, x12: T12, x13: T13, x14: T14, x15: T15, x16: T16) => self.apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16)).curried + (x1: T1) => ((x2: T2, x3: T3, x4: T4, x5: T5, x6: T6, x7: T7, x8: T8, x9: T9, x10: T10, x11: T11, x12: T12, x13: T13, x14: T14, x15: T15, x16: T16) => this.apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16)).curried } /** Creates a tupled version of this function: instead of 16 arguments, * it accepts a single [[scala.Tuple16]] argument. diff --git a/library/src/scala/Function17.scala b/library/src/scala/Function17.scala index 874ea3bcd9fc..4e054ba2833d 100644 --- a/library/src/scala/Function17.scala +++ b/library/src/scala/Function17.scala @@ -19,7 +19,7 @@ import scala.language.`2.13` /** A function of 17 parameters. * */ -trait Function17[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, -T13, -T14, -T15, -T16, -T17, +R] extends AnyRef { self => +trait Function17[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, -T13, -T14, -T15, -T16, -T17, +R] extends AnyRef { /** Apply the body of this function to the arguments. * @return the result of function application. */ @@ -29,7 +29,7 @@ trait Function17[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, * @return a function `f` such that `f(x1)(x2)(x3)(x4)(x5)(x6)(x7)(x8)(x9)(x10)(x11)(x12)(x13)(x14)(x15)(x16)(x17) == apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17)` */ @annotation.unspecialized def curried: T1 => T2 => T3 => T4 => T5 => T6 => T7 => T8 => T9 => T10 => T11 => T12 => T13 => T14 => T15 => T16 => T17 => R = { - (x1: T1) => ((x2: T2, x3: T3, x4: T4, x5: T5, x6: T6, x7: T7, x8: T8, x9: T9, x10: T10, x11: T11, x12: T12, x13: T13, x14: T14, x15: T15, x16: T16, x17: T17) => self.apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17)).curried + (x1: T1) => ((x2: T2, x3: T3, x4: T4, x5: T5, x6: T6, x7: T7, x8: T8, x9: T9, x10: T10, x11: T11, x12: T12, x13: T13, x14: T14, x15: T15, x16: T16, x17: T17) => this.apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17)).curried } /** Creates a tupled version of this function: instead of 17 arguments, * it accepts a single [[scala.Tuple17]] argument. diff --git a/library/src/scala/Function18.scala b/library/src/scala/Function18.scala index 4fa20a649416..cceb289ab6f9 100644 --- a/library/src/scala/Function18.scala +++ b/library/src/scala/Function18.scala @@ -19,7 +19,7 @@ import scala.language.`2.13` /** A function of 18 parameters. * */ -trait Function18[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, -T13, -T14, -T15, -T16, -T17, -T18, +R] extends AnyRef { self => +trait Function18[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, -T13, -T14, -T15, -T16, -T17, -T18, +R] extends AnyRef { /** Apply the body of this function to the arguments. * @return the result of function application. */ @@ -29,7 +29,7 @@ trait Function18[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, * @return a function `f` such that `f(x1)(x2)(x3)(x4)(x5)(x6)(x7)(x8)(x9)(x10)(x11)(x12)(x13)(x14)(x15)(x16)(x17)(x18) == apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18)` */ @annotation.unspecialized def curried: T1 => T2 => T3 => T4 => T5 => T6 => T7 => T8 => T9 => T10 => T11 => T12 => T13 => T14 => T15 => T16 => T17 => T18 => R = { - (x1: T1) => ((x2: T2, x3: T3, x4: T4, x5: T5, x6: T6, x7: T7, x8: T8, x9: T9, x10: T10, x11: T11, x12: T12, x13: T13, x14: T14, x15: T15, x16: T16, x17: T17, x18: T18) => self.apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18)).curried + (x1: T1) => ((x2: T2, x3: T3, x4: T4, x5: T5, x6: T6, x7: T7, x8: T8, x9: T9, x10: T10, x11: T11, x12: T12, x13: T13, x14: T14, x15: T15, x16: T16, x17: T17, x18: T18) => this.apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18)).curried } /** Creates a tupled version of this function: instead of 18 arguments, * it accepts a single [[scala.Tuple18]] argument. diff --git a/library/src/scala/Function19.scala b/library/src/scala/Function19.scala index c59e32b4ae40..88a3dd10d9be 100644 --- a/library/src/scala/Function19.scala +++ b/library/src/scala/Function19.scala @@ -19,7 +19,7 @@ import scala.language.`2.13` /** A function of 19 parameters. * */ -trait Function19[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, -T13, -T14, -T15, -T16, -T17, -T18, -T19, +R] extends AnyRef { self => +trait Function19[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, -T13, -T14, -T15, -T16, -T17, -T18, -T19, +R] extends AnyRef { /** Apply the body of this function to the arguments. * @return the result of function application. */ @@ -29,7 +29,7 @@ trait Function19[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, * @return a function `f` such that `f(x1)(x2)(x3)(x4)(x5)(x6)(x7)(x8)(x9)(x10)(x11)(x12)(x13)(x14)(x15)(x16)(x17)(x18)(x19) == apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19)` */ @annotation.unspecialized def curried: T1 => T2 => T3 => T4 => T5 => T6 => T7 => T8 => T9 => T10 => T11 => T12 => T13 => T14 => T15 => T16 => T17 => T18 => T19 => R = { - (x1: T1) => ((x2: T2, x3: T3, x4: T4, x5: T5, x6: T6, x7: T7, x8: T8, x9: T9, x10: T10, x11: T11, x12: T12, x13: T13, x14: T14, x15: T15, x16: T16, x17: T17, x18: T18, x19: T19) => self.apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19)).curried + (x1: T1) => ((x2: T2, x3: T3, x4: T4, x5: T5, x6: T6, x7: T7, x8: T8, x9: T9, x10: T10, x11: T11, x12: T12, x13: T13, x14: T14, x15: T15, x16: T16, x17: T17, x18: T18, x19: T19) => this.apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19)).curried } /** Creates a tupled version of this function: instead of 19 arguments, * it accepts a single [[scala.Tuple19]] argument. diff --git a/library/src/scala/Function2.scala b/library/src/scala/Function2.scala index ccb066fb9f3a..ecb760e4e39c 100644 --- a/library/src/scala/Function2.scala +++ b/library/src/scala/Function2.scala @@ -34,7 +34,7 @@ import scala.language.`2.13` * } * }}} */ -trait Function2[@specialized(Specializable.Args) -T1, @specialized(Specializable.Args) -T2, @specialized(Specializable.Return) +R] extends AnyRef { self => +trait Function2[@specialized(Specializable.Args) -T1, @specialized(Specializable.Args) -T2, @specialized(Specializable.Return) +R] extends AnyRef { /** Apply the body of this function to the arguments. * @return the result of function application. */ diff --git a/library/src/scala/Function20.scala b/library/src/scala/Function20.scala index 1b445f783310..aac19a4309fc 100644 --- a/library/src/scala/Function20.scala +++ b/library/src/scala/Function20.scala @@ -19,7 +19,7 @@ import scala.language.`2.13` /** A function of 20 parameters. * */ -trait Function20[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, -T13, -T14, -T15, -T16, -T17, -T18, -T19, -T20, +R] extends AnyRef { self => +trait Function20[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, -T13, -T14, -T15, -T16, -T17, -T18, -T19, -T20, +R] extends AnyRef { /** Apply the body of this function to the arguments. * @return the result of function application. */ @@ -29,7 +29,7 @@ trait Function20[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, * @return a function `f` such that `f(x1)(x2)(x3)(x4)(x5)(x6)(x7)(x8)(x9)(x10)(x11)(x12)(x13)(x14)(x15)(x16)(x17)(x18)(x19)(x20) == apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20)` */ @annotation.unspecialized def curried: T1 => T2 => T3 => T4 => T5 => T6 => T7 => T8 => T9 => T10 => T11 => T12 => T13 => T14 => T15 => T16 => T17 => T18 => T19 => T20 => R = { - (x1: T1) => ((x2: T2, x3: T3, x4: T4, x5: T5, x6: T6, x7: T7, x8: T8, x9: T9, x10: T10, x11: T11, x12: T12, x13: T13, x14: T14, x15: T15, x16: T16, x17: T17, x18: T18, x19: T19, x20: T20) => self.apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20)).curried + (x1: T1) => ((x2: T2, x3: T3, x4: T4, x5: T5, x6: T6, x7: T7, x8: T8, x9: T9, x10: T10, x11: T11, x12: T12, x13: T13, x14: T14, x15: T15, x16: T16, x17: T17, x18: T18, x19: T19, x20: T20) => this.apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20)).curried } /** Creates a tupled version of this function: instead of 20 arguments, * it accepts a single [[scala.Tuple20]] argument. diff --git a/library/src/scala/Function21.scala b/library/src/scala/Function21.scala index e5e3047da3b3..cf5231f08939 100644 --- a/library/src/scala/Function21.scala +++ b/library/src/scala/Function21.scala @@ -19,7 +19,7 @@ import scala.language.`2.13` /** A function of 21 parameters. * */ -trait Function21[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, -T13, -T14, -T15, -T16, -T17, -T18, -T19, -T20, -T21, +R] extends AnyRef { self => +trait Function21[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, -T13, -T14, -T15, -T16, -T17, -T18, -T19, -T20, -T21, +R] extends AnyRef { /** Apply the body of this function to the arguments. * @return the result of function application. */ @@ -29,7 +29,7 @@ trait Function21[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, * @return a function `f` such that `f(x1)(x2)(x3)(x4)(x5)(x6)(x7)(x8)(x9)(x10)(x11)(x12)(x13)(x14)(x15)(x16)(x17)(x18)(x19)(x20)(x21) == apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, x21)` */ @annotation.unspecialized def curried: T1 => T2 => T3 => T4 => T5 => T6 => T7 => T8 => T9 => T10 => T11 => T12 => T13 => T14 => T15 => T16 => T17 => T18 => T19 => T20 => T21 => R = { - (x1: T1) => ((x2: T2, x3: T3, x4: T4, x5: T5, x6: T6, x7: T7, x8: T8, x9: T9, x10: T10, x11: T11, x12: T12, x13: T13, x14: T14, x15: T15, x16: T16, x17: T17, x18: T18, x19: T19, x20: T20, x21: T21) => self.apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, x21)).curried + (x1: T1) => ((x2: T2, x3: T3, x4: T4, x5: T5, x6: T6, x7: T7, x8: T8, x9: T9, x10: T10, x11: T11, x12: T12, x13: T13, x14: T14, x15: T15, x16: T16, x17: T17, x18: T18, x19: T19, x20: T20, x21: T21) => this.apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, x21)).curried } /** Creates a tupled version of this function: instead of 21 arguments, * it accepts a single [[scala.Tuple21]] argument. diff --git a/library/src/scala/Function22.scala b/library/src/scala/Function22.scala index 9eae1d43ce26..21e52c9d5562 100644 --- a/library/src/scala/Function22.scala +++ b/library/src/scala/Function22.scala @@ -19,7 +19,7 @@ import scala.language.`2.13` /** A function of 22 parameters. * */ -trait Function22[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, -T13, -T14, -T15, -T16, -T17, -T18, -T19, -T20, -T21, -T22, +R] extends AnyRef { self => +trait Function22[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, -T13, -T14, -T15, -T16, -T17, -T18, -T19, -T20, -T21, -T22, +R] extends AnyRef { /** Apply the body of this function to the arguments. * @return the result of function application. */ @@ -29,7 +29,7 @@ trait Function22[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, -T10, -T11, -T12, * @return a function `f` such that `f(x1)(x2)(x3)(x4)(x5)(x6)(x7)(x8)(x9)(x10)(x11)(x12)(x13)(x14)(x15)(x16)(x17)(x18)(x19)(x20)(x21)(x22) == apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, x21, x22)` */ @annotation.unspecialized def curried: T1 => T2 => T3 => T4 => T5 => T6 => T7 => T8 => T9 => T10 => T11 => T12 => T13 => T14 => T15 => T16 => T17 => T18 => T19 => T20 => T21 => T22 => R = { - (x1: T1) => ((x2: T2, x3: T3, x4: T4, x5: T5, x6: T6, x7: T7, x8: T8, x9: T9, x10: T10, x11: T11, x12: T12, x13: T13, x14: T14, x15: T15, x16: T16, x17: T17, x18: T18, x19: T19, x20: T20, x21: T21, x22: T22) => self.apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, x21, x22)).curried + (x1: T1) => ((x2: T2, x3: T3, x4: T4, x5: T5, x6: T6, x7: T7, x8: T8, x9: T9, x10: T10, x11: T11, x12: T12, x13: T13, x14: T14, x15: T15, x16: T16, x17: T17, x18: T18, x19: T19, x20: T20, x21: T21, x22: T22) => this.apply(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, x21, x22)).curried } /** Creates a tupled version of this function: instead of 22 arguments, * it accepts a single [[scala.Tuple22]] argument. diff --git a/library/src/scala/Function3.scala b/library/src/scala/Function3.scala index 5c29f6e4fbbd..b7cbec633261 100644 --- a/library/src/scala/Function3.scala +++ b/library/src/scala/Function3.scala @@ -19,7 +19,7 @@ import scala.language.`2.13` /** A function of 3 parameters. * */ -trait Function3[-T1, -T2, -T3, +R] extends AnyRef { self => +trait Function3[-T1, -T2, -T3, +R] extends AnyRef { /** Apply the body of this function to the arguments. * @return the result of function application. */ diff --git a/library/src/scala/Function4.scala b/library/src/scala/Function4.scala index efc3c56909eb..062a92b4abfc 100644 --- a/library/src/scala/Function4.scala +++ b/library/src/scala/Function4.scala @@ -19,7 +19,7 @@ import scala.language.`2.13` /** A function of 4 parameters. * */ -trait Function4[-T1, -T2, -T3, -T4, +R] extends AnyRef { self => +trait Function4[-T1, -T2, -T3, -T4, +R] extends AnyRef { /** Apply the body of this function to the arguments. * @return the result of function application. */ diff --git a/library/src/scala/Function5.scala b/library/src/scala/Function5.scala index a0e4b082c728..75115c78379d 100644 --- a/library/src/scala/Function5.scala +++ b/library/src/scala/Function5.scala @@ -19,7 +19,7 @@ import scala.language.`2.13` /** A function of 5 parameters. * */ -trait Function5[-T1, -T2, -T3, -T4, -T5, +R] extends AnyRef { self => +trait Function5[-T1, -T2, -T3, -T4, -T5, +R] extends AnyRef { /** Apply the body of this function to the arguments. * @return the result of function application. */ @@ -29,7 +29,7 @@ trait Function5[-T1, -T2, -T3, -T4, -T5, +R] extends AnyRef { self => * @return a function `f` such that `f(x1)(x2)(x3)(x4)(x5) == apply(x1, x2, x3, x4, x5)` */ @annotation.unspecialized def curried: T1 => T2 => T3 => T4 => T5 => R = { - (x1: T1) => ((x2: T2, x3: T3, x4: T4, x5: T5) => self.apply(x1, x2, x3, x4, x5)).curried + (x1: T1) => ((x2: T2, x3: T3, x4: T4, x5: T5) => this.apply(x1, x2, x3, x4, x5)).curried } /** Creates a tupled version of this function: instead of 5 arguments, * it accepts a single [[scala.Tuple5]] argument. diff --git a/library/src/scala/Function6.scala b/library/src/scala/Function6.scala index 58d428c8e888..99f4b4a2ffda 100644 --- a/library/src/scala/Function6.scala +++ b/library/src/scala/Function6.scala @@ -19,7 +19,7 @@ import scala.language.`2.13` /** A function of 6 parameters. * */ -trait Function6[-T1, -T2, -T3, -T4, -T5, -T6, +R] extends AnyRef { self => +trait Function6[-T1, -T2, -T3, -T4, -T5, -T6, +R] extends AnyRef { /** Apply the body of this function to the arguments. * @return the result of function application. */ @@ -29,7 +29,7 @@ trait Function6[-T1, -T2, -T3, -T4, -T5, -T6, +R] extends AnyRef { self => * @return a function `f` such that `f(x1)(x2)(x3)(x4)(x5)(x6) == apply(x1, x2, x3, x4, x5, x6)` */ @annotation.unspecialized def curried: T1 => T2 => T3 => T4 => T5 => T6 => R = { - (x1: T1) => ((x2: T2, x3: T3, x4: T4, x5: T5, x6: T6) => self.apply(x1, x2, x3, x4, x5, x6)).curried + (x1: T1) => ((x2: T2, x3: T3, x4: T4, x5: T5, x6: T6) => this.apply(x1, x2, x3, x4, x5, x6)).curried } /** Creates a tupled version of this function: instead of 6 arguments, * it accepts a single [[scala.Tuple6]] argument. diff --git a/library/src/scala/Function7.scala b/library/src/scala/Function7.scala index 8f4bfa19aa9d..04f54559b230 100644 --- a/library/src/scala/Function7.scala +++ b/library/src/scala/Function7.scala @@ -19,7 +19,7 @@ import scala.language.`2.13` /** A function of 7 parameters. * */ -trait Function7[-T1, -T2, -T3, -T4, -T5, -T6, -T7, +R] extends AnyRef { self => +trait Function7[-T1, -T2, -T3, -T4, -T5, -T6, -T7, +R] extends AnyRef { /** Apply the body of this function to the arguments. * @return the result of function application. */ @@ -29,7 +29,7 @@ trait Function7[-T1, -T2, -T3, -T4, -T5, -T6, -T7, +R] extends AnyRef { self => * @return a function `f` such that `f(x1)(x2)(x3)(x4)(x5)(x6)(x7) == apply(x1, x2, x3, x4, x5, x6, x7)` */ @annotation.unspecialized def curried: T1 => T2 => T3 => T4 => T5 => T6 => T7 => R = { - (x1: T1) => ((x2: T2, x3: T3, x4: T4, x5: T5, x6: T6, x7: T7) => self.apply(x1, x2, x3, x4, x5, x6, x7)).curried + (x1: T1) => ((x2: T2, x3: T3, x4: T4, x5: T5, x6: T6, x7: T7) => this.apply(x1, x2, x3, x4, x5, x6, x7)).curried } /** Creates a tupled version of this function: instead of 7 arguments, * it accepts a single [[scala.Tuple7]] argument. diff --git a/library/src/scala/Function8.scala b/library/src/scala/Function8.scala index 384f6132d1a5..44394a42915d 100644 --- a/library/src/scala/Function8.scala +++ b/library/src/scala/Function8.scala @@ -19,7 +19,7 @@ import scala.language.`2.13` /** A function of 8 parameters. * */ -trait Function8[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, +R] extends AnyRef { self => +trait Function8[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, +R] extends AnyRef { /** Apply the body of this function to the arguments. * @return the result of function application. */ @@ -29,7 +29,7 @@ trait Function8[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, +R] extends AnyRef { sel * @return a function `f` such that `f(x1)(x2)(x3)(x4)(x5)(x6)(x7)(x8) == apply(x1, x2, x3, x4, x5, x6, x7, x8)` */ @annotation.unspecialized def curried: T1 => T2 => T3 => T4 => T5 => T6 => T7 => T8 => R = { - (x1: T1) => ((x2: T2, x3: T3, x4: T4, x5: T5, x6: T6, x7: T7, x8: T8) => self.apply(x1, x2, x3, x4, x5, x6, x7, x8)).curried + (x1: T1) => ((x2: T2, x3: T3, x4: T4, x5: T5, x6: T6, x7: T7, x8: T8) => this.apply(x1, x2, x3, x4, x5, x6, x7, x8)).curried } /** Creates a tupled version of this function: instead of 8 arguments, * it accepts a single [[scala.Tuple8]] argument. diff --git a/library/src/scala/Function9.scala b/library/src/scala/Function9.scala index a60f59f0f25d..76b42793a06d 100644 --- a/library/src/scala/Function9.scala +++ b/library/src/scala/Function9.scala @@ -19,7 +19,7 @@ import scala.language.`2.13` /** A function of 9 parameters. * */ -trait Function9[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, +R] extends AnyRef { self => +trait Function9[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, +R] extends AnyRef { /** Apply the body of this function to the arguments. * @return the result of function application. */ @@ -29,7 +29,7 @@ trait Function9[-T1, -T2, -T3, -T4, -T5, -T6, -T7, -T8, -T9, +R] extends AnyRef * @return a function `f` such that `f(x1)(x2)(x3)(x4)(x5)(x6)(x7)(x8)(x9) == apply(x1, x2, x3, x4, x5, x6, x7, x8, x9)` */ @annotation.unspecialized def curried: T1 => T2 => T3 => T4 => T5 => T6 => T7 => T8 => T9 => R = { - (x1: T1) => ((x2: T2, x3: T3, x4: T4, x5: T5, x6: T6, x7: T7, x8: T8, x9: T9) => self.apply(x1, x2, x3, x4, x5, x6, x7, x8, x9)).curried + (x1: T1) => ((x2: T2, x3: T3, x4: T4, x5: T5, x6: T6, x7: T7, x8: T8, x9: T9) => this.apply(x1, x2, x3, x4, x5, x6, x7, x8, x9)).curried } /** Creates a tupled version of this function: instead of 9 arguments, * it accepts a single [[scala.Tuple9]] argument. diff --git a/project/GenerateLibraryNTemplates.scala b/project/GenerateLibraryNTemplates.scala index 94c026a98d0f..af7358d1e554 100644 --- a/project/GenerateLibraryNTemplates.scala +++ b/project/GenerateLibraryNTemplates.scala @@ -209,6 +209,8 @@ object Function { } class Function(val i: Int) extends Group("Function") with Arity { + // override def imports: String = "import language.experimental.captureChecking\n" + super.imports + // def selfType = className + (targs ++ List("R")).mkString("[", ",", "]") + "^" def descriptiveComment = "" def functionNTemplate = """ @@ -228,7 +230,7 @@ class Function(val i: Int) extends Group("Function") with Arity { /** A function of {i} parameter{s}. *{descriptiveComment} */ -{classAnnotation}trait {className}{contraCoArgs} extends AnyRef {{ self => +{classAnnotation}trait {className}{contraCoArgs} extends AnyRef {{ /** Apply the body of this function to the argument{s}. * @return the result of function application. */ @@ -247,11 +249,11 @@ class Function(val i: Int) extends Group("Function") with Arity { (xdefs, targs).zipped.map("(%s: %s) => ".format(_, _)).mkString("", "", body) } - // (x1: T1) => ((x2: T2, x3: T3, x4: T4, x5: T5, x6: T6, x7: T7) => self.apply(x1,x2,x3,x4,x5,x6,x7)).curried + // (x1: T1) => ((x2: T2, x3: T3, x4: T4, x5: T5, x6: T6, x7: T7) => this.apply(x1,x2,x3,x4,x5,x6,x7)).curried def longCurry = ((xdefs, targs).zipped.map(_ + ": " + _) drop 1).mkString( "(x1: T1) => ((", ", ", - ") => self.apply%s).curried".format(commaXs) + ") => this.apply%s).curried".format(commaXs) ) // f(x1,x2,x3,x4,x5,x6) == (f.curried)(x1)(x2)(x3)(x4)(x5)(x6) From 7aca4135f8adc7d530f943510116de709d479fad Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Tue, 12 Aug 2025 01:25:55 +0200 Subject: [PATCH 041/101] Add capture checking to PartialFunction --- library/src/scala/PartialFunction.scala | 56 ++++++++++--------- .../runtime/AbstractPartialFunction.scala | 4 +- .../captures/partial-function.scala | 6 ++ .../captures/partial-function.scala | 7 +++ 4 files changed, 45 insertions(+), 28 deletions(-) create mode 100644 tests/neg-custom-args/captures/partial-function.scala create mode 100644 tests/pos-custom-args/captures/partial-function.scala diff --git a/library/src/scala/PartialFunction.scala b/library/src/scala/PartialFunction.scala index af8199d6209e..31f3fbe77a83 100644 --- a/library/src/scala/PartialFunction.scala +++ b/library/src/scala/PartialFunction.scala @@ -15,6 +15,8 @@ package scala import scala.language.`2.13` import scala.annotation.nowarn +import language.experimental.captureChecking + /** A partial function of type `PartialFunction[A, B]` is a unary function * where the domain does not necessarily include all values of type `A`. * The function [[isDefinedAt]] allows to test dynamically if a value is in @@ -105,7 +107,7 @@ import scala.annotation.nowarn * may apply the first partial function and execute its side effect. * For efficiency, it is recommended to call [[applyOrElse]] instead of [[isDefinedAt]] or [[apply]]. */ -trait PartialFunction[-A, +B] extends (A => B) { self => +trait PartialFunction[-A, +B] extends Function1[A, B] { self: PartialFunction[A, B]^ => import PartialFunction._ /** Tries to extract a `B` from an `A` in a pattern matching expression. */ @@ -122,7 +124,7 @@ trait PartialFunction[-A, +B] extends (A => B) { self => * } * }}} */ - def elementWise: ElementWiseExtractor[A, B] = new ElementWiseExtractor[A, B](this) + def elementWise: ElementWiseExtractor[A, B]^{this} = new ElementWiseExtractor[A, B](this) /** Checks if a value is contained in the function's domain. * @@ -141,7 +143,7 @@ trait PartialFunction[-A, +B] extends (A => B) { self => * of this partial function and `that`. The resulting partial function * takes `x` to `this(x)` where `this` is defined, and to `that(x)` where it is not. */ - def orElse[A1 <: A, B1 >: B](that: PartialFunction[A1, B1]): PartialFunction[A1, B1] = + def orElse[A1 <: A, B1 >: B](that: PartialFunction[A1, B1]^): PartialFunction[A1, B1]^{this, that} = new OrElse[A1, B1] (this, that) //TODO: why not overload it with orElse(that: F1): F1? @@ -157,8 +159,8 @@ trait PartialFunction[-A, +B] extends (A => B) { self => * possibly narrowed by the specified function, which maps * arguments `x` to `k(this(x))`. */ - override def andThen[C](k: B => C): PartialFunction[A, C] = k match { - case pf: PartialFunction[B, C] => andThen(pf) + override def andThen[C](k: B => C): PartialFunction[A, C]^{this, k} = k match { + case pf: (PartialFunction[B, C]^{k}) => andThen(pf) case _ => new AndThen[A, B, C](this, k) } @@ -173,7 +175,7 @@ trait PartialFunction[-A, +B] extends (A => B) { self => * @return a partial function with the domain of this partial function narrowed by * other partial function, which maps arguments `x` to `k(this(x))`. */ - def andThen[C](k: PartialFunction[B, C]): PartialFunction[A, C] = + def andThen[C](k: PartialFunction[B, C]^): PartialFunction[A, C]^{this, k} = new Combined[A, B, C](this, k) /** @@ -187,7 +189,7 @@ trait PartialFunction[-A, +B] extends (A => B) { self => * @return a partial function with the domain of other partial function narrowed by * this partial function, which maps arguments `x` to `this(k(x))`. */ - def compose[R](k: PartialFunction[R, A]): PartialFunction[R, B] = + def compose[R](k: PartialFunction[R, A]^): PartialFunction[R, B]^{this, k} = new Combined[R, A, B](k, this) /** Turns this partial function into a plain function returning an `Option` result. @@ -195,7 +197,7 @@ trait PartialFunction[-A, +B] extends (A => B) { self => * @return a function that takes an argument `x` to `Some(this(x))` if `this` * is defined for `x`, and to `None` otherwise. */ - def lift: A => Option[B] = new Lifted(this) + def lift: A ->{this} Option[B] = new Lifted(this) /** Applies this partial function to the given argument when it is contained in the function domain. * Applies fallback function where this partial function is not defined. @@ -239,7 +241,7 @@ trait PartialFunction[-A, +B] extends (A => B) { self => * @return a function which maps arguments `x` to `isDefinedAt(x)`. The resulting function * runs `action(this(x))` where `this` is defined. */ - def runWith[U](action: B => U): A => Boolean = { x => + def runWith[U](action: B => U): A ->{this, action} Boolean = { x => val z = applyOrElse(x, checkFallback[B]) if (!fallbackOccurred(z)) { action(z); true } else false } @@ -259,7 +261,7 @@ trait PartialFunction[-A, +B] extends (A => B) { self => */ object PartialFunction { - final class ElementWiseExtractor[-A, +B] private[PartialFunction] (private val pf: PartialFunction[A, B]) extends AnyVal { + final class ElementWiseExtractor[-A, +B] private[PartialFunction] (private val pf: PartialFunction[A, B]^) extends AnyVal { this: ElementWiseExtractor[A, B]^ => @nowarn("cat=lint-nonlocal-return") def unapplySeq(seq: Seq[A]): Option[Seq[B]] = { Some(seq.map { @@ -271,7 +273,7 @@ object PartialFunction { /** Composite function produced by `PartialFunction#orElse` method */ - private class OrElse[-A, +B] (f1: PartialFunction[A, B], f2: PartialFunction[A, B]) + private class OrElse[-A, +B] (f1: PartialFunction[A, B]^, f2: PartialFunction[A, B]^) extends scala.runtime.AbstractPartialFunction[A, B] with Serializable { def isDefinedAt(x: A) = f1.isDefinedAt(x) || f2.isDefinedAt(x) @@ -282,16 +284,16 @@ object PartialFunction { if (!fallbackOccurred(z)) z else f2.applyOrElse(x, default) } - override def orElse[A1 <: A, B1 >: B](that: PartialFunction[A1, B1]): OrElse[A1, B1] = + override def orElse[A1 <: A, B1 >: B](that: PartialFunction[A1, B1]^): OrElse[A1, B1]^{this, that} = new OrElse[A1, B1] (f1, f2 orElse that) - override def andThen[C](k: B => C): OrElse[A, C] = + override def andThen[C](k: B => C): OrElse[A, C]^{this, k} = new OrElse[A, C] (f1 andThen k, f2 andThen k) } /** Composite function produced by `PartialFunction#andThen` method */ - private class AndThen[-A, B, +C] (pf: PartialFunction[A, B], k: B => C) extends PartialFunction[A, C] with Serializable { + private class AndThen[-A, B, +C] (pf: PartialFunction[A, B]^, k: B => C) extends PartialFunction[A, C] with Serializable { def isDefinedAt(x: A) = pf.isDefinedAt(x) def apply(x: A): C = k(pf(x)) @@ -304,7 +306,7 @@ object PartialFunction { /** Composite function produced by `PartialFunction#andThen` method */ - private class Combined[-A, B, +C] (pf: PartialFunction[A, B], k: PartialFunction[B, C]) extends PartialFunction[A, C] with Serializable { + private class Combined[-A, B, +C] (pf: PartialFunction[A, B]^, k: PartialFunction[B, C]^) extends PartialFunction[A, C] with Serializable { def isDefinedAt(x: A): Boolean = { val b: B = pf.applyOrElse(x, checkFallback[B]) if (!fallbackOccurred(b)) k.isDefinedAt(b) else false @@ -339,11 +341,11 @@ object PartialFunction { * * Here `fallback_fn` is used as both unique marker object and special fallback function that returns it. */ - private[this] val fallback_fn: Any => Any = _ => fallback_fn + private[this] val fallback_fn: Any -> Any = _ => fallback_fn private def checkFallback[B] = fallback_fn.asInstanceOf[Any => B] private def fallbackOccurred[B](x: B) = fallback_fn eq x.asInstanceOf[AnyRef] - private class Lifted[-A, +B] (val pf: PartialFunction[A, B]) + private class Lifted[-A, +B] (val pf: PartialFunction[A, B]^) extends scala.runtime.AbstractFunction1[A, Option[B]] with Serializable { def apply(x: A): Option[B] = { @@ -362,9 +364,9 @@ object PartialFunction { override def lift = f } - private[scala] def unlifted[A, B](f: A => Option[B]): PartialFunction[A, B] = f match { - case lf: Lifted[A, B] => lf.pf - case ff => new Unlifted(ff) + private[scala] def unlifted[A, B](f: A => Option[B]): PartialFunction[A, B]^{f} = f match { + case lf: (Lifted[A, B]^{f}) => caps.unsafe.unsafeAssumePure(lf).pf // SAFETY: lf.pf captures at most pf + case _ => new Unlifted(f) } /** Converts an ordinary function to a partial function. Note that calling `isDefinedAt(x)` on @@ -372,16 +374,16 @@ object PartialFunction { * @param f an ordinary function * @return a partial function which delegates to the ordinary function `f` */ - def fromFunction[A, B](f: A => B): PartialFunction[A, B] = { case x => f(x) } + def fromFunction[A, B](f: A => B): PartialFunction[A, B]^{f} = { case x => f(x) } - private[this] val constFalse: Any => Boolean = { _ => false} + private[this] val constFalse: Any -> Boolean = { _ => false} private[this] val empty_pf: PartialFunction[Any, Nothing] = new PartialFunction[Any, Nothing] with Serializable { def isDefinedAt(x: Any) = false def apply(x: Any) = throw new MatchError(x) - override def orElse[A1, B1](that: PartialFunction[A1, B1]) = that - override def andThen[C](k: Nothing => C): PartialFunction[Any, Nothing] = this - override val lift: Any => None.type = (x: Any) => None + override def orElse[A1, B1](that: PartialFunction[A1, B1]^) = that + override def andThen[C](k: PartialFunction[Nothing, C]^): PartialFunction[Any, C]^{k} = this + override val lift: Any -> None.type = (x: Any) => None override def runWith[U](action: Nothing => U) = constFalse } @@ -399,7 +401,7 @@ object PartialFunction { * @param pf the partial function * @return true, iff `x` is in the domain of `pf` and `pf(x) == true`. */ - def cond[A](x: A)(pf: PartialFunction[A, Boolean]): Boolean = pf.applyOrElse(x, constFalse) + def cond[A](x: A)(pf: PartialFunction[A, Boolean]^): Boolean = pf.applyOrElse(x, constFalse) /** Apply the function to the given value if defined, and return the result * in a `Some`; otherwise, return `None`. @@ -408,7 +410,7 @@ object PartialFunction { * @param pf the PartialFunction[T, U] * @return `Some(pf(x))` if `pf isDefinedAt x`, `None` otherwise. */ - def condOpt[A, B](x: A)(pf: PartialFunction[A, B]): Option[B] = { + def condOpt[A, B](x: A)(pf: PartialFunction[A, B]^): Option[B] = { val z = pf.applyOrElse(x, checkFallback[B]) if (!fallbackOccurred(z)) Some(z) else None } diff --git a/library/src/scala/runtime/AbstractPartialFunction.scala b/library/src/scala/runtime/AbstractPartialFunction.scala index 895dac79058b..a3506e45c114 100644 --- a/library/src/scala/runtime/AbstractPartialFunction.scala +++ b/library/src/scala/runtime/AbstractPartialFunction.scala @@ -15,6 +15,8 @@ package runtime import scala.language.`2.13` +import language.experimental.captureChecking + /** `AbstractPartialFunction` reformulates all operations of its supertrait `PartialFunction` * in terms of `isDefinedAt` and `applyOrElse`. * @@ -26,7 +28,7 @@ import scala.language.`2.13` * * This trait is used as a basis for implementation of all partial function literals. */ -abstract class AbstractPartialFunction[@specialized(Specializable.Arg) -T1, @specialized(Specializable.Return) +R] extends Function1[T1, R] with PartialFunction[T1, R] { self => +abstract class AbstractPartialFunction[@specialized(Specializable.Arg) -T1, @specialized(Specializable.Return) +R] extends Function1[T1, R] with PartialFunction[T1, R] { // this method must be overridden for better performance, // for backwards compatibility, fall back to the one inherited from PartialFunction // this assumes the old-school partial functions override the apply method, though diff --git a/tests/neg-custom-args/captures/partial-function.scala b/tests/neg-custom-args/captures/partial-function.scala new file mode 100644 index 000000000000..f746f1697536 --- /dev/null +++ b/tests/neg-custom-args/captures/partial-function.scala @@ -0,0 +1,6 @@ +class A extends caps.SharedCapability: + def even(x: Int) = x % 2 == 0 + +def f(a: A): List[Int] -> List[Int] = + (xs: List[Int]) => xs.collect: + case x if a.even(x) => 1 diff --git a/tests/pos-custom-args/captures/partial-function.scala b/tests/pos-custom-args/captures/partial-function.scala new file mode 100644 index 000000000000..e8e99148fcda --- /dev/null +++ b/tests/pos-custom-args/captures/partial-function.scala @@ -0,0 +1,7 @@ +class A extends caps.SharedCapability: + def even(x: Int) = x % 2 == 0 + +def f(a: A): List[Int] ->{a} List[Int] = + (xs: List[Int]) => xs.collect: + case x if a.even(x) => 1 + From ce268f30da012acdb82f5f5ec5deffd1ab0726ff Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Tue, 12 Aug 2025 01:45:03 +0200 Subject: [PATCH 042/101] Add captures to ArrayOps --- library/src/scala/IArray.scala | 6 +-- library/src/scala/collection/ArrayOps.scala | 46 +++++++++++---------- 2 files changed, 27 insertions(+), 25 deletions(-) diff --git a/library/src/scala/IArray.scala b/library/src/scala/IArray.scala index 12eac3985626..64df0c3426f5 100644 --- a/library/src/scala/IArray.scala +++ b/library/src/scala/IArray.scala @@ -272,9 +272,9 @@ object IArray: def concat[U >: T: ClassTag](suffix: IArray[U]): IArray[U] = genericArrayOps(arr).concat(suffix) def concat[U >: T: ClassTag](suffix: IterableOnce[U]^): IArray[U] = genericArrayOps(arr).concat(suffix) def diff[U >: T](that: IArray[U]): IArray[T] = genericArrayOps(arr).diff(that.toSeq) - def diff[U >: T](that: Seq[U]^): IArray[T] = genericArrayOps(arr).diff(that) + def diff[U >: T](that: Seq[U]): IArray[T] = genericArrayOps(arr).diff(that) def distinct: IArray[T] = genericArrayOps(arr).distinct - def distinctBy[U](f: T => U): IArray[T] = genericArrayOps(arr).distinctBy(f) + def distinctBy[U](f: T -> U): IArray[T] = genericArrayOps(arr).distinctBy(f) def startsWith[U >: T](that: IArray[U]): Boolean = genericArrayOps(arr).startsWith(that, 0) def startsWith[U >: T](that: IArray[U], offset: Int): Boolean = genericArrayOps(arr).startsWith(that, offset) def startsWith[U >: T](that: IterableOnce[U]^): Boolean = genericArrayOps(arr).startsWith(that, 0) @@ -286,7 +286,7 @@ object IArray: def grouped(size: Int): Iterator[IArray[T]] = genericArrayOps(arr).grouped(size) def inits: Iterator[IArray[T]] = genericArrayOps(arr).inits def intersect[U >: T](that: IArray[U]): IArray[T] = genericArrayOps(arr).intersect(that) - def intersect[U >: T](that: Seq[U]^): IArray[T] = genericArrayOps(arr).intersect(that) + def intersect[U >: T](that: Seq[U]): IArray[T] = genericArrayOps(arr).intersect(that) def lazyZip[U](that: IArray[U]): LazyZip2[T, U, IArray[T]] = genericArrayOps(arr).lazyZip[U](that).asInstanceOf[LazyZip2[T, U, IArray[T]]] def lazyZip[U](that: Iterable[U]^): LazyZip2[T, U, IArray[T]] = genericArrayOps(arr).lazyZip[U](that).asInstanceOf[LazyZip2[T, U, IArray[T]]] def lengthCompare(len: Int): Int = genericArrayOps(arr).lengthCompare(len) diff --git a/library/src/scala/collection/ArrayOps.scala b/library/src/scala/collection/ArrayOps.scala index c7f5ff67bd73..2ed401423857 100644 --- a/library/src/scala/collection/ArrayOps.scala +++ b/library/src/scala/collection/ArrayOps.scala @@ -17,6 +17,8 @@ import scala.language.`2.13` import java.lang.Math.{max, min} import java.util.Arrays +import language.experimental.captureChecking + import scala.Predef.{ // unimport all array-related implicit conversions to avoid triggering them accidentally genericArrayOps => _, booleanArrayOps => _, @@ -103,7 +105,7 @@ object ArrayOps { * @return a new array resulting from applying the given collection-valued function * `f` to each element of this array and concatenating the results. */ - def flatMap[B: ClassTag](f: A => IterableOnce[B]): Array[B] = { + def flatMap[B: ClassTag](f: A => IterableOnce[B]^): Array[B] = { val b = ArrayBuilder.make[B] var i = 0 while(i < xs.length) { @@ -118,7 +120,7 @@ object ArrayOps { flatMap[B](x => asIterable(f(x))) /** Creates a new non-strict filter which combines this filter with the given predicate. */ - def withFilter(q: A => Boolean): WithFilter[A] = new WithFilter[A](a => p(a) && q(a), xs) + def withFilter(q: A => Boolean): WithFilter[A]^{this, q} = new WithFilter[A](a => p(a) && q(a), xs) } @SerialVersionUID(3L) @@ -179,7 +181,7 @@ object ArrayOps { private final val MaxStableSortLength = 300 /** Avoid an allocation in [[collect]]. */ - private val fallback: Any => Any = _ => fallback + private val fallback: Any -> Any = _ => fallback } /** This class serves as a wrapper for `Array`s with many of the operations found in @@ -587,7 +589,7 @@ final class ArrayOps[A](private val xs: Array[A]) extends AnyVal { * @return an array consisting of the elements of this array * sorted according to the ordering `ord`. */ - def sorted[B >: A](implicit ord: Ordering[B]): Array[A] = { + def sorted[B >: A](implicit ord: Ordering[B]^): Array[A] = { val len = xs.length def boxed = if(len < ArrayOps.MaxStableSortLength) { val a = xs.clone() @@ -665,7 +667,7 @@ final class ArrayOps[A](private val xs: Array[A]) extends AnyVal { * All these operations apply to those elements of this array * which satisfy the predicate `p`. */ - def withFilter(p: A => Boolean): ArrayOps.WithFilter[A] = new ArrayOps.WithFilter[A](p, xs) + def withFilter(p: A => Boolean): ArrayOps.WithFilter[A]^{p} = new ArrayOps.WithFilter[A](p, xs) /** Finds index of first occurrence of some value in this array after or at some start index. * @@ -965,7 +967,7 @@ final class ArrayOps[A](private val xs: Array[A]) extends AnyVal { * @return a new array resulting from applying the given collection-valued function * `f` to each element of this array and concatenating the results. */ - def flatMap[B : ClassTag](f: A => IterableOnce[B]): Array[B] = { + def flatMap[B : ClassTag](f: A => IterableOnce[B]^): Array[B] = { val b = ArrayBuilder.make[B] var i = 0 while(i < xs.length) { @@ -975,7 +977,7 @@ final class ArrayOps[A](private val xs: Array[A]) extends AnyVal { b.result() } - def flatMap[BS, B](f: A => BS)(implicit asIterable: BS => Iterable[B], m: ClassTag[B]): Array[B] = + def flatMap[BS, B](f: A => BS)(implicit asIterable: BS => Iterable[B]^, m: ClassTag[B]): Array[B] = flatMap[B](x => asIterable(f(x))) /** Flattens a two-dimensional array by concatenating all its rows @@ -985,7 +987,7 @@ final class ArrayOps[A](private val xs: Array[A]) extends AnyVal { * @param asIterable A function that converts elements of this array to rows - Iterables of type `B`. * @return An array obtained by concatenating rows of this array. */ - def flatten[B](implicit asIterable: A => IterableOnce[B], m: ClassTag[B]): Array[B] = { + def flatten[B](implicit asIterable: A => IterableOnce[B]^, m: ClassTag[B]): Array[B] = { val b = ArrayBuilder.make[B] val len = xs.length var size = 0 @@ -1018,7 +1020,7 @@ final class ArrayOps[A](private val xs: Array[A]) extends AnyVal { * `pf` to each element on which it is defined and collecting the results. * The order of the elements is preserved. */ - def collect[B: ClassTag](pf: PartialFunction[A, B]): Array[B] = { + def collect[B: ClassTag](pf: PartialFunction[A, B]^): Array[B] = { val fallback: Any => Any = ArrayOps.fallback val b = ArrayBuilder.make[B] var i = 0 @@ -1032,7 +1034,7 @@ final class ArrayOps[A](private val xs: Array[A]) extends AnyVal { /** Finds the first element of the array for which the given partial function is defined, and applies the * partial function to it. */ - def collectFirst[B](@deprecatedName("f","2.13.9") pf: PartialFunction[A, B]): Option[B] = { + def collectFirst[B](@deprecatedName("f","2.13.9") pf: PartialFunction[A, B]^): Option[B] = { val fallback: Any => Any = ArrayOps.fallback var i = 0 while (i < xs.length) { @@ -1052,7 +1054,7 @@ final class ArrayOps[A](private val xs: Array[A]) extends AnyVal { * @return a new array containing pairs consisting of corresponding elements of this array and `that`. * The length of the returned array is the minimum of the lengths of this array and `that`. */ - def zip[B](that: IterableOnce[B]): Array[(A, B)] = { + def zip[B](that: IterableOnce[B]^): Array[(A, B)] = { val b = new ArrayBuilder.ofRef[(A, B)]() val k = that.knownSize b.sizeHint(if(k >= 0) min(k, xs.length) else xs.length) @@ -1082,7 +1084,7 @@ final class ArrayOps[A](private val xs: Array[A]) extends AnyVal { * @return a decorator `LazyZip2` that allows strict operations to be performed on the lazily evaluated pairs * or chained calls to `lazyZip`. Implicit conversion to `Iterable[(A, B)]` is also supported. */ - def lazyZip[B](that: Iterable[B]): LazyZip2[A, B, Array[A]] = new LazyZip2(xs, immutable.ArraySeq.unsafeWrapArray(xs), that) + def lazyZip[B](that: Iterable[B]^): LazyZip2[A, B, Array[A]]^{that} = new LazyZip2(xs, immutable.ArraySeq.unsafeWrapArray(xs), that) /** Returns an array formed from this array and another iterable collection * by combining corresponding elements in pairs. @@ -1097,7 +1099,7 @@ final class ArrayOps[A](private val xs: Array[A]) extends AnyVal { * If this array is shorter than `that`, `thisElem` values are used to pad the result. * If `that` is shorter than this array, `thatElem` values are used to pad the result. */ - def zipAll[A1 >: A, B](that: Iterable[B], thisElem: A1, thatElem: B): Array[(A1, B)] = { + def zipAll[A1 >: A, B](that: Iterable[B]^, thisElem: A1, thatElem: B): Array[(A1, B)] = { val b = new ArrayBuilder.ofRef[(A1, B)]() val k = that.knownSize b.sizeHint(max(k, xs.length)) @@ -1153,7 +1155,7 @@ final class ArrayOps[A](private val xs: Array[A]) extends AnyVal { @`inline` final def +: [B >: A : ClassTag](x: B): Array[B] = prepended(x) /** A copy of this array with all elements of a collection prepended. */ - def prependedAll[B >: A : ClassTag](prefix: IterableOnce[B]): Array[B] = { + def prependedAll[B >: A : ClassTag](prefix: IterableOnce[B]^): Array[B] = { val b = ArrayBuilder.make[B] val k = prefix.knownSize if(k >= 0) b.sizeHint(k + xs.length) @@ -1175,7 +1177,7 @@ final class ArrayOps[A](private val xs: Array[A]) extends AnyVal { @`inline` final def ++: [B >: A : ClassTag](prefix: Array[_ <: B]): Array[B] = prependedAll(prefix) /** A copy of this array with all elements of a collection appended. */ - def appendedAll[B >: A : ClassTag](suffix: IterableOnce[B]): Array[B] = { + def appendedAll[B >: A : ClassTag](suffix: IterableOnce[B]^): Array[B] = { val b = ArrayBuilder.make[B] b.sizeHint(suffix, delta = xs.length) b.addAll(xs) @@ -1190,15 +1192,15 @@ final class ArrayOps[A](private val xs: Array[A]) extends AnyVal { dest } - @`inline` final def :++ [B >: A : ClassTag](suffix: IterableOnce[B]): Array[B] = appendedAll(suffix) + @`inline` final def :++ [B >: A : ClassTag](suffix: IterableOnce[B]^): Array[B] = appendedAll(suffix) @`inline` final def :++ [B >: A : ClassTag](suffix: Array[_ <: B]): Array[B] = appendedAll(suffix) - @`inline` final def concat[B >: A : ClassTag](suffix: IterableOnce[B]): Array[B] = appendedAll(suffix) + @`inline` final def concat[B >: A : ClassTag](suffix: IterableOnce[B]^): Array[B] = appendedAll(suffix) @`inline` final def concat[B >: A : ClassTag](suffix: Array[_ <: B]): Array[B] = appendedAll(suffix) - @`inline` final def ++[B >: A : ClassTag](xs: IterableOnce[B]): Array[B] = appendedAll(xs) + @`inline` final def ++[B >: A : ClassTag](xs: IterableOnce[B]^): Array[B] = appendedAll(xs) @`inline` final def ++[B >: A : ClassTag](xs: Array[_ <: B]): Array[B] = appendedAll(xs) @@ -1219,7 +1221,7 @@ final class ArrayOps[A](private val xs: Array[A]) extends AnyVal { * @param other The patch values * @param replaced The number of values in the original array that are replaced by the patch. */ - def patch[B >: A : ClassTag](from: Int, other: IterableOnce[B], replaced: Int): Array[B] = { + def patch[B >: A : ClassTag](from: Int, other: IterableOnce[B]^, replaced: Int): Array[B] = { val b = ArrayBuilder.make[B] val k = other.knownSize val r = if(replaced < 0) 0 else replaced @@ -1347,7 +1349,7 @@ final class ArrayOps[A](private val xs: Array[A]) extends AnyVal { * @tparam B the type of the elements after being transformed by `f` * @return a new array consisting of all the elements of this array without duplicates. */ - def distinctBy[B](f: A => B): Array[A] = + def distinctBy[B](f: A -> B): Array[A] = ArrayBuilder.make[A].addAll(iterator.distinctBy(f)).result() /** A copy of this array with an element value appended until a given target length is reached. @@ -1655,7 +1657,7 @@ final class ArrayOps[A](private val xs: Array[A]) extends AnyVal { * @return `true` if the sequence `that` is contained in this array at * index `offset`, otherwise `false`. */ - def startsWith[B >: A](that: IterableOnce[B], offset: Int = 0): Boolean = mutable.ArraySeq.make(xs).startsWith(that, offset) + def startsWith[B >: A](that: IterableOnce[B]^, offset: Int = 0): Boolean = mutable.ArraySeq.make(xs).startsWith(that, offset) // we have another overload here, so we need to duplicate this method /** Tests whether this array ends with the given sequence. @@ -1663,5 +1665,5 @@ final class ArrayOps[A](private val xs: Array[A]) extends AnyVal { * @param that the sequence to test * @return `true` if this array has `that` as a suffix, `false` otherwise. */ - def endsWith[B >: A](that: Iterable[B]): Boolean = mutable.ArraySeq.make(xs).endsWith(that) + def endsWith[B >: A](that: Iterable[B]^): Boolean = mutable.ArraySeq.make(xs).endsWith(that) } From 300ee397019c7c071239c162279d84b44363a71f Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Tue, 12 Aug 2025 02:03:03 +0200 Subject: [PATCH 043/101] Add captures to BitSet --- library/src/scala/collection/BitSet.scala | 10 ++++++---- .../StrictOptimizedIterableOps.scala | 1 + .../scala/collection/immutable/BitSet.scala | 14 +++++++------ .../src/scala/collection/mutable/BitSet.scala | 20 ++++++++++--------- 4 files changed, 26 insertions(+), 19 deletions(-) diff --git a/library/src/scala/collection/BitSet.scala b/library/src/scala/collection/BitSet.scala index 98beab6ae521..89e5f6358870 100644 --- a/library/src/scala/collection/BitSet.scala +++ b/library/src/scala/collection/BitSet.scala @@ -14,6 +14,8 @@ package scala package collection import scala.language.`2.13` +import language.experimental.captureChecking + import java.io.{ObjectInputStream, ObjectOutputStream} import scala.annotation.nowarn @@ -33,8 +35,8 @@ import scala.collection.mutable.Builder * @define coll bitset * @define Coll `BitSet` */ -trait BitSet extends SortedSet[Int] with BitSetOps[BitSet] { - override protected def fromSpecific(coll: IterableOnce[Int]): BitSet = bitSetFactory.fromSpecific(coll) +trait BitSet extends SortedSet[Int] with BitSetOps[BitSet] { self: BitSet => + override protected def fromSpecific(coll: IterableOnce[Int]^): BitSet = bitSetFactory.fromSpecific(coll) override protected def newSpecificBuilder: Builder[Int, BitSet] = bitSetFactory.newBuilder override def empty: BitSet = bitSetFactory.empty @nowarn("""cat=deprecation&origin=scala\.collection\.Iterable\.stringPrefix""") @@ -298,9 +300,9 @@ transparent trait BitSetOps[+C <: BitSet with BitSetOps[C]] */ def map(f: Int => Int): C = fromSpecific(new View.Map(this, f)) - def flatMap(f: Int => IterableOnce[Int]): C = fromSpecific(new View.FlatMap(this, f)) + def flatMap(f: Int => IterableOnce[Int]^): C = fromSpecific(new View.FlatMap(this, f)) - def collect(pf: PartialFunction[Int, Int]): C = fromSpecific(super[SortedSetOps].collect(pf)) + def collect(pf: PartialFunction[Int, Int]^): C = fromSpecific(super[SortedSetOps].collect(pf)) override def partition(p: Int => Boolean): (C, C) = { val left = filter(p) diff --git a/library/src/scala/collection/StrictOptimizedIterableOps.scala b/library/src/scala/collection/StrictOptimizedIterableOps.scala index 81c8a79aedf4..7ed82bf7ae3d 100644 --- a/library/src/scala/collection/StrictOptimizedIterableOps.scala +++ b/library/src/scala/collection/StrictOptimizedIterableOps.scala @@ -14,6 +14,7 @@ package scala package collection import scala.language.`2.13` + import scala.annotation.nowarn import scala.annotation.unchecked.uncheckedVariance import scala.runtime.Statics diff --git a/library/src/scala/collection/immutable/BitSet.scala b/library/src/scala/collection/immutable/BitSet.scala index c454727fe4b9..ed18ec6c1663 100644 --- a/library/src/scala/collection/immutable/BitSet.scala +++ b/library/src/scala/collection/immutable/BitSet.scala @@ -15,6 +15,8 @@ package collection package immutable import scala.language.`2.13` +import language.experimental.captureChecking + import BitSetOps.{LogWL, updateArray} import mutable.Builder import scala.annotation.{implicitNotFound, nowarn} @@ -38,7 +40,7 @@ sealed abstract class BitSet override def unsorted: Set[Int] = this - override protected def fromSpecific(coll: IterableOnce[Int]): BitSet = bitSetFactory.fromSpecific(coll) + override protected def fromSpecific(coll: IterableOnce[Int]^): BitSet = bitSetFactory.fromSpecific(coll) override protected def newSpecificBuilder: Builder[Int, BitSet] = bitSetFactory.newBuilder override def empty: BitSet = bitSetFactory.empty @@ -71,12 +73,12 @@ sealed abstract class BitSet override def map[B](f: Int => B)(implicit @implicitNotFound(collection.BitSet.ordMsg) ev: Ordering[B]): SortedSet[B] = super[StrictOptimizedSortedSetOps].map(f) - override def flatMap(f: Int => IterableOnce[Int]): BitSet = strictOptimizedFlatMap(newSpecificBuilder, f) - override def flatMap[B](f: Int => IterableOnce[B])(implicit @implicitNotFound(collection.BitSet.ordMsg) ev: Ordering[B]): SortedSet[B] = + override def flatMap(f: Int => IterableOnce[Int]^): BitSet = strictOptimizedFlatMap(newSpecificBuilder, f) + override def flatMap[B](f: Int => IterableOnce[B]^)(implicit @implicitNotFound(collection.BitSet.ordMsg) ev: Ordering[B]): SortedSet[B] = super[StrictOptimizedSortedSetOps].flatMap(f) - override def collect(pf: PartialFunction[Int, Int]): BitSet = strictOptimizedCollect(newSpecificBuilder, pf) - override def collect[B](pf: scala.PartialFunction[Int, B])(implicit @implicitNotFound(collection.BitSet.ordMsg) ev: Ordering[B]): SortedSet[B] = + override def collect(pf: PartialFunction[Int, Int]^): BitSet = strictOptimizedCollect(newSpecificBuilder, pf) + override def collect[B](pf: scala.PartialFunction[Int, B]^)(implicit @implicitNotFound(collection.BitSet.ordMsg) ev: Ordering[B]): SortedSet[B] = super[StrictOptimizedSortedSetOps].collect(pf) // necessary for disambiguation @@ -95,7 +97,7 @@ sealed abstract class BitSet @SerialVersionUID(3L) object BitSet extends SpecificIterableFactory[Int, BitSet] { - def fromSpecific(it: scala.collection.IterableOnce[Int]): BitSet = + def fromSpecific(it: scala.collection.IterableOnce[Int]^): BitSet = it match { case bs: BitSet => bs case _ => (newBuilder ++= it).result() diff --git a/library/src/scala/collection/mutable/BitSet.scala b/library/src/scala/collection/mutable/BitSet.scala index 39124537758f..03f81bf64907 100644 --- a/library/src/scala/collection/mutable/BitSet.scala +++ b/library/src/scala/collection/mutable/BitSet.scala @@ -15,6 +15,8 @@ package collection package mutable import scala.language.`2.13` +import language.experimental.captureChecking + import scala.collection.immutable.Range import BitSetOps.{LogWL, MaxSize} import scala.annotation.implicitNotFound @@ -48,7 +50,7 @@ class BitSet(protected[collection] final var elems: Array[Long]) def this() = this(0) - override protected def fromSpecific(coll: IterableOnce[Int]): BitSet = bitSetFactory.fromSpecific(coll) + override protected def fromSpecific(coll: IterableOnce[Int]^): BitSet = bitSetFactory.fromSpecific(coll) override protected def newSpecificBuilder: Builder[Int, BitSet] = bitSetFactory.newBuilder override def empty: BitSet = bitSetFactory.empty @@ -176,19 +178,19 @@ class BitSet(protected[collection] final var elems: Array[Long]) override def map[B](f: Int => B)(implicit @implicitNotFound(collection.BitSet.ordMsg) ev: Ordering[B]): SortedSet[B] = super[StrictOptimizedSortedSetOps].map(f) - override def flatMap(f: Int => IterableOnce[Int]): BitSet = strictOptimizedFlatMap(newSpecificBuilder, f) - override def flatMap[B](f: Int => IterableOnce[B])(implicit @implicitNotFound(collection.BitSet.ordMsg) ev: Ordering[B]): SortedSet[B] = + override def flatMap(f: Int => IterableOnce[Int]^): BitSet = strictOptimizedFlatMap(newSpecificBuilder, f) + override def flatMap[B](f: Int => IterableOnce[B]^)(implicit @implicitNotFound(collection.BitSet.ordMsg) ev: Ordering[B]): SortedSet[B] = super[StrictOptimizedSortedSetOps].flatMap(f) - override def collect(pf: PartialFunction[Int, Int]): BitSet = strictOptimizedCollect(newSpecificBuilder, pf) - override def collect[B](pf: scala.PartialFunction[Int, B])(implicit @implicitNotFound(collection.BitSet.ordMsg) ev: Ordering[B]): SortedSet[B] = + override def collect(pf: PartialFunction[Int, Int]^): BitSet = strictOptimizedCollect(newSpecificBuilder, pf) + override def collect[B](pf: scala.PartialFunction[Int, B]^)(implicit @implicitNotFound(collection.BitSet.ordMsg) ev: Ordering[B]): SortedSet[B] = super[StrictOptimizedSortedSetOps].collect(pf) // necessary for disambiguation - override def zip[B](that: IterableOnce[B])(implicit @implicitNotFound(collection.BitSet.zipOrdMsg) ev: Ordering[(Int, B)]): SortedSet[(Int, B)] = + override def zip[B](that: IterableOnce[B]^)(implicit @implicitNotFound(collection.BitSet.zipOrdMsg) ev: Ordering[(Int, B)]): SortedSet[(Int, B)] = super.zip(that) - override def addAll(xs: IterableOnce[Int]): this.type = xs match { + override def addAll(xs: IterableOnce[Int]^): this.type = xs match { case bs: collection.BitSet => this |= bs case range: Range => @@ -261,7 +263,7 @@ class BitSet(protected[collection] final var elems: Array[Long]) super.subsetOf(other) } - override def subtractAll(xs: IterableOnce[Int]): this.type = xs match { + override def subtractAll(xs: IterableOnce[Int]^): this.type = xs match { case bs: collection.BitSet => this &~= bs case other => super.subtractAll(other) } @@ -361,7 +363,7 @@ class BitSet(protected[collection] final var elems: Array[Long]) @SerialVersionUID(3L) object BitSet extends SpecificIterableFactory[Int, BitSet] { - def fromSpecific(it: scala.collection.IterableOnce[Int]): BitSet = Growable.from(empty, it) + def fromSpecific(it: scala.collection.IterableOnce[Int]^): BitSet = Growable.from(empty, it) def empty: BitSet = new BitSet() From 426b0c7c953dd0da4d43b8b17d71734b0c8cca03 Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Tue, 12 Aug 2025 02:04:28 +0200 Subject: [PATCH 044/101] Add capture checking to BufferedIterator --- library/src/scala/collection/BufferedIterator.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/library/src/scala/collection/BufferedIterator.scala b/library/src/scala/collection/BufferedIterator.scala index e933ffcd4c16..4076b9662940 100644 --- a/library/src/scala/collection/BufferedIterator.scala +++ b/library/src/scala/collection/BufferedIterator.scala @@ -13,6 +13,7 @@ package scala.collection import scala.language.`2.13` +import language.experimental.captureChecking /** Buffered iterators are iterators which provide a method `head` * that inspects the next element without discarding it. From 498f156155df75ad7945be00cf5f44e085205816 Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Tue, 12 Aug 2025 02:15:22 +0200 Subject: [PATCH 045/101] Add capture checking to BuildFrom --- .../scala/collection/BufferedIterator.scala | 1 - library/src/scala/collection/BuildFrom.scala | 28 ++++++++++--------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/library/src/scala/collection/BufferedIterator.scala b/library/src/scala/collection/BufferedIterator.scala index 4076b9662940..e933ffcd4c16 100644 --- a/library/src/scala/collection/BufferedIterator.scala +++ b/library/src/scala/collection/BufferedIterator.scala @@ -13,7 +13,6 @@ package scala.collection import scala.language.`2.13` -import language.experimental.captureChecking /** Buffered iterators are iterators which provide a method `head` * that inspects the next element without discarding it. diff --git a/library/src/scala/collection/BuildFrom.scala b/library/src/scala/collection/BuildFrom.scala index 1a623228db55..1d7d7f722a32 100644 --- a/library/src/scala/collection/BuildFrom.scala +++ b/library/src/scala/collection/BuildFrom.scala @@ -13,6 +13,8 @@ package scala.collection import scala.language.`2.13` +import language.experimental.captureChecking + import scala.annotation.implicitNotFound import scala.collection.mutable.Builder import scala.collection.immutable.WrappedString @@ -27,7 +29,7 @@ import scala.reflect.ClassTag */ @implicitNotFound(msg = "Cannot construct a collection of type ${C} with elements of type ${A} based on a collection of type ${From}.") trait BuildFrom[-From, -A, +C] extends Any { self => - def fromSpecific(from: From)(it: IterableOnce[A]): C + def fromSpecific(from: From)(it: IterableOnce[A]^): C^{it} /** Get a Builder for the collection. For non-strict collection types this will use an intermediate buffer. * Building collections with `fromSpecific` is preferred because it can be lazy for lazy collections. */ @@ -38,7 +40,7 @@ trait BuildFrom[-From, -A, +C] extends Any { self => /** Partially apply a BuildFrom to a Factory */ def toFactory(from: From): Factory[A, C] = new Factory[A, C] { - def fromSpecific(it: IterableOnce[A]): C = self.fromSpecific(from)(it) + def fromSpecific(it: IterableOnce[A]^): C^{it} = self.fromSpecific(from)(it) def newBuilder: Builder[A, C] = self.newBuilder(from) } } @@ -49,42 +51,42 @@ object BuildFrom extends BuildFromLowPriority1 { implicit def buildFromMapOps[CC[X, Y] <: Map[X, Y] with MapOps[X, Y, CC, _], K0, V0, K, V]: BuildFrom[CC[K0, V0] with Map[K0, V0], (K, V), CC[K, V] with Map[K, V]] = new BuildFrom[CC[K0, V0], (K, V), CC[K, V]] { //TODO: Reuse a prototype instance def newBuilder(from: CC[K0, V0]): Builder[(K, V), CC[K, V]] = (from: MapOps[K0, V0, CC, _]).mapFactory.newBuilder[K, V] - def fromSpecific(from: CC[K0, V0])(it: IterableOnce[(K, V)]): CC[K, V] = (from: MapOps[K0, V0, CC, _]).mapFactory.from(it) + def fromSpecific(from: CC[K0, V0])(it: IterableOnce[(K, V)]^): CC[K, V] = (from: MapOps[K0, V0, CC, _]).mapFactory.from(it) } /** Build the source collection type from a SortedMapOps */ implicit def buildFromSortedMapOps[CC[X, Y] <: SortedMap[X, Y] with SortedMapOps[X, Y, CC, _], K0, V0, K : Ordering, V]: BuildFrom[CC[K0, V0] with SortedMap[K0, V0], (K, V), CC[K, V] with SortedMap[K, V]] = new BuildFrom[CC[K0, V0], (K, V), CC[K, V]] { def newBuilder(from: CC[K0, V0]): Builder[(K, V), CC[K, V]] = (from: SortedMapOps[K0, V0, CC, _]).sortedMapFactory.newBuilder[K, V] - def fromSpecific(from: CC[K0, V0])(it: IterableOnce[(K, V)]): CC[K, V] = (from: SortedMapOps[K0, V0, CC, _]).sortedMapFactory.from(it) + def fromSpecific(from: CC[K0, V0])(it: IterableOnce[(K, V)]^): CC[K, V] = (from: SortedMapOps[K0, V0, CC, _]).sortedMapFactory.from(it) } implicit def buildFromBitSet[C <: BitSet with BitSetOps[C]]: BuildFrom[C, Int, C] = new BuildFrom[C, Int, C] { - def fromSpecific(from: C)(it: IterableOnce[Int]): C = from.bitSetFactory.fromSpecific(it) + def fromSpecific(from: C)(it: IterableOnce[Int]^): C = from.bitSetFactory.fromSpecific(it) def newBuilder(from: C): Builder[Int, C] = from.bitSetFactory.newBuilder } implicit val buildFromString: BuildFrom[String, Char, String] = new BuildFrom[String, Char, String] { - def fromSpecific(from: String)(it: IterableOnce[Char]): String = Factory.stringFactory.fromSpecific(it) + def fromSpecific(from: String)(it: IterableOnce[Char]^): String = Factory.stringFactory.fromSpecific(it) def newBuilder(from: String): Builder[Char, String] = Factory.stringFactory.newBuilder } implicit val buildFromWrappedString: BuildFrom[WrappedString, Char, WrappedString] = new BuildFrom[WrappedString, Char, WrappedString] { - def fromSpecific(from: WrappedString)(it: IterableOnce[Char]): WrappedString = WrappedString.fromSpecific(it) + def fromSpecific(from: WrappedString)(it: IterableOnce[Char]^): WrappedString = WrappedString.fromSpecific(it) def newBuilder(from: WrappedString): mutable.Builder[Char, WrappedString] = WrappedString.newBuilder } implicit def buildFromArray[A : ClassTag]: BuildFrom[Array[_], A, Array[A]] = new BuildFrom[Array[_], A, Array[A]] { - def fromSpecific(from: Array[_])(it: IterableOnce[A]): Array[A] = Factory.arrayFactory[A].fromSpecific(it) + def fromSpecific(from: Array[_])(it: IterableOnce[A]^): Array[A] = Factory.arrayFactory[A].fromSpecific(it) def newBuilder(from: Array[_]): Builder[A, Array[A]] = Factory.arrayFactory[A].newBuilder } implicit def buildFromView[A, B]: BuildFrom[View[A], B, View[B]] = new BuildFrom[View[A], B, View[B]] { - def fromSpecific(from: View[A])(it: IterableOnce[B]): View[B] = View.from(it) + def fromSpecific(from: View[A])(it: IterableOnce[B]^): View[B] = View.from(it) def newBuilder(from: View[A]): Builder[B, View[B]] = View.newBuilder } @@ -98,12 +100,12 @@ trait BuildFromLowPriority1 extends BuildFromLowPriority2 { // test in test/junit/scala/collection/BuildFromTest.scala and discussion in https://github.com/scala/scala/pull/10209 implicit def buildFromSortedSetOps[CC[X] <: SortedSet[X] with SortedSetOps[X, CC, _], A0, A : Ordering]: BuildFrom[CC[A0] with SortedSet[A0], A, CC[A] with SortedSet[A]] = new BuildFrom[CC[A0], A, CC[A]] { def newBuilder(from: CC[A0]): Builder[A, CC[A]] = (from: SortedSetOps[A0, CC, _]).sortedIterableFactory.newBuilder[A] - def fromSpecific(from: CC[A0])(it: IterableOnce[A]): CC[A] = (from: SortedSetOps[A0, CC, _]).sortedIterableFactory.from(it) + def fromSpecific(from: CC[A0])(it: IterableOnce[A]^): CC[A] = (from: SortedSetOps[A0, CC, _]).sortedIterableFactory.from(it) } implicit def fallbackStringCanBuildFrom[A]: BuildFrom[String, A, immutable.IndexedSeq[A]] = new BuildFrom[String, A, immutable.IndexedSeq[A]] { - def fromSpecific(from: String)(it: IterableOnce[A]): immutable.IndexedSeq[A] = immutable.IndexedSeq.from(it) + def fromSpecific(from: String)(it: IterableOnce[A]^): immutable.IndexedSeq[A] = immutable.IndexedSeq.from(it) def newBuilder(from: String): Builder[A, immutable.IndexedSeq[A]] = immutable.IndexedSeq.newBuilder[A] } } @@ -113,11 +115,11 @@ trait BuildFromLowPriority2 { implicit def buildFromIterableOps[CC[X] <: Iterable[X] with IterableOps[X, CC, _], A0, A]: BuildFrom[CC[A0], A, CC[A]] = new BuildFrom[CC[A0], A, CC[A]] { //TODO: Reuse a prototype instance def newBuilder(from: CC[A0]): Builder[A, CC[A]] = (from: IterableOps[A0, CC, _]).iterableFactory.newBuilder[A] - def fromSpecific(from: CC[A0])(it: IterableOnce[A]): CC[A] = (from: IterableOps[A0, CC, _]).iterableFactory.from(it) + def fromSpecific(from: CC[A0])(it: IterableOnce[A]^): CC[A] = (from: IterableOps[A0, CC, _]).iterableFactory.from(it) } implicit def buildFromIterator[A]: BuildFrom[Iterator[_], A, Iterator[A]] = new BuildFrom[Iterator[_], A, Iterator[A]] { def newBuilder(from: Iterator[_]): mutable.Builder[A, Iterator[A]] = Iterator.newBuilder - def fromSpecific(from: Iterator[_])(it: IterableOnce[A]): Iterator[A] = Iterator.from(it) + def fromSpecific(from: Iterator[_])(it: IterableOnce[A]^): Iterator[A] = Iterator.from(it) } } From c235ee236b1fbf41f1ad2c56fb9c9aed9b268594 Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Tue, 12 Aug 2025 02:36:57 +0200 Subject: [PATCH 046/101] First pass thru IterableOnce --- .../src/scala/collection/IterableOnce.scala | 72 ++++++++++--------- 1 file changed, 37 insertions(+), 35 deletions(-) diff --git a/library/src/scala/collection/IterableOnce.scala b/library/src/scala/collection/IterableOnce.scala index 44a5eaa1ca20..ce691827139d 100644 --- a/library/src/scala/collection/IterableOnce.scala +++ b/library/src/scala/collection/IterableOnce.scala @@ -14,6 +14,8 @@ package scala package collection import scala.language.`2.13` +import language.experimental.captureChecking + import scala.annotation.tailrec import scala.annotation.unchecked.uncheckedVariance import scala.collection.mutable.StringBuilder @@ -51,7 +53,7 @@ trait IterableOnce[+A] extends Any { * in its current state, but if it is an [[scala.collection.Iterable]], this method always returns a new * [[scala.collection.Iterator]]. */ - def iterator: Iterator[A] + def iterator: Iterator[A]^{this} /** Returns a [[scala.collection.Stepper]] for the elements of this collection. * @@ -73,9 +75,9 @@ trait IterableOnce[+A] extends Any { * allow creating parallel streams, whereas bare Steppers can be converted only to sequential * streams. */ - def stepper[S <: Stepper[_]](implicit shape: StepperShape[A, S]): S = { + def stepper[S <: Stepper[_]^{this}](implicit shape: StepperShape[A, S]): S = { import convert.impl._ - val s = shape.shape match { + val s: Any = shape.shape match { case StepperShape.IntShape => new IntIteratorStepper (iterator.asInstanceOf[Iterator[Int]]) case StepperShape.LongShape => new LongIteratorStepper (iterator.asInstanceOf[Iterator[Long]]) case StepperShape.DoubleShape => new DoubleIteratorStepper(iterator.asInstanceOf[Iterator[Double]]) @@ -92,7 +94,7 @@ trait IterableOnce[+A] extends Any { final class IterableOnceExtensionMethods[A](private val it: IterableOnce[A]) extends AnyVal { @deprecated("Use .iterator.withFilter(...) instead", "2.13.0") - def withFilter(f: A => Boolean): Iterator[A] = it.iterator.withFilter(f) + def withFilter(f: A => Boolean): Iterator[A]^{f} = it.iterator.withFilter(f) @deprecated("Use .iterator.reduceLeftOption(...) instead", "2.13.0") def reduceLeftOption(f: (A, A) => A): Option[A] = it.iterator.reduceLeftOption(f) @@ -110,7 +112,7 @@ final class IterableOnceExtensionMethods[A](private val it: IterableOnce[A]) ext def reduceRight(f: (A, A) => A): A = it.iterator.reduceRight(f) @deprecated("Use .iterator.maxBy(...) instead", "2.13.0") - def maxBy[B](f: A => B)(implicit cmp: Ordering[B]): A = it.iterator.maxBy(f) + def maxBy[B](f: A -> B)(implicit cmp: Ordering[B]): A = it.iterator.maxBy(f) @deprecated("Use .iterator.reduceLeft(...) instead", "2.13.0") def reduceLeft(f: (A, A) => A): A = it.iterator.reduceLeft(f) @@ -128,7 +130,7 @@ final class IterableOnceExtensionMethods[A](private val it: IterableOnce[A]) ext def reduceOption(f: (A, A) => A): Option[A] = it.iterator.reduceOption(f) @deprecated("Use .iterator.minBy(...) instead", "2.13.0") - def minBy[B](f: A => B)(implicit cmp: Ordering[B]): A = it.iterator.minBy(f) + def minBy[B](f: A -> B)(implicit cmp: Ordering[B]): A = it.iterator.minBy(f) @deprecated("Use .iterator.size instead", "2.13.0") def size: Int = it.iterator.size @@ -137,7 +139,7 @@ final class IterableOnceExtensionMethods[A](private val it: IterableOnce[A]) ext def forall(f: A => Boolean): Boolean = it.iterator.forall(f) @deprecated("Use .iterator.collectFirst(...) instead", "2.13.0") - def collectFirst[B](f: PartialFunction[A, B]): Option[B] = it.iterator.collectFirst(f) + def collectFirst[B](f: PartialFunction[A, B]^): Option[B] = it.iterator.collectFirst(f) @deprecated("Use .iterator.filter(...) instead", "2.13.0") def filter(f: A => Boolean): Iterator[A] = it.iterator.filter(f) @@ -252,7 +254,7 @@ final class IterableOnceExtensionMethods[A](private val it: IterableOnce[A]) ext } @deprecated("Use .iterator.flatMap instead or consider requiring an Iterable", "2.13.0") - def flatMap[B](f: A => IterableOnce[B]): IterableOnce[B] = it match { + def flatMap[B](f: A => IterableOnce[B]^): IterableOnce[B] = it match { case it: Iterable[A] => it.flatMap(f) case _ => it.iterator.flatMap(f) } @@ -277,7 +279,7 @@ object IterableOnce { math.max(math.min(math.min(len, srcLen), destLen - start), 0) /** Calls `copyToArray` on the given collection, regardless of whether or not it is an `Iterable`. */ - @inline private[collection] def copyElemsToArray[A, B >: A](elems: IterableOnce[A], + @inline private[collection] def copyElemsToArray[A, B >: A](elems: IterableOnce[A]^, xs: Array[B], start: Int = 0, len: Int = Int.MaxValue): Int = @@ -321,7 +323,7 @@ object IterableOnce { * @define exactlyOnce * Each element appears exactly once in the computation. */ -transparent trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOnce[A] => +transparent trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOnce[A]^ => /////////////////////////////////////////////////////////////// Abstract methods that must be implemented /** Produces a $coll containing cumulative results of applying the @@ -335,7 +337,7 @@ transparent trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOn * @param op the binary operator applied to the intermediate result and the element * @return collection with intermediate results */ - def scanLeft[B](z: B)(op: (B, A) => B): CC[B] + def scanLeft[B](z: B)(op: (B, A) => B): CC[B]^{this, op} /** Selects all elements of this $coll which satisfy a predicate. * @@ -343,7 +345,7 @@ transparent trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOn * @return a new $coll consisting of all elements of this $coll that satisfy the given * predicate `p`. The order of the elements is preserved. */ - def filter(p: A => Boolean): C + def filter(p: A => Boolean): C^{this, p} /** Selects all elements of this $coll which do not satisfy a predicate. * @@ -351,7 +353,7 @@ transparent trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOn * @return a new $coll consisting of all elements of this $coll that do not satisfy the given * predicate `pred`. Their order may not be preserved. */ - def filterNot(pred: A => Boolean): C + def filterNot(pred: A => Boolean): C^{this, pred} /** Selects the first `n` elements. * $orderDependent @@ -360,7 +362,7 @@ transparent trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOn * or else the whole $coll, if it has less than `n` elements. * If `n` is negative, returns an empty $coll. */ - def take(n: Int): C + def take(n: Int): C^{this} /** Selects the longest prefix of elements that satisfy a predicate. * @@ -386,7 +388,7 @@ transparent trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOn * @return the longest prefix of this $coll whose elements all satisfy * the predicate `p`. */ - def takeWhile(p: A => Boolean): C + def takeWhile(p: A => Boolean): C^{this, p} /** Selects all elements except the first `n` ones. * $orderDependent @@ -395,7 +397,7 @@ transparent trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOn * empty $coll, if this $coll has less than `n` elements. * If `n` is negative, don't drop any elements. */ - def drop(n: Int): C + def drop(n: Int): C^{this} /** Selects all elements except the longest prefix that satisfies a predicate. * @@ -422,7 +424,7 @@ transparent trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOn * @return the longest suffix of this $coll whose first element * does not satisfy the predicate `p`. */ - def dropWhile(p: A => Boolean): C + def dropWhile(p: A => Boolean): C^{this, p} /** Selects an interval of elements. The returned $coll is made up * of all elements `x` which satisfy the invariant: @@ -437,7 +439,7 @@ transparent trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOn * index `from` extending up to (but not including) index `until` * of this $coll. */ - def slice(from: Int, until: Int): C + def slice(from: Int, until: Int): C^{this} /** Builds a new $ccoll by applying a function to all elements of this $coll. * @@ -446,7 +448,7 @@ transparent trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOn * @return a new $ccoll resulting from applying the given function * `f` to each element of this $coll and collecting the results. */ - def map[B](f: A => B): CC[B] + def map[B](f: A => B): CC[B]^{this, f} /** Builds a new $ccoll by applying a function to all elements of this $coll * and using the elements of the resulting collections. @@ -479,7 +481,7 @@ transparent trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOn * @return a new $ccoll resulting from applying the given collection-valued function * `f` to each element of this $coll and concatenating the results. */ - def flatMap[B](f: A => IterableOnce[B]): CC[B] + def flatMap[B](f: A => IterableOnce[B]^): CC[B]^{this, f} /** Given that the elements of this collection are themselves iterable collections, * converts this $coll into a $ccoll comprising the elements of these iterable collections. @@ -506,7 +508,7 @@ transparent trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOn * type of this $coll is an `Iterable`. * @return a new $ccoll resulting from concatenating all element collections. */ - def flatten[B](implicit asIterable: A => IterableOnce[B]): CC[B] + def flatten[B](implicit asIterable: A -> IterableOnce[B]): CC[B]^{this} /** Builds a new $ccoll by applying a partial function to all elements of this $coll * on which the function is defined. @@ -517,7 +519,7 @@ transparent trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOn * `pf` to each element on which it is defined and collecting the results. * The order of the elements is preserved. */ - def collect[B](pf: PartialFunction[A, B]): CC[B] + def collect[B](pf: PartialFunction[A, B]^): CC[B]^{this, pf} /** Zips this $coll with its indices. * @@ -526,7 +528,7 @@ transparent trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOn * @example * `List("a", "b", "c").zipWithIndex == List(("a", 0), ("b", 1), ("c", 2))` */ - def zipWithIndex: CC[(A @uncheckedVariance, Int)] + def zipWithIndex: CC[(A @uncheckedVariance, Int)]^{this} /** Splits this $coll into a prefix/suffix pair according to a predicate. * @@ -539,7 +541,7 @@ transparent trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOn * @return a pair consisting of the longest prefix of this $coll whose * elements all satisfy `p`, and the rest of this $coll. */ - def span(p: A => Boolean): (C, C) + def span(p: A => Boolean): (C^{this}, C^{this}) /** Splits this $coll into a prefix/suffix pair at a given position. * @@ -551,7 +553,7 @@ transparent trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOn * @return a pair of ${coll}s consisting of the first `n` * elements of this $coll, and the other elements. */ - def splitAt(n: Int): (C, C) = { + def splitAt(n: Int): (C^{this}, C^{this}) = { class Spanner extends runtime.AbstractFunction1[A, Boolean] { var i = 0 def apply(a: A) = i < n && { i += 1 ; true } @@ -569,7 +571,7 @@ transparent trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOn * @tparam U the return type of f * @return The same logical collection as this */ - def tapEach[U](f: A => U): C + def tapEach[U](f: A => U): C^{this, f} /////////////////////////////////////////////////////////////// Concrete methods based on iterator @@ -911,7 +913,7 @@ transparent trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOn case _ => Some(reduceLeft(op)) } private final def reduceLeftOptionIterator[B >: A](op: (B, A) => B): Option[B] = reduceOptionIterator[A, B](iterator)(op) - private final def reduceOptionIterator[X >: A, B >: X](it: Iterator[X])(op: (B, X) => B): Option[B] = { + private final def reduceOptionIterator[X >: A, B >: X](it: Iterator[X]^)(op: (B, X) => B): Option[B] = { if (it.hasNext) { var acc: B = it.next() while (it.hasNext) @@ -1154,13 +1156,13 @@ transparent trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOn * @return the first element of this $coll with the largest value measured by function `f` * with respect to the ordering `cmp`. */ - def maxBy[B](f: A => B)(implicit ord: Ordering[B]): A = + def maxBy[B](f: A -> B)(implicit ord: Ordering[B]): A = knownSize match { case 0 => throw new UnsupportedOperationException("empty.maxBy") case _ => foldLeft(new Maximized[A, B]("maxBy")(f)(ord.gt))((m, a) => m(m, a)).result } - private class Maximized[X, B](descriptor: String)(f: X => B)(cmp: (B, B) => Boolean) extends AbstractFunction2[Maximized[X, B], X, Maximized[X, B]] { + private class Maximized[X, B](descriptor: String)(f: X -> B)(cmp: (B, B) -> Boolean) extends AbstractFunction2[Maximized[X, B], X, Maximized[X, B]] { var maxElem: X = null.asInstanceOf[X] var maxF: B = null.asInstanceOf[B] var nonEmpty = false @@ -1193,7 +1195,7 @@ transparent trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOn * @return an option value containing the first element of this $coll with the * largest value measured by function `f` with respect to the ordering `cmp`. */ - def maxByOption[B](f: A => B)(implicit ord: Ordering[B]): Option[A] = + def maxByOption[B](f: A -> B)(implicit ord: Ordering[B]): Option[A] = knownSize match { case 0 => None case _ => foldLeft(new Maximized[A, B]("maxBy")(f)(ord.gt))((m, a) => m(m, a)).toOption @@ -1210,7 +1212,7 @@ transparent trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOn * @return the first element of this $coll with the smallest value measured by function `f` * with respect to the ordering `cmp`. */ - def minBy[B](f: A => B)(implicit ord: Ordering[B]): A = + def minBy[B](f: A -> B)(implicit ord: Ordering[B]): A = knownSize match { case 0 => throw new UnsupportedOperationException("empty.minBy") case _ => foldLeft(new Maximized[A, B]("minBy")(f)(ord.lt))((m, a) => m(m, a)).result @@ -1227,7 +1229,7 @@ transparent trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOn * with the smallest value measured by function `f` * with respect to the ordering `cmp`. */ - def minByOption[B](f: A => B)(implicit ord: Ordering[B]): Option[A] = + def minByOption[B](f: A -> B)(implicit ord: Ordering[B]): Option[A] = knownSize match { case 0 => None case _ => foldLeft(new Maximized[A, B]("minBy")(f)(ord.lt))((m, a) => m(m, a)).toOption @@ -1244,7 +1246,7 @@ transparent trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOn * value for which it is defined, or `None` if none exists. * @example `Seq("a", 1, 5L).collectFirst({ case x: Int => x*10 }) = Some(10)` */ - def collectFirst[B](pf: PartialFunction[A, B]): Option[B] = { + def collectFirst[B](pf: PartialFunction[A, B]^): Option[B] = { // Presumably the fastest way to get in and out of a partial function is for a sentinel function to return itself // (Tested to be lower-overhead than runWith. Would be better yet to not need to (formally) allocate it) val sentinel: scala.Function1[A, Any] = new AbstractFunction1[A, Any] { @@ -1436,7 +1438,7 @@ transparent trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOn def to[C1](factory: Factory[A, C1]): C1 = factory.fromSpecific(this) @deprecated("Use .iterator instead of .toIterator", "2.13.0") - @`inline` final def toIterator: Iterator[A] = iterator + @`inline` final def toIterator: Iterator[A]^{this} = iterator /** Converts this $coll to a `List`. * @@ -1478,7 +1480,7 @@ transparent trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOn def toIndexedSeq: immutable.IndexedSeq[A] = immutable.IndexedSeq.from(this) @deprecated("Use .to(LazyList) instead of .toStream", "2.13.0") - @inline final def toStream: immutable.Stream[A] = to(immutable.Stream) + @inline final def toStream: immutable.Stream[A]^{this} = to(immutable.Stream) /** Converts this $coll to a `Buffer`. * From 2e2393ab5cca24c0c0b5cb35bda4d35ff6222389 Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Tue, 12 Aug 2025 02:57:20 +0200 Subject: [PATCH 047/101] First pass to Iterable --- library/src/scala/collection/Iterable.scala | 82 ++++++++++++--------- 1 file changed, 46 insertions(+), 36 deletions(-) diff --git a/library/src/scala/collection/Iterable.scala b/library/src/scala/collection/Iterable.scala index 641b4bb4745e..d6547f45b58b 100644 --- a/library/src/scala/collection/Iterable.scala +++ b/library/src/scala/collection/Iterable.scala @@ -14,6 +14,8 @@ package scala package collection import scala.language.`2.13` +import language.experimental.captureChecking + import scala.annotation.nowarn import scala.annotation.unchecked.uncheckedVariance import scala.collection.mutable.Builder @@ -95,7 +97,7 @@ trait Iterable[+A] extends IterableOnce[A] * @return a decorator `LazyZip2` that allows strict operations to be performed on the lazily evaluated pairs * or chained calls to `lazyZip`. Implicit conversion to `Iterable[(A, B)]` is also supported. */ - def lazyZip[B](that: Iterable[B]): LazyZip2[A, B, this.type] = new LazyZip2(this, this, that) + def lazyZip[B](that: Iterable[B]^): LazyZip2[A, B, this.type]^{this, that} = new LazyZip2(this, this, that) } /** Base trait for Iterable operations @@ -138,24 +140,24 @@ transparent trait IterableOps[+A, +CC[_], +C] extends Any with IterableOnce[A] w */ // Should be `protected def asIterable`, or maybe removed altogether if it's not needed @deprecated("toIterable is internal and will be made protected; its name is similar to `toList` or `toSeq`, but it doesn't copy non-immutable collections", "2.13.7") - def toIterable: Iterable[A] + def toIterable: Iterable[A]^{this} /** Converts this $coll to an unspecified Iterable. Will return * the same collection if this instance is already Iterable. * @return An Iterable containing all elements of this $coll. */ @deprecated("toTraversable is internal and will be made protected; its name is similar to `toList` or `toSeq`, but it doesn't copy non-immutable collections", "2.13.0") - final def toTraversable: Traversable[A] = toIterable + final def toTraversable: Traversable[A]^{this} = toIterable override def isTraversableAgain: Boolean = true /** * @return This collection as a `C`. */ - protected def coll: C + protected def coll: C^{this} @deprecated("Use coll instead of repr in a collection implementation, use the collection value itself from the outside", "2.13.0") - final def repr: C = coll + final def repr: C^{this} = coll /** * Defines how to turn a given `Iterable[A]` into a collection of type `C`. @@ -175,7 +177,7 @@ transparent trait IterableOps[+A, +CC[_], +C] extends Any with IterableOnce[A] w * `Iterable[A]` obtained from `this` collection (as it is the case in the * implementations of operations where we use a `View[A]`), it is safe. */ - protected def fromSpecific(coll: IterableOnce[A @uncheckedVariance]): C + protected def fromSpecific(coll: IterableOnce[A @uncheckedVariance]^): C^{coll} /** The companion object of this ${coll}, providing various factory methods. * @@ -252,7 +254,7 @@ transparent trait IterableOps[+A, +CC[_], +C] extends Any with IterableOnce[A] w def lastOption: Option[A] = if (isEmpty) None else Some(last) /** A view over the elements of this collection. */ - def view: View[A] = View.fromIteratorProvider(() => iterator) + def view: View[A]^{this} = View.fromIteratorProvider(() => iterator) /** Compares the size of this $coll to a test value. * @@ -301,7 +303,7 @@ transparent trait IterableOps[+A, +CC[_], +C] extends Any with IterableOnce[A] w * this.sizeIs > size // this.sizeCompare(size) > 0 * }}} */ - @inline final def sizeIs: IterableOps.SizeCompareOps = new IterableOps.SizeCompareOps(this) + @inline final def sizeIs: IterableOps.SizeCompareOps^{this} = new IterableOps.SizeCompareOps(caps.unsafe.unsafeAssumePure(this) /* see comment in SizeCompareOps*/) /** Compares the size of this $coll to the size of another `Iterable`. * @@ -378,7 +380,7 @@ transparent trait IterableOps[+A, +CC[_], +C] extends Any with IterableOnce[A] w * @throws IllegalArgumentException if all collections in this $coll * are not of the same size. */ - def transpose[B](implicit asIterable: A => /*<: /*<: Boolean): C = fromSpecific(new View.Filter(this, pred, isFlipped = false)) + def filter(pred: A => Boolean): C^{this, pred} = fromSpecific(new View.Filter(this, pred, isFlipped = false)) - def filterNot(pred: A => Boolean): C = fromSpecific(new View.Filter(this, pred, isFlipped = true)) + def filterNot(pred: A => Boolean): C^{this, pred} = fromSpecific(new View.Filter(this, pred, isFlipped = true)) /** Creates a non-strict filter of this $coll. * @@ -417,7 +419,7 @@ transparent trait IterableOps[+A, +CC[_], +C] extends Any with IterableOnce[A] w * All these operations apply to those elements of this $coll * which satisfy the predicate `p`. */ - def withFilter(p: A => Boolean): collection.WithFilter[A, CC] = new IterableOps.WithFilter(this, p) + def withFilter(p: A => Boolean): collection.WithFilter[A, CC]^{this, p} = new IterableOps.WithFilter(this, p) /** A pair of, first, all elements that satisfy predicate `p` and, second, * all elements that do not. @@ -428,15 +430,15 @@ transparent trait IterableOps[+A, +CC[_], +C] extends Any with IterableOnce[A] w * Strict collections have an overridden version of `partition` in `StrictOptimizedIterableOps`, * which requires only a single traversal. */ - def partition(p: A => Boolean): (C, C) = { + def partition(p: A => Boolean): (C^{this, p}, C^{this, p}) = { val first = new View.Filter(this, p, isFlipped = false) val second = new View.Filter(this, p, isFlipped = true) (fromSpecific(first), fromSpecific(second)) } - override def splitAt(n: Int): (C, C) = (take(n), drop(n)) + override def splitAt(n: Int): (C^{this}, C^{this}) = (take(n), drop(n)) - def take(n: Int): C = fromSpecific(new View.Take(this, n)) + def take(n: Int): C^{this} = fromSpecific(new View.Take(this, n)) /** Selects the last ''n'' elements. * $orderDependent @@ -683,11 +685,11 @@ transparent trait IterableOps[+A, +CC[_], +C] extends Any with IterableOnce[A] w def map[B](f: A => B): CC[B] = iterableFactory.from(new View.Map(this, f)) - def flatMap[B](f: A => IterableOnce[B]): CC[B] = iterableFactory.from(new View.FlatMap(this, f)) + def flatMap[B](f: A => IterableOnce[B]^): CC[B] = iterableFactory.from(new View.FlatMap(this, f)) - def flatten[B](implicit asIterable: A => IterableOnce[B]): CC[B] = flatMap(asIterable) + def flatten[B](implicit asIterable: A -> IterableOnce[B]): CC[B] = flatMap(asIterable) - def collect[B](pf: PartialFunction[A, B]): CC[B] = + def collect[B](pf: PartialFunction[A, B]^): CC[B] = iterableFactory.from(new View.Collect(this, pf)) /** Applies a function `f` to each element of the $coll and returns a pair of ${coll}s: the first one @@ -726,7 +728,7 @@ transparent trait IterableOps[+A, +CC[_], +C] extends Any with IterableOnce[A] w * @return a new $coll which contains all elements * of this $coll followed by all elements of `suffix`. */ - def concat[B >: A](suffix: IterableOnce[B]): CC[B] = iterableFactory.from { + def concat[B >: A](suffix: IterableOnce[B]^): CC[B] = iterableFactory.from { suffix match { case suffix: Iterable[B] => new View.Concat(this, suffix) case suffix => iterator ++ suffix.iterator @@ -734,7 +736,7 @@ transparent trait IterableOps[+A, +CC[_], +C] extends Any with IterableOnce[A] w } /** Alias for `concat` */ - @inline final def ++ [B >: A](suffix: IterableOnce[B]): CC[B] = concat(suffix) + @inline final def ++ [B >: A](suffix: IterableOnce[B]^): CC[B] = concat(suffix) /** Returns a $ccoll formed from this $coll and another iterable collection * by combining corresponding elements in pairs. @@ -745,7 +747,7 @@ transparent trait IterableOps[+A, +CC[_], +C] extends Any with IterableOnce[A] w * @return a new $ccoll containing pairs consisting of corresponding elements of this $coll and `that`. * The length of the returned collection is the minimum of the lengths of this $coll and `that`. */ - def zip[B](that: IterableOnce[B]): CC[(A @uncheckedVariance, B)] = iterableFactory.from(that match { // sound bcs of VarianceNote + def zip[B](that: IterableOnce[B]^): CC[(A @uncheckedVariance, B)] = iterableFactory.from(that match { // sound bcs of VarianceNote case that: Iterable[B] => new View.Zip(this, that) case _ => iterator.zip(that) }) @@ -766,7 +768,7 @@ transparent trait IterableOps[+A, +CC[_], +C] extends Any with IterableOnce[A] w * If this $coll is shorter than `that`, `thisElem` values are used to pad the result. * If `that` is shorter than this $coll, `thatElem` values are used to pad the result. */ - def zipAll[A1 >: A, B](that: Iterable[B], thisElem: A1, thatElem: B): CC[(A1, B)] = iterableFactory.from(new View.ZipAll(this, that, thisElem, thatElem)) + def zipAll[A1 >: A, B](that: Iterable[B]^, thisElem: A1, thatElem: B): CC[(A1, B)] = iterableFactory.from(new View.ZipAll(this, that, thisElem, thatElem)) /** Converts this $coll of pairs into two collections of the first and second * half of each pair. @@ -787,7 +789,7 @@ transparent trait IterableOps[+A, +CC[_], +C] extends Any with IterableOnce[A] w * @return a pair of ${coll}s, containing the first, respectively second * half of each element pair of this $coll. */ - def unzip[A1, A2](implicit asPair: A => (A1, A2)): (CC[A1], CC[A2]) = { + def unzip[A1, A2](implicit asPair: A -> (A1, A2)): (CC[A1], CC[A2]) = { val first: View[A1] = new View.Map[A, A1](this, asPair(_)._1) val second: View[A2] = new View.Map[A, A2](this, asPair(_)._2) (iterableFactory.from(first), iterableFactory.from(second)) @@ -814,7 +816,7 @@ transparent trait IterableOps[+A, +CC[_], +C] extends Any with IterableOnce[A] w * @return a triple of ${coll}s, containing the first, second, respectively * third member of each element triple of this $coll. */ - def unzip3[A1, A2, A3](implicit asTriple: A => (A1, A2, A3)): (CC[A1], CC[A2], CC[A3]) = { + def unzip3[A1, A2, A3](implicit asTriple: A -> (A1, A2, A3)): (CC[A1], CC[A2], CC[A3]) = { val first: View[A1] = new View.Map[A, A1](this, asTriple(_)._1) val second: View[A2] = new View.Map[A, A2](this, asTriple(_)._2) val third: View[A3] = new View.Map[A, A3](this, asTriple(_)._3) @@ -828,7 +830,7 @@ transparent trait IterableOps[+A, +CC[_], +C] extends Any with IterableOnce[A] w * @return an iterator over all the tails of this $coll * @example `List(1,2,3).tails = Iterator(List(1,2,3), List(2,3), List(3), Nil)` */ - def tails: Iterator[C] = iterateUntilEmpty(_.tail) + def tails: Iterator[C^{this}]^{this} = iterateUntilEmpty(_.tail) /** Iterates over the inits of this $coll. The first value will be this * $coll and the final one will be an empty $coll, with the intervening @@ -839,12 +841,12 @@ transparent trait IterableOps[+A, +CC[_], +C] extends Any with IterableOnce[A] w * @return an iterator over all the inits of this $coll * @example `List(1,2,3).inits = Iterator(List(1,2,3), List(1,2), List(1), Nil)` */ - def inits: Iterator[C] = iterateUntilEmpty(_.init) + def inits: Iterator[C^{this}]^{this} = iterateUntilEmpty(_.init) override def tapEach[U](f: A => U): C = fromSpecific(new View.Map(this, { (a: A) => f(a); a })) // A helper for tails and inits. - private[this] def iterateUntilEmpty(f: Iterable[A] => Iterable[A]): Iterator[C] = { + private[this] def iterateUntilEmpty(f: Iterable[A]^{this} => Iterable[A]^{this}): Iterator[C^{this}]^{this, f} = { // toIterable ties the knot between `this: IterableOnceOps[A, CC, C]` and `this.tail: C` // `this.tail.tail` doesn't compile as `C` is unbounded // `Iterable.from(this)` would eagerly copy non-immutable collections @@ -853,7 +855,7 @@ transparent trait IterableOps[+A, +CC[_], +C] extends Any with IterableOnce[A] w } @deprecated("Use ++ instead of ++: for collections of type Iterable", "2.13.0") - def ++:[B >: A](that: IterableOnce[B]): CC[B] = iterableFactory.from(that match { + def ++:[B >: A](that: IterableOnce[B]^): CC[B] = iterableFactory.from(that match { case xs: Iterable[B] => new View.Concat(xs, this) case _ => that.iterator ++ iterator }) @@ -867,6 +869,13 @@ object IterableOps { * [[scala.collection.IterableOps!.sizeCompare(Int):Int* `sizeCompare(Int)`]] */ final class SizeCompareOps private[collection](val it: IterableOps[_, AnyConstr, _]) extends AnyVal { + // CC Problem: if we add the logically needed `^`s to the `it` parameter and the + // self type, separation checking fails in the compiler-generated equals$extends + // method of the value class. There seems to be something wrong how pattern + // matching interacts with separation checking. A minimized test case + // is pending/pos-custom-args/captures/SizeCompareOps-redux.scala. + // Without the `^`s, the `sizeIs` method needs an unsafeAssumePure. + /** Tests if the size of the collection is less than some value. */ @inline def <(size: Int): Boolean = it.sizeCompare(size) < 0 /** Tests if the size of the collection is less than or equal to some value. */ @@ -891,7 +900,7 @@ object IterableOps { */ @SerialVersionUID(3L) class WithFilter[+A, +CC[_]]( - self: IterableOps[A, CC, _], + self: IterableOps[A, CC, _]^, p: A => Boolean ) extends collection.WithFilter[A, CC] with Serializable { @@ -906,7 +915,7 @@ object IterableOps { def foreach[U](f: A => U): Unit = filtered.foreach(f) - def withFilter(q: A => Boolean): WithFilter[A, CC] = + def withFilter(q: A => Boolean): WithFilter[A, CC]^{this, q} = new WithFilter(self, (a: A) => p(a) && q(a)) } @@ -926,7 +935,7 @@ object Iterable extends IterableFactory.Delegate[Iterable](immutable.Iterable) { override def view: View.Single[A] = new View.Single(a) override def take(n: Int) = if (n > 0) this else Iterable.empty override def takeRight(n: Int) = if (n > 0) this else Iterable.empty - override def drop(n: Int) = if (n > 0) Iterable.empty else this + override def drop(n: Int): Iterable[A] = if (n > 0) Iterable.empty else this override def dropRight(n: Int) = if (n > 0) Iterable.empty else this override def tail: Iterable[Nothing] = Iterable.empty override def init: Iterable[Nothing] = Iterable.empty @@ -944,7 +953,7 @@ abstract class AbstractIterable[+A] extends Iterable[A] * same as `C`. */ trait IterableFactoryDefaults[+A, +CC[x] <: IterableOps[x, CC, CC[x]]] extends IterableOps[A, CC, CC[A @uncheckedVariance]] { - protected def fromSpecific(coll: IterableOnce[A @uncheckedVariance]): CC[A @uncheckedVariance] = iterableFactory.from(coll) + protected def fromSpecific(coll: IterableOnce[A @uncheckedVariance]^): CC[A @uncheckedVariance]^{coll} = iterableFactory.from(coll) protected def newSpecificBuilder: Builder[A @uncheckedVariance, CC[A @uncheckedVariance]] = iterableFactory.newBuilder[A] // overridden for efficiency, since we know CC[A] =:= C @@ -962,7 +971,7 @@ trait IterableFactoryDefaults[+A, +CC[x] <: IterableOps[x, CC, CC[x]]] extends I trait EvidenceIterableFactoryDefaults[+A, +CC[x] <: IterableOps[x, CC, CC[x]], Ev[_]] extends IterableOps[A, CC, CC[A @uncheckedVariance]] { protected def evidenceIterableFactory: EvidenceIterableFactory[CC, Ev] implicit protected def iterableEvidence: Ev[A @uncheckedVariance] - override protected def fromSpecific(coll: IterableOnce[A @uncheckedVariance]): CC[A @uncheckedVariance] = evidenceIterableFactory.from(coll) + override protected def fromSpecific(coll: IterableOnce[A @uncheckedVariance]^): CC[A @uncheckedVariance]^{this} = evidenceIterableFactory.from(coll) override protected def newSpecificBuilder: Builder[A @uncheckedVariance, CC[A @uncheckedVariance]] = evidenceIterableFactory.newBuilder[A] override def empty: CC[A @uncheckedVariance] = evidenceIterableFactory.empty } @@ -984,7 +993,7 @@ trait SortedSetFactoryDefaults[+A, +WithFilterCC[x] <: IterableOps[x, WithFilterCC, WithFilterCC[x]] with Set[x]] extends SortedSetOps[A @uncheckedVariance, CC, CC[A @uncheckedVariance]] { self: IterableOps[A, WithFilterCC, _] => - override protected def fromSpecific(coll: IterableOnce[A @uncheckedVariance]): CC[A @uncheckedVariance] = sortedIterableFactory.from(coll)(using ordering) + override protected def fromSpecific(coll: IterableOnce[A @uncheckedVariance]^): CC[A @uncheckedVariance] = sortedIterableFactory.from(coll)(using ordering) override protected def newSpecificBuilder: mutable.Builder[A @uncheckedVariance, CC[A @uncheckedVariance]] = sortedIterableFactory.newBuilder[A](using ordering) override def empty: CC[A @uncheckedVariance] = sortedIterableFactory.empty(using ordering) @@ -1008,7 +1017,8 @@ trait SortedSetFactoryDefaults[+A, trait MapFactoryDefaults[K, +V, +CC[x, y] <: IterableOps[(x, y), Iterable, Iterable[(x, y)]], +WithFilterCC[x] <: IterableOps[x, WithFilterCC, WithFilterCC[x]] with Iterable[x]] extends MapOps[K, V, CC, CC[K, V @uncheckedVariance]] with IterableOps[(K, V), WithFilterCC, CC[K, V @uncheckedVariance]] { - override protected def fromSpecific(coll: IterableOnce[(K, V @uncheckedVariance)]): CC[K, V @uncheckedVariance] = mapFactory.from(coll) + this: MapFactoryDefaults[K, V, CC, WithFilterCC] => + override protected def fromSpecific(coll: IterableOnce[(K, V @uncheckedVariance)]^): CC[K, V @uncheckedVariance]^{coll} = mapFactory.from(coll) override protected def newSpecificBuilder: mutable.Builder[(K, V @uncheckedVariance), CC[K, V @uncheckedVariance]] = mapFactory.newBuilder[K, V] override def empty: CC[K, V @uncheckedVariance] = (this: AnyRef) match { // Implemented here instead of in TreeSeqMap since overriding empty in TreeSeqMap is not forwards compatible (should be moved) @@ -1039,7 +1049,7 @@ trait SortedMapFactoryDefaults[K, +V, self: IterableOps[(K, V), WithFilterCC, _] => override def empty: CC[K, V @uncheckedVariance] = sortedMapFactory.empty(using ordering) - override protected def fromSpecific(coll: IterableOnce[(K, V @uncheckedVariance)]): CC[K, V @uncheckedVariance] = sortedMapFactory.from(coll)(using ordering) + override protected def fromSpecific(coll: IterableOnce[(K, V @uncheckedVariance)]^): CC[K, V @uncheckedVariance] = sortedMapFactory.from(coll)(using ordering) override protected def newSpecificBuilder: mutable.Builder[(K, V @uncheckedVariance), CC[K, V @uncheckedVariance]] = sortedMapFactory.newBuilder[K, V](using ordering) override def withFilter(p: ((K, V)) => Boolean): collection.SortedMapOps.WithFilter[K, V, WithFilterCC, UnsortedCC, CC] = From 20a38bb09a1b1bbad2224be4712099f6a3ab4c9b Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Tue, 12 Aug 2025 04:08:05 +0200 Subject: [PATCH 048/101] Iterators are compiling --- library/src/scala/Array.scala | 4 +- library/src/scala/collection/BitSet.scala | 2 +- .../scala/collection/BufferedIterator.scala | 1 + library/src/scala/collection/BuildFrom.scala | 4 +- library/src/scala/collection/Factory.scala | 78 ++++++++-------- library/src/scala/collection/Iterable.scala | 10 +- .../src/scala/collection/IterableOnce.scala | 16 ++-- library/src/scala/collection/Iterator.scala | 93 ++++++++++--------- .../src/scala/collection/mutable/Buffer.scala | 2 +- 9 files changed, 109 insertions(+), 101 deletions(-) diff --git a/library/src/scala/Array.scala b/library/src/scala/Array.scala index a1924d61846d..bb23b79eb1a4 100644 --- a/library/src/scala/Array.scala +++ b/library/src/scala/Array.scala @@ -50,7 +50,7 @@ object Array { implicit def toFactory[A : ClassTag](dummy: Array.type): Factory[A, Array[A]] = new ArrayFactory(dummy) @SerialVersionUID(3L) private class ArrayFactory[A : ClassTag](dummy: Array.type) extends Factory[A, Array[A]] with Serializable { - def fromSpecific(it: IterableOnce[A]): Array[A] = Array.from[A](it) + def fromSpecific(it: IterableOnce[A]^): Array[A] = Array.from[A](it) def newBuilder: mutable.Builder[A, Array[A]] = Array.newBuilder[A] } @@ -660,7 +660,7 @@ object Array { * @define collectExample * @define undefinedorder */ -final class Array[T](_length: Int) extends java.io.Serializable with java.lang.Cloneable { +final class Array[T](_length: Int) extends java.io.Serializable with java.lang.Cloneable { self: Array[T] => /** The length of the array */ def length: Int = throw new Error() diff --git a/library/src/scala/collection/BitSet.scala b/library/src/scala/collection/BitSet.scala index 89e5f6358870..5464d73a493e 100644 --- a/library/src/scala/collection/BitSet.scala +++ b/library/src/scala/collection/BitSet.scala @@ -51,7 +51,7 @@ object BitSet extends SpecificIterableFactory[Int, BitSet] { def empty: BitSet = immutable.BitSet.empty def newBuilder: Builder[Int, BitSet] = immutable.BitSet.newBuilder - def fromSpecific(it: IterableOnce[Int]): BitSet = immutable.BitSet.fromSpecific(it) + def fromSpecific(it: IterableOnce[Int]^): BitSet = immutable.BitSet.fromSpecific(it) @SerialVersionUID(3L) private[collection] abstract class SerializationProxy(@transient protected val coll: BitSet) extends Serializable { diff --git a/library/src/scala/collection/BufferedIterator.scala b/library/src/scala/collection/BufferedIterator.scala index e933ffcd4c16..4076b9662940 100644 --- a/library/src/scala/collection/BufferedIterator.scala +++ b/library/src/scala/collection/BufferedIterator.scala @@ -13,6 +13,7 @@ package scala.collection import scala.language.`2.13` +import language.experimental.captureChecking /** Buffered iterators are iterators which provide a method `head` * that inspects the next element without discarding it. diff --git a/library/src/scala/collection/BuildFrom.scala b/library/src/scala/collection/BuildFrom.scala index 1d7d7f722a32..3f0d30eb0692 100644 --- a/library/src/scala/collection/BuildFrom.scala +++ b/library/src/scala/collection/BuildFrom.scala @@ -115,11 +115,11 @@ trait BuildFromLowPriority2 { implicit def buildFromIterableOps[CC[X] <: Iterable[X] with IterableOps[X, CC, _], A0, A]: BuildFrom[CC[A0], A, CC[A]] = new BuildFrom[CC[A0], A, CC[A]] { //TODO: Reuse a prototype instance def newBuilder(from: CC[A0]): Builder[A, CC[A]] = (from: IterableOps[A0, CC, _]).iterableFactory.newBuilder[A] - def fromSpecific(from: CC[A0])(it: IterableOnce[A]^): CC[A] = (from: IterableOps[A0, CC, _]).iterableFactory.from(it) + def fromSpecific(from: CC[A0])(it: IterableOnce[A]^): CC[A]^{it} = (from: IterableOps[A0, CC, _]).iterableFactory.from(it) } implicit def buildFromIterator[A]: BuildFrom[Iterator[_], A, Iterator[A]] = new BuildFrom[Iterator[_], A, Iterator[A]] { def newBuilder(from: Iterator[_]): mutable.Builder[A, Iterator[A]] = Iterator.newBuilder - def fromSpecific(from: Iterator[_])(it: IterableOnce[A]^): Iterator[A] = Iterator.from(it) + def fromSpecific(from: Iterator[_])(it: IterableOnce[A]^): Iterator[A]^{it} = Iterator.from(it) } } diff --git a/library/src/scala/collection/Factory.scala b/library/src/scala/collection/Factory.scala index 8e40f2955853..fb742ce1ac4c 100644 --- a/library/src/scala/collection/Factory.scala +++ b/library/src/scala/collection/Factory.scala @@ -14,6 +14,8 @@ package scala package collection import scala.language.`2.13` +import language.experimental.captureChecking + import scala.collection.immutable.NumericRange import scala.language.implicitConversions import scala.collection.mutable.Builder @@ -30,14 +32,14 @@ import scala.reflect.ClassTag * @tparam A Type of elements (e.g. `Int`, `Boolean`, etc.) * @tparam C Type of collection (e.g. `List[Int]`, `TreeMap[Int, String]`, etc.) */ -trait Factory[-A, +C] extends Any { +trait Factory[-A, +C] extends Any { this: Factory[A, C] => /** * @return A collection of type `C` containing the same elements * as the source collection `it`. * @param it Source collection */ - def fromSpecific(it: IterableOnce[A]): C + def fromSpecific(it: IterableOnce[A]^): C^{it} /** Get a Builder for the collection. For non-strict collection types this will use an intermediate buffer. * Building collections with `fromSpecific` is preferred because it can be lazy for lazy collections. */ @@ -49,7 +51,7 @@ object Factory { implicit val stringFactory: Factory[Char, String] = new StringFactory @SerialVersionUID(3L) private class StringFactory extends Factory[Char, String] with Serializable { - def fromSpecific(it: IterableOnce[Char]): String = { + def fromSpecific(it: IterableOnce[Char]^): String = { val b = new mutable.StringBuilder(scala.math.max(0, it.knownSize)) b ++= it b.result() @@ -60,7 +62,7 @@ object Factory { implicit def arrayFactory[A: ClassTag]: Factory[A, Array[A]] = new ArrayFactory[A] @SerialVersionUID(3L) private class ArrayFactory[A: ClassTag] extends Factory[A, Array[A]] with Serializable { - def fromSpecific(it: IterableOnce[A]): Array[A] = { + def fromSpecific(it: IterableOnce[A]^): Array[A] = { val b = newBuilder b.sizeHint(it, delta = 0) b ++= it @@ -81,7 +83,7 @@ object Factory { * @define coll collection * @define Coll `Iterable` */ -trait IterableFactory[+CC[_]] extends Serializable { +trait IterableFactory[+CC[_]] extends Serializable { this: IterableFactory[CC] => /** Creates a target $coll from an existing source collection * @@ -89,7 +91,7 @@ trait IterableFactory[+CC[_]] extends Serializable { * @tparam A the type of the collection’s elements * @return a new $coll with the elements of `source` */ - def from[A](source: IterableOnce[A]): CC[A] + def from[A](source: IterableOnce[A]^): CC[A]^{source} /** An empty $coll * @tparam A the type of the ${coll}'s elements @@ -110,7 +112,7 @@ trait IterableFactory[+CC[_]] extends Serializable { * @param f the function that's repeatedly applied * @return a $coll with `len` values in the sequence `start, f(start), f(f(start)), ...` */ - def iterate[A](start: A, len: Int)(f: A => A): CC[A] = from(new View.Iterate(start, len)(f)) + def iterate[A](start: A, len: Int)(f: A => A): CC[A]^{f} = from(new View.Iterate(start, len)(f)) /** Produces a $coll that uses a function `f` to produce elements of type `A` * and update an internal state of type `S`. @@ -122,7 +124,7 @@ trait IterableFactory[+CC[_]] extends Serializable { * @tparam S Type of the internal state * @return a $coll that produces elements using `f` until `f` returns `None` */ - def unfold[A, S](init: S)(f: S => Option[(A, S)]): CC[A] = from(new View.Unfold(init)(f)) + def unfold[A, S](init: S)(f: S => Option[(A, S)]): CC[A]^{f} = from(new View.Unfold(init)(f)) /** Produces a $coll containing a sequence of increasing of integers. * @@ -151,7 +153,7 @@ trait IterableFactory[+CC[_]] extends Serializable { * @param elem the element computation * @return A $coll that contains the results of `n` evaluations of `elem`. */ - def fill[A](n: Int)(elem: => A): CC[A] = from(new View.Fill(n)(elem)) + def fill[A](n: Int)(elem: => A): CC[A]^{elem} = from(new View.Fill(n)(elem)) /** Produces a two-dimensional $coll containing the results of some element computation a number of times. * @param n1 the number of elements in the 1st dimension @@ -159,7 +161,7 @@ trait IterableFactory[+CC[_]] extends Serializable { * @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] = fill(n1)(fill(n2)(elem)) + def fill[A](n1: Int, n2: Int)(elem: => A): CC[(CC[A]^{elem}) @uncheckedVariance]^{elem} = 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 @@ -168,7 +170,7 @@ trait IterableFactory[+CC[_]] extends Serializable { * @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] = fill(n1)(fill(n2, n3)(elem)) + 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 @@ -178,7 +180,7 @@ trait IterableFactory[+CC[_]] extends Serializable { * @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] = + 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. @@ -190,7 +192,7 @@ trait IterableFactory[+CC[_]] extends Serializable { * @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] = + 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. @@ -198,7 +200,7 @@ trait IterableFactory[+CC[_]] extends Serializable { * @param f The function computing element values * @return A $coll consisting of elements `f(0), ..., f(n -1)` */ - def tabulate[A](n: Int)(f: Int => A): CC[A] = from(new View.Tabulate(n)(f)) + def tabulate[A](n: Int)(f: Int => A): CC[A]^{f} = from(new View.Tabulate(n)(f)) /** Produces a two-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 @@ -207,7 +209,7 @@ trait IterableFactory[+CC[_]] extends Serializable { * @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] = + 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. @@ -218,7 +220,7 @@ trait IterableFactory[+CC[_]] extends Serializable { * @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] = + 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. @@ -230,7 +232,7 @@ trait IterableFactory[+CC[_]] extends Serializable { * @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] = + 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. @@ -243,7 +245,7 @@ trait IterableFactory[+CC[_]] extends Serializable { * @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] = + 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. @@ -272,13 +274,13 @@ object IterableFactory { @SerialVersionUID(3L) private[this] class ToFactory[A, CC[_]](factory: IterableFactory[CC]) extends Factory[A, CC[A]] with Serializable { - def fromSpecific(it: IterableOnce[A]): CC[A] = factory.from[A](it) + def fromSpecific(it: IterableOnce[A]^): CC[A]^{it} = factory.from[A](it) def newBuilder: Builder[A, CC[A]] = factory.newBuilder[A] } implicit def toBuildFrom[A, CC[_]](factory: IterableFactory[CC]): BuildFrom[Any, A, CC[A]] = new BuildFrom[Any, A, CC[A]] { - def fromSpecific(from: Any)(it: IterableOnce[A]) = factory.from(it) + def fromSpecific(from: Any)(it: IterableOnce[A]^) = factory.from(it) def newBuilder(from: Any) = factory.newBuilder } @@ -286,7 +288,7 @@ object IterableFactory { class Delegate[CC[_]](delegate: IterableFactory[CC]) extends IterableFactory[CC] { override def apply[A](elems: A*): CC[A] = delegate.apply(elems: _*) def empty[A]: CC[A] = delegate.empty - def from[E](it: IterableOnce[E]): CC[E] = delegate.from(it) + def from[E](it: IterableOnce[E]^): CC[E]^{it} = delegate.from(it) def newBuilder[A]: Builder[A, CC[A]] = delegate.newBuilder[A] } } @@ -304,7 +306,7 @@ object SeqFactory { class Delegate[CC[A] <: SeqOps[A, Seq, Seq[A]]](delegate: SeqFactory[CC]) extends SeqFactory[CC] { override def apply[A](elems: A*): CC[A] = delegate.apply(elems: _*) def empty[A]: CC[A] = delegate.empty - def from[E](it: IterableOnce[E]): CC[E] = delegate.from(it) + def from[E](it: IterableOnce[E]^): CC[E]^{it} = delegate.from(it) def newBuilder[A]: Builder[A, CC[A]] = delegate.newBuilder[A] } @@ -382,7 +384,7 @@ trait SpecificIterableFactory[-A, +C] extends Factory[A, C] { * @define coll collection * @define Coll `Iterable` */ -trait MapFactory[+CC[_, _]] extends Serializable { +trait MapFactory[+CC[_, _]] extends Serializable { this: MapFactory[CC] => /** * An empty Map @@ -392,7 +394,7 @@ trait MapFactory[+CC[_, _]] extends Serializable { /** * A collection of type Map generated from given iterable object. */ - def from[K, V](it: IterableOnce[(K, V)]): CC[K, V] + def from[K, V](it: IterableOnce[(K, V)]^): CC[K, V]^{it} /** * A collection of type Map that contains given key/value bindings. @@ -425,20 +427,20 @@ object MapFactory { @SerialVersionUID(3L) private[this] class ToFactory[K, V, CC[_, _]](factory: MapFactory[CC]) extends Factory[(K, V), CC[K, V]] with Serializable { - def fromSpecific(it: IterableOnce[(K, V)]): CC[K, V] = factory.from[K, V](it) + def fromSpecific(it: IterableOnce[(K, V)]^): CC[K, V]^{it} = factory.from[K, V](it) def newBuilder: Builder[(K, V), CC[K, V]] = factory.newBuilder[K, V] } implicit def toBuildFrom[K, V, CC[_, _]](factory: MapFactory[CC]): BuildFrom[Any, (K, V), CC[K, V]] = new BuildFrom[Any, (K, V), CC[K, V]] { - def fromSpecific(from: Any)(it: IterableOnce[(K, V)]) = factory.from(it) + def fromSpecific(from: Any)(it: IterableOnce[(K, V)]^) = factory.from(it) def newBuilder(from: Any) = factory.newBuilder[K, V] } @SerialVersionUID(3L) class Delegate[C[_, _]](delegate: MapFactory[C]) extends MapFactory[C] { override def apply[K, V](elems: (K, V)*): C[K, V] = delegate.apply(elems: _*) - def from[K, V](it: IterableOnce[(K, V)]): C[K, V] = delegate.from(it) + def from[K, V](it: IterableOnce[(K, V)]^): C[K, V]^{it} = delegate.from(it) def empty[K, V]: C[K, V] = delegate.empty def newBuilder[K, V]: Builder[(K, V), C[K, V]] = delegate.newBuilder } @@ -455,9 +457,9 @@ object MapFactory { * @define coll collection * @define Coll `Iterable` */ -trait EvidenceIterableFactory[+CC[_], Ev[_]] extends Serializable { +trait EvidenceIterableFactory[+CC[_], Ev[_]] extends Serializable { this: EvidenceIterableFactory[CC, Ev] => - def from[E : Ev](it: IterableOnce[E]): CC[E] + def from[E : Ev](it: IterableOnce[E]^): CC[E]^{it} def empty[A : Ev]: CC[A] @@ -518,13 +520,13 @@ object EvidenceIterableFactory { @SerialVersionUID(3L) private[this] class ToFactory[Ev[_], A: Ev, CC[_]](factory: EvidenceIterableFactory[CC, Ev]) extends Factory[A, CC[A]] with Serializable { - def fromSpecific(it: IterableOnce[A]): CC[A] = factory.from[A](it) + def fromSpecific(it: IterableOnce[A]^) = factory.from[A](it) def newBuilder: Builder[A, CC[A]] = factory.newBuilder[A] } implicit def toBuildFrom[Ev[_], A: Ev, CC[_]](factory: EvidenceIterableFactory[CC, Ev]): BuildFrom[Any, A, CC[A]] = new EvidenceIterableFactoryToBuildFrom(factory) private class EvidenceIterableFactoryToBuildFrom[Ev[_], A: Ev, CC[_]](factory: EvidenceIterableFactory[CC, Ev]) extends BuildFrom[Any, A, CC[A]] { - def fromSpecific(from: Any)(it: IterableOnce[A]): CC[A] = factory.from[A](it) + def fromSpecific(from: Any)(it: IterableOnce[A]^) = factory.from[A](it) def newBuilder(from: Any): Builder[A, CC[A]] = factory.newBuilder[A] } @@ -532,7 +534,7 @@ object EvidenceIterableFactory { class Delegate[CC[_], Ev[_]](delegate: EvidenceIterableFactory[CC, Ev]) extends EvidenceIterableFactory[CC, Ev] { override def apply[A: Ev](xs: A*): CC[A] = delegate.apply(xs: _*) def empty[A : Ev]: CC[A] = delegate.empty - def from[E : Ev](it: IterableOnce[E]): CC[E] = delegate.from(it) + def from[E : Ev](it: IterableOnce[E]^) = delegate.from(it) def newBuilder[A : Ev]: Builder[A, CC[A]] = delegate.newBuilder[A] } } @@ -669,7 +671,7 @@ object ClassTagIterableFactory { @SerialVersionUID(3L) class AnyIterableDelegate[CC[_]](delegate: ClassTagIterableFactory[CC]) extends IterableFactory[CC] { def empty[A]: CC[A] = delegate.empty(using ClassTag.Any).asInstanceOf[CC[A]] - def from[A](it: IterableOnce[A]): CC[A] = delegate.from[Any](it)(using ClassTag.Any).asInstanceOf[CC[A]] + def from[A](it: IterableOnce[A]^): CC[A] = delegate.from[Any](it)(using ClassTag.Any).asInstanceOf[CC[A]] def newBuilder[A]: Builder[A, CC[A]] = delegate.newBuilder(using ClassTag.Any).asInstanceOf[Builder[A, CC[A]]] override def apply[A](elems: A*): CC[A] = delegate.apply[Any](elems: _*)(using ClassTag.Any).asInstanceOf[CC[A]] override def iterate[A](start: A, len: Int)(f: A => A): CC[A] = delegate.iterate[A](start, len)(f)(using ClassTag.Any.asInstanceOf[ClassTag[A]]) @@ -734,11 +736,11 @@ trait StrictOptimizedClassTagSeqFactory[+CC[A] <: SeqOps[A, Seq, Seq[A]]] extend * @define coll collection * @define Coll `Iterable` */ -trait SortedMapFactory[+CC[_, _]] extends Serializable { +trait SortedMapFactory[+CC[_, _]] extends Serializable { this: SortedMapFactory[CC] => def empty[K : Ordering, V]: CC[K, V] - def from[K : Ordering, V](it: IterableOnce[(K, V)]): CC[K, V] + def from[K : Ordering, V](it: IterableOnce[(K, V)]^): CC[K, V] def apply[K : Ordering, V](elems: (K, V)*): CC[K, V] = from(elems) @@ -765,20 +767,20 @@ object SortedMapFactory { @SerialVersionUID(3L) private[this] class ToFactory[K : Ordering, V, CC[_, _]](factory: SortedMapFactory[CC]) extends Factory[(K, V), CC[K, V]] with Serializable { - def fromSpecific(it: IterableOnce[(K, V)]): CC[K, V] = factory.from[K, V](it) + def fromSpecific(it: IterableOnce[(K, V)]^): CC[K, V] = factory.from[K, V](it) def newBuilder: Builder[(K, V), CC[K, V]] = factory.newBuilder[K, V] } implicit def toBuildFrom[K : Ordering, V, CC[_, _]](factory: SortedMapFactory[CC]): BuildFrom[Any, (K, V), CC[K, V]] = new SortedMapFactoryToBuildFrom(factory) private class SortedMapFactoryToBuildFrom[K : Ordering, V, CC[_, _]](factory: SortedMapFactory[CC]) extends BuildFrom[Any, (K, V), CC[K, V]] { - def fromSpecific(from: Any)(it: IterableOnce[(K, V)]) = factory.from(it) + def fromSpecific(from: Any)(it: IterableOnce[(K, V)]^) = factory.from(it) def newBuilder(from: Any) = factory.newBuilder[K, V] } @SerialVersionUID(3L) class Delegate[CC[_, _]](delegate: SortedMapFactory[CC]) extends SortedMapFactory[CC] { override def apply[K: Ordering, V](elems: (K, V)*): CC[K, V] = delegate.apply(elems: _*) - def from[K : Ordering, V](it: IterableOnce[(K, V)]): CC[K, V] = delegate.from(it) + def from[K : Ordering, V](it: IterableOnce[(K, V)]^): CC[K, V] = delegate.from(it) def empty[K : Ordering, V]: CC[K, V] = delegate.empty def newBuilder[K : Ordering, V]: Builder[(K, V), CC[K, V]] = delegate.newBuilder } diff --git a/library/src/scala/collection/Iterable.scala b/library/src/scala/collection/Iterable.scala index d6547f45b58b..b30c6448418f 100644 --- a/library/src/scala/collection/Iterable.scala +++ b/library/src/scala/collection/Iterable.scala @@ -728,7 +728,7 @@ transparent trait IterableOps[+A, +CC[_], +C] extends Any with IterableOnce[A] w * @return a new $coll which contains all elements * of this $coll followed by all elements of `suffix`. */ - def concat[B >: A](suffix: IterableOnce[B]^): CC[B] = iterableFactory.from { + def concat[B >: A](suffix: IterableOnce[B]^): CC[B]^{this, suffix} = iterableFactory.from { suffix match { case suffix: Iterable[B] => new View.Concat(this, suffix) case suffix => iterator ++ suffix.iterator @@ -736,7 +736,7 @@ transparent trait IterableOps[+A, +CC[_], +C] extends Any with IterableOnce[A] w } /** Alias for `concat` */ - @inline final def ++ [B >: A](suffix: IterableOnce[B]^): CC[B] = concat(suffix) + @inline final def ++ [B >: A](suffix: IterableOnce[B]^): CC[B]^{this, suffix} = concat(suffix) /** Returns a $ccoll formed from this $coll and another iterable collection * by combining corresponding elements in pairs. @@ -747,7 +747,7 @@ transparent trait IterableOps[+A, +CC[_], +C] extends Any with IterableOnce[A] w * @return a new $ccoll containing pairs consisting of corresponding elements of this $coll and `that`. * The length of the returned collection is the minimum of the lengths of this $coll and `that`. */ - def zip[B](that: IterableOnce[B]^): CC[(A @uncheckedVariance, B)] = iterableFactory.from(that match { // sound bcs of VarianceNote + def zip[B](that: IterableOnce[B]^): CC[(A @uncheckedVariance, B)]^{that} = iterableFactory.from(that match { // sound bcs of VarianceNote case that: Iterable[B] => new View.Zip(this, that) case _ => iterator.zip(that) }) @@ -855,7 +855,7 @@ transparent trait IterableOps[+A, +CC[_], +C] extends Any with IterableOnce[A] w } @deprecated("Use ++ instead of ++: for collections of type Iterable", "2.13.0") - def ++:[B >: A](that: IterableOnce[B]^): CC[B] = iterableFactory.from(that match { + def ++:[B >: A](that: IterableOnce[B]^): CC[B]^{this, that} = iterableFactory.from(that match { case xs: Iterable[B] => new View.Concat(xs, this) case _ => that.iterator ++ iterator }) @@ -971,7 +971,7 @@ trait IterableFactoryDefaults[+A, +CC[x] <: IterableOps[x, CC, CC[x]]] extends I trait EvidenceIterableFactoryDefaults[+A, +CC[x] <: IterableOps[x, CC, CC[x]], Ev[_]] extends IterableOps[A, CC, CC[A @uncheckedVariance]] { protected def evidenceIterableFactory: EvidenceIterableFactory[CC, Ev] implicit protected def iterableEvidence: Ev[A @uncheckedVariance] - override protected def fromSpecific(coll: IterableOnce[A @uncheckedVariance]^): CC[A @uncheckedVariance]^{this} = evidenceIterableFactory.from(coll) + override protected def fromSpecific(coll: IterableOnce[A @uncheckedVariance]^): CC[A @uncheckedVariance]^{coll} = evidenceIterableFactory.from(coll) override protected def newSpecificBuilder: Builder[A @uncheckedVariance, CC[A @uncheckedVariance]] = evidenceIterableFactory.newBuilder[A] override def empty: CC[A @uncheckedVariance] = evidenceIterableFactory.empty } diff --git a/library/src/scala/collection/IterableOnce.scala b/library/src/scala/collection/IterableOnce.scala index ce691827139d..a1e5820e2a6d 100644 --- a/library/src/scala/collection/IterableOnce.scala +++ b/library/src/scala/collection/IterableOnce.scala @@ -45,7 +45,7 @@ import scala.runtime.{AbstractFunction1, AbstractFunction2} * @define coll collection * @define ccoll $coll */ -trait IterableOnce[+A] extends Any { +trait IterableOnce[+A] extends Any { this: IterableOnce[A]^ => /** An [[scala.collection.Iterator]] over the elements of this $coll. * @@ -142,7 +142,7 @@ final class IterableOnceExtensionMethods[A](private val it: IterableOnce[A]) ext def collectFirst[B](f: PartialFunction[A, B]^): Option[B] = it.iterator.collectFirst(f) @deprecated("Use .iterator.filter(...) instead", "2.13.0") - def filter(f: A => Boolean): Iterator[A] = it.iterator.filter(f) + def filter(f: A => Boolean): Iterator[A]^{f} = it.iterator.filter(f) @deprecated("Use .iterator.exists(...) instead", "2.13.0") def exists(f: A => Boolean): Boolean = it.iterator.exists(f) @@ -248,13 +248,13 @@ final class IterableOnceExtensionMethods[A](private val it: IterableOnce[A]) ext @`inline` def :\ [B](z: B)(op: (A, B) => B): B = foldRight[B](z)(op) @deprecated("Use .iterator.map instead or consider requiring an Iterable", "2.13.0") - def map[B](f: A => B): IterableOnce[B] = it match { - case it: Iterable[A] => it.map(f) + def map[B](f: A => B): IterableOnce[B]^{f} = it match { + case it: Iterable[A]^{f} => it.map(f) case _ => it.iterator.map(f) } @deprecated("Use .iterator.flatMap instead or consider requiring an Iterable", "2.13.0") - def flatMap[B](f: A => IterableOnce[B]^): IterableOnce[B] = it match { + def flatMap[B](f: A => IterableOnce[B]^): IterableOnce[B]^{f} = it match { case it: Iterable[A] => it.flatMap(f) case _ => it.iterator.flatMap(f) } @@ -541,7 +541,7 @@ transparent trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOn * @return a pair consisting of the longest prefix of this $coll whose * elements all satisfy `p`, and the rest of this $coll. */ - def span(p: A => Boolean): (C^{this}, C^{this}) + def span(p: A => Boolean): (C^{this, p}, C^{this, p}) /** Splits this $coll into a prefix/suffix pair at a given position. * @@ -1435,7 +1435,7 @@ transparent trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOn * xs.to(BitSet) // for xs: Iterable[Int] * }}} */ - def to[C1](factory: Factory[A, C1]): C1 = factory.fromSpecific(this) + def to[C1](factory: Factory[A, C1]): C1^{this} = factory.fromSpecific(this) @deprecated("Use .iterator instead of .toIterator", "2.13.0") @`inline` final def toIterator: Iterator[A]^{this} = iterator @@ -1487,7 +1487,7 @@ transparent trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOn * @tparam B The type of elements of the result, a supertype of `A`. * @return This $coll as a `Buffer[B]`. */ - @inline final def toBuffer[B >: A]: mutable.Buffer[B] = mutable.Buffer.from(this) + @inline final def toBuffer[B >: A]: mutable.Buffer[B] = caps.unsafe.unsafeAssumePure(mutable.Buffer.from(this)) // TODO will go away when buffer is fixed /** Converts this $coll to an `Array`. * diff --git a/library/src/scala/collection/Iterator.scala b/library/src/scala/collection/Iterator.scala index 4fb1cc6d362b..752adb1fba8e 100644 --- a/library/src/scala/collection/Iterator.scala +++ b/library/src/scala/collection/Iterator.scala @@ -13,6 +13,8 @@ package scala.collection import scala.language.`2.13` +import language.experimental.captureChecking + import scala.collection.mutable.{ArrayBuffer, ArrayBuilder, Builder, ImmutableBuilder} import scala.annotation.tailrec import scala.annotation.unchecked.uncheckedVariance @@ -72,7 +74,8 @@ import scala.runtime.Statics * iterators as well. * @define coll iterator */ -trait Iterator[+A] extends IterableOnce[A] with IterableOnceOps[A, Iterator, Iterator[A]] { self => +trait Iterator[+A] extends IterableOnce[A] with IterableOnceOps[A, Iterator, Iterator[A]] { + self: Iterator[A]^ => /** Check if there is a next element available. * @@ -122,6 +125,7 @@ trait Iterator[+A] extends IterableOnce[A] with IterableOnceOps[A, Iterator, Ite private[this] var hd: A = _ private[this] var hdDefined: Boolean = false + def head: A = { if (!hdDefined) { hd = next() @@ -154,7 +158,7 @@ trait Iterator[+A] extends IterableOnce[A] with IterableOnceOps[A, Iterator, Ite * A `GroupedIterator` is yielded by `grouped` and by `sliding`, * where the `step` may differ from the group `size`. */ - class GroupedIterator[B >: A](self: Iterator[B], size: Int, step: Int) extends AbstractIterator[immutable.Seq[B]] { + class GroupedIterator[B >: A](self: Iterator[B]^, size: Int, step: Int) extends AbstractIterator[immutable.Seq[B]] { require(size >= 1 && step >= 1, f"size=$size%d and step=$step%d, but both must be positive") @@ -163,7 +167,7 @@ trait Iterator[+A] extends IterableOnce[A] with IterableOnceOps[A, Iterator, Ite private[this] var first = true // if !first, advancing may skip ahead private[this] var filled = false // whether the buffer is "hot" private[this] var partial = true // whether to emit partial sequence - private[this] var padding: () => B = null // what to pad short sequences with + private[this] var padding: () -> B = null // what to pad short sequences with private[this] def pad = padding != null // irrespective of partial flag private[this] def newBuilder = { val b = ArrayBuilder.make[Any] @@ -186,7 +190,7 @@ trait Iterator[+A] extends IterableOnce[A] with IterableOnceOps[A, Iterator, Ite * @note This method is mutually exclusive with `withPartial`. * @group Configuration */ - def withPadding(x: => B): this.type = { + def withPadding(x: -> B): this.type = { padding = () => x partial = true // redundant, as padding always results in complete segment this @@ -322,7 +326,7 @@ trait Iterator[+A] extends IterableOnce[A] with IterableOnceOps[A, Iterator, Ite * is the same as in the original iterator. * @note Reuse: $consumesOneAndProducesTwoIterators */ - def partition(p: A => Boolean): (Iterator[A], Iterator[A]) = { + def partition(p: A => Boolean): (Iterator[A]^{p}, Iterator[A]^{p}) = { val (a, b) = duplicate (a filter p, b filterNot p) } @@ -342,7 +346,7 @@ trait Iterator[+A] extends IterableOnce[A] with IterableOnceOps[A, Iterator, Ite * * @note Reuse: $consumesAndProducesIterator */ - def grouped[B >: A](size: Int): GroupedIterator[B] = + def grouped[B >: A](size: Int): GroupedIterator[B]^{this} = new GroupedIterator[B](self, size, size) /** Returns an iterator which presents a "sliding window" view of @@ -378,13 +382,13 @@ trait Iterator[+A] extends IterableOnce[A] with IterableOnceOps[A, Iterator, Ite * * @note Reuse: $consumesAndProducesIterator */ - def sliding[B >: A](size: Int, step: Int = 1): GroupedIterator[B] = + def sliding[B >: A](size: Int, step: Int = 1): GroupedIterator[B]^{this} = new GroupedIterator[B](self, size, step) - def scanLeft[B](z: B)(op: (B, A) => B): Iterator[B] = new AbstractIterator[B] { + def scanLeft[B](z: B)(op: (B, A) => B): Iterator[B]^{op} = new AbstractIterator[B] { // We use an intermediate iterator that iterates through the first element `z` // and then that will be modified to iterate through the collection - private[this] var current: Iterator[B] = + private[this] var current: Iterator[B]^{op} = new AbstractIterator[B] { override def knownSize = { val thisSize = self.knownSize @@ -476,11 +480,11 @@ trait Iterator[+A] extends IterableOnce[A] with IterableOnceOps[A, Iterator, Ite @deprecatedOverriding("isEmpty is defined as !hasNext; override hasNext instead", "2.13.0") override def isEmpty: Boolean = !hasNext - def filter(p: A => Boolean): Iterator[A] = filterImpl(p, isFlipped = false) + def filter(p: A => Boolean): Iterator[A]^{p} = filterImpl(p, isFlipped = false) - def filterNot(p: A => Boolean): Iterator[A] = filterImpl(p, isFlipped = true) + def filterNot(p: A => Boolean): Iterator[A]^{p} = filterImpl(p, isFlipped = true) - private[collection] def filterImpl(p: A => Boolean, isFlipped: Boolean): Iterator[A] = new AbstractIterator[A] { + private[collection] def filterImpl(p: A => Boolean, isFlipped: Boolean): Iterator[A]^{p} = new AbstractIterator[A] { private[this] var hd: A = _ private[this] var hdDefined: Boolean = false @@ -514,9 +518,9 @@ trait Iterator[+A] extends IterableOnce[A] with IterableOnceOps[A, Iterator, Ite * @return an iterator which produces those values of this iterator which satisfy the predicate `p`. * @note Reuse: $consumesAndProducesIterator */ - def withFilter(p: A => Boolean): Iterator[A] = filter(p) + def withFilter(p: A => Boolean): Iterator[A]^{p} = filter(p) - def collect[B](pf: PartialFunction[A, B]): Iterator[B] = new AbstractIterator[B] with (A => B) { + def collect[B](pf: PartialFunction[A, B]^): Iterator[B]^{pf} = new AbstractIterator[B] with (A => B) { // Manually buffer to avoid extra layer of wrapping with buffered private[this] var hd: B = _ @@ -564,7 +568,7 @@ trait Iterator[+A] extends IterableOnce[A] with IterableOnceOps[A, Iterator, Ite * * @note Reuse: $consumesIterator */ - def distinctBy[B](f: A => B): Iterator[A] = new AbstractIterator[A] { + def distinctBy[B](f: A -> B): Iterator[A] = new AbstractIterator[A] { private[this] val traversedValues = mutable.HashSet.empty[B] private[this] var nextElementDefined: Boolean = false @@ -589,14 +593,14 @@ trait Iterator[+A] extends IterableOnce[A] with IterableOnceOps[A, Iterator, Ite } } - def map[B](f: A => B): Iterator[B] = new AbstractIterator[B] { + def map[B](f: A => B): Iterator[B]^{f} = new AbstractIterator[B] { override def knownSize = self.knownSize def hasNext = self.hasNext def next() = f(self.next()) } - def flatMap[B](f: A => IterableOnce[B]): Iterator[B] = new AbstractIterator[B] { - private[this] var cur: Iterator[B] = Iterator.empty + def flatMap[B](f: A => IterableOnce[B]^): Iterator[B]^{f} = new AbstractIterator[B] { + private[this] var cur: Iterator[B]^{f} = Iterator.empty /** Trillium logic boolean: -1 = unknown, 0 = false, 1 = true */ private[this] var _hasNext: Int = -1 @@ -630,19 +634,19 @@ trait Iterator[+A] extends IterableOnce[A] with IterableOnceOps[A, Iterator, Ite } } - def flatten[B](implicit ev: A => IterableOnce[B]): Iterator[B] = + def flatten[B](implicit ev: A -> IterableOnce[B]): Iterator[B] = flatMap[B](ev) - def concat[B >: A](xs: => IterableOnce[B]): Iterator[B] = new Iterator.ConcatIterator[B](self).concat(xs) + def concat[B >: A](xs: => IterableOnce[B]^): Iterator[B]^{this, xs} = new Iterator.ConcatIterator[B](self).concat(xs) - @`inline` final def ++ [B >: A](xs: => IterableOnce[B]): Iterator[B] = concat(xs) + @`inline` final def ++ [B >: A](xs: => IterableOnce[B]^): Iterator[B]^{this, xs} = concat(xs) - def take(n: Int): Iterator[A] = sliceIterator(0, n max 0) + def take(n: Int): Iterator[A]^{this} = sliceIterator(0, n max 0) - def takeWhile(p: A => Boolean): Iterator[A] = new AbstractIterator[A] { + def takeWhile(p: A => Boolean): Iterator[A]^{p} = new AbstractIterator[A] { private[this] var hd: A = _ private[this] var hdDefined: Boolean = false - private[this] var tail: Iterator[A] = self + private[this] var tail: Iterator[A]^{self} = self def hasNext = hdDefined || tail.hasNext && { hd = tail.next() @@ -653,9 +657,9 @@ trait Iterator[+A] extends IterableOnce[A] with IterableOnceOps[A, Iterator, Ite def next() = if (hasNext) { hdDefined = false; hd } else Iterator.empty.next() } - def drop(n: Int): Iterator[A] = sliceIterator(n, -1) + def drop(n: Int): Iterator[A]^{this} = sliceIterator(n, -1) - def dropWhile(p: A => Boolean): Iterator[A] = new AbstractIterator[A] { + def dropWhile(p: A => Boolean): Iterator[A]^{p} = new AbstractIterator[A] { // Magic value: -1 = hasn't dropped, 0 = found first, 1 = defer to parent iterator private[this] var status = -1 // Local buffering to avoid double-wrap with .buffered @@ -691,7 +695,7 @@ trait Iterator[+A] extends IterableOnce[A] with IterableOnceOps[A, Iterator, Ite * * @note Reuse: $consumesOneAndProducesTwoIterators */ - def span(p: A => Boolean): (Iterator[A], Iterator[A]) = { + def span(p: A => Boolean): (Iterator[A]^{p}, Iterator[A]^{p}) = { /* * Giving a name to following iterator (as opposed to trailing) because * anonymous class is represented as a structural type that trailing @@ -790,10 +794,10 @@ trait Iterator[+A] extends IterableOnce[A] with IterableOnceOps[A, Iterator, Ite (leading, trailing) } - def slice(from: Int, until: Int): Iterator[A] = sliceIterator(from, until max 0) + def slice(from: Int, until: Int): Iterator[A]^{this} = sliceIterator(from, until max 0) /** Creates an optionally bounded slice, unbounded if `until` is negative. */ - protected def sliceIterator(from: Int, until: Int): Iterator[A] = { + protected def sliceIterator(from: Int, until: Int): Iterator[A]^{this} = { val lo = from max 0 val rest = if (until < 0) -1 // unbounded @@ -804,7 +808,7 @@ trait Iterator[+A] extends IterableOnce[A] with IterableOnceOps[A, Iterator, Ite else new Iterator.SliceIterator(this, lo, rest) } - def zip[B](that: IterableOnce[B]): Iterator[(A, B)] = new AbstractIterator[(A, B)] { + def zip[B](that: IterableOnce[B]^): Iterator[(A, B)]^{that} = new AbstractIterator[(A, B)] { val thatIterator = that.iterator override def knownSize = self.knownSize min thatIterator.knownSize def hasNext = self.hasNext && thatIterator.hasNext @@ -952,7 +956,7 @@ trait Iterator[+A] extends IterableOnce[A] with IterableOnceOps[A, Iterator, Ite } } - override def tapEach[U](f: A => U): Iterator[A] = new AbstractIterator[A] { + override def tapEach[U](f: A => U): Iterator[A]^{f} = new AbstractIterator[A] { override def knownSize = self.knownSize override def hasNext = self.hasNext override def next() = { @@ -989,7 +993,7 @@ object Iterator extends IterableFactory[Iterator] { * @tparam A the type of the collection’s elements * @return a new $coll with the elements of `source` */ - override def from[A](source: IterableOnce[A]): Iterator[A] = source.iterator + override def from[A](source: IterableOnce[A]^): Iterator[A]^{source} = source.iterator /** The iterator which produces no values. */ @`inline` final def empty[T]: Iterator[T] = _empty @@ -1020,7 +1024,7 @@ object Iterator extends IterableFactory[Iterator] { * @param elem the element computation * @return An iterator that produces the results of `n` evaluations of `elem`. */ - override def fill[A](len: Int)(elem: => A): Iterator[A] = new AbstractIterator[A] { + override def fill[A](len: Int)(elem: => A): Iterator[A]^{elem} = new AbstractIterator[A] { private[this] var i = 0 override def knownSize: Int = (len - i) max 0 def hasNext: Boolean = i < len @@ -1035,7 +1039,7 @@ object Iterator extends IterableFactory[Iterator] { * @param f The function computing element values * @return An iterator that produces the values `f(0), ..., f(n -1)`. */ - override def tabulate[A](end: Int)(f: Int => A): Iterator[A] = new AbstractIterator[A] { + override def tabulate[A](end: Int)(f: Int => A): Iterator[A]^{f} = new AbstractIterator[A] { private[this] var i = 0 override def knownSize: Int = (end - i) max 0 def hasNext: Boolean = i < end @@ -1108,7 +1112,7 @@ object Iterator extends IterableFactory[Iterator] { * @param f the function that's repeatedly applied * @return the iterator producing the infinite sequence of values `start, f(start), f(f(start)), ...` */ - def iterate[T](start: T)(f: T => T): Iterator[T] = new AbstractIterator[T] { + def iterate[T](start: T)(f: T => T): Iterator[T]^{f} = new AbstractIterator[T] { private[this] var first = true private[this] var acc = start def hasNext: Boolean = true @@ -1130,7 +1134,7 @@ object Iterator extends IterableFactory[Iterator] { * @tparam S Type of the internal state * @return an Iterator that produces elements using `f` until `f` returns `None` */ - override def unfold[A, S](init: S)(f: S => Option[(A, S)]): Iterator[A] = new UnfoldIterator(init)(f) + override def unfold[A, S](init: S)(f: S => Option[(A, S)]): Iterator[A]^{f} = new UnfoldIterator(init)(f) /** Creates an infinite-length iterator returning the results of evaluating an expression. * The expression is recomputed for every element. @@ -1138,7 +1142,7 @@ object Iterator extends IterableFactory[Iterator] { * @param elem the element computation. * @return the iterator containing an infinite number of results of evaluating `elem`. */ - def continually[A](elem: => A): Iterator[A] = new AbstractIterator[A] { + def continually[A](elem: => A): Iterator[A]^{elem} = new AbstractIterator[A] { def hasNext = true def next() = elem } @@ -1146,7 +1150,8 @@ object Iterator extends IterableFactory[Iterator] { /** Creates an iterator to which other iterators can be appended efficiently. * Nested ConcatIterators are merged to avoid blowing the stack. */ - private final class ConcatIterator[+A](private var current: Iterator[A @uncheckedVariance]) extends AbstractIterator[A] { + private final class ConcatIterator[+A](val from: Iterator[A @uncheckedVariance]^) extends AbstractIterator[A] { + private var current: Iterator[A]^{from} = from private var tail: ConcatIteratorCell[A @uncheckedVariance] = null private var last: ConcatIteratorCell[A @uncheckedVariance] = null private var currentHasNextChecked = false @@ -1162,7 +1167,7 @@ object Iterator extends IterableFactory[Iterator] { // If we advanced the current iterator to a ConcatIterator, merge it into this one @tailrec def merge(): Unit = if (current.isInstanceOf[ConcatIterator[_]]) { - val c = current.asInstanceOf[ConcatIterator[A]] + val c = current.asInstanceOf[ConcatIterator[A]^{from}] current = c.current currentHasNextChecked = c.currentHasNextChecked if (c.tail != null) { @@ -1202,7 +1207,7 @@ object Iterator extends IterableFactory[Iterator] { current.next() } else Iterator.empty.next() - override def concat[B >: A](that: => IterableOnce[B]): Iterator[B] = { + override def concat[B >: A](that: => IterableOnce[B]^): Iterator[B]^{this, that} = { val c = new ConcatIteratorCell[B](that, null).asInstanceOf[ConcatIteratorCell[A]] if (tail == null) { tail = c @@ -1217,14 +1222,14 @@ object Iterator extends IterableFactory[Iterator] { } } - private[this] final class ConcatIteratorCell[A](head: => IterableOnce[A], var tail: ConcatIteratorCell[A]) { - def headIterator: Iterator[A] = head.iterator + private[this] final class ConcatIteratorCell[A](head: => IterableOnce[A]^, var tail: ConcatIteratorCell[A]^) { + def headIterator: Iterator[A]^{this} = head.iterator } /** Creates a delegating iterator capped by a limit count. Negative limit means unbounded. * Lazily skip to start on first evaluation. Avoids daisy-chained iterators due to slicing. */ - private[scala] final class SliceIterator[A](val underlying: Iterator[A], start: Int, limit: Int) extends AbstractIterator[A] { + private[scala] final class SliceIterator[A](val underlying: Iterator[A]^, start: Int, limit: Int) extends AbstractIterator[A] { private[this] var remaining = limit private[this] var dropping = start @inline private def unbounded = remaining < 0 @@ -1255,7 +1260,7 @@ object Iterator extends IterableFactory[Iterator] { else if (unbounded) underlying.next() else empty.next() } - override protected def sliceIterator(from: Int, until: Int): Iterator[A] = { + override protected def sliceIterator(from: Int, until: Int): Iterator[A]^{this} = { val lo = from max 0 def adjustedBound = if (unbounded) -1 diff --git a/library/src/scala/collection/mutable/Buffer.scala b/library/src/scala/collection/mutable/Buffer.scala index 333d86fce772..c3ede915ae3d 100644 --- a/library/src/scala/collection/mutable/Buffer.scala +++ b/library/src/scala/collection/mutable/Buffer.scala @@ -27,7 +27,7 @@ trait Buffer[A] with SeqOps[A, Buffer, Buffer[A]] with Growable[A] with Shrinkable[A] - with IterableFactoryDefaults[A, Buffer] { + with IterableFactoryDefaults[A, Buffer] { self => override def iterableFactory: SeqFactory[Buffer] = Buffer From 83c3d011cafc32929d3d227e2158539f31e1c2a7 Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Tue, 12 Aug 2025 18:03:05 +0200 Subject: [PATCH 049/101] Add this capture to Iterator methods --- library/src/scala/collection/ArrayOps.scala | 2 +- library/src/scala/collection/Iterable.scala | 8 ++-- library/src/scala/collection/Iterator.scala | 52 ++++++++++----------- 3 files changed, 31 insertions(+), 31 deletions(-) diff --git a/library/src/scala/collection/ArrayOps.scala b/library/src/scala/collection/ArrayOps.scala index 2ed401423857..e90a4cbc52ad 100644 --- a/library/src/scala/collection/ArrayOps.scala +++ b/library/src/scala/collection/ArrayOps.scala @@ -370,7 +370,7 @@ final class ArrayOps[A](private val xs: Array[A]) extends AnyVal { def inits: Iterator[Array[A]] = iterateUntilEmpty(xs => new ArrayOps(xs).init) // A helper for tails and inits. - private[this] def iterateUntilEmpty(f: Array[A] => Array[A]): Iterator[Array[A]] = + private[this] def iterateUntilEmpty(f: Array[A] => Array[A]): Iterator[Array[A]]^{f} = Iterator.iterate(xs)(f).takeWhile(x => x.length != 0) ++ Iterator.single(Array.empty[A]) /** An array containing the first `n` elements of this array. */ diff --git a/library/src/scala/collection/Iterable.scala b/library/src/scala/collection/Iterable.scala index b30c6448418f..9e804bd07c50 100644 --- a/library/src/scala/collection/Iterable.scala +++ b/library/src/scala/collection/Iterable.scala @@ -479,7 +479,7 @@ transparent trait IterableOps[+A, +CC[_], +C] extends Any with IterableOnce[A] w * @return An iterator producing ${coll}s of size `size`, except the * last will be less than size `size` if the elements don't divide evenly. */ - def grouped(size: Int): Iterator[C] = + def grouped(size: Int): Iterator[C]^{this} = iterator.grouped(size).map(fromSpecific) /** Groups elements in fixed size blocks by passing a "sliding window" @@ -501,7 +501,7 @@ transparent trait IterableOps[+A, +CC[_], +C] extends Any with IterableOnce[A] w * @example `List(1, 2).sliding(2) = Iterator(List(1, 2))` * @example `List(1, 2, 3).sliding(2) = Iterator(List(1, 2), List(2, 3))` */ - def sliding(size: Int): Iterator[C] = sliding(size, 1) + def sliding(size: Int): Iterator[C]^{this} = sliding(size, 1) /** Groups elements in fixed size blocks by passing a "sliding window" * over them (as opposed to partitioning them, as is done in `grouped`). @@ -522,7 +522,7 @@ transparent trait IterableOps[+A, +CC[_], +C] extends Any with IterableOnce[A] w * @example `List(1, 2, 3, 4, 5).sliding(2, 2) = Iterator(List(1, 2), List(3, 4), List(5))` * @example `List(1, 2, 3, 4, 5, 6).sliding(2, 3) = Iterator(List(1, 2), List(4, 5))` */ - def sliding(size: Int, step: Int): Iterator[C] = + def sliding(size: Int, step: Int): Iterator[C]^{this} = iterator.sliding(size, step).map(fromSpecific) /** The rest of the collection without its first element. */ @@ -747,7 +747,7 @@ transparent trait IterableOps[+A, +CC[_], +C] extends Any with IterableOnce[A] w * @return a new $ccoll containing pairs consisting of corresponding elements of this $coll and `that`. * The length of the returned collection is the minimum of the lengths of this $coll and `that`. */ - def zip[B](that: IterableOnce[B]^): CC[(A @uncheckedVariance, B)]^{that} = iterableFactory.from(that match { // sound bcs of VarianceNote + def zip[B](that: IterableOnce[B]^): CC[(A @uncheckedVariance, B)]^{this, that} = iterableFactory.from(that match { // sound bcs of VarianceNote case that: Iterable[B] => new View.Zip(this, that) case _ => iterator.zip(that) }) diff --git a/library/src/scala/collection/Iterator.scala b/library/src/scala/collection/Iterator.scala index 752adb1fba8e..6a6132990f76 100644 --- a/library/src/scala/collection/Iterator.scala +++ b/library/src/scala/collection/Iterator.scala @@ -121,7 +121,7 @@ trait Iterator[+A] extends IterableOnce[A] with IterableOnceOps[A, Iterator, Ite * @return a buffered iterator producing the same values as this iterator. * @note Reuse: $consumesAndProducesIterator */ - def buffered: BufferedIterator[A] = new AbstractIterator[A] with BufferedIterator[A] { + def buffered: BufferedIterator[A]^{this} = new AbstractIterator[A] with BufferedIterator[A] { private[this] var hd: A = _ private[this] var hdDefined: Boolean = false @@ -296,7 +296,7 @@ trait Iterator[+A] extends IterableOnce[A] with IterableOnceOps[A, Iterator, Ite * all elements of this $coll followed by the minimal number of occurrences of `elem` so * that the resulting collection has a length of at least `len`. */ - def padTo[B >: A](len: Int, elem: B): Iterator[B] = new AbstractIterator[B] { + def padTo[B >: A](len: Int, elem: B): Iterator[B]^{this} = new AbstractIterator[B] { private[this] var i = 0 override def knownSize: Int = { @@ -326,7 +326,7 @@ trait Iterator[+A] extends IterableOnce[A] with IterableOnceOps[A, Iterator, Ite * is the same as in the original iterator. * @note Reuse: $consumesOneAndProducesTwoIterators */ - def partition(p: A => Boolean): (Iterator[A]^{p}, Iterator[A]^{p}) = { + def partition(p: A => Boolean): (Iterator[A]^{this, p}, Iterator[A]^{this, p}) = { val (a, b) = duplicate (a filter p, b filterNot p) } @@ -385,10 +385,10 @@ trait Iterator[+A] extends IterableOnce[A] with IterableOnceOps[A, Iterator, Ite def sliding[B >: A](size: Int, step: Int = 1): GroupedIterator[B]^{this} = new GroupedIterator[B](self, size, step) - def scanLeft[B](z: B)(op: (B, A) => B): Iterator[B]^{op} = new AbstractIterator[B] { + def scanLeft[B](z: B)(op: (B, A) => B): Iterator[B]^{this, op} = new AbstractIterator[B] { // We use an intermediate iterator that iterates through the first element `z` // and then that will be modified to iterate through the collection - private[this] var current: Iterator[B]^{op} = + private[this] var current: Iterator[B]^{self, op} = new AbstractIterator[B] { override def knownSize = { val thisSize = self.knownSize @@ -417,7 +417,7 @@ trait Iterator[+A] extends IterableOnce[A] with IterableOnceOps[A, Iterator, Ite } @deprecated("Call scanRight on an Iterable instead.", "2.13.0") - def scanRight[B](z: B)(op: (A, B) => B): Iterator[B] = ArrayBuffer.from(this).scanRight(z)(op).iterator + def scanRight[B](z: B)(op: (A, B) => B): Iterator[B]^{this, op} = ArrayBuffer.from(this).scanRight(z)(op).iterator /** Finds index of the first element satisfying some predicate after or at some start index. * @@ -480,11 +480,11 @@ trait Iterator[+A] extends IterableOnce[A] with IterableOnceOps[A, Iterator, Ite @deprecatedOverriding("isEmpty is defined as !hasNext; override hasNext instead", "2.13.0") override def isEmpty: Boolean = !hasNext - def filter(p: A => Boolean): Iterator[A]^{p} = filterImpl(p, isFlipped = false) + def filter(p: A => Boolean): Iterator[A]^{this, p} = filterImpl(p, isFlipped = false) - def filterNot(p: A => Boolean): Iterator[A]^{p} = filterImpl(p, isFlipped = true) + def filterNot(p: A => Boolean): Iterator[A]^{this, p} = filterImpl(p, isFlipped = true) - private[collection] def filterImpl(p: A => Boolean, isFlipped: Boolean): Iterator[A]^{p} = new AbstractIterator[A] { + private[collection] def filterImpl(p: A => Boolean, isFlipped: Boolean): Iterator[A]^{this, p} = new AbstractIterator[A] { private[this] var hd: A = _ private[this] var hdDefined: Boolean = false @@ -518,9 +518,9 @@ trait Iterator[+A] extends IterableOnce[A] with IterableOnceOps[A, Iterator, Ite * @return an iterator which produces those values of this iterator which satisfy the predicate `p`. * @note Reuse: $consumesAndProducesIterator */ - def withFilter(p: A => Boolean): Iterator[A]^{p} = filter(p) + def withFilter(p: A => Boolean): Iterator[A]^{this, p} = filter(p) - def collect[B](pf: PartialFunction[A, B]^): Iterator[B]^{pf} = new AbstractIterator[B] with (A => B) { + def collect[B](pf: PartialFunction[A, B]^): Iterator[B]^{this, pf} = new AbstractIterator[B] with (A => B) { // Manually buffer to avoid extra layer of wrapping with buffered private[this] var hd: B = _ @@ -556,7 +556,7 @@ trait Iterator[+A] extends IterableOnce[A] with IterableOnceOps[A, Iterator, Ite * * @note Reuse: $consumesIterator */ - def distinct: Iterator[A] = distinctBy(identity) + def distinct: Iterator[A]^{this} = distinctBy(identity) /** * Builds a new iterator from this one without any duplicated elements as determined by `==` after applying @@ -568,7 +568,7 @@ trait Iterator[+A] extends IterableOnce[A] with IterableOnceOps[A, Iterator, Ite * * @note Reuse: $consumesIterator */ - def distinctBy[B](f: A -> B): Iterator[A] = new AbstractIterator[A] { + def distinctBy[B](f: A -> B): Iterator[A]^{this} = new AbstractIterator[A] { private[this] val traversedValues = mutable.HashSet.empty[B] private[this] var nextElementDefined: Boolean = false @@ -593,13 +593,13 @@ trait Iterator[+A] extends IterableOnce[A] with IterableOnceOps[A, Iterator, Ite } } - def map[B](f: A => B): Iterator[B]^{f} = new AbstractIterator[B] { + def map[B](f: A => B): Iterator[B]^{this, f} = new AbstractIterator[B] { override def knownSize = self.knownSize def hasNext = self.hasNext def next() = f(self.next()) } - def flatMap[B](f: A => IterableOnce[B]^): Iterator[B]^{f} = new AbstractIterator[B] { + def flatMap[B](f: A => IterableOnce[B]^): Iterator[B]^{this, f} = new AbstractIterator[B] { private[this] var cur: Iterator[B]^{f} = Iterator.empty /** Trillium logic boolean: -1 = unknown, 0 = false, 1 = true */ private[this] var _hasNext: Int = -1 @@ -634,7 +634,7 @@ trait Iterator[+A] extends IterableOnce[A] with IterableOnceOps[A, Iterator, Ite } } - def flatten[B](implicit ev: A -> IterableOnce[B]): Iterator[B] = + def flatten[B](implicit ev: A -> IterableOnce[B]): Iterator[B]^{this} = flatMap[B](ev) def concat[B >: A](xs: => IterableOnce[B]^): Iterator[B]^{this, xs} = new Iterator.ConcatIterator[B](self).concat(xs) @@ -643,7 +643,7 @@ trait Iterator[+A] extends IterableOnce[A] with IterableOnceOps[A, Iterator, Ite def take(n: Int): Iterator[A]^{this} = sliceIterator(0, n max 0) - def takeWhile(p: A => Boolean): Iterator[A]^{p} = new AbstractIterator[A] { + def takeWhile(p: A => Boolean): Iterator[A]^{this, p} = new AbstractIterator[A] { private[this] var hd: A = _ private[this] var hdDefined: Boolean = false private[this] var tail: Iterator[A]^{self} = self @@ -659,7 +659,7 @@ trait Iterator[+A] extends IterableOnce[A] with IterableOnceOps[A, Iterator, Ite def drop(n: Int): Iterator[A]^{this} = sliceIterator(n, -1) - def dropWhile(p: A => Boolean): Iterator[A]^{p} = new AbstractIterator[A] { + def dropWhile(p: A => Boolean): Iterator[A]^{this, p} = new AbstractIterator[A] { // Magic value: -1 = hasn't dropped, 0 = found first, 1 = defer to parent iterator private[this] var status = -1 // Local buffering to avoid double-wrap with .buffered @@ -695,7 +695,7 @@ trait Iterator[+A] extends IterableOnce[A] with IterableOnceOps[A, Iterator, Ite * * @note Reuse: $consumesOneAndProducesTwoIterators */ - def span(p: A => Boolean): (Iterator[A]^{p}, Iterator[A]^{p}) = { + def span(p: A => Boolean): (Iterator[A]^{this, p}, Iterator[A]^{this, p}) = { /* * Giving a name to following iterator (as opposed to trailing) because * anonymous class is represented as a structural type that trailing @@ -808,14 +808,14 @@ trait Iterator[+A] extends IterableOnce[A] with IterableOnceOps[A, Iterator, Ite else new Iterator.SliceIterator(this, lo, rest) } - def zip[B](that: IterableOnce[B]^): Iterator[(A, B)]^{that} = new AbstractIterator[(A, B)] { + def zip[B](that: IterableOnce[B]^): Iterator[(A, B)]^{this, that} = new AbstractIterator[(A, B)] { val thatIterator = that.iterator override def knownSize = self.knownSize min thatIterator.knownSize def hasNext = self.hasNext && thatIterator.hasNext def next() = (self.next(), thatIterator.next()) } - def zipAll[A1 >: A, B](that: IterableOnce[B], thisElem: A1, thatElem: B): Iterator[(A1, B)] = new AbstractIterator[(A1, B)] { + def zipAll[A1 >: A, B](that: IterableOnce[B]^, thisElem: A1, thatElem: B): Iterator[(A1, B)]^{this, that} = new AbstractIterator[(A1, B)] { val thatIterator = that.iterator override def knownSize = { val thisSize = self.knownSize @@ -832,7 +832,7 @@ trait Iterator[+A] extends IterableOnce[A] with IterableOnceOps[A, Iterator, Ite } } - def zipWithIndex: Iterator[(A, Int)] = new AbstractIterator[(A, Int)] { + def zipWithIndex: Iterator[(A, Int)]^{this} = new AbstractIterator[(A, Int)] { var idx = 0 override def knownSize = self.knownSize def hasNext = self.hasNext @@ -850,7 +850,7 @@ trait Iterator[+A] extends IterableOnce[A] with IterableOnceOps[A, Iterator, Ite * @tparam B the type of the elements of collection `that`. * @return `true` if both collections contain equal elements in the same order, `false` otherwise. */ - def sameElements[B >: A](that: IterableOnce[B]): Boolean = { + def sameElements[B >: A](that: IterableOnce[B]^): Boolean = { val those = that.iterator while (hasNext) { if (!those.hasNext) return false @@ -872,7 +872,7 @@ trait Iterator[+A] extends IterableOnce[A] with IterableOnceOps[A, Iterator, Ite * iterated by one iterator but not yet by the other. * @note Reuse: $consumesOneAndProducesTwoIterators */ - def duplicate: (Iterator[A], Iterator[A]) = { + def duplicate: (Iterator[A]^{this}, Iterator[A]^{this}) = { val gap = new scala.collection.mutable.Queue[A] var ahead: Iterator[A] = null class Partner extends AbstractIterator[A] { @@ -916,7 +916,7 @@ trait Iterator[+A] extends IterableOnce[A] with IterableOnceOps[A, Iterator, Ite * @param replaced The number of values in the original iterator that are replaced by the patch. * @note Reuse: $consumesTwoAndProducesOneIterator */ - def patch[B >: A](from: Int, patchElems: Iterator[B], replaced: Int): Iterator[B] = + def patch[B >: A](from: Int, patchElems: Iterator[B]^, replaced: Int): Iterator[B]^{this, patchElems} = new AbstractIterator[B] { private[this] var origElems = self // > 0 => that many more elems from `origElems` before switching to `patchElems` @@ -956,7 +956,7 @@ trait Iterator[+A] extends IterableOnce[A] with IterableOnceOps[A, Iterator, Ite } } - override def tapEach[U](f: A => U): Iterator[A]^{f} = new AbstractIterator[A] { + override def tapEach[U](f: A => U): Iterator[A]^{this, f} = new AbstractIterator[A] { override def knownSize = self.knownSize override def hasNext = self.hasNext override def next() = { From 117b9ab8f061300ab5e30fe894e70dac19071a0c Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Tue, 12 Aug 2025 19:21:33 +0200 Subject: [PATCH 050/101] Add capture checking to View --- library/src/scala/PartialFunction.scala | 2 +- library/src/scala/collection/BuildFrom.scala | 2 +- library/src/scala/collection/Factory.scala | 8 +- library/src/scala/collection/Iterable.scala | 68 ++++++------ library/src/scala/collection/View.scala | 105 ++++++++++--------- 5 files changed, 96 insertions(+), 89 deletions(-) diff --git a/library/src/scala/PartialFunction.scala b/library/src/scala/PartialFunction.scala index 31f3fbe77a83..d5379a96479c 100644 --- a/library/src/scala/PartialFunction.scala +++ b/library/src/scala/PartialFunction.scala @@ -263,7 +263,7 @@ object PartialFunction { final class ElementWiseExtractor[-A, +B] private[PartialFunction] (private val pf: PartialFunction[A, B]^) extends AnyVal { this: ElementWiseExtractor[A, B]^ => @nowarn("cat=lint-nonlocal-return") - def unapplySeq(seq: Seq[A]): Option[Seq[B]] = { + def unapplySeq(seq: Seq[A]): Option[Seq[B]^{this}] = { // TODO remove when Seq is capture-checked Some(seq.map { case pf(b) => b case _ => return None diff --git a/library/src/scala/collection/BuildFrom.scala b/library/src/scala/collection/BuildFrom.scala index 3f0d30eb0692..8ee65badf1a4 100644 --- a/library/src/scala/collection/BuildFrom.scala +++ b/library/src/scala/collection/BuildFrom.scala @@ -86,7 +86,7 @@ object BuildFrom extends BuildFromLowPriority1 { implicit def buildFromView[A, B]: BuildFrom[View[A], B, View[B]] = new BuildFrom[View[A], B, View[B]] { - def fromSpecific(from: View[A])(it: IterableOnce[B]^): View[B] = View.from(it) + def fromSpecific(from: View[A])(it: IterableOnce[B]^): View[B]^{it} = View.from(it) def newBuilder(from: View[A]): Builder[B, View[B]] = View.newBuilder } diff --git a/library/src/scala/collection/Factory.scala b/library/src/scala/collection/Factory.scala index fb742ce1ac4c..7e0ff9ede393 100644 --- a/library/src/scala/collection/Factory.scala +++ b/library/src/scala/collection/Factory.scala @@ -32,7 +32,7 @@ import scala.reflect.ClassTag * @tparam A Type of elements (e.g. `Int`, `Boolean`, etc.) * @tparam C Type of collection (e.g. `List[Int]`, `TreeMap[Int, String]`, etc.) */ -trait Factory[-A, +C] extends Any { this: Factory[A, C] => +trait Factory[-A, +C] extends Pure { /** * @return A collection of type `C` containing the same elements @@ -83,7 +83,7 @@ object Factory { * @define coll collection * @define Coll `Iterable` */ -trait IterableFactory[+CC[_]] extends Serializable { this: IterableFactory[CC] => +trait IterableFactory[+CC[_]] extends Serializable, Pure { /** Creates a target $coll from an existing source collection * @@ -457,9 +457,9 @@ object MapFactory { * @define coll collection * @define Coll `Iterable` */ -trait EvidenceIterableFactory[+CC[_], Ev[_]] extends Serializable { this: EvidenceIterableFactory[CC, Ev] => +trait EvidenceIterableFactory[+CC[_], Ev[_]] extends Serializable, Pure { - def from[E : Ev](it: IterableOnce[E]^): CC[E]^{it} + def from[E : Ev](it: IterableOnce[E]^): CC[E] def empty[A : Ev]: CC[A] diff --git a/library/src/scala/collection/Iterable.scala b/library/src/scala/collection/Iterable.scala index 9e804bd07c50..2a2df1261b1d 100644 --- a/library/src/scala/collection/Iterable.scala +++ b/library/src/scala/collection/Iterable.scala @@ -344,7 +344,7 @@ transparent trait IterableOps[+A, +CC[_], +C] extends Any with IterableOnce[A] w /** A view over a slice of the elements of this collection. */ @deprecated("Use .view.slice(from, until) instead of .view(from, until)", "2.13.0") - def view(from: Int, until: Int): View[A] = view.slice(from, until) + def view(from: Int, until: Int): View[A]^{this} = view.slice(from, until) /** Transposes this $coll of iterable collections into * a $coll of ${coll}s. @@ -447,7 +447,7 @@ transparent trait IterableOps[+A, +CC[_], +C] extends Any with IterableOnce[A] w * or else the whole $coll, if it has less than `n` elements. * If `n` is negative, returns an empty $coll. */ - def takeRight(n: Int): C = fromSpecific(new View.TakeRight(this, n)) + def takeRight(n: Int): C^{this} = fromSpecific(new View.TakeRight(this, n)) /** Takes longest prefix of elements that satisfy a predicate. * $orderDependent @@ -455,11 +455,11 @@ transparent trait IterableOps[+A, +CC[_], +C] extends Any with IterableOnce[A] w * @return the longest prefix of this $coll whose elements all satisfy * the predicate `p`. */ - def takeWhile(p: A => Boolean): C = fromSpecific(new View.TakeWhile(this, p)) + def takeWhile(p: A => Boolean): C^{this, p} = fromSpecific(new View.TakeWhile(this, p)) - def span(p: A => Boolean): (C, C) = (takeWhile(p), dropWhile(p)) + def span(p: A => Boolean): (C^{this, p}, C^{this, p}) = (takeWhile(p), dropWhile(p)) - def drop(n: Int): C = fromSpecific(new View.Drop(this, n)) + def drop(n: Int): C^{this} = fromSpecific(new View.Drop(this, n)) /** Selects all elements except last ''n'' ones. * $orderDependent @@ -467,15 +467,15 @@ transparent trait IterableOps[+A, +CC[_], +C] extends Any with IterableOnce[A] w * @return a $coll consisting of all elements of this $coll except the last `n` ones, or else the * empty $coll, if this $coll has less than `n` elements. * If `n` is negative, don't drop any elements. - */ - def dropRight(n: Int): C = fromSpecific(new View.DropRight(this, n)) + ^{this} */ + def dropRight(n: Int): C^{this} = fromSpecific(new View.DropRight(this, n)) - def dropWhile(p: A => Boolean): C = fromSpecific(new View.DropWhile(this, p)) + def dropWhile(p: A => Boolean): C^{this, p} = fromSpecific(new View.DropWhile(this, p)) /** Partitions elements in fixed size ${coll}s. * @see [[scala.collection.Iterator]], method `grouped` * - * @param size the number of elements per group + * @param si^{this}ze the number of elements per group * @return An iterator producing ${coll}s of size `size`, except the * last will be less than size `size` if the elements don't divide evenly. */ @@ -526,7 +526,7 @@ transparent trait IterableOps[+A, +CC[_], +C] extends Any with IterableOnce[A] w iterator.sliding(size, step).map(fromSpecific) /** The rest of the collection without its first element. */ - def tail: C = { + def tail: C^{this} = { if (isEmpty) throw new UnsupportedOperationException drop(1) } @@ -534,12 +534,12 @@ transparent trait IterableOps[+A, +CC[_], +C] extends Any with IterableOnce[A] w /** The initial part of the collection without its last element. * $willForceEvaluation */ - def init: C = { + def init: C^{this} = { if (isEmpty) throw new UnsupportedOperationException dropRight(1) } - def slice(from: Int, until: Int): C = + def slice(from: Int, until: Int): C^{this} = fromSpecific(new View.Drop(new View.Take(this, until), from)) /** Partitions this $coll into a map of ${coll}s according to some discriminator function. @@ -649,9 +649,9 @@ transparent trait IterableOps[+A, +CC[_], +C] extends Any with IterableOnce[A] w * * @return a new $coll containing the prefix scan of the elements in this $coll */ - def scan[B >: A](z: B)(op: (B, B) => B): CC[B] = scanLeft(z)(op) + def scan[B >: A](z: B)(op: (B, B) => B): CC[B]^{this, op} = scanLeft(z)(op) - def scanLeft[B](z: B)(op: (B, A) => B): CC[B] = iterableFactory.from(new View.ScanLeft(this, z, op)) + def scanLeft[B](z: B)(op: (B, A) => B): CC[B]^{this, op} = iterableFactory.from(new View.ScanLeft(this, z, op)) /** Produces a collection containing cumulative results of applying the operator going right to left. * The head of the collection is the last cumulative result. @@ -683,13 +683,13 @@ transparent trait IterableOps[+A, +CC[_], +C] extends Any with IterableOnce[A] w iterableFactory.from(scanner.scanned) } - def map[B](f: A => B): CC[B] = iterableFactory.from(new View.Map(this, f)) + def map[B](f: A => B): CC[B]^{this, f} = iterableFactory.from(new View.Map(this, f)) - def flatMap[B](f: A => IterableOnce[B]^): CC[B] = iterableFactory.from(new View.FlatMap(this, f)) + def flatMap[B](f: A => IterableOnce[B]^): CC[B]^{this, f} = iterableFactory.from(new View.FlatMap(this, f)) - def flatten[B](implicit asIterable: A -> IterableOnce[B]): CC[B] = flatMap(asIterable) + def flatten[B](implicit asIterable: A -> IterableOnce[B]): CC[B]^{this} = flatMap(asIterable) - def collect[B](pf: PartialFunction[A, B]^): CC[B] = + def collect[B](pf: PartialFunction[A, B]^): CC[B]^{this, pf} = iterableFactory.from(new View.Collect(this, pf)) /** Applies a function `f` to each element of the $coll and returns a pair of ${coll}s: the first one @@ -713,9 +713,9 @@ transparent trait IterableOps[+A, +CC[_], +C] extends Any with IterableOnce[A] w * @return a pair of ${coll}s: the first one made of those values returned by `f` that were wrapped in [[scala.util.Left]], * and the second one made of those wrapped in [[scala.util.Right]]. */ - def partitionMap[A1, A2](f: A => Either[A1, A2]): (CC[A1], CC[A2]) = { - val left: View[A1] = new LeftPartitionMapped(this, f) - val right: View[A2] = new RightPartitionMapped(this, f) + def partitionMap[A1, A2](f: A => Either[A1, A2]): (CC[A1]^{this, f}, CC[A2]^{this, f}) = { + val left: View[A1]^{this, f} = new LeftPartitionMapped(this, f) + val right: View[A2]^{this, f} = new RightPartitionMapped(this, f) (iterableFactory.from(left), iterableFactory.from(right)) } @@ -752,7 +752,7 @@ transparent trait IterableOps[+A, +CC[_], +C] extends Any with IterableOnce[A] w case _ => iterator.zip(that) }) - def zipWithIndex: CC[(A @uncheckedVariance, Int)] = iterableFactory.from(new View.ZipWithIndex(this)) + def zipWithIndex: CC[(A @uncheckedVariance, Int)]^{this} = iterableFactory.from(new View.ZipWithIndex(this)) /** Returns a $coll formed from this $coll and another iterable collection * by combining corresponding elements in pairs. @@ -768,7 +768,7 @@ transparent trait IterableOps[+A, +CC[_], +C] extends Any with IterableOnce[A] w * If this $coll is shorter than `that`, `thisElem` values are used to pad the result. * If `that` is shorter than this $coll, `thatElem` values are used to pad the result. */ - def zipAll[A1 >: A, B](that: Iterable[B]^, thisElem: A1, thatElem: B): CC[(A1, B)] = iterableFactory.from(new View.ZipAll(this, that, thisElem, thatElem)) + def zipAll[A1 >: A, B](that: Iterable[B]^, thisElem: A1, thatElem: B): CC[(A1, B)]^{this, that} = iterableFactory.from(new View.ZipAll(this, that, thisElem, thatElem)) /** Converts this $coll of pairs into two collections of the first and second * half of each pair. @@ -789,9 +789,9 @@ transparent trait IterableOps[+A, +CC[_], +C] extends Any with IterableOnce[A] w * @return a pair of ${coll}s, containing the first, respectively second * half of each element pair of this $coll. */ - def unzip[A1, A2](implicit asPair: A -> (A1, A2)): (CC[A1], CC[A2]) = { - val first: View[A1] = new View.Map[A, A1](this, asPair(_)._1) - val second: View[A2] = new View.Map[A, A2](this, asPair(_)._2) + def unzip[A1, A2](implicit asPair: A -> (A1, A2)): (CC[A1]^{this}, CC[A2]^{this}) = { + val first: View[A1]^{this} = new View.Map[A, A1](this, asPair(_)._1) + val second: View[A2]^{this} = new View.Map[A, A2](this, asPair(_)._2) (iterableFactory.from(first), iterableFactory.from(second)) } @@ -816,10 +816,10 @@ transparent trait IterableOps[+A, +CC[_], +C] extends Any with IterableOnce[A] w * @return a triple of ${coll}s, containing the first, second, respectively * third member of each element triple of this $coll. */ - def unzip3[A1, A2, A3](implicit asTriple: A -> (A1, A2, A3)): (CC[A1], CC[A2], CC[A3]) = { - val first: View[A1] = new View.Map[A, A1](this, asTriple(_)._1) - val second: View[A2] = new View.Map[A, A2](this, asTriple(_)._2) - val third: View[A3] = new View.Map[A, A3](this, asTriple(_)._3) + def unzip3[A1, A2, A3](implicit asTriple: A -> (A1, A2, A3)): (CC[A1]^{this}, CC[A2]^{this}, CC[A3]^{this}) = { + val first: View[A1]^{this} = new View.Map[A, A1](this, asTriple(_)._1) + val second: View[A2]^{this} = new View.Map[A, A2](this, asTriple(_)._2) + val third: View[A3]^{this} = new View.Map[A, A3](this, asTriple(_)._3) (iterableFactory.from(first), iterableFactory.from(second), iterableFactory.from(third)) } @@ -843,7 +843,7 @@ transparent trait IterableOps[+A, +CC[_], +C] extends Any with IterableOnce[A] w */ def inits: Iterator[C^{this}]^{this} = iterateUntilEmpty(_.init) - override def tapEach[U](f: A => U): C = fromSpecific(new View.Map(this, { (a: A) => f(a); a })) + override def tapEach[U](f: A => U): C^{this, f} = fromSpecific(new View.Map(this, { (a: A) => f(a); a })) // A helper for tails and inits. private[this] def iterateUntilEmpty(f: Iterable[A]^{this} => Iterable[A]^{this}): Iterator[C^{this}]^{this, f} = { @@ -904,13 +904,13 @@ object IterableOps { p: A => Boolean ) extends collection.WithFilter[A, CC] with Serializable { - protected def filtered: Iterable[A] = + protected def filtered: Iterable[A]^{this} = new View.Filter(self, p, isFlipped = false) - def map[B](f: A => B): CC[B] = + def map[B](f: A => B): CC[B]^{this, f} = self.iterableFactory.from(new View.Map(filtered, f)) - def flatMap[B](f: A => IterableOnce[B]): CC[B] = + def flatMap[B](f: A => IterableOnce[B]^): CC[B]^{this, f} = self.iterableFactory.from(new View.FlatMap(filtered, f)) def foreach[U](f: A => U): Unit = filtered.foreach(f) diff --git a/library/src/scala/collection/View.scala b/library/src/scala/collection/View.scala index 690ba49aafe8..6b862aece4ea 100644 --- a/library/src/scala/collection/View.scala +++ b/library/src/scala/collection/View.scala @@ -13,9 +13,12 @@ package scala.collection import scala.language.`2.13` +import language.experimental.captureChecking + import scala.annotation.{nowarn, tailrec} import scala.collection.mutable.{ArrayBuffer, Builder} import scala.collection.immutable.LazyList +import caps.unsafe.unsafeAssumePure /** Views are collections whose transformation operations are non strict: the resulting elements * are evaluated only when the view is effectively traversed (e.g. using `foreach` or `foldLeft`), @@ -25,7 +28,7 @@ import scala.collection.immutable.LazyList */ trait View[+A] extends Iterable[A] with IterableOps[A, View, View[A]] with IterableFactoryDefaults[A, View] with Serializable { - override def view: View[A] = this + override def view: View[A]^{this} = this override def iterableFactory: IterableFactory[View] = View @@ -56,7 +59,7 @@ object View extends IterableFactory[View] { * * @tparam A View element type */ - def fromIteratorProvider[A](it: () => Iterator[A]): View[A] = new AbstractView[A] { + def fromIteratorProvider[A](it: () => Iterator[A]^): View[A]^{it} = new AbstractView[A] { def iterator = it() } @@ -68,10 +71,10 @@ object View extends IterableFactory[View] { * * @tparam E View element type */ - def from[E](it: IterableOnce[E]): View[E] = it match { - case it: View[E] => it - case it: Iterable[E] => View.fromIteratorProvider(() => it.iterator) - case _ => LazyList.from(it).view + def from[E](it: IterableOnce[E]^): View[E]^{it} = it match { + case it: (View[E]^{it}) => it + case it: (Iterable[E]^{it}) => View.fromIteratorProvider(() => it.iterator) + case _ => LazyList.from(it).view } def empty[A]: View[A] = Empty @@ -115,7 +118,7 @@ object View extends IterableFactory[View] { /** A view containing values of a given function over a range of integer values starting from 0. */ @SerialVersionUID(3L) class Tabulate[A](n: Int)(f: Int => A) extends AbstractView[A] { - def iterator: Iterator[A] = Iterator.tabulate(n)(f) + def iterator: Iterator[A]^{f} = Iterator.tabulate(n)(f) override def knownSize: Int = 0 max n override def isEmpty: Boolean = n <= 0 } @@ -123,7 +126,7 @@ object View extends IterableFactory[View] { /** A view containing repeated applications of a function to a start value */ @SerialVersionUID(3L) class Iterate[A](start: A, len: Int)(f: A => A) extends AbstractView[A] { - def iterator: Iterator[A] = Iterator.iterate(start)(f).take(len) + def iterator: Iterator[A]^{f} = Iterator.iterate(start)(f).take(len) override def knownSize: Int = 0 max len override def isEmpty: Boolean = len <= 0 } @@ -133,7 +136,7 @@ object View extends IterableFactory[View] { */ @SerialVersionUID(3L) class Unfold[A, S](initial: S)(f: S => Option[(A, S)]) extends AbstractView[A] { - def iterator: Iterator[A] = Iterator.unfold(initial)(f) + def iterator: Iterator[A]^{f} = Iterator.unfold(initial)(f) } /** An `IterableOps` whose collection type and collection type constructor are unknown */ @@ -141,30 +144,34 @@ object View extends IterableFactory[View] { /** A view that filters an underlying collection. */ @SerialVersionUID(3L) - class Filter[A](val underlying: SomeIterableOps[A], val p: A => Boolean, val isFlipped: Boolean) extends AbstractView[A] { + class Filter[A](val underlying: SomeIterableOps[A]^, val p: A => Boolean, val isFlipped: Boolean) extends AbstractView[A] { def iterator = underlying.iterator.filterImpl(p, isFlipped) override def knownSize: Int = if (underlying.knownSize == 0) 0 else super.knownSize override def isEmpty: Boolean = iterator.isEmpty } object Filter { - def apply[A](underlying: Iterable[A], p: A => Boolean, isFlipped: Boolean): Filter[A] = + def apply[A](underlying: Iterable[A]^, p: A => Boolean, isFlipped: Boolean): Filter[A]^{underlying, p} = underlying match { - case filter: Filter[A] if filter.isFlipped == isFlipped => new Filter(filter.underlying, a => filter.p(a) && p(a), isFlipped) + case filter : Filter[A] if filter.isFlipped == isFlipped => { + val f: Filter[A]^{underlying} = filter.unsafeAssumePure // TODO remove when pattern matching works + val f2: Filter[A]^{f, p} = new Filter(f.underlying, a => f.p(a) && p(a), isFlipped) + f2 + } case _ => new Filter(underlying, p, isFlipped) } } /** A view that removes the duplicated elements as determined by the transformation function `f` */ @SerialVersionUID(3L) - class DistinctBy[A, B](underlying: SomeIterableOps[A], f: A => B) extends AbstractView[A] { - def iterator: Iterator[A] = underlying.iterator.distinctBy(f) + class DistinctBy[A, B](underlying: SomeIterableOps[A]^, f: A -> B) extends AbstractView[A] { + def iterator: Iterator[A]^{underlying} = underlying.iterator.distinctBy(f) override def knownSize: Int = if (underlying.knownSize == 0) 0 else super.knownSize override def isEmpty: Boolean = underlying.isEmpty } @SerialVersionUID(3L) - class LeftPartitionMapped[A, A1, A2](underlying: SomeIterableOps[A], f: A => Either[A1, A2]) extends AbstractView[A1] { + class LeftPartitionMapped[A, A1, A2](underlying: SomeIterableOps[A]^, f: A => Either[A1, A2]) extends AbstractView[A1] { def iterator: AbstractIterator[A1] = new AbstractIterator[A1] { private[this] val self = underlying.iterator private[this] var hd: A1 = _ @@ -189,7 +196,7 @@ object View extends IterableFactory[View] { } @SerialVersionUID(3L) - class RightPartitionMapped[A, A1, A2](underlying: SomeIterableOps[A], f: A => Either[A1, A2]) extends AbstractView[A2] { + class RightPartitionMapped[A, A1, A2](underlying: SomeIterableOps[A]^, f: A => Either[A1, A2]) extends AbstractView[A2] { def iterator: AbstractIterator[A2] = new AbstractIterator[A2] { private[this] val self = underlying.iterator private[this] var hd: A2 = _ @@ -215,7 +222,7 @@ object View extends IterableFactory[View] { /** A view that drops leading elements of the underlying collection. */ @SerialVersionUID(3L) - class Drop[A](underlying: SomeIterableOps[A], n: Int) extends AbstractView[A] { + class Drop[A](underlying: SomeIterableOps[A]^, n: Int) extends AbstractView[A] { def iterator = underlying.iterator.drop(n) protected val normN = n max 0 override def knownSize = { @@ -227,7 +234,7 @@ object View extends IterableFactory[View] { /** A view that drops trailing elements of the underlying collection. */ @SerialVersionUID(3L) - class DropRight[A](underlying: SomeIterableOps[A], n: Int) extends AbstractView[A] { + class DropRight[A](underlying: SomeIterableOps[A]^, n: Int) extends AbstractView[A] { def iterator = dropRightIterator(underlying.iterator, n) protected val normN = n max 0 override def knownSize = { @@ -240,7 +247,7 @@ object View extends IterableFactory[View] { } @SerialVersionUID(3L) - class DropWhile[A](underlying: SomeIterableOps[A], p: A => Boolean) extends AbstractView[A] { + class DropWhile[A](underlying: SomeIterableOps[A]^, p: A => Boolean) extends AbstractView[A] { def iterator = underlying.iterator.dropWhile(p) override def knownSize: Int = if (underlying.knownSize == 0) 0 else super.knownSize override def isEmpty: Boolean = iterator.isEmpty @@ -248,7 +255,7 @@ object View extends IterableFactory[View] { /** A view that takes leading elements of the underlying collection. */ @SerialVersionUID(3L) - class Take[+A](underlying: SomeIterableOps[A], n: Int) extends AbstractView[A] { + class Take[+A](underlying: SomeIterableOps[A]^, n: Int) extends AbstractView[A] { def iterator = underlying.iterator.take(n) protected val normN = n max 0 override def knownSize = { @@ -260,7 +267,7 @@ object View extends IterableFactory[View] { /** A view that takes trailing elements of the underlying collection. */ @SerialVersionUID(3L) - class TakeRight[+A](underlying: SomeIterableOps[A], n: Int) extends AbstractView[A] { + class TakeRight[+A](underlying: SomeIterableOps[A]^, n: Int) extends AbstractView[A] { def iterator = takeRightIterator(underlying.iterator, n) protected val normN = n max 0 override def knownSize = { @@ -273,15 +280,15 @@ object View extends IterableFactory[View] { } @SerialVersionUID(3L) - class TakeWhile[A](underlying: SomeIterableOps[A], p: A => Boolean) extends AbstractView[A] { - def iterator: Iterator[A] = underlying.iterator.takeWhile(p) + class TakeWhile[A](underlying: SomeIterableOps[A]^, p: A => Boolean) extends AbstractView[A] { + def iterator = underlying.iterator.takeWhile(p) override def knownSize: Int = if (underlying.knownSize == 0) 0 else super.knownSize override def isEmpty: Boolean = iterator.isEmpty } @SerialVersionUID(3L) - class ScanLeft[+A, +B](underlying: SomeIterableOps[A], z: B, op: (B, A) => B) extends AbstractView[B] { - def iterator: Iterator[B] = underlying.iterator.scanLeft(z)(op) + class ScanLeft[+A, +B](underlying: SomeIterableOps[A]^, z: B, op: (B, A) => B) extends AbstractView[B] { + def iterator = underlying.iterator.scanLeft(z)(op) override def knownSize: Int = { val size = underlying.knownSize if (size >= 0) size + 1 else -1 @@ -291,7 +298,7 @@ object View extends IterableFactory[View] { /** A view that maps elements of the underlying collection. */ @SerialVersionUID(3L) - class Map[+A, +B](underlying: SomeIterableOps[A], f: A => B) extends AbstractView[B] { + class Map[+A, +B](underlying: SomeIterableOps[A]^, f: A => B) extends AbstractView[B] { def iterator = underlying.iterator.map(f) override def knownSize = underlying.knownSize override def isEmpty: Boolean = underlying.isEmpty @@ -299,7 +306,7 @@ object View extends IterableFactory[View] { /** A view that flatmaps elements of the underlying collection. */ @SerialVersionUID(3L) - class FlatMap[A, B](underlying: SomeIterableOps[A], f: A => IterableOnce[B]) extends AbstractView[B] { + class FlatMap[A, B](underlying: SomeIterableOps[A]^, f: A => IterableOnce[B]^) extends AbstractView[B] { def iterator = underlying.iterator.flatMap(f) override def knownSize: Int = if (underlying.knownSize == 0) 0 else super.knownSize override def isEmpty: Boolean = iterator.isEmpty @@ -307,7 +314,7 @@ object View extends IterableFactory[View] { /** A view that collects elements of the underlying collection. */ @SerialVersionUID(3L) - class Collect[+A, B](underlying: SomeIterableOps[A], pf: PartialFunction[A, B]) extends AbstractView[B] { + class Collect[+A, B](underlying: SomeIterableOps[A]^, pf: PartialFunction[A, B]^) extends AbstractView[B] { def iterator = underlying.iterator.collect(pf) } @@ -315,7 +322,7 @@ object View extends IterableFactory[View] { * of the suffix collection or iterator. */ @SerialVersionUID(3L) - class Concat[A](prefix: SomeIterableOps[A], suffix: SomeIterableOps[A]) extends AbstractView[A] { + class Concat[A](prefix: SomeIterableOps[A]^, suffix: SomeIterableOps[A]^) extends AbstractView[A] { def iterator = prefix.iterator ++ suffix.iterator override def knownSize = { val prefixSize = prefix.knownSize @@ -333,7 +340,7 @@ object View extends IterableFactory[View] { * of another collection. */ @SerialVersionUID(3L) - class Zip[A, B](underlying: SomeIterableOps[A], other: Iterable[B]) extends AbstractView[(A, B)] { + class Zip[A, B](underlying: SomeIterableOps[A]^, other: Iterable[B]^) extends AbstractView[(A, B)] { def iterator = underlying.iterator.zip(other) override def knownSize = { val s1 = underlying.knownSize @@ -350,7 +357,7 @@ object View extends IterableFactory[View] { * placeholder elements are used to extend the shorter collection to the length of the longer. */ @SerialVersionUID(3L) - class ZipAll[A, B](underlying: SomeIterableOps[A], other: Iterable[B], thisElem: A, thatElem: B) extends AbstractView[(A, B)] { + class ZipAll[A, B](underlying: SomeIterableOps[A]^, other: Iterable[B]^, thisElem: A, thatElem: B) extends AbstractView[(A, B)] { def iterator = underlying.iterator.zipAll(other, thisElem, thatElem) override def knownSize = { val s1 = underlying.knownSize @@ -364,8 +371,8 @@ object View extends IterableFactory[View] { /** A view that appends an element to its elements */ @SerialVersionUID(3L) - class Appended[+A](underlying: SomeIterableOps[A], elem: A) extends AbstractView[A] { - def iterator: Iterator[A] = new Concat(underlying, new View.Single(elem)).iterator + class Appended[+A](underlying: SomeIterableOps[A]^, elem: A) extends AbstractView[A] { + def iterator = new Concat(underlying, new View.Single(elem)).iterator override def knownSize: Int = { val size = underlying.knownSize if (size >= 0) size + 1 else -1 @@ -375,8 +382,8 @@ object View extends IterableFactory[View] { /** A view that prepends an element to its elements */ @SerialVersionUID(3L) - class Prepended[+A](elem: A, underlying: SomeIterableOps[A]) extends AbstractView[A] { - def iterator: Iterator[A] = new Concat(new View.Single(elem), underlying).iterator + class Prepended[+A](elem: A, underlying: SomeIterableOps[A]^) extends AbstractView[A] { + def iterator = new Concat(new View.Single(elem), underlying).iterator override def knownSize: Int = { val size = underlying.knownSize if (size >= 0) size + 1 else -1 @@ -385,8 +392,8 @@ object View extends IterableFactory[View] { } @SerialVersionUID(3L) - class Updated[A](underlying: SomeIterableOps[A], index: Int, elem: A) extends AbstractView[A] { - def iterator: Iterator[A] = new AbstractIterator[A] { + class Updated[A](underlying: SomeIterableOps[A]^, index: Int, elem: A) extends AbstractView[A] { + def iterator: Iterator[A]^{underlying} = new AbstractIterator[A] { private[this] val it = underlying.iterator private[this] var i = 0 def next(): A = { @@ -404,28 +411,28 @@ object View extends IterableFactory[View] { } @SerialVersionUID(3L) - private[collection] class Patched[A](underlying: SomeIterableOps[A], from: Int, other: IterableOnce[A], replaced: Int) extends AbstractView[A] { + private[collection] class Patched[A](underlying: SomeIterableOps[A]^, from: Int, other: IterableOnce[A]^, replaced: Int) extends AbstractView[A] { // we may be unable to traverse `other` more than once, so we need to cache it if that's the case - private val _other: Iterable[A] = other match { + private val _other: Iterable[A]^{other} = other match { case other: Iterable[A] => other case other => LazyList.from(other) } - def iterator: Iterator[A] = underlying.iterator.patch(from, _other.iterator, replaced) + def iterator: Iterator[A]^{this} = underlying.iterator.patch(from, _other.iterator, replaced) override def knownSize: Int = if (underlying.knownSize == 0 && _other.knownSize == 0) 0 else super.knownSize override def isEmpty: Boolean = if (knownSize == 0) true else iterator.isEmpty } @SerialVersionUID(3L) - class ZipWithIndex[A](underlying: SomeIterableOps[A]) extends AbstractView[(A, Int)] { - def iterator: Iterator[(A, Int)] = underlying.iterator.zipWithIndex + class ZipWithIndex[A](underlying: SomeIterableOps[A]^) extends AbstractView[(A, Int)] { + def iterator: Iterator[(A, Int)]^{this} = underlying.iterator.zipWithIndex override def knownSize: Int = underlying.knownSize override def isEmpty: Boolean = underlying.isEmpty } @SerialVersionUID(3L) - class PadTo[A](underlying: SomeIterableOps[A], len: Int, elem: A) extends AbstractView[A] { - def iterator: Iterator[A] = underlying.iterator.padTo(len, elem) + class PadTo[A](underlying: SomeIterableOps[A]^, len: Int, elem: A) extends AbstractView[A] { + def iterator: Iterator[A]^{this} = underlying.iterator.padTo(len, elem) override def knownSize: Int = { val size = underlying.knownSize @@ -434,7 +441,7 @@ object View extends IterableFactory[View] { override def isEmpty: Boolean = underlying.isEmpty && len <= 0 } - private[collection] def takeRightIterator[A](it: Iterator[A], n: Int): Iterator[A] = { + private[collection] def takeRightIterator[A](it: Iterator[A]^, n: Int): Iterator[A]^{it} = { val k = it.knownSize if(k == 0 || n <= 0) Iterator.empty else if(n == Int.MaxValue) it @@ -442,7 +449,7 @@ object View extends IterableFactory[View] { else new TakeRightIterator[A](it, n) } - private final class TakeRightIterator[A](private[this] var underlying: Iterator[A], maxlen: Int) extends AbstractIterator[A] { + private final class TakeRightIterator[A](private[this] var underlying: Iterator[A]^, maxlen: Int) extends AbstractIterator[A] { private[this] var len: Int = -1 private[this] var pos: Int = 0 private[this] var buf: ArrayBuffer[AnyRef] = _ @@ -478,7 +485,7 @@ object View extends IterableFactory[View] { x } } - override def drop(n: Int): Iterator[A] = { + override def drop(n: Int): Iterator[A]^{this} = { init() if (n > 0) { len = (len - n) max 0 @@ -488,7 +495,7 @@ object View extends IterableFactory[View] { } } - private[collection] def dropRightIterator[A](it: Iterator[A], n: Int): Iterator[A] = { + private[collection] def dropRightIterator[A](it: Iterator[A]^, n: Int): Iterator[A]^{it} = { if(n <= 0) it else { val k = it.knownSize @@ -497,7 +504,7 @@ object View extends IterableFactory[View] { } } - private final class DropRightIterator[A](private[this] var underlying: Iterator[A], maxlen: Int) extends AbstractIterator[A] { + private final class DropRightIterator[A](private[this] var underlying: Iterator[A]^, maxlen: Int) extends AbstractIterator[A] { private[this] var len: Int = -1 // known size or -1 if the end of `underlying` has not been seen yet private[this] var pos: Int = 0 private[this] var buf: ArrayBuffer[AnyRef] = _ From 3ac001a83936ab1fc4dc53bdc32048b4b8434166 Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Tue, 12 Aug 2025 19:26:10 +0200 Subject: [PATCH 051/101] DO NOT MERGE remove warnings when building stdlib --- project/Build.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/project/Build.scala b/project/Build.scala index f9e678eab13f..a9f964d5959c 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1682,6 +1682,7 @@ object Build { Compile / scalacOptions ++= Seq( // Needed so that the library sources are visible when `dotty.tools.dotc.core.Definitions#init` is called "-sourcepath", (Compile / sourceDirectories).value.map(_.getCanonicalPath).distinct.mkString(File.pathSeparator), + "-nowarn", ), // Make sure that the produced artifacts have the minimum JVM version in the bytecode Compile / javacOptions ++= Seq("--release", Versions.minimumJVMVersion), @@ -1764,6 +1765,7 @@ object Build { Compile / scalacOptions ++= Seq( // Needed so that the library sources are visible when `dotty.tools.dotc.core.Definitions#init` is called "-sourcepath", (Compile / sourceDirectories).value.map(_.getCanonicalPath).distinct.mkString(File.pathSeparator), + "-nowarn", ), // Make sure that the produced artifacts have the minimum JVM version in the bytecode Compile / javacOptions ++= Seq("--release", Versions.minimumJVMVersion), From ba6167fcbb5a847eeae2f5ddb0886a27d7230f60 Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Wed, 13 Aug 2025 20:18:18 +0200 Subject: [PATCH 052/101] Split IterableFactory to a strict version, with a pure collection constraint --- library/src/scala/collection/Factory.scala | 55 ++++++++++++++++++---- 1 file changed, 47 insertions(+), 8 deletions(-) diff --git a/library/src/scala/collection/Factory.scala b/library/src/scala/collection/Factory.scala index 7e0ff9ede393..a5f3ea687adf 100644 --- a/library/src/scala/collection/Factory.scala +++ b/library/src/scala/collection/Factory.scala @@ -32,7 +32,7 @@ import scala.reflect.ClassTag * @tparam A Type of elements (e.g. `Int`, `Boolean`, etc.) * @tparam C Type of collection (e.g. `List[Int]`, `TreeMap[Int, String]`, etc.) */ -trait Factory[-A, +C] extends Pure { +trait Factory[-A, +C] extends Any { self => /** * @return A collection of type `C` containing the same elements @@ -83,7 +83,7 @@ object Factory { * @define coll collection * @define Coll `Iterable` */ -trait IterableFactory[+CC[_]] extends Serializable, Pure { +trait IterableFactory[+CC[_]] extends Serializable, caps.Pure { /** Creates a target $coll from an existing source collection * @@ -293,20 +293,59 @@ object IterableFactory { } } +trait StrictIterableFactory[+CC[_] <: caps.Pure] extends IterableFactory[CC] { + // pure overrides of the IterableFactory methods + override def from[A](source: IterableOnce[A]^): CC[A] + + override def iterate[A](start: A, len: Int)(f: A => A): CC[A] = from(new View.Iterate(start, len)(f)) + + override def unfold[A, S](init: S)(f: S => Option[(A, S)]): CC[A] = from(new View.Unfold(init)(f)) + + override def fill[A](n: Int)(elem: => A): CC[A] = from(new View.Fill(n)(elem)) + + override def fill[A](n1: Int, n2: Int)(elem: => A): CC[CC[A] @uncheckedVariance] = fill(n1)(fill(n2)(elem)) + + override def fill[A](n1: Int, n2: Int, n3: Int)(elem: => A): CC[CC[CC[A]] @uncheckedVariance] = fill(n1)(fill(n2, n3)(elem)) + + override def fill[A](n1: Int, n2: Int, n3: Int, n4: Int)(elem: => A): CC[CC[CC[CC[A]]] @uncheckedVariance] = + fill(n1)(fill(n2, n3, n4)(elem)) + + override def fill[A](n1: Int, n2: Int, n3: Int, n4: Int, n5: Int)(elem: => A): CC[CC[CC[CC[CC[A]]]] @uncheckedVariance] = + fill(n1)(fill(n2, n3, n4, n5)(elem)) + + override def tabulate[A](n: Int)(f: Int => A): CC[A] = from(new View.Tabulate(n)(f)) + + override def tabulate[A](n1: Int, n2: Int)(f: (Int, Int) => A): CC[CC[A] @uncheckedVariance] = + tabulate(n1)(i1 => tabulate(n2)(f(i1, _))) + + override def tabulate[A](n1: Int, n2: Int, n3: Int)(f: (Int, Int, Int) => A): CC[CC[CC[A]] @uncheckedVariance] = + tabulate(n1)(i1 => tabulate(n2, n3)(f(i1, _, _))) + + override def tabulate[A](n1: Int, n2: Int, n3: Int, n4: Int)(f: (Int, Int, Int, Int) => A): CC[CC[CC[CC[A]]] @uncheckedVariance] = + tabulate(n1)(i1 => tabulate(n2, n3, n4)(f(i1, _, _, _))) + + override 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] = + tabulate(n1)(i1 => tabulate(n2, n3, n4, n5)(f(i1, _, _, _, _))) + + override def concat[A](xss: Iterable[A]*): CC[A] = { + from(xss.foldLeft(View.empty[A])(_ ++ _)) + } +} + /** * @tparam CC Collection type constructor (e.g. `List`) */ -trait SeqFactory[+CC[A] <: SeqOps[A, Seq, Seq[A]]] extends IterableFactory[CC] { +trait SeqFactory[+CC[A] <: StrictSeqOps[A, Seq, Seq[A]]] extends StrictIterableFactory[CC] { import SeqFactory.UnapplySeqWrapper final def unapplySeq[A](x: CC[A] @uncheckedVariance): UnapplySeqWrapper[A] = new UnapplySeqWrapper(x) // TODO is uncheckedVariance sound here? } object SeqFactory { @SerialVersionUID(3L) - class Delegate[CC[A] <: SeqOps[A, Seq, Seq[A]]](delegate: SeqFactory[CC]) extends SeqFactory[CC] { + class Delegate[CC[A] <: StrictSeqOps[A, Seq, Seq[A]]](delegate: SeqFactory[CC]) extends SeqFactory[CC] { override def apply[A](elems: A*): CC[A] = delegate.apply(elems: _*) def empty[A]: CC[A] = delegate.empty - def from[E](it: IterableOnce[E]^): CC[E]^{it} = delegate.from(it) + def from[E](it: IterableOnce[E]^): CC[E] = delegate.from(it) def newBuilder[A]: Builder[A, CC[A]] = delegate.newBuilder[A] } @@ -323,7 +362,7 @@ object SeqFactory { } } -trait StrictOptimizedSeqFactory[+CC[A] <: SeqOps[A, Seq, Seq[A]]] extends SeqFactory[CC] { +trait StrictOptimizedSeqFactory[+CC[A] <: StrictSeqOps[A, Seq, Seq[A]]] extends SeqFactory[CC] { override def fill[A](n: Int)(elem: => A): CC[A] = { val b = newBuilder[A] @@ -457,7 +496,7 @@ object MapFactory { * @define coll collection * @define Coll `Iterable` */ -trait EvidenceIterableFactory[+CC[_], Ev[_]] extends Serializable, Pure { +trait EvidenceIterableFactory[+CC[_], Ev[_]] extends Serializable, caps.Pure { def from[E : Ev](it: IterableOnce[E]^): CC[E] @@ -699,7 +738,7 @@ object ClassTagSeqFactory { /** A SeqFactory that uses ClassTag.Any as the evidence for every element type. This may or may not be * sound depending on the use of the `ClassTag` by the collection implementation. */ @SerialVersionUID(3L) - class AnySeqDelegate[CC[A] <: SeqOps[A, Seq, Seq[A]]](delegate: ClassTagSeqFactory[CC]) + class AnySeqDelegate[CC[A] <: StrictSeqOps[A, Seq, Seq[A]]](delegate: ClassTagSeqFactory[CC]) extends ClassTagIterableFactory.AnyIterableDelegate[CC](delegate) with SeqFactory[CC] } From db848852d1dc40a170a7654eeff6fcffe32d85ba Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Wed, 13 Aug 2025 20:19:11 +0200 Subject: [PATCH 053/101] Capture-check Seq and IndexedSeq --- TODO.md | 33 +++--- library/src/scala/IArray.scala | 2 +- library/src/scala/collection/IndexedSeq.scala | 48 +++++---- .../src/scala/collection/IndexedSeqView.scala | 76 ++++++------- library/src/scala/collection/Iterable.scala | 2 +- library/src/scala/collection/LinearSeq.scala | 4 +- library/src/scala/collection/Seq.scala | 101 +++++++++++------- .../collection/StrictOptimizedSeqOps.scala | 6 +- .../src/scala/collection/immutable/Seq.scala | 6 +- .../immutable/StrictOptimizedSeqOps.scala | 4 +- .../collection/immutable/WrappedString.scala | 4 +- .../scala/collection/mutable/ArrayDeque.scala | 2 +- .../scala/collection/mutable/IndexedSeq.scala | 2 +- .../src/scala/collection/mutable/Seq.scala | 4 +- 14 files changed, 165 insertions(+), 129 deletions(-) diff --git a/TODO.md b/TODO.md index aa197bbcef2a..2de8c5f99244 100644 --- a/TODO.md +++ b/TODO.md @@ -46,7 +46,7 @@ - [x] library/src/scala/NamedTuple.scala - [ ] library/src/scala/NotImplementedError.scala - [ ] library/src/scala/Option.scala -- [ ] library/src/scala/PartialFunction.scala +- [x] library/src/scala/PartialFunction.scala - [x] library/src/scala/PolyFunction.scala - [x] library/src/scala/Precise.scala - [ ] library/src/scala/Predef.scala @@ -186,25 +186,25 @@ - [ ] library/src/scala/beans/BeanProperty.scala - [ ] library/src/scala/beans/BooleanBeanProperty.scala - [x] library/src/scala/caps/package.scala -- [ ] library/src/scala/collection/ArrayOps.scala -- [ ] library/src/scala/collection/BitSet.scala -- [ ] library/src/scala/collection/BufferedIterator.scala -- [ ] library/src/scala/collection/BuildFrom.scala +- [x] library/src/scala/collection/ArrayOps.scala +- [x] library/src/scala/collection/BitSet.scala +- [x] library/src/scala/collection/BufferedIterator.scala +- [x] library/src/scala/collection/BuildFrom.scala - [ ] library/src/scala/collection/DefaultMap.scala -- [ ] library/src/scala/collection/Factory.scala +- [x] library/src/scala/collection/Factory.scala - [ ] library/src/scala/collection/Hashing.scala -- [ ] library/src/scala/collection/IndexedSeq.scala -- [ ] library/src/scala/collection/IndexedSeqView.scala -- [ ] library/src/scala/collection/Iterable.scala -- [ ] library/src/scala/collection/IterableOnce.scala -- [ ] library/src/scala/collection/Iterator.scala +- [x] library/src/scala/collection/IndexedSeq.scala +- [x] library/src/scala/collection/IndexedSeqView.scala +- [x] library/src/scala/collection/Iterable.scala +- [x] library/src/scala/collection/IterableOnce.scala +- [x] library/src/scala/collection/Iterator.scala - [ ] library/src/scala/collection/JavaConverters.scala - [ ] library/src/scala/collection/LazyZipOps.scala - [ ] library/src/scala/collection/LinearSeq.scala - [ ] library/src/scala/collection/Map.scala - [ ] library/src/scala/collection/MapView.scala - [ ] library/src/scala/collection/Searching.scala -- [ ] library/src/scala/collection/Seq.scala +- [x] library/src/scala/collection/Seq.scala - [ ] library/src/scala/collection/SeqMap.scala - [ ] library/src/scala/collection/SeqView.scala - [ ] library/src/scala/collection/Set.scala @@ -221,7 +221,7 @@ - [ ] library/src/scala/collection/StrictOptimizedSortedSetOps.scala - [ ] library/src/scala/collection/StringOps.scala - [ ] library/src/scala/collection/StringParsers.scala -- [ ] library/src/scala/collection/View.scala +- [x] library/src/scala/collection/View.scala - [ ] library/src/scala/collection/WithFilter.scala - [ ] library/src/scala/collection/concurrent/Map.scala - [ ] library/src/scala/collection/concurrent/TrieMap.scala @@ -255,7 +255,7 @@ - [ ] library/src/scala/collection/generic/Subtractable.scala - [ ] library/src/scala/collection/generic/package.scala - [ ] library/src/scala/collection/immutable/ArraySeq.scala -- [ ] library/src/scala/collection/immutable/BitSet.scala +- [x] library/src/scala/collection/immutable/BitSet.scala - [ ] library/src/scala/collection/immutable/ChampCommon.scala - [ ] library/src/scala/collection/immutable/HashMap.scala - [ ] library/src/scala/collection/immutable/HashSet.scala @@ -290,7 +290,7 @@ - [ ] library/src/scala/collection/mutable/ArrayBuilder.scala - [ ] library/src/scala/collection/mutable/ArrayDeque.scala - [ ] library/src/scala/collection/mutable/ArraySeq.scala -- [ ] library/src/scala/collection/mutable/BitSet.scala +- [x] library/src/scala/collection/mutable/BitSet.scala - [ ] library/src/scala/collection/mutable/Buffer.scala - [ ] library/src/scala/collection/mutable/Builder.scala - [ ] library/src/scala/collection/mutable/CheckedIndexedSeqView.scala @@ -367,6 +367,7 @@ - [ ] library/src/scala/deprecatedInheritance.scala - [ ] library/src/scala/deprecatedName.scala - [ ] library/src/scala/deprecatedOverriding.scala +- [x] library/src/scala/deriving/Mirror.scala - [ ] library/src/scala/inline.scala - [ ] library/src/scala/io/AnsiColor.scala - [ ] library/src/scala/io/BufferedSource.scala @@ -469,7 +470,7 @@ - [ ] library/src/scala/runtime/AbstractFunction7.scala - [ ] library/src/scala/runtime/AbstractFunction8.scala - [ ] library/src/scala/runtime/AbstractFunction9.scala -- [ ] library/src/scala/runtime/AbstractPartialFunction.scala +- [x] library/src/scala/runtime/AbstractPartialFunction.scala - [ ] library/src/scala/runtime/ArrayCharSequence.scala - [x] library/src/scala/runtime/Arrays.scala - [ ] library/src/scala/runtime/ClassValueCompat.scala diff --git a/library/src/scala/IArray.scala b/library/src/scala/IArray.scala index 64df0c3426f5..281d1ef78a89 100644 --- a/library/src/scala/IArray.scala +++ b/library/src/scala/IArray.scala @@ -297,7 +297,7 @@ object IArray: def prepended[U >: T: ClassTag](x: U): IArray[U] = genericArrayOps(arr).prepended(x) def prependedAll[U >: T: ClassTag](prefix: IterableOnce[U]^): IArray[U] = genericArrayOps(arr).prependedAll(prefix) def reverseIterator: Iterator[T] = genericArrayOps(arr).reverseIterator - def search[U >: T](elem: U)(using Ordering[U]^): Searching.SearchResult = arr.toSeq.search(elem) + def search[U >: T](elem: U)(using Ordering[U]): Searching.SearchResult = arr.toSeq.search(elem) def search[U >: T](elem: U, from: Int, to: Int)(using Ordering[U]): Searching.SearchResult = arr.toSeq.search(elem, from, to) def sizeCompare(that: IArray[Any]): Int = arr.toSeq.sizeCompare(that) def sizeCompare(that: Iterable[?]): Int = arr.toSeq.sizeCompare(that) diff --git a/library/src/scala/collection/IndexedSeq.scala b/library/src/scala/collection/IndexedSeq.scala index 366fd526a022..10cf37001d23 100644 --- a/library/src/scala/collection/IndexedSeq.scala +++ b/library/src/scala/collection/IndexedSeq.scala @@ -14,6 +14,8 @@ package scala package collection import scala.language.`2.13` +import language.experimental.captureChecking + import scala.annotation.{nowarn, tailrec} import scala.collection.Searching.{Found, InsertionPoint, SearchResult} import scala.collection.Stepper.EfficientSplit @@ -21,7 +23,7 @@ import scala.math.Ordering /** Base trait for indexed sequences that have efficient `apply` and `length` */ trait IndexedSeq[+A] extends Seq[A] - with IndexedSeqOps[A, IndexedSeq, IndexedSeq[A]] + with StrictIndexedSeqOps[A, IndexedSeq, IndexedSeq[A]] with IterableFactoryDefaults[A, IndexedSeq] { @nowarn("""cat=deprecation&origin=scala\.collection\.Iterable\.stringPrefix""") override protected[this] def stringPrefix: String = "IndexedSeq" @@ -32,10 +34,13 @@ trait IndexedSeq[+A] extends Seq[A] @SerialVersionUID(3L) object IndexedSeq extends SeqFactory.Delegate[IndexedSeq](immutable.IndexedSeq) +transparent trait StrictIndexedSeqOps[+A, +CC[_] <: caps.Pure, +C] extends Any with StrictSeqOps[A, CC, C] with IndexedSeqOps[A, CC, C] { +} + /** Base trait for indexed Seq operations */ -transparent trait IndexedSeqOps[+A, +CC[_], +C] extends Any with SeqOps[A, CC, C] { self => +transparent trait IndexedSeqOps[+A, +CC[_], +C] extends Any with SeqOps[A, CC, C] { self: IndexedSeqOps[A, CC, C]^ => - def iterator: Iterator[A] = view.iterator + def iterator: Iterator[A]^{this} = view.iterator override def stepper[S <: Stepper[_]](implicit shape: StepperShape[A, S]): S with EfficientSplit = { import convert.impl._ @@ -48,7 +53,7 @@ transparent trait IndexedSeqOps[+A, +CC[_], +C] extends Any with SeqOps[A, CC, C s.asInstanceOf[S with EfficientSplit] } - override def reverseIterator: Iterator[A] = view.reverseIterator + override def reverseIterator: Iterator[A]^{this} = view.reverseIterator /* TODO 2.14+ uncomment and delete related code in IterableOnce @tailrec private def foldl[B](start: Int, end: Int, z: B, op: (B, A) => B): B = @@ -68,33 +73,34 @@ transparent trait IndexedSeqOps[+A, +CC[_], +C] extends Any with SeqOps[A, CC, C //override def reduceRight[B >: A](op: (A, B) => B): B = if (length > 0) foldr(0, length - 1, apply(length - 1), op) else super.reduceRight(op) - override def view: IndexedSeqView[A] = new IndexedSeqView.Id[A](this) + override def view: IndexedSeqView[A]^{this} = new IndexedSeqView.Id[A](this) @deprecated("Use .view.slice(from, until) instead of .view(from, until)", "2.13.0") - override def view(from: Int, until: Int): IndexedSeqView[A] = view.slice(from, until) + override def view(from: Int, until: Int): IndexedSeqView[A]^{this} = view.slice(from, until) - override protected def reversed: Iterable[A] = new IndexedSeqView.Reverse(this) + override protected def reversed: Iterable[A]^{this} = new IndexedSeqView.Reverse(this) // Override transformation operations to use more efficient views than the default ones - override def prepended[B >: A](elem: B): CC[B] = iterableFactory.from(new IndexedSeqView.Prepended(elem, this)) + override def prepended[B >: A](elem: B): CC[B]^{this} = iterableFactory.from(new IndexedSeqView.Prepended(elem, this)) - override def take(n: Int): C = fromSpecific(new IndexedSeqView.Take(this, n)) + override def take(n: Int): C^{this} = fromSpecific(new IndexedSeqView.Take(this, n)) - override def takeRight(n: Int): C = fromSpecific(new IndexedSeqView.TakeRight(this, n)) + override def takeRight(n: Int): C^{this} = fromSpecific(new IndexedSeqView.TakeRight(this, n)) - override def drop(n: Int): C = fromSpecific(new IndexedSeqView.Drop(this, n)) + override def drop(n: Int): C^{this} = fromSpecific(new IndexedSeqView.Drop(this, n)) - override def dropRight(n: Int): C = fromSpecific(new IndexedSeqView.DropRight(this, n)) + override def dropRight(n: Int): C^{this} = fromSpecific(new IndexedSeqView.DropRight(this, n)) - override def map[B](f: A => B): CC[B] = iterableFactory.from(new IndexedSeqView.Map(this, f)) + override def map[B](f: A => B): CC[B]^{this, f} = iterableFactory.from(new IndexedSeqView.Map(this, f)) - override def reverse: C = fromSpecific(new IndexedSeqView.Reverse(this)) + override def reverse: C^{this} = fromSpecific(new IndexedSeqView.Reverse(this)) - override def slice(from: Int, until: Int): C = fromSpecific(new IndexedSeqView.Slice(this, from, until)) + override def slice(from: Int, until: Int): C^{this} = fromSpecific(new IndexedSeqView.Slice(this, from, until)) - override def sliding(size: Int, step: Int): Iterator[C] = { + override def sliding(size: Int, step: Int): Iterator[C^{this}]^{this} = { require(size >= 1 && step >= 1, f"size=$size%d and step=$step%d, but both must be positive") - new IndexedSeqSlidingIterator[A, CC, C](this, size, step) + val it = new IndexedSeqSlidingIterator[A, CC, C](this, size, step) + it.asInstanceOf // TODO: seems like CC cannot figure this out yet } override def head: A = @@ -123,7 +129,7 @@ transparent trait IndexedSeqOps[+A, +CC[_], +C] extends Any with SeqOps[A, CC, C override def knownSize: Int = length - override final def lengthCompare(that: Iterable[_]): Int = { + override final def lengthCompare(that: Iterable[_]^): Int = { val res = that.sizeCompare(length) // can't just invert the result, because `-Int.MinValue == Int.MinValue` if (res == Int.MinValue) 1 else -res @@ -153,8 +159,8 @@ transparent trait IndexedSeqOps[+A, +CC[_], +C] extends Any with SeqOps[A, CC, C } /** A fast sliding iterator for IndexedSeqs which uses the underlying `slice` operation. */ -private final class IndexedSeqSlidingIterator[A, CC[_], C](s: IndexedSeqOps[A, CC, C], size: Int, step: Int) - extends AbstractIterator[C] { +private final class IndexedSeqSlidingIterator[A, CC[_], C](s: IndexedSeqOps[A, CC, C]^, size: Int, step: Int) + extends AbstractIterator[C^{s}] { private[this] val len = s.length private[this] var pos = 0 @@ -165,7 +171,7 @@ private final class IndexedSeqSlidingIterator[A, CC[_], C](s: IndexedSeqOps[A, C def hasNext: Boolean = chklen && pos < len - def next(): C = if (!chklen || !hasNext) Iterator.empty.next() else { + def next(): C^{s} = if (!chklen || !hasNext) Iterator.empty.next() else { val end = { val x = pos + size; if (x < 0 || x > len) len else x } // (pos.toLong + size).min(len).toInt val slice = s.slice(pos, end) pos = diff --git a/library/src/scala/collection/IndexedSeqView.scala b/library/src/scala/collection/IndexedSeqView.scala index d03ff1b1c689..b68ae56b4b40 100644 --- a/library/src/scala/collection/IndexedSeqView.scala +++ b/library/src/scala/collection/IndexedSeqView.scala @@ -14,34 +14,36 @@ package scala package collection import scala.language.`2.13` +import language.experimental.captureChecking + import scala.annotation.nowarn /** View defined in terms of indexing a range */ -trait IndexedSeqView[+A] extends IndexedSeqOps[A, View, View[A]] with SeqView[A] { self => +trait IndexedSeqView[+A] extends IndexedSeqOps[A, View, View[A]] with SeqView[A] { - override def view: IndexedSeqView[A] = this + override def view: IndexedSeqView[A]^{this} = this @deprecated("Use .view.slice(from, until) instead of .view(from, until)", "2.13.0") - override def view(from: Int, until: Int): IndexedSeqView[A] = view.slice(from, until) - - override def iterator: Iterator[A] = new IndexedSeqView.IndexedSeqViewIterator(this) - override def reverseIterator: Iterator[A] = new IndexedSeqView.IndexedSeqViewReverseIterator(this) - - override def appended[B >: A](elem: B): IndexedSeqView[B] = new IndexedSeqView.Appended(this, elem) - override def prepended[B >: A](elem: B): IndexedSeqView[B] = new IndexedSeqView.Prepended(elem, this) - override def take(n: Int): IndexedSeqView[A] = new IndexedSeqView.Take(this, n) - override def takeRight(n: Int): IndexedSeqView[A] = new IndexedSeqView.TakeRight(this, n) - override def drop(n: Int): IndexedSeqView[A] = new IndexedSeqView.Drop(this, n) - override def dropRight(n: Int): IndexedSeqView[A] = new IndexedSeqView.DropRight(this, n) - override def map[B](f: A => B): IndexedSeqView[B] = new IndexedSeqView.Map(this, f) - override def reverse: IndexedSeqView[A] = new IndexedSeqView.Reverse(this) - override def slice(from: Int, until: Int): IndexedSeqView[A] = new IndexedSeqView.Slice(this, from, until) - override def tapEach[U](f: A => U): IndexedSeqView[A] = new IndexedSeqView.Map(this, { (a: A) => f(a); a}) - - def concat[B >: A](suffix: IndexedSeqView.SomeIndexedSeqOps[B]): IndexedSeqView[B] = new IndexedSeqView.Concat(this, suffix) - def appendedAll[B >: A](suffix: IndexedSeqView.SomeIndexedSeqOps[B]): IndexedSeqView[B] = new IndexedSeqView.Concat(this, suffix) - def prependedAll[B >: A](prefix: IndexedSeqView.SomeIndexedSeqOps[B]): IndexedSeqView[B] = new IndexedSeqView.Concat(prefix, this) + override def view(from: Int, until: Int): IndexedSeqView[A]^{this} = view.slice(from, until) + + override def iterator: Iterator[A]^{this} = new IndexedSeqView.IndexedSeqViewIterator(this) + override def reverseIterator: Iterator[A]^{this} = new IndexedSeqView.IndexedSeqViewReverseIterator(this) + + override def appended[B >: A](elem: B): IndexedSeqView[B]^{this} = new IndexedSeqView.Appended(this, elem) + override def prepended[B >: A](elem: B): IndexedSeqView[B]^{this} = new IndexedSeqView.Prepended(elem, this) + override def take(n: Int): IndexedSeqView[A]^{this} = new IndexedSeqView.Take(this, n) + override def takeRight(n: Int): IndexedSeqView[A]^{this} = new IndexedSeqView.TakeRight(this, n) + override def drop(n: Int): IndexedSeqView[A]^{this} = new IndexedSeqView.Drop(this, n) + override def dropRight(n: Int): IndexedSeqView[A]^{this} = new IndexedSeqView.DropRight(this, n) + override def map[B](f: A => B): IndexedSeqView[B]^{this, f} = new IndexedSeqView.Map(this, f) + override def reverse: IndexedSeqView[A]^{this} = new IndexedSeqView.Reverse(this) + override def slice(from: Int, until: Int): IndexedSeqView[A]^{this} = new IndexedSeqView.Slice(this, from, until) + override def tapEach[U](f: A => U): IndexedSeqView[A]^{this, f} = new IndexedSeqView.Map(this, { (a: A) => f(a); a}) + + def concat[B >: A](suffix: IndexedSeqView.SomeIndexedSeqOps[B]^): IndexedSeqView[B]^{this, suffix} = new IndexedSeqView.Concat(this, suffix) + def appendedAll[B >: A](suffix: IndexedSeqView.SomeIndexedSeqOps[B]^): IndexedSeqView[B]^{this, suffix} = new IndexedSeqView.Concat(this, suffix) + def prependedAll[B >: A](prefix: IndexedSeqView.SomeIndexedSeqOps[B]^): IndexedSeqView[B]^{this, prefix} = new IndexedSeqView.Concat(prefix, this) @nowarn("""cat=deprecation&origin=scala\.collection\.Iterable\.stringPrefix""") override protected[this] def stringPrefix: String = "IndexedSeqView" @@ -50,7 +52,7 @@ trait IndexedSeqView[+A] extends IndexedSeqOps[A, View, View[A]] with SeqView[A] object IndexedSeqView { @SerialVersionUID(3L) - private[collection] class IndexedSeqViewIterator[A](self: IndexedSeqView[A]) extends AbstractIterator[A] with Serializable { + private[collection] class IndexedSeqViewIterator[A](self: IndexedSeqView[A]^) extends AbstractIterator[A] with Serializable { private[this] var current = 0 private[this] var remainder = self.length override def knownSize: Int = remainder @@ -64,7 +66,7 @@ object IndexedSeqView { r } else Iterator.empty.next() - override def drop(n: Int): Iterator[A] = { + override def drop(n: Int): Iterator[A]^{this} = { if (n > 0) { current += n remainder = Math.max(0, remainder - n) @@ -72,7 +74,7 @@ object IndexedSeqView { this } - override protected def sliceIterator(from: Int, until: Int): Iterator[A] = { + override protected def sliceIterator(from: Int, until: Int): Iterator[A]^{this} = { def formatRange(value : Int) : Int = if (value < 0) 0 else if (value > remainder) remainder else value @@ -84,7 +86,7 @@ object IndexedSeqView { } } @SerialVersionUID(3L) - private[collection] class IndexedSeqViewReverseIterator[A](self: IndexedSeqView[A]) extends AbstractIterator[A] with Serializable { + private[collection] class IndexedSeqViewReverseIterator[A](self: IndexedSeqView[A]^) extends AbstractIterator[A] with Serializable { private[this] var remainder = self.length private[this] var pos = remainder - 1 @inline private[this] def _hasNext: Boolean = remainder > 0 @@ -99,7 +101,7 @@ object IndexedSeqView { // from < 0 means don't move pos, until < 0 means don't limit remainder // - override protected def sliceIterator(from: Int, until: Int): Iterator[A] = { + override protected def sliceIterator(from: Int, until: Int): Iterator[A]^{this} = { if (_hasNext) { if (remainder <= from) remainder = 0 // exhausted by big skip else if (from <= 0) { // no skip, pos is same @@ -122,43 +124,43 @@ object IndexedSeqView { type SomeIndexedSeqOps[A] = IndexedSeqOps[A, AnyConstr, _] @SerialVersionUID(3L) - class Id[+A](underlying: SomeIndexedSeqOps[A]) + class Id[+A](underlying: SomeIndexedSeqOps[A]^) extends SeqView.Id(underlying) with IndexedSeqView[A] @SerialVersionUID(3L) - class Appended[+A](underlying: SomeIndexedSeqOps[A], elem: A) + class Appended[+A](underlying: SomeIndexedSeqOps[A]^, elem: A) extends SeqView.Appended(underlying, elem) with IndexedSeqView[A] @SerialVersionUID(3L) - class Prepended[+A](elem: A, underlying: SomeIndexedSeqOps[A]) + class Prepended[+A](elem: A, underlying: SomeIndexedSeqOps[A]^) extends SeqView.Prepended(elem, underlying) with IndexedSeqView[A] @SerialVersionUID(3L) - class Concat[A](prefix: SomeIndexedSeqOps[A], suffix: SomeIndexedSeqOps[A]) + class Concat[A](prefix: SomeIndexedSeqOps[A]^, suffix: SomeIndexedSeqOps[A]^) extends SeqView.Concat[A](prefix, suffix) with IndexedSeqView[A] @SerialVersionUID(3L) - class Take[A](underlying: SomeIndexedSeqOps[A], n: Int) + class Take[A](underlying: SomeIndexedSeqOps[A]^, n: Int) extends SeqView.Take(underlying, n) with IndexedSeqView[A] @SerialVersionUID(3L) - class TakeRight[A](underlying: SomeIndexedSeqOps[A], n: Int) + class TakeRight[A](underlying: SomeIndexedSeqOps[A]^, n: Int) extends SeqView.TakeRight(underlying, n) with IndexedSeqView[A] @SerialVersionUID(3L) - class Drop[A](underlying: SomeIndexedSeqOps[A], n: Int) + class Drop[A](underlying: SomeIndexedSeqOps[A]^, n: Int) extends SeqView.Drop[A](underlying, n) with IndexedSeqView[A] @SerialVersionUID(3L) - class DropRight[A](underlying: SomeIndexedSeqOps[A], n: Int) + class DropRight[A](underlying: SomeIndexedSeqOps[A]^, n: Int) extends SeqView.DropRight[A](underlying, n) with IndexedSeqView[A] @SerialVersionUID(3L) - class Map[A, B](underlying: SomeIndexedSeqOps[A], f: A => B) + class Map[A, B](underlying: SomeIndexedSeqOps[A]^, f: A => B) extends SeqView.Map(underlying, f) with IndexedSeqView[B] @SerialVersionUID(3L) - class Reverse[A](underlying: SomeIndexedSeqOps[A]) extends SeqView.Reverse[A](underlying) with IndexedSeqView[A] { + class Reverse[A](underlying: SomeIndexedSeqOps[A]^) extends SeqView.Reverse[A](underlying) with IndexedSeqView[A] { override def reverse: IndexedSeqView[A] = underlying match { case x: IndexedSeqView[A] => x case _ => super.reverse @@ -166,7 +168,7 @@ object IndexedSeqView { } @SerialVersionUID(3L) - class Slice[A](underlying: SomeIndexedSeqOps[A], from: Int, until: Int) extends AbstractIndexedSeqView[A] { + class Slice[A](underlying: SomeIndexedSeqOps[A]^, from: Int, until: Int) extends AbstractIndexedSeqView[A] { protected val lo = from max 0 protected val hi = (until max 0) min underlying.length protected val len = (hi - lo) max 0 diff --git a/library/src/scala/collection/Iterable.scala b/library/src/scala/collection/Iterable.scala index 2a2df1261b1d..df21b227e2ab 100644 --- a/library/src/scala/collection/Iterable.scala +++ b/library/src/scala/collection/Iterable.scala @@ -319,7 +319,7 @@ transparent trait IterableOps[+A, +CC[_], +C] extends Any with IterableOnce[A] w * is `O(this.size min that.size)` instead of `O(this.size + that.size)`. * The method should be overridden if computing `size` is cheap and `knownSize` returns `-1`. */ - def sizeCompare(that: Iterable[_]): Int = { + def sizeCompare(that: Iterable[_]^): Int = { val thatKnownSize = that.knownSize if (thatKnownSize >= 0) this sizeCompare thatKnownSize diff --git a/library/src/scala/collection/LinearSeq.scala b/library/src/scala/collection/LinearSeq.scala index f6ae57168fcd..d5792be72595 100644 --- a/library/src/scala/collection/LinearSeq.scala +++ b/library/src/scala/collection/LinearSeq.scala @@ -33,7 +33,7 @@ trait LinearSeq[+A] extends Seq[A] object LinearSeq extends SeqFactory.Delegate[LinearSeq](immutable.LinearSeq) /** Base trait for linear Seq operations */ -transparent trait LinearSeqOps[+A, +CC[X] <: LinearSeq[X], +C <: LinearSeq[A] with LinearSeqOps[A, CC, C]] extends Any with SeqOps[A, CC, C] { +transparent trait LinearSeqOps[+A, +CC[X] <: LinearSeq[X], +C <: LinearSeq[A] with LinearSeqOps[A, CC, C]] extends Any with StrictSeqOps[A, CC, C] { self => /** @inheritdoc * @@ -260,7 +260,7 @@ transparent trait LinearSeqOps[+A, +CC[X] <: LinearSeq[X], +C <: LinearSeq[A] wi } } -transparent trait StrictOptimizedLinearSeqOps[+A, +CC[X] <: LinearSeq[X], +C <: LinearSeq[A] with StrictOptimizedLinearSeqOps[A, CC, C]] extends Any with LinearSeqOps[A, CC, C] with StrictOptimizedSeqOps[A, CC, C] { +transparent trait StrictOptimizedLinearSeqOps[+A, +CC[X] <: LinearSeq[X], +C <: LinearSeq[A] with StrictOptimizedLinearSeqOps[A, CC, C]] extends Any with LinearSeqOps[A, CC, C] with StrictOptimizedSeqOps[A, CC, C] { self => // A more efficient iterator implementation than the default LinearSeqIterator override def iterator: Iterator[A] = new AbstractIterator[A] { private[this] var current = StrictOptimizedLinearSeqOps.this diff --git a/library/src/scala/collection/Seq.scala b/library/src/scala/collection/Seq.scala index 2f960a47f54f..81bc4204b846 100644 --- a/library/src/scala/collection/Seq.scala +++ b/library/src/scala/collection/Seq.scala @@ -13,6 +13,8 @@ package scala.collection import scala.language.`2.13` +import language.experimental.captureChecking + import scala.collection.immutable.Range import scala.util.hashing.MurmurHash3 import Searching.{Found, InsertionPoint, SearchResult} @@ -25,9 +27,10 @@ import scala.annotation.nowarn trait Seq[+A] extends Iterable[A] with PartialFunction[Int, A] - with SeqOps[A, Seq, Seq[A]] + with StrictSeqOps[A, Seq, Seq[A]] with IterableFactoryDefaults[A, Seq] - with Equals { + with Equals + with caps.Pure { override def iterableFactory: SeqFactory[Seq] = Seq @@ -55,6 +58,32 @@ trait Seq[+A] @SerialVersionUID(3L) object Seq extends SeqFactory.Delegate[Seq](immutable.Seq) +/** Base trait for strict Seq operations. + * + * Contains strict overrides of SeqOps operations, where the parameters are also captured. + */ +transparent trait StrictSeqOps[+A, +CC[B] <: caps.Pure, +C] extends Any with SeqOps[A, CC, C] with caps.Pure { + override def iterableFactory: StrictIterableFactory[CC] + + override def prependedAll[B >: A](prefix: IterableOnce[B]^): CC[B] = iterableFactory.from(prefix match { + case prefix: Iterable[B] => new View.Concat(prefix, this) + case _ => prefix.iterator ++ iterator + }) + + @inline override final def ++: [B >: A](prefix: IterableOnce[B]^): CC[B] = prependedAll(prefix) + + override def appendedAll[B >: A](suffix: IterableOnce[B]^): CC[B] = super.concat(suffix) + + @inline override final def :++ [B >: A](suffix: IterableOnce[B]^): CC[B] = appendedAll(suffix) + + @inline override final def concat[B >: A](suffix: IterableOnce[B]^): CC[B] = appendedAll(suffix) + + override def reverseMap[B](f: A => B): CC[B] = iterableFactory.from(new View.Map(View.fromIteratorProvider(() => reverseIterator), f)) + + override def patch[B >: A](from: Int, other: IterableOnce[B]^, replaced: Int): CC[B] = + iterableFactory.from(new View.Patched(this, from, other, replaced)) +} + /** Base trait for Seq operations * * @tparam A the element type of the collection @@ -76,7 +105,7 @@ object Seq extends SeqFactory.Delegate[Seq](immutable.Seq) * @define Coll `Seq` */ transparent trait SeqOps[+A, +CC[_], +C] extends Any - with IterableOps[A, CC, C] { self => + with IterableOps[A, CC, C] { self: SeqOps[A, CC, C]^ => override def view: SeqView[A] = new SeqView.Id[A](this) @@ -110,14 +139,14 @@ transparent trait SeqOps[+A, +CC[_], +C] extends Any * @return a new $coll consisting of `value` followed * by all elements of this $coll. */ - def prepended[B >: A](elem: B): CC[B] = iterableFactory.from(new View.Prepended(elem, this)) + def prepended[B >: A](elem: B): CC[B]^{this} = iterableFactory.from(new View.Prepended(elem, this)) /** Alias for `prepended`. * * Note that :-ending operators are right associative (see example). * A mnemonic for `+:` vs. `:+` is: the COLon goes on the COLlection side. */ - @`inline` final def +: [B >: A](elem: B): CC[B] = prepended(elem) + @`inline` final def +: [B >: A](elem: B): CC[B]^{this} = prepended(elem) /** A copy of this $coll with an element appended. * @@ -140,14 +169,14 @@ transparent trait SeqOps[+A, +CC[_], +C] extends Any * @return a new $coll consisting of * all elements of this $coll followed by `value`. */ - def appended[B >: A](elem: B): CC[B] = iterableFactory.from(new View.Appended(this, elem)) + def appended[B >: A](elem: B): CC[B]^{this} = iterableFactory.from(new View.Appended(this, elem)) /** Alias for `appended`. * * Note that :-ending operators are right associative (see example). * A mnemonic for `+:` vs. `:+` is: the COLon goes on the COLlection side. */ - @`inline` final def :+ [B >: A](elem: B): CC[B] = appended(elem) + @`inline` final def :+ [B >: A](elem: B): CC[B]^{this} = appended(elem) /** As with `:++`, returns a new collection containing the elements from the left operand followed by the * elements from the right operand. @@ -161,13 +190,13 @@ transparent trait SeqOps[+A, +CC[_], +C] extends Any * @return a new $coll which contains all elements of `prefix` followed * by all the elements of this $coll. */ - def prependedAll[B >: A](prefix: IterableOnce[B]): CC[B] = iterableFactory.from(prefix match { + def prependedAll[B >: A](prefix: IterableOnce[B]^): CC[B]^{this, prefix} = iterableFactory.from(prefix match { case prefix: Iterable[B] => new View.Concat(prefix, this) case _ => prefix.iterator ++ iterator }) /** Alias for `prependedAll`. */ - @`inline` override final def ++: [B >: A](prefix: IterableOnce[B]): CC[B] = prependedAll(prefix) + @inline override def ++: [B >: A](prefix: IterableOnce[B]^): CC[B]^{this, prefix} = prependedAll(prefix) /** 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 @@ -178,14 +207,14 @@ transparent trait SeqOps[+A, +CC[_], +C] extends Any * @return a new collection of type `CC[B]` which contains all elements * of this $coll followed by all elements of `suffix`. */ - def appendedAll[B >: A](suffix: IterableOnce[B]): CC[B] = super.concat(suffix) + def appendedAll[B >: A](suffix: IterableOnce[B]^): CC[B]^{this, suffix} = super.concat(suffix) /** Alias for `appendedAll`. */ - @inline final def :++ [B >: A](suffix: IterableOnce[B]): CC[B] = appendedAll(suffix) + @inline def :++ [B >: A](suffix: IterableOnce[B]^): CC[B]^{this, suffix} = appendedAll(suffix) // Make `concat` an alias for `appendedAll` so that it benefits from performance // overrides of this method - @inline final override def concat[B >: A](suffix: IterableOnce[B]): CC[B] = appendedAll(suffix) + @inline override def concat[B >: A](suffix: IterableOnce[B]^): CC[B]^{this, suffix} = appendedAll(suffix) /** Produces a new sequence which contains all elements of this $coll and also all elements of * a given sequence. `xs union ys` is equivalent to `xs ++ ys`. @@ -196,7 +225,7 @@ transparent trait SeqOps[+A, +CC[_], +C] extends Any * followed by all elements of `that`. */ @deprecated("Use `concat` instead", "2.13.0") - @inline final def union[B >: A](that: Seq[B]): CC[B] = concat(that) + @inline final def union[B >: A](that: Seq[B]): CC[B]^{this} = concat(that) final override def size: Int = length @@ -204,7 +233,7 @@ transparent trait SeqOps[+A, +CC[_], +C] extends Any * * @return a new $coll consisting of all the elements of this $coll without duplicates. */ - def distinct: C = distinctBy(identity) + def distinct: C^{this} = distinctBy(identity) /** Selects all the elements of this $coll ignoring the duplicates as determined by `==` after applying * the transforming function `f`. @@ -213,7 +242,7 @@ transparent trait SeqOps[+A, +CC[_], +C] extends Any * @tparam B the type of the elements after being transformed by `f` * @return a new $coll consisting of all the elements of this $coll without duplicates. */ - def distinctBy[B](f: A => B): C = fromSpecific(new View.DistinctBy(this, f)) + def distinctBy[B](f: A -> B): C^{this} = fromSpecific(new View.DistinctBy(this, f)) /** Returns a new $coll with the elements of this $coll in reverse order. * @@ -222,7 +251,7 @@ transparent trait SeqOps[+A, +CC[_], +C] extends Any * * @return a new $coll with all elements of this $coll in reverse order. */ - def reverse: C = fromSpecific(reversed) + def reverse: C^{this} = fromSpecific(reversed) /** An iterator yielding the elements of this $coll in reverse order. * @@ -232,7 +261,7 @@ transparent trait SeqOps[+A, +CC[_], +C] extends Any * * @return an iterator yielding the elements of this $coll in reverse order. */ - def reverseIterator: Iterator[A] = reversed.iterator + def reverseIterator: Iterator[A]^{this} = reversed.iterator /** Tests whether this $coll contains the given sequence at a given index. * @@ -244,7 +273,7 @@ transparent trait SeqOps[+A, +CC[_], +C] extends Any * @return `true` if the sequence `that` is contained in this $coll at * index `offset`, otherwise `false`. */ - def startsWith[B >: A](that: IterableOnce[B], offset: Int = 0): Boolean = { + def startsWith[B >: A](that: IterableOnce[B]^, offset: Int = 0): Boolean = { val i = iterator drop offset val j = that.iterator while (j.hasNext && i.hasNext) @@ -259,7 +288,7 @@ transparent trait SeqOps[+A, +CC[_], +C] extends Any * @param that the sequence to test * @return `true` if this $coll has `that` as a suffix, `false` otherwise. */ - def endsWith[B >: A](that: Iterable[B]): Boolean = { + def endsWith[B >: A](that: Iterable[B]^): Boolean = { if (that.isEmpty) true else { val i = iterator.drop(length - that.size) @@ -291,7 +320,7 @@ transparent trait SeqOps[+A, +CC[_], +C] extends Any * all elements of this $coll followed by the minimal number of occurrences of `elem` so * that the resulting collection has a length of at least `len`. */ - def padTo[B >: A](len: Int, elem: B): CC[B] = iterableFactory.from(new View.PadTo(this, len, elem)) + def padTo[B >: A](len: Int, elem: B): CC[B]^{this} = iterableFactory.from(new View.PadTo(this, len, elem)) /** Computes the length of the longest segment that starts from the first element * and whose elements all satisfy some predicate. @@ -527,7 +556,7 @@ transparent trait SeqOps[+A, +CC[_], +C] extends Any def contains[A1 >: A](elem: A1): Boolean = exists (_ == elem) @deprecated("Use .reverseIterator.map(f).to(...) instead of .reverseMap(f)", "2.13.0") - def reverseMap[B](f: A => B): CC[B] = iterableFactory.from(new View.Map(View.fromIteratorProvider(() => reverseIterator), f)) + def reverseMap[B](f: A => B): CC[B]^{this, f} = iterableFactory.from(new View.Map(View.fromIteratorProvider(() => reverseIterator), f)) /** Iterates over distinct permutations of elements. * @@ -541,7 +570,7 @@ transparent trait SeqOps[+A, +CC[_], +C] extends Any * // List(b, b, a) * }}} */ - def permutations: Iterator[C] = + def permutations: Iterator[C^{this}]^{this} = if (isEmpty) Iterator.single(coll) else new PermutationsItr @@ -582,7 +611,7 @@ transparent trait SeqOps[+A, +CC[_], +C] extends Any * // List(b, a) * }}} */ - def combinations(n: Int): Iterator[C] = + def combinations(n: Int): Iterator[C^{this}]^{this} = if (n < 0 || n > size) Iterator.empty else new CombinationsItr(n) @@ -717,7 +746,7 @@ transparent trait SeqOps[+A, +CC[_], +C] extends Any * @return a $coll consisting of the elements of this $coll * sorted according to the ordering `ord`. */ - def sorted[B >: A](implicit ord: Ordering[B]): C = { + def sorted[B >: A](implicit ord: Ordering[B]): C^{this} = { val len = this.length val b = newSpecificBuilder if (len == 1) b += head @@ -754,7 +783,7 @@ transparent trait SeqOps[+A, +CC[_], +C] extends Any * List("Bobby", "Bob", "John", "Steve", "Tom") * }}} */ - def sortWith(lt: (A, A) => Boolean): C = sorted(Ordering.fromLessThan(lt)) + def sortWith(lt: (A, A) => Boolean): C^{this} = sorted(Ordering.fromLessThan(lt)) /** Sorts this $coll according to the Ordering which results from transforming * an implicitly given Ordering with a transformation function. @@ -781,7 +810,7 @@ transparent trait SeqOps[+A, +CC[_], +C] extends Any * res0: Array[String] = Array(The, dog, fox, the, lazy, over, brown, quick, jumped) * }}} */ - def sortBy[B](f: A => B)(implicit ord: Ordering[B]): C = sorted(ord on f) + def sortBy[B](f: A => B)(implicit ord: Ordering[B]): C^{this} = sorted(ord on f) /** Produces the range of all indices of this sequence. * $willForceEvaluation @@ -809,7 +838,7 @@ transparent trait SeqOps[+A, +CC[_], +C] extends Any */ def lengthCompare(len: Int): Int = super.sizeCompare(len) - override final def sizeCompare(that: Iterable[_]): Int = lengthCompare(that) + override final def sizeCompare(that: Iterable[_]^): Int = lengthCompare(that) /** Compares the length of this $coll to the size of another `Iterable`. * @@ -824,7 +853,7 @@ transparent trait SeqOps[+A, +CC[_], +C] extends Any * is `O(this.length min that.size)` instead of `O(this.length + that.size)`. * The method should be overridden if computing `size` is cheap and `knownSize` returns `-1`. */ - def lengthCompare(that: Iterable[_]): Int = super.sizeCompare(that) + def lengthCompare(that: Iterable[_]^): Int = super.sizeCompare(that) /** Returns a value class containing operations for comparing the length of this $coll to a test value. * @@ -840,7 +869,7 @@ transparent trait SeqOps[+A, +CC[_], +C] extends Any * this.lengthIs > len // this.lengthCompare(len) > 0 * }}} */ - @inline final def lengthIs: IterableOps.SizeCompareOps = new IterableOps.SizeCompareOps(this) + @inline final def lengthIs: IterableOps.SizeCompareOps^{this} = new IterableOps.SizeCompareOps(caps.unsafe.unsafeAssumePure(this) /* see comment in SizeCompareOps*/) override def isEmpty: Boolean = lengthCompare(0) == 0 @@ -851,7 +880,7 @@ transparent trait SeqOps[+A, +CC[_], +C] extends Any * @tparam B the type of the elements of collection `that`. * @return `true` if both collections contain equal elements in the same order, `false` otherwise. */ - def sameElements[B >: A](that: IterableOnce[B]): Boolean = { + def sameElements[B >: A](that: IterableOnce[B]^): Boolean = { val thisKnownSize = knownSize if (thisKnownSize != -1) { val thatKnownSize = that.knownSize @@ -891,7 +920,7 @@ transparent trait SeqOps[+A, +CC[_], +C] extends Any * ''n'' times in `that`, then the first ''n'' occurrences of `x` will not form * part of the result, but any following occurrences will. */ - def diff[B >: A](that: Seq[B]): C = { + def diff[B >: A](that: Seq[B]): C^{this} = { val occ = occCounts(that) fromSpecific(iterator.filter { x => var include = false @@ -916,7 +945,7 @@ transparent trait SeqOps[+A, +CC[_], +C] extends Any * ''n'' times in `that`, then the first ''n'' occurrences of `x` will be retained * in the result, but any following occurrences will be omitted. */ - def intersect[B >: A](that: Seq[B]): C = { + def intersect[B >: A](that: Seq[B]): C^{this} = { val occ = occCounts(that) fromSpecific(iterator.filter { x => var include = true @@ -946,7 +975,7 @@ transparent trait SeqOps[+A, +CC[_], +C] extends Any * except that `replaced` elements starting from `from` are replaced * by all the elements of `other`. */ - def patch[B >: A](from: Int, other: IterableOnce[B], replaced: Int): CC[B] = + def patch[B >: A](from: Int, other: IterableOnce[B]^, replaced: Int): CC[B]^{this, other} = iterableFactory.from(new View.Patched(this, from, other, replaced)) /** A copy of this $coll with one single replaced element. @@ -958,7 +987,7 @@ transparent trait SeqOps[+A, +CC[_], +C] extends Any * lazy collection this exception may be thrown at a later time or not at * all (if the end of the collection is never evaluated). */ - def updated[B >: A](index: Int, elem: B): CC[B] = { + def updated[B >: A](index: Int, elem: B): CC[B]^{this} = { if(index < 0) throw new IndexOutOfBoundsException(index.toString) val k = knownSize if(k >= 0 && index >= k) throw new IndexOutOfBoundsException(index.toString) @@ -1020,7 +1049,7 @@ transparent trait SeqOps[+A, +CC[_], +C] extends Any def search[B >: A](elem: B, from: Int, to: Int) (implicit ord: Ordering[B]): SearchResult = linearSearch(view.slice(from, to), elem, math.max(0, from))(ord) - private[this] def linearSearch[B >: A](c: View[A], elem: B, offset: Int) + private[this] def linearSearch[B >: A](c: View[A]^, elem: B, offset: Int) (implicit ord: Ordering[B]): SearchResult = { var idx = offset val it = c.iterator @@ -1170,7 +1199,7 @@ object SeqOps { * @param wlen Just in case we're only IndexedSeq and not IndexedSeqOptimized * @return KMP jump table for target sequence */ - private def kmpJumpTable[B](Wopt: IndexedSeqView[B], wlen: Int) = { + private def kmpJumpTable[B](Wopt: IndexedSeqView[B]^, wlen: Int) = { val arr = new Array[Int](wlen) var pos = 2 var cnd = 0 diff --git a/library/src/scala/collection/StrictOptimizedSeqOps.scala b/library/src/scala/collection/StrictOptimizedSeqOps.scala index cf59f7b05b9e..2d2b87dce410 100644 --- a/library/src/scala/collection/StrictOptimizedSeqOps.scala +++ b/library/src/scala/collection/StrictOptimizedSeqOps.scala @@ -18,10 +18,8 @@ import scala.language.`2.13` * Trait that overrides operations on sequences in order * to take advantage of strict builders. */ -transparent trait StrictOptimizedSeqOps [+A, +CC[_], +C] - extends Any - with SeqOps[A, CC, C] - with StrictOptimizedIterableOps[A, CC, C] { +transparent trait StrictOptimizedSeqOps [+A, +CC[_] <: caps.Pure, +C] + extends Any with StrictSeqOps[A, CC, C] with StrictOptimizedIterableOps[A, CC, C] { override def distinctBy[B](f: A => B): C = { val builder = newSpecificBuilder diff --git a/library/src/scala/collection/immutable/Seq.scala b/library/src/scala/collection/immutable/Seq.scala index fc11b697dc0c..10104a8fc05c 100644 --- a/library/src/scala/collection/immutable/Seq.scala +++ b/library/src/scala/collection/immutable/Seq.scala @@ -30,7 +30,7 @@ trait Seq[+A] extends Iterable[A] * @define coll immutable sequence * @define Coll `immutable.Seq` */ -transparent trait SeqOps[+A, +CC[_], +C] extends Any with collection.SeqOps[A, CC, C] +transparent trait SeqOps[+A, +CC[B] <: collection.StrictSeqOps[B, collection.Seq, collection.Seq[B]], +C] extends Any with collection.StrictSeqOps[A, CC, C] /** * $factoryInfo @@ -119,9 +119,9 @@ object IndexedSeq extends SeqFactory.Delegate[IndexedSeq](Vector) { } /** Base trait for immutable indexed Seq operations */ -transparent trait IndexedSeqOps[+A, +CC[_], +C] +transparent trait IndexedSeqOps[+A, +CC[B] <: collection.StrictSeqOps[B, collection.Seq, collection.Seq[B]], +C] extends SeqOps[A, CC, C] - with collection.IndexedSeqOps[A, CC, C] { + with collection.StrictIndexedSeqOps[A, CC, C] { override def slice(from: Int, until: Int): C = { // since we are immutable we can just share the same collection diff --git a/library/src/scala/collection/immutable/StrictOptimizedSeqOps.scala b/library/src/scala/collection/immutable/StrictOptimizedSeqOps.scala index 51be923eb719..20bd90477454 100644 --- a/library/src/scala/collection/immutable/StrictOptimizedSeqOps.scala +++ b/library/src/scala/collection/immutable/StrictOptimizedSeqOps.scala @@ -19,9 +19,9 @@ import scala.collection.generic.CommonErrors /** Trait that overrides operations to take advantage of strict builders. */ -transparent trait StrictOptimizedSeqOps[+A, +CC[_], +C] +transparent trait StrictOptimizedSeqOps[+A, +CC[B] <: collection.StrictSeqOps[B, collection.Seq, collection.Seq[B]], +C] extends Any - with SeqOps[A, CC, C] + with StrictSeqOps[A, CC, C] with collection.StrictOptimizedSeqOps[A, CC, C] with StrictOptimizedIterableOps[A, CC, C] { diff --git a/library/src/scala/collection/immutable/WrappedString.scala b/library/src/scala/collection/immutable/WrappedString.scala index 76d69c092673..8e40b38be2cb 100644 --- a/library/src/scala/collection/immutable/WrappedString.scala +++ b/library/src/scala/collection/immutable/WrappedString.scala @@ -34,7 +34,7 @@ import scala.collection.mutable.{Builder, StringBuilder} */ @SerialVersionUID(3L) final class WrappedString(private val self: String) extends AbstractSeq[Char] with IndexedSeq[Char] - with IndexedSeqOps[Char, IndexedSeq, WrappedString] + with StrictIndexedSeqOps[Char, IndexedSeq, WrappedString] with Serializable { def apply(i: Int): Char = self.charAt(i) @@ -101,7 +101,7 @@ final class WrappedString(private val self: String) extends AbstractSeq[Char] wi override def appendedAll[B >: Char](suffix: IterableOnce[B]): IndexedSeq[B] = suffix match { case s: WrappedString => new WrappedString(self concat s.self) - case _ => super.appendedAll(suffix) + case _ => Predef.??? // TODO super.appendedAll(suffix) } override def sameElements[B >: Char](o: IterableOnce[B]) = o match { diff --git a/library/src/scala/collection/mutable/ArrayDeque.scala b/library/src/scala/collection/mutable/ArrayDeque.scala index 186df5b21c74..07e3c559236a 100644 --- a/library/src/scala/collection/mutable/ArrayDeque.scala +++ b/library/src/scala/collection/mutable/ArrayDeque.scala @@ -569,7 +569,7 @@ object ArrayDeque extends StrictOptimizedSeqFactory[ArrayDeque] { } } -transparent trait ArrayDequeOps[A, +CC[_], +C <: AnyRef] extends StrictOptimizedSeqOps[A, CC, C] { +transparent trait ArrayDequeOps[A, +CC[_] <: caps.Pure, +C <: AnyRef] extends StrictOptimizedSeqOps[A, CC, C] { protected def array: Array[AnyRef] final override def clone(): C = klone() diff --git a/library/src/scala/collection/mutable/IndexedSeq.scala b/library/src/scala/collection/mutable/IndexedSeq.scala index 31feb34395f8..9a7fdd506876 100644 --- a/library/src/scala/collection/mutable/IndexedSeq.scala +++ b/library/src/scala/collection/mutable/IndexedSeq.scala @@ -26,7 +26,7 @@ trait IndexedSeq[T] extends Seq[T] @SerialVersionUID(3L) object IndexedSeq extends SeqFactory.Delegate[IndexedSeq](ArrayBuffer) -transparent trait IndexedSeqOps[A, +CC[_], +C <: AnyRef] +transparent trait IndexedSeqOps[A, +CC[_] <: caps.Pure, +C <: AnyRef] extends scala.collection.IndexedSeqOps[A, CC, C] with SeqOps[A, CC, C] { diff --git a/library/src/scala/collection/mutable/Seq.scala b/library/src/scala/collection/mutable/Seq.scala index ed5416ab7fe6..cf73d896be64 100644 --- a/library/src/scala/collection/mutable/Seq.scala +++ b/library/src/scala/collection/mutable/Seq.scala @@ -36,8 +36,8 @@ object Seq extends SeqFactory.Delegate[Seq](ArrayBuffer) * @define coll mutable sequence * @define Coll `mutable.Seq` */ -transparent trait SeqOps[A, +CC[_], +C <: AnyRef] - extends collection.SeqOps[A, CC, C] +transparent trait SeqOps[A, +CC[_] <: caps.Pure, +C <: AnyRef] + extends collection.StrictSeqOps[A, CC, C] with Cloneable[C] { override def clone(): C = { From b3f868adde65335fcd659e2257a826cf7cab0fcd Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Wed, 13 Aug 2025 20:41:13 +0200 Subject: [PATCH 054/101] Capture check LazyZipOps, LinearSeq, Searching --- TODO.md | 7 +- library/src/scala/collection/LazyZipOps.scala | 79 ++++++++++--------- library/src/scala/collection/LinearSeq.scala | 6 +- library/src/scala/collection/Searching.scala | 2 + 4 files changed, 49 insertions(+), 45 deletions(-) diff --git a/TODO.md b/TODO.md index 2de8c5f99244..09b3dc27b6c3 100644 --- a/TODO.md +++ b/TODO.md @@ -74,7 +74,6 @@ - [ ] library/src/scala/Product8.scala - [ ] library/src/scala/Product9.scala - [ ] library/src/scala/Proxy.scala -- [x] library/src/scala/Pure.scala - [x] library/src/scala/Selectable.scala - [ ] library/src/scala/SerialVersionUID.scala - [ ] library/src/scala/Short.scala @@ -199,11 +198,11 @@ - [x] library/src/scala/collection/IterableOnce.scala - [x] library/src/scala/collection/Iterator.scala - [ ] library/src/scala/collection/JavaConverters.scala -- [ ] library/src/scala/collection/LazyZipOps.scala -- [ ] library/src/scala/collection/LinearSeq.scala +- [x] library/src/scala/collection/LazyZipOps.scala +- [x] library/src/scala/collection/LinearSeq.scala - [ ] library/src/scala/collection/Map.scala - [ ] library/src/scala/collection/MapView.scala -- [ ] library/src/scala/collection/Searching.scala +- [x] library/src/scala/collection/Searching.scala - [x] library/src/scala/collection/Seq.scala - [ ] library/src/scala/collection/SeqMap.scala - [ ] library/src/scala/collection/SeqView.scala diff --git a/library/src/scala/collection/LazyZipOps.scala b/library/src/scala/collection/LazyZipOps.scala index dba5b6432def..d68f5e966719 100644 --- a/library/src/scala/collection/LazyZipOps.scala +++ b/library/src/scala/collection/LazyZipOps.scala @@ -14,6 +14,7 @@ package scala.collection import scala.language.`2.13` import scala.language.implicitConversions +import language.experimental.captureChecking /** Decorator representing lazily zipped pairs. * @@ -22,7 +23,7 @@ import scala.language.implicitConversions * * Note: will not terminate for infinite-sized collections. */ -final class LazyZip2[+El1, +El2, C1] private[collection](src: C1, coll1: Iterable[El1], coll2: Iterable[El2]) { +final class LazyZip2[+El1, +El2, C1] private[collection](src: C1, coll1: Iterable[El1]^, coll2: Iterable[El2]^) { /** Zips `that` iterable collection with an existing `LazyZip2`. The elements in each collection are * not consumed until a strict operation is invoked on the returned `LazyZip3` decorator. @@ -32,11 +33,11 @@ final class LazyZip2[+El1, +El2, C1] private[collection](src: C1, coll1: Iterabl * @return a decorator `LazyZip3` that allows strict operations to be performed on the lazily evaluated tuples or * chained calls to `lazyZip`. Implicit conversion to `Iterable[(El1, El2, B)]` is also supported. */ - def lazyZip[B](that: Iterable[B]): LazyZip3[El1, El2, B, C1] = new LazyZip3(src, coll1, coll2, that) + def lazyZip[B](that: Iterable[B]^): LazyZip3[El1, El2, B, C1]^{this, that} = new LazyZip3(src, coll1, coll2, that) - def map[B, C](f: (El1, El2) => B)(implicit bf: BuildFrom[C1, B, C]): C = { + def map[B, C](f: (El1, El2) => B)(implicit bf: BuildFrom[C1, B, C]): C^{this, f} = { bf.fromSpecific(src)(new AbstractView[B] { - def iterator: AbstractIterator[B] = new AbstractIterator[B] { + def iterator: AbstractIterator[B]^{this, f} = new AbstractIterator[B] { private[this] val elems1 = coll1.iterator private[this] val elems2 = coll2.iterator def hasNext = elems1.hasNext && elems2.hasNext @@ -47,12 +48,12 @@ final class LazyZip2[+El1, +El2, C1] private[collection](src: C1, coll1: Iterabl }) } - def flatMap[B, C](f: (El1, El2) => Iterable[B])(implicit bf: BuildFrom[C1, B, C]): C = { + def flatMap[B, C](f: (El1, El2) => Iterable[B]^)(implicit bf: BuildFrom[C1, B, C]): C^{this, f} = { bf.fromSpecific(src)(new AbstractView[B] { - def iterator: AbstractIterator[B] = new AbstractIterator[B] { + def iterator: AbstractIterator[B]^{this, f} = new AbstractIterator[B] { private[this] val elems1 = coll1.iterator private[this] val elems2 = coll2.iterator - private[this] var _current: Iterator[B] = Iterator.empty + private[this] var _current: Iterator[B]^{this, f} = Iterator.empty private def current = { while (!_current.hasNext && elems1.hasNext && elems2.hasNext) _current = f(elems1.next(), elems2.next()).iterator @@ -66,9 +67,9 @@ final class LazyZip2[+El1, +El2, C1] private[collection](src: C1, coll1: Iterabl }) } - def filter[C](p: (El1, El2) => Boolean)(implicit bf: BuildFrom[C1, (El1, El2), C]): C = { + def filter[C](p: (El1, El2) => Boolean)(implicit bf: BuildFrom[C1, (El1, El2), C]): C^{this, p} = { bf.fromSpecific(src)(new AbstractView[(El1, El2)] { - def iterator: AbstractIterator[(El1, El2)] = new AbstractIterator[(El1, El2)] { + def iterator: AbstractIterator[(El1, El2)]^{this, p} = new AbstractIterator[(El1, El2)] { private[this] val elems1 = coll1.iterator private[this] val elems2 = coll2.iterator private[this] var _current: (El1, El2) = _ @@ -113,8 +114,8 @@ final class LazyZip2[+El1, +El2, C1] private[collection](src: C1, coll1: Iterabl while (elems1.hasNext && elems2.hasNext) f(elems1.next(), elems2.next()) } - private def toIterable: View[(El1, El2)] = new AbstractView[(El1, El2)] { - def iterator: AbstractIterator[(El1, El2)] = new AbstractIterator[(El1, El2)] { + private def toIterable: View[(El1, El2)]^{this} = new AbstractView[(El1, El2)] { + def iterator: AbstractIterator[(El1, El2)]^{this} = new AbstractIterator[(El1, El2)] { private[this] val elems1 = coll1.iterator private[this] val elems2 = coll2.iterator def hasNext = elems1.hasNext && elems2.hasNext @@ -136,7 +137,7 @@ final class LazyZip2[+El1, +El2, C1] private[collection](src: C1, coll1: Iterabl } object LazyZip2 { - implicit def lazyZip2ToIterable[El1, El2](zipped2: LazyZip2[El1, El2, _]): View[(El1, El2)] = zipped2.toIterable + implicit def lazyZip2ToIterable[El1, El2](zipped2: LazyZip2[El1, El2, _]^): View[(El1, El2)]^{zipped2} = zipped2.toIterable } @@ -148,9 +149,9 @@ object LazyZip2 { * Note: will not terminate for infinite-sized collections. */ final class LazyZip3[+El1, +El2, +El3, C1] private[collection](src: C1, - coll1: Iterable[El1], - coll2: Iterable[El2], - coll3: Iterable[El3]) { + coll1: Iterable[El1]^, + coll2: Iterable[El2]^, + coll3: Iterable[El3]^) { /** Zips `that` iterable collection with an existing `LazyZip3`. The elements in each collection are * not consumed until a strict operation is invoked on the returned `LazyZip4` decorator. @@ -160,11 +161,11 @@ final class LazyZip3[+El1, +El2, +El3, C1] private[collection](src: C1, * @return a decorator `LazyZip4` that allows strict operations to be performed on the lazily evaluated tuples. * Implicit conversion to `Iterable[(El1, El2, El3, B)]` is also supported. */ - def lazyZip[B](that: Iterable[B]): LazyZip4[El1, El2, El3, B, C1] = new LazyZip4(src, coll1, coll2, coll3, that) + def lazyZip[B](that: Iterable[B]^): LazyZip4[El1, El2, El3, B, C1]^{this, that} = new LazyZip4(src, coll1, coll2, coll3, that) - def map[B, C](f: (El1, El2, El3) => B)(implicit bf: BuildFrom[C1, B, C]): C = { + def map[B, C](f: (El1, El2, El3) => B)(implicit bf: BuildFrom[C1, B, C]): C^{this, f} = { bf.fromSpecific(src)(new AbstractView[B] { - def iterator: AbstractIterator[B] = new AbstractIterator[B] { + def iterator: AbstractIterator[B]^{this, f} = new AbstractIterator[B] { private[this] val elems1 = coll1.iterator private[this] val elems2 = coll2.iterator private[this] val elems3 = coll3.iterator @@ -176,13 +177,13 @@ final class LazyZip3[+El1, +El2, +El3, C1] private[collection](src: C1, }) } - def flatMap[B, C](f: (El1, El2, El3) => Iterable[B])(implicit bf: BuildFrom[C1, B, C]): C = { + def flatMap[B, C](f: (El1, El2, El3) => Iterable[B])(implicit bf: BuildFrom[C1, B, C]): C^{this, f} = { bf.fromSpecific(src)(new AbstractView[B] { - def iterator: AbstractIterator[B] = new AbstractIterator[B] { + def iterator: AbstractIterator[B]^{this, f} = new AbstractIterator[B] { private[this] val elems1 = coll1.iterator private[this] val elems2 = coll2.iterator private[this] val elems3 = coll3.iterator - private[this] var _current: Iterator[B] = Iterator.empty + private[this] var _current: Iterator[B]^{this, f} = Iterator.empty private def current = { while (!_current.hasNext && elems1.hasNext && elems2.hasNext && elems3.hasNext) _current = f(elems1.next(), elems2.next(), elems3.next()).iterator @@ -196,9 +197,9 @@ final class LazyZip3[+El1, +El2, +El3, C1] private[collection](src: C1, }) } - def filter[C](p: (El1, El2, El3) => Boolean)(implicit bf: BuildFrom[C1, (El1, El2, El3), C]): C = { + def filter[C](p: (El1, El2, El3) => Boolean)(implicit bf: BuildFrom[C1, (El1, El2, El3), C]): C^{this, p} = { bf.fromSpecific(src)(new AbstractView[(El1, El2, El3)] { - def iterator: AbstractIterator[(El1, El2, El3)] = new AbstractIterator[(El1, El2, El3)] { + def iterator: AbstractIterator[(El1, El2, El3)]^{this, p} = new AbstractIterator[(El1, El2, El3)] { private[this] val elems1 = coll1.iterator private[this] val elems2 = coll2.iterator private[this] val elems3 = coll3.iterator @@ -249,8 +250,8 @@ final class LazyZip3[+El1, +El2, +El3, C1] private[collection](src: C1, f(elems1.next(), elems2.next(), elems3.next()) } - private def toIterable: View[(El1, El2, El3)] = new AbstractView[(El1, El2, El3)] { - def iterator: AbstractIterator[(El1, El2, El3)] = new AbstractIterator[(El1, El2, El3)] { + private def toIterable: View[(El1, El2, El3)]^{this} = new AbstractView[(El1, El2, El3)] { + def iterator: AbstractIterator[(El1, El2, El3)]^{this} = new AbstractIterator[(El1, El2, El3)] { private[this] val elems1 = coll1.iterator private[this] val elems2 = coll2.iterator private[this] val elems3 = coll3.iterator @@ -276,7 +277,7 @@ final class LazyZip3[+El1, +El2, +El3, C1] private[collection](src: C1, } object LazyZip3 { - implicit def lazyZip3ToIterable[El1, El2, El3](zipped3: LazyZip3[El1, El2, El3, _]): View[(El1, El2, El3)] = zipped3.toIterable + implicit def lazyZip3ToIterable[El1, El2, El3](zipped3: LazyZip3[El1, El2, El3, _]^): View[(El1, El2, El3)]^{zipped3} = zipped3.toIterable } @@ -289,14 +290,14 @@ object LazyZip3 { * Note: will not terminate for infinite-sized collections. */ final class LazyZip4[+El1, +El2, +El3, +El4, C1] private[collection](src: C1, - coll1: Iterable[El1], - coll2: Iterable[El2], - coll3: Iterable[El3], - coll4: Iterable[El4]) { + coll1: Iterable[El1]^, + coll2: Iterable[El2]^, + coll3: Iterable[El3]^, + coll4: Iterable[El4]^) { - def map[B, C](f: (El1, El2, El3, El4) => B)(implicit bf: BuildFrom[C1, B, C]): C = { + def map[B, C](f: (El1, El2, El3, El4) => B)(implicit bf: BuildFrom[C1, B, C]): C^{this, f} = { bf.fromSpecific(src)(new AbstractView[B] { - def iterator: AbstractIterator[B] = new AbstractIterator[B] { + def iterator: AbstractIterator[B]^{this, f} = new AbstractIterator[B] { private[this] val elems1 = coll1.iterator private[this] val elems2 = coll2.iterator private[this] val elems3 = coll3.iterator @@ -309,14 +310,14 @@ final class LazyZip4[+El1, +El2, +El3, +El4, C1] private[collection](src: C1, }) } - def flatMap[B, C](f: (El1, El2, El3, El4) => Iterable[B])(implicit bf: BuildFrom[C1, B, C]): C = { + def flatMap[B, C](f: (El1, El2, El3, El4) => Iterable[B])(implicit bf: BuildFrom[C1, B, C]): C^{this, f} = { bf.fromSpecific(src)(new AbstractView[B] { - def iterator: AbstractIterator[B] = new AbstractIterator[B] { + def iterator: AbstractIterator[B]^{this, f} = new AbstractIterator[B] { private[this] val elems1 = coll1.iterator private[this] val elems2 = coll2.iterator private[this] val elems3 = coll3.iterator private[this] val elems4 = coll4.iterator - private[this] var _current: Iterator[B] = Iterator.empty + private[this] var _current: Iterator[B]^{this, f} = Iterator.empty private def current = { while (!_current.hasNext && elems1.hasNext && elems2.hasNext && elems3.hasNext && elems4.hasNext) _current = f(elems1.next(), elems2.next(), elems3.next(), elems4.next()).iterator @@ -330,9 +331,9 @@ final class LazyZip4[+El1, +El2, +El3, +El4, C1] private[collection](src: C1, }) } - def filter[C](p: (El1, El2, El3, El4) => Boolean)(implicit bf: BuildFrom[C1, (El1, El2, El3, El4), C]): C = { + def filter[C](p: (El1, El2, El3, El4) => Boolean)(implicit bf: BuildFrom[C1, (El1, El2, El3, El4), C]): C^{this, p} = { bf.fromSpecific(src)(new AbstractView[(El1, El2, El3, El4)] { - def iterator: AbstractIterator[(El1, El2, El3, El4)] = new AbstractIterator[(El1, El2, El3, El4)] { + def iterator: AbstractIterator[(El1, El2, El3, El4)]^{this, p} = new AbstractIterator[(El1, El2, El3, El4)] { private[this] val elems1 = coll1.iterator private[this] val elems2 = coll2.iterator private[this] val elems3 = coll3.iterator @@ -387,7 +388,7 @@ final class LazyZip4[+El1, +El2, +El3, +El4, C1] private[collection](src: C1, f(elems1.next(), elems2.next(), elems3.next(), elems4.next()) } - private def toIterable: View[(El1, El2, El3, El4)] = new AbstractView[(El1, El2, El3, El4)] { + private def toIterable: View[(El1, El2, El3, El4)]^{this} = new AbstractView[(El1, El2, El3, El4)] { def iterator: AbstractIterator[(El1, El2, El3, El4)] = new AbstractIterator[(El1, El2, El3, El4)] { private[this] val elems1 = coll1.iterator private[this] val elems2 = coll2.iterator @@ -418,6 +419,6 @@ final class LazyZip4[+El1, +El2, +El3, +El4, C1] private[collection](src: C1, } object LazyZip4 { - implicit def lazyZip4ToIterable[El1, El2, El3, El4](zipped4: LazyZip4[El1, El2, El3, El4, _]): View[(El1, El2, El3, El4)] = + implicit def lazyZip4ToIterable[El1, El2, El3, El4](zipped4: LazyZip4[El1, El2, El3, El4, _]^): View[(El1, El2, El3, El4)]^{zipped4} = zipped4.toIterable } diff --git a/library/src/scala/collection/LinearSeq.scala b/library/src/scala/collection/LinearSeq.scala index d5792be72595..2fa512e32ef9 100644 --- a/library/src/scala/collection/LinearSeq.scala +++ b/library/src/scala/collection/LinearSeq.scala @@ -14,6 +14,8 @@ package scala package collection import scala.language.`2.13` +import language.experimental.captureChecking + import scala.annotation.{nowarn, tailrec} /** Base trait for linearly accessed sequences that have efficient `head` and @@ -97,7 +99,7 @@ transparent trait LinearSeqOps[+A, +CC[X] <: LinearSeq[X], +C <: LinearSeq[A] wi else loop(0, coll) } - override def lengthCompare(that: Iterable[_]): Int = { + override def lengthCompare(that: Iterable[_]^): Int = { val thatKnownSize = that.knownSize if (thatKnownSize >= 0) this lengthCompare thatKnownSize @@ -187,7 +189,7 @@ transparent trait LinearSeqOps[+A, +CC[X] <: LinearSeq[X], +C <: LinearSeq[A] wi acc } - override def sameElements[B >: A](that: IterableOnce[B]): Boolean = { + override def sameElements[B >: A](that: IterableOnce[B]^): Boolean = { @tailrec def linearSeqEq(a: LinearSeq[B], b: LinearSeq[B]): Boolean = (a eq b) || { if (a.nonEmpty && b.nonEmpty && a.head == b.head) { diff --git a/library/src/scala/collection/Searching.scala b/library/src/scala/collection/Searching.scala index 7148f54606bf..cd5842f81057 100644 --- a/library/src/scala/collection/Searching.scala +++ b/library/src/scala/collection/Searching.scala @@ -14,6 +14,8 @@ package scala.collection import scala.language.`2.13` import scala.language.implicitConversions +import language.experimental.captureChecking + import scala.collection.generic.IsSeq object Searching { From 9993dd65819e6aceb1d3d4fc0d24afb114b1a272 Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Wed, 13 Aug 2025 21:15:56 +0200 Subject: [PATCH 055/101] Capture-check stepper --- TODO.md | 8 +-- .../src/scala/collection/IterableOnce.scala | 2 +- library/src/scala/collection/Stepper.scala | 72 ++++++++++--------- .../src/scala/collection/StepperShape.scala | 42 +++++------ 4 files changed, 64 insertions(+), 60 deletions(-) diff --git a/TODO.md b/TODO.md index 09b3dc27b6c3..3bc95a8da1e6 100644 --- a/TODO.md +++ b/TODO.md @@ -210,11 +210,11 @@ - [ ] library/src/scala/collection/SortedMap.scala - [ ] library/src/scala/collection/SortedOps.scala - [ ] library/src/scala/collection/SortedSet.scala -- [ ] library/src/scala/collection/Stepper.scala -- [ ] library/src/scala/collection/StepperShape.scala -- [ ] library/src/scala/collection/StrictOptimizedIterableOps.scala +- [x] library/src/scala/collection/Stepper.scala +- [x] library/src/scala/collection/StepperShape.scala +- [x] library/src/scala/collection/StrictOptimizedIterableOps.scala - [ ] library/src/scala/collection/StrictOptimizedMapOps.scala -- [ ] library/src/scala/collection/StrictOptimizedSeqOps.scala +- [x] library/src/scala/collection/StrictOptimizedSeqOps.scala - [ ] library/src/scala/collection/StrictOptimizedSetOps.scala - [ ] library/src/scala/collection/StrictOptimizedSortedMapOps.scala - [ ] library/src/scala/collection/StrictOptimizedSortedSetOps.scala diff --git a/library/src/scala/collection/IterableOnce.scala b/library/src/scala/collection/IterableOnce.scala index a1e5820e2a6d..bd479ab06a81 100644 --- a/library/src/scala/collection/IterableOnce.scala +++ b/library/src/scala/collection/IterableOnce.scala @@ -75,7 +75,7 @@ trait IterableOnce[+A] extends Any { this: IterableOnce[A]^ => * allow creating parallel streams, whereas bare Steppers can be converted only to sequential * streams. */ - def stepper[S <: Stepper[_]^{this}](implicit shape: StepperShape[A, S]): S = { + def stepper[S <: Stepper[_]](implicit shape: StepperShape[A, S]): S^{this} = { import convert.impl._ val s: Any = shape.shape match { case StepperShape.IntShape => new IntIteratorStepper (iterator.asInstanceOf[Iterator[Int]]) diff --git a/library/src/scala/collection/Stepper.scala b/library/src/scala/collection/Stepper.scala index 8755142d7209..7c4b5821aef9 100644 --- a/library/src/scala/collection/Stepper.scala +++ b/library/src/scala/collection/Stepper.scala @@ -13,6 +13,8 @@ package scala.collection import scala.language.`2.13` +import language.experimental.captureChecking + import java.util.function.{Consumer, DoubleConsumer, IntConsumer, LongConsumer} import java.util.{PrimitiveIterator, Spliterator, Iterator => JIterator} import java.{lang => jl} @@ -52,7 +54,7 @@ trait Stepper[@specialized(Double, Int, Long) +A] { * * See method `trySplit` in [[java.util.Spliterator]]. */ - def trySplit(): Stepper[A] + def trySplit(): Stepper[A]^{this} /** Returns an estimate of the number of elements of this Stepper, or [[Long.MaxValue]]. See * method `estimateSize` in [[java.util.Spliterator]]. @@ -70,7 +72,7 @@ trait Stepper[@specialized(Double, Int, Long) +A] { * a [[java.util.Spliterator.OfInt]] (which is a `Spliterator[Integer]`) in the subclass [[IntStepper]] * (which is a `Stepper[Int]`). */ - def spliterator[B >: A]: Spliterator[_] + def spliterator[B >: A]: Spliterator[_]^{this} /** Returns a Java [[java.util.Iterator]] corresponding to this Stepper. * @@ -78,12 +80,12 @@ trait Stepper[@specialized(Double, Int, Long) +A] { * a [[java.util.PrimitiveIterator.OfInt]] (which is a `Iterator[Integer]`) in the subclass * [[IntStepper]] (which is a `Stepper[Int]`). */ - def javaIterator[B >: A]: JIterator[_] + def javaIterator[B >: A]: JIterator[_]^{this} /** Returns an [[Iterator]] corresponding to this Stepper. Note that Iterators corresponding to * primitive Steppers box the elements. */ - def iterator: Iterator[A] = new AbstractIterator[A] { + def iterator: Iterator[A]^{this} = new AbstractIterator[A] { def hasNext: Boolean = hasStep def next(): A = nextStep() } @@ -104,78 +106,78 @@ object Stepper { * of the data is boxed. In other cases native implementations of the primitive stepper types should be provided * (see for example IntArrayStepper and WidenedByteArrayStepper). */ - private[collection] class UnboxingDoubleStepper(st: AnyStepper[Double]) extends DoubleStepper { + private[collection] class UnboxingDoubleStepper(st: AnyStepper[Double]^) extends DoubleStepper { def hasStep: Boolean = st.hasStep def nextStep(): Double = st.nextStep() def estimateSize: Long = st.estimateSize def characteristics: Int = st.characteristics - def trySplit(): DoubleStepper = { + def trySplit(): DoubleStepper^{this} = { val s = st.trySplit() if (s == null) null else new UnboxingDoubleStepper(s) } } - private[collection] class UnboxingIntStepper(st: AnyStepper[Int]) extends IntStepper { + private[collection] class UnboxingIntStepper(st: AnyStepper[Int]^) extends IntStepper { def hasStep: Boolean = st.hasStep def nextStep(): Int = st.nextStep() def estimateSize: Long = st.estimateSize def characteristics: Int = st.characteristics - def trySplit(): IntStepper = { + def trySplit(): IntStepper^{this} = { val s = st.trySplit() if (s == null) null else new UnboxingIntStepper(s) } } - private[collection] class UnboxingLongStepper(st: AnyStepper[Long]) extends LongStepper { + private[collection] class UnboxingLongStepper(st: AnyStepper[Long]^) extends LongStepper { def hasStep: Boolean = st.hasStep def nextStep(): Long = st.nextStep() def estimateSize: Long = st.estimateSize def characteristics: Int = st.characteristics - def trySplit(): LongStepper = { + def trySplit(): LongStepper^{this} = { val s = st.trySplit() if (s == null) null else new UnboxingLongStepper(s) } } - private[collection] class UnboxingByteStepper(st: AnyStepper[Byte]) extends IntStepper { + private[collection] class UnboxingByteStepper(st: AnyStepper[Byte]^) extends IntStepper { def hasStep: Boolean = st.hasStep def nextStep(): Int = st.nextStep() def estimateSize: Long = st.estimateSize def characteristics: Int = st.characteristics - def trySplit(): IntStepper = { + def trySplit(): IntStepper^{this} = { val s = st.trySplit() if (s == null) null else new UnboxingByteStepper(s) } } - private[collection] class UnboxingCharStepper(st: AnyStepper[Char]) extends IntStepper { + private[collection] class UnboxingCharStepper(st: AnyStepper[Char]^) extends IntStepper { def hasStep: Boolean = st.hasStep def nextStep(): Int = st.nextStep() def estimateSize: Long = st.estimateSize def characteristics: Int = st.characteristics - def trySplit(): IntStepper = { + def trySplit(): IntStepper^{this} = { val s = st.trySplit() if (s == null) null else new UnboxingCharStepper(s) } } - private[collection] class UnboxingShortStepper(st: AnyStepper[Short]) extends IntStepper { + private[collection] class UnboxingShortStepper(st: AnyStepper[Short]^) extends IntStepper { def hasStep: Boolean = st.hasStep def nextStep(): Int = st.nextStep() def estimateSize: Long = st.estimateSize def characteristics: Int = st.characteristics - def trySplit(): IntStepper = { + def trySplit(): IntStepper^{this} = { val s = st.trySplit() if (s == null) null else new UnboxingShortStepper(s) } } - private[collection] class UnboxingFloatStepper(st: AnyStepper[Float]) extends DoubleStepper { + private[collection] class UnboxingFloatStepper(st: AnyStepper[Float]^) extends DoubleStepper { def hasStep: Boolean = st.hasStep def nextStep(): Double = st.nextStep() def estimateSize: Long = st.estimateSize def characteristics: Int = st.characteristics - def trySplit(): DoubleStepper = { + def trySplit(): DoubleStepper^{this} = { val s = st.trySplit() if (s == null) null else new UnboxingFloatStepper(s) } @@ -184,21 +186,21 @@ object Stepper { /** A Stepper for arbitrary element types. See [[Stepper]]. */ trait AnyStepper[+A] extends Stepper[A] { - def trySplit(): AnyStepper[A] + def trySplit(): AnyStepper[A]^{this} - def spliterator[B >: A]: Spliterator[B] = new AnyStepper.AnyStepperSpliterator(this) + def spliterator[B >: A]: Spliterator[B]^{this} = new AnyStepper.AnyStepperSpliterator(this) - def javaIterator[B >: A]: JIterator[B] = new JIterator[B] { + def javaIterator[B >: A]: JIterator[B]^{this} = new JIterator[B] { def hasNext: Boolean = hasStep def next(): B = nextStep() } } object AnyStepper { - class AnyStepperSpliterator[A](s: AnyStepper[A]) extends Spliterator[A] { + class AnyStepperSpliterator[A](s: AnyStepper[A]^) extends Spliterator[A] { def tryAdvance(c: Consumer[_ >: A]): Boolean = if (s.hasStep) { c.accept(s.nextStep()); true } else false - def trySplit(): Spliterator[A] = { + def trySplit(): Spliterator[A]^{this} = { val sp = s.trySplit() if (sp == null) null else sp.spliterator } @@ -254,9 +256,9 @@ object AnyStepper { /** A Stepper for Ints. See [[Stepper]]. */ trait IntStepper extends Stepper[Int] { - def trySplit(): IntStepper + def trySplit(): IntStepper^{this} - def spliterator[B >: Int]: Spliterator.OfInt = new IntStepper.IntStepperSpliterator(this) + def spliterator[B >: Int]: Spliterator.OfInt^{this} = new IntStepper.IntStepperSpliterator(this) def javaIterator[B >: Int]: PrimitiveIterator.OfInt = new PrimitiveIterator.OfInt { def hasNext: Boolean = hasStep @@ -264,7 +266,7 @@ trait IntStepper extends Stepper[Int] { } } object IntStepper { - class IntStepperSpliterator(s: IntStepper) extends Spliterator.OfInt { + class IntStepperSpliterator(s: IntStepper^) extends Spliterator.OfInt { def tryAdvance(c: IntConsumer): Boolean = if (s.hasStep) { c.accept(s.nextStep()); true } else false // Override for efficiency: don't wrap the function and call the `tryAdvance` overload @@ -273,7 +275,7 @@ object IntStepper { case _ => if (s.hasStep) { c.accept(jl.Integer.valueOf(s.nextStep())); true } else false } // override required for dotty#6152 - override def trySplit(): Spliterator.OfInt = { + override def trySplit(): Spliterator.OfInt^{this} = { val sp = s.trySplit() if (sp == null) null else sp.spliterator } @@ -292,9 +294,9 @@ object IntStepper { /** A Stepper for Doubles. See [[Stepper]]. */ trait DoubleStepper extends Stepper[Double] { - def trySplit(): DoubleStepper + def trySplit(): DoubleStepper^{this} - def spliterator[B >: Double]: Spliterator.OfDouble = new DoubleStepper.DoubleStepperSpliterator(this) + def spliterator[B >: Double]: Spliterator.OfDouble^{this} = new DoubleStepper.DoubleStepperSpliterator(this) def javaIterator[B >: Double]: PrimitiveIterator.OfDouble = new PrimitiveIterator.OfDouble { def hasNext: Boolean = hasStep @@ -303,7 +305,7 @@ trait DoubleStepper extends Stepper[Double] { } object DoubleStepper { - class DoubleStepperSpliterator(s: DoubleStepper) extends Spliterator.OfDouble { + class DoubleStepperSpliterator(s: DoubleStepper^) extends Spliterator.OfDouble { def tryAdvance(c: DoubleConsumer): Boolean = if (s.hasStep) { c.accept(s.nextStep()); true } else false // Override for efficiency: don't wrap the function and call the `tryAdvance` overload @@ -312,7 +314,7 @@ object DoubleStepper { case _ => if (s.hasStep) { c.accept(java.lang.Double.valueOf(s.nextStep())); true } else false } // override required for dotty#6152 - override def trySplit(): Spliterator.OfDouble = { + override def trySplit(): Spliterator.OfDouble^{this} = { val sp = s.trySplit() if (sp == null) null else sp.spliterator } @@ -331,9 +333,9 @@ object DoubleStepper { /** A Stepper for Longs. See [[Stepper]]. */ trait LongStepper extends Stepper[Long] { - def trySplit(): LongStepper + def trySplit(): LongStepper^{this} - def spliterator[B >: Long]: Spliterator.OfLong = new LongStepper.LongStepperSpliterator(this) + def spliterator[B >: Long]: Spliterator.OfLong^{this} = new LongStepper.LongStepperSpliterator(this) def javaIterator[B >: Long]: PrimitiveIterator.OfLong = new PrimitiveIterator.OfLong { def hasNext: Boolean = hasStep @@ -342,7 +344,7 @@ trait LongStepper extends Stepper[Long] { } object LongStepper { - class LongStepperSpliterator(s: LongStepper) extends Spliterator.OfLong { + class LongStepperSpliterator(s: LongStepper^) extends Spliterator.OfLong { def tryAdvance(c: LongConsumer): Boolean = if (s.hasStep) { c.accept(s.nextStep()); true } else false // Override for efficiency: don't wrap the function and call the `tryAdvance` overload @@ -351,7 +353,7 @@ object LongStepper { case _ => if (s.hasStep) { c.accept(java.lang.Long.valueOf(s.nextStep())); true } else false } // override required for dotty#6152 - override def trySplit(): Spliterator.OfLong = { + override def trySplit(): Spliterator.OfLong^{this} = { val sp = s.trySplit() if (sp == null) null else sp.spliterator } diff --git a/library/src/scala/collection/StepperShape.scala b/library/src/scala/collection/StepperShape.scala index ec193cc27fc2..1b25d8758da0 100644 --- a/library/src/scala/collection/StepperShape.scala +++ b/library/src/scala/collection/StepperShape.scala @@ -15,22 +15,24 @@ package scala.collection import java.{lang => jl} import scala.language.`2.13` +import language.experimental.captureChecking + import scala.collection.Stepper.EfficientSplit /** An implicit StepperShape instance is used in the [[IterableOnce.stepper]] to return a possibly * specialized Stepper `S` according to the element type `T`. */ -sealed trait StepperShape[-T, S <: Stepper[_]] { +sealed trait StepperShape[-T, S <: Stepper[_]] { self => /** Return the Int constant (as defined in the `StepperShape` companion object) for this `StepperShape`. */ def shape: StepperShape.Shape /** Create an unboxing primitive sequential Stepper from a boxed `AnyStepper`. * This is an identity operation for reference shapes. */ - def seqUnbox(st: AnyStepper[T]): S + def seqUnbox(st: AnyStepper[T]^): S^{st} /** Create an unboxing primitive parallel (i.e. `with EfficientSplit`) Stepper from a boxed `AnyStepper`. * This is an identity operation for reference shapes. */ - def parUnbox(st: AnyStepper[T] with EfficientSplit): S with EfficientSplit + def parUnbox(st: (AnyStepper[T] with EfficientSplit)^): (S with EfficientSplit)^{st} } object StepperShape extends StepperShapeLowPriority1 { @@ -52,50 +54,50 @@ object StepperShape extends StepperShapeLowPriority1 { implicit val intStepperShape: StepperShape[Int, IntStepper] = new StepperShape[Int, IntStepper] { def shape = IntShape - def seqUnbox(st: AnyStepper[Int]): IntStepper = new Stepper.UnboxingIntStepper(st) - def parUnbox(st: AnyStepper[Int] with EfficientSplit): IntStepper with EfficientSplit = new Stepper.UnboxingIntStepper(st) with EfficientSplit + def seqUnbox(st: AnyStepper[Int]^): IntStepper^{st} = new Stepper.UnboxingIntStepper(st) + def parUnbox(st: (AnyStepper[Int] with EfficientSplit)^): (IntStepper with EfficientSplit)^{st} = new Stepper.UnboxingIntStepper(st) with EfficientSplit } implicit val jIntegerStepperShape: StepperShape[jl.Integer, IntStepper] = intStepperShape.asInstanceOf[StepperShape[jl.Integer, IntStepper]] implicit val longStepperShape: StepperShape[Long, LongStepper] = new StepperShape[Long, LongStepper] { def shape = LongShape - def seqUnbox(st: AnyStepper[Long]): LongStepper = new Stepper.UnboxingLongStepper(st) - def parUnbox(st: AnyStepper[Long] with EfficientSplit): LongStepper with EfficientSplit = new Stepper.UnboxingLongStepper(st) with EfficientSplit + def seqUnbox(st: AnyStepper[Long]^): LongStepper^{st} = new Stepper.UnboxingLongStepper(st) + def parUnbox(st: (AnyStepper[Long] with EfficientSplit)^): (LongStepper with EfficientSplit)^{st} = new Stepper.UnboxingLongStepper(st) with EfficientSplit } implicit val jLongStepperShape: StepperShape[jl.Long, LongStepper] = longStepperShape.asInstanceOf[StepperShape[jl.Long, LongStepper]] implicit val doubleStepperShape: StepperShape[Double, DoubleStepper] = new StepperShape[Double, DoubleStepper] { def shape = DoubleShape - def seqUnbox(st: AnyStepper[Double]): DoubleStepper = new Stepper.UnboxingDoubleStepper(st) - def parUnbox(st: AnyStepper[Double] with EfficientSplit): DoubleStepper with EfficientSplit = new Stepper.UnboxingDoubleStepper(st) with EfficientSplit + def seqUnbox(st: AnyStepper[Double]^): DoubleStepper^{st} = new Stepper.UnboxingDoubleStepper(st) + def parUnbox(st: (AnyStepper[Double] with EfficientSplit)^): (DoubleStepper with EfficientSplit)^{st} = new Stepper.UnboxingDoubleStepper(st) with EfficientSplit } implicit val jDoubleStepperShape: StepperShape[jl.Double, DoubleStepper] = doubleStepperShape.asInstanceOf[StepperShape[jl.Double, DoubleStepper]] implicit val byteStepperShape: StepperShape[Byte, IntStepper] = new StepperShape[Byte, IntStepper] { def shape = ByteShape - def seqUnbox(st: AnyStepper[Byte]): IntStepper = new Stepper.UnboxingByteStepper(st) - def parUnbox(st: AnyStepper[Byte] with EfficientSplit): IntStepper with EfficientSplit = new Stepper.UnboxingByteStepper(st) with EfficientSplit + def seqUnbox(st: AnyStepper[Byte]^): IntStepper^{st} = new Stepper.UnboxingByteStepper(st) + def parUnbox(st: (AnyStepper[Byte] with EfficientSplit)^): (IntStepper with EfficientSplit)^{st} = new Stepper.UnboxingByteStepper(st) with EfficientSplit } implicit val jByteStepperShape: StepperShape[jl.Byte, IntStepper] = byteStepperShape.asInstanceOf[StepperShape[jl.Byte, IntStepper]] implicit val shortStepperShape: StepperShape[Short, IntStepper] = new StepperShape[Short, IntStepper] { def shape = ShortShape - def seqUnbox(st: AnyStepper[Short]): IntStepper = new Stepper.UnboxingShortStepper(st) - def parUnbox(st: AnyStepper[Short] with EfficientSplit): IntStepper with EfficientSplit = new Stepper.UnboxingShortStepper(st) with EfficientSplit + def seqUnbox(st: AnyStepper[Short]^): IntStepper^{st} = new Stepper.UnboxingShortStepper(st) + def parUnbox(st: (AnyStepper[Short] with EfficientSplit)^): (IntStepper with EfficientSplit)^{st} = new Stepper.UnboxingShortStepper(st) with EfficientSplit } implicit val jShortStepperShape: StepperShape[jl.Short, IntStepper] = shortStepperShape.asInstanceOf[StepperShape[jl.Short, IntStepper]] implicit val charStepperShape: StepperShape[Char, IntStepper] = new StepperShape[Char, IntStepper] { def shape = CharShape - def seqUnbox(st: AnyStepper[Char]): IntStepper = new Stepper.UnboxingCharStepper(st) - def parUnbox(st: AnyStepper[Char] with EfficientSplit): IntStepper with EfficientSplit = new Stepper.UnboxingCharStepper(st) with EfficientSplit + def seqUnbox(st: AnyStepper[Char]^): IntStepper^{st} = new Stepper.UnboxingCharStepper(st) + def parUnbox(st: (AnyStepper[Char] with EfficientSplit)^): (IntStepper with EfficientSplit)^{st} = new Stepper.UnboxingCharStepper(st) with EfficientSplit } implicit val jCharacterStepperShape: StepperShape[jl.Character, IntStepper] = charStepperShape.asInstanceOf[StepperShape[jl.Character, IntStepper]] implicit val floatStepperShape: StepperShape[Float, DoubleStepper] = new StepperShape[Float, DoubleStepper] { def shape = FloatShape - def seqUnbox(st: AnyStepper[Float]): DoubleStepper = new Stepper.UnboxingFloatStepper(st) - def parUnbox(st: AnyStepper[Float] with EfficientSplit): DoubleStepper with EfficientSplit = new Stepper.UnboxingFloatStepper(st) with EfficientSplit + def seqUnbox(st: AnyStepper[Float]^): DoubleStepper^{st} = new Stepper.UnboxingFloatStepper(st) + def parUnbox(st: (AnyStepper[Float] with EfficientSplit)^): (DoubleStepper with EfficientSplit)^{st} = new Stepper.UnboxingFloatStepper(st) with EfficientSplit } implicit val jFloatStepperShape: StepperShape[jl.Float, DoubleStepper] = floatStepperShape.asInstanceOf[StepperShape[jl.Float, DoubleStepper]] } @@ -109,7 +111,7 @@ trait StepperShapeLowPriority2 { protected val anyStepperShapePrototype: StepperShape[AnyRef, Stepper[AnyRef]] = new StepperShape[AnyRef, Stepper[AnyRef]] { def shape = StepperShape.ReferenceShape - def seqUnbox(st: AnyStepper[AnyRef]): Stepper[AnyRef] = st - def parUnbox(st: AnyStepper[AnyRef] with EfficientSplit): Stepper[AnyRef] with EfficientSplit = st + def seqUnbox(st: AnyStepper[AnyRef]^): Stepper[AnyRef]^{st} = st + def parUnbox(st: (AnyStepper[AnyRef] with EfficientSplit)^): (Stepper[AnyRef] with EfficientSplit)^{st} = st } -} \ No newline at end of file +} From 71a45e307a5e2f60e0e1c3af463f773b23e022ec Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Wed, 13 Aug 2025 21:16:17 +0200 Subject: [PATCH 056/101] Capture-check StrictOptimizedOps for Seq and Iterable --- .../StrictOptimizedIterableOps.scala | 23 ++++++++++--------- .../collection/StrictOptimizedSeqOps.scala | 7 +++--- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/library/src/scala/collection/StrictOptimizedIterableOps.scala b/library/src/scala/collection/StrictOptimizedIterableOps.scala index 7ed82bf7ae3d..b89acb715cf7 100644 --- a/library/src/scala/collection/StrictOptimizedIterableOps.scala +++ b/library/src/scala/collection/StrictOptimizedIterableOps.scala @@ -14,6 +14,7 @@ package scala package collection import scala.language.`2.13` +import language.experimental.captureChecking import scala.annotation.nowarn import scala.annotation.unchecked.uncheckedVariance @@ -57,7 +58,7 @@ transparent trait StrictOptimizedIterableOps[+A, +CC[_], +C] (first.result(), second.result()) } - override def unzip[A1, A2](implicit asPair: A => (A1, A2)): (CC[A1], CC[A2]) = { + override def unzip[A1, A2](implicit asPair: A -> (A1, A2)): (CC[A1], CC[A2]) = { val first = iterableFactory.newBuilder[A1] val second = iterableFactory.newBuilder[A2] foreach { a => @@ -68,7 +69,7 @@ transparent trait StrictOptimizedIterableOps[+A, +CC[_], +C] (first.result(), second.result()) } - override def unzip3[A1, A2, A3](implicit asTriple: A => (A1, A2, A3)): (CC[A1], CC[A2], CC[A3]) = { + override def unzip3[A1, A2, A3](implicit asTriple: A -> (A1, A2, A3)): (CC[A1], CC[A2], CC[A3]) = { val b1 = iterableFactory.newBuilder[A1] val b2 = iterableFactory.newBuilder[A2] val b3 = iterableFactory.newBuilder[A3] @@ -104,7 +105,7 @@ transparent trait StrictOptimizedIterableOps[+A, +CC[_], +C] b.result() } - override def flatMap[B](f: A => IterableOnce[B]): CC[B] = + override def flatMap[B](f: A => IterableOnce[B]^): CC[B] = strictOptimizedFlatMap(iterableFactory.newBuilder, f) /** @@ -114,7 +115,7 @@ transparent trait StrictOptimizedIterableOps[+A, +CC[_], +C] * @tparam C2 Type of the resulting collection (e.g. `List[String]`) * @return The resulting collection */ - @inline protected[this] final def strictOptimizedFlatMap[B, C2](b: mutable.Builder[B, C2], f: A => IterableOnce[B]): C2 = { + @inline protected[this] final def strictOptimizedFlatMap[B, C2](b: mutable.Builder[B, C2]^, f: A => IterableOnce[B]^): C2 = { val it = iterator while (it.hasNext) { b ++= f(it.next()) @@ -129,13 +130,13 @@ transparent trait StrictOptimizedIterableOps[+A, +CC[_], +C] * @tparam C2 Type of the resulting collection (e.g. `List[Int]`) * @return The resulting collection */ - @inline protected[this] final def strictOptimizedConcat[B >: A, C2](that: IterableOnce[B], b: mutable.Builder[B, C2]): C2 = { + @inline protected[this] final def strictOptimizedConcat[B >: A, C2](that: IterableOnce[B]^, b: mutable.Builder[B, C2]^): C2 = { b ++= this b ++= that b.result() } - override def collect[B](pf: PartialFunction[A, B]): CC[B] = + override def collect[B](pf: PartialFunction[A, B]^): CC[B] = strictOptimizedCollect(iterableFactory.newBuilder, pf) /** @@ -145,7 +146,7 @@ transparent trait StrictOptimizedIterableOps[+A, +CC[_], +C] * @tparam C2 Type of the resulting collection (e.g. `List[String]`) * @return The resulting collection */ - @inline protected[this] final def strictOptimizedCollect[B, C2](b: mutable.Builder[B, C2], pf: PartialFunction[A, B]): C2 = { + @inline protected[this] final def strictOptimizedCollect[B, C2](b: mutable.Builder[B, C2]^, pf: PartialFunction[A, B]^): C2 = { val marker = Statics.pfMarker val it = iterator while (it.hasNext) { @@ -156,7 +157,7 @@ transparent trait StrictOptimizedIterableOps[+A, +CC[_], +C] b.result() } - override def flatten[B](implicit toIterableOnce: A => IterableOnce[B]): CC[B] = + override def flatten[B](implicit toIterableOnce: A -> IterableOnce[B]): CC[B] = strictOptimizedFlatten(iterableFactory.newBuilder) /** @@ -174,7 +175,7 @@ transparent trait StrictOptimizedIterableOps[+A, +CC[_], +C] b.result() } - override def zip[B](that: IterableOnce[B]): CC[(A @uncheckedVariance, B)] = + override def zip[B](that: IterableOnce[B]^): CC[(A @uncheckedVariance, B)] = strictOptimizedZip(that, iterableFactory.newBuilder[(A, B)]) /** @@ -184,7 +185,7 @@ transparent trait StrictOptimizedIterableOps[+A, +CC[_], +C] * @tparam C2 Type of the resulting collection (e.g. `List[(Int, String)]`) * @return The resulting collection */ - @inline protected[this] final def strictOptimizedZip[B, C2](that: IterableOnce[B], b: mutable.Builder[(A, B), C2]): C2 = { + @inline protected[this] final def strictOptimizedZip[B, C2](that: IterableOnce[B]^, b: mutable.Builder[(A, B), C2]^): C2 = { val it1 = iterator val it2 = that.iterator while (it1.hasNext && it2.hasNext) { @@ -247,7 +248,7 @@ transparent trait StrictOptimizedIterableOps[+A, +CC[_], +C] } // Optimization avoids creation of second collection - override def tapEach[U](f: A => U): C = { + override def tapEach[U](f: A => U): C^{this} = { foreach(f) coll } diff --git a/library/src/scala/collection/StrictOptimizedSeqOps.scala b/library/src/scala/collection/StrictOptimizedSeqOps.scala index 2d2b87dce410..15fa8be919ed 100644 --- a/library/src/scala/collection/StrictOptimizedSeqOps.scala +++ b/library/src/scala/collection/StrictOptimizedSeqOps.scala @@ -13,6 +13,7 @@ package scala.collection import scala.language.`2.13` +import language.experimental.captureChecking /** * Trait that overrides operations on sequences in order @@ -21,7 +22,7 @@ import scala.language.`2.13` transparent trait StrictOptimizedSeqOps [+A, +CC[_] <: caps.Pure, +C] extends Any with StrictSeqOps[A, CC, C] with StrictOptimizedIterableOps[A, CC, C] { - override def distinctBy[B](f: A => B): C = { + override def distinctBy[B](f: A -> B): C = { val builder = newSpecificBuilder val seen = mutable.HashSet.empty[B] val it = this.iterator @@ -48,10 +49,10 @@ transparent trait StrictOptimizedSeqOps [+A, +CC[_] <: caps.Pure, +C] b.result() } - override def appendedAll[B >: A](suffix: IterableOnce[B]): CC[B] = + override def appendedAll[B >: A](suffix: IterableOnce[B]^): CC[B] = strictOptimizedConcat(suffix, iterableFactory.newBuilder) - override def prependedAll[B >: A](prefix: IterableOnce[B]): CC[B] = { + override def prependedAll[B >: A](prefix: IterableOnce[B]^): CC[B] = { val b = iterableFactory.newBuilder[B] b ++= prefix b ++= this From 919a59db342e9feff36d514907ae01985eacb8a4 Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Wed, 13 Aug 2025 21:24:41 +0200 Subject: [PATCH 057/101] Capture check StringOps and StringParsers --- TODO.md | 4 +- library/src/scala/collection/StringOps.scala | 46 ++++++++++--------- .../src/scala/collection/StringParsers.scala | 2 + 3 files changed, 28 insertions(+), 24 deletions(-) diff --git a/TODO.md b/TODO.md index 3bc95a8da1e6..c0ad72f115f2 100644 --- a/TODO.md +++ b/TODO.md @@ -218,8 +218,8 @@ - [ ] library/src/scala/collection/StrictOptimizedSetOps.scala - [ ] library/src/scala/collection/StrictOptimizedSortedMapOps.scala - [ ] library/src/scala/collection/StrictOptimizedSortedSetOps.scala -- [ ] library/src/scala/collection/StringOps.scala -- [ ] library/src/scala/collection/StringParsers.scala +- [x] library/src/scala/collection/StringOps.scala +- [x] library/src/scala/collection/StringParsers.scala - [x] library/src/scala/collection/View.scala - [ ] library/src/scala/collection/WithFilter.scala - [ ] library/src/scala/collection/concurrent/Map.scala diff --git a/library/src/scala/collection/StringOps.scala b/library/src/scala/collection/StringOps.scala index ab5ec5c1ff49..8cc391f98a7a 100644 --- a/library/src/scala/collection/StringOps.scala +++ b/library/src/scala/collection/StringOps.scala @@ -14,6 +14,8 @@ package scala package collection import scala.language.`2.13` +import language.experimental.captureChecking + import java.lang.{StringBuilder => JStringBuilder} import scala.collection.Stepper.EfficientSplit @@ -154,7 +156,7 @@ object StringOps { } /** Creates a new non-strict filter which combines this filter with the given predicate. */ - def withFilter(q: Char => Boolean): WithFilter = new WithFilter(a => p(a) && q(a), s) + def withFilter(q: Char => Boolean): WithFilter^{this, q} = new WithFilter(a => p(a) && q(a), s) } /** Avoid an allocation in [[collect]]. */ @@ -176,7 +178,7 @@ object StringOps { * are handled correctly. Failing to do so may result in * an invalid Unicode string. */ -final class StringOps(private val s: String) extends AnyVal { +final class StringOps(private val s: String) extends AnyVal { self => import StringOps._ @inline def view: StringView = new StringView(s) @@ -237,7 +239,7 @@ final class StringOps(private val s: String) extends AnyVal { * @return a new collection resulting from applying the given collection-valued function * `f` to each char of this string and concatenating the results. */ - def flatMap[B](f: Char => IterableOnce[B]): immutable.IndexedSeq[B] = { + def flatMap[B](f: Char => IterableOnce[B]^): immutable.IndexedSeq[B] = { val len = s.length val b = immutable.IndexedSeq.newBuilder[B] var i = 0 @@ -273,7 +275,7 @@ final class StringOps(private val s: String) extends AnyVal { * @return a new String resulting from applying the given partial function * `pf` to each char on which it is defined and collecting the results. */ - def collect(pf: PartialFunction[Char, Char]): String = { + def collect(pf: PartialFunction[Char, Char]^): String = { val fallback: Any => Any = StringOps.fallback var i = 0 val b = new StringBuilder @@ -293,7 +295,7 @@ final class StringOps(private val s: String) extends AnyVal { * @return a new collection resulting from applying the given partial function * `pf` to each char on which it is defined and collecting the results. */ - def collect[B](pf: PartialFunction[Char, B]): immutable.IndexedSeq[B] = { + def collect[B](pf: PartialFunction[Char, B]^): immutable.IndexedSeq[B] = { val fallback: Any => Any = StringOps.fallback var i = 0 val b = immutable.IndexedSeq.newBuilder[B] @@ -312,7 +314,7 @@ final class StringOps(private val s: String) extends AnyVal { * @return a new collection which contains all chars * of this string followed by all elements of `suffix`. */ - def concat[B >: Char](suffix: IterableOnce[B]): immutable.IndexedSeq[B] = { + def concat[B >: Char](suffix: IterableOnce[B]^): immutable.IndexedSeq[B] = { val b = immutable.IndexedSeq.newBuilder[B] val k = suffix.knownSize b.sizeHint(s.length + (if(k >= 0) k else 16)) @@ -328,7 +330,7 @@ final class StringOps(private val s: String) extends AnyVal { * @return a new string which contains all chars * of this string followed by all chars of `suffix`. */ - def concat(suffix: IterableOnce[Char]): String = { + def concat(suffix: IterableOnce[Char]^): String = { val k = suffix.knownSize val sb = new JStringBuilder(s.length + (if(k >= 0) k else 16)) sb.append(s) @@ -346,10 +348,10 @@ final class StringOps(private val s: String) extends AnyVal { @inline def concat(suffix: String): String = s + suffix /** Alias for `concat` */ - @inline def ++[B >: Char](suffix: Iterable[B]): immutable.IndexedSeq[B] = concat(suffix) + @inline def ++[B >: Char](suffix: Iterable[B]^): immutable.IndexedSeq[B] = concat(suffix) /** Alias for `concat` */ - @inline def ++(suffix: IterableOnce[Char]): String = concat(suffix) + @inline def ++(suffix: IterableOnce[Char]^): String = concat(suffix) /** Alias for `concat` */ def ++(xs: String): String = concat(xs) @@ -421,7 +423,7 @@ final class StringOps(private val s: String) extends AnyVal { @inline def +: (c: Char): String = prepended(c) /** A copy of the string with all elements from a collection prepended */ - def prependedAll[B >: Char](prefix: IterableOnce[B]): immutable.IndexedSeq[B] = { + def prependedAll[B >: Char](prefix: IterableOnce[B]^): immutable.IndexedSeq[B] = { val b = immutable.IndexedSeq.newBuilder[B] val k = prefix.knownSize b.sizeHint(s.length + (if(k >= 0) k else 16)) @@ -431,7 +433,7 @@ final class StringOps(private val s: String) extends AnyVal { } /** Alias for `prependedAll` */ - @inline def ++: [B >: Char] (prefix: IterableOnce[B]): immutable.IndexedSeq[B] = prependedAll(prefix) + @inline def ++: [B >: Char] (prefix: IterableOnce[B]^): immutable.IndexedSeq[B] = prependedAll(prefix) /** A copy of the string with another string prepended */ def prependedAll(prefix: String): String = prefix + s @@ -459,11 +461,11 @@ final class StringOps(private val s: String) extends AnyVal { @inline def :+ (c: Char): String = appended(c) /** A copy of the string with all elements from a collection appended */ - @inline def appendedAll[B >: Char](suffix: IterableOnce[B]): immutable.IndexedSeq[B] = + @inline def appendedAll[B >: Char](suffix: IterableOnce[B]^): immutable.IndexedSeq[B] = concat(suffix) /** Alias for `appendedAll` */ - @inline def :++ [B >: Char](suffix: IterableOnce[B]): immutable.IndexedSeq[B] = + @inline def :++ [B >: Char](suffix: IterableOnce[B]^): immutable.IndexedSeq[B] = concat(suffix) /** A copy of the string with another string appended */ @@ -485,7 +487,7 @@ final class StringOps(private val s: String) extends AnyVal { * except that `replaced` chars starting from `from` are replaced * by `other`. */ - def patch[B >: Char](from: Int, other: IterableOnce[B], replaced: Int): immutable.IndexedSeq[B] = { + def patch[B >: Char](from: Int, other: IterableOnce[B]^, replaced: Int): immutable.IndexedSeq[B] = { val len = s.length @inline def slc(off: Int, length: Int): WrappedString = new WrappedString(s.substring(off, off+length)) @@ -514,7 +516,7 @@ final class StringOps(private val s: String) extends AnyVal { * by `other`. * @note $unicodeunaware */ - def patch(from: Int, other: IterableOnce[Char], replaced: Int): String = + def patch(from: Int, other: IterableOnce[Char]^, replaced: Int): String = patch(from, other.iterator.mkString, replaced) /** Produces a new string where a slice of characters in this string is replaced by another string. @@ -593,14 +595,14 @@ final class StringOps(private val s: String) extends AnyVal { @inline final def mkString: String = s /** Appends this string to a string builder. */ - @inline final def addString(b: StringBuilder): b.type = b.append(s) + @inline final def addString(b: StringBuilder^): b.type = b.append(s) /** Appends this string to a string builder using a separator string. */ - @inline final def addString(b: StringBuilder, sep: String): b.type = + @inline final def addString(b: StringBuilder^, sep: String): b.type = addString(b, "", sep, "") /** Appends this string to a string builder using start, end and separator strings. */ - final def addString(b: StringBuilder, start: String, sep: String, end: String): b.type = { + final def addString(b: StringBuilder^, start: String, sep: String, end: String): b.type = { val jsb = b.underlying if (start.length != 0) jsb.append(start) val len = s.length @@ -1196,7 +1198,7 @@ final class StringOps(private val s: String) extends AnyVal { * All these operations apply to those chars of this string * which satisfy the predicate `p`. */ - def withFilter(p: Char => Boolean): StringOps.WithFilter = new StringOps.WithFilter(p, s) + def withFilter(p: Char => Boolean): StringOps.WithFilter^{p} = new StringOps.WithFilter(p, s) /** The rest of the string without its first char. * @throws UnsupportedOperationException if the string is empty. @@ -1249,7 +1251,7 @@ final class StringOps(private val s: String) extends AnyVal { def inits: Iterator[String] = iterateUntilEmpty(_.init) // A helper for tails and inits. - private[this] def iterateUntilEmpty(f: String => String): Iterator[String] = + private[this] def iterateUntilEmpty(f: String => String): Iterator[String]^{f} = Iterator.iterate(s)(f).takeWhile(x => !x.isEmpty) ++ Iterator.single("") /** Selects all chars of this string which satisfy a predicate. */ @@ -1467,7 +1469,7 @@ final class StringOps(private val s: String) extends AnyVal { * @return a decorator `LazyZip2` that allows strict operations to be performed on the lazily evaluated pairs * or chained calls to `lazyZip`. Implicit conversion to `Iterable[(A, B)]` is also supported. */ - def lazyZip[B](that: Iterable[B]): LazyZip2[Char, B, String] = new LazyZip2(s, new WrappedString(s), that) + def lazyZip[B](that: Iterable[B]^): LazyZip2[Char, B, String]^{that} = new LazyZip2(s, new WrappedString(s), that) /* ************************************************************************************************************ @@ -1515,7 +1517,7 @@ final class StringOps(private val s: String) extends AnyVal { * @return a new string consisting of all the chars of this string without duplicates. * @note $unicodeunaware */ - def distinctBy[B](f: Char => B): String = new WrappedString(s).distinctBy(f).unwrap + def distinctBy[B](f: Char -> B): String = new WrappedString(s).distinctBy(f).unwrap /** Sorts the characters of this string according to an Ordering. * diff --git a/library/src/scala/collection/StringParsers.scala b/library/src/scala/collection/StringParsers.scala index cfaa4f86985f..6c4807bf1929 100644 --- a/library/src/scala/collection/StringParsers.scala +++ b/library/src/scala/collection/StringParsers.scala @@ -14,6 +14,8 @@ package scala package collection import scala.language.`2.13` +import language.experimental.captureChecking + import scala.annotation.tailrec /** A module containing the implementations of parsers from strings to numeric types, and boolean From 48044bc1b76d4e8231d3b6a78e5a673313fdd61f Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Wed, 13 Aug 2025 21:27:45 +0200 Subject: [PATCH 058/101] Capture-check WithFilter --- TODO.md | 2 +- library/src/scala/collection/WithFilter.scala | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/TODO.md b/TODO.md index c0ad72f115f2..19d3fee9858f 100644 --- a/TODO.md +++ b/TODO.md @@ -221,7 +221,7 @@ - [x] library/src/scala/collection/StringOps.scala - [x] library/src/scala/collection/StringParsers.scala - [x] library/src/scala/collection/View.scala -- [ ] library/src/scala/collection/WithFilter.scala +- [x] library/src/scala/collection/WithFilter.scala - [ ] library/src/scala/collection/concurrent/Map.scala - [ ] library/src/scala/collection/concurrent/TrieMap.scala - [ ] library/src/scala/collection/convert/AsJavaConverters.scala diff --git a/library/src/scala/collection/WithFilter.scala b/library/src/scala/collection/WithFilter.scala index da65ba6fab27..fa4cf28e43a4 100644 --- a/library/src/scala/collection/WithFilter.scala +++ b/library/src/scala/collection/WithFilter.scala @@ -13,6 +13,7 @@ package scala.collection import scala.language.`2.13` +import language.experimental.captureChecking /** A template trait that contains just the `map`, `flatMap`, `foreach` and `withFilter` methods * of trait `Iterable`. @@ -34,7 +35,7 @@ abstract class WithFilter[+A, +CC[_]] extends Serializable { * the given function `f` to each element of the filtered outer $coll * and collecting the results. */ - def map[B](f: A => B): CC[B] + def map[B](f: A => B): CC[B]^{this, f} /** Builds a new collection by applying a function to all elements of the * `filtered` outer $coll containing this `WithFilter` instance that satisfy @@ -46,7 +47,7 @@ abstract class WithFilter[+A, +CC[_]] extends Serializable { * of the filtered outer $coll and * concatenating the results. */ - def flatMap[B](f: A => IterableOnce[B]): CC[B] + def flatMap[B](f: A => IterableOnce[B]^): CC[B]^{this, f} /** Applies a function `f` to all elements of the `filtered` outer $coll. * @@ -67,6 +68,6 @@ abstract class WithFilter[+A, +CC[_]] extends Serializable { * All these operations apply to those elements of this $coll which * also satisfy both `p` and `q` predicates. */ - def withFilter(q: A => Boolean): WithFilter[A, CC] + def withFilter(q: A => Boolean): WithFilter[A, CC]^{this, q} } From a864d710606154f1184147bf518dc4cf8ad4b112 Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Wed, 13 Aug 2025 21:42:32 +0200 Subject: [PATCH 059/101] Capture-check Set and SortedSet --- TODO.md | 8 ++++---- library/src/scala/collection/BitSet.scala | 2 +- library/src/scala/collection/Iterable.scala | 2 +- library/src/scala/collection/Set.scala | 14 +++++++++----- library/src/scala/collection/SortedSet.scala | 10 ++++++---- .../scala/collection/StrictOptimizedSetOps.scala | 3 ++- .../collection/StrictOptimizedSortedSetOps.scala | 8 +++++--- .../src/scala/collection/immutable/BitSet.scala | 2 +- 8 files changed, 29 insertions(+), 20 deletions(-) diff --git a/TODO.md b/TODO.md index 19d3fee9858f..a4a1c045d08c 100644 --- a/TODO.md +++ b/TODO.md @@ -206,18 +206,18 @@ - [x] library/src/scala/collection/Seq.scala - [ ] library/src/scala/collection/SeqMap.scala - [ ] library/src/scala/collection/SeqView.scala -- [ ] library/src/scala/collection/Set.scala +- [x] library/src/scala/collection/Set.scala - [ ] library/src/scala/collection/SortedMap.scala - [ ] library/src/scala/collection/SortedOps.scala -- [ ] library/src/scala/collection/SortedSet.scala +- [x] library/src/scala/collection/SortedSet.scala - [x] library/src/scala/collection/Stepper.scala - [x] library/src/scala/collection/StepperShape.scala - [x] library/src/scala/collection/StrictOptimizedIterableOps.scala - [ ] library/src/scala/collection/StrictOptimizedMapOps.scala - [x] library/src/scala/collection/StrictOptimizedSeqOps.scala -- [ ] library/src/scala/collection/StrictOptimizedSetOps.scala +- [x] library/src/scala/collection/StrictOptimizedSetOps.scala - [ ] library/src/scala/collection/StrictOptimizedSortedMapOps.scala -- [ ] library/src/scala/collection/StrictOptimizedSortedSetOps.scala +- [x] library/src/scala/collection/StrictOptimizedSortedSetOps.scala - [x] library/src/scala/collection/StringOps.scala - [x] library/src/scala/collection/StringParsers.scala - [x] library/src/scala/collection/View.scala diff --git a/library/src/scala/collection/BitSet.scala b/library/src/scala/collection/BitSet.scala index 5464d73a493e..dc3d3b7ab7e7 100644 --- a/library/src/scala/collection/BitSet.scala +++ b/library/src/scala/collection/BitSet.scala @@ -245,7 +245,7 @@ transparent trait BitSetOps[+C <: BitSet with BitSetOps[C]] coll.fromBitMaskNoCopy(a) } - override def concat(other: collection.IterableOnce[Int]): C = other match { + override def concat(other: collection.IterableOnce[Int]^): C = other match { case otherBitset: BitSet => val len = coll.nwords max otherBitset.nwords val words = new Array[Long](len) diff --git a/library/src/scala/collection/Iterable.scala b/library/src/scala/collection/Iterable.scala index df21b227e2ab..b88c6aef5b74 100644 --- a/library/src/scala/collection/Iterable.scala +++ b/library/src/scala/collection/Iterable.scala @@ -997,7 +997,7 @@ trait SortedSetFactoryDefaults[+A, override protected def newSpecificBuilder: mutable.Builder[A @uncheckedVariance, CC[A @uncheckedVariance]] = sortedIterableFactory.newBuilder[A](using ordering) override def empty: CC[A @uncheckedVariance] = sortedIterableFactory.empty(using ordering) - override def withFilter(p: A => Boolean): SortedSetOps.WithFilter[A, WithFilterCC, CC] = + override def withFilter(p: A => Boolean): SortedSetOps.WithFilter[A, WithFilterCC, CC]^{p} = new SortedSetOps.WithFilter[A, WithFilterCC, CC](this, p) } diff --git a/library/src/scala/collection/Set.scala b/library/src/scala/collection/Set.scala index 78bf6351c84c..c5f879dc1644 100644 --- a/library/src/scala/collection/Set.scala +++ b/library/src/scala/collection/Set.scala @@ -14,6 +14,8 @@ package scala package collection import scala.language.`2.13` +import language.experimental.captureChecking + import scala.util.hashing.MurmurHash3 import java.lang.String @@ -25,7 +27,8 @@ trait Set[A] extends Iterable[A] with SetOps[A, Set, Set[A]] with Equals - with IterableFactoryDefaults[A, Set] { + with IterableFactoryDefaults[A, Set] + with caps.Pure { def canEqual(that: Any) = true @@ -88,7 +91,8 @@ trait Set[A] */ transparent trait SetOps[A, +CC[_], +C <: SetOps[A, CC, C]] extends IterableOps[A, CC, C] - with (A => Boolean) { + with (A => Boolean) + with caps.Pure { def contains(elem: A): Boolean @@ -200,7 +204,7 @@ transparent trait SetOps[A, +CC[_], +C <: SetOps[A, CC, C]] @`inline` final def &~ (that: Set[A]): C = this diff that @deprecated("Consider requiring an immutable Set", "2.13.0") - def -- (that: IterableOnce[A]): C = { + def -- (that: IterableOnce[A]^): C = { val toRemove = that.iterator.to(immutable.Set) fromSpecific(view.filterNot(toRemove)) } @@ -222,7 +226,7 @@ transparent trait SetOps[A, +CC[_], +C <: SetOps[A, CC, C]] * @param that the collection containing the elements to add. * @return a new $coll with the given elements added, omitting duplicates. */ - def concat(that: collection.IterableOnce[A]): C = this match { + def concat(that: collection.IterableOnce[A]^): C = this match { case optimizedSet @ (_ : scala.collection.immutable.Set.Set1[A] | _: scala.collection.immutable.Set.Set2[A] | _: scala.collection.immutable.Set.Set3[A] | _: scala.collection.immutable.Set.Set4[A]) => // StrictOptimizedSetOps optimization of concat (these Sets cannot extend StrictOptimizedSetOps because of binary-incompatible return type; cf. PR #10036) var result = optimizedSet.asInstanceOf[scala.collection.immutable.SetOps[A, scala.collection.immutable.Set, scala.collection.immutable.Set[A]]] @@ -242,7 +246,7 @@ transparent trait SetOps[A, +CC[_], +C <: SetOps[A, CC, C]] def + (elem1: A, elem2: A, elems: A*): C = fromSpecific(new View.Concat(new View.Appended(new View.Appended(this, elem1), elem2), elems)) /** Alias for `concat` */ - @`inline` final def ++ (that: collection.IterableOnce[A]): C = concat(that) + @`inline` final def ++ (that: collection.IterableOnce[A]^): C = concat(that) /** Computes the union between of set and another set. * diff --git a/library/src/scala/collection/SortedSet.scala b/library/src/scala/collection/SortedSet.scala index 9290087cf06b..212dd73192bb 100644 --- a/library/src/scala/collection/SortedSet.scala +++ b/library/src/scala/collection/SortedSet.scala @@ -13,6 +13,8 @@ package scala.collection import scala.language.`2.13` +import language.experimental.captureChecking + import scala.annotation.{implicitNotFound, nowarn} import scala.annotation.unchecked.uncheckedVariance @@ -130,7 +132,7 @@ transparent trait SortedSetOps[A, +CC[X] <: SortedSet[X], +C <: SortedSetOps[A, * @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[B](f: A => IterableOnce[B])(implicit @implicitNotFound(SortedSetOps.ordMsg) ev: Ordering[B]): CC[B] = + def flatMap[B](f: A => IterableOnce[B]^)(implicit @implicitNotFound(SortedSetOps.ordMsg) ev: Ordering[B]): CC[B] = sortedIterableFactory.from(new View.FlatMap(this, f)) /** Returns a $coll formed from this $coll and another iterable collection @@ -142,7 +144,7 @@ transparent trait SortedSetOps[A, +CC[X] <: SortedSet[X], +C <: SortedSetOps[A, * @return a new $coll containing pairs consisting of corresponding elements of this $coll and `that`. * The length of the returned collection is the minimum of the lengths of this $coll and `that`. */ - def zip[B](that: IterableOnce[B])(implicit @implicitNotFound(SortedSetOps.zipOrdMsg) ev: Ordering[(A @uncheckedVariance, B)]): CC[(A @uncheckedVariance, B)] = // sound bcs of VarianceNote + def zip[B](that: IterableOnce[B]^)(implicit @implicitNotFound(SortedSetOps.zipOrdMsg) ev: Ordering[(A @uncheckedVariance, B)]): CC[(A @uncheckedVariance, B)] = // sound bcs of VarianceNote sortedIterableFactory.from(that match { case that: Iterable[B] => new View.Zip(this, that) case _ => iterator.zip(that) @@ -157,7 +159,7 @@ transparent trait SortedSetOps[A, +CC[X] <: SortedSet[X], +C <: SortedSetOps[A, * `pf` to each element on which it is defined and collecting the results. * The order of the elements is preserved. */ - def collect[B](pf: scala.PartialFunction[A, B])(implicit @implicitNotFound(SortedSetOps.ordMsg) ev: Ordering[B]): CC[B] = + def collect[B](pf: scala.PartialFunction[A, B]^)(implicit @implicitNotFound(SortedSetOps.ordMsg) ev: Ordering[B]): CC[B] = sortedIterableFactory.from(new View.Collect(this, pf)) } @@ -180,7 +182,7 @@ object SortedSetOps { def flatMap[B : Ordering](f: A => IterableOnce[B]): CC[B] = self.sortedIterableFactory.from(new View.FlatMap(filtered, f)) - override def withFilter(q: A => Boolean): WithFilter[A, IterableCC, CC] = + override def withFilter(q: A => Boolean): WithFilter[A, IterableCC, CC]^{this, q} = new WithFilter[A, IterableCC, CC](self, (a: A) => p(a) && q(a)) } diff --git a/library/src/scala/collection/StrictOptimizedSetOps.scala b/library/src/scala/collection/StrictOptimizedSetOps.scala index cef517c39fd6..5e5349b6cd78 100644 --- a/library/src/scala/collection/StrictOptimizedSetOps.scala +++ b/library/src/scala/collection/StrictOptimizedSetOps.scala @@ -13,6 +13,7 @@ package scala.collection import scala.language.`2.13` +import language.experimental.captureChecking /** * Trait that overrides set operations to take advantage of strict builders. @@ -25,7 +26,7 @@ transparent trait StrictOptimizedSetOps[A, +CC[_], +C <: SetOps[A, CC, C]] extends SetOps[A, CC, C] with StrictOptimizedIterableOps[A, CC, C] { - override def concat(that: IterableOnce[A]): C = + override def concat(that: IterableOnce[A]^): C = strictOptimizedConcat(that, newSpecificBuilder) } diff --git a/library/src/scala/collection/StrictOptimizedSortedSetOps.scala b/library/src/scala/collection/StrictOptimizedSortedSetOps.scala index aef85be58bb3..d9c437573ca2 100644 --- a/library/src/scala/collection/StrictOptimizedSortedSetOps.scala +++ b/library/src/scala/collection/StrictOptimizedSortedSetOps.scala @@ -14,6 +14,8 @@ package scala package collection import scala.language.`2.13` +import language.experimental.captureChecking + import scala.annotation.implicitNotFound import scala.annotation.unchecked.uncheckedVariance @@ -31,13 +33,13 @@ transparent trait StrictOptimizedSortedSetOps[A, +CC[X] <: SortedSet[X], +C <: S override def map[B](f: A => B)(implicit @implicitNotFound(SortedSetOps.ordMsg) ev: Ordering[B]): CC[B] = strictOptimizedMap(sortedIterableFactory.newBuilder, f) - override def flatMap[B](f: A => IterableOnce[B])(implicit @implicitNotFound(SortedSetOps.ordMsg) ev: Ordering[B]): CC[B] = + override def flatMap[B](f: A => IterableOnce[B]^)(implicit @implicitNotFound(SortedSetOps.ordMsg) ev: Ordering[B]): CC[B] = strictOptimizedFlatMap(sortedIterableFactory.newBuilder, f) - override def zip[B](that: IterableOnce[B])(implicit @implicitNotFound(SortedSetOps.zipOrdMsg) ev: Ordering[(A @uncheckedVariance, B)]): CC[(A @uncheckedVariance, B)] = + override def zip[B](that: IterableOnce[B]^)(implicit @implicitNotFound(SortedSetOps.zipOrdMsg) ev: Ordering[(A @uncheckedVariance, B)]): CC[(A @uncheckedVariance, B)] = strictOptimizedZip(that, sortedIterableFactory.newBuilder[(A, B)]) - override def collect[B](pf: PartialFunction[A, B])(implicit @implicitNotFound(SortedSetOps.ordMsg) ev: Ordering[B]): CC[B] = + override def collect[B](pf: PartialFunction[A, B]^)(implicit @implicitNotFound(SortedSetOps.ordMsg) ev: Ordering[B]): CC[B] = strictOptimizedCollect(sortedIterableFactory.newBuilder, pf) } diff --git a/library/src/scala/collection/immutable/BitSet.scala b/library/src/scala/collection/immutable/BitSet.scala index ed18ec6c1663..176c31794621 100644 --- a/library/src/scala/collection/immutable/BitSet.scala +++ b/library/src/scala/collection/immutable/BitSet.scala @@ -82,7 +82,7 @@ sealed abstract class BitSet super[StrictOptimizedSortedSetOps].collect(pf) // necessary for disambiguation - override def zip[B](that: scala.IterableOnce[B])(implicit @implicitNotFound(collection.BitSet.zipOrdMsg) ev: Ordering[(Int, B)]): SortedSet[(Int, B)] = + override def zip[B](that: scala.IterableOnce[B]^)(implicit @implicitNotFound(collection.BitSet.zipOrdMsg) ev: Ordering[(Int, B)]): SortedSet[(Int, B)] = super.zip(that) protected[this] def writeReplace(): AnyRef = new BitSet.SerializationProxy(this) From f161436f6344d96f5ac23a70b25d0c2e85ec08c8 Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Wed, 13 Aug 2025 22:44:34 +0200 Subject: [PATCH 060/101] Add captures to Map, SeqMap, MapView New classes: StrictMapFactory <: MapFactory and StrictMapOps <: MapOps --- TODO.md | 8 +- library/src/scala/collection/Factory.scala | 15 ++- library/src/scala/collection/Hashing.scala | 1 + library/src/scala/collection/Iterable.scala | 4 +- library/src/scala/collection/Map.scala | 102 +++++++++++++----- library/src/scala/collection/MapView.scala | 54 +++++----- library/src/scala/collection/SeqMap.scala | 6 +- library/src/scala/collection/SortedMap.scala | 4 +- .../scala/collection/concurrent/TrieMap.scala | 4 +- .../scala/collection/immutable/HashMap.scala | 6 +- .../scala/collection/immutable/ListMap.scala | 4 +- .../src/scala/collection/immutable/Map.scala | 8 +- .../scala/collection/immutable/SeqMap.scala | 4 +- .../collection/immutable/TreeSeqMap.scala | 4 +- .../collection/immutable/VectorMap.scala | 4 +- .../scala/collection/mutable/HashMap.scala | 4 +- .../collection/mutable/LinkedHashMap.scala | 4 +- .../scala/collection/mutable/ListMap.scala | 4 +- .../src/scala/collection/mutable/Map.scala | 6 +- .../collection/mutable/OpenHashMap.scala | 4 +- .../src/scala/collection/mutable/SeqMap.scala | 2 +- .../collection/mutable/WeakHashMap.scala | 4 +- 22 files changed, 157 insertions(+), 99 deletions(-) diff --git a/TODO.md b/TODO.md index a4a1c045d08c..0e9889bb9d72 100644 --- a/TODO.md +++ b/TODO.md @@ -191,7 +191,7 @@ - [x] library/src/scala/collection/BuildFrom.scala - [ ] library/src/scala/collection/DefaultMap.scala - [x] library/src/scala/collection/Factory.scala -- [ ] library/src/scala/collection/Hashing.scala +- [x] library/src/scala/collection/Hashing.scala - [x] library/src/scala/collection/IndexedSeq.scala - [x] library/src/scala/collection/IndexedSeqView.scala - [x] library/src/scala/collection/Iterable.scala @@ -200,11 +200,11 @@ - [ ] library/src/scala/collection/JavaConverters.scala - [x] library/src/scala/collection/LazyZipOps.scala - [x] library/src/scala/collection/LinearSeq.scala -- [ ] library/src/scala/collection/Map.scala -- [ ] library/src/scala/collection/MapView.scala +- [x] library/src/scala/collection/Map.scala +- [x] library/src/scala/collection/MapView.scala - [x] library/src/scala/collection/Searching.scala - [x] library/src/scala/collection/Seq.scala -- [ ] library/src/scala/collection/SeqMap.scala +- [x] library/src/scala/collection/SeqMap.scala - [ ] library/src/scala/collection/SeqView.scala - [x] library/src/scala/collection/Set.scala - [ ] library/src/scala/collection/SortedMap.scala diff --git a/library/src/scala/collection/Factory.scala b/library/src/scala/collection/Factory.scala index a5f3ea687adf..6e8f95fcdbbf 100644 --- a/library/src/scala/collection/Factory.scala +++ b/library/src/scala/collection/Factory.scala @@ -293,6 +293,7 @@ object IterableFactory { } } +/** IterableFactory but with strict operations that do not capture the inputs. */ trait StrictIterableFactory[+CC[_] <: caps.Pure] extends IterableFactory[CC] { // pure overrides of the IterableFactory methods override def from[A](source: IterableOnce[A]^): CC[A] @@ -423,7 +424,7 @@ trait SpecificIterableFactory[-A, +C] extends Factory[A, C] { * @define coll collection * @define Coll `Iterable` */ -trait MapFactory[+CC[_, _]] extends Serializable { this: MapFactory[CC] => +trait MapFactory[+CC[_, _]] extends Serializable { self => /** * An empty Map @@ -451,6 +452,14 @@ trait MapFactory[+CC[_, _]] extends Serializable { this: MapFactory[CC] => implicit def mapFactory[K, V]: Factory[(K, V), CC[K, V]] = MapFactory.toFactory(this) } +/** Like MapFactory, but with a strict constructor. */ +trait StrictMapFactory[+CC[_, _] <: caps.Pure] extends MapFactory[CC] { + /** + * A collection of type Map generated from given iterable object. + */ + def from[K, V](it: IterableOnce[(K, V)]^): CC[K, V] +} + object MapFactory { /** @@ -477,9 +486,9 @@ object MapFactory { } @SerialVersionUID(3L) - class Delegate[C[_, _]](delegate: MapFactory[C]) extends MapFactory[C] { + class Delegate[C[_, _] <: caps.Pure](delegate: StrictMapFactory[C]) extends StrictMapFactory[C] { override def apply[K, V](elems: (K, V)*): C[K, V] = delegate.apply(elems: _*) - def from[K, V](it: IterableOnce[(K, V)]^): C[K, V]^{it} = delegate.from(it) + def from[K, V](it: IterableOnce[(K, V)]^): C[K, V] = delegate.from(it) def empty[K, V]: C[K, V] = delegate.empty def newBuilder[K, V]: Builder[(K, V), C[K, V]] = delegate.newBuilder } diff --git a/library/src/scala/collection/Hashing.scala b/library/src/scala/collection/Hashing.scala index 5ba8ad24948f..40850a524a40 100644 --- a/library/src/scala/collection/Hashing.scala +++ b/library/src/scala/collection/Hashing.scala @@ -14,6 +14,7 @@ package scala package collection import scala.language.`2.13` +import language.experimental.captureChecking protected[collection] object Hashing { diff --git a/library/src/scala/collection/Iterable.scala b/library/src/scala/collection/Iterable.scala index b88c6aef5b74..a5acb609425b 100644 --- a/library/src/scala/collection/Iterable.scala +++ b/library/src/scala/collection/Iterable.scala @@ -1026,7 +1026,7 @@ trait MapFactoryDefaults[K, +V, case _ => mapFactory.empty } - override def withFilter(p: ((K, V)) => Boolean): MapOps.WithFilter[K, V, WithFilterCC, CC] = + override def withFilter(p: ((K, V)) => Boolean): MapOps.WithFilter[K, V, WithFilterCC, CC]^{p} = new MapOps.WithFilter[K, V, WithFilterCC, CC](this, p) } @@ -1045,7 +1045,7 @@ trait MapFactoryDefaults[K, +V, trait SortedMapFactoryDefaults[K, +V, +CC[x, y] <: Map[x, y] with SortedMapOps[x, y, CC, CC[x, y]] with UnsortedCC[x, y], +WithFilterCC[x] <: IterableOps[x, WithFilterCC, WithFilterCC[x]] with Iterable[x], - +UnsortedCC[x, y] <: Map[x, y]] extends SortedMapOps[K, V, CC, CC[K, V @uncheckedVariance]] with MapOps[K, V, UnsortedCC, CC[K, V @uncheckedVariance]] { + +UnsortedCC[x, y] <: Map[x, y]] extends SortedMapOps[K, V, CC, CC[K, V @uncheckedVariance]] with StrictMapOps[K, V, UnsortedCC, CC[K, V @uncheckedVariance]] { self: IterableOps[(K, V), WithFilterCC, _] => override def empty: CC[K, V @uncheckedVariance] = sortedMapFactory.empty(using ordering) diff --git a/library/src/scala/collection/Map.scala b/library/src/scala/collection/Map.scala index 4d448f96c7cb..a2c79949d58b 100644 --- a/library/src/scala/collection/Map.scala +++ b/library/src/scala/collection/Map.scala @@ -14,6 +14,9 @@ package scala package collection import scala.language.`2.13` +import language.experimental.captureChecking +import scala.annotation.unchecked.uncheckedVariance + import scala.annotation.nowarn import scala.collection.generic.DefaultSerializable import scala.collection.mutable.StringBuilder @@ -22,11 +25,12 @@ 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 StrictMapOps[K, V, Map, Map[K, V]] with MapFactoryDefaults[K, V, Map, Iterable] - with Equals { + with Equals + with caps.Pure { - def mapFactory: scala.collection.MapFactory[Map] = Map + def mapFactory: scala.collection.StrictMapFactory[Map] = Map def canEqual(that: Any): Boolean = true @@ -86,6 +90,46 @@ trait Map[K, +V] override def toString(): String = super[Iterable].toString() // Because `Function1` overrides `toString` too } +/** Map operations for strict maps. */ +transparent trait StrictMapOps[K, +V, +CC[_, _] <: IterableOps[_, AnyConstr, _] with caps.Pure, +C] + extends MapOps[K, V, CC, C] with caps.Pure { + + override def mapFactory: StrictMapFactory[CC] + override protected def fromSpecific(coll: IterableOnce[(K, V) @uncheckedVariance]^): C + + @`inline` override protected def mapFromIterable[K2, V2](it: Iterable[(K2, V2)]^): CC[K2, V2] = mapFactory.from(it) + + override def map[K2, V2](f: ((K, V)) => (K2, V2)): CC[K2, V2] = mapFactory.from(new View.Map(this, f)) + + override def collect[K2, V2](pf: PartialFunction[(K, V), (K2, V2)]^): CC[K2, V2] = + mapFactory.from(new View.Collect(this, pf)) + + override def flatMap[K2, V2](f: ((K, V)) => IterableOnce[(K2, V2)]^): CC[K2, V2] = mapFactory.from(new View.FlatMap(this, f)) + + override def concat[V2 >: V](suffix: collection.IterableOnce[(K, V2)]^): CC[K, V2] = mapFactory.from(suffix match { + case it: Iterable[(K, V2)] => new View.Concat(this, it) + case _ => iterator.concat(suffix.iterator) + }) + + override def ++ [V2 >: V](xs: collection.IterableOnce[(K, V2)]^): CC[K, V2] = concat(xs) + + @`inline` override def -- (keys: IterableOnce[K]^): C = { + 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") + override def ++: [V1 >: V](that: IterableOnce[(K,V1)]^): CC[K,V1] = { + 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)) + } + + +} + /** Base Map implementation type * * @tparam K Type of keys @@ -101,9 +145,9 @@ trait Map[K, +V] // 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] { + with PartialFunction[K, V] { self: MapOps[K, V, CC, C]^ => - override def view: MapView[K, V] = new MapView.Id(this) + 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 = { @@ -132,7 +176,7 @@ transparent trait MapOps[K, +V, +CC[_, _] <: IterableOps[_, AnyConstr, _], +C] /** 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] = mapFactory.from(it) + @`inline` protected 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. * @@ -199,8 +243,8 @@ transparent trait MapOps[K, +V, +CC[_, _] <: IterableOps[_, AnyConstr, _], +C] } /** A generic trait that is reused by keyset implementations */ - protected trait GenKeySet { this: Set[K] => - def iterator: Iterator[K] = MapOps.this.keysIterator + protected trait GenKeySet { this: Set[K]^ => + def iterator: Iterator[K]^{this} = MapOps.this.keysIterator def contains(key: K): Boolean = MapOps.this.contains(key) override def size: Int = MapOps.this.size override def knownSize: Int = MapOps.this.knownSize @@ -218,22 +262,22 @@ transparent trait MapOps[K, +V, +CC[_, _] <: IterableOps[_, AnyConstr, _], +C] * @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] = keySet + def keys: Iterable[K]^{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] = new AbstractIterable[V] with DefaultSerializable { + def values: Iterable[V]^{this} = new AbstractIterable[V] with DefaultSerializable { override def knownSize: Int = MapOps.this.knownSize - override def iterator: Iterator[V] = valuesIterator + 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] = new AbstractIterator[K] { + def keysIterator: Iterator[K]^{this} = new AbstractIterator[K] { val iter = MapOps.this.iterator def hasNext = iter.hasNext def next() = iter.next()._1 @@ -243,7 +287,7 @@ transparent trait MapOps[K, +V, +CC[_, _] <: IterableOps[_, AnyConstr, _], +C] * * @return an iterator over all values that are associated with some key in this map. */ - def valuesIterator: Iterator[V] = new AbstractIterator[V] { + def valuesIterator: Iterator[V]^{this} = new AbstractIterator[V] { val iter = MapOps.this.iterator def hasNext = iter.hasNext def next() = iter.next()._2 @@ -266,7 +310,7 @@ transparent trait MapOps[K, +V, +CC[_, _] <: IterableOps[_, AnyConstr, _], +C] * 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] = new MapView.FilterKeys(this, p) + 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. @@ -274,7 +318,7 @@ transparent trait MapOps[K, +V, +CC[_, _] <: IterableOps[_, AnyConstr, _], +C] * 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] = new MapView.MapValues(this, f) + 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. @@ -312,7 +356,7 @@ transparent trait MapOps[K, +V, +CC[_, _] <: IterableOps[_, AnyConstr, _], +C] * @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] = mapFactory.from(new View.Map(this, f)) + 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. @@ -324,7 +368,7 @@ transparent trait MapOps[K, +V, +CC[_, _] <: IterableOps[_, AnyConstr, _], +C] * `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] = + 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 @@ -334,7 +378,7 @@ transparent trait MapOps[K, +V, +CC[_, _] <: IterableOps[_, AnyConstr, _], +C] * @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] = mapFactory.from(new View.FlatMap(this, f)) + 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 @@ -344,7 +388,7 @@ transparent trait MapOps[K, +V, +CC[_, _] <: IterableOps[_, AnyConstr, _], +C] * @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] = mapFactory.from(suffix match { + 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) }) @@ -352,28 +396,28 @@ transparent trait MapOps[K, +V, +CC[_, _] <: IterableOps[_, AnyConstr, _], +C] // 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] = concat(xs) + /*@`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] = + 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] = + 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 = { + @`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] = { - val thatIterable: Iterable[(K, V1)] = that match { + 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) } @@ -389,17 +433,17 @@ object MapOps { */ @SerialVersionUID(3L) class WithFilter[K, +V, +IterableCC[_], +CC[_, _] <: IterableOps[_, AnyConstr, _]]( - self: MapOps[K, V, CC, _] with IterableOps[(K, V), IterableCC, _], + self: (MapOps[K, V, CC, _] with 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] = + 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] = + 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] = + 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)) } diff --git a/library/src/scala/collection/MapView.scala b/library/src/scala/collection/MapView.scala index a058f3f79514..bfc3906760dd 100644 --- a/library/src/scala/collection/MapView.scala +++ b/library/src/scala/collection/MapView.scala @@ -13,6 +13,8 @@ package scala.collection import scala.language.`2.13` +import language.experimental.captureChecking + import scala.annotation.nowarn import scala.collection.MapView.SomeMapOps import scala.collection.mutable.Builder @@ -21,7 +23,7 @@ trait MapView[K, +V] extends MapOps[K, V, ({ type l[X, Y] = View[(X, Y)] })#l, View[(K, V)]] with View[(K, V)] { - override def view: MapView[K, V] = this + override def view: MapView[K, V]^{this} = this // Ideally this returns a `View`, but bincompat /** Creates a view over all keys of this map. @@ -29,42 +31,42 @@ trait MapView[K, +V] * @return the keys of this map as a view. */ @nowarn("msg=overriding method keys") - override def keys: Iterable[K] = new MapView.Keys(this) + override def keys: Iterable[K]^{this} = new MapView.Keys(this) // Ideally this returns a `View`, but bincompat /** Creates a view over all values of this map. * * @return the values of this map as a view. */ - override def values: Iterable[V] = new MapView.Values(this) + override def values: Iterable[V]^{this} = new MapView.Values(this) /** 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. */ - override def filterKeys(p: K => Boolean): MapView[K, V] = new MapView.FilterKeys(this, p) + override 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. */ - override def mapValues[W](f: V => W): MapView[K, W] = new MapView.MapValues(this, f) + override def mapValues[W](f: V => W): MapView[K, W]^{this, f} = new MapView.MapValues(this, f) - override def filter(pred: ((K, V)) => Boolean): MapView[K, V] = new MapView.Filter(this, isFlipped = false, pred) + override def filter(pred: ((K, V)) => Boolean): MapView[K, V]^{this, pred} = new MapView.Filter(this, isFlipped = false, pred) - override def filterNot(pred: ((K, V)) => Boolean): MapView[K, V] = new MapView.Filter(this, isFlipped = true, pred) + override def filterNot(pred: ((K, V)) => Boolean): MapView[K, V]^{this, pred} = new MapView.Filter(this, isFlipped = true, pred) - override def partition(p: ((K, V)) => Boolean): (MapView[K, V], MapView[K, V]) = (filter(p), filterNot(p)) + override def partition(p: ((K, V)) => Boolean): (MapView[K, V]^{this, p}, MapView[K, V]^{this, p}) = (filter(p), filterNot(p)) - override def tapEach[U](f: ((K, V)) => U): MapView[K, V] = new MapView.TapEach(this, f) + override def tapEach[U](f: ((K, V)) => U): MapView[K, V]^{this, f} = new MapView.TapEach(this, f) def mapFactory: MapViewFactory = MapView override def empty: MapView[K, V] = mapFactory.empty - override def withFilter(p: ((K, V)) => Boolean): MapOps.WithFilter[K, V, View, ({ type l[X, Y] = View[(X, Y)] })#l] = new MapOps.WithFilter(this, p) + override def withFilter(p: ((K, V)) => Boolean): MapOps.WithFilter[K, V, View, ({ type l[X, Y] = View[(X, Y)] })#l]^{this, p} = new MapOps.WithFilter(this, p) override def toString: String = super[View].toString @@ -86,55 +88,55 @@ object MapView extends MapViewFactory { override def knownSize: Int = 0 override def isEmpty: Boolean = true override def filterKeys(p: Any => Boolean): MapView[Any, Nothing] = this - override def mapValues[W](f: Nothing => W): MapView[Any, Nothing] = this + override def mapValues[W](f: Nothing => W): MapView[Any, W] = this override def filter(pred: ((Any, Nothing)) => Boolean): MapView[Any, Nothing] = this override def filterNot(pred: ((Any, Nothing)) => Boolean): MapView[Any, Nothing] = this override def partition(p: ((Any, Nothing)) => Boolean): (MapView[Any, Nothing], MapView[Any, Nothing]) = (this, this) } @SerialVersionUID(3L) - class Id[K, +V](underlying: SomeMapOps[K, V]) extends AbstractMapView[K, V] { + class Id[K, +V](underlying: SomeMapOps[K, V]^) extends AbstractMapView[K, V] { def get(key: K): Option[V] = underlying.get(key) - def iterator: Iterator[(K, V)] = underlying.iterator + def iterator: Iterator[(K, V)]^{this} = underlying.iterator override def knownSize: Int = underlying.knownSize override def isEmpty: Boolean = underlying.isEmpty } // Ideally this is public, but bincompat @SerialVersionUID(3L) - private class Keys[K](underlying: SomeMapOps[K, _]) extends AbstractView[K] { - def iterator: Iterator[K] = underlying.keysIterator + private class Keys[K](underlying: SomeMapOps[K, _]^) extends AbstractView[K] { + def iterator: Iterator[K]^{this} = underlying.keysIterator override def knownSize: Int = underlying.knownSize override def isEmpty: Boolean = underlying.isEmpty } // Ideally this is public, but bincompat @SerialVersionUID(3L) - private class Values[+V](underlying: SomeMapOps[_, V]) extends AbstractView[V] { - def iterator: Iterator[V] = underlying.valuesIterator + private class Values[+V](underlying: SomeMapOps[_, V]^) extends AbstractView[V] { + def iterator: Iterator[V]^{this} = underlying.valuesIterator override def knownSize: Int = underlying.knownSize override def isEmpty: Boolean = underlying.isEmpty } @SerialVersionUID(3L) - class MapValues[K, +V, +W](underlying: SomeMapOps[K, V], f: V => W) extends AbstractMapView[K, W] { - def iterator: Iterator[(K, W)] = underlying.iterator.map(kv => (kv._1, f(kv._2))) + class MapValues[K, +V, +W](underlying: SomeMapOps[K, V]^, f: V => W) extends AbstractMapView[K, W] { + def iterator: Iterator[(K, W)]^{this} = underlying.iterator.map(kv => (kv._1, f(kv._2))) def get(key: K): Option[W] = underlying.get(key).map(f) override def knownSize: Int = underlying.knownSize override def isEmpty: Boolean = underlying.isEmpty } @SerialVersionUID(3L) - class FilterKeys[K, +V](underlying: SomeMapOps[K, V], p: K => Boolean) extends AbstractMapView[K, V] { - def iterator: Iterator[(K, V)] = underlying.iterator.filter { case (k, _) => p(k) } + class FilterKeys[K, +V](underlying: SomeMapOps[K, V]^, p: K => Boolean) extends AbstractMapView[K, V] { + def iterator: Iterator[(K, V)]^{this} = underlying.iterator.filter { case (k, _) => p(k) } def get(key: K): Option[V] = if (p(key)) underlying.get(key) else None override def knownSize: Int = if (underlying.knownSize == 0) 0 else super.knownSize override def isEmpty: Boolean = iterator.isEmpty } @SerialVersionUID(3L) - class Filter[K, +V](underlying: SomeMapOps[K, V], isFlipped: Boolean, p: ((K, V)) => Boolean) extends AbstractMapView[K, V] { - def iterator: Iterator[(K, V)] = underlying.iterator.filterImpl(p, isFlipped) + class Filter[K, +V](underlying: SomeMapOps[K, V]^, isFlipped: Boolean, p: ((K, V)) => Boolean) extends AbstractMapView[K, V] { + def iterator: Iterator[(K, V)]^{this} = underlying.iterator.filterImpl(p, isFlipped) def get(key: K): Option[V] = underlying.get(key) match { case s @ Some(v) if p((key, v)) != isFlipped => s case _ => None @@ -144,7 +146,7 @@ object MapView extends MapViewFactory { } @SerialVersionUID(3L) - class TapEach[K, +V, +U](underlying: SomeMapOps[K, V], f: ((K, V)) => U) extends AbstractMapView[K, V] { + class TapEach[K, +V, +U](underlying: SomeMapOps[K, V]^, f: ((K, V)) => U) extends AbstractMapView[K, V] { override def get(key: K): Option[V] = { underlying.get(key) match { case s @ Some(v) => @@ -153,7 +155,7 @@ object MapView extends MapViewFactory { case None => None } } - override def iterator: Iterator[(K, V)] = underlying.iterator.tapEach(f) + override def iterator: Iterator[(K, V)]^{this} = underlying.iterator.tapEach(f) override def knownSize: Int = underlying.knownSize override def isEmpty: Boolean = underlying.isEmpty } @@ -162,7 +164,7 @@ object MapView extends MapViewFactory { override def empty[K, V]: MapView[K, V] = EmptyMapView.asInstanceOf[MapView[K, V]] - override def from[K, V](it: IterableOnce[(K, V)]): View[(K, V)] = View.from(it) + override def from[K, V](it: IterableOnce[(K, V)]^): View[(K, V)]^{it} = View.from(it) override def from[K, V](it: SomeMapOps[K, V]): MapView[K, V] = it match { case mv: MapView[K, V] => mv diff --git a/library/src/scala/collection/SeqMap.scala b/library/src/scala/collection/SeqMap.scala index 17d187975af2..27e5ddf00a68 100644 --- a/library/src/scala/collection/SeqMap.scala +++ b/library/src/scala/collection/SeqMap.scala @@ -13,6 +13,8 @@ package scala.collection import scala.language.`2.13` +import language.experimental.captureChecking + import scala.annotation.nowarn /** @@ -29,12 +31,12 @@ import scala.annotation.nowarn */ trait SeqMap[K, +V] extends Map[K, V] - with MapOps[K, V, SeqMap, SeqMap[K, V]] + with StrictMapOps[K, V, SeqMap, SeqMap[K, V]] with MapFactoryDefaults[K, V, SeqMap, Iterable] { @nowarn("""cat=deprecation&origin=scala\.collection\.Iterable\.stringPrefix""") override protected[this] def stringPrefix: String = "SeqMap" - override def mapFactory: MapFactory[SeqMap] = SeqMap + override def mapFactory: StrictMapFactory[SeqMap] = SeqMap } object SeqMap extends MapFactory.Delegate[immutable.SeqMap](immutable.SeqMap) diff --git a/library/src/scala/collection/SortedMap.scala b/library/src/scala/collection/SortedMap.scala index d4f1aae2b62e..601039b1f28f 100644 --- a/library/src/scala/collection/SortedMap.scala +++ b/library/src/scala/collection/SortedMap.scala @@ -48,8 +48,8 @@ trait SortedMap[K, +V] } } -transparent trait SortedMapOps[K, +V, +CC[X, Y] <: Map[X, Y] with SortedMapOps[X, Y, CC, _], +C <: SortedMapOps[K, V, CC, C]] - extends MapOps[K, V, Map, C] +transparent trait SortedMapOps[K, +V, +CC[X, Y] <: Map[X, Y] with SortedMapOps[X, Y, CC, _] with caps.Pure, +C <: SortedMapOps[K, V, CC, C]] + extends StrictMapOps[K, V, Map, C] with SortedOps[K, C] { /** The companion object of this sorted map, providing various factory methods. diff --git a/library/src/scala/collection/concurrent/TrieMap.scala b/library/src/scala/collection/concurrent/TrieMap.scala index 4e3916c6981c..2df64be85521 100644 --- a/library/src/scala/collection/concurrent/TrieMap.scala +++ b/library/src/scala/collection/concurrent/TrieMap.scala @@ -711,7 +711,7 @@ final class TrieMap[K, V] private (r: AnyRef, rtupd: AtomicReferenceFieldUpdater def this() = this(Hashing.default, Equiv.universal) - override def mapFactory: MapFactory[TrieMap] = TrieMap + override def mapFactory: StrictMapFactory[TrieMap] = TrieMap /* internal methods */ @@ -1038,7 +1038,7 @@ final class TrieMap[K, V] private (r: AnyRef, rtupd: AtomicReferenceFieldUpdater @SerialVersionUID(3L) -object TrieMap extends MapFactory[TrieMap] { +object TrieMap extends StrictMapFactory[TrieMap] { def empty[K, V]: TrieMap[K, V] = new TrieMap[K, V] diff --git a/library/src/scala/collection/immutable/HashMap.scala b/library/src/scala/collection/immutable/HashMap.scala index eefdf6b6adbb..cb8f4318bcbe 100644 --- a/library/src/scala/collection/immutable/HashMap.scala +++ b/library/src/scala/collection/immutable/HashMap.scala @@ -22,7 +22,7 @@ import scala.collection.Hashing.improve import scala.collection.Stepper.EfficientSplit import scala.collection.generic.DefaultSerializable import scala.collection.mutable, mutable.ReusableBuilder -import scala.collection.{Iterator, MapFactory, MapFactoryDefaults, Stepper, StepperShape, mutable} +import scala.collection.{Iterator, StrictMapFactory, MapFactoryDefaults, Stepper, StepperShape, mutable} import scala.runtime.AbstractFunction2 import scala.runtime.Statics.releaseFence import scala.util.hashing.MurmurHash3 @@ -48,7 +48,7 @@ final class HashMap[K, +V] private[immutable] (private[immutable] val rootNode: // This release fence is present because rootNode may have previously been mutated during construction. releaseFence() - override def mapFactory: MapFactory[HashMap] = HashMap + override def mapFactory: StrictMapFactory[HashMap] = HashMap override def knownSize: Int = rootNode.size @@ -2192,7 +2192,7 @@ private final class MapNodeRemoveAllSetNodeIterator[K](rootSetNode: SetNode[K]) * @define coll immutable champ hash map */ @SerialVersionUID(3L) -object HashMap extends MapFactory[HashMap] { +object HashMap extends StrictMapFactory[HashMap] { @transient private final val EmptyMap = new HashMap(MapNode.empty) diff --git a/library/src/scala/collection/immutable/ListMap.scala b/library/src/scala/collection/immutable/ListMap.scala index 8b2cd61175fe..8e638434f622 100644 --- a/library/src/scala/collection/immutable/ListMap.scala +++ b/library/src/scala/collection/immutable/ListMap.scala @@ -48,7 +48,7 @@ sealed class ListMap[K, +V] with MapFactoryDefaults[K, V, ListMap, Iterable] with DefaultSerializable { - override def mapFactory: MapFactory[ListMap] = ListMap + override def mapFactory: StrictMapFactory[ListMap] = ListMap override def size: Int = 0 @@ -127,7 +127,7 @@ sealed class ListMap[K, +V] * @define coll list map */ @SerialVersionUID(3L) -object ListMap extends MapFactory[ListMap] { +object ListMap extends StrictMapFactory[ListMap] { /** * Represents an entry in the `ListMap`. */ diff --git a/library/src/scala/collection/immutable/Map.scala b/library/src/scala/collection/immutable/Map.scala index b262243901c9..86b45a160d62 100644 --- a/library/src/scala/collection/immutable/Map.scala +++ b/library/src/scala/collection/immutable/Map.scala @@ -28,7 +28,7 @@ trait Map[K, +V] with MapOps[K, V, Map, Map[K, V]] with MapFactoryDefaults[K, V, Map, Iterable] { - override def mapFactory: scala.collection.MapFactory[Map] = Map + override def mapFactory: scala.collection.StrictMapFactory[Map] = Map override final def toMap[K2, V2](implicit ev: (K, V) <:< (K2, V2)): Map[K2, V2] = Map.from(this.asInstanceOf[Map[K2, V2]]) @@ -62,7 +62,7 @@ trait Map[K, +V] */ transparent trait MapOps[K, +V, +CC[X, +Y] <: MapOps[X, Y, CC, _], +C <: MapOps[K, V, CC, C]] extends IterableOps[(K, V), Iterable, C] - with collection.MapOps[K, V, CC, C] { + with collection.StrictMapOps[K, V, CC, C] { protected def coll: C with CC[K, V] @@ -170,7 +170,7 @@ transparent trait StrictOptimizedMapOps[K, +V, +CC[X, +Y] <: MapOps[X, Y, CC, _] * @define Coll `immutable.Map` */ @SerialVersionUID(3L) -object Map extends MapFactory[Map] { +object Map extends StrictMapFactory[Map] { @SerialVersionUID(3L) class WithDefault[K, +V](val underlying: Map[K, V], val defaultValue: K => V) @@ -187,7 +187,7 @@ object Map extends MapFactory[Map] { override def isEmpty: Boolean = underlying.isEmpty - override def mapFactory: MapFactory[Map] = underlying.mapFactory + override def mapFactory: StrictMapFactory[Map] = underlying.mapFactory override def concat [V2 >: V](xs: collection.IterableOnce[(K, V2)]): WithDefault[K, V2] = new WithDefault(underlying.concat(xs), defaultValue) diff --git a/library/src/scala/collection/immutable/SeqMap.scala b/library/src/scala/collection/immutable/SeqMap.scala index 50a5dcfce382..13ce6bc1c858 100644 --- a/library/src/scala/collection/immutable/SeqMap.scala +++ b/library/src/scala/collection/immutable/SeqMap.scala @@ -38,11 +38,11 @@ trait SeqMap[K, +V] with collection.SeqMap[K, V] with MapOps[K, V, SeqMap, SeqMap[K, V]] with MapFactoryDefaults[K, V, SeqMap, Iterable] { - override def mapFactory: MapFactory[SeqMap] = SeqMap + override def mapFactory: StrictMapFactory[SeqMap] = SeqMap } -object SeqMap extends MapFactory[SeqMap] { +object SeqMap extends StrictMapFactory[SeqMap] { def empty[K, V]: SeqMap[K, V] = EmptySeqMap.asInstanceOf[SeqMap[K, V]] def from[K, V](it: collection.IterableOnce[(K, V)]): SeqMap[K, V] = diff --git a/library/src/scala/collection/immutable/TreeSeqMap.scala b/library/src/scala/collection/immutable/TreeSeqMap.scala index e3a7552c5934..4e57aab10e2f 100644 --- a/library/src/scala/collection/immutable/TreeSeqMap.scala +++ b/library/src/scala/collection/immutable/TreeSeqMap.scala @@ -59,7 +59,7 @@ final class TreeSeqMap[K, +V] private ( override protected[this] def className: String = "TreeSeqMap" - override def mapFactory: MapFactory[TreeSeqMap] = TreeSeqMap + override def mapFactory: StrictMapFactory[TreeSeqMap] = TreeSeqMap override val size = mapping.size @@ -287,7 +287,7 @@ final class TreeSeqMap[K, +V] private ( @`inline` private[this] def value(p: (_, V)) = p._2 @`inline` private[this] def binding(k: K) = mapping(k).copy(_1 = k) } -object TreeSeqMap extends MapFactory[TreeSeqMap] { +object TreeSeqMap extends StrictMapFactory[TreeSeqMap] { sealed trait OrderBy object OrderBy { case object Insertion extends OrderBy diff --git a/library/src/scala/collection/immutable/VectorMap.scala b/library/src/scala/collection/immutable/VectorMap.scala index 316d02037ec0..5a4ecd20b2a3 100644 --- a/library/src/scala/collection/immutable/VectorMap.scala +++ b/library/src/scala/collection/immutable/VectorMap.scala @@ -166,7 +166,7 @@ final class VectorMap[K, +V] private ( } } - override def mapFactory: MapFactory[VectorMap] = VectorMap + override def mapFactory: StrictMapFactory[VectorMap] = VectorMap override def contains(key: K): Boolean = underlying.contains(key) @@ -219,7 +219,7 @@ final class VectorMap[K, +V] private ( } } -object VectorMap extends MapFactory[VectorMap] { +object VectorMap extends StrictMapFactory[VectorMap] { //Class to mark deleted slots in 'fields'. //When one or more consecutive slots are deleted, the 'distance' of the first 'Tombstone' // represents the distance to the location of the next undeleted slot (or the last slot in 'fields' +1 if it does not exist). diff --git a/library/src/scala/collection/mutable/HashMap.scala b/library/src/scala/collection/mutable/HashMap.scala index 7980ffe08bd7..7459b550a12a 100644 --- a/library/src/scala/collection/mutable/HashMap.scala +++ b/library/src/scala/collection/mutable/HashMap.scala @@ -568,7 +568,7 @@ class HashMap[K, V](initialCapacity: Int, loadFactor: Double) this } - override def mapFactory: MapFactory[HashMap] = HashMap + override def mapFactory: StrictMapFactory[HashMap] = HashMap @nowarn("""cat=deprecation&origin=scala\.collection\.Iterable\.stringPrefix""") override protected[this] def stringPrefix = "HashMap" @@ -595,7 +595,7 @@ class HashMap[K, V](initialCapacity: Int, loadFactor: Double) * @define coll mutable hash map */ @SerialVersionUID(3L) -object HashMap extends MapFactory[HashMap] { +object HashMap extends StrictMapFactory[HashMap] { def empty[K, V]: HashMap[K, V] = new HashMap[K, V] diff --git a/library/src/scala/collection/mutable/LinkedHashMap.scala b/library/src/scala/collection/mutable/LinkedHashMap.scala index 5a4f9285972d..99cbaafaf34a 100644 --- a/library/src/scala/collection/mutable/LinkedHashMap.scala +++ b/library/src/scala/collection/mutable/LinkedHashMap.scala @@ -43,7 +43,7 @@ class LinkedHashMap[K, V] with MapFactoryDefaults[K, V, LinkedHashMap, Iterable] with DefaultSerializable { - override def mapFactory: MapFactory[LinkedHashMap] = LinkedHashMap + override def mapFactory: StrictMapFactory[LinkedHashMap] = LinkedHashMap // stepper / keyStepper / valueStepper are not overridden to use XTableStepper because that stepper // would not return the elements in insertion order @@ -475,7 +475,7 @@ class LinkedHashMap[K, V] * @define coll linked hash map */ @SerialVersionUID(3L) -object LinkedHashMap extends MapFactory[LinkedHashMap] { +object LinkedHashMap extends StrictMapFactory[LinkedHashMap] { def empty[K, V] = new LinkedHashMap[K, V] diff --git a/library/src/scala/collection/mutable/ListMap.scala b/library/src/scala/collection/mutable/ListMap.scala index 3bb7e9bd54bb..6337549e313f 100644 --- a/library/src/scala/collection/mutable/ListMap.scala +++ b/library/src/scala/collection/mutable/ListMap.scala @@ -39,7 +39,7 @@ class ListMap[K, V] with MapFactoryDefaults[K, V, ListMap, Iterable] with DefaultSerializable { - override def mapFactory: MapFactory[ListMap] = ListMap + override def mapFactory: StrictMapFactory[ListMap] = ListMap private[this] var elems: List[(K, V)] = List() private[this] var siz: Int = 0 @@ -76,7 +76,7 @@ class ListMap[K, V] */ @SerialVersionUID(3L) @deprecated("Use an immutable.ListMap assigned to a var instead of mutable.ListMap", "2.13.0") -object ListMap extends MapFactory[ListMap] { +object ListMap extends StrictMapFactory[ListMap] { def empty[K, V]: ListMap[K, V] = new ListMap[K, V] def from[K, V](it: IterableOnce[(K, V)]): ListMap[K,V] = Growable.from(empty[K, V], it) def newBuilder[K, V]: Builder[(K, V), ListMap[K,V]] = new GrowableBuilder(empty[K, V]) diff --git a/library/src/scala/collection/mutable/Map.scala b/library/src/scala/collection/mutable/Map.scala index df6f036b62d9..a6975c90201d 100644 --- a/library/src/scala/collection/mutable/Map.scala +++ b/library/src/scala/collection/mutable/Map.scala @@ -25,7 +25,7 @@ trait Map[K, V] with Shrinkable[K] with MapFactoryDefaults[K, V, Map, Iterable] { - override def mapFactory: scala.collection.MapFactory[Map] = Map + override def mapFactory: scala.collection.StrictMapFactory[Map] = Map /* //TODO consider keeping `remove` because it returns the removed entry @@ -66,7 +66,7 @@ trait Map[K, V] */ transparent trait MapOps[K, V, +CC[X, Y] <: MapOps[X, Y, CC, _], +C <: MapOps[K, V, CC, C]] extends IterableOps[(K, V), Iterable, C] - with collection.MapOps[K, V, CC, C] + with collection.StrictMapOps[K, V, CC, C] with Cloneable[C] with Builder[(K, V), C] with Growable[(K, V)] @@ -242,7 +242,7 @@ object Map extends MapFactory.Delegate[Map](HashMap) { def iterator: scala.collection.Iterator[(K, V)] = underlying.iterator override def isEmpty: Boolean = underlying.isEmpty override def knownSize: Int = underlying.knownSize - override def mapFactory: MapFactory[Map] = underlying.mapFactory + override def mapFactory: StrictMapFactory[Map] = underlying.mapFactory override def clear(): Unit = underlying.clear() diff --git a/library/src/scala/collection/mutable/OpenHashMap.scala b/library/src/scala/collection/mutable/OpenHashMap.scala index 36d77098512b..d8222ba6914a 100644 --- a/library/src/scala/collection/mutable/OpenHashMap.scala +++ b/library/src/scala/collection/mutable/OpenHashMap.scala @@ -24,7 +24,7 @@ import scala.collection.generic.DefaultSerializable */ @deprecated("Use HashMap or one of the specialized versions (LongMap, AnyRefMap) instead of OpenHashMap", "2.13.0") @SerialVersionUID(3L) -object OpenHashMap extends MapFactory[OpenHashMap] { +object OpenHashMap extends StrictMapFactory[OpenHashMap] { def empty[K, V] = new OpenHashMap[K, V] def from[K, V](it: IterableOnce[(K, V)]): OpenHashMap[K,V] = empty ++= it @@ -76,7 +76,7 @@ class OpenHashMap[Key, Value](initialSize : Int) */ def this() = this(8) - override def mapFactory: MapFactory[OpenHashMap] = OpenHashMap + override def mapFactory: StrictMapFactory[OpenHashMap] = OpenHashMap private[this] val actualInitialSize = OpenHashMap.nextPositivePowerOfTwo(initialSize) diff --git a/library/src/scala/collection/mutable/SeqMap.scala b/library/src/scala/collection/mutable/SeqMap.scala index bad3ea10b243..be74e085d566 100644 --- a/library/src/scala/collection/mutable/SeqMap.scala +++ b/library/src/scala/collection/mutable/SeqMap.scala @@ -34,7 +34,7 @@ trait SeqMap[K, V] extends Map[K, V] with collection.SeqMap[K, V] with MapOps[K, V, SeqMap, SeqMap[K, V]] with MapFactoryDefaults[K, V, SeqMap, Iterable] { - override def mapFactory: MapFactory[SeqMap] = SeqMap + override def mapFactory: StrictMapFactory[SeqMap] = SeqMap } object SeqMap extends MapFactory.Delegate[SeqMap](LinkedHashMap) diff --git a/library/src/scala/collection/mutable/WeakHashMap.scala b/library/src/scala/collection/mutable/WeakHashMap.scala index e0f592faf9f1..183cd55c51b8 100644 --- a/library/src/scala/collection/mutable/WeakHashMap.scala +++ b/library/src/scala/collection/mutable/WeakHashMap.scala @@ -38,7 +38,7 @@ class WeakHashMap[K, V] extends JMapWrapper[K, V](new java.util.WeakHashMap) with JMapWrapperLike[K, V, WeakHashMap, WeakHashMap[K, V]] with MapFactoryDefaults[K, V, WeakHashMap, Iterable] { override def empty = new WeakHashMap[K, V] - override def mapFactory: MapFactory[WeakHashMap] = WeakHashMap + override def mapFactory: StrictMapFactory[WeakHashMap] = WeakHashMap @nowarn("""cat=deprecation&origin=scala\.collection\.Iterable\.stringPrefix""") override protected[this] def stringPrefix = "WeakHashMap" } @@ -48,7 +48,7 @@ class WeakHashMap[K, V] extends JMapWrapper[K, V](new java.util.WeakHashMap) * @define coll weak hash map */ @SerialVersionUID(3L) -object WeakHashMap extends MapFactory[WeakHashMap] { +object WeakHashMap extends StrictMapFactory[WeakHashMap] { def empty[K, V]: WeakHashMap[K,V] = new WeakHashMap[K, V] def from[K, V](it: collection.IterableOnce[(K, V)]): WeakHashMap[K,V] = Growable.from(empty[K, V], it) def newBuilder[K, V]: Builder[(K, V), WeakHashMap[K,V]] = new GrowableBuilder(WeakHashMap.empty[K, V]) From ba2c1b014ecaf2100895e971019ea86c32fd9ed0 Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Wed, 13 Aug 2025 22:49:52 +0200 Subject: [PATCH 061/101] Capture-check sorted maps --- TODO.md | 10 +++++----- library/src/scala/collection/DefaultMap.scala | 1 + library/src/scala/collection/Iterable.scala | 2 +- library/src/scala/collection/SortedMap.scala | 18 ++++++++++-------- library/src/scala/collection/SortedOps.scala | 1 + .../collection/StrictOptimizedMapOps.scala | 7 ++++--- .../StrictOptimizedSortedMapOps.scala | 8 +++++--- 7 files changed, 27 insertions(+), 20 deletions(-) diff --git a/TODO.md b/TODO.md index 0e9889bb9d72..78610d6490a1 100644 --- a/TODO.md +++ b/TODO.md @@ -189,7 +189,7 @@ - [x] library/src/scala/collection/BitSet.scala - [x] library/src/scala/collection/BufferedIterator.scala - [x] library/src/scala/collection/BuildFrom.scala -- [ ] library/src/scala/collection/DefaultMap.scala +- [x] library/src/scala/collection/DefaultMap.scala - [x] library/src/scala/collection/Factory.scala - [x] library/src/scala/collection/Hashing.scala - [x] library/src/scala/collection/IndexedSeq.scala @@ -207,16 +207,16 @@ - [x] library/src/scala/collection/SeqMap.scala - [ ] library/src/scala/collection/SeqView.scala - [x] library/src/scala/collection/Set.scala -- [ ] library/src/scala/collection/SortedMap.scala -- [ ] library/src/scala/collection/SortedOps.scala +- [x] library/src/scala/collection/SortedMap.scala +- [x] library/src/scala/collection/SortedOps.scala - [x] library/src/scala/collection/SortedSet.scala - [x] library/src/scala/collection/Stepper.scala - [x] library/src/scala/collection/StepperShape.scala - [x] library/src/scala/collection/StrictOptimizedIterableOps.scala -- [ ] library/src/scala/collection/StrictOptimizedMapOps.scala +- [x] library/src/scala/collection/StrictOptimizedMapOps.scala - [x] library/src/scala/collection/StrictOptimizedSeqOps.scala - [x] library/src/scala/collection/StrictOptimizedSetOps.scala -- [ ] library/src/scala/collection/StrictOptimizedSortedMapOps.scala +- [x] library/src/scala/collection/StrictOptimizedSortedMapOps.scala - [x] library/src/scala/collection/StrictOptimizedSortedSetOps.scala - [x] library/src/scala/collection/StringOps.scala - [x] library/src/scala/collection/StringParsers.scala diff --git a/library/src/scala/collection/DefaultMap.scala b/library/src/scala/collection/DefaultMap.scala index d957a4ef245e..08549fd8f975 100644 --- a/library/src/scala/collection/DefaultMap.scala +++ b/library/src/scala/collection/DefaultMap.scala @@ -14,6 +14,7 @@ package scala package collection import scala.language.`2.13` +import language.experimental.captureChecking /** A default map which builds a default `immutable.Map` implementation for all * transformations. diff --git a/library/src/scala/collection/Iterable.scala b/library/src/scala/collection/Iterable.scala index a5acb609425b..9fc797513496 100644 --- a/library/src/scala/collection/Iterable.scala +++ b/library/src/scala/collection/Iterable.scala @@ -1052,6 +1052,6 @@ trait SortedMapFactoryDefaults[K, +V, override protected def fromSpecific(coll: IterableOnce[(K, V @uncheckedVariance)]^): CC[K, V @uncheckedVariance] = sortedMapFactory.from(coll)(using ordering) override protected def newSpecificBuilder: mutable.Builder[(K, V @uncheckedVariance), CC[K, V @uncheckedVariance]] = sortedMapFactory.newBuilder[K, V](using ordering) - override def withFilter(p: ((K, V)) => Boolean): collection.SortedMapOps.WithFilter[K, V, WithFilterCC, UnsortedCC, CC] = + override def withFilter(p: ((K, V)) => Boolean): collection.SortedMapOps.WithFilter[K, V, WithFilterCC, UnsortedCC, CC]^{p} = new collection.SortedMapOps.WithFilter[K, V, WithFilterCC, UnsortedCC, CC](this, p) } diff --git a/library/src/scala/collection/SortedMap.scala b/library/src/scala/collection/SortedMap.scala index 601039b1f28f..10f6cc14a01f 100644 --- a/library/src/scala/collection/SortedMap.scala +++ b/library/src/scala/collection/SortedMap.scala @@ -14,6 +14,8 @@ package scala package collection import scala.language.`2.13` +import language.experimental.captureChecking + import scala.annotation.{implicitNotFound, nowarn} /** A Map whose keys are sorted according to a [[scala.math.Ordering]]*/ @@ -63,7 +65,7 @@ transparent trait SortedMapOps[K, +V, +CC[X, Y] <: Map[X, Y] with SortedMapOps[X /** Similar to `mapFromIterable`, but returns a SortedMap collection type. * Note that the return type is now `CC[K2, V2]`. */ - @`inline` protected final def sortedMapFromIterable[K2, V2](it: Iterable[(K2, V2)])(implicit ordering: Ordering[K2]): CC[K2, V2] = sortedMapFactory.from(it) + @`inline` protected final def sortedMapFromIterable[K2, V2](it: Iterable[(K2, V2)]^)(implicit ordering: Ordering[K2]): CC[K2, V2] = sortedMapFactory.from(it) def unsorted: Map[K, V] @@ -163,7 +165,7 @@ transparent trait SortedMapOps[K, +V, +CC[X, Y] <: Map[X, Y] with SortedMapOps[X * @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)])(implicit @implicitNotFound(SortedMapOps.ordMsg) ordering: Ordering[K2]): CC[K2, V2] = + def flatMap[K2, V2](f: ((K, V)) => IterableOnce[(K2, V2)]^)(implicit @implicitNotFound(SortedMapOps.ordMsg) ordering: Ordering[K2]): CC[K2, V2] = sortedMapFactory.from(new View.FlatMap(this, f)) /** Builds a new sorted map by applying a partial function to all elements of this $coll @@ -174,16 +176,16 @@ transparent trait SortedMapOps[K, +V, +CC[X, Y] <: Map[X, Y] with SortedMapOps[X * `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)])(implicit @implicitNotFound(SortedMapOps.ordMsg) ordering: Ordering[K2]): CC[K2, V2] = + def collect[K2, V2](pf: PartialFunction[(K, V), (K2, V2)]^)(implicit @implicitNotFound(SortedMapOps.ordMsg) ordering: Ordering[K2]): CC[K2, V2] = sortedMapFactory.from(new View.Collect(this, pf)) - override def concat[V2 >: V](suffix: IterableOnce[(K, V2)]): CC[K, V2] = sortedMapFactory.from(suffix match { + override def concat[V2 >: V](suffix: IterableOnce[(K, V2)]^): CC[K, V2] = sortedMapFactory.from(suffix match { case it: Iterable[(K, V2)] => new View.Concat(this, it) case _ => iterator.concat(suffix.iterator) })(using ordering) /** Alias for `concat` */ - @`inline` override final def ++ [V2 >: V](xs: IterableOnce[(K, V2)]): CC[K, V2] = concat(xs) + @`inline` override final def ++ [V2 >: V](xs: IterableOnce[(K, V2)]^): CC[K, V2] = concat(xs) @deprecated("Consider requiring an immutable Map or fall back to Map.concat", "2.13.0") override def + [V1 >: V](kv: (K, V1)): CC[K, V1] = sortedMapFactory.from(new View.Appended(this, kv))(using ordering) @@ -204,13 +206,13 @@ object SortedMapOps { p: ((K, V)) => Boolean ) extends MapOps.WithFilter[K, V, IterableCC, MapCC](self, p) { - def map[K2 : Ordering, V2](f: ((K, V)) => (K2, V2)): CC[K2, V2] = + def map[K2 : Ordering, V2](f: ((K, V)) => (K2, V2)): CC[K2, V2]^{this, f} = self.sortedMapFactory.from(new View.Map(filtered, f)) - def flatMap[K2 : Ordering, V2](f: ((K, V)) => IterableOnce[(K2, V2)]): CC[K2, V2] = + def flatMap[K2 : Ordering, V2](f: ((K, V)) => IterableOnce[(K2, V2)]^): CC[K2, V2]^{this, f} = self.sortedMapFactory.from(new View.FlatMap(filtered, f)) - override def withFilter(q: ((K, V)) => Boolean): WithFilter[K, V, IterableCC, MapCC, CC] = + override def withFilter(q: ((K, V)) => Boolean): WithFilter[K, V, IterableCC, MapCC, CC]^{this, q} = new WithFilter[K, V, IterableCC, MapCC, CC](self, (kv: (K, V)) => p(kv) && q(kv)) } diff --git a/library/src/scala/collection/SortedOps.scala b/library/src/scala/collection/SortedOps.scala index 4fefcc4038d0..b995ef9e1911 100644 --- a/library/src/scala/collection/SortedOps.scala +++ b/library/src/scala/collection/SortedOps.scala @@ -13,6 +13,7 @@ package scala.collection import scala.language.`2.13` +import language.experimental.captureChecking /** Base trait for sorted collections */ transparent trait SortedOps[A, +C] { diff --git a/library/src/scala/collection/StrictOptimizedMapOps.scala b/library/src/scala/collection/StrictOptimizedMapOps.scala index cc594f268c07..b5eaa0d1638d 100644 --- a/library/src/scala/collection/StrictOptimizedMapOps.scala +++ b/library/src/scala/collection/StrictOptimizedMapOps.scala @@ -13,6 +13,7 @@ package scala.collection import scala.language.`2.13` +import language.experimental.captureChecking /** * Trait that overrides map operations to take advantage of strict builders. @@ -29,13 +30,13 @@ transparent trait StrictOptimizedMapOps[K, +V, +CC[_, _] <: IterableOps[_, AnyCo override def map[K2, V2](f: ((K, V)) => (K2, V2)): CC[K2, V2] = strictOptimizedMap(mapFactory.newBuilder, f) - override def flatMap[K2, V2](f: ((K, V)) => IterableOnce[(K2, V2)]): CC[K2, V2] = + override def flatMap[K2, V2](f: ((K, V)) => IterableOnce[(K2, V2)]^): CC[K2, V2] = strictOptimizedFlatMap(mapFactory.newBuilder, f) - override def concat[V2 >: V](suffix: IterableOnce[(K, V2)]): CC[K, V2] = + override def concat[V2 >: V](suffix: IterableOnce[(K, V2)]^): CC[K, V2] = strictOptimizedConcat(suffix, mapFactory.newBuilder) - override def collect[K2, V2](pf: PartialFunction[(K, V), (K2, V2)]): CC[K2, V2] = + override def collect[K2, V2](pf: PartialFunction[(K, V), (K2, V2)]^): CC[K2, V2] = strictOptimizedCollect(mapFactory.newBuilder, pf) @deprecated("Use ++ with an explicit collection argument instead of + with varargs", "2.13.0") diff --git a/library/src/scala/collection/StrictOptimizedSortedMapOps.scala b/library/src/scala/collection/StrictOptimizedSortedMapOps.scala index c71ad8d1e447..e9328dfb01e4 100644 --- a/library/src/scala/collection/StrictOptimizedSortedMapOps.scala +++ b/library/src/scala/collection/StrictOptimizedSortedMapOps.scala @@ -13,6 +13,8 @@ package scala.collection import scala.language.`2.13` +import language.experimental.captureChecking + import scala.annotation.implicitNotFound /** @@ -30,13 +32,13 @@ transparent trait StrictOptimizedSortedMapOps[K, +V, +CC[X, Y] <: Map[X, Y] with override def map[K2, V2](f: ((K, V)) => (K2, V2))(implicit @implicitNotFound(SortedMapOps.ordMsg) ordering: Ordering[K2]): CC[K2, V2] = strictOptimizedMap(sortedMapFactory.newBuilder, f) - override def flatMap[K2, V2](f: ((K, V)) => IterableOnce[(K2, V2)])(implicit @implicitNotFound(SortedMapOps.ordMsg) ordering: Ordering[K2]): CC[K2, V2] = + override def flatMap[K2, V2](f: ((K, V)) => IterableOnce[(K2, V2)]^)(implicit @implicitNotFound(SortedMapOps.ordMsg) ordering: Ordering[K2]): CC[K2, V2] = strictOptimizedFlatMap(sortedMapFactory.newBuilder, f) - override def concat[V2 >: V](xs: IterableOnce[(K, V2)]): CC[K, V2] = + override def concat[V2 >: V](xs: IterableOnce[(K, V2)]^): CC[K, V2] = strictOptimizedConcat(xs, sortedMapFactory.newBuilder(using ordering)) - override def collect[K2, V2](pf: PartialFunction[(K, V), (K2, V2)])(implicit @implicitNotFound(SortedMapOps.ordMsg) ordering: Ordering[K2]): CC[K2, V2] = + override def collect[K2, V2](pf: PartialFunction[(K, V), (K2, V2)]^)(implicit @implicitNotFound(SortedMapOps.ordMsg) ordering: Ordering[K2]): CC[K2, V2] = strictOptimizedCollect(sortedMapFactory.newBuilder, pf) @deprecated("Use ++ with an explicit collection argument instead of + with varargs", "2.13.0") From c4e076ef55eec72d8ce132e3574a8c402821205e Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Wed, 13 Aug 2025 23:04:52 +0200 Subject: [PATCH 062/101] Capture check SeqView --- TODO.md | 2 +- .../src/scala/collection/IterableOnce.scala | 2 +- library/src/scala/collection/Seq.scala | 2 +- library/src/scala/collection/SeqView.scala | 78 ++++++++++--------- 4 files changed, 44 insertions(+), 40 deletions(-) diff --git a/TODO.md b/TODO.md index 78610d6490a1..54074a2d6d38 100644 --- a/TODO.md +++ b/TODO.md @@ -205,7 +205,7 @@ - [x] library/src/scala/collection/Searching.scala - [x] library/src/scala/collection/Seq.scala - [x] library/src/scala/collection/SeqMap.scala -- [ ] library/src/scala/collection/SeqView.scala +- [x] library/src/scala/collection/SeqView.scala - [x] library/src/scala/collection/Set.scala - [x] library/src/scala/collection/SortedMap.scala - [x] library/src/scala/collection/SortedOps.scala diff --git a/library/src/scala/collection/IterableOnce.scala b/library/src/scala/collection/IterableOnce.scala index bd479ab06a81..a139e3de14f8 100644 --- a/library/src/scala/collection/IterableOnce.scala +++ b/library/src/scala/collection/IterableOnce.scala @@ -1506,7 +1506,7 @@ transparent trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOn else mutable.ArrayBuilder.make[B].addAll(this).result() // For internal use - protected def reversed: Iterable[A] = { + protected def reversed: Iterable[A]^{this} = { var xs: immutable.List[A] = immutable.Nil val it = iterator while (it.hasNext) xs = it.next() :: xs diff --git a/library/src/scala/collection/Seq.scala b/library/src/scala/collection/Seq.scala index 81bc4204b846..05d65442ef6d 100644 --- a/library/src/scala/collection/Seq.scala +++ b/library/src/scala/collection/Seq.scala @@ -107,7 +107,7 @@ transparent trait StrictSeqOps[+A, +CC[B] <: caps.Pure, +C] extends Any with Seq transparent trait SeqOps[+A, +CC[_], +C] extends Any with IterableOps[A, CC, C] { self: SeqOps[A, CC, C]^ => - override def view: SeqView[A] = new SeqView.Id[A](this) + override def view: SeqView[A]^{this} = new SeqView.Id[A](this) /** Gets the element at the specified index. This operation is provided for convenience in `Seq`. It should * not be assumed to be efficient unless you have an `IndexedSeq`. */ diff --git a/library/src/scala/collection/SeqView.scala b/library/src/scala/collection/SeqView.scala index 3f6a4c5c3ea4..960de6f1d752 100644 --- a/library/src/scala/collection/SeqView.scala +++ b/library/src/scala/collection/SeqView.scala @@ -14,28 +14,30 @@ package scala package collection import scala.language.`2.13` +import language.experimental.captureChecking + import scala.annotation.nowarn import scala.collection.generic.CommonErrors trait SeqView[+A] extends SeqOps[A, View, View[A]] with View[A] { - override def view: SeqView[A] = this + override def view: SeqView[A]^{this} = this - override def map[B](f: A => B): SeqView[B] = new SeqView.Map(this, f) - override def appended[B >: A](elem: B): SeqView[B] = new SeqView.Appended(this, elem) - override def prepended[B >: A](elem: B): SeqView[B] = new SeqView.Prepended(elem, this) - override def reverse: SeqView[A] = new SeqView.Reverse(this) - override def take(n: Int): SeqView[A] = new SeqView.Take(this, n) - override def drop(n: Int): SeqView[A] = new SeqView.Drop(this, n) - override def takeRight(n: Int): SeqView[A] = new SeqView.TakeRight(this, n) - override def dropRight(n: Int): SeqView[A] = new SeqView.DropRight(this, n) - override def tapEach[U](f: A => U): SeqView[A] = new SeqView.Map(this, { (a: A) => f(a); a }) + override def map[B](f: A => B): SeqView[B]^{this, f} = new SeqView.Map(this, f) + override def appended[B >: A](elem: B): SeqView[B]^{this} = new SeqView.Appended(this, elem) + override def prepended[B >: A](elem: B): SeqView[B]^{this} = new SeqView.Prepended(elem, this) + override def reverse: SeqView[A]^{this} = new SeqView.Reverse(this) + override def take(n: Int): SeqView[A]^{this} = new SeqView.Take(this, n) + override def drop(n: Int): SeqView[A]^{this} = new SeqView.Drop(this, n) + override def takeRight(n: Int): SeqView[A]^{this} = new SeqView.TakeRight(this, n) + override def dropRight(n: Int): SeqView[A]^{this} = new SeqView.DropRight(this, n) + override def tapEach[U](f: A => U): SeqView[A]^{this, f} = new SeqView.Map(this, { (a: A) => f(a); a }) - def concat[B >: A](suffix: SeqView.SomeSeqOps[B]): SeqView[B] = new SeqView.Concat(this, suffix) - def appendedAll[B >: A](suffix: SeqView.SomeSeqOps[B]): SeqView[B] = new SeqView.Concat(this, suffix) - def prependedAll[B >: A](prefix: SeqView.SomeSeqOps[B]): SeqView[B] = new SeqView.Concat(prefix, this) + def concat[B >: A](suffix: SeqView.SomeSeqOps[B]^): SeqView[B]^{this, suffix} = new SeqView.Concat(this, suffix) + def appendedAll[B >: A](suffix: SeqView.SomeSeqOps[B]^): SeqView[B]^{this, suffix} = new SeqView.Concat(this, suffix) + def prependedAll[B >: A](prefix: SeqView.SomeSeqOps[B]^): SeqView[B]^{this, prefix} = new SeqView.Concat(prefix, this) - override def sorted[B >: A](implicit ord: Ordering[B]): SeqView[A] = new SeqView.Sorted(this, ord) + override def sorted[B >: A](implicit ord: Ordering[B]): SeqView[A]^{this} = new SeqView.Sorted(this, ord) @nowarn("""cat=deprecation&origin=scala\.collection\.Iterable\.stringPrefix""") override protected[this] def stringPrefix: String = "SeqView" @@ -48,34 +50,34 @@ object SeqView { /** A view that doesn’t apply any transformation to an underlying sequence */ @SerialVersionUID(3L) - class Id[+A](underlying: SomeSeqOps[A]) extends AbstractSeqView[A] { + class Id[+A](underlying: SomeSeqOps[A]^) extends AbstractSeqView[A] { def apply(idx: Int): A = underlying.apply(idx) def length: Int = underlying.length - def iterator: Iterator[A] = underlying.iterator + def iterator: Iterator[A]^{this} = underlying.iterator override def knownSize: Int = underlying.knownSize override def isEmpty: Boolean = underlying.isEmpty } @SerialVersionUID(3L) - class Map[+A, +B](underlying: SomeSeqOps[A], f: A => B) extends View.Map[A, B](underlying, f) with SeqView[B] { + class Map[+A, +B](underlying: SomeSeqOps[A]^, f: A => B) extends View.Map[A, B](underlying, f) with SeqView[B] { def apply(idx: Int): B = f(underlying(idx)) def length: Int = underlying.length } @SerialVersionUID(3L) - class Appended[+A](underlying: SomeSeqOps[A], elem: A) extends View.Appended(underlying, elem) with SeqView[A] { + class Appended[+A](underlying: SomeSeqOps[A]^, elem: A) extends View.Appended(underlying, elem) with SeqView[A] { def apply(idx: Int): A = if (idx == underlying.length) elem else underlying(idx) def length: Int = underlying.length + 1 } @SerialVersionUID(3L) - class Prepended[+A](elem: A, underlying: SomeSeqOps[A]) extends View.Prepended(elem, underlying) with SeqView[A] { + class Prepended[+A](elem: A, underlying: SomeSeqOps[A]^) extends View.Prepended(elem, underlying) with SeqView[A] { def apply(idx: Int): A = if (idx == 0) elem else underlying(idx - 1) def length: Int = underlying.length + 1 } @SerialVersionUID(3L) - class Concat[A](prefix: SomeSeqOps[A], suffix: SomeSeqOps[A]) extends View.Concat[A](prefix, suffix) with SeqView[A] { + class Concat[A](prefix: SomeSeqOps[A]^, suffix: SomeSeqOps[A]^) extends View.Concat[A](prefix, suffix) with SeqView[A] { def apply(idx: Int): A = { val l = prefix.length if (idx < l) prefix(idx) else suffix(idx - l) @@ -84,16 +86,16 @@ object SeqView { } @SerialVersionUID(3L) - class Reverse[A](underlying: SomeSeqOps[A]) extends AbstractSeqView[A] { + class Reverse[A](underlying: SomeSeqOps[A]^) extends AbstractSeqView[A] { def apply(i: Int) = underlying.apply(size - 1 - i) def length = underlying.size - def iterator: Iterator[A] = underlying.reverseIterator + def iterator: Iterator[A]^{this} = underlying.reverseIterator override def knownSize: Int = underlying.knownSize override def isEmpty: Boolean = underlying.isEmpty } @SerialVersionUID(3L) - class Take[+A](underlying: SomeSeqOps[A], n: Int) extends View.Take(underlying, n) with SeqView[A] { + class Take[+A](underlying: SomeSeqOps[A]^, n: Int) extends View.Take(underlying, n) with SeqView[A] { def apply(idx: Int): A = if (idx < n) { underlying(idx) } else { @@ -106,7 +108,7 @@ object SeqView { } @SerialVersionUID(3L) - class TakeRight[+A](underlying: SomeSeqOps[A], n: Int) extends View.TakeRight(underlying, n) with SeqView[A] { + class TakeRight[+A](underlying: SomeSeqOps[A]^, n: Int) extends View.TakeRight(underlying, n) with SeqView[A] { private[this] val delta = (underlying.size - (n max 0)) max 0 def length = underlying.size - delta @throws[IndexOutOfBoundsException] @@ -114,15 +116,15 @@ object SeqView { } @SerialVersionUID(3L) - class Drop[A](underlying: SomeSeqOps[A], n: Int) extends View.Drop[A](underlying, n) with SeqView[A] { + class Drop[A](underlying: SomeSeqOps[A]^, n: Int) extends View.Drop[A](underlying, n) with SeqView[A] { def length = (underlying.size - normN) max 0 @throws[IndexOutOfBoundsException] def apply(i: Int) = underlying.apply(i + normN) - override def drop(n: Int): SeqView[A] = new Drop(underlying, this.n + n) + override def drop(n: Int): SeqView[A]^{this} = new Drop(underlying, this.n + n) } @SerialVersionUID(3L) - class DropRight[A](underlying: SomeSeqOps[A], n: Int) extends View.DropRight[A](underlying, n) with SeqView[A] { + class DropRight[A](underlying: SomeSeqOps[A]^, n: Int) extends View.DropRight[A](underlying, n) with SeqView[A] { private[this] val len = (underlying.size - (n max 0)) max 0 def length = len @throws[IndexOutOfBoundsException] @@ -130,15 +132,17 @@ object SeqView { } @SerialVersionUID(3L) - class Sorted[A, B >: A] private (private[this] var underlying: SomeSeqOps[A], + class Sorted[A, B >: A] private (underlying_ : SomeSeqOps[A]^, private[this] val len: Int, ord: Ordering[B]) extends SeqView[A] { - outer => + outer: Sorted[A, B]^ => + + private var underlying = underlying_ // force evaluation immediately by calling `length` so infinite collections // hang on `sorted`/`sortWith`/`sortBy` rather than on arbitrary method calls - def this(underlying: SomeSeqOps[A], ord: Ordering[B]) = this(underlying, underlying.length, ord) + def this(underlying: SomeSeqOps[A]^, ord: Ordering[B]) = this(underlying, underlying.length, ord) @SerialVersionUID(3L) private[this] class ReverseSorted extends SeqView[A] { @@ -150,10 +154,10 @@ object SeqView { override def knownSize: Int = len override def isEmpty: Boolean = len == 0 override def to[C1](factory: Factory[A, C1]): C1 = _reversed.to(factory) - override def reverse: SeqView[A] = outer - override protected def reversed: Iterable[A] = outer + override def reverse: SeqView[A]^{this} = outer + override protected def reversed: Iterable[A]^{outer} = outer - override def sorted[B1 >: A](implicit ord1: Ordering[B1]): SeqView[A] = + override def sorted[B1 >: A](implicit ord1: Ordering[B1]): SeqView[A]^{this} = if (ord1 == Sorted.this.ord) outer else if (ord1.isReverseOf(Sorted.this.ord)) this else new Sorted(elems, len, ord1) @@ -187,14 +191,14 @@ object SeqView { res } - private[this] def elems: SomeSeqOps[A] = { - val orig = underlying + private[this] def elems: SomeSeqOps[A]^{this} = { + val orig: SomeSeqOps[A]^{this} = underlying if (evaluated) _sorted else orig } def apply(i: Int): A = _sorted.apply(i) def length: Int = len - def iterator: Iterator[A] = Iterator.empty ++ _sorted.iterator // very lazy + def iterator: Iterator[A]^{this} = Iterator.empty ++ _sorted.iterator // very lazy override def knownSize: Int = len override def isEmpty: Boolean = len == 0 override def to[C1](factory: Factory[A, C1]): C1 = _sorted.to(factory) @@ -203,7 +207,7 @@ object SeqView { // so this is acceptable for `reversed` override protected def reversed: Iterable[A] = new ReverseSorted - override def sorted[B1 >: A](implicit ord1: Ordering[B1]): SeqView[A] = + override def sorted[B1 >: A](implicit ord1: Ordering[B1]): SeqView[A]^{this} = if (ord1 == this.ord) this else if (ord1.isReverseOf(this.ord)) reverse else new Sorted(elems, len, ord1) From 1165f3e2fd8982bf6b3e63380b5239b6f91f2400 Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Wed, 13 Aug 2025 23:06:44 +0200 Subject: [PATCH 063/101] Capture-check JavaConverters --- TODO.md | 2 +- library/src/scala/collection/JavaConverters.scala | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/TODO.md b/TODO.md index 54074a2d6d38..e959a407f46a 100644 --- a/TODO.md +++ b/TODO.md @@ -197,7 +197,7 @@ - [x] library/src/scala/collection/Iterable.scala - [x] library/src/scala/collection/IterableOnce.scala - [x] library/src/scala/collection/Iterator.scala -- [ ] library/src/scala/collection/JavaConverters.scala +- [x] library/src/scala/collection/JavaConverters.scala - [x] library/src/scala/collection/LazyZipOps.scala - [x] library/src/scala/collection/LinearSeq.scala - [x] library/src/scala/collection/Map.scala diff --git a/library/src/scala/collection/JavaConverters.scala b/library/src/scala/collection/JavaConverters.scala index b49e472c04b2..8d1503200188 100644 --- a/library/src/scala/collection/JavaConverters.scala +++ b/library/src/scala/collection/JavaConverters.scala @@ -13,6 +13,8 @@ package scala.collection import scala.language.`2.13` +import language.experimental.captureChecking + import java.util.{concurrent => juc} import java.{lang => jl, util => ju} From a01682f3c9dfea95a62d9db21e4418f43895a01e Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Thu, 14 Aug 2025 13:46:25 +0200 Subject: [PATCH 064/101] Add strict operations to StrictIndexedSeqOps --- library/src/scala/collection/IndexedSeq.scala | 8 ++++++++ library/src/scala/collection/mutable/IndexedSeq.scala | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/library/src/scala/collection/IndexedSeq.scala b/library/src/scala/collection/IndexedSeq.scala index 10cf37001d23..004cb5c76562 100644 --- a/library/src/scala/collection/IndexedSeq.scala +++ b/library/src/scala/collection/IndexedSeq.scala @@ -35,6 +35,12 @@ trait IndexedSeq[+A] extends Seq[A] object IndexedSeq extends SeqFactory.Delegate[IndexedSeq](immutable.IndexedSeq) transparent trait StrictIndexedSeqOps[+A, +CC[_] <: caps.Pure, +C] extends Any with StrictSeqOps[A, CC, C] with IndexedSeqOps[A, CC, C] { + override def map[B](f: A => B): CC[B] = iterableFactory.from(new IndexedSeqView.Map(this, f)) + override def sliding(size: Int, step: Int): Iterator[C] = { + require(size >= 1 && step >= 1, f"size=$size%d and step=$step%d, but both must be positive") + val it = new IndexedSeqSlidingIterator[A, CC, C](this, size, step) + it.asInstanceOf // TODO: seems like CC cannot figure this out yet + } } /** Base trait for indexed Seq operations */ @@ -161,6 +167,8 @@ transparent trait IndexedSeqOps[+A, +CC[_], +C] extends Any with SeqOps[A, CC, C /** A fast sliding iterator for IndexedSeqs which uses the underlying `slice` operation. */ private final class IndexedSeqSlidingIterator[A, CC[_], C](s: IndexedSeqOps[A, CC, C]^, size: Int, step: Int) extends AbstractIterator[C^{s}] { + // CC note: seems like the compiler cannot figure out that this class <: Iterator[C^{s}], + // so we need a cast when upcasting is needed. private[this] val len = s.length private[this] var pos = 0 diff --git a/library/src/scala/collection/mutable/IndexedSeq.scala b/library/src/scala/collection/mutable/IndexedSeq.scala index 9a7fdd506876..c965bb076844 100644 --- a/library/src/scala/collection/mutable/IndexedSeq.scala +++ b/library/src/scala/collection/mutable/IndexedSeq.scala @@ -27,7 +27,7 @@ trait IndexedSeq[T] extends Seq[T] object IndexedSeq extends SeqFactory.Delegate[IndexedSeq](ArrayBuffer) transparent trait IndexedSeqOps[A, +CC[_] <: caps.Pure, +C <: AnyRef] - extends scala.collection.IndexedSeqOps[A, CC, C] + extends scala.collection.StrictIndexedSeqOps[A, CC, C] with SeqOps[A, CC, C] { /** Modifies this $coll by applying a function to all elements of this $coll. From 778f86d2ac1b0a2834a8d5c4b9e571abc1e6ca1a Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Thu, 14 Aug 2025 13:47:25 +0200 Subject: [PATCH 065/101] Capture check immutable.Seq, ArraySeq --- TODO.md | 4 ++-- library/src/scala/collection/immutable/ArraySeq.scala | 10 ++++++---- library/src/scala/collection/immutable/Seq.scala | 9 +++++---- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/TODO.md b/TODO.md index e959a407f46a..e594f2323c5e 100644 --- a/TODO.md +++ b/TODO.md @@ -253,7 +253,7 @@ - [ ] library/src/scala/collection/generic/IsSeq.scala - [ ] library/src/scala/collection/generic/Subtractable.scala - [ ] library/src/scala/collection/generic/package.scala -- [ ] library/src/scala/collection/immutable/ArraySeq.scala +- [x] library/src/scala/collection/immutable/ArraySeq.scala - [x] library/src/scala/collection/immutable/BitSet.scala - [ ] library/src/scala/collection/immutable/ChampCommon.scala - [ ] library/src/scala/collection/immutable/HashMap.scala @@ -270,7 +270,7 @@ - [ ] library/src/scala/collection/immutable/Queue.scala - [ ] library/src/scala/collection/immutable/Range.scala - [ ] library/src/scala/collection/immutable/RedBlackTree.scala -- [ ] library/src/scala/collection/immutable/Seq.scala +- [x] library/src/scala/collection/immutable/Seq.scala - [ ] library/src/scala/collection/immutable/SeqMap.scala - [ ] library/src/scala/collection/immutable/Set.scala - [ ] library/src/scala/collection/immutable/SortedMap.scala diff --git a/library/src/scala/collection/immutable/ArraySeq.scala b/library/src/scala/collection/immutable/ArraySeq.scala index 9cd2700779fb..a6c13b6fdd95 100644 --- a/library/src/scala/collection/immutable/ArraySeq.scala +++ b/library/src/scala/collection/immutable/ArraySeq.scala @@ -14,6 +14,8 @@ package scala.collection package immutable import scala.language.`2.13` +import language.experimental.captureChecking + import java.util.Arrays import scala.annotation.unchecked.uncheckedVariance @@ -125,7 +127,7 @@ sealed abstract class ArraySeq[+A] } } - override def appendedAll[B >: A](suffix: collection.IterableOnce[B]): ArraySeq[B] = { + override def appendedAll[B >: A](suffix: collection.IterableOnce[B]^): ArraySeq[B] = { def genericResult = { val k = suffix.knownSize if (k == 0) this @@ -148,7 +150,7 @@ sealed abstract class ArraySeq[+A] } } - override def prependedAll[B >: A](prefix: collection.IterableOnce[B]): ArraySeq[B] = { + override def prependedAll[B >: A](prefix: collection.IterableOnce[B]^): ArraySeq[B] = { def genericResult = { val k = prefix.knownSize if (k == 0) this @@ -172,7 +174,7 @@ sealed abstract class ArraySeq[+A] } } - override def zip[B](that: collection.IterableOnce[B]): ArraySeq[(A, B)] = + override def zip[B](that: collection.IterableOnce[B]^): ArraySeq[(A, B)] = that match { case bs: ArraySeq[B] => ArraySeq.tabulate(length min bs.length) { i => @@ -278,7 +280,7 @@ object ArraySeq extends StrictOptimizedClassTagSeqFactory[ArraySeq] { self => def empty[A : ClassTag]: ArraySeq[A] = emptyImpl - def from[A](it: scala.collection.IterableOnce[A])(implicit tag: ClassTag[A]): ArraySeq[A] = it match { + def from[A](it: scala.collection.IterableOnce[A]^)(implicit tag: ClassTag[A]): ArraySeq[A] = it match { case as: ArraySeq[A] => as case _ => unsafeWrapArray(Array.from[A](it)) } diff --git a/library/src/scala/collection/immutable/Seq.scala b/library/src/scala/collection/immutable/Seq.scala index 10104a8fc05c..44eea79f54f3 100644 --- a/library/src/scala/collection/immutable/Seq.scala +++ b/library/src/scala/collection/immutable/Seq.scala @@ -15,6 +15,7 @@ package collection package immutable import scala.language.`2.13` +import language.experimental.captureChecking trait Seq[+A] extends Iterable[A] with collection.Seq[A] @@ -39,7 +40,7 @@ transparent trait SeqOps[+A, +CC[B] <: collection.StrictSeqOps[B, collection.Seq */ @SerialVersionUID(3L) object Seq extends SeqFactory.Delegate[Seq](List) { - override def from[E](it: IterableOnce[E]): Seq[E] = it match { + override def from[E](it: IterableOnce[E]^): Seq[E] = it match { case s: Seq[E] => s case _ => super.from(it) } @@ -59,7 +60,7 @@ trait IndexedSeq[+A] extends Seq[A] } - override def sameElements[B >: A](o: IterableOnce[B]): Boolean = o match { + override def sameElements[B >: A](o: IterableOnce[B]^): Boolean = o match { case that: IndexedSeq[_] => (this eq that) || { val length = this.length @@ -112,7 +113,7 @@ object IndexedSeqDefaults { @SerialVersionUID(3L) object IndexedSeq extends SeqFactory.Delegate[IndexedSeq](Vector) { - override def from[E](it: IterableOnce[E]): IndexedSeq[E] = it match { + override def from[E](it: IterableOnce[E]^): IndexedSeq[E] = it match { case is: IndexedSeq[E] => is case _ => super.from(it) } @@ -143,7 +144,7 @@ trait LinearSeq[+A] @SerialVersionUID(3L) object LinearSeq extends SeqFactory.Delegate[LinearSeq](List) { - override def from[E](it: IterableOnce[E]): LinearSeq[E] = it match { + override def from[E](it: IterableOnce[E]^): LinearSeq[E] = it match { case ls: LinearSeq[E] => ls case _ => super.from(it) } From 4f56818e9ef7ddd3a4ccdd0dd8d29ba9a36474bd Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Thu, 14 Aug 2025 13:51:58 +0200 Subject: [PATCH 066/101] Capture-check immutable.Iterable and ChampCommon --- TODO.md | 4 ++-- library/src/scala/collection/immutable/ChampCommon.scala | 2 ++ library/src/scala/collection/immutable/Iterable.scala | 4 +++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/TODO.md b/TODO.md index e594f2323c5e..6a6a52f3a283 100644 --- a/TODO.md +++ b/TODO.md @@ -255,11 +255,11 @@ - [ ] library/src/scala/collection/generic/package.scala - [x] library/src/scala/collection/immutable/ArraySeq.scala - [x] library/src/scala/collection/immutable/BitSet.scala -- [ ] library/src/scala/collection/immutable/ChampCommon.scala +- [x] library/src/scala/collection/immutable/ChampCommon.scala - [ ] library/src/scala/collection/immutable/HashMap.scala - [ ] library/src/scala/collection/immutable/HashSet.scala - [ ] library/src/scala/collection/immutable/IntMap.scala -- [ ] library/src/scala/collection/immutable/Iterable.scala +- [x] library/src/scala/collection/immutable/Iterable.scala - [ ] library/src/scala/collection/immutable/LazyList.scala - [ ] library/src/scala/collection/immutable/List.scala - [ ] library/src/scala/collection/immutable/ListMap.scala diff --git a/library/src/scala/collection/immutable/ChampCommon.scala b/library/src/scala/collection/immutable/ChampCommon.scala index 899525e822a6..86b212de72f9 100644 --- a/library/src/scala/collection/immutable/ChampCommon.scala +++ b/library/src/scala/collection/immutable/ChampCommon.scala @@ -13,6 +13,8 @@ package scala.collection.immutable import scala.language.`2.13` +import language.experimental.captureChecking + import scala.collection.AbstractIterator import java.lang.Integer.bitCount import java.lang.Math.ceil diff --git a/library/src/scala/collection/immutable/Iterable.scala b/library/src/scala/collection/immutable/Iterable.scala index 0691663e7e4d..65f95ad87a27 100644 --- a/library/src/scala/collection/immutable/Iterable.scala +++ b/library/src/scala/collection/immutable/Iterable.scala @@ -13,6 +13,8 @@ package scala.collection.immutable import scala.language.`2.13` +import language.experimental.captureChecking + import scala.collection.{IterableFactory, IterableFactoryDefaults} /** A trait for collections that are guaranteed immutable. @@ -31,7 +33,7 @@ trait Iterable[+A] extends collection.Iterable[A] @SerialVersionUID(3L) object Iterable extends IterableFactory.Delegate[Iterable](List) { - override def from[E](it: IterableOnce[E]): Iterable[E] = it match { + override def from[E](it: IterableOnce[E]^): Iterable[E]^{it} = it match { case iterable: Iterable[E] => iterable case _ => super.from(it) } From ef53024cd4602dc11296b5f6e1c6f6d9c5b24882 Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Thu, 14 Aug 2025 13:55:34 +0200 Subject: [PATCH 067/101] Capture-checking immutable.StrictOptimizedSeqOps --- TODO.md | 4 ++-- .../scala/collection/immutable/StrictOptimizedSeqOps.scala | 6 ++++-- library/src/scala/collection/immutable/package.scala | 1 + 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/TODO.md b/TODO.md index 6a6a52f3a283..22543a78def5 100644 --- a/TODO.md +++ b/TODO.md @@ -276,14 +276,14 @@ - [ ] library/src/scala/collection/immutable/SortedMap.scala - [ ] library/src/scala/collection/immutable/SortedSet.scala - [ ] library/src/scala/collection/immutable/Stream.scala -- [ ] library/src/scala/collection/immutable/StrictOptimizedSeqOps.scala +- [x] library/src/scala/collection/immutable/StrictOptimizedSeqOps.scala - [ ] library/src/scala/collection/immutable/TreeMap.scala - [ ] library/src/scala/collection/immutable/TreeSeqMap.scala - [ ] library/src/scala/collection/immutable/TreeSet.scala - [ ] library/src/scala/collection/immutable/Vector.scala - [ ] library/src/scala/collection/immutable/VectorMap.scala - [ ] library/src/scala/collection/immutable/WrappedString.scala -- [ ] library/src/scala/collection/immutable/package.scala +- [x] library/src/scala/collection/immutable/package.scala - [ ] library/src/scala/collection/mutable/AnyRefMap.scala - [ ] library/src/scala/collection/mutable/ArrayBuffer.scala - [ ] library/src/scala/collection/mutable/ArrayBuilder.scala diff --git a/library/src/scala/collection/immutable/StrictOptimizedSeqOps.scala b/library/src/scala/collection/immutable/StrictOptimizedSeqOps.scala index 20bd90477454..a3ca5d25b287 100644 --- a/library/src/scala/collection/immutable/StrictOptimizedSeqOps.scala +++ b/library/src/scala/collection/immutable/StrictOptimizedSeqOps.scala @@ -15,6 +15,8 @@ package collection package immutable import scala.language.`2.13` +import language.experimental.captureChecking + import scala.collection.generic.CommonErrors /** Trait that overrides operations to take advantage of strict builders. @@ -25,7 +27,7 @@ transparent trait StrictOptimizedSeqOps[+A, +CC[B] <: collection.StrictSeqOps[B, with collection.StrictOptimizedSeqOps[A, CC, C] with StrictOptimizedIterableOps[A, CC, C] { - override def distinctBy[B](f: A => B): C = { + override def distinctBy[B](f: A -> B): C = { if (lengthCompare(1) <= 0) coll else { val builder = newSpecificBuilder @@ -62,7 +64,7 @@ transparent trait StrictOptimizedSeqOps[+A, +CC[B] <: collection.StrictSeqOps[B, b.result() } - override def patch[B >: A](from: Int, other: IterableOnce[B], replaced: Int): CC[B] = { + override def patch[B >: A](from: Int, other: IterableOnce[B]^, replaced: Int): CC[B] = { val b = iterableFactory.newBuilder[B] var i = 0 val it = iterator diff --git a/library/src/scala/collection/immutable/package.scala b/library/src/scala/collection/immutable/package.scala index 061619cb8b86..5ddc1810d701 100644 --- a/library/src/scala/collection/immutable/package.scala +++ b/library/src/scala/collection/immutable/package.scala @@ -13,6 +13,7 @@ package scala.collection import scala.language.`2.13` +import language.experimental.captureChecking package object immutable { type StringOps = scala.collection.StringOps From 3a01da5763845c7e683bd95b38cc43a1e39501c0 Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Thu, 14 Aug 2025 14:05:09 +0200 Subject: [PATCH 068/101] Capture check immutable.Map and HashMap --- TODO.md | 4 ++-- .../scala/collection/immutable/HashMap.scala | 10 +++++---- .../src/scala/collection/immutable/Map.scala | 22 ++++++++++--------- 3 files changed, 20 insertions(+), 16 deletions(-) diff --git a/TODO.md b/TODO.md index 22543a78def5..27e0961f39be 100644 --- a/TODO.md +++ b/TODO.md @@ -256,7 +256,7 @@ - [x] library/src/scala/collection/immutable/ArraySeq.scala - [x] library/src/scala/collection/immutable/BitSet.scala - [x] library/src/scala/collection/immutable/ChampCommon.scala -- [ ] library/src/scala/collection/immutable/HashMap.scala +- [x] library/src/scala/collection/immutable/HashMap.scala - [ ] library/src/scala/collection/immutable/HashSet.scala - [ ] library/src/scala/collection/immutable/IntMap.scala - [x] library/src/scala/collection/immutable/Iterable.scala @@ -265,7 +265,7 @@ - [ ] library/src/scala/collection/immutable/ListMap.scala - [ ] library/src/scala/collection/immutable/ListSet.scala - [ ] library/src/scala/collection/immutable/LongMap.scala -- [ ] library/src/scala/collection/immutable/Map.scala +- [x] library/src/scala/collection/immutable/Map.scala - [ ] library/src/scala/collection/immutable/NumericRange.scala - [ ] library/src/scala/collection/immutable/Queue.scala - [ ] library/src/scala/collection/immutable/Range.scala diff --git a/library/src/scala/collection/immutable/HashMap.scala b/library/src/scala/collection/immutable/HashMap.scala index cb8f4318bcbe..b808b2e93e8a 100644 --- a/library/src/scala/collection/immutable/HashMap.scala +++ b/library/src/scala/collection/immutable/HashMap.scala @@ -14,6 +14,8 @@ package scala package collection.immutable import scala.language.`2.13` +import language.experimental.captureChecking + import java.lang.Integer.bitCount import java.lang.System.arraycopy @@ -162,7 +164,7 @@ final class HashMap[K, +V] private[immutable] (private[immutable] val rootNode: newHashMapOrThis(rootNode.removed(key, keyUnimprovedHash, improve(keyUnimprovedHash), 0)) } - override def concat[V1 >: V](that: scala.IterableOnce[(K, V1)]): HashMap[K, V1] = that match { + override def concat[V1 >: V](that: scala.IterableOnce[(K, V1)]^): HashMap[K, V1] = that match { case hm: HashMap[K, V1] => if (isEmpty) hm else { @@ -385,7 +387,7 @@ final class HashMap[K, +V] private[immutable] (private[immutable] val rootNode: else new HashMap(newRootNode) } - override def removedAll(keys: IterableOnce[K]): HashMap[K, V] = { + override def removedAll(keys: IterableOnce[K]^): HashMap[K, V] = { if (isEmpty) { this } else { @@ -2200,7 +2202,7 @@ object HashMap extends StrictMapFactory[HashMap] { def empty[K, V]: HashMap[K, V] = EmptyMap.asInstanceOf[HashMap[K, V]] - def from[K, V](source: collection.IterableOnce[(K, V)]): HashMap[K, V] = + def from[K, V](source: collection.IterableOnce[(K, V)]^): HashMap[K, V] = source match { case hs: HashMap[K, V] => hs case _ => (newBuilder[K, V] ++= source).result() @@ -2364,7 +2366,7 @@ private[immutable] final class HashMapBuilder[K, V] extends ReusableBuilder[(K, this } - override def addAll(xs: IterableOnce[(K, V)]): this.type = { + override def addAll(xs: IterableOnce[(K, V)]^): this.type = { ensureUnaliased() xs match { case hm: HashMap[K, V] => diff --git a/library/src/scala/collection/immutable/Map.scala b/library/src/scala/collection/immutable/Map.scala index 86b45a160d62..fe45a21e98d3 100644 --- a/library/src/scala/collection/immutable/Map.scala +++ b/library/src/scala/collection/immutable/Map.scala @@ -15,6 +15,8 @@ package collection package immutable import scala.language.`2.13` +import language.experimental.captureChecking + import scala.annotation.unchecked.uncheckedVariance import scala.collection.generic.DefaultSerializable import scala.collection.immutable.Map.Map4 @@ -41,7 +43,7 @@ trait Map[K, +V] * @param d the function mapping keys to values, used for non-present keys * @return a wrapper of the map with a default value */ - def withDefault[V1 >: V](d: K => V1): Map[K, V1] = new Map.WithDefault[K, V1](this, d) + def withDefault[V1 >: V](d: K -> V1): Map[K, V1] = new Map.WithDefault[K, V1](this, d) /** The same map with a given default value. * Note: The default is only used for `apply`. Other methods like `get`, `contains`, `iterator`, `keys`, etc. @@ -88,10 +90,10 @@ transparent trait MapOps[K, +V, +CC[X, +Y] <: MapOps[X, Y, CC, _], +C <: MapOps[ * @return a new $coll that contains all elements of the current $coll * except one less occurrence of each of the elements of `elems`. */ - def removedAll(keys: IterableOnce[K]): C = keys.iterator.foldLeft[C](coll)(_ - _) + def removedAll(keys: IterableOnce[K]^): C = keys.iterator.foldLeft[C](coll)(_ - _) /** Alias for `removedAll` */ - @`inline` final override def -- (keys: IterableOnce[K]): C = removedAll(keys) + @`inline` final override def -- (keys: IterableOnce[K]^): C = removedAll(keys) /** Creates a new map obtained by updating this map with a given key/value pair. * @param key the key @@ -155,7 +157,7 @@ transparent trait StrictOptimizedMapOps[K, +V, +CC[X, +Y] <: MapOps[X, Y, CC, _] with collection.StrictOptimizedMapOps[K, V, CC, C] with StrictOptimizedIterableOps[(K, V), Iterable, C] { - override def concat [V1 >: V](that: collection.IterableOnce[(K, V1)]): CC[K, V1] = { + override def concat [V1 >: V](that: collection.IterableOnce[(K, V1)]^): CC[K, V1] = { var result: CC[K, V1] = coll val it = that.iterator while (it.hasNext) result = result + it.next() @@ -173,7 +175,7 @@ transparent trait StrictOptimizedMapOps[K, +V, +CC[X, +Y] <: MapOps[X, Y, CC, _] object Map extends StrictMapFactory[Map] { @SerialVersionUID(3L) - class WithDefault[K, +V](val underlying: Map[K, V], val defaultValue: K => V) + class WithDefault[K, +V](val underlying: Map[K, V], val defaultValue: K -> V) extends AbstractMap[K, V] with MapOps[K, V, Map, WithDefault[K, V]] with Serializable { @@ -189,7 +191,7 @@ object Map extends StrictMapFactory[Map] { override def mapFactory: StrictMapFactory[Map] = underlying.mapFactory - override def concat [V2 >: V](xs: collection.IterableOnce[(K, V2)]): WithDefault[K, V2] = + override def concat [V2 >: V](xs: collection.IterableOnce[(K, V2)]^): WithDefault[K, V2] = new WithDefault(underlying.concat(xs), defaultValue) def removed(key: K): WithDefault[K, V] = new WithDefault[K, V](underlying.removed(key), defaultValue) @@ -199,7 +201,7 @@ object Map extends StrictMapFactory[Map] { override def empty: WithDefault[K, V] = new WithDefault[K, V](underlying.empty, defaultValue) - override protected def fromSpecific(coll: collection.IterableOnce[(K, V)] @uncheckedVariance): WithDefault[K, V] = + override protected def fromSpecific(coll: (collection.IterableOnce[(K, V)]^) @uncheckedVariance): WithDefault[K, V] = new WithDefault[K, V](mapFactory.from(coll), defaultValue) override protected def newSpecificBuilder: Builder[(K, V), WithDefault[K, V]] @uncheckedVariance = @@ -208,7 +210,7 @@ object Map extends StrictMapFactory[Map] { def empty[K, V]: Map[K, V] = EmptyMap.asInstanceOf[Map[K, V]] - def from[K, V](it: IterableOnce[(K, V)]): Map[K, V] = + def from[K, V](it: IterableOnce[(K, V)]^): Map[K, V] = it match { case it: Iterable[_] if it.isEmpty => empty[K, V] // Since IterableOnce[(K, V)] launders the variance of K, @@ -250,7 +252,7 @@ object Map extends StrictMapFactory[Map] { override def valuesIterator: Iterator[Nothing] = Iterator.empty def updated [V1] (key: Any, value: V1): Map[Any, V1] = new Map1(key, value) def removed(key: Any): Map[Any, Nothing] = this - override def concat[V2 >: Nothing](suffix: IterableOnce[(Any, V2)]): Map[Any, V2] = suffix match { + override def concat[V2 >: Nothing](suffix: IterableOnce[(Any, V2)]^): Map[Any, V2] = suffix match { case m: immutable.Map[Any, V2] => m case _ => super.concat(suffix) } @@ -703,7 +705,7 @@ private[immutable] final class MapBuilderImpl[K, V] extends ReusableBuilder[(K, def addOne(elem: (K, V)) = addOne(elem._1, elem._2) - override def addAll(xs: IterableOnce[(K, V)]): this.type = + override def addAll(xs: IterableOnce[(K, V)]^): this.type = if (switchedToHashMapBuilder) { hashMapBuilder.addAll(xs) this From 9d850c0a74e5778456ce9cb0cac245464963d802 Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Thu, 14 Aug 2025 14:29:15 +0200 Subject: [PATCH 069/101] Capture check immutable.Set and HashSet --- TODO.md | 4 ++-- library/src/scala/collection/immutable/HashSet.scala | 10 ++++++---- library/src/scala/collection/immutable/Set.scala | 12 +++++++----- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/TODO.md b/TODO.md index 27e0961f39be..3ba05a555bc0 100644 --- a/TODO.md +++ b/TODO.md @@ -257,7 +257,7 @@ - [x] library/src/scala/collection/immutable/BitSet.scala - [x] library/src/scala/collection/immutable/ChampCommon.scala - [x] library/src/scala/collection/immutable/HashMap.scala -- [ ] library/src/scala/collection/immutable/HashSet.scala +- [x] library/src/scala/collection/immutable/HashSet.scala - [ ] library/src/scala/collection/immutable/IntMap.scala - [x] library/src/scala/collection/immutable/Iterable.scala - [ ] library/src/scala/collection/immutable/LazyList.scala @@ -272,7 +272,7 @@ - [ ] library/src/scala/collection/immutable/RedBlackTree.scala - [x] library/src/scala/collection/immutable/Seq.scala - [ ] library/src/scala/collection/immutable/SeqMap.scala -- [ ] library/src/scala/collection/immutable/Set.scala +- [x] library/src/scala/collection/immutable/Set.scala - [ ] library/src/scala/collection/immutable/SortedMap.scala - [ ] library/src/scala/collection/immutable/SortedSet.scala - [ ] library/src/scala/collection/immutable/Stream.scala diff --git a/library/src/scala/collection/immutable/HashSet.scala b/library/src/scala/collection/immutable/HashSet.scala index 8ce8035fd015..e5d2ee0c857e 100644 --- a/library/src/scala/collection/immutable/HashSet.scala +++ b/library/src/scala/collection/immutable/HashSet.scala @@ -15,6 +15,8 @@ package collection package immutable import scala.language.`2.13` +import language.experimental.captureChecking + import java.lang.Integer.{bitCount, numberOfTrailingZeros} import java.lang.System.arraycopy @@ -92,7 +94,7 @@ final class HashSet[A] private[immutable](private[immutable] val rootNode: Bitma newHashSetOrThis(newRootNode) } - override def concat(that: IterableOnce[A]): HashSet[A] = + override def concat(that: IterableOnce[A]^): HashSet[A] = that match { case hs: HashSet[A] => if (isEmpty) hs @@ -277,7 +279,7 @@ final class HashSet[A] private[immutable](private[immutable] val rootNode: Bitma * * That is, this method is safe to call on published sets because it does not mutate `this` */ - private[this] def removedAllWithShallowMutations(that: IterableOnce[A]): HashSet[A] = { + private[this] def removedAllWithShallowMutations(that: IterableOnce[A]^): HashSet[A] = { val iter = that.iterator var curr = rootNode while (iter.hasNext) { @@ -306,7 +308,7 @@ final class HashSet[A] private[immutable](private[immutable] val rootNode: Bitma this } - override def removedAll(that: IterableOnce[A]): HashSet[A] = that match { + override def removedAll(that: IterableOnce[A]^): HashSet[A] = that match { case set: scala.collection.Set[A] => diff(set) case range: Range if range.length > size => filter { @@ -1937,7 +1939,7 @@ object HashSet extends IterableFactory[HashSet] { def empty[A]: HashSet[A] = EmptySet.asInstanceOf[HashSet[A]] - def from[A](source: collection.IterableOnce[A]): HashSet[A] = + def from[A](source: collection.IterableOnce[A]^): HashSet[A] = source match { case hs: HashSet[A] => hs case _ if source.knownSize == 0 => empty[A] diff --git a/library/src/scala/collection/immutable/Set.scala b/library/src/scala/collection/immutable/Set.scala index 08fd4a13e9cd..dc49c19f7432 100644 --- a/library/src/scala/collection/immutable/Set.scala +++ b/library/src/scala/collection/immutable/Set.scala @@ -15,6 +15,8 @@ package collection package immutable import scala.language.`2.13` +import language.experimental.captureChecking + import scala.collection.immutable.Set.Set4 import scala.collection.mutable.{Builder, ReusableBuilder} @@ -66,10 +68,10 @@ transparent trait SetOps[A, +CC[X], +C <: SetOps[A, CC, C]] * @param that the collection containing the elements to remove. * @return a new $coll with the given elements removed, omitting duplicates. */ - def removedAll(that: IterableOnce[A]): C = that.iterator.foldLeft[C](coll)(_ - _) + def removedAll(that: IterableOnce[A]^): C = that.iterator.foldLeft[C](coll)(_ - _) /** Alias for removedAll */ - override final def -- (that: IterableOnce[A]): C = removedAll(that) + override final def -- (that: IterableOnce[A]^): C = removedAll(that) } transparent trait StrictOptimizedSetOps[A, +CC[X], +C <: SetOps[A, CC, C]] @@ -77,7 +79,7 @@ transparent trait StrictOptimizedSetOps[A, +CC[X], +C <: SetOps[A, CC, C]] with collection.StrictOptimizedSetOps[A, CC, C] with StrictOptimizedIterableOps[A, CC, C] { - override def concat(that: collection.IterableOnce[A]): C = { + override def concat(that: collection.IterableOnce[A]^): C = { var result: C = coll val it = that.iterator while (it.hasNext) result = result + it.next() @@ -95,7 +97,7 @@ object Set extends IterableFactory[Set] { def empty[A]: Set[A] = EmptySet.asInstanceOf[Set[A]] - def from[E](it: collection.IterableOnce[E]): Set[E] = + def from[E](it: collection.IterableOnce[E]^): Set[E] = it match { case _ if it.knownSize == 0 => empty[E] // Since IterableOnce[E] launders the variance of E, @@ -124,7 +126,7 @@ object Set extends IterableFactory[Set] { override def knownSize: Int = size override def filter(pred: Any => Boolean): Set[Any] = this override def filterNot(pred: Any => Boolean): Set[Any] = this - override def removedAll(that: IterableOnce[Any]): Set[Any] = this + override def removedAll(that: IterableOnce[Any]^): Set[Any] = this override def diff(that: collection.Set[Any]): Set[Any] = this override def subsetOf(that: collection.Set[Any]): Boolean = true override def intersect(that: collection.Set[Any]): Set[Any] = this From 3a0999e8e0bb8fe906d9a2eb9ee71d39183c5c99 Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Thu, 14 Aug 2025 14:38:08 +0200 Subject: [PATCH 070/101] Capture-check IntMap and LongMap --- TODO.md | 4 ++-- .../scala/collection/immutable/IntMap.scala | 18 ++++++++++-------- .../scala/collection/immutable/LongMap.scala | 16 +++++++++------- 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/TODO.md b/TODO.md index 3ba05a555bc0..4291e537ea2e 100644 --- a/TODO.md +++ b/TODO.md @@ -258,13 +258,13 @@ - [x] library/src/scala/collection/immutable/ChampCommon.scala - [x] library/src/scala/collection/immutable/HashMap.scala - [x] library/src/scala/collection/immutable/HashSet.scala -- [ ] library/src/scala/collection/immutable/IntMap.scala +- [x] library/src/scala/collection/immutable/IntMap.scala - [x] library/src/scala/collection/immutable/Iterable.scala - [ ] library/src/scala/collection/immutable/LazyList.scala - [ ] library/src/scala/collection/immutable/List.scala - [ ] library/src/scala/collection/immutable/ListMap.scala - [ ] library/src/scala/collection/immutable/ListSet.scala -- [ ] library/src/scala/collection/immutable/LongMap.scala +- [x] library/src/scala/collection/immutable/LongMap.scala - [x] library/src/scala/collection/immutable/Map.scala - [ ] library/src/scala/collection/immutable/NumericRange.scala - [ ] library/src/scala/collection/immutable/Queue.scala diff --git a/library/src/scala/collection/immutable/IntMap.scala b/library/src/scala/collection/immutable/IntMap.scala index 1728da6c5710..6705091640b8 100644 --- a/library/src/scala/collection/immutable/IntMap.scala +++ b/library/src/scala/collection/immutable/IntMap.scala @@ -14,6 +14,8 @@ package scala.collection package immutable import scala.language.`2.13` +import language.experimental.captureChecking + import scala.collection.generic.{BitOperations, DefaultSerializationProxy} import scala.collection.mutable.{Builder, ImmutableBuilder} import scala.annotation.tailrec @@ -53,7 +55,7 @@ object IntMap { def apply[T](elems: (Int, T)*): IntMap[T] = elems.foldLeft(empty[T])((x, y) => x.updated(y._1, y._2)) - def from[V](coll: IterableOnce[(Int, V)]): IntMap[V] = + def from[V](coll: IterableOnce[(Int, V)]^): IntMap[V] = newBuilder[V].addAll(coll).result() private[immutable] case object Nil extends IntMap[Nothing] { @@ -90,13 +92,13 @@ object IntMap { @SerialVersionUID(3L) private[this] object ToFactory extends Factory[(Int, AnyRef), IntMap[AnyRef]] with Serializable { - def fromSpecific(it: IterableOnce[(Int, AnyRef)]): IntMap[AnyRef] = IntMap.from[AnyRef](it) + def fromSpecific(it: IterableOnce[(Int, AnyRef)]^): IntMap[AnyRef] = IntMap.from[AnyRef](it) def newBuilder: Builder[(Int, AnyRef), IntMap[AnyRef]] = IntMap.newBuilder[AnyRef] } implicit def toBuildFrom[V](factory: IntMap.type): BuildFrom[Any, (Int, V), IntMap[V]] = ToBuildFrom.asInstanceOf[BuildFrom[Any, (Int, V), IntMap[V]]] private[this] object ToBuildFrom extends BuildFrom[Any, (Int, AnyRef), IntMap[AnyRef]] { - def fromSpecific(from: Any)(it: IterableOnce[(Int, AnyRef)]) = IntMap.from(it) + def fromSpecific(from: Any)(it: IterableOnce[(Int, AnyRef)]^) = IntMap.from(it) def newBuilder(from: Any) = IntMap.newBuilder[AnyRef] } @@ -181,9 +183,9 @@ sealed abstract class IntMap[+T] extends AbstractMap[Int, T] with StrictOptimizedMapOps[Int, T, Map, IntMap[T]] with Serializable { - override protected def fromSpecific(coll: scala.collection.IterableOnce[(Int, T) @uncheckedVariance]): IntMap[T] = + override protected def fromSpecific(coll: scala.collection.IterableOnce[(Int, T) @uncheckedVariance]^): IntMap[T] = intMapFrom[T](coll) - protected def intMapFrom[V2](coll: scala.collection.IterableOnce[(Int, V2)]): IntMap[V2] = { + protected def intMapFrom[V2](coll: scala.collection.IterableOnce[(Int, V2)]^): IntMap[V2] = { val b = IntMap.newBuilder[V2] b.sizeHint(coll) b.addAll(coll) @@ -326,12 +328,12 @@ sealed abstract class IntMap[+T] extends AbstractMap[Int, T] def map[V2](f: ((Int, T)) => (Int, V2)): IntMap[V2] = intMapFrom(new View.Map(this, f)) - def flatMap[V2](f: ((Int, T)) => IterableOnce[(Int, V2)]): IntMap[V2] = intMapFrom(new View.FlatMap(this, f)) + def flatMap[V2](f: ((Int, T)) => IterableOnce[(Int, V2)]^): IntMap[V2] = intMapFrom(new View.FlatMap(this, f)) - override def concat[V1 >: T](that: collection.IterableOnce[(Int, V1)]): IntMap[V1] = + override def concat[V1 >: T](that: collection.IterableOnce[(Int, V1)]^): IntMap[V1] = super.concat(that).asInstanceOf[IntMap[V1]] // Already has correct type but not declared as such - override def ++ [V1 >: T](that: collection.IterableOnce[(Int, V1)]): IntMap[V1] = concat(that) + override def ++ [V1 >: T](that: collection.IterableOnce[(Int, V1)]^): IntMap[V1] = concat(that) def collect[V2](pf: PartialFunction[(Int, T), (Int, V2)]): IntMap[V2] = strictOptimizedCollect(IntMap.newBuilder[V2], pf) diff --git a/library/src/scala/collection/immutable/LongMap.scala b/library/src/scala/collection/immutable/LongMap.scala index 9832b4a7d55c..fab52e1d1db2 100644 --- a/library/src/scala/collection/immutable/LongMap.scala +++ b/library/src/scala/collection/immutable/LongMap.scala @@ -14,6 +14,8 @@ package scala.collection package immutable import scala.language.`2.13` +import language.experimental.captureChecking + import java.lang.IllegalStateException import scala.collection.generic.{BitOperations, DefaultSerializationProxy} @@ -53,7 +55,7 @@ object LongMap { def apply[T](elems: (Long, T)*): LongMap[T] = elems.foldLeft(empty[T])((x, y) => x.updated(y._1, y._2)) - def from[V](coll: IterableOnce[(Long, V)]): LongMap[V] = + def from[V](coll: IterableOnce[(Long, V)]^): LongMap[V] = newBuilder[V].addAll(coll).result() def newBuilder[V]: Builder[(Long, V), LongMap[V]] = @@ -87,13 +89,13 @@ object LongMap { @SerialVersionUID(3L) private[this] object ToFactory extends Factory[(Long, AnyRef), LongMap[AnyRef]] with Serializable { - def fromSpecific(it: IterableOnce[(Long, AnyRef)]): LongMap[AnyRef] = LongMap.from[AnyRef](it) + def fromSpecific(it: IterableOnce[(Long, AnyRef)]^): LongMap[AnyRef] = LongMap.from[AnyRef](it) def newBuilder: Builder[(Long, AnyRef), LongMap[AnyRef]] = LongMap.newBuilder[AnyRef] } implicit def toBuildFrom[V](factory: LongMap.type): BuildFrom[Any, (Long, V), LongMap[V]] = ToBuildFrom.asInstanceOf[BuildFrom[Any, (Long, V), LongMap[V]]] private[this] object ToBuildFrom extends BuildFrom[Any, (Long, AnyRef), LongMap[AnyRef]] { - def fromSpecific(from: Any)(it: IterableOnce[(Long, AnyRef)]) = LongMap.from(it) + def fromSpecific(from: Any)(it: IterableOnce[(Long, AnyRef)]^) = LongMap.from(it) def newBuilder(from: Any) = LongMap.newBuilder[AnyRef] } @@ -177,7 +179,7 @@ sealed abstract class LongMap[+T] extends AbstractMap[Long, T] with StrictOptimizedMapOps[Long, T, Map, LongMap[T]] with Serializable { - override protected def fromSpecific(coll: scala.collection.IterableOnce[(Long, T)] @uncheckedVariance): LongMap[T] = { + override protected def fromSpecific(coll: (scala.collection.IterableOnce[(Long, T)]^) @uncheckedVariance): LongMap[T] = { //TODO should this be the default implementation of this method in StrictOptimizedIterableOps? val b = newSpecificBuilder b.sizeHint(coll) @@ -477,12 +479,12 @@ sealed abstract class LongMap[+T] extends AbstractMap[Long, T] def map[V2](f: ((Long, T)) => (Long, V2)): LongMap[V2] = LongMap.from(new View.Map(coll, f)) - def flatMap[V2](f: ((Long, T)) => IterableOnce[(Long, V2)]): LongMap[V2] = LongMap.from(new View.FlatMap(coll, f)) + def flatMap[V2](f: ((Long, T)) => IterableOnce[(Long, V2)]^): LongMap[V2] = LongMap.from(new View.FlatMap(coll, f)) - override def concat[V1 >: T](that: scala.collection.IterableOnce[(Long, V1)]): LongMap[V1] = + override def concat[V1 >: T](that: scala.collection.IterableOnce[(Long, V1)]^): LongMap[V1] = super.concat(that).asInstanceOf[LongMap[V1]] // Already has correct type but not declared as such - override def ++ [V1 >: T](that: scala.collection.IterableOnce[(Long, V1)]): LongMap[V1] = concat(that) + override def ++ [V1 >: T](that: scala.collection.IterableOnce[(Long, V1)]^): LongMap[V1] = concat(that) def collect[V2](pf: PartialFunction[(Long, T), (Long, V2)]): LongMap[V2] = strictOptimizedCollect(LongMap.newBuilder[V2], pf) From e31c1a2f55ed67c3f7d7e3ee10a07faf61093849 Mon Sep 17 00:00:00 2001 From: Yichen Xu Date: Thu, 14 Aug 2025 15:33:44 +0200 Subject: [PATCH 071/101] Capture-check mutable maps --- .../scala/collection/mutable/AnyRefMap.scala | 23 ++++++++++--------- .../mutable/CollisionProofHashMap.scala | 11 +++++---- .../scala/collection/mutable/HashMap.scala | 7 +++--- .../scala/collection/mutable/Iterable.scala | 1 + .../collection/mutable/LinkedHashMap.scala | 3 ++- .../scala/collection/mutable/ListMap.scala | 3 ++- .../scala/collection/mutable/LongMap.scala | 23 ++++++++++--------- .../src/scala/collection/mutable/Map.scala | 10 ++++---- .../scala/collection/mutable/MultiMap.scala | 1 + .../collection/mutable/OpenHashMap.scala | 3 ++- .../collection/mutable/RedBlackTree.scala | 1 + .../src/scala/collection/mutable/SeqMap.scala | 1 + .../scala/collection/mutable/SortedMap.scala | 9 ++++---- .../scala/collection/mutable/TreeMap.scala | 4 +++- .../collection/mutable/WeakHashMap.scala | 3 ++- 15 files changed, 60 insertions(+), 43 deletions(-) diff --git a/library/src/scala/collection/mutable/AnyRefMap.scala b/library/src/scala/collection/mutable/AnyRefMap.scala index 3caccdbc194d..dc940a56051a 100644 --- a/library/src/scala/collection/mutable/AnyRefMap.scala +++ b/library/src/scala/collection/mutable/AnyRefMap.scala @@ -15,6 +15,7 @@ package collection package mutable import scala.language.`2.13` +import language.experimental.captureChecking import scala.annotation.meta.companionClass import scala.annotation.nowarn import scala.collection.generic.DefaultSerializationProxy @@ -44,7 +45,7 @@ import scala.language.implicitConversions * */ @(deprecated @companionClass)("Use `scala.collection.mutable.HashMap` instead for better performance.", since = "2.13.16") -class AnyRefMap[K <: AnyRef, V] private[collection] (defaultEntry: K => V, initialBufferSize: Int, initBlank: Boolean) +class AnyRefMap[K <: AnyRef, V] private[collection] (defaultEntry: K -> V, initialBufferSize: Int, initBlank: Boolean) extends AbstractMap[K, V] with MapOps[K, V, Map, AnyRefMap[K, V]] with StrictOptimizedIterableOps[(K, V), Iterable, AnyRefMap[K, V]] @@ -54,7 +55,7 @@ class AnyRefMap[K <: AnyRef, V] private[collection] (defaultEntry: K => V, initi def this() = this(AnyRefMap.exceptionDefault, 16, initBlank = true) /** Creates a new `AnyRefMap` that returns default values according to a supplied key-value mapping. */ - def this(defaultEntry: K => V) = this(defaultEntry, 16, initBlank = true) + def this(defaultEntry: K -> V) = this(defaultEntry, 16, initBlank = true) /** Creates a new `AnyRefMap` with an initial buffer of specified size. * @@ -64,7 +65,7 @@ class AnyRefMap[K <: AnyRef, V] private[collection] (defaultEntry: K => V, initi def this(initialBufferSize: Int) = this(AnyRefMap.exceptionDefault, initialBufferSize, initBlank = true) /** Creates a new `AnyRefMap` with specified default values and initial buffer size. */ - def this(defaultEntry: K => V, initialBufferSize: Int) = this(defaultEntry, initialBufferSize, initBlank = true) + def this(defaultEntry: K -> V, initialBufferSize: Int) = this(defaultEntry, initialBufferSize, initBlank = true) private[this] var mask = 0 private[this] var _size = 0 @@ -90,7 +91,7 @@ class AnyRefMap[K <: AnyRef, V] private[collection] (defaultEntry: K => V, initi mask = m; _size = sz; _vacant = vc; _hashes = hz; _keys = kz; _values = vz } - override protected def fromSpecific(coll: scala.collection.IterableOnce[(K, V)]): AnyRefMap[K,V] = { + override protected def fromSpecific(coll: scala.collection.IterableOnce[(K, V)]^): AnyRefMap[K,V] = { var sz = coll.knownSize if(sz < 0) sz = 4 val arm = new AnyRefMap[K, V](sz * 2) @@ -408,13 +409,13 @@ class AnyRefMap[K <: AnyRef, V] private[collection] (defaultEntry: K => V, initi if(elems.isEmpty) m else m.concat(elems) } - override def concat[V2 >: V](xs: scala.collection.IterableOnce[(K, V2)]): AnyRefMap[K, V2] = { + override def concat[V2 >: V](xs: scala.collection.IterableOnce[(K, V2)]^): AnyRefMap[K, V2] = { val arm = clone().asInstanceOf[AnyRefMap[K, V2]] xs.iterator.foreach(kv => arm += kv) arm } - override def ++[V2 >: V](xs: scala.collection.IterableOnce[(K, V2)]): AnyRefMap[K, V2] = concat(xs) + override def ++[V2 >: V](xs: scala.collection.IterableOnce[(K, V2)]^): AnyRefMap[K, V2] = concat(xs) @deprecated("Use m.clone().addOne(k,v) instead of m.updated(k, v)", "2.13.0") override def updated[V1 >: V](key: K, value: V1): AnyRefMap[K, V1] = @@ -556,7 +557,7 @@ object AnyRefMap { def newBuilder[K <: AnyRef, V]: ReusableBuilder[(K, V), AnyRefMap[K, V]] = new AnyRefMapBuilder[K, V] - private def buildFromIterableOnce[K <: AnyRef, V](elems: IterableOnce[(K, V)]): AnyRefMap[K, V] = { + private def buildFromIterableOnce[K <: AnyRef, V](elems: IterableOnce[(K, V)]^): AnyRefMap[K, V] = { var sz = elems.knownSize if(sz < 0) sz = 4 val arm = new AnyRefMap[K, V](sz * 2) @@ -569,7 +570,7 @@ object AnyRefMap { def empty[K <: AnyRef, V]: AnyRefMap[K, V] = new AnyRefMap[K, V] /** Creates a new empty `AnyRefMap` with the supplied default */ - def withDefault[K <: AnyRef, V](default: K => V): AnyRefMap[K, V] = new AnyRefMap[K, V](default) + def withDefault[K <: AnyRef, V](default: K -> V): AnyRefMap[K, V] = new AnyRefMap[K, V](default) /** Creates a new `AnyRefMap` from an existing source collection. A source collection * which is already an `AnyRefMap` gets cloned. @@ -579,7 +580,7 @@ object AnyRefMap { * @tparam V the type of the values * @return a new `AnyRefMap` with the elements of `source` */ - def from[K <: AnyRef, V](source: IterableOnce[(K, V)]): AnyRefMap[K, V] = source match { + def from[K <: AnyRef, V](source: IterableOnce[(K, V)]^): AnyRefMap[K, V] = source match { case source: AnyRefMap[_, _] => source.clone().asInstanceOf[AnyRefMap[K, V]] case _ => buildFromIterableOnce(source) } @@ -613,13 +614,13 @@ object AnyRefMap { @SerialVersionUID(3L) private[this] object ToFactory extends Factory[(AnyRef, AnyRef), AnyRefMap[AnyRef, AnyRef]] with Serializable { - def fromSpecific(it: IterableOnce[(AnyRef, AnyRef)]): AnyRefMap[AnyRef, AnyRef] = AnyRefMap.from[AnyRef, AnyRef](it) + def fromSpecific(it: IterableOnce[(AnyRef, AnyRef)]^): AnyRefMap[AnyRef, AnyRef] = AnyRefMap.from[AnyRef, AnyRef](it) def newBuilder: Builder[(AnyRef, AnyRef), AnyRefMap[AnyRef, AnyRef]] = AnyRefMap.newBuilder[AnyRef, AnyRef] } implicit def toBuildFrom[K <: AnyRef, V](factory: AnyRefMap.type): BuildFrom[Any, (K, V), AnyRefMap[K, V]] = ToBuildFrom.asInstanceOf[BuildFrom[Any, (K, V), AnyRefMap[K, V]]] private[this] object ToBuildFrom extends BuildFrom[Any, (AnyRef, AnyRef), AnyRefMap[AnyRef, AnyRef]] { - def fromSpecific(from: Any)(it: IterableOnce[(AnyRef, AnyRef)]): AnyRefMap[AnyRef, AnyRef] = AnyRefMap.from(it) + def fromSpecific(from: Any)(it: IterableOnce[(AnyRef, AnyRef)]^): AnyRefMap[AnyRef, AnyRef] = AnyRefMap.from(it) def newBuilder(from: Any): ReusableBuilder[(AnyRef, AnyRef), AnyRefMap[AnyRef, AnyRef]] = AnyRefMap.newBuilder[AnyRef, AnyRef] } diff --git a/library/src/scala/collection/mutable/CollisionProofHashMap.scala b/library/src/scala/collection/mutable/CollisionProofHashMap.scala index 18d469c42900..9413af8f9569 100644 --- a/library/src/scala/collection/mutable/CollisionProofHashMap.scala +++ b/library/src/scala/collection/mutable/CollisionProofHashMap.scala @@ -14,6 +14,7 @@ package scala.collection package mutable import scala.language.`2.13` +import language.experimental.captureChecking import scala.{unchecked => uc} import scala.annotation.{implicitNotFound, tailrec, unused} import scala.annotation.unchecked.uncheckedVariance @@ -64,7 +65,7 @@ final class CollisionProofHashMap[K, V](initialCapacity: Int, loadFactor: Double @`inline` private[this] final def index(hash: Int) = hash & (table.length - 1) - override protected def fromSpecific(coll: IterableOnce[(K, V)] @uncheckedVariance): CollisionProofHashMap[K, V] @uncheckedVariance = CollisionProofHashMap.from(coll) + override protected def fromSpecific(coll: (IterableOnce[(K, V)] @uncheckedVariance)^): CollisionProofHashMap[K, V] @uncheckedVariance = CollisionProofHashMap.from(coll) override protected def newSpecificBuilder: Builder[(K, V), CollisionProofHashMap[K, V]] @uncheckedVariance = CollisionProofHashMap.newBuilder[K, V] override def empty: CollisionProofHashMap[K, V] = new CollisionProofHashMap[K, V] @@ -442,13 +443,13 @@ final class CollisionProofHashMap[K, V](initialCapacity: Int, loadFactor: Double (implicit @implicitNotFound(CollisionProofHashMap.ordMsg) ordering: Ordering[K2]): CollisionProofHashMap[K2, V2] = sortedMapFactory.from(new View.Collect(this, pf)) - override def concat[V2 >: V](suffix: IterableOnce[(K, V2)]): CollisionProofHashMap[K, V2] = sortedMapFactory.from(suffix match { + override def concat[V2 >: V](suffix: IterableOnce[(K, V2)]^): CollisionProofHashMap[K, V2] = sortedMapFactory.from(suffix match { case it: Iterable[(K, V2)] => new View.Concat(this, it) case _ => iterator.concat(suffix.iterator) }) /** Alias for `concat` */ - @`inline` override final def ++ [V2 >: V](xs: IterableOnce[(K, V2)]): CollisionProofHashMap[K, V2] = concat(xs) + @`inline` override final def ++ [V2 >: V](xs: IterableOnce[(K, V2)]^): CollisionProofHashMap[K, V2] = concat(xs) @deprecated("Consider requiring an immutable Map or fall back to Map.concat", "2.13.0") override def + [V1 >: V](kv: (K, V1)): CollisionProofHashMap[K, V1] = @@ -743,7 +744,7 @@ final class CollisionProofHashMap[K, V](initialCapacity: Int, loadFactor: Double object CollisionProofHashMap extends SortedMapFactory[CollisionProofHashMap] { private[collection] final val ordMsg = "No implicit Ordering[${K2}] found to build a CollisionProofHashMap[${K2}, ${V2}]. You may want to upcast to a Map[${K}, ${V}] first by calling `unsorted`." - def from[K : Ordering, V](it: scala.collection.IterableOnce[(K, V)]): CollisionProofHashMap[K, V] = { + def from[K : Ordering, V](it: scala.collection.IterableOnce[(K, V)]^): CollisionProofHashMap[K, V] = { val k = it.knownSize val cap = if(k > 0) ((k + 1).toDouble / defaultLoadFactor).toInt else defaultInitialCapacity new CollisionProofHashMap[K, V](cap, defaultLoadFactor) ++= it @@ -766,7 +767,7 @@ object CollisionProofHashMap extends SortedMapFactory[CollisionProofHashMap] { @SerialVersionUID(3L) private final class DeserializationFactory[K, V](val tableLength: Int, val loadFactor: Double, val ordering: Ordering[K]) extends Factory[(K, V), CollisionProofHashMap[K, V]] with Serializable { - def fromSpecific(it: IterableOnce[(K, V)]): CollisionProofHashMap[K, V] = new CollisionProofHashMap[K, V](tableLength, loadFactor)(ordering) ++= it + def fromSpecific(it: IterableOnce[(K, V)]^): CollisionProofHashMap[K, V] = new CollisionProofHashMap[K, V](tableLength, loadFactor)(ordering) ++= it def newBuilder: Builder[(K, V), CollisionProofHashMap[K, V]] = CollisionProofHashMap.newBuilder(tableLength, loadFactor)(using ordering) } diff --git a/library/src/scala/collection/mutable/HashMap.scala b/library/src/scala/collection/mutable/HashMap.scala index 7459b550a12a..c4da374a7236 100644 --- a/library/src/scala/collection/mutable/HashMap.scala +++ b/library/src/scala/collection/mutable/HashMap.scala @@ -14,6 +14,7 @@ package scala.collection package mutable import scala.language.`2.13` +import language.experimental.captureChecking import scala.annotation.{nowarn, tailrec} import scala.collection.Stepper.EfficientSplit import scala.collection.generic.DefaultSerializationProxy @@ -95,7 +96,7 @@ class HashMap[K, V](initialCapacity: Int, loadFactor: Double) if(target > table.length) growTable(target) } - override def addAll(xs: IterableOnce[(K, V)]): this.type = { + override def addAll(xs: IterableOnce[(K, V)]^): this.type = { sizeHint(xs) xs match { @@ -599,7 +600,7 @@ object HashMap extends StrictMapFactory[HashMap] { def empty[K, V]: HashMap[K, V] = new HashMap[K, V] - def from[K, V](it: collection.IterableOnce[(K, V)]): HashMap[K, V] = { + def from[K, V](it: collection.IterableOnce[(K, V)]^): HashMap[K, V] = { val k = it.knownSize val cap = if(k > 0) ((k + 1).toDouble / defaultLoadFactor).toInt else defaultInitialCapacity new HashMap[K, V](cap, defaultLoadFactor).addAll(it) @@ -620,7 +621,7 @@ object HashMap extends StrictMapFactory[HashMap] { @SerialVersionUID(3L) private final class DeserializationFactory[K, V](val tableLength: Int, val loadFactor: Double) extends Factory[(K, V), HashMap[K, V]] with Serializable { - def fromSpecific(it: IterableOnce[(K, V)]): HashMap[K, V] = new HashMap[K, V](tableLength, loadFactor).addAll(it) + def fromSpecific(it: IterableOnce[(K, V)]^): HashMap[K, V] = new HashMap[K, V](tableLength, loadFactor).addAll(it) def newBuilder: Builder[(K, V), HashMap[K, V]] = HashMap.newBuilder(tableLength, loadFactor) } diff --git a/library/src/scala/collection/mutable/Iterable.scala b/library/src/scala/collection/mutable/Iterable.scala index 7f018b00c17f..14de48a768b5 100644 --- a/library/src/scala/collection/mutable/Iterable.scala +++ b/library/src/scala/collection/mutable/Iterable.scala @@ -13,6 +13,7 @@ package scala.collection.mutable import scala.language.`2.13` +import language.experimental.captureChecking import scala.collection.{IterableFactory, IterableFactoryDefaults} trait Iterable[A] diff --git a/library/src/scala/collection/mutable/LinkedHashMap.scala b/library/src/scala/collection/mutable/LinkedHashMap.scala index 99cbaafaf34a..0b5ebedf9a23 100644 --- a/library/src/scala/collection/mutable/LinkedHashMap.scala +++ b/library/src/scala/collection/mutable/LinkedHashMap.scala @@ -15,6 +15,7 @@ package collection package mutable import scala.language.`2.13` +import language.experimental.captureChecking import scala.annotation.{nowarn, tailrec} import scala.collection.generic.DefaultSerializable import scala.util.hashing.MurmurHash3 @@ -479,7 +480,7 @@ object LinkedHashMap extends StrictMapFactory[LinkedHashMap] { def empty[K, V] = new LinkedHashMap[K, V] - def from[K, V](it: collection.IterableOnce[(K, V)]) = { + def from[K, V](it: collection.IterableOnce[(K, V)]^) = { val newlhm = empty[K, V] newlhm.sizeHint(it, delta = 0) newlhm.addAll(it) diff --git a/library/src/scala/collection/mutable/ListMap.scala b/library/src/scala/collection/mutable/ListMap.scala index 6337549e313f..494eebdadd73 100644 --- a/library/src/scala/collection/mutable/ListMap.scala +++ b/library/src/scala/collection/mutable/ListMap.scala @@ -14,6 +14,7 @@ package scala.collection package mutable import scala.language.`2.13` +import language.experimental.captureChecking import scala.annotation.tailrec import scala.collection.generic.DefaultSerializable import scala.collection.immutable.List @@ -78,6 +79,6 @@ class ListMap[K, V] @deprecated("Use an immutable.ListMap assigned to a var instead of mutable.ListMap", "2.13.0") object ListMap extends StrictMapFactory[ListMap] { def empty[K, V]: ListMap[K, V] = new ListMap[K, V] - def from[K, V](it: IterableOnce[(K, V)]): ListMap[K,V] = Growable.from(empty[K, V], it) + def from[K, V](it: IterableOnce[(K, V)]^): ListMap[K,V] = Growable.from(empty[K, V], it) def newBuilder[K, V]: Builder[(K, V), ListMap[K,V]] = new GrowableBuilder(empty[K, V]) } diff --git a/library/src/scala/collection/mutable/LongMap.scala b/library/src/scala/collection/mutable/LongMap.scala index a56d874a5fc2..5df80d912440 100644 --- a/library/src/scala/collection/mutable/LongMap.scala +++ b/library/src/scala/collection/mutable/LongMap.scala @@ -14,6 +14,7 @@ package scala.collection package mutable import scala.language.`2.13` +import language.experimental.captureChecking import scala.collection.generic.DefaultSerializationProxy import scala.language.implicitConversions @@ -37,7 +38,7 @@ import scala.language.implicitConversions * rapidly as 2^30 is approached. * */ -final class LongMap[V] private[collection] (defaultEntry: Long => V, initialBufferSize: Int, initBlank: Boolean) +final class LongMap[V] private[collection] (defaultEntry: Long -> V, initialBufferSize: Int, initBlank: Boolean) extends AbstractMap[Long, V] with MapOps[Long, V, Map, LongMap[V]] with StrictOptimizedIterableOps[(Long, V), Iterable, LongMap[V]] @@ -47,7 +48,7 @@ final class LongMap[V] private[collection] (defaultEntry: Long => V, initialBuff def this() = this(LongMap.exceptionDefault, 16, initBlank = true) // TODO: override clear() with an optimization more tailored for efficiency. - override protected def fromSpecific(coll: scala.collection.IterableOnce[(Long, V)]): LongMap[V] = { + override protected def fromSpecific(coll: scala.collection.IterableOnce[(Long, V)]^): LongMap[V] = { //TODO should this be the default implementation of this method in StrictOptimizedIterableOps? val b = newSpecificBuilder b.sizeHint(coll) @@ -57,7 +58,7 @@ final class LongMap[V] private[collection] (defaultEntry: Long => V, initialBuff override protected def newSpecificBuilder: Builder[(Long, V),LongMap[V]] = new GrowableBuilder(LongMap.empty[V]) /** Creates a new `LongMap` that returns default values according to a supplied key-value mapping. */ - def this(defaultEntry: Long => V) = this(defaultEntry, 16, initBlank = true) + def this(defaultEntry: Long -> V) = this(defaultEntry, 16, initBlank = true) /** Creates a new `LongMap` with an initial buffer of specified size. * @@ -67,7 +68,7 @@ final class LongMap[V] private[collection] (defaultEntry: Long => V, initialBuff def this(initialBufferSize: Int) = this(LongMap.exceptionDefault, initialBufferSize, initBlank = true) /** Creates a new `LongMap` with specified default values and initial buffer size. */ - def this(defaultEntry: Long => V, initialBufferSize: Int) = this(defaultEntry, initialBufferSize, initBlank = true) + def this(defaultEntry: Long -> V, initialBufferSize: Int) = this(defaultEntry, initialBufferSize, initBlank = true) private[this] var mask = 0 private[this] var extraKeys: Int = 0 @@ -473,13 +474,13 @@ final class LongMap[V] private[collection] (defaultEntry: Long => V, initialBuff if(elems.isEmpty) m else m.concat(elems) } - override def concat[V1 >: V](xs: scala.collection.IterableOnce[(Long, V1)]): LongMap[V1] = { + override def concat[V1 >: V](xs: scala.collection.IterableOnce[(Long, V1)]^): LongMap[V1] = { val lm = clone().asInstanceOf[LongMap[V1]] xs.iterator.foreach(kv => lm += kv) lm } - override def ++ [V1 >: V](xs: scala.collection.IterableOnce[(Long, V1)]): LongMap[V1] = concat(xs) + override def ++ [V1 >: V](xs: scala.collection.IterableOnce[(Long, V1)]^): LongMap[V1] = concat(xs) @deprecated("Use m.clone().addOne(k,v) instead of m.updated(k, v)", "2.13.0") override def updated[V1 >: V](key: Long, value: V1): LongMap[V1] = @@ -612,7 +613,7 @@ object LongMap { /** Creates a new `LongMap` with zero or more key/value pairs. */ def apply[V](elems: (Long, V)*): LongMap[V] = buildFromIterableOnce(elems) - private def buildFromIterableOnce[V](elems: IterableOnce[(Long, V)]): LongMap[V] = { + private def buildFromIterableOnce[V](elems: IterableOnce[(Long, V)]^): LongMap[V] = { var sz = elems.knownSize if(sz < 0) sz = 4 val lm = new LongMap[V](sz * 2) @@ -625,7 +626,7 @@ object LongMap { def empty[V]: LongMap[V] = new LongMap[V] /** Creates a new empty `LongMap` with the supplied default */ - def withDefault[V](default: Long => V): LongMap[V] = new LongMap[V](default) + def withDefault[V](default: Long -> V): LongMap[V] = new LongMap[V](default) /** Creates a new `LongMap` from an existing source collection. A source collection * which is already a `LongMap` gets cloned. @@ -634,7 +635,7 @@ object LongMap { * @tparam A the type of the collection’s elements * @return a new `LongMap` with the elements of `source` */ - def from[V](source: IterableOnce[(Long, V)]): LongMap[V] = source match { + def from[V](source: IterableOnce[(Long, V)]^): LongMap[V] = source match { case source: LongMap[_] => source.clone().asInstanceOf[LongMap[V]] case _ => buildFromIterableOnce(source) } @@ -670,13 +671,13 @@ object LongMap { @SerialVersionUID(3L) private[this] object ToFactory extends Factory[(Long, AnyRef), LongMap[AnyRef]] with Serializable { - def fromSpecific(it: IterableOnce[(Long, AnyRef)]): LongMap[AnyRef] = LongMap.from[AnyRef](it) + def fromSpecific(it: IterableOnce[(Long, AnyRef)]^): LongMap[AnyRef] = LongMap.from[AnyRef](it) def newBuilder: Builder[(Long, AnyRef), LongMap[AnyRef]] = LongMap.newBuilder[AnyRef] } implicit def toBuildFrom[V](factory: LongMap.type): BuildFrom[Any, (Long, V), LongMap[V]] = ToBuildFrom.asInstanceOf[BuildFrom[Any, (Long, V), LongMap[V]]] private object ToBuildFrom extends BuildFrom[Any, (Long, AnyRef), LongMap[AnyRef]] { - def fromSpecific(from: Any)(it: IterableOnce[(Long, AnyRef)]) = LongMap.from(it) + def fromSpecific(from: Any)(it: IterableOnce[(Long, AnyRef)]^) = LongMap.from(it) def newBuilder(from: Any): ReusableBuilder[(Long, AnyRef), LongMap[AnyRef]] = LongMap.newBuilder[AnyRef] } diff --git a/library/src/scala/collection/mutable/Map.scala b/library/src/scala/collection/mutable/Map.scala index a6975c90201d..617c5f2040be 100644 --- a/library/src/scala/collection/mutable/Map.scala +++ b/library/src/scala/collection/mutable/Map.scala @@ -14,6 +14,8 @@ package scala package collection package mutable +import language.experimental.captureChecking + import scala.language.`2.13` /** Base type of mutable Maps */ @@ -46,7 +48,7 @@ trait Map[K, V] * @param d the function mapping keys to values, used for non-present keys * @return a wrapper of the map with a default value */ - def withDefault(d: K => V): Map[K, V] = new Map.WithDefault[K, V](this, d) + def withDefault(d: K -> V): Map[K, V] = new Map.WithDefault[K, V](this, d) /** The same map with a given default value. * Note: The default is only used for `apply`. Other methods like `get`, `contains`, `iterator`, `keys`, etc. @@ -233,7 +235,7 @@ transparent trait MapOps[K, V, +CC[X, Y] <: MapOps[X, Y, CC, _], +C <: MapOps[K, object Map extends MapFactory.Delegate[Map](HashMap) { @SerialVersionUID(3L) - class WithDefault[K, V](val underlying: Map[K, V], val defaultValue: K => V) + class WithDefault[K, V](val underlying: Map[K, V], val defaultValue: K -> V) extends AbstractMap[K, V] with MapOps[K, V, Map, WithDefault[K, V]] with Serializable { @@ -252,12 +254,12 @@ object Map extends MapFactory.Delegate[Map](HashMap) { def addOne(elem: (K, V)): WithDefault.this.type = { underlying.addOne(elem); this } - override def concat[V2 >: V](suffix: collection.IterableOnce[(K, V2)]): Map[K, V2] = + override def concat[V2 >: V](suffix: collection.IterableOnce[(K, V2)]^): Map[K, V2] = underlying.concat(suffix).withDefault(defaultValue) override def empty: WithDefault[K, V] = new WithDefault[K, V](underlying.empty, defaultValue) - override protected def fromSpecific(coll: scala.collection.IterableOnce[(K, V)]): WithDefault[K, V] = + override protected def fromSpecific(coll: scala.collection.IterableOnce[(K, V)]^): WithDefault[K, V] = new WithDefault[K, V](mapFactory.from(coll), defaultValue) override protected def newSpecificBuilder: Builder[(K, V), WithDefault[K, V]] = diff --git a/library/src/scala/collection/mutable/MultiMap.scala b/library/src/scala/collection/mutable/MultiMap.scala index f96a2ca94754..a92f5a8f5712 100644 --- a/library/src/scala/collection/mutable/MultiMap.scala +++ b/library/src/scala/collection/mutable/MultiMap.scala @@ -13,6 +13,7 @@ package scala.collection.mutable import scala.language.`2.13` +import language.experimental.captureChecking /** A trait for mutable maps with multiple values assigned to a key. * diff --git a/library/src/scala/collection/mutable/OpenHashMap.scala b/library/src/scala/collection/mutable/OpenHashMap.scala index d8222ba6914a..5c37d22d5cb0 100644 --- a/library/src/scala/collection/mutable/OpenHashMap.scala +++ b/library/src/scala/collection/mutable/OpenHashMap.scala @@ -14,6 +14,7 @@ package scala.collection package mutable import scala.language.`2.13` +import language.experimental.captureChecking import java.lang.Integer.numberOfLeadingZeros import java.util.ConcurrentModificationException import scala.collection.generic.DefaultSerializable @@ -27,7 +28,7 @@ import scala.collection.generic.DefaultSerializable object OpenHashMap extends StrictMapFactory[OpenHashMap] { def empty[K, V] = new OpenHashMap[K, V] - def from[K, V](it: IterableOnce[(K, V)]): OpenHashMap[K,V] = empty ++= it + def from[K, V](it: IterableOnce[(K, V)]^): OpenHashMap[K,V] = empty ++= it def newBuilder[K, V]: Builder[(K, V), OpenHashMap[K,V]] = new GrowableBuilder[(K, V), OpenHashMap[K, V]](empty) diff --git a/library/src/scala/collection/mutable/RedBlackTree.scala b/library/src/scala/collection/mutable/RedBlackTree.scala index e4f2c54017a8..7a64730d7449 100644 --- a/library/src/scala/collection/mutable/RedBlackTree.scala +++ b/library/src/scala/collection/mutable/RedBlackTree.scala @@ -14,6 +14,7 @@ package scala package collection.mutable import scala.language.`2.13` +import language.experimental.captureChecking import scala.annotation.tailrec import collection.{AbstractIterator, Iterator} import java.lang.String diff --git a/library/src/scala/collection/mutable/SeqMap.scala b/library/src/scala/collection/mutable/SeqMap.scala index be74e085d566..2519c09b95b6 100644 --- a/library/src/scala/collection/mutable/SeqMap.scala +++ b/library/src/scala/collection/mutable/SeqMap.scala @@ -15,6 +15,7 @@ package collection package mutable import scala.language.`2.13` +import language.experimental.captureChecking /** * A generic trait for ordered mutable maps. Concrete classes have to provide diff --git a/library/src/scala/collection/mutable/SortedMap.scala b/library/src/scala/collection/mutable/SortedMap.scala index 534149aaf053..c16a01384785 100644 --- a/library/src/scala/collection/mutable/SortedMap.scala +++ b/library/src/scala/collection/mutable/SortedMap.scala @@ -14,6 +14,7 @@ package scala package collection.mutable import scala.language.`2.13` +import language.experimental.captureChecking import scala.collection.{SortedMapFactory, SortedMapFactoryDefaults} /** @@ -38,7 +39,7 @@ trait SortedMap[K, V] * @param d the function mapping keys to values, used for non-present keys * @return a wrapper of the map with a default value */ - override def withDefault(d: K => V): SortedMap[K, V] = new SortedMap.WithDefault[K, V](this, d) + override def withDefault(d: K -> V): SortedMap[K, V] = new SortedMap.WithDefault[K, V](this, d) /** The same map with a given default value. * Note: The default is only used for `apply`. Other methods like `get`, `contains`, `iterator`, `keys`, etc. @@ -67,7 +68,7 @@ transparent trait SortedMapOps[K, V, +CC[X, Y] <: Map[X, Y] with SortedMapOps[X, object SortedMap extends SortedMapFactory.Delegate[SortedMap](TreeMap) { @SerialVersionUID(3L) - final class WithDefault[K, V](underlying: SortedMap[K, V], defaultValue: K => V) + final class WithDefault[K, V](underlying: SortedMap[K, V], defaultValue: K -> V) extends Map.WithDefault[K, V](underlying, defaultValue) with SortedMap[K, V] with SortedMapOps[K, V, SortedMap, WithDefault[K, V]] @@ -92,10 +93,10 @@ object SortedMap extends SortedMapFactory.Delegate[SortedMap](TreeMap) { override def empty: WithDefault[K, V] = new WithDefault[K, V](underlying.empty, defaultValue) - override def concat[V2 >: V](suffix: collection.IterableOnce[(K, V2)]): SortedMap[K, V2] = + override def concat[V2 >: V](suffix: collection.IterableOnce[(K, V2)]^): SortedMap[K, V2] = underlying.concat(suffix).withDefault(defaultValue) - override protected def fromSpecific(coll: scala.collection.IterableOnce[(K, V)]): WithDefault[K, V] = + override protected def fromSpecific(coll: scala.collection.IterableOnce[(K, V)]^): WithDefault[K, V] = new WithDefault[K, V](sortedMapFactory.from(coll), defaultValue) override protected def newSpecificBuilder: Builder[(K, V), WithDefault[K, V]] = diff --git a/library/src/scala/collection/mutable/TreeMap.scala b/library/src/scala/collection/mutable/TreeMap.scala index 85b8d897f22a..9ebea9d63956 100644 --- a/library/src/scala/collection/mutable/TreeMap.scala +++ b/library/src/scala/collection/mutable/TreeMap.scala @@ -15,6 +15,7 @@ package collection package mutable import scala.language.`2.13` +import language.experimental.captureChecking import scala.collection.Stepper.EfficientSplit import scala.collection.generic.DefaultSerializable import scala.collection.mutable.{RedBlackTree => RB} @@ -167,6 +168,7 @@ sealed class TreeMap[K, V] private (tree: RB.Tree[K, V])(implicit val ordering: * bound. */ private[this] final class TreeMapProjection(from: Option[K], until: Option[K]) extends TreeMap[K, V](tree) { + this: TreeMapProjection^{} => /** * Given a possible new lower bound, chooses and returns the most constraining one (the maximum). @@ -248,7 +250,7 @@ sealed class TreeMap[K, V] private (tree: RB.Tree[K, V])(implicit val ordering: @SerialVersionUID(3L) object TreeMap extends SortedMapFactory[TreeMap] { - def from[K : Ordering, V](it: IterableOnce[(K, V)]): TreeMap[K, V] = + def from[K : Ordering, V](it: IterableOnce[(K, V)]^): TreeMap[K, V] = Growable.from(empty[K, V], it) def empty[K : Ordering, V]: TreeMap[K, V] = new TreeMap[K, V]() diff --git a/library/src/scala/collection/mutable/WeakHashMap.scala b/library/src/scala/collection/mutable/WeakHashMap.scala index 183cd55c51b8..37879befb945 100644 --- a/library/src/scala/collection/mutable/WeakHashMap.scala +++ b/library/src/scala/collection/mutable/WeakHashMap.scala @@ -15,6 +15,7 @@ package collection package mutable import scala.language.`2.13` +import language.experimental.captureChecking import scala.annotation.nowarn import scala.collection.convert.JavaCollectionWrappers.{JMapWrapper, JMapWrapperLike} @@ -50,7 +51,7 @@ class WeakHashMap[K, V] extends JMapWrapper[K, V](new java.util.WeakHashMap) @SerialVersionUID(3L) object WeakHashMap extends StrictMapFactory[WeakHashMap] { def empty[K, V]: WeakHashMap[K,V] = new WeakHashMap[K, V] - def from[K, V](it: collection.IterableOnce[(K, V)]): WeakHashMap[K,V] = Growable.from(empty[K, V], it) + def from[K, V](it: collection.IterableOnce[(K, V)]^): WeakHashMap[K,V] = Growable.from(empty[K, V], it) def newBuilder[K, V]: Builder[(K, V), WeakHashMap[K,V]] = new GrowableBuilder(WeakHashMap.empty[K, V]) } From 0df11f93ad974780f745b611ab0616236ba180e9 Mon Sep 17 00:00:00 2001 From: Yichen Xu Date: Thu, 14 Aug 2025 16:03:10 +0200 Subject: [PATCH 072/101] Capture check mutable buffers --- .../scala/collection/mutable/AnyRefMap.scala | 5 +++-- .../src/scala/collection/mutable/Buffer.scala | 17 +++++++++-------- .../mutable/CollisionProofHashMap.scala | 4 ++-- .../src/scala/collection/mutable/Growable.scala | 7 ++++--- .../src/scala/collection/mutable/HashMap.scala | 2 +- .../scala/collection/mutable/ListBuffer.scala | 13 +++++++------ .../src/scala/collection/mutable/LongMap.scala | 3 ++- .../scala/collection/mutable/Shrinkable.scala | 5 +++-- 8 files changed, 31 insertions(+), 25 deletions(-) diff --git a/library/src/scala/collection/mutable/AnyRefMap.scala b/library/src/scala/collection/mutable/AnyRefMap.scala index dc940a56051a..984fb1ff781e 100644 --- a/library/src/scala/collection/mutable/AnyRefMap.scala +++ b/library/src/scala/collection/mutable/AnyRefMap.scala @@ -404,7 +404,8 @@ class AnyRefMap[K <: AnyRef, V] private[collection] (defaultEntry: K -> V, initi override def + [V1 >: V](kv: (K, V1)): AnyRefMap[K, V1] = AnyRefMap.from(new View.Appended(this, kv)) @deprecated("Use ++ with an explicit collection argument instead of + with varargs", "2.13.0") - override def + [V1 >: V](elem1: (K, V1), elem2: (K, V1), elems: (K, V1)*): AnyRefMap[K, V1] = { + override def + [V1 >: V](elem1: (K, V1), elem2: (K, V1), elems: (K, V1)*): AnyRefMap[K, V1]^{} = { + // An empty capture annotation is needed in the result type to satisfy the overriding checker. val m = this + elem1 + elem2 if(elems.isEmpty) m else m.concat(elems) } @@ -499,7 +500,7 @@ class AnyRefMap[K <: AnyRef, V] private[collection] (defaultEntry: K -> V, initi * @param f the mapping function must produce key-value pairs where the key is an `AnyRef` * @param dummy an implicit placeholder for purposes of distinguishing the (erased) signature of this method */ - def flatMap[K2 <: AnyRef, V2](f: ((K, V)) => IterableOnce[(K2, V2)])(implicit dummy: DummyImplicit): AnyRefMap[K2, V2] = + def flatMap[K2 <: AnyRef, V2](f: ((K, V)) => IterableOnce[(K2, V2)]^)(implicit dummy: DummyImplicit): AnyRefMap[K2, V2] = AnyRefMap.from(new View.FlatMap(this, f)) /** * An overload of `collect` which produces an `AnyRefMap`. diff --git a/library/src/scala/collection/mutable/Buffer.scala b/library/src/scala/collection/mutable/Buffer.scala index c3ede915ae3d..bfed8dd0add4 100644 --- a/library/src/scala/collection/mutable/Buffer.scala +++ b/library/src/scala/collection/mutable/Buffer.scala @@ -14,6 +14,7 @@ package scala.collection package mutable import scala.language.`2.13` +import language.experimental.captureChecking import scala.annotation.nowarn @@ -55,7 +56,7 @@ trait Buffer[A] * @param elems the iterable object containing the elements to append. * @return this $coll */ - @`inline` final def appendAll(@deprecatedName("xs") elems: IterableOnce[A]): this.type = addAll(elems) + @`inline` final def appendAll(@deprecatedName("xs") elems: IterableOnce[A]^): this.type = addAll(elems) /** Alias for `prepend` */ @`inline` final def +=: (elem: A): this.type = prepend(elem) @@ -64,13 +65,13 @@ trait Buffer[A] * @param elems the iterable object containing the elements to append. * @return this $coll */ - def prependAll(elems: IterableOnce[A]): this.type = { insertAll(0, elems); this } + def prependAll(elems: IterableOnce[A]^): this.type = { insertAll(0, elems); this } @deprecated("Use prependAll instead", "2.13.0") @`inline` final def prepend(elems: A*): this.type = prependAll(elems) /** Alias for `prependAll` */ - @inline final def ++=:(elems: IterableOnce[A]): this.type = prependAll(elems) + @inline final def ++=:(elems: IterableOnce[A]^): this.type = prependAll(elems) /** Inserts a new element at a given index into this buffer. * @@ -91,7 +92,7 @@ trait Buffer[A] * @throws IndexOutOfBoundsException if `idx` is out of bounds. */ @throws[IndexOutOfBoundsException] - def insertAll(idx: Int, elems: IterableOnce[A]): Unit + def insertAll(idx: Int, elems: IterableOnce[A]^): Unit /** Removes the element at a given index position. * @@ -153,7 +154,7 @@ trait Buffer[A] * @param replaced the number of elements to drop in the original $coll * @return this $coll */ - def patchInPlace(from: Int, patch: scala.collection.IterableOnce[A], replaced: Int): this.type + def patchInPlace(from: Int, patch: scala.collection.IterableOnce[A]^, replaced: Int): this.type // +=, ++=, clear inherited from Growable // Per remark of @ichoran, we should preferably not have these: @@ -262,11 +263,11 @@ trait IndexedBuffer[A] extends IndexedSeq[A] * @param f the mapping function * @return this $coll */ - def flatMapInPlace(f: A => IterableOnce[A]): this.type = { + def flatMapInPlace(f: A => IterableOnce[A]^): this.type = { // There's scope for a better implementation which copies elements in place. var i = 0 val s = size - val newElems = new Array[IterableOnce[A]](s) + val newElems = new Array[IterableOnce[A]^{f}](s) while (i < s) { newElems(i) = f(this(i)); i += 1 } clear() i = 0 @@ -294,7 +295,7 @@ trait IndexedBuffer[A] extends IndexedSeq[A] if (i == j) this else takeInPlace(j) } - def patchInPlace(from: Int, patch: scala.collection.IterableOnce[A], replaced: Int): this.type = { + def patchInPlace(from: Int, patch: scala.collection.IterableOnce[A]^, replaced: Int): this.type = { val replaced0 = math.min(math.max(replaced, 0), length) val i = math.min(math.max(from, 0), length) var j = 0 diff --git a/library/src/scala/collection/mutable/CollisionProofHashMap.scala b/library/src/scala/collection/mutable/CollisionProofHashMap.scala index 9413af8f9569..35fa6393deab 100644 --- a/library/src/scala/collection/mutable/CollisionProofHashMap.scala +++ b/library/src/scala/collection/mutable/CollisionProofHashMap.scala @@ -175,7 +175,7 @@ final class CollisionProofHashMap[K, V](initialCapacity: Int, loadFactor: Double } } - override def addAll(xs: IterableOnce[(K, V)]): this.type = { + override def addAll(xs: IterableOnce[(K, V)]^): this.type = { sizeHint(xs, delta = contentSize) super.addAll(xs) } @@ -427,7 +427,7 @@ final class CollisionProofHashMap[K, V](initialCapacity: Int, loadFactor: Double * @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)]) + def flatMap[K2, V2](f: ((K, V)) => IterableOnce[(K2, V2)]^) (implicit @implicitNotFound(CollisionProofHashMap.ordMsg) ordering: Ordering[K2]): CollisionProofHashMap[K2, V2] = sortedMapFactory.from(new View.FlatMap(this, f)) diff --git a/library/src/scala/collection/mutable/Growable.scala b/library/src/scala/collection/mutable/Growable.scala index 9caed94240e2..1ce3b3b0d99e 100644 --- a/library/src/scala/collection/mutable/Growable.scala +++ b/library/src/scala/collection/mutable/Growable.scala @@ -15,6 +15,7 @@ package collection package mutable import scala.language.`2.13` +import language.experimental.captureChecking /** This trait forms part of collections that can be augmented * using a `+=` operator and that can be cleared of all elements using @@ -56,7 +57,7 @@ trait Growable[-A] extends Clearable { * @param elems the IterableOnce producing the elements to $add. * @return the $coll itself. */ - def addAll(@deprecatedName("xs") elems: IterableOnce[A]): this.type = { + def addAll(@deprecatedName("xs") elems: IterableOnce[A]^): this.type = { if (elems.asInstanceOf[AnyRef] eq this) addAll(Buffer.from(elems)) // avoid mutating under our own iterator else { val it = elems.iterator @@ -68,7 +69,7 @@ trait Growable[-A] extends Clearable { } /** Alias for `addAll` */ - @inline final def ++= (@deprecatedName("xs") elems: IterableOnce[A]): this.type = addAll(elems) + @inline final def ++= (@deprecatedName("xs") elems: IterableOnce[A]^): this.type = addAll(elems) /** The number of elements in the collection under construction, if it can be cheaply computed, -1 otherwise. * @@ -86,7 +87,7 @@ object Growable { * @tparam A Element type * @return The filled instance */ - def from[A](empty: Growable[A], it: collection.IterableOnce[A]): empty.type = empty ++= it + def from[A](empty: Growable[A], it: collection.IterableOnce[A]^): empty.type = empty ++= it } diff --git a/library/src/scala/collection/mutable/HashMap.scala b/library/src/scala/collection/mutable/HashMap.scala index c4da374a7236..8024347f9a03 100644 --- a/library/src/scala/collection/mutable/HashMap.scala +++ b/library/src/scala/collection/mutable/HashMap.scala @@ -184,7 +184,7 @@ class HashMap[K, V](initialCapacity: Int, loadFactor: Double) } } - override def subtractAll(xs: IterableOnce[K]): this.type = { + override def subtractAll(xs: IterableOnce[K]^): this.type = { if (size == 0) { return this } diff --git a/library/src/scala/collection/mutable/ListBuffer.scala b/library/src/scala/collection/mutable/ListBuffer.scala index e5858f79eb5e..f90423b40f67 100644 --- a/library/src/scala/collection/mutable/ListBuffer.scala +++ b/library/src/scala/collection/mutable/ListBuffer.scala @@ -14,6 +14,7 @@ package scala.collection package mutable import scala.language.`2.13` +import language.experimental.captureChecking import scala.annotation.{nowarn, tailrec} import scala.collection.generic.CommonErrors import scala.collection.immutable.{::, List, Nil} @@ -123,7 +124,7 @@ class ListBuffer[A] } // MUST only be called on fresh instances - private def freshFrom(xs: IterableOnce[A]): this.type = { + private def freshFrom(xs: IterableOnce[A]^): this.type = { val it = xs.iterator if (it.hasNext) { var len = 1 @@ -142,7 +143,7 @@ class ListBuffer[A] this } - override final def addAll(xs: IterableOnce[A]): this.type = { + override final def addAll(xs: IterableOnce[A]^): this.type = { val it = xs.iterator if (it.hasNext) { val fresh = new ListBuffer[A].freshFrom(it) @@ -251,7 +252,7 @@ class ListBuffer[A] } } - def insertAll(idx: Int, elems: IterableOnce[A]): Unit = { + def insertAll(idx: Int, elems: IterableOnce[A]^): Unit = { if (idx < 0 || idx > len) throw CommonErrors.indexOutOfBounds(index = idx, max = len - 1) val it = elems.iterator if (it.hasNext) { @@ -318,7 +319,7 @@ class ListBuffer[A] * @param f the mapping function * @return this $coll */ - def flatMapInPlace(f: A => IterableOnce[A]): this.type = { + def flatMapInPlace(f: A => IterableOnce[A]^): this.type = { mutationCount += 1 var src = first var dst: List[A] = null @@ -363,7 +364,7 @@ class ListBuffer[A] this } - def patchInPlace(from: Int, patch: collection.IterableOnce[A], replaced: Int): this.type = { + def patchInPlace(from: Int, patch: collection.IterableOnce[A]^, replaced: Int): this.type = { val _len = len val _from = math.max(from, 0) // normalized val _replaced = math.max(replaced, 0) // normalized @@ -413,7 +414,7 @@ class ListBuffer[A] @SerialVersionUID(3L) object ListBuffer extends StrictOptimizedSeqFactory[ListBuffer] { - def from[A](coll: collection.IterableOnce[A]): ListBuffer[A] = new ListBuffer[A].freshFrom(coll) + def from[A](coll: collection.IterableOnce[A]^): ListBuffer[A] = new ListBuffer[A].freshFrom(coll) def newBuilder[A]: Builder[A, ListBuffer[A]] = new GrowableBuilder(empty[A]) diff --git a/library/src/scala/collection/mutable/LongMap.scala b/library/src/scala/collection/mutable/LongMap.scala index 5df80d912440..e4b2cecbf1af 100644 --- a/library/src/scala/collection/mutable/LongMap.scala +++ b/library/src/scala/collection/mutable/LongMap.scala @@ -469,7 +469,8 @@ final class LongMap[V] private[collection] (defaultEntry: Long -> V, initialBuff } @deprecated("Use ++ with an explicit collection argument instead of + with varargs", "2.13.0") - override def + [V1 >: V](elem1: (Long, V1), elem2: (Long, V1), elems: (Long, V1)*): LongMap[V1] = { + override def + [V1 >: V](elem1: (Long, V1), elem2: (Long, V1), elems: (Long, V1)*): LongMap[V1]^{} = { + // An empty capture annotation is needed in the result type to satisfy the overriding checker. val m = this + elem1 + elem2 if(elems.isEmpty) m else m.concat(elems) } diff --git a/library/src/scala/collection/mutable/Shrinkable.scala b/library/src/scala/collection/mutable/Shrinkable.scala index 96171c6df024..587530dba044 100644 --- a/library/src/scala/collection/mutable/Shrinkable.scala +++ b/library/src/scala/collection/mutable/Shrinkable.scala @@ -14,6 +14,7 @@ package scala package collection.mutable import scala.language.`2.13` +import language.experimental.captureChecking import scala.annotation.tailrec /** This trait forms part of collections that can be reduced @@ -53,7 +54,7 @@ trait Shrinkable[-A] { * @param xs the iterator producing the elements to remove. * @return the $coll itself */ - def subtractAll(xs: collection.IterableOnce[A]): this.type = { + def subtractAll(xs: collection.IterableOnce[A]^): this.type = { @tailrec def loop(xs: collection.LinearSeq[A]): Unit = { if (xs.nonEmpty) { subtractOne(xs.head) @@ -75,6 +76,6 @@ trait Shrinkable[-A] { } /** Alias for `subtractAll` */ - @`inline` final def --= (xs: collection.IterableOnce[A]): this.type = subtractAll(xs) + @`inline` final def --= (xs: collection.IterableOnce[A]^): this.type = subtractAll(xs) } From 5f26a75553d18c36082033fc9961647a2cdc385e Mon Sep 17 00:00:00 2001 From: Yichen Xu Date: Thu, 14 Aug 2025 16:59:03 +0200 Subject: [PATCH 073/101] Capture-check various collections --- .../collection/mutable/ArrayBuffer.scala | 44 ++++++------- .../collection/mutable/ArrayBuilder.scala | 6 +- .../scala/collection/mutable/ArrayDeque.scala | 9 +-- .../scala/collection/mutable/ArraySeq.scala | 5 +- .../mutable/CheckedIndexedSeqView.scala | 61 ++++++++++--------- .../src/scala/collection/mutable/Queue.scala | 5 +- .../src/scala/collection/mutable/Seq.scala | 1 + .../src/scala/collection/mutable/Set.scala | 1 + .../src/scala/collection/mutable/Stack.scala | 5 +- .../collection/mutable/UnrolledBuffer.scala | 10 +-- 10 files changed, 80 insertions(+), 67 deletions(-) diff --git a/library/src/scala/collection/mutable/ArrayBuffer.scala b/library/src/scala/collection/mutable/ArrayBuffer.scala index 72384e3f65a2..7abe265a3897 100644 --- a/library/src/scala/collection/mutable/ArrayBuffer.scala +++ b/library/src/scala/collection/mutable/ArrayBuffer.scala @@ -15,6 +15,7 @@ package collection package mutable import scala.language.`2.13` +import language.experimental.captureChecking import java.util.Arrays import scala.annotation.{nowarn, tailrec} import scala.collection.Stepper.EfficientSplit @@ -147,7 +148,7 @@ class ArrayBuffer[A] private (initialElements: Array[AnyRef], initialSize: Int) } // Overridden to use array copying for efficiency where possible. - override def addAll(elems: IterableOnce[A]): this.type = { + override def addAll(elems: IterableOnce[A]^): this.type = { elems match { case elems: ArrayBuffer[_] => val elemsLength = elems.size0 @@ -176,7 +177,7 @@ class ArrayBuffer[A] private (initialElements: Array[AnyRef], initialSize: Int) this } - def insertAll(@deprecatedName("n", "2.13.0") index: Int, elems: IterableOnce[A]): Unit = { + def insertAll(@deprecatedName("n", "2.13.0") index: Int, elems: IterableOnce[A]^): Unit = { checkWithinBounds(index, index) elems match { case elems: collection.Iterable[A] => @@ -294,7 +295,7 @@ object ArrayBuffer extends StrictOptimizedSeqFactory[ArrayBuffer] { final val DefaultInitialSize = 16 private[this] val emptyArray = new Array[AnyRef](0) - def from[B](coll: collection.IterableOnce[B]): ArrayBuffer[B] = { + def from[B](coll: collection.IterableOnce[B]^): ArrayBuffer[B] = { val k = coll.knownSize if (k >= 0) { // Avoid reallocation of buffer if length is known @@ -372,10 +373,11 @@ final class ArrayBufferView[A] private[mutable](underlying: ArrayBuffer[A], muta this({ val _array = array val _length = length - new ArrayBuffer[A](0) { + val buf = new ArrayBuffer[A](0) { this.array = _array this.size0 = _length } + buf }, () => 0) } @@ -388,21 +390,21 @@ final class ArrayBufferView[A] private[mutable](underlying: ArrayBuffer[A], muta override protected[this] def className = "ArrayBufferView" // we could inherit all these from `CheckedIndexedSeqView`, except this class is public - override def iterator: Iterator[A] = new CheckedIndexedSeqView.CheckedIterator(this, mutationCount()) - override def reverseIterator: Iterator[A] = new CheckedIndexedSeqView.CheckedReverseIterator(this, mutationCount()) - - override def appended[B >: A](elem: B): IndexedSeqView[B] = new CheckedIndexedSeqView.Appended(this, elem)(mutationCount) - override def prepended[B >: A](elem: B): IndexedSeqView[B] = new CheckedIndexedSeqView.Prepended(elem, this)(mutationCount) - override def take(n: Int): IndexedSeqView[A] = new CheckedIndexedSeqView.Take(this, n)(mutationCount) - override def takeRight(n: Int): IndexedSeqView[A] = new CheckedIndexedSeqView.TakeRight(this, n)(mutationCount) - override def drop(n: Int): IndexedSeqView[A] = new CheckedIndexedSeqView.Drop(this, n)(mutationCount) - override def dropRight(n: Int): IndexedSeqView[A] = new CheckedIndexedSeqView.DropRight(this, n)(mutationCount) - override def map[B](f: A => B): IndexedSeqView[B] = new CheckedIndexedSeqView.Map(this, f)(mutationCount) - override def reverse: IndexedSeqView[A] = new CheckedIndexedSeqView.Reverse(this)(mutationCount) - override def slice(from: Int, until: Int): IndexedSeqView[A] = new CheckedIndexedSeqView.Slice(this, from, until)(mutationCount) - override def tapEach[U](f: A => U): IndexedSeqView[A] = new CheckedIndexedSeqView.Map(this, { (a: A) => f(a); a})(mutationCount) - - override def concat[B >: A](suffix: IndexedSeqView.SomeIndexedSeqOps[B]): IndexedSeqView[B] = new CheckedIndexedSeqView.Concat(this, suffix)(mutationCount) - override def appendedAll[B >: A](suffix: IndexedSeqView.SomeIndexedSeqOps[B]): IndexedSeqView[B] = new CheckedIndexedSeqView.Concat(this, suffix)(mutationCount) - override def prependedAll[B >: A](prefix: IndexedSeqView.SomeIndexedSeqOps[B]): IndexedSeqView[B] = new CheckedIndexedSeqView.Concat(prefix, this)(mutationCount) + override def iterator: Iterator[A]^{this} = new CheckedIndexedSeqView.CheckedIterator(this, mutationCount()) + override def reverseIterator: Iterator[A]^{this} = new CheckedIndexedSeqView.CheckedReverseIterator(this, mutationCount()) + + override def appended[B >: A](elem: B): IndexedSeqView[B]^{this} = new CheckedIndexedSeqView.Appended(this, elem)(mutationCount) + override def prepended[B >: A](elem: B): IndexedSeqView[B]^{this} = new CheckedIndexedSeqView.Prepended(elem, this)(mutationCount) + override def take(n: Int): IndexedSeqView[A]^{this} = new CheckedIndexedSeqView.Take(this, n)(mutationCount) + override def takeRight(n: Int): IndexedSeqView[A]^{this} = new CheckedIndexedSeqView.TakeRight(this, n)(mutationCount) + override def drop(n: Int): IndexedSeqView[A]^{this} = new CheckedIndexedSeqView.Drop(this, n)(mutationCount) + override def dropRight(n: Int): IndexedSeqView[A]^{this} = new CheckedIndexedSeqView.DropRight(this, n)(mutationCount) + override def map[B](f: A => B): IndexedSeqView[B]^{this, f} = new CheckedIndexedSeqView.Map(this, f)(mutationCount) + override def reverse: IndexedSeqView[A]^{this} = new CheckedIndexedSeqView.Reverse(this)(mutationCount) + override def slice(from: Int, until: Int): IndexedSeqView[A]^{this} = new CheckedIndexedSeqView.Slice(this, from, until)(mutationCount) + override def tapEach[U](f: A => U): IndexedSeqView[A]^{this, f} = new CheckedIndexedSeqView.Map(this, { (a: A) => f(a); a})(mutationCount) + + override def concat[B >: A](suffix: IndexedSeqView.SomeIndexedSeqOps[B]^): IndexedSeqView[B]^{suffix, this} = new CheckedIndexedSeqView.Concat(this, suffix)(mutationCount) + override def appendedAll[B >: A](suffix: IndexedSeqView.SomeIndexedSeqOps[B]^): IndexedSeqView[B]^{suffix, this} = new CheckedIndexedSeqView.Concat(this, suffix)(mutationCount) + override def prependedAll[B >: A](prefix: IndexedSeqView.SomeIndexedSeqOps[B]^): IndexedSeqView[B]^{prefix, this} = new CheckedIndexedSeqView.Concat(prefix, this)(mutationCount) } diff --git a/library/src/scala/collection/mutable/ArrayBuilder.scala b/library/src/scala/collection/mutable/ArrayBuilder.scala index c6bd34a4001c..608fb85564a3 100644 --- a/library/src/scala/collection/mutable/ArrayBuilder.scala +++ b/library/src/scala/collection/mutable/ArrayBuilder.scala @@ -14,6 +14,7 @@ package scala.collection package mutable import scala.language.`2.13` +import language.experimental.captureChecking import scala.collection.mutable.ArrayBuffer.resizeUp import scala.reflect.ClassTag @@ -66,7 +67,7 @@ sealed abstract class ArrayBuilder[T] this } - override def addAll(xs: IterableOnce[T]): this.type = { + override def addAll(xs: IterableOnce[T]^): this.type = { val k = xs.knownSize if (k > 0) { ensureSize(this.size + k) @@ -452,6 +453,7 @@ object ArrayBuilder { /** A class for array builders for arrays of `boolean`s. It can be reused. */ @SerialVersionUID(3L) class ofBoolean extends ArrayBuilder[Boolean] { + this: ofBoolean^{} => protected var elems: Array[Boolean] = _ @@ -504,7 +506,7 @@ object ArrayBuilder { this } - override def addAll(xs: IterableOnce[Unit]): this.type = { + override def addAll(xs: IterableOnce[Unit]^): this.type = { val newSize = size + xs.iterator.size ensureSize(newSize) size = newSize diff --git a/library/src/scala/collection/mutable/ArrayDeque.scala b/library/src/scala/collection/mutable/ArrayDeque.scala index 07e3c559236a..e395a651778c 100644 --- a/library/src/scala/collection/mutable/ArrayDeque.scala +++ b/library/src/scala/collection/mutable/ArrayDeque.scala @@ -15,6 +15,7 @@ package collection package mutable import scala.language.`2.13` +import language.experimental.captureChecking import scala.annotation.nowarn import scala.collection.Stepper.EfficientSplit import scala.collection.generic.{CommonErrors, DefaultSerializable} @@ -100,7 +101,7 @@ class ArrayDeque[A] protected ( this } - override def prependAll(elems: IterableOnce[A]): this.type = { + override def prependAll(elems: IterableOnce[A]^): this.type = { val it = elems.iterator if (it.nonEmpty) { val n = length @@ -132,7 +133,7 @@ class ArrayDeque[A] protected ( this } - override def addAll(elems: IterableOnce[A]): this.type = { + override def addAll(elems: IterableOnce[A]^): this.type = { elems.knownSize match { case srcLength if srcLength > 0 => ensureSize(srcLength + length) @@ -178,7 +179,7 @@ class ArrayDeque[A] protected ( } } - def insertAll(idx: Int, elems: IterableOnce[A]): Unit = { + def insertAll(idx: Int, elems: IterableOnce[A]^): Unit = { requireBounds(idx, length+1) val n = length if (idx == 0) { @@ -528,7 +529,7 @@ class ArrayDeque[A] protected ( @SerialVersionUID(3L) object ArrayDeque extends StrictOptimizedSeqFactory[ArrayDeque] { - def from[B](coll: collection.IterableOnce[B]): ArrayDeque[B] = { + def from[B](coll: collection.IterableOnce[B]^): ArrayDeque[B] = { val s = coll.knownSize if (s >= 0) { val array = alloc(s) diff --git a/library/src/scala/collection/mutable/ArraySeq.scala b/library/src/scala/collection/mutable/ArraySeq.scala index a6af2be04fff..53afc6cf66be 100644 --- a/library/src/scala/collection/mutable/ArraySeq.scala +++ b/library/src/scala/collection/mutable/ArraySeq.scala @@ -14,6 +14,7 @@ package scala.collection package mutable import scala.language.`2.13` +import language.experimental.captureChecking import java.util.Arrays import scala.collection.Stepper.EfficientSplit import scala.collection.convert.impl._ @@ -43,7 +44,7 @@ sealed abstract class ArraySeq[T] override def iterableFactory: scala.collection.SeqFactory[ArraySeq] = ArraySeq.untagged - override protected def fromSpecific(coll: scala.collection.IterableOnce[T]): ArraySeq[T] = { + override protected def fromSpecific(coll: scala.collection.IterableOnce[T]^): ArraySeq[T] = { val b = ArrayBuilder.make(using elemTag).asInstanceOf[ArrayBuilder[T]] b.sizeHint(coll, delta = 0) b ++= coll @@ -106,7 +107,7 @@ object ArraySeq extends StrictOptimizedClassTagSeqFactory[ArraySeq] { self => private[this] val EmptyArraySeq = new ofRef[AnyRef](new Array[AnyRef](0)) def empty[T : ClassTag]: ArraySeq[T] = EmptyArraySeq.asInstanceOf[ArraySeq[T]] - def from[A : ClassTag](it: scala.collection.IterableOnce[A]): ArraySeq[A] = make(Array.from[A](it)) + def from[A : ClassTag](it: scala.collection.IterableOnce[A]^): ArraySeq[A] = make(Array.from[A](it)) def newBuilder[A : ClassTag]: Builder[A, ArraySeq[A]] = ArrayBuilder.make[A].mapResult(make) diff --git a/library/src/scala/collection/mutable/CheckedIndexedSeqView.scala b/library/src/scala/collection/mutable/CheckedIndexedSeqView.scala index 720e8ce90b95..3c7afaf0b79f 100644 --- a/library/src/scala/collection/mutable/CheckedIndexedSeqView.scala +++ b/library/src/scala/collection/mutable/CheckedIndexedSeqView.scala @@ -15,34 +15,35 @@ package collection package mutable import scala.language.`2.13` +import language.experimental.captureChecking private[mutable] trait CheckedIndexedSeqView[+A] extends IndexedSeqView[A] { protected val mutationCount: () => Int - override def iterator: Iterator[A] = new CheckedIndexedSeqView.CheckedIterator(this, mutationCount()) - override def reverseIterator: Iterator[A] = new CheckedIndexedSeqView.CheckedReverseIterator(this, mutationCount()) - - override def appended[B >: A](elem: B): IndexedSeqView[B] = new CheckedIndexedSeqView.Appended(this, elem)(mutationCount) - override def prepended[B >: A](elem: B): IndexedSeqView[B] = new CheckedIndexedSeqView.Prepended(elem, this)(mutationCount) - override def take(n: Int): IndexedSeqView[A] = new CheckedIndexedSeqView.Take(this, n)(mutationCount) - override def takeRight(n: Int): IndexedSeqView[A] = new CheckedIndexedSeqView.TakeRight(this, n)(mutationCount) - override def drop(n: Int): IndexedSeqView[A] = new CheckedIndexedSeqView.Drop(this, n)(mutationCount) - override def dropRight(n: Int): IndexedSeqView[A] = new CheckedIndexedSeqView.DropRight(this, n)(mutationCount) - override def map[B](f: A => B): IndexedSeqView[B] = new CheckedIndexedSeqView.Map(this, f)(mutationCount) - override def reverse: IndexedSeqView[A] = new CheckedIndexedSeqView.Reverse(this)(mutationCount) - override def slice(from: Int, until: Int): IndexedSeqView[A] = new CheckedIndexedSeqView.Slice(this, from, until)(mutationCount) - override def tapEach[U](f: A => U): IndexedSeqView[A] = new CheckedIndexedSeqView.Map(this, { (a: A) => f(a); a})(mutationCount) - - override def concat[B >: A](suffix: IndexedSeqView.SomeIndexedSeqOps[B]): IndexedSeqView[B] = new CheckedIndexedSeqView.Concat(this, suffix)(mutationCount) - override def appendedAll[B >: A](suffix: IndexedSeqView.SomeIndexedSeqOps[B]): IndexedSeqView[B] = new CheckedIndexedSeqView.Concat(this, suffix)(mutationCount) - override def prependedAll[B >: A](prefix: IndexedSeqView.SomeIndexedSeqOps[B]): IndexedSeqView[B] = new CheckedIndexedSeqView.Concat(prefix, this)(mutationCount) + override def iterator: Iterator[A]^{this} = new CheckedIndexedSeqView.CheckedIterator(this, mutationCount()) + override def reverseIterator: Iterator[A]^{this} = new CheckedIndexedSeqView.CheckedReverseIterator(this, mutationCount()) + + override def appended[B >: A](elem: B): IndexedSeqView[B]^{this} = new CheckedIndexedSeqView.Appended(this, elem)(mutationCount) + override def prepended[B >: A](elem: B): IndexedSeqView[B]^{this} = new CheckedIndexedSeqView.Prepended(elem, this)(mutationCount) + override def take(n: Int): IndexedSeqView[A]^{this} = new CheckedIndexedSeqView.Take(this, n)(mutationCount) + override def takeRight(n: Int): IndexedSeqView[A]^{this} = new CheckedIndexedSeqView.TakeRight(this, n)(mutationCount) + override def drop(n: Int): IndexedSeqView[A]^{this} = new CheckedIndexedSeqView.Drop(this, n)(mutationCount) + override def dropRight(n: Int): IndexedSeqView[A]^{this} = new CheckedIndexedSeqView.DropRight(this, n)(mutationCount) + override def map[B](f: A => B): IndexedSeqView[B]^{this, f} = new CheckedIndexedSeqView.Map(this, f)(mutationCount) + override def reverse: IndexedSeqView[A]^{this} = new CheckedIndexedSeqView.Reverse(this)(mutationCount) + override def slice(from: Int, until: Int): IndexedSeqView[A]^{this} = new CheckedIndexedSeqView.Slice(this, from, until)(mutationCount) + override def tapEach[U](f: A => U): IndexedSeqView[A]^{this, f} = new CheckedIndexedSeqView.Map(this, { (a: A) => f(a); a})(mutationCount) + + override def concat[B >: A](suffix: IndexedSeqView.SomeIndexedSeqOps[B]^): IndexedSeqView[B]^{this, suffix} = new CheckedIndexedSeqView.Concat(this, suffix)(mutationCount) + override def appendedAll[B >: A](suffix: IndexedSeqView.SomeIndexedSeqOps[B]^): IndexedSeqView[B]^{this, suffix} = new CheckedIndexedSeqView.Concat(this, suffix)(mutationCount) + override def prependedAll[B >: A](prefix: IndexedSeqView.SomeIndexedSeqOps[B]^): IndexedSeqView[B]^{this, prefix} = new CheckedIndexedSeqView.Concat(prefix, this)(mutationCount) } private[mutable] object CheckedIndexedSeqView { import IndexedSeqView.SomeIndexedSeqOps @SerialVersionUID(3L) - private[mutable] class CheckedIterator[A](self: IndexedSeqView[A], mutationCount: => Int) + private[mutable] class CheckedIterator[A](self: IndexedSeqView[A]^, mutationCount: => Int) extends IndexedSeqView.IndexedSeqViewIterator[A](self) { private[this] val expectedCount = mutationCount override def hasNext: Boolean = { @@ -52,7 +53,7 @@ private[mutable] object CheckedIndexedSeqView { } @SerialVersionUID(3L) - private[mutable] class CheckedReverseIterator[A](self: IndexedSeqView[A], mutationCount: => Int) + private[mutable] class CheckedReverseIterator[A](self: IndexedSeqView[A]^, mutationCount: => Int) extends IndexedSeqView.IndexedSeqViewReverseIterator[A](self) { private[this] val expectedCount = mutationCount override def hasNext: Boolean = { @@ -62,43 +63,43 @@ private[mutable] object CheckedIndexedSeqView { } @SerialVersionUID(3L) - class Id[+A](underlying: SomeIndexedSeqOps[A])(protected val mutationCount: () => Int) + class Id[+A](underlying: SomeIndexedSeqOps[A]^)(protected val mutationCount: () => Int) extends IndexedSeqView.Id(underlying) with CheckedIndexedSeqView[A] @SerialVersionUID(3L) - class Appended[+A](underlying: SomeIndexedSeqOps[A], elem: A)(protected val mutationCount: () => Int) + class Appended[+A](underlying: SomeIndexedSeqOps[A]^, elem: A)(protected val mutationCount: () => Int) extends IndexedSeqView.Appended(underlying, elem) with CheckedIndexedSeqView[A] @SerialVersionUID(3L) - class Prepended[+A](elem: A, underlying: SomeIndexedSeqOps[A])(protected val mutationCount: () => Int) + class Prepended[+A](elem: A, underlying: SomeIndexedSeqOps[A]^)(protected val mutationCount: () => Int) extends IndexedSeqView.Prepended(elem, underlying) with CheckedIndexedSeqView[A] @SerialVersionUID(3L) - class Concat[A](prefix: SomeIndexedSeqOps[A], suffix: SomeIndexedSeqOps[A])(protected val mutationCount: () => Int) + class Concat[A](prefix: SomeIndexedSeqOps[A]^, suffix: SomeIndexedSeqOps[A]^)(protected val mutationCount: () => Int) extends IndexedSeqView.Concat[A](prefix, suffix) with CheckedIndexedSeqView[A] @SerialVersionUID(3L) - class Take[A](underlying: SomeIndexedSeqOps[A], n: Int)(protected val mutationCount: () => Int) + class Take[A](underlying: SomeIndexedSeqOps[A]^, n: Int)(protected val mutationCount: () => Int) extends IndexedSeqView.Take(underlying, n) with CheckedIndexedSeqView[A] @SerialVersionUID(3L) - class TakeRight[A](underlying: SomeIndexedSeqOps[A], n: Int)(protected val mutationCount: () => Int) + class TakeRight[A](underlying: SomeIndexedSeqOps[A]^, n: Int)(protected val mutationCount: () => Int) extends IndexedSeqView.TakeRight(underlying, n) with CheckedIndexedSeqView[A] @SerialVersionUID(3L) - class Drop[A](underlying: SomeIndexedSeqOps[A], n: Int)(protected val mutationCount: () => Int) + class Drop[A](underlying: SomeIndexedSeqOps[A]^, n: Int)(protected val mutationCount: () => Int) extends IndexedSeqView.Drop[A](underlying, n) with CheckedIndexedSeqView[A] @SerialVersionUID(3L) - class DropRight[A](underlying: SomeIndexedSeqOps[A], n: Int)(protected val mutationCount: () => Int) + class DropRight[A](underlying: SomeIndexedSeqOps[A]^, n: Int)(protected val mutationCount: () => Int) extends IndexedSeqView.DropRight[A](underlying, n) with CheckedIndexedSeqView[A] @SerialVersionUID(3L) - class Map[A, B](underlying: SomeIndexedSeqOps[A], f: A => B)(protected val mutationCount: () => Int) + class Map[A, B](underlying: SomeIndexedSeqOps[A]^, f: A => B)(protected val mutationCount: () => Int) extends IndexedSeqView.Map(underlying, f) with CheckedIndexedSeqView[B] @SerialVersionUID(3L) - class Reverse[A](underlying: SomeIndexedSeqOps[A])(protected val mutationCount: () => Int) + class Reverse[A](underlying: SomeIndexedSeqOps[A]^)(protected val mutationCount: () => Int) extends IndexedSeqView.Reverse[A](underlying) with CheckedIndexedSeqView[A] { override def reverse: IndexedSeqView[A] = underlying match { case x: IndexedSeqView[A] => x @@ -107,7 +108,7 @@ private[mutable] object CheckedIndexedSeqView { } @SerialVersionUID(3L) - class Slice[A](underlying: SomeIndexedSeqOps[A], from: Int, until: Int)(protected val mutationCount: () => Int) + class Slice[A](underlying: SomeIndexedSeqOps[A]^, from: Int, until: Int)(protected val mutationCount: () => Int) extends AbstractIndexedSeqView[A] with CheckedIndexedSeqView[A] { protected val lo = from max 0 protected val hi = (until max 0) min underlying.length diff --git a/library/src/scala/collection/mutable/Queue.scala b/library/src/scala/collection/mutable/Queue.scala index 7a0c25c049b6..91c2fdd81334 100644 --- a/library/src/scala/collection/mutable/Queue.scala +++ b/library/src/scala/collection/mutable/Queue.scala @@ -14,6 +14,7 @@ package scala.collection package mutable import scala.language.`2.13` +import language.experimental.captureChecking import scala.annotation.nowarn import scala.collection.generic.DefaultSerializable @@ -67,7 +68,7 @@ class Queue[A] protected (array: Array[AnyRef], start: Int, end: Int) * @param elems the iterable object. * @return this */ - def enqueueAll(elems: scala.collection.IterableOnce[A]): this.type = this ++= elems + def enqueueAll(elems: scala.collection.IterableOnce[A]^): this.type = this ++= elems /** * Removes the first element from this queue and returns it @@ -130,7 +131,7 @@ class Queue[A] protected (array: Array[AnyRef], start: Int, end: Int) @SerialVersionUID(3L) object Queue extends StrictOptimizedSeqFactory[Queue] { - def from[A](source: IterableOnce[A]): Queue[A] = empty ++= source + def from[A](source: IterableOnce[A]^): Queue[A] = empty ++= source def empty[A]: Queue[A] = new Queue diff --git a/library/src/scala/collection/mutable/Seq.scala b/library/src/scala/collection/mutable/Seq.scala index cf73d896be64..fed5d12e1a94 100644 --- a/library/src/scala/collection/mutable/Seq.scala +++ b/library/src/scala/collection/mutable/Seq.scala @@ -13,6 +13,7 @@ package scala.collection.mutable import scala.language.`2.13` +import language.experimental.captureChecking import scala.collection.{IterableFactoryDefaults, SeqFactory} trait Seq[A] diff --git a/library/src/scala/collection/mutable/Set.scala b/library/src/scala/collection/mutable/Set.scala index 299cab4ac23c..808132ea2dbc 100644 --- a/library/src/scala/collection/mutable/Set.scala +++ b/library/src/scala/collection/mutable/Set.scala @@ -13,6 +13,7 @@ package scala.collection.mutable import scala.language.`2.13` +import language.experimental.captureChecking import scala.collection.{IterableFactory, IterableFactoryDefaults, IterableOps} /** Base trait for mutable sets */ diff --git a/library/src/scala/collection/mutable/Stack.scala b/library/src/scala/collection/mutable/Stack.scala index f888475c7544..8ca4b58a7af6 100644 --- a/library/src/scala/collection/mutable/Stack.scala +++ b/library/src/scala/collection/mutable/Stack.scala @@ -13,6 +13,7 @@ package scala.collection.mutable import scala.language.`2.13` +import language.experimental.captureChecking import scala.annotation.{migration, nowarn} import scala.collection.generic.DefaultSerializable import scala.collection.{IterableFactoryDefaults, IterableOnce, SeqFactory, StrictOptimizedSeqFactory, StrictOptimizedSeqOps} @@ -77,7 +78,7 @@ class Stack[A] protected (array: Array[AnyRef], start: Int, end: Int) * @param elems the iterable object. * @return the stack with the new elements on top. */ - def pushAll(elems: scala.collection.IterableOnce[A]): this.type = + def pushAll(elems: scala.collection.IterableOnce[A]^): this.type = prependAll(elems match { case it: scala.collection.Seq[A] => it.view.reverse case it => IndexedSeq.from(it).view.reverse @@ -134,7 +135,7 @@ class Stack[A] protected (array: Array[AnyRef], start: Int, end: Int) @SerialVersionUID(3L) object Stack extends StrictOptimizedSeqFactory[Stack] { - def from[A](source: IterableOnce[A]): Stack[A] = empty ++= source + def from[A](source: IterableOnce[A]^): Stack[A] = empty ++= source def empty[A]: Stack[A] = new Stack diff --git a/library/src/scala/collection/mutable/UnrolledBuffer.scala b/library/src/scala/collection/mutable/UnrolledBuffer.scala index 4e60b3555a07..aee908369f0f 100644 --- a/library/src/scala/collection/mutable/UnrolledBuffer.scala +++ b/library/src/scala/collection/mutable/UnrolledBuffer.scala @@ -14,6 +14,7 @@ package scala.collection package mutable import scala.language.`2.13` +import language.experimental.captureChecking import scala.annotation.tailrec import scala.collection.generic.{CommonErrors, DefaultSerializable} import scala.reflect.ClassTag @@ -196,7 +197,7 @@ sealed class UnrolledBuffer[T](implicit val tag: ClassTag[T]) def insert(idx: Int, elem: T): Unit = insertAll(idx, elem :: Nil) - def insertAll(idx: Int, elems: IterableOnce[T]): Unit = + def insertAll(idx: Int, elems: IterableOnce[T]^): Unit = if (idx >= 0 && idx <= sz) { sz += headptr.insertAll(idx, elems, this) } else throw CommonErrors.indexOutOfBounds(index = idx, max = sz - 1) @@ -208,7 +209,7 @@ sealed class UnrolledBuffer[T](implicit val tag: ClassTag[T]) this } - def patchInPlace(from: Int, patch: collection.IterableOnce[T], replaced: Int): this.type = { + def patchInPlace(from: Int, patch: collection.IterableOnce[T]^, replaced: Int): this.type = { remove(from, replaced) insertAll(from, patch) this @@ -248,7 +249,7 @@ object UnrolledBuffer extends StrictOptimizedClassTagSeqFactory[UnrolledBuffer] def empty[A : ClassTag]: UnrolledBuffer[A] = new UnrolledBuffer[A] - def from[A : ClassTag](source: scala.collection.IterableOnce[A]): UnrolledBuffer[A] = newBuilder[A].addAll(source) + def from[A : ClassTag](source: scala.collection.IterableOnce[A]^): UnrolledBuffer[A] = newBuilder[A].addAll(source) def newBuilder[A : ClassTag]: UnrolledBuffer[A] = new UnrolledBuffer[A] @@ -264,6 +265,7 @@ object UnrolledBuffer extends StrictOptimizedClassTagSeqFactory[UnrolledBuffer] /** Unrolled buffer node. */ class Unrolled[T: ClassTag] private[collection] (var size: Int, var array: Array[T], var next: Unrolled[T], val buff: UnrolledBuffer[T] = null) { + this: Unrolled[T]^{} => private[collection] def this() = this(0, new Array[T](unrolledlength), null, null) private[collection] def this(b: UnrolledBuffer[T]) = this(0, new Array[T](unrolledlength), null, b) @@ -378,7 +380,7 @@ object UnrolledBuffer extends StrictOptimizedClassTagSeqFactory[UnrolledBuffer] if (next eq null) true else false // checks if last node was thrown out } else false - @tailrec final def insertAll(idx: Int, t: scala.collection.IterableOnce[T], buffer: UnrolledBuffer[T]): Int = { + @tailrec final def insertAll(idx: Int, t: scala.collection.IterableOnce[T]^, buffer: UnrolledBuffer[T]): Int = { if (idx < size) { // divide this node at the appropriate position and insert all into head // update new next From c49e17bfc91a759fcce2244223482db3db7d7d76 Mon Sep 17 00:00:00 2001 From: Yichen Xu Date: Thu, 14 Aug 2025 17:05:00 +0200 Subject: [PATCH 074/101] Capture check Builder --- .../src/scala/collection/mutable/ArrayBuffer.scala | 2 +- library/src/scala/collection/mutable/Builder.scala | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/library/src/scala/collection/mutable/ArrayBuffer.scala b/library/src/scala/collection/mutable/ArrayBuffer.scala index 7abe265a3897..b8fa9c806e5e 100644 --- a/library/src/scala/collection/mutable/ArrayBuffer.scala +++ b/library/src/scala/collection/mutable/ArrayBuffer.scala @@ -231,7 +231,7 @@ class ArrayBuffer[A] private (initialElements: Array[AnyRef], initialSize: Int) @deprecated("Use 'new GrowableBuilder(this).mapResult(f)' instead", "2.13.0") @deprecatedOverriding("ArrayBuffer[A] no longer extends Builder[A, ArrayBuffer[A]]", "2.13.0") - @inline def mapResult[NewTo](f: (ArrayBuffer[A]) => NewTo): Builder[A, NewTo] = new GrowableBuilder[A, ArrayBuffer[A]](this).mapResult(f) + @inline def mapResult[NewTo](f: (ArrayBuffer[A]) => NewTo): Builder[A, NewTo]^{f} = new GrowableBuilder[A, ArrayBuffer[A]](this).mapResult(f) @nowarn("""cat=deprecation&origin=scala\.collection\.Iterable\.stringPrefix""") override protected[this] def stringPrefix = "ArrayBuffer" diff --git a/library/src/scala/collection/mutable/Builder.scala b/library/src/scala/collection/mutable/Builder.scala index c545929fa33f..715ae02bc519 100644 --- a/library/src/scala/collection/mutable/Builder.scala +++ b/library/src/scala/collection/mutable/Builder.scala @@ -13,6 +13,7 @@ package scala.collection.mutable import scala.language.`2.13` +import language.experimental.captureChecking /** Base trait for collection builders. * @@ -22,7 +23,7 @@ import scala.language.`2.13` * * @see [[scala.collection.mutable.ReusableBuilder]] for Builders which can be reused after calling `result()` */ -trait Builder[-A, +To] extends Growable[A] { self => +trait Builder[-A, +To] extends Growable[A] { self: Builder[A, To]^ => /** Clears the contents of this builder. * After execution of this method the builder will contain no elements. @@ -67,7 +68,7 @@ trait Builder[-A, +To] extends Growable[A] { self => * @param coll the collection which serves as a hint for the result's size. * @param delta a correction to add to the `coll.size` to produce the size hint (zero if omitted). */ - final def sizeHint(coll: scala.collection.IterableOnce[_], delta: Int = 0): Unit = + final def sizeHint(coll: scala.collection.IterableOnce[_]^, delta: Int = 0): Unit = coll.knownSize match { case -1 => case sz => sizeHint(0 max sz + delta) @@ -86,7 +87,7 @@ trait Builder[-A, +To] extends Growable[A] { self => * than collection's size are reduced. */ // should probably be `boundingColl: IterableOnce[_]`, but binary compatibility - final def sizeHintBounded(size: Int, boundingColl: scala.collection.Iterable[_]): Unit = { + final def sizeHintBounded(size: Int, boundingColl: scala.collection.Iterable[_]^): Unit = { val s = boundingColl.knownSize if (s != -1) { sizeHint(scala.math.min(s, size)) @@ -94,10 +95,10 @@ trait Builder[-A, +To] extends Growable[A] { self => } /** A builder resulting from this builder by mapping the result using `f`. */ - def mapResult[NewTo](f: To => NewTo): Builder[A, NewTo] = new Builder[A, NewTo] { + def mapResult[NewTo](f: To => NewTo): Builder[A, NewTo]^{this, f} = new Builder[A, NewTo] { def addOne(x: A): this.type = { self += x; this } def clear(): Unit = self.clear() - override def addAll(xs: IterableOnce[A]): this.type = { self ++= xs; this } + override def addAll(xs: IterableOnce[A]^): this.type = { self ++= xs; this } override def sizeHint(size: Int): Unit = self.sizeHint(size) def result(): NewTo = f(self.result()) override def knownSize: Int = self.knownSize From 25ce3ec8bf85aeebefadc1fa817b44e2f3739417 Mon Sep 17 00:00:00 2001 From: Yichen Xu Date: Thu, 14 Aug 2025 17:16:52 +0200 Subject: [PATCH 075/101] Capture check mutable collections done --- library/src/scala/collection/mutable/Cloneable.scala | 1 + .../src/scala/collection/mutable/GrowableBuilder.scala | 3 ++- library/src/scala/collection/mutable/HashSet.scala | 9 +++++---- library/src/scala/collection/mutable/HashTable.scala | 1 + library/src/scala/collection/mutable/IndexedSeq.scala | 1 + library/src/scala/collection/mutable/LinkedHashSet.scala | 3 ++- .../src/scala/collection/mutable/MutationTracker.scala | 1 + library/src/scala/collection/mutable/PriorityQueue.scala | 9 +++++---- .../src/scala/collection/mutable/ReusableBuilder.scala | 1 + library/src/scala/collection/mutable/SortedSet.scala | 1 + library/src/scala/collection/mutable/StringBuilder.scala | 7 ++++--- library/src/scala/collection/mutable/TreeSet.scala | 4 +++- library/src/scala/collection/mutable/package.scala | 1 + 13 files changed, 28 insertions(+), 14 deletions(-) diff --git a/library/src/scala/collection/mutable/Cloneable.scala b/library/src/scala/collection/mutable/Cloneable.scala index 7480b5bae613..b84c5b225bb1 100644 --- a/library/src/scala/collection/mutable/Cloneable.scala +++ b/library/src/scala/collection/mutable/Cloneable.scala @@ -13,6 +13,7 @@ package scala.collection.mutable import scala.language.`2.13` +import language.experimental.captureChecking /** A trait for cloneable collections. * diff --git a/library/src/scala/collection/mutable/GrowableBuilder.scala b/library/src/scala/collection/mutable/GrowableBuilder.scala index e98aa16c2581..cb18a6838b0f 100644 --- a/library/src/scala/collection/mutable/GrowableBuilder.scala +++ b/library/src/scala/collection/mutable/GrowableBuilder.scala @@ -14,6 +14,7 @@ package scala package collection.mutable import scala.language.`2.13` +import language.experimental.captureChecking /** The canonical builder for collections that are growable, i.e. that support an * efficient `+=` method which adds an element to the collection. @@ -32,7 +33,7 @@ class GrowableBuilder[Elem, To <: Growable[Elem]](protected val elems: To) def addOne(elem: Elem): this.type = { elems += elem; this } - override def addAll(xs: IterableOnce[Elem]): this.type = { elems.addAll(xs); this } + override def addAll(xs: IterableOnce[Elem]^): this.type = { elems.addAll(xs); this } override def knownSize: Int = elems.knownSize } diff --git a/library/src/scala/collection/mutable/HashSet.scala b/library/src/scala/collection/mutable/HashSet.scala index df0cb0a9ee33..a92db177c110 100644 --- a/library/src/scala/collection/mutable/HashSet.scala +++ b/library/src/scala/collection/mutable/HashSet.scala @@ -14,6 +14,7 @@ package scala.collection package mutable import scala.language.`2.13` +import language.experimental.captureChecking import scala.annotation.tailrec import scala.collection.Stepper.EfficientSplit import scala.collection.generic.DefaultSerializationProxy @@ -91,7 +92,7 @@ final class HashSet[A](initialCapacity: Int, loadFactor: Double) addElem(elem, computeHash(elem)) } - override def addAll(xs: IterableOnce[A]): this.type = { + override def addAll(xs: IterableOnce[A]^): this.type = { sizeHint(xs, delta = 0) xs match { case hs: immutable.HashSet[A] => @@ -115,7 +116,7 @@ final class HashSet[A](initialCapacity: Int, loadFactor: Double) } } - override def subtractAll(xs: IterableOnce[A]): this.type = { + override def subtractAll(xs: IterableOnce[A]^): this.type = { if (size == 0) { return this } @@ -407,7 +408,7 @@ final class HashSet[A](initialCapacity: Int, loadFactor: Double) @SerialVersionUID(3L) object HashSet extends IterableFactory[HashSet] { - def from[B](it: scala.collection.IterableOnce[B]): HashSet[B] = { + def from[B](it: scala.collection.IterableOnce[B]^): HashSet[B] = { val k = it.knownSize val cap = if(k > 0) ((k + 1).toDouble / defaultLoadFactor).toInt else defaultInitialCapacity new HashSet[B](cap, defaultLoadFactor) ++= it @@ -430,7 +431,7 @@ object HashSet extends IterableFactory[HashSet] { @SerialVersionUID(3L) private final class DeserializationFactory[A](val tableLength: Int, val loadFactor: Double) extends Factory[A, HashSet[A]] with Serializable { - def fromSpecific(it: IterableOnce[A]): HashSet[A] = new HashSet[A](tableLength, loadFactor) ++= it + def fromSpecific(it: IterableOnce[A]^): HashSet[A] = new HashSet[A](tableLength, loadFactor) ++= it def newBuilder: Builder[A, HashSet[A]] = HashSet.newBuilder(tableLength, loadFactor) } diff --git a/library/src/scala/collection/mutable/HashTable.scala b/library/src/scala/collection/mutable/HashTable.scala index 08bdd68eece3..738adce7dfaa 100644 --- a/library/src/scala/collection/mutable/HashTable.scala +++ b/library/src/scala/collection/mutable/HashTable.scala @@ -14,6 +14,7 @@ package scala package collection.mutable import scala.language.`2.13` +import language.experimental.captureChecking import collection.{AbstractIterator, Iterator} import java.lang.Integer.{numberOfLeadingZeros, rotateRight} diff --git a/library/src/scala/collection/mutable/IndexedSeq.scala b/library/src/scala/collection/mutable/IndexedSeq.scala index c965bb076844..bb1af54d3088 100644 --- a/library/src/scala/collection/mutable/IndexedSeq.scala +++ b/library/src/scala/collection/mutable/IndexedSeq.scala @@ -14,6 +14,7 @@ package scala.collection package mutable import scala.language.`2.13` +import language.experimental.captureChecking trait IndexedSeq[T] extends Seq[T] with scala.collection.IndexedSeq[T] diff --git a/library/src/scala/collection/mutable/LinkedHashSet.scala b/library/src/scala/collection/mutable/LinkedHashSet.scala index 79d3d62a41d1..f0c2d2d843c3 100644 --- a/library/src/scala/collection/mutable/LinkedHashSet.scala +++ b/library/src/scala/collection/mutable/LinkedHashSet.scala @@ -15,6 +15,7 @@ package collection package mutable import scala.language.`2.13` +import language.experimental.captureChecking import scala.annotation.{nowarn, tailrec} import scala.collection.generic.DefaultSerializable import scala.util.hashing.MurmurHash3 @@ -317,7 +318,7 @@ object LinkedHashSet extends IterableFactory[LinkedHashSet] { override def empty[A]: LinkedHashSet[A] = new LinkedHashSet[A] - def from[E](it: collection.IterableOnce[E]) = { + def from[E](it: collection.IterableOnce[E]^) = { val newlhs = empty[E] newlhs.sizeHint(it, delta = 0) newlhs.addAll(it) diff --git a/library/src/scala/collection/mutable/MutationTracker.scala b/library/src/scala/collection/mutable/MutationTracker.scala index 6b41ac22db37..ddd3a912a840 100644 --- a/library/src/scala/collection/mutable/MutationTracker.scala +++ b/library/src/scala/collection/mutable/MutationTracker.scala @@ -15,6 +15,7 @@ package collection package mutable import scala.language.`2.13` +import language.experimental.captureChecking import java.util.ConcurrentModificationException /** diff --git a/library/src/scala/collection/mutable/PriorityQueue.scala b/library/src/scala/collection/mutable/PriorityQueue.scala index f0146450e50f..050fa5159ee6 100644 --- a/library/src/scala/collection/mutable/PriorityQueue.scala +++ b/library/src/scala/collection/mutable/PriorityQueue.scala @@ -14,6 +14,7 @@ package scala.collection package mutable import scala.language.`2.13` +import language.experimental.captureChecking import scala.collection.generic.DefaultSerializationProxy import scala.math.Ordering @@ -112,7 +113,7 @@ sealed class PriorityQueue[A](implicit val ord: Ordering[A]) override def isEmpty: Boolean = resarr.p_size0 < 2 // not eligible for EvidenceIterableFactoryDefaults since C != CC[A] (PriorityQueue[A] != Iterable[A]) - override protected def fromSpecific(coll: scala.collection.IterableOnce[A]): PriorityQueue[A] = PriorityQueue.from(coll) + override protected def fromSpecific(coll: scala.collection.IterableOnce[A]^): PriorityQueue[A] = PriorityQueue.from(coll) override protected def newSpecificBuilder: Builder[A, PriorityQueue[A]] = PriorityQueue.newBuilder override def empty: PriorityQueue[A] = PriorityQueue.empty @@ -172,7 +173,7 @@ sealed class PriorityQueue[A](implicit val ord: Ordering[A]) this } - override def addAll(xs: IterableOnce[A]): this.type = { + override def addAll(xs: IterableOnce[A]^): this.type = { val from = resarr.p_size0 for (x <- xs.iterator) unsafeAdd(x) heapify(from) @@ -238,7 +239,7 @@ sealed class PriorityQueue[A](implicit val ord: Ordering[A]) * @param xs a iterable object. * @return a new priority queue containing elements of both `xs` and `this`. */ - def ++(xs: IterableOnce[A]): PriorityQueue[A] = { this.clone() ++= xs } + def ++(xs: IterableOnce[A]^): PriorityQueue[A] = { this.clone() ++= xs } /** Adds all elements to the queue. * @@ -406,7 +407,7 @@ object PriorityQueue extends SortedIterableFactory[PriorityQueue] { def empty[A : Ordering]: PriorityQueue[A] = new PriorityQueue[A] - def from[E : Ordering](it: IterableOnce[E]): PriorityQueue[E] = { + def from[E : Ordering](it: IterableOnce[E]^): PriorityQueue[E] = { val b = newBuilder[E] b ++= it b.result() diff --git a/library/src/scala/collection/mutable/ReusableBuilder.scala b/library/src/scala/collection/mutable/ReusableBuilder.scala index 1cba786c749a..0523c2c3066e 100644 --- a/library/src/scala/collection/mutable/ReusableBuilder.scala +++ b/library/src/scala/collection/mutable/ReusableBuilder.scala @@ -15,6 +15,7 @@ package collection package mutable import scala.language.`2.13` +import language.experimental.captureChecking /** `ReusableBuilder` is a marker trait that indicates that a `Builder` * can be reused to build more than one instance of a collection. In diff --git a/library/src/scala/collection/mutable/SortedSet.scala b/library/src/scala/collection/mutable/SortedSet.scala index dbb31ba5a18d..e56fdc2859f6 100644 --- a/library/src/scala/collection/mutable/SortedSet.scala +++ b/library/src/scala/collection/mutable/SortedSet.scala @@ -15,6 +15,7 @@ package collection package mutable import scala.language.`2.13` +import language.experimental.captureChecking /** Base type for mutable sorted set collections */ diff --git a/library/src/scala/collection/mutable/StringBuilder.scala b/library/src/scala/collection/mutable/StringBuilder.scala index dfd778b44b34..91baa747f219 100644 --- a/library/src/scala/collection/mutable/StringBuilder.scala +++ b/library/src/scala/collection/mutable/StringBuilder.scala @@ -13,6 +13,7 @@ package scala.collection.mutable import scala.language.`2.13` +import language.experimental.captureChecking import scala.collection.{IterableFactoryDefaults, IterableOnce} import scala.collection.immutable.WrappedString @@ -82,7 +83,7 @@ final class StringBuilder(val underlying: java.lang.StringBuilder) extends Abstr // Methods required to make this an IndexedSeq: def apply(i: Int): Char = underlying.charAt(i) - override protected def fromSpecific(coll: scala.collection.IterableOnce[Char]): StringBuilder = + override protected def fromSpecific(coll: scala.collection.IterableOnce[Char]^): StringBuilder = new StringBuilder() appendAll coll override protected def newSpecificBuilder: Builder[Char, StringBuilder] = @@ -185,7 +186,7 @@ final class StringBuilder(val underlying: java.lang.StringBuilder) extends Abstr * @param xs the characters to be appended. * @return this StringBuilder. */ - def appendAll(xs: IterableOnce[Char]): this.type = { + def appendAll(xs: IterableOnce[Char]^): this.type = { xs match { case x: WrappedString => underlying append x.unwrap case x: ArraySeq.ofChar => underlying append x.array @@ -314,7 +315,7 @@ final class StringBuilder(val underlying: java.lang.StringBuilder) extends Abstr * @return this StringBuilder. * @throws StringIndexOutOfBoundsException if the index is out of bounds. */ - def insertAll(index: Int, xs: IterableOnce[Char]): this.type = + def insertAll(index: Int, xs: IterableOnce[Char]^): this.type = insertAll(index, (ArrayBuilder.make[Char] ++= xs).result()) /** Inserts the given Array[Char] into this sequence at the given index. diff --git a/library/src/scala/collection/mutable/TreeSet.scala b/library/src/scala/collection/mutable/TreeSet.scala index 4f302d46906e..f54423cbe12d 100644 --- a/library/src/scala/collection/mutable/TreeSet.scala +++ b/library/src/scala/collection/mutable/TreeSet.scala @@ -14,6 +14,7 @@ package scala package collection.mutable import scala.language.`2.13` +import language.experimental.captureChecking import scala.collection.Stepper.EfficientSplit import scala.collection.generic.DefaultSerializable import scala.collection.mutable.{RedBlackTree => RB} @@ -115,6 +116,7 @@ sealed class TreeSet[A] private (private val tree: RB.Tree[A, Null])(implicit va * bound. */ private[this] final class TreeSetProjection(from: Option[A], until: Option[A]) extends TreeSet[A](tree) { + self: TreeSetProjection^{} => /** * Given a possible new lower bound, chooses and returns the most constraining one (the maximum). @@ -194,7 +196,7 @@ object TreeSet extends SortedIterableFactory[TreeSet] { def empty[A : Ordering]: TreeSet[A] = new TreeSet[A]() - def from[E](it: IterableOnce[E])(implicit ordering: Ordering[E]): TreeSet[E] = + def from[E](it: IterableOnce[E]^)(implicit ordering: Ordering[E]): TreeSet[E] = it match { case ts: TreeSet[E] if ordering == ts.ordering => new TreeSet[E](ts.tree.treeCopy()) diff --git a/library/src/scala/collection/mutable/package.scala b/library/src/scala/collection/mutable/package.scala index 475aec0afea7..5013839d5587 100644 --- a/library/src/scala/collection/mutable/package.scala +++ b/library/src/scala/collection/mutable/package.scala @@ -13,6 +13,7 @@ package scala.collection import scala.language.`2.13` +import language.experimental.captureChecking package object mutable { @deprecated("Use ArraySeq instead of WrappedArray; it can represent both, boxed and unboxed arrays", "2.13.0") From 849254c97abf60fd6619014999e64b067eea535d Mon Sep 17 00:00:00 2001 From: Yichen Xu Date: Thu, 14 Aug 2025 17:18:19 +0200 Subject: [PATCH 076/101] Capture check ImmutableBuilder --- library/src/scala/collection/mutable/ImmutableBuilder.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/library/src/scala/collection/mutable/ImmutableBuilder.scala b/library/src/scala/collection/mutable/ImmutableBuilder.scala index 6806367e53a4..5c263dab8ed7 100644 --- a/library/src/scala/collection/mutable/ImmutableBuilder.scala +++ b/library/src/scala/collection/mutable/ImmutableBuilder.scala @@ -15,6 +15,7 @@ package collection package mutable import scala.language.`2.13` +import language.experimental.captureChecking /** * Reusable builder for immutable collections From 5dd6c8105b7e9c2817f72e7f0cd94daabb017da5 Mon Sep 17 00:00:00 2001 From: Yichen Xu Date: Thu, 14 Aug 2025 17:24:44 +0200 Subject: [PATCH 077/101] One missing carrot --- library/src/scala/collection/mutable/LongMap.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/src/scala/collection/mutable/LongMap.scala b/library/src/scala/collection/mutable/LongMap.scala index e4b2cecbf1af..208c4be0910e 100644 --- a/library/src/scala/collection/mutable/LongMap.scala +++ b/library/src/scala/collection/mutable/LongMap.scala @@ -574,7 +574,7 @@ final class LongMap[V] private[collection] (defaultEntry: Long -> V, initialBuff * * @param f the mapping function */ - def flatMap[V2](f: ((Long, V)) => IterableOnce[(Long, V2)]): LongMap[V2] = LongMap.from(new View.FlatMap(coll, f)) + def flatMap[V2](f: ((Long, V)) => IterableOnce[(Long, V2)]^): LongMap[V2] = LongMap.from(new View.FlatMap(coll, f)) /** An overload of `collect` which produces a `LongMap`. * From 4222ebaa61e9b8bda3d80db2a3233d2e1ee30efe Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Thu, 14 Aug 2025 18:39:17 +0200 Subject: [PATCH 078/101] Capture check LazyList as LazyListIterable --- library/src/scala/collection/Iterable.scala | 10 +- ...{LazyList.scala => LazyListIterable.scala} | 639 ++++++++++-------- 2 files changed, 349 insertions(+), 300 deletions(-) rename library/src/scala/collection/immutable/{LazyList.scala => LazyListIterable.scala} (60%) diff --git a/library/src/scala/collection/Iterable.scala b/library/src/scala/collection/Iterable.scala index 9fc797513496..9018667d1e18 100644 --- a/library/src/scala/collection/Iterable.scala +++ b/library/src/scala/collection/Iterable.scala @@ -380,9 +380,9 @@ transparent trait IterableOps[+A, +CC[_], +C] extends Any with IterableOnce[A] w * @throws IllegalArgumentException if all collections in this $coll * are not of the same size. */ - def transpose[B](implicit asIterable: A -> /*<: /*<: JStringBuilder} @@ -32,30 +34,30 @@ import scala.runtime.Statics * Elements are computed in order and are never skipped. * As a consequence, accessing the tail causes the head to be computed first. * - * How lazy is a `LazyList`? When you have a value of type `LazyList`, you + * How lazy is a `LazyListIterable`? When you have a value of type `LazyListIterable`, you * don't know yet whether the list is empty. * We say that it is lazy in its head. * If you have tested that it is non-empty, * then you also know that the head has been computed. * - * It is also lazy in its tail, which is also a `LazyList`. + * It is also lazy in its tail, which is also a `LazyListIterable`. * You don't know whether the tail is empty until it is "forced", which is to say, * until an element of the tail is computed. * - * These important properties of `LazyList` depend on its construction using `#::` (or `#:::`). + * These important properties of `LazyListIterable` depend on its construction using `#::` (or `#:::`). * That operator is analogous to the "cons" of a strict `List`, `::`. * It is "right-associative", so that the collection goes on the "right", * and the element on the left of the operator is prepended to the collection. * However, unlike the cons of a strict `List`, `#::` is lazy in its parameter, * which is the element prepended to the left, and also lazy in its right-hand side, - * which is the `LazyList` being prepended to. - * (That is accomplished by implicitly wrapping the `LazyList`, as shown in the Scaladoc.) + * which is the `LazyListIterable` being prepended to. + * (That is accomplished by implicitly wrapping the `LazyListIterable`, as shown in the Scaladoc.) * * Other combinators from the collections API do not preserve this laziness. * In particular, `++`, or `concat`, is "eager" or "strict" in its parameter - * and should not be used to compose `LazyList`s. + * and should not be used to compose `LazyListIterable`s. * - * A `LazyList` may be infinite. For example, `LazyList.from(0)` contains + * A `LazyListIterable` may be infinite. For example, `LazyListIterable.from(0)` contains * all of the natural numbers `0`, `1`, `2`, ... For infinite sequences, * some methods (such as `count`, `sum`, `max` or `min`) will not terminate. * @@ -65,7 +67,7 @@ import scala.runtime.Statics * {{{ * import scala.math.BigInt * object Main extends App { - * val fibs: LazyList[BigInt] = + * val fibs: LazyListIterable[BigInt] = * BigInt(0) #:: BigInt(1) #:: fibs.zip(fibs.tail).map(n => n._1 + n._2) * println { * fibs.take(5).mkString(", ") @@ -81,7 +83,7 @@ import scala.runtime.Statics * import scala.math.BigInt * import scala.util.chaining._ * object Main extends App { - * val fibs: LazyList[BigInt] = + * val fibs: LazyListIterable[BigInt] = * BigInt(0) #:: BigInt(1) #:: * fibs.zip(fibs.tail).map(n => (n._1 + n._2) * .tap(sum => println(s"Adding ${n._1} and ${n._2} => $sum"))) @@ -112,11 +114,11 @@ import scala.runtime.Statics * }}} * * Note that the definition of `fibs` uses `val` not `def`. - * Memoization of the `LazyList` requires us to retain a reference to the computed values. + * Memoization of the `LazyListIterable` requires us to retain a reference to the computed values. * - * `LazyList` is considered an immutable data structure, even though its elements are computed on demand. + * `LazyListIterable` is considered an immutable data structure, even though its elements are computed on demand. * Once the values are memoized they do not change. - * Moreover, the `LazyList` itself is defined once and references to it are interchangeable. + * Moreover, the `LazyListIterable` itself is defined once and references to it are interchangeable. * Values that have yet to be memoized still "exist"; they simply haven't been computed yet. * * Memoization can be a source of memory leaks and must be used with caution. @@ -140,30 +142,30 @@ import scala.runtime.Statics * } * } * - * // Our first LazyList definition will be a val definition - * val lazylist1: LazyList[Int] = { - * def loop(v: Int): LazyList[Int] = v #:: loop(v + 1) + * // Our first LazyListIterable definition will be a val definition + * val lazylist1: LazyListIterable[Int] = { + * def loop(v: Int): LazyListIterable[Int] = v #:: loop(v + 1) * loop(0) * } * * // Because lazylist1 is a val, everything that the iterator produces is held - * // by virtue of the fact that the head of the LazyList is held in lazylist1 + * // by virtue of the fact that the head of the LazyListIterable is held in lazylist1 * val it1 = lazylist1.iterator * loop("Iterator1: ", it1.next(), it1) * - * // We can redefine this LazyList such that we retain only a reference to its Iterator. - * // That allows the LazyList to be garbage collected. - * // Using `def` to produce the LazyList in a method ensures + * // We can redefine this LazyListIterable such that we retain only a reference to its Iterator. + * // That allows the LazyListIterable to be garbage collected. + * // Using `def` to produce the LazyListIterable in a method ensures * // that no val is holding onto the head, as with lazylist1. - * def lazylist2: LazyList[Int] = { - * def loop(v: Int): LazyList[Int] = v #:: loop(v + 1) + * def lazylist2: LazyListIterable[Int] = { + * def loop(v: Int): LazyListIterable[Int] = v #:: loop(v + 1) * loop(0) * } * val it2 = lazylist2.iterator * loop("Iterator2: ", it2.next(), it2) * - * // And, of course, we don't actually need a LazyList at all for such a simple - * // problem. There's no reason to use a LazyList if you don't actually need + * // And, of course, we don't actually need a LazyListIterable at all for such a simple + * // problem. There's no reason to use a LazyListIterable if you don't actually need * // one. * val it3 = new Iterator[Int] { * var i = -1 @@ -174,7 +176,7 @@ import scala.runtime.Statics * }}} * * In the `fibs` example earlier, the fact that `tail` works at all is of interest. - * `fibs` has an initial `(0, 1, LazyList(...))`, so `tail` is deterministic. + * `fibs` has an initial `(0, 1, LazyListIterable(...))`, so `tail` is deterministic. * If we defined `fibs` such that only `0` were concretely known, then the act * of determining `tail` would require the evaluation of `tail`, so the * computation would be unable to progress, as in this code: @@ -182,7 +184,7 @@ import scala.runtime.Statics * // The first time we try to access the tail we're going to need more * // information which will require us to recurse, which will require us to * // recurse, which... - * lazy val sov: LazyList[Vector[Int]] = Vector(0) #:: sov.zip(sov.tail).map { n => n._1 ++ n._2 } + * lazy val sov: LazyListIterable[Vector[Int]] = Vector(0) #:: sov.zip(sov.tail).map { n => n._1 ++ n._2 } * }}} * * The definition of `fibs` above creates a larger number of objects than @@ -191,8 +193,8 @@ import scala.runtime.Statics * fact that it has a more direct route to the numbers themselves: * * {{{ - * lazy val fib: LazyList[Int] = { - * def loop(h: Int, n: Int): LazyList[Int] = h #:: loop(n, h + n) + * lazy val fib: LazyListIterable[Int] = { + * def loop(h: Int, n: Int): LazyListIterable[Int] = h #:: loop(n, h + n) * loop(1, 1) * } * }}} @@ -203,25 +205,25 @@ import scala.runtime.Statics * the tail's content is deferred until the tail's empty status, head or tail is * evaluated. * - * Delaying the evaluation of whether a LazyList is empty until it's needed - * allows LazyList to not eagerly evaluate any elements on a call to `filter`. + * Delaying the evaluation of whether a LazyListIterable is empty until it's needed + * allows LazyListIterable to not eagerly evaluate any elements on a call to `filter`. * * Only when it's further evaluated (which may be never!) do any of the elements get forced. * * For example: * * {{{ - * def tailWithSideEffect: LazyList[Nothing] = { - * println("getting empty LazyList") - * LazyList.empty + * def tailWithSideEffect: LazyListIterable[Nothing] = { + * println("getting empty LazyListIterable") + * LazyListIterable.empty * } * - * val emptyTail = tailWithSideEffect // prints "getting empty LazyList" + * val emptyTail = tailWithSideEffect // prints "getting empty LazyListIterable" * * val suspended = 1 #:: tailWithSideEffect // doesn't print anything * val tail = suspended.tail // although the tail is evaluated, *still* nothing is yet printed * val filtered = tail.filter(_ => false) // still nothing is printed - * filtered.isEmpty // prints "getting empty LazyList" + * filtered.isEmpty // prints "getting empty LazyListIterable" * }}} * * ---- @@ -229,15 +231,15 @@ import scala.runtime.Statics * You may sometimes encounter an exception like the following: * * {{{ - * java.lang.RuntimeException: "LazyList evaluation depends on its own result (self-reference); see docs for more info + * java.lang.RuntimeException: "LazyListIterable evaluation depends on its own result (self-reference); see docs for more info * }}} * - * This exception occurs when a `LazyList` is attempting to derive its next element + * This exception occurs when a `LazyListIterable` is attempting to derive its next element * from itself, and is attempting to read the element currently being evaluated. * As a trivial example: * * {{{ - * lazy val a: LazyList[Int] = 1 #:: 2 #:: a.filter(_ > 2) + * lazy val a: LazyListIterable[Int] = 1 #:: 2 #:: a.filter(_ > 2) * }}} * * When attempting to evaluate the third element of `a`, it will skip the first two @@ -247,9 +249,9 @@ import scala.runtime.Statics * * @tparam A the type of the elements contained in this lazy list. * - * @see [[https://docs.scala-lang.org/overviews/collections-2.13/concrete-immutable-collection-classes.html#lazylists "Scala's Collection Library overview"]] - * section on `LazyLists` for a summary. - * @define Coll `LazyList` + * @see [[https://docs.scala-lang.org/overviews/collections-2.13/concrete-immutable-collection-classes.html#lazylistsIterable "Scala's Collection Library overview"]] + * section on `LazyListIterables` for a summary. + * @define Coll `LazyListIterable` * @define coll lazy list * @define orderDependent * @define orderDependentFold @@ -264,20 +266,19 @@ import scala.runtime.Statics * @define evaluatesAllElements This method evaluates all elements of the collection. */ @SerialVersionUID(4L) -final class LazyList[+A] private (lazyState: AnyRef /* EmptyMarker.type | () => LazyList[A] */) - extends AbstractSeq[A] - with LinearSeq[A] - with LinearSeqOps[A, LazyList, LazyList[A]] - with IterableFactoryDefaults[A, LazyList] - with Serializable { - import LazyList._ +final class LazyListIterable[+A] private (lazyState: LazyListIterable.EmptyMarker.type | (() => LazyListIterable[A]^) /* EmptyMarker.type | () => LazyListIterable[A] */) + extends Iterable[A] + with collection.SeqOps[A, LazyListIterable, LazyListIterable[A]] + with IterableFactoryDefaults[A, LazyListIterable] + with Serializable { self: LazyListIterable[A]^ => + import LazyListIterable._ // kount() // LazyListTest.countAlloc - private def this(head: A, tail: LazyList[A]) = { - this(LazyList.EmptyMarker) + private def this(head: A, tail: LazyListIterable[A]^) = { + this(LazyListIterable.EmptyMarker) _head = head - _tail = tail + _tail = caps.unsafe.unsafeAssumePure(tail) // SAFETY: we initialize LazyListIterable capturing tail } // used to synchronize lazy state evaluation @@ -288,16 +289,16 @@ final class LazyList[+A] private (lazyState: AnyRef /* EmptyMarker.type | () => if (lazyState eq EmptyMarker) null else Uninitialized // when `_head eq Uninitialized` - // - `lazySate: () => LazyList[A]` + // - `lazySate: () => LazyListIterable[A]` // - MidEvaluation while evaluating lazyState // when `_head ne Uninitialized` // - `null` if this is an empty lazy list - // - `tail: LazyList[A]` otherwise - private[this] var _tail: AnyRef /* () => LazyList[A] | MidEvaluation.type | LazyList[A] */ = + // - `tail: LazyListIterable[A]` otherwise + private[this] var _tail: AnyRef^{this} /* () => LazyListIterable[A] | MidEvaluation.type | LazyListIterable[A] */ = if (lazyState eq EmptyMarker) null else lazyState private def rawHead: Any = _head - private def rawTail: AnyRef = _tail + private def rawTail: AnyRef^{this} = _tail @inline private def isEvaluated: Boolean = _head.asInstanceOf[AnyRef] ne Uninitialized @@ -307,12 +308,12 @@ final class LazyList[+A] private (lazyState: AnyRef /* EmptyMarker.type | () => // self-referential loop (also it's empty) if (_tail eq MidEvaluation) throw new RuntimeException( - "LazyList evaluation depends on its own result (self-reference); see docs for more info") + "LazyListIterable evaluation depends on its own result (self-reference); see docs for more info") - val fun = _tail.asInstanceOf[() => LazyList[A]] + val fun = _tail.asInstanceOf[() ->{this} LazyListIterable[A]^] _tail = MidEvaluation val l = - // `fun` returns a LazyList that represents the state (head/tail) of `this`. We call `l.evaluated` to ensure + // `fun` returns a LazyListIterable that represents the state (head/tail) of `this`. We call `l.evaluated` to ensure // `l` is initialized, to prevent races when reading `rawTail` / `rawHead` below. // Often, lazy lists are created with `newLL(eagerCons(...))` so `l` is already initialized, but `newLL` also // accepts non-evaluated lazy lists. @@ -324,7 +325,7 @@ final class LazyList[+A] private (lazyState: AnyRef /* EmptyMarker.type | () => } } - @tailrec private def evaluated: LazyList[A] = + @tailrec private def evaluated: LazyListIterable[A]^{this} = if (isEvaluated) { if (_tail == null) Empty else this @@ -333,7 +334,7 @@ final class LazyList[+A] private (lazyState: AnyRef /* EmptyMarker.type | () => evaluated } - override def iterableFactory: SeqFactory[LazyList] = LazyList + override def iterableFactory: IterableFactory[LazyListIterable] = LazyListIterable // NOTE: `evaluated; this eq Empty` would be wrong. Deserialization of `Empty` creates a new // instance with `null` fields, but the `evaluated` method always returns the canonical `Empty`. @@ -350,10 +351,32 @@ final class LazyList[+A] private (lazyState: AnyRef /* EmptyMarker.type | () => if (evaluated eq Empty) throw new NoSuchElementException("head of empty lazy list") else rawHead.asInstanceOf[A] - override def tail: LazyList[A] = + override def tail: LazyListIterable[A]^{this} = // inlined `isEmpty` to make it clear that `rawTail` below is initialized if (evaluated eq Empty) throw new UnsupportedOperationException("tail of empty lazy list") - else rawTail.asInstanceOf[LazyList[A]] + else rawTail.asInstanceOf[LazyListIterable[A]] + + /* Same implementation as of LinearSeq */ + override def length: Int = { + var these: LazyListIterable[A]^{this} = coll + var len = 0 + while (these.nonEmpty) { + len += 1 + these = these.tail + } + len + } + + /* Same implementation as of LinearSeq */ + // `apply` is defined in terms of `drop`, which is in turn defined in + // terms of `tail`. + @throws[IndexOutOfBoundsException] + override def apply(n: Int): A = { + if (n < 0) throw new IndexOutOfBoundsException(n.toString) + val skipped = drop(n) + if (skipped.isEmpty) throw new IndexOutOfBoundsException(n.toString) + skipped.head + } @inline private[this] def knownIsEmpty: Boolean = isEvaluated && isEmpty @inline private def knownNonEmpty: Boolean = isEvaluated && !isEmpty @@ -364,13 +387,13 @@ final class LazyList[+A] private (lazyState: AnyRef /* EmptyMarker.type | () => * elements of the cycle are evaluated. For example: * * {{{ - * val ring: LazyList[Int] = 1 #:: 2 #:: 3 #:: ring + * val ring: LazyListIterable[Int] = 1 #:: 2 #:: 3 #:: ring * ring.force * ring.toString * * // prints * // - * // LazyList(1, 2, 3, ...) + * // LazyListIterable(1, 2, 3, ...) * }}} * * This method will *not* terminate for non-cyclic infinite-sized collections. @@ -379,7 +402,7 @@ final class LazyList[+A] private (lazyState: AnyRef /* EmptyMarker.type | () => */ def force: this.type = { // Use standard 2x 1x iterator trick for cycle detection ("those" is slow one) - var these, those: LazyList[A] = this + var these, those: LazyListIterable[A]^{this} = this if (!these.isEmpty) { these = these.tail } @@ -399,7 +422,7 @@ final class LazyList[+A] private (lazyState: AnyRef /* EmptyMarker.type | () => * The iterator returned by this method preserves laziness; elements are * only evaluated individually as needed. */ - override def iterator: Iterator[A] = + override def iterator: Iterator[A]^{this} = if (knownIsEmpty) Iterator.empty else new LazyIterator(this) @@ -409,9 +432,9 @@ final class LazyList[+A] private (lazyState: AnyRef /* EmptyMarker.type | () => * @param f The treatment to apply to each element. * @note Overridden here as final to trigger tail-call optimization, which * replaces 'this' with 'tail' at each iteration. This is absolutely - * necessary for allowing the GC to collect the underlying LazyList as elements + * necessary for allowing the GC to collect the underlying LazyListIterable as elements * are consumed. - * @note This function will force the realization of the entire LazyList + * @note This function will force the realization of the entire LazyListIterable * unless the `f` throws an exception. */ @tailrec @@ -422,12 +445,12 @@ final class LazyList[+A] private (lazyState: AnyRef /* EmptyMarker.type | () => } } - /** LazyList specialization of foldLeft which allows GC to collect along the + /** LazyListIterable specialization of foldLeft which allows GC to collect along the * way. * * @tparam B The type of value being accumulated. * @param z The initial value seeded into the function `op`. - * @param op The operation to perform on successive elements of the `LazyList`. + * @param op The operation to perform on successive elements of the `LazyListIterable`. * @return The accumulated value from successive applications of `op`. */ @tailrec @@ -435,11 +458,11 @@ final class LazyList[+A] private (lazyState: AnyRef /* EmptyMarker.type | () => if (isEmpty) z else tail.foldLeft(op(z, head))(op) - // LazyList.Empty doesn't use the SerializationProxy - protected[this] def writeReplace(): AnyRef = + // LazyListIterable.Empty doesn't use the SerializationProxy + protected[this] def writeReplace(): AnyRef^{this} = if (knownNonEmpty) new SerializationProxy[A](this) else this - override protected[this] def className = "LazyList" + override protected[this] def className = "LazyListIterable" /** The lazy list resulting from the concatenation of this lazy list with the argument lazy list. * @@ -450,10 +473,10 @@ final class LazyList[+A] private (lazyState: AnyRef /* EmptyMarker.type | () => * @param suffix The collection that gets appended to this lazy list * @return The lazy list containing elements of this lazy list and the iterable object. */ - def lazyAppendedAll[B >: A](suffix: => collection.IterableOnce[B]): LazyList[B] = + def lazyAppendedAll[B >: A](suffix: => collection.IterableOnce[B]^): LazyListIterable[B]^{this, suffix} = newLL { if (isEmpty) suffix match { - case lazyList: LazyList[B] => lazyList // don't recompute the LazyList + case lazyList: LazyListIterable[B] => lazyList // don't recompute the LazyListIterable case coll if coll.knownSize == 0 => Empty case coll => eagerHeadFromIterator(coll.iterator) } @@ -466,8 +489,8 @@ final class LazyList[+A] private (lazyState: AnyRef /* EmptyMarker.type | () => * * $appendStackSafety */ - override def appendedAll[B >: A](suffix: IterableOnce[B]): LazyList[B] = - if (knownIsEmpty) LazyList.from(suffix) + override def appendedAll[B >: A](suffix: IterableOnce[B]^): LazyListIterable[B]^{this, suffix} = + if (knownIsEmpty) LazyListIterable.from(suffix) else lazyAppendedAll(suffix) /** @inheritdoc @@ -476,7 +499,7 @@ final class LazyList[+A] private (lazyState: AnyRef /* EmptyMarker.type | () => * * $appendStackSafety */ - override def appended[B >: A](elem: B): LazyList[B] = + override def appended[B >: A](elem: B): LazyListIterable[B]^{this} = if (knownIsEmpty) eagerCons(elem, Empty) else lazyAppendedAll(Iterator.single(elem)) @@ -484,11 +507,11 @@ final class LazyList[+A] private (lazyState: AnyRef /* EmptyMarker.type | () => * * $preservesLaziness */ - override def scanLeft[B](z: B)(op: (B, A) => B): LazyList[B] = + override def scanLeft[B](z: B)(op: (B, A) => B): LazyListIterable[B]^{this, op} = if (knownIsEmpty) eagerCons(z, Empty) else scanLeftImpl(z)(op) - private def scanLeftImpl[B](z: B)(op: (B, A) => B): LazyList[B] = + private def scanLeftImpl[B](z: B)(op: (B, A) => B): LazyListIterable[B]^{this, op} = eagerCons( z, newLL { @@ -497,18 +520,18 @@ final class LazyList[+A] private (lazyState: AnyRef /* EmptyMarker.type | () => } ) - /** LazyList specialization of reduceLeft which allows GC to collect + /** LazyListIterable specialization of reduceLeft which allows GC to collect * along the way. * * @tparam B The type of value being accumulated. - * @param f The operation to perform on successive elements of the `LazyList`. + * @param f The operation to perform on successive elements of the `LazyListIterable`. * @return The accumulated value from successive applications of `f`. */ override def reduceLeft[B >: A](f: (B, A) => B): B = { if (isEmpty) throw new UnsupportedOperationException("empty.reduceLeft") else { var reducedRes: B = head - var left: LazyList[A] = tail + var left: LazyListIterable[A]^{this} = tail while (!left.isEmpty) { reducedRes = f(reducedRes, left.head) left = left.tail @@ -521,22 +544,22 @@ final class LazyList[+A] private (lazyState: AnyRef /* EmptyMarker.type | () => * * $preservesLaziness */ - override def partition(p: A => Boolean): (LazyList[A], LazyList[A]) = (filter(p), filterNot(p)) + override def partition(p: A => Boolean): (LazyListIterable[A]^{this, p}, LazyListIterable[A]^{this, p}) = (filter(p), filterNot(p)) /** @inheritdoc * * $preservesLaziness */ - override def partitionMap[A1, A2](f: A => Either[A1, A2]): (LazyList[A1], LazyList[A2]) = { - val (left, right) = map(f).partition(_.isLeft) - (left.map(_.asInstanceOf[Left[A1, _]].value), right.map(_.asInstanceOf[Right[_, A2]].value)) + override def partitionMap[A1, A2](f: A => Either[A1, A2]): (LazyListIterable[A1]^{this, f}, LazyListIterable[A2]^{this, f}) = { + val p: (LazyListIterable[Either[A1, A2]]^{this, f}, LazyListIterable[Either[A1, A2]]^{this, f}) = map(f).partition(_.isLeft) + (p._1.map(_.asInstanceOf[Left[A1, _]].value), p._2.map(_.asInstanceOf[Right[_, A2]].value)) } /** @inheritdoc * * $preservesLaziness */ - override def filter(pred: A => Boolean): LazyList[A] = + override def filter(pred: A => Boolean): LazyListIterable[A]^{this, pred} = if (knownIsEmpty) Empty else filterImpl(this, pred, isFlipped = false) @@ -544,7 +567,7 @@ final class LazyList[+A] private (lazyState: AnyRef /* EmptyMarker.type | () => * * $preservesLaziness */ - override def filterNot(pred: A => Boolean): LazyList[A] = + override def filterNot(pred: A => Boolean): LazyListIterable[A]^{this, pred} = if (knownIsEmpty) Empty else filterImpl(this, pred, isFlipped = true) @@ -556,21 +579,21 @@ final class LazyList[+A] private (lazyState: AnyRef /* EmptyMarker.type | () => * The `collection.WithFilter` returned by this method preserves laziness; elements are * only evaluated individually as needed. */ - override def withFilter(p: A => Boolean): collection.WithFilter[A, LazyList] = - new LazyList.WithFilter(coll, p) + override def withFilter(p: A => Boolean): collection.WithFilter[A, LazyListIterable]^{this, p} = + new LazyListIterable.WithFilter(coll, p) /** @inheritdoc * * $preservesLaziness */ - override def prepended[B >: A](elem: B): LazyList[B] = eagerCons(elem, this) + override def prepended[B >: A](elem: B): LazyListIterable[B]^{this} = eagerCons(elem, this) /** @inheritdoc * * $preservesLaziness */ - override def prependedAll[B >: A](prefix: collection.IterableOnce[B]): LazyList[B] = - if (knownIsEmpty) LazyList.from(prefix) + override def prependedAll[B >: A](prefix: collection.IterableOnce[B]^): LazyListIterable[B]^{this, prefix} = + if (knownIsEmpty) LazyListIterable.from(prefix) else if (prefix.knownSize == 0) this else newLL(eagerHeadPrependIterator(prefix.iterator)(this)) @@ -578,7 +601,7 @@ final class LazyList[+A] private (lazyState: AnyRef /* EmptyMarker.type | () => * * $preservesLaziness */ - override def map[B](f: A => B): LazyList[B] = + override def map[B](f: A => B): LazyListIterable[B]^{this, f} = if (knownIsEmpty) Empty else mapImpl(f) @@ -586,9 +609,9 @@ final class LazyList[+A] private (lazyState: AnyRef /* EmptyMarker.type | () => * * $preservesLaziness */ - override def tapEach[U](f: A => U): LazyList[A] = map { a => f(a); a } + override def tapEach[U](f: A => U): LazyListIterable[A]^{this, f} = map { a => f(a); a } - private def mapImpl[B](f: A => B): LazyList[B] = + private def mapImpl[B](f: A => B): LazyListIterable[B]^{this, f} = newLL { if (isEmpty) Empty else eagerCons(f(head), tail.mapImpl(f)) @@ -598,7 +621,7 @@ final class LazyList[+A] private (lazyState: AnyRef /* EmptyMarker.type | () => * * $preservesLaziness */ - override def collect[B](pf: PartialFunction[A, B]): LazyList[B] = + override def collect[B](pf: PartialFunction[A, B]^): LazyListIterable[B]^{this, pf} = if (knownIsEmpty) Empty else collectImpl(this, pf) @@ -608,7 +631,7 @@ final class LazyList[+A] private (lazyState: AnyRef /* EmptyMarker.type | () => * the first element for which the partial function is defined. */ @tailrec - override def collectFirst[B](pf: PartialFunction[A, B]): Option[B] = + override def collectFirst[B](pf: PartialFunction[A, B]^): Option[B] = if (isEmpty) None else { val res = pf.applyOrElse(head, anyToMarker.asInstanceOf[A => B]) @@ -636,7 +659,7 @@ final class LazyList[+A] private (lazyState: AnyRef /* EmptyMarker.type | () => */ // optimisations are not for speed, but for functionality // see tickets #153, #498, #2147, and corresponding tests in run/ (as well as run/stream_flatmap_odds.scala) - override def flatMap[B](f: A => IterableOnce[B]): LazyList[B] = + override def flatMap[B](f: A => IterableOnce[B]^): LazyListIterable[B]^{this, f} = if (knownIsEmpty) Empty else flatMapImpl(this, f) @@ -644,17 +667,17 @@ final class LazyList[+A] private (lazyState: AnyRef /* EmptyMarker.type | () => * * $preservesLaziness */ - override def flatten[B](implicit asIterable: A => IterableOnce[B]): LazyList[B] = flatMap(asIterable) + override def flatten[B](implicit asIterable: A -> IterableOnce[B]): LazyListIterable[B]^{this} = flatMap(asIterable) /** @inheritdoc * * $preservesLaziness */ - override def zip[B](that: collection.IterableOnce[B]): LazyList[(A, B)] = + override def zip[B](that: collection.IterableOnce[B]^): LazyListIterable[(A, B)]^{this, that} = if (knownIsEmpty || that.knownSize == 0) Empty else newLL(eagerHeadZipImpl(that.iterator)) - private def eagerHeadZipImpl[B](it: Iterator[B]): LazyList[(A, B)] = + private def eagerHeadZipImpl[B](it: Iterator[B]^): LazyListIterable[(A, B)]^{this, it} = if (isEmpty || !it.hasNext) Empty else eagerCons((head, it.next()), newLL { tail eagerHeadZipImpl it }) @@ -662,29 +685,29 @@ final class LazyList[+A] private (lazyState: AnyRef /* EmptyMarker.type | () => * * $preservesLaziness */ - override def zipWithIndex: LazyList[(A, Int)] = this zip LazyList.from(0) + override def zipWithIndex: LazyListIterable[(A, Int)]^{this} = this zip LazyListIterable.from(0) /** @inheritdoc * * $preservesLaziness */ - override def zipAll[A1 >: A, B](that: collection.Iterable[B], thisElem: A1, thatElem: B): LazyList[(A1, B)] = { + override def zipAll[A1 >: A, B](that: collection.Iterable[B]^, thisElem: A1, thatElem: B): LazyListIterable[(A1, B)]^{this, that} = { if (knownIsEmpty) { if (that.knownSize == 0) Empty - else LazyList.continually(thisElem) zip that + else LazyListIterable.continually(thisElem) zip that } else { - if (that.knownSize == 0) zip(LazyList.continually(thatElem)) + if (that.knownSize == 0) zip(LazyListIterable.continually(thatElem)) else newLL(eagerHeadZipAllImpl(that.iterator, thisElem, thatElem)) } } - private def eagerHeadZipAllImpl[A1 >: A, B](it: Iterator[B], thisElem: A1, thatElem: B): LazyList[(A1, B)] = { + private def eagerHeadZipAllImpl[A1 >: A, B](it: Iterator[B]^, thisElem: A1, thatElem: B): LazyListIterable[(A1, B)]^{this, it} = { if (it.hasNext) { - if (isEmpty) eagerCons((thisElem, it.next()), newLL { LazyList.continually(thisElem) eagerHeadZipImpl it }) + if (isEmpty) eagerCons((thisElem, it.next()), newLL { LazyListIterable.continually(thisElem) eagerHeadZipImpl it }) else eagerCons((head, it.next()), newLL { tail.eagerHeadZipAllImpl(it, thisElem, thatElem) }) } else { if (isEmpty) Empty - else eagerCons((head, thatElem), tail zip LazyList.continually(thatElem)) + else eagerCons((head, thatElem), tail zip LazyListIterable.continually(thatElem)) } } @@ -697,21 +720,21 @@ final class LazyList[+A] private (lazyState: AnyRef /* EmptyMarker.type | () => * only evaluated individually as needed. */ // just in case it can be meaningfully overridden at some point - override def lazyZip[B](that: collection.Iterable[B]): LazyZip2[A, B, this.type] = + override def lazyZip[B](that: collection.Iterable[B]^): LazyZip2[A, B, this.type]^{this, that} = super.lazyZip(that) /** @inheritdoc * * $preservesLaziness */ - override def unzip[A1, A2](implicit asPair: A => (A1, A2)): (LazyList[A1], LazyList[A2]) = + override def unzip[A1, A2](implicit asPair: A -> (A1, A2)): (LazyListIterable[A1]^{this}, LazyListIterable[A2]^{this}) = (map(asPair(_)._1), map(asPair(_)._2)) /** @inheritdoc * * $preservesLaziness */ - override def unzip3[A1, A2, A3](implicit asTriple: A => (A1, A2, A3)): (LazyList[A1], LazyList[A2], LazyList[A3]) = + override def unzip3[A1, A2, A3](implicit asTriple: A -> (A1, A2, A3)): (LazyListIterable[A1]^{this}, LazyListIterable[A2]^{this}, LazyListIterable[A3]^{this}) = (map(asTriple(_)._1), map(asTriple(_)._2), map(asTriple(_)._3)) /** @inheritdoc @@ -719,7 +742,7 @@ final class LazyList[+A] private (lazyState: AnyRef /* EmptyMarker.type | () => * $initiallyLazy * Additionally, it preserves laziness for all except the first `n` elements. */ - override def drop(n: Int): LazyList[A] = + override def drop(n: Int): LazyListIterable[A]^{this} = if (n <= 0) this else if (knownIsEmpty) Empty else dropImpl(this, n) @@ -729,7 +752,7 @@ final class LazyList[+A] private (lazyState: AnyRef /* EmptyMarker.type | () => * $initiallyLazy * Additionally, it preserves laziness for all elements after the predicate returns `false`. */ - override def dropWhile(p: A => Boolean): LazyList[A] = + override def dropWhile(p: A => Boolean): LazyListIterable[A]^{this, p} = if (knownIsEmpty) Empty else dropWhileImpl(this, p) @@ -737,11 +760,11 @@ final class LazyList[+A] private (lazyState: AnyRef /* EmptyMarker.type | () => * * $initiallyLazy */ - override def dropRight(n: Int): LazyList[A] = { + override def dropRight(n: Int): LazyListIterable[A]^{this} = { if (n <= 0) this else if (knownIsEmpty) Empty else newLL { - var scout = this + var scout: LazyListIterable[A]^{this} = this var remaining = n // advance scout n elements ahead (or until empty) while (remaining > 0 && !scout.isEmpty) { @@ -752,7 +775,7 @@ final class LazyList[+A] private (lazyState: AnyRef /* EmptyMarker.type | () => } } - private def eagerHeadDropRightImpl(scout: LazyList[_]): LazyList[A] = + private def eagerHeadDropRightImpl(scout: LazyListIterable[_]^): LazyListIterable[A]^{this, scout} = if (scout.isEmpty) Empty else eagerCons(head, newLL(tail.eagerHeadDropRightImpl(scout.tail))) @@ -760,11 +783,11 @@ final class LazyList[+A] private (lazyState: AnyRef /* EmptyMarker.type | () => * * $preservesLaziness */ - override def take(n: Int): LazyList[A] = + override def take(n: Int): LazyListIterable[A]^{this} = if (knownIsEmpty) Empty else takeImpl(n) - private def takeImpl(n: Int): LazyList[A] = { + private def takeImpl(n: Int): LazyListIterable[A]^{this} = { if (n <= 0) Empty else newLL { if (isEmpty) Empty @@ -776,11 +799,11 @@ final class LazyList[+A] private (lazyState: AnyRef /* EmptyMarker.type | () => * * $preservesLaziness */ - override def takeWhile(p: A => Boolean): LazyList[A] = + override def takeWhile(p: A => Boolean): LazyListIterable[A]^{this, p} = if (knownIsEmpty) Empty else takeWhileImpl(p) - private def takeWhileImpl(p: A => Boolean): LazyList[A] = + private def takeWhileImpl(p: A => Boolean): LazyListIterable[A]^{this, p} = newLL { if (isEmpty || !p(head)) Empty else eagerCons(head, tail.takeWhileImpl(p)) @@ -790,7 +813,7 @@ final class LazyList[+A] private (lazyState: AnyRef /* EmptyMarker.type | () => * * $initiallyLazy */ - override def takeRight(n: Int): LazyList[A] = + override def takeRight(n: Int): LazyListIterable[A]^{this} = if (n <= 0 || knownIsEmpty) Empty else takeRightImpl(this, n) @@ -799,17 +822,17 @@ final class LazyList[+A] private (lazyState: AnyRef /* EmptyMarker.type | () => * $initiallyLazy * Additionally, it preserves laziness for all but the first `from` elements. */ - override def slice(from: Int, until: Int): LazyList[A] = take(until).drop(from) + override def slice(from: Int, until: Int): LazyListIterable[A]^{this} = take(until).drop(from) /** @inheritdoc * * $evaluatesAllElements */ - override def reverse: LazyList[A] = reverseOnto(Empty) + override def reverse: LazyListIterable[A]^{this} = reverseOnto(Empty) - // need contravariant type B to make the compiler happy - still returns LazyList[A] + // need contravariant type B to make the compiler happy - still returns LazyListIterable[A] @tailrec - private def reverseOnto[B >: A](tl: LazyList[B]): LazyList[B] = + private def reverseOnto[B >: A](tl: LazyListIterable[B]^): LazyListIterable[B]^{this, tl} = if (isEmpty) tl else tail.reverseOnto(newLL(eagerCons(head, tl))) @@ -817,7 +840,7 @@ final class LazyList[+A] private (lazyState: AnyRef /* EmptyMarker.type | () => * * $preservesLaziness */ - override def diff[B >: A](that: collection.Seq[B]): LazyList[A] = + override def diff[B >: A](that: collection.Seq[B]^): LazyListIterable[A]^{this} = if (knownIsEmpty) Empty else super.diff(that) @@ -825,7 +848,7 @@ final class LazyList[+A] private (lazyState: AnyRef /* EmptyMarker.type | () => * * $preservesLaziness */ - override def intersect[B >: A](that: collection.Seq[B]): LazyList[A] = + override def intersect[B >: A](that: collection.Seq[B]^): LazyListIterable[A]^{this} = if (knownIsEmpty) Empty else super.intersect(that) @@ -840,7 +863,7 @@ final class LazyList[+A] private (lazyState: AnyRef /* EmptyMarker.type | () => * The iterator returned by this method mostly preserves laziness; * a single element ahead of the iterator is evaluated. */ - override def grouped(size: Int): Iterator[LazyList[A]] = { + override def grouped(size: Int): Iterator[LazyListIterable[A]^{this}]^{this} = { require(size > 0, "size must be positive, but was " + size) slidingImpl(size = size, step = size) } @@ -850,23 +873,25 @@ final class LazyList[+A] private (lazyState: AnyRef /* EmptyMarker.type | () => * The iterator returned by this method mostly preserves laziness; * `size - step max 1` elements ahead of the iterator are evaluated. */ - override def sliding(size: Int, step: Int): Iterator[LazyList[A]] = { + override def sliding(size: Int, step: Int): Iterator[LazyListIterable[A]^{this}]^{this} = { require(size > 0 && step > 0, s"size=$size and step=$step, but both must be positive") slidingImpl(size = size, step = step) } - @inline private def slidingImpl(size: Int, step: Int): Iterator[LazyList[A]] = + @inline private def slidingImpl(size: Int, step: Int): Iterator[LazyListIterable[A]^{this}]^{this} = if (knownIsEmpty) Iterator.empty - else new SlidingIterator[A](this, size = size, step = step) + else + val it = new SlidingIterator[A](this, size = size, step = step) + it.asInstanceOf // CC cannot figure this out yet /** @inheritdoc * * $preservesLaziness */ - override def padTo[B >: A](len: Int, elem: B): LazyList[B] = + override def padTo[B >: A](len: Int, elem: B): LazyListIterable[B]^{this} = if (len <= 0) this else newLL { - if (isEmpty) LazyList.fill(len)(elem) + if (isEmpty) LazyListIterable.fill(len)(elem) else eagerCons(head, tail.padTo(len - 1, elem)) } @@ -874,11 +899,11 @@ final class LazyList[+A] private (lazyState: AnyRef /* EmptyMarker.type | () => * * $preservesLaziness */ - override def patch[B >: A](from: Int, other: IterableOnce[B], replaced: Int): LazyList[B] = - if (knownIsEmpty) LazyList from other + override def patch[B >: A](from: Int, other: IterableOnce[B]^, replaced: Int): LazyListIterable[B]^{this, other} = + if (knownIsEmpty) LazyListIterable from other else patchImpl(from, other, replaced) - private def patchImpl[B >: A](from: Int, other: IterableOnce[B], replaced: Int): LazyList[B] = + private def patchImpl[B >: A](from: Int, other: IterableOnce[B]^, replaced: Int): LazyListIterable[B]^{this, other} = newLL { if (from <= 0) eagerHeadPrependIterator(other.iterator)(dropImpl(this, replaced)) else if (isEmpty) eagerHeadFromIterator(other.iterator) @@ -890,17 +915,17 @@ final class LazyList[+A] private (lazyState: AnyRef /* EmptyMarker.type | () => * $evaluatesAllElements */ // overridden just in case a lazy implementation is developed at some point - override def transpose[B](implicit asIterable: A => collection.Iterable[B]): LazyList[LazyList[B]] = super.transpose + override def transpose[B](implicit asIterable: A -> collection.Iterable[B]): LazyListIterable[LazyListIterable[B]^{this}]^{this} = super.transpose /** @inheritdoc * * $preservesLaziness */ - override def updated[B >: A](index: Int, elem: B): LazyList[B] = + override def updated[B >: A](index: Int, elem: B): LazyListIterable[B]^{this} = if (index < 0) throw new IndexOutOfBoundsException(s"$index") else updatedImpl(index, elem, index) - private def updatedImpl[B >: A](index: Int, elem: B, startIndex: Int): LazyList[B] = + private def updatedImpl[B >: A](index: Int, elem: B, startIndex: Int): LazyListIterable[B]^{this} = newLL { if (index <= 0) eagerCons(elem, tail) else if (tail.isEmpty) throw new IndexOutOfBoundsException(startIndex.toString) @@ -933,10 +958,10 @@ final class LazyList[+A] private (lazyState: AnyRef /* EmptyMarker.type | () => if (!isEvaluated) b.append("") else if (!isEmpty) { b.append(head) - var cursor = this + var cursor: LazyListIterable[A]^{this} = this // explicit param to prevent an ObjectRef for cursor - @inline def appendHead(c: LazyList[A]): Unit = b.append(sep).append(c.head) - var scout = tail + @inline def appendHead(c: LazyListIterable[A]^): Unit = b.append(sep).append(c.head) + var scout: LazyListIterable[A]^{this} = tail if (cursor ne scout) { cursor = scout if (scout.knownNonEmpty) { @@ -969,7 +994,7 @@ final class LazyList[+A] private (lazyState: AnyRef /* EmptyMarker.type | () => // the cycle: (C - (P%C) + (P%C)) == C == 0. So if another runner // starts at the beginning of the prefix, they'll collide exactly at // the start of the loop. - var runner = this + var runner: LazyListIterable[A]^{this} = this while (runner ne scout) { runner = runner.tail scout = scout.tail @@ -977,7 +1002,7 @@ final class LazyList[+A] private (lazyState: AnyRef /* EmptyMarker.type | () => while({ val ct = cursor.tail if (ct ne scout) { - // In `lazy val xs: LazyList[Int] = 1 #:: 2 #:: xs`, method `#::` creates a LazyList instance which ends up as the 3rd element. + // In `lazy val xs: LazyListIterable[Int] = 1 #:: 2 #:: xs`, method `#::` creates a LazyListIterable instance which ends up as the 3rd element. // That 3rd element initially has unknown head/tail. Once it completes, the tail is assigned to be `xs.tail`. // So in memory the structure is `LLx(1, LLy(2, LLz(1, )))`. // In `toString` we skip the last element to maintain the illusion. @@ -1001,9 +1026,9 @@ final class LazyList[+A] private (lazyState: AnyRef /* EmptyMarker.type | () => * * Examples: * - * - `"LazyList(4, <not computed>)"`, a non-empty lazy list ; - * - `"LazyList(1, 2, 3, <not computed>)"`, a lazy list with at least three elements ; - * - `"LazyList(1, 2, 3, <cycle>)"`, an infinite lazy list that contains + * - `"LazyListIterable(4, <not computed>)"`, a non-empty lazy list ; + * - `"LazyListIterable(1, 2, 3, <not computed>)"`, a lazy list with at least three elements ; + * - `"LazyListIterable(1, 2, 3, <cycle>)"`, an infinite lazy list that contains * a cycle at the fourth element. */ override def toString(): String = addStringNoForce(new JStringBuilder(className), "(", ", ", ")").toString @@ -1018,8 +1043,8 @@ final class LazyList[+A] private (lazyState: AnyRef /* EmptyMarker.type | () => else if (isEmpty) true else { // Two-iterator trick (2x & 1x speed) for cycle detection. - var those = this - var these = tail + var those: LazyListIterable[A]^{this} = this + var these: LazyListIterable[A]^{this} = tail while (those ne these) { if (!these.isEvaluated) return false else if (these.isEmpty) return true @@ -1038,10 +1063,10 @@ final class LazyList[+A] private (lazyState: AnyRef /* EmptyMarker.type | () => /** * $factoryInfo * @define coll lazy list - * @define Coll `LazyList` + * @define Coll `LazyListIterable` */ @SerialVersionUID(4L) -object LazyList extends SeqFactory[LazyList] { +object LazyListIterable extends IterableFactory[LazyListIterable] { // LazyListTest.countAlloc // var k = 0 @@ -1051,31 +1076,31 @@ object LazyList extends SeqFactory[LazyList] { private object MidEvaluation private object EmptyMarker - private val Empty: LazyList[Nothing] = new LazyList(EmptyMarker) + private val Empty: LazyListIterable[Nothing] = new LazyListIterable(EmptyMarker) - /** Creates a new LazyList. */ - @inline private def newLL[A](state: => LazyList[A]): LazyList[A] = new LazyList[A](() => state) + /** Creates a new LazyListIterable. */ + @inline private def newLL[A](state: => LazyListIterable[A]^): LazyListIterable[A]^{state} = new LazyListIterable[A](() => state) - /** Creates a new LazyList with evaluated `head` and `tail`. */ - @inline private def eagerCons[A](hd: A, tl: LazyList[A]): LazyList[A] = new LazyList[A](hd, tl) + /** Creates a new LazyListIterable with evaluated `head` and `tail`. */ + @inline private def eagerCons[A](hd: A, tl: LazyListIterable[A]^): LazyListIterable[A]^{tl} = new LazyListIterable[A](hd, tl) - private val anyToMarker: Any => Any = _ => Statics.pfMarker + private val anyToMarker: Any -> Any = _ => Statics.pfMarker /* All of the following `Impl` methods are carefully written so as not to - * leak the beginning of the `LazyList`. They copy the initial `LazyList` (`ll`) into + * leak the beginning of the `LazyListIterable`. They copy the initial `LazyListIterable` (`ll`) into * `var rest`, which gets closed over as a `scala.runtime.ObjectRef`, thus not permanently - * leaking the head of the `LazyList`. Additionally, the methods are written so that, should - * an exception be thrown by the evaluation of the `LazyList` or any supplied function, they + * leaking the head of the `LazyListIterable`. Additionally, the methods are written so that, should + * an exception be thrown by the evaluation of the `LazyListIterable` or any supplied function, they * can continue their execution where they left off. */ - private def filterImpl[A](ll: LazyList[A], p: A => Boolean, isFlipped: Boolean): LazyList[A] = { + private def filterImpl[A](ll: LazyListIterable[A]^, p: A => Boolean, isFlipped: Boolean): LazyListIterable[A]^{ll, p} = { // DO NOT REFERENCE `ll` ANYWHERE ELSE, OR IT WILL LEAK THE HEAD - var restRef = ll // val restRef = new ObjectRef(ll) + var restRef: LazyListIterable[A]^{ll} = ll // val restRef = new ObjectRef(ll) newLL { var elem: A = null.asInstanceOf[A] - var found = false - var rest = restRef // var rest = restRef.elem + var found = false + var rest: LazyListIterable[A]^{ll} = restRef // var rest = restRef.elem while (!found && !rest.isEmpty) { elem = rest.head found = p(elem) != isFlipped @@ -1086,15 +1111,15 @@ object LazyList extends SeqFactory[LazyList] { } } - private def collectImpl[A, B](ll: LazyList[A], pf: PartialFunction[A, B]): LazyList[B] = { + private def collectImpl[A, B](ll: LazyListIterable[A]^, pf: PartialFunction[A, B]^): LazyListIterable[B]^{ll, pf} = { // DO NOT REFERENCE `ll` ANYWHERE ELSE, OR IT WILL LEAK THE HEAD - var restRef = ll // val restRef = new ObjectRef(ll) + var restRef: LazyListIterable[A]^{ll} = ll // val restRef = new ObjectRef(ll) newLL { val marker = Statics.pfMarker val toMarker = anyToMarker.asInstanceOf[A => B] // safe because Function1 is erased var res: B = marker.asInstanceOf[B] // safe because B is unbounded - var rest = restRef // var rest = restRef.elem + var rest: LazyListIterable[A]^{ll} = restRef // var rest = restRef.elem while((res.asInstanceOf[AnyRef] eq marker) && !rest.isEmpty) { res = pf.applyOrElse(rest.head, toMarker) rest = rest.tail @@ -1105,13 +1130,13 @@ object LazyList extends SeqFactory[LazyList] { } } - private def flatMapImpl[A, B](ll: LazyList[A], f: A => IterableOnce[B]): LazyList[B] = { + private def flatMapImpl[A, B](ll: LazyListIterable[A]^, f: A => IterableOnce[B]^): LazyListIterable[B]^{ll, f} = { // DO NOT REFERENCE `ll` ANYWHERE ELSE, OR IT WILL LEAK THE HEAD - var restRef = ll // val restRef = new ObjectRef(ll) + var restRef: LazyListIterable[A]^{ll} = ll // val restRef = new ObjectRef(ll) newLL { - var it: Iterator[B] = null - var itHasNext = false - var rest = restRef // var rest = restRef.elem + var it: Iterator[B]^{f} = null + var itHasNext = false + var rest: LazyListIterable[A]^{ll} = restRef // var rest = restRef.elem while (!itHasNext && !rest.isEmpty) { it = f(rest.head).iterator itHasNext = it.hasNext @@ -1129,13 +1154,13 @@ object LazyList extends SeqFactory[LazyList] { } } - private def dropImpl[A](ll: LazyList[A], n: Int): LazyList[A] = { + private def dropImpl[A](ll: LazyListIterable[A]^, n: Int): LazyListIterable[A]^{ll} = { // DO NOT REFERENCE `ll` ANYWHERE ELSE, OR IT WILL LEAK THE HEAD - var restRef = ll // val restRef = new ObjectRef(ll) - var iRef = n // val iRef = new IntRef(n) + var restRef: LazyListIterable[A]^{ll} = ll // val restRef = new ObjectRef(ll) + var iRef = n // val iRef = new IntRef(n) newLL { - var rest = restRef // var rest = restRef.elem - var i = iRef // var i = iRef.elem + var rest: LazyListIterable[A]^{ll} = restRef // var rest = restRef.elem + var i = iRef // var i = iRef.elem while (i > 0 && !rest.isEmpty) { rest = rest.tail restRef = rest // restRef.elem = rest @@ -1146,11 +1171,11 @@ object LazyList extends SeqFactory[LazyList] { } } - private def dropWhileImpl[A](ll: LazyList[A], p: A => Boolean): LazyList[A] = { + private def dropWhileImpl[A](ll: LazyListIterable[A]^, p: A => Boolean): LazyListIterable[A]^{ll, p} = { // DO NOT REFERENCE `ll` ANYWHERE ELSE, OR IT WILL LEAK THE HEAD - var restRef = ll // val restRef = new ObjectRef(ll) + var restRef: LazyListIterable[A]^{ll} = ll // val restRef = new ObjectRef(ll) newLL { - var rest = restRef // var rest = restRef.elem + var rest: LazyListIterable[A]^{ll} = restRef // var rest = restRef.elem while (!rest.isEmpty && p(rest.head)) { rest = rest.tail restRef = rest // restRef.elem = rest @@ -1159,22 +1184,22 @@ object LazyList extends SeqFactory[LazyList] { } } - private def takeRightImpl[A](ll: LazyList[A], n: Int): LazyList[A] = { + private def takeRightImpl[A](ll: LazyListIterable[A]^, n: Int): LazyListIterable[A]^{ll} = { // DO NOT REFERENCE `ll` ANYWHERE ELSE, OR IT WILL LEAK THE HEAD - var restRef = ll // val restRef = new ObjectRef(ll) - var scoutRef = ll // val scoutRef = new ObjectRef(ll) - var remainingRef = n // val remainingRef = new IntRef(n) + var restRef: LazyListIterable[A]^{ll} = ll // val restRef = new ObjectRef(ll) + var scoutRef: LazyListIterable[A]^{ll} = ll // val scoutRef = new ObjectRef(ll) + var remainingRef = n // val remainingRef = new IntRef(n) newLL { - var scout = scoutRef // var scout = scoutRef.elem - var remaining = remainingRef // var remaining = remainingRef.elem + var scout: LazyListIterable[A]^{ll} = scoutRef // var scout = scoutRef.elem + var remaining = remainingRef // var remaining = remainingRef.elem // advance `scout` `n` elements ahead (or until empty) while (remaining > 0 && !scout.isEmpty) { scout = scout.tail - scoutRef = scout // scoutRef.elem = scout + scoutRef = scout // scoutRef.elem = scout remaining -= 1 - remainingRef = remaining // remainingRef.elem = remaining + remainingRef = remaining // remainingRef.elem = remaining } - var rest = restRef // var rest = restRef.elem + var rest: LazyListIterable[A]^{ll} = restRef // var rest = restRef.elem // advance `rest` and `scout` in tandem until `scout` reaches the end while(!scout.isEmpty) { scout = scout.tail @@ -1187,117 +1212,117 @@ object LazyList extends SeqFactory[LazyList] { } } - /** An alternative way of building and matching lazy lists using LazyList.cons(hd, tl). + /** An alternative way of building and matching lazy lists using LazyListIterable.cons(hd, tl). */ object cons { /** A lazy list consisting of a given first element and remaining elements * @param hd The first element of the result lazy list * @param tl The remaining elements of the result lazy list */ - def apply[A](hd: => A, tl: => LazyList[A]): LazyList[A] = newLL(eagerCons(hd, newLL(tl))) + def apply[A](hd: => A, tl: => LazyListIterable[A]): LazyListIterable[A]^{hd, tl} = newLL(eagerCons(hd, newLL(tl))) /** Maps a lazy list to its head and tail */ - def unapply[A](xs: LazyList[A]): Option[(A, LazyList[A])] = #::.unapply(xs) + def unapply[A](xs: LazyListIterable[A]^): Option[(A, LazyListIterable[A]^{xs})] = #::.unapply(xs) } - implicit def toDeferrer[A](l: => LazyList[A]): Deferrer[A] = new Deferrer[A](() => l) + implicit def toDeferrer[A](l: => LazyListIterable[A]^): Deferrer[A]^{l} = new Deferrer[A](() => l) - final class Deferrer[A] private[LazyList] (private val l: () => LazyList[A]) extends AnyVal { - /** Construct a LazyList consisting of a given first element followed by elements - * from another LazyList. + final class Deferrer[A] private[LazyListIterable] (private val l: () => LazyListIterable[A]^) extends AnyVal { self: Deferrer[A]^ => + /** Construct a LazyListIterable consisting of a given first element followed by elements + * from another LazyListIterable. */ - def #:: [B >: A](elem: => B): LazyList[B] = newLL(eagerCons(elem, newLL(l()))) - /** Construct a LazyList consisting of the concatenation of the given LazyList and - * another LazyList. + def #:: [B >: A](elem: => B): LazyListIterable[B]^{this, elem} = newLL(eagerCons(elem, newLL(l()))) + /** Construct a LazyListIterable consisting of the concatenation of the given LazyListIterable and + * another LazyListIterable. */ - def #:::[B >: A](prefix: LazyList[B]): LazyList[B] = prefix lazyAppendedAll l() + def #:::[B >: A](prefix: LazyListIterable[B]^): LazyListIterable[B]^{this, prefix} = prefix lazyAppendedAll l() } object #:: { - def unapply[A](s: LazyList[A]): Option[(A, LazyList[A])] = + def unapply[A](s: LazyListIterable[A]^): Option[(A, LazyListIterable[A]^{s})] = if (!s.isEmpty) Some((s.head, s.tail)) else None } - def from[A](coll: collection.IterableOnce[A]): LazyList[A] = coll match { - case lazyList: LazyList[A] => lazyList + def from[A](coll: collection.IterableOnce[A]^): LazyListIterable[A]^{coll} = coll match { + case lazyList: LazyListIterable[A] => lazyList case _ if coll.knownSize == 0 => empty[A] case _ => newLL(eagerHeadFromIterator(coll.iterator)) } - def empty[A]: LazyList[A] = Empty + def empty[A]: LazyListIterable[A] = Empty - /** Creates a LazyList with the elements of an iterator followed by a LazyList suffix. + /** Creates a LazyListIterable with the elements of an iterator followed by a LazyListIterable suffix. * Eagerly evaluates the first element. */ - private def eagerHeadPrependIterator[A](it: Iterator[A])(suffix: => LazyList[A]): LazyList[A] = + private def eagerHeadPrependIterator[A](it: Iterator[A]^)(suffix: => LazyListIterable[A]^): LazyListIterable[A]^{it, suffix} = if (it.hasNext) eagerCons(it.next(), newLL(eagerHeadPrependIterator(it)(suffix))) else suffix - /** Creates a LazyList from an Iterator. Eagerly evaluates the first element. */ - private def eagerHeadFromIterator[A](it: Iterator[A]): LazyList[A] = + /** Creates a LazyListIterable from an Iterator. Eagerly evaluates the first element. */ + private def eagerHeadFromIterator[A](it: Iterator[A]^): LazyListIterable[A]^{it} = if (it.hasNext) eagerCons(it.next(), newLL(eagerHeadFromIterator(it))) else Empty - override def concat[A](xss: collection.Iterable[A]*): LazyList[A] = + override def concat[A](xss: collection.Iterable[A]*): LazyListIterable[A] = if (xss.knownSize == 0) empty else newLL(eagerHeadConcatIterators(xss.iterator)) - private def eagerHeadConcatIterators[A](it: Iterator[collection.Iterable[A]]): LazyList[A] = + private def eagerHeadConcatIterators[A](it: Iterator[collection.Iterable[A]^]^): LazyListIterable[A]^{it} = if (!it.hasNext) Empty else eagerHeadPrependIterator(it.next().iterator)(eagerHeadConcatIterators(it)) - /** An infinite LazyList that repeatedly applies a given function to a start value. + /** An infinite LazyListIterable that repeatedly applies a given function to a start value. * - * @param start the start value of the LazyList + * @param start the start value of the LazyListIterable * @param f the function that's repeatedly applied - * @return the LazyList returning the infinite sequence of values `start, f(start), f(f(start)), ...` + * @return the LazyListIterable returning the infinite sequence of values `start, f(start), f(f(start)), ...` */ - def iterate[A](start: => A)(f: A => A): LazyList[A] = + def iterate[A](start: => A)(f: A => A): LazyListIterable[A]^{start, f} = newLL { val head = start eagerCons(head, iterate(f(head))(f)) } /** - * Create an infinite LazyList starting at `start` and incrementing by + * Create an infinite LazyListIterable starting at `start` and incrementing by * step `step`. * - * @param start the start value of the LazyList - * @param step the increment value of the LazyList - * @return the LazyList starting at value `start`. + * @param start the start value of the LazyListIterable + * @param step the increment value of the LazyListIterable + * @return the LazyListIterable starting at value `start`. */ - def from(start: Int, step: Int): LazyList[Int] = + def from(start: Int, step: Int): LazyListIterable[Int] = newLL(eagerCons(start, from(start + step, step))) /** - * Create an infinite LazyList starting at `start` and incrementing by `1`. + * Create an infinite LazyListIterable starting at `start` and incrementing by `1`. * - * @param start the start value of the LazyList - * @return the LazyList starting at value `start`. + * @param start the start value of the LazyListIterable + * @return the LazyListIterable starting at value `start`. */ - def from(start: Int): LazyList[Int] = from(start, 1) + def from(start: Int): LazyListIterable[Int] = from(start, 1) /** - * Create an infinite LazyList containing the given element expression (which + * Create an infinite LazyListIterable containing the given element expression (which * is computed for each occurrence). * - * @param elem the element composing the resulting LazyList - * @return the LazyList containing an infinite number of elem + * @param elem the element composing the resulting LazyListIterable + * @return the LazyListIterable containing an infinite number of elem */ - def continually[A](elem: => A): LazyList[A] = newLL(eagerCons(elem, continually(elem))) + def continually[A](elem: => A): LazyListIterable[A]^{elem} = newLL(eagerCons(elem, continually(elem))) - override def fill[A](n: Int)(elem: => A): LazyList[A] = - if (n > 0) newLL(eagerCons(elem, LazyList.fill(n - 1)(elem))) else empty + override def fill[A](n: Int)(elem: => A): LazyListIterable[A]^{elem} = + if (n > 0) newLL(eagerCons(elem, LazyListIterable.fill(n - 1)(elem))) else empty - override def tabulate[A](n: Int)(f: Int => A): LazyList[A] = { - def at(index: Int): LazyList[A] = + override def tabulate[A](n: Int)(f: Int => A): LazyListIterable[A]^{f} = { + def at(index: Int): LazyListIterable[A]^{f} = if (index < n) newLL(eagerCons(f(index), at(index + 1))) else empty at(0) } // significantly simpler than the iterator returned by Iterator.unfold - override def unfold[A, S](init: S)(f: S => Option[(A, S)]): LazyList[A] = + override def unfold[A, S](init: S)(f: S => Option[(A, S)]): LazyListIterable[A]^{f} = newLL { f(init) match { case Some((elem, state)) => eagerCons(elem, unfold(state)(f)) @@ -1305,15 +1330,16 @@ object LazyList extends SeqFactory[LazyList] { } } - /** The builder returned by this method only evaluates elements - * of collections added to it as needed. + /** Unlike LazyList, the builder returned by this method will eagerly evaluate all elements + * passed to it in `addAll`. + * To create a LazyListIterable from an IterableOnce, use `fromSpecific`. * * @tparam A the type of the ${coll}’s elements * @return A builder for $Coll objects. */ - def newBuilder[A]: Builder[A, LazyList[A]] = new LazyBuilder[A] + def newBuilder[A]: Builder[A, LazyListIterable[A]] = new LazyListIterableBuilder[A] - private class LazyIterator[+A](private[this] var lazyList: LazyList[A]) extends AbstractIterator[A] { + private class LazyIterator[+A](private[this] var lazyList: LazyListIterable[A]^) extends AbstractIterator[A] { override def hasNext: Boolean = !lazyList.isEmpty override def next(): A = @@ -1325,8 +1351,9 @@ object LazyList extends SeqFactory[LazyList] { } } - private class SlidingIterator[A](private[this] var lazyList: LazyList[A], size: Int, step: Int) - extends AbstractIterator[LazyList[A]] { + private class SlidingIterator[A](l: LazyListIterable[A]^, size: Int, step: Int) + extends AbstractIterator[LazyListIterable[A]^{l}] { + private[this] var lazyList: LazyListIterable[A]^{l} = l private val minLen = size - step max 0 private var first = true @@ -1334,7 +1361,7 @@ object LazyList extends SeqFactory[LazyList] { if (first) !lazyList.isEmpty else lazyList.lengthGt(minLen) - def next(): LazyList[A] = { + def next(): LazyListIterable[A]^{l} = { if (!hasNext) Iterator.empty.next() else { first = false @@ -1345,81 +1372,103 @@ object LazyList extends SeqFactory[LazyList] { } } - private final class WithFilter[A] private[LazyList](lazyList: LazyList[A], p: A => Boolean) - extends collection.WithFilter[A, LazyList] { + private final class WithFilter[A] private[LazyListIterable](lazyList: LazyListIterable[A]^, p: A => Boolean) + extends collection.WithFilter[A, LazyListIterable] { private[this] val filtered = lazyList.filter(p) - def map[B](f: A => B): LazyList[B] = filtered.map(f) - def flatMap[B](f: A => IterableOnce[B]): LazyList[B] = filtered.flatMap(f) + def map[B](f: A => B): LazyListIterable[B]^{this, f} = filtered.map(f) + def flatMap[B](f: A => IterableOnce[B]^): LazyListIterable[B]^{this, f} = filtered.flatMap(f) def foreach[U](f: A => U): Unit = filtered.foreach(f) - def withFilter(q: A => Boolean): collection.WithFilter[A, LazyList] = new WithFilter(filtered, q) + def withFilter(q: A => Boolean): collection.WithFilter[A, LazyListIterable]^{this, q} = new WithFilter(filtered, q) } - private final class LazyBuilder[A] extends ReusableBuilder[A, LazyList[A]] { - import LazyBuilder._ - - private[this] var next: DeferredState[A] = _ - private[this] var list: LazyList[A] = _ + // wraps a list buffer and use it to build the non-lazy LazyListIterable. + private final class LazyListIterableBuilder[A] extends ReusableBuilder[A, LazyListIterable[A]] { + private val buf = collection.mutable.ListBuffer[A]() - clear() - - override def clear(): Unit = { - val deferred = new DeferredState[A] - list = newLL(deferred.eval()) - next = deferred - } + override def clear() = buf.clear() - override def result(): LazyList[A] = { - next init Empty - list - } + override def result(): LazyListIterable[A] = LazyListIterable.fromSpecific(buf.result()) - override def addOne(elem: A): this.type = { - val deferred = new DeferredState[A] - next init eagerCons(elem, newLL(deferred.eval())) - next = deferred + override def addOne(elem: A) = { + buf.addOne(elem) this } - // lazy implementation which doesn't evaluate the collection being added - override def addAll(xs: IterableOnce[A]): this.type = { - if (xs.knownSize != 0) { - val deferred = new DeferredState[A] - next init eagerHeadPrependIterator(xs.iterator)(deferred.eval()) - next = deferred - } + override def addAll(xs: IterableOnce[A]^) = { + buf.addAll(xs) this } } - private object LazyBuilder { - final class DeferredState[A] { - private[this] var _tail: () => LazyList[A] = _ - - def eval(): LazyList[A] = { - val state = _tail - if (state == null) throw new IllegalStateException("uninitialized") - state() - } - - // racy - def init(state: => LazyList[A]): Unit = { - if (_tail != null) throw new IllegalStateException("already initialized") - _tail = () => state - } - } - } - - /** This serialization proxy is used for LazyLists which start with a sequence of evaluated cons cells. + // CC Note: Lazy Builder is not unsafe, but requires an explicit capture set. + + // private final class LazyBuilder[A, Cap^] extends ReusableBuilder[A, LazyListIterable[A]] { + // import LazyBuilder._ + + // private[this] var next: DeferredState[A, Cap]^{Cap} = _ + // private[this] var list: LazyListIterable[A]^{Cap} = _ + + // clear() + + // override def clear(): Unit = { + // val deferred = new DeferredState[A, Cap] + // list = newLL(deferred.eval()) + // next = deferred + // } + + // override def result(): LazyListIterable[A]^{Cap} = { + // next init Empty + // list + // } + + // override def addOne(elem: A): this.type = { + // val deferred = new DeferredState[A, Cap] + // next init eagerCons(elem, newLL(deferred.eval())) + // next = deferred + // this + // } + + // // lazy implementation which doesn't evaluate the collection being added + // override def addAll(xs: IterableOnce[A]^{Cap}): this.type = { + // if (xs.knownSize != 0) { + // val deferred = new DeferredState[A, Cap] + // next init eagerHeadPrependIterator(xs.iterator)(deferred.eval()) + // next = deferred + // } + // this + // } + // } + + // private object LazyBuilder { + // final class DeferredState[A, Cap^] { + // private[this] var _tail: () ->{Cap} LazyListIterable[A]^{Cap} = _ + + // def eval(): LazyListIterable[A]^{Cap} = { + // val state = _tail + // if (state == null) throw new IllegalStateException("uninitialized") + // state() + // } + + // // racy + // def init(state: ->{Cap} LazyListIterable[A]^{Cap}): Unit = { + // if (_tail != null) throw new IllegalStateException("already initialized") + // _tail = () => state + // } + // } + // } + + /** This serialization proxy is used for LazyListIterables which start with a sequence of evaluated cons cells. * The forced sequence is serialized in a compact, sequential format, followed by the unevaluated tail, which uses * standard Java serialization to store the complete structure of unevaluated thunks. This allows the serialization * of long evaluated lazy lists without exhausting the stack through recursive serialization of cons cells. */ @SerialVersionUID(4L) - final class SerializationProxy[A](@transient protected var coll: LazyList[A]) extends Serializable { + final class SerializationProxy[A](of: LazyListIterable[A]^) extends Serializable { + @transient protected var coll: LazyListIterable[A]^{this} = of private[this] def writeObject(out: ObjectOutputStream): Unit = { out.defaultWriteObject() - var these = coll + var these: LazyListIterable[A]^{this} = coll while (these.knownNonEmpty) { out.writeObject(these.head) these = these.tail @@ -1436,9 +1485,9 @@ object LazyList extends SeqFactory[LazyList] { case SerializeEnd => initRead = true case a => init += a.asInstanceOf[A] } - val tail = in.readObject().asInstanceOf[LazyList[A]] + val tail = in.readObject().asInstanceOf[LazyListIterable[A]] // scala/scala#10118: caution that no code path can evaluate `tail.evaluated` - // before the resulting LazyList is returned + // before the resulting LazyListIterable is returned val it = init.toList.iterator coll = newLL(eagerHeadPrependIterator(it)(tail)) } From 4b70944b038ed1a1ddc48693843d20546a0fece0 Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Thu, 14 Aug 2025 18:42:23 +0200 Subject: [PATCH 079/101] Add back LazyList with a note --- TODO.md | 89 +- .../scala/collection/immutable/LazyList.scala | 1451 +++++++++++++++++ 2 files changed, 1496 insertions(+), 44 deletions(-) create mode 100644 library/src/scala/collection/immutable/LazyList.scala diff --git a/TODO.md b/TODO.md index 4291e537ea2e..9fc64211078f 100644 --- a/TODO.md +++ b/TODO.md @@ -261,6 +261,7 @@ - [x] library/src/scala/collection/immutable/IntMap.scala - [x] library/src/scala/collection/immutable/Iterable.scala - [ ] library/src/scala/collection/immutable/LazyList.scala +- [x] library/src/scala/collection/immutable/LazyListIterable.scala - [ ] library/src/scala/collection/immutable/List.scala - [ ] library/src/scala/collection/immutable/ListMap.scala - [ ] library/src/scala/collection/immutable/ListSet.scala @@ -284,51 +285,51 @@ - [ ] library/src/scala/collection/immutable/VectorMap.scala - [ ] library/src/scala/collection/immutable/WrappedString.scala - [x] library/src/scala/collection/immutable/package.scala -- [ ] library/src/scala/collection/mutable/AnyRefMap.scala -- [ ] library/src/scala/collection/mutable/ArrayBuffer.scala -- [ ] library/src/scala/collection/mutable/ArrayBuilder.scala -- [ ] library/src/scala/collection/mutable/ArrayDeque.scala -- [ ] library/src/scala/collection/mutable/ArraySeq.scala +- [x] library/src/scala/collection/mutable/AnyRefMap.scala +- [x] library/src/scala/collection/mutable/ArrayBuffer.scala +- [x] library/src/scala/collection/mutable/ArrayBuilder.scala +- [x] library/src/scala/collection/mutable/ArrayDeque.scala +- [x] library/src/scala/collection/mutable/ArraySeq.scala - [x] library/src/scala/collection/mutable/BitSet.scala -- [ ] library/src/scala/collection/mutable/Buffer.scala -- [ ] library/src/scala/collection/mutable/Builder.scala -- [ ] library/src/scala/collection/mutable/CheckedIndexedSeqView.scala -- [ ] library/src/scala/collection/mutable/Cloneable.scala -- [ ] library/src/scala/collection/mutable/CollisionProofHashMap.scala -- [ ] library/src/scala/collection/mutable/Growable.scala -- [ ] library/src/scala/collection/mutable/GrowableBuilder.scala -- [ ] library/src/scala/collection/mutable/HashMap.scala -- [ ] library/src/scala/collection/mutable/HashSet.scala -- [ ] library/src/scala/collection/mutable/HashTable.scala -- [ ] library/src/scala/collection/mutable/ImmutableBuilder.scala -- [ ] library/src/scala/collection/mutable/IndexedSeq.scala -- [ ] library/src/scala/collection/mutable/Iterable.scala -- [ ] library/src/scala/collection/mutable/LinkedHashMap.scala -- [ ] library/src/scala/collection/mutable/LinkedHashSet.scala -- [ ] library/src/scala/collection/mutable/ListBuffer.scala -- [ ] library/src/scala/collection/mutable/ListMap.scala -- [ ] library/src/scala/collection/mutable/LongMap.scala -- [ ] library/src/scala/collection/mutable/Map.scala -- [ ] library/src/scala/collection/mutable/MultiMap.scala -- [ ] library/src/scala/collection/mutable/MutationTracker.scala -- [ ] library/src/scala/collection/mutable/OpenHashMap.scala -- [ ] library/src/scala/collection/mutable/PriorityQueue.scala -- [ ] library/src/scala/collection/mutable/Queue.scala -- [ ] library/src/scala/collection/mutable/RedBlackTree.scala -- [ ] library/src/scala/collection/mutable/ReusableBuilder.scala -- [ ] library/src/scala/collection/mutable/Seq.scala -- [ ] library/src/scala/collection/mutable/SeqMap.scala -- [ ] library/src/scala/collection/mutable/Set.scala -- [ ] library/src/scala/collection/mutable/Shrinkable.scala -- [ ] library/src/scala/collection/mutable/SortedMap.scala -- [ ] library/src/scala/collection/mutable/SortedSet.scala -- [ ] library/src/scala/collection/mutable/Stack.scala -- [ ] library/src/scala/collection/mutable/StringBuilder.scala -- [ ] library/src/scala/collection/mutable/TreeMap.scala -- [ ] library/src/scala/collection/mutable/TreeSet.scala -- [ ] library/src/scala/collection/mutable/UnrolledBuffer.scala -- [ ] library/src/scala/collection/mutable/WeakHashMap.scala -- [ ] library/src/scala/collection/mutable/package.scala +- [x] library/src/scala/collection/mutable/Buffer.scala +- [x] library/src/scala/collection/mutable/Builder.scala +- [x] library/src/scala/collection/mutable/CheckedIndexedSeqView.scala +- [x] library/src/scala/collection/mutable/Cloneable.scala +- [x] library/src/scala/collection/mutable/CollisionProofHashMap.scala +- [x] library/src/scala/collection/mutable/Growable.scala +- [x] library/src/scala/collection/mutable/GrowableBuilder.scala +- [x] library/src/scala/collection/mutable/HashMap.scala +- [x] library/src/scala/collection/mutable/HashSet.scala +- [x] library/src/scala/collection/mutable/HashTable.scala +- [x] library/src/scala/collection/mutable/ImmutableBuilder.scala +- [x] library/src/scala/collection/mutable/IndexedSeq.scala +- [x] library/src/scala/collection/mutable/Iterable.scala +- [x] library/src/scala/collection/mutable/LinkedHashMap.scala +- [x] library/src/scala/collection/mutable/LinkedHashSet.scala +- [x] library/src/scala/collection/mutable/ListBuffer.scala +- [x] library/src/scala/collection/mutable/ListMap.scala +- [x] library/src/scala/collection/mutable/LongMap.scala +- [x] library/src/scala/collection/mutable/Map.scala +- [x] library/src/scala/collection/mutable/MultiMap.scala +- [x] library/src/scala/collection/mutable/MutationTracker.scala +- [x] library/src/scala/collection/mutable/OpenHashMap.scala +- [x] library/src/scala/collection/mutable/PriorityQueue.scala +- [x] library/src/scala/collection/mutable/Queue.scala +- [x] library/src/scala/collection/mutable/RedBlackTree.scala +- [x] library/src/scala/collection/mutable/ReusableBuilder.scala +- [x] library/src/scala/collection/mutable/Seq.scala +- [x] library/src/scala/collection/mutable/SeqMap.scala +- [x] library/src/scala/collection/mutable/Set.scala +- [x] library/src/scala/collection/mutable/Shrinkable.scala +- [x] library/src/scala/collection/mutable/SortedMap.scala +- [x] library/src/scala/collection/mutable/SortedSet.scala +- [x] library/src/scala/collection/mutable/Stack.scala +- [x] library/src/scala/collection/mutable/StringBuilder.scala +- [x] library/src/scala/collection/mutable/TreeMap.scala +- [x] library/src/scala/collection/mutable/TreeSet.scala +- [x] library/src/scala/collection/mutable/UnrolledBuffer.scala +- [x] library/src/scala/collection/mutable/WeakHashMap.scala +- [x] library/src/scala/collection/mutable/package.scala - [ ] library/src/scala/collection/package.scala - [ ] library/src/scala/compat/Platform.scala - [ ] library/src/scala/compiletime/Erased.scala diff --git a/library/src/scala/collection/immutable/LazyList.scala b/library/src/scala/collection/immutable/LazyList.scala new file mode 100644 index 000000000000..823144a19338 --- /dev/null +++ b/library/src/scala/collection/immutable/LazyList.scala @@ -0,0 +1,1451 @@ +/* + * 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 +package immutable + +import scala.language.`2.13` +import java.io.{ObjectInputStream, ObjectOutputStream} +import java.lang.{StringBuilder => JStringBuilder} + +import scala.annotation.tailrec +import scala.collection.generic.SerializeEnd +import scala.collection.mutable.{Builder, ReusableBuilder, StringBuilder} +import scala.language.implicitConversions +import scala.runtime.Statics + +/** This class implements an immutable linked list. We call it "lazy" + * because it computes its elements only when they are needed. + * + * Elements are memoized; that is, the value of each element is computed at most once. + * + * Elements are computed in order and are never skipped. + * As a consequence, accessing the tail causes the head to be computed first. + * + * How lazy is a `LazyList`? When you have a value of type `LazyList`, you + * don't know yet whether the list is empty. + * We say that it is lazy in its head. + * If you have tested that it is non-empty, + * then you also know that the head has been computed. + * + * It is also lazy in its tail, which is also a `LazyList`. + * You don't know whether the tail is empty until it is "forced", which is to say, + * until an element of the tail is computed. + * + * These important properties of `LazyList` depend on its construction using `#::` (or `#:::`). + * That operator is analogous to the "cons" of a strict `List`, `::`. + * It is "right-associative", so that the collection goes on the "right", + * and the element on the left of the operator is prepended to the collection. + * However, unlike the cons of a strict `List`, `#::` is lazy in its parameter, + * which is the element prepended to the left, and also lazy in its right-hand side, + * which is the `LazyList` being prepended to. + * (That is accomplished by implicitly wrapping the `LazyList`, as shown in the Scaladoc.) + * + * Other combinators from the collections API do not preserve this laziness. + * In particular, `++`, or `concat`, is "eager" or "strict" in its parameter + * and should not be used to compose `LazyList`s. + * + * A `LazyList` may be infinite. For example, `LazyList.from(0)` contains + * all of the natural numbers `0`, `1`, `2`, ... For infinite sequences, + * some methods (such as `count`, `sum`, `max` or `min`) will not terminate. + * + * Here is an example showing the Fibonacci sequence, + * which may be evaluated to an arbitrary number of elements: + * + * {{{ + * import scala.math.BigInt + * object Main extends App { + * val fibs: LazyList[BigInt] = + * BigInt(0) #:: BigInt(1) #:: fibs.zip(fibs.tail).map(n => n._1 + n._2) + * println { + * fibs.take(5).mkString(", ") + * } + * } + * // prints: 0, 1, 1, 2, 3 + * }}} + * + * To illustrate, let's add some output to the definition `fibs`, so we + * see what's going on. + * + * {{{ + * import scala.math.BigInt + * import scala.util.chaining._ + * object Main extends App { + * val fibs: LazyList[BigInt] = + * BigInt(0) #:: BigInt(1) #:: + * fibs.zip(fibs.tail).map(n => (n._1 + n._2) + * .tap(sum => println(s"Adding ${n._1} and ${n._2} => $sum"))) + * fibs.take(5).foreach(println) + * fibs.take(6).foreach(println) + * } + * + * // prints + * // + * // 0 + * // 1 + * // Adding 0 and 1 => 1 + * // 1 + * // Adding 1 and 1 => 2 + * // 2 + * // Adding 1 and 2 => 3 + * // 3 + * + * // And then prints + * // + * // 0 + * // 1 + * // 1 + * // 2 + * // 3 + * // Adding 2 and 3 => 5 + * // 5 + * }}} + * + * Note that the definition of `fibs` uses `val` not `def`. + * Memoization of the `LazyList` requires us to retain a reference to the computed values. + * + * `LazyList` is considered an immutable data structure, even though its elements are computed on demand. + * Once the values are memoized they do not change. + * Moreover, the `LazyList` itself is defined once and references to it are interchangeable. + * Values that have yet to be memoized still "exist"; they simply haven't been computed yet. + * + * Memoization can be a source of memory leaks and must be used with caution. + * It avoids recomputing elements of the list, but if a reference to the head + * is retained unintentionally, then all elements will be retained. + * + * The caveat that all elements are computed in order means + * that some operations, such as [[drop]], [[dropWhile]], [[flatMap]] or [[collect]], + * may process a large number of intermediate elements before returning. + * + * Here's an example that illustrates these behaviors. + * Let's begin with an iteration of the natural numbers. + * + * {{{ + * // We'll start with a silly iteration + * def loop(s: String, i: Int, iter: Iterator[Int]): Unit = { + * // Stop after 200,000 + * if (i < 200001) { + * if (i % 50000 == 0) println(s + i) + * loop(s, iter.next(), iter) + * } + * } + * + * // Our first LazyList definition will be a val definition + * val lazylist1: LazyList[Int] = { + * def loop(v: Int): LazyList[Int] = v #:: loop(v + 1) + * loop(0) + * } + * + * // Because lazylist1 is a val, everything that the iterator produces is held + * // by virtue of the fact that the head of the LazyList is held in lazylist1 + * val it1 = lazylist1.iterator + * loop("Iterator1: ", it1.next(), it1) + * + * // We can redefine this LazyList such that we retain only a reference to its Iterator. + * // That allows the LazyList to be garbage collected. + * // Using `def` to produce the LazyList in a method ensures + * // that no val is holding onto the head, as with lazylist1. + * def lazylist2: LazyList[Int] = { + * def loop(v: Int): LazyList[Int] = v #:: loop(v + 1) + * loop(0) + * } + * val it2 = lazylist2.iterator + * loop("Iterator2: ", it2.next(), it2) + * + * // And, of course, we don't actually need a LazyList at all for such a simple + * // problem. There's no reason to use a LazyList if you don't actually need + * // one. + * val it3 = new Iterator[Int] { + * var i = -1 + * def hasNext = true + * def next(): Int = { i += 1; i } + * } + * loop("Iterator3: ", it3.next(), it3) + * }}} + * + * In the `fibs` example earlier, the fact that `tail` works at all is of interest. + * `fibs` has an initial `(0, 1, LazyList(...))`, so `tail` is deterministic. + * If we defined `fibs` such that only `0` were concretely known, then the act + * of determining `tail` would require the evaluation of `tail`, so the + * computation would be unable to progress, as in this code: + * {{{ + * // The first time we try to access the tail we're going to need more + * // information which will require us to recurse, which will require us to + * // recurse, which... + * lazy val sov: LazyList[Vector[Int]] = Vector(0) #:: sov.zip(sov.tail).map { n => n._1 ++ n._2 } + * }}} + * + * The definition of `fibs` above creates a larger number of objects than + * necessary depending on how you might want to implement it. The following + * implementation provides a more "cost effective" implementation due to the + * fact that it has a more direct route to the numbers themselves: + * + * {{{ + * lazy val fib: LazyList[Int] = { + * def loop(h: Int, n: Int): LazyList[Int] = h #:: loop(n, h + n) + * loop(1, 1) + * } + * }}} + * + * The head, the tail and whether the list is empty is initially unknown. + * Once any of those are evaluated, they are all known, though if the tail is + * built with `#::` or `#:::`, its content still isn't evaluated. Instead, evaluating + * the tail's content is deferred until the tail's empty status, head or tail is + * evaluated. + * + * Delaying the evaluation of whether a LazyList is empty until it's needed + * allows LazyList to not eagerly evaluate any elements on a call to `filter`. + * + * Only when it's further evaluated (which may be never!) do any of the elements get forced. + * + * For example: + * + * {{{ + * def tailWithSideEffect: LazyList[Nothing] = { + * println("getting empty LazyList") + * LazyList.empty + * } + * + * val emptyTail = tailWithSideEffect // prints "getting empty LazyList" + * + * val suspended = 1 #:: tailWithSideEffect // doesn't print anything + * val tail = suspended.tail // although the tail is evaluated, *still* nothing is yet printed + * val filtered = tail.filter(_ => false) // still nothing is printed + * filtered.isEmpty // prints "getting empty LazyList" + * }}} + * + * ---- + * + * You may sometimes encounter an exception like the following: + * + * {{{ + * java.lang.RuntimeException: "LazyList evaluation depends on its own result (self-reference); see docs for more info + * }}} + * + * This exception occurs when a `LazyList` is attempting to derive its next element + * from itself, and is attempting to read the element currently being evaluated. + * As a trivial example: + * + * {{{ + * lazy val a: LazyList[Int] = 1 #:: 2 #:: a.filter(_ > 2) + * }}} + * + * When attempting to evaluate the third element of `a`, it will skip the first two + * elements and read the third, but that element is already being evaluated. This is + * often caused by a subtle logic error; in this case, using `>=` in the `filter` + * would fix the error. + * + * @tparam A the type of the elements contained in this lazy list. + * + * @see [[https://docs.scala-lang.org/overviews/collections-2.13/concrete-immutable-collection-classes.html#lazylists "Scala's Collection Library overview"]] + * section on `LazyLists` for a summary. + * @define Coll `LazyList` + * @define coll lazy list + * @define orderDependent + * @define orderDependentFold + * @define appendStackSafety Note: Repeated chaining of calls to append methods (`appended`, + * `appendedAll`, `lazyAppendedAll`) without forcing any of the + * intermediate resulting lazy lists may overflow the stack when + * the final result is forced. + * @define preservesLaziness This method preserves laziness; elements are only evaluated + * individually as needed. + * @define initiallyLazy This method does not evaluate anything until an operation is performed + * on the result (e.g. calling `head` or `tail`, or checking if it is empty). + * @define evaluatesAllElements This method evaluates all elements of the collection. + * @note Under Capture Checking, LazyList is unsound with regards to lazy/strict separation of collections. + * LazyList as-is will not be capture checked and might be deprecated in the future. + * Use LazyListIterable instead. + */ +@SerialVersionUID(4L) +final class LazyList[+A] private (lazyState: AnyRef /* EmptyMarker.type | () => LazyList[A] */) + extends AbstractSeq[A] + with LinearSeq[A] + with LinearSeqOps[A, LazyList, LazyList[A]] + with IterableFactoryDefaults[A, LazyList] + with Serializable { + import LazyList._ + + // kount() // LazyListTest.countAlloc + + private def this(head: A, tail: LazyList[A]) = { + this(LazyList.EmptyMarker) + _head = head + _tail = tail + } + + // used to synchronize lazy state evaluation + // after initialization (`_head ne Uninitialized`) + // - `null` if this is an empty lazy list + // - `head: A` otherwise (can be `null`, `_tail == null` is used to test emptiness) + @volatile private[this] var _head: Any /* Uninitialized | A */ = + if (lazyState eq EmptyMarker) null else Uninitialized + + // when `_head eq Uninitialized` + // - `lazySate: () => LazyList[A]` + // - MidEvaluation while evaluating lazyState + // when `_head ne Uninitialized` + // - `null` if this is an empty lazy list + // - `tail: LazyList[A]` otherwise + private[this] var _tail: AnyRef /* () => LazyList[A] | MidEvaluation.type | LazyList[A] */ = + if (lazyState eq EmptyMarker) null else lazyState + + private def rawHead: Any = _head + private def rawTail: AnyRef = _tail + + @inline private def isEvaluated: Boolean = _head.asInstanceOf[AnyRef] ne Uninitialized + + private def initState(): Unit = synchronized { + if (!isEvaluated) { + // if it's already mid-evaluation, we're stuck in an infinite + // self-referential loop (also it's empty) + if (_tail eq MidEvaluation) + throw new RuntimeException( + "LazyList evaluation depends on its own result (self-reference); see docs for more info") + + val fun = _tail.asInstanceOf[() => LazyList[A]] + _tail = MidEvaluation + val l = + // `fun` returns a LazyList that represents the state (head/tail) of `this`. We call `l.evaluated` to ensure + // `l` is initialized, to prevent races when reading `rawTail` / `rawHead` below. + // Often, lazy lists are created with `newLL(eagerCons(...))` so `l` is already initialized, but `newLL` also + // accepts non-evaluated lazy lists. + try fun().evaluated + // restore `fun` in finally so we can try again later if an exception was thrown (similar to lazy val) + finally _tail = fun + _tail = l.rawTail + _head = l.rawHead + } + } + + @tailrec private def evaluated: LazyList[A] = + if (isEvaluated) { + if (_tail == null) Empty + else this + } else { + initState() + evaluated + } + + override def iterableFactory: SeqFactory[LazyList] = LazyList + + // NOTE: `evaluated; this eq Empty` would be wrong. Deserialization of `Empty` creates a new + // instance with `null` fields, but the `evaluated` method always returns the canonical `Empty`. + @inline override def isEmpty: Boolean = evaluated eq Empty + + /** @inheritdoc + * + * $preservesLaziness + */ + override def knownSize: Int = if (knownIsEmpty) 0 else -1 + + override def head: A = + // inlined `isEmpty` to make it clear that `rawHead` below is initialized + if (evaluated eq Empty) throw new NoSuchElementException("head of empty lazy list") + else rawHead.asInstanceOf[A] + + override def tail: LazyList[A] = + // inlined `isEmpty` to make it clear that `rawTail` below is initialized + if (evaluated eq Empty) throw new UnsupportedOperationException("tail of empty lazy list") + else rawTail.asInstanceOf[LazyList[A]] + + @inline private[this] def knownIsEmpty: Boolean = isEvaluated && isEmpty + @inline private def knownNonEmpty: Boolean = isEvaluated && !isEmpty + + /** Evaluates all undefined elements of the lazy list. + * + * This method detects cycles in lazy lists, and terminates after all + * elements of the cycle are evaluated. For example: + * + * {{{ + * val ring: LazyList[Int] = 1 #:: 2 #:: 3 #:: ring + * ring.force + * ring.toString + * + * // prints + * // + * // LazyList(1, 2, 3, ...) + * }}} + * + * This method will *not* terminate for non-cyclic infinite-sized collections. + * + * @return this + */ + def force: this.type = { + // Use standard 2x 1x iterator trick for cycle detection ("those" is slow one) + var these, those: LazyList[A] = this + if (!these.isEmpty) { + these = these.tail + } + while (those ne these) { + if (these.isEmpty) return this + these = these.tail + if (these.isEmpty) return this + these = these.tail + if (these eq those) return this + those = those.tail + } + this + } + + /** @inheritdoc + * + * The iterator returned by this method preserves laziness; elements are + * only evaluated individually as needed. + */ + override def iterator: Iterator[A] = + if (knownIsEmpty) Iterator.empty + else new LazyIterator(this) + + /** Apply the given function `f` to each element of this linear sequence + * (while respecting the order of the elements). + * + * @param f The treatment to apply to each element. + * @note Overridden here as final to trigger tail-call optimization, which + * replaces 'this' with 'tail' at each iteration. This is absolutely + * necessary for allowing the GC to collect the underlying LazyList as elements + * are consumed. + * @note This function will force the realization of the entire LazyList + * unless the `f` throws an exception. + */ + @tailrec + override def foreach[U](f: A => U): Unit = { + if (!isEmpty) { + f(head) + tail.foreach(f) + } + } + + /** LazyList specialization of foldLeft which allows GC to collect along the + * way. + * + * @tparam B The type of value being accumulated. + * @param z The initial value seeded into the function `op`. + * @param op The operation to perform on successive elements of the `LazyList`. + * @return The accumulated value from successive applications of `op`. + */ + @tailrec + override def foldLeft[B](z: B)(op: (B, A) => B): B = + if (isEmpty) z + else tail.foldLeft(op(z, head))(op) + + // LazyList.Empty doesn't use the SerializationProxy + protected[this] def writeReplace(): AnyRef = + if (knownNonEmpty) new SerializationProxy[A](this) else this + + override protected[this] def className = "LazyList" + + /** The lazy list resulting from the concatenation of this lazy list with the argument lazy list. + * + * $preservesLaziness + * + * $appendStackSafety + * + * @param suffix The collection that gets appended to this lazy list + * @return The lazy list containing elements of this lazy list and the iterable object. + */ + def lazyAppendedAll[B >: A](suffix: => collection.IterableOnce[B]): LazyList[B] = + newLL { + if (isEmpty) suffix match { + case lazyList: LazyList[B] => lazyList // don't recompute the LazyList + case coll if coll.knownSize == 0 => Empty + case coll => eagerHeadFromIterator(coll.iterator) + } + else eagerCons(head, tail lazyAppendedAll suffix) + } + + /** @inheritdoc + * + * $preservesLaziness + * + * $appendStackSafety + */ + override def appendedAll[B >: A](suffix: IterableOnce[B]): LazyList[B] = + if (knownIsEmpty) LazyList.from(suffix) + else lazyAppendedAll(suffix) + + /** @inheritdoc + * + * $preservesLaziness + * + * $appendStackSafety + */ + override def appended[B >: A](elem: B): LazyList[B] = + if (knownIsEmpty) eagerCons(elem, Empty) + else lazyAppendedAll(Iterator.single(elem)) + + /** @inheritdoc + * + * $preservesLaziness + */ + override def scanLeft[B](z: B)(op: (B, A) => B): LazyList[B] = + if (knownIsEmpty) eagerCons(z, Empty) + else scanLeftImpl(z)(op) + + private def scanLeftImpl[B](z: B)(op: (B, A) => B): LazyList[B] = + eagerCons( + z, + newLL { + if (isEmpty) Empty + else tail.scanLeftImpl(op(z, head))(op) + } + ) + + /** LazyList specialization of reduceLeft which allows GC to collect + * along the way. + * + * @tparam B The type of value being accumulated. + * @param f The operation to perform on successive elements of the `LazyList`. + * @return The accumulated value from successive applications of `f`. + */ + override def reduceLeft[B >: A](f: (B, A) => B): B = { + if (isEmpty) throw new UnsupportedOperationException("empty.reduceLeft") + else { + var reducedRes: B = head + var left: LazyList[A] = tail + while (!left.isEmpty) { + reducedRes = f(reducedRes, left.head) + left = left.tail + } + reducedRes + } + } + + /** @inheritdoc + * + * $preservesLaziness + */ + override def partition(p: A => Boolean): (LazyList[A], LazyList[A]) = (filter(p), filterNot(p)) + + /** @inheritdoc + * + * $preservesLaziness + */ + override def partitionMap[A1, A2](f: A => Either[A1, A2]): (LazyList[A1], LazyList[A2]) = { + val (left, right) = map(f).partition(_.isLeft) + (left.map(_.asInstanceOf[Left[A1, _]].value), right.map(_.asInstanceOf[Right[_, A2]].value)) + } + + /** @inheritdoc + * + * $preservesLaziness + */ + override def filter(pred: A => Boolean): LazyList[A] = + if (knownIsEmpty) Empty + else filterImpl(this, pred, isFlipped = false) + + /** @inheritdoc + * + * $preservesLaziness + */ + override def filterNot(pred: A => Boolean): LazyList[A] = + if (knownIsEmpty) Empty + else filterImpl(this, pred, isFlipped = true) + + /** A `collection.WithFilter` which allows GC of the head of lazy list during processing. + * + * This method is not particularly useful for a lazy list, as [[filter]] already preserves + * laziness. + * + * The `collection.WithFilter` returned by this method preserves laziness; elements are + * only evaluated individually as needed. + */ + override def withFilter(p: A => Boolean): collection.WithFilter[A, LazyList] = + new LazyList.WithFilter(coll, p) + + /** @inheritdoc + * + * $preservesLaziness + */ + override def prepended[B >: A](elem: B): LazyList[B] = eagerCons(elem, this) + + /** @inheritdoc + * + * $preservesLaziness + */ + override def prependedAll[B >: A](prefix: collection.IterableOnce[B]): LazyList[B] = + if (knownIsEmpty) LazyList.from(prefix) + else if (prefix.knownSize == 0) this + else newLL(eagerHeadPrependIterator(prefix.iterator)(this)) + + /** @inheritdoc + * + * $preservesLaziness + */ + override def map[B](f: A => B): LazyList[B] = + if (knownIsEmpty) Empty + else mapImpl(f) + + /** @inheritdoc + * + * $preservesLaziness + */ + override def tapEach[U](f: A => U): LazyList[A] = map { a => f(a); a } + + private def mapImpl[B](f: A => B): LazyList[B] = + newLL { + if (isEmpty) Empty + else eagerCons(f(head), tail.mapImpl(f)) + } + + /** @inheritdoc + * + * $preservesLaziness + */ + override def collect[B](pf: PartialFunction[A, B]): LazyList[B] = + if (knownIsEmpty) Empty + else collectImpl(this, pf) + + /** @inheritdoc + * + * This method does not evaluate any elements further than + * the first element for which the partial function is defined. + */ + @tailrec + override def collectFirst[B](pf: PartialFunction[A, B]): Option[B] = + if (isEmpty) None + else { + val res = pf.applyOrElse(head, anyToMarker.asInstanceOf[A => B]) + if (res.asInstanceOf[AnyRef] eq Statics.pfMarker) tail.collectFirst(pf) + else Some(res) + } + + /** @inheritdoc + * + * This method does not evaluate any elements further than + * the first element matching the predicate. + */ + @tailrec + override def find(p: A => Boolean): Option[A] = + if (isEmpty) None + else { + val elem = head + if (p(elem)) Some(elem) + else tail.find(p) + } + + /** @inheritdoc + * + * $preservesLaziness + */ + // optimisations are not for speed, but for functionality + // see tickets #153, #498, #2147, and corresponding tests in run/ (as well as run/stream_flatmap_odds.scala) + override def flatMap[B](f: A => IterableOnce[B]): LazyList[B] = + if (knownIsEmpty) Empty + else flatMapImpl(this, f) + + /** @inheritdoc + * + * $preservesLaziness + */ + override def flatten[B](implicit asIterable: A => IterableOnce[B]): LazyList[B] = flatMap(asIterable) + + /** @inheritdoc + * + * $preservesLaziness + */ + override def zip[B](that: collection.IterableOnce[B]): LazyList[(A, B)] = + if (knownIsEmpty || that.knownSize == 0) Empty + else newLL(eagerHeadZipImpl(that.iterator)) + + private def eagerHeadZipImpl[B](it: Iterator[B]): LazyList[(A, B)] = + if (isEmpty || !it.hasNext) Empty + else eagerCons((head, it.next()), newLL { tail eagerHeadZipImpl it }) + + /** @inheritdoc + * + * $preservesLaziness + */ + override def zipWithIndex: LazyList[(A, Int)] = this zip LazyList.from(0) + + /** @inheritdoc + * + * $preservesLaziness + */ + override def zipAll[A1 >: A, B](that: collection.Iterable[B], thisElem: A1, thatElem: B): LazyList[(A1, B)] = { + if (knownIsEmpty) { + if (that.knownSize == 0) Empty + else LazyList.continually(thisElem) zip that + } else { + if (that.knownSize == 0) zip(LazyList.continually(thatElem)) + else newLL(eagerHeadZipAllImpl(that.iterator, thisElem, thatElem)) + } + } + + private def eagerHeadZipAllImpl[A1 >: A, B](it: Iterator[B], thisElem: A1, thatElem: B): LazyList[(A1, B)] = { + if (it.hasNext) { + if (isEmpty) eagerCons((thisElem, it.next()), newLL { LazyList.continually(thisElem) eagerHeadZipImpl it }) + else eagerCons((head, it.next()), newLL { tail.eagerHeadZipAllImpl(it, thisElem, thatElem) }) + } else { + if (isEmpty) Empty + else eagerCons((head, thatElem), tail zip LazyList.continually(thatElem)) + } + } + + /** @inheritdoc + * + * This method is not particularly useful for a lazy list, as [[zip]] already preserves + * laziness. + * + * The `collection.LazyZip2` returned by this method preserves laziness; elements are + * only evaluated individually as needed. + */ + // just in case it can be meaningfully overridden at some point + override def lazyZip[B](that: collection.Iterable[B]): LazyZip2[A, B, this.type] = + super.lazyZip(that) + + /** @inheritdoc + * + * $preservesLaziness + */ + override def unzip[A1, A2](implicit asPair: A => (A1, A2)): (LazyList[A1], LazyList[A2]) = + (map(asPair(_)._1), map(asPair(_)._2)) + + /** @inheritdoc + * + * $preservesLaziness + */ + override def unzip3[A1, A2, A3](implicit asTriple: A => (A1, A2, A3)): (LazyList[A1], LazyList[A2], LazyList[A3]) = + (map(asTriple(_)._1), map(asTriple(_)._2), map(asTriple(_)._3)) + + /** @inheritdoc + * + * $initiallyLazy + * Additionally, it preserves laziness for all except the first `n` elements. + */ + override def drop(n: Int): LazyList[A] = + if (n <= 0) this + else if (knownIsEmpty) Empty + else dropImpl(this, n) + + /** @inheritdoc + * + * $initiallyLazy + * Additionally, it preserves laziness for all elements after the predicate returns `false`. + */ + override def dropWhile(p: A => Boolean): LazyList[A] = + if (knownIsEmpty) Empty + else dropWhileImpl(this, p) + + /** @inheritdoc + * + * $initiallyLazy + */ + override def dropRight(n: Int): LazyList[A] = { + if (n <= 0) this + else if (knownIsEmpty) Empty + else newLL { + var scout = this + var remaining = n + // advance scout n elements ahead (or until empty) + while (remaining > 0 && !scout.isEmpty) { + remaining -= 1 + scout = scout.tail + } + eagerHeadDropRightImpl(scout) + } + } + + private def eagerHeadDropRightImpl(scout: LazyList[_]): LazyList[A] = + if (scout.isEmpty) Empty + else eagerCons(head, newLL(tail.eagerHeadDropRightImpl(scout.tail))) + + /** @inheritdoc + * + * $preservesLaziness + */ + override def take(n: Int): LazyList[A] = + if (knownIsEmpty) Empty + else takeImpl(n) + + private def takeImpl(n: Int): LazyList[A] = { + if (n <= 0) Empty + else newLL { + if (isEmpty) Empty + else eagerCons(head, tail.takeImpl(n - 1)) + } + } + + /** @inheritdoc + * + * $preservesLaziness + */ + override def takeWhile(p: A => Boolean): LazyList[A] = + if (knownIsEmpty) Empty + else takeWhileImpl(p) + + private def takeWhileImpl(p: A => Boolean): LazyList[A] = + newLL { + if (isEmpty || !p(head)) Empty + else eagerCons(head, tail.takeWhileImpl(p)) + } + + /** @inheritdoc + * + * $initiallyLazy + */ + override def takeRight(n: Int): LazyList[A] = + if (n <= 0 || knownIsEmpty) Empty + else takeRightImpl(this, n) + + /** @inheritdoc + * + * $initiallyLazy + * Additionally, it preserves laziness for all but the first `from` elements. + */ + override def slice(from: Int, until: Int): LazyList[A] = take(until).drop(from) + + /** @inheritdoc + * + * $evaluatesAllElements + */ + override def reverse: LazyList[A] = reverseOnto(Empty) + + // need contravariant type B to make the compiler happy - still returns LazyList[A] + @tailrec + private def reverseOnto[B >: A](tl: LazyList[B]): LazyList[B] = + if (isEmpty) tl + else tail.reverseOnto(newLL(eagerCons(head, tl))) + + /** @inheritdoc + * + * $preservesLaziness + */ + override def diff[B >: A](that: collection.Seq[B]): LazyList[A] = + if (knownIsEmpty) Empty + else super.diff(that) + + /** @inheritdoc + * + * $preservesLaziness + */ + override def intersect[B >: A](that: collection.Seq[B]): LazyList[A] = + if (knownIsEmpty) Empty + else super.intersect(that) + + @tailrec + private def lengthGt(len: Int): Boolean = + if (len < 0) true + else if (isEmpty) false + else tail.lengthGt(len - 1) + + /** @inheritdoc + * + * The iterator returned by this method mostly preserves laziness; + * a single element ahead of the iterator is evaluated. + */ + override def grouped(size: Int): Iterator[LazyList[A]] = { + require(size > 0, "size must be positive, but was " + size) + slidingImpl(size = size, step = size) + } + + /** @inheritdoc + * + * The iterator returned by this method mostly preserves laziness; + * `size - step max 1` elements ahead of the iterator are evaluated. + */ + override def sliding(size: Int, step: Int): Iterator[LazyList[A]] = { + require(size > 0 && step > 0, s"size=$size and step=$step, but both must be positive") + slidingImpl(size = size, step = step) + } + + @inline private def slidingImpl(size: Int, step: Int): Iterator[LazyList[A]] = + if (knownIsEmpty) Iterator.empty + else new SlidingIterator[A](this, size = size, step = step) + + /** @inheritdoc + * + * $preservesLaziness + */ + override def padTo[B >: A](len: Int, elem: B): LazyList[B] = + if (len <= 0) this + else newLL { + if (isEmpty) LazyList.fill(len)(elem) + else eagerCons(head, tail.padTo(len - 1, elem)) + } + + /** @inheritdoc + * + * $preservesLaziness + */ + override def patch[B >: A](from: Int, other: IterableOnce[B], replaced: Int): LazyList[B] = + if (knownIsEmpty) LazyList from other + else patchImpl(from, other, replaced) + + private def patchImpl[B >: A](from: Int, other: IterableOnce[B], replaced: Int): LazyList[B] = + newLL { + if (from <= 0) eagerHeadPrependIterator(other.iterator)(dropImpl(this, replaced)) + else if (isEmpty) eagerHeadFromIterator(other.iterator) + else eagerCons(head, tail.patchImpl(from - 1, other, replaced)) + } + + /** @inheritdoc + * + * $evaluatesAllElements + */ + // overridden just in case a lazy implementation is developed at some point + override def transpose[B](implicit asIterable: A => collection.Iterable[B]): LazyList[LazyList[B]] = super.transpose + + /** @inheritdoc + * + * $preservesLaziness + */ + override def updated[B >: A](index: Int, elem: B): LazyList[B] = + if (index < 0) throw new IndexOutOfBoundsException(s"$index") + else updatedImpl(index, elem, index) + + private def updatedImpl[B >: A](index: Int, elem: B, startIndex: Int): LazyList[B] = + newLL { + if (index <= 0) eagerCons(elem, tail) + else if (tail.isEmpty) throw new IndexOutOfBoundsException(startIndex.toString) + else eagerCons(head, tail.updatedImpl(index - 1, elem, startIndex)) + } + + /** Appends all elements of this $coll to a string builder using start, end, and separator strings. + * The written text begins with the string `start` and ends with the string `end`. + * Inside, the string representations (w.r.t. the method `toString`) + * of all elements of this $coll are separated by the string `sep`. + * + * An undefined state is represented with `"<not computed>"` and cycles are represented with `"<cycle>"`. + * + * $evaluatesAllElements + * + * @param sb the string builder to which elements are appended. + * @param start the starting string. + * @param sep the separator string. + * @param end the ending string. + * @return the string builder `b` to which elements were appended. + */ + override def addString(sb: StringBuilder, start: String, sep: String, end: String): sb.type = { + force + addStringNoForce(sb.underlying, start, sep, end) + sb + } + + private[this] def addStringNoForce(b: JStringBuilder, start: String, sep: String, end: String): b.type = { + b.append(start) + if (!isEvaluated) b.append("") + else if (!isEmpty) { + b.append(head) + var cursor = this + // explicit param to prevent an ObjectRef for cursor + @inline def appendHead(c: LazyList[A]): Unit = b.append(sep).append(c.head) + var scout = tail + if (cursor ne scout) { + cursor = scout + if (scout.knownNonEmpty) { + scout = scout.tail + // Use 2x 1x iterator trick for cycle detection; slow iterator can add strings + while ((cursor ne scout) && scout.knownNonEmpty) { + appendHead(cursor) + cursor = cursor.tail + scout = scout.tail + if (scout.knownNonEmpty) scout = scout.tail + } + } + } + if (!scout.knownNonEmpty) { // Not a cycle, scout hit an end (empty or non-evaluated) + while (cursor ne scout) { + appendHead(cursor) + cursor = cursor.tail + } + // if cursor (eq scout) has state defined, it is empty; else unknown state + if (!cursor.isEvaluated) b.append(sep).append("") + } else { + // Cycle: the scout is `knownNonEmpty` and `eq cursor`. + // if the cycle starts at `this`, its elements were already added + if (cursor ne this) { + // If we have a prefix of length P followed by a cycle of length C, + // the scout will be at position (P%C) in the cycle when the cursor + // enters it at P. They'll then collide when the scout advances another + // C - (P%C) ahead of the cursor. + // If we run the scout P farther, then it will be at the start of + // the cycle: (C - (P%C) + (P%C)) == C == 0. So if another runner + // starts at the beginning of the prefix, they'll collide exactly at + // the start of the loop. + var runner = this + while (runner ne scout) { + runner = runner.tail + scout = scout.tail + } + while({ + val ct = cursor.tail + if (ct ne scout) { + // In `lazy val xs: LazyList[Int] = 1 #:: 2 #:: xs`, method `#::` creates a LazyList instance which ends up as the 3rd element. + // That 3rd element initially has unknown head/tail. Once it completes, the tail is assigned to be `xs.tail`. + // So in memory the structure is `LLx(1, LLy(2, LLz(1, )))`. + // In `toString` we skip the last element to maintain the illusion. + appendHead(cursor) + } + cursor = ct + cursor ne scout + }) () + } + b.append(sep).append("") + } + } + b.append(end) + b + } + + /** $preservesLaziness + * + * @return a string representation of this collection. An undefined state is + * represented with `"<not computed>"` and cycles are represented with `"<cycle>"` + * + * Examples: + * + * - `"LazyList(4, <not computed>)"`, a non-empty lazy list ; + * - `"LazyList(1, 2, 3, <not computed>)"`, a lazy list with at least three elements ; + * - `"LazyList(1, 2, 3, <cycle>)"`, an infinite lazy list that contains + * a cycle at the fourth element. + */ + override def toString(): String = addStringNoForce(new JStringBuilder(className), "(", ", ", ")").toString + + /** @inheritdoc + * + * $preservesLaziness + */ + @deprecated("Check .knownSize instead of .hasDefiniteSize for more actionable information (see scaladoc for details)", "2.13.0") + override def hasDefiniteSize: Boolean = { + if (!isEvaluated) false + else if (isEmpty) true + else { + // Two-iterator trick (2x & 1x speed) for cycle detection. + var those = this + var these = tail + while (those ne these) { + if (!these.isEvaluated) return false + else if (these.isEmpty) return true + these = these.tail + if (!these.isEvaluated) return false + else if (these.isEmpty) return true + these = these.tail + if (those eq these) return false + those = those.tail + } + false // Cycle detected + } + } +} + +/** + * $factoryInfo + * @define coll lazy list + * @define Coll `LazyList` + */ +@SerialVersionUID(4L) +object LazyList extends SeqFactory[LazyList] { + + // LazyListTest.countAlloc + // var k = 0 + // def kount(): Unit = k += 1 + + private object Uninitialized extends Serializable + private object MidEvaluation + private object EmptyMarker + + private val Empty: LazyList[Nothing] = new LazyList(EmptyMarker) + + /** Creates a new LazyList. */ + @inline private def newLL[A](state: => LazyList[A]): LazyList[A] = new LazyList[A](() => state) + + /** Creates a new LazyList with evaluated `head` and `tail`. */ + @inline private def eagerCons[A](hd: A, tl: LazyList[A]): LazyList[A] = new LazyList[A](hd, tl) + + private val anyToMarker: Any => Any = _ => Statics.pfMarker + + /* All of the following `Impl` methods are carefully written so as not to + * leak the beginning of the `LazyList`. They copy the initial `LazyList` (`ll`) into + * `var rest`, which gets closed over as a `scala.runtime.ObjectRef`, thus not permanently + * leaking the head of the `LazyList`. Additionally, the methods are written so that, should + * an exception be thrown by the evaluation of the `LazyList` or any supplied function, they + * can continue their execution where they left off. + */ + + private def filterImpl[A](ll: LazyList[A], p: A => Boolean, isFlipped: Boolean): LazyList[A] = { + // DO NOT REFERENCE `ll` ANYWHERE ELSE, OR IT WILL LEAK THE HEAD + var restRef = ll // val restRef = new ObjectRef(ll) + newLL { + var elem: A = null.asInstanceOf[A] + var found = false + var rest = restRef // var rest = restRef.elem + while (!found && !rest.isEmpty) { + elem = rest.head + found = p(elem) != isFlipped + rest = rest.tail + restRef = rest // restRef.elem = rest + } + if (found) eagerCons(elem, filterImpl(rest, p, isFlipped)) else Empty + } + } + + private def collectImpl[A, B](ll: LazyList[A], pf: PartialFunction[A, B]): LazyList[B] = { + // DO NOT REFERENCE `ll` ANYWHERE ELSE, OR IT WILL LEAK THE HEAD + var restRef = ll // val restRef = new ObjectRef(ll) + newLL { + val marker = Statics.pfMarker + val toMarker = anyToMarker.asInstanceOf[A => B] // safe because Function1 is erased + + var res: B = marker.asInstanceOf[B] // safe because B is unbounded + var rest = restRef // var rest = restRef.elem + while((res.asInstanceOf[AnyRef] eq marker) && !rest.isEmpty) { + res = pf.applyOrElse(rest.head, toMarker) + rest = rest.tail + restRef = rest // restRef.elem = rest + } + if (res.asInstanceOf[AnyRef] eq marker) Empty + else eagerCons(res, collectImpl(rest, pf)) + } + } + + private def flatMapImpl[A, B](ll: LazyList[A], f: A => IterableOnce[B]): LazyList[B] = { + // DO NOT REFERENCE `ll` ANYWHERE ELSE, OR IT WILL LEAK THE HEAD + var restRef = ll // val restRef = new ObjectRef(ll) + newLL { + var it: Iterator[B] = null + var itHasNext = false + var rest = restRef // var rest = restRef.elem + while (!itHasNext && !rest.isEmpty) { + it = f(rest.head).iterator + itHasNext = it.hasNext + if (!itHasNext) { // wait to advance `rest` because `it.next()` can throw + rest = rest.tail + restRef = rest // restRef.elem = rest + } + } + if (itHasNext) { + val head = it.next() + rest = rest.tail + restRef = rest // restRef.elem = rest + eagerCons(head, newLL(eagerHeadPrependIterator(it)(flatMapImpl(rest, f)))) + } else Empty + } + } + + private def dropImpl[A](ll: LazyList[A], n: Int): LazyList[A] = { + // DO NOT REFERENCE `ll` ANYWHERE ELSE, OR IT WILL LEAK THE HEAD + var restRef = ll // val restRef = new ObjectRef(ll) + var iRef = n // val iRef = new IntRef(n) + newLL { + var rest = restRef // var rest = restRef.elem + var i = iRef // var i = iRef.elem + while (i > 0 && !rest.isEmpty) { + rest = rest.tail + restRef = rest // restRef.elem = rest + i -= 1 + iRef = i // iRef.elem = i + } + rest + } + } + + private def dropWhileImpl[A](ll: LazyList[A], p: A => Boolean): LazyList[A] = { + // DO NOT REFERENCE `ll` ANYWHERE ELSE, OR IT WILL LEAK THE HEAD + var restRef = ll // val restRef = new ObjectRef(ll) + newLL { + var rest = restRef // var rest = restRef.elem + while (!rest.isEmpty && p(rest.head)) { + rest = rest.tail + restRef = rest // restRef.elem = rest + } + rest + } + } + + private def takeRightImpl[A](ll: LazyList[A], n: Int): LazyList[A] = { + // DO NOT REFERENCE `ll` ANYWHERE ELSE, OR IT WILL LEAK THE HEAD + var restRef = ll // val restRef = new ObjectRef(ll) + var scoutRef = ll // val scoutRef = new ObjectRef(ll) + var remainingRef = n // val remainingRef = new IntRef(n) + newLL { + var scout = scoutRef // var scout = scoutRef.elem + var remaining = remainingRef // var remaining = remainingRef.elem + // advance `scout` `n` elements ahead (or until empty) + while (remaining > 0 && !scout.isEmpty) { + scout = scout.tail + scoutRef = scout // scoutRef.elem = scout + remaining -= 1 + remainingRef = remaining // remainingRef.elem = remaining + } + var rest = restRef // var rest = restRef.elem + // advance `rest` and `scout` in tandem until `scout` reaches the end + while(!scout.isEmpty) { + scout = scout.tail + scoutRef = scout // scoutRef.elem = scout + rest = rest.tail // can't throw an exception as `scout` has already evaluated its tail + restRef = rest // restRef.elem = rest + } + // `rest` is the last `n` elements (or all of them) + rest + } + } + + /** An alternative way of building and matching lazy lists using LazyList.cons(hd, tl). + */ + object cons { + /** A lazy list consisting of a given first element and remaining elements + * @param hd The first element of the result lazy list + * @param tl The remaining elements of the result lazy list + */ + def apply[A](hd: => A, tl: => LazyList[A]): LazyList[A] = newLL(eagerCons(hd, newLL(tl))) + + /** Maps a lazy list to its head and tail */ + def unapply[A](xs: LazyList[A]): Option[(A, LazyList[A])] = #::.unapply(xs) + } + + implicit def toDeferrer[A](l: => LazyList[A]): Deferrer[A] = new Deferrer[A](() => l) + + final class Deferrer[A] private[LazyList] (private val l: () => LazyList[A]) extends AnyVal { + /** Construct a LazyList consisting of a given first element followed by elements + * from another LazyList. + */ + def #:: [B >: A](elem: => B): LazyList[B] = newLL(eagerCons(elem, newLL(l()))) + /** Construct a LazyList consisting of the concatenation of the given LazyList and + * another LazyList. + */ + def #:::[B >: A](prefix: LazyList[B]): LazyList[B] = prefix lazyAppendedAll l() + } + + object #:: { + def unapply[A](s: LazyList[A]): Option[(A, LazyList[A])] = + if (!s.isEmpty) Some((s.head, s.tail)) else None + } + + def from[A](coll: collection.IterableOnce[A]): LazyList[A] = coll match { + case lazyList: LazyList[A] => lazyList + case _ if coll.knownSize == 0 => empty[A] + case _ => newLL(eagerHeadFromIterator(coll.iterator)) + } + + def empty[A]: LazyList[A] = Empty + + /** Creates a LazyList with the elements of an iterator followed by a LazyList suffix. + * Eagerly evaluates the first element. + */ + private def eagerHeadPrependIterator[A](it: Iterator[A])(suffix: => LazyList[A]): LazyList[A] = + if (it.hasNext) eagerCons(it.next(), newLL(eagerHeadPrependIterator(it)(suffix))) + else suffix + + /** Creates a LazyList from an Iterator. Eagerly evaluates the first element. */ + private def eagerHeadFromIterator[A](it: Iterator[A]): LazyList[A] = + if (it.hasNext) eagerCons(it.next(), newLL(eagerHeadFromIterator(it))) + else Empty + + override def concat[A](xss: collection.Iterable[A]*): LazyList[A] = + if (xss.knownSize == 0) empty + else newLL(eagerHeadConcatIterators(xss.iterator)) + + private def eagerHeadConcatIterators[A](it: Iterator[collection.Iterable[A]]): LazyList[A] = + if (!it.hasNext) Empty + else eagerHeadPrependIterator(it.next().iterator)(eagerHeadConcatIterators(it)) + + /** An infinite LazyList that repeatedly applies a given function to a start value. + * + * @param start the start value of the LazyList + * @param f the function that's repeatedly applied + * @return the LazyList returning the infinite sequence of values `start, f(start), f(f(start)), ...` + */ + def iterate[A](start: => A)(f: A => A): LazyList[A] = + newLL { + val head = start + eagerCons(head, iterate(f(head))(f)) + } + + /** + * Create an infinite LazyList starting at `start` and incrementing by + * step `step`. + * + * @param start the start value of the LazyList + * @param step the increment value of the LazyList + * @return the LazyList starting at value `start`. + */ + def from(start: Int, step: Int): LazyList[Int] = + newLL(eagerCons(start, from(start + step, step))) + + /** + * Create an infinite LazyList starting at `start` and incrementing by `1`. + * + * @param start the start value of the LazyList + * @return the LazyList starting at value `start`. + */ + def from(start: Int): LazyList[Int] = from(start, 1) + + /** + * Create an infinite LazyList containing the given element expression (which + * is computed for each occurrence). + * + * @param elem the element composing the resulting LazyList + * @return the LazyList containing an infinite number of elem + */ + def continually[A](elem: => A): LazyList[A] = newLL(eagerCons(elem, continually(elem))) + + override def fill[A](n: Int)(elem: => A): LazyList[A] = + if (n > 0) newLL(eagerCons(elem, LazyList.fill(n - 1)(elem))) else empty + + override def tabulate[A](n: Int)(f: Int => A): LazyList[A] = { + def at(index: Int): LazyList[A] = + if (index < n) newLL(eagerCons(f(index), at(index + 1))) else empty + + at(0) + } + + // significantly simpler than the iterator returned by Iterator.unfold + override def unfold[A, S](init: S)(f: S => Option[(A, S)]): LazyList[A] = + newLL { + f(init) match { + case Some((elem, state)) => eagerCons(elem, unfold(state)(f)) + case None => Empty + } + } + + /** The builder returned by this method only evaluates elements + * of collections added to it as needed. + * + * @tparam A the type of the ${coll}’s elements + * @return A builder for $Coll objects. + */ + def newBuilder[A]: Builder[A, LazyList[A]] = new LazyBuilder[A] + + private class LazyIterator[+A](private[this] var lazyList: LazyList[A]) extends AbstractIterator[A] { + override def hasNext: Boolean = !lazyList.isEmpty + + override def next(): A = + if (lazyList.isEmpty) Iterator.empty.next() + else { + val res = lazyList.head + lazyList = lazyList.tail + res + } + } + + private class SlidingIterator[A](private[this] var lazyList: LazyList[A], size: Int, step: Int) + extends AbstractIterator[LazyList[A]] { + private val minLen = size - step max 0 + private var first = true + + def hasNext: Boolean = + if (first) !lazyList.isEmpty + else lazyList.lengthGt(minLen) + + def next(): LazyList[A] = { + if (!hasNext) Iterator.empty.next() + else { + first = false + val list = lazyList + lazyList = list.drop(step) + list.take(size) + } + } + } + + private final class WithFilter[A] private[LazyList](lazyList: LazyList[A], p: A => Boolean) + extends collection.WithFilter[A, LazyList] { + private[this] val filtered = lazyList.filter(p) + def map[B](f: A => B): LazyList[B] = filtered.map(f) + def flatMap[B](f: A => IterableOnce[B]): LazyList[B] = filtered.flatMap(f) + def foreach[U](f: A => U): Unit = filtered.foreach(f) + def withFilter(q: A => Boolean): collection.WithFilter[A, LazyList] = new WithFilter(filtered, q) + } + + private final class LazyBuilder[A] extends ReusableBuilder[A, LazyList[A]] { + import LazyBuilder._ + + private[this] var next: DeferredState[A] = _ + private[this] var list: LazyList[A] = _ + + clear() + + override def clear(): Unit = { + val deferred = new DeferredState[A] + list = newLL(deferred.eval()) + next = deferred + } + + override def result(): LazyList[A] = { + next init Empty + list + } + + override def addOne(elem: A): this.type = { + val deferred = new DeferredState[A] + next init eagerCons(elem, newLL(deferred.eval())) + next = deferred + this + } + + // lazy implementation which doesn't evaluate the collection being added + override def addAll(xs: IterableOnce[A]): this.type = { + if (xs.knownSize != 0) { + val deferred = new DeferredState[A] + next init eagerHeadPrependIterator(xs.iterator)(deferred.eval()) + next = deferred + } + this + } + } + + private object LazyBuilder { + final class DeferredState[A] { + private[this] var _tail: () => LazyList[A] = _ + + def eval(): LazyList[A] = { + val state = _tail + if (state == null) throw new IllegalStateException("uninitialized") + state() + } + + // racy + def init(state: => LazyList[A]): Unit = { + if (_tail != null) throw new IllegalStateException("already initialized") + _tail = () => state + } + } + } + + /** This serialization proxy is used for LazyLists which start with a sequence of evaluated cons cells. + * The forced sequence is serialized in a compact, sequential format, followed by the unevaluated tail, which uses + * standard Java serialization to store the complete structure of unevaluated thunks. This allows the serialization + * of long evaluated lazy lists without exhausting the stack through recursive serialization of cons cells. + */ + @SerialVersionUID(4L) + final class SerializationProxy[A](@transient protected var coll: LazyList[A]) extends Serializable { + + private[this] def writeObject(out: ObjectOutputStream): Unit = { + out.defaultWriteObject() + var these = coll + while (these.knownNonEmpty) { + out.writeObject(these.head) + these = these.tail + } + out.writeObject(SerializeEnd) + out.writeObject(these) + } + + private[this] def readObject(in: ObjectInputStream): Unit = { + in.defaultReadObject() + val init = new mutable.ListBuffer[A] + var initRead = false + while (!initRead) in.readObject match { + case SerializeEnd => initRead = true + case a => init += a.asInstanceOf[A] + } + val tail = in.readObject().asInstanceOf[LazyList[A]] + // scala/scala#10118: caution that no code path can evaluate `tail.evaluated` + // before the resulting LazyList is returned + val it = init.toList.iterator + coll = newLL(eagerHeadPrependIterator(it)(tail)) + } + + private[this] def readResolve(): Any = coll + } +} From 1d6da4cba41fffa6decbe50d4f808bb510f12158 Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Thu, 14 Aug 2025 18:44:57 +0200 Subject: [PATCH 080/101] Make Set/HashSet compile again --- library/src/scala/collection/immutable/HashSet.scala | 2 +- library/src/scala/collection/immutable/Set.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/src/scala/collection/immutable/HashSet.scala b/library/src/scala/collection/immutable/HashSet.scala index e5d2ee0c857e..65dca3dbba79 100644 --- a/library/src/scala/collection/immutable/HashSet.scala +++ b/library/src/scala/collection/immutable/HashSet.scala @@ -2079,7 +2079,7 @@ private[collection] final class HashSetBuilder[A] extends ReusableBuilder[A, Has this } - override def addAll(xs: IterableOnce[A]) = { + override def addAll(xs: IterableOnce[A]^) = { ensureUnaliased() xs match { case hm: HashSet[A] => diff --git a/library/src/scala/collection/immutable/Set.scala b/library/src/scala/collection/immutable/Set.scala index dc49c19f7432..c35189acbc0d 100644 --- a/library/src/scala/collection/immutable/Set.scala +++ b/library/src/scala/collection/immutable/Set.scala @@ -400,7 +400,7 @@ private final class SetBuilderImpl[A] extends ReusableBuilder[A, Set[A]] { this } - override def addAll(xs: IterableOnce[A]): this.type = + override def addAll(xs: IterableOnce[A]^): this.type = if (switchedToHashSetBuilder) { hashSetBuilder.addAll(xs) this From 92b84db6234f461f2c69846ded716ad25a9e8109 Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Thu, 14 Aug 2025 18:52:36 +0200 Subject: [PATCH 081/101] Capture check immutable.List{,Map,Set} --- TODO.md | 6 +++--- library/src/scala/collection/immutable/List.scala | 14 ++++++++------ .../src/scala/collection/immutable/ListMap.scala | 6 ++++-- .../src/scala/collection/immutable/ListSet.scala | 4 +++- 4 files changed, 18 insertions(+), 12 deletions(-) diff --git a/TODO.md b/TODO.md index 9fc64211078f..b03c9aafb880 100644 --- a/TODO.md +++ b/TODO.md @@ -262,9 +262,9 @@ - [x] library/src/scala/collection/immutable/Iterable.scala - [ ] library/src/scala/collection/immutable/LazyList.scala - [x] library/src/scala/collection/immutable/LazyListIterable.scala -- [ ] library/src/scala/collection/immutable/List.scala -- [ ] library/src/scala/collection/immutable/ListMap.scala -- [ ] library/src/scala/collection/immutable/ListSet.scala +- [x] library/src/scala/collection/immutable/List.scala +- [x] library/src/scala/collection/immutable/ListMap.scala +- [x] library/src/scala/collection/immutable/ListSet.scala - [x] library/src/scala/collection/immutable/LongMap.scala - [x] library/src/scala/collection/immutable/Map.scala - [ ] library/src/scala/collection/immutable/NumericRange.scala diff --git a/library/src/scala/collection/immutable/List.scala b/library/src/scala/collection/immutable/List.scala index f7b828bb97b5..dde3e5e8a05c 100644 --- a/library/src/scala/collection/immutable/List.scala +++ b/library/src/scala/collection/immutable/List.scala @@ -15,6 +15,8 @@ package collection package immutable import scala.language.`2.13` +import language.experimental.captureChecking + import scala.annotation.unchecked.uncheckedVariance import scala.annotation.tailrec import mutable.{Builder, ListBuffer} @@ -144,7 +146,7 @@ sealed abstract class List[+A] override def prepended[B >: A](elem: B): List[B] = elem :: this - override def prependedAll[B >: A](prefix: collection.IterableOnce[B]): List[B] = prefix match { + override def prependedAll[B >: A](prefix: collection.IterableOnce[B]^): List[B] = prefix match { case xs: List[B] => xs ::: this case _ if prefix.knownSize == 0 => this case b: ListBuffer[B] if this.isEmpty => b.toList @@ -166,7 +168,7 @@ sealed abstract class List[+A] } // When calling appendAll with another list `suffix`, avoid copying `suffix` - override def appendedAll[B >: A](suffix: collection.IterableOnce[B]): List[B] = suffix match { + override def appendedAll[B >: A](suffix: collection.IterableOnce[B]^): List[B] = suffix match { case xs: List[B] => this ::: xs case _ => super.appendedAll(suffix) } @@ -259,7 +261,7 @@ sealed abstract class List[+A] } } - final override def collect[B](pf: PartialFunction[A, B]): List[B] = { + final override def collect[B](pf: PartialFunction[A, B]^): List[B] = { if (this eq Nil) Nil else { var rest = this var h: ::[B] = null @@ -287,7 +289,7 @@ sealed abstract class List[+A] } } - final override def flatMap[B](f: A => IterableOnce[B]): List[B] = { + final override def flatMap[B](f: A => IterableOnce[B]^): List[B] = { var rest = this var h: ::[B] = null var t: ::[B] = null @@ -671,7 +673,7 @@ case object Nil extends List[Nothing] { override def init: Nothing = throw new UnsupportedOperationException("init of empty list") override def knownSize: Int = 0 override def iterator: Iterator[Nothing] = Iterator.empty - override def unzip[A1, A2](implicit asPair: Nothing => (A1, A2)): (List[A1], List[A2]) = EmptyUnzip + override def unzip[A1, A2](implicit asPair: Nothing -> (A1, A2)): (List[A1], List[A2]) = EmptyUnzip @transient private[this] val EmptyUnzip = (Nil, Nil) @@ -686,7 +688,7 @@ case object Nil extends List[Nothing] { object List extends StrictOptimizedSeqFactory[List] { private val TupleOfNil = (Nil, Nil) - def from[B](coll: collection.IterableOnce[B]): List[B] = Nil.prependedAll(coll) + def from[B](coll: collection.IterableOnce[B]^): List[B] = Nil.prependedAll(coll) def newBuilder[A]: Builder[A, List[A]] = new ListBuffer() diff --git a/library/src/scala/collection/immutable/ListMap.scala b/library/src/scala/collection/immutable/ListMap.scala index 8e638434f622..1a7a465fc3c8 100644 --- a/library/src/scala/collection/immutable/ListMap.scala +++ b/library/src/scala/collection/immutable/ListMap.scala @@ -15,6 +15,8 @@ package collection package immutable import scala.language.`2.13` +import language.experimental.captureChecking + import scala.annotation.{nowarn, tailrec} import scala.collection.mutable.ReusableBuilder import scala.collection.generic.DefaultSerializable @@ -241,7 +243,7 @@ object ListMap extends StrictMapFactory[ListMap] { private object EmptyListMap extends ListMap[Any, Nothing] - def from[K, V](it: collection.IterableOnce[(K, V)]): ListMap[K, V] = + def from[K, V](it: collection.IterableOnce[(K, V)]^): ListMap[K, V] = it match { case lm: ListMap[K, V] => lm case lhm: collection.mutable.LinkedHashMap[K, V] => @@ -324,7 +326,7 @@ private[immutable] final class ListMapBuilder[K, V] extends mutable.ReusableBuil } this } - override def addAll(xs: IterableOnce[(K, V)]): this.type = { + override def addAll(xs: IterableOnce[(K, V)]^): this.type = { if (isAliased) { super.addAll(xs) } else if (underlying.nonEmpty) { diff --git a/library/src/scala/collection/immutable/ListSet.scala b/library/src/scala/collection/immutable/ListSet.scala index 4325bd500b14..ed08c69d1b37 100644 --- a/library/src/scala/collection/immutable/ListSet.scala +++ b/library/src/scala/collection/immutable/ListSet.scala @@ -15,6 +15,8 @@ package collection package immutable import scala.language.`2.13` +import language.experimental.captureChecking + import mutable.{Builder, ImmutableBuilder} import scala.annotation.tailrec import scala.collection.generic.DefaultSerializable @@ -118,7 +120,7 @@ sealed class ListSet[A] @SerialVersionUID(3L) object ListSet extends IterableFactory[ListSet] { - def from[E](it: scala.collection.IterableOnce[E]): ListSet[E] = + def from[E](it: scala.collection.IterableOnce[E]^): ListSet[E] = it match { case ls: ListSet[E] => ls case _ if it.knownSize == 0 => empty[E] From b4db5d9827f7d567de40e43a9c8f2e38ee8aed7d Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Thu, 14 Aug 2025 18:57:52 +0200 Subject: [PATCH 082/101] Capture check Range and NumericRange --- TODO.md | 4 ++-- library/src/scala/collection/immutable/NumericRange.scala | 2 ++ library/src/scala/collection/immutable/Range.scala | 8 +++++--- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/TODO.md b/TODO.md index b03c9aafb880..40b63c251b1f 100644 --- a/TODO.md +++ b/TODO.md @@ -267,9 +267,9 @@ - [x] library/src/scala/collection/immutable/ListSet.scala - [x] library/src/scala/collection/immutable/LongMap.scala - [x] library/src/scala/collection/immutable/Map.scala -- [ ] library/src/scala/collection/immutable/NumericRange.scala +- [x] library/src/scala/collection/immutable/NumericRange.scala - [ ] library/src/scala/collection/immutable/Queue.scala -- [ ] library/src/scala/collection/immutable/Range.scala +- [x] library/src/scala/collection/immutable/Range.scala - [ ] library/src/scala/collection/immutable/RedBlackTree.scala - [x] library/src/scala/collection/immutable/Seq.scala - [ ] library/src/scala/collection/immutable/SeqMap.scala diff --git a/library/src/scala/collection/immutable/NumericRange.scala b/library/src/scala/collection/immutable/NumericRange.scala index 6a10bef7171f..c09b49810b24 100644 --- a/library/src/scala/collection/immutable/NumericRange.scala +++ b/library/src/scala/collection/immutable/NumericRange.scala @@ -13,6 +13,8 @@ package scala.collection.immutable import scala.language.`2.13` +import language.experimental.captureChecking + import scala.collection.Stepper.EfficientSplit import scala.collection.{AbstractIterator, AnyStepper, IterableFactoryDefaults, Iterator, Stepper, StepperShape} import scala.collection.generic.CommonErrors diff --git a/library/src/scala/collection/immutable/Range.scala b/library/src/scala/collection/immutable/Range.scala index 5fd0490596d7..1f361c70b7bb 100644 --- a/library/src/scala/collection/immutable/Range.scala +++ b/library/src/scala/collection/immutable/Range.scala @@ -14,6 +14,8 @@ package scala package collection.immutable import scala.language.`2.13` +import language.experimental.captureChecking + import scala.collection.Stepper.EfficientSplit import scala.collection.convert.impl.RangeStepper import scala.collection.generic.CommonErrors @@ -217,7 +219,7 @@ sealed abstract class Range( private[this] def posOf(i: Int): Int = if (contains(i)) (i - start) / step else -1 - override def sameElements[B >: Int](that: IterableOnce[B]): Boolean = that match { + override def sameElements[B >: Int](that: IterableOnce[B]^): Boolean = that match { case other: Range => (this.length : @annotation.switch) match { case 0 => other.isEmpty @@ -613,7 +615,7 @@ object Range { // As there is no appealing default step size for not-really-integral ranges, // we offer a partially constructed object. - class Partial[T, U](private val f: T => U) extends AnyVal { + class Partial[T, U](private val f: T => U) extends AnyVal { self: Partial[T, U]^ => def by(x: T): U = f(x) override def toString = "Range requires step" } @@ -641,7 +643,7 @@ private class RangeIterator( step: Int, lastElement: Int, initiallyEmpty: Boolean -) extends AbstractIterator[Int] with Serializable { +) extends AbstractIterator[Int] with Serializable { self => private[this] var _hasNext: Boolean = !initiallyEmpty private[this] var _next: Int = start override def knownSize: Int = if (_hasNext) (lastElement - _next) / step + 1 else 0 From fbf47c4e9686e85261804ed98f300114ce569ac8 Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Thu, 14 Aug 2025 19:01:48 +0200 Subject: [PATCH 083/101] Use ListBuffer.mapResult instead of declaring our own builder class --- .../immutable/LazyListIterable.scala | 21 +------------------ 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/library/src/scala/collection/immutable/LazyListIterable.scala b/library/src/scala/collection/immutable/LazyListIterable.scala index ceae6f7b3e0c..49e928ca99a4 100644 --- a/library/src/scala/collection/immutable/LazyListIterable.scala +++ b/library/src/scala/collection/immutable/LazyListIterable.scala @@ -1337,7 +1337,7 @@ object LazyListIterable extends IterableFactory[LazyListIterable] { * @tparam A the type of the ${coll}’s elements * @return A builder for $Coll objects. */ - def newBuilder[A]: Builder[A, LazyListIterable[A]] = new LazyListIterableBuilder[A] + def newBuilder[A]: Builder[A, LazyListIterable[A]] = (new collection.mutable.ListBuffer[A]).mapResult(from) private class LazyIterator[+A](private[this] var lazyList: LazyListIterable[A]^) extends AbstractIterator[A] { override def hasNext: Boolean = !lazyList.isEmpty @@ -1381,25 +1381,6 @@ object LazyListIterable extends IterableFactory[LazyListIterable] { def withFilter(q: A => Boolean): collection.WithFilter[A, LazyListIterable]^{this, q} = new WithFilter(filtered, q) } - // wraps a list buffer and use it to build the non-lazy LazyListIterable. - private final class LazyListIterableBuilder[A] extends ReusableBuilder[A, LazyListIterable[A]] { - private val buf = collection.mutable.ListBuffer[A]() - - override def clear() = buf.clear() - - override def result(): LazyListIterable[A] = LazyListIterable.fromSpecific(buf.result()) - - override def addOne(elem: A) = { - buf.addOne(elem) - this - } - - override def addAll(xs: IterableOnce[A]^) = { - buf.addAll(xs) - this - } - } - // CC Note: Lazy Builder is not unsafe, but requires an explicit capture set. // private final class LazyBuilder[A, Cap^] extends ReusableBuilder[A, LazyListIterable[A]] { From 618fa314675b1a260eb12994c1d34a4b9b8e7364 Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Thu, 14 Aug 2025 19:03:21 +0200 Subject: [PATCH 084/101] Capture-check immutable.Queue and WrappedString --- TODO.md | 4 ++-- library/src/scala/collection/immutable/Queue.scala | 10 ++++++---- .../scala/collection/immutable/WrappedString.scala | 14 ++++++++------ 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/TODO.md b/TODO.md index 40b63c251b1f..f3d1e1c68267 100644 --- a/TODO.md +++ b/TODO.md @@ -268,7 +268,7 @@ - [x] library/src/scala/collection/immutable/LongMap.scala - [x] library/src/scala/collection/immutable/Map.scala - [x] library/src/scala/collection/immutable/NumericRange.scala -- [ ] library/src/scala/collection/immutable/Queue.scala +- [x] library/src/scala/collection/immutable/Queue.scala - [x] library/src/scala/collection/immutable/Range.scala - [ ] library/src/scala/collection/immutable/RedBlackTree.scala - [x] library/src/scala/collection/immutable/Seq.scala @@ -283,7 +283,7 @@ - [ ] library/src/scala/collection/immutable/TreeSet.scala - [ ] library/src/scala/collection/immutable/Vector.scala - [ ] library/src/scala/collection/immutable/VectorMap.scala -- [ ] library/src/scala/collection/immutable/WrappedString.scala +- [x] library/src/scala/collection/immutable/WrappedString.scala - [x] library/src/scala/collection/immutable/package.scala - [x] library/src/scala/collection/mutable/AnyRefMap.scala - [x] library/src/scala/collection/mutable/ArrayBuffer.scala diff --git a/library/src/scala/collection/immutable/Queue.scala b/library/src/scala/collection/immutable/Queue.scala index 6deaabc5c7f7..326d3c7c56a6 100644 --- a/library/src/scala/collection/immutable/Queue.scala +++ b/library/src/scala/collection/immutable/Queue.scala @@ -14,6 +14,8 @@ package scala.collection package immutable import scala.language.`2.13` +import language.experimental.captureChecking + import scala.collection.generic.DefaultSerializable import scala.collection.mutable.{Builder, ListBuffer} @@ -120,7 +122,7 @@ sealed class Queue[+A] protected(protected val in: List[A], protected val out: L override def appended[B >: A](elem: B): Queue[B] = enqueue(elem) - override def appendedAll[B >: A](that: scala.collection.IterableOnce[B]): Queue[B] = { + override def appendedAll[B >: A](that: scala.collection.IterableOnce[B]^): Queue[B] = { val newIn = that match { case that: Queue[B] => that.in ++ (that.out reverse_::: this.in) case that: List[B] => that reverse_::: this.in @@ -151,7 +153,7 @@ sealed class Queue[+A] protected(protected val in: List[A], protected val out: L * @param iter an iterable object */ @deprecated("Use `enqueueAll` instead of `enqueue` to enqueue a collection of elements", "2.13.0") - @`inline` final def enqueue[B >: A](iter: scala.collection.Iterable[B]) = enqueueAll(iter) + @`inline` final def enqueue[B >: A](iter: scala.collection.Iterable[B]^) = enqueueAll(iter) /** Creates a new queue with all elements provided by an `Iterable` object * added at the end of the old queue. @@ -161,7 +163,7 @@ sealed class Queue[+A] protected(protected val in: List[A], protected val out: L * * @param iter an iterable object */ - def enqueueAll[B >: A](iter: scala.collection.Iterable[B]): Queue[B] = appendedAll(iter) + def enqueueAll[B >: A](iter: scala.collection.Iterable[B]^): Queue[B] = appendedAll(iter) /** Returns a tuple with the first element in the queue, * and a new queue with this element removed. @@ -203,7 +205,7 @@ sealed class Queue[+A] protected(protected val in: List[A], protected val out: L object Queue extends StrictOptimizedSeqFactory[Queue] { def newBuilder[A]: Builder[A, Queue[A]] = new ListBuffer[A] mapResult (x => new Queue[A](Nil, x)) - def from[A](source: IterableOnce[A]): Queue[A] = source match { + def from[A](source: IterableOnce[A]^): Queue[A] = source match { case q: Queue[A] => q case _ => val list = List.from(source) diff --git a/library/src/scala/collection/immutable/WrappedString.scala b/library/src/scala/collection/immutable/WrappedString.scala index 8e40b38be2cb..9c88584f8feb 100644 --- a/library/src/scala/collection/immutable/WrappedString.scala +++ b/library/src/scala/collection/immutable/WrappedString.scala @@ -14,6 +14,8 @@ package scala.collection package immutable import scala.language.`2.13` +import language.experimental.captureChecking + import scala.Predef.{wrapString => _, assert} import scala.collection.Stepper.EfficientSplit import scala.collection.convert.impl.CharStringStepper @@ -39,7 +41,7 @@ final class WrappedString(private val self: String) extends AbstractSeq[Char] wi def apply(i: Int): Char = self.charAt(i) - override protected def fromSpecific(coll: scala.collection.IterableOnce[Char]): WrappedString = WrappedString.fromSpecific(coll) + override protected def fromSpecific(coll: scala.collection.IterableOnce[Char]^): WrappedString = WrappedString.fromSpecific(coll) override protected def newSpecificBuilder: Builder[Char, WrappedString] = WrappedString.newBuilder override def empty: WrappedString = WrappedString.empty @@ -66,13 +68,13 @@ final class WrappedString(private val self: String) extends AbstractSeq[Char] wi r.asInstanceOf[S with EfficientSplit] } - override def startsWith[B >: Char](that: IterableOnce[B], offset: Int = 0): Boolean = + override def startsWith[B >: Char](that: IterableOnce[B]^, offset: Int = 0): Boolean = that match { case s: WrappedString => self.startsWith(s.self, offset) case _ => super.startsWith(that, offset) } - override def endsWith[B >: Char](that: collection.Iterable[B]): Boolean = + override def endsWith[B >: Char](that: collection.Iterable[B]^): Boolean = that match { case s: WrappedString => self.endsWith(s.self) case _ => super.endsWith(that) @@ -98,13 +100,13 @@ final class WrappedString(private val self: String) extends AbstractSeq[Char] wi case _ => super.copyToArray(xs, start, len) } - override def appendedAll[B >: Char](suffix: IterableOnce[B]): IndexedSeq[B] = + override def appendedAll[B >: Char](suffix: IterableOnce[B]^): IndexedSeq[B] = suffix match { case s: WrappedString => new WrappedString(self concat s.self) case _ => Predef.??? // TODO super.appendedAll(suffix) } - override def sameElements[B >: Char](o: IterableOnce[B]) = o match { + override def sameElements[B >: Char](o: IterableOnce[B]^) = o match { case s: WrappedString => self == s.self case _ => super.sameElements(o) } @@ -124,7 +126,7 @@ final class WrappedString(private val self: String) extends AbstractSeq[Char] wi */ @SerialVersionUID(3L) object WrappedString extends SpecificIterableFactory[Char, WrappedString] { - def fromSpecific(it: IterableOnce[Char]): WrappedString = { + def fromSpecific(it: IterableOnce[Char]^): WrappedString = { val b = newBuilder b.sizeHint(it) b ++= it From fc0b73369568213c7be12d34d7d6ece7ab8835a5 Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Thu, 14 Aug 2025 19:09:57 +0200 Subject: [PATCH 085/101] Capture-check immutable.Tree and Sorted --- TODO.md | 14 +++++++------- .../scala/collection/immutable/RedBlackTree.scala | 2 ++ .../src/scala/collection/immutable/SeqMap.scala | 6 ++++-- .../src/scala/collection/immutable/SortedMap.scala | 14 ++++++++------ .../src/scala/collection/immutable/SortedSet.scala | 3 ++- .../src/scala/collection/immutable/Stream.scala | 2 +- .../src/scala/collection/immutable/TreeMap.scala | 10 ++++++---- .../scala/collection/immutable/TreeSeqMap.scala | 10 ++++++---- .../src/scala/collection/immutable/TreeSet.scala | 10 ++++++---- 9 files changed, 42 insertions(+), 29 deletions(-) diff --git a/TODO.md b/TODO.md index f3d1e1c68267..71e6ef8edef1 100644 --- a/TODO.md +++ b/TODO.md @@ -270,17 +270,17 @@ - [x] library/src/scala/collection/immutable/NumericRange.scala - [x] library/src/scala/collection/immutable/Queue.scala - [x] library/src/scala/collection/immutable/Range.scala -- [ ] library/src/scala/collection/immutable/RedBlackTree.scala +- [x] library/src/scala/collection/immutable/RedBlackTree.scala - [x] library/src/scala/collection/immutable/Seq.scala -- [ ] library/src/scala/collection/immutable/SeqMap.scala +- [x] library/src/scala/collection/immutable/SeqMap.scala - [x] library/src/scala/collection/immutable/Set.scala -- [ ] library/src/scala/collection/immutable/SortedMap.scala -- [ ] library/src/scala/collection/immutable/SortedSet.scala +- [x] library/src/scala/collection/immutable/SortedMap.scala +- [x] library/src/scala/collection/immutable/SortedSet.scala - [ ] library/src/scala/collection/immutable/Stream.scala - [x] library/src/scala/collection/immutable/StrictOptimizedSeqOps.scala -- [ ] library/src/scala/collection/immutable/TreeMap.scala -- [ ] library/src/scala/collection/immutable/TreeSeqMap.scala -- [ ] library/src/scala/collection/immutable/TreeSet.scala +- [x] library/src/scala/collection/immutable/TreeMap.scala +- [x] library/src/scala/collection/immutable/TreeSeqMap.scala +- [x] library/src/scala/collection/immutable/TreeSet.scala - [ ] library/src/scala/collection/immutable/Vector.scala - [ ] library/src/scala/collection/immutable/VectorMap.scala - [x] library/src/scala/collection/immutable/WrappedString.scala diff --git a/library/src/scala/collection/immutable/RedBlackTree.scala b/library/src/scala/collection/immutable/RedBlackTree.scala index a57785bbf741..94d6ed434d75 100644 --- a/library/src/scala/collection/immutable/RedBlackTree.scala +++ b/library/src/scala/collection/immutable/RedBlackTree.scala @@ -15,6 +15,8 @@ package collection package immutable import scala.language.`2.13` +import language.experimental.captureChecking + import scala.annotation.meta.{getter, setter} import scala.annotation.tailrec import scala.runtime.Statics.releaseFence diff --git a/library/src/scala/collection/immutable/SeqMap.scala b/library/src/scala/collection/immutable/SeqMap.scala index 13ce6bc1c858..bba801ee04a1 100644 --- a/library/src/scala/collection/immutable/SeqMap.scala +++ b/library/src/scala/collection/immutable/SeqMap.scala @@ -15,6 +15,8 @@ package collection package immutable import scala.language.`2.13` +import language.experimental.captureChecking + import scala.collection.mutable.{Builder, ReusableBuilder} /** A base trait for ordered, immutable maps. @@ -45,7 +47,7 @@ trait SeqMap[K, +V] object SeqMap extends StrictMapFactory[SeqMap] { def empty[K, V]: SeqMap[K, V] = EmptySeqMap.asInstanceOf[SeqMap[K, V]] - def from[K, V](it: collection.IterableOnce[(K, V)]): SeqMap[K, V] = + def from[K, V](it: collection.IterableOnce[(K, V)]^): SeqMap[K, V] = it match { //case sm: SeqMap[K, V] => sm case m: ListMap[K, V] => m @@ -274,7 +276,7 @@ object SeqMap extends StrictMapFactory[SeqMap] { this } - override def addAll(xs: IterableOnce[(K, V)]): this.type = + override def addAll(xs: IterableOnce[(K, V)]^): this.type = if (switchedToVectorMapBuilder) { vectorMapBuilder.addAll(xs) this diff --git a/library/src/scala/collection/immutable/SortedMap.scala b/library/src/scala/collection/immutable/SortedMap.scala index a25321235f89..b045c498d3c9 100644 --- a/library/src/scala/collection/immutable/SortedMap.scala +++ b/library/src/scala/collection/immutable/SortedMap.scala @@ -15,6 +15,8 @@ package collection package immutable import scala.language.`2.13` +import language.experimental.captureChecking + import scala.annotation.unchecked.uncheckedVariance import scala.collection.mutable.Builder @@ -70,7 +72,7 @@ trait SortedMap[K, +V] * @param d the function mapping keys to values, used for non-present keys * @return a wrapper of the map with a default value */ - override def withDefault[V1 >: V](d: K => V1): SortedMap[K, V1] = new SortedMap.WithDefault[K, V1](this, d) + override def withDefault[V1 >: V](d: K -> V1): SortedMap[K, V1] = new SortedMap.WithDefault[K, V1](this, d) /** The same map with a given default value. * Note: The default is only used for `apply`. Other methods like `get`, `contains`, `iterator`, `keys`, etc. @@ -124,7 +126,7 @@ transparent trait StrictOptimizedSortedMapOps[K, +V, +CC[X, +Y] <: Map[X, Y] wit with collection.StrictOptimizedSortedMapOps[K, V, CC, C] with StrictOptimizedMapOps[K, V, Map, C] { - override def concat[V2 >: V](xs: collection.IterableOnce[(K, V2)]): CC[K, V2] = { + override def concat[V2 >: V](xs: collection.IterableOnce[(K, V2)]^): CC[K, V2] = { var result: CC[K, V2] = coll val it = xs.iterator while (it.hasNext) result = result + it.next() @@ -135,12 +137,12 @@ transparent trait StrictOptimizedSortedMapOps[K, +V, +CC[X, +Y] <: Map[X, Y] wit @SerialVersionUID(3L) object SortedMap extends SortedMapFactory.Delegate[SortedMap](TreeMap) { - override def from[K: Ordering, V](it: IterableOnce[(K, V)]): SortedMap[K, V] = it match { + override def from[K: Ordering, V](it: IterableOnce[(K, V)]^): SortedMap[K, V] = it match { case sm: SortedMap[K, V] if Ordering[K] == sm.ordering => sm case _ => super.from(it) } - final class WithDefault[K, +V](underlying: SortedMap[K, V], defaultValue: K => V) + final class WithDefault[K, +V](underlying: SortedMap[K, V], defaultValue: K -> V) extends Map.WithDefault[K, V](underlying, defaultValue) with SortedMap[K, V] with SortedMapOps[K, V, SortedMap, WithDefault[K, V]] with Serializable { @@ -162,14 +164,14 @@ object SortedMap extends SortedMapFactory.Delegate[SortedMap](TreeMap) { override def updated[V1 >: V](key: K, value: V1): WithDefault[K, V1] = new WithDefault[K, V1](underlying.updated(key, value), defaultValue) - override def concat [V2 >: V](xs: collection.IterableOnce[(K, V2)]): WithDefault[K, V2] = + override def concat [V2 >: V](xs: collection.IterableOnce[(K, V2)]^): WithDefault[K, V2] = new WithDefault( underlying.concat(xs) , defaultValue) override def removed(key: K): WithDefault[K, V] = new WithDefault[K, V](underlying.removed(key), defaultValue) override def empty: WithDefault[K, V] = new WithDefault[K, V](underlying.empty, defaultValue) - override protected def fromSpecific(coll: scala.collection.IterableOnce[(K, V)] @uncheckedVariance): WithDefault[K, V] = + override protected def fromSpecific(coll: (scala.collection.IterableOnce[(K, V)]^) @uncheckedVariance): WithDefault[K, V] = new WithDefault[K, V](sortedMapFactory.from(coll), defaultValue) override protected def newSpecificBuilder: Builder[(K, V), WithDefault[K, V]] @uncheckedVariance = diff --git a/library/src/scala/collection/immutable/SortedSet.scala b/library/src/scala/collection/immutable/SortedSet.scala index e8bbf1810d48..ceeafeb6296b 100644 --- a/library/src/scala/collection/immutable/SortedSet.scala +++ b/library/src/scala/collection/immutable/SortedSet.scala @@ -15,6 +15,7 @@ package collection package immutable import scala.language.`2.13` +import language.experimental.captureChecking /** Base trait for sorted sets */ trait SortedSet[A] @@ -52,7 +53,7 @@ transparent trait StrictOptimizedSortedSetOps[A, +CC[X] <: SortedSet[X], +C <: S */ @SerialVersionUID(3L) object SortedSet extends SortedIterableFactory.Delegate[SortedSet](TreeSet) { - override def from[E: Ordering](it: IterableOnce[E]): SortedSet[E] = it match { + override def from[E: Ordering](it: IterableOnce[E]^): SortedSet[E] = it match { case ss: SortedSet[E] if Ordering[E] == ss.ordering => ss case _ => super.from(it) } diff --git a/library/src/scala/collection/immutable/Stream.scala b/library/src/scala/collection/immutable/Stream.scala index a7017a23a16d..b31c5c8018c6 100644 --- a/library/src/scala/collection/immutable/Stream.scala +++ b/library/src/scala/collection/immutable/Stream.scala @@ -25,7 +25,7 @@ import scala.collection.mutable.{ArrayBuffer, StringBuilder} import scala.language.implicitConversions import Stream.cons -@deprecated("Use LazyList (which is fully lazy) instead of Stream (which has a lazy tail only)", "2.13.0") +@deprecated("Use LazyListIterable (which is fully lazy) instead of Stream (which has a lazy tail only)", "2.13.0") @SerialVersionUID(3L) sealed abstract class Stream[+A] extends AbstractSeq[A] with LinearSeq[A] diff --git a/library/src/scala/collection/immutable/TreeMap.scala b/library/src/scala/collection/immutable/TreeMap.scala index b4a7e4b6605d..84ca78c48b75 100644 --- a/library/src/scala/collection/immutable/TreeMap.scala +++ b/library/src/scala/collection/immutable/TreeMap.scala @@ -15,6 +15,8 @@ package collection package immutable import scala.language.`2.13` +import language.experimental.captureChecking + import scala.annotation.tailrec import scala.collection.Stepper.EfficientSplit import scala.collection.generic.DefaultSerializable @@ -149,7 +151,7 @@ final class TreeMap[K, +V] private (private val tree: RB.Tree[K, V])(implicit va def updated[V1 >: V](key: K, value: V1): TreeMap[K, V1] = newMapOrSelf(RB.update(tree, key, value, overwrite = true)) - override def concat[V1 >: V](that: collection.IterableOnce[(K, V1)]): TreeMap[K, V1] = + override def concat[V1 >: V](that: collection.IterableOnce[(K, V1)]^): TreeMap[K, V1] = newMapOrSelf(that match { case tm: TreeMap[K, V] @unchecked if ordering == tm.ordering => RB.union(tree, tm.tree) @@ -169,7 +171,7 @@ final class TreeMap[K, +V] private (private val tree: RB.Tree[K, V])(implicit va adder.finalTree }) - override def removedAll(keys: IterableOnce[K]): TreeMap[K, V] = keys match { + override def removedAll(keys: IterableOnce[K]^): TreeMap[K, V] = keys match { case ts: TreeSet[K] if ordering == ts.ordering => newMapOrSelf(RB.difference(tree, ts.tree)) case _ => super.removedAll(keys) @@ -310,7 +312,7 @@ object TreeMap extends SortedMapFactory[TreeMap] { def empty[K : Ordering, V]: TreeMap[K, V] = new TreeMap() - def from[K, V](it: IterableOnce[(K, V)])(implicit ordering: Ordering[K]): TreeMap[K, V] = + def from[K, V](it: IterableOnce[(K, V)]^)(implicit ordering: Ordering[K]): TreeMap[K, V] = it match { case tm: TreeMap[K, V] if ordering == tm.ordering => tm case sm: scala.collection.SortedMap[K, V] if ordering == sm.ordering => @@ -354,7 +356,7 @@ object TreeMap extends SortedMapFactory[TreeMap] { } } - override def addAll(xs: IterableOnce[(K, V)]): this.type = { + override def addAll(xs: IterableOnce[(K, V)]^): this.type = { xs match { // TODO consider writing a mutable-safe union for TreeSet/TreeMap builder ++= // for the moment we have to force immutability before the union diff --git a/library/src/scala/collection/immutable/TreeSeqMap.scala b/library/src/scala/collection/immutable/TreeSeqMap.scala index 4e57aab10e2f..e568841188dd 100644 --- a/library/src/scala/collection/immutable/TreeSeqMap.scala +++ b/library/src/scala/collection/immutable/TreeSeqMap.scala @@ -15,6 +15,8 @@ package collection package immutable import scala.language.`2.13` +import language.experimental.captureChecking + import scala.annotation.tailrec /** This class implements an immutable map that preserves order using @@ -234,7 +236,7 @@ final class TreeSeqMap[K, +V] private ( bdr.result() } - override def flatMap[K2, V2](f: ((K, V)) => IterableOnce[(K2, V2)]): TreeSeqMap[K2, V2] = { + override def flatMap[K2, V2](f: ((K, V)) => IterableOnce[(K2, V2)]^): TreeSeqMap[K2, V2] = { val bdr = newBuilder[K2, V2](orderedBy) val iter = ordering.iterator while (iter.hasNext) { @@ -249,7 +251,7 @@ final class TreeSeqMap[K, +V] private ( bdr.result() } - override def collect[K2, V2](pf: PartialFunction[(K, V), (K2, V2)]): TreeSeqMap[K2, V2] = { + override def collect[K2, V2](pf: PartialFunction[(K, V), (K2, V2)]^): TreeSeqMap[K2, V2] = { val bdr = newBuilder[K2, V2](orderedBy) val iter = ordering.iterator while (iter.hasNext) { @@ -260,7 +262,7 @@ final class TreeSeqMap[K, +V] private ( bdr.result() } - override def concat[V2 >: V](suffix: IterableOnce[(K, V2)]): TreeSeqMap[K, V2] = { + override def concat[V2 >: V](suffix: IterableOnce[(K, V2)]^): TreeSeqMap[K, V2] = { var ong: Ordering[K] = ordering var mng: Mapping[K, V2] = mapping var ord = increment(ordinal) @@ -303,7 +305,7 @@ object TreeSeqMap extends StrictMapFactory[TreeSeqMap] { else EmptyByInsertion }.asInstanceOf[TreeSeqMap[K, V]] - def from[K, V](it: collection.IterableOnce[(K, V)]): TreeSeqMap[K, V] = + def from[K, V](it: collection.IterableOnce[(K, V)]^): TreeSeqMap[K, V] = it match { case om: TreeSeqMap[K, V] => om case _ => (newBuilder[K, V] ++= it).result() diff --git a/library/src/scala/collection/immutable/TreeSet.scala b/library/src/scala/collection/immutable/TreeSet.scala index a9bf1979700a..d30380b88101 100644 --- a/library/src/scala/collection/immutable/TreeSet.scala +++ b/library/src/scala/collection/immutable/TreeSet.scala @@ -15,6 +15,8 @@ package collection package immutable import scala.language.`2.13` +import language.experimental.captureChecking + import scala.collection.Stepper.EfficientSplit import scala.collection.generic.DefaultSerializable import scala.collection.mutable.ReusableBuilder @@ -171,7 +173,7 @@ final class TreeSet[A] private[immutable] (private[immutable] val tree: RB.Tree[ def excl(elem: A): TreeSet[A] = newSetOrSelf(RB.delete(tree, elem)) - override def concat(that: collection.IterableOnce[A]): TreeSet[A] = { + override def concat(that: collection.IterableOnce[A]^): TreeSet[A] = { val t = that match { case ts: TreeSet[A] if ordering == ts.ordering => RB.union(tree, ts.tree) @@ -184,7 +186,7 @@ final class TreeSet[A] private[immutable] (private[immutable] val tree: RB.Tree[ newSetOrSelf(t) } - override def removedAll(that: IterableOnce[A]): TreeSet[A] = that match { + override def removedAll(that: IterableOnce[A]^): TreeSet[A] = that match { case ts: TreeSet[A] if ordering == ts.ordering => newSetOrSelf(RB.difference(tree, ts.tree)) case _ => @@ -240,7 +242,7 @@ object TreeSet extends SortedIterableFactory[TreeSet] { def empty[A: Ordering]: TreeSet[A] = new TreeSet[A] - def from[E](it: scala.collection.IterableOnce[E])(implicit ordering: Ordering[E]): TreeSet[E] = + def from[E](it: scala.collection.IterableOnce[E]^)(implicit ordering: Ordering[E]): TreeSet[E] = it match { case ts: TreeSet[E] if ordering == ts.ordering => ts case ss: scala.collection.SortedSet[E] if ordering == ss.ordering => @@ -270,7 +272,7 @@ object TreeSet extends SortedIterableFactory[TreeSet] { this } - override def addAll(xs: IterableOnce[A]): this.type = { + override def addAll(xs: IterableOnce[A]^): this.type = { xs match { // TODO consider writing a mutable-safe union for TreeSet/TreeMap builder ++= // for the moment we have to force immutability before the union From dbac08e1939dd85fb048bc58d92adff73b35720b Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Thu, 14 Aug 2025 19:18:26 +0200 Subject: [PATCH 086/101] Capture-check immutable.Vector and VectorMap --- TODO.md | 4 +- .../scala/collection/immutable/Vector.scala | 46 ++++++++++--------- .../collection/immutable/VectorMap.scala | 6 ++- 3 files changed, 30 insertions(+), 26 deletions(-) diff --git a/TODO.md b/TODO.md index 71e6ef8edef1..2f41ef482093 100644 --- a/TODO.md +++ b/TODO.md @@ -281,8 +281,8 @@ - [x] library/src/scala/collection/immutable/TreeMap.scala - [x] library/src/scala/collection/immutable/TreeSeqMap.scala - [x] library/src/scala/collection/immutable/TreeSet.scala -- [ ] library/src/scala/collection/immutable/Vector.scala -- [ ] library/src/scala/collection/immutable/VectorMap.scala +- [x] library/src/scala/collection/immutable/Vector.scala +- [x] library/src/scala/collection/immutable/VectorMap.scala - [x] library/src/scala/collection/immutable/WrappedString.scala - [x] library/src/scala/collection/immutable/package.scala - [x] library/src/scala/collection/mutable/AnyRefMap.scala diff --git a/library/src/scala/collection/immutable/Vector.scala b/library/src/scala/collection/immutable/Vector.scala index 4ea962fc8fa9..78768577b99b 100644 --- a/library/src/scala/collection/immutable/Vector.scala +++ b/library/src/scala/collection/immutable/Vector.scala @@ -14,6 +14,8 @@ package scala.collection package immutable import scala.language.`2.13` +import language.experimental.captureChecking + import java.lang.Math.{abs, max => mmax, min => mmin} import java.util.Arrays.{copyOf, copyOfRange} import java.util.{Arrays, Spliterator} @@ -36,7 +38,7 @@ object Vector extends StrictOptimizedSeqFactory[Vector] { def empty[A]: Vector[A] = Vector0 - def from[E](it: collection.IterableOnce[E]): Vector[E] = + def from[E](it: collection.IterableOnce[E]^): Vector[E] = it match { case v: Vector[E] => v case _ => @@ -195,21 +197,21 @@ sealed abstract class Vector[+A] private[immutable] (private[immutable] final va override def updated[B >: A](index: Int, elem: B): Vector[B] = super.updated(index, elem) override def appended[B >: A](elem: B): Vector[B] = super.appended(elem) override def prepended[B >: A](elem: B): Vector[B] = super.prepended(elem) - override def prependedAll[B >: A](prefix: collection.IterableOnce[B]): Vector[B] = { + override def prependedAll[B >: A](prefix: collection.IterableOnce[B]^): Vector[B] = { val k = prefix.knownSize if (k == 0) this else if (k < 0) super.prependedAll(prefix) else prependedAll0(prefix, k) } - override final def appendedAll[B >: A](suffix: collection.IterableOnce[B]): Vector[B] = { + override final def appendedAll[B >: A](suffix: collection.IterableOnce[B]^): Vector[B] = { val k = suffix.knownSize if (k == 0) this else if (k < 0) super.appendedAll(suffix) else appendedAll0(suffix, k) } - protected[this] def prependedAll0[B >: A](prefix: collection.IterableOnce[B], k: Int): Vector[B] = { + protected[this] def prependedAll0[B >: A](prefix: collection.IterableOnce[B]^, k: Int): Vector[B] = { // k >= 0, k = prefix.knownSize val tinyAppendLimit = 4 + vectorSliceCount if (k < tinyAppendLimit /*|| k < (this.size >>> Log2ConcatFaster)*/) { @@ -227,7 +229,7 @@ sealed abstract class Vector[+A] private[immutable] (private[immutable] final va } else super.prependedAll(prefix) } - protected[this] def appendedAll0[B >: A](suffix: collection.IterableOnce[B], k: Int): Vector[B] = { + protected[this] def appendedAll0[B >: A](suffix: collection.IterableOnce[B]^, k: Int): Vector[B] = { // k >= 0, k = suffix.knownSize val tinyAppendLimit = 4 + vectorSliceCount if (k < tinyAppendLimit) { @@ -373,10 +375,10 @@ private object Vector0 extends BigVector[Nothing](empty1, empty1, 0) { } } - override protected[this]def prependedAll0[B >: Nothing](prefix: collection.IterableOnce[B], k: Int): Vector[B] = + override protected[this]def prependedAll0[B >: Nothing](prefix: collection.IterableOnce[B]^, k: Int): Vector[B] = Vector.from(prefix) - override protected[this]def appendedAll0[B >: Nothing](suffix: collection.IterableOnce[B], k: Int): Vector[B] = + override protected[this]def appendedAll0[B >: Nothing](suffix: collection.IterableOnce[B]^, k: Int): Vector[B] = Vector.from(suffix) override protected[this] def ioob(index: Int): IndexOutOfBoundsException = @@ -427,13 +429,13 @@ private final class Vector1[+A](_data1: Arr1) extends VectorImpl[A](_data1) { protected[immutable] def vectorSlice(idx: Int): Array[_ <: AnyRef] = prefix1 protected[immutable] def vectorSlicePrefixLength(idx: Int): Int = prefix1.length - override protected[this] def prependedAll0[B >: A](prefix: collection.IterableOnce[B], k: Int): Vector[B] = + override protected[this] def prependedAll0[B >: A](prefix: collection.IterableOnce[B]^, k: Int): Vector[B] = prepend1IfSpace(prefix1, prefix) match { case null => super.prependedAll0(prefix, k) case data1b => new Vector1(data1b) } - override protected[this] def appendedAll0[B >: A](suffix: collection.IterableOnce[B], k: Int): Vector[B] = { + override protected[this] def appendedAll0[B >: A](suffix: collection.IterableOnce[B]^, k: Int): Vector[B] = { val data1b = append1IfSpace(prefix1, suffix) if(data1b ne null) new Vector1(data1b) else super.appendedAll0(suffix, k) @@ -522,7 +524,7 @@ private final class Vector2[+A](_prefix1: Arr1, private[immutable] val len1: Int case 2 => length0 } - override protected[this] def prependedAll0[B >: A](prefix: collection.IterableOnce[B], k: Int): Vector[B] = + override protected[this] def prependedAll0[B >: A](prefix: collection.IterableOnce[B]^, k: Int): Vector[B] = prepend1IfSpace(prefix1, prefix) match { case null => super.prependedAll0(prefix, k) case prefix1b => @@ -533,7 +535,7 @@ private final class Vector2[+A](_prefix1: Arr1, private[immutable] val len1: Int ) } - override protected[this] def appendedAll0[B >: A](suffix: collection.IterableOnce[B], k: Int): Vector[B] = { + override protected[this] def appendedAll0[B >: A](suffix: collection.IterableOnce[B]^, k: Int): Vector[B] = { val suffix1b = append1IfSpace(suffix1, suffix) if(suffix1b ne null) copy(suffix1 = suffix1b, length0 = length0-suffix1.length+suffix1b.length) else super.appendedAll0(suffix, k) @@ -644,7 +646,7 @@ private final class Vector3[+A](_prefix1: Arr1, private[immutable] val len1: Int case 4 => length0 } - override protected[this] def prependedAll0[B >: A](prefix: collection.IterableOnce[B], k: Int): Vector[B] = + override protected[this] def prependedAll0[B >: A](prefix: collection.IterableOnce[B]^, k: Int): Vector[B] = prepend1IfSpace(prefix1, prefix) match { case null => super.prependedAll0(prefix, k) case prefix1b => @@ -656,7 +658,7 @@ private final class Vector3[+A](_prefix1: Arr1, private[immutable] val len1: Int ) } - override protected[this] def appendedAll0[B >: A](suffix: collection.IterableOnce[B], k: Int): Vector[B] = { + override protected[this] def appendedAll0[B >: A](suffix: collection.IterableOnce[B]^, k: Int): Vector[B] = { val suffix1b = append1IfSpace(suffix1, suffix) if(suffix1b ne null) copy(suffix1 = suffix1b, length0 = length0-suffix1.length+suffix1b.length) else super.appendedAll0(suffix, k) @@ -787,7 +789,7 @@ private final class Vector4[+A](_prefix1: Arr1, private[immutable] val len1: Int case 6 => length0 } - override protected[this] def prependedAll0[B >: A](prefix: collection.IterableOnce[B], k: Int): Vector[B] = + override protected[this] def prependedAll0[B >: A](prefix: collection.IterableOnce[B]^, k: Int): Vector[B] = prepend1IfSpace(prefix1, prefix) match { case null => super.prependedAll0(prefix, k) case prefix1b => @@ -800,7 +802,7 @@ private final class Vector4[+A](_prefix1: Arr1, private[immutable] val len1: Int ) } - override protected[this] def appendedAll0[B >: A](suffix: collection.IterableOnce[B], k: Int): Vector[B] = { + override protected[this] def appendedAll0[B >: A](suffix: collection.IterableOnce[B]^, k: Int): Vector[B] = { val suffix1b = append1IfSpace(suffix1, suffix) if(suffix1b ne null) copy(suffix1 = suffix1b, length0 = length0-suffix1.length+suffix1b.length) else super.appendedAll0(suffix, k) @@ -951,7 +953,7 @@ private final class Vector5[+A](_prefix1: Arr1, private[immutable] val len1: Int case 8 => length0 } - override protected[this] def prependedAll0[B >: A](prefix: collection.IterableOnce[B], k: Int): Vector[B] = + override protected[this] def prependedAll0[B >: A](prefix: collection.IterableOnce[B]^, k: Int): Vector[B] = prepend1IfSpace(prefix1, prefix) match { case null => super.prependedAll0(prefix, k) case prefix1b => @@ -965,7 +967,7 @@ private final class Vector5[+A](_prefix1: Arr1, private[immutable] val len1: Int ) } - override protected[this] def appendedAll0[B >: A](suffix: collection.IterableOnce[B], k: Int): Vector[B] = { + override protected[this] def appendedAll0[B >: A](suffix: collection.IterableOnce[B]^, k: Int): Vector[B] = { val suffix1b = append1IfSpace(suffix1, suffix) if(suffix1b ne null) copy(suffix1 = suffix1b, length0 = length0-suffix1.length+suffix1b.length) else super.appendedAll0(suffix, k) @@ -1136,7 +1138,7 @@ private final class Vector6[+A](_prefix1: Arr1, private[immutable] val len1: Int case 10 => length0 } - override protected[this] def prependedAll0[B >: A](prefix: collection.IterableOnce[B], k: Int): Vector[B] = + override protected[this] def prependedAll0[B >: A](prefix: collection.IterableOnce[B]^, k: Int): Vector[B] = prepend1IfSpace(prefix1, prefix) match { case null => super.prependedAll0(prefix, k) case prefix1b => @@ -1151,7 +1153,7 @@ private final class Vector6[+A](_prefix1: Arr1, private[immutable] val len1: Int ) } - override protected[this] def appendedAll0[B >: A](suffix: collection.IterableOnce[B], k: Int): Vector[B] = { + override protected[this] def appendedAll0[B >: A](suffix: collection.IterableOnce[B]^, k: Int): Vector[B] = { val suffix1b = append1IfSpace(suffix1, suffix) if(suffix1b ne null) copy(suffix1 = suffix1b, length0 = length0-suffix1.length+suffix1b.length) else super.appendedAll0(suffix, k) @@ -1818,7 +1820,7 @@ final class VectorBuilder[A] extends ReusableBuilder[A, Vector[A]] { this } - override def addAll(xs: IterableOnce[A]): this.type = xs match { + override def addAll(xs: IterableOnce[A]^): this.type = xs match { case v: Vector[_] => if(len1 == 0 && lenRest == 0 && !prefixIsRightAligned) initFrom(v) else addVector(v.asInstanceOf[Vector[A]]) @@ -2187,7 +2189,7 @@ private object VectorStatics { ac.asInstanceOf[Array[T]] } - final def prepend1IfSpace(prefix1: Arr1, xs: IterableOnce[_]): Arr1 = xs match { + final def prepend1IfSpace(prefix1: Arr1, xs: IterableOnce[_]^): Arr1 = xs match { case it: Iterable[_] => if(it.sizeCompare(WIDTH-prefix1.length) <= 0) { it.size match { @@ -2212,7 +2214,7 @@ private object VectorStatics { } else null } - final def append1IfSpace(suffix1: Arr1, xs: IterableOnce[_]): Arr1 = xs match { + final def append1IfSpace(suffix1: Arr1, xs: IterableOnce[_]^): Arr1 = xs match { case it: Iterable[_] => if(it.sizeCompare(WIDTH-suffix1.length) <= 0) { it.size match { diff --git a/library/src/scala/collection/immutable/VectorMap.scala b/library/src/scala/collection/immutable/VectorMap.scala index 5a4ecd20b2a3..5a89b90e8ac2 100644 --- a/library/src/scala/collection/immutable/VectorMap.scala +++ b/library/src/scala/collection/immutable/VectorMap.scala @@ -15,6 +15,8 @@ package collection package immutable import scala.language.`2.13` +import language.experimental.captureChecking + import scala.annotation.{nowarn, tailrec} /** This class implements immutable maps using a vector/map-based data structure, which preserves insertion order. @@ -58,7 +60,7 @@ final class VectorMap[K, +V] private ( } } - override def withDefault[V1 >: V](d: K => V1): Map[K, V1] = + override def withDefault[V1 >: V](d: K -> V1): Map[K, V1] = new Map.WithDefault(this, d) override def withDefaultValue[V1 >: V](d: V1): Map[K, V1] = @@ -233,7 +235,7 @@ object VectorMap extends StrictMapFactory[VectorMap] { def empty[K, V]: VectorMap[K, V] = EmptyMap.asInstanceOf[VectorMap[K, V]] - def from[K, V](it: collection.IterableOnce[(K, V)]): VectorMap[K, V] = + def from[K, V](it: collection.IterableOnce[(K, V)]^): VectorMap[K, V] = it match { case vm: VectorMap[K, V] => vm case _ => (newBuilder[K, V] ++= it).result() From 7d83add4d59b465cb7869dfc5d1513bda44962e0 Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Thu, 14 Aug 2025 19:23:44 +0200 Subject: [PATCH 087/101] Capture-check concurrent files --- TODO.md | 4 ++-- library/src/scala/collection/concurrent/Map.scala | 2 ++ library/src/scala/collection/concurrent/TrieMap.scala | 10 ++++++---- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/TODO.md b/TODO.md index 2f41ef482093..c2e755dfaa19 100644 --- a/TODO.md +++ b/TODO.md @@ -222,8 +222,8 @@ - [x] library/src/scala/collection/StringParsers.scala - [x] library/src/scala/collection/View.scala - [x] library/src/scala/collection/WithFilter.scala -- [ ] library/src/scala/collection/concurrent/Map.scala -- [ ] library/src/scala/collection/concurrent/TrieMap.scala +- [x] library/src/scala/collection/concurrent/Map.scala +- [x] library/src/scala/collection/concurrent/TrieMap.scala - [ ] library/src/scala/collection/convert/AsJavaConverters.scala - [ ] library/src/scala/collection/convert/AsJavaExtensions.scala - [ ] library/src/scala/collection/convert/AsScalaConverters.scala diff --git a/library/src/scala/collection/concurrent/Map.scala b/library/src/scala/collection/concurrent/Map.scala index 818fcda2a510..2269816f49b6 100644 --- a/library/src/scala/collection/concurrent/Map.scala +++ b/library/src/scala/collection/concurrent/Map.scala @@ -14,6 +14,8 @@ package scala package collection.concurrent import scala.language.`2.13` +import language.experimental.captureChecking + import scala.annotation.tailrec /** A template trait for mutable maps that allow concurrent access. diff --git a/library/src/scala/collection/concurrent/TrieMap.scala b/library/src/scala/collection/concurrent/TrieMap.scala index 2df64be85521..679c8258a0b1 100644 --- a/library/src/scala/collection/concurrent/TrieMap.scala +++ b/library/src/scala/collection/concurrent/TrieMap.scala @@ -15,6 +15,8 @@ package collection package concurrent import scala.language.`2.13` +import language.experimental.captureChecking + import java.util.concurrent.atomic._ import scala.{unchecked => uc} import scala.annotation.tailrec @@ -1016,10 +1018,10 @@ final class TrieMap[K, V] private (r: AnyRef, rtupd: AtomicReferenceFieldUpdater override def view: MapView[K, V] = if (nonReadOnly) readOnlySnapshot().view else super.view @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") - override def filterKeys(p: K => Boolean): collection.MapView[K, V] = view.filterKeys(p) + override def filterKeys(p: K => Boolean): collection.MapView[K, V]^{p} = view.filterKeys(p) @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") - override def mapValues[W](f: V => W): collection.MapView[K, W] = view.mapValues(f) + override def mapValues[W](f: V => W): collection.MapView[K, W]^{f} = view.mapValues(f) // END extra overrides /////////////////////////////////////////////////////////////////// @@ -1042,7 +1044,7 @@ object TrieMap extends StrictMapFactory[TrieMap] { def empty[K, V]: TrieMap[K, V] = new TrieMap[K, V] - def from[K, V](it: IterableOnce[(K, V)]): TrieMap[K, V] = new TrieMap[K, V]() ++= it + def from[K, V](it: IterableOnce[(K, V)]^): TrieMap[K, V] = new TrieMap[K, V]() ++= it def newBuilder[K, V]: mutable.GrowableBuilder[(K, V), TrieMap[K, V]] = new GrowableBuilder(empty[K, V]) @@ -1068,7 +1070,7 @@ object TrieMap extends StrictMapFactory[TrieMap] { } // non-final as an extension point for parallel collections -private[collection] class TrieMapIterator[K, V](var level: Int, private var ct: TrieMap[K, V], mustInit: Boolean = true) extends AbstractIterator[(K, V)] { +private[collection] class TrieMapIterator[K, V](var level: Int, private var ct: TrieMap[K, V], mustInit: Boolean = true) extends AbstractIterator[(K, V)] { self => private val stack = new Array[Array[BasicNode]](7) private val stackpos = new Array[Int](7) private var depth = -1 From 6121ae2fa93245accc8992421fd97b6ec837ca94 Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Thu, 14 Aug 2025 19:40:47 +0200 Subject: [PATCH 088/101] Capture-check converters Iterator and IterableWrappers are capturing, the rest stays pure-only --- TODO.md | 12 ++++++------ .../collection/convert/AsJavaExtensions.scala | 2 ++ .../collection/convert/AsScalaConverters.scala | 11 +++++++---- .../collection/convert/AsScalaExtensions.scala | 2 ++ .../collection/convert/ImplicitConversions.scala | 2 ++ .../convert/JavaCollectionWrappers.scala | 14 ++++++++------ .../collection/convert/StreamExtensions.scala | 2 ++ 7 files changed, 29 insertions(+), 16 deletions(-) diff --git a/TODO.md b/TODO.md index c2e755dfaa19..423b865031aa 100644 --- a/TODO.md +++ b/TODO.md @@ -225,12 +225,12 @@ - [x] library/src/scala/collection/concurrent/Map.scala - [x] library/src/scala/collection/concurrent/TrieMap.scala - [ ] library/src/scala/collection/convert/AsJavaConverters.scala -- [ ] library/src/scala/collection/convert/AsJavaExtensions.scala -- [ ] library/src/scala/collection/convert/AsScalaConverters.scala -- [ ] library/src/scala/collection/convert/AsScalaExtensions.scala -- [ ] library/src/scala/collection/convert/ImplicitConversions.scala -- [ ] library/src/scala/collection/convert/JavaCollectionWrappers.scala -- [ ] library/src/scala/collection/convert/StreamExtensions.scala +- [x] library/src/scala/collection/convert/AsJavaExtensions.scala +- [x] library/src/scala/collection/convert/AsScalaConverters.scala +- [x] library/src/scala/collection/convert/AsScalaExtensions.scala +- [x] library/src/scala/collection/convert/ImplicitConversions.scala +- [x] library/src/scala/collection/convert/JavaCollectionWrappers.scala +- [x] library/src/scala/collection/convert/StreamExtensions.scala - [ ] library/src/scala/collection/convert/impl/ArrayStepper.scala - [ ] library/src/scala/collection/convert/impl/BinaryTreeStepper.scala - [ ] library/src/scala/collection/convert/impl/BitSetStepper.scala diff --git a/library/src/scala/collection/convert/AsJavaExtensions.scala b/library/src/scala/collection/convert/AsJavaExtensions.scala index 406ad7e8b82f..67f127d9e042 100644 --- a/library/src/scala/collection/convert/AsJavaExtensions.scala +++ b/library/src/scala/collection/convert/AsJavaExtensions.scala @@ -15,6 +15,8 @@ package collection package convert import scala.language.`2.13` +import language.experimental.captureChecking + import java.util.{concurrent => juc} import java.{lang => jl, util => ju} diff --git a/library/src/scala/collection/convert/AsScalaConverters.scala b/library/src/scala/collection/convert/AsScalaConverters.scala index dac9b9484369..e2fa0f39bfef 100644 --- a/library/src/scala/collection/convert/AsScalaConverters.scala +++ b/library/src/scala/collection/convert/AsScalaConverters.scala @@ -15,6 +15,9 @@ package collection package convert import scala.language.`2.13` +import language.experimental.captureChecking +import caps.unsafe.unsafeAssumePure + import java.util.{concurrent => juc} import java.{lang => jl, util => ju} @@ -40,7 +43,7 @@ trait AsScalaConverters { */ def asScala[A](i: ju.Iterator[A]): Iterator[A] = i match { case null => null - case wrapper: IteratorWrapper[A @uc] => wrapper.underlying + case wrapper: IteratorWrapper[A @uc] => wrapper.underlying.unsafeAssumePure // TODO: Remove when pattern matching recognizes this is safe case _ => new JIteratorWrapper(i) } @@ -58,7 +61,7 @@ trait AsScalaConverters { */ def asScala[A](e: ju.Enumeration[A]): Iterator[A] = e match { case null => null - case wrapper: IteratorWrapper[A @uc] => wrapper.underlying + case wrapper: IteratorWrapper[A @uc] => wrapper.underlying.unsafeAssumePure // TODO: Remove when pattern matching recognizes this is safe case _ => new JEnumerationWrapper(e) } @@ -76,7 +79,7 @@ trait AsScalaConverters { */ def asScala[A](i: jl.Iterable[A]): Iterable[A] = i match { case null => null - case wrapper: IterableWrapper[A @uc] => wrapper.underlying + case wrapper: IterableWrapper[A @uc] => wrapper.underlying.unsafeAssumePure // TODO: Remove when pattern matching recognizes this is safe case _ => new JIterableWrapper(i) } @@ -91,7 +94,7 @@ trait AsScalaConverters { */ def asScala[A](c: ju.Collection[A]): Iterable[A] = c match { case null => null - case wrapper: IterableWrapper[A @uc] => wrapper.underlying + case wrapper: IterableWrapper[A @uc] => wrapper.underlying.unsafeAssumePure // TODO: Remove when pattern matching recognizes this is safe case _ => new JCollectionWrapper(c) } diff --git a/library/src/scala/collection/convert/AsScalaExtensions.scala b/library/src/scala/collection/convert/AsScalaExtensions.scala index 65f017495280..3ef08f0690f3 100644 --- a/library/src/scala/collection/convert/AsScalaExtensions.scala +++ b/library/src/scala/collection/convert/AsScalaExtensions.scala @@ -15,6 +15,8 @@ package collection package convert import scala.language.`2.13` +import language.experimental.captureChecking + import java.util.{concurrent => juc} import java.{lang => jl, util => ju} diff --git a/library/src/scala/collection/convert/ImplicitConversions.scala b/library/src/scala/collection/convert/ImplicitConversions.scala index ef72a79d5795..b2ebd48f16e9 100644 --- a/library/src/scala/collection/convert/ImplicitConversions.scala +++ b/library/src/scala/collection/convert/ImplicitConversions.scala @@ -15,6 +15,8 @@ package collection package convert import scala.language.`2.13` +import language.experimental.captureChecking + import java.util.{concurrent => juc} import java.{lang => jl, util => ju} diff --git a/library/src/scala/collection/convert/JavaCollectionWrappers.scala b/library/src/scala/collection/convert/JavaCollectionWrappers.scala index 1679491efcb0..5058f93fc48f 100644 --- a/library/src/scala/collection/convert/JavaCollectionWrappers.scala +++ b/library/src/scala/collection/convert/JavaCollectionWrappers.scala @@ -15,6 +15,8 @@ package collection package convert import scala.language.`2.13` +import language.experimental.captureChecking + import java.util.{concurrent => juc} import java.util.{NavigableMap} import java.{lang => jl, util => ju} @@ -29,7 +31,7 @@ import scala.util.control.ControlThrowable // not private[convert] because `WeakHashMap` uses JMapWrapper private[collection] object JavaCollectionWrappers extends Serializable { @SerialVersionUID(3L) - class IteratorWrapper[A](val underlying: Iterator[A]) extends ju.Iterator[A] with ju.Enumeration[A] with Serializable { + class IteratorWrapper[A](val underlying: Iterator[A]^) extends ju.Iterator[A] with ju.Enumeration[A] with Serializable { def hasNext = underlying.hasNext def next() = underlying.next() def hasMoreElements = underlying.hasNext @@ -65,14 +67,14 @@ private[collection] object JavaCollectionWrappers extends Serializable { } trait IterableWrapperTrait[A] extends ju.AbstractCollection[A] { - val underlying: Iterable[A] + val underlying: Iterable[A]^ def size = underlying.size - override def iterator: IteratorWrapper[A] = new IteratorWrapper(underlying.iterator) + override def iterator: IteratorWrapper[A]^{this} = new IteratorWrapper(underlying.iterator) override def isEmpty = underlying.isEmpty } @SerialVersionUID(3L) - class IterableWrapper[A](val underlying: Iterable[A]) extends ju.AbstractCollection[A] with IterableWrapperTrait[A] with Serializable { + class IterableWrapper[A](val underlying: Iterable[A]^) extends ju.AbstractCollection[A] with IterableWrapperTrait[A] with Serializable { override def equals(other: Any): Boolean = other match { case that: IterableWrapper[_] => this.underlying == that.underlying case _ => false @@ -151,7 +153,7 @@ private[collection] object JavaCollectionWrappers extends Serializable { def prepend(elem: A) = { underlying.subList(0, 0) add elem; this } def addOne(elem: A): this.type = { underlying add elem; this } def insert(idx: Int,elem: A): Unit = underlying.subList(0, idx).add(elem) - def insertAll(i: Int, elems: IterableOnce[A]) = { + def insertAll(i: Int, elems: IterableOnce[A]^) = { val ins = underlying.subList(0, i) elems.iterator.foreach(ins.add(_)) } @@ -160,7 +162,7 @@ private[collection] object JavaCollectionWrappers extends Serializable { // Note: Clone cannot just call underlying.clone because in Java, only specific collections // expose clone methods. Generically, they're protected. override def clone(): JListWrapper[A] = new JListWrapper(new ju.ArrayList[A](underlying)) - def patchInPlace(from: Int, patch: scala.collection.IterableOnce[A], replaced: Int): this.type = { + def patchInPlace(from: Int, patch: scala.collection.IterableOnce[A]^, replaced: Int): this.type = { remove(from, replaced) insertAll(from, patch) this diff --git a/library/src/scala/collection/convert/StreamExtensions.scala b/library/src/scala/collection/convert/StreamExtensions.scala index 4457989ead40..5e70ed1b4fd0 100644 --- a/library/src/scala/collection/convert/StreamExtensions.scala +++ b/library/src/scala/collection/convert/StreamExtensions.scala @@ -13,6 +13,8 @@ package scala.collection.convert import scala.language.`2.13` +import language.experimental.captureChecking + import java.util.Spliterator import java.util.stream._ import java.{lang => jl} From 62143472ba99aab310e89a44ca49f8b48d17f923 Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Thu, 14 Aug 2025 20:13:59 +0200 Subject: [PATCH 089/101] Capture-check collection/generic --- TODO.md | 18 +++++++++--------- .../collection/generic/BitOperations.scala | 1 + .../collection/generic/CommonErrors.scala | 1 + .../generic/DefaultSerializationProxy.scala | 8 +++++--- .../scala/collection/generic/IsIterable.scala | 1 + .../collection/generic/IsIterableOnce.scala | 1 + .../src/scala/collection/generic/IsMap.scala | 2 ++ .../src/scala/collection/generic/IsSeq.scala | 6 ++++-- .../collection/generic/Subtractable.scala | 3 ++- .../src/scala/collection/generic/package.scala | 1 + 10 files changed, 27 insertions(+), 15 deletions(-) diff --git a/TODO.md b/TODO.md index 423b865031aa..48bf20e6a56f 100644 --- a/TODO.md +++ b/TODO.md @@ -244,15 +244,15 @@ - [ ] library/src/scala/collection/convert/impl/StringStepper.scala - [ ] library/src/scala/collection/convert/impl/TableStepper.scala - [ ] library/src/scala/collection/convert/impl/VectorStepper.scala -- [ ] library/src/scala/collection/generic/BitOperations.scala -- [ ] library/src/scala/collection/generic/CommonErrors.scala -- [ ] library/src/scala/collection/generic/DefaultSerializationProxy.scala -- [ ] library/src/scala/collection/generic/IsIterable.scala -- [ ] library/src/scala/collection/generic/IsIterableOnce.scala -- [ ] library/src/scala/collection/generic/IsMap.scala -- [ ] library/src/scala/collection/generic/IsSeq.scala -- [ ] library/src/scala/collection/generic/Subtractable.scala -- [ ] library/src/scala/collection/generic/package.scala +- [x] library/src/scala/collection/generic/BitOperations.scala +- [x] library/src/scala/collection/generic/CommonErrors.scala +- [x] library/src/scala/collection/generic/DefaultSerializationProxy.scala +- [x] library/src/scala/collection/generic/IsIterable.scala +- [x] library/src/scala/collection/generic/IsIterableOnce.scala +- [x] library/src/scala/collection/generic/IsMap.scala +- [x] library/src/scala/collection/generic/IsSeq.scala +- [x] library/src/scala/collection/generic/Subtractable.scala +- [x] library/src/scala/collection/generic/package.scala - [x] library/src/scala/collection/immutable/ArraySeq.scala - [x] library/src/scala/collection/immutable/BitSet.scala - [x] library/src/scala/collection/immutable/ChampCommon.scala diff --git a/library/src/scala/collection/generic/BitOperations.scala b/library/src/scala/collection/generic/BitOperations.scala index 39aa29d1daf9..a0a2806bd0dc 100644 --- a/library/src/scala/collection/generic/BitOperations.scala +++ b/library/src/scala/collection/generic/BitOperations.scala @@ -14,6 +14,7 @@ package scala.collection package generic import scala.language.`2.13` +import language.experimental.captureChecking /** Some bit operations. * diff --git a/library/src/scala/collection/generic/CommonErrors.scala b/library/src/scala/collection/generic/CommonErrors.scala index 3ced8e3debbf..c7cb23a3d112 100644 --- a/library/src/scala/collection/generic/CommonErrors.scala +++ b/library/src/scala/collection/generic/CommonErrors.scala @@ -14,6 +14,7 @@ package scala.collection package generic import scala.language.`2.13` +import language.experimental.captureChecking /** Some precomputed common errors to reduce the generated code size. */ diff --git a/library/src/scala/collection/generic/DefaultSerializationProxy.scala b/library/src/scala/collection/generic/DefaultSerializationProxy.scala index 056e53b6f882..d6b5e4e47693 100644 --- a/library/src/scala/collection/generic/DefaultSerializationProxy.scala +++ b/library/src/scala/collection/generic/DefaultSerializationProxy.scala @@ -15,6 +15,8 @@ package scala.collection.generic import java.io.{ObjectInputStream, ObjectOutputStream} import scala.language.`2.13` +import language.experimental.captureChecking + import scala.collection.{Factory, Iterable} import scala.collection.mutable.Builder @@ -26,7 +28,7 @@ import scala.collection.mutable.Builder * additional state required to create the proper `Builder` needs to be captured by the `factory`. */ @SerialVersionUID(3L) -final class DefaultSerializationProxy[A](factory: Factory[A, Any], @transient private[this] val coll: Iterable[A]) extends Serializable { +final class DefaultSerializationProxy[A](factory: Factory[A, Any], @transient private[this] val coll: Iterable[A]^) extends Serializable { @transient protected var builder: Builder[A, Any] = _ @@ -75,8 +77,8 @@ private[collection] case object SerializeEnd * it directly without using this trait if you need a non-standard factory or if you want to use a different * serialization scheme. */ -transparent trait DefaultSerializable extends Serializable { this: scala.collection.Iterable[_] => - protected[this] def writeReplace(): AnyRef = { +transparent trait DefaultSerializable extends Serializable { this: scala.collection.Iterable[_]^ => + protected[this] def writeReplace(): AnyRef^{this} = { val f: Factory[Any, Any] = this match { case it: scala.collection.SortedMap[_, _] => it.sortedMapFactory.sortedMapFactory[Any, Any](using it.ordering.asInstanceOf[Ordering[Any]]).asInstanceOf[Factory[Any, Any]] case it: scala.collection.Map[_, _] => it.mapFactory.mapFactory[Any, Any].asInstanceOf[Factory[Any, Any]] diff --git a/library/src/scala/collection/generic/IsIterable.scala b/library/src/scala/collection/generic/IsIterable.scala index fd5db475536f..0b80203037fc 100644 --- a/library/src/scala/collection/generic/IsIterable.scala +++ b/library/src/scala/collection/generic/IsIterable.scala @@ -14,6 +14,7 @@ package scala.collection package generic import scala.language.`2.13` +import language.experimental.captureChecking /** A trait which can be used to avoid code duplication when defining extension * methods that should be applicable both to existing Scala collections (i.e., diff --git a/library/src/scala/collection/generic/IsIterableOnce.scala b/library/src/scala/collection/generic/IsIterableOnce.scala index a5c2c4889722..bced6e190206 100644 --- a/library/src/scala/collection/generic/IsIterableOnce.scala +++ b/library/src/scala/collection/generic/IsIterableOnce.scala @@ -15,6 +15,7 @@ package collection package generic import scala.language.`2.13` +import language.experimental.captureChecking /** Type class witnessing that a collection representation type `Repr` has * elements of type `A` and has a conversion to `IterableOnce[A]`. diff --git a/library/src/scala/collection/generic/IsMap.scala b/library/src/scala/collection/generic/IsMap.scala index 47eac5587f11..5b5a988792c0 100644 --- a/library/src/scala/collection/generic/IsMap.scala +++ b/library/src/scala/collection/generic/IsMap.scala @@ -14,6 +14,8 @@ package scala.collection package generic import scala.language.`2.13` +import language.experimental.captureChecking + import IsMap.Tupled import scala.collection.immutable.{IntMap, LongMap} diff --git a/library/src/scala/collection/generic/IsSeq.scala b/library/src/scala/collection/generic/IsSeq.scala index 2707ce988e34..04a94c5e5da0 100644 --- a/library/src/scala/collection/generic/IsSeq.scala +++ b/library/src/scala/collection/generic/IsSeq.scala @@ -14,6 +14,8 @@ package scala.collection package generic import scala.language.`2.13` +import language.experimental.captureChecking + import scala.reflect.ClassTag /** Type class witnessing that a collection representation type `Repr` has @@ -69,7 +71,7 @@ object IsSeq { def apply(i: Int): Char = s.charAt(i) def toIterable: Iterable[Char] = new immutable.WrappedString(s) protected[this] def coll: String = s - protected[this] def fromSpecific(coll: IterableOnce[Char]): String = coll.iterator.mkString + protected[this] def fromSpecific(coll: IterableOnce[Char]^): String = coll.iterator.mkString def iterableFactory: IterableFactory[immutable.ArraySeq] = immutable.ArraySeq.untagged override def empty: String = "" protected[this] def newSpecificBuilder: mutable.Builder[Char, String] = new StringBuilder @@ -94,7 +96,7 @@ object IsSeq { def length: Int = a.length def toIterable: Iterable[A] = mutable.ArraySeq.make(a) protected def coll: Array[A] = a - protected def fromSpecific(coll: IterableOnce[A]): Array[A] = Array.from(coll) + protected def fromSpecific(coll: IterableOnce[A]^): Array[A] = Array.from(coll) def iterableFactory: IterableFactory[mutable.ArraySeq] = mutable.ArraySeq.untagged override def empty: Array[A] = Array.empty[A] protected def newSpecificBuilder: mutable.Builder[A, Array[A]] = Array.newBuilder diff --git a/library/src/scala/collection/generic/Subtractable.scala b/library/src/scala/collection/generic/Subtractable.scala index 2e06819b6832..87c8f57c79d2 100644 --- a/library/src/scala/collection/generic/Subtractable.scala +++ b/library/src/scala/collection/generic/Subtractable.scala @@ -15,6 +15,7 @@ package collection package generic import scala.language.`2.13` +import language.experimental.captureChecking /** This trait represents collection-like objects that can be reduced * using a '+' operator. It defines variants of `-` and `--` @@ -60,5 +61,5 @@ trait Subtractable[A, +Repr <: Subtractable[A, Repr]] { self => * @return a new $coll that contains all elements of the current $coll * except one less occurrence of each of the elements of `elems`. */ - def --(xs: IterableOnce[A]): Repr = (repr /: xs.iterator) (_ - _) + def --(xs: IterableOnce[A]^): Repr = (repr /: xs.iterator) (_ - _) } diff --git a/library/src/scala/collection/generic/package.scala b/library/src/scala/collection/generic/package.scala index 0dc855d81d54..91181e0f7751 100644 --- a/library/src/scala/collection/generic/package.scala +++ b/library/src/scala/collection/generic/package.scala @@ -13,6 +13,7 @@ package scala.collection import scala.language.`2.13` +import language.experimental.captureChecking package object generic { @deprecated("Clearable was moved from collection.generic to collection.mutable", "2.13.0") From 468e05c4a4a3b9069a3c27cbd31b78df4a0112a3 Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Thu, 14 Aug 2025 20:14:46 +0200 Subject: [PATCH 090/101] Capture-check AsJavaConverters --- TODO.md | 2 +- library/src/scala/collection/convert/AsJavaConverters.scala | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/TODO.md b/TODO.md index 48bf20e6a56f..2858b1f222cd 100644 --- a/TODO.md +++ b/TODO.md @@ -224,7 +224,7 @@ - [x] library/src/scala/collection/WithFilter.scala - [x] library/src/scala/collection/concurrent/Map.scala - [x] library/src/scala/collection/concurrent/TrieMap.scala -- [ ] library/src/scala/collection/convert/AsJavaConverters.scala +- [x] library/src/scala/collection/convert/AsJavaConverters.scala - [x] library/src/scala/collection/convert/AsJavaExtensions.scala - [x] library/src/scala/collection/convert/AsScalaConverters.scala - [x] library/src/scala/collection/convert/AsScalaExtensions.scala diff --git a/library/src/scala/collection/convert/AsJavaConverters.scala b/library/src/scala/collection/convert/AsJavaConverters.scala index f82546e251a7..079847776368 100644 --- a/library/src/scala/collection/convert/AsJavaConverters.scala +++ b/library/src/scala/collection/convert/AsJavaConverters.scala @@ -15,6 +15,8 @@ package collection package convert import scala.language.`2.13` +import language.experimental.captureChecking + import java.util.{concurrent => juc} import java.{lang => jl, util => ju} From ebd6887ca518105abb2b0c5426b13eea05a2bf7e Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Thu, 14 Aug 2025 20:16:40 +0200 Subject: [PATCH 091/101] Capture-check collection package --- TODO.md | 2 +- library/src/scala/collection/package.scala | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/TODO.md b/TODO.md index 2858b1f222cd..af74563e322e 100644 --- a/TODO.md +++ b/TODO.md @@ -330,7 +330,7 @@ - [x] library/src/scala/collection/mutable/UnrolledBuffer.scala - [x] library/src/scala/collection/mutable/WeakHashMap.scala - [x] library/src/scala/collection/mutable/package.scala -- [ ] library/src/scala/collection/package.scala +- [x] library/src/scala/collection/package.scala - [ ] library/src/scala/compat/Platform.scala - [ ] library/src/scala/compiletime/Erased.scala - [x] library/src/scala/compiletime/ops/any.scala diff --git a/library/src/scala/collection/package.scala b/library/src/scala/collection/package.scala index aced3cb25e03..f43e22b531a7 100644 --- a/library/src/scala/collection/package.scala +++ b/library/src/scala/collection/package.scala @@ -13,6 +13,7 @@ package scala import scala.language.`2.13` +import language.experimental.captureChecking package object collection { @deprecated("Use Iterable instead of Traversable", "2.13.0") @@ -65,7 +66,7 @@ package object collection { /** Splits a sequence into head +: tail. * @return Some((head, tail)) if sequence is non-empty. None otherwise. */ - def unapply[A, CC[_] <: Seq[_], C <: SeqOps[A, CC, C]](t: C with SeqOps[A, CC, C]): Option[(A, C)] = + def unapply[A, CC[_] <: Seq[_], C <: SeqOps[A, CC, C]](t: (C with SeqOps[A, CC, C])^): Option[(A, C^{t})] = if(t.isEmpty) None else Some(t.head -> t.tail) } @@ -75,7 +76,7 @@ package object collection { /** Splits a sequence into init :+ last. * @return Some((init, last)) if sequence is non-empty. None otherwise. */ - def unapply[A, CC[_] <: Seq[_], C <: SeqOps[A, CC, C]](t: C with SeqOps[A, CC, C]): Option[(C, A)] = + def unapply[A, CC[_] <: Seq[_], C <: SeqOps[A, CC, C]](t: (C with SeqOps[A, CC, C])^): Option[(C^{t}, A)] = if(t.isEmpty) None else Some(t.init -> t.last) } From 8f2fd148dd889e38da4174a512b03244d5008a85 Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Thu, 14 Aug 2025 21:36:32 +0200 Subject: [PATCH 092/101] Fix partial function test --- tests/neg-custom-args/captures/partial-function.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/neg-custom-args/captures/partial-function.scala b/tests/neg-custom-args/captures/partial-function.scala index f746f1697536..5d75ee193eb6 100644 --- a/tests/neg-custom-args/captures/partial-function.scala +++ b/tests/neg-custom-args/captures/partial-function.scala @@ -2,5 +2,5 @@ class A extends caps.SharedCapability: def even(x: Int) = x % 2 == 0 def f(a: A): List[Int] -> List[Int] = - (xs: List[Int]) => xs.collect: + (xs: List[Int]) => xs.collect: // error case x if a.even(x) => 1 From c8dd6811002b46f96e824436f31beedef51a0f7a Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Fri, 15 Aug 2025 17:57:59 +0200 Subject: [PATCH 093/101] Fix wrong casting to Nothing --- library/src/scala/collection/IndexedSeq.scala | 4 ++-- library/src/scala/collection/immutable/LazyListIterable.scala | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/library/src/scala/collection/IndexedSeq.scala b/library/src/scala/collection/IndexedSeq.scala index 004cb5c76562..df69f7210689 100644 --- a/library/src/scala/collection/IndexedSeq.scala +++ b/library/src/scala/collection/IndexedSeq.scala @@ -39,7 +39,7 @@ transparent trait StrictIndexedSeqOps[+A, +CC[_] <: caps.Pure, +C] extends Any w override def sliding(size: Int, step: Int): Iterator[C] = { require(size >= 1 && step >= 1, f"size=$size%d and step=$step%d, but both must be positive") val it = new IndexedSeqSlidingIterator[A, CC, C](this, size, step) - it.asInstanceOf // TODO: seems like CC cannot figure this out yet + it.asInstanceOf[Iterator[C]] // TODO: seems like CC cannot figure this out yet } } @@ -106,7 +106,7 @@ transparent trait IndexedSeqOps[+A, +CC[_], +C] extends Any with SeqOps[A, CC, C override def sliding(size: Int, step: Int): Iterator[C^{this}]^{this} = { require(size >= 1 && step >= 1, f"size=$size%d and step=$step%d, but both must be positive") val it = new IndexedSeqSlidingIterator[A, CC, C](this, size, step) - it.asInstanceOf // TODO: seems like CC cannot figure this out yet + it.asInstanceOf[Iterator[Nothing]] // TODO: seems like CC cannot figure this out yet } override def head: A = diff --git a/library/src/scala/collection/immutable/LazyListIterable.scala b/library/src/scala/collection/immutable/LazyListIterable.scala index 49e928ca99a4..5f8a13f84964 100644 --- a/library/src/scala/collection/immutable/LazyListIterable.scala +++ b/library/src/scala/collection/immutable/LazyListIterable.scala @@ -882,7 +882,7 @@ final class LazyListIterable[+A] private (lazyState: LazyListIterable.EmptyMarke if (knownIsEmpty) Iterator.empty else val it = new SlidingIterator[A](this, size = size, step = step) - it.asInstanceOf // CC cannot figure this out yet + it.asInstanceOf[Iterator[Nothing]] // CC cannot figure this out yet /** @inheritdoc * From 9fd6bb157b2f299cd2e179581afe011a64feea90 Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Sat, 16 Aug 2025 01:00:06 +0200 Subject: [PATCH 094/101] Map's KeySet has to be pure, so we make it strict --- library/src/scala/collection/Map.scala | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/library/src/scala/collection/Map.scala b/library/src/scala/collection/Map.scala index a2c79949d58b..a2b0ec84be82 100644 --- a/library/src/scala/collection/Map.scala +++ b/library/src/scala/collection/Map.scala @@ -243,8 +243,11 @@ transparent trait MapOps[K, +V, +CC[_, _] <: IterableOps[_, AnyConstr, _], +C] } /** A generic trait that is reused by keyset implementations */ - protected trait GenKeySet { this: Set[K]^ => - def iterator: Iterator[K]^{this} = MapOps.this.keysIterator + protected trait GenKeySet { this: Set[K] => + def iterator: Iterator[K] = + // CC note: this is unavoidable to make the KeySet pure. + // If you need a generic, capturing KeySet, create a View from keysIterator + MapOps.this.keysIterator.toSet.iterator def contains(key: K): Boolean = MapOps.this.contains(key) override def size: Int = MapOps.this.size override def knownSize: Int = MapOps.this.knownSize From f5f5b94b05f20fbf02aae063baba050119322f2e Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Sat, 16 Aug 2025 01:00:28 +0200 Subject: [PATCH 095/101] Removal of extra captures --- library/src/scala/PartialFunction.scala | 2 +- library/src/scala/collection/IterableOnce.scala | 2 +- library/src/scala/collection/SortedMap.scala | 4 ++-- library/src/scala/collection/StringOps.scala | 6 +++--- .../src/scala/collection/immutable/LazyListIterable.scala | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/library/src/scala/PartialFunction.scala b/library/src/scala/PartialFunction.scala index d5379a96479c..31f3fbe77a83 100644 --- a/library/src/scala/PartialFunction.scala +++ b/library/src/scala/PartialFunction.scala @@ -263,7 +263,7 @@ object PartialFunction { final class ElementWiseExtractor[-A, +B] private[PartialFunction] (private val pf: PartialFunction[A, B]^) extends AnyVal { this: ElementWiseExtractor[A, B]^ => @nowarn("cat=lint-nonlocal-return") - def unapplySeq(seq: Seq[A]): Option[Seq[B]^{this}] = { // TODO remove when Seq is capture-checked + def unapplySeq(seq: Seq[A]): Option[Seq[B]] = { Some(seq.map { case pf(b) => b case _ => return None diff --git a/library/src/scala/collection/IterableOnce.scala b/library/src/scala/collection/IterableOnce.scala index a139e3de14f8..5384be361a3e 100644 --- a/library/src/scala/collection/IterableOnce.scala +++ b/library/src/scala/collection/IterableOnce.scala @@ -1480,7 +1480,7 @@ transparent trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOn def toIndexedSeq: immutable.IndexedSeq[A] = immutable.IndexedSeq.from(this) @deprecated("Use .to(LazyList) instead of .toStream", "2.13.0") - @inline final def toStream: immutable.Stream[A]^{this} = to(immutable.Stream) + @inline final def toStream: immutable.Stream[A] = to(immutable.Stream) /** Converts this $coll to a `Buffer`. * diff --git a/library/src/scala/collection/SortedMap.scala b/library/src/scala/collection/SortedMap.scala index 10f6cc14a01f..827ec8a19ea1 100644 --- a/library/src/scala/collection/SortedMap.scala +++ b/library/src/scala/collection/SortedMap.scala @@ -206,10 +206,10 @@ object SortedMapOps { p: ((K, V)) => Boolean ) extends MapOps.WithFilter[K, V, IterableCC, MapCC](self, p) { - def map[K2 : Ordering, V2](f: ((K, V)) => (K2, V2)): CC[K2, V2]^{this, f} = + def map[K2 : Ordering, V2](f: ((K, V)) => (K2, V2)): CC[K2, V2] = self.sortedMapFactory.from(new View.Map(filtered, f)) - def flatMap[K2 : Ordering, V2](f: ((K, V)) => IterableOnce[(K2, V2)]^): CC[K2, V2]^{this, f} = + def flatMap[K2 : Ordering, V2](f: ((K, V)) => IterableOnce[(K2, V2)]^): CC[K2, V2] = self.sortedMapFactory.from(new View.FlatMap(filtered, f)) override def withFilter(q: ((K, V)) => Boolean): WithFilter[K, V, IterableCC, MapCC, CC]^{this, q} = diff --git a/library/src/scala/collection/StringOps.scala b/library/src/scala/collection/StringOps.scala index 8cc391f98a7a..10292f330d8f 100644 --- a/library/src/scala/collection/StringOps.scala +++ b/library/src/scala/collection/StringOps.scala @@ -595,14 +595,14 @@ final class StringOps(private val s: String) extends AnyVal { self => @inline final def mkString: String = s /** Appends this string to a string builder. */ - @inline final def addString(b: StringBuilder^): b.type = b.append(s) + @inline final def addString(b: StringBuilder): b.type = b.append(s) /** Appends this string to a string builder using a separator string. */ - @inline final def addString(b: StringBuilder^, sep: String): b.type = + @inline final def addString(b: StringBuilder, sep: String): b.type = addString(b, "", sep, "") /** Appends this string to a string builder using start, end and separator strings. */ - final def addString(b: StringBuilder^, start: String, sep: String, end: String): b.type = { + final def addString(b: StringBuilder, start: String, sep: String, end: String): b.type = { val jsb = b.underlying if (start.length != 0) jsb.append(start) val len = s.length diff --git a/library/src/scala/collection/immutable/LazyListIterable.scala b/library/src/scala/collection/immutable/LazyListIterable.scala index 5f8a13f84964..f1d14bbb1fad 100644 --- a/library/src/scala/collection/immutable/LazyListIterable.scala +++ b/library/src/scala/collection/immutable/LazyListIterable.scala @@ -840,7 +840,7 @@ final class LazyListIterable[+A] private (lazyState: LazyListIterable.EmptyMarke * * $preservesLaziness */ - override def diff[B >: A](that: collection.Seq[B]^): LazyListIterable[A]^{this} = + override def diff[B >: A](that: collection.Seq[B]): LazyListIterable[A]^{this} = if (knownIsEmpty) Empty else super.diff(that) @@ -848,7 +848,7 @@ final class LazyListIterable[+A] private (lazyState: LazyListIterable.EmptyMarke * * $preservesLaziness */ - override def intersect[B >: A](that: collection.Seq[B]^): LazyListIterable[A]^{this} = + override def intersect[B >: A](that: collection.Seq[B]): LazyListIterable[A]^{this} = if (knownIsEmpty) Empty else super.intersect(that) From 07547f44087bb715070e125890a12bbebb5b77b0 Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Sat, 16 Aug 2025 01:42:30 +0200 Subject: [PATCH 096/101] For some reason Pure has to extend Any --- library/src/scala/caps/Pure.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/src/scala/caps/Pure.scala b/library/src/scala/caps/Pure.scala index 11d0e3f039e9..05f7e64f884d 100644 --- a/library/src/scala/caps/Pure.scala +++ b/library/src/scala/caps/Pure.scala @@ -7,5 +7,5 @@ import language.experimental.captureChecking * sense that their values retain no capabilities including capabilities needed * to perform effects. This has formal meaning only under capture checking. */ -trait Pure: +trait Pure extends Any: this: Pure => From cb2fd3b7980fefc6c83bcd0eef2dc13db08b662e Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Sat, 16 Aug 2025 02:17:55 +0200 Subject: [PATCH 097/101] Add a couple missing hats --- library/src/scala/collection/ArrayOps.scala | 2 +- library/src/scala/collection/Iterable.scala | 2 +- library/src/scala/collection/IterableOnce.scala | 2 +- library/src/scala/collection/LazyZipOps.scala | 4 ++-- library/src/scala/collection/SortedSet.scala | 2 +- library/src/scala/collection/StringOps.scala | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/library/src/scala/collection/ArrayOps.scala b/library/src/scala/collection/ArrayOps.scala index e90a4cbc52ad..40570e5c261b 100644 --- a/library/src/scala/collection/ArrayOps.scala +++ b/library/src/scala/collection/ArrayOps.scala @@ -1172,7 +1172,7 @@ final class ArrayOps[A](private val xs: Array[A]) extends AnyVal { dest } - @`inline` final def ++: [B >: A : ClassTag](prefix: IterableOnce[B]): Array[B] = prependedAll(prefix) + @`inline` final def ++: [B >: A : ClassTag](prefix: IterableOnce[B]^): Array[B] = prependedAll(prefix) @`inline` final def ++: [B >: A : ClassTag](prefix: Array[_ <: B]): Array[B] = prependedAll(prefix) diff --git a/library/src/scala/collection/Iterable.scala b/library/src/scala/collection/Iterable.scala index 9018667d1e18..7f3ed1a0a39c 100644 --- a/library/src/scala/collection/Iterable.scala +++ b/library/src/scala/collection/Iterable.scala @@ -669,7 +669,7 @@ transparent trait IterableOps[+A, +CC[_], +C] extends Any with IterableOnce[A] w * @param op the binary operator applied to the intermediate result and the element * @return collection with intermediate results */ - def scanRight[B](z: B)(op: (A, B) => B): CC[B] = { + def scanRight[B](z: B)(op: (A, B) => B): CC[B]^{this, op} = { class Scanner extends runtime.AbstractFunction1[A, Unit] { var acc = z var scanned = acc :: immutable.Nil diff --git a/library/src/scala/collection/IterableOnce.scala b/library/src/scala/collection/IterableOnce.scala index 5384be361a3e..e750d955e8fb 100644 --- a/library/src/scala/collection/IterableOnce.scala +++ b/library/src/scala/collection/IterableOnce.scala @@ -1288,7 +1288,7 @@ transparent trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOn * `p(x, y)` is `true` for all corresponding elements `x` of this iterator * and `y` of `that`, otherwise `false` */ - def corresponds[B](that: IterableOnce[B])(p: (A, B) => Boolean): Boolean = { + def corresponds[B](that: IterableOnce[B]^)(p: (A, B) => Boolean): Boolean = { val a = iterator val b = that.iterator diff --git a/library/src/scala/collection/LazyZipOps.scala b/library/src/scala/collection/LazyZipOps.scala index d68f5e966719..9a7dc3173926 100644 --- a/library/src/scala/collection/LazyZipOps.scala +++ b/library/src/scala/collection/LazyZipOps.scala @@ -177,7 +177,7 @@ final class LazyZip3[+El1, +El2, +El3, C1] private[collection](src: C1, }) } - def flatMap[B, C](f: (El1, El2, El3) => Iterable[B])(implicit bf: BuildFrom[C1, B, C]): C^{this, f} = { + def flatMap[B, C](f: (El1, El2, El3) => Iterable[B]^)(implicit bf: BuildFrom[C1, B, C]): C^{this, f} = { bf.fromSpecific(src)(new AbstractView[B] { def iterator: AbstractIterator[B]^{this, f} = new AbstractIterator[B] { private[this] val elems1 = coll1.iterator @@ -310,7 +310,7 @@ final class LazyZip4[+El1, +El2, +El3, +El4, C1] private[collection](src: C1, }) } - def flatMap[B, C](f: (El1, El2, El3, El4) => Iterable[B])(implicit bf: BuildFrom[C1, B, C]): C^{this, f} = { + def flatMap[B, C](f: (El1, El2, El3, El4) => Iterable[B]^)(implicit bf: BuildFrom[C1, B, C]): C^{this, f} = { bf.fromSpecific(src)(new AbstractView[B] { def iterator: AbstractIterator[B]^{this, f} = new AbstractIterator[B] { private[this] val elems1 = coll1.iterator diff --git a/library/src/scala/collection/SortedSet.scala b/library/src/scala/collection/SortedSet.scala index 212dd73192bb..ed3cfaee73da 100644 --- a/library/src/scala/collection/SortedSet.scala +++ b/library/src/scala/collection/SortedSet.scala @@ -179,7 +179,7 @@ object SortedSetOps { def map[B : Ordering](f: A => B): CC[B] = self.sortedIterableFactory.from(new View.Map(filtered, f)) - def flatMap[B : Ordering](f: A => IterableOnce[B]): CC[B] = + def flatMap[B : Ordering](f: A => IterableOnce[B]^): CC[B] = self.sortedIterableFactory.from(new View.FlatMap(filtered, f)) override def withFilter(q: A => Boolean): WithFilter[A, IterableCC, CC]^{this, q} = diff --git a/library/src/scala/collection/StringOps.scala b/library/src/scala/collection/StringOps.scala index 10292f330d8f..17800cb1d98b 100644 --- a/library/src/scala/collection/StringOps.scala +++ b/library/src/scala/collection/StringOps.scala @@ -124,7 +124,7 @@ object StringOps { * @return a new collection resulting from applying the given collection-valued function * `f` to each char of this string and concatenating the results. */ - def flatMap[B](f: Char => IterableOnce[B]): immutable.IndexedSeq[B] = { + def flatMap[B](f: Char => IterableOnce[B]^): immutable.IndexedSeq[B] = { val len = s.length val b = immutable.IndexedSeq.newBuilder[B] var i = 0 From ab8cde16ac9e342429fa2f7b9af6d7bdf31b6bae Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Sat, 16 Aug 2025 02:18:29 +0200 Subject: [PATCH 098/101] Remove generators --- project/GenerateAnyVals.scala | 526 ----------------------- project/GenerateFunctionConverters.scala | 450 ------------------- project/GenerateLibraryNTemplates.scala | 488 --------------------- project/ScalaLibraryPlugin.scala | 12 - 4 files changed, 1476 deletions(-) delete mode 100644 project/GenerateAnyVals.scala delete mode 100644 project/GenerateFunctionConverters.scala delete mode 100644 project/GenerateLibraryNTemplates.scala diff --git a/project/GenerateAnyVals.scala b/project/GenerateAnyVals.scala deleted file mode 100644 index 12b68fc3e29e..000000000000 --- a/project/GenerateAnyVals.scala +++ /dev/null @@ -1,526 +0,0 @@ -package dotty.tools - -/** Code generation of the AnyVal types and their companions. */ -trait GenerateAnyValReps { - self: GenerateAnyVals => - - sealed abstract class AnyValNum(name: String, repr: Option[String], javaEquiv: String) - extends AnyValRep(name,repr,javaEquiv) { - - case class Op(op : String, doc : String) - - private def companionCoercions(deprecated: Boolean, tos: AnyValRep*): List[String] = - tos.toList.flatMap { to => - val code = s"implicit def @javaequiv@2${to.javaEquiv}(x: @name@): ${to.name} = x.to${to.name}" - if (deprecated) - List(s"""@deprecated("Implicit conversion from @name@ to ${to.name} is dangerous because it loses precision. Write `.to${to.name}` instead.", "2.13.1")""", code) - else - List(code) - } - - def coercionComment = -"""/** Language mandated coercions from @name@ to "wider" types. */ -import scala.language.implicitConversions""" - - def implicitCoercions: List[String] = { - val coercions = this match { - case B => companionCoercions(deprecated = false, S, I, L, F, D) - case S | C => companionCoercions(deprecated = false, I, L, F, D) - case I => companionCoercions(deprecated = true, F) ++ companionCoercions(deprecated = false, L, D) - case L => companionCoercions(deprecated = true, F, D) - case F => companionCoercions(deprecated = false, D) - case _ => Nil - } - if (coercions.isEmpty) Nil - else coercionComment.linesIterator.toList ++ coercions - } - - def isInteger: Boolean = isIntegerType(this) - - def unaryOps = { - val ops = List( - Op("+", "/** Returns this value, unmodified. */"), - Op("-", "/** Returns the negation of this value. */")) - - if (isInteger) - Op("~", "/**\n" + - " * Returns the bitwise negation of this value.\n" + - " * @example {{{\n" + - " * ~5 == -6\n" + - " * // in binary: ~00000101 ==\n" + - " * // 11111010\n" + - " * }}}\n" + - " */") :: ops - else ops - } - - def bitwiseOps = - if (isInteger) - List( - Op("|", "/**\n" + - " * Returns the bitwise OR of this value and `x`.\n" + - " * @example {{{\n" + - " * (0xf0 | 0xaa) == 0xfa\n" + - " * // in binary: 11110000\n" + - " * // | 10101010\n" + - " * // --------\n" + - " * // 11111010\n" + - " * }}}\n" + - " */"), - Op("&", "/**\n" + - " * Returns the bitwise AND of this value and `x`.\n" + - " * @example {{{\n" + - " * (0xf0 & 0xaa) == 0xa0\n" + - " * // in binary: 11110000\n" + - " * // & 10101010\n" + - " * // --------\n" + - " * // 10100000\n" + - " * }}}\n" + - " */"), - Op("^", "/**\n" + - " * Returns the bitwise XOR of this value and `x`.\n" + - " * @example {{{\n" + - " * (0xf0 ^ 0xaa) == 0x5a\n" + - " * // in binary: 11110000\n" + - " * // ^ 10101010\n" + - " * // --------\n" + - " * // 01011010\n" + - " * }}}\n" + - " */")) - else Nil - - def shiftOps = - if (isInteger) - List( - Op("<<", "/**\n" + - " * Returns this value bit-shifted left by the specified number of bits,\n" + - " * filling in the new right bits with zeroes.\n" + - " * @example {{{ 6 << 3 == 48 // in binary: 0110 << 3 == 0110000 }}}\n" + - " */"), - - Op(">>>", "/**\n" + - " * Returns this value bit-shifted right by the specified number of bits,\n" + - " * filling the new left bits with zeroes.\n" + - " * @example {{{ 21 >>> 3 == 2 // in binary: 010101 >>> 3 == 010 }}}\n" + - " * @example {{{\n" + - " * -21 >>> 3 == 536870909\n" + - " * // in binary: 11111111 11111111 11111111 11101011 >>> 3 ==\n" + - " * // 00011111 11111111 11111111 11111101\n" + - " * }}}\n" + - " */"), - - Op(">>", "/**\n" + - " * Returns this value bit-shifted right by the specified number of bits,\n" + - " * filling in the left bits with the same value as the left-most bit of this.\n" + - " * The effect of this is to retain the sign of the value.\n" + - " * @example {{{\n" + - " * -21 >> 3 == -3\n" + - " * // in binary: 11111111 11111111 11111111 11101011 >> 3 ==\n" + - " * // 11111111 11111111 11111111 11111101\n" + - " * }}}\n" + - " */")) - else Nil - - def comparisonOps = List( - Op("==", "/** Returns `true` if this value is equal to x, `false` otherwise. */"), - Op("!=", "/** Returns `true` if this value is not equal to x, `false` otherwise. */"), - Op("<", "/** Returns `true` if this value is less than x, `false` otherwise. */"), - Op("<=", "/** Returns `true` if this value is less than or equal to x, `false` otherwise. */"), - Op(">", "/** Returns `true` if this value is greater than x, `false` otherwise. */"), - Op(">=", "/** Returns `true` if this value is greater than or equal to x, `false` otherwise. */")) - - def otherOps = List( - Op("+", "/** Returns the sum of this value and `x`. */"), - Op("-", "/** Returns the difference of this value and `x`. */"), - Op("*", "/** Returns the product of this value and `x`. */"), - Op("/", "/** Returns the quotient of this value and `x`. */"), - Op("%", "/** Returns the remainder of the division of this value by `x`. */")) - - // Given two numeric value types S and T, the operation type of S and T is defined as follows: - // If both S and T are subrange types then the operation type of S and T is Int. - // Otherwise the operation type of S and T is the larger of the two types w.r.t. the ordering - // of numeric types defined in `fullTypes` below. - // Given two numeric values v and w the operation type of v and w is the operation type - // of their run-time types. - def opType(that: AnyValNum): AnyValNum = { - val fullTypes = IndexedSeq(I, L, F, D) - (fullTypes.indexOf(this), fullTypes.indexOf(that)) match { - case (-1, -1) => I // both are subrange types - case (-1, _) => that // one is subrange - case (_, -1) => this - case (r1, r2) => fullTypes(r1.max(r2)) // use the larger type - } - } - - def mkCoercions = numeric.map(x => "def to%s: %s".format(x, x)) - - def mkUnaryOps = unaryOps.map(x => "%s\n def unary_%s : %s".format(x.doc, x.op, this opType I)) - - def mkStringOps = List( - "@deprecated(\"Adding a number and a String is deprecated. Use the string interpolation `s\\\"$num$str\\\"`\", \"2.13.0\")\n def +(x: String): String" - ) - - def mkShiftOps = ( - for (op <- shiftOps ; arg <- List(I, L)) yield { - val doc = op.doc + (if (this == L || arg == I) "" else "\n @deprecated(\"shifting a value by a `Long` argument is deprecated (except when the value is a `Long`).\\nCall `toInt` on the argument to maintain the current behavior and avoid the deprecation warning.\", \"2.12.7\")") - "%s\n def %s(x: %s): %s".format(doc, op.op, arg, this opType I) - } - ) - - def clumps: List[List[String]] = { - val xs1 = List(mkCoercions, mkUnaryOps, mkStringOps, mkShiftOps) - .filter(!_.isEmpty) - .map(_ :+ "") // add a trailing empty line - val xs2 = List( - mkBinOpsGroup(comparisonOps, numeric, _ => Z), - mkBinOpsGroup(bitwiseOps, integer, this opType _), - mkBinOpsGroup(otherOps, numeric, this opType _) - ) - xs1 ++ xs2 - } - - def classLines = { - val groups = (clumps :+ commonClassLines).filter(!_.isEmpty) - val interpolated = groups.map(_.map { - case "" => "" - case s => interpolate(s) - }) - interpolated.flatten - } - - def objectLines = { - val comp = if (isInteger) integerCompanion else floatingCompanion - interpolate(comp + allCompanions + "\n" + nonUnitCompanions).trim.linesIterator.toList ++ (implicitCoercions map interpolate) - } - - /** Makes a set of binary operations based on the given set of ops, args, and resultFn. - * - * @param ops list of function names e.g. List(">>", "%") - * @param args list of types which should appear as arguments - * @param resultFn function which calculates return type based on arg type - * @return list of function definitions - */ - def mkBinOpsGroup(ops: List[Op], args: List[AnyValNum], resultFn: AnyValNum => AnyValRep): List[String] = ( - ops flatMap (op => - args.map(arg => - "%s\n def %s(x: %s): %s".format(op.doc, op.op, arg, resultFn(arg))) :+ "" - ) - ).toList - } - - sealed abstract class AnyValRep(val name: String, val repr: Option[String], val javaEquiv: String) { - def classLines: List[String] - def objectLines: List[String] - def commonClassLines = List( - "// Provide a more specific return type for Scaladoc", - "override def getClass(): Class[@name@] = ???" - ) - - def lcname = name.toLowerCase - def boxedSimpleName = this match { - case C => "Character" - case I => "Integer" - case _ => name - } - def boxedName = this match { - case U => "scala.runtime.BoxedUnit" - case _ => "java.lang." + boxedSimpleName - } - def zeroRep = this match { - case L => "0L" - case F => "0.0f" - case D => "0.0d" - case _ => "0" - } - - def representation = repr.map(", a " + _).getOrElse("") - - def indent(s: String) = if (s == "") "" else " " + s - def indentN(s: String) = s.linesIterator map indent mkString "\n" - - def boxUnboxInterpolations = Map( - "@boxRunTimeDoc@" -> """ - * Runtime implementation determined by `scala.runtime.BoxesRunTime.boxTo%s`. See [[https://github.com/scala/scala src/library/scala/runtime/BoxesRunTime.java]]. - *""".format(boxedSimpleName), - "@unboxRunTimeDoc@" -> """ - * Runtime implementation determined by `scala.runtime.BoxesRunTime.unboxTo%s`. See [[https://github.com/scala/scala src/library/scala/runtime/BoxesRunTime.java]]. - *""".format(name), - "@unboxDoc@" -> "the %s resulting from calling %sValue() on `x`".format(name, lcname), - "@boxImpl@" -> "???", - "@unboxImpl@" -> "???" - ) - def interpolations = Map( - "@article@" -> (if (this == I) "an" else "a"), - "@name@" -> name, - "@representation@" -> representation, - "@javaequiv@" -> javaEquiv, - "@boxed@" -> boxedName, - "@lcname@" -> lcname, - "@zero@" -> zeroRep - ) ++ boxUnboxInterpolations - - def interpolate(s: String): String = interpolations.foldLeft(s) { - case (str, (key, value)) => str.replaceAll(key, value) - } - def classDoc = interpolate(classDocTemplate) - def objectDoc = "" - def mkImports = "" - - def mkClass = assemble("final abstract class " + name + " private extends AnyVal", classLines) - def mkObject = assemble("object " + name + " extends AnyValCompanion", objectLines) - def make() = List[String]( - headerTemplate, - mkImports, - classDoc, - mkClass, - objectDoc, - mkObject - ) mkString "" - - def assemble(decl: String, lines: List[String]): String = { - val body = if (lines.isEmpty) " { }\n\n" else lines.map(indent).mkString(" {\n", "\n", "\n}\n") - decl + body + "\n" - } - - override def toString = name - } -} - -trait GenerateAnyValTemplates { - def headerTemplate = """/* - * 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. - */ - -// DO NOT EDIT, CHANGES WILL BE LOST -// This auto-generated code can be modified in "project/GenerateAnyVals.scala". -// Afterwards, running "sbt generateSources" regenerates this source file. - -package scala - -import scala.language.`2.13` - -""" - - def classDocTemplate = (""" -/** `@name@`@representation@ (equivalent to Java's `@javaequiv@` primitive type) is a - * subtype of [[scala.AnyVal]]. Instances of `@name@` are not - * represented by an object in the underlying runtime system. - * - * There is an implicit conversion from [[scala.@name@]] => [[scala.runtime.Rich@name@]] - * which provides useful non-primitive operations. - */ -""".trim + "\n") - - def allCompanions = """ -/** Transforms a value type into a boxed reference type. - *@boxRunTimeDoc@ - * @param x the @name@ to be boxed - * @return a @boxed@ offering `x` as its underlying value. - */ -def box(x: @name@): @boxed@ = @boxImpl@ - -/** Transforms a boxed type into a value type. Note that this - * method is not typesafe: it accepts any Object, but will throw - * an exception if the argument is not a @boxed@. - *@unboxRunTimeDoc@ - * @param x the @boxed@ to be unboxed. - * @throws ClassCastException if the argument is not a @boxed@ - * @return @unboxDoc@ - */ -def unbox(x: java.lang.Object): @name@ = @unboxImpl@ - -/** The String representation of the scala.@name@ companion object. */ -override def toString = "object scala.@name@" -""" - - def nonUnitCompanions = "" // todo - - def integerCompanion = """ -/** The smallest value representable as @article@ @name@. */ -final val MinValue = @boxed@.MIN_VALUE - -/** The largest value representable as @article@ @name@. */ -final val MaxValue = @boxed@.MAX_VALUE -""" - - def floatingCompanion = """ -/** The smallest positive value greater than @zero@ which is - * representable as a @name@. - */ -final val MinPositiveValue = @boxed@.MIN_VALUE -final val NaN = @boxed@.NaN -final val PositiveInfinity = @boxed@.POSITIVE_INFINITY -final val NegativeInfinity = @boxed@.NEGATIVE_INFINITY - -/** The negative number with the greatest (finite) absolute value which is representable - * by a @name@. Note that it differs from [[java.lang.@name@.MIN_VALUE]], which - * is the smallest positive value representable by a @name@. In Scala that number - * is called @name@.MinPositiveValue. - */ -final val MinValue = -@boxed@.MAX_VALUE - -/** The largest finite positive number representable as a @name@. */ -final val MaxValue = @boxed@.MAX_VALUE -""" -} - -class GenerateAnyVals extends GenerateAnyValReps with GenerateAnyValTemplates { - object B extends AnyValNum("Byte", Some("8-bit signed integer"), "byte") - object S extends AnyValNum("Short", Some("16-bit signed integer"), "short") - object C extends AnyValNum("Char", Some("16-bit unsigned integer"), "char") - object I extends AnyValNum("Int", Some("32-bit signed integer"), "int") - object L extends AnyValNum("Long", Some("64-bit signed integer"), "long") - object F extends AnyValNum("Float", Some("32-bit IEEE-754 floating point number"), "float") - object D extends AnyValNum("Double", Some("64-bit IEEE-754 floating point number"), "double") - object Z extends AnyValRep("Boolean", None, "boolean") { - def classLines = """ -/** Negates a Boolean expression. - * - * - `!a` results in `false` if and only if `a` evaluates to `true` and - * - `!a` results in `true` if and only if `a` evaluates to `false`. - * - * @return the negated expression - */ -def unary_! : Boolean - -/** Compares two Boolean expressions and returns `true` if they evaluate to the same value. - * - * `a == b` returns `true` if and only if - * - `a` and `b` are `true` or - * - `a` and `b` are `false`. - */ -def ==(x: Boolean): Boolean - -/** - * Compares two Boolean expressions and returns `true` if they evaluate to a different value. - * - * `a != b` returns `true` if and only if - * - `a` is `true` and `b` is `false` or - * - `a` is `false` and `b` is `true`. - */ -def !=(x: Boolean): Boolean - -/** Compares two Boolean expressions and returns `true` if one or both of them evaluate to true. - * - * `a || b` returns `true` if and only if - * - `a` is `true` or - * - `b` is `true` or - * - `a` and `b` are `true`. - * - * @note This method uses 'short-circuit' evaluation and - * behaves as if it was declared as `def ||(x: => Boolean): Boolean`. - * If `a` evaluates to `true`, `true` is returned without evaluating `b`. - */ -def ||(x: Boolean): Boolean - -/** Compares two Boolean expressions and returns `true` if both of them evaluate to true. - * - * `a && b` returns `true` if and only if - * - `a` and `b` are `true`. - * - * @note This method uses 'short-circuit' evaluation and - * behaves as if it was declared as `def &&(x: => Boolean): Boolean`. - * If `a` evaluates to `false`, `false` is returned without evaluating `b`. - */ -def &&(x: Boolean): Boolean - -// Compiler won't build with these seemingly more accurate signatures -// def ||(x: => Boolean): Boolean -// def &&(x: => Boolean): Boolean - -/** Compares two Boolean expressions and returns `true` if one or both of them evaluate to true. - * - * `a | b` returns `true` if and only if - * - `a` is `true` or - * - `b` is `true` or - * - `a` and `b` are `true`. - * - * @note This method evaluates both `a` and `b`, even if the result is already determined after evaluating `a`. - */ -def |(x: Boolean): Boolean - -/** Compares two Boolean expressions and returns `true` if both of them evaluate to true. - * - * `a & b` returns `true` if and only if - * - `a` and `b` are `true`. - * - * @note This method evaluates both `a` and `b`, even if the result is already determined after evaluating `a`. - */ -def &(x: Boolean): Boolean - -/** Compares two Boolean expressions and returns `true` if they evaluate to a different value. - * - * `a ^ b` returns `true` if and only if - * - `a` is `true` and `b` is `false` or - * - `a` is `false` and `b` is `true`. - */ -def ^(x: Boolean): Boolean - -// Provide a more specific return type for Scaladoc -override def getClass(): Class[Boolean] = ??? - """.trim.linesIterator.toList - - def objectLines = interpolate(allCompanions + "\n" + nonUnitCompanions).linesIterator.toList - } - object U extends AnyValRep("Unit", None, "void") { - override def classDoc = """ -/** `Unit` is a subtype of [[scala.AnyVal]]. There is only one value of type - * `Unit`, `()`, and it is not represented by any object in the underlying - * runtime system. A method with return type `Unit` is analogous to a Java - * method which is declared `void`. - */ -""".trim + "\n" - def classLines = List( - "// Provide a more specific return type for Scaladoc", - "override def getClass(): Class[Unit] = ???" - ) - def objectLines = interpolate(allCompanions).linesIterator.toList - - private def nono = "`Unit` companion object is not allowed in source; instead, use `()` for the unit value" - override def mkObject = s"""@scala.annotation.compileTimeOnly("$nono")\n${super.mkObject}""" - - override def boxUnboxInterpolations = Map( - "@boxRunTimeDoc@" -> """ - * This method is not intended for use in source code. - * The runtime representation of this value is platform specific. - *""", - "@unboxRunTimeDoc@" -> """ - * This method is not intended for use in source code. - * The result of successfully unboxing a value is `()`. - *""", - "@unboxDoc@" -> "the Unit value ()", - "@boxImpl@" -> "scala.runtime.BoxedUnit.UNIT", - "@unboxImpl@" -> "x.asInstanceOf[scala.runtime.BoxedUnit]" - ) - } - - def isSubrangeType = Set(B, S, C) - def isIntegerType = Set(B, S, C, I, L) - def isFloatingType = Set(F, D) - def isWideType = Set(L, D) - - def integer = numeric filter isIntegerType - def numeric = List(B, S, C, I, L, F, D) - def values = List(U, Z) ++ numeric - - def make() = values map (x => (x.name, x.make())) -} - -object GenerateAnyVals { - def run(outDir: java.io.File): Unit = { - val av = new GenerateAnyVals - - av.make() foreach { case (name, code ) => - val file = new java.io.File(outDir, name + ".scala") - sbt.IO.write(file, code, java.nio.charset.StandardCharsets.UTF_8, false) - } - } -} diff --git a/project/GenerateFunctionConverters.scala b/project/GenerateFunctionConverters.scala deleted file mode 100644 index 5bdc04175969..000000000000 --- a/project/GenerateFunctionConverters.scala +++ /dev/null @@ -1,450 +0,0 @@ -package dotty.tools - -/* - * 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. - */ - -object GenerateFunctionConverters { - import scala.tools.nsc._ - - case class Artifact(name: String, content: String) - - val copyright = - s"""/* - | * 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. - | */ - | - |// GENERATED CODE: DO NOT EDIT. - |""".stripMargin - - val packaging = "package scala.jdk" - - val settings = new Settings(msg => sys.error(msg)) - def us(cl: ClassLoader): List[String] = cl match { - case ucl: java.net.URLClassLoader => ucl.getURLs.map(u => new java.io.File(u.toURI).getAbsolutePath).toList ::: us(ucl.getParent) - case _ => Nil - } - settings.classpath.value = us(settings.getClass.getClassLoader).mkString(java.io.File.pathSeparator) - val compiler = new Global(settings) - val run = new compiler.Run - - import compiler._, definitions._ - - locally { - // make sure `java.lang.Double` prints as `java.lang.Double`, not just `Double` (which resolves to `scala.Double`) - val f = classOf[scala.reflect.internal.Definitions#DefinitionsClass].getDeclaredField("UnqualifiedOwners") - f.setAccessible(true) - f.set(definitions, definitions.UnqualifiedOwners.filter(_.fullNameString != "java.lang")) - } - - def primitiveBox(tp: Type): Type = tp.typeSymbol match { - case UnitClass => BoxedUnitClass.tpe - case ByteClass => BoxedByteClass.tpe - case ShortClass => BoxedShortClass.tpe - case CharClass => BoxedCharacterClass.tpe - case IntClass => BoxedIntClass.tpe - case LongClass => BoxedLongClass.tpe - case FloatClass => BoxedFloatClass.tpe - case DoubleClass => BoxedDoubleClass.tpe - case BooleanClass => BoxedBooleanClass.tpe - case _ => tp - } - - implicit class IndentMe(v: Vector[String]) { - /** Adds indentation to every line in the vector. */ - def indent: Vector[String] = v.map(" " + _) - } - - implicit class WriteToBuilder(v: Vector[Vector[String]]) { - /** Writes the vector into a builder, with `join` inserted between each group. */ - def writeTo(builder: collection.mutable.Builder[String, Vector[String]], join: String = "") = { - var first = true - for (vi <- v) { - if (!first) builder += join - first = false - builder ++= vi - } - } - } - - implicit class FlattenMe(v: Vector[Vector[String]]) { - /** Joins the given `Vector[Vector[String]]`, with `join` inserted in between. */ - def mkVec(join: String = ""): Vector[String] = { - val vb = Vector.newBuilder[String] - v.writeTo(vb) - vb.result() - } - } - - implicit class DoubleFlattenMe(v: Vector[Vector[Vector[String]]]) { - /** Like `mkVec`, but done twice, with `join` inserted twice between outer blocks. */ - def mkVecVec(join: String = ""): Vector[String] = { - val vb = Vector.newBuilder[String] - var first = true - for (vi <- v) { - if (!first) { vb += join; vb += join } - first = false - vi.writeTo(vb, join) - } - vb.result() - } - } - - implicit class StringExtensions(s: String) { - // work around scala/bug#11125 - /** Splits the string into a `Vector` of lines. */ - def toVec = Predef.augmentString(s).lines.toVector - /** Is the string a blank line? */ - def nonBlank = s.trim.length > 0 - } - - implicit class TreeToText(t: Tree) { - // work around scala/bug#11125 - def text = Predef.augmentString(showCode(t).replace("$", "")).lines.toVector - } - - /** Blocks of code with priority (lower priority has higher precedence in implicit search). */ - case class Prioritized(lines: Vector[String], priority: Int) { - def withPriority(i: Int) = copy(priority = i) - } - - /** Contains code for conversion between functions and SAMs (single abstract methods). */ - case class SamConversionCode( - base: String, - wrappedAsScala: Vector[String], - asScalaAnyVal: Vector[String], - implicitToScala: Vector[String], - asScalaDef: Vector[String], - wrappedAsJava: Vector[String], - asJavaAnyVal: Vector[String], - implicitToJava: Prioritized, - asJavaDef: Vector[String] - ) { - def impls: Vector[Vector[String]] = Vector(wrappedAsScala, asScalaAnyVal, wrappedAsJava, asJavaAnyVal) - def defs: Vector[Vector[String]] = Vector(asScalaDef, asJavaDef) - def withPriority(i: Int): SamConversionCode = copy(implicitToJava = implicitToJava.withPriority(i)) - } - object SamConversionCode { - def apply(scc: SamConversionCode*): (Vector[String], Vector[String], Vector[Vector[String]]) = { - val sccDepthSet = scc.map(_.implicitToJava.priority).to[collection.SortedSet] - val codes = - { - val size = sccDepthSet.size - // normalize the elements to a fix [0..size-1] range - if (sccDepthSet.firstKey != 0 || sccDepthSet.lastKey != size - 1) { - val sccDepthMap = sccDepthSet.iterator.zipWithIndex.toMap - scc.map(x => x.withPriority(sccDepthMap(x.implicitToJava.priority))) - } - else scc - }.toVector.sortBy(_.base) - def priorityName(n: Int): String = s"Priority${n}FunctionExtensions" - def priorityHeader(n: Int): String = { - val pre = s"trait ${priorityName(n)}" - if (n < sccDepthSet.size - 1) s"$pre extends ${priorityName(n+1)}" else pre - } - val impls = - "object FunctionWrappers {" +: { - codes.map(_.impls).mkVecVec().indent - } :+ "}" - val explicitDefs = codes.map(_.defs).mkVecVec() - val traits = codes.groupBy(_.implicitToJava.priority).toVector.sortBy(- _._1).map{ case (k,vs) => - s"import language.implicitConversions" +: - "" +: - s"${priorityHeader(k)} {" +: - s" import FunctionWrappers._" +: - s" " +: - { - vs.map(_.implicitToJava.lines).mkVec().indent ++ - ( - if (k == 0) Vector.fill(3)(" ") ++ codes.map(_.implicitToScala).mkVec().indent - else Vector() - ) - } :+ - s"}" - } - (impls, explicitDefs, traits) - } - } - - private def buildWrappersViaReflection: Seq[SamConversionCode] = { - - val pack: Symbol = rootMirror.getPackageIfDefined("java.util.function") - - case class Jfn(iface: Symbol, sam: Symbol) { - lazy val genericCount = iface.typeParams.length - lazy val name = sam.name.toTermName - lazy val title = iface.name.encoded - lazy val params = sam.info.params - lazy val sig = sam typeSignatureIn iface.info - lazy val pTypes = sig.params.map(_.info) - lazy val rType = sig.resultType - def arity = params.length - } - - val sams = pack.info.decls. - map(d => (d, d.typeSignature.members.filter(_.isAbstract).toList)). - collect{ case (d, m :: Nil) if d.isAbstract => Jfn(d, m) } - - def generate(jfn: Jfn): SamConversionCode = { - def mkRef(tp: Type): Tree = if (tp.typeSymbol.isTypeParameter) Ident(tp.typeSymbol.name.toTypeName) else tq"$tp" - - // Types for the Java SAM and the corresponding Scala function, plus all type parameters - val scalaType = gen.mkAttributedRef(FunctionClass(jfn.arity)) - val javaType = gen.mkAttributedRef(jfn.iface) - val tnParams: List[TypeName] = jfn.iface.typeParams.map(_.name.toTypeName) - val tdParams: List[TypeDef] = tnParams.map(TypeDef(NoMods, _, Nil, EmptyTree)) - val javaTargs: List[Tree] = tdParams.map(_.name).map(Ident(_)) - val scalaTargTps = jfn.pTypes :+ jfn.rType - val scalaTargBoxedTps = scalaTargTps.map(primitiveBox) - val scalaTargs: List[Tree] = scalaTargTps.map(mkRef) - val scalaTargsBoxed: List[Tree] = scalaTargBoxedTps.map(mkRef) - val boxComment = - if (scalaTargTps.map(_.typeSymbol) != scalaTargBoxedTps.map(_.typeSymbol)) - Literal(Constant("primitiveComment")) - else - Literal(Constant("noComment")) - - // Conversion wrappers have three or four components that we need to name - // (1) The wrapper class that wraps a Java SAM as Scala function, or vice versa (ClassN) - // (2) A value class that provides .asJava or .asScala to request the conversion (ValCN) - // (3) A name for an explicit conversion method (DefN) - // (4) An implicit conversion method name (ImpN) that invokes the value class - - // Names for Java conversions to Scala - val j2sClassN = TypeName("FromJava" + jfn.title) - val j2sCompanionN = j2sClassN.toTermName - val j2sValCN = TypeName("Rich" + jfn.title + "As" + scalaType.name.encoded) - val j2sDefN = TermName("asScalaFrom" + jfn.title) - val j2sImpN = TermName("enrichAsScalaFrom" + jfn.title) - - // Names for Scala conversions to Java - val s2jAsJavaTitle = TermName("asJava" + jfn.title) - val s2jClassN = TypeName("AsJava" + jfn.title) - val s2jCompanionN = s2jClassN.toTermName - val s2jValCN = TypeName("Rich" + scalaType.name.encoded + "As" + jfn.title) - val s2jDefN = TermName("asJava" + jfn.title) - val s2jImpN = TermName("enrichAsJava" + jfn.title) - - // Argument lists for the function / SAM - val vParams = (jfn.params zip jfn.pTypes).map{ case (p,t) => - ValDef(NoMods, p.name.toTermName, if (t.typeSymbol.isTypeParameter) Ident(t.typeSymbol.name) else gen.mkAttributedRef(t.typeSymbol), EmptyTree) - } - val vParamRefs = vParams.map(_.name).map(Ident(_)) - - val j2sClassTree = - q"""case class $j2sClassN[..$tdParams](jf: $javaType[..$javaTargs]) extends $scalaType[..$scalaTargs] { - def apply(..$vParams) = jf.${jfn.name}(..$vParamRefs) - }""" - - val j2sValCTree = - q"""class $j2sValCN[..$tdParams](private val underlying: $javaType[..$javaTargs]) extends AnyVal { - @inline def asScala: $scalaType[..$scalaTargs] = underlying match { - case $s2jCompanionN(sf) => sf.asInstanceOf[$scalaType[..$scalaTargs]] - case _ => new $j2sClassN[..$tnParams](underlying) - } - }""" - - val j2sDefTree = - q"""@deprecated($boxComment) @inline def $j2sDefN[..$tdParams](jf: $javaType[..$javaTargs]): $scalaType[..$scalaTargsBoxed] = jf match { - case $s2jCompanionN(f) => f.asInstanceOf[$scalaType[..$scalaTargsBoxed]] - case _ => new $j2sClassN[..$tnParams](jf).asInstanceOf[$scalaType[..$scalaTargsBoxed]] - }""" - - val j2sImpTree = - q"""@inline implicit def $j2sImpN[..$tdParams](jf: $javaType[..$javaTargs]): $j2sValCN[..$tnParams] = new $j2sValCN[..$tnParams](jf)""" - - val s2jClassTree = - q"""case class $s2jClassN[..$tdParams](sf: $scalaType[..$scalaTargs]) extends $javaType[..$javaTargs] { - def ${jfn.name}(..$vParams) = sf.apply(..$vParamRefs) - }""" - - val s2jDefTree = - q"""@deprecated($boxComment) @inline def $s2jDefN[..$tdParams](sf: $scalaType[..$scalaTargsBoxed]): $javaType[..$javaTargs] = (sf: AnyRef) match { - case $j2sCompanionN(f) => f.asInstanceOf[$javaType[..$javaTargs]] - case _ => new $s2jClassN[..$tnParams](sf.asInstanceOf[$scalaType[..$scalaTargs]]) - }""" - - // This is especially tricky because functions are contravariant in their arguments - // Need to prevent e.g. Any => String from "downcasting" itself to Int => String; we want the more exact conversion - val (s2jImpTree, priority) = - if (jfn.pTypes.forall(! _.isFinalType) && jfn.sig == jfn.sam.typeSignature) - ( - q"""@inline implicit def $s2jImpN[..$tdParams](sf: $scalaType[..$scalaTargs]): $s2jValCN[..$tnParams] = new $s2jValCN[..$tnParams](sf)""", - tdParams.length - ) - else { - // Some types are not generic or are re-used; we had better catch those. - // Making up new type names, so switch everything to TypeName or TypeDef - // Instead of foo[A](f: (Int, A) => Long): Fuu[A] = new Foo[A](f) - // we want foo[X, A](f: (X, A) => Long)(implicit evX: Int =:= X): Fuu[A] = new Foo[A](f.asInstanceOf[(Int, A) => Long]) - // Instead of bar[A](f: A => A): Brr[A] = new Foo[A](f) - // we want bar[A, B](f: A => B)(implicit evB: A =:= B): Brr[A] = new Foo[A](f.asInstanceOf[A => B]) - val An = "A(\\d+)".r - val numberedA = collection.mutable.Set.empty[Int] - val evidences = collection.mutable.ArrayBuffer.empty[(TypeName, TypeName)] - numberedA ++= scalaTargs.map(_.toString).collect{ case An(digits) if (digits.length < 10) => digits.toInt } - val scalafnTnames = (jfn.pTypes :+ jfn.rType).zipWithIndex.map{ - case (pt, i) if (i < jfn.pTypes.length && pt.isFinalType) || (!pt.isFinalType && jfn.pTypes.take(i).exists(_ == pt)) => - val j = Iterator.from(i).dropWhile(numberedA).next - val genericName = TypeName(s"A$j") - numberedA += j - evidences += ((genericName, pt.typeSymbol.name.toTypeName)) - genericName - case (pt, _) => pt.typeSymbol.name.toTypeName - } - val scalafnTdefs = scalafnTnames. - map(TypeDef(NoMods, _, Nil, EmptyTree)). - dropRight(if (jfn.rType.isFinalType) 1 else 0) - val evs = evidences.map{ case (generic, specific) => ValDef(NoMods, TermName("ev"+generic.toString), tq"$generic =:= $specific", EmptyTree) } - val tree = - q"""@inline implicit def $s2jImpN[..$scalafnTdefs](sf: $scalaType[..$scalafnTnames])(implicit ..$evs): $s2jValCN[..$tnParams] = - new $s2jValCN[..$tnParams](sf.asInstanceOf[$scalaType[..$scalaTargs]]) - """ - (tree, tdParams.length) - } - - val s2jValFullNameAsJavaMethodTree = - if (priority > 0) - q"""@inline def $s2jAsJavaTitle: $javaType[..$javaTargs] = underlying match { - case $j2sCompanionN(sf) => sf.asInstanceOf[$javaType[..$javaTargs]] - case _ => new $s2jClassN[..$tnParams](underlying) - }""" - else EmptyTree - - val s2jValCTree = - q"""class $s2jValCN[..$tdParams](private val underlying: $scalaType[..$scalaTargs]) extends AnyVal { - @inline def asJava: $javaType[..$javaTargs] = underlying match { - case $j2sCompanionN(jf) => jf.asInstanceOf[$javaType[..$javaTargs]] - case _ => new $s2jClassN[..$tnParams](underlying) - } - $s2jValFullNameAsJavaMethodTree - }""" - - SamConversionCode( - base = jfn.title, - wrappedAsScala = j2sClassTree.text, - asScalaAnyVal = j2sValCTree.text, - implicitToScala = j2sImpTree.text, - asScalaDef = j2sDefTree.text, - wrappedAsJava = s2jClassTree.text, - asJavaAnyVal = s2jValCTree.text, - implicitToJava = Prioritized(s2jImpTree.text, priority), - asJavaDef = s2jDefTree.text - ) - } - - sams.toSeq.map(generate) - } - - def sourceFile(subPack: String, body: String): String = { - val body1 = if (body.startsWith("import "))body else "\n" + body - s"""$copyright - | - |$packaging$subPack - | - |import scala.language.`2.13` - |$body1 - |""".stripMargin - } - - def sameText(f: java.io.File, text: String): Boolean = { - val x = scala.io.Source.fromFile(f) - val lines = try { x.getLines().toVector } finally { x.close } - // work around scala/bug#11125 - lines.iterator.filter(_.nonBlank) == Predef.augmentString(text).lines.filter(_.nonBlank) - } - - def write(outDir: java.io.File, artifact: Artifact): Unit = { - val f = scala.tools.nsc.io.Path(outDir.getAbsolutePath) / artifact.name - if (!f.exists || !sameText(f.jfile, artifact.content)) { - f.parent.createDirectory(force = true) - f.toFile writeAll artifact.content - } - } - - def run(outDir: java.io.File): Unit = { - val (impls, explicitDefs, defss) = SamConversionCode(buildWrappersViaReflection: _*) - val javaFunConvsNoComments = - s"""/** This object contains methods that convert between Scala and Java function types. - | * - | * The explicit conversion methods defined here are intended to be used in Java code. For Scala - | * code, it is recommended to use the extension methods defined in [[scala.jdk.FunctionConverters]]. - | * - | * For details how the function converters work, see [[scala.jdk.FunctionConverters]]. - | * - | */ - |object FunctionConverters { - | import scala.jdk.FunctionWrappers._ - | - |${explicitDefs.indent.mkString("\n")} - |}""".stripMargin - - // cannot generate comments with quasiquotes - val javaFunConvs = javaFunConvsNoComments.replace(""" @deprecated("primitiveComment") """, - s""" /** Note: this method uses the boxed type `java.lang.X` (or `BoxedUnit`) instead of the - | * primitive type `scala.X` to improve compatibility when using it in Java code (the - | * Scala compiler emits `C[Int]` as `C[Object]` in bytecode due to - | * [[https://github.com/scala/bug/issues/4214 scala/bug#4214]]). In Scala code, add - | * `import scala.jdk.FunctionConverters._` and use the extension methods instead. - | */ - |""".stripMargin + " ").replace("""@deprecated("noComment") """, "") - - val scalaFunConvs = - """/** This object provides extension methods that convert between Scala and Java function types. - | * - | * When writing Java code, use the explicit conversion methods defined in - | * [[javaapi.FunctionConverters]] instead. - | * - | * Using the `.asJava` extension method on a Scala function produces the most specific possible - | * Java function type: - | * - | * {{{ - | * scala> import scala.jdk.FunctionConverters._ - | * scala> val f = (x: Int) => x + 1 - | * - | * scala> val jf1 = f.asJava - | * jf1: java.util.function.IntUnaryOperator = ... - | * }}} - | * - | * More generic Java function types can be created using the corresponding `asJavaXYZ` extension - | * method: - | * - | * {{{ - | * scala> val jf2 = f.asJavaFunction - | * jf2: java.util.function.Function[Int,Int] = ... - | * - | * scala> val jf3 = f.asJavaUnaryOperator - | * jf3: java.util.function.UnaryOperator[Int] = ... - | * }}} - | * - | * Converting a Java function to Scala is done using the `asScala` extension method: - | * - | * {{{ - | * scala> List(1,2,3).map(jf2.asScala) - | * res1: List[Int] = List(2, 3, 4) - | * }}} - | */ - |object FunctionConverters extends Priority0FunctionExtensions""".stripMargin - - write(outDir, Artifact("jdk/javaapi/FunctionConverters.scala", sourceFile(".javaapi", javaFunConvs))) - write(outDir, Artifact("jdk/FunctionConverters.scala", sourceFile("", scalaFunConvs))) - write(outDir, Artifact("jdk/FunctionWrappers.scala", sourceFile("", impls.mkString("\n")))) - write(outDir, Artifact("jdk/FunctionExtensions.scala", sourceFile("", defss.map(_.mkString("\n")).mkString("\n\n\n\n")))) - } -} diff --git a/project/GenerateLibraryNTemplates.scala b/project/GenerateLibraryNTemplates.scala deleted file mode 100644 index af7358d1e554..000000000000 --- a/project/GenerateLibraryNTemplates.scala +++ /dev/null @@ -1,488 +0,0 @@ -package dotty.tools - -/* - * 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. - */ - -/** - * This program generates the ProductN, TupleN, FunctionN, and AbstractFunctionN, - * where 0 <= N <= MaxArity. Usage: sbt generateSources - */ -object GenerateLibraryNTemplates { - final val MaxArity = 22 - def arities = (1 to MaxArity).toList - - class Group(val name: String) { - def className(i: Int) = name + i - def fileName(i: Int) = className(i) + ".scala" - } - - def productFiles = arities map Product.make - def tupleFiles = arities map Tuple.make - def functionFiles = (0 :: arities) map Function.make - def absFunctionFiles = (0 :: arities) map AbstractFunction.make - def allfiles = productFiles ::: tupleFiles ::: functionFiles ::: absFunctionFiles - - trait Arity extends Group { - def i: Int // arity - - def typeArgsString(xs: Seq[String]) = xs.mkString("[", ", ", "]") - def typeArgsToTupleSyntacticSugarString(xs: Seq[String]) = xs.mkString("(", ", ", ")") - - def to = (1 to i).toList - def s = if (i == 1) "" else "s" - def className = name + i - def classAnnotation = "" - def fileName = className + ".scala" - def targs = to map ("T" + _) - def vdefs = to map ("v" + _) - def xdefs = to map ("x" + _) - def mdefs = to map ("_" + _) - def invariantArgs = typeArgsString(targs) - def covariantArgs = typeArgsString(targs map (covariantSpecs + "+" + _)) - def covariantSpecs = "" - def contravariantSpecs = "" - def contraCoArgs = typeArgsString((targs map (contravariantSpecs + "-" + _)) ::: List(covariantSpecs + "+R")) - def constructorArgs = (targs).map( _.toLowerCase ) mkString ", " - def fields = (mdefs, targs).zipped.map(_ + ": " + _) mkString ", " - def funArgs = (vdefs, targs).zipped.map(_ + ": " + _) mkString ", " - - def genprodString = " See scala.Function0 for timestamp." - def moreMethods = "" - def companionObject = "" - def packageDef = "scala" - def imports = "" - - def header = """/* - * 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. - */ - -// GENERATED CODE: DO NOT EDIT.%s - -package %s - -import scala.language.`2.13` -%s -""".format(genprodString, packageDef, imports).trim - } - - def run(outDir: java.io.File): Unit = { - val out = outDir.getAbsolutePath - def writeFile(node: scala.xml.Node): Unit = { - import scala.tools.nsc.io._ - val f = Path(out) / node.attributes("name").toString - f.parent.createDirectory(force = true) - f.toFile writeAll node.text - } - allfiles foreach writeFile - } -} - -import GenerateLibraryNTemplates._ - - -/* zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz - F U N C T I O N -zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz */ - -object FunctionZero extends Function(0) { - override def genprodString = "\n// genprod generated these sources at: " + java.time.Instant.now() - override def covariantSpecs = "@specialized(Specializable.Primitives) " - override def descriptiveComment = " " + functionNTemplate.format("greeting", "anonfun0", -raw""" - * val name = "world" - * val greeting = () => s"hello, $$name" - * - * val anonfun0 = new Function0[String] { - * def apply(): String = s"hello, $$name" - * } - * assert(greeting() == anonfun0()) - * """) - override def moreMethods = "" -} - -object FunctionOne extends Function(1) { - override def classAnnotation = "@annotation.implicitNotFound(msg = \"No implicit view available from ${T1} => ${R}.\")\n" - override def contravariantSpecs = "@specialized(Specializable.Arg) " - override def covariantSpecs = "@specialized(Specializable.Return) " - - override def descriptiveComment = " " + functionNTemplate.format("succ", "anonfun1", -""" - * val succ = (x: Int) => x + 1 - * val anonfun1 = new Function1[Int, Int] { - * def apply(x: Int): Int = x + 1 - * } - * assert(succ(0) == anonfun1(0)) - * """) + """ - * - * Note that the difference between `Function1` and [[scala.PartialFunction]] - * is that the latter can specify inputs which it will not handle.""" - - override def moreMethods = """ - /** Composes two instances of `Function1` in a new `Function1`, with this function applied last. - * - * @tparam A the type to which function `g` can be applied - * @param g a function A => T1 - * @return a new function `f` such that `f(x) == apply(g(x))` - */ - @annotation.unspecialized def compose[A](g: A => T1): A => R = { x => apply(g(x)) } - - /** Composes two instances of `Function1` in a new `Function1`, with this function applied first. - * - * @tparam A the result type of function `g` - * @param g a function R => A - * @return a new function `f` such that `f(x) == g(apply(x))` - */ - @annotation.unspecialized def andThen[A](g: R => A): T1 => A = { x => g(apply(x)) } -""" - override def companionObject = -""" -object Function1 { - - implicit final class UnliftOps[A, B] private[Function1](private val f: A => Option[B]) extends AnyVal { - /** Converts an optional function to a partial function. - * - * @example Unlike [[Function.unlift]], this [[UnliftOps.unlift]] method can be used in extractors. - * {{{ - * val of: Int => Option[String] = { i => - * if (i == 2) { - * Some("matched by an optional function") - * } else { - * None - * } - * } - * - * util.Random.nextInt(4) match { - * case of.unlift(m) => // Convert an optional function to a pattern - * println(m) - * case _ => - * println("Not matched") - * } - * }}} - */ - def unlift: PartialFunction[A, B] = Function.unlift(f) - } - -} -""" -} - -object FunctionTwo extends Function(2) { - override def contravariantSpecs = "@specialized(Specializable.Args) " - override def covariantSpecs = "@specialized(Specializable.Return) " - - override def descriptiveComment = " " + functionNTemplate.format("max", "anonfun2", -""" - * val max = (x: Int, y: Int) => if (x < y) y else x - * - * val anonfun2 = new Function2[Int, Int, Int] { - * def apply(x: Int, y: Int): Int = if (x < y) y else x - * } - * assert(max(0, 1) == anonfun2(0, 1)) - * """) -} - -object Function { - def make(i: Int) = apply(i)() - def apply(i: Int) = i match { - case 0 => FunctionZero - case 1 => FunctionOne - case 2 => FunctionTwo - case _ => new Function(i) - } -} - -class Function(val i: Int) extends Group("Function") with Arity { - // override def imports: String = "import language.experimental.captureChecking\n" + super.imports - // def selfType = className + (targs ++ List("R")).mkString("[", ",", "]") + "^" - def descriptiveComment = "" - def functionNTemplate = -""" - * In the following example, the definition of `%s` is - * shorthand, conceptually, for the anonymous class definition - * `%s`, although the implementation details of how the - * function value is constructed may differ: - * - * {{{ - * object Main extends App {%s} - * }}}""" - - def toStr = "\"" + ("" format i) + "\"" - def apply() = { -{header} -{companionObject} -/** A function of {i} parameter{s}. - *{descriptiveComment} - */ -{classAnnotation}trait {className}{contraCoArgs} extends AnyRef {{ - /** Apply the body of this function to the argument{s}. - * @return the result of function application. - */ - def apply({funArgs}): R -{moreMethods} - override def toString(): String = {toStr} -}} - -} - - private def commaXs = xdefs.mkString("(", ", ", ")") - - // (x1: T1) => (x2: T2) => (x3: T3) => (x4: T4) => apply(x1,x2,x3,x4) - def shortCurry = { - val body = "apply" + commaXs - (xdefs, targs).zipped.map("(%s: %s) => ".format(_, _)).mkString("", "", body) - } - - // (x1: T1) => ((x2: T2, x3: T3, x4: T4, x5: T5, x6: T6, x7: T7) => this.apply(x1,x2,x3,x4,x5,x6,x7)).curried - def longCurry = ((xdefs, targs).zipped.map(_ + ": " + _) drop 1).mkString( - "(x1: T1) => ((", - ", ", - ") => this.apply%s).curried".format(commaXs) - ) - - // f(x1,x2,x3,x4,x5,x6) == (f.curried)(x1)(x2)(x3)(x4)(x5)(x6) - def curryComment = { -""" /** Creates a curried version of this function. - * - * @return a function `f` such that `f%s == apply%s` - */""".format(xdefs.map("(" + _ + ")").mkString, commaXs) - } - - def tupleMethod = { - def comment = -""" /** Creates a tupled version of this function: instead of %d arguments, - * it accepts a single [[scala.Tuple%d]] argument. - * - * @return a function `f` such that `f(%s) == f(Tuple%d%s) == apply%s` - */ -""".format(i, i, commaXs, i, commaXs, commaXs) - def body = "case (%s) => apply%s".format(commaXs, commaXs) - - comment + "\n @annotation.unspecialized def tupled: (%s) => R = {\n %s\n }".format( - typeArgsToTupleSyntacticSugarString(targs), body) - } - - def curryMethod = { - val body = if (i < 5) shortCurry else longCurry - - curryComment + - "\n @annotation.unspecialized def curried: %s => R = {\n %s\n }\n".format( - targs mkString " => ", body - ) - } - - override def moreMethods = curryMethod + tupleMethod -} // object Function - - -/* zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz - T U P L E -zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz */ - -object Tuple { - val zipImports = "" - - def make(i: Int) = apply(i)() - def apply(i: Int) = i match { - case 1 => TupleOne - case 2 => TupleTwo - case 3 => TupleThree - case _ => new Tuple(i) - } -} - -object TupleOne extends Tuple(1) -{ - override def covariantSpecs = "@specialized(Int, Long, Double) " -} - -object TupleTwo extends Tuple(2) -{ - override def imports = Tuple.zipImports - override def covariantSpecs = "@specialized(Int, Long, Double, Char, Boolean/*, AnyRef*/) " - override def moreMethods = """ - /** Swaps the elements of this `Tuple`. - * @return a new Tuple where the first element is the second element of this Tuple and the - * second element is the first element of this Tuple. - */ - def swap: Tuple2[T2,T1] = Tuple2(_2, _1) -""" -} - -object TupleThree extends Tuple(3) { - override def imports = Tuple.zipImports -} - -class Tuple(val i: Int) extends Group("Tuple") with Arity { - private def idiomatic = - if (i < 2) "" - else " Note that it is more idiomatic to create a %s via `(%s)`".format(className, constructorArgs) - - private def params = ( - 1 to i map (x => " * @param _%d Element %d of this Tuple%d".format(x, x, i)) - ) mkString "\n" - - // prettifies it a little if it's overlong - def mkToString() = { - def str(xs: List[String]) = xs.mkString(""" + "," + """) - if (i <= MaxArity / 2) str(mdefs) - else { - val s1 = str(mdefs take (i / 2)) - val s2 = str(mdefs drop (i / 2)) - s1 + " +\n \",\" + " + s2 - } - } - - def apply() = { -{header} - -/** A tuple of {i} elements; the canonical representation of a [[scala.{Product.className(i)}]]. - * - * @constructor Create a new tuple with {i} elements.{idiomatic} -{params} - */ -final case class {className}{covariantArgs}({fields}) - extends {Product.className(i)}{invariantArgs} -{{ - override def toString(): String = "(" + {mkToString} + ")" - {moreMethods} -}} -} -} // object Tuple - - -/* zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz - P R O D U C T -zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz */ - -object Product extends Group("Product") -{ - def make(i: Int) = apply(i)() - def apply(i: Int) = i match { - case 1 => ProductOne - case 2 => ProductTwo - case _ => new Product(i) - } -} - -object ProductOne extends Product(1) -{ - override def covariantSpecs = "@specialized(Int, Long, Double) " -} - -object ProductTwo extends Product(2) -{ - override def covariantSpecs = "@specialized(Int, Long, Double) " -} - -class Product(val i: Int) extends Group("Product") with Arity { - val productElementComment = s""" - /** Returns the n-th projection of this product if 0 <= n < productArity, - * otherwise throws an `IndexOutOfBoundsException`. - * - * @param n number of the projection to be returned - * @return same as `._(n+1)`, for example `productElement(0)` is the same as `._1`. - * @throws IndexOutOfBoundsException if the `n` is out of range(n < 0 || n >= $i). - */ -""" - - def cases = { - val xs = for ((x, i) <- mdefs.zipWithIndex) yield "case %d => %s".format(i, x) - val default = "case _ => throw new IndexOutOfBoundsException(s\"$n is out of bounds (min 0, max " + (i-1) +")\")" - "\n" + ((xs ::: List(default)).map(" " + _ + "\n").mkString) - } - def proj = { - (mdefs,targs).zipped.map( (_,_) ).zipWithIndex.map { case ((method,typeName),index) => - """| /** A projection of element %d of this Product. - | * @return A projection of element %d. - | */ - | def %s: %s - |""".stripMargin.format(index + 1, index + 1, method, typeName) - }.mkString - } - - def apply() = { -{header} - -object {className} {{ - def unapply{invariantArgs}(x: {className}{invariantArgs}): Option[{className}{invariantArgs}] = - Some(x) -}} - -/** {className} is a Cartesian product of {i} component{s}. - */ -trait {className}{covariantArgs} extends Any with Product {{ - /** The arity of this product. - * @return {i} - */ - override def productArity: Int = {i} - - {productElementComment} - @throws(classOf[IndexOutOfBoundsException]) - override def productElement(n: Int): Any = n match {{ {cases} }} - -{proj} -{moreMethods} -}} -} - -} - -/** Abstract functions **/ - -object AbstractFunctionZero extends AbstractFunction(0) { - override def covariantSpecs = FunctionZero.covariantSpecs -} - -object AbstractFunctionOne extends AbstractFunction(1) { - override def covariantSpecs = FunctionOne.covariantSpecs - override def contravariantSpecs = FunctionOne.contravariantSpecs -} - -object AbstractFunctionTwo extends AbstractFunction(2) { - override def covariantSpecs = FunctionTwo.covariantSpecs - override def contravariantSpecs = FunctionTwo.contravariantSpecs -} - -class AbstractFunction(val i: Int) extends Group("AbstractFunction") with Arity -{ - override def packageDef = "scala.runtime" - - val superTypeArgs = typeArgsString(targs ::: List("R")) - - def apply() = { -{header} - -abstract class {className}{contraCoArgs} extends Function{i}{superTypeArgs} {{ -{moreMethods} -}} -} - -} -object AbstractFunction -{ - def make(i: Int) = apply(i)() - def apply(i: Int) = i match { - case 0 => AbstractFunctionZero - case 1 => AbstractFunctionOne - case 2 => AbstractFunctionTwo - case _ => new AbstractFunction(i) - } -} - diff --git a/project/ScalaLibraryPlugin.scala b/project/ScalaLibraryPlugin.scala index 66972e68495c..ed749e9126bd 100644 --- a/project/ScalaLibraryPlugin.scala +++ b/project/ScalaLibraryPlugin.scala @@ -111,18 +111,6 @@ object ScalaLibraryPlugin extends AutoPlugin { .withAnalysis(analysis.copy(stamps = stamps)) // update the analysis with the correct stamps .withHasModified(true) // mark it as updated for sbt to update its caches }, - - // Generate (Product|TupleN|Function|AbstractFunction)*.scala files and scaladoc stubs for all AnyVal sources. - // They should really go into a managedSources dir instead of overwriting sources checked into git but scaladoc - // source links (could be fixed by shipping these sources with the scaladoc bundles) and scala-js source maps - // rely on them being on github. - commands += Command.command("generateSources") { state => - val dir = ((ThisBuild / baseDirectory).value / "library" / "src" / "scala").getAbsoluteFile - dotty.tools.GenerateLibraryNTemplates.run(dir) - dotty.tools.GenerateAnyVals.run(dir) - dotty.tools.GenerateFunctionConverters.run(dir) - state - }, ) private lazy val filesToCopy = Set( From 64c3cd0e57d829bc70861389a05c2f3625961877 Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Sat, 16 Aug 2025 02:23:03 +0200 Subject: [PATCH 099/101] Undo build changes --- project/Build.scala | 2 -- project/ScalaLibraryPlugin.scala | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/project/Build.scala b/project/Build.scala index a9f964d5959c..f9e678eab13f 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -1682,7 +1682,6 @@ object Build { Compile / scalacOptions ++= Seq( // Needed so that the library sources are visible when `dotty.tools.dotc.core.Definitions#init` is called "-sourcepath", (Compile / sourceDirectories).value.map(_.getCanonicalPath).distinct.mkString(File.pathSeparator), - "-nowarn", ), // Make sure that the produced artifacts have the minimum JVM version in the bytecode Compile / javacOptions ++= Seq("--release", Versions.minimumJVMVersion), @@ -1765,7 +1764,6 @@ object Build { Compile / scalacOptions ++= Seq( // Needed so that the library sources are visible when `dotty.tools.dotc.core.Definitions#init` is called "-sourcepath", (Compile / sourceDirectories).value.map(_.getCanonicalPath).distinct.mkString(File.pathSeparator), - "-nowarn", ), // Make sure that the produced artifacts have the minimum JVM version in the bytecode Compile / javacOptions ++= Seq("--release", Versions.minimumJVMVersion), diff --git a/project/ScalaLibraryPlugin.scala b/project/ScalaLibraryPlugin.scala index ed749e9126bd..1d77a2f873b3 100644 --- a/project/ScalaLibraryPlugin.scala +++ b/project/ScalaLibraryPlugin.scala @@ -110,7 +110,7 @@ object ScalaLibraryPlugin extends AutoPlugin { previous .withAnalysis(analysis.copy(stamps = stamps)) // update the analysis with the correct stamps .withHasModified(true) // mark it as updated for sbt to update its caches - }, + } ) private lazy val filesToCopy = Set( From 7d99e9f3ab5f668c3a48369c2e8baab45c7970f7 Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Sat, 16 Aug 2025 02:23:37 +0200 Subject: [PATCH 100/101] Remove TODO --- TODO.md | 645 -------------------------------------------------------- 1 file changed, 645 deletions(-) delete mode 100644 TODO.md diff --git a/TODO.md b/TODO.md deleted file mode 100644 index af74563e322e..000000000000 --- a/TODO.md +++ /dev/null @@ -1,645 +0,0 @@ -- [ ] library/src/scala/AnyValCompanion.scala -- [ ] library/src/scala/App.scala -- [x] library/src/scala/Array.scala -- [ ] library/src/scala/Boolean.scala -- [ ] library/src/scala/Byte.scala -- [x] library/src/scala/CanEqual.scala -- [x] library/src/scala/CanThrow.scala -- [ ] library/src/scala/Char.scala -- [ ] library/src/scala/Console.scala -- [x] library/src/scala/Conversion.scala -- [ ] library/src/scala/DelayedInit.scala -- [ ] library/src/scala/Double.scala -- [ ] library/src/scala/DummyImplicit.scala -- [ ] library/src/scala/Dynamic.scala -- [ ] library/src/scala/Enumeration.scala -- [ ] library/src/scala/Equals.scala -- [ ] library/src/scala/Float.scala -- [ ] library/src/scala/Function.scala -- [ ] library/src/scala/Function0.scala -- [ ] library/src/scala/Function1.scala -- [ ] library/src/scala/Function10.scala -- [ ] library/src/scala/Function11.scala -- [ ] library/src/scala/Function12.scala -- [ ] library/src/scala/Function13.scala -- [ ] library/src/scala/Function14.scala -- [ ] library/src/scala/Function15.scala -- [ ] library/src/scala/Function16.scala -- [ ] library/src/scala/Function17.scala -- [ ] library/src/scala/Function18.scala -- [ ] library/src/scala/Function19.scala -- [ ] library/src/scala/Function2.scala -- [ ] library/src/scala/Function20.scala -- [ ] library/src/scala/Function21.scala -- [ ] library/src/scala/Function22.scala -- [ ] library/src/scala/Function3.scala -- [ ] library/src/scala/Function4.scala -- [ ] library/src/scala/Function5.scala -- [ ] library/src/scala/Function6.scala -- [ ] library/src/scala/Function7.scala -- [ ] library/src/scala/Function8.scala -- [ ] library/src/scala/Function9.scala -- [x] library/src/scala/IArray.scala -- [ ] library/src/scala/Int.scala -- [ ] library/src/scala/Long.scala -- [ ] library/src/scala/MatchError.scala -- [x] library/src/scala/NamedTuple.scala -- [ ] library/src/scala/NotImplementedError.scala -- [ ] library/src/scala/Option.scala -- [x] library/src/scala/PartialFunction.scala -- [x] library/src/scala/PolyFunction.scala -- [x] library/src/scala/Precise.scala -- [ ] library/src/scala/Predef.scala -- [ ] library/src/scala/Product.scala -- [ ] library/src/scala/Product1.scala -- [ ] library/src/scala/Product10.scala -- [ ] library/src/scala/Product11.scala -- [ ] library/src/scala/Product12.scala -- [ ] library/src/scala/Product13.scala -- [ ] library/src/scala/Product14.scala -- [ ] library/src/scala/Product15.scala -- [ ] library/src/scala/Product16.scala -- [ ] library/src/scala/Product17.scala -- [ ] library/src/scala/Product18.scala -- [ ] library/src/scala/Product19.scala -- [ ] library/src/scala/Product2.scala -- [ ] library/src/scala/Product20.scala -- [ ] library/src/scala/Product21.scala -- [ ] library/src/scala/Product22.scala -- [ ] library/src/scala/Product3.scala -- [ ] library/src/scala/Product4.scala -- [ ] library/src/scala/Product5.scala -- [ ] library/src/scala/Product6.scala -- [ ] library/src/scala/Product7.scala -- [ ] library/src/scala/Product8.scala -- [ ] library/src/scala/Product9.scala -- [ ] library/src/scala/Proxy.scala -- [x] library/src/scala/Selectable.scala -- [ ] library/src/scala/SerialVersionUID.scala -- [ ] library/src/scala/Short.scala -- [ ] library/src/scala/Specializable.scala -- [ ] library/src/scala/StringContext.scala -- [ ] library/src/scala/Symbol.scala -- [x] library/src/scala/Tuple.scala -- [ ] library/src/scala/Tuple1.scala -- [ ] library/src/scala/Tuple10.scala -- [ ] library/src/scala/Tuple11.scala -- [ ] library/src/scala/Tuple12.scala -- [ ] library/src/scala/Tuple13.scala -- [ ] library/src/scala/Tuple14.scala -- [ ] library/src/scala/Tuple15.scala -- [ ] library/src/scala/Tuple16.scala -- [ ] library/src/scala/Tuple17.scala -- [ ] library/src/scala/Tuple18.scala -- [ ] library/src/scala/Tuple19.scala -- [ ] library/src/scala/Tuple2.scala -- [ ] library/src/scala/Tuple20.scala -- [ ] library/src/scala/Tuple21.scala -- [ ] library/src/scala/Tuple22.scala -- [ ] library/src/scala/Tuple3.scala -- [ ] library/src/scala/Tuple4.scala -- [ ] library/src/scala/Tuple5.scala -- [ ] library/src/scala/Tuple6.scala -- [ ] library/src/scala/Tuple7.scala -- [ ] library/src/scala/Tuple8.scala -- [ ] library/src/scala/Tuple9.scala -- [ ] library/src/scala/UninitializedError.scala -- [ ] library/src/scala/UninitializedFieldError.scala -- [ ] library/src/scala/Unit.scala -- [ ] library/src/scala/ValueOf.scala -- [ ] library/src/scala/annotation/Annotation.scala -- [ ] library/src/scala/annotation/ClassfileAnnotation.scala -- [ ] library/src/scala/annotation/ConstantAnnotation.scala -- [x] library/src/scala/annotation/MacroAnnotation.scala -- [x] library/src/scala/annotation/RefiningAnnotation.scala -- [ ] library/src/scala/annotation/StaticAnnotation.scala -- [ ] library/src/scala/annotation/TypeConstraint.scala -- [x] library/src/scala/annotation/alpha.scala -- [x] library/src/scala/annotation/capability.scala -- [ ] library/src/scala/annotation/compileTimeOnly.scala -- [x] library/src/scala/annotation/constructorOnly.scala -- [ ] library/src/scala/annotation/elidable.scala -- [x] library/src/scala/annotation/experimental.scala -- [ ] library/src/scala/annotation/implicitAmbiguous.scala -- [ ] library/src/scala/annotation/implicitNotFound.scala -- [x] library/src/scala/annotation/init.scala -- [x] library/src/scala/annotation/internal/$into.scala -- [x] library/src/scala/annotation/internal/Alias.scala -- [x] library/src/scala/annotation/internal/AnnotationDefault.scala -- [x] library/src/scala/annotation/internal/AssignedNonLocally.scala -- [x] library/src/scala/annotation/internal/Body.scala -- [x] library/src/scala/annotation/internal/CaptureChecked.scala -- [x] library/src/scala/annotation/internal/Child.scala -- [x] library/src/scala/annotation/internal/ContextResultCount.scala -- [x] library/src/scala/annotation/internal/ErasedParam.scala -- [x] library/src/scala/annotation/internal/InlineParam.scala -- [x] library/src/scala/annotation/internal/MappedAlternative.scala -- [x] library/src/scala/annotation/internal/ProvisionalSuperClass.scala -- [x] library/src/scala/annotation/internal/Repeated.scala -- [x] library/src/scala/annotation/internal/RuntimeChecked.scala -- [x] library/src/scala/annotation/internal/SourceFile.scala -- [x] library/src/scala/annotation/internal/WithPureFuns.scala -- [x] library/src/scala/annotation/internal/WitnessNames.scala -- [ ] library/src/scala/annotation/internal/onlyCapability.scala -- [x] library/src/scala/annotation/internal/preview.scala -- [x] library/src/scala/annotation/internal/reachCapability.scala -- [x] library/src/scala/annotation/internal/readOnlyCapability.scala -- [x] library/src/scala/annotation/internal/requiresCapability.scala -- [x] library/src/scala/annotation/internal/sharable.scala -- [x] library/src/scala/annotation/internal/unshared.scala -- [ ] library/src/scala/annotation/meta/beanGetter.scala -- [ ] library/src/scala/annotation/meta/beanSetter.scala -- [ ] library/src/scala/annotation/meta/companionClass.scala -- [ ] library/src/scala/annotation/meta/companionMethod.scala -- [ ] library/src/scala/annotation/meta/companionObject.scala -- [ ] library/src/scala/annotation/meta/defaultArg.scala -- [ ] library/src/scala/annotation/meta/field.scala -- [ ] library/src/scala/annotation/meta/getter.scala -- [ ] library/src/scala/annotation/meta/languageFeature.scala -- [ ] library/src/scala/annotation/meta/package.scala -- [ ] library/src/scala/annotation/meta/param.scala -- [ ] library/src/scala/annotation/meta/setter.scala -- [ ] library/src/scala/annotation/meta/superArg.scala -- [ ] library/src/scala/annotation/migration.scala -- [ ] library/src/scala/annotation/nowarn.scala -- [x] library/src/scala/annotation/publicInBinary.scala -- [x] library/src/scala/annotation/retains.scala -- [x] library/src/scala/annotation/retainsByName.scala -- [ ] library/src/scala/annotation/showAsInfix.scala -- [ ] library/src/scala/annotation/stableNull.scala -- [x] library/src/scala/annotation/static.scala -- [ ] library/src/scala/annotation/strictfp.scala -- [ ] library/src/scala/annotation/switch.scala -- [ ] library/src/scala/annotation/tailrec.scala -- [x] library/src/scala/annotation/targetName.scala -- [x] library/src/scala/annotation/threadUnsafe.scala -- [x] library/src/scala/annotation/transparentTrait.scala -- [ ] library/src/scala/annotation/unchecked/uncheckedCapabilityLeaks.scala -- [x] library/src/scala/annotation/unchecked/uncheckedCaptures.scala -- [ ] library/src/scala/annotation/unchecked/uncheckedStable.scala -- [ ] library/src/scala/annotation/unchecked/uncheckedVariance.scala -- [x] library/src/scala/annotation/unroll.scala -- [ ] library/src/scala/annotation/unspecialized.scala -- [ ] library/src/scala/annotation/unused.scala -- [ ] library/src/scala/annotation/varargs.scala -- [ ] library/src/scala/beans/BeanProperty.scala -- [ ] library/src/scala/beans/BooleanBeanProperty.scala -- [x] library/src/scala/caps/package.scala -- [x] library/src/scala/collection/ArrayOps.scala -- [x] library/src/scala/collection/BitSet.scala -- [x] library/src/scala/collection/BufferedIterator.scala -- [x] library/src/scala/collection/BuildFrom.scala -- [x] library/src/scala/collection/DefaultMap.scala -- [x] library/src/scala/collection/Factory.scala -- [x] library/src/scala/collection/Hashing.scala -- [x] library/src/scala/collection/IndexedSeq.scala -- [x] library/src/scala/collection/IndexedSeqView.scala -- [x] library/src/scala/collection/Iterable.scala -- [x] library/src/scala/collection/IterableOnce.scala -- [x] library/src/scala/collection/Iterator.scala -- [x] library/src/scala/collection/JavaConverters.scala -- [x] library/src/scala/collection/LazyZipOps.scala -- [x] library/src/scala/collection/LinearSeq.scala -- [x] library/src/scala/collection/Map.scala -- [x] library/src/scala/collection/MapView.scala -- [x] library/src/scala/collection/Searching.scala -- [x] library/src/scala/collection/Seq.scala -- [x] library/src/scala/collection/SeqMap.scala -- [x] library/src/scala/collection/SeqView.scala -- [x] library/src/scala/collection/Set.scala -- [x] library/src/scala/collection/SortedMap.scala -- [x] library/src/scala/collection/SortedOps.scala -- [x] library/src/scala/collection/SortedSet.scala -- [x] library/src/scala/collection/Stepper.scala -- [x] library/src/scala/collection/StepperShape.scala -- [x] library/src/scala/collection/StrictOptimizedIterableOps.scala -- [x] library/src/scala/collection/StrictOptimizedMapOps.scala -- [x] library/src/scala/collection/StrictOptimizedSeqOps.scala -- [x] library/src/scala/collection/StrictOptimizedSetOps.scala -- [x] library/src/scala/collection/StrictOptimizedSortedMapOps.scala -- [x] library/src/scala/collection/StrictOptimizedSortedSetOps.scala -- [x] library/src/scala/collection/StringOps.scala -- [x] library/src/scala/collection/StringParsers.scala -- [x] library/src/scala/collection/View.scala -- [x] library/src/scala/collection/WithFilter.scala -- [x] library/src/scala/collection/concurrent/Map.scala -- [x] library/src/scala/collection/concurrent/TrieMap.scala -- [x] library/src/scala/collection/convert/AsJavaConverters.scala -- [x] library/src/scala/collection/convert/AsJavaExtensions.scala -- [x] library/src/scala/collection/convert/AsScalaConverters.scala -- [x] library/src/scala/collection/convert/AsScalaExtensions.scala -- [x] library/src/scala/collection/convert/ImplicitConversions.scala -- [x] library/src/scala/collection/convert/JavaCollectionWrappers.scala -- [x] library/src/scala/collection/convert/StreamExtensions.scala -- [ ] library/src/scala/collection/convert/impl/ArrayStepper.scala -- [ ] library/src/scala/collection/convert/impl/BinaryTreeStepper.scala -- [ ] library/src/scala/collection/convert/impl/BitSetStepper.scala -- [ ] library/src/scala/collection/convert/impl/ChampStepper.scala -- [ ] library/src/scala/collection/convert/impl/InOrderStepperBase.scala -- [ ] library/src/scala/collection/convert/impl/IndexedSeqStepper.scala -- [ ] library/src/scala/collection/convert/impl/IndexedStepperBase.scala -- [ ] library/src/scala/collection/convert/impl/IteratorStepper.scala -- [ ] library/src/scala/collection/convert/impl/NumericRangeStepper.scala -- [ ] library/src/scala/collection/convert/impl/RangeStepper.scala -- [ ] library/src/scala/collection/convert/impl/StringStepper.scala -- [ ] library/src/scala/collection/convert/impl/TableStepper.scala -- [ ] library/src/scala/collection/convert/impl/VectorStepper.scala -- [x] library/src/scala/collection/generic/BitOperations.scala -- [x] library/src/scala/collection/generic/CommonErrors.scala -- [x] library/src/scala/collection/generic/DefaultSerializationProxy.scala -- [x] library/src/scala/collection/generic/IsIterable.scala -- [x] library/src/scala/collection/generic/IsIterableOnce.scala -- [x] library/src/scala/collection/generic/IsMap.scala -- [x] library/src/scala/collection/generic/IsSeq.scala -- [x] library/src/scala/collection/generic/Subtractable.scala -- [x] library/src/scala/collection/generic/package.scala -- [x] library/src/scala/collection/immutable/ArraySeq.scala -- [x] library/src/scala/collection/immutable/BitSet.scala -- [x] library/src/scala/collection/immutable/ChampCommon.scala -- [x] library/src/scala/collection/immutable/HashMap.scala -- [x] library/src/scala/collection/immutable/HashSet.scala -- [x] library/src/scala/collection/immutable/IntMap.scala -- [x] library/src/scala/collection/immutable/Iterable.scala -- [ ] library/src/scala/collection/immutable/LazyList.scala -- [x] library/src/scala/collection/immutable/LazyListIterable.scala -- [x] library/src/scala/collection/immutable/List.scala -- [x] library/src/scala/collection/immutable/ListMap.scala -- [x] library/src/scala/collection/immutable/ListSet.scala -- [x] library/src/scala/collection/immutable/LongMap.scala -- [x] library/src/scala/collection/immutable/Map.scala -- [x] library/src/scala/collection/immutable/NumericRange.scala -- [x] library/src/scala/collection/immutable/Queue.scala -- [x] library/src/scala/collection/immutable/Range.scala -- [x] library/src/scala/collection/immutable/RedBlackTree.scala -- [x] library/src/scala/collection/immutable/Seq.scala -- [x] library/src/scala/collection/immutable/SeqMap.scala -- [x] library/src/scala/collection/immutable/Set.scala -- [x] library/src/scala/collection/immutable/SortedMap.scala -- [x] library/src/scala/collection/immutable/SortedSet.scala -- [ ] library/src/scala/collection/immutable/Stream.scala -- [x] library/src/scala/collection/immutable/StrictOptimizedSeqOps.scala -- [x] library/src/scala/collection/immutable/TreeMap.scala -- [x] library/src/scala/collection/immutable/TreeSeqMap.scala -- [x] library/src/scala/collection/immutable/TreeSet.scala -- [x] library/src/scala/collection/immutable/Vector.scala -- [x] library/src/scala/collection/immutable/VectorMap.scala -- [x] library/src/scala/collection/immutable/WrappedString.scala -- [x] library/src/scala/collection/immutable/package.scala -- [x] library/src/scala/collection/mutable/AnyRefMap.scala -- [x] library/src/scala/collection/mutable/ArrayBuffer.scala -- [x] library/src/scala/collection/mutable/ArrayBuilder.scala -- [x] library/src/scala/collection/mutable/ArrayDeque.scala -- [x] library/src/scala/collection/mutable/ArraySeq.scala -- [x] library/src/scala/collection/mutable/BitSet.scala -- [x] library/src/scala/collection/mutable/Buffer.scala -- [x] library/src/scala/collection/mutable/Builder.scala -- [x] library/src/scala/collection/mutable/CheckedIndexedSeqView.scala -- [x] library/src/scala/collection/mutable/Cloneable.scala -- [x] library/src/scala/collection/mutable/CollisionProofHashMap.scala -- [x] library/src/scala/collection/mutable/Growable.scala -- [x] library/src/scala/collection/mutable/GrowableBuilder.scala -- [x] library/src/scala/collection/mutable/HashMap.scala -- [x] library/src/scala/collection/mutable/HashSet.scala -- [x] library/src/scala/collection/mutable/HashTable.scala -- [x] library/src/scala/collection/mutable/ImmutableBuilder.scala -- [x] library/src/scala/collection/mutable/IndexedSeq.scala -- [x] library/src/scala/collection/mutable/Iterable.scala -- [x] library/src/scala/collection/mutable/LinkedHashMap.scala -- [x] library/src/scala/collection/mutable/LinkedHashSet.scala -- [x] library/src/scala/collection/mutable/ListBuffer.scala -- [x] library/src/scala/collection/mutable/ListMap.scala -- [x] library/src/scala/collection/mutable/LongMap.scala -- [x] library/src/scala/collection/mutable/Map.scala -- [x] library/src/scala/collection/mutable/MultiMap.scala -- [x] library/src/scala/collection/mutable/MutationTracker.scala -- [x] library/src/scala/collection/mutable/OpenHashMap.scala -- [x] library/src/scala/collection/mutable/PriorityQueue.scala -- [x] library/src/scala/collection/mutable/Queue.scala -- [x] library/src/scala/collection/mutable/RedBlackTree.scala -- [x] library/src/scala/collection/mutable/ReusableBuilder.scala -- [x] library/src/scala/collection/mutable/Seq.scala -- [x] library/src/scala/collection/mutable/SeqMap.scala -- [x] library/src/scala/collection/mutable/Set.scala -- [x] library/src/scala/collection/mutable/Shrinkable.scala -- [x] library/src/scala/collection/mutable/SortedMap.scala -- [x] library/src/scala/collection/mutable/SortedSet.scala -- [x] library/src/scala/collection/mutable/Stack.scala -- [x] library/src/scala/collection/mutable/StringBuilder.scala -- [x] library/src/scala/collection/mutable/TreeMap.scala -- [x] library/src/scala/collection/mutable/TreeSet.scala -- [x] library/src/scala/collection/mutable/UnrolledBuffer.scala -- [x] library/src/scala/collection/mutable/WeakHashMap.scala -- [x] library/src/scala/collection/mutable/package.scala -- [x] library/src/scala/collection/package.scala -- [ ] library/src/scala/compat/Platform.scala -- [ ] library/src/scala/compiletime/Erased.scala -- [x] library/src/scala/compiletime/ops/any.scala -- [x] library/src/scala/compiletime/ops/boolean.scala -- [x] library/src/scala/compiletime/ops/double.scala -- [x] library/src/scala/compiletime/ops/float.scala -- [x] library/src/scala/compiletime/ops/int.scala -- [x] library/src/scala/compiletime/ops/long.scala -- [x] library/src/scala/compiletime/ops/string.scala -- [x] library/src/scala/compiletime/package.scala -- [x] library/src/scala/compiletime/testing/Error.scala -- [x] library/src/scala/compiletime/testing/ErrorKind.scala -- [x] library/src/scala/compiletime/testing/package.scala -- [ ] library/src/scala/concurrent/Awaitable.scala -- [ ] library/src/scala/concurrent/BatchingExecutor.scala -- [ ] library/src/scala/concurrent/BlockContext.scala -- [ ] library/src/scala/concurrent/Channel.scala -- [ ] library/src/scala/concurrent/DelayedLazyVal.scala -- [ ] library/src/scala/concurrent/ExecutionContext.scala -- [ ] library/src/scala/concurrent/Future.scala -- [ ] library/src/scala/concurrent/JavaConversions.scala -- [ ] library/src/scala/concurrent/Promise.scala -- [ ] library/src/scala/concurrent/SyncChannel.scala -- [ ] library/src/scala/concurrent/SyncVar.scala -- [ ] library/src/scala/concurrent/duration/Deadline.scala -- [ ] library/src/scala/concurrent/duration/Duration.scala -- [ ] library/src/scala/concurrent/duration/DurationConversions.scala -- [ ] library/src/scala/concurrent/duration/package.scala -- [ ] library/src/scala/concurrent/impl/ExecutionContextImpl.scala -- [ ] library/src/scala/concurrent/impl/FutureConvertersImpl.scala -- [ ] library/src/scala/concurrent/impl/Promise.scala -- [ ] library/src/scala/concurrent/package.scala -- [ ] library/src/scala/deprecated.scala -- [ ] library/src/scala/deprecatedInheritance.scala -- [ ] library/src/scala/deprecatedName.scala -- [ ] library/src/scala/deprecatedOverriding.scala -- [x] library/src/scala/deriving/Mirror.scala -- [ ] library/src/scala/inline.scala -- [ ] library/src/scala/io/AnsiColor.scala -- [ ] library/src/scala/io/BufferedSource.scala -- [ ] library/src/scala/io/Codec.scala -- [ ] library/src/scala/io/Position.scala -- [ ] library/src/scala/io/Source.scala -- [ ] library/src/scala/io/StdIn.scala -- [ ] library/src/scala/jdk/Accumulator.scala -- [ ] library/src/scala/jdk/AnyAccumulator.scala -- [ ] library/src/scala/jdk/CollectionConverters.scala -- [ ] library/src/scala/jdk/DoubleAccumulator.scala -- [ ] library/src/scala/jdk/DurationConverters.scala -- [ ] library/src/scala/jdk/FunctionConverters.scala -- [ ] library/src/scala/jdk/FunctionExtensions.scala -- [ ] library/src/scala/jdk/FunctionWrappers.scala -- [ ] library/src/scala/jdk/FutureConverters.scala -- [ ] library/src/scala/jdk/IntAccumulator.scala -- [ ] library/src/scala/jdk/LongAccumulator.scala -- [ ] library/src/scala/jdk/OptionConverters.scala -- [ ] library/src/scala/jdk/OptionShape.scala -- [ ] library/src/scala/jdk/StreamConverters.scala -- [ ] library/src/scala/jdk/javaapi/CollectionConverters.scala -- [ ] library/src/scala/jdk/javaapi/DurationConverters.scala -- [ ] library/src/scala/jdk/javaapi/FunctionConverters.scala -- [ ] library/src/scala/jdk/javaapi/FutureConverters.scala -- [ ] library/src/scala/jdk/javaapi/OptionConverters.scala -- [ ] library/src/scala/jdk/javaapi/StreamConverters.scala -- [ ] library/src/scala/jdk/package.scala -- [ ] library/src/scala/language.scala -- [ ] library/src/scala/languageFeature.scala -- [x] library/src/scala/main.scala -- [ ] library/src/scala/math/BigDecimal.scala -- [ ] library/src/scala/math/BigInt.scala -- [ ] library/src/scala/math/Equiv.scala -- [ ] library/src/scala/math/Fractional.scala -- [ ] library/src/scala/math/Integral.scala -- [ ] library/src/scala/math/Numeric.scala -- [ ] library/src/scala/math/Ordered.scala -- [ ] library/src/scala/math/Ordering.scala -- [ ] library/src/scala/math/PartialOrdering.scala -- [ ] library/src/scala/math/PartiallyOrdered.scala -- [ ] library/src/scala/math/ScalaNumericConversions.scala -- [ ] library/src/scala/math/package.scala -- [ ] library/src/scala/native.scala -- [ ] library/src/scala/noinline.scala -- [ ] library/src/scala/package.scala -- [x] library/src/scala/quoted/Expr.scala -- [x] library/src/scala/quoted/ExprMap.scala -- [x] library/src/scala/quoted/Exprs.scala -- [x] library/src/scala/quoted/FromExpr.scala -- [x] library/src/scala/quoted/Quotes.scala -- [x] library/src/scala/quoted/ToExpr.scala -- [x] library/src/scala/quoted/Type.scala -- [x] library/src/scala/quoted/Varargs.scala -- [x] library/src/scala/quoted/runtime/Expr.scala -- [x] library/src/scala/quoted/runtime/Patterns.scala -- [x] library/src/scala/quoted/runtime/QuoteMatching.scala -- [x] library/src/scala/quoted/runtime/QuoteUnpickler.scala -- [x] library/src/scala/quoted/runtime/SplicedType.scala -- [x] library/src/scala/quoted/runtime/StopMacroExpansion.scala -- [ ] library/src/scala/ref/PhantomReference.scala -- [ ] library/src/scala/ref/Reference.scala -- [ ] library/src/scala/ref/ReferenceQueue.scala -- [ ] library/src/scala/ref/ReferenceWrapper.scala -- [ ] library/src/scala/ref/SoftReference.scala -- [ ] library/src/scala/ref/WeakReference.scala -- [ ] library/src/scala/reflect/ClassManifestDeprecatedApis.scala -- [ ] library/src/scala/reflect/ClassTag.scala -- [x] library/src/scala/reflect/Enum.scala -- [ ] library/src/scala/reflect/Manifest.scala -- [ ] library/src/scala/reflect/NameTransformer.scala -- [ ] library/src/scala/reflect/NoManifest.scala -- [ ] library/src/scala/reflect/OptManifest.scala -- [x] library/src/scala/reflect/Selectable.scala -- [x] library/src/scala/reflect/TypeTest.scala -- [x] library/src/scala/reflect/Typeable.scala -- [ ] library/src/scala/reflect/macros/internal/macroImpl.scala -- [ ] library/src/scala/reflect/package.scala -- [x] library/src/scala/runtime/$throws.scala -- [ ] library/src/scala/runtime/AbstractFunction0.scala -- [ ] library/src/scala/runtime/AbstractFunction1.scala -- [ ] library/src/scala/runtime/AbstractFunction10.scala -- [ ] library/src/scala/runtime/AbstractFunction11.scala -- [ ] library/src/scala/runtime/AbstractFunction12.scala -- [ ] library/src/scala/runtime/AbstractFunction13.scala -- [ ] library/src/scala/runtime/AbstractFunction14.scala -- [ ] library/src/scala/runtime/AbstractFunction15.scala -- [ ] library/src/scala/runtime/AbstractFunction16.scala -- [ ] library/src/scala/runtime/AbstractFunction17.scala -- [ ] library/src/scala/runtime/AbstractFunction18.scala -- [ ] library/src/scala/runtime/AbstractFunction19.scala -- [ ] library/src/scala/runtime/AbstractFunction2.scala -- [ ] library/src/scala/runtime/AbstractFunction20.scala -- [ ] library/src/scala/runtime/AbstractFunction21.scala -- [ ] library/src/scala/runtime/AbstractFunction22.scala -- [ ] library/src/scala/runtime/AbstractFunction3.scala -- [ ] library/src/scala/runtime/AbstractFunction4.scala -- [ ] library/src/scala/runtime/AbstractFunction5.scala -- [ ] library/src/scala/runtime/AbstractFunction6.scala -- [ ] library/src/scala/runtime/AbstractFunction7.scala -- [ ] library/src/scala/runtime/AbstractFunction8.scala -- [ ] library/src/scala/runtime/AbstractFunction9.scala -- [x] library/src/scala/runtime/AbstractPartialFunction.scala -- [ ] library/src/scala/runtime/ArrayCharSequence.scala -- [x] library/src/scala/runtime/Arrays.scala -- [ ] library/src/scala/runtime/ClassValueCompat.scala -- [x] library/src/scala/runtime/EnumValue.scala -- [x] library/src/scala/runtime/FunctionXXL.scala -- [ ] library/src/scala/runtime/LambdaDeserialize.scala -- [ ] library/src/scala/runtime/LambdaDeserializer.scala -- [ ] library/src/scala/runtime/LazyRef.scala -- [x] library/src/scala/runtime/LazyVals.scala -- [x] library/src/scala/runtime/MatchCase.scala -- [ ] library/src/scala/runtime/MethodCache.scala -- [ ] library/src/scala/runtime/ModuleSerializationProxy.scala -- [ ] library/src/scala/runtime/NonLocalReturnControl.scala -- [ ] library/src/scala/runtime/Nothing$.scala -- [ ] library/src/scala/runtime/Null$.scala -- [ ] library/src/scala/runtime/PStatics.scala -- [ ] library/src/scala/runtime/RichBoolean.scala -- [ ] library/src/scala/runtime/RichByte.scala -- [ ] library/src/scala/runtime/RichChar.scala -- [ ] library/src/scala/runtime/RichDouble.scala -- [ ] library/src/scala/runtime/RichFloat.scala -- [ ] library/src/scala/runtime/RichInt.scala -- [ ] library/src/scala/runtime/RichLong.scala -- [ ] library/src/scala/runtime/RichShort.scala -- [x] library/src/scala/runtime/Scala3RunTime.scala -- [ ] library/src/scala/runtime/ScalaNumberProxy.scala -- [ ] library/src/scala/runtime/ScalaRunTime.scala -- [ ] library/src/scala/runtime/StructuralCallSite.scala -- [ ] library/src/scala/runtime/Tuple2Zipped.scala -- [ ] library/src/scala/runtime/Tuple3Zipped.scala -- [x] library/src/scala/runtime/TupleMirror.scala -- [x] library/src/scala/runtime/TupleXXL.scala -- [x] library/src/scala/runtime/TupledFunctions.scala -- [x] library/src/scala/runtime/Tuples.scala -- [x] library/src/scala/runtime/TypeBox.scala -- [x] library/src/scala/runtime/coverage/Invoker.scala -- [ ] library/src/scala/runtime/java8/JFunction0$mcB$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction0$mcC$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction0$mcD$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction0$mcF$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction0$mcI$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction0$mcJ$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction0$mcS$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction0$mcV$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction0$mcZ$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction1$mcDD$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction1$mcDF$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction1$mcDI$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction1$mcDJ$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction1$mcFD$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction1$mcFF$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction1$mcFI$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction1$mcFJ$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction1$mcID$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction1$mcIF$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction1$mcII$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction1$mcIJ$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction1$mcJD$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction1$mcJF$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction1$mcJI$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction1$mcJJ$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction1$mcVD$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction1$mcVF$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction1$mcVI$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction1$mcVJ$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction1$mcZD$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction1$mcZF$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction1$mcZI$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction1$mcZJ$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction2$mcDDD$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction2$mcDDI$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction2$mcDDJ$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction2$mcDID$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction2$mcDII$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction2$mcDIJ$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction2$mcDJD$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction2$mcDJI$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction2$mcDJJ$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction2$mcFDD$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction2$mcFDI$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction2$mcFDJ$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction2$mcFID$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction2$mcFII$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction2$mcFIJ$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction2$mcFJD$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction2$mcFJI$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction2$mcFJJ$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction2$mcIDD$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction2$mcIDI$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction2$mcIDJ$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction2$mcIID$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction2$mcIII$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction2$mcIIJ$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction2$mcIJD$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction2$mcIJI$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction2$mcIJJ$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction2$mcJDD$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction2$mcJDI$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction2$mcJDJ$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction2$mcJID$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction2$mcJII$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction2$mcJIJ$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction2$mcJJD$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction2$mcJJI$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction2$mcJJJ$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction2$mcVDD$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction2$mcVDI$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction2$mcVDJ$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction2$mcVID$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction2$mcVII$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction2$mcVIJ$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction2$mcVJD$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction2$mcVJI$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction2$mcVJJ$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction2$mcZDD$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction2$mcZDI$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction2$mcZDJ$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction2$mcZID$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction2$mcZII$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction2$mcZIJ$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction2$mcZJD$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction2$mcZJI$sp.scala -- [ ] library/src/scala/runtime/java8/JFunction2$mcZJJ$sp.scala -- [x] library/src/scala/runtime/stdLibPatches/Predef.scala -- [x] library/src/scala/runtime/stdLibPatches/language.scala -- [ ] library/src/scala/specialized.scala -- [ ] library/src/scala/sys/BooleanProp.scala -- [ ] library/src/scala/sys/Prop.scala -- [ ] library/src/scala/sys/PropImpl.scala -- [ ] library/src/scala/sys/ShutdownHookThread.scala -- [ ] library/src/scala/sys/SystemProperties.scala -- [ ] library/src/scala/sys/package.scala -- [ ] library/src/scala/sys/process/BasicIO.scala -- [ ] library/src/scala/sys/process/Parser.scala -- [ ] library/src/scala/sys/process/Process.scala -- [ ] library/src/scala/sys/process/ProcessBuilder.scala -- [ ] library/src/scala/sys/process/ProcessBuilderImpl.scala -- [ ] library/src/scala/sys/process/ProcessIO.scala -- [ ] library/src/scala/sys/process/ProcessImpl.scala -- [ ] library/src/scala/sys/process/ProcessLogger.scala -- [ ] library/src/scala/sys/process/package.scala -- [ ] library/src/scala/throws.scala -- [ ] library/src/scala/transient.scala -- [ ] library/src/scala/typeConstraints.scala -- [ ] library/src/scala/unchecked.scala -- [ ] library/src/scala/util/ChainingOps.scala -- [x] library/src/scala/util/CommandLineParser.scala -- [ ] library/src/scala/util/DynamicVariable.scala -- [ ] library/src/scala/util/Either.scala -- [x] library/src/scala/util/FromDigits.scala -- [x] library/src/scala/util/NotGiven.scala -- [ ] library/src/scala/util/Properties.scala -- [ ] library/src/scala/util/Random.scala -- [ ] library/src/scala/util/Sorting.scala -- [ ] library/src/scala/util/Try.scala -- [x] library/src/scala/util/TupledFunction.scala -- [ ] library/src/scala/util/Using.scala -- [x] library/src/scala/util/boundary.scala -- [ ] library/src/scala/util/control/Breaks.scala -- [ ] library/src/scala/util/control/ControlThrowable.scala -- [ ] library/src/scala/util/control/Exception.scala -- [ ] library/src/scala/util/control/NoStackTrace.scala -- [ ] library/src/scala/util/control/NonFatal.scala -- [x] library/src/scala/util/control/NonLocalReturns.scala -- [ ] library/src/scala/util/control/TailCalls.scala -- [ ] library/src/scala/util/hashing/ByteswapHashing.scala -- [ ] library/src/scala/util/hashing/Hashing.scala -- [ ] library/src/scala/util/hashing/MurmurHash3.scala -- [ ] library/src/scala/util/hashing/package.scala -- [ ] library/src/scala/util/matching/Regex.scala -- [ ] library/src/scala/util/package.scala -- [ ] library/src/scala/volatile.scala From b94bf42986003a6df22d09ac1e320b4a96591958 Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Sat, 16 Aug 2025 03:16:44 +0200 Subject: [PATCH 101/101] Emit StrictFactory for arrays and strings Specifically for Scala.js, which the compiler hasn't yet known that Array is pure. --- library/src/scala/collection/Factory.scala | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/library/src/scala/collection/Factory.scala b/library/src/scala/collection/Factory.scala index 6e8f95fcdbbf..067d9633f8e9 100644 --- a/library/src/scala/collection/Factory.scala +++ b/library/src/scala/collection/Factory.scala @@ -46,11 +46,16 @@ trait Factory[-A, +C] extends Any { self => def newBuilder: Builder[A, C] } +/** A strict version of [[Factory]], where the returned collection from [[fromSpecific]] do not capture its input. */ +trait StrictFactory[-A, +C] extends Factory[A, C] { + override def fromSpecific(it: IterableOnce[A]^): C +} + object Factory { - implicit val stringFactory: Factory[Char, String] = new StringFactory + implicit val stringFactory: StrictFactory[Char, String] = new StringFactory @SerialVersionUID(3L) - private class StringFactory extends Factory[Char, String] with Serializable { + private class StringFactory extends StrictFactory[Char, String] with Serializable { def fromSpecific(it: IterableOnce[Char]^): String = { val b = new mutable.StringBuilder(scala.math.max(0, it.knownSize)) b ++= it @@ -59,9 +64,9 @@ object Factory { def newBuilder: Builder[Char, String] = new mutable.StringBuilder() } - implicit def arrayFactory[A: ClassTag]: Factory[A, Array[A]] = new ArrayFactory[A] + implicit def arrayFactory[A: ClassTag]: StrictFactory[A, Array[A]] = new ArrayFactory[A] @SerialVersionUID(3L) - private class ArrayFactory[A: ClassTag] extends Factory[A, Array[A]] with Serializable { + private class ArrayFactory[A: ClassTag] extends StrictFactory[A, Array[A]] with Serializable { def fromSpecific(it: IterableOnce[A]^): Array[A] = { val b = newBuilder b.sizeHint(it, delta = 0)