deranged 0.1.0 copy "deranged: ^0.1.0" to clipboard
deranged: ^0.1.0 copied to clipboard

Generic range and progression types, inspired by Rust and Kotlin

Changelog #

All notable changes to this project will be documented in this file.

This project adheres to Semantic Versioning.

0.1.0 · 2026-08-11 #

⚠️ BREAKING CHANGES #

RangeTo changed meaning. Naming now follows one convention throughout: until means an exclusive end, to means an inclusive end, in class names as well as method names. Previously, RangeTo(5) was ..<5, while x.rangeTo(5) was x..=5, i.e., to meant opposite things depending on where you read it. (4561e4a)

Old New Meaning
RangeTo RangeUntil ..<x
RangeToInclusive RangeTo ..=x
IntRangeTo IntRangeUntil ..<x
DoubleRangeTo DoubleRangeUntil ..<x
DoubleRangeToInclusive DoubleRangeTo ..=x

⚠️ RangeTo and DoubleRangeTo are reused rather than retired, so existing code that names them still compiles but now builds an inclusive range. Search your code for RangeTo before upgrading. The x.rangeUntil(…)/x.rangeTo(…) methods are unchanged.

Matching factory renames on RangeBounds: RangeBounds.to(…)RangeBounds.until(…) and RangeBounds.toInclusive(…)RangeBounds.to(…). (4561e4a, 41995a3)

Other breaking changes:

  • Every extension declaration was renamed from a …Extension suffix to a Deranged… prefix: IntExtensionDerangedInt, ComparableExtensionDerangedComparable, RangeOfStepExtensionDerangedRangeOfStep, and so on. (715a4bb)
  • Step was split into Step and StepUnlimited, and both became mixins instead of an abstract interface class. Step's stepping methods return null when the step can't be taken, while StepUnlimited (for types like int that can always step) narrows them to non-nullable returns. (2456dad, cd171ce)
  • Progression.end was renamed to Progression.endInclusive. The name said exclusive while length, last, contains and toString all treated it as inclusive. Affects IntProgression and StepProgression. (4561e4a)
  • The bound-conversion getters on RangeBounds dropped their As infix: startAsInclusivestartInclusive, startAsExclusivestartExclusive, endAsInclusiveendInclusive, endAsExclusiveendExclusive. They now match the names the concrete range classes already used. (eabe3e2)
  • IntRange no longer implements IntProgression. The two used different == implementations, which made equality asymmetric (IntProgression(0, 3, 1) == IntRange(0, 3) was true, the reverse false). IntRange.step was removed along with it; use IntRange.stepBy(…) to get an IntProgression. (9e25702)
  • IntRangeUntil.contains(…) (formerly IntRangeTo) now excludes its end, matching its exclusive endBound and its ..<end representation. (f5cba63)
  • DoubleRange.contains(…) now excludes its end, matching its exclusive endBound. (f5cba63)
  • double.rangeTo(…) now returns a DoubleRangeInclusive instead of the exclusive DoubleRange, matching its documented inclusive semantics. (f5cba63)
  • The endExclusive getters on the two unbounded-start double ranges were removed – they subtracted 1 from a double end, which is meaningless. (f5cba63)
  • IntRangeFrom.length now throws an UnsupportedError instead of looping forever. (f5cba63)
  • Removed rangeToWithLength(…) from both DerangedStep and DerangedStepUnlimited. A range's length is only end - start when the end is exclusive, so a closed range built from a length contained length + 1 values – the name did not match the result. Use rangeUntilWithLength(…), followed by .inclusive if you need a RangeInclusive. (9d9c323)
  • Renamed int.rangeWithLength(…) to int.rangeUntilWithLength(…), matching DerangedStep and making the exclusive end explicit. (bdfb268)
  • RangeInclusive.operator | was the smallest range covering both operands, bridging any gap between them. | is now the true union on all of RangeBounds and returns a RangeSet, so gaps survive. The old behavior moved to RangeBounds.span(…), which returns an AnyRange. ⚠️ Both spellings still compile where the result is only passed on, so check any | on ranges when upgrading. (7a923c6, f199321)
  • Iterable<RangeInclusive>.union was renamed to .span, matching the above, and still returns a RangeInclusive?. For the true union, use .asRangeSet. (f199321)
  • RangeBounds and RangeSet now share a RangeLike base class, which carries the set algebra. |, &, -, ~, contains(…), containsAll(…), intersects(…), isEmpty, isFull, bounds, span(…), asRangeSet, mapBounds(…), and castBounds(…) all live there, so they accept and mix ranges and sets freely: ~someRange, range | set, and set & range all work now. (f199321)
  • RangeBounds.isUnbounded was renamed to isFull. It means "describes every value", which for a set isn't the same as being unbounded: {..<0, 5..} has no bound in either direction yet is missing everything in between. The old name also collided with Bound.isUnbounded, which is about a single bound. (f199321)
  • RangeBounds.containsRange(…) and RangeSet.containsRange(…) were renamed to containsAll(…), since they now accept a set as well as a range. RangeBounds.containsAll(…) also returns true for an empty argument, which the old containsRange(…) got wrong. (f199321)
  • RangeInclusive.operator & returned a precise RangeInclusive?; & now comes from RangeLike and returns a RangeSet. The precise pairwise form is RangeBounds.intersect(…), which returns an AnyRange that is empty when the ranges are disjoint. It pairs with span(…) exactly as & pairs with |. (f199321)
  • RangeSet.span (the getter for the covering range) was renamed to bounds, freeing span to mean the same thing everywhere: a.span(b) is the smallest range covering both, and now works between ranges and sets in either direction. (f199321)
  • RangeBoundsAsRangeSetExtension is gone; asRangeSet is a RangeLike member. IterableOfRangeBoundsExtension became DerangedIterableOfRangeLike, and RangeSet.of(…)/RangeSet.single(…) accept sets as well as ranges. (f199321, 715a4bb)
  • The range codecs take a single optional positional innerCodec instead of the innerCodec + encodeInner + decodeInner named triple, which could previously be combined in ways an assert had to reject. RangeAsMapCodec(innerCodec: c) becomes RangeAsMapCodec(c); to pass functions, wrap them in the new FunctionBasedCodec. (22e6570)

