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, diff --git a/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala b/compiler/src/dotty/tools/dotc/cc/CaptureSet.scala index 9de68220d2cf..3f2beaa3ff55 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: @@ -863,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 diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index c8c1180abd51..8967558c3ab3 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -290,6 +290,38 @@ 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 with IdempotentCaptRefMap: + variance = initialVariance + def apply(t: Type): Type = t match + case t: MethodType => + mapOver(t) + case t: TypeLambda => + t.derivedLambdaType(resType = this(t.resType)) + case CapturingType(_, _) => + t + case _ => + 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 + && !sym.owner.is(CaptureChecked) + && !defn.isFunctionSymbol(sym.owner) + + 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 +329,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 +359,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") @@ -362,7 +394,16 @@ 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)) @@ -662,8 +703,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) @@ -883,7 +926,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 @@ -905,7 +948,11 @@ class CheckCaptures extends Recheck, SymTransformer: case _ => adapted finally curEnv = saved actual1 frozen_<:< expected1 - } + + override def adjustInfo(tp: Type, member: Symbol)(using Context): Type = + handleBackwardsCompat(tp, member, 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/cc/Setup.scala b/compiler/src/dotty/tools/dotc/cc/Setup.scala index f091142f07bc..6f34e3e416b4 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 @@ -385,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 _ => @@ -428,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 @@ -474,4 +392,97 @@ 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 sym = tp.typeSymbol + if sym.isClass then + !sym.isPureClass && sym != defn.AnyClass + else + 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 => + 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/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/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index e2afe906a9c4..75bf3af1d166 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") @@ -1028,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 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") @@ -1386,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/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/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/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/core/tasty/TreeUnpickler.scala b/compiler/src/dotty/tools/dotc/core/tasty/TreeUnpickler.scala index c178485d924b..804a1f8244f3 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.CaptureCheckedAnnot)) 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/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/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/compiler/src/dotty/tools/dotc/transform/Pickler.scala b/compiler/src/dotty/tools/dotc/transform/Pickler.scala index d69076107d48..36042e6624eb 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,24 @@ 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 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 diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index 1d9493e6b1f7..5fa2ead7f3a3 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.CaptureCheckedAnnot then + if Feature.ccEnabled then + sym.addAnnotation(Annotation(defn.CaptureCheckedAnnot, 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/transform/Recheck.scala b/compiler/src/dotty/tools/dotc/transform/Recheck.scala index 7ebb8dd17045..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) @@ -253,13 +252,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 +276,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 => 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), 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/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index 597ab3a48c12..74ad3489952b 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 `member` and `other` before checking conformance. + * Overridden in capture checking to handle non-capture checked classes leniently. + */ + def adjustInfo(tp: Type, member: 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,14 +357,18 @@ 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. */ 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) = self.memberInfo(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)}") @@ -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() 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 _ => 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/library/src/scala/annotation/internal/CaptureChecked.scala b/library/src/scala/annotation/internal/CaptureChecked.scala new file mode 100644 index 000000000000..0bc6d628a542 --- /dev/null +++ b/library/src/scala/annotation/internal/CaptureChecked.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 CaptureChecked extends StaticAnnotation + diff --git a/library/src/scala/annotation/internal/WithPureFuns.scala b/library/src/scala/annotation/internal/WithPureFuns.scala index f0fc45c7f584..979dd7c405cc 100644 --- a/library/src/scala/annotation/internal/WithPureFuns.scala +++ b/library/src/scala/annotation/internal/WithPureFuns.scala @@ -1,9 +1,8 @@ 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. */ -@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/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/project/MiMaFilters.scala b/project/MiMaFilters.scala index 98dbf4859d92..df4aca72c9a7 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.CaptureChecked"), // end of New experimental features in 3.3.X ) val TastyCore: Seq[ProblemFilter] = Seq( 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 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 ---------------------------------------- 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. 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-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) + 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/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) 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} = ??? 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..74f93131b940 --- /dev/null +++ b/tests/pos-custom-args/captures/inlined-closure.scala @@ -0,0 +1,14 @@ +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 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 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/cc-backwards-compat/A.scala b/tests/pos/cc-backwards-compat/A.scala new file mode 100644 index 000000000000..90280bf3d1a0 --- /dev/null +++ b/tests/pos/cc-backwards-compat/A.scala @@ -0,0 +1,5 @@ +package p +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 new file mode 100644 index 000000000000..e2f0775a058f --- /dev/null +++ b/tests/pos/cc-backwards-compat/Iter.scala @@ -0,0 +1,12 @@ +package p +import language.experimental.captureChecking + +class Iter: + self: Iter^ => + +def test(it: Iter^) = + 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) 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/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/stdlib/IterableOnce.scala b/tests/pos/stdlib/IterableOnce.scala new file mode 120000 index 000000000000..0022157fb0e7 --- /dev/null +++ b/tests/pos/stdlib/IterableOnce.scala @@ -0,0 +1 @@ +collection/IterableOnce.scala \ No newline at end of file diff --git a/tests/pos/stdlib/Iterator.scala b/tests/pos/stdlib/Iterator.scala new file mode 120000 index 000000000000..9f998ff25601 --- /dev/null +++ b/tests/pos/stdlib/Iterator.scala @@ -0,0 +1 @@ +collection/Iterator.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/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/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/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/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/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") + } +} 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/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/Iterable.scala b/tests/pos/stdlib/collection/Iterable.scala new file mode 100644 index 000000000000..85c0debc6685 --- /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, this} = new LeftPartitionMapped(this, f) + val right: View[A2]^{f, this} = 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]^{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)) + } + + /** 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]^{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)) + } + + /** 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/stdlib/collection/IterableOnce.scala b/tests/pos/stdlib/collection/IterableOnce.scala new file mode 100644 index 000000000000..f75eab54a89b --- /dev/null +++ b/tests/pos/stdlib/collection/IterableOnce.scala @@ -0,0 +1,1364 @@ +/* + * 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 +import language.experimental.captureChecking + +/** + * 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 { + this: IterableOnce[A]^ => + + /** Iterator can be used only once */ + def iterator: Iterator[A]^{this} + + /** 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[_]^{this}](implicit shape: StepperShape[A, S]): S = { + import convert.impl._ + 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]]) + 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]^{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) + + @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]^{f} = 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]^{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]^{f} = 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 + + import IterableOnceOps.Maximized + + /** 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]^{this, op} + + /** 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^{this, p} + + /** 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(p: A => Boolean): C^{this, p} + + /** 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^{this} + + /** 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} + + /** 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^{this} + + /** 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^{this, p} + + /** 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^{this} + + /** 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]^{this, f} + + /** 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]^{this, f} + + /** 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]^{this} + + /** 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]^{this, pf} + + /** 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)]^{this} + + /** 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^{this, p}, C^{this, p}) + + /** 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^{this}, C^{this}) = { + 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^{this, f} + + /////////////////////////////////////////////////////////////// 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 + } + + /** 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]^{this} = 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 + } +} + +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/stdlib/collection/Iterator.scala b/tests/pos/stdlib/collection/Iterator.scala new file mode 100644 index 000000000000..4903a1dae0a6 --- /dev/null +++ b/tests/pos/stdlib/collection/Iterator.scala @@ -0,0 +1,1308 @@ +/* + * 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 +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 + * 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: Iterator[A]^ => + + /** 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: Iterator[A]^{this} = 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]^{this} = 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]^{this} = 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]^{this, p}, Iterator[A]^{this, p}) = { + 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]^{this} = + 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]^{this} = + new GroupedIterator[B](self, size, step) + + 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]^{self, op} = + 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]^{this} = 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]^{this, p} = filterImpl(p, isFlipped = false) + + def filterNot(p: A => Boolean): Iterator[A]^{this, p} = filterImpl(p, isFlipped = true) + + 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 + + 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]^{this, p} = filter(p) + + 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 = _ + + // 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]^{this} = 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]^{this} = 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]^{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]^{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 + + 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]^{this} = + flatMap[B](ev) + + 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) + + def take(n: Int): Iterator[A]^{this} = sliceIterator(0, n max 0) + + 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} = 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]^{this} = sliceIterator(n, -1) + + 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 + 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]^{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 + * 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]^{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]^{this} = { + 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)]^{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)]^{this, that} = 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)]^{this} = 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]^{this}, Iterator[A]^{this}) = { + 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]^{this, patchElems} = + 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]^{this, f} = 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} = 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]^{elem} = 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]^{f} = 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]^{f} = 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]^{f} = 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]^{elem} = 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](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 + + 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]^{this, that} = { + 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]^{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[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]^{underlying} = { + 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]: + this: Iterator[A]^ => 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/Map.scala b/tests/pos/stdlib/collection/Map.scala new file mode 100644 index 000000000000..ef4f915ea573 --- /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} = 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]^{p, q} = + 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/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] 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() + } +} 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/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] 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/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/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/Buffer.scala b/tests/pos/stdlib/collection/mutable/Buffer.scala new file mode 100644 index 000000000000..0a70c75bac0c --- /dev/null +++ b/tests/pos/stdlib/collection/mutable/Buffer.scala @@ -0,0 +1,233 @@ +/* + * 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 +import language.experimental.captureChecking + + +/** 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/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/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/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/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/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/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/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/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_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_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 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_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 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 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 diff --git a/tests/pos/stdlib/runtime/PStatics.scala b/tests/pos/stdlib/runtime/PStatics.scala new file mode 100644 index 000000000000..788a56962855 --- /dev/null +++ b/tests/pos/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/stdlib/runtime_PStatics.scala b/tests/pos/stdlib/runtime_PStatics.scala new file mode 120000 index 000000000000..5937e46f6765 --- /dev/null +++ b/tests/pos/stdlib/runtime_PStatics.scala @@ -0,0 +1 @@ +runtime/PStatics.scala \ No newline at end of file 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",