Skip to content

Commit

Permalink
Merge pull request #65 from simonmckenzie/fix/add-new-type-constraint…
Browse files Browse the repository at this point in the history
…-to-generated-interface-methods

Emit new() type constraints on generic type parameters; emit params keyword for method parameters
  • Loading branch information
ChristianSauer authored Jan 24, 2025
2 parents 9d66cad + c305b5e commit 1b386c1
Show file tree
Hide file tree
Showing 5 changed files with 79 additions and 73 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<EnableNETAnalyzers>True</EnableNETAnalyzers>
<AnalysisLevel>latest-Recommended</AnalysisLevel>
<Version>5.1.0</Version>
<Version>5.1.1</Version>
<PackageReadmeFile>README.md</PackageReadmeFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<NoWarn>1701;1702;NU5128</NoWarn>
Expand Down
80 changes: 16 additions & 64 deletions AutomaticInterface/AutomaticInterface/Builder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,14 @@ private static string InheritDoc(ISymbol source) =>
genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters,
memberOptions: SymbolDisplayMemberOptions.IncludeParameters,
parameterOptions: SymbolDisplayParameterOptions.IncludeType
| SymbolDisplayParameterOptions.IncludeParamsRefOut,
| SymbolDisplayParameterOptions.IncludeParamsRefOut
| SymbolDisplayParameterOptions.IncludeDefaultValue
| SymbolDisplayParameterOptions.IncludeName,
typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces,
globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.Included,
miscellaneousOptions: SymbolDisplayMiscellaneousOptions.UseSpecialTypes
| SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier
| SymbolDisplayMiscellaneousOptions.EscapeKeywordIdentifiers
);

