Skip to content

Comments

Fix DefaultMethodInfoLocator to handle overloaded methods#3315

Closed
Copilot wants to merge 5 commits intomainfrom
copilot/fix-overloaded-methods-issue
Closed

Fix DefaultMethodInfoLocator to handle overloaded methods#3315
Copilot wants to merge 5 commits intomainfrom
copilot/fix-overloaded-methods-issue

Conversation

Copy link
Contributor

Copilot AI commented Jan 28, 2026

Issue describing the changes in this PR

DefaultMethodInfoLocator.GetMethod() throws AmbiguousMatchException when a class contains overloaded methods with the same name, even when only one has the [Function] attribute.

Changes

Core Fix

  • Replace Type.GetMethod(methodName) with GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly).Where(m => m.Name == methodName)
  • When multiple methods match: use SingleOrDefault to select the one decorated with [Function] attribute (throws exception if multiple have the attribute)
  • When single method matches: return it directly (preserves existing behavior)
  • Use BindingFlags.DeclaredOnly to match only methods declared on the type itself, consistent with original Type.GetMethod() behavior

Implementation Details

  • Use reflection to check for FunctionAttribute by name: m.GetCustomAttributes().Any(a => a.GetType().Name == "FunctionAttribute")
  • No compile-time dependencies added - attribute is checked via reflection at runtime
  • SingleOrDefault ensures configuration errors (multiple methods with [Function] attribute) are caught and reported

Test Coverage

  • GrpcFunctionDefinition_OverloadedMethod_Creates - Happy path: overloaded methods where only one has [Function] attribute
  • GrpcFunctionDefinition_OverloadedMethod_NoFunctionAttribute_ThrowsException - Edge case: multiple overloads with no [Function] attribute
  • GrpcFunctionDefinition_OverloadedMethod_MultipleFunctionAttributes_ThrowsException - Edge case: multiple overloads with multiple [Function] attributes

Example Scenario

public class MyFunctions
{
    // Helper method - no [Function] attribute
    public void Run(string message) { }
    
    // Actual Azure Function
    [Function("MyFunction")]
    public HttpResponseData Run(HttpRequestData req) { }
}

Previously: Throws AmbiguousMatchException
Now: Correctly identifies the method with [Function] attribute

Pull request checklist

  • My changes do not require documentation changes
  • My changes should not be added to the release notes for the next release
  • My changes do not need to be backported to a previous version
  • I have added all required tests (Unit tests, E2E tests)
Original prompt

This section details on the original issue you should resolve

<issue_title>DefaultMethodInfoLocator doesn't cater for overloaded methods</issue_title>
<issue_description>### Description

We have a situation where there are overloaded methods in function "controller".

Only 1 is annotated with FunctionAttribute, The other overload is does not have the function attribute, not does it have an trigger attributes on any parameter.

MethodInfo? methodInfo = functionType?.GetMethod(methodName);

This assumes a no overloads.

This needs to be modified to filter for methods that have the FunctionName attribute.

Steps to reproduce

Add 2 overloads for a method. Only add FunctionName to one method.

</issue_description>

<agent_instructions>Use my last comment in this github issue to fix this bug and make sure to include tests.</agent_instructions>

Comments on the Issue (you are @copilot in this section)

@liliankasem Thanks for reporting this. Confirmed as a bug.

Summary

DefaultMethodInfoLocator.GetMethod uses Type.GetMethod(string) which throws AmbiguousMatchException when there are overloaded methods with the same name, even if only one has the [Function] attribute.

Solution for the fix

File: src/DotNetWorker.Core/Invocation/DefaultMethodInfoLocator.cs

Replace line 39:

MethodInfo? methodInfo = functionType?.GetMethod(methodName);

With logic that handles overloads by filtering for the method with FunctionAttribute:

var methods = functionType?.GetMethods().Where(m => m.Name == methodName).ToArray();
MethodInfo? methodInfo = methods?.Length switch
{
    1 => methods[0],
    > 1 => methods.SingleOrDefault(m => m.GetCustomAttribute<FunctionAttribute>() is not null),
    _ => null
};

