Repository navigation
Mark the range unions closed (C# 15) for compile-time exhaustiveness, on both lines #149
Copy link
Copy link
Open
Labels
area/range-setRangeSet and multirange algebraRangeSet and multirange algebraarea/rangesRange unions, variants and the shape enginesRange unions, variants and the shape enginesbreakingChanges an answer or a signature — needs a majorChanges an answer or a signature — needs a majorenhancementNew feature or requestNew feature or requestpackage/coreAffects CodoMetis.ValueRangesAffects CodoMetis.ValueRangespackage/nodatimeAffects CodoMetis.ValueRanges.NodaTimeAffects CodoMetis.ValueRanges.NodaTime
Milestone
Description
Decided 2026-10-18 (maintainer, after the #148 probe): closed, not union. Ships on both lines.
closedon every union type (Int32Range…TimeRange, the three NodaTime ones,YearMonthRange) and onRangeSetwhere a shape switch exists.closedis implicitly abstract, so theabstractmodifier goes. The private constructor stays: a closed type is only assembly-scoped (CS9382 outside the assembly).- An in-project
System.Runtime.CompilerServices.IsClosedTypeAttributepolyfill under#if !NET11_0_OR_GREATERin each shipping assembly; anet11.0consumer reads it from metadata and gets exhaustiveness all the same (measured). - Consumers keep working unchanged: an existing
_ => throwarm next to complete arms raises nothing; a missing arm is CS8509 naming the variant. So this is not breaking for consumers, and the ApiCompat pack should stay clean (verify). - Both lines. First on
net11as11.0.0-preview.2. On 10.x it ships as a minor after .NET 11 GA, because a stable package must not build withLangVersionpreview; from GA on,LangVersion15 is stable and the 10.x line builds with the .NET 11 SDK targetingnet10.0. CI on the 10 line then needs the .NET 11 SDK too (CI matrix for two lines: .NET 10 and 11 SDKs, PostgreSQL floor and 18 legs per line #152). unionis not adopted: it changes the representation (boxedobject?value, different default) and every consumer's declared type for no gain in exhaustiveness.
The headline of 11.0.0. Today the range union is a closed class hierarchy only at runtime (AGENTS.md, "Critical Rules"): a switch over Int32Range warns CS8509 without a discard, so consumers write _ => throw, the three engines throw ShapePair.Unreachable, and EngineDispatchConventionTests parses src/**/Internals/ to make sure the discards throw. The rewrite makes the compiler carry that guarantee.
Scope, per the probe's outcome:
- Every union type (
Int32Range…TimeRange, the three NodaTime ones,YearMonthRange) andRangeSetwhere a shape switch exists. - Consumers gain compile-time exhaustiveness: a switch over a union's variants loses its
_ => throw. The three engines do not: they switch over the structural interface patterns (IFiniteRange<T>, …) onIRange<T>operands, and closing the concrete classes does not make those switches exhaustive.ShapePair.UnreachableandEngineDispatchConventionTestsstay as they are. Whether a per-type closed dispatch inside the engines is worth it is decided in Compile probe: C# 15 closed hierarchies and union types for the five variants, on net10.0 and net11.0 #148, not promised here. - JSON, TypeConverter and the EF variant retyper (
RangeVariantConstantRetyper) are re-measured: an inlined constant still arrives typed as its variant. - ApiCompat: generate the suppression file and document every entry in the changelog's "what can break you" list;
docs/migration.mdgets the v10.x section showing a consumer switch before and after. - Native AOT: the consumer smoke test's AOT consumer must switch over a range.
- The private constructor stays if the probe shows
closedis assembly-scoped only.
Not on the 10.x line unless the probe shows the feature compiles on net10.0 with no runtime support.
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
area/range-setRangeSet and multirange algebraRangeSet and multirange algebraarea/rangesRange unions, variants and the shape enginesRange unions, variants and the shape enginesbreakingChanges an answer or a signature — needs a majorChanges an answer or a signature — needs a majorenhancementNew feature or requestNew feature or requestpackage/coreAffects CodoMetis.ValueRangesAffects CodoMetis.ValueRangespackage/nodatimeAffects CodoMetis.ValueRanges.NodaTimeAffects CodoMetis.ValueRanges.NodaTime
Activity
CaffeinatedCoder commentedon Oct 18, 2026
Scope correction from an independent review (GPT 6 Astra, 2026-10-18), confirmed against the code: a
closedhierarchy makes a switch over the concrete variants exhaustive (Int32Range.Finite,.UnboundedStart, …). The three engines insrc/**/Internals/switch over the structural interface patterns (IFiniteRange<T>,IUnboundedStartRange<T>, …) onIRange<T>operands, and closing the concrete classes does not make those switches exhaustive. So this issue's "remove the throwing discards from the engines" holds only if the engines are restructured to dispatch on closed concrete types, which they cannot do generically.Revised scope: compile-time exhaustiveness is for consumers (their switches over a union's variants lose the
_ => throw), andShapePair.UnreachableplusEngineDispatchConventionTestsstay for the engines. Whether a per-type closed dispatch inside the engines is worth it is a question for #148's decision comment, not a promise of this issue.The reviewer also tested
closedon the .NET 11 RC1 SDK: a switch over the variants compiled without a fallback, and removing an arm produced CS8509 naming the missing variant. Target framework of that probe unknown; #148'snet10.0question stays open.[-]Rewrite the five range variants as a C# 15 closed hierarchy or union, with compile-time exhaustiveness[/-][+]Mark the range unions closed (C# 15) for compile-time exhaustiveness, on both lines[/+]CaffeinatedCoder commentedon Oct 18, 2026
Release window, 2026-10-18 (#178, PR #180): 10.x now takes additions for six months after
v11.0.0, not only until it. The 10.x half of this issue (a minor after .NET 11 GA, built with LangVersion 15 targetingnet10.0) falls inside that window, so the plan above stands. It has to ship before the cut-off date CONTRIBUTING records at thev11.0.0tag.