Add `separated`, `separatedList` and `separate` to iterables and lists. (#919)

diff --git a/pkgs/collection/CHANGELOG.md b/pkgs/collection/CHANGELOG.md
index 743fddf..6517421 100644
--- a/pkgs/collection/CHANGELOG.md
+++ b/pkgs/collection/CHANGELOG.md
@@ -1,5 +1,7 @@
 ## 1.20.0-wip
 
+- Adds `separated` and `separatedList` extension methods to `Iterable`.
+- Adds `separate` extension method to `List`
 - Add `IterableMapEntryExtension` for working on `Map` as a list of pairs, using
   `Map.entries`.
 - Explicitly mark `BoolList` as `abstract interface`
diff --git a/pkgs/collection/lib/src/iterable_extensions.dart b/pkgs/collection/lib/src/iterable_extensions.dart
index d9516e6..b3da5ea 100644
--- a/pkgs/collection/lib/src/iterable_extensions.dart
+++ b/pkgs/collection/lib/src/iterable_extensions.dart
@@ -4,6 +4,7 @@
 
 import 'dart:math' show Random;
 
+import '../collection.dart' show DelegatingIterable;
 import 'algorithms.dart';
 import 'functions.dart' as functions;
 import 'utils.dart';
@@ -56,6 +57,99 @@
     return chosen;
   }
 
+  /// The elements of this iterable separated by [separator].
+  ///
+  /// Emits the same elements as this iterable, and also emits
+  /// a [separator] between any two of those elements.
+  ///
+  /// If [before] is set to `true`, a [separator] is also
+  /// emitted before the first element.
+  /// If [after] is set to `true`, a [separator] is also
+  /// emitted after the last element.
+  ///
+  /// If this iterable is empty, [before] and [after] have no effect.
+  ///
+  /// Example:
+  /// ```dart
+  /// print(([1, 2, 3] as Iterable<int>).separated(-1)); // (1, -1, 2, -1, 3)
+  /// print(([1] as Iterable<int>).separated(-1)); // (1)
+  /// print(([] as Iterable<int>).separated(-1)); // ()
+  ///
+  /// print(([1, 2, 3] as Iterable<int>).separated(
+  ///   -1,
+  ///   before: true,
+  /// )); // (-1, 1, -1, 2, -1, 3)
+  ///
+  /// print(([1] as Iterable<int>).separated(
+  ///   -1,
+  ///   before: true,
+  ///   after: true,
+  /// )); // (-1, 1, -1)
+  ///
+  /// print(([] as Iterable<int>).separated(
+  ///   -1,
+  ///   before: true,
+  ///   after: true,
+  /// )); // ()
+  /// ```
+  Iterable<T> separated(T separator,
+          {bool before = false, bool after = false}) =>
+      _SeparatedIterable<T>(this, separator, before, after);
+
+  /// Creates list with the elements of this iterable separated by [separator].
+  ///
+  /// Returns a new list which contains the same elements as this iterable,
+  /// with a [separator] between any two of those elements.
+  ///
+  /// If [before] is set to `true`, a [separator] is also
+  /// added before the first element.
+  /// If [after] is set to `true`, a [separator] is also
+  /// added after the last element.
+  ///
+  /// If this iterable is empty, [before] and [after] have no effect.
+  ///
+  /// Example:
+  /// ```dart
+  /// print([1, 2, 3].separatedList(-1)); // [1, -1, 2, -1, 3]
+  /// print([1].separatedList(-1)); // [1]
+  /// print([].separatedList(-1)); // []
+  ///
+  /// print([1, 2, 3].separatedList(
+  ///   -1,
+  ///   before: true,
+  /// )); // [-1, 1, -1, 2, -1, 3]
+  ///
+  /// print([1].separatedList(
+  ///   -1,
+  ///   before: true,
+  ///   after: true,
+  /// )); // [-1, 1, -1]
+  ///
+  /// print([].separatedList(
+  ///   -1,
+  ///   before: true,
+  ///   after: true,
+  /// )); // []
+  /// ```
+  List<T> separatedList(T separator,
+      {bool before = false, bool after = false}) {
+    var result = <T>[];
+    var iterator = this.iterator;
+    if (iterator.moveNext()) {
+      if (before) result.add(separator);
+      while (true) {
+        result.add(iterator.current);
+        if (iterator.moveNext()) {
+          result.add(separator);
+        } else {
+          break;
+        }
+      }
+      if (after) result.add(separator);
+    }
+    return result;
+  }
+
   /// The elements that do not satisfy [test].
   Iterable<T> whereNot(bool Function(T element) test) =>
       where((element) => !test(element));
@@ -1056,3 +1150,206 @@
         return result;
       };
 }
