diff --git a/AutomaticInterface/AutomaticInterface/AutomaticInterface.csproj b/AutomaticInterface/AutomaticInterface/AutomaticInterface.csproj
index 4de54bd..78913f7 100644
--- a/AutomaticInterface/AutomaticInterface/AutomaticInterface.csproj
+++ b/AutomaticInterface/AutomaticInterface/AutomaticInterface.csproj
@@ -25,7 +25,7 @@
MIT
True
latest-Recommended
- 5.1.3
+ 5.1.4
README.md
true
1701;1702;NU5128
diff --git a/AutomaticInterface/AutomaticInterface/Builder.cs b/AutomaticInterface/AutomaticInterface/Builder.cs
index 0a61b40..1f028e9 100644
--- a/AutomaticInterface/AutomaticInterface/Builder.cs
+++ b/AutomaticInterface/AutomaticInterface/Builder.cs
@@ -46,12 +46,12 @@ 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()}";
@@ -59,7 +59,7 @@ is not ClassDeclarationSyntax classSyntax
var interfaceGenerator = new InterfaceBuilder(namespaceName, interfaceName);
interfaceGenerator.AddClassDocumentation(GetDocumentationForClass(classSyntax));
- interfaceGenerator.AddGeneric(GetGeneric(classSyntax));
+ interfaceGenerator.AddGeneric(GetGeneric(classSyntax, namedTypeSymbol));
var members = typeSymbol
.GetAllMembers()
@@ -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();
}
}
diff --git a/AutomaticInterface/AutomaticInterface/RoslynExtensions.cs b/AutomaticInterface/AutomaticInterface/RoslynExtensions.cs
index 09a77ee..6606fe4 100644
--- a/AutomaticInterface/AutomaticInterface/RoslynExtensions.cs
+++ b/AutomaticInterface/AutomaticInterface/RoslynExtensions.cs
@@ -42,103 +42,41 @@ SymbolDisplayFormat typeDisplayFormat
{
var result = $"where {typeParameterSymbol.Name} : ";
- var constraints = "";
-
- var isFirstConstraint = true;
+ var constraints = new List();
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> typeArgGetter
- )
- {
- var typeArgs = typeArgGetter(symbol);
-
- if (!typeArgs.Any())
- return string.Empty;
-
- var stringsToAdd = new List();
- 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;
}
diff --git a/AutomaticInterface/Tests/Misc/Misc.MakesGenericInterface.verified.txt b/AutomaticInterface/Tests/GenericInterfaces/GenericInterfaces.MakesGenericInterface.verified.txt
similarity index 90%
rename from AutomaticInterface/Tests/Misc/Misc.MakesGenericInterface.verified.txt
rename to AutomaticInterface/Tests/GenericInterfaces/GenericInterfaces.MakesGenericInterface.verified.txt
index 2675ff2..a05b037 100644
--- a/AutomaticInterface/Tests/Misc/Misc.MakesGenericInterface.verified.txt
+++ b/AutomaticInterface/Tests/GenericInterfaces/GenericInterfaces.MakesGenericInterface.verified.txt
@@ -12,7 +12,7 @@ namespace AutomaticInterfaceExample
/// Bla bla
///
[global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")]
- public partial interface IDemoClass where T:class
+ public partial interface IDemoClass where T : class
{
}
}
diff --git a/AutomaticInterface/Tests/GenericInterfaces/GenericInterfaces.MakesGenericInterfaceWithClassTypeConstraints.verified.txt b/AutomaticInterface/Tests/GenericInterfaces/GenericInterfaces.MakesGenericInterfaceWithClassTypeConstraints.verified.txt
new file mode 100644
index 0000000..c788c6a
--- /dev/null
+++ b/AutomaticInterface/Tests/GenericInterfaces/GenericInterfaces.MakesGenericInterfaceWithClassTypeConstraints.verified.txt
@@ -0,0 +1,18 @@
+//--------------------------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+//
+// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated.
+//
+//--------------------------------------------------------------------------------------------------
+
+namespace AutomaticInterfaceExample
+{
+ ///
+ /// Bla bla
+ ///
+ [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")]
+ public partial interface IDemoClass where T1 : global::AutomaticInterfaceExample.DemoModel, new() where T2 : global::AutomaticInterfaceExample.DemoModel
+ {
+ }
+}
diff --git a/AutomaticInterface/Tests/GenericInterfaces/GenericInterfaces.MakesGenericInterfaceWithDependentTypeConstraints.verified.txt b/AutomaticInterface/Tests/GenericInterfaces/GenericInterfaces.MakesGenericInterfaceWithDependentTypeConstraints.verified.txt
new file mode 100644
index 0000000..47dd7a0
--- /dev/null
+++ b/AutomaticInterface/Tests/GenericInterfaces/GenericInterfaces.MakesGenericInterfaceWithDependentTypeConstraints.verified.txt
@@ -0,0 +1,18 @@
+//--------------------------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+//
+// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated.
+//
+//--------------------------------------------------------------------------------------------------
+
+namespace AutomaticInterfaceExample
+{
+ ///
+ /// Bla bla
+ ///
+ [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")]
+ public partial interface IDemoClass where T : U where V : List
+ {
+ }
+}
diff --git a/AutomaticInterface/Tests/GenericInterfaces/GenericInterfaces.MakesGenericInterfaceWithInterfaceTypeConstraints.verified.txt b/AutomaticInterface/Tests/GenericInterfaces/GenericInterfaces.MakesGenericInterfaceWithInterfaceTypeConstraints.verified.txt
new file mode 100644
index 0000000..88f14e2
--- /dev/null
+++ b/AutomaticInterface/Tests/GenericInterfaces/GenericInterfaces.MakesGenericInterfaceWithInterfaceTypeConstraints.verified.txt
@@ -0,0 +1,18 @@
+//--------------------------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+//
+// Changes to this file may cause incorrect behavior and will be lost if the code is regenerated.
+//
+//--------------------------------------------------------------------------------------------------
+
+namespace AutomaticInterfaceExample
+{
+ ///
+ /// Bla bla
+ ///
+ [global::System.CodeDom.Compiler.GeneratedCode("AutomaticInterface", "")]
+ public partial interface IDemoClass where T : class, global::AutomaticInterfaceExample.IDemoModel where U : struct, global::AutomaticInterfaceExample.IDemoModel where V : notnull, global::AutomaticInterfaceExample.IDemoModel
+ {
+ }
+}
diff --git a/AutomaticInterface/Tests/GenericInterfaces/GenericInterfaces.cs b/AutomaticInterface/Tests/GenericInterfaces/GenericInterfaces.cs
new file mode 100644
index 0000000..303bb46
--- /dev/null
+++ b/AutomaticInterface/Tests/GenericInterfaces/GenericInterfaces.cs
@@ -0,0 +1,95 @@
+namespace Tests.GenericInterfaces;
+
+public class GenericInterfaces
+{
+ [Fact]
+ public async Task MakesGenericInterface()
+ {
+ const string code = """
+
+ using AutomaticInterface;
+
+ namespace AutomaticInterfaceExample
+ {
+ ///
+ /// Bla bla
+ ///
+ [GenerateAutomaticInterface]
+ class DemoClass where T:class
+ {
+ }
+ }
+
+ """;
+
+ await Verify(Infrastructure.GenerateCode(code));
+ }
+
+ [Fact]
+ public async Task MakesGenericInterfaceWithInterfaceTypeConstraints()
+ {
+ const string code = """
+
+ using AutomaticInterface;
+
+ namespace AutomaticInterfaceExample;
+
+ public interface IDemoModel;
+
+ ///
+ /// Bla bla
+ ///
+ [GenerateAutomaticInterface]
+ public class DemoClass
+ 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;
+
+ ///
+ /// Bla bla
+ ///
+ [GenerateAutomaticInterface]
+ public class DemoClass
+ where T1: DemoModel, new()
+ where T2: DemoModel;
+ """;
+
+ await Verify(Infrastructure.GenerateCode(code));
+ }
+
+ [Fact]
+ public async Task MakesGenericInterfaceWithDependentTypeConstraints()
+ {
+ const string code = """
+
+ using AutomaticInterface;
+
+ namespace AutomaticInterfaceExample;
+
+ ///
+ /// Bla bla
+ ///
+ [GenerateAutomaticInterface]
+ public class DemoClass
+ where T: U
+ where V: List;
+ """;
+
+ await Verify(Infrastructure.GenerateCode(code));
+ }
+}
diff --git a/AutomaticInterface/Tests/Misc/Misc.cs b/AutomaticInterface/Tests/Misc/Misc.cs
index 68fcd09..c573a0b 100644
--- a/AutomaticInterface/Tests/Misc/Misc.cs
+++ b/AutomaticInterface/Tests/Misc/Misc.cs
@@ -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
- {
- ///
- /// Bla bla
- ///
- [GenerateAutomaticInterface]
- class DemoClass where T:class
- {
- }
- }
-
- """;
-
- await Verify(Infrastructure.GenerateCode(code));
- }
-
[Fact]
public async Task DoesNotCopyIndexerToInterface()
{
diff --git a/README.md b/README.md
index 2789cac..bfbd2f5 100644
--- a/README.md
+++ b/README.md
@@ -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