Skip to content

fix(mocks): dispatch abstract indexers through the engine instead of calling base - #6517

Merged
thomhurst merged 1 commit into
mainfrom
fix/6516-abstract-indexer
Jul 30, 2026
Merged

fix(mocks): dispatch abstract indexers through the engine instead of calling base#6517
thomhurst merged 1 commit into
mainfrom
fix/6516-abstract-indexer

Conversation

@thomhurst

Copy link
Copy Markdown
Owner

Summary

Fixes #6516T.Mock() on an abstract class with an abstract indexer failed to compile with CS0205: Cannot call an abstract base member, because the generated override always emitted a return base[...]; / base[...] = value; fallback.

Root cause

CreateIndexerModel in MemberDiscovery never set IsAbstractMember / IsVirtualMember, so every class indexer — abstract or not — took the virtual-member path in GenerateOverrideIndexer, whose fallback calls base[...]. 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 (and IsVirtualMember for parity with the method/property models) on the indexer model. The existing abstract branches in GenerateOverrideIndexer then 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; full TUnit.Mocks.Tests suite 1186/1186 on net10.0.
  • Abstract_Class_With_Abstract_Indexer (source generator): asserts no base[...] in abstract indexer bodies, base fallback retained for the virtual one, no CS0205 in the generated compilation + snapshot. Full generator suite 91/91.

…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-apps

greptile-apps Bot commented Jul 29, 2026

Copy link
Copy Markdown

Greptile Summary

Fixes abstract-indexer mock generation by classifying indexers consistently with methods and properties.

  • Abstract indexers now dispatch directly through the mock engine instead of generating invalid abstract base calls.
  • Virtual and override indexers retain their existing base-fallback behavior.
  • Adds source-generator snapshots and runtime coverage for get-only, read/write, configured, unconfigured, and virtual indexers.

Confidence Score: 5/5

The 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.

Important Files Changed

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

@claude

claude Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Code review

No issues found. Checked for bugs and CLAUDE.md compliance.

The fix is minimal and correctly scoped: it sets IsAbstractMember/IsVirtualMember on the indexer model in CreateIndexerModel, mirroring the exact same flags already set for methods (MemberDiscovery.cs:746-747) and non-indexer properties (MemberDiscovery.cs:815-816). GenerateOverrideIndexer already branches on IsAbstractMember to dispatch engine-only vs. base-fallback, so no codegen changes were needed — just correct classification.

  • Consistent with established patterns for methods/properties; no new abstraction or edge case introduced.
  • Well tested: source-gen snapshot test asserts no base[...] calls in abstract indexer bodies and retains base fallback for the virtual case, plus runtime regression tests covering get-only, read/write, and virtual indexers.
  • Snapshot file was added correctly (.verified.txt, no stray .received.txt).
  • Root CLAUDE.md is the only one in scope for the changed paths; no violations found.

@thomhurst
thomhurst merged commit 8350a00 into main Jul 30, 2026
13 checks passed
@thomhurst
thomhurst deleted the fix/6516-abstract-indexer branch July 30, 2026 00:21
This was referenced Jul 30, 2026
This was referenced Jul 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

TUnit.Mocks: CS0205 "Cannot call an abstract base member" when mocking an abstract class with an abstract indexer

1 participant