From d681d7b2a5dba7d0d70936e6d4f2c460eebbb7d6 Mon Sep 17 00:00:00 2001 From: Robert Konklewski Date: Tue, 11 Jul 2023 12:00:25 +0200 Subject: [PATCH] Fix handling of unmanaged type parameter constraint When a method had the 'unmanaged' constraint specified, then the interface generator would output both 'struct' and 'unmanaged' constraints. However, the C# specification explicitly forbids to combine these constraints. The problem is that when the `HasUnmanagedTypeConstraint` property of `ITypeParameterSymbol` is true, then `HasValueTypeConstraint` is also true. To fix the issue the `class` / `struct` / `unmanaged` / `notnull` constraints need to be treated as mutually exclusive, and `unmanaged` needs to be checked before `struct`. --- .../MethodGenerationTests.cs | 34 +++++++++++++++++++ .../TypeParameterSymbolExtensions.cs | 16 ++++----- 2 files changed, 41 insertions(+), 9 deletions(-) diff --git a/InterfaceGenerator.Tests/MethodGenerationTests.cs b/InterfaceGenerator.Tests/MethodGenerationTests.cs index fb9a748..83fe29d 100644 --- a/InterfaceGenerator.Tests/MethodGenerationTests.cs +++ b/InterfaceGenerator.Tests/MethodGenerationTests.cs @@ -202,6 +202,34 @@ public void GenericVoidMethodWithConstraints_IsImplemented() _sut.GenericVoidMethodWithConstraints(); } + [Fact] + public void GenericVoidMethodWithValueTypeConstraints_IsImplemented() + { + var method = typeof(IMethodsTestService) + .GetMethods() + .First(x => x.Name == nameof(MethodsTestService.GenericVoidMethodWithValueTypeConstraints)); + + method.Should().NotBeNull(); + method.ReturnType.Should().Be(typeof(void)); + + var genericArgs = method.GetGenericArguments(); + genericArgs.Should().HaveCount(2); + + genericArgs[0].IsValueType.Should().BeTrue(); + genericArgs[0] + .GenericParameterAttributes.Should() + .HaveFlag(GenericParameterAttributes.DefaultConstructorConstraint) + .And.HaveFlag(GenericParameterAttributes.NotNullableValueTypeConstraint); + + genericArgs[1].IsValueType.Should().BeTrue(); + genericArgs[1] + .GenericParameterAttributes.Should() + .HaveFlag(GenericParameterAttributes.DefaultConstructorConstraint) + .And.HaveFlag(GenericParameterAttributes.NotNullableValueTypeConstraint); + + _sut.GenericVoidMethodWithValueTypeConstraints, long>(); + } + [Fact] public void VoidMethodWithOptionalParams_IsImplemented() { @@ -315,6 +343,12 @@ public void GenericVoidMethodWithConstraints() { } + public void GenericVoidMethodWithValueTypeConstraints() + where TX : struct + where TY : unmanaged + { + } + public void VoidMethodWithOptionalParams( string stringLiteral = "cGFyYW0=", string stringConstant = StringConstant, diff --git a/InterfaceGenerator/TypeParameterSymbolExtensions.cs b/InterfaceGenerator/TypeParameterSymbolExtensions.cs index bfaa883..53cf1b3 100644 --- a/InterfaceGenerator/TypeParameterSymbolExtensions.cs +++ b/InterfaceGenerator/TypeParameterSymbolExtensions.cs @@ -7,23 +7,21 @@ internal static class TypeParameterSymbolExtensions { public static IEnumerable EnumGenericConstraints(this ITypeParameterSymbol symbol) { - // the class/struct/unmanaged/notnull constraint has to be the last + // the class/struct/unmanaged/notnull constraint has to be the first + // and cannot be combined with one another if (symbol.HasNotNullConstraint) { yield return "notnull"; } - - if (symbol.HasValueTypeConstraint) + else if (symbol.HasUnmanagedTypeConstraint) { - yield return "struct"; + yield return "unmanaged"; } - - if (symbol.HasUnmanagedTypeConstraint) + else if (symbol.HasValueTypeConstraint) { - yield return "unmanaged"; + yield return "struct"; } - - if (symbol.HasReferenceTypeConstraint) + else if (symbol.HasReferenceTypeConstraint) { yield return symbol.ReferenceTypeConstraintNullableAnnotation == NullableAnnotation.Annotated ? "class?"