fix(mocks): dispatch abstract indexers through the engine instead of calling base - #6517
Conversation
…calling base CreateIndexerModel never set IsAbstractMember, so every class indexer took the virtual-member path in GenerateOverrideIndexer and emitted a `return base[...];` / `base[...] = value;` fallback. For an abstract indexer there is no base implementation, so the generated mock failed to compile with CS0205 — blocking e.g. DbDataReader, which declares two abstract indexers. Carry IsAbstractMember/IsVirtualMember on the indexer model like methods and properties already do; the existing abstract branches in GenerateOverrideIndexer then emit engine-only dispatch. Virtual indexers keep their base fallback. Fixes #6516
Greptile SummaryFixes abstract-indexer mock generation by classifying indexers consistently with methods and properties.
Confidence Score: 5/5The PR appears safe to merge with no actionable defects identified. The indexer flags now match the established method and property modeling behavior, and the added tests cover both corrected abstract dispatch and preserved virtual fallback behavior.
|
| Filename | Overview |
|---|---|
| src/TUnit.Mocks.SourceGenerator/Discovery/MemberDiscovery.cs | Adds abstract and virtual classification to indexer models, matching the established method and property classification. |
| tests/TUnit.Mocks.SourceGenerator.Tests/MockGeneratorTests.cs | Adds generated-compilation and output assertions covering abstract and virtual indexer dispatch. |
| tests/TUnit.Mocks.SourceGenerator.Tests/Snapshots/Abstract_Class_With_Abstract_Indexer.verified.txt | Captures engine-only abstract indexer implementations and retained virtual base fallbacks. |
| tests/TUnit.Mocks.Tests/Issue6516Tests.cs | Adds runtime regressions for abstract getter/setter dispatch, setup and verification, smart defaults, and virtual fallback behavior. |
Reviews (1): Last reviewed commit: "fix(mocks): dispatch abstract indexers t..." | Re-trigger Greptile
Code reviewNo issues found. Checked for bugs and CLAUDE.md compliance. The fix is minimal and correctly scoped: it sets
|
Summary
Fixes #6516 —
T.Mock()on an abstract class with an abstract indexer failed to compile withCS0205: Cannot call an abstract base member, because the generated override always emitted areturn base[...];/base[...] = value;fallback.Root cause
CreateIndexerModelinMemberDiscoverynever setIsAbstractMember/IsVirtualMember, so every class indexer — abstract or not — took the virtual-member path inGenerateOverrideIndexer, whose fallback callsbase[...]. Abstract indexers have no base implementation, so the mock impl didn't compile. Abstract methods and non-indexer properties already carried the flag and dispatched engine-only; indexers were the one member kind that didn't.Fix
Set
IsAbstractMember = indexer.IsAbstract(andIsVirtualMemberfor parity with the method/property models) on the indexer model. The existing abstract branches inGenerateOverrideIndexerthen emit engine-only dispatch — unconfigured calls return the smart default (or throw in strict mode), exactly like other abstract members. Virtual indexers keep their base fallback (covered by a regression test).This unblocks mocking
System.Data.Common.DbDataReader(two abstract indexers), reported as blocking an NSubstitute migration.Tests
Issue6516Tests(runtime): issue repro shape (two abstract get-only indexers, int + string keys), abstract get/set indexer setter dispatch, virtual indexer base-fallback regression. 4/4 pass; fullTUnit.Mocks.Testssuite 1186/1186 on net10.0.Abstract_Class_With_Abstract_Indexer(source generator): asserts nobase[...]in abstract indexer bodies, base fallback retained for the virtual one, no CS0205 in the generated compilation + snapshot. Full generator suite 91/91.