Conversation
A ref struct reaching a value position (method or delegate return, property type, indexer value, event argument) parameterized a generic that stores a Func<T> and so cannot carry the allows ref struct anti-constraint, making the generated mock fail to compile with CS9244. Only parameter positions had been routed through the ref-struct pipeline; value positions were never gated, so instead of the intended graceful degradation the user got a raw compiler error on generated code they cannot edit. Events and Span-typed properties are now genuinely supported: the IDefaultEventParameters raise overload is omitted for ref-struct arguments (it fills parameters via Generate<T>, whose body type-tests an object against T and therefore cannot be anti-constrained) while the strongly-typed overload passes the value straight to the backing delegate, and property setup/verify facades now use the SpanWrapper carve-out the property body and the indexer facades already used, which also fixes the latent mismatch between a setup stored as Span<T> and a body reading SpanWrapper<T>. The remaining shapes have no wrapper to route through and stay unsupported by design, so they degrade to the NotSupportedException stub already used for ref-struct-returning methods rather than breaking the build: non-span ref-struct returns on methods and delegates, ref-struct parameters on delegates, ref-struct properties, and ref-struct indexer values. MockabilityAnalyzer had the matching blind spots - it flagged ref-struct method returns and indexer keys but not properties or indexer values - so every unsupported shape now reports Mockolate0003 at the CreateMock() call site instead of silently producing a throwing member. The delegate Invoke body is extracted into its own method so the guard stays small; the move is otherwise verbatim.
🚀 Benchmark ResultsDetails
Details
Details
Details
Details
Details
|
A method that both returned and took a non-span ref struct was routed into the ref-struct parameter pipeline, which #errors below .NET 9, so a member that can never be supported anyway broke the whole compilation instead of degrading to a stub. The return type now decides first, in the generator and the analyzer alike, because it disqualifies the member regardless of target. Verify now follows Setup for ref-struct returns: the body is a NotSupportedException stub, so the call never reaches interaction recording and the surface could only ever report zero - which is already why properties and indexers drop it. Mockolate0003 called every member a "method" while reporting on properties and indexers, and its description still claimed out/ref ref-struct parameters were unmockable; the docs carried the same stale list and neither new value position. IsUnsupportedRefStructValue was a pure alias for NeedsRefStructPipeline, so a change to either would have silently redefined the other. The tests were gated to net9.0+ even though none of these shapes are, hiding the targets where the old code broke hardest, and left five warnings in every build.
… usable A Span-valued indexer setter inferred `ApplyIndexerSetter<Span<T>>` and failed with CS9244, so the whole mock stopped compiling with no diagnostic pointing at it. The value type now goes through the same wrapper the getter and the property pipeline already use. A ref struct in a value position still has no setup surface, but that is no reason to break a member that works: a virtual class member forwards to the wrapped instance or to base instead of throwing, which only interface and abstract members now do. A ref-struct return also no longer claims a fast-interaction buffer it can never record into.
The list still claimed `out`/`ref`/`ref readonly` ref-struct parameters are rejected, while the analyzer page this section is linked from as the authority says they are supported. It also predates the value-position and delegate-parameter carve-outs.
…members Span-valued properties and indexers were compilable but silently unverifiable: the value is matched through `EqualityComparer<SpanWrapper<T>>.Default`, and the wrapper had no `Equals`, so the by-value `Set(someSpan)` overload compiled and never matched. Content equality on both wrappers makes the form a user reaches for first work, rather than removing it in favour of the predicate matchers; `ToString` is there because recorded interactions render the wrapper, and the type name alone made the failure message useless. A `virtual` class member with a ref struct in a value position keeps working - the override forwards to the wrapped instance or to `base` - so warning that it "cannot be mocked" only forced a suppression on code that needs no fix. The analyzer now mirrors the generator's forwarding condition, which also suppresses the parameter diagnostic for those members: the generator takes the return branch first, so their ref-struct parameters never reach the pipeline either. The by-ref-return storage field was emitted before the body was known to be a stub, so a ref-struct return made the field itself illegal (CS8345) and broke the whole mock over a member that was already given up on - the opposite of what degrading is for. The behaviour flags the passthrough bypasses are now written down instead of inferred: with no setup to honour them against, `SkipBaseClass` has nothing to return in the base call's place and `ThrowWhenNotSetup` would reject a member that can never be set up. Tests cover the value-position accessors that had none: the setter paths on interfaces and classes, protected accessors, init-only getters, by-ref returns, and Span-typed members on classes rather than only on interfaces.
The analyzer only ever modelled the type-level half of the generator's ref-struct rule, so `ref readonly Span<T>` slipped past it. That ref kind is the one span shape without a wrapper-based emit branch, so the generator routes it through the ref-struct pipeline like a custom ref struct: on a delegate it degrades to a NotSupportedException stub, and on an interface or class it needs .NET 9 / C# 13 or the emitted source hits an #error. Neither said anything at compile time. Mirroring the parameter-level predicate in a deliberately distinct name, rather than a second overload, since the two rules disagree and silently picking the wrong one is what caused this.
|



A ref struct reaching a value position (method or delegate return, property type, indexer value, event argument) parameterized a generic that stores a Func and so cannot carry the allows ref struct anti-constraint, making the generated mock fail to compile with CS9244. Only parameter positions had been routed through the ref-struct pipeline; value positions were never gated, so instead of the intended graceful degradation the user got a raw compiler error on generated code they cannot edit.
Events and Span-typed properties are now genuinely supported: the IDefaultEventParameters raise overload is omitted for ref-struct arguments (it fills parameters via Generate, whose body type-tests an object against T and therefore cannot be anti-constrained) while the strongly-typed overload passes the value straight to the backing delegate, and property setup/verify facades now use the SpanWrapper carve-out the property body and the indexer facades already used, which also fixes the latent mismatch between a setup stored as Span and a body reading SpanWrapper.
The remaining shapes have no wrapper to route through and stay unsupported by design, so they degrade to the NotSupportedException stub already used for ref-struct-returning methods rather than breaking the build: non-span ref-struct returns on methods and delegates, ref-struct parameters on delegates, ref-struct properties, and ref-struct indexer values.
MockabilityAnalyzer had the matching blind spots - it flagged ref-struct method returns and indexer keys but not properties or indexer values - so every unsupported shape now reports Mockolate0003 at the CreateMock() call site instead of silently producing a throwing member.
The delegate Invoke body is extracted into its own method so the guard stays small; the move is otherwise verbatim.