+
+/// Implementation of [IterableExtension.separated].
+///
+/// Optimizes direct accesses.
+class _SeparatedIterable<T> extends Iterable<T> {
+  final T _separator;
+  final Iterable<T> _elements;
+
+  static const int _afterFlag = 1 << 0;
+  static const int _beforeFlag = 1 << 1;
+
+  /// Two bit-flags, for whether the `before` and `after` arguments were `true`.
+  final int _flags;
+
+  _SeparatedIterable(this._elements, this._separator, bool before, bool after)
+      : _flags = (before ? _beforeFlag : 0) + (after ? _afterFlag : 0);
+
+  @override
+  bool get isEmpty => _elements.isEmpty;
+  @override
+  bool get isNotEmpty => _elements.isNotEmpty;
+  @override
+  int get length {
+    var length = _elements.length;
+    if (length != 0) {
+      length = length * 2 - 1 + (_flags & 1) + (_flags >> 1);
+    }
+    return length;
+  }
+
+  @override
+  T elementAt(int index) {
+    RangeError.checkNotNegative(index, 'index');
+    // Figure out which element must exist in [_elements] for this index
+    // to exist in the separated output.
+    var indexWithoutBefore = index - (_flags >> 1);
+    var elementIndex = indexWithoutBefore ~/ 2; // Rounds both -1 and 1 to 0.
+    if (indexWithoutBefore.isEven) {
+      // It's an element.
+      return _elements.elementAt(elementIndex);
+    }
+    // It's a separator after that element (or before the first element).
+    // Check if that element exists, unless the `_afterFlag` is set,
+    // in which case to check if the next element exists by adding 1
+    // to elementIndex.
+    // (But if `index` is zero, it's the before separator, so it should
+    // check that a first element exists.)
+    if (index != 0) {
+      assert(_afterFlag == 1);
+      elementIndex += (_flags ^ _afterFlag) & _afterFlag;
+    }
+    _elements.elementAt(elementIndex); // If throws, not an element.
+    return _separator;
+  }
+
+  @override
+  T get first {
+    if (_flags & _beforeFlag == 0) return _elements.first;
+    if (_elements.isNotEmpty) return _separator;
+    throw StateError('No element');
+  }
+
+  @override
+  T get last {
+    if (_flags & _afterFlag == 0) return _elements.last;
+    if (_elements.isNotEmpty) return _separator;
+    throw StateError('No element');
+  }
+
+  @override
+  Iterable<T> take(int count) {
+    if (count == 0) return Iterable<T>.empty();
+    var beforeCount = _flags >> 1;
+    if (count == 1) {
+      if (beforeCount == 0) {
+        return _elements.take(1);
+      }
+      // return Iterable<T>.value(_separator); // Why you no exist?!
+      return DelegatingIterable<T>([_separator]);
+    }
+    var countWithoutBefore = count - beforeCount;
+    var elementCount = (countWithoutBefore + 1) >> 1;
+    return _SeparatedIterable<T>(
+      _elements.take(elementCount),
+      _separator,
+      beforeCount != 0,
+      countWithoutBefore.isEven,
+    );
+  }
+
+  @override
+  Iterable<T> skip(int count) {
+    if (count == 0) return this;
+    var beforeCount = _flags >> 1;
+    var countWithoutBefore = count - beforeCount;
+    var hasAfter = _flags & _afterFlag != 0;
+    if (countWithoutBefore.isOdd && hasAfter) {
+      // Remainder could be just the final separator, which cannot
+      // be created by a `_SeparatedIterable`.
+      // (Unlike `take`, cannot see that without iterating.)
+      return super.skip(count);
+    }
+    // Starts or ends on an element, not a separator,
+    // so remainder cannot be a single separator.
+    var elementCount = (countWithoutBefore + 1) >> 1;
+    return _SeparatedIterable<T>(
+      elementCount == 0 ? _elements : _elements.skip(elementCount),
+      _separator,
+      countWithoutBefore.isOdd,
+      hasAfter,
+    );
+  }
+
+  @override
+  Iterator<T> get iterator =>
+      _SeparatedIterator<T>(_elements.iterator, _separator, _flags);
+}
+
+/// Iterator for [_SeparatedIterable].
+class _SeparatedIterator<T> implements Iterator<T> {
+  final T _separator;
+  final Iterator<T> _elements;
+
+  // Flags set in [_state].
+
+  /// Set if adding a separator after the last element.
+  ///
+  /// State never changes, just storing a boolean as a bit.
+  static const _noAddAfterFlag = 1 << 0;
+
+  // Set when the next element to emit is a separator.
+  //
+  // Otherwise the element to emit is [_elements.current].
+  static const _separatorFlag = 1 << 1;
+
+  // Set when next step should check if there is a next element.
+  //
+  // If there is no next element, iteration ends.
+  static const _ifHasNextFlag = 1 << 2;
+
+  /// Current state.
+  ///
+  /// A combination of the [_noAddAfterFlag], [_separatorFlag]
+  /// and [_ifHasNextFlag].
+  ///
+  /// Transitions:
+  /// * If `_ifHasNextFlag`:
+  ///    - if `!_elements.moveNext()`, then end.
+  ///      (No state change, next call will do the same).
+  ///    - otherwise continue.
+  /// * If `_separatorFlag`:
+  ///    - emit `_separator`,
+  ///    - clear `_separatorFlag` (next is an element),
+  ///    - toggle `_ifHasNextFlag`.
+  /// * else:
+  ///    - emit `_elements.current`,
+  ///    - set `_separatorFlag` (next will be a separator),
+  ///    - set `_ifHasNextFlag` if `_noAddAfterFlag` is set.
+  ///
+  /// Starts with `ifHasNextFlag` set,
+  /// with `_separatorFlag` set if the `before` parameter of the iterable
+  /// was `true`, and with `noAddAfterFlag` set if the `after` parameter
+  /// of the iterable was `false`.
+  int _state;
+
+  T? _current;
+
+  _SeparatedIterator(this._elements, this._separator, int flags)
+      : assert(_noAddAfterFlag == _SeparatedIterable._afterFlag),
+        assert(_separatorFlag == _SeparatedIterable._beforeFlag),
+        // `_separatorFlag` set if `_beforeFlag` was set.
+        // `_noAddAfterFlag` set if `_afterFlag` was not.
+        // `_ifHasNextFlag` always set at the start.
+        _state = (flags ^ _noAddAfterFlag) | _ifHasNextFlag;
+
+  @override
+  T get current => _current as T;
+
+  @override
+  bool moveNext() {
+    var state = _state;
+    if (state & _ifHasNextFlag == 0 || _elements.moveNext()) {
+      if (state & _separatorFlag != 0) {
+        _current = _separator;
+        // Next is not separator.
+        // Check if there is a next if this call didn't.
+        state ^= _separatorFlag | _ifHasNextFlag;
+      } else {
+        _current = _elements.current;
+        // Next is separator.
+        // Check if there is a next if not adding separator after last element.
+        state = (state & _noAddAfterFlag) * (_noAddAfterFlag | _ifHasNextFlag) +
+            _separatorFlag;
+      }
+      _state = state;
+      return true;
+    }
+    // Next call will check `_elements.moveNext()` again.
+    assert(state & _ifHasNextFlag != 0);
+    _current = null;
+    return false;
+  }
+}
diff --git a/pkgs/collection/lib/src/list_extensions.dart b/pkgs/collection/lib/src/list_extensions.dart
index a301a1e..85b5a28 100644
--- a/pkgs/collection/lib/src/list_extensions.dart
+++ b/pkgs/collection/lib/src/list_extensions.dart
@@ -3,12 +3,15 @@
 // BSD-style license that can be found in the LICENSE file.
 
 // Extension methods on common collection types.