🎉 New Features #

Ranges

  • Added the IntRange.inclusive(start, endInclusive) constructor. int is discrete, so half-open and closed ranges represent the same values; IntRange stays the single canonical half-open type and this constructor covers the inclusive spelling. int.rangeTo(…) now delegates to it. (9e25702)
  • Added isEmpty and isNotEmpty to RangeBounds, so a range can be tested without materializing its values. (93f1bf7)
  • Added isSingle, which reports whether a range describes exactly one value, first to RangeInclusive (8faa051) and then to Range (for Step values), IntRange, and DoubleRangeInclusive (87ceefe).
  • Added clamp(…), which limits a value to a range. It's available wherever it's well-defined: on any RangeBounds whose values implement Step, on RangeInclusive/RangeFrom/RangeTo for any Comparable, and on the int/double equivalents. It's deliberately absent from DoubleRange and DoubleRangeUntil, since there is no largest double below an exclusive end. (39962f3)
  • Added mapBounds(…) and castBounds(…) on RangeBounds, which convert the bound values while preserving the range's shape. They aren't called map/cast because IntRange & co. also implement Iterable, where those names mean mapping the range's elements. The int and double ranges narrow mapBounds(…), so the mapper receives an int or double rather than a num. (8d9d75f)
  • Added shift(…), which moves a range's bounds by an offset. On Step types it returns null if a bound can't be stepped that far; on StepUnlimited, int, and double types it's non-nullable. (eadb75f)
  • Added copyWith(…) on the ranges with both a start and an end (Range, RangeInclusive, IntRange, DoubleRange, DoubleRangeInclusive), which narrow it to their own return and parameter types. Single-bound ranges don't have it – RangeFrom(newStart) is already as short as a copyWith call. (5ddd4c4)
  • Added copyWithBounds(…) on RangeBounds, which replaces whole Bounds rather than their values. It returns an AnyRange, since replacing a bound can change the range's shape. (5ddd4c4)
  • Added reverse to Range and RangeInclusive of Step values (b9632d6, 5c4686f), then to IntRange, IntProgression, and StepProgression (e5e9b3b). A progression's reverse starts at its last value rather than its endInclusive, so both contain exactly the same values.
  • Added factory constructors on RangeBounds – .full(), .from(…), .until(…), .to(…), .inclusiveOrUnbounded(…), .exclusiveOrUnbounded(…) – so ranges can be written with dot shorthands, and made them const where possible. (41995a3, c4780f1, d0783f7)
  • Added AnyRange.inclusiveOrUnbounded(…) and AnyRange.exclusiveOrUnbounded(…), which build a range from nullable bound values. (f6b4d41)
  • RangeBounds and Progression now override ==, hashCode, and toString(). (a6ac502)

