You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Generic sweeps call the span overload when they mean Parse(string): ISpanParsable hides IParsable in overload resolution #142
Found while writing the sibling parity sweeps (#139).
Problem
In generic code constrained on IRangeFactory<TRange, T> or IValueSetFactory<TSet, T>, a call such as TSet.Parse(text, provider) or TSet.TryParse(text, provider, out var r) with a string argument binds to the span overload. Both interfaces derive from ISpanParsable<TSelf>. Overload resolution removes the base interface's IParsable<TSelf>.Parse(string, …) candidate whenever the derived interface's Parse(ReadOnlySpan<char>, …) is applicable, and the implicit string → ReadOnlySpan<char> conversion makes it applicable.
Measured: with Int32Set.TryParse(string) seeded to read a blank literal as {}, a generic TSet.TryParse(" ", …) under an IValueSetFactory constraint still answers false. The same call under an IParsable<TSet> constraint answers true.
So the generic convention sweeps that say they cover Parse/TryParse "through every entry point" never call the string overloads. Each type declares those overloads separately (SetFormat.Parse(s.AsSpan(), …)), and they're what reflection-based consumers find, such as minimal-API binding's TryParse lookup. Today every pair delegates to the same parser, so nothing is wrong yet. But a string overload can drift without any sweep noticing.
Call sites (generic string calls under such a constraint):
DecorativeElementContractTests
LiteralProviderContractTests (3)
RangeLiteralGrammarContractTests (3)
SetElementWhiteSpaceContractTests (4)
TemporalLiteralStrictnessContractTests (8)
WrapperGrammarContractTests (3)
ShapeMatrixParityTests (integration, 1)
Change
Route those calls through StringOverloads.Parse<T> / StringOverloads.TryParse<T> (added in #139, constrained on IParsable<TSelf> alone). Where a sweep lists Parse(span) as a separate entry point, it then really compares the two. For the oracles among them, show the reach with the set-tryparse-reads-blank-as-empty seed or a string-only sibling.
Done
No generic Parse/TryParse call with a string argument goes through an ISpanParsable-derived constraint (a convention test could hold this, in the style of EngineDispatchConventionTests)
A seed that changes only a string overload is caught by the literal sweeps that claim it
Implemented in #144, stacked on #143. The scope grew by two classes that landed on main after this was filed: CultureEntryPointProbeTests (constrained on ISpanParsable<TValue> directly, which has the same trap) and the integration LiteralCorpusProbeTests. That makes 33 calls in ten classes. The guard is StringOverloadConventionTests, which binds the calls with Roslyn and is proven by putting one back. The reach is shown by the seed string-parse-localizes-decimal-separator: LiteralProviderContractTests and CultureEntryPointProbeTests missed it before and catch it now.
Activity
CaffeinatedCoder commentedon Oct 18, 2026
Implemented in #144, stacked on #143. The scope grew by two classes that landed on
mainafter this was filed:CultureEntryPointProbeTests(constrained onISpanParsable<TValue>directly, which has the same trap) and the integrationLiteralCorpusProbeTests. That makes 33 calls in ten classes. The guard isStringOverloadConventionTests, which binds the calls with Roslyn and is proven by putting one back. The reach is shown by the seedstring-parse-localizes-decimal-separator:LiteralProviderContractTestsandCultureEntryPointProbeTestsmissed it before and catch it now.