+
 import 'dart:collection';
 import 'dart:math';
 
 import 'algorithms.dart';
 import 'algorithms.dart' as algorithms;
 import 'equality.dart';
+// For documentation only. Use docImport when reaching language version 3.8.
+import 'iterable_extensions.dart';
 import 'utils.dart';
 
 /// Various extensions on lists of arbitrary elements.
@@ -327,6 +330,169 @@
       yield slice(i, min(i + length, this.length));
     }
   }
+
+  /// The elements of this list separated by [separator] as a lazy list.
+  ///
+  /// Creates an unmodifiable list backed by this list, which has [separator]s
+  /// between, and optionally before and or/after, the elements of this list.
+  /// Changes to this list will be reflected in the returned list.
+  ///
+  /// If [before] is set to `true`, the returned list has a separator
+  /// before each element of this list.
+  /// If [after] is set to `true`, the returned list has a separator
+  /// after each element of this list.
+  /// The returned list always has a separator between two elements
+  /// of this list.
+  ///
+  /// If this iterable is empty, [before] and [after] have no effect.
+  ///
+  /// Compared to [IterableExtension.separatedList], this function
+  /// doesn't copy the elements into a new list, it creates a view
+  /// of the existing list with separators between the original elements.
+  /// To do that efficiently, the source must be a list, not just
+  /// an iterable.
+  ///
+  /// Example:
+  /// ```dart
+  /// print([1, 2, 3].separated(-1)); // [1, -1, 2, -1, 3]
+  /// print([1].separated(-1)); // [1]
+  /// print([].separated(-1)); // []
+  ///
+  /// print([1, 2, 3].separated(
+  ///   -1,
+  ///   before: true,
+  /// )); // [-1, 1, -1, 2, -1, 3]
+  ///
+  /// print([1].separated(
+  ///   -1,
+  ///   before: true,
+  ///   after: true,
+  /// )); // [-1, 1, -1]
+  ///
+  /// print([].separated(
+  ///   -1,
+  ///   before: true,
+  ///   after: true,
+  /// )); // []
+  /// ```
+  List<E> separated(E separator, {bool before = false, bool after = false}) =>
+      _SeparatedList<E>(this, separator, before, after);
+
+  /// Creates new list with the elements of this list separated by [separator].
+  ///
+  /// Returns a new list which contains the same elements as this list,
+  /// with a [separator] between any two of those elements.
+  ///
+  /// If [before] is set to `true`, a [separator] is also
+  /// added before the first element.
+  /// If [after] is set to `true`, a [separator] is also
+  /// added after the last element.
+  ///
+  /// If this list is empty, [before] and [after] have no effect.
+  ///
+  /// Example:
+  /// ```dart
+  /// print([1, 2, 3].separatedList(-1)); // [1, -1, 2, -1, 3]
+  /// print([1].separatedList(-1)); // [1]
+  /// print([].separatedList(-1)); // []
+  ///
+  /// print([1, 2, 3].separatedList(
+  ///   -1
+  ///   before: true,
+  /// )); // [-1, 1, -1, 2, -1, 3]
+  ///
+  /// print([1].separatedList(
+  ///   -1
+  ///   before: true,
+  ///   after: true,
+  /// )); // [-1, 1, -1]
+  ///
+  /// print([].separatedList(
+  ///   -1
+  ///   before: true,
+  ///   after: true,
+  /// )); // []
+  /// ```
+  List<E> separatedList(E separator,
+          {bool before = false, bool after = false}) =>
+      isEmpty
+          ? []
+          : [
+              if (!before) this[0],
+              for (var i = before ? 0 : 1; i < length; i++) ...[
+                separator,
+                this[i],
+              ],
+              if (after) separator
+            ];
+
+  /// Inserts [separator] between elements of this list.
+  ///
+  /// Afterwards, the list will contains all the original elements,
+  /// with a [separator] between any two of those elements.
+  ///
+  /// If [before] is set to `true`, a [separator] is also
+  /// inserted before the first element.
+  /// If [after] is set to `true`, a [separator] is also
+  /// added after the last element.
+  ///
+  /// If this list is empty, [before] and [after] have no effect.
+  ///
+  /// Example:
+  /// ```dart
+  /// print([1, 2, 3]..separate(-1)); // [1, -1, 2, -1, 3]
+  /// print([1]..separate(-1)); // [1]
+  /// print([]..separate(-1)); // []
+  ///
+  /// print([1, 2, 3]..separate(
+  ///   -1,
+  ///   before: true,
+  /// )); // [-1, 1, -1, 2, -1, 3]
+  ///
+  /// print([1]..separate(
+  ///   -1,
+  ///   before: true,
+  ///   after: true,
+  /// )); // [-1, 1, -1]
+  ///
+  /// print([]..separate(
+  ///   -1,
+  ///   before: true,
+  ///   after: true,
+  /// )); // []
+  /// ```
+  void separate(E separator, {bool before = false, bool after = false}) {
+    var length = this.length;
+    if (length == 0) return;
+    // New position of first element.
+    var newFirst = before ? 1 : 0;
+    // New position of last element.
+    var newLast = length * 2 - (newFirst ^ 1);
+
+    var splitIndex = length - newFirst;
+    var cursor = splitIndex >> 1;
+    if (splitIndex.isEven) {
+      add(this[cursor]);
+    }
+    cursor++;
+    while (this.length < newLast) {
+      add(separator);
+      add(this[cursor++]);
+    }
+    assert(cursor == length);
+    if (after) add(separator);
+
+    cursor = splitIndex >> 1;
+    if (splitIndex.isOdd) {
+      this[--length] = this[cursor];
+    }
+    cursor--;
+    for (var i = length; i > 1;) {
+      this[--i] = separator;
+      this[--i] = this[cursor--];
+    }
+    if (newFirst != 0) this[0] = separator;
+  }
 }
 
 /// Various extensions on lists of comparable elements.
