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
|
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> [](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>
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> [](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>
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.