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

Ensure two interfaces with the same name do not cause compile errors #1542

Merged
merged 11 commits into from
Jun 9, 2024
5 changes: 3 additions & 2 deletions InterfaceStubGenerator.Shared/InterfaceStubGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@
/// <param name="candidateMethods">The candidate methods.</param>
/// <param name="candidateInterfaces">The candidate interfaces.</param>
/// <returns></returns>
public void GenerateInterfaceStubs<TContext>(

Check warning on line 93 in InterfaceStubGenerator.Shared/InterfaceStubGenerator.cs

View workflow job for this annotation

GitHub Actions / build / build

Member 'GenerateInterfaceStubs' does not access instance data and can be marked as static (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1822)
TContext context,
Action<TContext, Diagnostic> reportDiagnostic,
Action<TContext, string, SourceText> addSource,
Expand All @@ -105,7 +105,7 @@

// we're going to create a new compilation that contains the attribute.
// TODO: we should allow source generators to provide source during initialize, so that this step isn't required.
var options = (CSharpParseOptions)compilation.SyntaxTrees[0].Options;

Check warning on line 108 in InterfaceStubGenerator.Shared/InterfaceStubGenerator.cs

View workflow job for this annotation

GitHub Actions / build / build

In externally visible method 'void InterfaceStubGenerator.GenerateInterfaceStubs<TContext>(TContext context, Action<TContext, Diagnostic> reportDiagnostic, Action<TContext, string, SourceText> addSource, CSharpCompilation compilation, string? refitInternalNamespace, ImmutableArray<MethodDeclarationSyntax> candidateMethods, ImmutableArray<InterfaceDeclarationSyntax> candidateInterfaces)', validate parameter 'compilation' is non-null before using it. If appropriate, throw an 'ArgumentNullException' when the argument is 'null'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1062)

var disposableInterfaceSymbol = compilation.GetTypeByMetadataName(
"System.IDisposable"
Expand All @@ -116,7 +116,7 @@

if (httpMethodBaseAttributeSymbol == null)
{
reportDiagnostic(context, Diagnostic.Create(RefitNotReferenced, null));

Check warning on line 119 in InterfaceStubGenerator.Shared/InterfaceStubGenerator.cs

View workflow job for this annotation

GitHub Actions / build / build

In externally visible method 'void InterfaceStubGenerator.GenerateInterfaceStubs<TContext>(TContext context, Action<TContext, Diagnostic> reportDiagnostic, Action<TContext, string, SourceText> addSource, CSharpCompilation compilation, string? refitInternalNamespace, ImmutableArray<MethodDeclarationSyntax> candidateMethods, ImmutableArray<InterfaceDeclarationSyntax> candidateInterfaces)', validate parameter 'reportDiagnostic' is non-null before using it. If appropriate, throw an 'ArgumentNullException' when the argument is 'null'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1062)
return;
}

Expand Down Expand Up @@ -222,7 +222,7 @@
);

// add the attribute text
addSource(

Check warning on line 225 in InterfaceStubGenerator.Shared/InterfaceStubGenerator.cs

View workflow job for this annotation

GitHub Actions / build / build

In externally visible method 'void InterfaceStubGenerator.GenerateInterfaceStubs<TContext>(TContext context, Action<TContext, Diagnostic> reportDiagnostic, Action<TContext, string, SourceText> addSource, CSharpCompilation compilation, string? refitInternalNamespace, ImmutableArray<MethodDeclarationSyntax> candidateMethods, ImmutableArray<InterfaceDeclarationSyntax> candidateInterfaces)', validate parameter 'addSource' is non-null before using it. If appropriate, throw an 'ArgumentNullException' when the argument is 'null'. (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1062)
context,
"PreserveAttribute.g.cs",
SourceText.From(attributeText, Encoding.UTF8)
Expand Down Expand Up @@ -291,7 +291,8 @@
);

var keyName = group.Key.Name;
if (keyCount.TryGetValue(keyName, out var value))
int value;
while(keyCount.TryGetValue(keyName, out value))
{
keyName = $"{keyName}{++value}";
}
Expand Down Expand Up @@ -583,7 +584,7 @@
bool isOverrideOrExplicitImplementation
)
{
// Explicit interface implementations and ovverrides can only have class or struct constraints
// Explicit interface implementations and overrides can only have class or struct constraints

var parameters = new List<string>();
if (typeParameter.HasReferenceTypeConstraint)
Expand Down Expand Up @@ -875,7 +876,7 @@
context.RegisterForSyntaxNotifications(() => new SyntaxReceiver());
}

class SyntaxReceiver : ISyntaxReceiver