public static string BuildInterfaceFor(ITypeSymbol typeSymbol)
Expand Down Expand Up @@ -63,12 +66,9 @@ private static string GetNameSpace(ISymbol typeSymbol)
{
var generationAttribute = typeSymbol
.GetAttributes()
.FirstOrDefault(
x =>
x.AttributeClass != null
&& x.AttributeClass
.Name
.Contains(AutomaticInterfaceGenerator.DefaultAttributeName)
.FirstOrDefault(x =>
x.AttributeClass != null
&& x.AttributeClass.Name.Contains(AutomaticInterfaceGenerator.DefaultAttributeName)
);

if (generationAttribute == null)
Expand Down Expand Up @@ -105,7 +105,10 @@ private static void AddMethod(InterfaceBuilder codeGenerator, IMethodSymbol meth
ActivateNullableIfNeeded(codeGenerator, method);

var paramResult = new HashSet<string>();
method.Parameters.Select(GetMethodSignature).ToList().ForEach(x => paramResult.Add(x));
method
.Parameters.Select(x => x.ToDisplayString(FullyQualifiedDisplayFormat))
.ToList()
.ForEach(x => paramResult.Add(x));

var typedArgs = method
.TypeParameters.Select(arg =>
Expand Down Expand Up @@ -197,56 +200,6 @@ private static void AddEventsToInterface(List<ISymbol> members, InterfaceBuilder
});
}

private static string GetMethodSignature(IParameterSymbol x)
{
var name = GetMethodName(x);
var refKindText = GetRefKind(x);
var optionalValue = GetMethodOptionalValue(x);

return $"{refKindText}{x.Type.ToDisplayString(FullyQualifiedDisplayFormat)} {name}{optionalValue}";
}

private static string GetMethodOptionalValue(IParameterSymbol x)
{
if (!x.HasExplicitDefaultValue)
{
return string.Empty;
}

return x.ExplicitDefaultValue switch
{
string => $" = \"{x.ExplicitDefaultValue}\"",
bool value => $" = {(value ? "true" : "false")}",
// struct
null when x.Type.IsValueType
=> $" = default({x.Type.ToDisplayString(FullyQualifiedDisplayFormat)})",
null => " = null",
_ => $" = {x.ExplicitDefaultValue}",
};
}

private static string GetMethodName(IParameterSymbol x)
{
var syntaxReference = x.DeclaringSyntaxReferences.FirstOrDefault();

return syntaxReference != null
? ((ParameterSyntax)syntaxReference.GetSyntax()).Identifier.Text
: x.Name;
}

private static string GetRefKind(IParameterSymbol x)
{
return x.RefKind switch
{
RefKind.Ref => "ref ",
RefKind.Out => "out ",
RefKind.In => "in ",
// Not sure why RefReadOnly and In both has Enum index 3.
// RefKind.RefReadOnly => "ref readonly ",
_ => string.Empty,
};
}

private static void AddPropertiesToInterface(
List<ISymbol> members,
InterfaceBuilder interfaceGenerator
Expand Down Expand Up @@ -298,12 +251,11 @@ private static PropertySetKind GetSetKind(IMethodSymbol? setMethodSymbol)
private static bool HasIgnoreAttribute(ISymbol x)
{
return x.GetAttributes()
.Any(
a =>
a.AttributeClass != null
&& a.AttributeClass
.Name
.Contains(AutomaticInterfaceGenerator.IgnoreAutomaticInterfaceAttributeName)
.Any(a =>
a.AttributeClass != null
&& a.AttributeClass.Name.Contains(
AutomaticInterfaceGenerator.IgnoreAutomaticInterfaceAttributeName
)
);
}

Expand Down
6 changes: 6 additions & 0 deletions AutomaticInterface/AutomaticInterface/RoslynExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ SymbolDisplayFormat typeDisplayFormat
isFirstConstraint = false;
}

if (typeParameterSymbol.HasConstructorConstraint)
{
constraints += "new()";
isFirstConstraint = false;
}

foreach (var constraintType in typeParameterSymbol.ConstraintTypes)
{
// if not first constraint prepend with comma
Expand Down
58 changes: 51 additions & 7 deletions AutomaticInterface/Tests/GeneratorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,49 @@ public partial interface IDemoClass
GenerateCode(code).Should().Be(expected);
}

[Fact]
public void WorksWithParamsParameters()
{
const string code = """
using AutomaticInterface;
namespace AutomaticInterfaceExample;
[GenerateAutomaticInterface]
public class DemoClass
{
public void AMethod(params int[] numbers)
{
}
}
""";
const string expected = """
//--------------------------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated.
// </auto-generated>
//--------------------------------------------------------------------------------------------------
namespace AutomaticInterfaceExample
{
[global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")]
public partial interface IDemoClass
{
/// <inheritdoc cref="AutomaticInterfaceExample.DemoClass.AMethod(params int[])" />
void AMethod(params int[] numbers);
}
}
""";
GenerateCode(code).Should().Be(expected);
}

[Fact]
public void WorksWithOptionalStructParameters()
{
Expand Down Expand Up @@ -1053,11 +1096,12 @@ namespace AutomaticInterfaceExample;
public class DemoClass
{
/// <inheritdoc />
public string CMethod<T, T1, T2, T3, T4>(string x, string y)
public string CMethod<T, T1, T2, T3, T4, T5>(string x, string y)
where T : class
where T1 : struct
where T3 : DemoClass
where T4 : IDemoClass
where T5 : new()
{
return "Ok";
}
Expand All @@ -1080,8 +1124,8 @@ namespace AutomaticInterfaceExample
[global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")]
public partial interface IDemoClass
{
/// <inheritdoc cref="AutomaticInterfaceExample.DemoClass.CMethod{T, T1, T2, T3, T4}(string, string)" />
string CMethod<T, T1, T2, T3, T4>(string x, string y) where T : class where T1 : struct where T3 : global::AutomaticInterfaceExample.DemoClass where T4 : IDemoClass;
/// <inheritdoc cref="AutomaticInterfaceExample.DemoClass.CMethod{T, T1, T2, T3, T4, T5}(string, string)" />
string CMethod<T, T1, T2, T3, T4, T5>(string x, string y) where T : class where T1 : struct where T3 : global::AutomaticInterfaceExample.DemoClass where T4 : IDemoClass where T5 : new();
}
}
Expand Down Expand Up @@ -1820,10 +1864,10 @@ namespace AutomaticInterfaceExample
[global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")]
public partial interface IDemoClass
{
/// <inheritdoc />
/// <inheritdoc cref="AutomaticInterfaceExample.DemoClass.AMethod(Func{Task{int}})" />
void AMethod(Func<Task<int>> getValue);
/// <inheritdoc />
/// <inheritdoc cref="AutomaticInterfaceExample.DemoClass.AMethod(Func{Task{float}})" />
void AMethod(Func<Task<float>> getValue);
}
Expand Down Expand Up @@ -1875,10 +1919,10 @@ namespace AutomaticInterfaceExample
[global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")]
public partial interface IDemoClass
{
/// <inheritdoc />
/// <inheritdoc cref="AutomaticInterfaceExample.DemoClass.AMethod(Func{Task{AutomaticInterfaceExample.Types1.Model}})" />
void AMethod(Func<Task<global::AutomaticInterfaceExample.Types1.Model>> getValue);
/// <inheritdoc />
/// <inheritdoc cref="AutomaticInterfaceExample.DemoClass.AMethod(Func{Task{AutomaticInterfaceExample.Types2.Model}})" />
void AMethod(Func<Task<global::AutomaticInterfaceExample.Types2.Model>> getValue);
}
Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,10 @@ Should be simply a build and run Tests

## Changelog

### 5.1.1

- Emit `new()` type constraints on generic type parameters; emit `params` keyword for method parameters. Thanks, @simonmckenzie!

### 5.1.0

- Improves inheritdoc so that developer documentation is properly referenced on the autogenerated interfaces. Thanks, CFlorell!
Expand All @@ -195,7 +199,7 @@ Should be simply a build and run Tests

### 5.0.2

- Fully qualify type references; remove usings . Thanks, @simonmckenzie!
- Fully qualify type references; remove usings. Thanks, @simonmckenzie!

### 5.0.1

Expand Down

0 comments on commit 1b386c1

Please sign in to comment.