Skip to content

[Bug]: default constraint missing from explicit interface implementations with unconstrained generics #5362

@Kaskadee

Description

@Kaskadee

Description

When TUnit.Mocks generates an implementation for a method with an unconstrained generic type, it currently produces something like this:

public interface IFoo {
    Task<T?> DoSomethingAsync<T>();
}

public sealed class IFooMock : global::TUnit.Mocks.Mock<global::Sandbox.IFoo>, IFoo
{
    // ...
        
    global::System.Threading.Tasks.Task<T?> global::Sandbox.IFoo.DoSomethingAsync<T>() => Object.DoSomethingAsync<T>();
}

Expected Behavior

When a generic is unconstrained, the default constraint should be added to the generated explicit interface implementation.

From https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/generics/constraints-on-type-parameters:

Constraint Description
where T : default This constraint resolves the ambiguity when you need to specify an unconstrained type parameter when you override a method or provide an explicit interface implementation. The default constraint implies the base method without either the class or struct constraint. For more information, see the default constraint spec proposal.

Actual Behavior

The explicit interface implementation is generated without constraints and causes the error CS0453: The type parameter 'T' must be non-nullable value type in order to use it as underlying of 'System.Nullable<T>'. Consider adding a 'class' or 'default' constraint to allow using '?' as a nullable annotation.

Steps to Reproduce

using TUnit.Mocks.Generated.Sandbox;

namespace Sandbox;

public interface IFoo {
    Task<T?> DoSomethingAsync<T>();
}

public class FooImpl : IFoo {
    Task<T?> IFoo.DoSomethingAsync<T>() { // The type parameter 'T' must be non-nullable value type in order to use it as underlying of 'System.Nullable<T>'. Consider adding a 'class' or 'default' constraint to allow using '?' as a nullable annotation
        return Task.FromResult<T?>(default);
    }

    Task<T?> IFoo.DoSomethingAsync<T>() where T : default { // No errors when using `default` constraint
        return Task.FromResult<T?>(default);
    }
}

public class TestClass {
    [Test]
    public async Task SomeTest() {
        IFooMock mock = IFoo.Mock();
    }
}

TUnit Version

1.24.31

.NET Version

.NET 10.0

Operating System

Windows

IDE / Test Runner

JetBrains Rider

Error Output / Stack Trace

Restore complete (0.8s)
  Sandbox net10.0 failed with 5 error(s) (0.5s)
    /mnt/x/Sandbox/Sandbox/obj/Debug/net10.0/TUnit.Mocks.SourceGenerator/TUnit.Mocks.SourceGenerator.MockGenerator/Sandbox_IFoo_Mock.g.cs(12,70): error CS0539: 'IFooMock.DoSomethingAsync<T>()' in explicit interface declaration is not found among members of the interface that can be implemented
    /mnt/x/Sandbox/Sandbox/obj/Debug/net10.0/TUnit.Mocks.SourceGenerator/TUnit.Mocks.SourceGenerator.MockGenerator/Sandbox_IFoo_Mock.g.cs(6,84): error CS0535: 'IFooMock' does not implement interface member 'IFoo.DoSomethingAsync<T>()'
    /mnt/x/Sandbox/Sandbox/obj/Debug/net10.0/TUnit.Mocks.SourceGenerator/TUnit.Mocks.SourceGenerator.MockGenerator/Sandbox_IFoo_Mock.g.cs(12,70): error CS0453: The type 'T' must be a non-nullable value type in order to use it as parameter 'T' in the generic type or method 'Nullable<T>'

Build failed with 5 error(s) in 1.7s

Additional Context

No response

IDE-Specific Issue?

  • I've confirmed this issue occurs when running via dotnet test or dotnet run, not just in my IDE

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions