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

Support generic constraints on methods #1044

Merged
merged 2 commits into from
Jan 27, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 19 additions & 12 deletions InterfaceStubGenerator.Core/InterfaceStubGenerator.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;

Expand Down Expand Up @@ -197,7 +198,7 @@ namespace {ns}
[global::System.Reflection.Obfuscation(Exclude=true)]
[global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)]
partial class AutoGenerated{classDeclaration}
: {interfaceSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}{GenerateConstraints(interfaceSymbol)}
: {interfaceSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}{GenerateConstraints(interfaceSymbol.TypeParameters, false)}

{{
/// <inheritdoc />
Expand Down Expand Up @@ -312,43 +313,49 @@ void ProcessDisposableMethod(StringBuilder source, IMethodSymbol methodSymbol)
WriteMethodClosing(source);
}

string GenerateConstraints(INamedTypeSymbol iface)
string GenerateConstraints(ImmutableArray<ITypeParameterSymbol> typeParameters, bool isOverrideOrExplicitImplementation)
{
var source = new StringBuilder();
// Need to loop over the constraints and create them
foreach(var typeParameter in iface.TypeParameters)
foreach(var typeParameter in typeParameters)
{
WriteConstraitsForTypeParameter(source, typeParameter);
WriteConstraitsForTypeParameter(source, typeParameter, isOverrideOrExplicitImplementation);
}

return source.ToString();
}

void WriteConstraitsForTypeParameter(StringBuilder source, ITypeParameterSymbol typeParameter)
void WriteConstraitsForTypeParameter(StringBuilder source, ITypeParameterSymbol typeParameter, bool isOverrideOrExplicitImplementation)
{
// Explicit interface implementations and ovverrides can only have class or struct constraints

var parameters = new List<string>();
if(typeParameter.HasReferenceTypeConstraint)
{
parameters.Add("class");
}
if (typeParameter.HasUnmanagedTypeConstraint)
if (typeParameter.HasUnmanagedTypeConstraint && !isOverrideOrExplicitImplementation)
{
parameters.Add("unmanaged");
}
if (typeParameter.HasValueTypeConstraint)
{
parameters.Add("struct");
}
if (typeParameter.HasNotNullConstraint)
if (typeParameter.HasNotNullConstraint && !isOverrideOrExplicitImplementation)
{
parameters.Add("notnull");
}
foreach(var typeConstraint in typeParameter.ConstraintTypes)
if (!isOverrideOrExplicitImplementation)
{
parameters.Add(typeConstraint.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat));
foreach (var typeConstraint in typeParameter.ConstraintTypes)
{
parameters.Add(typeConstraint.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat));
}
}

// new constraint has to be last
if (typeParameter.HasConstructorConstraint)
if (typeParameter.HasConstructorConstraint && !isOverrideOrExplicitImplementation)
{
parameters.Add("new()");
}
Expand Down Expand Up @@ -398,8 +405,8 @@ void WriteMethodOpening(StringBuilder source, IMethodSymbol methodSymbol)
source.Append(string.Join(", ", list));
}

source.Append(@")
{");
source.Append(@$") {GenerateConstraints(methodSymbol.TypeParameters, true)}
{{");
}

void WriteMethodClosing(StringBuilder source) => source.Append(@" }");
Expand Down
6 changes: 5 additions & 1 deletion Refit.Tests/InheritedGenericInterfacesApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,11 @@ public interface IDataCrudApi<T, TKey> where T : class
Task<T> Create([Body] T payload);

[Get("")]
Task<List<T>> ReadAll();
Task<List<T>> ReadAll<TFoo>() where TFoo : new();

[Get("")]
Task<List<T>> ReadAll<TFoo, TBar>() where TFoo : new()
where TBar : struct;

[Get("/{key}")]
Task<T> ReadOne(TKey key);
Expand Down
22 changes: 21 additions & 1 deletion Refit.Tests/InterfaceStubGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,11 @@ public void GenerateInterfaceStubsSmokeTest()
IntegrationTestHelper.GetPath("InheritedInterfacesApi.cs"),
IntegrationTestHelper.GetPath("InheritedGenericInterfacesApi.cs"));

var diags = inputCompilation.GetDiagnostics();

// Make sure we don't have any errors
Assert.Empty(diags.Where(d => d.Severity == DiagnosticSeverity.Error));

var rundriver = driver.RunGeneratorsAndUpdateCompilation(inputCompilation, out var outputCompiliation, out var diagnostics);

var runResult = rundriver.GetRunResult();
Expand All @@ -47,9 +52,24 @@ public void GenerateInterfaceStubsSmokeTest()

static Compilation CreateCompilation(params string[] sourceFiles)
{
var keyReferences = new[]
{
typeof(Binder),
typeof(GetAttribute),
typeof(RichardSzalay.MockHttp.MockHttpMessageHandler),
typeof(System.Reactive.Unit),
typeof(System.Linq.Enumerable),
typeof(Newtonsoft.Json.JsonConvert),
typeof(Xunit.FactAttribute),
typeof(System.Net.Http.HttpContent),
typeof(ModelObject),
typeof(Attribute)
};


return CSharpCompilation.Create("compilation",
sourceFiles.Select(source => CSharpSyntaxTree.ParseText(File.ReadAllText(source))),
new[] { MetadataReference.CreateFromFile(typeof(GetAttribute).GetTypeInfo().Assembly.Location) },
keyReferences.Select(t => MetadataReference.CreateFromFile(t.Assembly.Location)),
new CSharpCompilationOptions(OutputKind.ConsoleApplication));

}
Expand Down