Skip to content
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
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.3</Version>
<Version>5.1.4</Version>
<PackageReadmeFile>README.md</PackageReadmeFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<NoWarn>1701;1702;NU5128</NoWarn>
Expand Down
22 changes: 10 additions & 12 deletions AutomaticInterface/AutomaticInterface/Builder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,20 +46,20 @@ public static string BuildInterfaceFor(ITypeSymbol typeSymbol)
{
if (
typeSymbol.DeclaringSyntaxReferences.First().GetSyntax()
is not ClassDeclarationSyntax classSyntax
is not ClassDeclarationSyntax classSyntax
|| typeSymbol is not INamedTypeSymbol namedTypeSymbol
)
{
return string.Empty;
}

var namespaceName = GetNameSpace(typeSymbol);

var interfaceName = $"I{classSyntax.GetClassName()}";

var interfaceGenerator = new InterfaceBuilder(namespaceName, interfaceName);

interfaceGenerator.AddClassDocumentation(GetDocumentationForClass(classSyntax));
interfaceGenerator.AddGeneric(GetGeneric(classSyntax));
interfaceGenerator.AddGeneric(GetGeneric(classSyntax, namedTypeSymbol));

var members = typeSymbol
.GetAllMembers()
Expand Down Expand Up @@ -298,16 +298,14 @@ private static string GetDocumentationForClass(CSharpSyntaxNode classSyntax)
return trivia.ToFullString().Trim();
}

private static string GetGeneric(TypeDeclarationSyntax classSyntax)
private static string GetGeneric(TypeDeclarationSyntax classSyntax, INamedTypeSymbol typeSymbol)
{
if (classSyntax.TypeParameterList?.Parameters.Count == 0)
{
return string.Empty;
}

var formattedGeneric =
$"{classSyntax.TypeParameterList?.ToFullString().Trim()} {classSyntax.ConstraintClauses}".Trim();
var whereStatements = typeSymbol
.TypeParameters.Select(typeParameter =>
typeParameter.GetWhereStatement(FullyQualifiedDisplayFormat)
)
.Where(constraint => !string.IsNullOrEmpty(constraint));

return formattedGeneric;
return $"{classSyntax.TypeParameterList?.ToFullString().Trim()} {string.Join(" ", whereStatements)}".Trim();
}
}
92 changes: 15 additions & 77 deletions AutomaticInterface/AutomaticInterface/RoslynExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,103 +42,41 @@ SymbolDisplayFormat typeDisplayFormat
{
var result = $"where {typeParameterSymbol.Name} : ";

var constraints = "";

var isFirstConstraint = true;
var constraints = new List<string>();
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I refactored this to emit the new() constraint last.

https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/new-constraint

When you use the new() constraint with other constraints, it must be specified last


if (typeParameterSymbol.HasReferenceTypeConstraint)
{
constraints += "class";
isFirstConstraint = false;
constraints.Add("class");
}

if (typeParameterSymbol.HasValueTypeConstraint)
{
constraints += "struct";
isFirstConstraint = false;
}

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

if (typeParameterSymbol.HasNotNullConstraint)
{
constraints += "notnull";
isFirstConstraint = false;
constraints.Add("notnull");
}

foreach (var constraintType in typeParameterSymbol.ConstraintTypes)
{
// if not first constraint prepend with comma
if (!isFirstConstraint)
{
constraints += ", ";
}
else
{
isFirstConstraint = false;
}
constraints.AddRange(
typeParameterSymbol.ConstraintTypes.Select(t =>
t.ToDisplayString(typeDisplayFormat)
)
);

constraints += constraintType.GetFullTypeString(typeDisplayFormat);
}

if (string.IsNullOrEmpty(constraints))
// The new() constraint must be last
if (typeParameterSymbol.HasConstructorConstraint)
{
return "";
constraints.Add("new()");
}

result += constraints;

return result;
}

private static string GetFullTypeString(
this ISymbol type,
SymbolDisplayFormat typeDisplayFormat
)
{
return type.ToDisplayString(typeDisplayFormat)
+ type.GetTypeArgsStr(
typeDisplayFormat,
symbol => ((INamedTypeSymbol)symbol).TypeArguments
);
}

