diff --git a/__tests__/List.ts b/__tests__/List.ts index f1a8dd7013..f91f7a859f 100644 --- a/__tests__/List.ts +++ b/__tests__/List.ts @@ -642,12 +642,36 @@ describe('List', () => { }); it('concat works like Array.prototype.concat', () => { - const v1 = List.of(1, 2, 3); + const v1 = List([1, 2, 3]); const v2 = v1.concat(4, List([ 5, 6 ]), [7, 8], Seq([ 9, 10 ]), Set.of(11, 12), null as any); expect(v1.toArray()).toEqual([1, 2, 3]); expect(v2.toArray()).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, null]); }); + it('concat returns self when no changes', () => { + const v1 = List([1, 2, 3]); + expect(v1.concat([])).toBe(v1); + }); + + it('concat returns arg when concat to empty', () => { + const v1 = List([1, 2, 3]); + expect(List().concat(v1)).toBe(v1); + }); + + it('concats a single value', () => { + const v1 = List([1, 2, 3]); + expect(v1.concat(4)).toEqual(List([1, 2, 3, 4])); + }); + + it('concat returns List-coerced arg when concat to empty', () => { + expect(List().concat([1, 2, 3])).toEqual(List([1, 2, 3])); + }); + + it('concat does not spread in string characters', () => { + const v1 = List([1, 2, 3]); + expect(v1.concat('abcdef')).toEqual(List([1, 2, 3, 'abcdef'])); + }); + it('allows chained mutations', () => { const v1 = List(); const v2 = v1.push(1); diff --git a/src/List.js b/src/List.js index 4825962903..fda05e20e6 100644 --- a/src/List.js +++ b/src/List.js @@ -21,7 +21,7 @@ import { } from './TrieUtils'; import { IndexedCollection } from './Collection'; import { MapPrototype } from './Map'; -import { Iterator, iteratorValue, iteratorDone } from './Iterator'; +import { hasIterator, Iterator, iteratorValue, iteratorDone } from './Iterator'; import assertNotInfinite from './utils/assertNotInfinite'; @@ -135,8 +135,28 @@ export class List extends IndexedCollection { // @pragma Composition - merge(/*...collections*/) { - return this.concat.apply(this, arguments); + concat(/*...collections*/) { + const seqs = []; + for (let i = 0; i < arguments.length; i++) { + const argument = arguments[i]; + const seq = IndexedCollection( + typeof argument !== 'string' && hasIterator(argument) + ? argument + : [argument] + ); + if (seq.size !== 0) { + seqs.push(seq); + } + } + if (seqs.length === 0) { + return this; + } + if (this.size === 0 && !this.__ownerID && seqs.length === 1) { + return this.constructor(seqs[0]); + } + return this.withMutations(list => { + seqs.forEach(seq => seq.forEach(value => list.push(value))); + }); } setSize(size) { @@ -215,6 +235,7 @@ const IS_LIST_SENTINEL = '@@__IMMUTABLE_LIST__@@'; export const ListPrototype = List.prototype; ListPrototype[IS_LIST_SENTINEL] = true; ListPrototype[DELETE] = ListPrototype.remove; +ListPrototype.merge = ListPrototype.concat; ListPrototype.setIn = MapPrototype.setIn; ListPrototype.deleteIn = ListPrototype.removeIn = MapPrototype.removeIn; ListPrototype.update = MapPrototype.update; diff --git a/src/Map.js b/src/Map.js index e354243da7..09e884ea7c 100644 --- a/src/Map.js +++ b/src/Map.js @@ -250,6 +250,7 @@ MapPrototype[IS_MAP_SENTINEL] = true; MapPrototype[DELETE] = MapPrototype.remove; MapPrototype.removeIn = MapPrototype.deleteIn; MapPrototype.removeAll = MapPrototype.deleteAll; +MapPrototype.concat = MapPrototype.merge; MapPrototype['@@transducer/init'] = MapPrototype.asMutable; MapPrototype['@@transducer/step'] = function(result, arr) { return result.set(arr[0], arr[1]); diff --git a/src/Set.js b/src/Set.js index f1bfd74f6c..d1658ede45 100644 --- a/src/Set.js +++ b/src/Set.js @@ -178,7 +178,7 @@ const IS_SET_SENTINEL = '@@__IMMUTABLE_SET__@@'; const SetPrototype = Set.prototype; SetPrototype[IS_SET_SENTINEL] = true; SetPrototype[DELETE] = SetPrototype.remove; -SetPrototype.merge = SetPrototype.union; +SetPrototype.merge = SetPrototype.concat = SetPrototype.union; SetPrototype.withMutations = MapPrototype.withMutations; SetPrototype.asMutable = MapPrototype.asMutable; SetPrototype.asImmutable = MapPrototype.asImmutable; diff --git a/type-definitions/Immutable.d.ts b/type-definitions/Immutable.d.ts index bf3de10a7d..e9900c67b4 100644 --- a/type-definitions/Immutable.d.ts +++ b/type-definitions/Immutable.d.ts @@ -787,7 +787,7 @@ declare module Immutable { /** * Returns a new List with other values or collections concatenated to this one. * - * Note: `concat` *cannot* be safely used in `withMutations`. + * Note: `concat` can be used in `withMutations`. * * @alias merge */ @@ -1223,8 +1223,13 @@ declare module Immutable { * ``` * * Note: `merge` can be used in `withMutations`. + * + * @alias concat */ - merge(...collections: Array | {[key: string]: V}>): this; + merge(...collections: Array>): Map; + merge(...collections: Array<{[key: string]: C}>): Map; + concat(...collections: Array>): Map; + concat(...collections: Array<{[key: string]: C}>): Map; /** * Like `merge()`, `mergeWith()` returns a new Map resulting from merging @@ -1512,12 +1517,6 @@ declare module Immutable { // Sequence algorithms - /** - * Returns a new Map with other collections concatenated to this one. - */ - concat(...collections: Array>): Map; - concat(...collections: Array<{[key: string]: C}>): Map; - /** * Returns a new Map with values passed through a * `mapper` function. @@ -1628,14 +1627,34 @@ declare module Immutable { */ readonly size: number; - // Sequence algorithms - /** - * Returns a new OrderedMap with other collections concatenated to this one. + * Returns a new OrderedMap resulting from merging the provided Collections + * (or JS objects) into this OrderedMap. In other words, this takes each + * entry of each collection and sets it on this OrderedMap. + * + * Note: Values provided to `merge` are shallowly converted before being + * merged. No nested values are altered. + * + * + * ```js + * const { OrderedMap } = require('immutable@4.0.0-rc.7') + * const one = OrderedMap({ a: 10, b: 20, c: 30 }) + * const two = OrderedMap({ b: 40, a: 50, d: 60 }) + * one.merge(two) // OrderedMap { "a": 50, "b": 40, "c": 30, "d": 60 } + * two.merge(one) // OrderedMap { "b": 20, "a": 10, "d": 60, "c": 30 } + * ``` + * + * Note: `merge` can be used in `withMutations`. + * + * @alias concat */ + merge(...collections: Array>): OrderedMap; + merge(...collections: Array<{[key: string]: C}>): OrderedMap; concat(...collections: Array>): OrderedMap; concat(...collections: Array<{[key: string]: C}>): OrderedMap; + // Sequence algorithms + /** * Returns a new OrderedMap with values passed through a * `mapper` function. @@ -1811,9 +1830,11 @@ declare module Immutable { * * Note: `union` can be used in `withMutations`. * @alias merge + * @alias concat */ - union(...collections: Array>): this; - merge(...collections: Array>): this; + union(...collections: Array>): Set; + merge(...collections: Array>): Set; + concat(...collections: Array>): Set; /** * Returns a Set which has removed any values not also contained @@ -1821,14 +1842,14 @@ declare module Immutable { * * Note: `intersect` can be used in `withMutations`. */ - intersect(...collections: Array | Array>): this; + intersect(...collections: Array>): this; /** * Returns a Set excluding any values contained within `collections`. * * Note: `subtract` can be used in `withMutations`. */ - subtract(...collections: Array | Array>): this; + subtract(...collections: Array>): this; // Transient changes @@ -1863,11 +1884,6 @@ declare module Immutable { // Sequence algorithms - /** - * Returns a new Set with other collections concatenated to this one. - */ - concat(...valuesOrCollections: Array | C>): Set; - /** * Returns a new Set with values passed through a * `mapper` function. @@ -1956,12 +1972,19 @@ declare module Immutable { */ readonly size: number; - // Sequence algorithms - /** - * Returns a new OrderedSet with other collections concatenated to this one. + * Returns an OrderedSet including any value from `collections` that does + * not already exist in this OrderedSet. + * + * Note: `union` can be used in `withMutations`. + * @alias merge + * @alias concat */ - concat(...valuesOrCollections: Array | C>): OrderedSet; + union(...collections: Array>): OrderedSet; + merge(...collections: Array>): OrderedSet; + concat(...collections: Array>): OrderedSet; + + // Sequence algorithms /** * Returns a new Set with values passed through a @@ -3013,7 +3036,7 @@ declare module Immutable { * All entries will be present in the resulting Seq, even if they * are duplicates. */ - concat(...valuesOrCollections: Array | C>): Seq.Set; + concat(...collections: Array>): Seq.Set; /** * Returns a new Seq.Set with values passed through a @@ -3717,7 +3740,7 @@ declare module Immutable { /** * Returns a new Collection with other collections concatenated to this one. */ - concat(...valuesOrCollections: Array | C>): Collection.Set; + concat(...collections: Array>): Collection.Set; /** * Returns a new Collection.Set with values passed through a diff --git a/type-definitions/immutable.js.flow b/type-definitions/immutable.js.flow index 1b2d814504..7c431bc2a0 100644 --- a/type-definitions/immutable.js.flow +++ b/type-definitions/immutable.js.flow @@ -401,7 +401,7 @@ declare class SetCollection<+T> extends Collection { @@iterator(): Iterator; toSeq(): SetSeq; - concat(...iters: Array | C>): SetCollection; + concat(...collections: Iterable[]): SetCollection; // `filter`, `map` and `flatMap` cannot be defined further up the hierarchy, // because the implementation for `KeyedCollection` allows the value type to @@ -619,7 +619,7 @@ declare class SetSeq<+T> extends Seq mixins SetCollection { // Override specialized return types - concat(...iters: Array | C>): SetSeq; + concat(...collections: Iterable[]): SetSeq; filter(predicate: typeof Boolean): SetSeq<$NonMaybeType>; filter( @@ -836,6 +836,9 @@ declare class Map extends KeyedCollection { merge( ...collections: (Iterable<[K_, V_]> | PlainObjInput)[] ): Map; + concat( + ...collections: (Iterable<[K_, V_]> | PlainObjInput)[] + ): Map; mergeWith( merger: (oldVal: V, newVal: W, key: K) => X, @@ -883,9 +886,6 @@ declare class Map extends KeyedCollection { flip(): Map; - concat(...iters: Array>): Map; - concat(...iters: Array>): Map; - filter(predicate: typeof Boolean): Map>; filter( predicate: (value: V, key: K, iter: this) => mixed, @@ -937,6 +937,9 @@ declare class OrderedMap extends Map { merge( ...collections: (Iterable<[K_, V_]> | PlainObjInput)[] ): OrderedMap; + concat( + ...collections: (Iterable<[K_, V_]> | PlainObjInput)[] + ): OrderedMap; mergeWith( merger: (oldVal: V, newVal: W, key: K) => X, @@ -984,9 +987,6 @@ declare class OrderedMap extends Map { flip(): OrderedMap; - concat(...iters: Array>): OrderedMap; - concat(...iters: Array>): OrderedMap; - filter(predicate: typeof Boolean): OrderedMap>; filter( predicate: (value: V, key: K, iter: this) => mixed, @@ -1038,6 +1038,7 @@ declare class Set<+T> extends SetCollection { clear(): this; union(...collections: Iterable[]): Set; merge(...collections: Iterable[]): Set; + concat(...collections: Iterable[]): Set; intersect(...collections: Iterable[]): Set; subtract(...collections: Iterable[]): this; @@ -1048,8 +1049,6 @@ declare class Set<+T> extends SetCollection { // Override specialized return types - concat(...iters: Array | C>): Set; - filter(predicate: typeof Boolean): Set<$NonMaybeType>; filter( predicate: (value: T, value: T, iter: this) => mixed, @@ -1087,10 +1086,9 @@ declare class OrderedSet<+T> extends Set { add(value: U): OrderedSet; union(...collections: Iterable[]): OrderedSet; merge(...collections: Iterable[]): OrderedSet; + concat(...collections: Iterable[]): OrderedSet; intersect(...collections: Iterable[]): OrderedSet; - concat(...iters: Array | C>): OrderedSet; - filter(predicate: typeof Boolean): OrderedSet<$NonMaybeType>; filter( predicate: (value: T, value: T, iter: this) => mixed, diff --git a/type-definitions/ts-tests/map.ts b/type-definitions/ts-tests/map.ts index 3eef16d976..1a8732aae4 100644 --- a/type-definitions/ts-tests/map.ts +++ b/type-definitions/ts-tests/map.ts @@ -252,13 +252,13 @@ import { Map, List } from '../../'; // $ExpectType Map Map().merge({ a: 1 }); - // $ExpectError + // $ExpectType Map Map().merge({ a: { b: 1 } }); // $ExpectType Map Map().merge(Map()); - // $ExpectError + // $ExpectType Map Map().merge(Map()); // $ExpectType Map diff --git a/type-definitions/ts-tests/ordered-map.ts b/type-definitions/ts-tests/ordered-map.ts index 609c882015..5fcc6e2591 100644 --- a/type-definitions/ts-tests/ordered-map.ts +++ b/type-definitions/ts-tests/ordered-map.ts @@ -252,13 +252,13 @@ import { OrderedMap, List } from '../../'; // $ExpectType OrderedMap OrderedMap().merge({ a: 1 }); - // $ExpectError + // $ExpectType OrderedMap OrderedMap().merge({ a: { b: 1 } }); // $ExpectType OrderedMap OrderedMap().merge(OrderedMap()); - // $ExpectError + // $ExpectType OrderedMap OrderedMap().merge(OrderedMap()); // $ExpectType OrderedMap diff --git a/type-definitions/ts-tests/ordered-set.ts b/type-definitions/ts-tests/ordered-set.ts index ca0607f371..351cb0d5c6 100644 --- a/type-definitions/ts-tests/ordered-set.ts +++ b/type-definitions/ts-tests/ordered-set.ts @@ -155,7 +155,7 @@ import { OrderedSet, Map } from '../../'; // $ExpectType OrderedSet OrderedSet().union(OrderedSet()); - // $ExpectError + // $ExpectType OrderedSet OrderedSet().union(OrderedSet()); // $ExpectType OrderedSet @@ -170,7 +170,7 @@ import { OrderedSet, Map } from '../../'; // $ExpectType OrderedSet OrderedSet().merge(OrderedSet()); - // $ExpectError + // $ExpectType OrderedSet OrderedSet().merge(OrderedSet()); // $ExpectType OrderedSet diff --git a/type-definitions/ts-tests/set.ts b/type-definitions/ts-tests/set.ts index db120afa4d..6f9a33d8d5 100644 --- a/type-definitions/ts-tests/set.ts +++ b/type-definitions/ts-tests/set.ts @@ -155,7 +155,7 @@ import { Set, Map } from '../../'; // $ExpectType Set Set().union(Set()); - // $ExpectError + // $ExpectType Set Set().union(Set()); // $ExpectType Set @@ -170,7 +170,7 @@ import { Set, Map } from '../../'; // $ExpectType Set Set().merge(Set()); - // $ExpectError + // $ExpectType Set Set().merge(Set()); // $ExpectType Set