You'll need to add using System.Linq; at the top of the file.

Test coverage: Add a test case in GrpcFunctionDefinitionTests.cs with a class that has overloaded methods where only one has [Function].

private class MyOverloadedFunctionClass
{
    // Helper overload - no Function attribute
    public void Run(string message) { }
    
    [Function("MyFunction")]
    public HttpResponseData Run(HttpRequestData req)
    {
        return req.CreateResponse();
    }
}
```</body></comment_new>
</comments>

💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

Copilot AI and others added 2 commits January 28, 2026 22:15
Co-authored-by: liliankasem <2198905+liliankasem@users.noreply.github.com>
Co-authored-by: liliankasem <2198905+liliankasem@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix DefaultMethodInfoLocator to handle overloaded methods Fix DefaultMethodInfoLocator to handle overloaded methods Jan 28, 2026
Copilot AI requested a review from liliankasem January 28, 2026 22:21
@liliankasem liliankasem marked this pull request as ready for review January 28, 2026 22:23
Copilot AI review requested due to automatic review settings January 28, 2026 22:23
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes a bug in DefaultMethodInfoLocator.GetMethod() that threw AmbiguousMatchException when a class contained overloaded methods with the same name, even when only one had the [Function] attribute.

Changes:

  • Modified method lookup logic to handle overloaded methods by filtering for the method with [Function] attribute
  • Added project reference from DotNetWorker.Core to Worker.Extensions.Abstractions for FunctionAttribute access
  • Added test case for overloaded methods scenario

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.

File Description
src/DotNetWorker.Core/Invocation/DefaultMethodInfoLocator.cs Replaced Type.GetMethod() with GetMethods().Where() and added logic to select method with [Function] attribute when multiple overloads exist
src/DotNetWorker.Core/DotNetWorker.Core.csproj Added project reference to Worker.Extensions.Abstractions for FunctionAttribute access
test/DotNetWorker.Tests/GrpcFunctionDefinitionTests.cs Added test case with overloaded methods where only one has [Function] attribute

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

MethodInfo? methodInfo = methods?.Length switch
{
1 => methods[0],
> 1 => methods.FirstOrDefault(m => m.GetCustomAttribute<FunctionAttribute>() is not null),
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

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

Using FirstOrDefault instead of SingleOrDefault could silently return the wrong method when multiple overloads are decorated with [Function] attribute. This would be a configuration error by the developer, but FirstOrDefault will silently pick the first match instead of throwing an exception to alert them to the problem. Consider using SingleOrDefault to ensure that only one method has the [Function] attribute when there are multiple overloads.

Suggested change
> 1 => methods.FirstOrDefault(m => m.GetCustomAttribute<FunctionAttribute>() is not null),
> 1 => methods.SingleOrDefault(m => m.GetCustomAttribute<FunctionAttribute>() is not null),

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed to SingleOrDefault in commit 9c1bfba. This will now throw an InvalidOperationException if multiple overloads have the [Function] attribute, alerting developers to the configuration error.

Type? functionType = assembly.GetType(typeName);

MethodInfo? methodInfo = functionType?.GetMethod(methodName);
var methods = functionType?.GetMethods().Where(m => m.Name == methodName).ToArray();
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

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

GetMethods() without BindingFlags will include all public methods including inherited methods from base classes. This could lead to unexpected behavior if a base class has a method with the same name. Consider using BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly to only search methods declared on the type itself, which would be more consistent with the original Type.GetMethod() behavior.

Suggested change
var methods = functionType?.GetMethods().Where(m => m.Name == methodName).ToArray();
var methods = functionType?.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly).Where(m => m.Name == methodName).ToArray();

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly in commit 9c1bfba. This ensures we only search methods declared on the type itself, matching the original Type.GetMethod() behavior.

Comment on lines 257 to 313
[Fact]
public void GrpcFunctionDefinition_OverloadedMethod_Creates()
{
using var testVariables = new TestScopedEnvironmentVariable("FUNCTIONS_WORKER_DIRECTORY", ".");

var bindingInfoProvider = new DefaultOutputBindingsInfoProvider();
var methodInfoLocator = new DefaultMethodInfoLocator();

string fullPathToThisAssembly = GetType().Assembly.Location;
var functionLoadRequest = new FunctionLoadRequest
{
FunctionId = "abc",
Metadata = new RpcFunctionMetadata
{
EntryPoint = $"Microsoft.Azure.Functions.Worker.Tests.{nameof(GrpcFunctionDefinitionTests)}+{nameof(MyOverloadedFunctionClass)}.{nameof(MyOverloadedFunctionClass.Run)}",
ScriptFile = Path.GetFileName(fullPathToThisAssembly),
Name = "myfunction"
}
};

// We base this on the request exclusively, not the binding attributes.
functionLoadRequest.Metadata.Bindings.Add("req", new BindingInfo { Type = "HttpTrigger", Direction = Direction.In });
functionLoadRequest.Metadata.Bindings.Add("$return", new BindingInfo { Type = "Http", Direction = Direction.Out });

FunctionDefinition definition = functionLoadRequest.ToFunctionDefinition(methodInfoLocator);

Assert.Equal(functionLoadRequest.FunctionId, definition.Id);
Assert.Equal(functionLoadRequest.Metadata.EntryPoint, definition.EntryPoint);
Assert.Equal(functionLoadRequest.Metadata.Name, definition.Name);
Assert.Equal(fullPathToThisAssembly, definition.PathToAssembly);

// Parameters - should match the overload with [Function] attribute
Assert.Collection(definition.Parameters,
p =>
{
Assert.Equal("req", p.Name);
Assert.Equal(typeof(HttpRequestData), p.Type);
});

// InputBindings
Assert.Collection(definition.InputBindings,
p =>
{
Assert.Equal("req", p.Key);
Assert.Equal(BindingDirection.In, p.Value.Direction);
Assert.Equal("HttpTrigger", p.Value.Type);
});

// OutputBindings
Assert.Collection(definition.OutputBindings,
p =>
{
Assert.Equal("$return", p.Key);
Assert.Equal(BindingDirection.Out, p.Value.Direction);
Assert.Equal("Http", p.Value.Type);
});
}
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

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

The test only covers the happy path where one overload has [Function] attribute and others don't. Consider adding additional test cases for edge cases: 1) Multiple overloads where none have [Function] attribute (should fail with a clear error), and 2) Multiple overloads where more than one has [Function] attribute (should fail or handle deterministically). These edge cases would help ensure the implementation handles configuration errors appropriately.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added two edge case tests in commit 9c1bfba:

  1. GrpcFunctionDefinition_OverloadedMethod_NoFunctionAttribute_ThrowsException - Tests multiple overloads with no [Function] attribute
  2. GrpcFunctionDefinition_OverloadedMethod_MultipleFunctionAttributes_ThrowsException - Tests multiple overloads with multiple [Function] attributes

Both tests verify appropriate error handling.

{
using var testVariables = new TestScopedEnvironmentVariable("FUNCTIONS_WORKER_DIRECTORY", ".");

var bindingInfoProvider = new DefaultOutputBindingsInfoProvider();
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

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

This assignment to bindingInfoProvider is useless, since its value is never read.

Suggested change
var bindingInfoProvider = new DefaultOutputBindingsInfoProvider();

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed the unused bindingInfoProvider variable in commit 9c1bfba.

Copy link
Member

@kshyju kshyju left a comment

Choose a reason for hiding this comment

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

Are we even using the DefaultMethodInfoLocator? Are we not using the source generated executor version which does not rely on this class?

@liliankasem
Copy link
Member

liliankasem commented Jan 28, 2026

DefaultMethodInfoLocator

I forgot we did the work to move to source gen. But is DefaultMethodInfoLocator 100% unused?

Asked copilot to search for this:

DefaultMethodInfoLocator IS still used in these scenarios:

  1. Slow path fallback - The source-generated DirectFunctionExecutor has two paths:
  • Fast path: Public methods are invoked directly (no reflection, no DefaultMethodInfoLocator)

  • Slow path: Falls back to DefaultFunctionExecutor → DefaultFunctionInvokerFactory → DefaultMethodInfoLocator for:
    Non-public function methods (internal, protected, etc.)
    Functions in dependent assemblies

  1. Source generation disabled - If users disable source generation or it fails for some reason

  2. Test scenarios - Many tests use DefaultMethodInfoLocator directly

So yes, the fix is still needed for users with:

  • internal or non-public function methods
  • Functions defined in class libraries (dependent assemblies)
  • Scenarios where source generation isn't active

</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\extensions\Worker.Extensions.Abstractions\src\Worker.Extensions.Abstractions.csproj" />
Copy link
Contributor

Choose a reason for hiding this comment

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

This dependency is concerning. Why is now the first time we are introducing this? Will this be problematic to add?

Copy link
Member

Choose a reason for hiding this comment

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

Yeah I don't understand why this would even be added?

@copilot can you remove this dependency? Why did you add in the first place?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed the project reference in commit 9c1bfba.

The dependency was initially added because I used GetCustomAttribute<FunctionAttribute>() which required a compile-time reference. However, this was unnecessary - the attribute can be checked using reflection by name: m.GetCustomAttributes().Any(a => a.GetType().Name == "FunctionAttribute"). This works because the attribute is loaded from the assembly at runtime, so no compile-time reference is needed.

Copy link
Contributor

Choose a reason for hiding this comment

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

m.GetCustomAttributes().Any(a => a.GetType().Name == "FunctionAttribute").

This is not equivalent. Assembly or namespace is not being validated.

Copilot AI and others added 2 commits February 4, 2026 22:14
…dge case tests

Co-authored-by: liliankasem <2198905+liliankasem@users.noreply.github.com>
Co-authored-by: liliankasem <2198905+liliankasem@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Feb 4, 2026

Just as a heads up, I was blocked by some firewall rules while working on your feedback. Expand below for details.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • azfunc.pkgs.visualstudio.com
    • Triggering command: /usr/bin/dotnet dotnet build src/DotNetWorker.Core/DotNetWorker.Core.csproj --no-restore (dns block)
    • Triggering command: /usr/bin/dotnet dotnet restore DotNetWorker.sln --ignore-failed-sources (dns block)
    • Triggering command: /usr/bin/dotnet dotnet build DotNetWorker.sln --no-restore (dns block)
  • vb4vsblobprodcus33.vsblob.vsassets.io
    • Triggering command: /usr/bin/dotnet dotnet restore DotNetWorker.sln --ignore-failed-sources (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

@jviau
Copy link
Contributor

jviau commented Feb 5, 2026

@liliankasem I think copilot got it a bit wrong with this being a slow fallback path. I thought they were mutually exclusive. The source gen executor completely replaces the default executor. No fallback mechanism is in place. I question the priority of this issue/fix given the source gen route is the better option. Customers would be better served to investigate why they can't use source gen than us investing in this executor. I am also cautious because of the FunctionAttribute dependency not being in Core. It is weird we don't have access to it there, but investigation for that is a potentially much larger task.

@liliankasem
Copy link
Member

@liliankasem I think copilot got it a bit wrong with this being a slow fallback path. I thought they were mutually exclusive. The source gen executor completely replaces the default executor. No fallback mechanism is in place. I question the priority of this issue/fix given the source gen route is the better option. Customers would be better served to investigate why they can't use source gen than us investing in this executor. I am also cautious because of the FunctionAttribute dependency not being in Core. It is weird we don't have access to it there, but investigation for that is a potentially much larger task.

I agree - I don't think its worth the effort here. I was hoping this was a quick fix when I assigned to copilot but didn't have all of this context. I think let's close this and point folks at source gen.

@liliankasem liliankasem closed this Feb 5, 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.

DefaultMethodInfoLocator doesn't cater for overloaded methods

4 participants