Iteration

  • Added .iter on ranges of Step values, which iterates the range one value at a time. (6f8f212)
  • Added stepBy(…) on ranges of Step values, producing a StepProgression. (6d7a1cd)
  • Added IntProgression.stepBy(…), for parity with StepProgression.stepBy(…). (e5e9b3b)
  • Added length to the iterable ranges of Step values. (61d3d25)
  • Added operator [] to the iterable ranges and to Progression, as a shorthand for elementAt(…). (18df627, 899f4e1)
  • Added rangeUntilWithLength(…) on int and on Step/StepUnlimited values, which builds a range from a start and a length. (3ee643d, d3623a0)

Step and Bound

  • Added StepUnlimited, the counterpart of Step for types that can always take a step, along with the DerangedStep and DerangedStepUnlimited extensions carrying previous, next, and the range-building helpers. (2456dad, 1ff8e26, 2d00eb1)
  • Added inclusive/exclusive bound conversion for ranges of Step values: the startInclusive, startExclusive, endInclusive, and endExclusive getters, plus the .inclusive/.exclusive conversions between Range and RangeInclusive. (27e52ed, 43a0ccc, 8d79ef6)
  • Added Bound factory constructors – .inclusive(…), .exclusive(…), .unbounded(), .inclusiveOrUnbounded(…), .exclusiveOrUnbounded(…) – usable with dot shorthands. (71f6d48, c4780f1)
  • Added Bound.valueOrNull (d47b002), Bound.map(…)/Bound.cast(…) (171d952), and ==/hashCode overrides (94c5898).
  • Added bound-level helpers on Bound, which the range and set logic is built from: the static Bound.compareStarts(…)/Bound.compareEnds(…) order two bounds by where a range starting/ending there begins/stops, Bound.laterStart(…)/Bound.earlierEnd(…) pick the narrower of two, Bound.adjoins(end, start) reports whether two ranges meet without a gap, and Bound.inverted turns a start bound into the end bound of everything before it and vice versa. (78aa979, 24a9dbc)

Set operations

  • Added RangeSet, a normalized set of disjoint ranges, with the full set algebra spelled as the bitwise operators – | union, & intersection, - difference, and ~ complement – plus contains(…), containsAll(…), intersects(…), bounds, and mapBounds(…). It stores AnyRanges, so unbounded and exclusive bounds work throughout, which is what makes ~ expressible. Normalization drops empty ranges, merges overlapping and adjoining ones, and sorts the rest, so two sets are equal exactly when they describe the same values. Since the ranges stay sorted, | and & are a single linear sweep rather than a re-sort. (7a923c6)
  • Added RangeSet.coalescedBy(…), which merges ranges with no values between them. Normalization only compares bound values, so it keeps {0..=4, 5..=9} apart; coalescedBy(…) takes the successor function that decides such cases. DerangedRangeSetOfStep.coalesced derives it from a Step type, and DerangedRangeSetOfInt.coalescedAsInts covers int sets – spelled out because a RangeSet<num> can just as well hold double ranges, which must not be stepped by one. (7a923c6)
  • Added RangeBounds.asRangeSet and Iterable<RangeBounds>.asRangeSet, and RangeSetAsListCodec. (7a923c6)
  • RangeSet gained isFull, the counterpart of isEmpty: whether it describes every value. A set is full exactly when it normalized down to a single full range, so a set with a gap is not full even though its bounds are. (f199321)
  • Added the pairwise | and & on RangeInclusive, & on ranges of Step values, and intersectRangeBounds(…), ahead of the general RangeLike algebra. (715dcb2, 759832b, d38153c)
  • Added DerangedIterableOfRangeInclusive, with span and intersection over an iterable of ranges. (aeb981b)

