From 4beba0a9125b31b768043d6ecf059689c8e0ad58 Mon Sep 17 00:00:00 2001 From: odersky Date: Wed, 12 Jul 2023 10:33:01 +0200 Subject: [PATCH 01/37] Add unsafeAssumePure method Add unsafeAssumePure method as an escape hatch which is more specific than a cast. --- compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala | 10 +++++++++- compiler/src/dotty/tools/dotc/core/Definitions.scala | 1 + .../src/scala/annotation/internal/WithPureFuns.scala | 1 - library/src/scala/caps.scala | 6 ++++++ tests/pos-custom-args/captures/unsafeAssumePure.scala | 4 ++++ 5 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 tests/pos-custom-args/captures/unsafeAssumePure.scala diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index c8c1180abd51..c3ad71945c02 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -362,7 +362,15 @@ class CheckCaptures extends Recheck, SymTransformer: val argType0 = f(recheckStart(arg, pt)) val argType = super.recheckFinish(argType0, arg, pt) super.recheckFinish(argType, tree, pt) - if meth == defn.Caps_unsafeBox then + + if meth == defn.Caps_unsafeAssumePure then + val arg :: Nil = tree.args: @unchecked + val argType0 = recheck(arg, pt.capturing(CaptureSet.universal)) + val argType = if argType0.captureSet.isAlwaysEmpty then argType0 + else argType0.widen.stripCapturing + capt.println(i"rechecking $arg with ${pt.capturing(CaptureSet.universal)}: $argType") + super.recheckFinish(argType, tree, pt) + else if meth == defn.Caps_unsafeBox then mapArgUsing(_.forceBoxStatus(true)) else if meth == defn.Caps_unsafeUnbox then mapArgUsing(_.forceBoxStatus(false)) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index e2afe906a9c4..4049b4bc374d 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -971,6 +971,7 @@ class Definitions { @tu lazy val CapsModule: Symbol = requiredModule("scala.caps") @tu lazy val captureRoot: TermSymbol = CapsModule.requiredValue("cap") @tu lazy val CapsUnsafeModule: Symbol = requiredModule("scala.caps.unsafe") + @tu lazy val Caps_unsafeAssumePure: Symbol = CapsUnsafeModule.requiredMethod("unsafeAssumePure") @tu lazy val Caps_unsafeBox: Symbol = CapsUnsafeModule.requiredMethod("unsafeBox") @tu lazy val Caps_unsafeUnbox: Symbol = CapsUnsafeModule.requiredMethod("unsafeUnbox") @tu lazy val Caps_unsafeBoxFunArg: Symbol = CapsUnsafeModule.requiredMethod("unsafeBoxFunArg") diff --git a/library/src/scala/annotation/internal/WithPureFuns.scala b/library/src/scala/annotation/internal/WithPureFuns.scala index f0fc45c7f584..36fa7283214d 100644 --- a/library/src/scala/annotation/internal/WithPureFuns.scala +++ b/library/src/scala/annotation/internal/WithPureFuns.scala @@ -1,6 +1,5 @@ package scala.annotation package internal -import annotation.experimental /** 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/caps.scala b/library/src/scala/caps.scala index e4520d48354c..50f65a29c912 100644 --- a/library/src/scala/caps.scala +++ b/library/src/scala/caps.scala @@ -14,6 +14,12 @@ import annotation.experimental object unsafe: extension [T](x: T) + /** A specific cast operation to remove a capture set. + * If argument is of type `T^C`, assume it is of type `T` instead. + * Calls to this method are treated specially by the capture checker. + */ + def unsafeAssumePure: T = x + /** If argument is of type `cs T`, converts to type `box cs T`. This * avoids the error that would be raised when boxing `*`. */ diff --git a/tests/pos-custom-args/captures/unsafeAssumePure.scala b/tests/pos-custom-args/captures/unsafeAssumePure.scala new file mode 100644 index 000000000000..ac81be57aa76 --- /dev/null +++ b/tests/pos-custom-args/captures/unsafeAssumePure.scala @@ -0,0 +1,4 @@ +class C +import caps.unsafe.* + +def foo(x: C^): C = x.unsafeAssumePure From 18a9d2cd86e06b7275ac3fa1fffe7598af565f61 Mon Sep 17 00:00:00 2001 From: odersky Date: Thu, 13 Jul 2023 11:23:34 +0200 Subject: [PATCH 02/37] Avoid spurious dealiasing in alignDependentFunction This would become a problem when adding fluid capture sets to FromJavaObject types. And it anyway gives better error messages. --- compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala | 6 ++++-- tests/neg-custom-args/captures/i15772.check | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index c3ad71945c02..172ad0c3c499 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -670,8 +670,10 @@ class CheckCaptures extends Recheck, SymTransformer: /** Turn `expected` into a dependent function when `actual` is dependent. */ private def alignDependentFunction(expected: Type, actual: Type)(using Context): Type = def recur(expected: Type): Type = expected.dealias match - case expected @ CapturingType(eparent, refs) => - CapturingType(recur(eparent), refs, boxed = expected.isBoxed) + case expected0 @ CapturingType(eparent, refs) => + val eparent1 = recur(eparent) + if eparent1 eq eparent then expected + else CapturingType(eparent1, refs, boxed = expected0.isBoxed) case expected @ defn.FunctionOf(args, resultType, isContextual) if defn.isNonRefinedFunction(expected) && defn.isFunctionNType(actual) && !defn.isNonRefinedFunction(actual) => val expected1 = toDepFun(args, resultType, isContextual) diff --git a/tests/neg-custom-args/captures/i15772.check b/tests/neg-custom-args/captures/i15772.check index 949f7ca48588..04cbe14f40a3 100644 --- a/tests/neg-custom-args/captures/i15772.check +++ b/tests/neg-custom-args/captures/i15772.check @@ -16,7 +16,7 @@ 33 | val boxed2 : Observe[C]^ = box2(c) // error | ^^^^^^^ | Found: (C{val arg: C^}^ => Unit) ->? Unit - | Required: (C => Unit) => Unit + | Required: Observe[C]^ | | longer explanation available when compiling with `-explain` -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i15772.scala:44:2 ---------------------------------------- From cf2b2c802f7a0705780422b28a27bd17aa7db002 Mon Sep 17 00:00:00 2001 From: odersky Date: Thu, 13 Jul 2023 14:50:07 +0200 Subject: [PATCH 03/37] Handle capture checking language imports correctly for -Ytest-pickler The pickler test did not track whether capture checking was on. This meant that files with import language.experimental.captureChecking has to be compiled additionally with the capture checking setting -language.experimental.captureChecking to pass pickler tests. This made it impossible to test mixed code bases where some files require capture checking and others don't. We now use the previous setting of `CompilationUnit#needsCaptureChecking` when unpickling. --- .../src/dotty/tools/dotc/transform/Pickler.scala | 16 ++++++++-------- tests/pos/cc-backwards-compat/A.scala | 4 ++++ tests/pos/cc-backwards-compat/Iter.scala | 10 ++++++++++ 3 files changed, 22 insertions(+), 8 deletions(-) create mode 100644 tests/pos/cc-backwards-compat/A.scala create mode 100644 tests/pos/cc-backwards-compat/Iter.scala diff --git a/compiler/src/dotty/tools/dotc/transform/Pickler.scala b/compiler/src/dotty/tools/dotc/transform/Pickler.scala index d69076107d48..84aae572971a 100644 --- a/compiler/src/dotty/tools/dotc/transform/Pickler.scala +++ b/compiler/src/dotty/tools/dotc/transform/Pickler.scala @@ -48,7 +48,7 @@ class Pickler extends Phase { // Maps that keep a record if -Ytest-pickler is set. private val beforePickling = new mutable.HashMap[ClassSymbol, String] - private val pickledBytes = new mutable.HashMap[ClassSymbol, Array[Byte]] + private val pickledBytes = new mutable.HashMap[ClassSymbol, (CompilationUnit, Array[Byte])] /** Drop any elements of this list that are linked module classes of other elements in the list */ private def dropCompanionModuleClasses(clss: List[ClassSymbol])(using Context): List[ClassSymbol] = { @@ -137,7 +137,7 @@ class Pickler extends Phase { else val pickled = computePickled() reportPositionWarnings() - if ctx.settings.YtestPickler.value then pickledBytes(cls) = pickled + if ctx.settings.YtestPickler.value then pickledBytes(cls) = (unit, pickled) () => pickled unit.pickled += (cls -> demandPickled) @@ -163,21 +163,21 @@ class Pickler extends Phase { result } - private def testUnpickler(using Context): Unit = { + private def testUnpickler(using Context): Unit = pickling.println(i"testing unpickler at run ${ctx.runId}") ctx.initialize() val unpicklers = - for ((cls, bytes) <- pickledBytes) yield { + for ((cls, (unit, bytes)) <- pickledBytes) yield { val unpickler = new DottyUnpickler(bytes) unpickler.enter(roots = Set.empty) - cls -> unpickler + cls -> (unit, unpickler) } pickling.println("************* entered toplevel ***********") - for ((cls, unpickler) <- unpicklers) { + val rootCtx = ctx + for ((cls, (unit, unpickler)) <- unpicklers) do + ctx.compilationUnit.needsCaptureChecking = unit.needsCaptureChecking val unpickled = unpickler.rootTrees testSame(i"$unpickled%\n%", beforePickling(cls), cls) - } - } private def testSame(unpickled: String, previous: String, cls: ClassSymbol)(using Context) = import java.nio.charset.StandardCharsets.UTF_8 diff --git a/tests/pos/cc-backwards-compat/A.scala b/tests/pos/cc-backwards-compat/A.scala new file mode 100644 index 000000000000..29c817f8115d --- /dev/null +++ b/tests/pos/cc-backwards-compat/A.scala @@ -0,0 +1,4 @@ +package p +class A: + def map(other: Iter): Iter = other + def pair[T](x: T): (T, T) = (x, x) diff --git a/tests/pos/cc-backwards-compat/Iter.scala b/tests/pos/cc-backwards-compat/Iter.scala new file mode 100644 index 000000000000..dee72ddfcf4d --- /dev/null +++ b/tests/pos/cc-backwards-compat/Iter.scala @@ -0,0 +1,10 @@ +package p +import language.experimental.captureChecking + +class Iter: + self: Iter^ => + +def test(it: Iter^) = + val a = A() + //val b = a.map(it) // does not work yet + val c = a.pair(it) From 6ff1774363208cfdd7e4d7f4eac60aad68c52cb1 Mon Sep 17 00:00:00 2001 From: odersky Date: Thu, 13 Jul 2023 15:23:30 +0200 Subject: [PATCH 04/37] Keep track which classes were compiled with capture checking Similar to the treatment of WithPureFuns, we add an annotation WithCaptureChecks to toplevel classes if they were compiled with capture checking on. We also keep a flag on all classes so that we can find out fast whether the class was capture checked or not. However, that flag is not pickled. It is instead reconstituted by the unpickler when it sees the WtihCaptureChecks annotation. --- .../dotty/tools/dotc/core/Definitions.scala | 1 + .../src/dotty/tools/dotc/core/Flags.scala | 4 ++-- .../tools/dotc/core/tasty/TreeUnpickler.scala | 21 +++++++++++++------ .../tools/dotc/transform/PostTyper.scala | 7 +++++-- .../src/dotty/tools/dotc/typer/Namer.scala | 2 ++ .../internal/WithCaptureChecks.scala | 8 +++++++ .../annotation/internal/WithPureFuns.scala | 2 +- .../internal/requiresCapability.scala | 5 ++--- .../stdlibExperimentalDefinitions.scala | 2 -- 9 files changed, 36 insertions(+), 16 deletions(-) create mode 100644 library/src/scala/annotation/internal/WithCaptureChecks.scala diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 4049b4bc374d..cf42e2d70312 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -1029,6 +1029,7 @@ class Definitions { @tu lazy val UncheckedCapturesAnnot: ClassSymbol = requiredClass("scala.annotation.unchecked.uncheckedCaptures") @tu lazy val VolatileAnnot: ClassSymbol = requiredClass("scala.volatile") @tu lazy val WithPureFunsAnnot: ClassSymbol = requiredClass("scala.annotation.internal.WithPureFuns") + @tu lazy val WithCaptureChecksAnnot: ClassSymbol = requiredClass("scala.annotation.internal.WithCaptureChecks") @tu lazy val BeanGetterMetaAnnot: ClassSymbol = requiredClass("scala.annotation.meta.beanGetter") @tu lazy val BeanSetterMetaAnnot: ClassSymbol = requiredClass("scala.annotation.meta.beanSetter") @tu lazy val FieldMetaAnnot: ClassSymbol = requiredClass("scala.annotation.meta.field") diff --git a/compiler/src/dotty/tools/dotc/core/Flags.scala b/compiler/src/dotty/tools/dotc/core/Flags.scala index 8100bea374eb..4b06140f75c2 100644 --- a/compiler/src/dotty/tools/dotc/core/Flags.scala +++ b/compiler/src/dotty/tools/dotc/core/Flags.scala @@ -404,10 +404,10 @@ object Flags { /** Children were queried on this class */ val (_, _, ChildrenQueried @ _) = newFlags(56, "") - /** A module variable (Scala 2.x only) + /** A module variable (Scala 2.x only) / a capture-checked class * (re-used as a flag for private parameter accessors in Recheck) */ - val (_, Scala2ModuleVar @ _, _) = newFlags(57, "") + val (_, Scala2ModuleVar @ _, CaptureChecked @ _) = newFlags(57, "/") /** A macro */ val (Macro @ _, _, _) = newFlags(58, "") diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index c178485d924b..f7399782b423 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -92,7 +92,10 @@ class TreeUnpickler(reader: TastyReader, private var ownerTree: OwnerTree = _ /** Was unpickled class compiled with pureFunctions? */ - private var knowsPureFuns: Boolean = false + private var withPureFuns: Boolean = false + + /** Was unpickled class compiled with capture checks? */ + private var withCaptureChecks: Boolean = false private def registerSym(addr: Addr, sym: Symbol) = symAtAddr(addr) = sym @@ -458,7 +461,7 @@ class TreeUnpickler(reader: TastyReader, typeAtAddr.getOrElseUpdate(ref, forkAt(ref).readType()) case BYNAMEtype => val arg = readType() - ExprType(if knowsPureFuns then arg else arg.adaptByNameArgUnderPureFuns) + ExprType(if withPureFuns then arg else arg.adaptByNameArgUnderPureFuns) case _ => ConstantType(readConstant(tag)) } @@ -496,7 +499,7 @@ class TreeUnpickler(reader: TastyReader, * unless the unpickled class was also compiled with pureFunctions. */ private def postProcessFunction(tp: Type)(using Context): Type = - if knowsPureFuns then tp else tp.adaptFunctionTypeUnderPureFuns + if withPureFuns then tp else tp.adaptFunctionTypeUnderPureFuns // ------ Reading definitions ----------------------------------------------------- @@ -518,6 +521,8 @@ class TreeUnpickler(reader: TastyReader, flags |= (if (tag == VALDEF) ModuleValCreationFlags else ModuleClassCreationFlags) if flags.is(Enum, butNot = Method) && name.isTermName then flags |= StableRealizable + if name.isTypeName && withCaptureChecks then + flags |= CaptureChecked if (ctx.owner.isClass) { if (tag == TYPEPARAM) flags |= Param else if (tag == PARAM) { @@ -647,8 +652,12 @@ class TreeUnpickler(reader: TastyReader, } registerSym(start, sym) if (isClass) { - if sym.owner.is(Package) && annots.exists(_.hasSymbol(defn.WithPureFunsAnnot)) then - knowsPureFuns = true + if sym.owner.is(Package) then + if annots.exists(_.hasSymbol(defn.WithCaptureChecksAnnot)) then + withCaptureChecks = true + withPureFuns = true + else if annots.exists(_.hasSymbol(defn.WithPureFunsAnnot)) then + withPureFuns = true sym.completer.withDecls(newScope) forkAt(templateStart).indexTemplateParams()(using localContext(sym)) } @@ -1231,7 +1240,7 @@ class TreeUnpickler(reader: TastyReader, SingletonTypeTree(readTree()) case BYNAMEtpt => val arg = readTpt() - ByNameTypeTree(if knowsPureFuns then arg else arg.adaptByNameArgUnderPureFuns) + ByNameTypeTree(if withPureFuns then arg else arg.adaptByNameArgUnderPureFuns) case NAMEDARG => NamedArg(readName(), readTree()) case _ => diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index 1d9493e6b1f7..cbca783fe6a3 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -418,8 +418,11 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => val reference = ctx.settings.sourceroot.value val relativePath = util.SourceFile.relativePath(ctx.compilationUnit.source, reference) sym.addAnnotation(Annotation.makeSourceFile(relativePath, tree.span)) - if Feature.pureFunsEnabled && sym != defn.WithPureFunsAnnot then - sym.addAnnotation(Annotation(defn.WithPureFunsAnnot, tree.span)) + if sym != defn.WithPureFunsAnnot && sym != defn.WithCaptureChecksAnnot then + if Feature.ccEnabled then + sym.addAnnotation(Annotation(defn.WithCaptureChecksAnnot, tree.span)) + else if Feature.pureFunsEnabled then + sym.addAnnotation(Annotation(defn.WithPureFunsAnnot, tree.span)) else if !sym.is(Param) && !sym.owner.isOneOf(AbstractOrTrait) then Checking.checkGoodBounds(tree.symbol) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 44787dcfeedc..b43240a1fbb1 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -221,6 +221,8 @@ class Namer { typer: Typer => else NoSymbol var flags1 = flags + if name.isTypeName && Feature.ccEnabled then + flags1 |= CaptureChecked var privateWithin = privateWithinClass(tree.mods) val effectiveOwner = owner.skipWeakOwner if (flags.is(Private) && effectiveOwner.is(Package)) { diff --git a/library/src/scala/annotation/internal/WithCaptureChecks.scala b/library/src/scala/annotation/internal/WithCaptureChecks.scala new file mode 100644 index 000000000000..b10e53fcd9ea --- /dev/null +++ b/library/src/scala/annotation/internal/WithCaptureChecks.scala @@ -0,0 +1,8 @@ +package scala.annotation +package internal + +/** A marker annotation on a toplevel class that indicates + * that the class was typed with the captureChecking language import. + */ +class WithCaptureChecks extends StaticAnnotation + diff --git a/library/src/scala/annotation/internal/WithPureFuns.scala b/library/src/scala/annotation/internal/WithPureFuns.scala index 36fa7283214d..979dd7c405cc 100644 --- a/library/src/scala/annotation/internal/WithPureFuns.scala +++ b/library/src/scala/annotation/internal/WithPureFuns.scala @@ -4,5 +4,5 @@ package internal /** A marker annotation on a toplevel class that indicates * that the class was typed with the pureFunctions language import. */ -@experimental class WithPureFuns extends StaticAnnotation +class WithPureFuns extends StaticAnnotation diff --git a/library/src/scala/annotation/internal/requiresCapability.scala b/library/src/scala/annotation/internal/requiresCapability.scala index d376ba565211..56fca22d2982 100644 --- a/library/src/scala/annotation/internal/requiresCapability.scala +++ b/library/src/scala/annotation/internal/requiresCapability.scala @@ -1,8 +1,7 @@ package scala.annotation.internal - -import annotation.{StaticAnnotation, experimental} +import annotation.StaticAnnotation /** An annotation to record a required capaility in the type of a throws */ -@experimental class requiresCapability(capability: Any) extends StaticAnnotation +class requiresCapability(capability: Any) extends StaticAnnotation diff --git a/tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala b/tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala index e2499ef48883..b896eac0172a 100644 --- a/tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala +++ b/tests/run-custom-args/tasty-inspector/stdlibExperimentalDefinitions.scala @@ -49,8 +49,6 @@ val experimentalDefinitionInLibrary = Set( //// New feature: capture checking "scala.annotation.capability", - "scala.annotation.internal.WithPureFuns", - "scala.annotation.internal.requiresCapability", "scala.annotation.retains", "scala.annotation.retainsByName", "scala.Pure", From 58c3041196ed4db0fddf15528a4293899990022a Mon Sep 17 00:00:00 2001 From: odersky Date: Thu, 13 Jul 2023 15:30:29 +0200 Subject: [PATCH 05/37] Better interop with non-capture checked files When interacting with files that are not captue checked, we should assume that types can have arbitrary capture sets. This is analogous to interacting with non-null checked files, where we also assume that types in the interface can be null or not. This commit enables interop for normal rechecking by decorating types coming from sources that are not capture-checked with a special `Fluid` capture set. This set both super- and subcaptures any other capture set. --- .../src/dotty/tools/dotc/cc/CaptureSet.scala | 14 ++ .../dotty/tools/dotc/cc/CheckCaptures.scala | 33 +++- compiler/src/dotty/tools/dotc/cc/Setup.scala | 175 +++++++++--------- .../tools/dotc/printing/PlainPrinter.scala | 1 + .../captures/fromJavaObject.scala | 5 + 5 files changed, 143 insertions(+), 85 deletions(-) create mode 100644 tests/pos-custom-args/captures/fromJavaObject.scala diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala index 9de68220d2cf..c57be2720ae6 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala @@ -356,6 +356,20 @@ object CaptureSet: override def toString = elems.toString end Const + /** A special capture set that gets added to the types of symbols that were not + * themselves capture checked, in order to admit arbitrary corresponding capture + * sets in subcapturing comparisons. Similar to platform types for explicit + * nulls, this provides more lenient checking against compilation units that + * were not yet compiled with capture checking on. + */ + object Fluid extends Const(emptySet): + override def isAlwaysEmpty = false + override def addNewElems(elems: Refs, origin: CaptureSet)(using Context, VarState) = CompareResult.OK + override def accountsFor(x: CaptureRef)(using Context): Boolean = true + override def mightAccountFor(x: CaptureRef)(using Context): Boolean = true + override def toString = "" + end Fluid + /** The subclass of captureset variables with given initial elements */ class Var(initialElems: Refs = emptySet) extends CaptureSet: diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 172ad0c3c499..259e6a3ede65 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -290,6 +290,35 @@ class CheckCaptures extends Recheck, SymTransformer: def includeCallCaptures(sym: Symbol, pos: SrcPos)(using Context): Unit = if sym.exists && curEnv.isOpen then markFree(capturedVars(sym), pos) + private def handleBackwardsCompat(tp: Type, sym: Symbol, initialVariance: Int = 1)(using Context): Type = + val fluidify = new TypeMap: + variance = initialVariance + def apply(t: Type): Type = t match + case tp: MethodType => + mapOver(tp) + case tp: TypeLambda => + tp.derivedLambdaType(resType = this(tp.resType)) + case tp @ RefinedType(parent, rname, rinfo: MethodType) if defn.isFunctionOrPolyType(tp) => + tp.derivedRefinedType(parent, rname, this(rinfo)) + case tp @ AppliedType(tycon, args) if defn.isNonRefinedFunction(tp) => + mapOver(tp) + case _ => + if variance > 0 then t + else Setup.decorate(t, Function.const(CaptureSet.Fluid)) + + def isPreCC(sym: Symbol): Boolean = + sym.isTerm && sym.maybeOwner.isClass + && !defn.isFunctionSymbol(sym.owner) + && !sym.owner.is(CaptureChecked) + + if isPreCC(sym) then + val tpw = tp.widen + val fluidTp = fluidify(tpw) + if fluidTp eq tpw then tp + else fluidTp.showing(i"fluid for ${sym.showLocated}, ${sym.is(JavaDefined)}: $tp --> $result", capt) + else tp + end handleBackwardsCompat + override def recheckIdent(tree: Ident)(using Context): Type = if tree.symbol.is(Method) then if tree.symbol.info.isParameterless then @@ -297,7 +326,7 @@ class CheckCaptures extends Recheck, SymTransformer: includeCallCaptures(tree.symbol, tree.srcPos) else markFree(tree.symbol, tree.srcPos) - super.recheckIdent(tree) + handleBackwardsCompat(super.recheckIdent(tree), tree.symbol) /** A specialized implementation of the selection rule. * @@ -327,7 +356,7 @@ class CheckCaptures extends Recheck, SymTransformer: val selType = recheckSelection(tree, qualType, name, disambiguate) val selCs = selType.widen.captureSet if selCs.isAlwaysEmpty || selType.widen.isBoxedCapturing || qualType.isBoxedCapturing then - selType + handleBackwardsCompat(selType, tree.symbol) else val qualCs = qualType.captureSet capt.println(i"pick one of $qualType, ${selType.widen}, $qualCs, $selCs in $tree") diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index f091142f07bc..97cee79fe1ca 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -114,88 +114,6 @@ extends tpd.TreeTraverser: case _ => tp case _ => tp - private def superTypeIsImpure(tp: Type): Boolean = { - tp.dealias match - case CapturingType(_, refs) => - !refs.isAlwaysEmpty - case tp: (TypeRef | AppliedType) => - val sym = tp.typeSymbol - if sym.isClass then - sym == defn.AnyClass - // we assume Any is a shorthand of {cap} Any, so if Any is an upper - // bound, the type is taken to be impure. - else superTypeIsImpure(tp.superType) - case tp: (RefinedOrRecType | MatchType) => - superTypeIsImpure(tp.underlying) - case tp: AndType => - superTypeIsImpure(tp.tp1) || needsVariable(tp.tp2) - case tp: OrType => - superTypeIsImpure(tp.tp1) && superTypeIsImpure(tp.tp2) - case _ => - false - }.showing(i"super type is impure $tp = $result", capt) - - /** Should a capture set variable be added on type `tp`? */ - def needsVariable(tp: Type): Boolean = { - tp.typeParams.isEmpty && tp.match - case tp: (TypeRef | AppliedType) => - val tp1 = tp.dealias - if tp1 ne tp then needsVariable(tp1) - else - val sym = tp1.typeSymbol - if sym.isClass then - !sym.isPureClass && sym != defn.AnyClass - else superTypeIsImpure(tp1) - case tp: (RefinedOrRecType | MatchType) => - needsVariable(tp.underlying) - case tp: AndType => - needsVariable(tp.tp1) && needsVariable(tp.tp2) - case tp: OrType => - needsVariable(tp.tp1) || needsVariable(tp.tp2) - case CapturingType(parent, refs) => - needsVariable(parent) - && refs.isConst // if refs is a variable, no need to add another - && !refs.isUniversal // if refs is {cap}, an added variable would not change anything - case _ => - false - }.showing(i"can have inferred capture $tp = $result", capt) - - /** Add a capture set variable to `tp` if necessary, or maybe pull out - * an embedded capture set variable from a part of `tp`. - */ - def addVar(tp: Type) = tp match - case tp @ RefinedType(parent @ CapturingType(parent1, refs), rname, rinfo) => - CapturingType(tp.derivedRefinedType(parent1, rname, rinfo), refs, parent.isBoxed) - case tp: RecType => - tp.parent match - case parent @ CapturingType(parent1, refs) => - CapturingType(tp.derivedRecType(parent1), refs, parent.isBoxed) - case _ => - tp // can return `tp` here since unlike RefinedTypes, RecTypes are never created - // by `mapInferred`. Hence if the underlying type admits capture variables - // a variable was already added, and the first case above would apply. - case AndType(tp1 @ CapturingType(parent1, refs1), tp2 @ CapturingType(parent2, refs2)) => - assert(refs1.asVar.elems.isEmpty) - assert(refs2.asVar.elems.isEmpty) - assert(tp1.isBoxed == tp2.isBoxed) - CapturingType(AndType(parent1, parent2), refs1 ** refs2, tp1.isBoxed) - case tp @ OrType(tp1 @ CapturingType(parent1, refs1), tp2 @ CapturingType(parent2, refs2)) => - assert(refs1.asVar.elems.isEmpty) - assert(refs2.asVar.elems.isEmpty) - assert(tp1.isBoxed == tp2.isBoxed) - CapturingType(OrType(parent1, parent2, tp.isSoft), refs1 ++ refs2, tp1.isBoxed) - case tp @ OrType(tp1 @ CapturingType(parent1, refs1), tp2) => - CapturingType(OrType(parent1, tp2, tp.isSoft), refs1, tp1.isBoxed) - case tp @ OrType(tp1, tp2 @ CapturingType(parent2, refs2)) => - CapturingType(OrType(tp1, parent2, tp.isSoft), refs2, tp2.isBoxed) - case _ if needsVariable(tp) => - val cs = tp.dealias match - case CapturingType(_, refs) => CaptureSet.Var(refs.elems) - case _ => CaptureSet.Var() - CapturingType(tp, cs) - case _ => - tp - private var isTopLevel = true private def mapNested(ts: List[Type]): List[Type] = @@ -246,7 +164,7 @@ extends tpd.TreeTraverser: resType = this(tp.resType)) case _ => mapOver(tp) - addVar(addCaptureRefinements(tp1)) + Setup.addVar(addCaptureRefinements(tp1)) end apply end mapInferred @@ -474,4 +392,95 @@ object Setup: def isDuringSetup(using Context): Boolean = ctx.property(IsDuringSetupKey).isDefined + + private def superTypeIsImpure(tp: Type)(using Context): Boolean = { + tp.dealias match + case CapturingType(_, refs) => + !refs.isAlwaysEmpty + case tp: (TypeRef | AppliedType) => + val sym = tp.typeSymbol + if sym.isClass then + sym == defn.AnyClass + // we assume Any is a shorthand of {cap} Any, so if Any is an upper + // bound, the type is taken to be impure. + else superTypeIsImpure(tp.superType) + case tp: (RefinedOrRecType | MatchType) => + superTypeIsImpure(tp.underlying) + case tp: AndType => + superTypeIsImpure(tp.tp1) || needsVariable(tp.tp2) + case tp: OrType => + superTypeIsImpure(tp.tp1) && superTypeIsImpure(tp.tp2) + case _ => + false + }.showing(i"super type is impure $tp = $result", capt) + + /** Should a capture set variable be added on type `tp`? */ + def needsVariable(tp: Type)(using Context): Boolean = { + tp.typeParams.isEmpty && tp.match + case tp: (TypeRef | AppliedType) => + val tp1 = tp.dealias + if tp1 ne tp then needsVariable(tp1) + else + val sym = tp1.typeSymbol + if sym.isClass then + !sym.isPureClass + && sym != defn.AnyClass + && sym != defn.FromJavaObjectSymbol + else superTypeIsImpure(tp1) + case tp: (RefinedOrRecType | MatchType) => + needsVariable(tp.underlying) + case tp: AndType => + needsVariable(tp.tp1) && needsVariable(tp.tp2) + case tp: OrType => + needsVariable(tp.tp1) || needsVariable(tp.tp2) + case CapturingType(parent, refs) => + needsVariable(parent) + && refs.isConst // if refs is a variable, no need to add another + && !refs.isUniversal // if refs is {cap}, an added variable would not change anything + case _ => + false + }.showing(i"can have inferred capture $tp = $result", capt) + + /** Add a capture set variable to `tp` if necessary, or maybe pull out + * an embedded capture set variable from a part of `tp`. + */ + def decorate(tp: Type, addedSet: Type => CaptureSet)(using Context): Type = tp match + case tp @ RefinedType(parent @ CapturingType(parent1, refs), rname, rinfo) => + CapturingType(tp.derivedRefinedType(parent1, rname, rinfo), refs, parent.isBoxed) + case tp: RecType => + tp.parent match + case parent @ CapturingType(parent1, refs) => + CapturingType(tp.derivedRecType(parent1), refs, parent.isBoxed) + case _ => + tp // can return `tp` here since unlike RefinedTypes, RecTypes are never created + // by `mapInferred`. Hence if the underlying type admits capture variables + // a variable was already added, and the first case above would apply. + case AndType(tp1 @ CapturingType(parent1, refs1), tp2 @ CapturingType(parent2, refs2)) => + assert(refs1.elems.isEmpty) + assert(refs2.elems.isEmpty) + assert(tp1.isBoxed == tp2.isBoxed) + CapturingType(AndType(parent1, parent2), refs1 ** refs2, tp1.isBoxed) + case tp @ OrType(tp1 @ CapturingType(parent1, refs1), tp2 @ CapturingType(parent2, refs2)) => + assert(refs1.elems.isEmpty) + assert(refs2.elems.isEmpty) + assert(tp1.isBoxed == tp2.isBoxed) + CapturingType(OrType(parent1, parent2, tp.isSoft), refs1 ++ refs2, tp1.isBoxed) + case tp @ OrType(tp1 @ CapturingType(parent1, refs1), tp2) => + CapturingType(OrType(parent1, tp2, tp.isSoft), refs1, tp1.isBoxed) + case tp @ OrType(tp1, tp2 @ CapturingType(parent2, refs2)) => + CapturingType(OrType(tp1, parent2, tp.isSoft), refs2, tp2.isBoxed) + case _ if needsVariable(tp) => + CapturingType(tp, addedSet(tp)) + case _ => + tp + + /** Add a capture set variable to `tp` if necessary, or maybe pull out + * an embedded capture set variable from a part of `tp`. + */ + def addVar(tp: Type)(using Context): Type = + decorate(tp, + addedSet = _.dealias.match + case CapturingType(_, refs) => CaptureSet.Var(refs.elems) + case _ => CaptureSet.Var()) + end Setup \ No newline at end of file diff --git a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala index 55d66525f7fd..6cba2f78776b 100644 --- a/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala +++ b/compiler/src/dotty/tools/dotc/printing/PlainPrinter.scala @@ -152,6 +152,7 @@ class PlainPrinter(_ctx: Context) extends Printer { def toTextCaptureSet(cs: CaptureSet): Text = if printDebug && !cs.isConst then cs.toString else if ctx.settings.YccDebug.value then cs.show + else if cs == CaptureSet.Fluid then "" else if !cs.isConst && cs.elems.isEmpty then "?" else "{" ~ Text(cs.elems.toList.map(toTextCaptureRef), ", ") ~ "}" diff --git a/tests/pos-custom-args/captures/fromJavaObject.scala b/tests/pos-custom-args/captures/fromJavaObject.scala new file mode 100644 index 000000000000..1f640af799f5 --- /dev/null +++ b/tests/pos-custom-args/captures/fromJavaObject.scala @@ -0,0 +1,5 @@ +// Test that CC handles FromJavaObject correctly +object Test: + val x: Any = 12 + String.valueOf(x) + From 66670c269ae828024c55bcb287d22b09dbc2a4d9 Mon Sep 17 00:00:00 2001 From: odersky Date: Thu, 13 Jul 2023 15:34:04 +0200 Subject: [PATCH 06/37] Better interop with non-capture-checked files for override checking --- compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala | 8 ++++++-- compiler/src/dotty/tools/dotc/typer/RefChecks.scala | 12 ++++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 259e6a3ede65..c022ff1c248a 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -922,7 +922,7 @@ class CheckCaptures extends Recheck, SymTransformer: * But maybe we can then elide the check during the RefChecks phase under captureChecking? */ def checkOverrides = new TreeTraverser: - class OverridingPairsCheckerCC(clazz: ClassSymbol, self: Type, srcPos: SrcPos)(using Context) extends OverridingPairsChecker(clazz, self) { + class OverridingPairsCheckerCC(clazz: ClassSymbol, self: Type, srcPos: SrcPos)(using Context) extends OverridingPairsChecker(clazz, self): /** Check subtype with box adaptation. * This function is passed to RefChecks to check the compatibility of overriding pairs. * @param sym symbol of the field definition that is being checked @@ -944,7 +944,11 @@ class CheckCaptures extends Recheck, SymTransformer: case _ => adapted finally curEnv = saved actual1 frozen_<:< expected1 - } + + override def adjustOtherType(tp: Type, other: Symbol)(using Context): Type = + handleBackwardsCompat(tp, other, initialVariance = 0) + //.showing(i"adjust $other: $tp --> $result") + end OverridingPairsCheckerCC def traverse(t: Tree)(using Context) = t match diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index 597ab3a48c12..b17ae0908b9e 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -239,6 +239,11 @@ object RefChecks { // compatibility checking. def checkSubType(tp1: Type, tp2: Type)(using Context): Boolean = tp1 frozen_<:< tp2 + /** A hook that allows to adjust the type of `other` before checking conformance. + * Overridden in capture checking to handle non-capture checked superclasses leniently. + */ + def adjustOtherType(tp: Type, other: Symbol)(using Context): Type = tp + private val subtypeChecker: (Type, Type) => Context ?=> Boolean = this.checkSubType def checkAll(checkOverride: ((Type, Type) => Context ?=> Boolean, Symbol, Symbol) => Unit) = @@ -352,6 +357,10 @@ object RefChecks { && atPhase(typerPhase): loop(member.info.paramInfoss, other.info.paramInfoss) + val checker = + if makeOverridingPairsChecker == null then OverridingPairsChecker(clazz, self) + else makeOverridingPairsChecker(clazz, self) + /* Check that all conditions for overriding `other` by `member` * of class `clazz` are met. */ @@ -359,7 +368,7 @@ object RefChecks { def memberTp(self: Type) = if (member.isClass) TypeAlias(member.typeRef.EtaExpand(member.typeParams)) else self.memberInfo(member) - def otherTp(self: Type) = self.memberInfo(other) + def otherTp(self: Type) = checker.adjustOtherType(self.memberInfo(other), other) refcheck.println(i"check override ${infoString(member)} overriding ${infoString(other)}") @@ -564,7 +573,6 @@ object RefChecks { overrideDeprecation("", member, other, "removed or renamed") end checkOverride - val checker = if makeOverridingPairsChecker == null then OverridingPairsChecker(clazz, self) else makeOverridingPairsChecker(clazz, self) checker.checkAll(checkOverride) printMixinOverrideErrors() From 8f04d3a2b6d0b1865424dd7162ceab0a2df087b7 Mon Sep 17 00:00:00 2001 From: odersky Date: Thu, 13 Jul 2023 15:35:46 +0200 Subject: [PATCH 07/37] Better interop for non-capture-checked file for bounds checking Supress bounds checking in CheckCaptures if the type constructor of an applied type has not been compiled with capture checking. --- compiler/src/dotty/tools/dotc/typer/Checking.scala | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Checking.scala b/compiler/src/dotty/tools/dotc/typer/Checking.scala index c985680d9186..7f74a039fed1 100644 --- a/compiler/src/dotty/tools/dotc/typer/Checking.scala +++ b/compiler/src/dotty/tools/dotc/typer/Checking.scala @@ -146,12 +146,17 @@ object Checking { def traverse(tp: Type) = tp match case AppliedType(tycon, argTypes) - if !(tycon.typeSymbol.is(JavaDefined) && ctx.compilationUnit.isJava) => + if !(tycon.typeSymbol.is(JavaDefined) && ctx.compilationUnit.isJava) // Don't check bounds in Java units that refer to Java type constructors. // Scala is not obliged to do Java type checking and in fact i17763 goes wrong // if we attempt to check bounds of F-bounded mutually recursive Java interfaces. // Do check all bounds in Scala units and those bounds in Java units that // occur in applications of Scala type constructors. + && !(ctx.phase == Phases.checkCapturesPhase && !tycon.typeSymbol.is(CaptureChecked)) + // Don't check bounds when capture checking type constructors that were not + // themselves capture checked. Since the type constructor could not foresee + // possible capture sets, it's better to be lenient for backwards compatibility. + => checkAppliedType( untpd.AppliedTypeTree(TypeTree(tycon), argTypes.map(TypeTree(_))) .withType(tp).withSpan(tpt.span.toSynthetic), From 80943e43ffb0e1c92b9985cc7aee2fcee5bbf8cf Mon Sep 17 00:00:00 2001 From: odersky Date: Thu, 13 Jul 2023 15:36:46 +0200 Subject: [PATCH 08/37] Disable mapJavaArgs when rechecking Apply nodes It seems it's no longer needed. --- .../src/dotty/tools/dotc/transform/Recheck.scala | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/Recheck.scala b/compiler/src/dotty/tools/dotc/transform/Recheck.scala index 7ebb8dd17045..2d661e5e06dd 100644 --- a/compiler/src/dotty/tools/dotc/transform/Recheck.scala +++ b/compiler/src/dotty/tools/dotc/transform/Recheck.scala @@ -253,13 +253,16 @@ abstract class Recheck extends Phase, SymTransformer: sym.typeRef /** Assuming `formals` are parameters of a Java-defined method, remap Object - * to FromJavaObject since it got lost in ElimRepeated + * to FromJavaObject since it got lost in ElimRepeated. + * NOTE: It seems this is no longer true, and `mapJavaArgs` is not needed. + * The invocation is currently disabled in recheckApply. */ private def mapJavaArgs(formals: List[Type])(using Context): List[Type] = val tm = new TypeMap with IdempotentCaptRefMap: - def apply(t: Type) = t match - case t: TypeRef if t.symbol == defn.ObjectClass => defn.FromJavaObjectType - case _ => mapOver(t) + def apply(t: Type) = + t match + case t: TypeRef if t.symbol == defn.ObjectClass => defn.FromJavaObjectType + case _ => mapOver(t) formals.mapConserve(tm) /** Hook for method type instantiation */ @@ -274,7 +277,8 @@ abstract class Recheck extends Phase, SymTransformer: case fntpe: MethodType => assert(fntpe.paramInfos.hasSameLengthAs(tree.args)) val formals = - if tree.symbol.is(JavaDefined) then mapJavaArgs(fntpe.paramInfos) + if false && tree.symbol.is(JavaDefined) // see NOTE in mapJavaArgs + then mapJavaArgs(fntpe.paramInfos) else fntpe.paramInfos def recheckArgs(args: List[Tree], formals: List[Type], prefs: List[ParamRef]): List[Type] = args match case arg :: args1 => From df78771864757d6cf126745e27aadd490857c59f Mon Sep 17 00:00:00 2001 From: odersky Date: Thu, 13 Jul 2023 15:41:14 +0200 Subject: [PATCH 09/37] Capture check first collection classes -- original state The original versions of Iterator and IterableOnce before adding capture checking annotations --- .../stdlib/collection/IterableOnce.scala | 1354 +++++++++++++++++ .../captures/stdlib/collection/Iterator.scala | 1300 ++++++++++++++++ 2 files changed, 2654 insertions(+) create mode 100644 tests/pos-custom-args/captures/stdlib/collection/IterableOnce.scala create mode 100644 tests/pos-custom-args/captures/stdlib/collection/Iterator.scala diff --git a/tests/pos-custom-args/captures/stdlib/collection/IterableOnce.scala b/tests/pos-custom-args/captures/stdlib/collection/IterableOnce.scala new file mode 100644 index 000000000000..65d8dce08ae4 --- /dev/null +++ b/tests/pos-custom-args/captures/stdlib/collection/IterableOnce.scala @@ -0,0 +1,1354 @@ +/* + * Scala (https://www.scala-lang.org) + * + * Copyright EPFL and Lightbend, Inc. + * + * Licensed under Apache License 2.0 + * (http://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package scala +package collection + +import scala.annotation.tailrec +import scala.annotation.unchecked.uncheckedVariance +import scala.collection.mutable.StringBuilder +import scala.language.implicitConversions +import scala.math.{Numeric, Ordering} +import scala.reflect.ClassTag +import scala.runtime.AbstractFunction2 + +/** + * A template trait for collections which can be traversed either once only + * or one or more times. + * + * Note: `IterableOnce` does not extend [[IterableOnceOps]]. This is different than the general + * design of the collections library, which uses the following pattern: + * {{{ + * trait Seq extends Iterable with SeqOps + * trait SeqOps extends IterableOps + * + * trait IndexedSeq extends Seq with IndexedSeqOps + * trait IndexedSeqOps extends SeqOps + * }}} + * + * The goal is to provide a minimal interface without any sequential operations. This allows + * third-party extension like Scala parallel collections to integrate at the level of IterableOnce + * without inheriting unwanted implementations. + * + * @define coll collection + */ +trait IterableOnce[+A] extends Any { + /** Iterator can be used only once */ + def iterator: Iterator[A] + + /** Returns a [[scala.collection.Stepper]] for the elements of this collection. + * + * The Stepper enables creating a Java stream to operate on the collection, see + * [[scala.jdk.StreamConverters]]. For collections holding primitive values, the Stepper can be + * used as an iterator which doesn't box the elements. + * + * The implicit [[scala.collection.StepperShape]] parameter defines the resulting Stepper type according to the + * element type of this collection. + * + * - For collections of `Int`, `Short`, `Byte` or `Char`, an [[scala.collection.IntStepper]] is returned + * - For collections of `Double` or `Float`, a [[scala.collection.DoubleStepper]] is returned + * - For collections of `Long` a [[scala.collection.LongStepper]] is returned + * - For any other element type, an [[scala.collection.AnyStepper]] is returned + * + * Note that this method is overridden in subclasses and the return type is refined to + * `S with EfficientSplit`, for example [[scala.collection.IndexedSeqOps.stepper]]. For Steppers marked with + * [[scala.collection.Stepper.EfficientSplit]], the converters in [[scala.jdk.StreamConverters]] + * allow creating parallel streams, whereas bare Steppers can be converted only to sequential + * streams. + */ + def stepper[S <: Stepper[_]](implicit shape: StepperShape[A, S]): S = { + import convert.impl._ + val s = 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]]) + case _ => shape.seqUnbox(new AnyIteratorStepper[A](iterator)) + } + s.asInstanceOf[S] + } + + /** @return The number of elements in this $coll, if it can be cheaply computed, + * -1 otherwise. Cheaply usually means: Not requiring a collection traversal. + */ + def knownSize: Int = -1 +} + +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) + + @deprecated("Use .iterator.reduceLeftOption(...) instead", "2.13.0") + def reduceLeftOption(f: (A, A) => A): Option[A] = it.iterator.reduceLeftOption(f) + + @deprecated("Use .iterator.min instead", "2.13.0") + def min(implicit ord: Ordering[A]): A = it.iterator.min + + @deprecated("Use .iterator.nonEmpty instead", "2.13.0") + def nonEmpty: Boolean = it.iterator.nonEmpty + + @deprecated("Use .iterator.max instead", "2.13.0") + def max(implicit ord: Ordering[A]): A = it.iterator.max + + @deprecated("Use .iterator.reduceRight(...) instead", "2.13.0") + 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) + + @deprecated("Use .iterator.reduceLeft(...) instead", "2.13.0") + def reduceLeft(f: (A, A) => A): A = it.iterator.reduceLeft(f) + + @deprecated("Use .iterator.sum instead", "2.13.0") + def sum(implicit num: Numeric[A]): A = it.iterator.sum + + @deprecated("Use .iterator.product instead", "2.13.0") + def product(implicit num: Numeric[A]): A = it.iterator.product + + @deprecated("Use .iterator.count(...) instead", "2.13.0") + def count(f: A => Boolean): Int = it.iterator.count(f) + + @deprecated("Use .iterator.reduceOption(...) instead", "2.13.0") + 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) + + @deprecated("Use .iterator.size instead", "2.13.0") + def size: Int = it.iterator.size + + @deprecated("Use .iterator.forall(...) instead", "2.13.0") + 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) + + @deprecated("Use .iterator.filter(...) instead", "2.13.0") + def filter(f: A => Boolean): Iterator[A] = it.iterator.filter(f) + + @deprecated("Use .iterator.exists(...) instead", "2.13.0") + def exists(f: A => Boolean): Boolean = it.iterator.exists(f) + + @deprecated("Use .iterator.copyToBuffer(...) instead", "2.13.0") + def copyToBuffer(dest: mutable.Buffer[A]): Unit = it.iterator.copyToBuffer(dest) + + @deprecated("Use .iterator.reduce(...) instead", "2.13.0") + def reduce(f: (A, A) => A): A = it.iterator.reduce(f) + + @deprecated("Use .iterator.reduceRightOption(...) instead", "2.13.0") + def reduceRightOption(f: (A, A) => A): Option[A] = it.iterator.reduceRightOption(f) + + @deprecated("Use .iterator.toIndexedSeq instead", "2.13.0") + def toIndexedSeq: IndexedSeq[A] = it.iterator.toIndexedSeq + + @deprecated("Use .iterator.foreach(...) instead", "2.13.0") + @`inline` def foreach[U](f: A => U): Unit = it match { + case it: Iterable[A] => it.foreach(f) + case _ => it.iterator.foreach(f) + } + + @deprecated("Use .iterator.to(factory) instead", "2.13.0") + def to[C1](factory: Factory[A, C1]): C1 = factory.fromSpecific(it) + + @deprecated("Use .iterator.to(ArrayBuffer) instead", "2.13.0") + def toBuffer[B >: A]: mutable.Buffer[B] = mutable.ArrayBuffer.from(it) + + @deprecated("Use .iterator.toArray", "2.13.0") + def toArray[B >: A: ClassTag]: Array[B] = it match { + case it: Iterable[B] => it.toArray[B] + case _ => it.iterator.toArray[B] + } + + @deprecated("Use .iterator.to(List) instead", "2.13.0") + def toList: immutable.List[A] = immutable.List.from(it) + + @deprecated("Use .iterator.to(Set) instead", "2.13.0") + @`inline` def toSet[B >: A]: immutable.Set[B] = immutable.Set.from(it) + + @deprecated("Use .iterator.to(Iterable) instead", "2.13.0") + @`inline` final def toTraversable: Traversable[A] = toIterable + + @deprecated("Use .iterator.to(Iterable) instead", "2.13.0") + @`inline` final def toIterable: Iterable[A] = Iterable.from(it) + + @deprecated("Use .iterator.to(Seq) instead", "2.13.0") + @`inline` def toSeq: immutable.Seq[A] = immutable.Seq.from(it) + + @deprecated("Use .iterator.to(LazyList) instead", "2.13.0") + @`inline` def toStream: immutable.Stream[A] = immutable.Stream.from(it) + + @deprecated("Use .iterator.to(Vector) instead", "2.13.0") + @`inline` def toVector: immutable.Vector[A] = immutable.Vector.from(it) + + @deprecated("Use .iterator.to(Map) instead", "2.13.0") + def toMap[K, V](implicit ev: A <:< (K, V)): immutable.Map[K, V] = + immutable.Map.from(it.asInstanceOf[IterableOnce[(K, V)]]) + + @deprecated("Use .iterator instead", "2.13.0") + @`inline` def toIterator: Iterator[A] = it.iterator + + @deprecated("Use .iterator.isEmpty instead", "2.13.0") + def isEmpty: Boolean = it match { + case it: Iterable[A] => it.isEmpty + case _ => it.iterator.isEmpty + } + + @deprecated("Use .iterator.mkString instead", "2.13.0") + def mkString(start: String, sep: String, end: String): String = it match { + case it: Iterable[A] => it.mkString(start, sep, end) + case _ => it.iterator.mkString(start, sep, end) + } + + @deprecated("Use .iterator.mkString instead", "2.13.0") + def mkString(sep: String): String = it match { + case it: Iterable[A] => it.mkString(sep) + case _ => it.iterator.mkString(sep) + } + + @deprecated("Use .iterator.mkString instead", "2.13.0") + def mkString: String = it match { + case it: Iterable[A] => it.mkString + case _ => it.iterator.mkString + } + + @deprecated("Use .iterator.find instead", "2.13.0") + def find(p: A => Boolean): Option[A] = it.iterator.find(p) + + @deprecated("Use .iterator.foldLeft instead", "2.13.0") + @`inline` def foldLeft[B](z: B)(op: (B, A) => B): B = it.iterator.foldLeft(z)(op) + + @deprecated("Use .iterator.foldRight instead", "2.13.0") + @`inline` def foldRight[B](z: B)(op: (A, B) => B): B = it.iterator.foldRight(z)(op) + + @deprecated("Use .iterator.fold instead", "2.13.0") + def fold[A1 >: A](z: A1)(op: (A1, A1) => A1): A1 = it.iterator.fold(z)(op) + + @deprecated("Use .iterator.foldLeft instead", "2.13.0") + @`inline` def /: [B](z: B)(op: (B, A) => B): B = foldLeft[B](z)(op) + + @deprecated("Use .iterator.foldRight instead", "2.13.0") + @`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) + 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 { + case it: Iterable[A] => it.flatMap(f) + case _ => it.iterator.flatMap(f) + } + + @deprecated("Use .iterator.sameElements instead", "2.13.0") + def sameElements[B >: A](that: IterableOnce[B]): Boolean = it.iterator.sameElements(that) +} + +object IterableOnce { + @`inline` implicit def iterableOnceExtensionMethods[A](it: IterableOnce[A]): IterableOnceExtensionMethods[A] = + new IterableOnceExtensionMethods[A](it) + + /** Computes the number of elements to copy to an array from a source IterableOnce + * + * @param srcLen the length of the source collection + * @param destLen the length of the destination array + * @param start the index in the destination array at which to start copying elements to + * @param len the requested number of elements to copy (we may only be able to copy less than this) + * @return the number of elements that will be copied to the destination array + */ + @inline private[collection] def elemsToCopyToArray(srcLen: Int, destLen: Int, start: Int, len: Int): Int = + 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], + xs: Array[B], + start: Int = 0, + len: Int = Int.MaxValue): Int = + elems match { + case src: Iterable[A] => src.copyToArray[B](xs, start, len) + case src => src.iterator.copyToArray[B](xs, start, len) + } + + @inline private[collection] def checkArraySizeWithinVMLimit(size: Int): Unit = { + import scala.runtime.PStatics.VM_MaxArraySize + if (size > VM_MaxArraySize) { + throw new Exception(s"Size of array-backed collection exceeds VM array size limit of ${VM_MaxArraySize}") + } + } +} + +/** This implementation trait can be mixed into an `IterableOnce` to get the basic methods that are shared between + * `Iterator` and `Iterable`. The `IterableOnce` must support multiple calls to `iterator` but may or may not + * return the same `Iterator` every time. + * + * @define orderDependent + * + * Note: might return different results for different runs, unless the underlying collection type is ordered. + * @define orderDependentFold + * + * Note: might return different results for different runs, unless the + * underlying collection type is ordered or the operator is associative + * and commutative. + * @define mayNotTerminateInf + * + * Note: may not terminate for infinite-sized collections. + * @define willNotTerminateInf + * + * Note: will not terminate for infinite-sized collections. + * @define willForceEvaluation + * Note: Even when applied to a view or a lazy collection it will always force the elements. + * @define consumesIterator + * After calling this method, one should discard the iterator it was called + * on. Using it is undefined and subject to change. + * @define undefinedorder + * The order in which operations are performed on elements is unspecified + * and may be nondeterministic. + * @define coll collection + * + */ +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 + * operator going left to right, including the initial value. + * + * $willNotTerminateInf + * $orderDependent + * + * @tparam B the type of the elements in the resulting collection + * @param z the initial value + * @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] + + /** Selects all elements of this $coll which satisfy a predicate. + * + * @param p the predicate used to test elements. + * @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 + + /** Selects all elements of this $coll which do not satisfy a predicate. + * + * @param pred the predicate used to test elements. + * @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 + + /** Selects the first ''n'' elements. + * $orderDependent + * @param n the number of elements to take from this $coll. + * @return a $coll consisting only of the first `n` elements of this $coll, + * 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 + + /** Takes longest prefix of elements that satisfy a predicate. + * $orderDependent + * @param p The predicate used to test elements. + * @return the longest prefix of this $coll whose elements all satisfy + * the predicate `p`. + */ + def takeWhile(p: A => Boolean): C + + /** Selects all elements except first ''n'' ones. + * $orderDependent + * @param n the number of elements to drop from this $coll. + * @return a $coll consisting of all elements of this $coll except the first `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 drop(n: Int): C + + /** Drops longest prefix of elements that satisfy a predicate. + * $orderDependent + * @param p The predicate used to test elements. + * @return the longest suffix of this $coll whose first element + * does not satisfy the predicate `p`. + */ + def dropWhile(p: A => Boolean): C + + /** Selects an interval of elements. The returned $coll is made up + * of all elements `x` which satisfy the invariant: + * {{{ + * from <= indexOf(x) < until + * }}} + * $orderDependent + * + * @param from the lowest index to include from this $coll. + * @param until the lowest index to EXCLUDE from this $coll. + * @return a $coll containing the elements greater than or equal to + * index `from` extending up to (but not including) index `until` + * of this $coll. + */ + def slice(from: Int, until: Int): C + + /** Builds a new $coll by applying a function to all elements of this $coll. + * + * @param f the function to apply to each element. + * @tparam B the element type of the returned $coll. + * @return a new $coll 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] + + /** Builds a new $coll by applying a function to all elements of this $coll + * and using the elements of the resulting collections. + * + * For example: + * + * {{{ + * def getWords(lines: Seq[String]): Seq[String] = lines flatMap (line => line split "\\W+") + * }}} + * + * The type of the resulting collection is guided by the static type of $coll. This might + * cause unexpected results sometimes. For example: + * + * {{{ + * // lettersOf will return a Seq[Char] of likely repeated letters, instead of a Set + * def lettersOf(words: Seq[String]) = words flatMap (word => word.toSet) + * + * // lettersOf will return a Set[Char], not a Seq + * def lettersOf(words: Seq[String]) = words.toSet flatMap ((word: String) => word.toSeq) + * + * // xs will be an Iterable[Int] + * val xs = Map("a" -> List(11,111), "b" -> List(22,222)).flatMap(_._2) + * + * // ys will be a Map[Int, Int] + * val ys = Map("a" -> List(1 -> 11,1 -> 111), "b" -> List(2 -> 22,2 -> 222)).flatMap(_._2) + * }}} + * + * @param f the function to apply to each element. + * @tparam B the element type of the returned collection. + * @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]): CC[B] + + /** Converts this $coll of iterable collections into + * a $coll formed by the elements of these iterable + * collections. + * + * The resulting collection's type will be guided by the + * type of $coll. For example: + * + * {{{ + * val xs = List( + * Set(1, 2, 3), + * Set(1, 2, 3) + * ).flatten + * // xs == List(1, 2, 3, 1, 2, 3) + * + * val ys = Set( + * List(1, 2, 3), + * List(3, 2, 1) + * ).flatten + * // ys == Set(1, 2, 3) + * }}} + * + * @tparam B the type of the elements of each iterable collection. + * @param asIterable an implicit conversion which asserts that the element + * type of this $coll is an `Iterable`. + * @return a new $coll resulting from concatenating all element ${coll}s. + */ + def flatten[B](implicit asIterable: A => IterableOnce[B]): CC[B] + + /** Builds a new $coll by applying a partial function to all elements of this $coll + * on which the function is defined. + * + * @param pf the partial function which filters and maps the $coll. + * @tparam B the element type of the returned $coll. + * @return a new $coll resulting from applying the given partial function + * `pf` to each element on which it is defined and collecting the results. + * The order of the elements is preserved. + */ + def collect[B](pf: PartialFunction[A, B]): CC[B] + + /** Zips this $coll with its indices. + * + * @return A new $coll containing pairs consisting of all elements of this $coll paired with their index. + * Indices start at `0`. + * @example + * `List("a", "b", "c").zipWithIndex == List(("a", 0), ("b", 1), ("c", 2))` + */ + def zipWithIndex: CC[(A @uncheckedVariance, Int)] + + /** Splits this $coll into a prefix/suffix pair according to a predicate. + * + * Note: `c span p` is equivalent to (but possibly more efficient than) + * `(c takeWhile p, c dropWhile p)`, provided the evaluation of the + * predicate `p` does not cause any side-effects. + * $orderDependent + * + * @param p the test predicate + * @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) + + /** Splits this $coll into a prefix/suffix pair at a given position. + * + * Note: `c splitAt n` is equivalent to (but possibly more efficient than) + * `(c take n, c drop n)`. + * $orderDependent + * + * @param n the position at which to split. + * @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) = { + class Spanner extends runtime.AbstractFunction1[A, Boolean] { + var i = 0 + def apply(a: A) = i < n && { i += 1 ; true } + } + val spanner = new Spanner + span(spanner) + } + + /** Applies a side-effecting function to each element in this collection. + * Strict collections will apply `f` to their elements immediately, while lazy collections + * like Views and LazyLists will only apply `f` on each element if and when that element + * is evaluated, and each time that element is evaluated. + * + * @param f a function to apply to each element in this $coll + * @tparam U the return type of f + * @return The same logical collection as this + */ + def tapEach[U](f: A => U): C + + /////////////////////////////////////////////////////////////// Concrete methods based on iterator + + /** Tests whether this $coll is known to have a finite size. + * All strict collections are known to have finite size. For a non-strict + * collection such as `Stream`, the predicate returns `'''true'''` if all + * elements have been computed. It returns `'''false'''` if the stream is + * not yet evaluated to the end. Non-empty Iterators usually return + * `'''false'''` even if they were created from a collection with a known + * finite size. + * + * Note: many collection methods will not work on collections of infinite sizes. + * The typical failure mode is an infinite loop. These methods always attempt a + * traversal without checking first that `hasDefiniteSize` returns `'''true'''`. + * However, checking `hasDefiniteSize` can provide an assurance that size is + * well-defined and non-termination is not a concern. + * + * @deprecated This method is deprecated in 2.13 because it does not provide any + * actionable information. As noted above, even the collection library itself + * does not use it. When there is no guarantee that a collection is finite, it + * is generally best to attempt a computation anyway and document that it will + * not terminate for infinite collections rather than backing out because this + * would prevent performing the computation on collections that are in fact + * finite even though `hasDefiniteSize` returns `false`. + * + * @see method `knownSize` for a more useful alternative + * + * @return `'''true'''` if this collection is known to have finite size, + * `'''false'''` otherwise. + */ + @deprecated("Check .knownSize instead of .hasDefiniteSize for more actionable information (see scaladoc for details)", "2.13.0") + def hasDefiniteSize: Boolean = true + + /** Tests whether this $coll can be repeatedly traversed. Always + * true for Iterables and false for Iterators unless overridden. + * + * @return `true` if it is repeatedly traversable, `false` otherwise. + */ + def isTraversableAgain: Boolean = false + + /** Apply `f` to each element for its side effects + * Note: [U] parameter needed to help scalac's type inference. + */ + def foreach[U](f: A => U): Unit = { + val it = iterator + while(it.hasNext) f(it.next()) + } + + /** Tests whether a predicate holds for all elements of this $coll. + * + * $mayNotTerminateInf + * + * @param p the predicate used to test elements. + * @return `true` if this $coll is empty or the given predicate `p` + * holds for all elements of this $coll, otherwise `false`. + */ + def forall(p: A => Boolean): Boolean = { + var res = true + val it = iterator + while (res && it.hasNext) res = p(it.next()) + res + } + + /** Tests whether a predicate holds for at least one element of this $coll. + * + * $mayNotTerminateInf + * + * @param p the predicate used to test elements. + * @return `true` if the given predicate `p` is satisfied by at least one element of this $coll, otherwise `false` + */ + def exists(p: A => Boolean): Boolean = { + var res = false + val it = iterator + while (!res && it.hasNext) res = p(it.next()) + res + } + + /** Counts the number of elements in the $coll which satisfy a predicate. + * + * $willNotTerminateInf + * + * @param p the predicate used to test elements. + * @return the number of elements satisfying the predicate `p`. + */ + def count(p: A => Boolean): Int = { + var res = 0 + val it = iterator + while (it.hasNext) if (p(it.next())) res += 1 + res + } + + /** Finds the first element of the $coll satisfying a predicate, if any. + * + * $mayNotTerminateInf + * $orderDependent + * + * @param p the predicate used to test elements. + * @return an option value containing the first element in the $coll + * that satisfies `p`, or `None` if none exists. + */ + def find(p: A => Boolean): Option[A] = { + val it = iterator + while (it.hasNext) { + val a = it.next() + if (p(a)) return Some(a) + } + None + } + + // in future, move to IndexedSeqOps + private def foldl[X >: A, B](seq: IndexedSeq[X], start: Int, z: B, op: (B, X) => B): B = { + @tailrec def loop(at: Int, end: Int, acc: B): B = + if (at == end) acc + else loop(at + 1, end, op(acc, seq(at))) + loop(start, seq.length, z) + } + + private def foldr[X >: A, B >: X](seq: IndexedSeq[X], op: (X, B) => B): B = { + @tailrec def loop(at: Int, acc: B): B = + if (at == 0) acc + else loop(at - 1, op(seq(at - 1), acc)) + loop(seq.length - 1, seq(seq.length - 1)) + } + + /** Applies a binary operator to a start value and all elements of this $coll, + * going left to right. + * + * $willNotTerminateInf + * $orderDependentFold + * + * @param z the start value. + * @param op the binary operator. + * @tparam B the result type of the binary operator. + * @return the result of inserting `op` between consecutive elements of this $coll, + * going left to right with the start value `z` on the left: + * `op(...op(z, x,,1,,), x,,2,,, ..., x,,n,,)` where `x,,1,,, ..., x,,n,,` + * are the elements of this $coll. + * Returns `z` if this $coll is empty. + */ + def foldLeft[B](z: B)(op: (B, A) => B): B = this match { + case seq: IndexedSeq[A @unchecked] => foldl[A, B](seq, 0, z, op) + case _ => + var result = z + val it = iterator + while (it.hasNext) { + result = op(result, it.next()) + } + result + } + + /** Applies a binary operator to all elements of this $coll and a start value, + * going right to left. + * + * $willNotTerminateInf + * $orderDependentFold + * @param z the start value. + * @param op the binary operator. + * @tparam B the result type of the binary operator. + * @return the result of inserting `op` between consecutive elements of this $coll, + * going right to left with the start value `z` on the right: + * `op(x,,1,,, op(x,,2,,, ... op(x,,n,,, z)...))` where `x,,1,,, ..., x,,n,,` + * are the elements of this $coll. + * Returns `z` if this $coll is empty. + */ + def foldRight[B](z: B)(op: (A, B) => B): B = reversed.foldLeft(z)((b, a) => op(a, b)) + + @deprecated("Use foldLeft instead of /:", "2.13.0") + @`inline` final def /: [B](z: B)(op: (B, A) => B): B = foldLeft[B](z)(op) + + @deprecated("Use foldRight instead of :\\", "2.13.0") + @`inline` final def :\ [B](z: B)(op: (A, B) => B): B = foldRight[B](z)(op) + + /** Folds the elements of this $coll using the specified associative binary operator. + * The default implementation in `IterableOnce` is equivalent to `foldLeft` but may be + * overridden for more efficient traversal orders. + * + * $undefinedorder + * $willNotTerminateInf + * + * @tparam A1 a type parameter for the binary operator, a supertype of `A`. + * @param z a neutral element for the fold operation; may be added to the result + * an arbitrary number of times, and must not change the result (e.g., `Nil` for list concatenation, + * 0 for addition, or 1 for multiplication). + * @param op a binary operator that must be associative. + * @return the result of applying the fold operator `op` between all the elements and `z`, or `z` if this $coll is empty. + */ + def fold[A1 >: A](z: A1)(op: (A1, A1) => A1): A1 = foldLeft(z)(op) + + /** Reduces the elements of this $coll using the specified associative binary operator. + * + * $undefinedorder + * + * @tparam B A type parameter for the binary operator, a supertype of `A`. + * @param op A binary operator that must be associative. + * @return The result of applying reduce operator `op` between all the elements if the $coll is nonempty. + * @throws UnsupportedOperationException if this $coll is empty. + */ + def reduce[B >: A](op: (B, B) => B): B = reduceLeft(op) + + /** Reduces the elements of this $coll, if any, using the specified + * associative binary operator. + * + * $undefinedorder + * + * @tparam B A type parameter for the binary operator, a supertype of `A`. + * @param op A binary operator that must be associative. + * @return An option value containing result of applying reduce operator `op` between all + * the elements if the collection is nonempty, and `None` otherwise. + */ + def reduceOption[B >: A](op: (B, B) => B): Option[B] = reduceLeftOption(op) + + /** Applies a binary operator to all elements of this $coll, + * going left to right. + * $willNotTerminateInf + * $orderDependentFold + * + * @param op the binary operator. + * @tparam B the result type of the binary operator. + * @return the result of inserting `op` between consecutive elements of this $coll, + * going left to right: + * `op( op( ... op(x,,1,,, x,,2,,) ..., x,,n-1,,), x,,n,,)` where `x,,1,,, ..., x,,n,,` + * are the elements of this $coll. + * @throws UnsupportedOperationException if this $coll is empty. + */ + def reduceLeft[B >: A](op: (B, A) => B): B = this match { + case seq: IndexedSeq[A @unchecked] if seq.length > 0 => foldl(seq, 1, seq(0), op) + case _ if knownSize == 0 => throw new UnsupportedOperationException("empty.reduceLeft") + case _ => reduceLeftIterator[B](throw new UnsupportedOperationException("empty.reduceLeft"))(op) + } + private final def reduceLeftIterator[B >: A](onEmpty: => B)(op: (B, A) => B): B = { + val it = iterator + if (it.hasNext) { + var acc: B = it.next() + while (it.hasNext) + acc = op(acc, it.next()) + acc + } + else onEmpty + } + + /** Applies a binary operator to all elements of this $coll, going right to left. + * $willNotTerminateInf + * $orderDependentFold + * + * @param op the binary operator. + * @tparam B the result type of the binary operator. + * @return the result of inserting `op` between consecutive elements of this $coll, + * going right to left: + * `op(x,,1,,, op(x,,2,,, ..., op(x,,n-1,,, x,,n,,)...))` where `x,,1,,, ..., x,,n,,` + * are the elements of this $coll. + * @throws UnsupportedOperationException if this $coll is empty. + */ + def reduceRight[B >: A](op: (A, B) => B): B = this match { + case seq: IndexedSeq[A @unchecked] if seq.length > 0 => foldr[A, B](seq, op) + case _ if knownSize == 0 => throw new UnsupportedOperationException("empty.reduceRight") + case _ => reversed.reduceLeft[B]((x, y) => op(y, x)) // reduceLeftIterator + } + + /** Optionally applies a binary operator to all elements of this $coll, going left to right. + * $willNotTerminateInf + * $orderDependentFold + * + * @param op the binary operator. + * @tparam B the result type of the binary operator. + * @return an option value containing the result of `reduceLeft(op)` if this $coll is nonempty, + * `None` otherwise. + */ + def reduceLeftOption[B >: A](op: (B, A) => B): Option[B] = + knownSize match { + case -1 => reduceLeftOptionIterator[B](op) + case 0 => None + 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] = { + if (it.hasNext) { + var acc: B = it.next() + while (it.hasNext) + acc = op(acc, it.next()) + Some(acc) + } + else None + } + + /** Optionally applies a binary operator to all elements of this $coll, going + * right to left. + * $willNotTerminateInf + * $orderDependentFold + * + * @param op the binary operator. + * @tparam B the result type of the binary operator. + * @return an option value containing the result of `reduceRight(op)` if this $coll is nonempty, + * `None` otherwise. + */ + def reduceRightOption[B >: A](op: (A, B) => B): Option[B] = + knownSize match { + case -1 => reduceOptionIterator[A, B](reversed.iterator)((x, y) => op(y, x)) + case 0 => None + case _ => Some(reduceRight(op)) + } + + /** Tests whether the $coll is empty. + * + * Note: The default implementation creates and discards an iterator. + * + * Note: Implementations in subclasses that are not repeatedly iterable must take + * care not to consume any elements when `isEmpty` is called. + * + * @return `true` if the $coll contains no elements, `false` otherwise. + */ + def isEmpty: Boolean = + knownSize match { + case -1 => !iterator.hasNext + case 0 => true + case _ => false + } + + /** Tests whether the $coll is not empty. + * + * @return `true` if the $coll contains at least one element, `false` otherwise. + */ + @deprecatedOverriding("nonEmpty is defined as !isEmpty; override isEmpty instead", "2.13.0") + def nonEmpty: Boolean = !isEmpty + + /** The size of this $coll. + * + * $willNotTerminateInf + * + * @return the number of elements in this $coll. + */ + def size: Int = + if (knownSize >= 0) knownSize + else { + val it = iterator + var len = 0 + while (it.hasNext) { len += 1; it.next() } + len + } + + @deprecated("Use `dest ++= coll` instead", "2.13.0") + @inline final def copyToBuffer[B >: A](dest: mutable.Buffer[B]): Unit = dest ++= this + + /** Copy elements to an array, returning the number of elements written. + * + * Fills the given array `xs` starting at index `start` with values of this $coll. + * + * Copying will stop once either all the elements of this $coll have been copied, + * or the end of the array is reached. + * + * @param xs the array to fill. + * @tparam B the type of the elements of the array. + * @return the number of elements written to the array + * + * @note Reuse: $consumesIterator + */ + @deprecatedOverriding("This should always forward to the 3-arg version of this method", since = "2.13.4") + def copyToArray[B >: A](xs: Array[B]): Int = copyToArray(xs, 0, Int.MaxValue) + + /** Copy elements to an array, returning the number of elements written. + * + * Fills the given array `xs` starting at index `start` with values of this $coll. + * + * Copying will stop once either all the elements of this $coll have been copied, + * or the end of the array is reached. + * + * @param xs the array to fill. + * @param start the starting index of xs. + * @tparam B the type of the elements of the array. + * @return the number of elements written to the array + * + * @note Reuse: $consumesIterator + */ + @deprecatedOverriding("This should always forward to the 3-arg version of this method", since = "2.13.4") + def copyToArray[B >: A](xs: Array[B], start: Int): Int = copyToArray(xs, start, Int.MaxValue) + + /** Copy elements to an array, returning the number of elements written. + * + * Fills the given array `xs` starting at index `start` with at most `len` elements of this $coll. + * + * Copying will stop once either all the elements of this $coll have been copied, + * or the end of the array is reached, or `len` elements have been copied. + * + * @param xs the array to fill. + * @param start the starting index of xs. + * @param len the maximal number of elements to copy. + * @tparam B the type of the elements of the array. + * @return the number of elements written to the array + * + * @note Reuse: $consumesIterator + */ + def copyToArray[B >: A](xs: Array[B], start: Int, len: Int): Int = { + val it = iterator + var i = start + val end = start + math.min(len, xs.length - start) + while (i < end && it.hasNext) { + xs(i) = it.next() + i += 1 + } + i - start + } + + /** Sums the elements of this collection. + * + * The default implementation uses `reduce` for a known non-empty collection, `foldLeft` otherwise. + * + * $willNotTerminateInf + * + * @param num an implicit parameter defining a set of numeric operations + * which includes the `+` operator to be used in forming the sum. + * @tparam B the result type of the `+` operator. + * @return the sum of all elements of this $coll with respect to the `+` operator in `num`. + */ + def sum[B >: A](implicit num: Numeric[B]): B = + knownSize match { + case -1 => foldLeft(num.zero)(num.plus) + case 0 => num.zero + case _ => reduce(num.plus) + } + + /** Multiplies together the elements of this collection. + * + * The default implementation uses `reduce` for a known non-empty collection, `foldLeft` otherwise. + * + * $willNotTerminateInf + * + * @param num an implicit parameter defining a set of numeric operations + * which includes the `*` operator to be used in forming the product. + * @tparam B the result type of the `*` operator. + * @return the product of all elements of this $coll with respect to the `*` operator in `num`. + */ + def product[B >: A](implicit num: Numeric[B]): B = + knownSize match { + case -1 => foldLeft(num.one)(num.times) + case 0 => num.one + case _ => reduce(num.times) + } + + /** Finds the smallest element. + * + * $willNotTerminateInf + * + * @param ord An ordering to be used for comparing elements. + * @tparam B The type over which the ordering is defined. + * @throws UnsupportedOperationException if this $coll is empty. + * @return the smallest element of this $coll with respect to the ordering `ord`. + * + */ + def min[B >: A](implicit ord: Ordering[B]): A = + knownSize match { + case -1 => reduceLeftIterator[A](throw new UnsupportedOperationException("empty.min"))(ord.min) + case 0 => throw new UnsupportedOperationException("empty.min") + case _ => reduceLeft(ord.min) + } + + /** Finds the smallest element. + * + * $willNotTerminateInf + * + * @param ord An ordering to be used for comparing elements. + * @tparam B The type over which the ordering is defined. + * @return an option value containing the smallest element of this $coll + * with respect to the ordering `ord`. + */ + def minOption[B >: A](implicit ord: Ordering[B]): Option[A] = + knownSize match { + case -1 => reduceLeftOptionIterator[A](ord.min) + case 0 => None + case _ => Some(reduceLeft(ord.min)) + } + + /** Finds the largest element. + * + * $willNotTerminateInf + * + * @param ord An ordering to be used for comparing elements. + * @tparam B The type over which the ordering is defined. + * @throws UnsupportedOperationException if this $coll is empty. + * @return the largest element of this $coll with respect to the ordering `ord`. + */ + def max[B >: A](implicit ord: Ordering[B]): A = + knownSize match { + case -1 => reduceLeftIterator[A](throw new UnsupportedOperationException("empty.max"))(ord.max) + case 0 => throw new UnsupportedOperationException("empty.max") + case _ => reduceLeft(ord.max) + } + + /** Finds the largest element. + * + * $willNotTerminateInf + * + * @param ord An ordering to be used for comparing elements. + * @tparam B The type over which the ordering is defined. + * @return an option value containing the largest element of this $coll with + * respect to the ordering `ord`. + */ + def maxOption[B >: A](implicit ord: Ordering[B]): Option[A] = + knownSize match { + case -1 => reduceLeftOptionIterator[A](ord.max) + case 0 => None + case _ => Some(reduceLeft(ord.max)) + } + + /** Finds the first element which yields the largest value measured by function f. + * + * $willNotTerminateInf + * + * @param cmp An ordering to be used for comparing elements. + * @tparam B The result type of the function f. + * @param f The measuring function. + * @throws UnsupportedOperationException if this $coll is empty. + * @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 = + 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]] { + var maxElem: X = null.asInstanceOf[X] + var maxF: B = null.asInstanceOf[B] + var nonEmpty = false + def toOption: Option[X] = if (nonEmpty) Some(maxElem) else None + def result: X = if (nonEmpty) maxElem else throw new UnsupportedOperationException(s"empty.$descriptor") + def apply(m: Maximized[X, B], a: X): Maximized[X, B] = + if (m.nonEmpty) { + val fa = f(a) + if (cmp(fa, maxF)) { + maxF = fa + maxElem = a + } + m + } + else { + m.nonEmpty = true + m.maxElem = a + m.maxF = f(a) + m + } + } + + /** Finds the first element which yields the largest value measured by function f. + * + * $willNotTerminateInf + * + * @param cmp An ordering to be used for comparing elements. + * @tparam B The result type of the function f. + * @param f The measuring function. + * @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] = + knownSize match { + case 0 => None + case _ => foldLeft(new Maximized[A, B]("maxBy")(f)(ord.gt))((m, a) => m(m, a)).toOption + } + + /** Finds the first element which yields the smallest value measured by function f. + * + * $willNotTerminateInf + * + * @param cmp An ordering to be used for comparing elements. + * @tparam B The result type of the function f. + * @param f The measuring function. + * @throws UnsupportedOperationException if this $coll is empty. + * @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 = + 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 + } + + /** Finds the first element which yields the smallest value measured by function f. + * + * $willNotTerminateInf + * + * @param cmp An ordering to be used for comparing elements. + * @tparam B The result type of the function f. + * @param f The measuring function. + * @return an option value containing the first element of this $coll + * 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] = + knownSize match { + case 0 => None + case _ => foldLeft(new Maximized[A, B]("minBy")(f)(ord.lt))((m, a) => m(m, a)).toOption + } + + /** Finds the first element of the $coll for which the given partial + * function is defined, and applies the partial function to it. + * + * $mayNotTerminateInf + * $orderDependent + * + * @param pf the partial function + * @return an option value containing pf applied to the first + * 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] = { + // 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 scala.runtime.AbstractFunction1[A, Any] { + def apply(a: A) = this + } + val it = iterator + while (it.hasNext) { + val x = pf.applyOrElse(it.next(), sentinel) + if (x.asInstanceOf[AnyRef] ne sentinel) return Some(x.asInstanceOf[B]) + } + None + } + + @deprecated("`aggregate` is not relevant for sequential collections. Use `foldLeft(z)(seqop)` instead.", "2.13.0") + def aggregate[B](z: => B)(seqop: (B, A) => B, combop: (B, B) => B): B = foldLeft(z)(seqop) + + /** Tests whether every element of this collection's iterator relates to the + * corresponding element of another collection by satisfying a test predicate. + * + * $willNotTerminateInf + * + * @param that the other collection + * @param p the test predicate, which relates elements from both collections + * @tparam B the type of the elements of `that` + * @return `true` if both collections have the same length and + * `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 = { + val a = iterator + val b = that.iterator + + while (a.hasNext && b.hasNext) { + if (!p(a.next(), b.next())) return false + } + + a.hasNext == b.hasNext + } + + /** Displays all elements of this $coll in a string using start, end, and separator strings. + * + * Delegates to addString, which can be overridden. + * + * @param start the starting string. + * @param sep the separator string. + * @param end the ending string. + * @return a string representation of this $coll. The resulting string + * 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`. + * + * @example `List(1, 2, 3).mkString("(", "; ", ")") = "(1; 2; 3)"` + */ + final def mkString(start: String, sep: String, end: String): String = + if (knownSize == 0) start + end + else addString(new StringBuilder(), start, sep, end).result() + + /** Displays all elements of this $coll in a string using a separator string. + * + * Delegates to addString, which can be overridden. + * + * @param sep the separator string. + * @return a string representation of this $coll. In the resulting string + * the string representations (w.r.t. the method `toString`) + * of all elements of this $coll are separated by the string `sep`. + * + * @example `List(1, 2, 3).mkString("|") = "1|2|3"` + */ + @inline final def mkString(sep: String): String = mkString("", sep, "") + + /** Displays all elements of this $coll in a string. + * + * Delegates to addString, which can be overridden. + * + * @return a string representation of this $coll. In the resulting string + * the string representations (w.r.t. the method `toString`) + * of all elements of this $coll follow each other without any + * separator string. + */ + @inline final def mkString: String = mkString("") + + /** 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`. + * + * Example: + * + * {{{ + * scala> val a = List(1,2,3,4) + * a: List[Int] = List(1, 2, 3, 4) + * + * scala> val b = new StringBuilder() + * b: StringBuilder = + * + * scala> a.addString(b , "List(" , ", " , ")") + * res5: StringBuilder = List(1, 2, 3, 4) + * }}} + * + * @param b 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. + */ + def addString(b: StringBuilder, start: String, sep: String, end: String): b.type = { + val jsb = b.underlying + if (start.length != 0) jsb.append(start) + val it = iterator + if (it.hasNext) { + jsb.append(it.next()) + while (it.hasNext) { + jsb.append(sep) + jsb.append(it.next()) + } + } + if (end.length != 0) jsb.append(end) + b + } + + /** Appends all elements of this $coll to a string builder using a separator string. + * The written text consists of the string representations (w.r.t. the method `toString`) + * of all elements of this $coll, separated by the string `sep`. + * + * Example: + * + * {{{ + * scala> val a = List(1,2,3,4) + * a: List[Int] = List(1, 2, 3, 4) + * + * scala> val b = new StringBuilder() + * b: StringBuilder = + * + * scala> a.addString(b, ", ") + * res0: StringBuilder = 1, 2, 3, 4 + * }}} + * + * @param b the string builder to which elements are appended. + * @param sep the separator string. + * @return the string builder `b` to which elements were appended. + */ + @inline final def addString(b: StringBuilder, sep: String): b.type = addString(b, "", sep, "") + + /** Appends all elements of this $coll to a string builder. + * The written text consists of the string representations (w.r.t. the method + * `toString`) of all elements of this $coll without any separator string. + * + * Example: + * + * {{{ + * scala> val a = List(1,2,3,4) + * a: List[Int] = List(1, 2, 3, 4) + * + * scala> val b = new StringBuilder() + * b: StringBuilder = + * + * scala> val h = a.addString(b) + * h: StringBuilder = 1234 + * }}} + * + * @param b the string builder to which elements are appended. + * @return the string builder `b` to which elements were appended. + */ + @inline final def addString(b: StringBuilder): b.type = addString(b, "") + + /** Given a collection factory `factory`, convert this collection to the appropriate + * representation for the current element type `A`. Example uses: + * + * {{{ + * xs.to(List) + * xs.to(ArrayBuffer) + * xs.to(BitSet) // for xs: Iterable[Int] + * }}} + */ + 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 + + def toList: immutable.List[A] = immutable.List.from(this) + + def toVector: immutable.Vector[A] = immutable.Vector.from(this) + + def toMap[K, V](implicit ev: A <:< (K, V)): immutable.Map[K, V] = + immutable.Map.from(this.asInstanceOf[IterableOnce[(K, V)]]) + + def toSet[B >: A]: immutable.Set[B] = immutable.Set.from(this) + + /** @return This collection as a `Seq[A]`. This is equivalent to `to(Seq)` but might be faster. + */ + def toSeq: immutable.Seq[A] = immutable.Seq.from(this) + + 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 toBuffer[B >: A]: mutable.Buffer[B] = mutable.Buffer.from(this) + + /** Convert collection to array. + * + * Implementation note: DO NOT call [[Array.from]] from this method. + */ + def toArray[B >: A: ClassTag]: Array[B] = + if (knownSize >= 0) { + val destination = new Array[B](knownSize) + copyToArray(destination, 0) + destination + } + else mutable.ArrayBuilder.make[B].addAll(this).result() + + // For internal use + protected def reversed: Iterable[A] = { + var xs: immutable.List[A] = immutable.Nil + val it = iterator + while (it.hasNext) xs = it.next() :: xs + xs + } +} diff --git a/tests/pos-custom-args/captures/stdlib/collection/Iterator.scala b/tests/pos-custom-args/captures/stdlib/collection/Iterator.scala new file mode 100644 index 000000000000..4b8338ed1b17 --- /dev/null +++ b/tests/pos-custom-args/captures/stdlib/collection/Iterator.scala @@ -0,0 +1,1300 @@ +/* + * Scala (https://www.scala-lang.org) + * + * Copyright EPFL and Lightbend, Inc. + * + * 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.collection + +import scala.collection.mutable.{ArrayBuffer, ArrayBuilder, Builder, ImmutableBuilder} +import scala.annotation.tailrec +import scala.annotation.unchecked.uncheckedVariance +import scala.runtime.Statics + +/** Iterators are data structures that allow to iterate over a sequence + * of elements. They have a `hasNext` method for checking + * if there is a next element available, and a `next` method + * which returns the next element and advances the iterator. + * + * An iterator is mutable: most operations on it change its state. While it is often used + * to iterate through the elements of a collection, it can also be used without + * being backed by any collection (see constructors on the companion object). + * + * It is of particular importance to note that, unless stated otherwise, ''one should never + * use an iterator after calling a method on it''. The two most important exceptions + * are also the sole abstract methods: `next` and `hasNext`. + * + * Both these methods can be called any number of times without having to discard the + * iterator. Note that even `hasNext` may cause mutation -- such as when iterating + * from an input stream, where it will block until the stream is closed or some + * input becomes available. + * + * Consider this example for safe and unsafe use: + * + * {{{ + * def f[A](it: Iterator[A]) = { + * if (it.hasNext) { // Safe to reuse "it" after "hasNext" + * it.next() // Safe to reuse "it" after "next" + * val remainder = it.drop(2) // it is *not* safe to use "it" again after this line! + * remainder.take(2) // it is *not* safe to use "remainder" after this line! + * } else it + * } + * }}} + * + * @define mayNotTerminateInf + * Note: may not terminate for infinite iterators. + * @define preservesIterator + * The iterator remains valid for further use whatever result is returned. + * @define consumesIterator + * After calling this method, one should discard the iterator it was called + * on. Using it is undefined and subject to change. + * @define consumesAndProducesIterator + * After calling this method, one should discard the iterator it was called + * on, and use only the iterator that was returned. Using the old iterator + * is undefined, subject to change, and may result in changes to the new + * iterator as well. + * @define consumesTwoAndProducesOneIterator + * After calling this method, one should discard the iterator it was called + * on, as well as the one passed as a parameter, and use only the iterator + * that was returned. Using the old iterators is undefined, subject to change, + * and may result in changes to the new iterator as well. + * @define consumesOneAndProducesTwoIterators + * After calling this method, one should discard the iterator it was called + * on, and use only the iterators that were returned. Using the old iterator + * is undefined, subject to change, and may result in changes to the new + * iterators as well. + * @define coll iterator + */ +trait Iterator[+A] extends IterableOnce[A] with IterableOnceOps[A, Iterator, Iterator[A]] { self => + + /** Check if there is a next element available. + * + * @return `true` if there is a next element, `false` otherwise + * @note Reuse: $preservesIterator + */ + def hasNext: Boolean + + @deprecated("hasDefiniteSize on Iterator is the same as isEmpty", "2.13.0") + @`inline` override final def hasDefiniteSize = isEmpty + + /** Return the next element and advance the iterator. + * + * @throws NoSuchElementException if there is no next element. + * @return the next element. + * @note Reuse: Advances the iterator, which may exhaust the elements. It is valid to + * make additional calls on the iterator. + */ + @throws[NoSuchElementException] + def next(): A + + @inline final def iterator = this + + /** Wraps the value of `next()` in an option. + * + * @return `Some(next)` if a next element exists, `None` otherwise. + */ + def nextOption(): Option[A] = if (hasNext) Some(next()) else None + + /** Tests whether this iterator contains a given value as an element. + * $mayNotTerminateInf + * + * @param elem the element to test. + * @return `true` if this iterator produces some value that is + * is equal (as determined by `==`) to `elem`, `false` otherwise. + * @note Reuse: $consumesIterator + */ + def contains(elem: Any): Boolean = exists(_ == elem) // Note--this seems faster than manual inlining! + + /** Creates a buffered iterator from this iterator. + * + * @see [[scala.collection.BufferedIterator]] + * @return a buffered iterator producing the same values as this iterator. + * @note Reuse: $consumesAndProducesIterator + */ + def buffered: BufferedIterator[A] = new AbstractIterator[A] with BufferedIterator[A] { + private[this] var hd: A = _ + private[this] var hdDefined: Boolean = false + + def head: A = { + if (!hdDefined) { + hd = next() + hdDefined = true + } + hd + } + + override def knownSize = { + val thisSize = self.knownSize + if (thisSize >= 0 && hdDefined) thisSize + 1 + else thisSize + } + + def hasNext = + hdDefined || self.hasNext + + def next() = + if (hdDefined) { + hdDefined = false + hd + } else self.next() + } + + /** A flexible iterator for transforming an `Iterator[A]` into an + * `Iterator[Seq[A]]`, with configurable sequence size, step, and + * strategy for dealing with remainder elements which don't fit evenly + * into the last group. + * + * 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]] { + + require(size >= 1 && step >= 1, f"size=$size%d and step=$step%d, but both must be positive") + + private[this] var buffer: Array[B] = null // current result + private[this] var prev: Array[B] = null // if sliding, overlap from previous result + 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] def pad = padding != null // irrespective of partial flag + private[this] def newBuilder = { + val b = ArrayBuilder.make[Any] + val k = self.knownSize + if (k > 0) b.sizeHint(k min size) // if k < size && !partial, buffer will grow on padding + b + } + + /** Specifies a fill element used to pad a partial segment + * so that all segments have the same size. + * + * Any previous setting of `withPartial` is ignored, + * as the last group will always be padded to `size` elements. + * + * The by-name argument is evaluated for each fill element. + * + * @param x The element that will be appended to the last segment, if necessary. + * @return The same iterator, and ''not'' a new iterator. + * @note This method mutates the iterator it is called on, which can be safely used afterwards. + * @note This method is mutually exclusive with `withPartial`. + * @group Configuration + */ + def withPadding(x: => B): this.type = { + padding = () => x + partial = true // redundant, as padding always results in complete segment + this + } + /** Specify whether to drop the last segment if it has less than `size` elements. + * + * If this flag is `false`, elements of a partial segment at the end of the iterator + * are not returned. + * + * The flag defaults to `true`. + * + * Any previous setting of `withPadding` is ignored, + * as the last group will never be padded. + * A partial segment is either retained or dropped, per the flag. + * + * @param x `true` if partial segments may be returned, `false` otherwise. + * @return The same iterator, and ''not'' a new iterator. + * @note This method mutates the iterator it is called on, which can be safely used afterwards. + * @note This method is mutually exclusive with `withPadding`. + * @group Configuration + */ + def withPartial(x: Boolean): this.type = { + partial = x + padding = null + this + } + + /** Eagerly fetch `size` elements to buffer. + * + * If buffer is dirty and stepping, copy prefix. + * If skipping, skip ahead. + * Fetch remaining elements. + * If unable to deliver size, then pad if padding enabled, otherwise drop segment. + * Returns true if successful in delivering `count` elements, + * or padded segment, or partial segment. + */ + private def fulfill(): Boolean = { + val builder = newBuilder + var done = false + // keep prefix of previous buffer if stepping + if (prev != null) builder.addAll(prev) + // skip ahead + if (!first && step > size) { + var dropping = step - size + while (dropping > 0 && self.hasNext) { + self.next(): Unit + dropping -= 1 + } + done = dropping > 0 // skip failed + } + var index = builder.length + if (!done) { + // advance to rest of segment if possible + while (index < size && self.hasNext) { + builder.addOne(self.next()) + index += 1 + } + // if unable to complete segment, pad if possible + if (index < size && pad) { + builder.sizeHint(size) + while (index < size) { + builder.addOne(padding()) + index += 1 + } + } + } + // segment must have data, and must be complete unless they allow partial + val ok = index > 0 && (partial || index == size) + if (ok) buffer = builder.result().asInstanceOf[Array[B]] + else prev = null + ok + } + + // fill() returns false if no more sequences can be produced + private def fill(): Boolean = filled || { filled = self.hasNext && fulfill() ; filled } + + def hasNext = fill() + + @throws[NoSuchElementException] + def next(): immutable.Seq[B] = + if (!fill()) Iterator.empty.next() + else { + filled = false + // if stepping, retain overlap in prev + if (step < size) { + if (first) prev = buffer.drop(step) + else if (buffer.length == size) Array.copy(src = buffer, srcPos = step, dest = prev, destPos = 0, length = size - step) + else prev = null + } + val res = immutable.ArraySeq.unsafeWrapArray(buffer).asInstanceOf[immutable.ArraySeq[B]] + buffer = null + first = false + res + } + } + + /** A copy of this $coll with an element value appended until a given target length is reached. + * + * @param len the target length + * @param elem the padding value + * @tparam B the element type of the returned $coll. + * @return a new $coll consisting of + * 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] { + private[this] var i = 0 + + override def knownSize: Int = { + val thisSize = self.knownSize + if (thisSize < 0) -1 + else thisSize max (len - i) + } + + def next(): B = { + val b = + if (self.hasNext) self.next() + else if (i < len) elem + else Iterator.empty.next() + i += 1 + b + } + + def hasNext: Boolean = self.hasNext || i < len + } + + /** Partitions this iterator in two iterators according to a predicate. + * + * @param p the predicate on which to partition + * @return a pair of iterators: the iterator that satisfies the predicate + * `p` and the iterator that does not. + * The relative order of the elements in the resulting iterators + * is the same as in the original iterator. + * @note Reuse: $consumesOneAndProducesTwoIterators + */ + def partition(p: A => Boolean): (Iterator[A], Iterator[A]) = { + val (a, b) = duplicate + (a filter p, b filterNot p) + } + + /** Returns an iterator which groups this iterator into fixed size + * blocks. Example usages: + * {{{ + * // Returns List(List(1, 2, 3), List(4, 5, 6), List(7))) + * (1 to 7).iterator.grouped(3).toList + * // Returns List(List(1, 2, 3), List(4, 5, 6)) + * (1 to 7).iterator.grouped(3).withPartial(false).toList + * // Returns List(List(1, 2, 3), List(4, 5, 6), List(7, 20, 25) + * // Illustrating that withPadding's argument is by-name. + * val it2 = Iterator.iterate(20)(_ + 5) + * (1 to 7).iterator.grouped(3).withPadding(it2.next).toList + * }}} + * + * @note Reuse: $consumesAndProducesIterator + */ + def grouped[B >: A](size: Int): GroupedIterator[B] = + new GroupedIterator[B](self, size, size) + + /** Returns an iterator which presents a "sliding window" view of + * this iterator. The first argument is the window size, and + * the second argument `step` is how far to advance the window + * on each iteration. The `step` defaults to `1`. + * + * The returned `GroupedIterator` can be configured to either + * pad a partial result to size `size` or suppress the partial + * result entirely. + * + * Example usages: + * {{{ + * // Returns List(ArraySeq(1, 2, 3), ArraySeq(2, 3, 4), ArraySeq(3, 4, 5)) + * (1 to 5).iterator.sliding(3).toList + * // Returns List(ArraySeq(1, 2, 3, 4), ArraySeq(4, 5)) + * (1 to 5).iterator.sliding(4, 3).toList + * // Returns List(ArraySeq(1, 2, 3, 4)) + * (1 to 5).iterator.sliding(4, 3).withPartial(false).toList + * // Returns List(ArraySeq(1, 2, 3, 4), ArraySeq(4, 5, 20, 25)) + * // Illustrating that withPadding's argument is by-name. + * val it2 = Iterator.iterate(20)(_ + 5) + * (1 to 5).iterator.sliding(4, 3).withPadding(it2.next).toList + * }}} + * + * @param size the number of elements per group + * @param step the distance between the first elements of successive + * groups + * @return A `GroupedIterator` producing `Seq[B]`s of size `size`, except the + * last element (which may be the only element) will be truncated + * if there are fewer than `size` elements remaining to be grouped. + * This behavior can be configured. + * + * @note Reuse: $consumesAndProducesIterator + */ + def sliding[B >: A](size: Int, step: Int = 1): GroupedIterator[B] = + new GroupedIterator[B](self, size, step) + + def scanLeft[B](z: B)(op: (B, A) => B): Iterator[B] = 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] = + new AbstractIterator[B] { + override def knownSize = { + val thisSize = self.knownSize + + if (thisSize < 0) -1 + else thisSize + 1 + } + def hasNext: Boolean = true + def next(): B = { + // Here we change our self-reference to a new iterator that iterates through `self` + current = new AbstractIterator[B] { + private[this] var acc = z + def next(): B = { + acc = op(acc, self.next()) + acc + } + def hasNext: Boolean = self.hasNext + override def knownSize = self.knownSize + } + z + } + } + override def knownSize = current.knownSize + def next(): B = current.next() + def hasNext: Boolean = current.hasNext + } + + @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 indexWhere(p: A => Boolean, from: Int = 0): Int = { + var i = math.max(from, 0) + val dropped = drop(from) + while (dropped.hasNext) { + if (p(dropped.next())) return i + i += 1 + } + -1 + } + + /** Returns the index of the first occurrence of the specified + * object in this iterable object. + * $mayNotTerminateInf + * + * @param elem element to search for. + * @return the index of the first occurrence of `elem` in the values produced by this iterator, + * or -1 if such an element does not exist until the end of the iterator is reached. + * @note Reuse: $consumesIterator + */ + def indexOf[B >: A](elem: B): Int = indexOf(elem, 0) + + /** Returns the index of the first occurrence of the specified object in this iterable object + * after or at some start index. + * $mayNotTerminateInf + * + * @param elem element to search for. + * @param from the start index + * @return the index `>= from` of the first occurrence of `elem` in the values produced by this + * iterator, or -1 if such an element does not exist until the end of the iterator is + * reached. + * @note Reuse: $consumesIterator + */ + def indexOf[B >: A](elem: B, from: Int): Int = { + var i = 0 + while (i < from && hasNext) { + next() + i += 1 + } + + while (hasNext) { + if (next() == elem) return i + i += 1 + } + -1 + } + + @inline final def length: Int = size + + @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 filterNot(p: A => Boolean): Iterator[A] = filterImpl(p, isFlipped = true) + + private[collection] def filterImpl(p: A => Boolean, isFlipped: Boolean): Iterator[A] = new AbstractIterator[A] { + private[this] var hd: A = _ + private[this] var hdDefined: Boolean = false + + def hasNext: Boolean = hdDefined || { + if (!self.hasNext) return false + hd = self.next() + while (p(hd) == isFlipped) { + if (!self.hasNext) return false + hd = self.next() + } + hdDefined = true + true + } + + def next() = + if (hasNext) { + hdDefined = false + hd + } + else Iterator.empty.next() + } + + /** Creates an iterator over all the elements of this iterator that + * satisfy the predicate `p`. The order of the elements + * is preserved. + * + * '''Note:''' `withFilter` is the same as `filter` on iterators. It exists so that + * for-expressions with filters work over iterators. + * + * @param p the predicate used to test values. + * @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 collect[B](pf: PartialFunction[A, B]): Iterator[B] = new AbstractIterator[B] with (A => B) { + // Manually buffer to avoid extra layer of wrapping with buffered + private[this] var hd: B = _ + + // Little state machine to keep track of where we are + // Seek = 0; Found = 1; Empty = -1 + // Not in vals because scalac won't make them static (@inline def only works with -optimize) + // BE REALLY CAREFUL TO KEEP COMMENTS AND NUMBERS IN SYNC! + private[this] var status = 0/*Seek*/ + + def apply(value: A): B = Statics.pfMarker.asInstanceOf[B] + + def hasNext = { + val marker = Statics.pfMarker + while (status == 0/*Seek*/) { + if (self.hasNext) { + val x = self.next() + val v = pf.applyOrElse(x, this) + if (marker ne v.asInstanceOf[AnyRef]) { + hd = v + status = 1/*Found*/ + } + } + else status = -1/*Empty*/ + } + status == 1/*Found*/ + } + def next() = if (hasNext) { status = 0/*Seek*/; hd } else Iterator.empty.next() + } + + /** + * Builds a new iterator from this one without any duplicated elements on it. + * @return iterator with distinct elements + * + * @note Reuse: $consumesIterator + */ + def distinct: Iterator[A] = distinctBy(identity) + + /** + * Builds a new iterator from this one without any duplicated elements as determined by `==` after applying + * the transforming function `f`. + * + * @param f The transforming function whose result is used to determine the uniqueness of each element + * @tparam B the type of the elements after being transformed by `f` + * @return iterator with distinct elements + * + * @note Reuse: $consumesIterator + */ + 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 + private[this] var nextElement: A = _ + + def hasNext: Boolean = nextElementDefined || (self.hasNext && { + val a = self.next() + if (traversedValues.add(f(a))) { + nextElement = a + nextElementDefined = true + true + } + else hasNext + }) + + def next(): A = + if (hasNext) { + nextElementDefined = false + nextElement + } else { + Iterator.empty.next() + } + } + + def map[B](f: A => B): Iterator[B] = 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 + /** Trillium logic boolean: -1 = unknown, 0 = false, 1 = true */ + private[this] var _hasNext: Int = -1 + + private[this] def nextCur(): Unit = { + cur = null + cur = f(self.next()).iterator + _hasNext = -1 + } + + def hasNext: Boolean = { + if (_hasNext == -1) { + while (!cur.hasNext) { + if (!self.hasNext) { + _hasNext = 0 + // since we know we are exhausted, we can release cur for gc, and as well replace with + // static Iterator.empty which will support efficient subsequent `hasNext`/`next` calls + cur = Iterator.empty + return false + } + nextCur() + } + _hasNext = 1 + true + } else _hasNext == 1 + } + def next(): B = { + if (hasNext) { + _hasNext = -1 + } + cur.next() + } + } + + 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) + + @`inline` final def ++ [B >: A](xs: => IterableOnce[B]): Iterator[B] = concat(xs) + + def take(n: Int): Iterator[A] = sliceIterator(0, n max 0) + + def takeWhile(p: A => Boolean): Iterator[A] = new AbstractIterator[A] { + private[this] var hd: A = _ + private[this] var hdDefined: Boolean = false + private[this] var tail: Iterator[A] = self + + def hasNext = hdDefined || tail.hasNext && { + hd = tail.next() + if (p(hd)) hdDefined = true + else tail = Iterator.empty + hdDefined + } + def next() = if (hasNext) { hdDefined = false; hd } else Iterator.empty.next() + } + + def drop(n: Int): Iterator[A] = sliceIterator(n, -1) + + def dropWhile(p: A => Boolean): Iterator[A] = 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 + private[this] var fst: A = _ + def hasNext: Boolean = + if (status == 1) self.hasNext + else if (status == 0) true + else { + while (self.hasNext) { + val a = self.next() + if (!p(a)) { + fst = a + status = 0 + return true + } + } + status = 1 + false + } + def next() = + if (hasNext) { + if (status == 1) self.next() + else { + status = 1 + fst + } + } + else Iterator.empty.next() + } + + /** + * @inheritdoc + * + * @note Reuse: $consumesOneAndProducesTwoIterators + */ + def span(p: A => Boolean): (Iterator[A], Iterator[A]) = { + /* + * Giving a name to following iterator (as opposed to trailing) because + * anonymous class is represented as a structural type that trailing + * iterator is referring (the finish() method) and thus triggering + * handling of structural calls. It's not what's intended here. + */ + final class Leading extends AbstractIterator[A] { + private[this] var lookahead: mutable.Queue[A] = null + private[this] var hd: A = _ + /* Status is kept with magic numbers + * 1 means next element is in hd and we're still reading into this iterator + * 0 means we're still reading but haven't found a next element + * -1 means we are done reading into the iterator, so we must rely on lookahead + * -2 means we are done but have saved hd for the other iterator to use as its first element + */ + private[this] var status = 0 + private def store(a: A): Unit = { + if (lookahead == null) lookahead = new mutable.Queue[A] + lookahead += a + } + def hasNext = { + if (status < 0) (lookahead ne null) && lookahead.nonEmpty + else if (status > 0) true + else { + if (self.hasNext) { + hd = self.next() + status = if (p(hd)) 1 else -2 + } + else status = -1 + status > 0 + } + } + def next() = { + if (hasNext) { + if (status == 1) { status = 0; hd } + else lookahead.dequeue() + } + else Iterator.empty.next() + } + @tailrec + def finish(): Boolean = status match { + case -2 => status = -1 ; true + case -1 => false + case 1 => store(hd) ; status = 0 ; finish() + case 0 => + status = -1 + while (self.hasNext) { + val a = self.next() + if (p(a)) store(a) + else { + hd = a + return true + } + } + false + } + def trailer: A = hd + } + + val leading = new Leading + + val trailing = new AbstractIterator[A] { + private[this] var myLeading = leading + /* Status flag meanings: + * -1 not yet accessed + * 0 single element waiting in leading + * 1 defer to self + * 2 self.hasNext already + * 3 exhausted + */ + private[this] var status = -1 + def hasNext = status match { + case 3 => false + case 2 => true + case 1 => if (self.hasNext) { status = 2 ; true } else { status = 3 ; false } + case 0 => true + case _ => + if (myLeading.finish()) { status = 0 ; true } else { status = 1 ; myLeading = null ; hasNext } + } + def next() = { + if (hasNext) { + if (status == 0) { + status = 1 + val res = myLeading.trailer + myLeading = null + res + } else { + status = 1 + self.next() + } + } + else Iterator.empty.next() + } + } + + (leading, trailing) + } + + def slice(from: Int, until: Int): Iterator[A] = sliceIterator(from, until max 0) + + /** Creates an optionally bounded slice, unbounded if `until` is negative. */ + protected def sliceIterator(from: Int, until: Int): Iterator[A] = { + val lo = from max 0 + val rest = + if (until < 0) -1 // unbounded + else if (until <= lo) 0 // empty + else until - lo // finite + + if (rest == 0) Iterator.empty + else new Iterator.SliceIterator(this, lo, rest) + } + + def zip[B](that: IterableOnce[B]): Iterator[(A, B)] = 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)] { + val thatIterator = that.iterator + override def knownSize = { + val thisSize = self.knownSize + val thatSize = thatIterator.knownSize + if (thisSize < 0 || thatSize < 0) -1 + else thisSize max thatSize + } + def hasNext = self.hasNext || thatIterator.hasNext + def next(): (A1, B) = { + val next1 = self.hasNext + val next2 = thatIterator.hasNext + if(!(next1 || next2)) throw new NoSuchElementException + (if(next1) self.next() else thisElem, if(next2) thatIterator.next() else thatElem) + } + } + + def zipWithIndex: Iterator[(A, Int)] = new AbstractIterator[(A, Int)] { + var idx = 0 + override def knownSize = self.knownSize + def hasNext = self.hasNext + def next() = { + val ret = (self.next(), idx) + idx += 1 + ret + } + } + + /** Checks whether corresponding elements of the given iterable collection + * compare equal (with respect to `==`) to elements of this $coll. + * + * @param that the collection to compare + * @tparam B the type of the elements of collection `that`. + * @return `true` if both collections contain equal elements in the same order, `false` otherwise. + * + * @inheritdoc + */ + def sameElements[B >: A](that: IterableOnce[B]): Boolean = { + val those = that.iterator + while (hasNext && those.hasNext) + if (next() != those.next()) + return false + // At that point we know that *at least one* iterator has no next element + // If *both* of them have no elements then the collections are the same + hasNext == those.hasNext + } + + /** Creates two new iterators that both iterate over the same elements + * as this iterator (in the same order). The duplicate iterators are + * considered equal if they are positioned at the same element. + * + * Given that most methods on iterators will make the original iterator + * unfit for further use, this methods provides a reliable way of calling + * multiple such methods on an iterator. + * + * @return a pair of iterators + * @note The implementation may allocate temporary storage for elements + * iterated by one iterator but not yet by the other. + * @note Reuse: $consumesOneAndProducesTwoIterators + */ + def duplicate: (Iterator[A], Iterator[A]) = { + val gap = new scala.collection.mutable.Queue[A] + var ahead: Iterator[A] = null + class Partner extends AbstractIterator[A] { + override def knownSize: Int = self.synchronized { + val thisSize = self.knownSize + + if (this eq ahead) thisSize + else if (thisSize < 0 || gap.knownSize < 0) -1 + else thisSize + gap.knownSize + } + def hasNext: Boolean = self.synchronized { + (this ne ahead) && !gap.isEmpty || self.hasNext + } + def next(): A = self.synchronized { + if (gap.isEmpty) ahead = this + if (this eq ahead) { + val e = self.next() + gap enqueue e + e + } else gap.dequeue() + } + // to verify partnerhood we use reference equality on gap because + // type testing does not discriminate based on origin. + private def compareGap(queue: scala.collection.mutable.Queue[A]) = gap eq queue + override def hashCode = gap.hashCode() + override def equals(other: Any) = other match { + case x: Partner => x.compareGap(gap) && gap.isEmpty + case _ => super.equals(other) + } + } + (new Partner, new Partner) + } + + /** Returns this iterator with patched values. + * Patching at negative indices is the same as patching starting at 0. + * Patching at indices at or larger than the length of the original iterator appends the patch to the end. + * If more values are replaced than actually exist, the excess is ignored. + * + * @param from The start index from which to patch + * @param patchElems The iterator of patch values + * @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] = + new AbstractIterator[B] { + private[this] var origElems = self + // > 0 => that many more elems from `origElems` before switching to `patchElems` + // 0 => need to drop elems from `origElems` and start using `patchElems` + // -1 => have dropped elems from `origElems`, will be using `patchElems` until it's empty + // and then using what's left of `origElems` after the drop + private[this] var state = if (from > 0) from else 0 + + // checks state and handles 0 => -1 + @inline private[this] def switchToPatchIfNeeded(): Unit = + if (state == 0) { + origElems = origElems drop replaced + state = -1 + } + + def hasNext: Boolean = { + switchToPatchIfNeeded() + origElems.hasNext || patchElems.hasNext + } + + def next(): B = { + switchToPatchIfNeeded() + if (state < 0 /* == -1 */) { + if (patchElems.hasNext) patchElems.next() + else origElems.next() + } + else { + if (origElems.hasNext) { + state -= 1 + origElems.next() + } + else { + state = -1 + patchElems.next() + } + } + } + } + + override def tapEach[U](f: A => U): Iterator[A] = new AbstractIterator[A] { + override def knownSize = self.knownSize + override def hasNext = self.hasNext + override def next() = { + val _next = self.next() + f(_next) + _next + } + } + + /** Converts this iterator to a string. + * + * @return `""` + * @note Reuse: $preservesIterator + */ + override def toString = "" + + @deprecated("Iterator.seq always returns the iterator itself", "2.13.0") + def seq: this.type = this +} + +@SerialVersionUID(3L) +object Iterator extends IterableFactory[Iterator] { + + private[this] val _empty: Iterator[Nothing] = new AbstractIterator[Nothing] { + def hasNext = false + def next() = throw new NoSuchElementException("next on empty iterator") + override def knownSize: Int = 0 + override protected def sliceIterator(from: Int, until: Int) = this + } + + /** Creates a target $coll from an existing source collection + * + * @param source Source collection + * @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 + + /** The iterator which produces no values. */ + @`inline` final def empty[T]: Iterator[T] = _empty + + def single[A](a: A): Iterator[A] = new AbstractIterator[A] { + private[this] var consumed: Boolean = false + def hasNext = !consumed + def next() = if (consumed) empty.next() else { consumed = true; a } + override protected def sliceIterator(from: Int, until: Int) = + if (consumed || from > 0 || until == 0) empty + else this + } + + override def apply[A](xs: A*): Iterator[A] = xs.iterator + + /** + * @return A builder for $Coll objects. + * @tparam A the type of the ${coll}’s elements + */ + def newBuilder[A]: Builder[A, Iterator[A]] = + new ImmutableBuilder[A, Iterator[A]](empty[A]) { + override def addOne(elem: A): this.type = { elems = elems ++ single(elem); this } + } + + /** Creates iterator that produces the results of some element computation a number of times. + * + * @param len the number of elements returned by the 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] { + private[this] var i = 0 + override def knownSize: Int = (len - i) max 0 + def hasNext: Boolean = i < len + def next(): A = + if (hasNext) { i += 1; elem } + else empty.next() + } + + /** Creates an iterator producing the values of a given function over a range of integer values starting from 0. + * + * @param end The number of elements returned by the 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] { + private[this] var i = 0 + override def knownSize: Int = (end - i) max 0 + def hasNext: Boolean = i < end + def next(): A = + if (hasNext) { val result = f(i); i += 1; result } + else empty.next() + } + + /** Creates an infinite-length iterator which returns successive values from some start value. + + * @param start the start value of the iterator + * @return the iterator producing the infinite sequence of values `start, start + 1, start + 2, ...` + */ + def from(start: Int): Iterator[Int] = from(start, 1) + + /** Creates an infinite-length iterator returning values equally spaced apart. + * + * @param start the start value of the iterator + * @param step the increment between successive values + * @return the iterator producing the infinite sequence of values `start, start + 1 * step, start + 2 * step, ...` + */ + def from(start: Int, step: Int): Iterator[Int] = new AbstractIterator[Int] { + private[this] var i = start + def hasNext: Boolean = true + def next(): Int = { val result = i; i += step; result } + } + + /** Creates nn iterator returning successive values in some integer interval. + * + * @param start the start value of the iterator + * @param end the end value of the iterator (the first value NOT returned) + * @return the iterator producing values `start, start + 1, ..., end - 1` + */ + def range(start: Int, end: Int): Iterator[Int] = range(start, end, 1) + + /** An iterator producing equally spaced values in some integer interval. + * + * @param start the start value of the iterator + * @param end the end value of the iterator (the first value NOT returned) + * @param step the increment value of the iterator (must be positive or negative) + * @return the iterator producing values `start, start + step, ...` up to, but excluding `end` + */ + def range(start: Int, end: Int, step: Int): Iterator[Int] = new AbstractIterator[Int] { + if (step == 0) throw new IllegalArgumentException("zero step") + private[this] var i = start + private[this] var hasOverflowed = false + override def knownSize: Int = { + val size = math.ceil((end.toLong - i.toLong) / step.toDouble) + if (size < 0) 0 + else if (size > Int.MaxValue) -1 + else size.toInt + } + def hasNext: Boolean = { + (step <= 0 || i < end) && (step >= 0 || i > end) && !hasOverflowed + } + def next(): Int = + if (hasNext) { + val result = i + val nextValue = i + step + hasOverflowed = (step > 0) == nextValue < i + i = nextValue + result + } + else empty.next() + } + + /** Creates an infinite iterator that repeatedly applies a given function to the previous result. + * + * @param start the start value of the 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] { + private[this] var first = true + private[this] var acc = start + def hasNext: Boolean = true + def next(): T = { + if (first) first = false + else acc = f(acc) + + acc + } + } + + /** Creates an Iterator that uses a function `f` to produce elements of type `A` + * and update an internal state of type `S`. + * + * @param init State initial value + * @param f Computes the next element (or returns `None` to signal + * the end of the collection) + * @tparam A Type of the elements + * @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) + + /** Creates an infinite-length iterator returning the results of evaluating an expression. + * The expression is recomputed for every element. + * + * @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 hasNext = true + def next() = elem + } + + /** 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 var tail: ConcatIteratorCell[A @uncheckedVariance] = null + private var last: ConcatIteratorCell[A @uncheckedVariance] = null + private var currentHasNextChecked = false + + def hasNext = + if (currentHasNextChecked) true + else if (current == null) false + else if (current.hasNext) { + currentHasNextChecked = true + true + } + else { + // 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]] + current = c.current + currentHasNextChecked = c.currentHasNextChecked + if (c.tail != null) { + if (last == null) last = c.last + c.last.tail = tail + tail = c.tail + } + merge() + } + + // Advance current to the next non-empty iterator + // current is set to null when all iterators are exhausted + @tailrec def advance(): Boolean = + if (tail == null) { + current = null + last = null + false + } + else { + current = tail.headIterator + if (last eq tail) last = last.tail + tail = tail.tail + merge() + if (currentHasNextChecked) true + else if (current != null && current.hasNext) { + currentHasNextChecked = true + true + } else advance() + } + + advance() + } + + def next() = + if (hasNext) { + currentHasNextChecked = false + current.next() + } else Iterator.empty.next() + + override def concat[B >: A](that: => IterableOnce[B]): Iterator[B] = { + val c = new ConcatIteratorCell[B](that, null).asInstanceOf[ConcatIteratorCell[A]] + if (tail == null) { + tail = c + last = c + } + else { + last.tail = c + last = c + } + if (current == null) current = Iterator.empty + this + } + } + + private[this] final class ConcatIteratorCell[A](head: => IterableOnce[A], var tail: ConcatIteratorCell[A]) { + def headIterator: Iterator[A] = 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[this] var remaining = limit + private[this] var dropping = start + @inline private def unbounded = remaining < 0 + private def skip(): Unit = + while (dropping > 0) { + if (underlying.hasNext) { + underlying.next() + dropping -= 1 + } else + dropping = 0 + } + override def knownSize: Int = { + val size = underlying.knownSize + if (size < 0) -1 + else { + val dropSize = 0 max (size - dropping) + if (unbounded) dropSize + else remaining min dropSize + } + } + def hasNext = { skip(); remaining != 0 && underlying.hasNext } + def next() = { + skip() + if (remaining > 0) { + remaining -= 1 + underlying.next() + } + else if (unbounded) underlying.next() + else empty.next() + } + override protected def sliceIterator(from: Int, until: Int): Iterator[A] = { + val lo = from max 0 + def adjustedBound = + if (unbounded) -1 + else 0 max (remaining - lo) + val rest = + if (until < 0) adjustedBound // respect current bound, if any + else if (until <= lo) 0 // empty + else if (unbounded) until - lo // now finite + else adjustedBound min (until - lo) // keep lesser bound + if (rest == 0) empty + else { + dropping += lo + remaining = rest + this + } + } + } + + /** Creates an iterator that uses a function `f` to produce elements of + * type `A` and update an internal state of type `S`. + */ + private final class UnfoldIterator[A, S](init: S)(f: S => Option[(A, S)]) extends AbstractIterator[A] { + private[this] var state: S = init + private[this] var nextResult: Option[(A, S)] = null + + override def hasNext: Boolean = { + if (nextResult eq null) { + nextResult = { + val res = f(state) + if (res eq null) throw new NullPointerException("null during unfold") + res + } + state = null.asInstanceOf[S] // allow GC + } + nextResult.isDefined + } + + override def next(): A = { + if (hasNext) { + val (value, newState) = nextResult.get + state = newState + nextResult = null + value + } else Iterator.empty.next() + } + } +} + +/** Explicit instantiation of the `Iterator` trait to reduce class file size in subclasses. */ +abstract class AbstractIterator[+A] extends Iterator[A] From a882fe967db09a50fa901480228624b6679c4017 Mon Sep 17 00:00:00 2001 From: odersky Date: Thu, 13 Jul 2023 15:44:19 +0200 Subject: [PATCH 10/37] Capture checked versions of Iterator and IterableOnce --- .../captures/stdlib/IterableOnce.scala | 1 + .../captures/stdlib/Iterator.scala | 1 + .../stdlib/collection/IterableOnce.scala | 120 ++++++++++-------- .../captures/stdlib/collection/Iterator.scala | 118 +++++++++-------- .../captures/stdlib/runtime/PStatics.scala | 19 +++ .../captures/stdlib/runtime_PStatics.scala | 1 + 6 files changed, 150 insertions(+), 110 deletions(-) create mode 120000 tests/pos-custom-args/captures/stdlib/IterableOnce.scala create mode 120000 tests/pos-custom-args/captures/stdlib/Iterator.scala create mode 100644 tests/pos-custom-args/captures/stdlib/runtime/PStatics.scala create mode 120000 tests/pos-custom-args/captures/stdlib/runtime_PStatics.scala diff --git a/tests/pos-custom-args/captures/stdlib/IterableOnce.scala b/tests/pos-custom-args/captures/stdlib/IterableOnce.scala new file mode 120000 index 000000000000..0022157fb0e7 --- /dev/null +++ b/tests/pos-custom-args/captures/stdlib/IterableOnce.scala @@ -0,0 +1 @@ +collection/IterableOnce.scala \ No newline at end of file diff --git a/tests/pos-custom-args/captures/stdlib/Iterator.scala b/tests/pos-custom-args/captures/stdlib/Iterator.scala new file mode 120000 index 000000000000..9f998ff25601 --- /dev/null +++ b/tests/pos-custom-args/captures/stdlib/Iterator.scala @@ -0,0 +1 @@ +collection/Iterator.scala \ No newline at end of file diff --git a/tests/pos-custom-args/captures/stdlib/collection/IterableOnce.scala b/tests/pos-custom-args/captures/stdlib/collection/IterableOnce.scala index 65d8dce08ae4..359f6ad5226c 100644 --- a/tests/pos-custom-args/captures/stdlib/collection/IterableOnce.scala +++ b/tests/pos-custom-args/captures/stdlib/collection/IterableOnce.scala @@ -20,6 +20,7 @@ import scala.language.implicitConversions import scala.math.{Numeric, Ordering} import scala.reflect.ClassTag import scala.runtime.AbstractFunction2 +import language.experimental.captureChecking /** * A template trait for collections which can be traversed either once only @@ -42,8 +43,10 @@ import scala.runtime.AbstractFunction2 * @define coll collection */ trait IterableOnce[+A] extends Any { + this: IterableOnce[A]^ => + /** Iterator can be used only once */ - def iterator: Iterator[A] + def iterator: Iterator[A]^{this} /** Returns a [[scala.collection.Stepper]] for the elements of this collection. * @@ -65,9 +68,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]]) @@ -84,7 +87,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) @@ -102,7 +105,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) @@ -120,7 +123,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 @@ -132,7 +135,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) @@ -238,13 +241,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 { + def map[B](f: A => B): IterableOnce[B]^{f} = it match { case it: Iterable[A] => 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) } @@ -315,9 +318,11 @@ object IterableOnce { * @define coll collection * */ -trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOnce[A] => +trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOnce[A]^ => /////////////////////////////////////////////////////////////// Abstract methods that must be implemented + import IterableOnceOps.Maximized + /** Produces a $coll containing cumulative results of applying the * operator going left to right, including the initial value. * @@ -329,7 +334,7 @@ trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOnce[A] => * @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. * @@ -337,7 +342,7 @@ trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOnce[A] => * @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. * @@ -345,7 +350,7 @@ trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOnce[A] => * @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(p: A => Boolean): C^{this, p} /** Selects the first ''n'' elements. * $orderDependent @@ -354,7 +359,7 @@ trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOnce[A] => * 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} /** Takes longest prefix of elements that satisfy a predicate. * $orderDependent @@ -362,7 +367,7 @@ trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOnce[A] => * @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 first ''n'' ones. * $orderDependent @@ -371,7 +376,7 @@ trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOnce[A] => * 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} /** Drops longest prefix of elements that satisfy a predicate. * $orderDependent @@ -379,7 +384,7 @@ trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOnce[A] => * @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: @@ -394,7 +399,7 @@ trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOnce[A] => * 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 $coll by applying a function to all elements of this $coll. * @@ -403,7 +408,7 @@ trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOnce[A] => * @return a new $coll 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 $coll by applying a function to all elements of this $coll * and using the elements of the resulting collections. @@ -436,7 +441,7 @@ trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOnce[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]): CC[B] + def flatMap[B](f: A => IterableOnce[B]): CC[B]^{this, f} /** Converts this $coll of iterable collections into * a $coll formed by the elements of these iterable @@ -464,7 +469,7 @@ trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOnce[A] => * type of this $coll is an `Iterable`. * @return a new $coll resulting from concatenating all element ${coll}s. */ - def flatten[B](implicit asIterable: A => IterableOnce[B]): CC[B] + def flatten[B](implicit asIterable: A -> IterableOnce[B]): CC[B]^{this} /** Builds a new $coll by applying a partial function to all elements of this $coll * on which the function is defined. @@ -475,7 +480,7 @@ trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOnce[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: PartialFunction[A, B]): CC[B] + def collect[B](pf: PartialFunction[A, B]^): CC[B]^{this, pf} /** Zips this $coll with its indices. * @@ -484,7 +489,7 @@ trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOnce[A] => * @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. * @@ -497,7 +502,7 @@ trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOnce[A] => * @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, p}, C^{this, p}) /** Splits this $coll into a prefix/suffix pair at a given position. * @@ -509,7 +514,7 @@ trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOnce[A] => * @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 } @@ -527,7 +532,7 @@ trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOnce[A] => * @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 @@ -802,7 +807,7 @@ trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOnce[A] => 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) @@ -1041,35 +1046,12 @@ trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOnce[A] => * @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]] { - var maxElem: X = null.asInstanceOf[X] - var maxF: B = null.asInstanceOf[B] - var nonEmpty = false - def toOption: Option[X] = if (nonEmpty) Some(maxElem) else None - def result: X = if (nonEmpty) maxElem else throw new UnsupportedOperationException(s"empty.$descriptor") - def apply(m: Maximized[X, B], a: X): Maximized[X, B] = - if (m.nonEmpty) { - val fa = f(a) - if (cmp(fa, maxF)) { - maxF = fa - maxElem = a - } - m - } - else { - m.nonEmpty = true - m.maxElem = a - m.maxF = f(a) - m - } - } - /** Finds the first element which yields the largest value measured by function f. * * $willNotTerminateInf @@ -1080,7 +1062,7 @@ trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOnce[A] => * @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 @@ -1097,7 +1079,7 @@ trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOnce[A] => * @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 @@ -1114,7 +1096,7 @@ trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOnce[A] => * 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 @@ -1310,7 +1292,7 @@ trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOnce[A] => 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 def toList: immutable.List[A] = immutable.List.from(this) @@ -1352,3 +1334,31 @@ trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOnce[A] => xs } } + +object IterableOnceOps: + + // Moved out of trait IterableOnceOps to here, since universal traits cannot + // have nested classes in Scala 3 + 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 + def toOption: Option[X] = if (nonEmpty) Some(maxElem) else None + def result: X = if (nonEmpty) maxElem else throw new UnsupportedOperationException(s"empty.$descriptor") + def apply(m: Maximized[X, B], a: X): Maximized[X, B] = + if (m.nonEmpty) { + val fa = f(a) + if (cmp(fa, maxF)) { + maxF = fa + maxElem = a + } + m + } + else { + m.nonEmpty = true + m.maxElem = a + m.maxF = f(a) + m + } + } +end IterableOnceOps \ No newline at end of file diff --git a/tests/pos-custom-args/captures/stdlib/collection/Iterator.scala b/tests/pos-custom-args/captures/stdlib/collection/Iterator.scala index 4b8338ed1b17..24bdeb2a3dca 100644 --- a/tests/pos-custom-args/captures/stdlib/collection/Iterator.scala +++ b/tests/pos-custom-args/captures/stdlib/collection/Iterator.scala @@ -16,6 +16,9 @@ import scala.collection.mutable.{ArrayBuffer, ArrayBuilder, Builder, ImmutableBu import scala.annotation.tailrec import scala.annotation.unchecked.uncheckedVariance import scala.runtime.Statics +import language.experimental.captureChecking +import caps.unsafe.unsafeAssumePure + /** Iterators are data structures that allow to iterate over a sequence * of elements. They have a `hasNext` method for checking @@ -71,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. * @@ -93,7 +97,7 @@ trait Iterator[+A] extends IterableOnce[A] with IterableOnceOps[A, Iterator, Ite @throws[NoSuchElementException] def next(): A - @inline final def iterator = this + @inline final def iterator: Iterator[A]^{this} = this /** Wraps the value of `next()` in an option. * @@ -117,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 @@ -153,7 +157,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") @@ -162,7 +166,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] @@ -185,7 +189,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 @@ -291,7 +295,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 = { @@ -321,7 +325,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]^{this, p}, Iterator[A]^{this, p}) = { val (a, b) = duplicate (a filter p, b filterNot p) } @@ -341,7 +345,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 @@ -377,13 +381,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]^{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] = + private[this] var current: Iterator[B]^{self, op} = new AbstractIterator[B] { override def knownSize = { val thisSize = self.knownSize @@ -412,7 +416,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} = ArrayBuffer.from(this).scanRight(z)(op).iterator def indexWhere(p: A => Boolean, from: Int = 0): Int = { var i = math.max(from, 0) @@ -465,11 +469,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]^{this, p} = filterImpl(p, isFlipped = false) - def filterNot(p: A => Boolean): Iterator[A] = 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] = 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 @@ -479,9 +483,9 @@ trait Iterator[+A] extends IterableOnce[A] with IterableOnceOps[A, Iterator, Ite while (p(hd) == isFlipped) { if (!self.hasNext) return false hd = self.next() - } + } hdDefined = true - true + true } def next() = @@ -503,9 +507,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]^{this, 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]^{this, pf} = new AbstractIterator[B] with (A -> B) { // Manually buffer to avoid extra layer of wrapping with buffered private[this] var hd: B = _ @@ -541,7 +545,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 @@ -553,7 +557,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 @@ -578,13 +582,13 @@ 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]^{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] = new AbstractIterator[B] { + def flatMap[B](f: A => IterableOnce[B]): Iterator[B]^{this, f} = new AbstractIterator[B] { private[this] var cur: Iterator[B] = Iterator.empty /** Trillium logic boolean: -1 = unknown, 0 = false, 1 = true */ private[this] var _hasNext: Int = -1 @@ -619,19 +623,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]^{this} = 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]^{self, 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() @@ -642,9 +646,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]^{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 @@ -680,7 +684,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]^{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 @@ -779,10 +783,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 @@ -793,14 +797,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)] = 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 @@ -817,7 +821,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 @@ -860,7 +864,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] { @@ -904,7 +908,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` @@ -944,7 +948,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]^{this, f} = new AbstractIterator[A] { override def knownSize = self.knownSize override def hasNext = self.hasNext override def next() = { @@ -981,7 +985,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 @@ -1012,7 +1016,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 @@ -1027,7 +1031,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 @@ -1100,7 +1104,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 @@ -1122,7 +1126,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. @@ -1130,7 +1134,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 } @@ -1138,7 +1142,10 @@ 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]^) extends AbstractIterator[A] { + private var current: Iterator[A] = from.unsafeAssumePure + // This should be Iteratpr[A]^, but fails since mutable variables can't capture cap. + // To do better we'd need to track nesting levels for universal capabiltities. private var tail: ConcatIteratorCell[A @uncheckedVariance] = null private var last: ConcatIteratorCell[A @uncheckedVariance] = null private var currentHasNextChecked = false @@ -1194,7 +1201,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 @@ -1209,14 +1216,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 // CC todo: can't use {head} as capture set, gives "cannot establish a reference" } /** 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 @@ -1247,7 +1254,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]^{underlying} = { val lo = from max 0 def adjustedBound = if (unbounded) -1 @@ -1269,7 +1276,7 @@ object Iterator extends IterableFactory[Iterator] { /** Creates an iterator that uses a function `f` to produce elements of * type `A` and update an internal state of type `S`. */ - private final class UnfoldIterator[A, S](init: S)(f: S => Option[(A, S)]) extends AbstractIterator[A] { + private final class UnfoldIterator[A, S](init: S)(f: S => Option[(A, S)])extends AbstractIterator[A] { private[this] var state: S = init private[this] var nextResult: Option[(A, S)] = null @@ -1297,4 +1304,5 @@ object Iterator extends IterableFactory[Iterator] { } /** Explicit instantiation of the `Iterator` trait to reduce class file size in subclasses. */ -abstract class AbstractIterator[+A] extends Iterator[A] +abstract class AbstractIterator[+A] extends Iterator[A]: + this: Iterator[A]^ => diff --git a/tests/pos-custom-args/captures/stdlib/runtime/PStatics.scala b/tests/pos-custom-args/captures/stdlib/runtime/PStatics.scala new file mode 100644 index 000000000000..788a56962855 --- /dev/null +++ b/tests/pos-custom-args/captures/stdlib/runtime/PStatics.scala @@ -0,0 +1,19 @@ +/* + * Scala (https://www.scala-lang.org) + * + * Copyright EPFL and Lightbend, Inc. + * + * 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.runtime + +// things that should be in `Statics`, but can't be yet for bincompat reasons +// TODO 3.T: move to `Statics` +private[scala] object PStatics { + final val VM_MaxArraySize = 2147483645 // == `Int.MaxValue - 2`, hotspot limit +} diff --git a/tests/pos-custom-args/captures/stdlib/runtime_PStatics.scala b/tests/pos-custom-args/captures/stdlib/runtime_PStatics.scala new file mode 120000 index 000000000000..5937e46f6765 --- /dev/null +++ b/tests/pos-custom-args/captures/stdlib/runtime_PStatics.scala @@ -0,0 +1 @@ +runtime/PStatics.scala \ No newline at end of file From c0bcfbe41436dd8e11fc6e05390d58fe961656b1 Mon Sep 17 00:00:00 2001 From: odersky Date: Thu, 13 Jul 2023 16:15:53 +0200 Subject: [PATCH 11/37] Drop redundant statements --- compiler/src/dotty/tools/dotc/transform/Pickler.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/transform/Pickler.scala b/compiler/src/dotty/tools/dotc/transform/Pickler.scala index 84aae572971a..3c09b75b9a35 100644 --- a/compiler/src/dotty/tools/dotc/transform/Pickler.scala +++ b/compiler/src/dotty/tools/dotc/transform/Pickler.scala @@ -173,7 +173,6 @@ class Pickler extends Phase { cls -> (unit, unpickler) } pickling.println("************* entered toplevel ***********") - val rootCtx = ctx for ((cls, (unit, unpickler)) <- unpicklers) do ctx.compilationUnit.needsCaptureChecking = unit.needsCaptureChecking val unpickled = unpickler.rootTrees From 714606c288c7e8278f87383709bd4369305ae5bd Mon Sep 17 00:00:00 2001 From: odersky Date: Thu, 13 Jul 2023 19:52:24 +0200 Subject: [PATCH 12/37] Update MimaFilters --- project/MiMaFilters.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/project/MiMaFilters.scala b/project/MiMaFilters.scala index 98dbf4859d92..08426f55d149 100644 --- a/project/MiMaFilters.scala +++ b/project/MiMaFilters.scala @@ -15,6 +15,7 @@ object MiMaFilters { ProblemFilters.exclude[MissingClassProblem]("scala.runtime.stdLibPatches.language$experimental$relaxedExtensionImports$"), ProblemFilters.exclude[DirectMissingMethodProblem]("scala.runtime.Tuples.reverse"), ProblemFilters.exclude[MissingFieldProblem]("scala.Tuple.Helpers"), + ProblemFilters.exclude[MissingClassProblem]("scala.annotation.internal.WithCaptureChecks"), // end of New experimental features in 3.3.X ) val TastyCore: Seq[ProblemFilter] = Seq( From 08deac1cc1d2f5f169f7383f94789432d7ce5bd4 Mon Sep 17 00:00:00 2001 From: odersky Date: Thu, 13 Jul 2023 22:31:21 +0200 Subject: [PATCH 13/37] Fix rebase breakage --- compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index c022ff1c248a..c61919f47146 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -298,7 +298,7 @@ class CheckCaptures extends Recheck, SymTransformer: mapOver(tp) case tp: TypeLambda => tp.derivedLambdaType(resType = this(tp.resType)) - case tp @ RefinedType(parent, rname, rinfo: MethodType) if defn.isFunctionOrPolyType(tp) => + case tp @ RefinedType(parent, rname, rinfo: MethodType) if defn.isFunctionType(tp) => tp.derivedRefinedType(parent, rname, this(rinfo)) case tp @ AppliedType(tycon, args) if defn.isNonRefinedFunction(tp) => mapOver(tp) From 9734a36dbf34c2c24161b438f32d018d96d65a8d Mon Sep 17 00:00:00 2001 From: odersky Date: Thu, 13 Jul 2023 22:36:47 +0200 Subject: [PATCH 14/37] Fix more rebase breakage --- compiler/src/dotty/tools/dotc/cc/CaptureSet.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala index c57be2720ae6..3f2beaa3ff55 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala @@ -877,7 +877,7 @@ object CaptureSet: case CapturingType(parent, refs) => recur(parent) ++ refs case tpd @ RefinedType(parent, _, rinfo: MethodType) - if followResult && defn.isFunctionType(tpd) => + if followResult && defn.isFunctionNType(tpd) => ofType(parent, followResult = false) // pick up capture set from parent type ++ (recur(rinfo.resType) // add capture set of result -- CaptureSet(rinfo.paramRefs.filter(_.isTracked)*)) // but disregard bound parameters From 8140273600eda8c2cf2988eb344fcb4156824979 Mon Sep 17 00:00:00 2001 From: odersky Date: Thu, 13 Jul 2023 22:44:26 +0200 Subject: [PATCH 15/37] Test for #16415 --- tests/pos-custom-args/captures/i16415.scala | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 tests/pos-custom-args/captures/i16415.scala diff --git a/tests/pos-custom-args/captures/i16415.scala b/tests/pos-custom-args/captures/i16415.scala new file mode 100644 index 000000000000..aede36a15be8 --- /dev/null +++ b/tests/pos-custom-args/captures/i16415.scala @@ -0,0 +1,8 @@ +abstract class A[X]: + def foo(x: X): X + +class IO +class C +def test(io: IO^) = + class B extends A[C^{io}]: // error, but should work + override def foo(x: C^{io}): C^{io} = ??? From 12735bd8e5a9897dd91fd897e7f143dadceb47ac Mon Sep 17 00:00:00 2001 From: odersky Date: Thu, 13 Jul 2023 23:56:44 +0200 Subject: [PATCH 16/37] Enable test that failed in #18168 --- tests/pos-custom-args/captures/i13816.scala | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/pos-custom-args/captures/i13816.scala b/tests/pos-custom-args/captures/i13816.scala index b6e15023752b..0ba84ef84c64 100644 --- a/tests/pos-custom-args/captures/i13816.scala +++ b/tests/pos-custom-args/captures/i13816.scala @@ -37,15 +37,11 @@ def foo7(i: Int)(using CanThrow[Ex1]): Unit throws Ex1 | Ex2 = def foo8(i: Int)(using CanThrow[Ex2]): Unit throws Ex2 | Ex1 = if i > 0 then throw new Ex1 else throw new Ex2 -/** Does not work yet since the type of the rhs is not hygienic - def foo9(i: Int): Unit throws Ex1 | Ex2 | Ex3 = if i > 0 then throw new Ex1 else if i < 0 then throw new Ex2 else throw new Ex3 -*/ - def test(): Unit = try foo1(1) From 25104d1edf7450a772342ef767762af425715683 Mon Sep 17 00:00:00 2001 From: odersky Date: Sat, 15 Jul 2023 09:49:39 +0200 Subject: [PATCH 17/37] Improve fluidify - Also add Fluid to function types - Add Fluid recursively in arguments of fluidified types - But stop when a capturing type is encountered Also, fix needsVariable for FromJavaObject. FromJavaObject should behave like Any (i.e. no capture set variable needs to be added). --- .../dotty/tools/dotc/cc/CheckCaptures.scala | 25 +++++++++++-------- compiler/src/dotty/tools/dotc/cc/Setup.scala | 18 +++++++------ tests/pos-custom-args/captures/foreach.scala | 3 +-- tests/pos/cc-backwards-compat/A.scala | 3 ++- tests/pos/cc-backwards-compat/Iter.scala | 6 +++-- 5 files changed, 31 insertions(+), 24 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index c61919f47146..c442d6ae4963 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -291,20 +291,23 @@ class CheckCaptures extends Recheck, SymTransformer: if sym.exists && curEnv.isOpen then markFree(capturedVars(sym), pos) private def handleBackwardsCompat(tp: Type, sym: Symbol, initialVariance: Int = 1)(using Context): Type = - val fluidify = new TypeMap: + val fluidify = new TypeMap with IdempotentCaptRefMap: variance = initialVariance def apply(t: Type): Type = t match - case tp: MethodType => - mapOver(tp) - case tp: TypeLambda => - tp.derivedLambdaType(resType = this(tp.resType)) - case tp @ RefinedType(parent, rname, rinfo: MethodType) if defn.isFunctionType(tp) => - tp.derivedRefinedType(parent, rname, this(rinfo)) - case tp @ AppliedType(tycon, args) if defn.isNonRefinedFunction(tp) => - mapOver(tp) + case t: MethodType => + mapOver(t) + case t: TypeLambda => + t.derivedLambdaType(resType = this(t.resType)) + case CapturingType(_, _) => + t case _ => - if variance > 0 then t - else Setup.decorate(t, Function.const(CaptureSet.Fluid)) + val t1 = t match + case t @ RefinedType(parent, rname, rinfo: MethodType) if defn.isFunctionType(t) => + t.derivedRefinedType(parent, rname, this(rinfo)) + case _ => + mapOver(t) + if variance > 0 then t1 + else Setup.decorate(t1, Function.const(CaptureSet.Fluid)) def isPreCC(sym: Symbol): Boolean = sym.isTerm && sym.maybeOwner.isClass diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index 97cee79fe1ca..fb49cced25cb 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -418,15 +418,17 @@ object Setup: def needsVariable(tp: Type)(using Context): Boolean = { tp.typeParams.isEmpty && tp.match case tp: (TypeRef | AppliedType) => - val tp1 = tp.dealias - if tp1 ne tp then needsVariable(tp1) + val sym = tp.typeSymbol + if sym.isClass then + !sym.isPureClass && sym != defn.AnyClass else - val sym = tp1.typeSymbol - if sym.isClass then - !sym.isPureClass - && sym != defn.AnyClass - && sym != defn.FromJavaObjectSymbol - else superTypeIsImpure(tp1) + sym != defn.FromJavaObjectSymbol + // For capture checking, we assume Object from Java is the same as Any + && { + val tp1 = tp.dealias + if tp1 ne tp then needsVariable(tp1) + else superTypeIsImpure(tp1) + } case tp: (RefinedOrRecType | MatchType) => needsVariable(tp.underlying) case tp: AndType => diff --git a/tests/pos-custom-args/captures/foreach.scala b/tests/pos-custom-args/captures/foreach.scala index b7dfc49272a9..0656025da657 100644 --- a/tests/pos-custom-args/captures/foreach.scala +++ b/tests/pos-custom-args/captures/foreach.scala @@ -1,4 +1,3 @@ -import caps.unsafe.* def test = val tasks = new collection.mutable.ArrayBuffer[() => Unit] - val _: Unit = tasks.foreach(((task: () => Unit) => task()).unsafeBoxFunArg) + val _: Unit = tasks.foreach(((task: () => Unit) => task())) diff --git a/tests/pos/cc-backwards-compat/A.scala b/tests/pos/cc-backwards-compat/A.scala index 29c817f8115d..90280bf3d1a0 100644 --- a/tests/pos/cc-backwards-compat/A.scala +++ b/tests/pos/cc-backwards-compat/A.scala @@ -1,4 +1,5 @@ package p -class A: +class A(f: Int => Int): + def foo(f: Int => Int) = ??? def map(other: Iter): Iter = other def pair[T](x: T): (T, T) = (x, x) diff --git a/tests/pos/cc-backwards-compat/Iter.scala b/tests/pos/cc-backwards-compat/Iter.scala index dee72ddfcf4d..e2f0775a058f 100644 --- a/tests/pos/cc-backwards-compat/Iter.scala +++ b/tests/pos/cc-backwards-compat/Iter.scala @@ -5,6 +5,8 @@ class Iter: self: Iter^ => def test(it: Iter^) = - val a = A() - //val b = a.map(it) // does not work yet + val f: Int ->{it} Int = ??? + val a = new A(f) + val b = a.map(it) // does not work yet val c = a.pair(it) + val d = a.foo(f) From e104f8e31558bf534867d588a2ed98467a75ead6 Mon Sep 17 00:00:00 2001 From: odersky Date: Sat, 15 Jul 2023 11:09:59 +0200 Subject: [PATCH 18/37] Exclude AnyVal from set of pure base classes User-defined value classes should be able to capture capabilities --- compiler/src/dotty/tools/dotc/core/Definitions.scala | 5 +++-- tests/neg-custom-args/captures/exception-definitions.check | 4 ---- tests/neg-custom-args/captures/exception-definitions.scala | 2 +- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index cf42e2d70312..a007e41c4c1d 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -1388,11 +1388,12 @@ class Definitions { /** Base classes that are assumed to be pure for the purposes of capture checking. * Every class inheriting from a pure baseclass is pure. */ - @tu lazy val pureBaseClasses = Set(defn.AnyValClass, defn.ThrowableClass) + @tu lazy val pureBaseClasses = Set(defn.ThrowableClass) /** Non-inheritable lasses that are assumed to be pure for the purposes of capture checking, */ - @tu lazy val pureSimpleClasses = Set(StringClass, NothingClass, NullClass) + @tu lazy val pureSimpleClasses = + Set(StringClass, NothingClass, NullClass) ++ ScalaValueClasses() @tu lazy val AbstractFunctionType: Array[TypeRef] = mkArityArray("scala.runtime.AbstractFunction", MaxImplementedFunctionArity, 0).asInstanceOf[Array[TypeRef]] val AbstractFunctionClassPerRun: PerRun[Array[Symbol]] = new PerRun(AbstractFunctionType.map(_.symbol.asClass)) diff --git a/tests/neg-custom-args/captures/exception-definitions.check b/tests/neg-custom-args/captures/exception-definitions.check index deec9343e58b..189a6f091c0b 100644 --- a/tests/neg-custom-args/captures/exception-definitions.check +++ b/tests/neg-custom-args/captures/exception-definitions.check @@ -3,10 +3,6 @@ |^ |reference (caps.cap : Any) is not included in allowed capture set {} of pure base class class Throwable 3 | self: Err^ => --- Error: tests/neg-custom-args/captures/exception-definitions.scala:10:6 ---------------------------------------------- -10 |class Err4(c: Any^) extends AnyVal // error - |^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - |reference (Err4.this.c : Any^) is not included in allowed capture set {} of pure base class class AnyVal -- Error: tests/neg-custom-args/captures/exception-definitions.scala:7:12 ---------------------------------------------- 7 | val x = c // error | ^ diff --git a/tests/neg-custom-args/captures/exception-definitions.scala b/tests/neg-custom-args/captures/exception-definitions.scala index 996f64ae4bd1..a19b751825b8 100644 --- a/tests/neg-custom-args/captures/exception-definitions.scala +++ b/tests/neg-custom-args/captures/exception-definitions.scala @@ -7,6 +7,6 @@ def test(c: Any^) = val x = c // error class Err3(c: Any^) extends Exception // error -class Err4(c: Any^) extends AnyVal // error +class Err4(c: Any^) extends AnyVal // was error, now ok From 1a30250bb09688308c70b30b3e5fdd4e94071629 Mon Sep 17 00:00:00 2001 From: odersky Date: Sun, 16 Jul 2023 11:53:19 +0200 Subject: [PATCH 19/37] Make assigned types of inlined expressions InferredTypes Otherwise we force purity where none might exist. --- .../src/dotty/tools/dotc/inlines/Inliner.scala | 2 +- .../captures/inlined-closure.scala | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 tests/pos-custom-args/captures/inlined-closure.scala diff --git a/compiler/src/dotty/tools/dotc/inlines/Inliner.scala b/compiler/src/dotty/tools/dotc/inlines/Inliner.scala index 87ef7cb93e76..bbfaf3bae1c9 100644 --- a/compiler/src/dotty/tools/dotc/inlines/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/inlines/Inliner.scala @@ -596,7 +596,7 @@ class Inliner(val call: tpd.Tree)(using Context): val inlinedSingleton = singleton(t).withSpan(argSpan) inlinedFromOutside(inlinedSingleton)(tree.span) case Some(t) if tree.isType => - inlinedFromOutside(TypeTree(t).withSpan(argSpan))(tree.span) + inlinedFromOutside(new InferredTypeTree().withType(t).withSpan(argSpan))(tree.span) case _ => tree } case tree @ Select(qual: This, name) if tree.symbol.is(Private) && tree.symbol.isInlineMethod => diff --git a/tests/pos-custom-args/captures/inlined-closure.scala b/tests/pos-custom-args/captures/inlined-closure.scala new file mode 100644 index 000000000000..86be577334f6 --- /dev/null +++ b/tests/pos-custom-args/captures/inlined-closure.scala @@ -0,0 +1,18 @@ +class ContextClass +type Context = ContextClass^ +class ParamRef: + def isTracked(using Context): Boolean = ??? +trait Lam[PR <: ParamRef]: + val paramRefs: List[PR] = ??? +inline def atPhase[T]()(inline op: Context ?=> T)(using ctx: Context): T = + op(using ctx) + +def Test(using ctx: Context) = + val info: Lam[ParamRef] = ??? + info.paramRefs.filter(_.isTracked) + val p = atPhase()((_: ParamRef).isTracked) + val _: ParamRef ->{ctx} Boolean = p + + //val f: String => ParamRef = ??? + //val q = f.andThen(p) + From 6c949a999ea174d9ce7ccc9d53fd2b9f9582f0bb Mon Sep 17 00:00:00 2001 From: odersky Date: Sun, 16 Jul 2023 14:25:46 +0200 Subject: [PATCH 20/37] Special treatment of types of function members Members andThen, compose, curried, tupled of function types are now given special types for capture checking that reflect fine-grained capture dependencies. --- .../src/dotty/tools/dotc/cc/Synthetics.scala | 47 ++++++++++++++++++- .../src/dotty/tools/dotc/core/StdNames.scala | 6 ++- .../captures/function-combinators.scala | 28 +++++++++++ .../captures/inlined-closure.scala | 4 -- 4 files changed, 78 insertions(+), 7 deletions(-) create mode 100644 tests/pos-custom-args/captures/function-combinators.scala diff --git a/compiler/src/dotty/tools/dotc/cc/Synthetics.scala b/compiler/src/dotty/tools/dotc/cc/Synthetics.scala index 5fe68dd6a7ac..e1c0a4272276 100644 --- a/compiler/src/dotty/tools/dotc/cc/Synthetics.scala +++ b/compiler/src/dotty/tools/dotc/cc/Synthetics.scala @@ -10,8 +10,8 @@ import NameKinds.DefaultGetterName import Phases.checkCapturesPhase import config.Printers.capt -/** Classification and transformation methods for synthetic - * case class methods that need to be treated specially. +/** Classification and transformation methods for function methods and + * synthetic case class methods that need to be treated specially. * In particular, compute capturing types for some of these methods which * have inferred (result-)types that need to be established under separate * compilation. @@ -27,6 +27,9 @@ object Synthetics: case DefaultGetterName(nme.copy, _) => sym.is(Synthetic) && sym.owner.isClass && sym.owner.is(Case) case _ => false + private val functionCombinatorNames = Set[Name]( + nme.andThen, nme.compose, nme.curried, nme.tupled) + /** Is `sym` a synthetic apply, copy, or copy default getter method? * The types of these symbols are transformed in a special way without * looking at the definitions's RHS @@ -37,6 +40,7 @@ object Synthetics: || isSyntheticCopyDefaultGetterMethod(symd) || (symd.symbol eq defn.Object_eq) || (symd.symbol eq defn.Object_ne) + || defn.isFunctionClass(symd.owner) && functionCombinatorNames.contains(symd.name) /** Method is excluded from regular capture checking. * Excluded are synthetic class members @@ -156,6 +160,37 @@ object Synthetics: case info: PolyType => info.derivedLambdaType(resType = dropUnapplyCaptures(info.resType)) + private def transformComposeCaptures(symd: SymDenotation, toCC: Boolean)(using Context): Type = + val (pt: PolyType) = symd.info: @unchecked + val (mt: MethodType) = pt.resType: @unchecked + val (enclThis: ThisType) = symd.owner.thisType: @unchecked + val mt1 = + if toCC then + MethodType(mt.paramNames)( + mt1 => mt.paramInfos.map(_.capturing(CaptureSet.universal)), + mt1 => CapturingType(mt.resType, CaptureSet(enclThis, mt1.paramRefs.head))) + else + MethodType(mt.paramNames)( + mt1 => mt.paramInfos.map(_.stripCapturing), + mt1 => mt.resType.stripCapturing) + pt.derivedLambdaType(resType = mt1) + + def transformCurriedTupledCaptures(symd: SymDenotation, toCC: Boolean)(using Context): Type = + val (et: ExprType) = symd.info: @unchecked + val (enclThis: ThisType) = symd.owner.thisType: @unchecked + def mapFinalResult(tp: Type, f: Type => Type): Type = + val defn.FunctionOf(args, res, isContextual) = tp: @unchecked + if defn.isFunctionNType(res) then + defn.FunctionOf(args, mapFinalResult(res, f), isContextual) + else + f(tp) + val resType1 = + if toCC then + mapFinalResult(et.resType, CapturingType(_, CaptureSet(enclThis))) + else + et.resType.stripCapturing + ExprType(resType1) + /** If `sym` refers to a synthetic apply, unapply, copy, or copy default getter method * of a case class, transform it to account for capture information. * The method is run in phase CheckCaptures.Pre @@ -168,6 +203,10 @@ object Synthetics: sym.copySymDenotation(info = addUnapplyCaptures(sym.info)) case nme.apply | nme.copy => sym.copySymDenotation(info = addCaptureDeps(sym.info)) + case nme.andThen | nme.compose => + sym.copySymDenotation(info = transformComposeCaptures(sym, toCC = true)) + case nme.curried | nme.tupled => + sym.copySymDenotation(info = transformCurriedTupledCaptures(sym, toCC = true)) case n if n == nme.eq || n == nme.ne => sym.copySymDenotation(info = MethodType(defn.ObjectType.capturing(CaptureSet.universal) :: Nil, defn.BooleanType)) @@ -183,6 +222,10 @@ object Synthetics: sym.copySymDenotation(info = dropUnapplyCaptures(sym.info)) case nme.apply | nme.copy => sym.copySymDenotation(info = dropCaptureDeps(sym.info)) + case nme.andThen | nme.compose => + sym.copySymDenotation(info = transformComposeCaptures(sym, toCC = false)) + case nme.curried | nme.tupled => + sym.copySymDenotation(info = transformCurriedTupledCaptures(sym, toCC = false)) case n if n == nme.eq || n == nme.ne => sym.copySymDenotation(info = defn.methOfAnyRef(defn.BooleanType)) diff --git a/compiler/src/dotty/tools/dotc/core/StdNames.scala b/compiler/src/dotty/tools/dotc/core/StdNames.scala index cd9526b27a21..b392c0ec0467 100644 --- a/compiler/src/dotty/tools/dotc/core/StdNames.scala +++ b/compiler/src/dotty/tools/dotc/core/StdNames.scala @@ -395,6 +395,7 @@ object StdNames { val UNIT : N = "UNIT" val acc: N = "acc" val adhocExtensions: N = "adhocExtensions" + val andThen: N = "andThen" val annotation: N = "annotation" val any2stringadd: N = "any2stringadd" val anyHash: N = "anyHash" @@ -443,11 +444,13 @@ object StdNames { val command: N = "command" val common: N = "common" val compiletime : N = "compiletime" + val compose: N = "compose" val conforms_ : N = "$conforms" val contents: N = "contents" val copy: N = "copy" - val currentMirror: N = "currentMirror" val create: N = "create" + val currentMirror: N = "currentMirror" + val curried: N = "curried" val definitions: N = "definitions" val delayedInit: N = "delayedInit" val delayedInitArg: N = "delayedInit$body" @@ -622,6 +625,7 @@ object StdNames { val transparent : N = "transparent" val tree : N = "tree" val true_ : N = "true" + val tupled: N = "tupled" val typedProductIterator: N = "typedProductIterator" val typeTagToManifest: N = "typeTagToManifest" val unapply: N = "unapply" diff --git a/tests/pos-custom-args/captures/function-combinators.scala b/tests/pos-custom-args/captures/function-combinators.scala new file mode 100644 index 000000000000..4354af4c7636 --- /dev/null +++ b/tests/pos-custom-args/captures/function-combinators.scala @@ -0,0 +1,28 @@ +class ContextClass +type Context = ContextClass^ + +def Test(using ctx1: Context, ctx2: Context) = + val f: Int => Int = identity + val g1: Int ->{ctx1} Int = identity + val g2: Int ->{ctx2} Int = identity + val h: Int -> Int = identity + val a1 = f.andThen(f); val _: Int ->{f} Int = a1 + val a2 = f.andThen(g1); val _: Int ->{f, g1} Int = a2 + val a3 = f.andThen(g2); val _: Int ->{f, g2} Int = a3 + val a4 = f.andThen(h); val _: Int ->{f} Int = a4 + val b1 = g1.andThen(f); val _: Int ->{f, g1} Int = b1 + val b2 = g1.andThen(g1); val _: Int ->{g1} Int = b2 + val b3 = g1.andThen(g2); val _: Int ->{g1, g2} Int = b3 + val b4 = g1.andThen(h); val _: Int ->{g1} Int = b4 + val c1 = h.andThen(f); val _: Int ->{f} Int = c1 + val c2 = h.andThen(g1); val _: Int ->{g1} Int = c2 + val c3 = h.andThen(g2); val _: Int ->{g2} Int = c3 + val c4 = h.andThen(h); val _: Int -> Int = c4 + + val f2: (Int, Int) => Int = _ + _ + val f2c = f2.curried; val _: Int -> Int ->{f2} Int = f2c + val f2t = f2.tupled; val _: ((Int, Int)) ->{f2} Int = f2t + + val f3: (Int, Int, Int) => Int = ??? + val f3c = f3.curried; val _: Int -> Int -> Int ->{f3} Int = f3c + val f3t = f3.tupled; val _: ((Int, Int, Int)) ->{f3} Int = f3t diff --git a/tests/pos-custom-args/captures/inlined-closure.scala b/tests/pos-custom-args/captures/inlined-closure.scala index 86be577334f6..74f93131b940 100644 --- a/tests/pos-custom-args/captures/inlined-closure.scala +++ b/tests/pos-custom-args/captures/inlined-closure.scala @@ -12,7 +12,3 @@ def Test(using ctx: Context) = info.paramRefs.filter(_.isTracked) val p = atPhase()((_: ParamRef).isTracked) val _: ParamRef ->{ctx} Boolean = p - - //val f: String => ParamRef = ??? - //val q = f.andThen(p) - From cd1a7b2dcc0c083a418ae312270d39ce9dfc9e6e Mon Sep 17 00:00:00 2001 From: odersky Date: Sun, 16 Jul 2023 14:43:12 +0200 Subject: [PATCH 21/37] Don't adapt function and by name types when unpickling With the new gradual typing using Fluid capture sets, this should be no longer needed. --- compiler/src/dotty/tools/dotc/cc/CaptureOps.scala | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala index 3ba26c92cab5..d6c6dd9ec2c0 100644 --- a/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala +++ b/compiler/src/dotty/tools/dotc/cc/CaptureOps.scala @@ -15,6 +15,12 @@ import config.Feature private val Captures: Key[CaptureSet] = Key() private val BoxedType: Key[BoxedTypeCache] = Key() +/** Switch whether unpickled function types and byname types should be mapped to + * impure types. With the new gradual typing using Fluid capture sets, this should + * be no longer needed. Also, it has bad interactions with pickling tests. + */ +private val adaptUnpickledFunctionTypes = false + /** The arguments of a @retains or @retainsByName annotation */ private[cc] def retainedElems(tree: Tree)(using Context): List[Tree] = tree match case Apply(_, Typed(SeqLiteral(elems, _), _) :: Nil) => elems @@ -49,7 +55,7 @@ extension (tree: Tree) * a by name parameter type, turning the latter into an impure by name parameter type. */ def adaptByNameArgUnderPureFuns(using Context): Tree = - if Feature.pureFunsEnabledSomewhere then + if adaptUnpickledFunctionTypes && Feature.pureFunsEnabledSomewhere then val rbn = defn.RetainsByNameAnnot Annotated(tree, New(rbn.typeRef).select(rbn.primaryConstructor).appliedTo( @@ -145,7 +151,7 @@ extension (tp: Type) */ def adaptFunctionTypeUnderPureFuns(using Context): Type = tp match case AppliedType(fn, args) - if Feature.pureFunsEnabledSomewhere && defn.isFunctionClass(fn.typeSymbol) => + if adaptUnpickledFunctionTypes && Feature.pureFunsEnabledSomewhere && defn.isFunctionClass(fn.typeSymbol) => val fname = fn.typeSymbol.name defn.FunctionType( fname.functionArity, From 9c4afd6230a9a1c27dacf6e1f06f718647dbf716 Mon Sep 17 00:00:00 2001 From: odersky Date: Sun, 16 Jul 2023 14:43:53 +0200 Subject: [PATCH 22/37] Avpid global side effects in unpickle tests --- compiler/src/dotty/tools/dotc/transform/Pickler.scala | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/Pickler.scala b/compiler/src/dotty/tools/dotc/transform/Pickler.scala index 3c09b75b9a35..36042e6624eb 100644 --- a/compiler/src/dotty/tools/dotc/transform/Pickler.scala +++ b/compiler/src/dotty/tools/dotc/transform/Pickler.scala @@ -173,10 +173,14 @@ class Pickler extends Phase { cls -> (unit, unpickler) } pickling.println("************* entered toplevel ***********") + val rootCtx = ctx for ((cls, (unit, unpickler)) <- unpicklers) do - ctx.compilationUnit.needsCaptureChecking = unit.needsCaptureChecking val unpickled = unpickler.rootTrees - testSame(i"$unpickled%\n%", beforePickling(cls), cls) + val freshUnit = CompilationUnit(rootCtx.compilationUnit.source) + freshUnit.needsCaptureChecking = unit.needsCaptureChecking + freshUnit.knowsPureFuns = unit.knowsPureFuns + inContext(rootCtx.fresh.setCompilationUnit(freshUnit)): + testSame(i"$unpickled%\n%", beforePickling(cls), cls) private def testSame(unpickled: String, previous: String, cls: ClassSymbol)(using Context) = import java.nio.charset.StandardCharsets.UTF_8 From af8124f8b0dab06384720d720cd2f1c8521e5770 Mon Sep 17 00:00:00 2001 From: odersky Date: Sun, 16 Jul 2023 15:26:46 +0200 Subject: [PATCH 23/37] Move stdlib tests to pos That way we can compile mixed capture-checked and non-capture checked sources. Also, add collection/Iterable.scala to test. We had to add Map as well since there is an incompatibility between the CB sources (from which we take the tests) and the build binaries (against which they are compiled) there. --- .../test/dotc/pos-test-pickling.blacklist | 3 + tests/pos/stdlib/Iterable.scala | 1 + .../stdlib/IterableOnce.scala | 0 .../captures => pos}/stdlib/Iterator.scala | 0 tests/pos/stdlib/Map.scala | 1 + tests/pos/stdlib/collection/Iterable.scala | 1052 +++++++++++++++++ .../stdlib/collection/IterableOnce.scala | 0 .../stdlib/collection/Iterator.scala | 6 +- tests/pos/stdlib/collection/Map.scala | 406 +++++++ .../stdlib/runtime/PStatics.scala | 0 .../stdlib/runtime_PStatics.scala | 0 11 files changed, 1466 insertions(+), 3 deletions(-) create mode 120000 tests/pos/stdlib/Iterable.scala rename tests/{pos-custom-args/captures => pos}/stdlib/IterableOnce.scala (100%) rename tests/{pos-custom-args/captures => pos}/stdlib/Iterator.scala (100%) create mode 120000 tests/pos/stdlib/Map.scala create mode 100644 tests/pos/stdlib/collection/Iterable.scala rename tests/{pos-custom-args/captures => pos}/stdlib/collection/IterableOnce.scala (100%) rename tests/{pos-custom-args/captures => pos}/stdlib/collection/Iterator.scala (99%) create mode 100644 tests/pos/stdlib/collection/Map.scala rename tests/{pos-custom-args/captures => pos}/stdlib/runtime/PStatics.scala (100%) rename tests/{pos-custom-args/captures => pos}/stdlib/runtime_PStatics.scala (100%) diff --git a/compiler/test/dotc/pos-test-pickling.blacklist b/compiler/test/dotc/pos-test-pickling.blacklist index f1e6ce805b27..6404444be87f 100644 --- a/compiler/test/dotc/pos-test-pickling.blacklist +++ b/compiler/test/dotc/pos-test-pickling.blacklist @@ -27,6 +27,9 @@ i17588.scala # Tree is huge and blows stack for printing Text i7034.scala +# Causes cyclic reference by interacting with compiler stdlib types +stdlib + # Stale symbol: package object scala seqtype-cycle diff --git a/tests/pos/stdlib/Iterable.scala b/tests/pos/stdlib/Iterable.scala new file mode 120000 index 000000000000..a91995808d97 --- /dev/null +++ b/tests/pos/stdlib/Iterable.scala @@ -0,0 +1 @@ +collection/Iterable.scala \ No newline at end of file diff --git a/tests/pos-custom-args/captures/stdlib/IterableOnce.scala b/tests/pos/stdlib/IterableOnce.scala similarity index 100% rename from tests/pos-custom-args/captures/stdlib/IterableOnce.scala rename to tests/pos/stdlib/IterableOnce.scala diff --git a/tests/pos-custom-args/captures/stdlib/Iterator.scala b/tests/pos/stdlib/Iterator.scala similarity index 100% rename from tests/pos-custom-args/captures/stdlib/Iterator.scala rename to tests/pos/stdlib/Iterator.scala diff --git a/tests/pos/stdlib/Map.scala b/tests/pos/stdlib/Map.scala new file mode 120000 index 000000000000..b20070fe8f92 --- /dev/null +++ b/tests/pos/stdlib/Map.scala @@ -0,0 +1 @@ +collection/Map.scala \ No newline at end of file diff --git a/tests/pos/stdlib/collection/Iterable.scala b/tests/pos/stdlib/collection/Iterable.scala new file mode 100644 index 000000000000..9bf65f4f5994 --- /dev/null +++ b/tests/pos/stdlib/collection/Iterable.scala @@ -0,0 +1,1052 @@ +/* + * Scala (https://www.scala-lang.org) + * + * Copyright EPFL and Lightbend, Inc. + * + * Licensed under Apache License 2.0 + * (http://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package scala +package collection + +import scala.annotation.nowarn +import scala.annotation.unchecked.uncheckedVariance +import scala.collection.mutable.Builder +import scala.collection.View.{LeftPartitionMapped, RightPartitionMapped} +import language.experimental.captureChecking + +/** Base trait for generic collections. + * + * @tparam A the element type of the collection + * + * @define Coll `Iterable` + * @define coll iterable collection + */ +trait Iterable[+A] extends IterableOnce[A] + with IterableOps[A, Iterable, Iterable[A]] + with IterableFactoryDefaults[A, Iterable] { + this: Iterable[A]^ => + + // The collection itself + @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") + final def toIterable: this.type = this + + final protected def coll: this.type = this + + def iterableFactory: IterableFactory[Iterable] = Iterable + + @deprecated("Iterable.seq always returns the iterable itself", "2.13.0") + def seq: this.type = this + + /** Defines the prefix of this object's `toString` representation. + * + * It is recommended to return the name of the concrete collection type, but + * not implementation subclasses. For example, for `ListMap` this method should + * return `"ListMap"`, not `"Map"` (the supertype) or `"Node"` (an implementation + * subclass). + * + * The default implementation returns "Iterable". It is overridden for the basic + * collection kinds "Seq", "IndexedSeq", "LinearSeq", "Buffer", "Set", "Map", + * "SortedSet", "SortedMap" and "View". + * + * @return a string representation which starts the result of `toString` + * applied to this $coll. By default the string prefix is the + * simple name of the collection class $coll. + */ + protected[this] def className: String = stringPrefix + + /** Forwarder to `className` for use in `scala.runtime.ScalaRunTime`. + * + * This allows the proper visibility for `className` to be + * published, but provides the exclusive access needed by + * `scala.runtime.ScalaRunTime.stringOf` (and a few tests in + * the test suite). + */ + private[scala] final def collectionClassName: String = className + + @deprecatedOverriding("Override className instead", "2.13.0") + protected[this] def stringPrefix: String = "Iterable" + + /** Converts this $coll to a string. + * + * @return a string representation of this collection. By default this + * string consists of the `className` of this $coll, followed + * by all elements separated by commas and enclosed in parentheses. + */ + override def toString = mkString(className + "(", ", ", ")") + + /** Analogous to `zip` except that the elements in each collection are not consumed until a strict operation is + * invoked on the returned `LazyZip2` decorator. + * + * Calls to `lazyZip` can be chained to support higher arities (up to 4) without incurring the expense of + * constructing and deconstructing intermediary tuples. + * + * {{{ + * val xs = List(1, 2, 3) + * val res = (xs lazyZip xs lazyZip xs lazyZip xs).map((a, b, c, d) => a + b + c + d) + * // res == List(4, 8, 12) + * }}} + * + * @param that the iterable providing the second element of each eventual pair + * @tparam B the type of the second element in each eventual pair + * @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) +} + +/** Base trait for Iterable operations + * + * =VarianceNote= + * + * We require that for all child classes of Iterable the variance of + * the child class and the variance of the `C` parameter passed to `IterableOps` + * are the same. We cannot express this since we lack variance polymorphism. That's + * why we have to resort at some places to write `C[A @uncheckedVariance]`. + * + * @tparam CC type constructor of the collection (e.g. `List`, `Set`). Operations returning a collection + * with a different type of element `B` (e.g. `map`) return a `CC[B]`. + * @tparam C type of the collection (e.g. `List[Int]`, `String`, `BitSet`). Operations returning a collection + * with the same type of element (e.g. `drop`, `filter`) return a `C`. + * + * @define Coll Iterable + * @define coll iterable collection + * @define orderDependent + * + * Note: might return different results for different runs, unless the underlying collection type is ordered. + * @define orderDependentFold + * + * Note: might return different results for different runs, unless the + * underlying collection type is ordered or the operator is associative + * and commutative. + * @define mayNotTerminateInf + * + * Note: may not terminate for infinite-sized collections. + * @define willNotTerminateInf + * + * Note: will not terminate for infinite-sized collections. + * @define undefinedorder + * The order in which operations are performed on elements is unspecified + * and may be nondeterministic. + */ +trait IterableOps[+A, +CC[_], +C] extends Any with IterableOnce[A] with IterableOnceOps[A, CC, C] { + this: IterableOps[A, CC, C]^ => + + /** + * @return This collection as an `Iterable[A]`. No new collection will be built if `this` is already an `Iterable[A]`. + */ + // 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]^{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]^{this} = toIterable + + override def isTraversableAgain: Boolean = true + + /** + * @return This collection as a `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^{this} = coll + + /** + * Defines how to turn a given `Iterable[A]` into a collection of type `C`. + * + * This process can be done in a strict way or a non-strict way (ie. without evaluating + * the elements of the resulting collections). In other words, this methods defines + * the evaluation model of the collection. + * + * @note When implementing a custom collection type and refining `C` to the new type, this + * method needs to be overridden (the compiler will issue an error otherwise). In the + * common case where `C =:= CC[A]`, this can be done by mixing in the + * [[scala.collection.IterableFactoryDefaults]] trait, which implements the method using + * [[iterableFactory]]. + * + * @note As witnessed by the `@uncheckedVariance` annotation, using this method + * might be unsound. However, as long as it is called with an + * `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^{coll} + + /** The companion object of this ${coll}, providing various factory methods. + * + * @note When implementing a custom collection type and refining `CC` to the new type, this + * method needs to be overridden to return a factory for the new type (the compiler will + * issue an error otherwise). + */ + def iterableFactory: IterableFactory[CC] + + @deprecated("Use iterableFactory instead", "2.13.0") + @deprecatedOverriding("Use iterableFactory instead", "2.13.0") + @`inline` def companion: IterableFactory[CC] = iterableFactory + + /** + * @return a strict builder for the same collection type. + * + * Note that in the case of lazy collections (e.g. [[scala.collection.View]] or [[scala.collection.immutable.LazyList]]), + * it is possible to implement this method but the resulting `Builder` will break laziness. + * As a consequence, operations should preferably be implemented with `fromSpecific` + * instead of this method. + * + * @note When implementing a custom collection type and refining `C` to the new type, this + * method needs to be overridden (the compiler will issue an error otherwise). In the + * common case where `C =:= CC[A]`, this can be done by mixing in the + * [[scala.collection.IterableFactoryDefaults]] trait, which implements the method using + * [[iterableFactory]]. + * + * @note As witnessed by the `@uncheckedVariance` annotation, using this method might + * be unsound. However, as long as the returned builder is only fed + * with `A` values taken from `this` instance, it is safe. + */ + protected def newSpecificBuilder: Builder[A @uncheckedVariance, C] + + /** The empty iterable of the same type as this iterable + * + * @return an empty iterable of type `C`. + */ + def empty: C = fromSpecific(Nil) + + /** Selects the first element of this $coll. + * $orderDependent + * @return the first element of this $coll. + * @throws NoSuchElementException if the $coll is empty. + */ + def head: A = iterator.next() + + /** Optionally selects the first element. + * $orderDependent + * @return the first element of this $coll if it is nonempty, + * `None` if it is empty. + */ + def headOption: Option[A] = { + val it = iterator + if (it.hasNext) Some(it.next()) else None + } + + /** Selects the last element. + * $orderDependent + * @return The last element of this $coll. + * @throws NoSuchElementException If the $coll is empty. + */ + def last: A = { + val it = iterator + var lst = it.next() + while (it.hasNext) lst = it.next() + lst + } + + /** Optionally selects the last element. + * $orderDependent + * @return the last element of this $coll$ if it is nonempty, + * `None` if it is empty. + */ + def lastOption: Option[A] = if (isEmpty) None else Some(last) + + /** A view over the elements of this collection. */ + def view: View[A]^{this} = View.fromIteratorProvider(() => iterator) + + /** Compares the size of this $coll to a test value. + * + * @param otherSize the test value that gets compared with the size. + * @return A value `x` where + * {{{ + * x < 0 if this.size < otherSize + * x == 0 if this.size == otherSize + * x > 0 if this.size > otherSize + * }}} + * + * The method as implemented here does not call `size` directly; its running time + * is `O(size min otherSize)` instead of `O(size)`. The method should be overridden + * if computing `size` is cheap and `knownSize` returns `-1`. + * + * @see [[sizeIs]] + */ + def sizeCompare(otherSize: Int): Int = { + if (otherSize < 0) 1 + else { + val known = knownSize + if (known >= 0) Integer.compare(known, otherSize) + else { + var i = 0 + val it = iterator + while (it.hasNext) { + if (i == otherSize) return 1 + it.next() + i += 1 + } + i - otherSize + } + } + } + + /** Returns a value class containing operations for comparing the size of this $coll to a test value. + * + * These operations are implemented in terms of [[sizeCompare(Int) `sizeCompare(Int)`]], and + * allow the following more readable usages: + * + * {{{ + * this.sizeIs < size // this.sizeCompare(size) < 0 + * this.sizeIs <= size // this.sizeCompare(size) <= 0 + * this.sizeIs == size // this.sizeCompare(size) == 0 + * this.sizeIs != size // this.sizeCompare(size) != 0 + * this.sizeIs >= size // this.sizeCompare(size) >= 0 + * this.sizeIs > size // this.sizeCompare(size) > 0 + * }}} + */ + @inline final def sizeIs: IterableOps.SizeCompareOps^{this} = new IterableOps.SizeCompareOps(this) + + /** Compares the size of this $coll to the size of another `Iterable`. + * + * @param that the `Iterable` whose size is compared with this $coll's size. + * @return A value `x` where + * {{{ + * x < 0 if this.size < that.size + * x == 0 if this.size == that.size + * x > 0 if this.size > that.size + * }}} + * + * The method as implemented here does not call `size` directly; its running time + * 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 = { + val thatKnownSize = that.knownSize + + if (thatKnownSize >= 0) this sizeCompare thatKnownSize + else { + val thisKnownSize = this.knownSize + + if (thisKnownSize >= 0) { + val res = that sizeCompare thisKnownSize + // can't just invert the result, because `-Int.MinValue == Int.MinValue` + if (res == Int.MinValue) 1 else -res + } else { + val thisIt = this.iterator + val thatIt = that.iterator + while (thisIt.hasNext && thatIt.hasNext) { + thisIt.next() + thatIt.next() + } + java.lang.Boolean.compare(thisIt.hasNext, thatIt.hasNext) + } + } + } + + /** 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]^{this} = view.slice(from, until) + + /** Transposes this $coll of iterable collections into + * a $coll of ${coll}s. + * + * The resulting collection's type will be guided by the + * static type of $coll. For example: + * + * {{{ + * val xs = List( + * Set(1, 2, 3), + * Set(4, 5, 6)).transpose + * // xs == List( + * // List(1, 4), + * // List(2, 5), + * // List(3, 6)) + * + * val ys = Vector( + * List(1, 2, 3), + * List(4, 5, 6)).transpose + * // ys == Vector( + * // Vector(1, 4), + * // Vector(2, 5), + * // Vector(3, 6)) + * }}} + * + * $willForceEvaluation + * + * @tparam B the type of the elements of each iterable collection. + * @param asIterable an implicit conversion which asserts that the + * element type of this $coll is an `Iterable`. + * @return a two-dimensional $coll of ${coll}s which has as ''n''th row + * the ''n''th column of this $coll. + * @throws IllegalArgumentException if all collections in this $coll + * are not of the same size. + */ + def transpose[B](implicit asIterable: A -> /*<:= headSize) fail + bs(i) += x + i += 1 + } + if (i != headSize) + fail + } + iterableFactory.from(bs.map(_.result())) + } + + def filter(pred: A => Boolean): C^{this, pred} = fromSpecific(new View.Filter(this, pred, isFlipped = false)) + + def filterNot(pred: A => Boolean): C^{this, pred} = fromSpecific(new View.Filter(this, pred, isFlipped = true)) + + /** Creates a non-strict filter of this $coll. + * + * Note: the difference between `c filter p` and `c withFilter p` is that + * the former creates a new collection, whereas the latter only + * restricts the domain of subsequent `map`, `flatMap`, `foreach`, + * and `withFilter` operations. + * $orderDependent + * + * @param p the predicate used to test elements. + * @return an object of class `WithFilter`, which supports + * `map`, `flatMap`, `foreach`, and `withFilter` operations. + * All these operations apply to those elements of this $coll + * which satisfy the predicate `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. Interesting because it splits a collection in two. + * + * The default implementation provided here needs to traverse the collection twice. + * Strict collections have an overridden version of `partition` in `StrictOptimizedIterableOps`, + * which requires only a single traversal. + */ + def partition(p: A => Boolean): (C^{this, p}, C^{this, p}) = { + val first = new View.Filter(this, p, false) + val second = new View.Filter(this, p, true) + (fromSpecific(first), fromSpecific(second)) + } + + override def splitAt(n: Int): (C^{this}, C^{this}) = (take(n), drop(n)) + + def take(n: Int): C^{this} = fromSpecific(new View.Take(this, n)) + + /** Selects the last ''n'' elements. + * $orderDependent + * @param n the number of elements to take from this $coll. + * @return a $coll consisting only of the last `n` elements of this $coll, + * 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^{this} = fromSpecific(new View.TakeRight(this, n)) + + /** Takes longest prefix of elements that satisfy a predicate. + * $orderDependent + * @param p The predicate used to test elements. + * @return the longest prefix of this $coll whose elements all satisfy + * the predicate `p`. + */ + def takeWhile(p: A => Boolean): C^{this, p} = fromSpecific(new View.TakeWhile(this, p)) + + def span(p: A => Boolean): (C^{this, p}, C^{this, p}) = (takeWhile(p), dropWhile(p)) + + def drop(n: Int): C^{this} = fromSpecific(new View.Drop(this, n)) + + /** Selects all elements except last ''n'' ones. + * $orderDependent + * @param n the number of elements to drop from this $coll. + * @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^{this} = fromSpecific(new View.DropRight(this, n)) + + 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 + * @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^{this}]^{this} = + iterator.grouped(size).map(fromSpecific) + + /** Groups elements in fixed size blocks by passing a "sliding window" + * over them (as opposed to partitioning them, as is done in `grouped`.) + * + * An empty collection returns an empty iterator, and a non-empty + * collection containing fewer elements than the window size returns + * an iterator that will produce the original collection as its only + * element. + * @see [[scala.collection.Iterator]], method `sliding` + * + * @param size the number of elements per group + * @return An iterator producing ${coll}s of size `size`, except for a + * non-empty collection with less than `size` elements, which + * returns an iterator that produces the source collection itself + * as its only element. + * @example `List().sliding(2) = empty iterator` + * @example `List(1).sliding(2) = Iterator(List(1))` + * @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^{this}]^{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.) + * + * The returned iterator will be empty when called on an empty collection. + * The last element the iterator produces may be smaller than the window + * size when the original collection isn't exhausted by the window before + * it and its last element isn't skipped by the step before it. + * + * @see [[scala.collection.Iterator]], method `sliding` + * + * @param size the number of elements per group + * @param step the distance between the first elements of successive + * groups + * @return An iterator producing ${coll}s of size `size`, except the last + * element (which may be the only element) will be smaller + * if there are fewer than `size` elements remaining to be grouped. + * @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^{this}]^{this} = + iterator.sliding(size, step).map(fromSpecific) + + /** The rest of the collection without its first element. */ + def tail: C^{this} = { + if (isEmpty) throw new UnsupportedOperationException + drop(1) + } + + /** The initial part of the collection without its last element. + * $willForceEvaluation + */ + def init: C^{this} = { + if (isEmpty) throw new UnsupportedOperationException + dropRight(1) + } + + 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. + * + * $willForceEvaluation + * + * @param f the discriminator function. + * @tparam K the type of keys returned by the discriminator function. + * @return A map from keys to ${coll}s such that the following invariant holds: + * {{{ + * (xs groupBy f)(k) = xs filter (x => f(x) == k) + * }}} + * That is, every key `k` is bound to a $coll of those elements `x` + * for which `f(x)` equals `k`. + * + */ + def groupBy[K](f: A => K): immutable.Map[K, C] = { + val m = mutable.Map.empty[K, Builder[A, C]] + val it = iterator + while (it.hasNext) { + val elem = it.next() + val key = f(elem) + val bldr = m.getOrElseUpdate(key, newSpecificBuilder) + bldr += elem + } + var result = immutable.HashMap.empty[K, C] + val mapIt = m.iterator + while (mapIt.hasNext) { + val (k, v) = mapIt.next() + result = result.updated(k, v.result()) + } + result + } + + /** + * Partitions this $coll into a map of ${coll}s according to a discriminator function `key`. + * Each element in a group is transformed into a value of type `B` using the `value` function. + * + * It is equivalent to `groupBy(key).mapValues(_.map(f))`, but more efficient. + * + * {{{ + * case class User(name: String, age: Int) + * + * def namesByAge(users: Seq[User]): Map[Int, Seq[String]] = + * users.groupMap(_.age)(_.name) + * }}} + * + * $willForceEvaluation + * + * @param key the discriminator function + * @param f the element transformation function + * @tparam K the type of keys returned by the discriminator function + * @tparam B the type of values returned by the transformation function + */ + def groupMap[K, B](key: A => K)(f: A => B): immutable.Map[K, CC[B]] = { + val m = mutable.Map.empty[K, Builder[B, CC[B]]] + for (elem <- this) { + val k = key(elem) + val bldr = m.getOrElseUpdate(k, iterableFactory.newBuilder[B]) + bldr += f(elem) + } + class Result extends runtime.AbstractFunction1[(K, Builder[B, CC[B]]), Unit] { + var built = immutable.Map.empty[K, CC[B]] + def apply(kv: (K, Builder[B, CC[B]])) = + built = built.updated(kv._1, kv._2.result()) + } + val result = new Result + m.foreach(result) + result.built + } + + /** + * Partitions this $coll into a map according to a discriminator function `key`. All the values that + * have the same discriminator are then transformed by the `f` function and then reduced into a + * single value with the `reduce` function. + * + * It is equivalent to `groupBy(key).mapValues(_.map(f).reduce(reduce))`, but more efficient. + * + * {{{ + * def occurrences[A](as: Seq[A]): Map[A, Int] = + * as.groupMapReduce(identity)(_ => 1)(_ + _) + * }}} + * + * $willForceEvaluation + */ + def groupMapReduce[K, B](key: A => K)(f: A => B)(reduce: (B, B) => B): immutable.Map[K, B] = { + val m = mutable.Map.empty[K, B] + for (elem <- this) { + val k = key(elem) + val v = + m.get(k) match { + case Some(b) => reduce(b, f(elem)) + case None => f(elem) + } + m.put(k, v) + } + m.to(immutable.Map) + } + + /** Computes a prefix scan of the elements of the collection. + * + * Note: The neutral element `z` may be applied more than once. + * + * @tparam B element type of the resulting collection + * @param z neutral element for the operator `op` + * @param op the associative operator for the scan + * + * @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]^{this, op} = scanLeft(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. + * $willNotTerminateInf + * $orderDependent + * $willForceEvaluation + * + * Example: + * {{{ + * List(1, 2, 3, 4).scanRight(0)(_ + _) == List(10, 9, 7, 4, 0) + * }}} + * + * @tparam B the type of the elements in the resulting collection + * @param z the initial value + * @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]^{this, op} = { + class Scanner extends runtime.AbstractFunction1[A, Unit] { + var acc = z + var scanned = acc :: immutable.Nil + def apply(x: A) = { + acc = op(x, acc) + scanned ::= acc + } + } + val scanner = new Scanner + reversed.foreach(scanner) + iterableFactory.from(scanner.scanned) + } + + 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]^{this, f} = iterableFactory.from(new View.FlatMap(this, f)) + + def flatten[B](implicit asIterable: A -> IterableOnce[B]): CC[B]^{this} = flatMap(asIterable) + + 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 + * 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]]. + * + * Example: + * {{{ + * val xs = $Coll(1, "one", 2, "two", 3, "three") partitionMap { + * case i: Int => Left(i) + * case s: String => Right(s) + * } + * // xs == ($Coll(1, 2, 3), + * // $Coll(one, two, three)) + * }}} + * + * @tparam A1 the element type of the first resulting collection + * @tparam A2 the element type of the second resulting collection + * @param f the 'split function' mapping the elements of this $coll to an [[scala.util.Either]] + * + * @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]^{this, f}, CC[A2]^{this, f}) = { + val left: View[A1]^{f} = new LeftPartitionMapped(this, f) + val right: View[A2]^{f} = new RightPartitionMapped(this, f) + (iterableFactory.from(left), iterableFactory.from(right)) + } + + /** Returns a new $coll containing the elements from the left hand operand followed by the elements from the + * right hand operand. The element type of the $coll is the most specific superclass encompassing + * the element types of the two operands. + * + * @param suffix the iterable to append. + * @tparam B the element type of the returned collection. + * @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]^{this, suffix} = iterableFactory.from(suffix match { + case xs: Iterable[B] => new View.Concat(this, xs) + case xs => iterator ++ suffix.iterator + }) + + /** Alias for `concat` */ + @`inline` final def ++ [B >: A](suffix: IterableOnce[B]^): CC[B]^{this, suffix} = concat(suffix) + + /** Returns a $coll formed from this $coll and another iterable collection + * by combining corresponding elements in pairs. + * If one of the two collections is longer than the other, its remaining elements are ignored. + * + * @param that The iterable providing the second half of each result pair + * @tparam B the type of the second half of the returned pairs + * @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]^): 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) + }) + + 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. + * If one of the two collections is shorter than the other, + * placeholder elements are used to extend the shorter collection to the length of the longer. + * + * @param that the iterable providing the second half of each result pair + * @param thisElem the element to be used to fill up the result if this $coll is shorter than `that`. + * @param thatElem the element to be used to fill up the result if `that` is shorter than this $coll. + * @return a new collection of type `That` containing pairs consisting of + * corresponding elements of this $coll and `that`. The length + * of the returned collection is the maximum of the lengths of this $coll and `that`. + * 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)]^{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. + * + * {{{ + * val xs = $Coll( + * (1, "one"), + * (2, "two"), + * (3, "three")).unzip + * // xs == ($Coll(1, 2, 3), + * // $Coll(one, two, three)) + * }}} + * + * @tparam A1 the type of the first half of the element pairs + * @tparam A2 the type of the second half of the element pairs + * @param asPair an implicit conversion which asserts that the element type + * of this $coll is a pair. + * @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]^{this}, CC[A2]^{this}) = { + 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)) + } + + /** Converts this $coll of triples into three collections of the first, second, + * and third element of each triple. + * + * {{{ + * val xs = $Coll( + * (1, "one", '1'), + * (2, "two", '2'), + * (3, "three", '3')).unzip3 + * // xs == ($Coll(1, 2, 3), + * // $Coll(one, two, three), + * // $Coll(1, 2, 3)) + * }}} + * + * @tparam A1 the type of the first member of the element triples + * @tparam A2 the type of the second member of the element triples + * @tparam A3 the type of the third member of the element triples + * @param asTriple an implicit conversion which asserts that the element type + * of this $coll is a triple. + * @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]^{this}, CC[A2]^{this}, CC[A3]^{this}) = { + 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) + (iterableFactory.from(first), iterableFactory.from(second), iterableFactory.from(third)) + } + + /** Iterates over the tails of this $coll. The first value will be this + * $coll and the final one will be an empty $coll, with the intervening + * values the results of successive applications of `tail`. + * + * @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^{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 + * values the results of successive applications of `init`. + * + * $willForceEvaluation + * + * @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^{this}]^{this} = iterateUntilEmpty(_.init) + + 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} = { + // 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 + val it = Iterator.iterate(toIterable: @nowarn("cat=deprecation"))(f) + .takeWhile((itble: Iterable[A]^) => itble.iterator.nonEmpty) + // CC TODO type annotation for itble needed. + // The previous code `.takeWhile(_.iterator.nonEmpty)` does not work. + (it ++ Iterator.single(Iterable.empty)).map(fromSpecific) + } + + @deprecated("Use ++ instead of ++: for collections of type Iterable", "2.13.0") + 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 + }) +} + +object IterableOps { + + /** Operations for comparing the size of a collection to a test value. + * + * These operations are implemented in terms of + * [[scala.collection.IterableOps.sizeCompare(Int) `sizeCompare(Int)`]]. + */ + final class SizeCompareOps private[collection](val it: IterableOps[_, AnyConstr, _]^) extends AnyVal { + this: SizeCompareOps^{it} => + /** 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. */ + @inline def <=(size: Int): Boolean = it.sizeCompare(size) <= 0 + /** Tests if the size of the collection is equal to some value. */ + @inline def ==(size: Int): Boolean = it.sizeCompare(size) == 0 + /** Tests if the size of the collection is not equal to some value. */ + @inline def !=(size: Int): Boolean = it.sizeCompare(size) != 0 + /** Tests if the size of the collection is greater than or equal to some value. */ + @inline def >=(size: Int): Boolean = it.sizeCompare(size) >= 0 + /** Tests if the size of the collection is greater than some value. */ + @inline def >(size: Int): Boolean = it.sizeCompare(size) > 0 + } + + /** A trait that contains just the `map`, `flatMap`, `foreach` and `withFilter` methods + * of trait `Iterable`. + * + * @tparam A Element type (e.g. `Int`) + * @tparam CC Collection type constructor (e.g. `List`) + * + * @define coll collection + */ + @SerialVersionUID(3L) + class WithFilter[+A, +CC[_]]( + self: IterableOps[A, CC, _]^, + p: A => Boolean + ) extends collection.WithFilter[A, CC] with Serializable { + + protected def filtered: Iterable[A]^{this} = + new View.Filter(self, p, isFlipped = false) + + def map[B](f: A => B): CC[B]^{this} = + self.iterableFactory.from(new View.Map(filtered, f)) + + def flatMap[B](f: A => IterableOnce[B]): CC[B]^{this} = + self.iterableFactory.from(new View.FlatMap(filtered, f)) + + def foreach[U](f: A => U): Unit = filtered.foreach(f) + + def withFilter(q: A => Boolean): WithFilter[A, CC]^{this, q} = + new WithFilter(self, (a: A) => p(a) && q(a)) + + } + +} + +@SerialVersionUID(3L) +object Iterable extends IterableFactory.Delegate[Iterable](immutable.Iterable) { + + def single[A](a: A): Iterable[A] = new AbstractIterable[A] { + override def iterator = Iterator.single(a) + override def knownSize = 1 + override def head = a + override def headOption = Some(a) + override def last = a + override def lastOption = Some(a) + override def view = 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 dropRight(n: Int) = if (n > 0) Iterable.empty else this + override def tail = Iterable.empty + override def init = Iterable.empty + } +} + +/** Explicit instantiation of the `Iterable` trait to reduce class file size in subclasses. */ +abstract class AbstractIterable[+A] extends Iterable[A] + +/** This trait provides default implementations for the factory methods `fromSpecific` and + * `newSpecificBuilder` that need to be refined when implementing a collection type that refines + * the `CC` and `C` type parameters. + * + * The default implementations in this trait can be used in the common case when `CC[A]` is the + * 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]^{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 + override def empty: CC[A @uncheckedVariance] = iterableFactory.empty +} + +/** This trait provides default implementations for the factory methods `fromSpecific` and + * `newSpecificBuilder` that need to be refined when implementing a collection type that refines + * the `CC` and `C` type parameters. It is used for collections that have an additional constraint, + * expressed by the `evidenceIterableFactory` method. + * + * The default implementations in this trait can be used in the common case when `CC[A]` is the + * same as `C`. + */ +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]^{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 +} + +/** This trait provides default implementations for the factory methods `fromSpecific` and + * `newSpecificBuilder` that need to be refined when implementing a collection type that refines + * the `CC` and `C` type parameters. It is used for sorted sets. + * + * Note that in sorted sets, the `CC` type of the set is not the same as the `CC` type for the + * underlying iterable (which is fixed to `Set` in [[SortedSetOps]]). This trait has therefore + * two type parameters `CC` and `WithFilterCC`. The `withFilter` method inherited from + * `IterableOps` is overridden with a compatible default implementation. + * + * The default implementations in this trait can be used in the common case when `CC[A]` is the + * same as `C`. + */ +trait SortedSetFactoryDefaults[+A, + +CC[X] <: SortedSet[X] with SortedSetOps[X, CC, CC[X]], + +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]^{coll} = sortedIterableFactory.from(coll)(ordering) + override protected def newSpecificBuilder: mutable.Builder[A @uncheckedVariance, CC[A @uncheckedVariance]] = sortedIterableFactory.newBuilder[A](ordering) + override def empty: CC[A @uncheckedVariance] = sortedIterableFactory.empty(ordering) + + override def withFilter(p: A => Boolean): SortedSetOps.WithFilter[A, WithFilterCC, CC]^{p} = + new SortedSetOps.WithFilter[A, WithFilterCC, CC](this, p) +} + + +/** This trait provides default implementations for the factory methods `fromSpecific` and + * `newSpecificBuilder` that need to be refined when implementing a collection type that refines + * the `CC` and `C` type parameters. It is used for maps. + * + * Note that in maps, the `CC` type of the map is not the same as the `CC` type for the + * underlying iterable (which is fixed to `Map` in [[MapOps]]). This trait has therefore + * two type parameters `CC` and `WithFilterCC`. The `withFilter` method inherited from + * `IterableOps` is overridden with a compatible default implementation. + * + * The default implementations in this trait can be used in the common case when `CC[A]` is the + * same as `C`. + */ +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]] { + 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) + case self: immutable.TreeSeqMap[_, _] => immutable.TreeSeqMap.empty(self.orderedBy).asInstanceOf[CC[K, V]] + case _ => mapFactory.empty + } + + override def withFilter(p: ((K, V)) => Boolean): MapOps.WithFilter[K, V, WithFilterCC, CC]^{p} = + new MapOps.WithFilter[K, V, WithFilterCC, CC](this, p) +} + +/** This trait provides default implementations for the factory methods `fromSpecific` and + * `newSpecificBuilder` that need to be refined when implementing a collection type that refines + * the `CC` and `C` type parameters. It is used for sorted maps. + * + * Note that in sorted maps, the `CC` type of the map is not the same as the `CC` type for the + * underlying map (which is fixed to `Map` in [[SortedMapOps]]). This trait has therefore + * three type parameters `CC`, `WithFilterCC` and `UnsortedCC`. The `withFilter` method inherited + * from `IterableOps` is overridden with a compatible default implementation. + * + * The default implementations in this trait can be used in the common case when `CC[A]` is the + * same as `C`. + */ +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]] { + self: IterableOps[(K, V), WithFilterCC, _] => + + override def empty: CC[K, V @uncheckedVariance] = sortedMapFactory.empty(ordering) + override protected def fromSpecific(coll: IterableOnce[(K, V @uncheckedVariance)]^): CC[K, V @uncheckedVariance]^{coll} = sortedMapFactory.from(coll)(ordering) + override protected def newSpecificBuilder: mutable.Builder[(K, V @uncheckedVariance), CC[K, V @uncheckedVariance]] = sortedMapFactory.newBuilder[K, V](ordering) + + 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/tests/pos-custom-args/captures/stdlib/collection/IterableOnce.scala b/tests/pos/stdlib/collection/IterableOnce.scala similarity index 100% rename from tests/pos-custom-args/captures/stdlib/collection/IterableOnce.scala rename to tests/pos/stdlib/collection/IterableOnce.scala diff --git a/tests/pos-custom-args/captures/stdlib/collection/Iterator.scala b/tests/pos/stdlib/collection/Iterator.scala similarity index 99% rename from tests/pos-custom-args/captures/stdlib/collection/Iterator.scala rename to tests/pos/stdlib/collection/Iterator.scala index 24bdeb2a3dca..4b68258bd77f 100644 --- a/tests/pos-custom-args/captures/stdlib/collection/Iterator.scala +++ b/tests/pos/stdlib/collection/Iterator.scala @@ -626,9 +626,9 @@ trait Iterator[+A] extends IterableOnce[A] with IterableOnceOps[A, Iterator, Ite 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) + 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]^{this, xs} = concat(xs) + @`inline` final def ++ [B >: A](xs: => IterableOnce[B]^): Iterator[B]^{this, xs} = concat(xs) def take(n: Int): Iterator[A]^{this} = sliceIterator(0, n max 0) @@ -1201,7 +1201,7 @@ object Iterator extends IterableFactory[Iterator] { current.next() } else Iterator.empty.next() - override def concat[B >: A](that: => IterableOnce[B]): Iterator[B]^{this, that} = { + 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 diff --git a/tests/pos/stdlib/collection/Map.scala b/tests/pos/stdlib/collection/Map.scala new file mode 100644 index 000000000000..ed9b175e173d --- /dev/null +++ b/tests/pos/stdlib/collection/Map.scala @@ -0,0 +1,406 @@ +/* + * Scala (https://www.scala-lang.org) + * + * Copyright EPFL and Lightbend, Inc. + * + * Licensed under Apache License 2.0 + * (http://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package scala +package collection + +import scala.annotation.nowarn +import scala.collection.generic.DefaultSerializable +import scala.collection.mutable.StringBuilder +import scala.util.hashing.MurmurHash3 +//import language.experimental.captureChecking + +/** Base Map type */ +trait Map[K, +V] + extends Iterable[(K, V)] + with MapOps[K, V, Map, Map[K, V]] + with MapFactoryDefaults[K, V, Map, Iterable] + with Equals { + + def mapFactory: scala.collection.MapFactory[Map] = Map + + def canEqual(that: Any): Boolean = true + + /** + * Equality of maps is implemented using the lookup method [[get]]. This method returns `true` if + * - the argument `o` is a `Map`, + * - the two maps have the same [[size]], and + * - for every `(key, value)` pair in this map, `other.get(key) == Some(value)`. + * + * The implementation of `equals` checks the [[canEqual]] method, so subclasses of `Map` can narrow down the equality + * to specific map types. The `Map` implementations in the standard library can all be compared, their `canEqual` + * methods return `true`. + * + * Note: The `equals` method only respects the equality laws (symmetry, transitivity) if the two maps use the same + * key equivalence function in their lookup operation. For example, the key equivalence operation in a + * [[scala.collection.immutable.TreeMap]] is defined by its ordering. Comparing a `TreeMap` with a `HashMap` leads + * to unexpected results if `ordering.equiv(k1, k2)` (used for lookup in `TreeMap`) is different from `k1 == k2` + * (used for lookup in `HashMap`). + * + * {{{ + * scala> import scala.collection.immutable._ + * scala> val ord: Ordering[String] = _ compareToIgnoreCase _ + * + * scala> TreeMap("A" -> 1)(ord) == HashMap("a" -> 1) + * val res0: Boolean = false + * + * scala> HashMap("a" -> 1) == TreeMap("A" -> 1)(ord) + * val res1: Boolean = true + * }}} + * + * + * @param o The map to which this map is compared + * @return `true` if the two maps are equal according to the description + */ + override def equals(o: Any): Boolean = + (this eq o.asInstanceOf[AnyRef]) || (o match { + case map: Map[K @unchecked, _] if map.canEqual(this) => + (this.size == map.size) && { + try this.forall(kv => map.getOrElse(kv._1, Map.DefaultSentinelFn()) == kv._2) + catch { case _: ClassCastException => false } // PR #9565 / scala/bug#12228 + } + case _ => + false + }) + + override def hashCode(): Int = MurmurHash3.mapHash(this) + + // These two methods are not in MapOps so that MapView is not forced to implement them + @deprecated("Use - or removed on an immutable Map", "2.13.0") + def - (key: K): Map[K, V] + @deprecated("Use -- or removedAll on an immutable Map", "2.13.0") + def - (key1: K, key2: K, keys: K*): Map[K, V] + + @nowarn("""cat=deprecation&origin=scala\.collection\.Iterable\.stringPrefix""") + override protected[this] def stringPrefix: String = "Map" + + override def toString(): String = super[Iterable].toString() // Because `Function1` overrides `toString` too +} + +/** Base Map implementation type + * + * @tparam K Type of keys + * @tparam V Type of values + * @tparam CC type constructor of the map (e.g. `HashMap`). Operations returning a collection + * with a different type of entries `(L, W)` (e.g. `map`) return a `CC[L, W]`. + * @tparam C type of the map (e.g. `HashMap[Int, String]`). Operations returning a collection + * with the same type of element (e.g. `drop`, `filter`) return a `C`. + * @define coll map + * @define Coll `Map` + */ +// Note: the upper bound constraint on CC is useful only to +// erase CC to IterableOps instead of Object +trait MapOps[K, +V, +CC[_, _] <: IterableOps[_, AnyConstr, _], +C] + extends IterableOps[(K, V), Iterable, C] + with PartialFunction[K, V] { + + override def view: MapView[K, V] = new MapView.Id(this) + + /** Returns a [[Stepper]] for the keys of this map. See method [[stepper]]. */ + def keyStepper[S <: Stepper[_]](implicit shape: StepperShape[K, S]): S = { + import convert.impl._ + val s = shape.shape match { + case StepperShape.IntShape => new IntIteratorStepper (keysIterator.asInstanceOf[Iterator[Int]]) + case StepperShape.LongShape => new LongIteratorStepper (keysIterator.asInstanceOf[Iterator[Long]]) + case StepperShape.DoubleShape => new DoubleIteratorStepper(keysIterator.asInstanceOf[Iterator[Double]]) + case _ => shape.seqUnbox(new AnyIteratorStepper(keysIterator)) + } + s.asInstanceOf[S] + } + + /** Returns a [[Stepper]] for the values of this map. See method [[stepper]]. */ + def valueStepper[S <: Stepper[_]](implicit shape: StepperShape[V, S]): S = { + import convert.impl._ + val s = shape.shape match { + case StepperShape.IntShape => new IntIteratorStepper (valuesIterator.asInstanceOf[Iterator[Int]]) + case StepperShape.LongShape => new LongIteratorStepper (valuesIterator.asInstanceOf[Iterator[Long]]) + case StepperShape.DoubleShape => new DoubleIteratorStepper(valuesIterator.asInstanceOf[Iterator[Double]]) + case _ => shape.seqUnbox(new AnyIteratorStepper(valuesIterator)) + } + s.asInstanceOf[S] + } + + /** Similar to `fromIterable`, but returns a Map collection type. + * Note that the return type is now `CC[K2, V2]`. + */ + @`inline` protected final def mapFromIterable[K2, V2](it: Iterable[(K2, V2)]): CC[K2, V2] = mapFactory.from(it) + + /** The companion object of this map, providing various factory methods. + * + * @note When implementing a custom collection type and refining `CC` to the new type, this + * method needs to be overridden to return a factory for the new type (the compiler will + * issue an error otherwise). + */ + def mapFactory: MapFactory[CC] + + /** Optionally returns the value associated with a key. + * + * @param key the key value + * @return an option value containing the value associated with `key` in this map, + * or `None` if none exists. + */ + def get(key: K): Option[V] + + /** Returns the value associated with a key, or a default value if the key is not contained in the map. + * @param key the key. + * @param default a computation that yields a default value in case no binding for `key` is + * found in the map. + * @tparam V1 the result type of the default computation. + * @return the value associated with `key` if it exists, + * otherwise the result of the `default` computation. + */ + def getOrElse[V1 >: V](key: K, default: => V1): V1 = get(key) match { + case Some(v) => v + case None => default + } + + /** Retrieves the value which is associated with the given key. This + * method invokes the `default` method of the map if there is no mapping + * from the given key to a value. Unless overridden, the `default` method throws a + * `NoSuchElementException`. + * + * @param key the key + * @return the value associated with the given key, or the result of the + * map's `default` method, if none exists. + */ + @throws[NoSuchElementException] + def apply(key: K): V = get(key) match { + case None => default(key) + case Some(value) => value + } + + override /*PartialFunction*/ def applyOrElse[K1 <: K, V1 >: V](x: K1, default: K1 => V1): V1 = getOrElse(x, default(x)) + + /** Collects all keys of this map in a set. + * @return a set containing all keys of this map. + */ + def keySet: Set[K] = new KeySet + + /** The implementation class of the set returned by `keySet`. + */ + protected class KeySet extends AbstractSet[K] with GenKeySet with DefaultSerializable { + def diff(that: Set[K]): Set[K] = fromSpecific(this.view.filterNot(that)) + } + + /** A generic trait that is reused by keyset implementations */ + protected trait GenKeySet { this: Set[K] => + def iterator: Iterator[K] = 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 + override def isEmpty: Boolean = MapOps.this.isEmpty + } + + /** Collects all keys of this map in an iterable collection. + * + * @return the keys of this map as an iterable. + */ + def keys: Iterable[K] = 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 { + override def knownSize: Int = MapOps.this.knownSize + override def iterator: Iterator[V] = valuesIterator + } + + /** Creates an iterator for all keys. + * + * @return an iterator over all keys. + */ + def keysIterator: Iterator[K] = new AbstractIterator[K] { + val iter = MapOps.this.iterator + def hasNext = iter.hasNext + def next() = iter.next()._1 + } + + /** Creates an iterator for all values in this map. + * + * @return an iterator over all values that are associated with some key in this map. + */ + def valuesIterator: Iterator[V] = new AbstractIterator[V] { + val iter = MapOps.this.iterator + def hasNext = iter.hasNext + def next() = iter.next()._2 + } + + /** Apply `f` to each key/value pair for its side effects + * Note: [U] parameter needed to help scalac's type inference. + */ + def foreachEntry[U](f: (K, V) => U): Unit = { + val it = iterator + while (it.hasNext) { + val next = it.next() + f(next._1, next._2) + } + } + + /** Filters this map by retaining only keys satisfying a predicate. + * @param p the predicate used to test keys + * @return an immutable map consisting only of those key value pairs of this map where the key satisfies + * the predicate `p`. The resulting map wraps the original map without copying any elements. + */ + @deprecated("Use .view.filterKeys(f). A future version will include a strict version of this method (for now, .view.filterKeys(p).toMap).", "2.13.0") + def filterKeys(p: K => Boolean): MapView[K, V] = new MapView.FilterKeys(this, p) + + /** Transforms this map by applying a function to every retrieved value. + * @param f the function used to transform values of this map. + * @return a map view which maps every key of this map + * to `f(this(key))`. The resulting map wraps the original map without copying any elements. + */ + @deprecated("Use .view.mapValues(f). A future version will include a strict version of this method (for now, .view.mapValues(f).toMap).", "2.13.0") + def mapValues[W](f: V => W): MapView[K, W] = new MapView.MapValues(this, f) + + /** Defines the default value computation for the map, + * returned when a key is not found + * The method implemented here throws an exception, + * but it might be overridden in subclasses. + * + * @param key the given key value for which a binding is missing. + * @throws NoSuchElementException + */ + @throws[NoSuchElementException] + def default(key: K): V = + throw new NoSuchElementException("key not found: " + key) + + /** Tests whether this map contains a binding for a key. + * + * @param key the key + * @return `true` if there is a binding for `key` in this map, `false` otherwise. + */ + def contains(key: K): Boolean = get(key).isDefined + + + /** Tests whether this map contains a binding for a key. This method, + * which implements an abstract method of trait `PartialFunction`, + * is equivalent to `contains`. + * + * @param key the key + * @return `true` if there is a binding for `key` in this map, `false` otherwise. + */ + def isDefinedAt(key: K): Boolean = contains(key) + + /** Builds a new map by applying a function to all elements of this $coll. + * + * @param f the function to apply to each element. + * @return a new $coll resulting from applying the given function + * `f` to each element of this $coll and collecting the results. + */ + def map[K2, V2](f: ((K, V)) => (K2, V2)): CC[K2, V2] = mapFactory.from(new View.Map(this, f)) + + /** Builds a new collection by applying a partial function to all elements of this $coll + * on which the function is defined. + * + * @param pf the partial function which filters and maps the $coll. + * @tparam K2 the key type of the returned $coll. + * @tparam V2 the value type of the returned $coll. + * @return a new $coll resulting from applying the given partial function + * `pf` to each element on which it is defined and collecting the results. + * The order of the elements is preserved. + */ + def collect[K2, V2](pf: PartialFunction[(K, V), (K2, V2)]): CC[K2, V2] = + mapFactory.from(new View.Collect(this, pf)) + + /** Builds a new map by applying a function to all elements of this $coll + * and using the elements of the resulting collections. + * + * @param f the function to apply to each element. + * @return a new $coll resulting from applying the given collection-valued function + * `f` to each element of this $coll and concatenating the results. + */ + def flatMap[K2, V2](f: ((K, V)) => IterableOnce[(K2, V2)]): CC[K2, V2] = mapFactory.from(new View.FlatMap(this, f)) + + /** Returns a new $coll containing the elements from the left hand operand followed by the elements from the + * right hand operand. The element type of the $coll is the most specific superclass encompassing + * the element types of the two operands. + * + * @param suffix the iterable to append. + * @return a new $coll which contains all elements + * of this $coll followed by all elements of `suffix`. + */ + def concat[V2 >: V](suffix: collection.IterableOnce[(K, V2)]): CC[K, V2] = mapFactory.from(suffix match { + case it: Iterable[(K, V2)] => new View.Concat(this, it) + case _ => iterator.concat(suffix.iterator) + }) + + // Not final because subclasses refine the result type, e.g. in SortedMap, the result type is + // SortedMap's CC, while Map's CC is fixed to Map + /** Alias for `concat` */ + /*@`inline` final*/ def ++ [V2 >: V](xs: collection.IterableOnce[(K, V2)]): CC[K, V2] = 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] = + 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] = + 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 = { + 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 { + case that: Iterable[(K, V1)] => that + case that => View.from(that) + } + mapFactory.from(new View.Concat(thatIterable, this)) + } +} + +object MapOps { + /** Specializes `WithFilter` for Map collection types by adding overloads to transformation + * operations that can return a Map. + * + * @define coll map collection + */ + @SerialVersionUID(3L) + class WithFilter[K, +V, +IterableCC[_], +CC[_, _] <: IterableOps[_, AnyConstr, _]]( + self: MapOps[K, V, CC, _] 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] = + self.mapFactory.from(new View.Map(filtered, f)) + + def flatMap[K2, V2](f: ((K, V)) => IterableOnce[(K2, V2)]): CC[K2, V2] = + self.mapFactory.from(new View.FlatMap(filtered, f)) + + override def withFilter(q: ((K, V)) => Boolean): WithFilter[K, V, IterableCC, CC] = + new WithFilter[K, V, IterableCC, CC](self, (kv: (K, V)) => p(kv) && q(kv)) + + } + +} + +/** + * $factoryInfo + * @define coll map + * @define Coll `Map` + */ +@SerialVersionUID(3L) +object Map extends MapFactory.Delegate[Map](immutable.Map) { + private val DefaultSentinel: AnyRef = new AnyRef + private val DefaultSentinelFn: () => AnyRef = () => DefaultSentinel +} + +/** Explicit instantiation of the `Map` trait to reduce class file size in subclasses. */ +abstract class AbstractMap[K, +V] extends AbstractIterable[(K, V)] with Map[K, V] diff --git a/tests/pos-custom-args/captures/stdlib/runtime/PStatics.scala b/tests/pos/stdlib/runtime/PStatics.scala similarity index 100% rename from tests/pos-custom-args/captures/stdlib/runtime/PStatics.scala rename to tests/pos/stdlib/runtime/PStatics.scala diff --git a/tests/pos-custom-args/captures/stdlib/runtime_PStatics.scala b/tests/pos/stdlib/runtime_PStatics.scala similarity index 100% rename from tests/pos-custom-args/captures/stdlib/runtime_PStatics.scala rename to tests/pos/stdlib/runtime_PStatics.scala From 46327f557450e30de0fa0422d4cf206482030e3f Mon Sep 17 00:00:00 2001 From: odersky Date: Sun, 16 Jul 2023 15:52:22 +0200 Subject: [PATCH 24/37] Add {mutable,immutable}.Iterable.scala to stdlib tests --- .../collection/immutable/Iterable.scala | 39 +++++++++++++++++++ .../stdlib/collection/mutable/Iterable.scala | 37 ++++++++++++++++++ tests/pos/stdlib/immutable_Iterable.scala | 1 + tests/pos/stdlib/mutable_Iterable.scala | 1 + 4 files changed, 78 insertions(+) create mode 100644 tests/pos/stdlib/collection/immutable/Iterable.scala create mode 100644 tests/pos/stdlib/collection/mutable/Iterable.scala create mode 120000 tests/pos/stdlib/immutable_Iterable.scala create mode 120000 tests/pos/stdlib/mutable_Iterable.scala diff --git a/tests/pos/stdlib/collection/immutable/Iterable.scala b/tests/pos/stdlib/collection/immutable/Iterable.scala new file mode 100644 index 000000000000..44f13d0f2895 --- /dev/null +++ b/tests/pos/stdlib/collection/immutable/Iterable.scala @@ -0,0 +1,39 @@ +/* + * Scala (https://www.scala-lang.org) + * + * Copyright EPFL and Lightbend, Inc. + * + * 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.collection.immutable + +import scala.collection.{IterableFactory, IterableFactoryDefaults} +import language.experimental.captureChecking + +/** A trait for collections that are guaranteed immutable. + * + * @tparam A the element type of the collection + * + * @define coll immutable collection + * @define Coll `immutable.Iterable` + */ +trait Iterable[+A] extends collection.Iterable[A] + with collection.IterableOps[A, Iterable, Iterable[A]] + with IterableFactoryDefaults[A, Iterable] { + this: Iterable[A]^ => + + override def iterableFactory: IterableFactory[Iterable] = Iterable +} + +@SerialVersionUID(3L) +object Iterable extends IterableFactory.Delegate[Iterable](List) { + override def from[E](it: IterableOnce[E]): Iterable[E] = it match { + case iterable: Iterable[E] => iterable + case _ => super.from(it) + } +} diff --git a/tests/pos/stdlib/collection/mutable/Iterable.scala b/tests/pos/stdlib/collection/mutable/Iterable.scala new file mode 100644 index 000000000000..bf286157b376 --- /dev/null +++ b/tests/pos/stdlib/collection/mutable/Iterable.scala @@ -0,0 +1,37 @@ +/* + * Scala (https://www.scala-lang.org) + * + * Copyright EPFL and Lightbend, Inc. + * + * 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.collection.mutable + +import scala.collection.{IterableFactory, IterableFactoryDefaults} +import language.experimental.captureChecking + +trait Iterable[A] + extends collection.Iterable[A] + with collection.IterableOps[A, Iterable, Iterable[A]] + with IterableFactoryDefaults[A, Iterable] { + this: Iterable[A]^ => + + override def iterableFactory: IterableFactory[Iterable] = Iterable +} + +/** + * $factoryInfo + * @define coll mutable collection + * @define Coll `mutable.Iterable` + */ +@SerialVersionUID(3L) +object Iterable extends IterableFactory.Delegate[Iterable](ArrayBuffer) + +/** Explicit instantiation of the `Iterable` trait to reduce class file size in subclasses. */ +abstract class AbstractIterable[A] extends scala.collection.AbstractIterable[A] with Iterable[A]: + this: AbstractIterable[A]^ => diff --git a/tests/pos/stdlib/immutable_Iterable.scala b/tests/pos/stdlib/immutable_Iterable.scala new file mode 120000 index 000000000000..272ce14cbf62 --- /dev/null +++ b/tests/pos/stdlib/immutable_Iterable.scala @@ -0,0 +1 @@ +collection/immutable/Iterable.scala \ No newline at end of file diff --git a/tests/pos/stdlib/mutable_Iterable.scala b/tests/pos/stdlib/mutable_Iterable.scala new file mode 120000 index 000000000000..cfc67fd086e6 --- /dev/null +++ b/tests/pos/stdlib/mutable_Iterable.scala @@ -0,0 +1 @@ +collection/mutable/Iterable.scala \ No newline at end of file From b23bf45d4ec367498b3331d0c9f4381a215bbe3d Mon Sep 17 00:00:00 2001 From: odersky Date: Sun, 16 Jul 2023 18:30:45 +0200 Subject: [PATCH 25/37] Add View.scala to stdlib test --- tests/pos/stdlib/View.scala | 1 + tests/pos/stdlib/collection/Iterable.scala | 16 +- .../pos/stdlib/collection/IterableOnce.scala | 4 +- tests/pos/stdlib/collection/Iterator.scala | 4 +- tests/pos/stdlib/collection/View.scala | 542 ++++++++++++++++++ 5 files changed, 555 insertions(+), 12 deletions(-) create mode 120000 tests/pos/stdlib/View.scala create mode 100644 tests/pos/stdlib/collection/View.scala diff --git a/tests/pos/stdlib/View.scala b/tests/pos/stdlib/View.scala new file mode 120000 index 000000000000..ed2ce971b9e6 --- /dev/null +++ b/tests/pos/stdlib/View.scala @@ -0,0 +1 @@ +collection/View.scala \ No newline at end of file diff --git a/tests/pos/stdlib/collection/Iterable.scala b/tests/pos/stdlib/collection/Iterable.scala index 9bf65f4f5994..85c0debc6685 100644 --- a/tests/pos/stdlib/collection/Iterable.scala +++ b/tests/pos/stdlib/collection/Iterable.scala @@ -685,7 +685,7 @@ trait IterableOps[+A, +CC[_], +C] extends Any with IterableOnce[A] with Iterable 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]^{this, f} = 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]^{this} = flatMap(asIterable) @@ -714,8 +714,8 @@ trait IterableOps[+A, +CC[_], +C] extends Any with IterableOnce[A] with Iterable * and the second one made of those wrapped in [[scala.util.Right]]. */ def partitionMap[A1, A2](f: A => Either[A1, A2]): (CC[A1]^{this, f}, CC[A2]^{this, f}) = { - val left: View[A1]^{f} = new LeftPartitionMapped(this, f) - val right: View[A2]^{f} = new RightPartitionMapped(this, f) + val left: View[A1]^{f, this} = new LeftPartitionMapped(this, f) + val right: View[A2]^{f, this} = new RightPartitionMapped(this, f) (iterableFactory.from(left), iterableFactory.from(right)) } @@ -788,8 +788,8 @@ trait IterableOps[+A, +CC[_], +C] extends Any with IterableOnce[A] with Iterable * half of each element pair of this $coll. */ def unzip[A1, A2](implicit asPair: A -> (A1, A2)): (CC[A1]^{this}, CC[A2]^{this}) = { - val first: View[A1] = new View.Map[A, A1](this, asPair(_)._1) - val second: View[A2] = new View.Map[A, A2](this, asPair(_)._2) + 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)) } @@ -815,9 +815,9 @@ trait IterableOps[+A, +CC[_], +C] extends Any with IterableOnce[A] with Iterable * third member of each element triple of this $coll. */ def unzip3[A1, A2, A3](implicit asTriple: A -> (A1, A2, A3)): (CC[A1]^{this}, CC[A2]^{this}, CC[A3]^{this}) = { - 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) + 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)) } diff --git a/tests/pos/stdlib/collection/IterableOnce.scala b/tests/pos/stdlib/collection/IterableOnce.scala index 359f6ad5226c..f75eab54a89b 100644 --- a/tests/pos/stdlib/collection/IterableOnce.scala +++ b/tests/pos/stdlib/collection/IterableOnce.scala @@ -247,7 +247,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]^{f} = 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) } @@ -441,7 +441,7 @@ trait IterableOnceOps[+A, +CC[_], +C] extends Any { this: IterableOnce[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]): CC[B]^{this, f} + def flatMap[B](f: A => IterableOnce[B]^): CC[B]^{this, f} /** Converts this $coll of iterable collections into * a $coll formed by the elements of these iterable diff --git a/tests/pos/stdlib/collection/Iterator.scala b/tests/pos/stdlib/collection/Iterator.scala index 4b68258bd77f..b5c9c42c4732 100644 --- a/tests/pos/stdlib/collection/Iterator.scala +++ b/tests/pos/stdlib/collection/Iterator.scala @@ -588,8 +588,8 @@ trait Iterator[+A] extends IterableOnce[A] with IterableOnceOps[A, Iterator, Ite def next() = f(self.next()) } - def flatMap[B](f: A => IterableOnce[B]): Iterator[B]^{this, f} = new AbstractIterator[B] { - private[this] var cur: Iterator[B] = Iterator.empty + 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 diff --git a/tests/pos/stdlib/collection/View.scala b/tests/pos/stdlib/collection/View.scala new file mode 100644 index 000000000000..8e2ee3ad9e32 --- /dev/null +++ b/tests/pos/stdlib/collection/View.scala @@ -0,0 +1,542 @@ +/* + * Scala (https://www.scala-lang.org) + * + * Copyright EPFL and Lightbend, Inc. + * + * 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.collection + +import scala.annotation.{nowarn, tailrec} +import scala.collection.mutable.{ArrayBuffer, Builder} +import scala.collection.immutable.LazyList +import language.experimental.captureChecking + +/** 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`), + * or when the view is converted to a strict collection type (using the `to` operation). + * @define coll view + * @define Coll `View` + */ +trait View[+A] extends Iterable[A] with IterableOps[A, View, View[A]] with IterableFactoryDefaults[A, View] with Serializable { + this: View[A]^ => + + override def view: View[A]^{this} = this + + override def iterableFactory: IterableFactory[View] = View + + override def empty: scala.collection.View[A] = iterableFactory.empty + + override def toString: String = className + "()" + + @nowarn("""cat=deprecation&origin=scala\.collection\.Iterable\.stringPrefix""") + override protected[this] def stringPrefix: String = "View" + + @deprecated("Views no longer know about their underlying collection type; .force always returns an IndexedSeq", "2.13.0") + @`inline` def force: IndexedSeq[A] = toIndexedSeq +} + +/** This object reifies operations on views as case classes + * + * @define Coll View + * @define coll view + */ +@SerialVersionUID(3L) +object View extends IterableFactory[View] { + + /** + * @return A `View[A]` whose underlying iterator is provided by the `it` parameter-less function. + * + * @param it Function creating the iterator to be used by the view. This function must always return + * a fresh `Iterator`, otherwise the resulting view will be effectively iterable only once. + * + * @tparam A View element type + */ + def fromIteratorProvider[A](it: () => Iterator[A]^): View[A]^{it} = new AbstractView[A] { + def iterator: Iterator[A]^{it} = it() + } + + /** + * @return A view iterating over the given `Iterable` + * + * @param it The `IterableOnce` to view. A proper `Iterable` is used directly. If it is really only + * `IterableOnce` it gets memoized on the first traversal. + * + * @tparam E View element type + */ + def from[E](it: IterableOnce[E]^): View[E]^{it} = it match { + case it: View[E] => it + case it: Iterable[E] => View.fromIteratorProvider(() => it.iterator) + case _ => LazyList.from(it).view + } + + def empty[A]: View[A] = Empty + + def newBuilder[A]: Builder[A, View[A]] = ArrayBuffer.newBuilder[A].mapResult(from) + + override def apply[A](xs: A*): View[A] = new Elems(xs: _*) + + /** The empty view */ + @SerialVersionUID(3L) + case object Empty extends AbstractView[Nothing] { + def iterator = Iterator.empty + override def knownSize = 0 + override def isEmpty: Boolean = true + } + + /** A view with exactly one element */ + @SerialVersionUID(3L) + class Single[A](a: A) extends AbstractView[A] { + def iterator: Iterator[A] = Iterator.single(a) + override def knownSize: Int = 1 + override def isEmpty: Boolean = false + } + + /** A view with given elements */ + @SerialVersionUID(3L) + class Elems[A](xs: A*) extends AbstractView[A], Pure { + def iterator = xs.iterator + override def knownSize = xs.knownSize + override def isEmpty: Boolean = xs.isEmpty + } + + /** A view containing the results of some element computation a number of times. */ + @SerialVersionUID(3L) + class Fill[A](n: Int)(elem: => A) extends AbstractView[A] { + def iterator: Iterator[A]^{elem} = Iterator.fill(n)(elem) + override def knownSize: Int = 0 max n + override def isEmpty: Boolean = n <= 0 + } + + /** 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]^{f} = Iterator.tabulate(n)(f) + override def knownSize: Int = 0 max n + override def isEmpty: Boolean = n <= 0 + } + + /** 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]^{f} = Iterator.iterate(start)(f).take(len) + override def knownSize: Int = 0 max len + override def isEmpty: Boolean = len <= 0 + } + + /** A view that uses a function `f` to produce elements of type `A` and update + * an internal state `S`. + */ + @SerialVersionUID(3L) + class Unfold[A, S](initial: S)(f: S => Option[(A, S)]) extends AbstractView[A] { + def iterator: Iterator[A]^{f} = Iterator.unfold(initial)(f) + } + + /** An `IterableOps` whose collection type and collection type constructor are unknown */ + type SomeIterableOps[A] = IterableOps[A, AnyConstr, _] + + /** 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] { + def iterator: Iterator[A]^{underlying, p} = 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]^{underlying, p} = + underlying match { + case filter: Filter[A] if filter.isFlipped == isFlipped => new Filter(filter.underlying, a => filter.p(a) && p(a), isFlipped) + 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} = 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] { + def iterator: Iterator[A1]^{underlying, f} = new AbstractIterator[A1] { + private[this] val self = underlying.iterator + private[this] var hd: A1 = _ + private[this] var hdDefined: Boolean = false + def hasNext = hdDefined || { + @tailrec + def findNext(): Boolean = + if (self.hasNext) { + f(self.next()) match { + case Left(a1) => hd = a1; hdDefined = true; true + case Right(_) => findNext() + } + } else false + findNext() + } + def next() = + if (hasNext) { + hdDefined = false + hd + } else Iterator.empty.next() + } + } + + @SerialVersionUID(3L) + class RightPartitionMapped[A, A1, A2](underlying: SomeIterableOps[A]^, f: A => Either[A1, A2]) extends AbstractView[A2] { + def iterator: Iterator[A2]^{this} = new AbstractIterator[A2] { + private[this] val self = underlying.iterator + private[this] var hd: A2 = _ + private[this] var hdDefined: Boolean = false + def hasNext = hdDefined || { + @tailrec + def findNext(): Boolean = + if (self.hasNext) { + f(self.next()) match { + case Left(_) => findNext() + case Right(a2) => hd = a2; hdDefined = true; true + } + } else false + findNext() + } + def next() = + if (hasNext) { + hdDefined = false + hd + } else Iterator.empty.next() + } + } + + /** A view that drops leading elements of the underlying collection. */ + @SerialVersionUID(3L) + class Drop[A](underlying: SomeIterableOps[A]^, n: Int) extends AbstractView[A] { + def iterator: Iterator[A]^{underlying} = underlying.iterator.drop(n) + protected val normN = n max 0 + override def knownSize = { + val size = underlying.knownSize + if (size >= 0) (size - normN) max 0 else -1 + } + override def isEmpty: Boolean = iterator.isEmpty + } + + /** A view that drops trailing elements of the underlying collection. */ + @SerialVersionUID(3L) + class DropRight[A](underlying: SomeIterableOps[A]^, n: Int) extends AbstractView[A] { + def iterator: Iterator[A]^{underlying} = dropRightIterator(underlying.iterator, n) + protected val normN = n max 0 + override def knownSize = { + val size = underlying.knownSize + if (size >= 0) (size - normN) max 0 else -1 + } + override def isEmpty: Boolean = + if(knownSize >= 0) knownSize == 0 + else iterator.isEmpty + } + + @SerialVersionUID(3L) + class DropWhile[A](underlying: SomeIterableOps[A]^, p: A => Boolean) extends AbstractView[A] { + def iterator: Iterator[A]^{underlying, p} = underlying.iterator.dropWhile(p) + override def knownSize: Int = if (underlying.knownSize == 0) 0 else super.knownSize + override def isEmpty: Boolean = iterator.isEmpty + } + + /** A view that takes leading elements of the underlying collection. */ + @SerialVersionUID(3L) + class Take[+A](underlying: SomeIterableOps[A]^, n: Int) extends AbstractView[A] { + def iterator: Iterator[A]^{underlying} = underlying.iterator.take(n) + protected val normN = n max 0 + override def knownSize = { + val size = underlying.knownSize + if (size >= 0) size min normN else -1 + } + override def isEmpty: Boolean = iterator.isEmpty + } + + /** A view that takes trailing elements of the underlying collection. */ + @SerialVersionUID(3L) + class TakeRight[+A](underlying: SomeIterableOps[A]^, n: Int) extends AbstractView[A] { + def iterator: Iterator[A]^{underlying} = takeRightIterator(underlying.iterator, n) + protected val normN = n max 0 + override def knownSize = { + val size = underlying.knownSize + if (size >= 0) size min normN else -1 + } + override def isEmpty: Boolean = + if(knownSize >= 0) knownSize == 0 + else iterator.isEmpty + } + + @SerialVersionUID(3L) + class TakeWhile[A](underlying: SomeIterableOps[A]^, p: A => Boolean) extends AbstractView[A] { + def iterator: Iterator[A]^{underlying, p} = 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, op} = underlying.iterator.scanLeft(z)(op) + override def knownSize: Int = { + val size = underlying.knownSize + if (size >= 0) size + 1 else -1 + } + override def isEmpty: Boolean = iterator.isEmpty + } + + /** A view that maps elements of the underlying collection. */ + @SerialVersionUID(3L) + class Map[+A, +B](underlying: SomeIterableOps[A]^, f: A => B) extends AbstractView[B] { + def iterator: Iterator[B]^{underlying, f} = underlying.iterator.map(f) + override def knownSize = underlying.knownSize + override def isEmpty: Boolean = underlying.isEmpty + } + + /** 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] { + def iterator: Iterator[B]^{underlying, f} = underlying.iterator.flatMap(f) + override def knownSize: Int = if (underlying.knownSize == 0) 0 else super.knownSize + override def isEmpty: Boolean = iterator.isEmpty + } + + /** 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] { + def iterator: Iterator[B]^{underlying, pf} = underlying.iterator.collect(pf) + } + + /** A view that concatenates elements of the prefix collection or iterator with the elements + * of the suffix collection or iterator. + */ + @SerialVersionUID(3L) + class Concat[A](prefix: SomeIterableOps[A]^, suffix: SomeIterableOps[A]^) extends AbstractView[A] { + def iterator: Iterator[A]^{prefix, suffix} = prefix.iterator ++ suffix.iterator + override def knownSize = { + val prefixSize = prefix.knownSize + if (prefixSize >= 0) { + val suffixSize = suffix.knownSize + if (suffixSize >= 0) prefixSize + suffixSize + else -1 + } + else -1 + } + override def isEmpty: Boolean = prefix.isEmpty && suffix.isEmpty + } + + /** A view that zips elements of the underlying collection with the elements + * of another collection. + */ + @SerialVersionUID(3L) + class Zip[A, B](underlying: SomeIterableOps[A]^, other: Iterable[B]^) extends AbstractView[(A, B)] { + def iterator: Iterator[(A, B)]^{underlying, other} = underlying.iterator.zip(other) + override def knownSize = { + val s1 = underlying.knownSize + if (s1 == 0) 0 else { + val s2 = other.knownSize + if (s2 == 0) 0 else s1 min s2 + } + } + override def isEmpty: Boolean = underlying.isEmpty || other.isEmpty + } + + /** A view that zips elements of the underlying collection with the elements + * of another collection. If one of the two collections is shorter than the other, + * 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)] { + def iterator: Iterator[(A, B)]^{underlying, other} = underlying.iterator.zipAll(other, thisElem, thatElem) + override def knownSize = { + val s1 = underlying.knownSize + if(s1 == -1) -1 else { + val s2 = other.knownSize + if(s2 == -1) -1 else s1 max s2 + } + } + override def isEmpty: Boolean = underlying.isEmpty && other.isEmpty + } + + /** 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]^{underlying} = + val ct = new Concat(underlying, new View.Single(elem)) + ct.iterator // CC TODO breakout into `ct` needed, otherwise "cannot establish a reference" error + override def knownSize: Int = { + val size = underlying.knownSize + if (size >= 0) size + 1 else -1 + } + override def isEmpty: Boolean = false + } + + /** 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]^{underlying} = + val ct = new Concat(new View.Single(elem), underlying) + ct.iterator // CC TODO breakout into `ct` needed, otherwise "cannot establish a reference" error + override def knownSize: Int = { + val size = underlying.knownSize + if (size >= 0) size + 1 else -1 + } + override def isEmpty: Boolean = false + } + + @SerialVersionUID(3L) + 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 = { + val value = if (i == index) { it.next(); elem } else it.next() + i += 1 + value + } + def hasNext: Boolean = + if(it.hasNext) true + else if(index >= i) throw new IndexOutOfBoundsException(index.toString) + else false + } + override def knownSize: Int = underlying.knownSize + override def isEmpty: Boolean = iterator.isEmpty + } + + @SerialVersionUID(3L) + 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} = other match { + case other: Iterable[A] => other + case other => LazyList.from(other) + } + + def iterator: Iterator[A]^{underlying, other} = 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} = 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} = underlying.iterator.padTo(len, elem) + + override def knownSize: Int = { + val size = underlying.knownSize + if (size >= 0) size max len else -1 + } + override def isEmpty: Boolean = underlying.isEmpty && len <= 0 + } + + 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 + else if(k > 0) it.drop((k-n) max 0) + else new TakeRightIterator[A](it, n) + } + + private final class TakeRightIterator[A](underlying: Iterator[A]^, maxlen: Int) extends AbstractIterator[A] { + private[this] var current: Iterator[A]^{underlying} = underlying + private[this] var len: Int = -1 + private[this] var pos: Int = 0 + private[this] var buf: ArrayBuffer[AnyRef] = _ + def init(): Unit = if(buf eq null) { + buf = new ArrayBuffer[AnyRef](maxlen min 256) + len = 0 + while(current.hasNext) { + val n = current.next().asInstanceOf[AnyRef] + if(pos >= buf.length) buf.addOne(n) + else buf(pos) = n + pos += 1 + if(pos == maxlen) pos = 0 + len += 1 + } + current = null + if(len > maxlen) len = maxlen + pos = pos - len + if(pos < 0) pos += maxlen + } + override def knownSize = len + def hasNext: Boolean = { + init() + len > 0 + } + def next(): A = { + init() + if(len == 0) Iterator.empty.next() + else { + val x = buf(pos).asInstanceOf[A] + pos += 1 + if(pos == maxlen) pos = 0 + len -= 1 + x + } + } + override def drop(n: Int): Iterator[A]^{this} = { + init() + if (n > 0) { + len = (len - n) max 0 + pos = (pos + n) % maxlen + } + this + } + } + + private[collection] def dropRightIterator[A](it: Iterator[A]^, n: Int): Iterator[A]^{it} = { + if(n <= 0) it + else { + val k = it.knownSize + if(k >= 0) it.take(k - n) + else new DropRightIterator[A](it, n) + } + } + + private final class DropRightIterator[A](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] = _ + def init(): Unit = if(buf eq null) { + buf = new ArrayBuffer[AnyRef](maxlen min 256) + while(pos < maxlen && underlying.hasNext) { + buf.addOne(underlying.next().asInstanceOf[AnyRef]) + pos += 1 + } + if(!underlying.hasNext) len = 0 + pos = 0 + } + override def knownSize = len + def hasNext: Boolean = { + init() + len != 0 + } + def next(): A = { + if(!hasNext) Iterator.empty.next() + else { + val x = buf(pos).asInstanceOf[A] + if(len == -1) { + buf(pos) = underlying.next().asInstanceOf[AnyRef] + if(!underlying.hasNext) len = 0 + } else len -= 1 + pos += 1 + if(pos == maxlen) pos = 0 + x + } + } + } +} + +/** Explicit instantiation of the `View` trait to reduce class file size in subclasses. */ +@SerialVersionUID(3L) +abstract class AbstractView[+A] extends scala.collection.AbstractIterable[A] with View[A] From fc9bb7547704c5fd825e9bfe9f30a0f64cbe4fda Mon Sep 17 00:00:00 2001 From: odersky Date: Sun, 16 Jul 2023 19:38:23 +0200 Subject: [PATCH 26/37] Add collection/Seq to stdlib tests --- tests/pos-custom-args/captures/unzip.scala | 7 + tests/pos/stdlib/Seq.scala | 1 + tests/pos/stdlib/collection/Iterator.scala | 2 +- tests/pos/stdlib/collection/Seq.scala | 1201 ++++++++++++++++++++ 4 files changed, 1210 insertions(+), 1 deletion(-) create mode 100644 tests/pos-custom-args/captures/unzip.scala create mode 120000 tests/pos/stdlib/Seq.scala create mode 100644 tests/pos/stdlib/collection/Seq.scala diff --git a/tests/pos-custom-args/captures/unzip.scala b/tests/pos-custom-args/captures/unzip.scala new file mode 100644 index 000000000000..228142e93b01 --- /dev/null +++ b/tests/pos-custom-args/captures/unzip.scala @@ -0,0 +1,7 @@ +class Seqq[A]: + def unzip[A1, A2](using asPair: A -> (A1, A2)): (Seq[A1], Seq[A2]) = ??? + +def Test = + val s: Seqq[(String, Int)] = ??? + s.unzip(using Predef.$conforms[(String, Int)]) + diff --git a/tests/pos/stdlib/Seq.scala b/tests/pos/stdlib/Seq.scala new file mode 120000 index 000000000000..c3292391820a --- /dev/null +++ b/tests/pos/stdlib/Seq.scala @@ -0,0 +1 @@ +collection/Seq.scala \ No newline at end of file diff --git a/tests/pos/stdlib/collection/Iterator.scala b/tests/pos/stdlib/collection/Iterator.scala index b5c9c42c4732..4903a1dae0a6 100644 --- a/tests/pos/stdlib/collection/Iterator.scala +++ b/tests/pos/stdlib/collection/Iterator.scala @@ -841,7 +841,7 @@ trait Iterator[+A] extends IterableOnce[A] with IterableOnceOps[A, Iterator, Ite * * @inheritdoc */ - def sameElements[B >: A](that: IterableOnce[B]): Boolean = { + def sameElements[B >: A](that: IterableOnce[B]^): Boolean = { val those = that.iterator while (hasNext && those.hasNext) if (next() != those.next()) diff --git a/tests/pos/stdlib/collection/Seq.scala b/tests/pos/stdlib/collection/Seq.scala new file mode 100644 index 000000000000..14a276cfa579 --- /dev/null +++ b/tests/pos/stdlib/collection/Seq.scala @@ -0,0 +1,1201 @@ +/* + * Scala (https://www.scala-lang.org) + * + * Copyright EPFL and Lightbend, Inc. + * + * 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.collection + +import scala.collection.immutable.Range +import scala.util.hashing.MurmurHash3 +import Searching.{Found, InsertionPoint, SearchResult} +import scala.annotation.nowarn +import language.experimental.captureChecking +import caps.unsafe.unsafeAssumePure + +/** Base trait for sequence collections + * + * @tparam A the element type of the collection + */ +trait Seq[+A] + extends Iterable[A] + with PartialFunction[Int, A] + with SeqOps[A, Seq, Seq[A]] + with IterableFactoryDefaults[A, Seq] + with Equals { + this: Seq[A] => + + override def iterableFactory: SeqFactory[Seq] = Seq + + def canEqual(that: Any): Boolean = true + + override def equals(o: Any): Boolean = + (this eq o.asInstanceOf[AnyRef]) || (o match { + case seq: Seq[A @unchecked] if seq.canEqual(this) => sameElements(seq) + case _ => false + }) + + override def hashCode(): Int = MurmurHash3.seqHash(this) + + override def toString(): String = super[Iterable].toString() + + @nowarn("""cat=deprecation&origin=scala\.collection\.Iterable\.stringPrefix""") + override protected[this] def stringPrefix: String = "Seq" +} + +/** + * $factoryInfo + * @define coll sequence + * @define Coll `Seq` + */ +@SerialVersionUID(3L) +object Seq extends SeqFactory.Delegate[Seq](immutable.Seq) + +/** Base trait for Seq operations + * + * @tparam A the element type of the collection + * @tparam CC type constructor of the collection (e.g. `List`, `Set`). Operations returning a collection + * with a different type of element `B` (e.g. `map`) return a `CC[B]`. + * @tparam C type of the collection (e.g. `List[Int]`, `String`, `BitSet`). Operations returning a collection + * with the same type of element (e.g. `drop`, `filter`) return a `C`. + * @define orderDependent + * @define orderDependentFold + * @define mayNotTerminateInf + * + * Note: may not terminate for infinite-sized collections. + * + * @define willNotTerminateInf + * + * Note: will not terminate for infinite-sized collections. + * + * @define coll sequence + * @define Coll `Seq` + */ +trait SeqOps[+A, +CC[_], +C] extends AnyRef + // CC TODO: Our treechecker disallows classes in universal traits, but the typer accepts + // them. Since SeqOps contains nested classes, I changed it to be no longer a universal trait. + // Alternatively we could + // - Change TreeChecker to accept this + // - Move nested classes out of the trait + with IterableOps[A, CC, C] { self => + + override def view: SeqView[A] = new SeqView.Id[A](this) + + /** Get 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`. */ + @throws[IndexOutOfBoundsException] + def apply(i: Int): A + + /** The length (number of elements) of the $coll. `size` is an alias for `length` in `Seq` collections. */ + def length: Int + + /** A copy of the $coll with an element prepended. + * + * Also, the original $coll is not modified, so you will want to capture the result. + * + * Example: + * {{{ + * scala> val x = List(1) + * x: List[Int] = List(1) + * + * scala> val y = 2 +: x + * y: List[Int] = List(2, 1) + * + * scala> println(x) + * List(1) + * }}} + * + * @param elem the prepended element + * @tparam B the element type of the returned $coll. + * + * @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)) + + /** 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) + + /** A copy of this $coll with an element appended. + * + * $willNotTerminateInf + * + * Example: + * {{{ + * scala> val a = List(1) + * a: List[Int] = List(1) + * + * scala> val b = a :+ 2 + * b: List[Int] = List(1, 2) + * + * scala> println(a) + * List(1) + * }}} + * + * @param elem the appended element + * @tparam B the element type of the returned $coll. + * @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)) + + /** 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) + + /** As with `:++`, returns a new collection containing the elements from the left operand followed by the + * elements from the right operand. + * + * It differs from `:++` in that the right operand determines the type of + * the resulting collection rather than the left one. + * Mnemonic: the COLon is on the side of the new COLlection type. + * + * @param prefix the iterable to prepend. + * @tparam B the element type of the returned collection. + * @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 { + 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) + + /** Returns a new $coll containing the elements from the left hand operand followed by the elements from the + * right hand operand. The element type of the $coll is the most specific superclass encompassing + * the element types of the two operands. + * + * @param suffix the iterable to append. + * @tparam B the element type of the returned collection. + * @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).unsafeAssumePure + + /** Alias for `appendedAll` */ + @`inline` final def :++ [B >: A](suffix: IterableOnce[B]^): CC[B] = 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) + + /** 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`. + * + * @param that the sequence to add. + * @tparam B the element type of the returned $coll. + * @return a new collection which contains all elements of this $coll + * 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) + + final override def size: Int = length + + /** Selects all the elements of this $coll ignoring the duplicates. + * + * @return a new $coll consisting of all the elements of this $coll without duplicates. + */ + def distinct: C = distinctBy(identity) + + /** Selects all the elements of this $coll ignoring the duplicates as determined by `==` after applying + * the transforming function `f`. + * + * @param f The transforming function whose result is used to determine the uniqueness of each element + * @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)) + + /** Returns new $coll with elements in reversed order. + * + * $willNotTerminateInf + * $willForceEvaluation + * + * @return A new $coll with all elements of this $coll in reversed order. + */ + def reverse: C = fromSpecific(reversed) + + /** An iterator yielding elements in reversed order. + * + * $willNotTerminateInf + * + * Note: `xs.reverseIterator` is the same as `xs.reverse.iterator` but might be more efficient. + * + * @return an iterator yielding the elements of this $coll in reversed order + */ + def reverseIterator: Iterator[A] = reversed.iterator + + /** Tests whether this $coll contains the given sequence at a given index. + * + * '''Note''': If the both the receiver object `this` and the argument + * `that` are infinite sequences this method may not terminate. + * + * @param that the sequence to test + * @param offset the index where the sequence is searched. + * @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 = { + val i = iterator drop offset + val j = that.iterator + while (j.hasNext && i.hasNext) + if (i.next() != j.next()) + return false + + !j.hasNext + } + + /** Tests whether this $coll ends with the given sequence. + * $willNotTerminateInf + * @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 = { + if (that.isEmpty) true + else { + val i = iterator.drop(length - that.size) + val j = that.iterator + while (i.hasNext && j.hasNext) + if (i.next() != j.next()) + return false + + !j.hasNext + } + } + + /** Tests whether this $coll contains given index. + * + * The implementations of methods `apply` and `isDefinedAt` turn a `Seq[A]` into + * a `PartialFunction[Int, A]`. + * + * @param idx the index to test + * @return `true` if this $coll contains an element at position `idx`, `false` otherwise. + */ + def isDefinedAt(idx: Int): Boolean = idx >= 0 && lengthIs > idx + + /** A copy of this $coll with an element value appended until a given target length is reached. + * + * @param len the target length + * @param elem the padding value + * @tparam B the element type of the returned $coll. + * @return a new $coll consisting of + * 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)) + + /** Computes the length of the longest segment that starts from the first element + * and whose elements all satisfy some predicate. + * + * $mayNotTerminateInf + * + * @param p the predicate used to test elements. + * @return the length of the longest segment of this $coll that starts from the first element + * such that every element of the segment satisfies the predicate `p`. + */ + final def segmentLength(p: A => Boolean): Int = segmentLength(p, 0) + + /** Computes the length of the longest segment that starts from some index + * and whose elements all satisfy some predicate. + * + * $mayNotTerminateInf + * + * @param p the predicate used to test elements. + * @param from the index where the search starts. + * @return the length of the longest segment of this $coll starting from index `from` + * such that every element of the segment satisfies the predicate `p`. + */ + def segmentLength(p: A => Boolean, from: Int): Int = { + var i = 0 + val it = iterator.drop(from) + while (it.hasNext && p(it.next())) + i += 1 + i + } + + /** Returns the length of the longest prefix whose elements all satisfy some predicate. + * + * $mayNotTerminateInf + * + * @param p the predicate used to test elements. + * @return the length of the longest prefix of this $coll + * such that every element of the segment satisfies the predicate `p`. + */ + @deprecated("Use segmentLength instead of prefixLength", "2.13.0") + @`inline` final def prefixLength(p: A => Boolean): Int = segmentLength(p, 0) + + /** Finds index of the first element satisfying some predicate after or at some start index. + * + * $mayNotTerminateInf + * + * @param p the predicate used to test elements. + * @param from the start index + * @return the index `>= from` of the first element of this $coll that satisfies the predicate `p`, + * or `-1`, if none exists. + */ + def indexWhere(p: A => Boolean, from: Int): Int = iterator.indexWhere(p, from) + + /** Finds index of the first element satisfying some predicate. + * + * $mayNotTerminateInf + * + * @param p the predicate used to test elements. + * @return the index `>= 0` of the first element of this $coll that satisfies the predicate `p`, + * or `-1`, if none exists. + */ + @deprecatedOverriding("Override indexWhere(p, from) instead - indexWhere(p) calls indexWhere(p, 0)", "2.13.0") + def indexWhere(p: A => Boolean): Int = indexWhere(p, 0) + + /** Finds index of first occurrence of some value in this $coll after or at some start index. + * + * @param elem the element value to search for. + * @tparam B the type of the element `elem`. + * @param from the start index + * @return the index `>= from` of the first element of this $coll that is equal (as determined by `==`) + * to `elem`, or `-1`, if none exists. + */ + def indexOf[B >: A](elem: B, from: Int): Int = indexWhere(elem == _, from) + + /** Finds index of first occurrence of some value in this $coll. + * + * @param elem the element value to search for. + * @tparam B the type of the element `elem`. + * @return the index `>= 0` of the first element of this $coll that is equal (as determined by `==`) + * to `elem`, or `-1`, if none exists. + */ + @deprecatedOverriding("Override indexOf(elem, from) instead - indexOf(elem) calls indexOf(elem, 0)", "2.13.0") + def indexOf[B >: A](elem: B): Int = indexOf(elem, 0) + + /** Finds index of last occurrence of some value in this $coll before or at a given end index. + * + * $willNotTerminateInf + * + * @param elem the element value to search for. + * @param end the end index. + * @tparam B the type of the element `elem`. + * @return the index `<= end` of the last element of this $coll that is equal (as determined by `==`) + * to `elem`, or `-1`, if none exists. + */ + def lastIndexOf[B >: A](elem: B, end: Int = length - 1): Int = lastIndexWhere(elem == _, end) + + /** Finds index of last element satisfying some predicate before or at given end index. + * + * $willNotTerminateInf + * + * @param p the predicate used to test elements. + * @return the index `<= end` of the last element of this $coll that satisfies the predicate `p`, + * or `-1`, if none exists. + */ + def lastIndexWhere(p: A => Boolean, end: Int): Int = { + var i = length - 1 + val it = reverseIterator + while (it.hasNext && { val elem = it.next(); (i > end || !p(elem)) }) i -= 1 + i + } + + /** Finds index of last element satisfying some predicate. + * + * $willNotTerminateInf + * + * @param p the predicate used to test elements. + * @return the index of the last element of this $coll that satisfies the predicate `p`, + * or `-1`, if none exists. + */ + @deprecatedOverriding("Override lastIndexWhere(p, end) instead - lastIndexWhere(p) calls lastIndexWhere(p, Int.MaxValue)", "2.13.0") + def lastIndexWhere(p: A => Boolean): Int = lastIndexWhere(p, Int.MaxValue) + + @inline private[this] def toGenericSeq: scala.collection.Seq[A] = this match { + case s: scala.collection.Seq[A] => s + case _ => toSeq + } + + /** Finds first index after or at a start index where this $coll contains a given sequence as a slice. + * $mayNotTerminateInf + * @param that the sequence to test + * @param from the start index + * @return the first index `>= from` such that the elements of this $coll starting at this index + * match the elements of sequence `that`, or `-1` if no such subsequence exists. + */ + // TODO Should be implemented in a way that preserves laziness + def indexOfSlice[B >: A](that: Seq[B], from: Int): Int = + if (that.isEmpty && from == 0) 0 + else { + val l = knownSize + val tl = that.knownSize + if (l >= 0 && tl >= 0) { + val clippedFrom = math.max(0, from) + if (from > l) -1 + else if (tl < 1) clippedFrom + else if (l < tl) -1 + else SeqOps.kmpSearch(toGenericSeq, clippedFrom, l, that, 0, tl, forward = true) + } + else { + var i = from + var s: scala.collection.Seq[A] = toGenericSeq.drop(i) + while (!s.isEmpty) { + if (s startsWith that) + return i + + i += 1 + s = s.tail + } + -1 + } + } + + /** Finds first index where this $coll contains a given sequence as a slice. + * $mayNotTerminateInf + * @param that the sequence to test + * @return the first index `>= 0` such that the elements of this $coll starting at this index + * match the elements of sequence `that`, or `-1` if no such subsequence exists. + */ + @deprecatedOverriding("Override indexOfSlice(that, from) instead - indexOfSlice(that) calls indexOfSlice(that, 0)", "2.13.0") + def indexOfSlice[B >: A](that: Seq[B]): Int = indexOfSlice(that, 0) + + /** Finds last index before or at a given end index where this $coll contains a given sequence as a slice. + * + * $willNotTerminateInf + * + * @param that the sequence to test + * @param end the end index + * @return the last index `<= end` such that the elements of this $coll starting at this index + * match the elements of sequence `that`, or `-1` if no such subsequence exists. + */ + def lastIndexOfSlice[B >: A](that: Seq[B], end: Int): Int = { + val l = length + val tl = that.length + val clippedL = math.min(l-tl, end) + + if (end < 0) -1 + else if (tl < 1) clippedL + else if (l < tl) -1 + else SeqOps.kmpSearch(toGenericSeq, 0, clippedL+tl, that, 0, tl, forward = false) + } + + /** Finds last index where this $coll contains a given sequence as a slice. + * + * $willNotTerminateInf + * + * @param that the sequence to test + * @return the last index such that the elements of this $coll starting at this index + * match the elements of sequence `that`, or `-1` if no such subsequence exists. + */ + @deprecatedOverriding("Override lastIndexOfSlice(that, end) instead - lastIndexOfSlice(that) calls lastIndexOfSlice(that, Int.MaxValue)", "2.13.0") + def lastIndexOfSlice[B >: A](that: Seq[B]): Int = lastIndexOfSlice(that, Int.MaxValue) + + /** Finds the last element of the $coll satisfying a predicate, if any. + * + * $willNotTerminateInf + * + * @param p the predicate used to test elements. + * @return an option value containing the last element in the $coll + * that satisfies `p`, or `None` if none exists. + */ + def findLast(p: A => Boolean): Option[A] = { + val it = reverseIterator + while (it.hasNext) { + val elem = it.next() + if (p(elem)) return Some(elem) + } + None + } + + /** Tests whether this $coll contains a given sequence as a slice. + * $mayNotTerminateInf + * @param that the sequence to test + * @return `true` if this $coll contains a slice with the same elements + * as `that`, otherwise `false`. + */ + def containsSlice[B >: A](that: Seq[B]): Boolean = indexOfSlice(that) != -1 + + /** Tests whether this $coll contains a given value as an element. + * $mayNotTerminateInf + * + * @param elem the element to test. + * @return `true` if this $coll has an element that is equal (as + * determined by `==`) to `elem`, `false` otherwise. + */ + 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)) + + /** Iterates over distinct permutations of elements. + * + * $willForceEvaluation + * + * @return An Iterator which traverses the distinct permutations of this $coll. + * @example {{{ + * Seq('a', 'b', 'b').permutations.foreach(println) + * // List(a, b, b) + * // List(b, a, b) + * // List(b, b, a) + * }}} + */ + def permutations: Iterator[C] = + if (isEmpty) Iterator.single(coll) + else new PermutationsItr + + /** Iterates over combinations of elements. + * + * A '''combination''' of length `n` is a sequence of `n` elements selected in order of their first index in this sequence. + * + * For example, `"xyx"` has two combinations of length 2. The `x` is selected first: `"xx"`, `"xy"`. + * The sequence `"yx"` is not returned as a combination because it is subsumed by `"xy"`. + * + * If there is more than one way to generate the same combination, only one will be returned. + * + * For example, the result `"xy"` arbitrarily selected one of the `x` elements. + * + * As a further illustration, `"xyxx"` has three different ways to generate `"xy"` because there are three elements `x` + * to choose from. Moreover, there are three unordered pairs `"xx"` but only one is returned. + * + * It is not specified which of these equal combinations is returned. It is an implementation detail + * that should not be relied on. For example, the combination `"xx"` does not necessarily contain + * the first `x` in this sequence. This behavior is observable if the elements compare equal + * but are not identical. + * + * As a consequence, `"xyx".combinations(3).next()` is `"xxy"`: the combination does not reflect the order + * of the original sequence, but the order in which elements were selected, by "first index"; + * the order of each `x` element is also arbitrary. + * + * $willForceEvaluation + * + * @return An Iterator which traverses the n-element combinations of this $coll. + * @example {{{ + * Seq('a', 'b', 'b', 'b', 'c').combinations(2).foreach(println) + * // List(a, b) + * // List(a, c) + * // List(b, b) + * // List(b, c) + * Seq('b', 'a', 'b').combinations(2).foreach(println) + * // List(b, b) + * // List(b, a) + * }}} + */ + def combinations(n: Int): Iterator[C] = + if (n < 0 || n > size) Iterator.empty + else new CombinationsItr(n) + + private class PermutationsItr extends AbstractIterator[C] { + private[this] val (elms, idxs) = init() + private[this] var _hasNext = true + + def hasNext = _hasNext + @throws[NoSuchElementException] + def next(): C = { + if (!hasNext) + Iterator.empty.next() + + val forcedElms = new mutable.ArrayBuffer[A](elms.size) ++= elms + val result = (newSpecificBuilder ++= forcedElms).result() + var i = idxs.length - 2 + while(i >= 0 && idxs(i) >= idxs(i+1)) + i -= 1 + + if (i < 0) + _hasNext = false + else { + var j = idxs.length - 1 + while(idxs(j) <= idxs(i)) j -= 1 + swap(i,j) + + val len = (idxs.length - i) / 2 + var k = 1 + while (k <= len) { + swap(i+k, idxs.length - k) + k += 1 + } + } + result + } + private def swap(i: Int, j: Int): Unit = { + val tmpI = idxs(i) + idxs(i) = idxs(j) + idxs(j) = tmpI + val tmpE = elms(i) + elms(i) = elms(j) + elms(j) = tmpE + } + + private[this] def init() = { + val m = mutable.HashMap[A, Int]() + //val s1 = self.toGenericSeq map (e => (e, m.getOrElseUpdate(e, m.size))) + //val s2: Seq[(A, Int)] = s1 sortBy (_._2) + //val (es, is) = s2.unzip(using Predef.$conforms[(A, Int)]) + val (es, is) = (self.toGenericSeq map (e => (e, m.getOrElseUpdate(e, m.size))) sortBy (_._2)).unzip + + (es.to(mutable.ArrayBuffer), is.toArray) + } + } + + private class CombinationsItr(n: Int) extends AbstractIterator[C] { + // generating all nums such that: + // (1) nums(0) + .. + nums(length-1) = n + // (2) 0 <= nums(i) <= cnts(i), where 0 <= i <= cnts.length-1 + private[this] val (elms, cnts, nums) = init() + private[this] val offs = cnts.scanLeft(0)(_ + _) + private[this] var _hasNext = true + + def hasNext = _hasNext + def next(): C = { + if (!hasNext) + Iterator.empty.next() + + /* Calculate this result. */ + val buf = newSpecificBuilder + for(k <- 0 until nums.length; j <- 0 until nums(k)) + buf += elms(offs(k)+j) + val res = buf.result() + + /* Prepare for the next call to next. */ + var idx = nums.length - 1 + while (idx >= 0 && nums(idx) == cnts(idx)) + idx -= 1 + + idx = nums.lastIndexWhere(_ > 0, idx - 1) + + if (idx < 0) + _hasNext = false + else { + // OPT: hand rolled version of `sum = nums.view(idx + 1, nums.length).sum + 1` + var sum = 1 + var i = idx + 1 + while (i < nums.length) { + sum += nums(i) + i += 1 + } + nums(idx) -= 1 + for (k <- (idx+1) until nums.length) { + nums(k) = sum min cnts(k) + sum -= nums(k) + } + } + + res + } + + /** Rearrange seq to newSeq a0a0..a0a1..a1...ak..ak such that + * seq.count(_ == aj) == cnts(j) + * + * @return (newSeq,cnts,nums) + */ + private def init(): (IndexedSeq[A], Array[Int], Array[Int]) = { + val m = mutable.HashMap[A, Int]() + + // e => (e, weight(e)) + val (es, is) = (self.toGenericSeq map (e => (e, m.getOrElseUpdate(e, m.size))) sortBy (_._2)).unzip + val cs = new Array[Int](m.size) + is foreach (i => cs(i) += 1) + val ns = new Array[Int](cs.length) + + var r = n + 0 until ns.length foreach { k => + ns(k) = r min cs(k) + r -= ns(k) + } + (es.to(IndexedSeq), cs, ns) + } + } + + /** Sorts this $coll according to an Ordering. + * + * The sort is stable. That is, elements that are equal (as determined by + * `ord.compare`) appear in the same order in the sorted sequence as in the original. + * + * @see [[scala.math.Ordering]] + * + * $willForceEvaluation + * + * @param ord the ordering to be used to compare elements. + * @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 = { + val len = this.length + val b = newSpecificBuilder + if (len == 1) b += head + else if (len > 1) { + b.sizeHint(len) + val arr = new Array[Any](len) + copyToArray(arr) + java.util.Arrays.sort(arr.asInstanceOf[Array[AnyRef]], ord.asInstanceOf[Ordering[AnyRef]]) + var i = 0 + while (i < len) { + b += arr(i).asInstanceOf[A] + i += 1 + } + } + b.result() + } + + /** Sorts this $coll according to a comparison function. + * $willNotTerminateInf + * $willForceEvaluation + * + * The sort is stable. That is, elements that are equal + * (`lt` returns false for both directions of comparison) + * appear in the same order in the sorted sequence as in the original. + * + * @param lt a predicate that is true if + * its first argument strictly precedes its second argument in + * the desired ordering. + * @return a $coll consisting of the elements of this $coll + * sorted according to the comparison function `lt`. + * @example {{{ + * List("Steve", "Bobby", "Tom", "John", "Bob").sortWith((x, y) => x.take(3).compareTo(y.take(3)) < 0) = + * List("Bobby", "Bob", "John", "Steve", "Tom") + * }}} + */ + def sortWith(lt: (A, A) => Boolean): C = sorted(Ordering.fromLessThan(lt)) + + /** Sorts this $coll according to the Ordering which results from transforming + * an implicitly given Ordering with a transformation function. + * $willNotTerminateInf + * $willForceEvaluation + * + * The sort is stable. That is, elements that are equal (as determined by + * `ord.compare`) appear in the same order in the sorted sequence as in the original. + * + * @see [[scala.math.Ordering]] + * @param f the transformation function mapping elements + * to some other domain `B`. + * @param ord the ordering assumed on domain `B`. + * @tparam B the target type of the transformation `f`, and the type where + * the ordering `ord` is defined. + * @return a $coll consisting of the elements of this $coll + * sorted according to the ordering where `x < y` if + * `ord.lt(f(x), f(y))`. + * + * @example {{{ + * val words = "The quick brown fox jumped over the lazy dog".split(' ') + * // this works because scala.Ordering will implicitly provide an Ordering[Tuple2[Int, Char]] + * words.sortBy(x => (x.length, x.head)) + * 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) + + /** Produces the range of all indices of this sequence. + * $willForceEvaluation + * + * @return a `Range` value from `0` to one less than the length of this $coll. + */ + def indices: Range = Range(0, length) + + override final def sizeCompare(otherSize: Int): Int = lengthCompare(otherSize) + + /** Compares the length of this $coll to a test value. + * + * @param len the test value that gets compared with the length. + * @return A value `x` where + * {{{ + * x < 0 if this.length < len + * x == 0 if this.length == len + * x > 0 if this.length > len + * }}} + * The method as implemented here does not call `length` directly; its running time + * is `O(length min len)` instead of `O(length)`. The method should be overridden + * if computing `length` is cheap and `knownSize` returns `-1`. + * + * @see [[lengthIs]] + */ + def lengthCompare(len: Int): Int = super.sizeCompare(len) + + override final def sizeCompare(that: Iterable[_]^): Int = lengthCompare(that) + + /** Compares the length of this $coll to the size of another `Iterable`. + * + * @param that the `Iterable` whose size is compared with this $coll's length. + * @return A value `x` where + * {{{ + * x < 0 if this.length < that.size + * x == 0 if this.length == that.size + * x > 0 if this.length > that.size + * }}} + * The method as implemented here does not call `length` or `size` directly; its running time + * 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) + + /** Returns a value class containing operations for comparing the length of this $coll to a test value. + * + * These operations are implemented in terms of [[lengthCompare(Int) `lengthCompare(Int)`]], and + * allow the following more readable usages: + * + * {{{ + * this.lengthIs < len // this.lengthCompare(len) < 0 + * this.lengthIs <= len // this.lengthCompare(len) <= 0 + * this.lengthIs == len // this.lengthCompare(len) == 0 + * this.lengthIs != len // this.lengthCompare(len) != 0 + * this.lengthIs >= len // this.lengthCompare(len) >= 0 + * this.lengthIs > len // this.lengthCompare(len) > 0 + * }}} + */ + @inline final def lengthIs: IterableOps.SizeCompareOps = new IterableOps.SizeCompareOps(this) + + override def isEmpty: Boolean = lengthCompare(0) == 0 + + /** Are the elements of this collection the same (and in the same order) + * as those of `that`? + */ + def sameElements[B >: A](that: IterableOnce[B]^): Boolean = { + val thisKnownSize = knownSize + val knownSizeDifference = thisKnownSize != -1 && { + val thatKnownSize = that.knownSize + thatKnownSize != -1 && thisKnownSize != thatKnownSize + } + !knownSizeDifference && iterator.sameElements(that) + } + + /** Tests whether every element of this $coll relates to the + * corresponding element of another sequence by satisfying a test predicate. + * + * @param that the other sequence + * @param p the test predicate, which relates elements from both sequences + * @tparam B the type of the elements of `that` + * @return `true` if both sequences have the same length and + * `p(x, y)` is `true` for all corresponding elements `x` of this $coll + * and `y` of `that`, otherwise `false`. + */ + def corresponds[B](that: Seq[B])(p: (A, B) => Boolean): Boolean = { + val i = iterator + val j = that.iterator + while (i.hasNext && j.hasNext) + if (!p(i.next(), j.next())) + return false + !i.hasNext && !j.hasNext + } + + /** Computes the multiset difference between this $coll and another sequence. + * + * @param that the sequence of elements to remove + * @return a new $coll which contains all elements of this $coll + * except some of occurrences of elements that also appear in `that`. + * If an element value `x` appears + * ''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 = { + val occ = occCounts(that) + fromSpecific(iterator.filter { x => + var include = false + occ.updateWith(x) { + case None => { + include = true + None + } + case Some(1) => None + case Some(n) => Some(n - 1) + } + include + }) + } + + /** Computes the multiset intersection between this $coll and another sequence. + * + * @param that the sequence of elements to intersect with. + * @return a new $coll which contains all elements of this $coll + * which also appear in `that`. + * If an element value `x` appears + * ''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 = { + val occ = occCounts(that) + fromSpecific(iterator.filter { x => + var include = true + occ.updateWith(x) { + case None => { + include = false + None + } + case Some(1) => None + case Some(n) => Some(n - 1) + } + include + }) + } + + /** Produces a new $coll where a slice of elements in this $coll is replaced by another sequence. + * + * Patching at negative indices is the same as patching starting at 0. + * Patching at indices at or larger than the length of the original $coll appends the patch to the end. + * If more values are replaced than actually exist, the excess is ignored. + * + * @param from the index of the first replaced element + * @param other the replacement sequence + * @param replaced the number of elements to drop in the original $coll + * @tparam B the element type of the returned $coll. + * @return a new $coll consisting of all elements of this $coll + * 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] = + iterableFactory.from(new View.Patched(this, from, other, replaced)) + + /** A copy of this $coll with one single replaced element. + * @param index the position of the replacement + * @param elem the replacing element + * @tparam B the element type of the returned $coll. + * @return a new $coll which is a copy of this $coll with the element at position `index` replaced by `elem`. + * @throws IndexOutOfBoundsException if `index` does not satisfy `0 <= index < length`. In case of a + * 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] = { + if(index < 0) throw new IndexOutOfBoundsException(index.toString) + val k = knownSize + if(k >= 0 && index >= k) throw new IndexOutOfBoundsException(index.toString) + iterableFactory.from(new View.Updated(this, index, elem)) + } + + protected[collection] def occCounts[B](sq: Seq[B]): mutable.Map[B, Int] = { + val occ = new mutable.HashMap[B, Int]() + for (y <- sq) occ.updateWith(y) { + case None => Some(1) + case Some(n) => Some(n + 1) + } + occ + } + + /** Search this sorted sequence for a specific element. If the sequence is an + * `IndexedSeq`, a binary search is used. Otherwise, a linear search is used. + * + * The sequence should be sorted with the same `Ordering` before calling; otherwise, + * the results are undefined. + * + * @see [[scala.collection.IndexedSeq]] + * @see [[scala.math.Ordering]] + * @see [[scala.collection.SeqOps]], method `sorted` + * + * @param elem the element to find. + * @param ord the ordering to be used to compare elements. + * + * @return a `Found` value containing the index corresponding to the element in the + * sequence, or the `InsertionPoint` where the element would be inserted if + * the element is not in the sequence. + */ + def search[B >: A](elem: B)(implicit ord: Ordering[B]): SearchResult = + linearSearch(view, elem, 0)(ord) + + /** Search within an interval in this sorted sequence for a specific element. If this + * sequence is an `IndexedSeq`, a binary search is used. Otherwise, a linear search + * is used. + * + * The sequence should be sorted with the same `Ordering` before calling; otherwise, + * the results are undefined. + * + * @see [[scala.collection.IndexedSeq]] + * @see [[scala.math.Ordering]] + * @see [[scala.collection.SeqOps]], method `sorted` + * + * @param elem the element to find. + * @param from the index where the search starts. + * @param to the index following where the search ends. + * @param ord the ordering to be used to compare elements. + * + * @return a `Found` value containing the index corresponding to the element in the + * sequence, or the `InsertionPoint` where the element would be inserted if + * the element is not in the sequence. + * + * @note if `to <= from`, the search space is empty, and an `InsertionPoint` at `from` + * is returned + */ + 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) + (implicit ord: Ordering[B]): SearchResult = { + var idx = offset + val it = c.iterator + while (it.hasNext) { + val cur = it.next() + if (ord.equiv(elem, cur)) return Found(idx) + else if (ord.lt(elem, cur)) return InsertionPoint(idx) + idx += 1 + } + InsertionPoint(idx) + } +} + +object SeqOps { + + // KMP search utilities + + /** A KMP implementation, based on the undoubtedly reliable wikipedia entry. + * Note: I made this private to keep it from entering the API. That can be reviewed. + * + * @param S Sequence that may contain target + * @param m0 First index of S to consider + * @param m1 Last index of S to consider (exclusive) + * @param W Target sequence + * @param n0 First index of W to match + * @param n1 Last index of W to match (exclusive) + * @param forward Direction of search (from beginning==true, from end==false) + * @return Index of start of sequence if found, -1 if not (relative to beginning of S, not m0). + */ + private def kmpSearch[B](S: scala.collection.Seq[B], m0: Int, m1: Int, W: scala.collection.Seq[B], n0: Int, n1: Int, forward: Boolean): Int = { + // Check for redundant case when target has single valid element + def clipR(x: Int, y: Int) = if (x < y) x else -1 + def clipL(x: Int, y: Int) = if (x > y) x else -1 + + if (n1 == n0+1) { + if (forward) + clipR(S.indexOf(W(n0), m0), m1) + else + clipL(S.lastIndexOf(W(n0), m1-1), m0-1) + } + + // Check for redundant case when both sequences are same size + else if (m1-m0 == n1-n0) { + // Accepting a little slowness for the uncommon case. + if (S.iterator.slice(m0, m1).sameElements(W.iterator.slice(n0, n1))) m0 + else -1 + } + // Now we know we actually need KMP search, so do it + else S match { + case xs: scala.collection.IndexedSeq[_] => + // We can index into S directly; it should be adequately fast + val Wopt = kmpOptimizeWord(W, n0, n1, forward) + val T = kmpJumpTable(Wopt, n1-n0) + var i, m = 0 + val zero = if (forward) m0 else m1-1 + val delta = if (forward) 1 else -1 + while (i+m < m1-m0) { + if (Wopt(i) == S(zero+delta*(i+m))) { + i += 1 + if (i == n1-n0) return (if (forward) m+m0 else m1-m-i) + } + else { + val ti = T(i) + m += i - ti + if (i > 0) i = ti + } + } + -1 + case _ => + // We had better not index into S directly! + val iter = S.iterator.drop(m0) + val Wopt = kmpOptimizeWord(W, n0, n1, forward = true) + val T = kmpJumpTable(Wopt, n1-n0) + val cache = new Array[AnyRef](n1-n0) // Ring buffer--need a quick way to do a look-behind + var largest = 0 + var i, m = 0 + var answer = -1 + while (m+m0+n1-n0 <= m1) { + while (i+m >= largest) { + cache(largest%(n1-n0)) = iter.next().asInstanceOf[AnyRef] + largest += 1 + } + if (Wopt(i) == cache((i+m)%(n1-n0)).asInstanceOf[B]) { + i += 1 + if (i == n1-n0) { + if (forward) return m+m0 + else { + i -= 1 + answer = m+m0 + val ti = T(i) + m += i - ti + if (i > 0) i = ti + } + } + } + else { + val ti = T(i) + m += i - ti + if (i > 0) i = ti + } + } + answer + } + } + + /** Make sure a target sequence has fast, correctly-ordered indexing for KMP. + * + * @param W The target sequence + * @param n0 The first element in the target sequence that we should use + * @param n1 The far end of the target sequence that we should use (exclusive) + * @return Target packed in an IndexedSeq (taken from iterator unless W already is an IndexedSeq) + */ + private def kmpOptimizeWord[B](W: scala.collection.Seq[B], n0: Int, n1: Int, forward: Boolean): IndexedSeqView[B] = W match { + case iso: IndexedSeq[B] => + // Already optimized for indexing--use original (or custom view of original) + if (forward && n0==0 && n1==W.length) iso.view + else if (forward) new AbstractIndexedSeqView[B] { + val length = n1 - n0 + def apply(x: Int) = iso(n0 + x) + } + else new AbstractIndexedSeqView[B] { + def length = n1 - n0 + def apply(x: Int) = iso(n1 - 1 - x) + } + case _ => + // W is probably bad at indexing. Pack in array (in correct orientation) + // Would be marginally faster to special-case each direction + new AbstractIndexedSeqView[B] { + private[this] val Warr = new Array[AnyRef](n1-n0) + private[this] val delta = if (forward) 1 else -1 + private[this] val done = if (forward) n1-n0 else -1 + val wit = W.iterator.drop(n0) + var i = if (forward) 0 else (n1-n0-1) + while (i != done) { + Warr(i) = wit.next().asInstanceOf[AnyRef] + i += delta + } + + val length = n1 - n0 + def apply(x: Int) = Warr(x).asInstanceOf[B] + } + } + + /** Make a jump table for KMP search. + * + * @param Wopt The target sequence + * @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) = { + val arr = new Array[Int](wlen) + var pos = 2 + var cnd = 0 + arr(0) = -1 + arr(1) = 0 + while (pos < wlen) { + if (Wopt(pos-1) == Wopt(cnd)) { + arr(pos) = cnd + 1 + pos += 1 + cnd += 1 + } + else if (cnd > 0) { + cnd = arr(cnd) + } + else { + arr(pos) = 0 + pos += 1 + } + } + arr + } +} + +/** Explicit instantiation of the `Seq` trait to reduce class file size in subclasses. */ +abstract class AbstractSeq[+A] extends AbstractIterable[A] with Seq[A] From 64e95b26360f8bb449bd9f950aa2f1123fb82dc3 Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 17 Jul 2023 19:25:01 +0200 Subject: [PATCH 27/37] Also fluidify member when doing overriding checks As `Map` shows, it's possible that an overriding check is from an external non-capture checked subclass member against a currently compiled & capture-checked superclass member. Hence, we have to add Fluid sets to either part of an overriding check if that part is not compiled with capture checking. This allows to include collection/Map in the set of capture checked stdlib sources. --- .../dotty/tools/dotc/cc/CheckCaptures.scala | 6 +++--- .../dotty/tools/dotc/typer/RefChecks.scala | 10 +++++----- tests/pos/stdlib/collection/Map.scala | 20 +++++++++---------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index c442d6ae4963..362604788056 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -311,8 +311,8 @@ class CheckCaptures extends Recheck, SymTransformer: def isPreCC(sym: Symbol): Boolean = sym.isTerm && sym.maybeOwner.isClass - && !defn.isFunctionSymbol(sym.owner) && !sym.owner.is(CaptureChecked) + && !defn.isFunctionSymbol(sym.owner) if isPreCC(sym) then val tpw = tp.widen @@ -948,8 +948,8 @@ class CheckCaptures extends Recheck, SymTransformer: finally curEnv = saved actual1 frozen_<:< expected1 - override def adjustOtherType(tp: Type, other: Symbol)(using Context): Type = - handleBackwardsCompat(tp, other, initialVariance = 0) + override def adjustInfo(tp: Type, member: Symbol)(using Context): Type = + handleBackwardsCompat(tp, member, initialVariance = 0) //.showing(i"adjust $other: $tp --> $result") end OverridingPairsCheckerCC diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index b17ae0908b9e..74ad3489952b 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -239,10 +239,10 @@ object RefChecks { // compatibility checking. def checkSubType(tp1: Type, tp2: Type)(using Context): Boolean = tp1 frozen_<:< tp2 - /** A hook that allows to adjust the type of `other` before checking conformance. - * Overridden in capture checking to handle non-capture checked superclasses leniently. + /** A hook that allows to adjust the type of `member` and `other` before checking conformance. + * Overridden in capture checking to handle non-capture checked classes leniently. */ - def adjustOtherType(tp: Type, other: Symbol)(using Context): Type = tp + def adjustInfo(tp: Type, member: Symbol)(using Context): Type = tp private val subtypeChecker: (Type, Type) => Context ?=> Boolean = this.checkSubType @@ -367,8 +367,8 @@ object RefChecks { def checkOverride(checkSubType: (Type, Type) => Context ?=> Boolean, member: Symbol, other: Symbol): Unit = def memberTp(self: Type) = if (member.isClass) TypeAlias(member.typeRef.EtaExpand(member.typeParams)) - else self.memberInfo(member) - def otherTp(self: Type) = checker.adjustOtherType(self.memberInfo(other), other) + else checker.adjustInfo(self.memberInfo(member), member) + def otherTp(self: Type) = checker.adjustInfo(self.memberInfo(other), other) refcheck.println(i"check override ${infoString(member)} overriding ${infoString(other)}") diff --git a/tests/pos/stdlib/collection/Map.scala b/tests/pos/stdlib/collection/Map.scala index ed9b175e173d..ef4f915ea573 100644 --- a/tests/pos/stdlib/collection/Map.scala +++ b/tests/pos/stdlib/collection/Map.scala @@ -17,7 +17,7 @@ import scala.annotation.nowarn import scala.collection.generic.DefaultSerializable import scala.collection.mutable.StringBuilder import scala.util.hashing.MurmurHash3 -//import language.experimental.captureChecking +import language.experimental.captureChecking /** Base Map type */ trait Map[K, +V] @@ -132,7 +132,7 @@ 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 final def mapFromIterable[K2, V2](it: Iterable[(K2, V2)]^): CC[K2, V2] = mapFactory.from(it) /** The companion object of this map, providing various factory methods. * @@ -319,7 +319,7 @@ 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] = 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 @@ -329,7 +329,7 @@ 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] = mapFactory.from(suffix match { case it: Iterable[(K, V2)] => new View.Concat(this, it) case _ => iterator.concat(suffix.iterator) }) @@ -337,7 +337,7 @@ 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] = 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) @@ -351,14 +351,14 @@ trait MapOps[K, +V, +CC[_, _] <: IterableOps[_, AnyConstr, _], +C] 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 = { 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] = { + val thatIterable: Iterable[(K, V1)]^{that} = that match { case that: Iterable[(K, V1)] => that case that => View.from(that) } @@ -381,10 +381,10 @@ object MapOps { def map[K2, V2](f: ((K, V)) => (K2, V2)): CC[K2, V2] = 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] = 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]^{p, q} = new WithFilter[K, V, IterableCC, CC](self, (kv: (K, V)) => p(kv) && q(kv)) } From fa71a5615c0c46fb043182902c5b318e8bf094f8 Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 18 Jul 2023 12:59:53 +0200 Subject: [PATCH 28/37] Add subclasses IndexedSeq, LinearSeq, {mutable,immutable}.Seq to stdlib tests --- tests/pos/stdlib/IndexedSeq.scala | 1 + tests/pos/stdlib/LinearSeq.scala | 1 + tests/pos/stdlib/collection/IndexedSeq.scala | 134 ++++++++ tests/pos/stdlib/collection/LinearSeq.scala | 311 ++++++++++++++++++ .../pos/stdlib/collection/immutable/Seq.scala | 157 +++++++++ tests/pos/stdlib/collection/mutable/Seq.scala | 68 ++++ tests/pos/stdlib/immutable_Seq.scala | 1 + tests/pos/stdlib/mutable_Seq.scala | 1 + 8 files changed, 674 insertions(+) create mode 120000 tests/pos/stdlib/IndexedSeq.scala create mode 120000 tests/pos/stdlib/LinearSeq.scala create mode 100644 tests/pos/stdlib/collection/IndexedSeq.scala create mode 100644 tests/pos/stdlib/collection/LinearSeq.scala create mode 100644 tests/pos/stdlib/collection/immutable/Seq.scala create mode 100644 tests/pos/stdlib/collection/mutable/Seq.scala create mode 120000 tests/pos/stdlib/immutable_Seq.scala create mode 120000 tests/pos/stdlib/mutable_Seq.scala diff --git a/tests/pos/stdlib/IndexedSeq.scala b/tests/pos/stdlib/IndexedSeq.scala new file mode 120000 index 000000000000..aeb1166173e6 --- /dev/null +++ b/tests/pos/stdlib/IndexedSeq.scala @@ -0,0 +1 @@ +collection/IndexedSeq.scala \ No newline at end of file diff --git a/tests/pos/stdlib/LinearSeq.scala b/tests/pos/stdlib/LinearSeq.scala new file mode 120000 index 000000000000..9f765e83bf8d --- /dev/null +++ b/tests/pos/stdlib/LinearSeq.scala @@ -0,0 +1 @@ +collection/LinearSeq.scala \ No newline at end of file diff --git a/tests/pos/stdlib/collection/IndexedSeq.scala b/tests/pos/stdlib/collection/IndexedSeq.scala new file mode 100644 index 000000000000..c48dc4932e62 --- /dev/null +++ b/tests/pos/stdlib/collection/IndexedSeq.scala @@ -0,0 +1,134 @@ +/* + * Scala (https://www.scala-lang.org) + * + * Copyright EPFL and Lightbend, Inc. + * + * Licensed under Apache License 2.0 + * (http://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package scala +package collection + +import scala.annotation.{nowarn, tailrec} +import scala.collection.Searching.{Found, InsertionPoint, SearchResult} +import scala.collection.Stepper.EfficientSplit +import scala.math.Ordering +import language.experimental.captureChecking + +/** Base trait for indexed sequences that have efficient `apply` and `length` */ +trait IndexedSeq[+A] extends Seq[A] + with IndexedSeqOps[A, IndexedSeq, IndexedSeq[A]] + with IterableFactoryDefaults[A, IndexedSeq] { + @nowarn("""cat=deprecation&origin=scala\.collection\.Iterable\.stringPrefix""") + override protected[this] def stringPrefix: String = "IndexedSeq" + + override def iterableFactory: SeqFactory[IndexedSeq] = IndexedSeq +} + +@SerialVersionUID(3L) +object IndexedSeq extends SeqFactory.Delegate[IndexedSeq](immutable.IndexedSeq) + +/** Base trait for indexed Seq operations */ +trait IndexedSeqOps[+A, +CC[_], +C] extends AnyRef with SeqOps[A, CC, C] { self => + + def iterator: Iterator[A] = view.iterator + + override def stepper[S <: Stepper[_]](implicit shape: StepperShape[A, S]): S with EfficientSplit = { + import convert.impl._ + val s = shape.shape match { + case StepperShape.IntShape => new IntIndexedSeqStepper (this.asInstanceOf[IndexedSeqOps[Int, AnyConstr, _]], 0, length) + case StepperShape.LongShape => new LongIndexedSeqStepper (this.asInstanceOf[IndexedSeqOps[Long, AnyConstr, _]], 0, length) + case StepperShape.DoubleShape => new DoubleIndexedSeqStepper(this.asInstanceOf[IndexedSeqOps[Double, AnyConstr, _]], 0, length) + case _ => shape.parUnbox(new AnyIndexedSeqStepper[A](this, 0, length)) + } + s.asInstanceOf[S with EfficientSplit] + } + + override def reverseIterator: Iterator[A] = 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 = + if (start == end) z + else foldl(start + 1, end, op(z, apply(start)), op) + */ + + @tailrec private def foldr[B](start: Int, end: Int, z: B, op: (A, B) => B): B = + if (start == end) z + else foldr(start, end - 1, op(apply(end - 1), z), op) + + //override def foldLeft[B](z: B)(op: (B, A) => B): B = foldl(0, length, z, op) + + override def foldRight[B](z: B)(op: (A, B) => B): B = foldr(0, length, z, op) + + //override def reduceLeft[B >: A](op: (B, A) => B): B = if (length > 0) foldl(1, length, apply(0), op) else super.reduceLeft(op) + + //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) + + @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 protected def reversed: Iterable[A] = 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 take(n: Int): C = fromSpecific(new IndexedSeqView.Take(this, n)) + + override def takeRight(n: Int): C = fromSpecific(new IndexedSeqView.TakeRight(this, n)) + + override def drop(n: Int): C = fromSpecific(new IndexedSeqView.Drop(this, n)) + + override def dropRight(n: Int): C = fromSpecific(new IndexedSeqView.DropRight(this, n)) + + override def map[B](f: A => B): CC[B] = iterableFactory.from(new IndexedSeqView.Map(this, f)) + + override def reverse: C = fromSpecific(new IndexedSeqView.Reverse(this)) + + override def slice(from: Int, until: Int): C = fromSpecific(new IndexedSeqView.Slice(this, from, until)) + + override def head: A = apply(0) + + override def headOption: Option[A] = if (isEmpty) None else Some(head) + + override def last: A = apply(length - 1) + + // We already inherit an efficient `lastOption = if (isEmpty) None else Some(last)` + + override final def lengthCompare(len: Int): Int = Integer.compare(length, len) + + override def knownSize: Int = length + + 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 + } + + override def search[B >: A](elem: B)(implicit ord: Ordering[B]): SearchResult = + binarySearch(elem, 0, length)(ord) + + override def search[B >: A](elem: B, from: Int, to: Int)(implicit ord: Ordering[B]): SearchResult = + binarySearch(elem, from, to)(ord) + + @tailrec + private[this] def binarySearch[B >: A](elem: B, from: Int, to: Int) + (implicit ord: Ordering[B]): SearchResult = { + if (from < 0) binarySearch(elem, 0, to) + else if (to > length) binarySearch(elem, from, length) + else if (to <= from) InsertionPoint(from) + else { + val idx = from + (to - from - 1) / 2 + math.signum(ord.compare(elem, apply(idx))) match { + case -1 => binarySearch(elem, from, idx)(ord) + case 1 => binarySearch(elem, idx + 1, to)(ord) + case _ => Found(idx) + } + } + } +} diff --git a/tests/pos/stdlib/collection/LinearSeq.scala b/tests/pos/stdlib/collection/LinearSeq.scala new file mode 100644 index 000000000000..393f5fda4187 --- /dev/null +++ b/tests/pos/stdlib/collection/LinearSeq.scala @@ -0,0 +1,311 @@ +/* + * Scala (https://www.scala-lang.org) + * + * Copyright EPFL and Lightbend, Inc. + * + * Licensed under Apache License 2.0 + * (http://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package scala +package collection + +import scala.annotation.{nowarn, tailrec} +import language.experimental.captureChecking + +/** Base trait for linearly accessed sequences that have efficient `head` and + * `tail` operations. + * Known subclasses: List, LazyList + */ +trait LinearSeq[+A] extends Seq[A] + with LinearSeqOps[A, LinearSeq, LinearSeq[A]] + with IterableFactoryDefaults[A, LinearSeq] { + @nowarn("""cat=deprecation&origin=scala\.collection\.Iterable\.stringPrefix""") + override protected[this] def stringPrefix: String = "LinearSeq" + + override def iterableFactory: SeqFactory[LinearSeq] = LinearSeq +} + +@SerialVersionUID(3L) +object LinearSeq extends SeqFactory.Delegate[LinearSeq](immutable.LinearSeq) + +/** Base trait for linear Seq operations */ +trait LinearSeqOps[+A, +CC[X] <: LinearSeq[X], +C <: LinearSeq[A] with LinearSeqOps[A, CC, C]] extends AnyRef with SeqOps[A, CC, C] { + + /** @inheritdoc + * + * Note: *Must* be overridden in subclasses. The default implementation that is inherited from [[SeqOps]] + * uses `lengthCompare`, which is defined here to use `isEmpty`. + */ + override def isEmpty: Boolean + + /** @inheritdoc + * + * Note: *Must* be overridden in subclasses. The default implementation is inherited from [[IterableOps]]. + */ + def head: A + + /** @inheritdoc + * + * Note: *Must* be overridden in subclasses. The default implementation is inherited from [[IterableOps]]. + */ + def tail: C + + override def headOption: Option[A] = + if (isEmpty) None else Some(head) + + def iterator: Iterator[A] = + if (knownSize == 0) Iterator.empty + else new LinearSeqIterator[A](this) + + def length: Int = { + var these = coll + var len = 0 + while (these.nonEmpty) { + len += 1 + these = these.tail + } + len + } + + override def last: A = { + if (isEmpty) throw new NoSuchElementException("LinearSeq.last") + else { + var these = coll + var scout = tail + while (scout.nonEmpty) { + these = scout + scout = scout.tail + } + these.head + } + } + + override def lengthCompare(len: Int): Int = { + @tailrec def loop(i: Int, xs: LinearSeq[A]): Int = { + if (i == len) + if (xs.isEmpty) 0 else 1 + else if (xs.isEmpty) + -1 + else + loop(i + 1, xs.tail) + } + if (len < 0) 1 + else loop(0, coll) + } + + override def lengthCompare(that: Iterable[_]^): Int = { + val thatKnownSize = that.knownSize + + if (thatKnownSize >= 0) this lengthCompare thatKnownSize + else that match { + case that: LinearSeq[_] => + var thisSeq = this + var thatSeq = that + while (thisSeq.nonEmpty && thatSeq.nonEmpty) { + thisSeq = thisSeq.tail + thatSeq = thatSeq.tail + } + java.lang.Boolean.compare(thisSeq.nonEmpty, thatSeq.nonEmpty) + case _ => + var thisSeq = this + val thatIt = that.iterator + while (thisSeq.nonEmpty && thatIt.hasNext) { + thisSeq = thisSeq.tail + thatIt.next() + } + java.lang.Boolean.compare(thisSeq.nonEmpty, thatIt.hasNext) + } + } + + override def isDefinedAt(x: Int): Boolean = x >= 0 && lengthCompare(x) > 0 + + // `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 + } + + override def foreach[U](f: A => U): Unit = { + var these: LinearSeq[A] = coll + while (!these.isEmpty) { + f(these.head) + these = these.tail + } + } + + override def forall(p: A => Boolean): Boolean = { + var these: LinearSeq[A] = coll + while (!these.isEmpty) { + if (!p(these.head)) return false + these = these.tail + } + true + } + + override def exists(p: A => Boolean): Boolean = { + var these: LinearSeq[A] = coll + while (!these.isEmpty) { + if (p(these.head)) return true + these = these.tail + } + false + } + + override def contains[A1 >: A](elem: A1): Boolean = { + var these: LinearSeq[A] = coll + while (!these.isEmpty) { + if (these.head == elem) return true + these = these.tail + } + false + } + + override def find(p: A => Boolean): Option[A] = { + var these: LinearSeq[A] = coll + while (!these.isEmpty) { + if (p(these.head)) return Some(these.head) + these = these.tail + } + None + } + + override def foldLeft[B](z: B)(op: (B, A) => B): B = { + var acc = z + var these: LinearSeq[A] = coll + while (!these.isEmpty) { + acc = op(acc, these.head) + these = these.tail + } + acc + } + + 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) { + linearSeqEq(a.tail, b.tail) + } + else { + a.isEmpty && b.isEmpty + } + } + + that match { + case that: LinearSeq[B] => linearSeqEq(coll, that) + case _ => super.sameElements(that) + } + } + + override def segmentLength(p: A => Boolean, from: Int): Int = { + var i = 0 + var seq = drop(from) + while (seq.nonEmpty && p(seq.head)) { + i += 1 + seq = seq.tail + } + i + } + + override def indexWhere(p: A => Boolean, from: Int): Int = { + var i = math.max(from, 0) + var these: LinearSeq[A] = this drop from + while (these.nonEmpty) { + if (p(these.head)) + return i + + i += 1 + these = these.tail + } + -1 + } + + override def lastIndexWhere(p: A => Boolean, end: Int): Int = { + var i = 0 + var these: LinearSeq[A] = coll + var last = -1 + while (!these.isEmpty && i <= end) { + if (p(these.head)) last = i + these = these.tail + i += 1 + } + last + } + + override def findLast(p: A => Boolean): Option[A] = { + var these: LinearSeq[A] = coll + var found = false + var last: A = null.asInstanceOf[A] // don't use `Option`, to prevent excessive `Some` allocation + while (these.nonEmpty) { + val elem = these.head + if (p(elem)) { + found = true + last = elem + } + these = these.tail + } + if (found) Some(last) else None + } + + override def tails: Iterator[C] = { + val end = Iterator.single(empty) + Iterator.iterate(coll)(_.tail).takeWhile(_.nonEmpty) ++ end + } +} + +trait StrictOptimizedLinearSeqOps[+A, +CC[X] <: LinearSeq[X], +C <: LinearSeq[A] with StrictOptimizedLinearSeqOps[A, CC, C]] extends AnyRef with LinearSeqOps[A, CC, C] with StrictOptimizedSeqOps[A, CC, C] { + // A more efficient iterator implementation than the default LinearSeqIterator + override def iterator: Iterator[A] = new AbstractIterator[A] { + private[this] var current = StrictOptimizedLinearSeqOps.this + def hasNext = !current.isEmpty + def next() = { val r = current.head; current = current.tail; r } + } + + // Optimized version of `drop` that avoids copying + override def drop(n: Int): C = { + @tailrec def loop(n: Int, s: C): C = + if (n <= 0 || s.isEmpty) s + else loop(n - 1, s.tail) + loop(n, coll) + } + + override def dropWhile(p: A => Boolean): C = { + @tailrec def loop(s: C): C = + if (s.nonEmpty && p(s.head)) loop(s.tail) + else s + loop(coll) + } +} + +/** A specialized Iterator for LinearSeqs that is lazy enough for Stream and LazyList. This is accomplished by not + * evaluating the tail after returning the current head. + */ +private[collection] final class LinearSeqIterator[A](coll: LinearSeqOps[A, LinearSeq, LinearSeq[A]]) extends AbstractIterator[A] { + // A call-by-need cell + private[this] final class LazyCell(st: => LinearSeqOps[A, LinearSeq, LinearSeq[A]]) { lazy val v = st } + + private[this] var these: LazyCell = { + // Reassign reference to avoid creating a private class field and holding a reference to the head. + // LazyCell would otherwise close over `coll`. + val initialHead = coll + new LazyCell(initialHead) + } + + def hasNext: Boolean = these.v.nonEmpty + + def next(): A = + if (isEmpty) Iterator.empty.next() + else { + val cur = these.v + val result = cur.head + these = new LazyCell(cur.tail) + result + } +} diff --git a/tests/pos/stdlib/collection/immutable/Seq.scala b/tests/pos/stdlib/collection/immutable/Seq.scala new file mode 100644 index 000000000000..5184cadaccae --- /dev/null +++ b/tests/pos/stdlib/collection/immutable/Seq.scala @@ -0,0 +1,157 @@ +/* + * Scala (https://www.scala-lang.org) + * + * Copyright EPFL and Lightbend, Inc. + * + * 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 language.experimental.captureChecking + +trait Seq[+A] extends Iterable[A] + with collection.Seq[A] + with SeqOps[A, Seq, Seq[A]] + with IterableFactoryDefaults[A, Seq] { + + override final def toSeq: this.type = this + + override def iterableFactory: SeqFactory[Seq] = Seq +} + +/** + * @define coll immutable sequence + * @define Coll `immutable.Seq` + */ +trait SeqOps[+A, +CC[_], +C] extends AnyRef with collection.SeqOps[A, CC, C] + +/** + * $factoryInfo + * @define coll immutable sequence + * @define Coll `immutable.Seq` + */ +@SerialVersionUID(3L) +object Seq extends SeqFactory.Delegate[Seq](List) { + override def from[E](it: IterableOnce[E]^): Seq[E] = it match { + case s: Seq[E] => s + case _ => super.from(it) + } +} + +/** Base trait for immutable indexed sequences that have efficient `apply` and `length` */ +trait IndexedSeq[+A] extends Seq[A] + with collection.IndexedSeq[A] + with IndexedSeqOps[A, IndexedSeq, IndexedSeq[A]] + with IterableFactoryDefaults[A, IndexedSeq] { + + final override def toIndexedSeq: IndexedSeq[A] = this + + override def canEqual(that: Any): Boolean = that match { + case otherIndexedSeq: IndexedSeq[_] => length == otherIndexedSeq.length && super.canEqual(that) + case _ => super.canEqual(that) + } + + + override def sameElements[B >: A](o: IterableOnce[B]^): Boolean = o match { + case that: IndexedSeq[_] => + (this eq that) || { + val length = this.length + var equal = length == that.length + if (equal) { + var index = 0 + // some IndexedSeq apply is less efficient than using Iterators + // e.g. Vector so we can compare the first few with apply and the rest with an iterator + // but if apply is more efficient than Iterators then we can use the apply for all the comparison + // we default to the minimum preferred length + val maxApplyCompare = { + val preferredLength = Math.min(applyPreferredMaxLength, that.applyPreferredMaxLength) + if (length > (preferredLength.toLong << 1)) preferredLength else length + } + while (index < maxApplyCompare && equal) { + equal = this (index) == that(index) + index += 1 + } + if ((index < length) && equal) { + val thisIt = this.iterator.drop(index) + val thatIt = that.iterator.drop(index) + while (equal && thisIt.hasNext) { + equal = thisIt.next() == thatIt.next() + } + } + } + equal + } + case _ => super.sameElements(o) + } + + /** a hint to the runtime when scanning values + * [[apply]] is preferred for scan with a max index less than this value + * [[iterator]] is preferred for scans above this range + * @return a hint about when to use [[apply]] or [[iterator]] + */ + protected def applyPreferredMaxLength: Int = IndexedSeqDefaults.defaultApplyPreferredMaxLength + + override def iterableFactory: SeqFactory[IndexedSeq] = IndexedSeq +} + +object IndexedSeqDefaults { + val defaultApplyPreferredMaxLength: Int = + try System.getProperty( + "scala.collection.immutable.IndexedSeq.defaultApplyPreferredMaxLength", "64").toInt + catch { + case _: SecurityException => 64 + } +} + +@SerialVersionUID(3L) +object IndexedSeq extends SeqFactory.Delegate[IndexedSeq](Vector) { + override def from[E](it: IterableOnce[E]^): IndexedSeq[E] = it match { + case is: IndexedSeq[E] => is + case _ => super.from(it) + } +} + +/** Base trait for immutable indexed Seq operations */ +trait IndexedSeqOps[+A, +CC[_], +C] + extends SeqOps[A, CC, C] + with collection.IndexedSeqOps[A, CC, C] { + + override def slice(from: Int, until: Int): C = { + // since we are immutable we can just share the same collection + if (from <= 0 && until >= length) coll + else super.slice(from, until) + } + +} + +/** Base trait for immutable linear sequences that have efficient `head` and `tail` */ +trait LinearSeq[+A] + extends Seq[A] + with collection.LinearSeq[A] + with LinearSeqOps[A, LinearSeq, LinearSeq[A]] + with IterableFactoryDefaults[A, LinearSeq] { + + override def iterableFactory: SeqFactory[LinearSeq] = LinearSeq +} + +@SerialVersionUID(3L) +object LinearSeq extends SeqFactory.Delegate[LinearSeq](List) { + override def from[E](it: IterableOnce[E]^): LinearSeq[E] = it match { + case ls: LinearSeq[E] => ls + case _ => super.from(it) + } +} + +trait LinearSeqOps[+A, +CC[X] <: LinearSeq[X], +C <: LinearSeq[A] with LinearSeqOps[A, CC, C]] + extends AnyRef with SeqOps[A, CC, C] + with collection.LinearSeqOps[A, CC, C] + +/** Explicit instantiation of the `Seq` trait to reduce class file size in subclasses. */ +abstract class AbstractSeq[+A] extends scala.collection.AbstractSeq[A] with Seq[A] diff --git a/tests/pos/stdlib/collection/mutable/Seq.scala b/tests/pos/stdlib/collection/mutable/Seq.scala new file mode 100644 index 000000000000..443eec379c1b --- /dev/null +++ b/tests/pos/stdlib/collection/mutable/Seq.scala @@ -0,0 +1,68 @@ +/* + * Scala (https://www.scala-lang.org) + * + * Copyright EPFL and Lightbend, Inc. + * + * 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.collection.mutable + +import scala.collection.{IterableFactoryDefaults, SeqFactory} +import language.experimental.captureChecking + +trait Seq[A] + extends Iterable[A] + with collection.Seq[A] + with SeqOps[A, Seq, Seq[A]] + with IterableFactoryDefaults[A, Seq] { + + override def iterableFactory: SeqFactory[Seq] = Seq +} + +/** + * $factoryInfo + * @define coll mutable sequence + * @define Coll `mutable.Seq` + */ +@SerialVersionUID(3L) +object Seq extends SeqFactory.Delegate[Seq](ArrayBuffer) + +/** + * @define coll mutable sequence + * @define Coll `mutable.Seq` + */ +trait SeqOps[A, +CC[_], +C <: AnyRef] + extends collection.SeqOps[A, CC, C] + with Cloneable[C] { + + override def clone(): C = { + val b = newSpecificBuilder + b ++= this + b.result() + } + + /** Replaces element at given index with a new value. + * + * @param idx the index of the element to replace. + * @param elem the new value. + * @throws IndexOutOfBoundsException if the index is not valid. + */ + @throws[IndexOutOfBoundsException] + def update(idx: Int, elem: A): Unit + + @deprecated("Use `mapInPlace` on an `IndexedSeq` instead", "2.13.0") + @`inline`final def transform(f: A => A): this.type = { + var i = 0 + val siz = size + while (i < siz) { this(i) = f(this(i)); i += 1 } + this + } +} + +/** Explicit instantiation of the `Seq` trait to reduce class file size in subclasses. */ +abstract class AbstractSeq[A] extends scala.collection.AbstractSeq[A] with Seq[A] diff --git a/tests/pos/stdlib/immutable_Seq.scala b/tests/pos/stdlib/immutable_Seq.scala new file mode 120000 index 000000000000..6635a8431e64 --- /dev/null +++ b/tests/pos/stdlib/immutable_Seq.scala @@ -0,0 +1 @@ +collection/immutable/Seq.scala \ No newline at end of file diff --git a/tests/pos/stdlib/mutable_Seq.scala b/tests/pos/stdlib/mutable_Seq.scala new file mode 120000 index 000000000000..fa82d6d311fa --- /dev/null +++ b/tests/pos/stdlib/mutable_Seq.scala @@ -0,0 +1 @@ +collection/mutable/Seq.scala \ No newline at end of file From 0230d03bdeff22d10f8f1f84ed18a13ff0747105 Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 18 Jul 2023 13:42:55 +0200 Subject: [PATCH 29/37] Add StrictOptimized ops to stdlib tests --- .../stdlib/StrictOptimizedIterableOps.scala | 1 + tests/pos/stdlib/StrictOptimizedSeqOps.scala | 1 + .../StrictOptimizedIterableOps.scala | 286 ++++++++++++++++++ .../collection/StrictOptimizedSeqOps.scala | 113 +++++++ 4 files changed, 401 insertions(+) create mode 120000 tests/pos/stdlib/StrictOptimizedIterableOps.scala create mode 120000 tests/pos/stdlib/StrictOptimizedSeqOps.scala create mode 100644 tests/pos/stdlib/collection/StrictOptimizedIterableOps.scala create mode 100644 tests/pos/stdlib/collection/StrictOptimizedSeqOps.scala diff --git a/tests/pos/stdlib/StrictOptimizedIterableOps.scala b/tests/pos/stdlib/StrictOptimizedIterableOps.scala new file mode 120000 index 000000000000..1477432b0912 --- /dev/null +++ b/tests/pos/stdlib/StrictOptimizedIterableOps.scala @@ -0,0 +1 @@ +collection/StrictOptimizedIterableOps.scala \ No newline at end of file diff --git a/tests/pos/stdlib/StrictOptimizedSeqOps.scala b/tests/pos/stdlib/StrictOptimizedSeqOps.scala new file mode 120000 index 000000000000..4c56fc5a0b77 --- /dev/null +++ b/tests/pos/stdlib/StrictOptimizedSeqOps.scala @@ -0,0 +1 @@ +collection/StrictOptimizedSeqOps.scala \ No newline at end of file diff --git a/tests/pos/stdlib/collection/StrictOptimizedIterableOps.scala b/tests/pos/stdlib/collection/StrictOptimizedIterableOps.scala new file mode 100644 index 000000000000..5b504a2469b5 --- /dev/null +++ b/tests/pos/stdlib/collection/StrictOptimizedIterableOps.scala @@ -0,0 +1,286 @@ +/* + * Scala (https://www.scala-lang.org) + * + * Copyright EPFL and Lightbend, Inc. + * + * Licensed under Apache License 2.0 + * (http://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package scala +package collection + +import scala.annotation.nowarn +import scala.annotation.unchecked.uncheckedVariance +import scala.runtime.Statics +import language.experimental.captureChecking + +/** + * Trait that overrides iterable operations to take advantage of strict builders. + * + * @tparam A Elements type + * @tparam CC Collection type constructor + * @tparam C Collection type + */ +trait StrictOptimizedIterableOps[+A, +CC[_], +C] + extends Any + with IterableOps[A, CC, C] { + this: StrictOptimizedIterableOps[A, CC, C] => + + // Optimized, push-based version of `partition` + override def partition(p: A => Boolean): (C, C) = { + val l, r = newSpecificBuilder + iterator.foreach(x => (if (p(x)) l else r) += x) + (l.result(), r.result()) + } + + override def span(p: A => Boolean): (C, C) = { + val first = newSpecificBuilder + val second = newSpecificBuilder + val it = iterator + var inFirst = true + while (it.hasNext && inFirst) { + val a = it.next() + if (p(a)) { + first += a + } else { + second += a + inFirst = false + } + } + while (it.hasNext) { + second += it.next() + } + (first.result(), second.result()) + } + + 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 => + val pair = asPair(a) + first += pair._1 + second += pair._2 + } + (first.result(), second.result()) + } + + 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] + + foreach { xyz => + val triple = asTriple(xyz) + b1 += triple._1 + b2 += triple._2 + b3 += triple._3 + } + (b1.result(), b2.result(), b3.result()) + } + + // The implementations of the following operations are not fundamentally different from + // the view-based implementations, but they turn out to be slightly faster because + // a couple of indirection levels are removed + + override def map[B](f: A => B): CC[B] = + strictOptimizedMap(iterableFactory.newBuilder, f) + + /** + * @param b Builder to use to build the resulting collection + * @param f Element transformation function + * @tparam B Type of elements of the resulting collection (e.g. `String`) + * @tparam C2 Type of the resulting collection (e.g. `List[String]`) + * @return The resulting collection + */ + @inline protected[this] final def strictOptimizedMap[B, C2](b: mutable.Builder[B, C2], f: A => B): C2 = { + val it = iterator + while (it.hasNext) { + b += f(it.next()) + } + b.result() + } + + override def flatMap[B](f: A => IterableOnce[B]^): CC[B] = + strictOptimizedFlatMap(iterableFactory.newBuilder, f) + + /** + * @param b Builder to use to build the resulting collection + * @param f Element transformation function + * @tparam B Type of elements of the resulting collection (e.g. `String`) + * @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 = { + val it = iterator + while (it.hasNext) { + b ++= f(it.next()) + } + b.result() + } + + /** + * @param that Elements to concatenate to this collection + * @param b Builder to use to build the resulting collection + * @tparam B Type of elements of the resulting collections (e.g. `Int`) + * @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 = { + b ++= this + b ++= that + b.result() + } + + override def collect[B](pf: PartialFunction[A, B]^): CC[B] = + strictOptimizedCollect(iterableFactory.newBuilder, pf) + + /** + * @param b Builder to use to build the resulting collection + * @param pf Element transformation partial function + * @tparam B Type of elements of the resulting collection (e.g. `String`) + * @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 = { + val marker = Statics.pfMarker + val it = iterator + while (it.hasNext) { + val elem = it.next() + val v = pf.applyOrElse(elem, ((x: A) => marker).asInstanceOf[Function[A, B]]) + if (marker ne v.asInstanceOf[AnyRef]) b += v + } + b.result() + } + + override def flatten[B](implicit toIterableOnce: A -> IterableOnce[B]): CC[B] = + strictOptimizedFlatten(iterableFactory.newBuilder) + + /** + * @param b Builder to use to build the resulting collection + * @param toIterableOnce Evidence that `A` can be seen as an `IterableOnce[B]` + * @tparam B Type of elements of the resulting collection (e.g. `Int`) + * @tparam C2 Type of the resulting collection (e.g. `List[Int]`) + * @return The resulting collection + */ + @inline protected[this] final def strictOptimizedFlatten[B, C2](b: mutable.Builder[B, C2])(implicit toIterableOnce: A -> IterableOnce[B]): C2 = { + val it = iterator + while (it.hasNext) { + b ++= toIterableOnce(it.next()) + } + b.result() + } + + override def zip[B](that: IterableOnce[B]^): CC[(A @uncheckedVariance, B)] = + strictOptimizedZip(that, iterableFactory.newBuilder[(A, B)]) + + /** + * @param that Collection to zip with this collection + * @param b Builder to use to build the resulting collection + * @tparam B Type of elements of the second collection (e.g. `String`) + * @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 = { + val it1 = iterator + val it2 = that.iterator + while (it1.hasNext && it2.hasNext) { + b += ((it1.next(), it2.next())) + } + b.result() + } + + override def zipWithIndex: CC[(A @uncheckedVariance, Int)] = { + val b = iterableFactory.newBuilder[(A, Int)] + var i = 0 + val it = iterator + while (it.hasNext) { + b += ((it.next(), i)) + i += 1 + } + b.result() + } + + override def scanLeft[B](z: B)(op: (B, A) => B): CC[B] = { + val b = iterableFactory.newBuilder[B] + b.sizeHint(this, delta = 0) + var acc = z + b += acc + val it = iterator + while (it.hasNext) { + acc = op(acc, it.next()) + b += acc + } + b.result() + } + + override def filter(pred: A => Boolean): C = filterImpl(pred, isFlipped = false) + + override def filterNot(pred: A => Boolean): C = filterImpl(pred, isFlipped = true) + + protected[collection] def filterImpl(pred: A => Boolean, isFlipped: Boolean): C = { + val b = newSpecificBuilder + val it = iterator + while (it.hasNext) { + val elem = it.next() + if (pred(elem) != isFlipped) { + b += elem + } + } + b.result() + } + + // Optimized, push-based version of `partitionMap` + override def partitionMap[A1, A2](f: A => Either[A1, A2]): (CC[A1], CC[A2]) = { + val l = iterableFactory.newBuilder[A1] + val r = iterableFactory.newBuilder[A2] + foreach { x => + f(x) match { + case Left(x1) => l += x1 + case Right(x2) => r += x2 + } + } + (l.result(), r.result()) + } + + // Optimization avoids creation of second collection + override def tapEach[U](f: A => U): C = { + foreach(f) + coll + } + + /** A collection containing the last `n` elements of this collection. + * $willForceEvaluation + */ + override def takeRight(n: Int): C = { + val b = newSpecificBuilder + b.sizeHintBounded(n, toIterable: @nowarn("cat=deprecation")) + val lead = iterator drop n + val it = iterator + while (lead.hasNext) { + lead.next() + it.next() + } + while (it.hasNext) b += it.next() + b.result() + } + + /** The rest of the collection without its `n` last elements. For + * linear, immutable collections this should avoid making a copy. + * $willForceEvaluation + */ + override def dropRight(n: Int): C = { + val b = newSpecificBuilder + if (n >= 0) b.sizeHint(this, delta = -n) + val lead = iterator drop n + val it = iterator + while (lead.hasNext) { + b += it.next() + lead.next() + } + b.result() + } +} diff --git a/tests/pos/stdlib/collection/StrictOptimizedSeqOps.scala b/tests/pos/stdlib/collection/StrictOptimizedSeqOps.scala new file mode 100644 index 000000000000..50ddbca30f9e --- /dev/null +++ b/tests/pos/stdlib/collection/StrictOptimizedSeqOps.scala @@ -0,0 +1,113 @@ +/* + * Scala (https://www.scala-lang.org) + * + * Copyright EPFL and Lightbend, Inc. + * + * 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.collection +import language.experimental.captureChecking + +/** + * Trait that overrides operations on sequences in order + * to take advantage of strict builders. + */ +trait StrictOptimizedSeqOps [+A, +CC[_], +C] + extends AnyRef + with SeqOps[A, CC, C] + with StrictOptimizedIterableOps[A, CC, C] { + + override def distinctBy[B](f: A -> B): C = { + val builder = newSpecificBuilder + val seen = mutable.HashSet.empty[B] + val it = this.iterator + while (it.hasNext) { + val next = it.next() + if (seen.add(f(next))) builder += next + } + builder.result() + } + + override def prepended[B >: A](elem: B): CC[B] = { + val b = iterableFactory.newBuilder[B] + if (knownSize >= 0) { + b.sizeHint(size + 1) + } + b += elem + b ++= this + b.result() + } + + override def appended[B >: A](elem: B): CC[B] = { + val b = iterableFactory.newBuilder[B] + if (knownSize >= 0) { + b.sizeHint(size + 1) + } + b ++= this + b += elem + b.result() + } + + override def appendedAll[B >: A](suffix: IterableOnce[B]^): CC[B] = + strictOptimizedConcat(suffix, iterableFactory.newBuilder) + + override def prependedAll[B >: A](prefix: IterableOnce[B]^): CC[B] = { + val b = iterableFactory.newBuilder[B] + b ++= prefix + b ++= this + b.result() + } + + override def padTo[B >: A](len: Int, elem: B): CC[B] = { + val b = iterableFactory.newBuilder[B] + val L = size + b.sizeHint(math.max(L, len)) + var diff = len - L + b ++= this + while (diff > 0) { + b += elem + diff -= 1 + } + b.result() + } + + override def diff[B >: A](that: Seq[B]): C = + if (isEmpty || that.isEmpty) coll + else { + val occ = occCounts(that) + val b = newSpecificBuilder + for (x <- this) { + occ.updateWith(x) { + case None => { + b.addOne(x) + None + } + case Some(1) => None + case Some(n) => Some(n - 1) + } + } + b.result() + } + + override def intersect[B >: A](that: Seq[B]): C = + if (isEmpty || that.isEmpty) empty + else { + val occ = occCounts(that) + val b = newSpecificBuilder + for (x <- this) { + occ.updateWith(x) { + case None => None + case Some(n) => { + b.addOne(x) + if (n == 1) None else Some(n - 1) + } + } + } + b.result() + } +} From 55431045103e173ad3a05cc676e5274028388fde Mon Sep 17 00:00:00 2001 From: odersky Date: Tue, 18 Jul 2023 16:20:16 +0200 Subject: [PATCH 30/37] Add List and ListBuffer to stdlib test Plus all supertraits of ListBuffer --- .../stdlib/collection/immutable/List.scala | 693 ++++++++++++++++++ .../stdlib/collection/mutable/Buffer.scala | 232 ++++++ .../stdlib/collection/mutable/Builder.scala | 92 +++ .../stdlib/collection/mutable/Growable.scala | 102 +++ .../collection/mutable/ListBuffer.scala | 404 ++++++++++ .../collection/mutable/MutationTracker.scala | 79 ++ .../collection/mutable/Shrinkable.scala | 80 ++ tests/pos/stdlib/immutable_List.scala | 1 + tests/pos/stdlib/mutable_Buffer.scala | 1 + tests/pos/stdlib/mutable_Builder.scala | 1 + tests/pos/stdlib/mutable_Growable.scala | 1 + tests/pos/stdlib/mutable_ListBuffer.scala | 1 + .../pos/stdlib/mutable_MutationTracker.scala | 1 + tests/pos/stdlib/mutable_Shrinkable.scala | 1 + 14 files changed, 1689 insertions(+) create mode 100644 tests/pos/stdlib/collection/immutable/List.scala create mode 100644 tests/pos/stdlib/collection/mutable/Buffer.scala create mode 100644 tests/pos/stdlib/collection/mutable/Builder.scala create mode 100644 tests/pos/stdlib/collection/mutable/Growable.scala create mode 100644 tests/pos/stdlib/collection/mutable/ListBuffer.scala create mode 100644 tests/pos/stdlib/collection/mutable/MutationTracker.scala create mode 100644 tests/pos/stdlib/collection/mutable/Shrinkable.scala create mode 120000 tests/pos/stdlib/immutable_List.scala create mode 120000 tests/pos/stdlib/mutable_Buffer.scala create mode 120000 tests/pos/stdlib/mutable_Builder.scala create mode 120000 tests/pos/stdlib/mutable_Growable.scala create mode 120000 tests/pos/stdlib/mutable_ListBuffer.scala create mode 120000 tests/pos/stdlib/mutable_MutationTracker.scala create mode 120000 tests/pos/stdlib/mutable_Shrinkable.scala diff --git a/tests/pos/stdlib/collection/immutable/List.scala b/tests/pos/stdlib/collection/immutable/List.scala new file mode 100644 index 000000000000..6245c5001776 --- /dev/null +++ b/tests/pos/stdlib/collection/immutable/List.scala @@ -0,0 +1,693 @@ +/* + * Scala (https://www.scala-lang.org) + * + * Copyright EPFL and Lightbend, Inc. + * + * 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.annotation.unchecked.uncheckedVariance +import scala.annotation.tailrec +import mutable.{Builder, ListBuffer} +import scala.collection.generic.DefaultSerializable +import scala.runtime.Statics.releaseFence +import language.experimental.captureChecking + +/** A class for immutable linked lists representing ordered collections + * of elements of type `A`. + * + * This class comes with two implementing case classes `scala.Nil` + * and `scala.::` that implement the abstract members `isEmpty`, + * `head` and `tail`. + * + * This class is optimal for last-in-first-out (LIFO), stack-like access patterns. If you need another access + * pattern, for example, random access or FIFO, consider using a collection more suited to this than `List`. + * + * ==Performance== + * '''Time:''' `List` has `O(1)` prepend and head/tail access. Most other operations are `O(n)` on the number of elements in the list. + * This includes the index-based lookup of elements, `length`, `append` and `reverse`. + * + * '''Space:''' `List` implements '''structural sharing''' of the tail list. This means that many operations are either + * zero- or constant-memory cost. + * {{{ + * val mainList = List(3, 2, 1) + * val with4 = 4 :: mainList // re-uses mainList, costs one :: instance + * val with42 = 42 :: mainList // also re-uses mainList, cost one :: instance + * val shorter = mainList.tail // costs nothing as it uses the same 2::1::Nil instances as mainList + * }}} + * + * @example {{{ + * // Make a list via the companion object factory + * val days = List("Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday") + * + * // Make a list element-by-element + * val when = "AM" :: "PM" :: Nil + * + * // Pattern match + * days match { + * case firstDay :: otherDays => + * println("The first day of the week is: " + firstDay) + * case Nil => + * println("There don't seem to be any week days.") + * } + * }}} + * + * @note The functional list is characterized by persistence and structural sharing, thus offering considerable + * performance and space consumption benefits in some scenarios if used correctly. + * However, note that objects having multiple references into the same functional list (that is, + * objects that rely on structural sharing), will be serialized and deserialized with multiple lists, one for + * each reference to it. I.e. structural sharing is lost after serialization/deserialization. + * + * @see [[https://docs.scala-lang.org/overviews/collections-2.13/concrete-immutable-collection-classes.html#lists "Scala's Collection Library overview"]] + * section on `Lists` for more information. + * + * @define coll list + * @define Coll `List` + * @define orderDependent + * @define orderDependentFold + * @define mayNotTerminateInf + * @define willNotTerminateInf + */ +@SerialVersionUID(3L) +sealed abstract class List[+A] + extends AbstractSeq[A] + with LinearSeq[A] + with LinearSeqOps[A, List, List[A]] + with StrictOptimizedLinearSeqOps[A, List, List[A]] + with StrictOptimizedSeqOps[A, List, List[A]] + with IterableFactoryDefaults[A, List] + with DefaultSerializable { + + override def iterableFactory: SeqFactory[List] = List + + /** Adds an element at the beginning of this list. + * @param elem the element to prepend. + * @return a list which contains `x` as first element and + * which continues with this list. + * Example: + * {{{1 :: List(2, 3) = List(2, 3).::(1) = List(1, 2, 3)}}} + */ + def :: [B >: A](elem: B): List[B] = new ::(elem, this) + + /** Adds the elements of a given list in front of this list. + * + * Example: + * {{{List(1, 2) ::: List(3, 4) = List(3, 4).:::(List(1, 2)) = List(1, 2, 3, 4)}}} + * + * @param prefix The list elements to prepend. + * @return a list resulting from the concatenation of the given + * list `prefix` and this list. + */ + def ::: [B >: A](prefix: List[B]): List[B] = + if (isEmpty) prefix + else if (prefix.isEmpty) this + else { + val result = new ::[B](prefix.head, this) + var curr = result + var that = prefix.tail + while (!that.isEmpty) { + val temp = new ::[B](that.head, this) + curr.next = temp + curr = temp + that = that.tail + } + releaseFence() + result + } + + /** Adds the elements of a given list in reverse order in front of this list. + * `xs reverse_::: ys` is equivalent to + * `xs.reverse ::: ys` but is more efficient. + * + * @param prefix the prefix to reverse and then prepend + * @return the concatenation of the reversed prefix and the current list. + */ + def reverse_:::[B >: A](prefix: List[B]): List[B] = { + var these: List[B] = this + var pres = prefix + while (!pres.isEmpty) { + these = pres.head :: these + pres = pres.tail + } + these + } + + override final def isEmpty: Boolean = this eq Nil + + override def prepended[B >: A](elem: B): List[B] = elem :: this + + 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 + case _ => + val iter = prefix.iterator + if (iter.hasNext) { + val result = new ::[B](iter.next(), this) + var curr = result + while (iter.hasNext) { + val temp = new ::[B](iter.next(), this) + curr.next = temp + curr = temp + } + releaseFence() + result + } else { + this + } + } + + // When calling appendAll with another list `suffix`, avoid copying `suffix` + override def appendedAll[B >: A](suffix: collection.IterableOnce[B]^): List[B] = suffix match { + case xs: List[B] => this ::: xs + case _ => super.appendedAll(suffix) + } + + override def take(n: Int): List[A] = if (isEmpty || n <= 0) Nil else { + val h = new ::(head, Nil) + var t = h + var rest = tail + var i = 1 + while ({if (rest.isEmpty) return this; i < n}) { + i += 1 + val nx = new ::(rest.head, Nil) + t.next = nx + t = nx + rest = rest.tail + } + releaseFence() + h + } + + /** + * @example {{{ + * // Given a list + * val letters = List('a','b','c','d','e') + * + * // `slice` returns all elements beginning at index `from` and afterwards, + * // up until index `until` (excluding index `until`.) + * letters.slice(1,3) // Returns List('b','c') + * }}} + */ + override def slice(from: Int, until: Int): List[A] = { + val lo = scala.math.max(from, 0) + if (until <= lo || isEmpty) Nil + else this drop lo take (until - lo) + } + + override def takeRight(n: Int): List[A] = { + @tailrec + def loop(lead: List[A], lag: List[A]): List[A] = lead match { + case Nil => lag + case _ :: tail => loop(tail, lag.tail) + } + loop(drop(n), this) + } + + // dropRight is inherited from LinearSeq + + override def splitAt(n: Int): (List[A], List[A]) = { + val b = new ListBuffer[A] + var i = 0 + var these = this + while (!these.isEmpty && i < n) { + i += 1 + b += these.head + these = these.tail + } + (b.toList, these) + } + + override def updated[B >: A](index: Int, elem: B): List[B] = { + var i = 0 + var current = this + val prefix = ListBuffer.empty[B] + while (i < index && current.nonEmpty) { + i += 1 + prefix += current.head + current = current.tail + } + if (i == index && current.nonEmpty) { + prefix.prependToList(elem :: current.tail) + } else { + throw new IndexOutOfBoundsException(s"$index is out of bounds (min 0, max ${length-1})") + } + } + + final override def map[B](f: A => B): List[B] = { + if (this eq Nil) Nil else { + val h = new ::[B](f(head), Nil) + var t: ::[B] = h + var rest = tail + while (rest ne Nil) { + val nx = new ::(f(rest.head), Nil) + t.next = nx + t = nx + rest = rest.tail + } + releaseFence() + h + } + } + + final override def collect[B](pf: PartialFunction[A, B]^): List[B] = { + if (this eq Nil) Nil else { + var rest = this + var h: ::[B] = null + var x: Any = null + // Special case for first element + while (h eq null) { + x = pf.applyOrElse(rest.head, List.partialNotApplied) + if (x.asInstanceOf[AnyRef] ne List.partialNotApplied) h = new ::(x.asInstanceOf[B], Nil) + rest = rest.tail + if (rest eq Nil) return if (h eq null) Nil else h + } + var t = h + // Remaining elements + while (rest ne Nil) { + x = pf.applyOrElse(rest.head, List.partialNotApplied) + if (x.asInstanceOf[AnyRef] ne List.partialNotApplied) { + val nx = new ::(x.asInstanceOf[B], Nil) + t.next = nx + t = nx + } + rest = rest.tail + } + releaseFence() + h + } + } + + final override def flatMap[B](f: A => IterableOnce[B]^): List[B] = { + var rest = this + var h: ::[B] = null + var t: ::[B] = null + while (rest ne Nil) { + val it = f(rest.head).iterator + while (it.hasNext) { + val nx = new ::(it.next(), Nil) + if (t eq null) { + h = nx + } else { + t.next = nx + } + t = nx + } + rest = rest.tail + } + if (h eq null) Nil else {releaseFence(); h} + } + + @inline final override def takeWhile(p: A => Boolean): List[A] = { + val b = new ListBuffer[A] + var these = this + while (!these.isEmpty && p(these.head)) { + b += these.head + these = these.tail + } + b.toList + } + + @inline final override def span(p: A => Boolean): (List[A], List[A]) = { + val b = new ListBuffer[A] + var these = this + while (!these.isEmpty && p(these.head)) { + b += these.head + these = these.tail + } + (b.toList, these) + } + + // Overridden with an implementation identical to the inherited one (at this time) + // solely so it can be finalized and thus inlinable. + @inline final override def foreach[U](f: A => U): Unit = { + var these = this + while (!these.isEmpty) { + f(these.head) + these = these.tail + } + } + + final override def reverse: List[A] = { + var result: List[A] = Nil + var these = this + while (!these.isEmpty) { + result = these.head :: result + these = these.tail + } + result + } + + final override def foldRight[B](z: B)(op: (A, B) => B): B = { + var acc = z + var these: List[A] = reverse + while (!these.isEmpty) { + acc = op(these.head, acc) + these = these.tail + } + acc + } + + // Copy/Paste overrides to avoid interface calls inside loops. + + override final def length: Int = { + var these = this + var len = 0 + while (!these.isEmpty) { + len += 1 + these = these.tail + } + len + } + + override final def lengthCompare(len: Int): Int = { + @tailrec def loop(i: Int, xs: List[A]): Int = { + if (i == len) + if (xs.isEmpty) 0 else 1 + else if (xs.isEmpty) + -1 + else + loop(i + 1, xs.tail) + } + if (len < 0) 1 + else loop(0, coll) + } + + override final def forall(p: A => Boolean): Boolean = { + var these: List[A] = this + while (!these.isEmpty) { + if (!p(these.head)) return false + these = these.tail + } + true + } + + override final def exists(p: A => Boolean): Boolean = { + var these: List[A] = this + while (!these.isEmpty) { + if (p(these.head)) return true + these = these.tail + } + false + } + + override final def contains[A1 >: A](elem: A1): Boolean = { + var these: List[A] = this + while (!these.isEmpty) { + if (these.head == elem) return true + these = these.tail + } + false + } + + override final def find(p: A => Boolean): Option[A] = { + var these: List[A] = this + while (!these.isEmpty) { + if (p(these.head)) return Some(these.head) + these = these.tail + } + None + } + + override def last: A = { + if (isEmpty) throw new NoSuchElementException("List.last") + else { + var these = this + var scout = tail + while (!scout.isEmpty) { + these = scout + scout = scout.tail + } + these.head + } + } + + override def corresponds[B](that: collection.Seq[B])(p: (A, B) => Boolean): Boolean = that match { + case that: LinearSeq[B] => + var i = this + var j = that + while (!(i.isEmpty || j.isEmpty)) { + if (!p(i.head, j.head)) + return false + i = i.tail + j = j.tail + } + i.isEmpty && j.isEmpty + case _ => + super.corresponds(that)(p) + } + + override protected[this] def className = "List" + + /** Builds a new list by applying a function to all elements of this list. + * Like `xs map f`, but returns `xs` unchanged if function + * `f` maps all elements to themselves (as determined by `eq`). + * + * @param f the function to apply to each element. + * @tparam B the element type of the returned collection. + * @return a list resulting from applying the given function + * `f` to each element of this list and collecting the results. + */ + @`inline` final def mapConserve[B >: A <: AnyRef](f: A => B): List[B] = { + // Note to developers: there exists a duplication between this function and `reflect.internal.util.Collections#map2Conserve`. + // If any successful optimization attempts or other changes are made, please rehash them there too. + @tailrec + def loop(mappedHead: List[B], mappedLast: ::[B], unchanged: List[A], pending: List[A]): List[B] = { + if (pending.isEmpty) { + if (mappedHead eq null) unchanged + else { + mappedLast.next = (unchanged: List[B]) + mappedHead + } + } + else { + val head0 = pending.head + val head1 = f(head0) + + if (head1 eq head0.asInstanceOf[AnyRef]) + loop(mappedHead, mappedLast, unchanged, pending.tail) + else { + var xc = unchanged + var mappedHead1: List[B] = mappedHead + var mappedLast1: ::[B] = mappedLast + while (xc ne pending) { + val next = new ::[B](xc.head, Nil) + if (mappedHead1 eq null) mappedHead1 = next + if (mappedLast1 ne null) mappedLast1.next = next + mappedLast1 = next + xc = xc.tail + } + val next = new ::(head1, Nil) + if (mappedHead1 eq null) mappedHead1 = next + if (mappedLast1 ne null) mappedLast1.next = next + mappedLast1 = next + val tail0 = pending.tail + loop(mappedHead1, mappedLast1, tail0, tail0) + + } + } + } + val result = loop(null, null, this, this) + releaseFence() + result + } + + override def filter(p: A => Boolean): List[A] = filterCommon(p, isFlipped = false) + + override def filterNot(p: A => Boolean): List[A] = filterCommon(p, isFlipped = true) + + private[this] def filterCommon(p: A => Boolean, isFlipped: Boolean): List[A] = { + + // everything seen so far so far is not included + @tailrec def noneIn(l: List[A]): List[A] = { + if (l.isEmpty) + Nil + else { + val h = l.head + val t = l.tail + if (p(h) != isFlipped) + allIn(l, t) + else + noneIn(t) + } + } + + // everything from 'start' is included, if everything from this point is in we can return the origin + // start otherwise if we discover an element that is out we must create a new partial list. + @tailrec def allIn(start: List[A], remaining: List[A]): List[A] = { + if (remaining.isEmpty) + start + else { + val x = remaining.head + if (p(x) != isFlipped) + allIn(start, remaining.tail) + else + partialFill(start, remaining) + } + } + + // we have seen elements that should be included then one that should be excluded, start building + def partialFill(origStart: List[A], firstMiss: List[A]): List[A] = { + val newHead = new ::(origStart.head, Nil) + var toProcess = origStart.tail + var currentLast = newHead + + // we know that all elements are :: until at least firstMiss.tail + while (!(toProcess eq firstMiss)) { + val newElem = new ::(toProcess.head, Nil) + currentLast.next = newElem + currentLast = newElem + toProcess = toProcess.tail + } + + // at this point newHead points to a list which is a duplicate of all the 'in' elements up to the first miss. + // currentLast is the last element in that list. + + // now we are going to try and share as much of the tail as we can, only moving elements across when we have to. + var next = firstMiss.tail + var nextToCopy = next // the next element we would need to copy to our list if we cant share. + while (!next.isEmpty) { + // generally recommended is next.isNonEmpty but this incurs an extra method call. + val head: A = next.head + if (p(head) != isFlipped) { + next = next.tail + } else { + // its not a match - do we have outstanding elements? + while (!(nextToCopy eq next)) { + val newElem = new ::(nextToCopy.head, Nil) + currentLast.next = newElem + currentLast = newElem + nextToCopy = nextToCopy.tail + } + nextToCopy = next.tail + next = next.tail + } + } + + // we have remaining elements - they are unchanged attach them to the end + if (!nextToCopy.isEmpty) + currentLast.next = nextToCopy + + newHead + } + + val result = noneIn(this) + releaseFence() + result + } + + override def partition(p: A => Boolean): (List[A], List[A]) = { + if (isEmpty) List.TupleOfNil + else super.partition(p) match { + case (Nil, xs) => (Nil, this) + case (xs, Nil) => (this, Nil) + case pair => pair + } + } + + final override def toList: List[A] = this + + // Override for performance + override def equals(o: scala.Any): Boolean = { + @tailrec def listEq(a: List[_], b: List[_]): Boolean = + (a eq b) || { + val aEmpty = a.isEmpty + val bEmpty = b.isEmpty + if (!(aEmpty || bEmpty) && a.head == b.head) { + listEq(a.tail, b.tail) + } + else { + aEmpty && bEmpty + } + } + + o match { + case that: List[_] => listEq(this, that) + case _ => super.equals(o) + } + } + + // TODO: uncomment once bincompat allows (reference: scala/scala#9365) + /* + // Override for performance: traverse only as much as needed + // and share tail when nothing needs to be filtered out anymore + override def diff[B >: A](that: collection.Seq[B]): AnyRef = { + if (that.isEmpty || this.isEmpty) this + else if (tail.isEmpty) if (that.contains(head)) Nil else this + else { + val occ = occCounts(that) + val b = new ListBuffer[A]() + @tailrec + def rec(remainder: List[A]): List[A] = { + if(occ.isEmpty) b.prependToList(remainder) + else remainder match { + case Nil => b.result() + case head :: next => { + occ.updateWith(head){ + case None => { + b.append(head) + None + } + case Some(1) => None + case Some(n) => Some(n - 1) + } + rec(next) + } + } + } + rec(this) + } + } + */ + +} + +// Internal code that mutates `next` _must_ call `Statics.releaseFence()` if either immediately, or +// before a newly-allocated, thread-local :: instance is aliased (e.g. in ListBuffer.toList) +final case class :: [+A](override val head: A, private[scala] var next: List[A @uncheckedVariance]) // sound because `next` is used only locally + extends List[A] { + releaseFence() + override def headOption: Some[A] = Some(head) + override def tail: List[A] = next +} + +case object Nil extends List[Nothing] { + override def head: Nothing = throw new NoSuchElementException("head of empty list") + override def headOption: None.type = None + override def tail: Nothing = throw new UnsupportedOperationException("tail of empty list") + override def last: Nothing = throw new NoSuchElementException("last of empty list") + 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 + + @transient + private[this] val EmptyUnzip = (Nil, Nil) +} + +/** + * $factoryInfo + * @define coll list + * @define Coll `List` + */ +@SerialVersionUID(3L) +object List extends StrictOptimizedSeqFactory[List] { + private val TupleOfNil = (Nil, Nil) + + def from[B](coll: collection.IterableOnce[B]^): List[B] = Nil.prependedAll(coll) + + def newBuilder[A]: Builder[A, List[A]] = new ListBuffer() + + def empty[A]: List[A] = Nil + + @transient + private[collection] val partialNotApplied = new Function1[Any, Any] { def apply(x: Any): Any = this } +} diff --git a/tests/pos/stdlib/collection/mutable/Buffer.scala b/tests/pos/stdlib/collection/mutable/Buffer.scala new file mode 100644 index 000000000000..847b924735ce --- /dev/null +++ b/tests/pos/stdlib/collection/mutable/Buffer.scala @@ -0,0 +1,232 @@ +/* + * Scala (https://www.scala-lang.org) + * + * Copyright EPFL and Lightbend, Inc. + * + * 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.collection +package mutable + +import scala.annotation.nowarn + + +/** A `Buffer` is a growable and shrinkable `Seq`. */ +trait Buffer[A] + extends Seq[A] + with SeqOps[A, Buffer, Buffer[A]] + with Growable[A] + with Shrinkable[A] + with IterableFactoryDefaults[A, Buffer] { + + override def iterableFactory: SeqFactory[Buffer] = Buffer + + override def knownSize: Int = super[Seq].knownSize + + //TODO Prepend is a logical choice for a readable name of `+=:` but it conflicts with the renaming of `append` to `add` + /** Prepends a single element at the front of this $coll. + * + * @param elem the element to $add. + * @return the $coll itself + */ + def prepend(elem: A): this.type + + /** Appends the given elements to this buffer. + * + * @param elem the element to append. + */ + @`inline` final def append(elem: A): this.type = addOne(elem) + + @deprecated("Use appendAll instead", "2.13.0") + @`inline` final def append(elems: A*): this.type = addAll(elems) + + /** Appends the elements contained in a iterable object to this buffer. + * @param xs the iterable object containing the elements to append. + */ + @`inline` final def appendAll(xs: IterableOnce[A]): this.type = addAll(xs) + + + /** Alias for `prepend` */ + @`inline` final def +=: (elem: A): this.type = prepend(elem) + + 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) + + /** Inserts a new element at a given index into this buffer. + * + * @param idx the index where the new elements is inserted. + * @param elem the element to insert. + * @throws IndexOutOfBoundsException if the index `idx` is not in the valid range + * `0 <= idx <= length`. + */ + @throws[IndexOutOfBoundsException] + def insert(idx: Int, elem: A): Unit + + /** Inserts new elements at the index `idx`. Opposed to method + * `update`, this method will not replace an element with a new + * one. Instead, it will insert a new element at index `idx`. + * + * @param idx the index where a new element will be inserted. + * @param elems the iterable object providing all elements to insert. + * @throws IndexOutOfBoundsException if `idx` is out of bounds. + */ + @throws[IndexOutOfBoundsException] + def insertAll(idx: Int, elems: IterableOnce[A]): Unit + + /** Removes the element at a given index position. + * + * @param idx the index which refers to the element to delete. + * @return the element that was formerly at index `idx`. + */ + @throws[IndexOutOfBoundsException] + def remove(idx: Int): A + + /** Removes the element on a given index position. It takes time linear in + * the buffer size. + * + * @param idx the index which refers to the first element to remove. + * @param count the number of elements to remove. + * @throws IndexOutOfBoundsException if the index `idx` is not in the valid range + * `0 <= idx <= length - count` (with `count > 0`). + * @throws IllegalArgumentException if `count < 0`. + */ + @throws[IndexOutOfBoundsException] + @throws[IllegalArgumentException] + def remove(idx: Int, count: Int): Unit + + /** Removes a single element from this buffer, at its first occurrence. + * If the buffer does not contain that element, it is unchanged. + * + * @param x the element to remove. + * @return the buffer itself + */ + def subtractOne (x: A): this.type = { + val i = indexOf(x) + if (i != -1) remove(i) + this + } + + /** Removes the first ''n'' elements of this buffer. + * + * @param n the number of elements to remove from the beginning + * of this buffer. + */ + @deprecated("use dropInPlace instead", since = "2.13.4") + def trimStart(n: Int): Unit = dropInPlace(n) + + /** Removes the last ''n'' elements of this buffer. + * + * @param n the number of elements to remove from the end + * of this buffer. + */ + @deprecated("use dropRightInPlace instead", since = "2.13.4") + def trimEnd(n: Int): Unit = dropRightInPlace(n) + + 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: + // + // def +=:(elem: A): this.type = { insert(0, elem); this } + // def +=:(elem1: A, elem2: A, elems: A*): this.type = elem1 +=: elem2 +=: elems ++=: this + // def ++=:(elems: IterableOnce[A]): this.type = { insertAll(0, elems); this } + + def dropInPlace(n: Int): this.type = { remove(0, normalized(n)); this } + def dropRightInPlace(n: Int): this.type = { + val norm = normalized(n) + remove(length - norm, norm) + this + } + def takeInPlace(n: Int): this.type = { + val norm = normalized(n) + remove(norm, length - norm) + this + } + def takeRightInPlace(n: Int): this.type = { remove(0, length - normalized(n)); this } + def sliceInPlace(start: Int, end: Int): this.type = takeInPlace(end).dropInPlace(start) + private def normalized(n: Int): Int = math.min(math.max(n, 0), length) + + def dropWhileInPlace(p: A => Boolean): this.type = { + val idx = indexWhere(!p(_)) + if (idx < 0) { clear(); this } else dropInPlace(idx) + } + def takeWhileInPlace(p: A => Boolean): this.type = { + val idx = indexWhere(!p(_)) + if (idx < 0) this else takeInPlace(idx) + } + def padToInPlace(len: Int, elem: A): this.type = { + while (length < len) +=(elem) + this + } + + @nowarn("""cat=deprecation&origin=scala\.collection\.Iterable\.stringPrefix""") + override protected[this] def stringPrefix = "Buffer" +} + +trait IndexedBuffer[A] extends IndexedSeq[A] + with IndexedSeqOps[A, IndexedBuffer, IndexedBuffer[A]] + with Buffer[A] + with IterableFactoryDefaults[A, IndexedBuffer] { + + override def iterableFactory: SeqFactory[IndexedBuffer] = IndexedBuffer + + 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) + while (i < s) { newElems(i) = f(this(i)); i += 1 } + clear() + i = 0 + while (i < s) { ++=(newElems(i)); i += 1 } + this + } + + def filterInPlace(p: A => Boolean): this.type = { + var i, j = 0 + while (i < size) { + if (p(apply(i))) { + if (i != j) { + this(j) = this(i) + } + j += 1 + } + i += 1 + } + + if (i == j) this else takeInPlace(j) + } + + 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 + val iter = patch.iterator + while (iter.hasNext && j < replaced0 && i + j < length) { + update(i + j, iter.next()) + j += 1 + } + if (iter.hasNext) insertAll(i + j, iter) + else if (j < replaced0) remove(i + j, math.min(replaced0 - j, length - i - j)) + this + } +} + +@SerialVersionUID(3L) +object Buffer extends SeqFactory.Delegate[Buffer](ArrayBuffer) + +@SerialVersionUID(3L) +object IndexedBuffer extends SeqFactory.Delegate[IndexedBuffer](ArrayBuffer) + +/** Explicit instantiation of the `Buffer` trait to reduce class file size in subclasses. */ +abstract class AbstractBuffer[A] extends AbstractSeq[A] with Buffer[A] diff --git a/tests/pos/stdlib/collection/mutable/Builder.scala b/tests/pos/stdlib/collection/mutable/Builder.scala new file mode 100644 index 000000000000..dd57cb75da91 --- /dev/null +++ b/tests/pos/stdlib/collection/mutable/Builder.scala @@ -0,0 +1,92 @@ +/* + * Scala (https://www.scala-lang.org) + * + * Copyright EPFL and Lightbend, Inc. + * + * 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.collection.mutable + +import language.experimental.captureChecking + + +/** Base trait for collection builders. + * + * After calling `result()` the behavior of a Builder (which is not also a [[scala.collection.mutable.ReusableBuilder]]) + * is undefined. No further methods should be called. It is common for mutable collections to be their own non-reusable + * Builder, in which case `result()` simply returns `this`. + * + * @see [[scala.collection.mutable.ReusableBuilder]] for Builders which can be reused after calling `result()` + */ +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. + */ + def clear(): Unit + + /** Result collection consisting of all elements appended so far. */ + def result(): To + + /** Gives a hint how many elements are expected to be added + * when the next `result` is called. Some builder classes + * will optimize their representation based on the hint. However, + * builder implementations are still required to work correctly even if the hint is + * wrong, i.e. a different number of elements is added. + * + * @param size the hint how many elements will be added. + */ + def sizeHint(size: Int): Unit = () + + /** Gives a hint that one expects the `result` of this builder + * to have the same size as the given collection, plus some delta. This will + * provide a hint only if the collection has a known size + * Some builder classes + * will optimize their representation based on the hint. However, + * builder implementations are still required to work correctly even if the hint is + * wrong, i.e. a different number of elements is added. + * + * @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. + */ + final def sizeHint(coll: scala.collection.IterableOnce[_]^, delta: Int = 0): Unit = { + val s = coll.knownSize + if (s != -1) sizeHint(s + delta) + } + + /** Gives a hint how many elements are expected to be added + * when the next `result` is called, together with an upper bound + * given by the size of some other collection. Some builder classes + * will optimize their representation based on the hint. However, + * builder implementations are still required to work correctly even if the hint is + * wrong, i.e. a different number of elements is added. + * + * @param size the hint how many elements will be added. + * @param boundingColl the bounding collection. If it is + * an IndexedSeqLike, then sizes larger + * than collection's size are reduced. + */ + // should probably be `boundingColl: IterableOnce[_]`, but binary compatibility + final def sizeHintBounded(size: Int, boundingColl: scala.collection.Iterable[_]^): Unit = { + val s = boundingColl.knownSize + if (s != -1) { + sizeHint(scala.math.min(s, size)) + } + } + + /** A builder resulting from this builder my mapping the result using `f`. */ + 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 sizeHint(size: Int): Unit = self.sizeHint(size) + def result(): NewTo = f(self.result()) + override def knownSize: Int = self.knownSize + } +} diff --git a/tests/pos/stdlib/collection/mutable/Growable.scala b/tests/pos/stdlib/collection/mutable/Growable.scala new file mode 100644 index 000000000000..3b5eabac37bf --- /dev/null +++ b/tests/pos/stdlib/collection/mutable/Growable.scala @@ -0,0 +1,102 @@ +/* + * Scala (https://www.scala-lang.org) + * + * Copyright EPFL and Lightbend, Inc. + * + * 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 mutable + +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 + * a `clear` method. + * + * @define coll growable collection + * @define Coll `Growable` + * @define add add + * @define Add Add + */ +trait Growable[-A] extends Clearable { + + /** ${Add}s a single element to this $coll. + * + * @param elem the element to $add. + * @return the $coll itself + */ + def addOne(elem: A): this.type + + /** Alias for `addOne` */ + @`inline` final def += (elem: A): this.type = addOne(elem) + + //TODO This causes a conflict in StringBuilder; looks like a compiler bug + //@deprecated("Use addOne or += instead of append", "2.13.0") + //@`inline` final def append(elem: A): Unit = addOne(elem) + + /** ${Add}s two or more elements to this $coll. + * + * @param elem1 the first element to $add. + * @param elem2 the second element to $add. + * @param elems the remaining elements to $add. + * @return the $coll itself + */ + @deprecated("Use `++=` aka `addAll` instead of varargs `+=`; infix operations with an operand of multiple args will be deprecated", "2.13.0") + @`inline` final def += (elem1: A, elem2: A, elems: A*): this.type = this += elem1 += elem2 ++= (elems: IterableOnce[A]) + + /** ${Add}s all elements produced by an IterableOnce to this $coll. + * + * @param xs the IterableOnce producing the elements to $add. + * @return the $coll itself. + */ + def addAll(xs: IterableOnce[A]^): this.type = { + if (xs.asInstanceOf[AnyRef] eq this) addAll(Buffer.from(xs)) // avoid mutating under our own iterator + else { + val it = xs.iterator + while (it.hasNext) { + addOne(it.next()) + } + } + this + } + + /** Alias for `addAll` */ + @`inline` final def ++= (xs: IterableOnce[A]^): this.type = addAll(xs) + + /** @return The number of elements in the collection under construction, if it can be cheaply computed, + * -1 otherwise. The default implementation always returns -1. + */ + def knownSize: Int = -1 +} + +object Growable { + + /** + * Fills a `Growable` instance with the elements of a given iterable + * @param empty Instance to fill + * @param it Elements to add + * @tparam A Element type + * @return The filled instance + */ + def from[A](empty: Growable[A], it: collection.IterableOnce[A]^): empty.type = empty ++= it + +} + +/** This trait forms part of collections that can be cleared + * with a clear() call. + * + * @define coll collection + */ +trait Clearable { + /** Clears the $coll's contents. After this operation, the + * $coll is empty. + */ + def clear(): Unit +} diff --git a/tests/pos/stdlib/collection/mutable/ListBuffer.scala b/tests/pos/stdlib/collection/mutable/ListBuffer.scala new file mode 100644 index 000000000000..570c815644ee --- /dev/null +++ b/tests/pos/stdlib/collection/mutable/ListBuffer.scala @@ -0,0 +1,404 @@ +/* + * Scala (https://www.scala-lang.org) + * + * Copyright EPFL and Lightbend, Inc. + * + * 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.collection +package mutable + +import scala.annotation.{nowarn, tailrec} +import scala.collection.immutable.{::, List, Nil} +import java.lang.{IllegalArgumentException, IndexOutOfBoundsException} + +import scala.collection.generic.DefaultSerializable +import scala.runtime.Statics.releaseFence +import language.experimental.captureChecking + +/** A `Buffer` implementation backed by a list. It provides constant time + * prepend and append. Most other operations are linear. + * + * @see [[https://docs.scala-lang.org/overviews/collections-2.13/concrete-mutable-collection-classes.html#list-buffers "Scala's Collection Library overview"]] + * section on `List Buffers` for more information. + * + * @tparam A the type of this list buffer's elements. + * + * @define Coll `ListBuffer` + * @define coll list buffer + * @define orderDependent + * @define orderDependentFold + * @define mayNotTerminateInf + * @define willNotTerminateInf + */ +@SerialVersionUID(-8428291952499836345L) +class ListBuffer[A] + extends AbstractBuffer[A] + with SeqOps[A, ListBuffer, ListBuffer[A]] + with StrictOptimizedSeqOps[A, ListBuffer, ListBuffer[A]] + with ReusableBuilder[A, immutable.List[A]] + with IterableFactoryDefaults[A, ListBuffer] + with DefaultSerializable { + @transient private[this] var mutationCount: Int = 0 + + private var first: List[A] = Nil + private var last0: ::[A] = null + private[this] var aliased = false + private[this] var len = 0 + + private type Predecessor[A0] = ::[A0] /*| Null*/ + + def iterator: Iterator[A] = new MutationTracker.CheckedIterator(first.iterator, mutationCount) + + override def iterableFactory: SeqFactory[ListBuffer] = ListBuffer + + @throws[IndexOutOfBoundsException] + def apply(i: Int) = first.apply(i) + + def length = len + override def knownSize = len + + override def isEmpty: Boolean = len == 0 + + private def copyElems(): Unit = { + val buf = new ListBuffer[A].freshFrom(this) + first = buf.first + last0 = buf.last0 + aliased = false + } + + // we only call this before mutating things, so it's + // a good place to track mutations for the iterator + private def ensureUnaliased(): Unit = { + mutationCount += 1 + if (aliased) copyElems() + } + + // Avoids copying where possible. + override def toList: List[A] = { + aliased = nonEmpty + // We've accumulated a number of mutations to `List.tail` by this stage. + // Make sure they are visible to threads that the client of this ListBuffer might be about + // to share this List with. + releaseFence() + first + } + + def result(): immutable.List[A] = toList + + /** Prepends the elements of this buffer to a given list + * + * @param xs the list to which elements are prepended + */ + def prependToList(xs: List[A]): List[A] = { + if (isEmpty) xs + else { + ensureUnaliased() + last0.next = xs + toList + } + } + + def clear(): Unit = { + mutationCount += 1 + first = Nil + len = 0 + last0 = null + aliased = false + } + + final def addOne(elem: A): this.type = { + ensureUnaliased() + val last1 = new ::[A](elem, Nil) + if (len == 0) first = last1 else last0.next = last1 + last0 = last1 + len += 1 + this + } + + // MUST only be called on fresh instances + private def freshFrom(xs: IterableOnce[A]^): this.type = { + val it = xs.iterator + if (it.hasNext) { + var len = 1 + var last0 = new ::[A](it.next(), Nil) + first = last0 + while (it.hasNext) { + val last1 = new ::[A](it.next(), Nil) + last0.next = last1 + last0 = last1 + len += 1 + } + // copy local vars into instance + this.len = len + this.last0 = last0 + } + this + } + + override final def addAll(xs: IterableOnce[A]^): this.type = { + val it = xs.iterator + if (it.hasNext) { + val fresh = new ListBuffer[A].freshFrom(it) + ensureUnaliased() + if (len == 0) first = fresh.first + else last0.next = fresh.first + last0 = fresh.last0 + len += fresh.length + } + this + } + + override def subtractOne(elem: A): this.type = { + ensureUnaliased() + if (isEmpty) {} + else if (first.head == elem) { + first = first.tail + reduceLengthBy(1) + } + else { + var cursor = first + while (!cursor.tail.isEmpty && cursor.tail.head != elem) { + cursor = cursor.tail + } + if (!cursor.tail.isEmpty) { + val z = cursor.asInstanceOf[::[A]] + if (z.next == last0) + last0 = z + z.next = cursor.tail.tail + reduceLengthBy(1) + } + } + this + } + + /** Reduce the length of the buffer, and null out last0 + * if this reduces the length to 0. + */ + private def reduceLengthBy(num: Int): Unit = { + len -= num + if (len <= 0) // obviously shouldn't be < 0, but still better not to leak + last0 = null + } + + private def locate(i: Int): Predecessor[A] = + if (i == 0) null + else if (i == len) last0 + else { + var j = i - 1 + var p = first + while (j > 0) { + p = p.tail + j -= 1 + } + p.asInstanceOf[Predecessor[A]] + } + + private def getNext(p: Predecessor[A]): List[A] = + if (p == null) first else p.next + + def update(idx: Int, elem: A): Unit = { + ensureUnaliased() + if (idx < 0 || idx >= len) throw new IndexOutOfBoundsException(s"$idx is out of bounds (min 0, max ${len-1})") + if (idx == 0) { + val newElem = new :: (elem, first.tail) + if (last0 eq first) { + last0 = newElem + } + first = newElem + } else { + // `p` can not be `null` because the case where `idx == 0` is handled above + val p = locate(idx) + val newElem = new :: (elem, p.tail.tail) + if (last0 eq p.tail) { + last0 = newElem + } + p.asInstanceOf[::[A]].next = newElem + } + } + + def insert(idx: Int, elem: A): Unit = { + ensureUnaliased() + if (idx < 0 || idx > len) throw new IndexOutOfBoundsException(s"$idx is out of bounds (min 0, max ${len-1})") + if (idx == len) addOne(elem) + else { + val p = locate(idx) + val nx = elem :: getNext(p) + if(p eq null) first = nx else p.next = nx + len += 1 + } + } + + def prepend(elem: A): this.type = { + insert(0, elem) + this + } + + // `fresh` must be a `ListBuffer` that only we have access to + private def insertAfter(prev: Predecessor[A], fresh: ListBuffer[A]): Unit = { + if (!fresh.isEmpty) { + val follow = getNext(prev) + if (prev eq null) first = fresh.first else prev.next = fresh.first + fresh.last0.next = follow + len += fresh.length + } + } + + def insertAll(idx: Int, elems: IterableOnce[A]^): Unit = { + if (idx < 0 || idx > len) throw new IndexOutOfBoundsException(s"$idx is out of bounds (min 0, max ${len-1})") + val it = elems.iterator + if (it.hasNext) { + if (idx == len) addAll(it) + else { + val fresh = new ListBuffer[A].freshFrom(it) + ensureUnaliased() + insertAfter(locate(idx), fresh) + } + } + } + + def remove(idx: Int): A = { + ensureUnaliased() + if (idx < 0 || idx >= len) throw new IndexOutOfBoundsException(s"$idx is out of bounds (min 0, max ${len-1})") + val p = locate(idx) + val nx = getNext(p) + if(p eq null) { + first = nx.tail + if(first.isEmpty) last0 = null + } else { + if(last0 eq nx) last0 = p + p.next = nx.tail + } + len -= 1 + nx.head + } + + def remove(idx: Int, count: Int): Unit = + if (count > 0) { + ensureUnaliased() + if (idx < 0 || idx + count > len) throw new IndexOutOfBoundsException(s"$idx to ${idx + count} is out of bounds (min 0, max ${len-1})") + removeAfter(locate(idx), count) + } else if (count < 0) { + throw new IllegalArgumentException("removing negative number of elements: " + count) + } + + private def removeAfter(prev: Predecessor[A], n: Int) = { + @tailrec def ahead(p: List[A], n: Int): List[A] = + if (n == 0) p else ahead(p.tail, n - 1) + val nx = ahead(getNext(prev), n) + if(prev eq null) first = nx else prev.next = nx + if(nx.isEmpty) last0 = prev + len -= n + } + + def mapInPlace(f: A => A): this.type = { + mutationCount += 1 + val buf = new ListBuffer[A] + for (elem <- this) buf += f(elem) + first = buf.first + last0 = buf.last0 + aliased = false // we just assigned from a new instance + this + } + + def flatMapInPlace(f: A => IterableOnce[A]^): this.type = { + mutationCount += 1 + var src = first + var dst: List[A] = null + last0 = null + len = 0 + while(!src.isEmpty) { + val it = f(src.head).iterator + while(it.hasNext) { + val v = new ::(it.next(), Nil) + if(dst eq null) dst = v else last0.next = v + last0 = v + len += 1 + } + src = src.tail + } + first = if(dst eq null) Nil else dst + aliased = false // we just rebuilt a fresh, unaliased instance + this + } + + def filterInPlace(p: A => Boolean): this.type = { + ensureUnaliased() + var prev: Predecessor[A] = null + var cur: List[A] = first + while (!cur.isEmpty) { + val follow = cur.tail + if (!p(cur.head)) { + if(prev eq null) first = follow + else prev.next = follow + len -= 1 + } else { + prev = cur.asInstanceOf[Predecessor[A]] + } + cur = follow + } + last0 = prev + this + } + + 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 + val it = patch.iterator + + val nonEmptyPatch = it.hasNext + val nonEmptyReplace = (_from < _len) && (_replaced > 0) + + // don't want to add a mutation or check aliasing (potentially expensive) + // if there's no patching to do + if (nonEmptyPatch || nonEmptyReplace) { + val fresh = new ListBuffer[A].freshFrom(it) + ensureUnaliased() + val i = math.min(_from, _len) + val n = math.min(_replaced, _len) + val p = locate(i) + removeAfter(p, math.min(n, _len - i)) + insertAfter(p, fresh) + } + this + } + + /** + * Selects the last element. + * + * Runs in constant time. + * + * @return The last element of this $coll. + * @throws NoSuchElementException If the $coll is empty. + */ + override def last: A = if (last0 eq null) throw new NoSuchElementException("last of empty ListBuffer") else last0.head + + /** + * Optionally selects the last element. + * + * Runs in constant time. + * + * @return the last element of this $coll$ if it is nonempty, `None` if it is empty. + */ + override def lastOption: Option[A] = if (last0 eq null) None else Some(last0.head) + + @nowarn("""cat=deprecation&origin=scala\.collection\.Iterable\.stringPrefix""") + override protected[this] def stringPrefix = "ListBuffer" + +} + +@SerialVersionUID(3L) +object ListBuffer extends StrictOptimizedSeqFactory[ListBuffer] { + + 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]) + + def empty[A]: ListBuffer[A] = new ListBuffer[A] +} diff --git a/tests/pos/stdlib/collection/mutable/MutationTracker.scala b/tests/pos/stdlib/collection/mutable/MutationTracker.scala new file mode 100644 index 000000000000..3e9b16540031 --- /dev/null +++ b/tests/pos/stdlib/collection/mutable/MutationTracker.scala @@ -0,0 +1,79 @@ +/* + * Scala (https://www.scala-lang.org) + * + * Copyright EPFL and Lightbend, Inc. + * + * 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 mutable + +import java.util.ConcurrentModificationException +import language.experimental.captureChecking + +/** + * Utilities to check that mutations to a client that tracks + * its mutations have not occurred since a given point. + * [[Iterator `Iterator`]]s that perform this check automatically + * during iteration can be created by wrapping an `Iterator` + * in a [[MutationTracker.CheckedIterator `CheckedIterator`]], + * or by manually using the [[MutationTracker.checkMutations() `checkMutations`]] + * and [[MutationTracker.checkMutationsForIteration() `checkMutationsForIteration`]] + * methods. + */ +private object MutationTracker { + + /** + * Checks whether or not the actual mutation count differs from + * the expected one, throwing an exception, if it does. + * + * @param expectedCount the expected mutation count + * @param actualCount the actual mutation count + * @param message the exception message in case of mutations + * @throws ConcurrentModificationException if the expected and actual + * mutation counts differ + */ + @throws[ConcurrentModificationException] + def checkMutations(expectedCount: Int, actualCount: Int, message: String): Unit = { + if (actualCount != expectedCount) throw new ConcurrentModificationException(message) + } + + /** + * Checks whether or not the actual mutation count differs from + * the expected one, throwing an exception, if it does. This method + * produces an exception message saying that it was called because a + * backing collection was mutated during iteration. + * + * @param expectedCount the expected mutation count + * @param actualCount the actual mutation count + * @throws ConcurrentModificationException if the expected and actual + * mutation counts differ + */ + @throws[ConcurrentModificationException] + @inline def checkMutationsForIteration(expectedCount: Int, actualCount: Int): Unit = + checkMutations(expectedCount, actualCount, "mutation occurred during iteration") + + /** + * An iterator wrapper that checks if the underlying collection has + * been mutated. + * + * @param underlying the underlying iterator + * @param mutationCount a by-name provider of the current mutation count + * @tparam A the type of the iterator's elements + */ + final class CheckedIterator[A](underlying: Iterator[A]^, mutationCount: => Int) extends AbstractIterator[A] { + private[this] val expectedCount = mutationCount + + def hasNext: Boolean = { + checkMutationsForIteration(expectedCount, mutationCount) + underlying.hasNext + } + def next(): A = underlying.next() + } +} diff --git a/tests/pos/stdlib/collection/mutable/Shrinkable.scala b/tests/pos/stdlib/collection/mutable/Shrinkable.scala new file mode 100644 index 000000000000..de2a24ecf01f --- /dev/null +++ b/tests/pos/stdlib/collection/mutable/Shrinkable.scala @@ -0,0 +1,80 @@ +/* + * Scala (https://www.scala-lang.org) + * + * Copyright EPFL and Lightbend, Inc. + * + * 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.mutable + +import scala.annotation.tailrec +import language.experimental.captureChecking + +/** This trait forms part of collections that can be reduced + * using a `-=` operator. + * + * @define coll shrinkable collection + * @define Coll `Shrinkable` + */ +trait Shrinkable[-A] { + + /** Removes a single element from this $coll. + * + * @param elem the element to remove. + * @return the $coll itself + */ + def subtractOne(elem: A): this.type + + /** Alias for `subtractOne` */ + @`inline` final def -= (elem: A): this.type = subtractOne(elem) + + /** Removes two or more elements from this $coll. + * + * @param elem1 the first element to remove. + * @param elem2 the second element to remove. + * @param elems the remaining elements to remove. + * @return the $coll itself + */ + @deprecated("Use `--=` aka `subtractAll` instead of varargs `-=`; infix operations with an operand of multiple args will be deprecated", "2.13.3") + def -= (elem1: A, elem2: A, elems: A*): this.type = { + this -= elem1 + this -= elem2 + this --= elems + } + + /** Removes all elements produced by an iterator from this $coll. + * + * @param xs the iterator producing the elements to remove. + * @return the $coll itself + */ + def subtractAll(xs: collection.IterableOnce[A]^): this.type = { + @tailrec def loop(xs: collection.LinearSeq[A]): Unit = { + if (xs.nonEmpty) { + subtractOne(xs.head) + loop(xs.tail) + } + } + if (xs.asInstanceOf[AnyRef] eq this) { // avoid mutating under our own iterator + xs match { + case xs: Clearable => xs.clear() + case xs => subtractAll(Buffer.from(xs)) + } + } else { + xs match { + case xs: collection.LinearSeq[A] => loop(xs) + case xs => xs.iterator.foreach(subtractOne) + } + } + this + } + + /** Alias for `subtractAll` */ + @`inline` final def --= (xs: collection.IterableOnce[A]^): this.type = subtractAll(xs) + +} diff --git a/tests/pos/stdlib/immutable_List.scala b/tests/pos/stdlib/immutable_List.scala new file mode 120000 index 000000000000..1d504cf2c0a6 --- /dev/null +++ b/tests/pos/stdlib/immutable_List.scala @@ -0,0 +1 @@ +collection/immutable/List.scala \ No newline at end of file diff --git a/tests/pos/stdlib/mutable_Buffer.scala b/tests/pos/stdlib/mutable_Buffer.scala new file mode 120000 index 000000000000..486734eda2e9 --- /dev/null +++ b/tests/pos/stdlib/mutable_Buffer.scala @@ -0,0 +1 @@ +collection/mutable/Buffer.scala \ No newline at end of file diff --git a/tests/pos/stdlib/mutable_Builder.scala b/tests/pos/stdlib/mutable_Builder.scala new file mode 120000 index 000000000000..a49f1beabbbd --- /dev/null +++ b/tests/pos/stdlib/mutable_Builder.scala @@ -0,0 +1 @@ +collection/mutable/Builder.scala \ No newline at end of file diff --git a/tests/pos/stdlib/mutable_Growable.scala b/tests/pos/stdlib/mutable_Growable.scala new file mode 120000 index 000000000000..6ca588bf99a6 --- /dev/null +++ b/tests/pos/stdlib/mutable_Growable.scala @@ -0,0 +1 @@ +collection/mutable/Growable.scala \ No newline at end of file diff --git a/tests/pos/stdlib/mutable_ListBuffer.scala b/tests/pos/stdlib/mutable_ListBuffer.scala new file mode 120000 index 000000000000..046900f34064 --- /dev/null +++ b/tests/pos/stdlib/mutable_ListBuffer.scala @@ -0,0 +1 @@ +collection/mutable/ListBuffer.scala \ No newline at end of file diff --git a/tests/pos/stdlib/mutable_MutationTracker.scala b/tests/pos/stdlib/mutable_MutationTracker.scala new file mode 120000 index 000000000000..61e7a79009c7 --- /dev/null +++ b/tests/pos/stdlib/mutable_MutationTracker.scala @@ -0,0 +1 @@ +collection/mutable/MutationTracker.scala \ No newline at end of file diff --git a/tests/pos/stdlib/mutable_Shrinkable.scala b/tests/pos/stdlib/mutable_Shrinkable.scala new file mode 120000 index 000000000000..5f1399ae5da8 --- /dev/null +++ b/tests/pos/stdlib/mutable_Shrinkable.scala @@ -0,0 +1 @@ +collection/mutable/Shrinkable.scala \ No newline at end of file From 7c642c2d032cab77e86b49cc296140c93890757d Mon Sep 17 00:00:00 2001 From: odersky Date: Fri, 21 Jul 2023 10:55:33 +0200 Subject: [PATCH 31/37] Add StringOps and StringBuilder to stdlib tests --- .../dotty/tools/dotc/cc/CheckCaptures.scala | 5 +- tests/pos/stdlib/StringOps.scala | 1 + tests/pos/stdlib/collection/StringOps.scala | 1649 +++++++++++++++++ .../stdlib/collection/mutable/Buffer.scala | 19 +- .../collection/mutable/StringBuilder.scala | 496 +++++ tests/pos/stdlib/mutable_StringBuilder.scala | 1 + 6 files changed, 2160 insertions(+), 11 deletions(-) create mode 120000 tests/pos/stdlib/StringOps.scala create mode 100644 tests/pos/stdlib/collection/StringOps.scala create mode 100644 tests/pos/stdlib/collection/mutable/StringBuilder.scala create mode 120000 tests/pos/stdlib/mutable_StringBuilder.scala diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 362604788056..8967558c3ab3 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -398,8 +398,9 @@ class CheckCaptures extends Recheck, SymTransformer: if meth == defn.Caps_unsafeAssumePure then val arg :: Nil = tree.args: @unchecked val argType0 = recheck(arg, pt.capturing(CaptureSet.universal)) - val argType = if argType0.captureSet.isAlwaysEmpty then argType0 - else argType0.widen.stripCapturing + val argType = + if argType0.captureSet.isAlwaysEmpty then argType0 + else argType0.widen.stripCapturing capt.println(i"rechecking $arg with ${pt.capturing(CaptureSet.universal)}: $argType") super.recheckFinish(argType, tree, pt) else if meth == defn.Caps_unsafeBox then diff --git a/tests/pos/stdlib/StringOps.scala b/tests/pos/stdlib/StringOps.scala new file mode 120000 index 000000000000..584ca610cd0d --- /dev/null +++ b/tests/pos/stdlib/StringOps.scala @@ -0,0 +1 @@ +collection/StringOps.scala \ No newline at end of file diff --git a/tests/pos/stdlib/collection/StringOps.scala b/tests/pos/stdlib/collection/StringOps.scala new file mode 100644 index 000000000000..f570531def98 --- /dev/null +++ b/tests/pos/stdlib/collection/StringOps.scala @@ -0,0 +1,1649 @@ +/* + * Scala (https://www.scala-lang.org) + * + * Copyright EPFL and Lightbend, Inc. + * + * Licensed under Apache License 2.0 + * (http://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package scala +package collection + +import java.lang.{StringBuilder => JStringBuilder} + +import scala.collection.Stepper.EfficientSplit +import scala.collection.convert.impl.{CharStringStepper, CodePointStringStepper} +import scala.collection.immutable.{ArraySeq, WrappedString} +import scala.collection.mutable.StringBuilder +import scala.math.{ScalaNumber, max, min} +import scala.reflect.ClassTag +import scala.util.matching.Regex +import language.experimental.captureChecking + +object StringOps { + // just statics for companion class. + private final val LF = 0x0A + private final val FF = 0x0C + private final val CR = 0x0D + private final val SU = 0x1A + + private class StringIterator(private[this] val s: String) extends AbstractIterator[Char] { + private[this] var pos = 0 + def hasNext: Boolean = pos < s.length + def next(): Char = { + if (pos >= s.length) Iterator.empty.next() + val r = s.charAt(pos) + pos += 1 + r + } + } + + private class ReverseIterator(private[this] val s: String) extends AbstractIterator[Char] { + private[this] var pos = s.length-1 + def hasNext: Boolean = pos >= 0 + def next(): Char = { + if (pos < 0) Iterator.empty.next() + val r = s.charAt(pos) + pos -= 1 + r + } + } + + private class GroupedIterator(s: String, groupSize: Int) extends AbstractIterator[String] { + private[this] var pos = 0 + def hasNext: Boolean = pos < s.length + def next(): String = { + if(pos >= s.length) Iterator.empty.next() + val r = s.slice(pos, pos+groupSize) + pos += groupSize + r + } + } + + /** A lazy filtered string. No filtering is applied until one of `foreach`, `map` or `flatMap` is called. */ + class WithFilter(p: Char => Boolean, s: String) { + + /** Apply `f` to each element for its side effects. + * Note: [U] parameter needed to help scalac's type inference. + */ + def foreach[U](f: Char => U): Unit = { + val len = s.length + var i = 0 + while(i < len) { + val x = s.charAt(i) + if(p(x)) f(x) + i += 1 + } + } + + /** Builds a new collection by applying a function to all chars of this filtered string. + * + * @param f the function to apply to each char. + * @return a new collection resulting from applying the given function + * `f` to each char of this string and collecting the results. + */ + def map[B](f: Char => B): immutable.IndexedSeq[B] = { + val len = s.length + val b = immutable.IndexedSeq.newBuilder[B] + b.sizeHint(len) + var i = 0 + while (i < len) { + val x = s.charAt(i) + if(p(x)) b.addOne(f(x)) + i += 1 + } + b.result() + } + + /** Builds a new string by applying a function to all chars of this filtered string. + * + * @param f the function to apply to each char. + * @return a new string resulting from applying the given function + * `f` to each char of this string and collecting the results. + */ + def map(f: Char => Char): String = { + val len = s.length + val sb = new JStringBuilder(len) + var i = 0 + while (i < len) { + val x = s.charAt(i) + if(p(x)) sb.append(f(x)) + i += 1 + } + sb.toString + } + + /** Builds a new collection by applying a function to all chars of this filtered string + * and using the elements of the resulting collections. + * + * @param f the function to apply to each char. + * @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] = { + val len = s.length + val b = immutable.IndexedSeq.newBuilder[B] + var i = 0 + while (i < len) { + val x = s.charAt(i) + if(p(x)) b.addAll(f(x)) + i += 1 + } + b.result() + } + + /** Builds a new string by applying a function to all chars of this filtered string + * and using the elements of the resulting Strings. + * + * @param f the function to apply to each char. + * @return a new string resulting from applying the given string-valued function + * `f` to each char of this string and concatenating the results. + */ + def flatMap(f: Char => String): String = { + val len = s.length + val sb = new JStringBuilder + var i = 0 + while (i < len) { + val x = s.charAt(i) + if(p(x)) sb.append(f(x)) + i += 1 + } + sb.toString + } + + /** Creates a new non-strict filter which combines this filter with the given predicate. */ + def withFilter(q: Char => Boolean): WithFilter^{p, q} = new WithFilter(a => p(a) && q(a), s) + } + + /** Avoid an allocation in [[collect]]. */ + private val fallback: Any => Any = _ => fallback +} + +/** Provides extension methods for strings. + * + * Some of these methods treat strings as a plain collection of [[Char]]s + * without any regard for Unicode handling. Unless the user takes Unicode + * handling in to account or makes sure the strings don't require such handling, + * these methods may result in unpaired or invalidly paired surrogate code + * units. + * + * @define unicodeunaware This method treats a string as a plain sequence of + * Char code units and makes no attempt to keep + * surrogate pairs or codepoint sequences together. + * The user is responsible for making sure such cases + * are handled correctly. Failing to do so may result in + * an invalid Unicode string. + */ +final class StringOps(private val s: String) extends AnyVal { + import StringOps._ + + @`inline` def view: StringView = new StringView(s) + + @`inline` def size: Int = s.length + + @`inline` def knownSize: Int = s.length + + /** Get the char at the specified index. */ + @`inline` def apply(i: Int): Char = s.charAt(i) + + def sizeCompare(otherSize: Int): Int = Integer.compare(s.length, otherSize) + + def lengthCompare(len: Int): Int = Integer.compare(s.length, len) + + def sizeIs: Int = s.length + + def lengthIs: Int = s.length + + /** Builds a new collection by applying a function to all chars of this string. + * + * @param f the function to apply to each char. + * @return a new collection resulting from applying the given function + * `f` to each char of this string and collecting the results. + */ + def map[B](f: Char => B): immutable.IndexedSeq[B] = { + val len = s.length + val dst = new Array[AnyRef](len) + var i = 0 + while (i < len) { + dst(i) = f(s charAt i).asInstanceOf[AnyRef] + i += 1 + } + new ArraySeq.ofRef(dst).asInstanceOf[immutable.IndexedSeq[B]] + } + + /** Builds a new string by applying a function to all chars of this string. + * + * @param f the function to apply to each char. + * @return a new string resulting from applying the given function + * `f` to each char of this string and collecting the results. + */ + def map(f: Char => Char): String = { + val len = s.length + val dst = new Array[Char](len) + var i = 0 + while (i < len) { + dst(i) = f(s charAt i) + i += 1 + } + new String(dst) + } + + /** Builds a new collection by applying a function to all chars of this string + * and using the elements of the resulting collections. + * + * @param f the function to apply to each char. + * @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] = { + val len = s.length + val b = immutable.IndexedSeq.newBuilder[B] + var i = 0 + while (i < len) { + b.addAll(f(s.charAt(i))) + i += 1 + } + b.result() + } + + /** Builds a new string by applying a function to all chars of this string + * and using the elements of the resulting strings. + * + * @param f the function to apply to each char. + * @return a new string resulting from applying the given string-valued function + * `f` to each char of this string and concatenating the results. + */ + def flatMap(f: Char => String): String = { + val len = s.length + val sb = new JStringBuilder + var i = 0 + while (i < len) { + sb append f(s.charAt(i)) + i += 1 + } + sb.toString + } + + /** Builds a new String by applying a partial function to all chars of this String + * on which the function is defined. + * + * @param pf the partial function which filters and maps the String. + * @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 = { + val fallback: Any => Any = StringOps.fallback + var i = 0 + val b = new StringBuilder + while (i < s.length) { + val v = pf.applyOrElse(s.charAt(i), fallback) + if (v.asInstanceOf[AnyRef] ne fallback) b.addOne(v.asInstanceOf[Char]) + i += 1 + } + b.result() + } + + /** Builds a new collection by applying a partial function to all chars of this String + * on which the function is defined. + * + * @param pf the partial function which filters and maps the String. + * @tparam B the element type of the returned collection. + * @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] = { + val fallback: Any => Any = StringOps.fallback + var i = 0 + val b = immutable.IndexedSeq.newBuilder[B] + while (i < s.length) { + val v = pf.applyOrElse(s.charAt(i), fallback) + if (v.asInstanceOf[AnyRef] ne fallback) b.addOne(v.asInstanceOf[B]) + i += 1 + } + b.result() + } + + /** Returns a new collection containing the chars from this string followed by the elements from the + * right hand operand. + * + * @param suffix the collection to append. + * @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] = { + val b = immutable.IndexedSeq.newBuilder[B] + val k = suffix.knownSize + b.sizeHint(s.length + (if(k >= 0) k else 16)) + b.addAll(new WrappedString(s)) + b.addAll(suffix) + b.result() + } + + /** Returns a new string containing the chars from this string followed by the chars from the + * right hand operand. + * + * @param suffix the collection to append. + * @return a new string which contains all chars + * of this string followed by all chars of `suffix`. + */ + 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) + for (ch <- suffix.iterator) sb.append(ch) + sb.toString + } + + /** Returns a new string containing the chars from this string followed by the chars from the + * right hand operand. + * + * @param suffix the string to append. + * @return a new string which contains all chars + * of this string followed by all chars of `suffix`. + */ + @`inline` def concat(suffix: String): String = s + suffix + + /** Alias for `concat` */ + @`inline` def ++[B >: Char](suffix: Iterable[B]^): immutable.IndexedSeq[B] = concat(suffix) + + /** Alias for `concat` */ + @`inline` def ++(suffix: IterableOnce[Char]^): String = concat(suffix) + + /** Alias for `concat` */ + def ++(xs: String): String = concat(xs) + + /** Returns a collection with an element appended until a given target length is reached. + * + * @param len the target length + * @param elem the padding value + * @return a collection consisting of + * this string followed by the minimal number of occurrences of `elem` so + * that the resulting collection has a length of at least `len`. + */ + def padTo[B >: Char](len: Int, elem: B): immutable.IndexedSeq[B] = { + val sLen = s.length + if (sLen >= len) new WrappedString(s) else { + val b = immutable.IndexedSeq.newBuilder[B] + b.sizeHint(len) + b.addAll(new WrappedString(s)) + var i = sLen + while (i < len) { + b.addOne(elem) + i += 1 + } + b.result() + } + } + + /** Returns a string with a char appended until a given target length is reached. + * + * @param len the target length + * @param elem the padding value + * @return a string consisting of + * this string followed by the minimal number of occurrences of `elem` so + * that the resulting string has a length of at least `len`. + */ + def padTo(len: Int, elem: Char): String = { + val sLen = s.length + if (sLen >= len) s else { + val sb = new JStringBuilder(len) + sb.append(s) + // With JDK 11, this can written as: + // sb.append(String.valueOf(elem).repeat(len - sLen)) + var i = sLen + while (i < len) { + sb.append(elem) + i += 1 + } + sb.toString + } + } + + /** A copy of the string with an element prepended */ + def prepended[B >: Char](elem: B): immutable.IndexedSeq[B] = { + val b = immutable.IndexedSeq.newBuilder[B] + b.sizeHint(s.length + 1) + b.addOne(elem) + b.addAll(new WrappedString(s)) + b.result() + } + + /** Alias for `prepended` */ + @`inline` def +: [B >: Char] (elem: B): immutable.IndexedSeq[B] = prepended(elem) + + /** A copy of the string with an char prepended */ + def prepended(c: Char): String = + new JStringBuilder(s.length + 1).append(c).append(s).toString + + /** Alias for `prepended` */ + @`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] = { + val b = immutable.IndexedSeq.newBuilder[B] + val k = prefix.knownSize + b.sizeHint(s.length + (if(k >= 0) k else 16)) + b.addAll(prefix) + b.addAll(new WrappedString(s)) + b.result() + } + + /** Alias for `prependedAll` */ + @`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 + + /** Alias for `prependedAll` */ + @`inline` def ++: (prefix: String): String = prependedAll(prefix) + + /** A copy of the string with an element appended */ + def appended[B >: Char](elem: B): immutable.IndexedSeq[B] = { + val b = immutable.IndexedSeq.newBuilder[B] + b.sizeHint(s.length + 1) + b.addAll(new WrappedString(s)) + b.addOne(elem) + b.result() + } + + /** Alias for `appended` */ + @`inline` def :+ [B >: Char](elem: B): immutable.IndexedSeq[B] = appended(elem) + + /** A copy of the string with an element appended */ + def appended(c: Char): String = + new JStringBuilder(s.length + 1).append(s).append(c).toString + + /** Alias for `appended` */ + @`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] = + concat(suffix) + + /** Alias for `appendedAll` */ + @`inline` def :++ [B >: Char](suffix: IterableOnce[B]^): immutable.IndexedSeq[B] = + concat(suffix) + + /** A copy of the string with another string appended */ + @`inline` def appendedAll(suffix: String): String = s + suffix + + /** Alias for `appendedAll` */ + @`inline` def :++ (suffix: String): String = s + suffix + + /** Produces a new collection where a slice of characters in this string is replaced by another collection. + * + * Patching at negative indices is the same as patching starting at 0. + * Patching at indices at or larger than the length of the original string appends the patch to the end. + * If more values are replaced than actually exist, the excess is ignored. + * + * @param from the index of the first replaced char + * @param other the replacement collection + * @param replaced the number of chars to drop in the original string + * @return a new collection consisting of all chars of this string + * 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] = { + val len = s.length + @`inline` def slc(off: Int, length: Int): WrappedString = + new WrappedString(s.substring(off, off+length)) + val b = immutable.IndexedSeq.newBuilder[B] + val k = other.knownSize + if(k >= 0) b.sizeHint(len + k - replaced) + val chunk1 = if(from > 0) min(from, len) else 0 + if(chunk1 > 0) b.addAll(slc(0, chunk1)) + b ++= other + val remaining = len - chunk1 - replaced + if(remaining > 0) b.addAll(slc(len - remaining, remaining)) + b.result() + } + + /** Produces a new collection where a slice of characters in this string is replaced by another collection. + * + * Patching at negative indices is the same as patching starting at 0. + * Patching at indices at or larger than the length of the original string appends the patch to the end. + * If more values are replaced than actually exist, the excess is ignored. + * + * @param from the index of the first replaced char + * @param other the replacement string + * @param replaced the number of chars to drop in the original string + * @return a new string consisting of all chars of this string + * except that `replaced` chars starting from `from` are replaced + * by `other`. + * @note $unicodeunaware + */ + 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. + * + * Patching at negative indices is the same as patching starting at 0. + * Patching at indices at or larger than the length of the original string appends the patch to the end. + * If more values are replaced than actually exist, the excess is ignored. + * + * @param from the index of the first replaced char + * @param other the replacement string + * @param replaced the number of chars to drop in the original string + * @return a new string consisting of all chars of this string + * except that `replaced` chars starting from `from` are replaced + * by `other`. + * @note $unicodeunaware + */ + def patch(from: Int, other: String, replaced: Int): String = { + val len = s.length + val sb = new JStringBuilder(len + other.size - replaced) + val chunk1 = if(from > 0) min(from, len) else 0 + if(chunk1 > 0) sb.append(s, 0, chunk1) + sb.append(other) + val remaining = len - chunk1 - replaced + if(remaining > 0) sb.append(s, len - remaining, len) + sb.toString + } + + /** A copy of this string with one single replaced element. + * @param index the position of the replacement + * @param elem the replacing element + * @return a new string which is a copy of this string with the element at position `index` replaced by `elem`. + * @throws IndexOutOfBoundsException if `index` does not satisfy `0 <= index < length`. + * @note $unicodeunaware + */ + def updated(index: Int, elem: Char): String = { + val sb = new JStringBuilder(s.length).append(s) + sb.setCharAt(index, elem) + sb.toString + } + + /** Tests whether this string contains the given character. + * + * @param elem the character to test. + * @return `true` if this string has an element that is equal (as + * determined by `==`) to `elem`, `false` otherwise. + */ + def contains(elem: Char): Boolean = s.indexOf(elem) >= 0 + + /** Displays all elements of this string in a string using start, end, and + * separator strings. + * + * @param start the starting string. + * @param sep the separator string. + * @param end the ending string. + * @return The resulting string + * begins with the string `start` and ends with the string + * `end`. Inside, the string chars of this string are separated by + * the string `sep`. + * @note $unicodeunaware + */ + final def mkString(start: String, sep: String, end: String): String = + addString(new StringBuilder(), start, sep, end).toString + + /** Displays all elements of this string in a string using a separator string. + * + * @param sep the separator string. + * @return In the resulting string + * the chars of this string are separated by the string `sep`. + * @note $unicodeunaware + */ + @inline final def mkString(sep: String): String = + if (sep.isEmpty || s.length < 2) s + else mkString("", sep, "") + + /** Returns this string */ + @inline final def mkString: String = s + + /** Appends this string to a string builder. */ + @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 = + 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 = { + val jsb = b.underlying + if (start.length != 0) jsb.append(start) + val len = s.length + if (len != 0) { + if (sep.isEmpty) jsb.append(s) + else { + jsb.ensureCapacity(jsb.length + len + end.length + (len - 1) * sep.length) + jsb.append(s.charAt(0)) + var i = 1 + while (i < len) { + jsb.append(sep) + jsb.append(s.charAt(i)) + i += 1 + } + } + } + if (end.length != 0) jsb.append(end) + b + } + + /** Selects an interval of elements. The returned string is made up + * of all elements `x` which satisfy the invariant: + * {{{ + * from <= indexOf(x) < until + * }}} + * + * @param from the lowest index to include from this string. + * @param until the lowest index to EXCLUDE from this string. + * @return a string containing the elements greater than or equal to + * index `from` extending up to (but not including) index `until` + * of this string. + * @note $unicodeunaware + */ + def slice(from: Int, until: Int): String = { + val start = from max 0 + val end = until min s.length + + if (start >= end) "" + else s.substring(start, end) + } + + // Note: String.repeat is added in JDK 11. + /** Return the current string concatenated `n` times. + */ + def *(n: Int): String = { + if (n <= 0) { + "" + } else { + val sb = new JStringBuilder(s.length * n) + var i = 0 + while (i < n) { + sb.append(s) + i += 1 + } + sb.toString + } + } + + @`inline` private[this] def isLineBreak(c: Char) = c == CR || c == LF + @`inline` private[this] def isLineBreak2(c0: Char, c: Char) = c0 == CR && c == LF + + /** Strip the trailing line separator from this string if there is one. + * The line separator is taken as `"\n"`, `"\r"`, or `"\r\n"`. + */ + def stripLineEnd: String = + if (s.isEmpty) s + else { + var i = s.length - 1 + val last = apply(i) + if (!isLineBreak(last)) s + else { + if (i > 0 && isLineBreak2(apply(i - 1), last)) i -= 1 + s.substring(0, i) + } + } + + /** Return an iterator of all lines embedded in this string, + * including trailing line separator characters. + * + * The empty string yields an empty iterator. + */ + def linesWithSeparators: Iterator[String] = linesSeparated(stripped = false) + + /** Lines in this string, where a line is terminated by + * `"\n"`, `"\r"`, `"\r\n"`, or the end of the string. + * A line may be empty. Line terminators are removed. + */ + def linesIterator: Iterator[String] = linesSeparated(stripped = true) + + // if `stripped`, exclude the line separators + private def linesSeparated(stripped: Boolean): Iterator[String] = new AbstractIterator[String] { + def hasNext: Boolean = !done + def next(): String = if (done) Iterator.empty.next() else advance() + + private[this] val len = s.length + private[this] var index = 0 + @`inline` private def done = index >= len + private def advance(): String = { + val start = index + while (!done && !isLineBreak(apply(index))) index += 1 + var end = index + if (!done) { + val c = apply(index) + index += 1 + if (!done && isLineBreak2(c, apply(index))) index += 1 + if (!stripped) end = index + } + s.substring(start, end) + } + } + + /** Return all lines in this string in an iterator, excluding trailing line + * end characters; i.e., apply `.stripLineEnd` to all lines + * returned by `linesWithSeparators`. + */ + @deprecated("Use `linesIterator`, because JDK 11 adds a `lines` method on String", "2.13.0") + def lines: Iterator[String] = linesIterator + + /** Returns this string with first character converted to upper case. + * If the first character of the string is capitalized, it is returned unchanged. + * This method does not convert characters outside the Basic Multilingual Plane (BMP). + */ + def capitalize: String = + if (s == null || s.length == 0 || !s.charAt(0).isLower) s + else updated(0, s.charAt(0).toUpper) + + /** Returns this string with the given `prefix` stripped. If this string does not + * start with `prefix`, it is returned unchanged. + */ + def stripPrefix(prefix: String) = + if (s startsWith prefix) s.substring(prefix.length) + else s + + /** Returns this string with the given `suffix` stripped. If this string does not + * end with `suffix`, it is returned unchanged. + */ + def stripSuffix(suffix: String) = + if (s endsWith suffix) s.substring(0, s.length - suffix.length) + else s + + /** Replace all literal occurrences of `literal` with the literal string `replacement`. + * This method is equivalent to [[java.lang.String#replace(CharSequence,CharSequence)]]. + * + * @param literal the string which should be replaced everywhere it occurs + * @param replacement the replacement string + * @return the resulting string + */ + @deprecated("Use `s.replace` as an exact replacement", "2.13.2") + def replaceAllLiterally(literal: String, replacement: String): String = s.replace(literal, replacement) + + /** For every line in this string: + * + * Strip a leading prefix consisting of blanks or control characters + * followed by `marginChar` from the line. + */ + def stripMargin(marginChar: Char): String = { + val sb = new JStringBuilder(s.length) + for (line <- linesWithSeparators) { + val len = line.length + var index = 0 + while (index < len && line.charAt(index) <= ' ') index += 1 + val stripped = + if (index < len && line.charAt(index) == marginChar) line.substring(index + 1) + else line + sb.append(stripped) + } + sb.toString + } + + /** For every line in this string: + * + * Strip a leading prefix consisting of blanks or control characters + * followed by `|` from the line. + */ + def stripMargin: String = stripMargin('|') + + private[this] def escape(ch: Char): String = if ( + (ch >= 'a') && (ch <= 'z') || + (ch >= 'A') && (ch <= 'Z') || + (ch >= '0' && ch <= '9')) ch.toString + else "\\" + ch + + /** Split this string around the separator character + * + * If this string is the empty string, returns an array of strings + * that contains a single empty string. + * + * If this string is not the empty string, returns an array containing + * the substrings terminated by the start of the string, the end of the + * string or the separator character, excluding empty trailing substrings + * + * If the separator character is a surrogate character, only split on + * matching surrogate characters if they are not part of a surrogate pair + * + * The behaviour follows, and is implemented in terms of String.split(re: String) + * + * + * @example {{{ + * "a.b".split('.') //returns Array("a", "b") + * + * //splitting the empty string always returns the array with a single + * //empty string + * "".split('.') //returns Array("") + * + * //only trailing empty substrings are removed + * "a.".split('.') //returns Array("a") + * ".a.".split('.') //returns Array("", "a") + * "..a..".split('.') //returns Array("", "", "a") + * + * //all parts are empty and trailing + * ".".split('.') //returns Array() + * "..".split('.') //returns Array() + * + * //surrogate pairs + * val high = 0xD852.toChar + * val low = 0xDF62.toChar + * val highstring = high.toString + * val lowstring = low.toString + * + * //well-formed surrogate pairs are not split + * val highlow = highstring + lowstring + * highlow.split(high) //returns Array(highlow) + * + * //bare surrogate characters are split + * val bare = "_" + highstring + "_" + * bare.split(high) //returns Array("_", "_") + * + * }}} + * + * @param separator the character used as a delimiter + */ + def split(separator: Char): Array[String] = s.split(escape(separator)) + + @throws(classOf[java.util.regex.PatternSyntaxException]) + def split(separators: Array[Char]): Array[String] = { + val re = separators.foldLeft("[")(_+escape(_)) + "]" + s.split(re) + } + + /** You can follow a string with `.r`, turning it into a `Regex`. E.g. + * + * `"""A\w*""".r` is the regular expression for ASCII-only identifiers starting with `A`. + * + * `"""(?\d\d)-(?\d\d)-(?\d\d\d\d)""".r` matches dates + * and provides its subcomponents through groups named "month", "day" and + * "year". + */ + def r: Regex = new Regex(s) + + /** You can follow a string with `.r(g1, ... , gn)`, turning it into a `Regex`, + * with group names g1 through gn. + * + * `"""(\d\d)-(\d\d)-(\d\d\d\d)""".r("month", "day", "year")` matches dates + * and provides its subcomponents through groups named "month", "day" and + * "year". + * + * @param groupNames The names of the groups in the pattern, in the order they appear. + */ + @deprecated("use inline group names like (?X) instead", "2.13.7") + def r(groupNames: String*): Regex = new Regex(s, groupNames: _*) + + /** + * @throws java.lang.IllegalArgumentException If the string does not contain a parsable `Boolean`. + */ + def toBoolean: Boolean = toBooleanImpl(s) + + /** + * Try to parse as a `Boolean` + * @return `Some(true)` if the string is "true" case insensitive, + * `Some(false)` if the string is "false" case insensitive, + * and `None` if the string is anything else + * @throws java.lang.NullPointerException if the string is `null` + */ + def toBooleanOption: Option[Boolean] = StringParsers.parseBool(s) + + /** + * Parse as a `Byte` (string must contain only decimal digits and optional leading `-` or `+`). + * @throws java.lang.NumberFormatException If the string does not contain a parsable `Byte`. + */ + def toByte: Byte = java.lang.Byte.parseByte(s) + + /** + * Try to parse as a `Byte` + * @return `Some(value)` if the string contains a valid byte value, otherwise `None` + * @throws java.lang.NullPointerException if the string is `null` + */ + def toByteOption: Option[Byte] = StringParsers.parseByte(s) + + /** + * Parse as a `Short` (string must contain only decimal digits and optional leading `-` or `+`). + * @throws java.lang.NumberFormatException If the string does not contain a parsable `Short`. + */ + def toShort: Short = java.lang.Short.parseShort(s) + + /** + * Try to parse as a `Short` + * @return `Some(value)` if the string contains a valid short value, otherwise `None` + * @throws java.lang.NullPointerException if the string is `null` + */ + def toShortOption: Option[Short] = StringParsers.parseShort(s) + + /** + * Parse as an `Int` (string must contain only decimal digits and optional leading `-` or `+`). + * @throws java.lang.NumberFormatException If the string does not contain a parsable `Int`. + */ + def toInt: Int = java.lang.Integer.parseInt(s) + + /** + * Try to parse as an `Int` + * @return `Some(value)` if the string contains a valid Int value, otherwise `None` + * @throws java.lang.NullPointerException if the string is `null` + */ + def toIntOption: Option[Int] = StringParsers.parseInt(s) + + /** + * Parse as a `Long` (string must contain only decimal digits and optional leading `-` or `+`). + * @throws java.lang.NumberFormatException If the string does not contain a parsable `Long`. + */ + def toLong: Long = java.lang.Long.parseLong(s) + + /** + * Try to parse as a `Long` + * @return `Some(value)` if the string contains a valid long value, otherwise `None` + * @throws java.lang.NullPointerException if the string is `null` + */ + def toLongOption: Option[Long] = StringParsers.parseLong(s) + + /** + * Parse as a `Float` (surrounding whitespace is removed with a `trim`). + * @throws java.lang.NumberFormatException If the string does not contain a parsable `Float`. + * @throws java.lang.NullPointerException If the string is null. + */ + def toFloat: Float = java.lang.Float.parseFloat(s) + + /** + * Try to parse as a `Float` + * @return `Some(value)` if the string is a parsable `Float`, `None` otherwise + * @throws java.lang.NullPointerException If the string is null + */ + def toFloatOption: Option[Float] = StringParsers.parseFloat(s) + + /** + * Parse as a `Double` (surrounding whitespace is removed with a `trim`). + * @throws java.lang.NumberFormatException If the string does not contain a parsable `Double`. + * @throws java.lang.NullPointerException If the string is null. + */ + def toDouble: Double = java.lang.Double.parseDouble(s) + + /** + * Try to parse as a `Double` + * @return `Some(value)` if the string is a parsable `Double`, `None` otherwise + * @throws java.lang.NullPointerException If the string is null + */ + def toDoubleOption: Option[Double] = StringParsers.parseDouble(s) + + private[this] def toBooleanImpl(s: String): Boolean = + if (s == null) throw new IllegalArgumentException("For input string: \"null\"") + else if (s.equalsIgnoreCase("true")) true + else if (s.equalsIgnoreCase("false")) false + else throw new IllegalArgumentException("For input string: \""+s+"\"") + + def toArray[B >: Char](implicit tag: ClassTag[B]): Array[B] = + if (tag == ClassTag.Char) s.toCharArray.asInstanceOf[Array[B]] + else new WrappedString(s).toArray[B] + + private[this] def unwrapArg(arg: Any): AnyRef = arg match { + case x: ScalaNumber => x.underlying + case x => x.asInstanceOf[AnyRef] + } + + /** Uses the underlying string as a pattern (in a fashion similar to + * printf in C), and uses the supplied arguments to fill in the + * holes. + * + * The interpretation of the formatting patterns is described in + * [[java.util.Formatter]], with the addition that + * classes deriving from `ScalaNumber` (such as [[scala.BigInt]] and + * [[scala.BigDecimal]]) are unwrapped to pass a type which `Formatter` + * understands. + * + * @param args the arguments used to instantiating the pattern. + * @throws java.lang.IllegalArgumentException + */ + def format(args : Any*): String = + java.lang.String.format(s, args map unwrapArg: _*) + + /** Like `format(args*)` but takes an initial `Locale` parameter + * which influences formatting as in `java.lang.String`'s format. + * + * The interpretation of the formatting patterns is described in + * [[java.util.Formatter]], with the addition that + * classes deriving from `ScalaNumber` (such as `scala.BigInt` and + * `scala.BigDecimal`) are unwrapped to pass a type which `Formatter` + * understands. + * + * @param l an instance of `java.util.Locale` + * @param args the arguments used to instantiating the pattern. + * @throws java.lang.IllegalArgumentException + */ + def formatLocal(l: java.util.Locale, args: Any*): String = + java.lang.String.format(l, s, args map unwrapArg: _*) + + def compare(that: String): Int = s.compareTo(that) + + /** Returns true if `this` is less than `that` */ + def < (that: String): Boolean = compare(that) < 0 + + /** Returns true if `this` is greater than `that`. */ + def > (that: String): Boolean = compare(that) > 0 + + /** Returns true if `this` is less than or equal to `that`. */ + def <= (that: String): Boolean = compare(that) <= 0 + + /** Returns true if `this` is greater than or equal to `that`. */ + def >= (that: String): Boolean = compare(that) >= 0 + + /** Counts the number of chars in this string which satisfy a predicate */ + def count(p: (Char) => Boolean): Int = { + var i, res = 0 + val len = s.length + while(i < len) { + if(p(s.charAt(i))) res += 1 + i += 1 + } + res + } + + /** Apply `f` to each element for its side effects. + * Note: [U] parameter needed to help scalac's type inference. + */ + def foreach[U](f: Char => U): Unit = { + val len = s.length + var i = 0 + while(i < len) { + f(s.charAt(i)) + i += 1 + } + } + + /** Tests whether a predicate holds for all chars of this string. + * + * @param p the predicate used to test elements. + * @return `true` if this string is empty or the given predicate `p` + * holds for all chars of this string, otherwise `false`. + */ + def forall(@deprecatedName("f", "2.13.3") p: Char => Boolean): Boolean = { + var i = 0 + val len = s.length + while(i < len) { + if(!p(s.charAt(i))) return false + i += 1 + } + true + } + + /** Applies a binary operator to a start value and all chars of this string, + * going left to right. + * + * @param z the start value. + * @param op the binary operator. + * @tparam B the result type of the binary operator. + * @return the result of inserting `op` between consecutive chars of this string, + * going left to right with the start value `z` on the left: + * {{{ + * op(...op(z, x_1), x_2, ..., x_n) + * }}} + * where `x,,1,,, ..., x,,n,,` are the chars of this string. + * Returns `z` if this string is empty. + */ + def foldLeft[B](z: B)(op: (B, Char) => B): B = { + var v = z + var i = 0 + val len = s.length + while(i < len) { + v = op(v, s.charAt(i)) + i += 1 + } + v + } + + /** Applies a binary operator to all chars of this string and a start value, + * going right to left. + * + * @param z the start value. + * @param op the binary operator. + * @tparam B the result type of the binary operator. + * @return the result of inserting `op` between consecutive chars of this string, + * going right to left with the start value `z` on the right: + * {{{ + * op(x_1, op(x_2, ... op(x_n, z)...)) + * }}} + * where `x,,1,,, ..., x,,n,,` are the chars of this string. + * Returns `z` if this string is empty. + */ + def foldRight[B](z: B)(op: (Char, B) => B): B = { + var v = z + var i = s.length - 1 + while(i >= 0) { + v = op(s.charAt(i), v) + i -= 1 + } + v + } + + /** Folds the chars of this string using the specified associative binary operator. + * + * @tparam A1 a type parameter for the binary operator, a supertype of Char. + * @param z a neutral element for the fold operation; may be added to the result + * an arbitrary number of times, and must not change the result (e.g., `Nil` for list concatenation, + * 0 for addition, or 1 for multiplication). + * @param op a binary operator that must be associative. + * @return the result of applying the fold operator `op` between all the chars and `z`, or `z` if this string is empty. + */ + @`inline` def fold[A1 >: Char](z: A1)(op: (A1, A1) => A1): A1 = foldLeft(z)(op) + + /** Selects the first char of this string. + * @return the first char of this string. + * @throws NoSuchElementException if the string is empty. + */ + def head: Char = if(s.isEmpty) throw new NoSuchElementException("head of empty String") else s.charAt(0) + + /** Optionally selects the first char. + * @return the first char of this string if it is nonempty, + * `None` if it is empty. + */ + def headOption: Option[Char] = + if(s.isEmpty) None else Some(s.charAt(0)) + + /** Selects the last char of this string. + * @return the last char of this string. + * @throws NoSuchElementException if the string is empty. + */ + def last: Char = if(s.isEmpty) throw new NoSuchElementException("last of empty String") else s.charAt(s.length-1) + + /** Optionally selects the last char. + * @return the last char of this string if it is nonempty, + * `None` if it is empty. + */ + def lastOption: Option[Char] = + if(s.isEmpty) None else Some(s.charAt(s.length-1)) + + /** Produces the range of all indices of this string. + * + * @return a `Range` value from `0` to one less than the length of this string. + */ + def indices: Range = Range(0, s.length) + + /** Iterator can be used only once */ + def iterator: Iterator[Char] = new StringIterator(s) + + /** Stepper can be used with Java 8 Streams. This method is equivalent to a call to + * [[charStepper]]. See also [[codePointStepper]]. + */ + @`inline` def stepper: IntStepper with EfficientSplit = charStepper + + /** Steps over characters in this string. Values are packed in `Int` for efficiency + * and compatibility with Java 8 Streams which have an efficient specialization for `Int`. + */ + @`inline` def charStepper: IntStepper with EfficientSplit = new CharStringStepper(s, 0, s.length) + + /** Steps over code points in this string. + */ + @`inline` def codePointStepper: IntStepper with EfficientSplit = new CodePointStringStepper(s, 0, s.length) + + /** Tests whether the string is not empty. */ + @`inline` def nonEmpty: Boolean = !s.isEmpty + + /** Returns new sequence with elements in reversed order. + * @note $unicodeunaware + */ + def reverse: String = new JStringBuilder(s).reverse().toString + + /** An iterator yielding chars in reversed order. + * + * Note: `xs.reverseIterator` is the same as `xs.reverse.iterator` but implemented more efficiently. + * + * @return an iterator yielding the chars of this string in reversed order + */ + def reverseIterator: Iterator[Char] = new ReverseIterator(s) + + /** Creates a non-strict filter of this string. + * + * @note the difference between `c filter p` and `c withFilter p` is that + * the former creates a new string, whereas the latter only + * restricts the domain of subsequent `map`, `flatMap`, `foreach`, + * and `withFilter` operations. + * + * @param p the predicate used to test elements. + * @return an object of class `stringOps.WithFilter`, which supports + * `map`, `flatMap`, `foreach`, and `withFilter` operations. + * All these operations apply to those chars of this string + * which satisfy the predicate `p`. + */ + def withFilter(p: Char => Boolean): StringOps.WithFilter^{p} = new StringOps.WithFilter(p, s) + + /** The rest of the string without its first char. + * @note $unicodeunaware + */ + def tail: String = slice(1, s.length) + + /** The initial part of the string without its last char. + * @note $unicodeunaware + */ + def init: String = slice(0, s.length-1) + + /** A string containing the first `n` chars of this string. + * @note $unicodeunaware + */ + def take(n: Int): String = slice(0, min(n, s.length)) + + /** The rest of the string without its `n` first chars. + * @note $unicodeunaware + */ + def drop(n: Int): String = slice(min(n, s.length), s.length) + + /** A string containing the last `n` chars of this string. + * @note $unicodeunaware + */ + def takeRight(n: Int): String = drop(s.length - max(n, 0)) + + /** The rest of the string without its `n` last chars. + * @note $unicodeunaware + */ + def dropRight(n: Int): String = take(s.length - max(n, 0)) + + /** Iterates over the tails of this string. The first value will be this + * string and the final one will be an empty string, with the intervening + * values the results of successive applications of `tail`. + * + * @return an iterator over all the tails of this string + * @note $unicodeunaware + */ + def tails: Iterator[String] = iterateUntilEmpty(_.tail) + + /** Iterates over the inits of this string. The first value will be this + * string and the final one will be an empty string, with the intervening + * values the results of successive applications of `init`. + * + * @return an iterator over all the inits of this string + * @note $unicodeunaware + */ + def inits: Iterator[String] = iterateUntilEmpty(_.init) + + // A helper for tails and inits. + 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. */ + def filter(pred: Char => Boolean): String = { + val len = s.length + val sb = new JStringBuilder(len) + var i = 0 + while (i < len) { + val x = s.charAt(i) + if(pred(x)) sb.append(x) + i += 1 + } + if(len == sb.length()) s else sb.toString + } + + /** Selects all chars of this string which do not satisfy a predicate. */ + @`inline` def filterNot(pred: Char => Boolean): String = filter(c => !pred(c)) + + /** Copy chars of this string to an array. + * Fills the given array `xs` starting at index 0. + * Copying will stop once either the entire string has been copied + * or the end of the array is reached + * + * @param xs the array to fill. + */ + @`inline` def copyToArray(xs: Array[Char]): Int = + copyToArray(xs, 0, Int.MaxValue) + + /** Copy chars of this string to an array. + * Fills the given array `xs` starting at index `start`. + * Copying will stop once either the entire string has been copied + * or the end of the array is reached + * + * @param xs the array to fill. + * @param start the starting index. + */ + @`inline` def copyToArray(xs: Array[Char], start: Int): Int = + copyToArray(xs, start, Int.MaxValue) + + /** Copy chars of this string to an array. + * Fills the given array `xs` starting at index `start` with at most `len` chars. + * Copying will stop once either the entire string has been copied, + * or the end of the array is reached or `len` chars have been copied. + * + * @param xs the array to fill. + * @param start the starting index. + * @param len the maximal number of elements to copy. + */ + def copyToArray(xs: Array[Char], start: Int, len: Int): Int = { + val copied = IterableOnce.elemsToCopyToArray(s.length, xs.length, start, len) + if (copied > 0) { + s.getChars(0, copied, xs, start) + } + copied + } + + /** Finds index of the first char satisfying some predicate after or at some start index. + * + * @param p the predicate used to test elements. + * @param from the start index + * @return the index `>= from` of the first element of this string that satisfies the predicate `p`, + * or `-1`, if none exists. + */ + def indexWhere(p: Char => Boolean, from: Int = 0): Int = { + val len = s.length + var i = from + while(i < len) { + if(p(s.charAt(i))) return i + i += 1 + } + -1 + } + + /** Finds index of the last char satisfying some predicate before or at some end index. + * + * @param p the predicate used to test elements. + * @param end the end index + * @return the index `<= end` of the last element of this string that satisfies the predicate `p`, + * or `-1`, if none exists. + */ + def lastIndexWhere(p: Char => Boolean, end: Int = Int.MaxValue): Int = { + val len = s.length + var i = min(end, len-1) + while(i >= 0) { + if(p(s.charAt(i))) return i + i -= 1 + } + -1 + } + + /** Tests whether a predicate holds for at least one char of this string. */ + def exists(p: Char => Boolean): Boolean = indexWhere(p) != -1 + + /** Finds the first char of the string satisfying a predicate, if any. + * + * @param p the predicate used to test elements. + * @return an option value containing the first element in the string + * that satisfies `p`, or `None` if none exists. + */ + def find(p: Char => Boolean): Option[Char] = indexWhere(p) match { + case -1 => None + case i => Some(s.charAt(i)) + } + + /** Drops longest prefix of chars that satisfy a predicate. + * + * @param p The predicate used to test elements. + * @return the longest suffix of this string whose first element + * does not satisfy the predicate `p`. + */ + def dropWhile(p: Char => Boolean): String = indexWhere(c => !p(c)) match { + case -1 => "" + case i => s.substring(i) + } + + /** Takes longest prefix of chars that satisfy a predicate. */ + def takeWhile(p: Char => Boolean): String = indexWhere(c => !p(c)) match { + case -1 => s + case i => s.substring(0, i) + } + + /** Splits this string into two at a given position. + * Note: `c splitAt n` is equivalent to `(c take n, c drop n)`. + * + * @param n the position at which to split. + * @return a pair of strings consisting of the first `n` + * chars of this string, and the other chars. + * @note $unicodeunaware + */ + def splitAt(n: Int): (String, String) = (take(n), drop(n)) + + /** Splits this string into a prefix/suffix pair according to a predicate. + * + * Note: `c span p` is equivalent to (but more efficient than) + * `(c takeWhile p, c dropWhile p)`, provided the evaluation of the + * predicate `p` does not cause any side-effects. + * + * @param p the test predicate + * @return a pair consisting of the longest prefix of this string whose + * chars all satisfy `p`, and the rest of this string. + */ + def span(p: Char => Boolean): (String, String) = indexWhere(c => !p(c)) match { + case -1 => (s, "") + case i => (s.substring(0, i), s.substring(i)) + } + + /** Partitions elements in fixed size strings. + * @see [[scala.collection.Iterator]], method `grouped` + * + * @param size the number of elements per group + * @return An iterator producing strings of size `size`, except the + * last will be less than size `size` if the elements don't divide evenly. + * @note $unicodeunaware + */ + def grouped(size: Int): Iterator[String] = new StringOps.GroupedIterator(s, size) + + /** A pair of, first, all chars that satisfy predicate `p` and, second, all chars that do not. */ + def partition(p: Char => Boolean): (String, String) = { + val res1, res2 = new JStringBuilder + var i = 0 + val len = s.length + while(i < len) { + val x = s.charAt(i) + (if(p(x)) res1 else res2).append(x) + i += 1 + } + (res1.toString, res2.toString) + } + + /** Applies a function `f` to each character of the string and returns a pair of strings: the first one + * made of those characters returned by `f` that were wrapped in [[scala.util.Left]], and the second + * one made of those wrapped in [[scala.util.Right]]. + * + * Example: + * {{{ + * val xs = "1one2two3three" partitionMap { c => + * if (c > 'a') Left(c) else Right(c) + * } + * // xs == ("onetwothree", "123") + * }}} + * + * @param f the 'split function' mapping the elements of this string to an [[scala.util.Either]] + * + * @return a pair of strings: the first one made of those characters returned by `f` that were wrapped in [[scala.util.Left]], + * and the second one made of those wrapped in [[scala.util.Right]]. + */ + def partitionMap(f: Char => Either[Char,Char]): (String, String) = { + val res1, res2 = new JStringBuilder + var i = 0 + val len = s.length + while(i < len) { + f(s.charAt(i)) match { + case Left(c) => res1.append(c) + case Right(c) => res2.append(c) + } + i += 1 + } + (res1.toString, res2.toString) + } + + /** Analogous to `zip` except that the elements in each collection are not consumed until a strict operation is + * invoked on the returned `LazyZip2` decorator. + * + * Calls to `lazyZip` can be chained to support higher arities (up to 4) without incurring the expense of + * constructing and deconstructing intermediary tuples. + * + * {{{ + * val xs = List(1, 2, 3) + * val res = (xs lazyZip xs lazyZip xs lazyZip xs).map((a, b, c, d) => a + b + c + d) + * // res == List(4, 8, 12) + * }}} + * + * @param that the iterable providing the second element of each eventual pair + * @tparam B the type of the second element in each eventual pair + * @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]^{that} = new LazyZip2(s, new WrappedString(s), that) + + + /* ************************************************************************************************************ + The remaining methods are provided for completeness but they delegate to WrappedString implementations which + may not provide the best possible performance. We need them in `StringOps` because their return type + mentions `C` (which is `String` in `StringOps` and `WrappedString` in `WrappedString`). + ************************************************************************************************************ */ + + + /** Computes the multiset difference between this string and another sequence. + * + * @param that the sequence of chars to remove + * @return a new string which contains all chars of this string + * except some of occurrences of elements that also appear in `that`. + * If an element value `x` appears + * ''n'' times in `that`, then the first ''n'' occurrences of `x` will not form + * part of the result, but any following occurrences will. + * @note $unicodeunaware + */ + def diff[B >: Char](that: Seq[B]): String = new WrappedString(s).diff(that).unwrap + + /** Computes the multiset intersection between this string and another sequence. + * + * @param that the sequence of chars to intersect with. + * @return a new string which contains all chars of this string + * which also appear in `that`. + * If an element value `x` appears + * ''n'' times in `that`, then the first ''n'' occurrences of `x` will be retained + * in the result, but any following occurrences will be omitted. + * @note $unicodeunaware + */ + def intersect[B >: Char](that: Seq[B]): String = new WrappedString(s).intersect(that).unwrap + + /** Selects all distinct chars of this string ignoring the duplicates. + * + * @note $unicodeunaware + */ + def distinct: String = new WrappedString(s).distinct.unwrap + + /** Selects all distinct chars of this string ignoring the duplicates as determined by `==` after applying + * the transforming function `f`. + * + * @param f The transforming function whose result is used to determine the uniqueness of each element + * @tparam B the type of the elements after being transformed by `f` + * @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 + + /** Sorts the characters of this string according to an Ordering. + * + * The sort is stable. That is, elements that are equal (as determined by + * `ord.compare`) appear in the same order in the sorted sequence as in the original. + * + * @see [[scala.math.Ordering]] + * + * @param ord the ordering to be used to compare elements. + * @return a string consisting of the chars of this string + * sorted according to the ordering `ord`. + * @note $unicodeunaware + */ + def sorted[B >: Char](implicit ord: Ordering[B]): String = new WrappedString(s).sorted(ord).unwrap + + /** Sorts this string according to a comparison function. + * + * The sort is stable. That is, elements that are equal (as determined by + * `lt`) appear in the same order in the sorted sequence as in the original. + * + * @param lt the comparison function which tests whether + * its first argument precedes its second argument in + * the desired ordering. + * @return a string consisting of the elements of this string + * sorted according to the comparison function `lt`. + * @note $unicodeunaware + */ + def sortWith(lt: (Char, Char) => Boolean): String = new WrappedString(s).sortWith(lt).unwrap + + /** Sorts this string according to the Ordering which results from transforming + * an implicitly given Ordering with a transformation function. + * + * The sort is stable. That is, elements that are equal (as determined by + * `ord.compare`) appear in the same order in the sorted sequence as in the original. + * + * @see [[scala.math.Ordering]] + * @param f the transformation function mapping elements + * to some other domain `B`. + * @param ord the ordering assumed on domain `B`. + * @tparam B the target type of the transformation `f`, and the type where + * the ordering `ord` is defined. + * @return a string consisting of the chars of this string + * sorted according to the ordering where `x < y` if + * `ord.lt(f(x), f(y))`. + * @note $unicodeunaware + */ + def sortBy[B](f: Char => B)(implicit ord: Ordering[B]): String = new WrappedString(s).sortBy(f)(ord).unwrap + + /** Partitions this string into a map of strings according to some discriminator function. + * + * @param f the discriminator function. + * @tparam K the type of keys returned by the discriminator function. + * @return A map from keys to strings such that the following invariant holds: + * {{{ + * (xs groupBy f)(k) = xs filter (x => f(x) == k) + * }}} + * That is, every key `k` is bound to a string of those elements `x` + * for which `f(x)` equals `k`. + * @note $unicodeunaware + */ + def groupBy[K](f: Char => K): immutable.Map[K, String] = new WrappedString(s).groupBy(f).view.mapValues(_.unwrap).toMap + + /** Groups chars in fixed size blocks by passing a "sliding window" + * over them (as opposed to partitioning them, as is done in grouped.) + * @see [[scala.collection.Iterator]], method `sliding` + * + * @param size the number of chars per group + * @param step the distance between the first chars of successive groups + * @return An iterator producing strings of size `size`, except the + * last element (which may be the only element) will be truncated + * if there are fewer than `size` chars remaining to be grouped. + * @note $unicodeunaware + */ + def sliding(size: Int, step: Int = 1): Iterator[String] = new WrappedString(s).sliding(size, step).map(_.unwrap) + + /** Iterates over combinations of elements. + * + * A '''combination''' of length `n` is a sequence of `n` elements selected in order of their first index in this sequence. + * + * For example, `"xyx"` has two combinations of length 2. The `x` is selected first: `"xx"`, `"xy"`. + * The sequence `"yx"` is not returned as a combination because it is subsumed by `"xy"`. + * + * If there is more than one way to generate the same combination, only one will be returned. + * + * For example, the result `"xy"` arbitrarily selected one of the `x` elements. + * + * As a further illustration, `"xyxx"` has three different ways to generate `"xy"` because there are three elements `x` + * to choose from. Moreover, there are three unordered pairs `"xx"` but only one is returned. + * + * It is not specified which of these equal combinations is returned. It is an implementation detail + * that should not be relied on. For example, the combination `"xx"` does not necessarily contain + * the first `x` in this sequence. This behavior is observable if the elements compare equal + * but are not identical. + * + * As a consequence, `"xyx".combinations(3).next()` is `"xxy"`: the combination does not reflect the order + * of the original sequence, but the order in which elements were selected, by "first index"; + * the order of each `x` element is also arbitrary. + * + * @return An Iterator which traverses the n-element combinations of this string. + * @example {{{ + * "abbbc".combinations(2).foreach(println) + * // ab + * // ac + * // bb + * // bc + * "bab".combinations(2).foreach(println) + * // bb + * // ba + * }}} + * @note $unicodeunaware + */ + def combinations(n: Int): Iterator[String] = new WrappedString(s).combinations(n).map(_.unwrap) + + /** Iterates over distinct permutations of elements. + * + * @return An Iterator which traverses the distinct permutations of this string. + * @example {{{ + * "abb".permutations.foreach(println) + * // abb + * // bab + * // bba + * }}} + * @note $unicodeunaware + */ + def permutations: Iterator[String] = new WrappedString(s).permutations.map(_.unwrap) +} + +final case class StringView(s: String) extends AbstractIndexedSeqView[Char] { + def length = s.length + @throws[StringIndexOutOfBoundsException] + def apply(n: Int) = s.charAt(n) + override def toString: String = s"StringView($s)" +} diff --git a/tests/pos/stdlib/collection/mutable/Buffer.scala b/tests/pos/stdlib/collection/mutable/Buffer.scala index 847b924735ce..0a70c75bac0c 100644 --- a/tests/pos/stdlib/collection/mutable/Buffer.scala +++ b/tests/pos/stdlib/collection/mutable/Buffer.scala @@ -14,6 +14,7 @@ package scala.collection package mutable import scala.annotation.nowarn +import language.experimental.captureChecking /** A `Buffer` is a growable and shrinkable `Seq`. */ @@ -48,19 +49,19 @@ trait Buffer[A] /** Appends the elements contained in a iterable object to this buffer. * @param xs the iterable object containing the elements to append. */ - @`inline` final def appendAll(xs: IterableOnce[A]): this.type = addAll(xs) + @`inline` final def appendAll(xs: IterableOnce[A]^): this.type = addAll(xs) /** Alias for `prepend` */ @`inline` final def +=: (elem: A): this.type = prepend(elem) - 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. * @@ -81,7 +82,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. * @@ -103,7 +104,7 @@ trait Buffer[A] @throws[IndexOutOfBoundsException] @throws[IllegalArgumentException] def remove(idx: Int, count: Int): Unit - + /** Removes a single element from this buffer, at its first occurrence. * If the buffer does not contain that element, it is unchanged. * @@ -132,7 +133,7 @@ trait Buffer[A] @deprecated("use dropRightInPlace instead", since = "2.13.4") def trimEnd(n: Int): Unit = dropRightInPlace(n) - 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: @@ -180,11 +181,11 @@ trait IndexedBuffer[A] extends IndexedSeq[A] override def iterableFactory: SeqFactory[IndexedBuffer] = IndexedBuffer - 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]^](s) while (i < s) { newElems(i) = f(this(i)); i += 1 } clear() i = 0 @@ -207,7 +208,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/tests/pos/stdlib/collection/mutable/StringBuilder.scala b/tests/pos/stdlib/collection/mutable/StringBuilder.scala new file mode 100644 index 000000000000..c7859214821d --- /dev/null +++ b/tests/pos/stdlib/collection/mutable/StringBuilder.scala @@ -0,0 +1,496 @@ +/* + * Scala (https://www.scala-lang.org) + * + * Copyright EPFL and Lightbend, Inc. + * + * 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.collection.mutable + +import scala.collection.{IterableFactoryDefaults, IterableOnce} +import scala.collection.immutable.WrappedString +import language.experimental.captureChecking + +import scala.Predef.{ // unimport char-related implicit conversions to avoid triggering them accidentally + genericArrayOps => _, + charArrayOps => _, + genericWrapArray => _, + wrapCharArray => _, + wrapString => _, + //_ +} + +/** A builder of `String` which is also a mutable sequence of characters. + * + * This class provides an API mostly compatible with `java.lang.StringBuilder`, + * except where there are conflicts with the Scala collections API, such as the `reverse` method: + * [[reverse]] produces a new `StringBuilder`, and [[reverseInPlace]] mutates this builder. + * + * Mutating operations return either `this.type`, i.e., the current builder, or `Unit`. + * + * Other methods extract data or information from the builder without mutating it. + * + * The distinction is also reflected in naming conventions used by collections, + * such as `append`, which mutates, and `appended`, which does not, or `reverse`, + * which does not mutate, and `reverseInPlace`, which does. + * + * The `String` result may be obtained using either `result()` or `toString`. + * + * $multipleResults + * + * @see [[https://docs.scala-lang.org/overviews/collections-2.13/concrete-mutable-collection-classes.html#stringbuilders "Scala's Collection Library overview"]] + * section on `StringBuilders` for more information. + * + * @define Coll `mutable.IndexedSeq` + * @define coll string builder + */ +@SerialVersionUID(3L) +final class StringBuilder(val underlying: java.lang.StringBuilder) extends AbstractSeq[Char] + with ReusableBuilder[Char, String] + with IndexedSeq[Char] + with IndexedSeqOps[Char, IndexedSeq, StringBuilder] + with IterableFactoryDefaults[Char, IndexedSeq] + with java.lang.CharSequence + with Serializable { + + def this() = this(new java.lang.StringBuilder) + + /** Constructs a string builder with no characters in it and an + * initial capacity specified by the `capacity` argument. + * + * @param capacity the initial capacity. + * @throws java.lang.NegativeArraySizeException if capacity < 0. + */ + def this(capacity: Int) = this(new java.lang.StringBuilder(capacity)) + + /** Constructs a string builder with initial characters + * equal to characters of `str`. + */ + def this(str: String) = this(new java.lang.StringBuilder(str)) + + /** Constructs a string builder initialized with string value `initValue` + * and with additional character capacity `initCapacity`. + */ + def this(initCapacity: Int, initValue: String) = + this(new java.lang.StringBuilder(initValue.length + initCapacity) append initValue) + + // 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 = + new StringBuilder() appendAll coll + + override protected def newSpecificBuilder: Builder[Char, StringBuilder] = + new GrowableBuilder(new StringBuilder()) + + override def empty: StringBuilder = new StringBuilder() + + @inline def length: Int = underlying.length + + def length_=(n: Int): Unit = underlying.setLength(n) + + override def knownSize: Int = super[IndexedSeqOps].knownSize + + def addOne(x: Char): this.type = { underlying.append(x); this } + + def clear(): Unit = underlying.setLength(0) + + /** Overloaded version of `addAll` that takes a string */ + def addAll(s: String): this.type = { underlying.append(s); this } + + /** Alias for `addAll` */ + def ++= (s: String): this.type = addAll(s) + + def result() = underlying.toString + + override def toString: String = result() + + override def toArray[B >: Char](implicit ct: scala.reflect.ClassTag[B]) = + ct.runtimeClass match { + case java.lang.Character.TYPE => toCharArray.asInstanceOf[Array[B]] + case _ => super.toArray + } + + /** Returns the contents of this StringBuilder as an `Array[Char]`. + * + * @return An array with the characters from this builder. + */ + def toCharArray: Array[Char] = { + val len = underlying.length + val arr = new Array[Char](len) + underlying.getChars(0, len, arr, 0) + arr + } + + // append* methods delegate to the underlying java.lang.StringBuilder: + + def appendAll(xs: String): this.type = { + underlying append xs + this + } + + /** Appends the string representation of the given argument, + * which is converted to a String with `String.valueOf`. + * + * @param x an `Any` object. + * @return this StringBuilder. + */ + def append(x: Any): this.type = { + underlying append String.valueOf(x) + this + } + + /** Appends the given String to this sequence. + * + * @param s a String. + * @return this StringBuilder. + */ + def append(s: String): this.type = { + underlying append s + this + } + + /** Appends the given CharSequence to this sequence. + * + * @param cs a CharSequence. + * @return this StringBuilder. + */ + def append(cs: java.lang.CharSequence): this.type = { + underlying.append(cs match { + // Both cases call into append(), but java SB + // looks up type at runtime and has fast path for SB. + case s: StringBuilder => s.underlying + case _ => cs + }) + this + } + + /** Appends the specified string builder to this sequence. + * + * @param s + * @return + */ + def append(s: StringBuilder): this.type = { + underlying append s.underlying + this + } + + /** Appends all the Chars in the given IterableOnce[Char] to this sequence. + * + * @param xs the characters to be appended. + * @return this StringBuilder. + */ + def appendAll(xs: IterableOnce[Char]^): this.type = { + xs match { + case x: WrappedString => underlying append x.unwrap + case x: ArraySeq.ofChar => underlying append x.array + case x: StringBuilder => underlying append x.underlying + case _ => + val ks = xs.knownSize + if (ks != 0) { + val b = underlying + if (ks > 0) b.ensureCapacity(b.length + ks) + val it = xs.iterator + while (it.hasNext) { b append it.next() } + } + } + this + } + + /** Appends all the Chars in the given Array[Char] to this sequence. + * + * @param xs the characters to be appended. + * @return a reference to this object. + */ + def appendAll(xs: Array[Char]): this.type = { + underlying append xs + this + } + + /** Appends a portion of the given Array[Char] to this sequence. + * + * @param xs the Array containing Chars to be appended. + * @param offset the index of the first Char to append. + * @param len the numbers of Chars to append. + * @return this StringBuilder. + */ + def appendAll(xs: Array[Char], offset: Int, len: Int): this.type = { + underlying.append(xs, offset, len) + this + } + + /** Append the String representation of the given primitive type + * to this sequence. The argument is converted to a String with + * String.valueOf. + * + * @param x a primitive value + * @return This StringBuilder. + */ + def append(x: Boolean): this.type = { underlying append x ; this } + def append(x: Byte): this.type = append(x.toInt) + def append(x: Short): this.type = append(x.toInt) + def append(x: Int): this.type = { underlying append x ; this } + def append(x: Long): this.type = { underlying append x ; this } + def append(x: Float): this.type = { underlying append x ; this } + def append(x: Double): this.type = { underlying append x ; this } + def append(x: Char): this.type = { underlying append x ; this } + + /** Remove a subsequence of Chars from this sequence, starting at the + * given start index (inclusive) and extending to the end index (exclusive) + * or to the end of the String, whichever comes first. + * + * @param start The beginning index, inclusive. + * @param end The ending index, exclusive. + * @return This StringBuilder. + * @throws StringIndexOutOfBoundsException if start < 0 || start > end + */ + def delete(start: Int, end: Int): this.type = { + underlying.delete(start, end) + this + } + + /** Replaces a subsequence of Chars with the given String. The semantics + * are as in delete, with the String argument then inserted at index 'start'. + * + * @param start The beginning index, inclusive. + * @param end The ending index, exclusive. + * @param str The String to be inserted at the start index. + * @return This StringBuilder. + * @throws StringIndexOutOfBoundsException if start < 0, start > length, or start > end + */ + def replace(start: Int, end: Int, str: String): this.type = { + underlying.replace(start, end, str) + this + } + + /** Inserts a subarray of the given Array[Char] at the given index + * of this sequence. + * + * @param index index at which to insert the subarray. + * @param str the Array from which Chars will be taken. + * @param offset the index of the first Char to insert. + * @param len the number of Chars from 'str' to insert. + * @return This StringBuilder. + * + * @throws StringIndexOutOfBoundsException if index < 0, index > length, + * offset < 0, len < 0, or (offset + len) > str.length. + */ + def insertAll(index: Int, str: Array[Char], offset: Int, len: Int): this.type = { + underlying.insert(index, str, offset, len) + this + } + + /** Inserts the String representation (via String.valueOf) of the given + * argument into this sequence at the given index. + * + * @param index the index at which to insert. + * @param x a value. + * @return this StringBuilder. + * @throws StringIndexOutOfBoundsException if the index is out of bounds. + */ + def insert(index: Int, x: Any): this.type = insert(index, String.valueOf(x)) + + /** Inserts the String into this character sequence. + * + * @param index the index at which to insert. + * @param x a String. + * @return this StringBuilder. + * @throws StringIndexOutOfBoundsException if the index is out of bounds. + */ + def insert(index: Int, x: String): this.type = { + underlying.insert(index, x) + this + } + + /** Inserts the given Seq[Char] into this sequence at the given index. + * + * @param index the index at which to insert. + * @param xs the Seq[Char]. + * @return this StringBuilder. + * @throws StringIndexOutOfBoundsException if the index is out of bounds. + */ + 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. + * + * @param index the index at which to insert. + * @param xs the Array[Char]. + * @return this StringBuilder. + * @throws StringIndexOutOfBoundsException if the index is out of bounds. + */ + def insertAll(index: Int, xs: Array[Char]): this.type = { + underlying.insert(index, xs) + this + } + + /** Calls String.valueOf on the given primitive value, and inserts the + * String at the given index. + * + * @param index the offset position. + * @param x a primitive value. + * @return this StringBuilder. + */ + def insert(index: Int, x: Boolean): this.type = insert(index, String.valueOf(x)) + def insert(index: Int, x: Byte): this.type = insert(index, x.toInt) + def insert(index: Int, x: Short): this.type = insert(index, x.toInt) + def insert(index: Int, x: Int): this.type = insert(index, String.valueOf(x)) + def insert(index: Int, x: Long): this.type = insert(index, String.valueOf(x)) + def insert(index: Int, x: Float): this.type = insert(index, String.valueOf(x)) + def insert(index: Int, x: Double): this.type = insert(index, String.valueOf(x)) + def insert(index: Int, x: Char): this.type = insert(index, String.valueOf(x)) + + /** Sets the length of the character sequence. If the current sequence + * is shorter than the given length, it is padded with nulls; if it is + * longer, it is truncated. + * + * @param len the new length + * @throws IndexOutOfBoundsException if the argument is negative. + */ + def setLength(len: Int): Unit = underlying.setLength(len) + + def update(idx: Int, elem: Char): Unit = underlying.setCharAt(idx, elem) + + + /** Like reverse, but destructively updates the target StringBuilder. + * + * @return the reversed StringBuilder (same as the target StringBuilder) + */ + @deprecated("Use reverseInPlace instead", "2.13.0") + final def reverseContents(): this.type = reverseInPlace() + + /** Like reverse, but destructively updates the target StringBuilder. + * + * @return the reversed StringBuilder (same as the target StringBuilder) + */ + def reverseInPlace(): this.type = { + underlying.reverse() + this + } + + + /** Returns the current capacity, which is the size of the underlying array. + * A new array will be allocated if the current capacity is exceeded. + * + * @return the capacity + */ + def capacity: Int = underlying.capacity + + /** Ensure that the capacity is at least the given argument. + * If the argument is greater than the current capacity, new + * storage will be allocated with size equal to the given + * argument or to `(2 * capacity + 2)`, whichever is larger. + * + * @param newCapacity the minimum desired capacity. + */ + def ensureCapacity(newCapacity: Int): Unit = { underlying.ensureCapacity(newCapacity) } + + /** Returns the Char at the specified index, counting from 0 as in Arrays. + * + * @param index the index to look up + * @return the Char at the given index. + * @throws IndexOutOfBoundsException if the index is out of bounds. + */ + def charAt(index: Int): Char = underlying.charAt(index) + + /** Removes the Char at the specified index. The sequence is + * shortened by one. + * + * @param index The index to remove. + * @return This StringBuilder. + * @throws IndexOutOfBoundsException if the index is out of bounds. + */ + def deleteCharAt(index: Int): this.type = { + underlying.deleteCharAt(index) + this + } + + /** Update the sequence at the given index to hold the specified Char. + * + * @param index the index to modify. + * @param ch the new Char. + * @throws IndexOutOfBoundsException if the index is out of bounds. + */ + def setCharAt(index: Int, ch: Char): this.type = { + underlying.setCharAt(index, ch) + this + } + + /** Returns a new String made up of a subsequence of this sequence, + * beginning at the given index and extending to the end of the sequence. + * + * target.substring(start) is equivalent to target.drop(start) + * + * @param start The starting index, inclusive. + * @return The new String. + * @throws IndexOutOfBoundsException if the index is out of bounds. + */ + def substring(start: Int): String = underlying.substring(start, length) + + /** Returns a new String made up of a subsequence of this sequence, + * beginning at the start index (inclusive) and extending to the + * end index (exclusive). + * + * target.substring(start, end) is equivalent to target.slice(start, end).mkString + * + * @param start The beginning index, inclusive. + * @param end The ending index, exclusive. + * @return The new String. + * @throws StringIndexOutOfBoundsException If either index is out of bounds, + * or if start > end. + */ + def substring(start: Int, end: Int): String = underlying.substring(start, end) + + /** For implementing CharSequence. + */ + def subSequence(start: Int, end: Int): java.lang.CharSequence = + underlying.substring(start, end) + + /** Finds the index of the first occurrence of the specified substring. + * + * @param str the target string to search for + * @return the first applicable index where target occurs, or -1 if not found. + */ + def indexOf(str: String): Int = underlying.indexOf(str) + + /** Finds the index of the first occurrence of the specified substring. + * + * @param str the target string to search for + * @param fromIndex the smallest index in the source string to consider + * @return the first applicable index where target occurs, or -1 if not found. + */ + def indexOf(str: String, fromIndex: Int): Int = underlying.indexOf(str, fromIndex) + + /** Finds the index of the last occurrence of the specified substring. + * + * @param str the target string to search for + * @return the last applicable index where target occurs, or -1 if not found. + */ + def lastIndexOf(str: String): Int = underlying.lastIndexOf(str) + + /** Finds the index of the last occurrence of the specified substring. + * + * @param str the target string to search for + * @param fromIndex the smallest index in the source string to consider + * @return the last applicable index where target occurs, or -1 if not found. + */ + def lastIndexOf(str: String, fromIndex: Int): Int = underlying.lastIndexOf(str, fromIndex) + + /** Tests whether this builder is empty. + * + * This method is required for JDK15+ compatibility + * + * @return `true` if this builder contains nothing, `false` otherwise. + */ + override def isEmpty: Boolean = underlying.length() == 0 +} + +object StringBuilder { + @deprecated("Use `new StringBuilder()` instead of `StringBuilder.newBuilder`", "2.13.0") + def newBuilder = new StringBuilder +} diff --git a/tests/pos/stdlib/mutable_StringBuilder.scala b/tests/pos/stdlib/mutable_StringBuilder.scala new file mode 120000 index 000000000000..f6e5fe72c9e2 --- /dev/null +++ b/tests/pos/stdlib/mutable_StringBuilder.scala @@ -0,0 +1 @@ +collection/mutable/StringBuilder.scala \ No newline at end of file From 74696b9e4c18cea0122488d3cf1accb43f412a99 Mon Sep 17 00:00:00 2001 From: odersky Date: Fri, 21 Jul 2023 15:17:45 +0200 Subject: [PATCH 32/37] Improve error message for escaping capabilities --- compiler/src/dotty/tools/dotc/cc/Setup.scala | 6 +++--- tests/neg-custom-args/captures/try.check | 8 ++++---- .../captures/usingLogFile-alt.check | 4 ++-- .../neg-custom-args/captures/usingLogFile.check | 16 ++++++++-------- tests/neg-custom-args/captures/vars.check | 4 ++-- 5 files changed, 19 insertions(+), 19 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index fb49cced25cb..6f34e3e416b4 100644 --- a/compiler/src/dotty/tools/dotc/cc/Setup.scala +++ b/compiler/src/dotty/tools/dotc/cc/Setup.scala @@ -303,9 +303,9 @@ extends tpd.TreeTraverser: val polyType = fn.tpe.widen.asInstanceOf[TypeLambda] for case (arg: TypeTree, pinfo, pname) <- args.lazyZip(polyType.paramInfos).lazyZip((polyType.paramNames)) do if pinfo.bounds.hi.hasAnnotation(defn.Caps_SealedAnnot) then - def where = if fn.symbol.exists then i" in the body of ${fn.symbol}" else "" + def where = if fn.symbol.exists then i" in an argument of ${fn.symbol}" else "" CheckCaptures.disallowRootCapabilitiesIn(arg.knownType, - i"Sealed type variable $pname", " be instantiated to", + i"Sealed type variable $pname", "be instantiated to", i"This is often caused by a local capability$where\nleaking as part of its result.", tree.srcPos) case _ => @@ -346,7 +346,7 @@ extends tpd.TreeTraverser: if prevLambdas.isEmpty then restp else SubstParams(prevPsymss, prevLambdas)(restp) - if tree.tpt.hasRememberedType && !sym.isConstructor then + if sym.exists && tree.tpt.hasRememberedType && !sym.isConstructor then val newInfo = integrateRT(sym.info, sym.paramSymss, Nil, Nil) .showing(i"update info $sym: ${sym.info} --> $result", capt) if newInfo ne sym.info then diff --git a/tests/neg-custom-args/captures/try.check b/tests/neg-custom-args/captures/try.check index 4af370bfba1a..9afbe61d2280 100644 --- a/tests/neg-custom-args/captures/try.check +++ b/tests/neg-custom-args/captures/try.check @@ -1,9 +1,9 @@ -- Error: tests/neg-custom-args/captures/try.scala:23:16 --------------------------------------------------------------- 23 | val a = handle[Exception, CanThrow[Exception]] { // error | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - | Sealed type variable R cannot be instantiated to box CT[Exception]^ since + | Sealed type variable R cannot be instantiated to box CT[Exception]^ since | that type captures the root capability `cap`. - | This is often caused by a local capability in the body of method handle + | This is often caused by a local capability in an argument of method handle | leaking as part of its result. -- [E007] Type Mismatch Error: tests/neg-custom-args/captures/try.scala:29:43 ------------------------------------------ 29 | val b = handle[Exception, () -> Nothing] { // error @@ -31,7 +31,7 @@ -- Error: tests/neg-custom-args/captures/try.scala:35:11 --------------------------------------------------------------- 35 | val xx = handle { // error | ^^^^^^ - | Sealed type variable R cannot be instantiated to box () => Int since + | Sealed type variable R cannot be instantiated to box () => Int since | that type captures the root capability `cap`. - | This is often caused by a local capability in the body of method handle + | This is often caused by a local capability in an argument of method handle | leaking as part of its result. diff --git a/tests/neg-custom-args/captures/usingLogFile-alt.check b/tests/neg-custom-args/captures/usingLogFile-alt.check index 31e97b7dfda1..9444bc9dc46a 100644 --- a/tests/neg-custom-args/captures/usingLogFile-alt.check +++ b/tests/neg-custom-args/captures/usingLogFile-alt.check @@ -1,7 +1,7 @@ -- Error: tests/neg-custom-args/captures/usingLogFile-alt.scala:18:2 --------------------------------------------------- 18 | usingFile( // error | ^^^^^^^^^ - | Sealed type variable T cannot be instantiated to box () => Unit since + | Sealed type variable T cannot be instantiated to box () => Unit since | that type captures the root capability `cap`. - | This is often caused by a local capability in the body of method usingFile + | This is often caused by a local capability in an argument of method usingFile | leaking as part of its result. diff --git a/tests/neg-custom-args/captures/usingLogFile.check b/tests/neg-custom-args/captures/usingLogFile.check index d3bc9082202c..ff4c9fd3105f 100644 --- a/tests/neg-custom-args/captures/usingLogFile.check +++ b/tests/neg-custom-args/captures/usingLogFile.check @@ -13,16 +13,16 @@ -- Error: tests/neg-custom-args/captures/usingLogFile.scala:23:14 ------------------------------------------------------ 23 | val later = usingLogFile { f => () => f.write(0) } // error | ^^^^^^^^^^^^ - | Sealed type variable T cannot be instantiated to box () => Unit since + | Sealed type variable T cannot be instantiated to box () => Unit since | that type captures the root capability `cap`. - | This is often caused by a local capability in the body of method usingLogFile + | This is often caused by a local capability in an argument of method usingLogFile | leaking as part of its result. -- Error: tests/neg-custom-args/captures/usingLogFile.scala:28:23 ------------------------------------------------------ 28 | private val later2 = usingLogFile { f => Cell(() => f.write(0)) } // error | ^^^^^^^^^^^^ - | Sealed type variable T cannot be instantiated to box Test2.Cell[() => Unit]^? since + | Sealed type variable T cannot be instantiated to box Test2.Cell[() => Unit]^? since | the part () => Unit of that type captures the root capability `cap`. - | This is often caused by a local capability in the body of method usingLogFile + | This is often caused by a local capability in an argument of method usingLogFile | leaking as part of its result. -- Error: tests/neg-custom-args/captures/usingLogFile.scala:47:6 ------------------------------------------------------- 47 | val later = usingLogFile { f => () => f.write(0) } // error @@ -34,14 +34,14 @@ -- Error: tests/neg-custom-args/captures/usingLogFile.scala:62:16 ------------------------------------------------------ 62 | val later = usingFile("out", f => (y: Int) => xs.foreach(x => f.write(x + y))) // error | ^^^^^^^^^ - | Sealed type variable T cannot be instantiated to box (x$0: Int) => Unit since + | Sealed type variable T cannot be instantiated to box (x$0: Int) => Unit since | that type captures the root capability `cap`. - | This is often caused by a local capability in the body of method usingFile + | This is often caused by a local capability in an argument of method usingFile | leaking as part of its result. -- Error: tests/neg-custom-args/captures/usingLogFile.scala:71:16 ------------------------------------------------------ 71 | val later = usingFile("logfile", // error | ^^^^^^^^^ - | Sealed type variable T cannot be instantiated to box () => Unit since + | Sealed type variable T cannot be instantiated to box () => Unit since | that type captures the root capability `cap`. - | This is often caused by a local capability in the body of method usingFile + | This is often caused by a local capability in an argument of method usingFile | leaking as part of its result. diff --git a/tests/neg-custom-args/captures/vars.check b/tests/neg-custom-args/captures/vars.check index e7055c810bb0..a7e38dbfdb8a 100644 --- a/tests/neg-custom-args/captures/vars.check +++ b/tests/neg-custom-args/captures/vars.check @@ -20,7 +20,7 @@ -- Error: tests/neg-custom-args/captures/vars.scala:32:2 --------------------------------------------------------------- 32 | local { cap3 => // error | ^^^^^ - | Sealed type variable T cannot be instantiated to box (x$0: String) => String since + | Sealed type variable T cannot be instantiated to box (x$0: String) => String since | that type captures the root capability `cap`. - | This is often caused by a local capability in the body of method local + | This is often caused by a local capability in an argument of method local | leaking as part of its result. From 4b812e3d0be326b316881e1b5060784426440e9b Mon Sep 17 00:00:00 2001 From: odersky Date: Fri, 21 Jul 2023 15:30:35 +0200 Subject: [PATCH 33/37] Align typer and rechecker for Labelled and Bind trees There was a subtle difference which causeed the stdlib tests to crash under -Xprint:cc --- compiler/src/dotty/tools/dotc/core/Types.scala | 2 +- compiler/src/dotty/tools/dotc/transform/Recheck.scala | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/core/Types.scala b/compiler/src/dotty/tools/dotc/core/Types.scala index 166f65533584..29b1ee7b133d 100644 --- a/compiler/src/dotty/tools/dotc/core/Types.scala +++ b/compiler/src/dotty/tools/dotc/core/Types.scala @@ -3590,7 +3590,7 @@ object Types { def expectValueTypeOrWildcard(tp: Type, where: => String)(using Context): Unit = if !tp.isValueTypeOrWildcard then - assert(!ctx.isAfterTyper, where) // we check correct kinds at PostTyper + assert(!ctx.isAfterTyper, s"$tp in $where") // we check correct kinds at PostTyper throw TypeError(em"$tp is not a value type, cannot be used $where") /** An extractor object to pattern match against a nullable union. diff --git a/compiler/src/dotty/tools/dotc/transform/Recheck.scala b/compiler/src/dotty/tools/dotc/transform/Recheck.scala index 2d661e5e06dd..9e6b4f30a2cc 100644 --- a/compiler/src/dotty/tools/dotc/transform/Recheck.scala +++ b/compiler/src/dotty/tools/dotc/transform/Recheck.scala @@ -224,14 +224,13 @@ abstract class Recheck extends Phase, SymTransformer: def recheckBind(tree: Bind, pt: Type)(using Context): Type = tree match case Bind(name, body) => recheck(body, pt) - val sym = tree.symbol - if sym.isType then sym.typeRef else sym.info + tree.symbol.namedType def recheckLabeled(tree: Labeled, pt: Type)(using Context): Type = tree match case Labeled(bind, expr) => - val bindType = recheck(bind, pt) + val (bindType: NamedType) = recheck(bind, pt): @unchecked val exprType = recheck(expr, defn.UnitType) - bindType + bindType.symbol.info def recheckValDef(tree: ValDef, sym: Symbol)(using Context): Unit = if !tree.rhs.isEmpty then recheck(tree.rhs, sym.info) From 0a8f76315717b6db6b599c46ac4148892348c119 Mon Sep 17 00:00:00 2001 From: odersky Date: Fri, 21 Jul 2023 15:42:52 +0200 Subject: [PATCH 34/37] Use InferredTypeTree for @unchecked match selectors Without this, we get stray capture sets in match selectors if the match is not exhaustive. --- compiler/src/dotty/tools/dotc/typer/Typer.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 3e2414966b51..3906928c0a70 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -1829,7 +1829,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer // TODO: move the check above to patternMatcher phase val uncheckedTpe = AnnotatedType(sel.tpe.widen, Annotation(defn.UncheckedAnnot, tree.selector.span)) tpd.cpy.Match(result)( - selector = tpd.Typed(sel, tpd.TypeTree(uncheckedTpe)), + selector = tpd.Typed(sel, new tpd.InferredTypeTree().withType(uncheckedTpe)), cases = result.cases ) case _ => From bd8a4de7ed1cdbcfc74c0fa501ec202dbcede7df Mon Sep 17 00:00:00 2001 From: odersky Date: Fri, 21 Jul 2023 15:59:41 +0200 Subject: [PATCH 35/37] Add test programs to stdlib tests --- tests/pos/stdlib/Test1.scala | 34 +++++ tests/pos/stdlib/Test2.scala | 232 +++++++++++++++++++++++++++++++++++ 2 files changed, 266 insertions(+) create mode 100644 tests/pos/stdlib/Test1.scala create mode 100644 tests/pos/stdlib/Test2.scala diff --git a/tests/pos/stdlib/Test1.scala b/tests/pos/stdlib/Test1.scala new file mode 100644 index 000000000000..e5ae39027f94 --- /dev/null +++ b/tests/pos/stdlib/Test1.scala @@ -0,0 +1,34 @@ +import language.experimental.captureChecking +import collection.{View, Seq} +import collection.mutable.{ArrayBuffer, ListBuffer} + +import java.io.* + +object Test0: + + def usingLogFile[sealed T](op: FileOutputStream^ => T): T = + val logFile = FileOutputStream("log") + val result = op(logFile) + logFile.close() + result + + def test(xs: List[Int]) = + usingLogFile: f => + xs.map: x => + f.write(x) + x * x + +object Test1: + def test(it: Iterator[Int]^, v: View[Int]^) = + val isEven: Int => Boolean = _ % 2 == 0 + val it2 = it.filter(isEven) + val _: Iterator[Int]^{it, isEven} = it2 + val it2c: Iterator[Int]^{it2} = it2 + val v2 = v.filter(isEven) + val _: View[Int]^{v, isEven} = v2 + val v2c: View[Int]^{v2} = v2 + val v3 = v.drop(2) + val _: View[Int]^{v} = v3 + val v3c: View[Int]^{v3} = v3 + val (xs6, xs7) = v.partition(isEven) + val (xs6a, xs7a) = v.partition(_ % 2 == 0) diff --git a/tests/pos/stdlib/Test2.scala b/tests/pos/stdlib/Test2.scala new file mode 100644 index 000000000000..cab9440c17db --- /dev/null +++ b/tests/pos/stdlib/Test2.scala @@ -0,0 +1,232 @@ +import scala.reflect.ClassTag +import language.experimental.captureChecking +import collection.{View, Seq} +import collection.mutable.{ArrayBuffer, ListBuffer} + +object Test { + + def seqOps(xs: Seq[Int]) = { // try with Seq[Int]^{cap} + val strPlusInt: (String, Int) => String = _ + _ + val intPlusStr: (Int, String) => String = _ + _ + val isEven: Int => Boolean = _ % 2 == 0 + val isNonNeg: Int => Boolean = _ > 0 + val flips: Int => List[Int] = x => x :: -x :: Nil + val x1 = xs.foldLeft("")(strPlusInt) + val y1: String = x1 + val x2 = xs.foldRight("")(intPlusStr) + val y2: String = x2 + val x3 = xs.indexWhere(isEven) + val y3: Int = x3 + val x4 = xs.head + val y4: Int = x4 + val x5 = xs.to(List) + val y5: List[Int] = x5 + val (xs6, xs7) = xs.partition(isEven) + val ys6: Seq[Int] = xs6 + val ys7: Seq[Int] = xs7 + val xs8 = xs.drop(2) + val ys8: Seq[Int] = xs8 + val xs9 = xs.map(isNonNeg) + val ys9: Seq[Boolean] = xs9 + val xs10 = xs.flatMap(flips) + val ys10: Seq[Int] = xs10 + val xs11 = xs ++ xs + val ys11: Seq[Int] = xs11 + val xs12 = xs ++ Nil + val ys12: Seq[Int] = xs12 + val xs13 = Nil ++ xs + val ys13: Seq[Int] = xs13 + val xs14 = xs ++ ("a" :: Nil) + val ys14: Seq[Any] = xs14 + val xs15 = xs.zip(xs9) + val ys15: Seq[(Int, Boolean)] = xs15 + val xs16 = xs.reverse + val ys16: Seq[Int] = xs16 + println("-------") + println(x1) + println(x2) + println(x3) + println(x4) + println(x5) + println(xs6) + println(xs7) + println(xs8) + println(xs9) + println(xs10) + println(xs11) + println(xs12) + println(xs13) + println(xs14) + println(xs15) + println(xs16) + } + + def iterOps(xs: => Iterator[Int]^) = + val strPlusInt: (String, Int) => String = _ + _ + val intPlusStr: (Int, String) => String = _ + _ + val isEven: Int => Boolean = _ % 2 == 0 + val isNonNeg: Int => Boolean = _ > 0 + val flips: Int => List[Int] = x => x :: -x :: Nil + val x1 = xs.foldLeft("")(strPlusInt) + val y1: String = x1 + val x2 = xs.foldRight("")(intPlusStr) + val y2: String = x2 + val x4 = xs.next() + val y4: Int = x4 + val x5 = xs.to(List) + val y5: List[Int] = x5 + val (xs6, xs7) = xs.partition(isEven) + val ys6: Iterator[Int]^{xs6, isEven} = xs6 + val ys7: Iterator[Int]^{xs7, isEven} = xs7 + val (xs6a, xs7a) = xs.partition(_ % 2 == 0) + val ys6a: Iterator[Int]^{xs6} = xs6 + val ys7a: Iterator[Int]^{xs7} = xs7 + val xs8 = xs.drop(2) + val ys8: Iterator[Int]^{xs8} = xs8 + val xs9 = xs.map(isNonNeg) + val ys9: Iterator[Boolean]^{xs9} = xs9 + val xs10 = xs.flatMap(flips) + val ys10: Iterator[Int]^{xs10} = xs10 + val xs11 = xs ++ xs + val ys11: Iterator[Int]^{xs11} = xs11 + val xs12 = xs ++ Nil + val ys12: Iterator[Int]^{xs12} = xs12 + val xs13 = Nil ++ xs + val ys13: List[Int] = xs13 + val xs14 = xs ++ ("a" :: Nil) + val ys14: Iterator[Any]^{xs14} = xs14 + val xs15 = xs.zip(xs9) + val ys15: Iterator[(Int, Boolean)]^{xs15} = xs15 + println("-------") + println(x1) + println(x2) + println(x4) + println(x5) + println(xs6.to(List)) + println(xs7.to(List)) + println(xs8.to(List)) + println(xs9.to(List)) + println(xs10.to(List)) + println(xs11.to(List)) + println(xs12.to(List)) + println(xs13.to(List)) + println(xs14.to(List)) + println(xs15.to(List)) + + def viewOps(xs: View[Int]^) = { + val strPlusInt: (String, Int) => String = _ + _ + val intPlusStr: (Int, String) => String = _ + _ + val isEven: Int => Boolean = _ % 2 == 0 + val isNonNeg: Int => Boolean = _ > 0 + val flips: Int => List[Int] = x => x :: -x :: Nil + val x1 = xs.foldLeft("")(strPlusInt) + val y1: String = x1 + val x2 = xs.foldRight("")(intPlusStr) + val y2: String = x2 + //val x3 = xs.indexWhere(_ % 2 == 0) // indexWhere does not exist on View + //val y3: Int = x3 + val x4 = xs.head + val y4: Int = x4 + val x5 = xs.to(List) + val y5: List[Int] = x5 + val (xs6, xs7) = xs.partition(isEven) + val ys6: View[Int]^{xs6, isEven} = xs6 + val ys7: View[Int]^{xs7, isEven} = xs7 + val (xs6a, xs7a) = xs.partition(_ % 2 == 0) + val ys6a: View[Int]^{xs6} = xs6 + val ys7a: View[Int]^{xs7} = xs7 + val xs8 = xs.drop(2) + val ys8: View[Int]^{xs8} = xs8 + val xs9 = xs.map(isNonNeg) + val ys9: View[Boolean]^{xs9} = xs9 + val xs10 = xs.flatMap(flips) + val ys10: View[Int]^{xs10} = xs10 + val xs11 = xs ++ xs + val ys11: View[Int]^{xs11} = xs11 + val xs12 = xs ++ Nil + val ys12: View[Int]^{xs12} = xs12 + val xs13 = Nil ++ xs + val ys13: List[Int] = xs13 + val xs14 = xs ++ ("a" :: Nil) + val ys14: View[Any]^{xs14} = xs14 + val xs15 = xs.zip(xs9) + val ys15: View[(Int, Boolean)]^{xs15} = xs15 + println("-------") + println(x1) + println(x2) + println(x4) + println(x5) + println(xs6.to(List)) + println(xs7.to(List)) + println(xs8.to(List)) + println(xs9.to(List)) + println(xs10.to(List)) + println(xs11.to(List)) + println(xs12.to(List)) + println(xs13.to(List)) + println(xs14.to(List)) + println(xs15.to(List)) + } + + def stringOps(xs: String) = { + val x1 = xs.foldLeft("")(_ + _) + val y1: String = x1 + val x2 = xs.foldRight("")(_ + _) + val y2: String = x2 + val x3 = xs.indexWhere(_ % 2 == 0) + val y3: Int = x3 + val x4 = xs.head + val y4: Int = x4 + val x5 = xs.to(List) + val y5: List[Char] = x5 + val (xs6, xs7) = xs.partition(_ % 2 == 0) + val ys6: String = xs6 + val ys7: String = xs7 + val xs8 = xs.drop(2) + val ys8: String = xs8 + val xs9 = xs.map(_ + 1) + val ys9: Seq[Int] = xs9 + val xs9a = xs.map(_.toUpper) + val ys9a: String = xs9a + val xs10 = xs.flatMap((x: Char) => s"$x,$x") + val ys10: String = xs10 + val xs11 = xs ++ xs + val ys11: String = xs11 + val ops = collection.StringOps(xs) // !!! otherwise we can a "cannot establish reference" + val xs13 = Nil ++ ops.iterator + val ys13: List[Char] = xs13 + val xs14 = xs ++ ("xyz" :: Nil) + val ys14: Seq[Any] = xs14 + val xs15 = xs.zip(xs9) + val ys15: Seq[(Char, Int)] = xs15 + println("-------") + println(x1) + println(x2) + println(x3) + println(x4) + println(x5) + println(xs6) + println(xs7) + println(xs8) + println(xs9) + println(xs9a) + println(xs10) + println(xs11) + println(xs13) + println(xs14) + println(xs15) + } + + def main(args: Array[String]) = { + val ints = List(1, 2, 3) + val intsBuf = ints.to(ArrayBuffer) + val intsListBuf = ints.to(ListBuffer) + val intsView = ints.view + seqOps(ints) + seqOps(intsBuf) + seqOps(intsListBuf) + viewOps(intsView) + iterOps(ints.iterator) + stringOps("abc") + } +} From 8ce0521ee010f188cd9f19b5354c1197654e7f36 Mon Sep 17 00:00:00 2001 From: odersky Date: Sat, 22 Jul 2023 11:43:40 +0200 Subject: [PATCH 36/37] Rename WithCaptureChecks to CaptureChecked --- compiler/src/dotty/tools/dotc/core/Definitions.scala | 2 +- compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala | 2 +- compiler/src/dotty/tools/dotc/transform/PostTyper.scala | 4 ++-- .../{WithCaptureChecks.scala => CaptureChecked.scala} | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) rename library/src/scala/annotation/internal/{WithCaptureChecks.scala => CaptureChecked.scala} (78%) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index a007e41c4c1d..75bf3af1d166 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -1029,7 +1029,7 @@ class Definitions { @tu lazy val UncheckedCapturesAnnot: ClassSymbol = requiredClass("scala.annotation.unchecked.uncheckedCaptures") @tu lazy val VolatileAnnot: ClassSymbol = requiredClass("scala.volatile") @tu lazy val WithPureFunsAnnot: ClassSymbol = requiredClass("scala.annotation.internal.WithPureFuns") - @tu lazy val WithCaptureChecksAnnot: ClassSymbol = requiredClass("scala.annotation.internal.WithCaptureChecks") + @tu lazy val CaptureCheckedAnnot: ClassSymbol = requiredClass("scala.annotation.internal.CaptureChecked") @tu lazy val BeanGetterMetaAnnot: ClassSymbol = requiredClass("scala.annotation.meta.beanGetter") @tu lazy val BeanSetterMetaAnnot: ClassSymbol = requiredClass("scala.annotation.meta.beanSetter") @tu lazy val FieldMetaAnnot: ClassSymbol = requiredClass("scala.annotation.meta.field") diff --git a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index f7399782b423..804a1f8244f3 100644 --- a/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala @@ -653,7 +653,7 @@ class TreeUnpickler(reader: TastyReader, registerSym(start, sym) if (isClass) { if sym.owner.is(Package) then - if annots.exists(_.hasSymbol(defn.WithCaptureChecksAnnot)) then + if annots.exists(_.hasSymbol(defn.CaptureCheckedAnnot)) then withCaptureChecks = true withPureFuns = true else if annots.exists(_.hasSymbol(defn.WithPureFunsAnnot)) then diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index cbca783fe6a3..5fa2ead7f3a3 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -418,9 +418,9 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase => val reference = ctx.settings.sourceroot.value val relativePath = util.SourceFile.relativePath(ctx.compilationUnit.source, reference) sym.addAnnotation(Annotation.makeSourceFile(relativePath, tree.span)) - if sym != defn.WithPureFunsAnnot && sym != defn.WithCaptureChecksAnnot then + if sym != defn.WithPureFunsAnnot && sym != defn.CaptureCheckedAnnot then if Feature.ccEnabled then - sym.addAnnotation(Annotation(defn.WithCaptureChecksAnnot, tree.span)) + sym.addAnnotation(Annotation(defn.CaptureCheckedAnnot, tree.span)) else if Feature.pureFunsEnabled then sym.addAnnotation(Annotation(defn.WithPureFunsAnnot, tree.span)) else diff --git a/library/src/scala/annotation/internal/WithCaptureChecks.scala b/library/src/scala/annotation/internal/CaptureChecked.scala similarity index 78% rename from library/src/scala/annotation/internal/WithCaptureChecks.scala rename to library/src/scala/annotation/internal/CaptureChecked.scala index b10e53fcd9ea..0bc6d628a542 100644 --- a/library/src/scala/annotation/internal/WithCaptureChecks.scala +++ b/library/src/scala/annotation/internal/CaptureChecked.scala @@ -4,5 +4,5 @@ package internal /** A marker annotation on a toplevel class that indicates * that the class was typed with the captureChecking language import. */ -class WithCaptureChecks extends StaticAnnotation +class CaptureChecked extends StaticAnnotation From 7a8fae7228f15b25c7a4368ba1c4347e33578a7e Mon Sep 17 00:00:00 2001 From: odersky Date: Sat, 22 Jul 2023 13:13:10 +0200 Subject: [PATCH 37/37] Update MiMaFilters --- project/MiMaFilters.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/MiMaFilters.scala b/project/MiMaFilters.scala index 08426f55d149..df4aca72c9a7 100644 --- a/project/MiMaFilters.scala +++ b/project/MiMaFilters.scala @@ -15,7 +15,7 @@ object MiMaFilters { ProblemFilters.exclude[MissingClassProblem]("scala.runtime.stdLibPatches.language$experimental$relaxedExtensionImports$"), ProblemFilters.exclude[DirectMissingMethodProblem]("scala.runtime.Tuples.reverse"), ProblemFilters.exclude[MissingFieldProblem]("scala.Tuple.Helpers"), - ProblemFilters.exclude[MissingClassProblem]("scala.annotation.internal.WithCaptureChecks"), + ProblemFilters.exclude[MissingClassProblem]("scala.annotation.internal.CaptureChecked"), // end of New experimental features in 3.3.X ) val TastyCore: Seq[ProblemFilter] = Seq(