@@ -570,3 +736,116 @@
     throw UnsupportedError('Cannot remove from a fixed-length list');
   }
 }
+
+/// A lazy separator mapper around a `List`.
+class _SeparatedList<E> extends ListBase<E> {
+  final E _separator;
+  final List<E> _elements;
+
+  static const int _lengthDeltaShift = 1;
+  static const int _lengthDeltaUnit = 1 << _lengthDeltaShift;
+  static const int _beforeFlag = 1 << 0;
+
+  /// Bit flags for before/after separator behavior.
+  ///
+  /// Bit 0: Have separator before first element.
+  /// Bit 1+: Number of separators before and after.
+  ///         One of 0, 1 or 2 depending on how many of `before` and `after`
+  ///         were true.
+  final int _flags;
+
+  _SeparatedList(this._elements, this._separator, bool before, bool after)
+      : _flags = (before ? (_beforeFlag + _lengthDeltaUnit) : 0) +
+            (after ? _lengthDeltaUnit : 0);
+
+  @override
+  bool get isEmpty => _elements.isEmpty;
+
+  @override
+  bool get isNotEmpty => _elements.isNotEmpty;
+
+  @override
+  int get length {
+    var length = _elements.length;
+    if (length != 0) length = length * 2 - 1 + (_flags >> _lengthDeltaShift);
+    return length;
+  }
+
+  @override
+  E operator [](int index) {
+    IndexError.check(index, length, indexable: this);
+    var indexWithoutBefore = index - (_flags & _beforeFlag);
+    return indexWithoutBefore.isEven
+        ? _elements[indexWithoutBefore >> 1]
+        : _separator;
+  }
+
+  @override
+  void operator []=(int index, E value) => _unmodifiable();
+
+  @override
+  set length(int newLength) => _unmodifiable();
+
+  @override
+  set first(E element) => _unmodifiable();
+
+  @override
+  set last(E element) => _unmodifiable();
+
+  @override
+  void setAll(int at, Iterable<E> iterable) => _unmodifiable();
+
+  @override
+  void add(E value) => _unmodifiable();
+
+  @override
+  void insert(int index, E element) => _unmodifiable();
+
+  @override
+  void insertAll(int at, Iterable<E> iterable) => _unmodifiable();
+
+  @override
+  void addAll(Iterable<E> iterable) => _unmodifiable();
+
+  @override
+  bool remove(Object? element) => _unmodifiable();
+
+  @override
+  void removeWhere(bool Function(E element) test) => _unmodifiable();
+
+  @override
+  void retainWhere(bool Function(E element) test) => _unmodifiable();
+
+  @override
+  void sort([Comparator<E>? compare]) => _unmodifiable();
+
+  @override
+  void shuffle([Random? random]) => _unmodifiable();
+
+  @override
+  void clear() => _unmodifiable();
+
+  @override
+  E removeAt(int index) => _unmodifiable();
+
+  @override
+  E removeLast() => _unmodifiable();
+
+  @override
+  void setRange(int start, int end, Iterable<E> iterable,
+          [int skipCount = 0]) =>
+      _unmodifiable();
+
+  @override
+  void removeRange(int start, int end) => _unmodifiable();
+
+  @override
+  void replaceRange(int start, int end, Iterable<E> iterable) =>
+      _unmodifiable();
+
+  @override
+  void fillRange(int start, int end, [E? fillValue]) => _unmodifiable();
+
+  static Never _unmodifiable() =>
+      throw UnsupportedError('Cannot modify lazily separated list');
+}
diff --git a/pkgs/collection/test/separate_extensions_test.dart b/pkgs/collection/test/separate_extensions_test.dart
new file mode 100644
index 0000000..55d2a12
--- /dev/null
+++ b/pkgs/collection/test/separate_extensions_test.dart
@@ -0,0 +1,424 @@
+// Copyright (c) 2025, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:collection/collection.dart';
+import 'package:test/test.dart';
+
+void main() {
+  group('Iterable.separated', () {
+    group('empty', () {
+      test('', () {
+        expectIterable(iterable(<int>[]).separated(42), <int>[]);
+      });
+      test('before', () {
+        expectIterable(iterable(<int>[]).separated(42, before: true), <int>[]);
+      });
+      test('after', () {
+        expectIterable(iterable(<int>[]).separated(42, after: true), <int>[]);
+      });
+      test('before and after', () {
+        expectIterable(
+            iterable(<int>[]).separated(42, before: true, after: true),
+            <int>[]);
+      });
+      test('List', () {
+        var separatedList = iterable(<int>[]).separated(42);
+        expect(separatedList, isNot(isA<List>()));
+        expectIterable(separatedList, <int>[]);
+      });
+    });
+    group('Singleton', () {
+      test('', () {
+        expectIterable(iterable(<int>[1]).separated(42), [1]);
+      });
+      test('before', () {
+        expectIterable(iterable(<int>[1]).separated(42, before: true), [42, 1]);
+      });
+      test('after', () {
+        expectIterable(iterable(<int>[1]).separated(42, after: true), [1, 42]);
+      });
+      test('before and after', () {
+        expectIterable(
+            iterable(<int>[1]).separated(42, before: true, after: true),
+            [42, 1, 42]);
+      });
+      test('List', () {
+        var separatedList = iterable(<int>[1]).separated(42);
+        expect(separatedList, isNot(isA<List>()));
+        expectIterable(separatedList, [1]);
+      });
+    });
+    group('Multiple', () {
+      test('', () {
+        expectIterable(
+            iterable(<int>[1, 2, 3]).separated(42), [1, 42, 2, 42, 3]);
+      });
+      test('before', () {
+        expectIterable(iterable(<int>[1, 2, 3]).separated(42, before: true),
+            [42, 1, 42, 2, 42, 3]);
+      });
+      test('after', () {
+        expectIterable(iterable(<int>[1, 2, 3]).separated(42, after: true),
+            [1, 42, 2, 42, 3, 42]);
+      });
+      test('before and after', () {
+        expectIterable(
+            iterable(<int>[1, 2, 3]).separated(42, before: true, after: true),
+            [42, 1, 42, 2, 42, 3, 42]);
+      });
+    });
+    test('nulls', () {
+      expectIterable(iterable<int?>([null, 2, null]).separated(null),
+          [null, null, 2, null, null]);
+    });
+    test('upcast receiver', () {
+      var source = <int>[1, 2, 3];
+      expectIterable(
+          iterable<num>(source).separated(3.14), [1, 3.14, 2, 3.14, 3]);
+    });
+  });
+
+  // ------------------------------------------------------------------
+  group('List.separated', () {
+    group('empty', () {
+      test('', () {
+        expectUnmodifiableList(<int>[].separated(42), <int>[], 0);
+      });
+      test('before', () {
+        expectUnmodifiableList(<int>[].separated(42, before: true), <int>[], 0);
+      });
+      test('after', () {
+        expectUnmodifiableList(<int>[].separated(42, after: true), <int>[], 0);
+      });
+      test('before and after', () {
+        expectUnmodifiableList(
+            <int>[].separated(42, before: true, after: true), <int>[], 0);
+      });
+      test('List', () {
+        var separatedList = <int>[].separated(42);
+        expect(separatedList, isA<List>());
+        expectUnmodifiableList(separatedList, <int>[], 0);
+      });
+    });
+    group('Singleton', () {
+      test('', () {
+        expectUnmodifiableList(<int>[1].separated(42), [1], 0);
+      });
+      test('before', () {
+        expectUnmodifiableList(
+            <int>[1].separated(42, before: true), [42, 1], 0);
+      });
+      test('after', () {
+        expectUnmodifiableList(<int>[1].separated(42, after: true), [1, 42], 0);
+      });
+      test('before and after', () {
+        expectUnmodifiableList(
+            <int>[1].separated(42, before: true, after: true), [42, 1, 42], 0);
+      });
+      test('List', () {
+        var separatedList = <int>[1].separated(42);
+        expect(separatedList, isA<List>());
+        expectUnmodifiableList(separatedList, [1], 0);
+      });
+    });
+    group('Multiple', () {
+      test('', () {
+        expectUnmodifiableList(
+            <int>[1, 2, 3].separated(42), [1, 42, 2, 42, 3], 0);
+      });
+      test('before', () {
+        expectUnmodifiableList(<int>[1, 2, 3].separated(42, before: true),
+            [42, 1, 42, 2, 42, 3], 0);
+      });
+      test('after', () {
+        expectUnmodifiableList(<int>[1, 2, 3].separated(42, after: true),
+            [1, 42, 2, 42, 3, 42], 0);
+      });
+      test('before and after', () {
+        expectUnmodifiableList(
+            <int>[1, 2, 3].separated(42, before: true, after: true),
+            [42, 1, 42, 2, 42, 3, 42],
+            0);
+      });
+      test('List', () {
+        var separatedList = <int>[1, 2, 3].separated(42);
+        expect(separatedList, isA<List>());
+        expectUnmodifiableList(separatedList, [1, 42, 2, 42, 3], 0);
+      });
+    });
+    test('nulls', () {
+      expectUnmodifiableList(
+          [null, 2, null].separated(null), [null, null, 2, null, null], 0);
+    });
+    test('upcast receiver', () {
+      var source = <int>[1, 2, 3];
+      expectUnmodifiableList(
+          (source as List<num>).separated(3.14), [1, 3.14, 2, 3.14, 3], 0);
+    });
+  });
+
+  // ------------------------------------------------------------------
+  group('Iterable.separatedList', () {
+    group('empty', () {
+      test('', () {
+        expect(iterable(<int>[]).separatedList(42), <int>[]);
+      });
+      test('before', () {
+        expect(iterable(<int>[]).separatedList(42, before: true), <int>[]);
+      });
+      test('after', () {
+        expect(iterable(<int>[]).separatedList(42, after: true), <int>[]);
+      });
+      test('before and after', () {
+        expect(iterable(<int>[]).separatedList(42, before: true, after: true),
+            <int>[]);
+      });
+    });
+    group('Singleton', () {
+      test('', () {
+        expect(iterable(<int>[1]).separatedList(42), [1]);
+      });
+      test('before', () {
+        expect(iterable(<int>[1]).separatedList(42, before: true), [42, 1]);
+      });
+      test('after', () {
+        expect(iterable(<int>[1]).separatedList(42, after: true), [1, 42]);
+      });
+      test('before and after', () {
+        expect(iterable(<int>[1]).separatedList(42, before: true, after: true),
+            [42, 1, 42]);
+      });
+    });
+    group('Multiple', () {
+      test('', () {
+        expect(iterable(<int>[1, 2, 3]).separatedList(42), [1, 42, 2, 42, 3]);
+      });
+      test('before', () {
+        expect(iterable(<int>[1, 2, 3]).separatedList(42, before: true),
+            [42, 1, 42, 2, 42, 3]);
+      });
+      test('after', () {
+        expect(iterable(<int>[1, 2, 3]).separatedList(42, after: true),
+            [1, 42, 2, 42, 3, 42]);
+      });
+      test('before and after', () {
+        expect(
+            iterable(<int>[1, 2, 3])
+                .separatedList(42, before: true, after: true),
+            [42, 1, 42, 2, 42, 3, 42]);
+      });
+    });
+    test('nulls', () {
+      expect(iterable<int?>([null, 2, null]).separatedList(null),
+          [null, null, 2, null, null]);
+    });
+    test('upcast receiver', () {
+      var source = <int>[1, 2, 3];
+      expect(iterable<num>(source).separatedList(3.14), [1, 3.14, 2, 3.14, 3]);
+    });
+  });
+
+  // ------------------------------------------------------------------
+  group('List.separatedList', () {
+    group('empty', () {
+      test('', () {
+        expect(<int>[].separatedList(42), <int>[]);
+      });
+      test('before', () {
+        expect(<int>[].separatedList(42, before: true), <int>[]);
+      });
+      test('after', () {
+        expect(<int>[].separatedList(42, after: true), <int>[]);
+      });
+      test('before and after', () {
+        expect(<int>[].separatedList(42, before: true, after: true), <int>[]);
+      });
+    });
+    group('Singleton', () {
+      test('', () {
+        expect(<int>[1].separatedList(42), [1]);
+      });
+      test('before', () {
+        expect(<int>[1].separatedList(42, before: true), [42, 1]);
+      });
+      test('after', () {
+        expect(<int>[1].separatedList(42, after: true), [1, 42]);
+      });
+      test('before and after', () {
+        expect(
+            <int>[1].separatedList(42, before: true, after: true), [42, 1, 42]);
+      });
+    });
+    group('Multiple', () {
+      test('', () {
+        expect(<int>[1, 2, 3].separatedList(42), [1, 42, 2, 42, 3]);
+      });
+      test('before', () {
+        expect(<int>[1, 2, 3].separatedList(42, before: true),
+            [42, 1, 42, 2, 42, 3]);
+      });
+      test('after', () {
+        expect(<int>[1, 2, 3].separatedList(42, after: true),
+            [1, 42, 2, 42, 3, 42]);
+      });
+      test('before and after', () {
+        expect(<int>[1, 2, 3].separatedList(42, before: true, after: true),
+            [42, 1, 42, 2, 42, 3, 42]);
+      });
+    });
+    test('nulls', () {
+      expect([null, 2, null].separatedList(null), [null, null, 2, null, null]);
+    });
+    test('upcast receiver', () {
+      var source = <int>[1, 2, 3];
+      expect((source as List<num>).separatedList(3.14), [1, 3.14, 2, 3.14, 3]);
+    });
+  });
+
+  // ------------------------------------------------------------------
+  group('List.separate', () {
+    group('empty', () {
+      test('', () {
+        expectList(<int>[]..separate(42), <int>[]);
+      });
+      test('before', () {
+        expectList(<int>[]..separate(42, before: true), <int>[]);
+      });
+      test('after', () {
+        expectList(<int>[]..separate(42, after: true), <int>[]);
+      });
+      test('before and after', () {
+        expectList(<int>[]..separate(42, before: true, after: true), <int>[]);
+      });
+    });
+    group('Singleton', () {
+      test('', () {
+        expectList(<int>[1]..separate(42), [1]);
+      });
+      test('before', () {
+        expectList(<int>[1]..separate(42, before: true), [42, 1]);
+      });
+      test('after', () {
+        expectList(<int>[1]..separate(42, after: true), [1, 42]);
+      });
+      test('before and after', () {
+        expectList(
+            <int>[1]..separate(42, before: true, after: true), [42, 1, 42]);
+      });
+    });
+    group('Multiple', () {
+      group('odd length', () {
+        test('', () {
+          expectList(<int>[1, 2, 3]..separate(42), [1, 42, 2, 42, 3]);
+        });
+        test('before', () {
+          expectList(<int>[1, 2, 3]..separate(42, before: true),
+              [42, 1, 42, 2, 42, 3]);
+        });
+        test('after', () {
+          expectList(
+              <int>[1, 2, 3]..separate(42, after: true), [1, 42, 2, 42, 3, 42]);
+        });
+        test('before and after', () {
+          expectList(<int>[1, 2, 3]..separate(42, before: true, after: true),
+              [42, 1, 42, 2, 42, 3, 42]);
+        });
+      });
+      group('even length', () {
+        test('', () {
+          expectList(<int>[1, 2, 3, 4]..separate(42), [1, 42, 2, 42, 3, 42, 4]);
+        });
+        test('before', () {
+          expectList(<int>[1, 2, 3, 4]..separate(42, before: true),
+              [42, 1, 42, 2, 42, 3, 42, 4]);
+        });
+        test('after', () {
+          expectList(<int>[1, 2, 3, 4]..separate(42, after: true),
+              [1, 42, 2, 42, 3, 42, 4, 42]);
+        });
+        test('before and after', () {
+          expectList(<int>[1, 2, 3, 4]..separate(42, before: true, after: true),
+              [42, 1, 42, 2, 42, 3, 42, 4, 42]);
+        });
+      });
+    });
+    test('nulls', () {
+      expectList([null, 2, null]..separate(null), [null, null, 2, null, null]);
+    });
+    test('upcast receiver throws', () {
+      // Modifying the list is a contravariant operation,
+      // throws if separator is not valid.
+      var source = <int>[1, 2, 3];
+      expect(() => (source as List<num>).separate(3.14),
+          throwsA(isA<TypeError>()));
+    });
+    test('upcast receiver succeeds if separator valid', () {
+      // Modifying the list is a contravariant operation,
+      // succeeds if the separator is a valid value.
+      // (All operations are read/write with existing elements
+      // or the separator, which works when upcast if values are valid.)
+      var source = <int>[1, 2, 3];
+      expectList((source as List<num>)..separate(42), [1, 42, 2, 42, 3]);
+    });
+  });
+}
+
+/// Creates a plain iterable not implementing any other class.
+Iterable<T> iterable<T>(Iterable<T> values) sync* {
+  yield* values;
+}
+
+void expectIterable<T>(Iterable<T> actual, List<T> expected) {
+  expect(actual, expected); // Elements are correct, uses `iterator`.
+
+  // Specialized members should work.
+  expect(actual.length, expected.length);
+  for (var i = 0; i < expected.length; i++) {
+    expect(actual.isEmpty, expected.isEmpty);
+    expect(actual.isNotEmpty, expected.isNotEmpty);
+    expect(actual.elementAt(i), expected[i]);
+    expect(actual.skip(i), expected.sublist(i));
+    expect(actual.take(i), expected.sublist(0, i));
+  }
+  expect(() => actual.elementAt(actual.length), throwsRangeError);
+  expect(() => actual.elementAt(-1), throwsRangeError);
+
+  if (expected.isNotEmpty) {
+    expect(actual.first, expected.first, reason: 'first');
+    expect(actual.last, expected.last, reason: 'last');
+  } else {
+    expect(() => actual.first, throwsStateError, reason: 'first');
+    expect(() => actual.last, throwsStateError, reason: 'last');
+  }
+}
+
+void expectList<T>(List<T> actual, List<T> expected) {
+  expectIterable(actual, expected);
+
+  for (var i = 0; i < expected.length; i++) {
+    expect(actual[i], expected[i]);
+  }
+  expect(() => actual[actual.length], throwsRangeError);
+  expect(() => actual[-1], throwsRangeError);
+}
+
+void expectUnmodifiableList<T>(List<T> actual, List<T> expected, T value) {
+  expectList(actual, expected);
+
+  expect(() => actual.length = 0, throwsUnsupportedError);
+  expect(() => actual.add(value), throwsUnsupportedError);
+  expect(() => actual.addAll([]), throwsUnsupportedError);
+  expect(() => actual.clear(), throwsUnsupportedError);
+  expect(() => actual.fillRange(0, 0, value), throwsUnsupportedError);
+  expect(() => actual.remove(0), throwsUnsupportedError);
+  expect(() => actual.removeAt(0), throwsUnsupportedError);
+  expect(() => actual.removeLast(), throwsUnsupportedError);
+  expect(() => actual.removeRange(0, 0), throwsUnsupportedError);
+  expect(() => actual.removeWhere((_) => false), throwsUnsupportedError);
+  expect(() => actual.replaceRange(0, 0, []), throwsUnsupportedError);
+  expect(() => actual.retainWhere((_) => true), throwsUnsupportedError);
+  expect(() => actual.setAll(0, []), throwsUnsupportedError);
+  expect(() => actual.setRange(0, 0, []), throwsUnsupportedError);
+  expect(() => actual.sort((a, b) => 0), throwsUnsupportedError);
+}