Skip to content

fix(mocks): emit 'where T : default' for interface-constrained generic methods#6458

Merged
thomhurst merged 1 commit into
mainfrom
fix/6456-mock-default-constraint
Jul 21, 2026
Merged

fix(mocks): emit 'where T : default' for interface-constrained generic methods#6458
thomhurst merged 1 commit into
mainfrom
fix/6456-mock-default-constraint

Conversation

@thomhurst

Copy link
Copy Markdown
Owner

Summary

Fixes #6456 — mocking Microsoft.Kiota.Abstractions.IRequestAdapter failed to compile with CS0539/CS0535/CS0453/CS0314.

Root cause

Kiota's generic methods carry an interface constraint:

Task<ModelType?> SendAsync<ModelType>(...) where ModelType : IParsable;

In an explicit interface implementation, constraints are inherited and T? is parsed as Nullable<T> unless T is known to be a reference/value type or a where T : default clause is present. The generator only emitted default for fully unconstrained type parameters (IsUnconstrained() gate), so an interface-only constraint like IParsable — which leaves T neither known-reference nor known-value — got no clause. The compiler then read Task<ModelType?> as Task<Nullable<ModelType>>, the signature no longer matched the interface member, and everything cascaded.

Fix

Gate the default clause on ITypeParameterSymbol.IsReferenceType/IsValueType instead, which Roslyn computes transitively (base-class constraints, type-parameter constraints). Type parameters constrained only by interfaces/notnull/new() now correctly get where T : default when T? appears in the signature; known-reference (class, base-class) and known-value (struct, unmanaged) parameters still don't.

Tests

  • KiotaMockTests (source-gen): generates a mock for the real Microsoft.Kiota.Abstractions.IRequestAdapter (new package reference), asserts the where ModelType : default clause is emitted and none of the four reported error codes are produced.
  • Issue6456Tests (runtime): mocks IRequestAdapter, verifies SendNoContentAsync and a configured SendAsync<TestModel> with an IParsable-constrained model — mirrors the issue repro.
  • Full TUnit.Mocks.SourceGenerator.Tests (78) and TUnit.Mocks.Tests (1160) pass on net10.0; all TFMs (net472/net8/net9/net10) build.

…c methods

Explicit interface implementations inherit constraints, but 'T?' in them is
parsed as Nullable<T> unless the type parameter is known to be a reference or
value type, or a 'where T : default' clause is present. The generator only
emitted 'default' for fully unconstrained type parameters, so interfaces like
Kiota's IRequestAdapter ('where ModelType : IParsable') generated mocks that
failed with CS0539/CS0535/CS0453/CS0314.

Gate the clause on ITypeParameterSymbol.IsReferenceType/IsValueType instead,
which Roslyn computes transitively across base-class and type-parameter
constraints.

Fixes #6456
@greptile-apps

greptile-apps Bot commented Jul 21, 2026

Copy link
Copy Markdown

Greptile Summary

This PR fixes mock generation for nullable generic methods constrained only by interfaces. The main changes are:

  • Detect generic parameters whose reference/value kind remains unknown.
  • Emit where T : default for affected explicit implementations.
  • Add source-generator and runtime tests using Kiota's IRequestAdapter.
  • Add the centrally managed Kiota test dependency.

Confidence Score: 5/5

This looks safe to merge.

  • No blocking issues found in the changed code.
  • Known reference and value types keep their existing constraint behavior.
  • Tests cover generated compilation and runtime invocation for the reported interface constraint.

Important Files Changed

Filename Overview
src/TUnit.Mocks.SourceGenerator/Extensions/MethodSymbolExtensions.cs Expands default-constraint detection to nullable parameters whose reference/value kind is unknown.
src/TUnit.Mocks.SourceGenerator/Discovery/MemberDiscovery.cs Uses the revised predicate when building generic method models.
tests/TUnit.Mocks.SourceGenerator.Tests/KiotaMockTests.cs Checks generated Kiota mocks for the default clause and reported compiler errors.
tests/TUnit.Mocks.Tests/Issue6456Tests.cs Exercises configuration, invocation, and verification of a Kiota request-adapter mock.
Directory.Packages.props Adds the centrally managed Kiota package version used by the tests.

Reviews (1): Last reviewed commit: "fix(mocks): emit 'where T : default' for..." | Re-trigger Greptile

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code review

Summary: Fixes #6456 by broadening the gate for emitting where T : default on explicit interface implementations from "fully unconstrained" (IsUnconstrained()) to "not known to be a reference or value type" (IsReferenceType/IsValueType). This correctly covers interface-only constraints like Kiota's where ModelType : IParsable. The root-cause analysis is sound, and IsReferenceType/IsValueType are computed transitively by Roslyn, so the fix also correctly handles base-class/type-parameter constraint chains, not just literal class/struct keywords. I checked the two existing snapshots that already exercise the default clause path (Interface_FluentUI_Shape_Nullable_Warnings.verified.txt, Interface_With_Unconstrained_Nullable_Generic.verified.txt) — both are fully-unconstrained cases, so they're unaffected by the widened gate. No regressions expected there.

Suggestion — test dependency choice: KiotaMockTests.cs (and the runtime counterpart Issue6456Tests.cs) pull in a real third-party package (Microsoft.Kiota.Abstractions, new PackageVersion + two new PackageReferences) purely to reproduce the interface-constraint shape. That's a departure from this test suite's existing convention: Interface_FluentUI_Shape_Nullable_Warnings in tests/TUnit.Mocks.SourceGenerator.Tests/MockGeneratorTests.cs:1961 deliberately uses a synthetic in-source interface shaped like FluentUI's IDialogService rather than referencing the real FluentUI package — with a comment noting earlier synthetic repros needed iteration to match the real-world shape exactly.

The same approach would work here: a local interface with where ModelType : ISomeMarker reproduces "interface-only constraint on an explicit-impl generic method" without a live NuGet dependency. The bug is fundamentally about Roslyn constraint-resolution semantics, not anything Kiota-specific, so a synthetic repro would be just as effective at pinning the regression while avoiding restore/network dependency and version-drift risk in the source-generator test project.

I'd keep the one small end-to-end runtime test against real Kiota (Issue6456Tests.cs) for exact bug-report fidelity, but convert the source-generator-level compile check (KiotaMockTests.cs) to a synthetic repro consistent with the FluentUI precedent — that drops one of the two new package dependencies while keeping equivalent coverage.

Everything else — the rename (IsUnconstrainedWithNullableUsageNeedsDefaultConstraintForNullableUsage), the explanatory comment, and the tests' actual assertions — looks solid and low-risk. No CLAUDE.md compliance issues found (this touches TUnit.Mocks.SourceGenerator, not core engine metadata collection, so the dual-mode rule doesn't apply).

@thomhurst
thomhurst enabled auto-merge (squash) July 21, 2026 16:37

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2f933b06ab

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread tests/TUnit.Mocks.SourceGenerator.Tests/KiotaMockTests.cs
@thomhurst
thomhurst merged commit e61d0f4 into main Jul 21, 2026
16 checks passed
@thomhurst
thomhurst deleted the fix/6456-mock-default-constraint branch July 21, 2026 16:55
This was referenced Jul 21, 2026
This was referenced Jul 22, 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.

[Bug]: Mocking interface IRequestAdapter with constrained generic type parameter throws error

1 participant