Use fully-qualified type names in generated partial method signatures (#1135)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
This commit is contained in:
parent
b2afc2fd65
commit
4a2c36cf0c
1 changed files with 394 additions and 5 deletions
|
|
@ -1561,7 +1561,7 @@ public partial class XmlToDescriptionGeneratorTests
|
|||
partial class TestTools
|
||||
{
|
||||
[Description("Async tool")]
|
||||
public partial Task<string> DoWorkAsync(string input);
|
||||
public partial global::System.Threading.Tasks.Task<string> DoWorkAsync(string input);
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
|
@ -1611,7 +1611,7 @@ public partial class XmlToDescriptionGeneratorTests
|
|||
partial class TestTools
|
||||
{
|
||||
[Description("Static async tool")]
|
||||
public static partial Task<string> StaticAsyncMethod(string input);
|
||||
public static partial global::System.Threading.Tasks.Task<string> StaticAsyncMethod(string input);
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
|
@ -1663,7 +1663,7 @@ public partial class XmlToDescriptionGeneratorTests
|
|||
partial class TestTools
|
||||
{
|
||||
[Description("Async tool with defaults")]
|
||||
public static partial Task<string> AsyncWithDefaults([Description("The input")] string input, [Description("Timeout in ms")] int timeout = 1000);
|
||||
public static partial global::System.Threading.Tasks.Task<string> AsyncWithDefaults([Description("The input")] string input, [Description("Timeout in ms")] int timeout = 1000);
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
|
@ -1719,6 +1719,387 @@ public partial class XmlToDescriptionGeneratorTests
|
|||
AssertGeneratedSourceEquals(expected, result.GeneratedSources[0].SourceText.ToString());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(true)]
|
||||
[InlineData(false)]
|
||||
public void Generator_WithTypeFromDifferentNamespace_GeneratesFullyQualifiedTypeName(bool useFullyQualifiedTypesInSource)
|
||||
{
|
||||
// This test validates that regardless of whether the source code uses fully qualified
|
||||
// or unqualified type names, the generator always emits fully qualified type names
|
||||
// with global:: prefix. This fixes the issue where parameter types from different
|
||||
// namespaces caused build failures.
|
||||
string usingDirective = useFullyQualifiedTypesInSource ? "" : "using MyApp.Actions;";
|
||||
string returnType = useFullyQualifiedTypesInSource ? "System.Threading.Tasks.Task<string>" : "Task<string>";
|
||||
string parameterType = useFullyQualifiedTypesInSource ? "MyApp.Actions.MyAction" : "MyAction";
|
||||
|
||||
var result = RunGenerator($$"""
|
||||
using ModelContextProtocol.Server;
|
||||
using System.ComponentModel;
|
||||
using System.Threading.Tasks;
|
||||
{{usingDirective}}
|
||||
|
||||
namespace MyApp.Actions
|
||||
{
|
||||
public enum MyAction
|
||||
{
|
||||
One,
|
||||
Two
|
||||
}
|
||||
}
|
||||
|
||||
namespace MyApp
|
||||
{
|
||||
[McpServerToolType]
|
||||
public sealed partial class Tools
|
||||
{
|
||||
/// <summary>Do a thing based on an action.</summary>
|
||||
/// <param name="action">The action to perform.</param>
|
||||
[McpServerTool]
|
||||
public async partial {{returnType}} DoThing({{parameterType}} action)
|
||||
=> await Task.FromResult("ok");
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
Assert.True(result.Success);
|
||||
Assert.Single(result.GeneratedSources);
|
||||
|
||||
// Regardless of source qualification, generated code should always use
|
||||
// fully qualified type names with global:: prefix
|
||||
var expected = $$"""
|
||||
// <auto-generated/>
|
||||
// ModelContextProtocol.Analyzers {{typeof(XmlToDescriptionGenerator).Assembly.GetName().Version}}
|
||||
|
||||
#pragma warning disable
|
||||
|
||||
using System.ComponentModel;
|
||||
using ModelContextProtocol.Server;
|
||||
|
||||
namespace MyApp
|
||||
{
|
||||
partial class Tools
|
||||
{
|
||||
[Description("Do a thing based on an action.")]
|
||||
public partial global::System.Threading.Tasks.Task<string> DoThing([Description("The action to perform.")] global::MyApp.Actions.MyAction action);
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
AssertGeneratedSourceEquals(expected, result.GeneratedSources[0].SourceText.ToString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Generator_WithGenericListParameter_GeneratesFullyQualifiedTypeName()
|
||||
{
|
||||
var result = RunGenerator("""
|
||||
using ModelContextProtocol.Server;
|
||||
using System.ComponentModel;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace MyApp.Models
|
||||
{
|
||||
public class Item { }
|
||||
}
|
||||
|
||||
namespace MyApp
|
||||
{
|
||||
[McpServerToolType]
|
||||
public sealed partial class Tools
|
||||
{
|
||||
/// <summary>Process items.</summary>
|
||||
/// <param name="items">The items to process.</param>
|
||||
[McpServerTool]
|
||||
public static partial string ProcessItems(List<MyApp.Models.Item> items)
|
||||
=> "ok";
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
Assert.True(result.Success);
|
||||
Assert.Single(result.GeneratedSources);
|
||||
|
||||
var expected = $$"""
|
||||
// <auto-generated/>
|
||||
// ModelContextProtocol.Analyzers {{typeof(XmlToDescriptionGenerator).Assembly.GetName().Version}}
|
||||
|
||||
#pragma warning disable
|
||||
|
||||
using System.ComponentModel;
|
||||
using ModelContextProtocol.Server;
|
||||
|
||||
namespace MyApp
|
||||
{
|
||||
partial class Tools
|
||||
{
|
||||
[Description("Process items.")]
|
||||
public static partial string ProcessItems([Description("The items to process.")] global::System.Collections.Generic.List<global::MyApp.Models.Item> items);
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
AssertGeneratedSourceEquals(expected, result.GeneratedSources[0].SourceText.ToString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Generator_WithGenericDictionaryParameter_GeneratesFullyQualifiedTypeName()
|
||||
{
|
||||
var result = RunGenerator("""
|
||||
using ModelContextProtocol.Server;
|
||||
using System.ComponentModel;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace MyApp.Models
|
||||
{
|
||||
public class Key { }
|
||||
public class Value { }
|
||||
}
|
||||
|
||||
namespace MyApp
|
||||
{
|
||||
[McpServerToolType]
|
||||
public sealed partial class Tools
|
||||
{
|
||||
/// <summary>Process mapping.</summary>
|
||||
/// <param name="mapping">The mapping to process.</param>
|
||||
[McpServerTool]
|
||||
public static partial string ProcessMapping(Dictionary<MyApp.Models.Key, MyApp.Models.Value> mapping)
|
||||
=> "ok";
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
Assert.True(result.Success);
|
||||
Assert.Single(result.GeneratedSources);
|
||||
|
||||
var expected = $$"""
|
||||
// <auto-generated/>
|
||||
// ModelContextProtocol.Analyzers {{typeof(XmlToDescriptionGenerator).Assembly.GetName().Version}}
|
||||
|
||||
#pragma warning disable
|
||||
|
||||
using System.ComponentModel;
|
||||
using ModelContextProtocol.Server;
|
||||
|
||||
namespace MyApp
|
||||
{
|
||||
partial class Tools
|
||||
{
|
||||
[Description("Process mapping.")]
|
||||
public static partial string ProcessMapping([Description("The mapping to process.")] global::System.Collections.Generic.Dictionary<global::MyApp.Models.Key, global::MyApp.Models.Value> mapping);
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
AssertGeneratedSourceEquals(expected, result.GeneratedSources[0].SourceText.ToString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Generator_WithArrayParameter_GeneratesFullyQualifiedTypeName()
|
||||
{
|
||||
var result = RunGenerator("""
|
||||
using ModelContextProtocol.Server;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace MyApp.Models
|
||||
{
|
||||
public class Item { }
|
||||
}
|
||||
|
||||
namespace MyApp
|
||||
{
|
||||
[McpServerToolType]
|
||||
public sealed partial class Tools
|
||||
{
|
||||
/// <summary>Process items array.</summary>
|
||||
/// <param name="items">The items array to process.</param>
|
||||
[McpServerTool]
|
||||
public static partial string ProcessItemsArray(MyApp.Models.Item[] items)
|
||||
=> "ok";
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
Assert.True(result.Success);
|
||||
Assert.Single(result.GeneratedSources);
|
||||
|
||||
var expected = $$"""
|
||||
// <auto-generated/>
|
||||
// ModelContextProtocol.Analyzers {{typeof(XmlToDescriptionGenerator).Assembly.GetName().Version}}
|
||||
|
||||
#pragma warning disable
|
||||
|
||||
using System.ComponentModel;
|
||||
using ModelContextProtocol.Server;
|
||||
|
||||
namespace MyApp
|
||||
{
|
||||
partial class Tools
|
||||
{
|
||||
[Description("Process items array.")]
|
||||
public static partial string ProcessItemsArray([Description("The items array to process.")] global::MyApp.Models.Item[] items);
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
AssertGeneratedSourceEquals(expected, result.GeneratedSources[0].SourceText.ToString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Generator_WithNullableReferenceTypeParameter_GeneratesFullyQualifiedTypeName()
|
||||
{
|
||||
var result = RunGenerator("""
|
||||
using ModelContextProtocol.Server;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace MyApp.Models
|
||||
{
|
||||
public class Item { }
|
||||
}
|
||||
|
||||
namespace MyApp
|
||||
{
|
||||
[McpServerToolType]
|
||||
public sealed partial class Tools
|
||||
{
|
||||
/// <summary>Process optional item.</summary>
|
||||
/// <param name="item">The optional item to process.</param>
|
||||
[McpServerTool]
|
||||
public static partial string ProcessOptionalItem(MyApp.Models.Item? item)
|
||||
=> "ok";
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
Assert.True(result.Success);
|
||||
Assert.Single(result.GeneratedSources);
|
||||
|
||||
var expected = $$"""
|
||||
// <auto-generated/>
|
||||
// ModelContextProtocol.Analyzers {{typeof(XmlToDescriptionGenerator).Assembly.GetName().Version}}
|
||||
|
||||
#pragma warning disable
|
||||
|
||||
using System.ComponentModel;
|
||||
using ModelContextProtocol.Server;
|
||||
|
||||
namespace MyApp
|
||||
{
|
||||
partial class Tools
|
||||
{
|
||||
[Description("Process optional item.")]
|
||||
public static partial string ProcessOptionalItem([Description("The optional item to process.")] global::MyApp.Models.Item? item);
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
AssertGeneratedSourceEquals(expected, result.GeneratedSources[0].SourceText.ToString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Generator_WithNestedTypeParameter_GeneratesFullyQualifiedTypeName()
|
||||
{
|
||||
var result = RunGenerator("""
|
||||
using ModelContextProtocol.Server;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace MyApp.Models
|
||||
{
|
||||
public class Container
|
||||
{
|
||||
public class NestedItem { }
|
||||
}
|
||||
}
|
||||
|
||||
namespace MyApp
|
||||
{
|
||||
[McpServerToolType]
|
||||
public sealed partial class Tools
|
||||
{
|
||||
/// <summary>Process nested item.</summary>
|
||||
/// <param name="item">The nested item to process.</param>
|
||||
[McpServerTool]
|
||||
public static partial string ProcessNestedItem(MyApp.Models.Container.NestedItem item)
|
||||
=> "ok";
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
Assert.True(result.Success);
|
||||
Assert.Single(result.GeneratedSources);
|
||||
|
||||
var expected = $$"""
|
||||
// <auto-generated/>
|
||||
// ModelContextProtocol.Analyzers {{typeof(XmlToDescriptionGenerator).Assembly.GetName().Version}}
|
||||
|
||||
#pragma warning disable
|
||||
|
||||
using System.ComponentModel;
|
||||
using ModelContextProtocol.Server;
|
||||
|
||||
namespace MyApp
|
||||
{
|
||||
partial class Tools
|
||||
{
|
||||
[Description("Process nested item.")]
|
||||
public static partial string ProcessNestedItem([Description("The nested item to process.")] global::MyApp.Models.Container.NestedItem item);
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
AssertGeneratedSourceEquals(expected, result.GeneratedSources[0].SourceText.ToString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Generator_WithNullableValueTypeParameter_GeneratesFullyQualifiedTypeName()
|
||||
{
|
||||
var result = RunGenerator("""
|
||||
using ModelContextProtocol.Server;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace MyApp.Models
|
||||
{
|
||||
public struct MyStruct { }
|
||||
}
|
||||
|
||||
namespace MyApp
|
||||
{
|
||||
[McpServerToolType]
|
||||
public sealed partial class Tools
|
||||
{
|
||||
/// <summary>Process optional struct.</summary>
|
||||
/// <param name="value">The optional struct to process.</param>
|
||||
[McpServerTool]
|
||||
public static partial string ProcessOptionalStruct(MyApp.Models.MyStruct? value)
|
||||
=> "ok";
|
||||
}
|
||||
}
|
||||
""");
|
||||
|
||||
Assert.True(result.Success);
|
||||
Assert.Single(result.GeneratedSources);
|
||||
|
||||
var expected = $$"""
|
||||
// <auto-generated/>
|
||||
// ModelContextProtocol.Analyzers {{typeof(XmlToDescriptionGenerator).Assembly.GetName().Version}}
|
||||
|
||||
#pragma warning disable
|
||||
|
||||
using System.ComponentModel;
|
||||
using ModelContextProtocol.Server;
|
||||
|
||||
namespace MyApp
|
||||
{
|
||||
partial class Tools
|
||||
{
|
||||
[Description("Process optional struct.")]
|
||||
public static partial string ProcessOptionalStruct([Description("The optional struct to process.")] global::MyApp.Models.MyStruct? value);
|
||||
}
|
||||
}
|
||||
""";
|
||||
|
||||
AssertGeneratedSourceEquals(expected, result.GeneratedSources[0].SourceText.ToString());
|
||||
}
|
||||
|
||||
private GeneratorRunResult RunGenerator([StringSyntax("C#-test")] string source, params string[] expectedDiagnosticIds)
|
||||
{
|
||||
var syntaxTree = CSharpSyntaxTree.ParseText(source);
|
||||
|
|
|
|||
Loading…
Reference in a new issue