louthy/language-ext · error · InvalidOperationException

diff broken

Error message

diff broken

What it means

Same `diff broken` invariant check, in the `VectorClock<OrdA, NumB, A, B>` variant's `diffOne`: a (None, Some) entry pair or unrecognized case when diffing the B-typed entries throws `InvalidOperationException("diff broken")`. The B-clock diff can never legitimately see a key absent in the receiver but present in the argument.

Solutions

  1. Only obtain clocks through public constructors and `Merge`/`Next`; never mutate internals.
  2. Ensure both clocks passed to Diff share the same key domain (same origin/lineage).
  3. If reachable via valid public API, file a bug with LanguageExt including repro steps.
Defensive patterns

Strategy: try-catch

Validate before calling

if (!clockA.Valid || !clockB.Valid) throw new InvalidOperationException("invalid vector clocks");

Try / catch

try { var d = a.Diff(b); }
catch (InvalidOperationException ex) when (ex.Message == "diff broken") { /* rebuild from common ancestor */ }

Prevention

When it happens

Trigger: Calling `Diff` on a two-type-parameter VectorClock where the argument clock contains a key the receiver lacks, or an option holds a non-B/non-null case — only possible with inconsistently built clocks.

Common situations: Mixing clocks from different merge lineages; custom merge/serialization code that mutates one clock but not the other; bugs in user code that manipulates the underlying map via reflection or unsafe casts.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of louthy/language-ext@2f0e362824 (2026-09-15). Data as JSON: /api/errors/706ae770cfa3f049. Report an issue: GitHub.

Appendix: source

Thrown at LanguageExt.Core/Concurrency/VectorClock/VectorClock.OrdA.NumB.A.B.cs:331

        
    /// <summary>
    /// If vc2 causes vc1, compute the smallest vc3
    /// </summary>
    /// <remarks>Note that the /first/ parameter is the newer vector clock.</remarks>
    public static Option<VectorClock<OrdA, NumB, A, B>> diff(VectorClock<OrdA, NumB, A, B> vc1, VectorClock<OrdA, NumB, A, B> vc2)
    {
        return vc1 == vc2
                   ? Some(Empty)
                   : causes(vc2, vc1)
                       ? Some(combine(diffOne, vc1, vc2))
                       : None;

        static Option<B> diffOne(A _, Option<B> ox, Option<B> oy) =>
            (ox.Case, oy.Case) switch
            {
                (null, null) => None,
                (B x, null)  => Some(x),
                (null, B)    => throw new InvalidOperationException("diff broken"),
                (B x, B y)   => equals<NumB, B>(x, y) ? None : Some(x),
                _            => None
            };
    }

    bool? valid;
    public bool Valid 
    { 
        get
        {
            if (valid.HasValue) return valid.Value;
            var keys     = Entries.Map(e => e.Item1);
            var sorted   = keys.Sort<OrdA, A>()                                         == keys;
            var distinct = Entries.Distinct<EqTuple2<OrdA, NumB, A, B>, (A, B)>().Count == Entries.Count;
            valid = sorted && distinct;
            return valid.Value;
        }
    }

View on GitHub (pinned to 2f0e362824)