Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AmbiguousArgumentsException exception thrown if struct type in matcher has ctor with this call #865

Open
Tornhoof opened this issue Feb 19, 2025 · 2 comments

Comments

@Tornhoof
Copy link

Tornhoof commented Feb 19, 2025

Describe the bug
As soon as the readonly struct Path in the repro below has a call to another ctor the following AmbiguousArgumentsException is thrown:

NSubstitute.Exceptions.AmbiguousArgumentsException
  HResult=0x80131500
  Message=Cannot determine argument specifications to use. Please use specifications for all arguments of the same type.
Method signature:
    Do<Parent, Base, Child>(Path<Base, Parent>, Func<Parent, Base>)
Method arguments (possible arg matchers are indicated with '*'):
    Do<Parent, Base, Child>(NSubRepro.Path`2[NSubRepro.Base,NSubRepro.Parent], *<null>*)
All queued specifications:
    any Path<Base, Parent>
    any Func<Parent, Child>
Matched argument specifications:
    Do<Parent, Base, Child>(NSubRepro.Path`2[NSubRepro.Base,NSubRepro.Parent], ???)

  Source=NSubstitute
  StackTrace:
   at NSubstitute.Core.Arguments.ArgumentSpecificationsFactory.Create(IList`1 argumentSpecs, Object[] arguments, IParameterInfo[] parameterInfos, MethodInfo methodInfo, MatchArgs matchArgs)
   at NSubstitute.Core.CallSpecificationFactory.CreateFrom(ICall call, MatchArgs matchArgs)
   at NSubstitute.Routing.Handlers.RecordCallSpecificationHandler.Handle(ICall call)
   at NSubstitute.Routing.Route.Handle(ICall call)
   at NSubstitute.Core.CallRouter.Route(ICall call)
   at NSubstitute.Proxies.CastleDynamicProxy.CastleForwardingInterceptor.Intercept(IInvocation invocation)
   at Castle.DynamicProxy.AbstractInvocation.Proceed()
   at NSubstitute.Proxies.CastleDynamicProxy.ProxyIdInterceptor.Intercept(IInvocation invocation)
   at Castle.DynamicProxy.AbstractInvocation.Proceed()
   at Castle.Proxies.ObjectProxy.Do[TParent,TBaseChild,TChild](Path`2 parentPath, Func`2 relation)
   at NSubRepro.Program.Main(String[] args) in D:\Repros\NSubRepro\NSubRepro\Program.cs:line 15

To Reproduce

Repro code:

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Threading.Tasks;
using NSubstitute;

namespace NSubRepro
{

    internal class Program
    {
        static void Main(string[] args)
        {
            var testMock = Substitute.For<ITest>();
            testMock.Do<Parent, Base, Child>(Arg.Any<Path<Base,  Parent>>(), Arg.Any<Func<Parent, Child>>()).Returns(Task.CompletedTask);
        }
    }


    public readonly struct Path<TVisit, TMatch>
    {

        private readonly ImmutableList<TVisit> _nodes;


        // As soon as the call to the other ctor is commented out, it works
        public Path() : this(ImmutableList<TVisit>.Empty)
        {

        }


        private Path(TVisit node) : this(ImmutableList<TVisit>.Empty.Add(node))
        {
        }

        private Path(ImmutableList<TVisit> nodes)
        {
            _nodes = nodes;
        }
    }

    public interface ITest
    {
        Task Do<TParent, TBaseChild, TChild>(Path<Base,TParent> parentPath, Func<TParent, TBaseChild> relation)
            where TParent : Base where TBaseChild : Base where TChild : TBaseChild;
    }

    public abstract class Base;

    public sealed class Parent : Base
    {
        public List<Child> Children { get; } = [];
    }

    public sealed class Child : Base;
}

As soon as the this call in the Path ctor is commented, the exception is gone. I'm not sure how that call is responsible for that exception.

Expected behaviour
No exception

Environment:

  • NSubstitute version: 5.3.0
  • NSubstitute.Analyzers version: Csharp 1.0.17
  • Platform: .NET 9, x64 Windows 11
@Ergamon
Copy link

Ergamon commented Feb 19, 2025

I dont know the reason, but surely it has something to do with the code in Arg.Any.

You can replace your mock setup with:

testMock.Do<Parent, Base, Child>(default, default!).ReturnsForAnyArgs(Task.CompletedTask);

and it does, what you want to achieve.

@Tornhoof
Copy link
Author

Thank you for your answer. That's a nice workaround :)

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

No branches or pull requests

2 participants