Serialization

  • Added codecs for the range and progression types: RangeAsMapCodec, RangeInclusiveAsMapCodec, IntRangeAsMapCodec, DoubleRangeAsMapCodec, RangeFromAsMapCodec, RangeUntilAsMapCodec, RangeToAsMapCodec, DoubleRangeInclusiveAsMapCodec, StepProgressionAsMapCodec, and IntProgressionAsMapCodec. (101342b, 44ebd4d, 8a20a7a, d46b2ad)
  • Added BoundAsMapCodec and AnyRangeAsMapCodec. Unlike the other range codecs, these round-trip the kind of each bound alongside its value ({"type": "inclusive", "value": …}), so every range shape can be serialized. (d46b2ad)
  • Every codec takes an optional innerCodec for the bound values, so ranges of custom types can be serialized. (4690fbb)
  • Added FunctionBasedCodec, so an innerCodec can be supplied without declaring a Codec class. (22e6570)
  • Exported the StartEndAsMapCodec, SingleBoundAsMapCodec, and ProgressionAsMapCodec base classes, so custom codecs can reuse them. CodecAndJsonConverter and AsMapCodec remain internal. (d46b2ad, 22e6570)

🐛 Bug Fixes #

  • double.rangeUntil(…) no longer subtracts 1 from the end. (f5cba63)
  • IntRange.stepBy(…) now passes its inclusive end to IntProgression, whose end is inclusive. 0.rangeTo(9).stepBy(2) returned 0, 2, 4, 6, 8, 10 and now returns 0, 2, 4, 6, 8. (f5cba63)
  • IntRangeFrom.elementAt(…) no longer hangs on negative indices; it throws a RangeError. (f5cba63)
  • Range.length, RangeInclusive.length, and IntRange.length now return 0 for empty ranges instead of a negative number. (f5cba63)
  • Range.inclusive now returns null when the range's end is the smallest possible value, instead of a one-element range for an empty range. (f5cba63)
  • DoubleRangeUntil.toString() now uses the exclusive ..<end notation. (f5cba63)
  • UnboundedBound.operator == no longer compares its type argument, which made equality asymmetric. const UnboundedBound() inside a generic class can't name that class's type parameter and becomes an UnboundedBound<Never> – which is what every range's unbounded startBound/endBound was – so UnboundedBound<Never>() == UnboundedBound<num>() was false while the reverse was true. An unbounded bound holds no value, so the type argument is phantom here. (1a1bc9e)
  • AnyRange no longer over-constrains the generic type of its bounds. (cdc895d)
  • Iterable<RangeInclusive<C>>.union no longer fails for an empty iterable. (ec94e1f)
  • The IntRange* and DoubleRange* constructors now take int/double parameters instead of num. (bae4053)
  • Bound.laterStart(…)/Bound.earlierEnd(…) (then Bound.maxLower(…)/.minUpper(…)) and RangeInclusive.operator & now accept null arguments. (d2661fa)
  • DerangedRangeOfStep.stepBy(…)'s return type is no longer nullable. (25461c2)

🏗️ Refactoring #

  • Progression now mixes in Iterable<T> itself, so IntProgression and StepProgression no longer each declare it. (4561e4a)
  • Step and StepUnlimited moved out of progression.dart into their own step.dart, and the codecs into codec.dart. (2456dad, d845a9d)

📜 Documentation updates #

  • Progression's documentation and toString() no longer describe its end as exclusive. (4561e4a)
  • Swapped the mixed-up references in Iterable<RangeInclusive>.union and .intersection. (4561e4a)
  • Documented the until/to convention, and the discrete-vs-continuous reasoning behind int having one range type where double has two, in the README and on RangeBounds. (4561e4a, dbb1bd4)
  • Documented Step, StepUnlimited, and previous/next. (899db1c, 4444618)
  • Added a serialization section to the README, documented .iter, and shortened the rest. (de43393, d5f3d2c, 41f9a95)

📦 Build & CI #

0.0.0 · 2025-01-07 #

Initial release 🎉

2
likes
160
points
114
downloads

Documentation

API reference

Publisher

verified publisherwanke.dev

Weekly Downloads

Generic range and progression types, inspired by Rust and Kotlin

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

collection, json_annotation, meta

More

Packages that depend on deranged