Skip to content

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

@Tornhoof

Description

@Tornhoof

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions