diff options
author | Andrew Arnott <andrewarnott@gmail.com> | 2020-09-27 16:40:30 +0300 |
---|---|---|
committer | Andrew Arnott <andrewarnott@gmail.com> | 2020-09-27 16:40:30 +0300 |
commit | 4683e150586ccd26dd63458bc24a58504658003f (patch) | |
tree | 301aaccce85cd1b061b39e304f28ed8457b6c431 /tests | |
parent | f6de4d19bf4f05f9233fba80542383bac1933fe0 (diff) | |
parent | d21e84dfabac5d233437b90e72ee463dea90a693 (diff) |
Merge remote-tracking branch 'upstream/master' into v2.2
Diffstat (limited to 'tests')
-rw-r--r-- | tests/MessagePack.Generator.Tests/GenerateGenericsFormatterTest.cs | 524 |
1 files changed, 524 insertions, 0 deletions
diff --git a/tests/MessagePack.Generator.Tests/GenerateGenericsFormatterTest.cs b/tests/MessagePack.Generator.Tests/GenerateGenericsFormatterTest.cs index 59dcfa5b..a518caff 100644 --- a/tests/MessagePack.Generator.Tests/GenerateGenericsFormatterTest.cs +++ b/tests/MessagePack.Generator.Tests/GenerateGenericsFormatterTest.cs @@ -7,8 +7,10 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using FluentAssertions; +using Microsoft.CodeAnalysis; using Xunit; using Xunit.Abstractions; +using SymbolDisplayFormat = Microsoft.CodeAnalysis.SymbolDisplayFormat; namespace MessagePack.Generator.Tests { @@ -176,6 +178,230 @@ namespace TempProject [Theory] [InlineData(true)] [InlineData(false)] + public async Task GenericsUnionFormatter_Nested(bool isSingleFileOutput) + { + using var tempWorkarea = TemporaryProjectWorkarea.Create(false); + var contents = @" +using System; +using System.Collections.Generic; +using MessagePack; + +namespace TempProject +{ + [MessagePackObject] + [Union(0, typeof(Wrapper<string>))] + [Union(1, typeof(Wrapper<int[]>))] + [Union(2, typeof(Wrapper<IEnumerable<Guid>>))] + public class Wrapper<T> + { + [Key(0)] + public List<T> Content1 { get; set; } + [Key(1)] + public MyGenericObject<T> Content2 { get; set; } + } + + [MessagePackObject] + public class MyGenericObject<T> + { + [Key(0)] + public MyInnerGenericObject<T> Content { get; set; } + } + + [MessagePackObject] + public class MyInnerGenericObject<T> + { + } +} + "; + tempWorkarea.AddFileToProject("MyMessagePackObject.cs", contents); + + var compiler = new MessagePackCompiler.CodeGenerator(testOutputHelper.WriteLine, CancellationToken.None); + await compiler.GenerateFileAsync( + tempWorkarea.CsProjectPath, + isSingleFileOutput ? Path.Combine(tempWorkarea.OutputDirectory, "Generated.cs") : tempWorkarea.OutputDirectory, + string.Empty, + "TempProjectResolver", + "TempProject.Generated", + false, + string.Empty); + + var compilation = tempWorkarea.GetOutputCompilation(); + compilation.Compilation.GetDiagnostics().Where(x => x.WarningLevel == 0).Should().BeEmpty(); + + var symbols = compilation.GetNamedTypeSymbolsFromGenerated(); + var formatters = symbols.SelectMany(x => x.Interfaces).Select(x => x.ToDisplayString()).ToArray(); + formatters.Should().Contain(new[] + { + "MessagePack.Formatters.IMessagePackFormatter<TempProject.Wrapper<T>>", + "MessagePack.Formatters.IMessagePackFormatter<TempProject.MyGenericObject<T>>", + "MessagePack.Formatters.IMessagePackFormatter<TempProject.MyInnerGenericObject<T>>", + }); + + compilation.GetResolverKnownFormatterTypes().Should().Contain(new[] + { + "TempProject.Generated.Formatters.TempProject.WrapperFormatter<global::System.Collections.Generic.IEnumerable<global::System.Guid>>", + "TempProject.Generated.Formatters.TempProject.WrapperFormatter<int[]>", + "TempProject.Generated.Formatters.TempProject.WrapperFormatter<string>", + "TempProject.Generated.Formatters.TempProject.MyGenericObjectFormatter<global::System.Collections.Generic.IEnumerable<global::System.Guid>>", + "TempProject.Generated.Formatters.TempProject.MyGenericObjectFormatter<int[]>", + "TempProject.Generated.Formatters.TempProject.MyGenericObjectFormatter<string>", + "TempProject.Generated.Formatters.TempProject.MyInnerGenericObjectFormatter<global::System.Collections.Generic.IEnumerable<global::System.Guid>>", + "TempProject.Generated.Formatters.TempProject.MyInnerGenericObjectFormatter<int[]>", + "TempProject.Generated.Formatters.TempProject.MyInnerGenericObjectFormatter<string>", + }); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task NestedGenericTypes(bool isSingleFileOutput) + { + using var tempWorkarea = TemporaryProjectWorkarea.Create(false); + var contents = @" +using System; +using System.Collections.Generic; +using MessagePack; + +namespace TempProject +{ + [MessagePackObject] + public class MyObject : Wrapper<MyObject2> + { + } + + [MessagePackObject] + public class MyObject2 + {} + + [MessagePackObject] + public class Wrapper<T> + { + [Key(0)] + public List<T> Content1 { get; set; } + [Key(1)] + public MyGenericObject<T> Content2 { get; set; } + } + + [MessagePackObject] + public class MyGenericObject<T> + { + [Key(0)] + public MyInnerGenericObject<T> Content { get; set; } + [Key(1)] + public T[] Content2 { get; set; } + } + + [MessagePackObject] + public class MyInnerGenericObject<T> + { + } +} + "; + tempWorkarea.AddFileToProject("MyMessagePackObject.cs", contents); + + var compiler = new MessagePackCompiler.CodeGenerator(testOutputHelper.WriteLine, CancellationToken.None); + await compiler.GenerateFileAsync( + tempWorkarea.CsProjectPath, + isSingleFileOutput ? Path.Combine(tempWorkarea.OutputDirectory, "Generated.cs") : tempWorkarea.OutputDirectory, + string.Empty, + "TempProjectResolver", + "TempProject.Generated", + false, + string.Empty); + + var compilation = tempWorkarea.GetOutputCompilation(); + compilation.Compilation.GetDiagnostics().Where(x => x.WarningLevel == 0).Should().BeEmpty(); + + var symbols = compilation.GetNamedTypeSymbolsFromGenerated(); + var formatters = symbols.SelectMany(x => x.Interfaces).Select(x => x.ToDisplayString()).ToArray(); + formatters.Should().Contain(new[] + { + "MessagePack.Formatters.IMessagePackFormatter<TempProject.MyObject>", + "MessagePack.Formatters.IMessagePackFormatter<TempProject.MyObject2>", + "MessagePack.Formatters.IMessagePackFormatter<TempProject.Wrapper<T>>", + "MessagePack.Formatters.IMessagePackFormatter<TempProject.MyGenericObject<T>>", + "MessagePack.Formatters.IMessagePackFormatter<TempProject.MyInnerGenericObject<T>>", + }); + + compilation.GetResolverKnownFormatterTypes().Should().Contain(new[] + { + // "TempProject.Generated.Formatters.TempProject.WrapperFormatter<global::TempProject.MyObject2>", // Wrapper<T> is not used as a property/field in the code. The generated resolver can ignore it. + "TempProject.Generated.Formatters.TempProject.MyGenericObjectFormatter<global::TempProject.MyObject2>", + "TempProject.Generated.Formatters.TempProject.MyInnerGenericObjectFormatter<global::TempProject.MyObject2>", + "global::MessagePack.Formatters.ListFormatter<global::TempProject.MyObject2>", + "global::MessagePack.Formatters.ArrayFormatter<global::TempProject.MyObject2>", + }); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task GenericsOfTFormatter_WithKnownTypes(bool isSingleFileOutput) + { + using var tempWorkarea = TemporaryProjectWorkarea.Create(false); + var contents = @" +using System; +using System.Collections.Generic; +using MessagePack; + +namespace TempProject +{ + [MessagePackObject] + public class MyObject : MyGenericObject<MyObject2> + { + } + + [MessagePackObject] + public class MyObject2 + { } + + [MessagePackObject] + public class MyGenericObject<T> + { + [Key(0)] + public List<T> Content1 { get; set; } + [Key(1)] + public T[] Content2 { get; set; } + } +} + "; + tempWorkarea.AddFileToProject("MyMessagePackObject.cs", contents); + + var compiler = new MessagePackCompiler.CodeGenerator(testOutputHelper.WriteLine, CancellationToken.None); + await compiler.GenerateFileAsync( + tempWorkarea.CsProjectPath, + isSingleFileOutput ? Path.Combine(tempWorkarea.OutputDirectory, "Generated.cs") : tempWorkarea.OutputDirectory, + string.Empty, + "TempProjectResolver", + "TempProject.Generated", + false, + string.Empty); + + var compilation = tempWorkarea.GetOutputCompilation(); + compilation.Compilation.GetDiagnostics().Where(x => x.WarningLevel == 0).Should().BeEmpty(); + + var symbols = compilation.GetNamedTypeSymbolsFromGenerated(); + var formatters = symbols.SelectMany(x => x.Interfaces).Select(x => x.ToDisplayString()).ToArray(); + formatters.Should().Contain(new[] + { + "MessagePack.Formatters.IMessagePackFormatter<TempProject.MyObject>", + "MessagePack.Formatters.IMessagePackFormatter<TempProject.MyObject2>", + "MessagePack.Formatters.IMessagePackFormatter<TempProject.MyGenericObject<T>>", + }); + + compilation.GetResolverKnownFormatterTypes().Should().Contain(new[] + { + "global::MessagePack.Formatters.ListFormatter<global::TempProject.MyObject2>", + "global::MessagePack.Formatters.ArrayFormatter<global::TempProject.MyObject2>", + // "TempProject.Generated.Formatters.TempProject.MyGenericObjectFormatter<string>", // MyGenericObjectFormatter<T> is not used as a property/field in the code. The generated resolver can ignore it. + "TempProject.Generated.Formatters.TempProject.MyObjectFormatter", + "TempProject.Generated.Formatters.TempProject.MyObject2Formatter", + }); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] public async Task GenericsOfTFormatter(bool isSingleFileOutput) { using var tempWorkarea = TemporaryProjectWorkarea.Create(); @@ -358,5 +584,303 @@ namespace TempProject // The generated resolver doesn't know closed-type generic formatter. compilation.GetResolverKnownFormatterTypes().Should().BeEmpty(); } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task Generics_Constraints_Type(bool isSingleFileOutput) + { + using var tempWorkarea = TemporaryProjectWorkarea.Create(); + var contents = @" +using System; +using System.Collections.Generic; +using MessagePack; + +namespace TempProject +{ + [MessagePackObject] + public class MyGenericObject<T> + where T : IDisposable + { + [Key(0)] + public T Content { get; set; } + } +} + "; + tempWorkarea.AddFileToProject("MyMessagePackObject.cs", contents); + + var compiler = new MessagePackCompiler.CodeGenerator(testOutputHelper.WriteLine, CancellationToken.None); + await compiler.GenerateFileAsync( + tempWorkarea.CsProjectPath, + isSingleFileOutput ? Path.Combine(tempWorkarea.OutputDirectory, "Generated.cs") : tempWorkarea.OutputDirectory, + string.Empty, + "TempProjectResolver", + "TempProject.Generated", + false, + string.Empty); + + var compilation = tempWorkarea.GetOutputCompilation(); + compilation.Compilation.GetDiagnostics().Where(x => x.WarningLevel == 0).Should().BeEmpty(); + + var symbols = compilation.GetNamedTypeSymbolsFromGenerated(); + + var formatterType = symbols.FirstOrDefault(x => x.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) == "global::TempProject.Generated.Formatters.TempProject.MyGenericObjectFormatter<T>"); + formatterType.Should().NotBeNull(); + // IDisposable + formatterType.TypeParameters[0].HasReferenceTypeConstraint.Should().BeFalse(); + formatterType.TypeParameters[0].HasValueTypeConstraint.Should().BeFalse(); + formatterType.TypeParameters[0].HasConstructorConstraint.Should().BeFalse(); + formatterType.TypeParameters[0].HasNotNullConstraint.Should().BeFalse(); + formatterType.TypeParameters[0].HasUnmanagedTypeConstraint.Should().BeFalse(); + formatterType.TypeParameters[0].ConstraintTypes.Should().Contain(x => x.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) == "global::System.IDisposable"); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task Generics_Constraints_NullableReferenceType(bool isSingleFileOutput) + { + using var tempWorkarea = TemporaryProjectWorkarea.Create(); + var contents = @" +using System; +using System.Collections.Generic; +using MessagePack; + +namespace TempProject +{ + [MessagePackObject] + public class MyGenericObject<T1, T2, T3, T4> + where T1 : MyClass? + where T2 : MyClass + where T3 : MyGenericClass<MyGenericClass<MyClass?>?>? + where T4 : MyClass, IMyInterface? + { + [Key(0)] + public T1 Content { get; set; } + } + + public class MyClass {} + public class MyGenericClass<T> {} + public interface IMyInterface {} +} + "; + tempWorkarea.AddFileToProject("MyMessagePackObject.cs", contents); + + var compiler = new MessagePackCompiler.CodeGenerator(testOutputHelper.WriteLine, CancellationToken.None); + await compiler.GenerateFileAsync( + tempWorkarea.CsProjectPath, + isSingleFileOutput ? Path.Combine(tempWorkarea.OutputDirectory, "Generated.cs") : tempWorkarea.OutputDirectory, + string.Empty, + "TempProjectResolver", + "TempProject.Generated", + false, + string.Empty); + + var compilation = tempWorkarea.GetOutputCompilation(); + compilation.Compilation.GetDiagnostics().Where(x => x.WarningLevel == 0).Should().BeEmpty(); + + var symbols = compilation.GetNamedTypeSymbolsFromGenerated(); + + var formatterType = symbols.FirstOrDefault(x => x.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) == "global::TempProject.Generated.Formatters.TempProject.MyGenericObjectFormatter<T1, T2, T3, T4>"); + formatterType.Should().NotBeNull(); + // MyClass? + formatterType.TypeParameters[0].ConstraintTypes.Should().Contain(x => x.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) == "global::TempProject.MyClass"); + formatterType.TypeParameters[0].ConstraintNullableAnnotations[0].Should().Be(NullableAnnotation.Annotated); + // MyClass + formatterType.TypeParameters[1].ConstraintTypes.Should().Contain(x => x.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) == "global::TempProject.MyClass"); + formatterType.TypeParameters[1].ConstraintNullableAnnotations[0].Should().Be(NullableAnnotation.None); + // MyGenericClass<MyGenericClass<MyClass?>?>? + formatterType.TypeParameters[2].ConstraintTypes.Should().Contain(x => x.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat.WithMiscellaneousOptions(SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier)) == "global::TempProject.MyGenericClass<global::TempProject.MyGenericClass<global::TempProject.MyClass?>?>"); + formatterType.TypeParameters[2].ConstraintNullableAnnotations[0].Should().Be(NullableAnnotation.Annotated); + // MyClass, IMyInterface? + formatterType.TypeParameters[3].ConstraintTypes.Should().Contain(x => x.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) == "global::TempProject.MyClass"); + formatterType.TypeParameters[3].ConstraintTypes.Should().Contain(x => x.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) == "global::TempProject.IMyInterface"); + formatterType.TypeParameters[3].ConstraintNullableAnnotations[0].Should().Be(NullableAnnotation.None); + formatterType.TypeParameters[3].ConstraintNullableAnnotations[1].Should().Be(NullableAnnotation.Annotated); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task Generics_Constraints_Struct(bool isSingleFileOutput) + { + using var tempWorkarea = TemporaryProjectWorkarea.Create(); + var contents = @" +using System; +using System.Collections.Generic; +using MessagePack; + +namespace TempProject +{ + [MessagePackObject] + public class MyGenericObject<T> + where T : struct + { + [Key(0)] + public T Content { get; set; } + } +} + "; + tempWorkarea.AddFileToProject("MyMessagePackObject.cs", contents); + + var compiler = new MessagePackCompiler.CodeGenerator(testOutputHelper.WriteLine, CancellationToken.None); + await compiler.GenerateFileAsync( + tempWorkarea.CsProjectPath, + isSingleFileOutput ? Path.Combine(tempWorkarea.OutputDirectory, "Generated.cs") : tempWorkarea.OutputDirectory, + string.Empty, + "TempProjectResolver", + "TempProject.Generated", + false, + string.Empty); + + var compilation = tempWorkarea.GetOutputCompilation(); + compilation.Compilation.GetDiagnostics().Where(x => x.WarningLevel == 0).Should().BeEmpty(); + + var symbols = compilation.GetNamedTypeSymbolsFromGenerated(); + + var formatterType = symbols.FirstOrDefault(x => x.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) == "global::TempProject.Generated.Formatters.TempProject.MyGenericObjectFormatter<T>"); + formatterType.Should().NotBeNull(); + // struct + formatterType.TypeParameters[0].HasReferenceTypeConstraint.Should().BeFalse(); + formatterType.TypeParameters[0].HasValueTypeConstraint.Should().BeTrue(); + formatterType.TypeParameters[0].HasConstructorConstraint.Should().BeFalse(); + formatterType.TypeParameters[0].HasNotNullConstraint.Should().BeFalse(); + formatterType.TypeParameters[0].HasUnmanagedTypeConstraint.Should().BeFalse(); + formatterType.TypeParameters[0].ConstraintTypes.Should().BeEmpty(); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task Generics_Constraints_Multiple(bool isSingleFileOutput) + { + using var tempWorkarea = TemporaryProjectWorkarea.Create(); + var contents = @" +using System; +using System.Collections.Generic; +using MessagePack; + +namespace TempProject +{ + [MessagePackObject] + public class MyGenericObject<T1, T2, T3, T4> + where T1 : struct + where T2 : IDisposable, new() + where T3 : notnull + where T4 : unmanaged + { + [Key(0)] + public T1 Content1 { get; set; } + [Key(1)] + public T2 Content2 { get; set; } + [Key(2)] + public T3 Content3 { get; set; } + [Key(3)] + public T4 Content4 { get; set; } + } +} + "; + tempWorkarea.AddFileToProject("MyMessagePackObject.cs", contents); + + var compiler = new MessagePackCompiler.CodeGenerator(testOutputHelper.WriteLine, CancellationToken.None); + await compiler.GenerateFileAsync( + tempWorkarea.CsProjectPath, + isSingleFileOutput ? Path.Combine(tempWorkarea.OutputDirectory, "Generated.cs") : tempWorkarea.OutputDirectory, + string.Empty, + "TempProjectResolver", + "TempProject.Generated", + false, + string.Empty); + + var compilation = tempWorkarea.GetOutputCompilation(); + compilation.Compilation.GetDiagnostics().Where(x => x.WarningLevel == 0).Should().BeEmpty(); + + var symbols = compilation.GetNamedTypeSymbolsFromGenerated(); + + var formatterType = symbols.FirstOrDefault(x => x.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) == "global::TempProject.Generated.Formatters.TempProject.MyGenericObjectFormatter<T1, T2, T3, T4>"); + formatterType.Should().NotBeNull(); + // struct + formatterType.TypeParameters[0].HasReferenceTypeConstraint.Should().BeFalse(); + formatterType.TypeParameters[0].HasValueTypeConstraint.Should().BeTrue(); + formatterType.TypeParameters[0].HasConstructorConstraint.Should().BeFalse(); + formatterType.TypeParameters[0].HasNotNullConstraint.Should().BeFalse(); + formatterType.TypeParameters[0].HasUnmanagedTypeConstraint.Should().BeFalse(); + formatterType.TypeParameters[0].ConstraintTypes.Should().BeEmpty(); + // IDisposable, new() + formatterType.TypeParameters[1].HasReferenceTypeConstraint.Should().BeFalse(); + formatterType.TypeParameters[1].HasValueTypeConstraint.Should().BeFalse(); + formatterType.TypeParameters[1].HasConstructorConstraint.Should().BeTrue(); + formatterType.TypeParameters[1].HasNotNullConstraint.Should().BeFalse(); + formatterType.TypeParameters[1].HasUnmanagedTypeConstraint.Should().BeFalse(); + formatterType.TypeParameters[1].ConstraintTypes.Should().Contain(x => x.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) == "global::System.IDisposable"); + // notnull + formatterType.TypeParameters[2].HasReferenceTypeConstraint.Should().BeFalse(); + formatterType.TypeParameters[2].HasValueTypeConstraint.Should().BeFalse(); + formatterType.TypeParameters[2].HasConstructorConstraint.Should().BeFalse(); + formatterType.TypeParameters[2].HasNotNullConstraint.Should().BeTrue(); + formatterType.TypeParameters[2].HasUnmanagedTypeConstraint.Should().BeFalse(); + formatterType.TypeParameters[2].ConstraintTypes.Should().BeEmpty(); + // unmanaged + formatterType.TypeParameters[3].HasReferenceTypeConstraint.Should().BeFalse(); + formatterType.TypeParameters[3].HasValueTypeConstraint.Should().BeTrue(); // unmanaged constraint includes value-type constraint + formatterType.TypeParameters[3].HasConstructorConstraint.Should().BeFalse(); + formatterType.TypeParameters[3].HasNotNullConstraint.Should().BeFalse(); + formatterType.TypeParameters[3].HasUnmanagedTypeConstraint.Should().BeTrue(); + formatterType.TypeParameters[3].ConstraintTypes.Should().BeEmpty(); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task Generics_Constraints_ReferenceType_Nullable(bool isSingleFileOutput) + { + using var tempWorkarea = TemporaryProjectWorkarea.Create(); + var contents = @" +using System; +using System.Collections.Generic; +using MessagePack; + +namespace TempProject +{ + [MessagePackObject] + public class MyGenericObject<T1, T2> + where T1 : class? + where T2 : class + { + [Key(0)] + public T1 Content1 { get; set; } + [Key(1)] + public T2 Content2 { get; set; } + } +} + "; + tempWorkarea.AddFileToProject("MyMessagePackObject.cs", contents); + + var compiler = new MessagePackCompiler.CodeGenerator(testOutputHelper.WriteLine, CancellationToken.None); + await compiler.GenerateFileAsync( + tempWorkarea.CsProjectPath, + isSingleFileOutput ? Path.Combine(tempWorkarea.OutputDirectory, "Generated.cs") : tempWorkarea.OutputDirectory, + string.Empty, + "TempProjectResolver", + "TempProject.Generated", + false, + string.Empty); + + var compilation = tempWorkarea.GetOutputCompilation(); + compilation.Compilation.GetDiagnostics().Where(x => x.WarningLevel == 0).Should().BeEmpty(); + + var symbols = compilation.GetNamedTypeSymbolsFromGenerated(); + + var formatterType = symbols.FirstOrDefault(x => x.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) == "global::TempProject.Generated.Formatters.TempProject.MyGenericObjectFormatter<T1, T2>"); + formatterType.Should().NotBeNull(); + // class? + formatterType.TypeParameters[0].HasReferenceTypeConstraint.Should().BeTrue(); + formatterType.TypeParameters[0].ConstraintTypes.Should().BeEmpty(); + formatterType.TypeParameters[0].ReferenceTypeConstraintNullableAnnotation.Should().Be(NullableAnnotation.Annotated); + // class + formatterType.TypeParameters[1].HasReferenceTypeConstraint.Should().BeTrue(); + formatterType.TypeParameters[1].ConstraintTypes.Should().BeEmpty(); + formatterType.TypeParameters[1].ReferenceTypeConstraintNullableAnnotation.Should().Be(NullableAnnotation.None); + } } } |