Check warning on line 879 in InterfaceStubGenerator.Shared/InterfaceStubGenerator.cs

View workflow job for this annotation

GitHub Actions / build / build

Type 'SyntaxReceiver' can be sealed because it has no subtypes in its containing assembly and is not externally visible (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1852)
{
public List<MethodDeclarationSyntax> CandidateMethods { get; } = new();

Expand Down
12 changes: 12 additions & 0 deletions Refit.Tests/NamespaceCollisionApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,21 @@ public static INamespaceCollisionApi Create()
namespace CollisionA
{
public class SomeType { }

public interface INamespaceCollisionApi
{
[Get("/")]
Task<SomeType> SomeRequest();
}
}

namespace CollisionB
{
public class SomeType { }

public interface INamespaceCollisionApi
{
[Get("/")]
Task<SomeType> SomeRequest();
}
}
81 changes: 61 additions & 20 deletions Refit.Tests/RestService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2199,28 +2199,31 @@ public void NonGenericCreate()
Assert.Equal(fixture.Client.BaseAddress.AbsoluteUri, expectedBaseAddress);
}

[Fact]
public async Task TypeCollisionTest()
{
var mockHttp = new MockHttpMessageHandler();
[Fact]
public async Task TypeCollisionTest()
{
var mockHttp = new MockHttpMessageHandler();

var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp, };
var settings = new RefitSettings { HttpMessageHandlerFactory = () => mockHttp, };

const string Url = "https://httpbin.org/get";
const string Url = "https://httpbin.org/get";

mockHttp.Expect(HttpMethod.Get, Url).Respond("application/json", "{ }");
mockHttp.Expect(HttpMethod.Get, Url).Respond("application/json", "{ }");

var fixtureA = RestService.For<ITypeCollisionApiA>(Url);
var fixtureA = RestService.For<ITypeCollisionApiA>(Url, settings);

var respA = await fixtureA.SomeARequest();
var respA = await fixtureA.SomeARequest();

var fixtureB = RestService.For<ITypeCollisionApiB>(Url);
mockHttp.Expect(HttpMethod.Get, Url)
.Respond("application/json", "{ }");

var respB = await fixtureB.SomeBRequest();
var fixtureB = RestService.For<ITypeCollisionApiB>(Url, settings);

Assert.IsType<CollisionA.SomeType>(respA);
Assert.IsType<CollisionB.SomeType>(respB);
}
var respB = await fixtureB.SomeBRequest();

Assert.IsType<CollisionA.SomeType>(respA);
Assert.IsType<CollisionB.SomeType>(respB);
}

internal static Stream GetTestFileStream(string relativeFilePath)
{
Expand Down Expand Up @@ -2261,10 +2264,48 @@ internal static Stream GetTestFileStream(string relativeFilePath)
return stream;
}

public void AssertFirstLineContains(string expectedSubstring, string actualString)
{
var eolIndex = actualString.IndexOf('\n');
var firstLine = eolIndex < 0 ? actualString : actualString.Substring(0, eolIndex);
Assert.Contains(expectedSubstring, firstLine);
}
[Fact]
public async Task SameTypeNameInMultipleNamespacesTest()
{
var mockHttp = new MockHttpMessageHandler();

var settings = new RefitSettings
{
HttpMessageHandlerFactory = () => mockHttp,
};

const string Url = "https://httpbin.org/get";

mockHttp.Expect(HttpMethod.Get, Url + "/")
.Respond("application/json", "{ }");

var fixtureA = RestService.For<INamespaceCollisionApi>(Url, settings);

var respA = await fixtureA.SomeRequest();

mockHttp.Expect(HttpMethod.Get, Url + "/")
.Respond("application/json", "{ }");

var fixtureB = RestService.For<CollisionA.INamespaceCollisionApi>(Url, settings);

var respB = await fixtureB.SomeRequest();

mockHttp.Expect(HttpMethod.Get, Url + "/")
.Respond("application/json", "{ }");

var fixtureC = RestService.For<CollisionB.INamespaceCollisionApi>(Url, settings);

var respC = await fixtureC.SomeRequest();

Assert.IsType<CollisionA.SomeType>(respA);
Assert.IsType<CollisionA.SomeType>(respB);
Assert.IsType<CollisionB.SomeType>(respC);
}

public void AssertFirstLineContains(string expectedSubstring, string actualString)
{
var eolIndex = actualString.IndexOf('\n');
var firstLine = eolIndex < 0 ? actualString : actualString.Substring(0, eolIndex);
Assert.Contains(expectedSubstring, firstLine);
}
}
Loading