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
github-actions Bot pushed a commit to BenjaminMichaelis/TrxLib that referenced this pull request Aug 3, 2026
Updated [TUnit](https://github.com/thomhurst/TUnit) from 1.61.38 to
1.63.0.

<details>
<summary>Release notes</summary>

_Sourced from [TUnit's
releases](https://github.com/thomhurst/TUnit/releases)._

## 1.63.0

<!-- Release notes generated using configuration in .github/release.yml
at v1.63.0 -->

## What's Changed
### Other Changes
* fix(mocks): dispatch abstract indexers through the engine instead of
calling base by @​thomhurst in
thomhurst/TUnit#6517
* feat(mocks): experimental compile-time internals access (#​6514 Tier
2) by @​thomhurst in thomhurst/TUnit#6520
* feat(mocks): make the async-factory Returns alias available below
net9.0 by @​thomhurst in thomhurst/TUnit#6518
* +semver:minor - feat(mocks): runtime auto-stubs for interfaces the
source generator cannot see by @​thomhurst in
thomhurst/TUnit#6519
### Dependencies
* chore(deps): update tunit to 1.62.0 by @​thomhurst in
thomhurst/TUnit#6508
* chore(deps): update dependency mockolate to 3.4.0 by @​thomhurst in
thomhurst/TUnit#6512
* chore(deps): update dependency cliwrap to 3.10.4 by @​thomhurst in
thomhurst/TUnit#6513
* chore(deps): update dependency stackexchange.redis to 3.0.25 by
@​thomhurst in thomhurst/TUnit#6522
* chore(deps): update dependency brace-expansion to v5.0.9 by
@​thomhurst in thomhurst/TUnit#6523
* chore(deps): update dependency microsoft.build.utilities.core to v18
by @​thomhurst in thomhurst/TUnit#6525


**Full Changelog**:
thomhurst/TUnit@v1.62.0...v1.63.0

## 1.62.0

<!-- Release notes generated using configuration in .github/release.yml
at v1.62.0 -->

## What's Changed
### Other Changes
* fix(docs): make Mermaid lifecycle diagrams readable by @​thomhurst in
thomhurst/TUnit#6485
* fix(mocks): strip nullable annotation from constructor dispatch
patterns by @​thomhurst in thomhurst/TUnit#6498
* fix(mocks): skip interfaces with inaccessible abstract members by
@​thomhurst in thomhurst/TUnit#6502
* feat(mocks): accept an async factory in Returns() on async members by
@​thomhurst in thomhurst/TUnit#6503
* fix(mocks): report TM006 instead of CS1729 for unsubclassable classes
by @​thomhurst in thomhurst/TUnit#6501
* fix(mocks): emit the setup surface into the globally-imported
namespace by @​thomhurst in thomhurst/TUnit#6504
* fix(mocks): reference TUnit.Mocks once in snapshot test compilations
by @​thomhurst in thomhurst/TUnit#6506
* fix(mocks): make generated identifier sanitization injective by
@​thomhurst in thomhurst/TUnit#6507
### Dependencies
* chore(deps): update tunit to 1.61.38 by @​thomhurst in
thomhurst/TUnit#6479
* chore(deps): update dependency brace-expansion to v5.0.8 by
@​thomhurst in thomhurst/TUnit#6478
* chore(deps): update dependency cliwrap to 3.10.3 by @​thomhurst in
thomhurst/TUnit#6482
* chore(deps): bump postcss from 8.5.10 to 8.5.22 in /docs by
@​dependabot[bot] in thomhurst/TUnit#6480
* chore(deps): update dependency fscheck to 3.3.4 by @​thomhurst in
thomhurst/TUnit#6486
* chore(deps): update dependency minimatch to v10.2.6 by @​thomhurst in
thomhurst/TUnit#6490
* chore(deps): update actions/stale action to v11 by @​thomhurst in
thomhurst/TUnit#6496
* chore(deps): update microsoft.testing to 2.3.3 by @​thomhurst in
thomhurst/TUnit#6499
* chore(deps): update mstest to 4.3.3 by @​thomhurst in
thomhurst/TUnit#6500


**Full Changelog**:
thomhurst/TUnit@v1.61.38...v1.62.0

Commits viewable in [compare
view](thomhurst/TUnit@v1.61.38...v1.63.0).
</details>

[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=TUnit&package-manager=nuget&previous-version=1.61.38&new-version=1.63.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
github-actions Bot pushed a commit to IntelliTect/CodingGuidelines that referenced this pull request Aug 3, 2026
Updated [TUnit.Core](https://github.com/thomhurst/TUnit) from 1.61.38 to
1.63.0.

<details>
<summary>Release notes</summary>

_Sourced from [TUnit.Core's
releases](https://github.com/thomhurst/TUnit/releases)._

## 1.63.0

<!-- Release notes generated using configuration in .github/release.yml
at v1.63.0 -->

## What's Changed
### Other Changes
* fix(mocks): dispatch abstract indexers through the engine instead of
calling base by @​thomhurst in
thomhurst/TUnit#6517
* feat(mocks): experimental compile-time internals access (#​6514 Tier
2) by @​thomhurst in thomhurst/TUnit#6520
* feat(mocks): make the async-factory Returns alias available below
net9.0 by @​thomhurst in thomhurst/TUnit#6518
* +semver:minor - feat(mocks): runtime auto-stubs for interfaces the
source generator cannot see by @​thomhurst in
thomhurst/TUnit#6519
### Dependencies
* chore(deps): update tunit to 1.62.0 by @​thomhurst in
thomhurst/TUnit#6508
* chore(deps): update dependency mockolate to 3.4.0 by @​thomhurst in
thomhurst/TUnit#6512
* chore(deps): update dependency cliwrap to 3.10.4 by @​thomhurst in
thomhurst/TUnit#6513
* chore(deps): update dependency stackexchange.redis to 3.0.25 by
@​thomhurst in thomhurst/TUnit#6522
* chore(deps): update dependency brace-expansion to v5.0.9 by
@​thomhurst in thomhurst/TUnit#6523
* chore(deps): update dependency microsoft.build.utilities.core to v18
by @​thomhurst in thomhurst/TUnit#6525


**Full Changelog**:
thomhurst/TUnit@v1.62.0...v1.63.0

## 1.62.0

<!-- Release notes generated using configuration in .github/release.yml
at v1.62.0 -->

## What's Changed
### Other Changes
* fix(docs): make Mermaid lifecycle diagrams readable by @​thomhurst in
thomhurst/TUnit#6485
* fix(mocks): strip nullable annotation from constructor dispatch
patterns by @​thomhurst in thomhurst/TUnit#6498
* fix(mocks): skip interfaces with inaccessible abstract members by
@​thomhurst in thomhurst/TUnit#6502
* feat(mocks): accept an async factory in Returns() on async members by
@​thomhurst in thomhurst/TUnit#6503
* fix(mocks): report TM006 instead of CS1729 for unsubclassable classes
by @​thomhurst in thomhurst/TUnit#6501
* fix(mocks): emit the setup surface into the globally-imported
namespace by @​thomhurst in thomhurst/TUnit#6504
* fix(mocks): reference TUnit.Mocks once in snapshot test compilations
by @​thomhurst in thomhurst/TUnit#6506
* fix(mocks): make generated identifier sanitization injective by
@​thomhurst in thomhurst/TUnit#6507
### Dependencies
* chore(deps): update tunit to 1.61.38 by @​thomhurst in
thomhurst/TUnit#6479
* chore(deps): update dependency brace-expansion to v5.0.8 by
@​thomhurst in thomhurst/TUnit#6478
* chore(deps): update dependency cliwrap to 3.10.3 by @​thomhurst in
thomhurst/TUnit#6482
* chore(deps): bump postcss from 8.5.10 to 8.5.22 in /docs by
@​dependabot[bot] in thomhurst/TUnit#6480
* chore(deps): update dependency fscheck to 3.3.4 by @​thomhurst in
thomhurst/TUnit#6486
* chore(deps): update dependency minimatch to v10.2.6 by @​thomhurst in
thomhurst/TUnit#6490
* chore(deps): update actions/stale action to v11 by @​thomhurst in
thomhurst/TUnit#6496
* chore(deps): update microsoft.testing to 2.3.3 by @​thomhurst in
thomhurst/TUnit#6499
* chore(deps): update mstest to 4.3.3 by @​thomhurst in
thomhurst/TUnit#6500


**Full Changelog**:
thomhurst/TUnit@v1.61.38...v1.62.0

Commits viewable in [compare
view](thomhurst/TUnit@v1.61.38...v1.63.0).
</details>

[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=TUnit.Core&package-manager=nuget&previous-version=1.61.38&new-version=1.63.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
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