private static string GetTypeArgsStr(
this ISymbol symbol,
SymbolDisplayFormat typeDisplayFormat,
Func<ISymbol, ImmutableArray<ITypeSymbol>> typeArgGetter
)
{
var typeArgs = typeArgGetter(symbol);

if (!typeArgs.Any())
return string.Empty;

var stringsToAdd = new List<string>();
foreach (var arg in typeArgs)
if (constraints.Count == 0)
{
string strToAdd;

if (arg is ITypeParameterSymbol typeParameterSymbol)
{
// this is a generic argument
strToAdd = typeParameterSymbol.ToDisplayString(typeDisplayFormat);
}
else
{
// this is a generic argument value.
var namedTypeSymbol = arg as INamedTypeSymbol;
strToAdd = namedTypeSymbol!.GetFullTypeString(typeDisplayFormat);
}

stringsToAdd.Add(strToAdd);
return "";
}

var result = $"<{string.Join(", ", stringsToAdd)}>";
result += string.Join(", ", constraints);

return result;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace AutomaticInterfaceExample
/// Bla bla
/// </summary>
[global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")]
public partial interface IDemoClass<T,U> where T:class
public partial interface IDemoClass<T,U> where T : class
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//--------------------------------------------------------------------------------------------------
// <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
{
/// <summary>
/// Bla bla
/// </summary>
[global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")]
public partial interface IDemoClass<T1, T2, U> where T1 : global::AutomaticInterfaceExample.DemoModel, new() where T2 : global::AutomaticInterfaceExample.DemoModel
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//--------------------------------------------------------------------------------------------------
// <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
{
/// <summary>
/// Bla bla
/// </summary>
[global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")]
public partial interface IDemoClass<T, U, V> where T : U where V : List<T>
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//--------------------------------------------------------------------------------------------------
// <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
{
/// <summary>
/// Bla bla
/// </summary>
[global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")]
public partial interface IDemoClass<T, U, V> where T : class, global::AutomaticInterfaceExample.IDemoModel where U : struct, global::AutomaticInterfaceExample.IDemoModel where V : notnull, global::AutomaticInterfaceExample.IDemoModel
{
}
}
95 changes: 95 additions & 0 deletions AutomaticInterface/Tests/GenericInterfaces/GenericInterfaces.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
namespace Tests.GenericInterfaces;

public class GenericInterfaces
{
[Fact]
public async Task MakesGenericInterface()
{
const string code = """

using AutomaticInterface;

namespace AutomaticInterfaceExample
{
/// <summary>
/// Bla bla
/// </summary>
[GenerateAutomaticInterface]
class DemoClass<T,U> where T:class
{
}
}

""";

await Verify(Infrastructure.GenerateCode(code));
}

[Fact]
public async Task MakesGenericInterfaceWithInterfaceTypeConstraints()
{
const string code = """

using AutomaticInterface;

namespace AutomaticInterfaceExample;

public interface IDemoModel;

/// <summary>
/// Bla bla
/// </summary>
[GenerateAutomaticInterface]
public class DemoClass<T, U, V>
where T: class, IDemoModel
where U: struct, IDemoModel
where V: notnull, IDemoModel;
""";

await Verify(Infrastructure.GenerateCode(code));
}

[Fact]
public async Task MakesGenericInterfaceWithClassTypeConstraints()
{
const string code = """

using AutomaticInterface;

namespace AutomaticInterfaceExample;

public class DemoModel;

/// <summary>
/// Bla bla
/// </summary>
[GenerateAutomaticInterface]
public class DemoClass<T1, T2, U>
where T1: DemoModel, new()
where T2: DemoModel;
""";

await Verify(Infrastructure.GenerateCode(code));
}

[Fact]
public async Task MakesGenericInterfaceWithDependentTypeConstraints()
{
const string code = """

using AutomaticInterface;

namespace AutomaticInterfaceExample;

/// <summary>
/// Bla bla
/// </summary>
[GenerateAutomaticInterface]
public class DemoClass<T, U, V>
where T: U
where V: List<T>;
""";

await Verify(Infrastructure.GenerateCode(code));
}
}
23 changes: 0 additions & 23 deletions AutomaticInterface/Tests/Misc/Misc.cs
Original file line number Diff line number Diff line change
Expand Up @@ -132,29 +132,6 @@ public static string StaticMethod() // method
await Verify(Infrastructure.GenerateCode(code));
}

[Fact]
public async Task MakesGenericInterface()
{
const string code = """

using AutomaticInterface;

namespace AutomaticInterfaceExample
{
/// <summary>
/// Bla bla
/// </summary>
[GenerateAutomaticInterface]
class DemoClass<T,U> where T:class
{
}
}

""";

await Verify(Infrastructure.GenerateCode(code));
}

[Fact]
public async Task DoesNotCopyIndexerToInterface()
{
Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,11 @@ Note that we use [Verify](https://github.com/VerifyTests/Verify) for testing. It

## Changelog

### 5.1.3.
### 5.1.4

- Emit fully qualified type constraints on generic interfaces

### 5.1.3

- Emit `notnull` type constraints on generic type parameters

Expand Down
Loading