Skip to content

RangeSet.From accepts a null element: the set holds null, IsEmpty() is false and Contains throws #356

Closed

Description

@CaffeinatedCoder

Problem

RangeSet<DateRange, DateOnly>.From([null!]) returns a set with Count == 1 whose element is null. IsEmpty() is false, Equals(RangeSet<DateRange, DateOnly>.Empty) is false, and Contains(day) throws NullReferenceException. That breaks the RangeSet rule (sorted, disjoint, non-adjacent, no empties) on a construction path.

You reach it without calling From yourself. On a null range, Except(Empty), Except((,)), Except(p) and Union(Empty) all return such a set: Except wraps the receiver in From([range]) when it doesn't overlap the operand, and Union is From([range, other]). Measured 2026-10-24 on main; RangeExtensions.cs is identical on release/10.x, so 10.1.0 does the same.

Change

From (both overloads, and RangeSet.Create through them) throws ArgumentException for a null element, so no path can build a set holding null. ArgumentException, not ArgumentNullException, as the value sets do: ValueSetCore throws ArgumentException("Value sets cannot contain null elements.", nameof(values)) for a null element and keeps ArgumentNullException for a bare null argument. Under #350's rule (c), Except and Union on a null range throw before they reach From on 11.0.0. On 10.x this check is what turns them into a loud failure.

That is also the value sets' rule. ValueSetNullContractTests holds that every way into a set refuses a null, "never by dropping", because a dropped null can't be told from a duplicate. Dropping a null range the way From drops empty ranges is ruled out on both counts: by that rule, and by (c), since it would make null.Except(x) an empty set, a value for a null range.

silent: the set is built and answers IsEmpty() and Equals wrongly before anything throws. On 10.x it ships with #350's predicate port in 10.2.0, under ### Changed answers. There, one row turns from a value into a throw: null.Union((,)) returns {(,)} today, because From collapses to the infinite set before it looks at the null.

Done when

A test calls every public way to build a RangeSet (From over a span and over an IEnumerable, and the extensions that call it) with a null element and expects ArgumentException. The test fails with the check removed. Done in #370: RangeSetNullContractTests, registered as an oracle with the seed rangeset-stores-a-null-range.patch.

Activity

  1. added this to the 11.0.0 milestone on Oct 24, 2026
  2. added
    bugSomething isn't working
    silentAnswers wrongly without throwing — fixes go in the next major, never deferred
    area/range-setRangeSet and multirange algebra
    package/coreAffects CodoMetis.ValueRanges
    severity/lowCosmetic, message or documentation-level difference
    on Oct 24, 2026
  3. added a commit that references this issue on Oct 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/range-setRangeSet and multirange algebrabreakingChanges an answer or a signature — needs a majorbugSomething isn't workingpackage/coreAffects CodoMetis.ValueRangesseverity/lowCosmetic, message or documentation-level differencesilentAnswers wrongly without throwing — fixes go in the next major, never deferred

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions