Skip to content

Commit f4a78b3

Browse files
committed
Added compiler warnings when key member has a comparer and no equality comparer and vice versa.
1 parent e03e765 commit f4a78b3

File tree

38 files changed

+770
-157
lines changed

38 files changed

+770
-157
lines changed

Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
<PropertyGroup>
44
<Copyright>(c) $([System.DateTime]::Now.Year), Pawel Gerr. All rights reserved.</Copyright>
5-
<VersionPrefix>8.4.0</VersionPrefix>
5+
<VersionPrefix>8.4.1</VersionPrefix>
66
<Authors>Pawel Gerr</Authors>
77
<GenerateDocumentationFile>true</GenerateDocumentationFile>
88
<PackageProjectUrl>https://github.com/PawelGerr/Thinktecture.Runtime.Extensions</PackageProjectUrl>

samples/Thinktecture.Runtime.Extensions.Benchmarking/Database/Description.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ namespace Thinktecture.Database;
44

55
[ValueObject<string>]
66
[ValueObjectKeyMemberEqualityComparer<ComparerAccessors.StringOrdinalIgnoreCase, string>]
7+
[ValueObjectKeyMemberComparer<ComparerAccessors.StringOrdinalIgnoreCase, string>]
78
public partial class Description
89
{
910
static partial void ValidateFactoryArguments(ref ValidationError? validationError, ref string value)

samples/Thinktecture.Runtime.Extensions.Benchmarking/Database/DescriptionStruct.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ namespace Thinktecture.Database;
44

55
[ValueObject<string>]
66
[ValueObjectKeyMemberEqualityComparer<ComparerAccessors.StringOrdinalIgnoreCase, string>]
7+
[ValueObjectKeyMemberComparer<ComparerAccessors.StringOrdinalIgnoreCase, string>]
78
public partial struct DescriptionStruct
89
{
910
static partial void ValidateFactoryArguments(ref ValidationError? validationError, ref string value)

samples/Thinktecture.Runtime.Extensions.Benchmarking/Database/Name.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ namespace Thinktecture.Database;
44

55
[ValueObject<string>]
66
[ValueObjectKeyMemberEqualityComparer<ComparerAccessors.StringOrdinalIgnoreCase, string>]
7+
[ValueObjectKeyMemberComparer<ComparerAccessors.StringOrdinalIgnoreCase, string>]
78
public partial class Name
89
{
910
static partial void ValidateFactoryArguments(ref ValidationError? validationError, ref string value)

samples/Thinktecture.Runtime.Extensions.Benchmarking/Database/NameStruct.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ namespace Thinktecture.Database;
44

55
[ValueObject<string>]
66
[ValueObjectKeyMemberEqualityComparer<ComparerAccessors.StringOrdinalIgnoreCase, string>]
7+
[ValueObjectKeyMemberComparer<ComparerAccessors.StringOrdinalIgnoreCase, string>]
78
public partial struct NameStruct
89
{
910
static partial void ValidateFactoryArguments(ref ValidationError? validationError, ref string value)

samples/Thinktecture.Runtime.Extensions.MessagePack.Samples/ProductNameWithMessagePackFormatter.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ namespace Thinktecture;
44

55
[ValueObject<string>(NullInFactoryMethodsYieldsNull = true)]
66
[ValueObjectKeyMemberEqualityComparer<ComparerAccessors.StringOrdinalIgnoreCase, string>]
7+
[ValueObjectKeyMemberComparer<ComparerAccessors.StringOrdinalIgnoreCase, string>]
78
public partial class ProductNameWithMessagePackFormatter
89
{
910
static partial void ValidateFactoryArguments(ref ValidationError? validationError, ref string value)

samples/Thinktecture.Runtime.Extensions.Samples/Unions/Animal.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ public override string ToString()
2020

2121
[ValueObject<string>]
2222
[ValueObjectKeyMemberEqualityComparer<ComparerAccessors.StringOrdinalIgnoreCase, string>]
23+
[ValueObjectKeyMemberComparer<ComparerAccessors.StringOrdinalIgnoreCase, string>]
2324
public partial class Dog : Animal;
2425

2526
public sealed class Cat : Animal

samples/Thinktecture.Runtime.Extensions.Samples/ValueObjects/OtherProductName.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ namespace Thinktecture.ValueObjects;
44

55
[ValueObject<string>(EmptyStringInFactoryMethodsYieldsNull = true)]
66
[ValueObjectKeyMemberEqualityComparer<ComparerAccessors.StringOrdinalIgnoreCase, string>]
7+
[ValueObjectKeyMemberComparer<ComparerAccessors.StringOrdinalIgnoreCase, string>]
78
public partial class OtherProductName
89
{
910
static partial void ValidateFactoryArguments(ref ValidationError? validationError, ref string value)

samples/Thinktecture.Runtime.Extensions.Samples/ValueObjects/ProductName.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ namespace Thinktecture.ValueObjects;
44

55
[ValueObject<string>(NullInFactoryMethodsYieldsNull = true)]
66
[ValueObjectKeyMemberEqualityComparer<ComparerAccessors.StringOrdinalIgnoreCase, string>]
7+
[ValueObjectKeyMemberComparer<ComparerAccessors.StringOrdinalIgnoreCase, string>]
78
public partial class ProductName
89
{
910
static partial void ValidateFactoryArguments(ref ValidationError? validationError, ref string value)

samples/Thinktecture.Runtime.Extensions.Samples/ValueObjects/ProductNameStruct.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ namespace Thinktecture.ValueObjects;
44

55
[ValueObject<string>(DefaultInstancePropertyName = "None")]
66
[ValueObjectKeyMemberEqualityComparer<ComparerAccessors.StringOrdinalIgnoreCase, string>]
7+
[ValueObjectKeyMemberComparer<ComparerAccessors.StringOrdinalIgnoreCase, string>]
78
public partial struct ProductNameStruct
89
{
910
static partial void ValidateFactoryArguments(ref ValidationError? validationError, ref string value)

src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/CodeFixes/ThinktectureRuntimeExtensionsCodeFixProvider.cs

Lines changed: 56 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ public sealed class ThinktectureRuntimeExtensionsCodeFixProvider : CodeFixProvid
1818
private const string _MAKE_TYPE_PUBLIC = "Make type public";
1919
private const string _SEAL_CLASS = "Seal class";
2020
private const string _DEFINE_VALUE_OBJECT_EQUALITY_COMPARER = "Define Value Object equality comparer";
21+
private const string _DEFINE_VALUE_OBJECT_COMPARER = "Define Value Object comparer";
2122

2223
/// <inheritdoc />
2324
public override ImmutableArray<string> FixableDiagnosticIds { get; } =
@@ -31,8 +32,10 @@ public sealed class ThinktectureRuntimeExtensionsCodeFixProvider : CodeFixProvid
3132
DiagnosticsDescriptors.InnerEnumOnNonFirstLevelMustBePublic.Id,
3233
DiagnosticsDescriptors.EnumWithoutDerivedTypesMustBeSealed.Id,
3334
DiagnosticsDescriptors.InitAccessorMustBePrivate.Id,
34-
DiagnosticsDescriptors.StringBaseValueObjectNeedsEqualityComparer.Id,
35-
DiagnosticsDescriptors.ComplexValueObjectWithStringMembersNeedsDefaultEqualityComparer.Id
35+
DiagnosticsDescriptors.StringBasedValueObjectNeedsEqualityComparer.Id,
36+
DiagnosticsDescriptors.ComplexValueObjectWithStringMembersNeedsDefaultEqualityComparer.Id,
37+
DiagnosticsDescriptors.ExplicitComparerWithoutEqualityComparer.Id,
38+
DiagnosticsDescriptors.ExplicitEqualityComparerWithoutComparer.Id,
3639
];
3740

3841
/// <inheritdoc />
@@ -90,14 +93,22 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context)
9093
{
9194
context.RegisterCodeFix(CodeAction.Create(_SEAL_CLASS, _ => AddTypeModifierAsync(context.Document, root, GetCodeFixesContext().TypeDeclaration, SyntaxKind.SealedKeyword), _SEAL_CLASS), diagnostic);
9295
}
93-
else if (diagnostic.Id == DiagnosticsDescriptors.StringBaseValueObjectNeedsEqualityComparer.Id)
96+
else if (diagnostic.Id == DiagnosticsDescriptors.StringBasedValueObjectNeedsEqualityComparer.Id)
9497
{
9598
context.RegisterCodeFix(CodeAction.Create(_DEFINE_VALUE_OBJECT_EQUALITY_COMPARER, t => AddValueObjectKeyMemberEqualityComparerAttributeAsync(context.Document, root, GetCodeFixesContext().TypeDeclaration, t), _DEFINE_VALUE_OBJECT_EQUALITY_COMPARER), diagnostic);
9699
}
97100
else if (diagnostic.Id == DiagnosticsDescriptors.ComplexValueObjectWithStringMembersNeedsDefaultEqualityComparer.Id)
98101
{
99102
context.RegisterCodeFix(CodeAction.Create(_DEFINE_VALUE_OBJECT_EQUALITY_COMPARER, t => AddDefaultStringComparisonAsync(context.Document, root, GetCodeFixesContext().TypeDeclaration, t), _DEFINE_VALUE_OBJECT_EQUALITY_COMPARER), diagnostic);
100103
}
104+
else if (diagnostic.Id == DiagnosticsDescriptors.ExplicitComparerWithoutEqualityComparer.Id)
105+
{
106+
context.RegisterCodeFix(CodeAction.Create(_DEFINE_VALUE_OBJECT_EQUALITY_COMPARER, t => AddValueObjectKeyMemberEqualityComparerAttributeAsync(context.Document, root, GetCodeFixesContext().TypeDeclaration, t), _DEFINE_VALUE_OBJECT_EQUALITY_COMPARER), diagnostic);
107+
}
108+
else if (diagnostic.Id == DiagnosticsDescriptors.ExplicitEqualityComparerWithoutComparer.Id)
109+
{
110+
context.RegisterCodeFix(CodeAction.Create(_DEFINE_VALUE_OBJECT_COMPARER, t => AddValueObjectKeyMemberComparerAttributeAsync(context.Document, root, GetCodeFixesContext().TypeDeclaration, t), _DEFINE_VALUE_OBJECT_COMPARER), diagnostic);
111+
}
101112
}
102113
}
103114

@@ -262,6 +273,35 @@ private static async Task<Document> AddValueObjectKeyMemberEqualityComparerAttri
262273
SyntaxNode root,
263274
TypeDeclarationSyntax? declaration,
264275
CancellationToken cancellationToken)
276+
{
277+
return await AddValueObjectKeyMemberComparerAsync(
278+
document,
279+
root,
280+
declaration,
281+
"ValueObjectKeyMemberEqualityComparer",
282+
cancellationToken);
283+
}
284+
285+
private static async Task<Document> AddValueObjectKeyMemberComparerAttributeAsync(
286+
Document document,
287+
SyntaxNode root,
288+
TypeDeclarationSyntax? declaration,
289+
CancellationToken cancellationToken)
290+
{
291+
return await AddValueObjectKeyMemberComparerAsync(
292+
document,
293+
root,
294+
declaration,
295+
"ValueObjectKeyMemberComparer",
296+
cancellationToken);
297+
}
298+
299+
private static async Task<Document> AddValueObjectKeyMemberComparerAsync(
300+
Document document,
301+
SyntaxNode root,
302+
TypeDeclarationSyntax? declaration,
303+
string comparerAttributeName,
304+
CancellationToken cancellationToken)
265305
{
266306
if (declaration is null)
267307
return document;
@@ -273,25 +313,32 @@ private static async Task<Document> AddValueObjectKeyMemberEqualityComparerAttri
273313

274314
var valueObjectType = model.GetDeclaredSymbol(declaration);
275315

276-
if (!valueObjectType.IsValueObjectType(out var valueObjectAttribute)
277-
|| valueObjectAttribute.AttributeClass?.IsKeyedValueObjectAttribute() != true)
316+
if ((!valueObjectType.IsValueObjectType(out var keyedAttribute)
317+
|| keyedAttribute.AttributeClass?.IsKeyedValueObjectAttribute() != true)
318+
&& !valueObjectType.IsEnum(out keyedAttribute))
278319
{
279320
return document;
280321
}
281322

282-
var keyType = valueObjectAttribute.AttributeClass?.TypeArguments[0];
323+
var keyType = keyedAttribute.AttributeClass?.TypeArguments[0];
283324

284325
if (keyType is null)
285326
return document;
286327

328+
var keyTypeName = SyntaxFactory.ParseTypeName(keyType.ToMinimalDisplayString(model, declaration.GetLocation().SourceSpan.Start));
329+
330+
var accessorType = keyType.SpecialType == SpecialType.System_String
331+
? SyntaxFactory.ParseTypeName("ComparerAccessors.StringOrdinalIgnoreCase")
332+
: SyntaxFactory.ParseTypeName($"ComparerAccessors.Default<{keyTypeName}>");
333+
287334
var genericParameters = new SyntaxNodeOrToken[]
288335
{
289-
SyntaxFactory.ParseTypeName("ComparerAccessors.StringOrdinalIgnoreCase"),
336+
accessorType,
290337
SyntaxFactory.Token(SyntaxKind.CommaToken),
291-
SyntaxFactory.ParseTypeName(keyType.ToMinimalDisplayString(model, declaration.GetLocation().SourceSpan.Start))
338+
keyTypeName
292339
};
293340

294-
var comparerAttributeType = SyntaxFactory.GenericName("ValueObjectKeyMemberEqualityComparer")
341+
var comparerAttributeType = SyntaxFactory.GenericName(comparerAttributeName)
295342
.WithTypeArgumentList(SyntaxFactory.TypeArgumentList(SyntaxFactory.SeparatedList<TypeSyntax>(genericParameters)));
296343
var comparerAttribute = SyntaxFactory.Attribute(comparerAttributeType);
297344

src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/Constants.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ public static class Properties
5353
public const string KEY_MEMBER_KIND = "KeyMemberKind";
5454
public const string CONSTRUCTOR_ACCESS_MODIFIER = "ConstructorAccessModifier";
5555
public const string DEFAULT_STRING_COMPARISON = "DefaultStringComparison";
56+
public const string SKIP_ICOMPARABLE = "SkipIComparable";
57+
public const string IS_VALIDATABLE = "IsValidatable";
5658
}
5759

5860
public static class ValueObject

src/Thinktecture.Runtime.Extensions.SourceGenerator/CodeAnalysis/Diagnostics/ThinktectureRuntimeExtensionsAnalyzer.cs

Lines changed: 51 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,10 @@ public sealed class ThinktectureRuntimeExtensionsAnalyzer : DiagnosticAnalyzer
4444
DiagnosticsDescriptors.CustomKeyMemberImplementationTypeMismatch,
4545
DiagnosticsDescriptors.IndexBasedSwitchAndMapMustUseNamedParameters,
4646
DiagnosticsDescriptors.VariableMustBeInitializedWithNonDefaultValue,
47-
DiagnosticsDescriptors.StringBaseValueObjectNeedsEqualityComparer,
48-
DiagnosticsDescriptors.ComplexValueObjectWithStringMembersNeedsDefaultEqualityComparer
47+
DiagnosticsDescriptors.StringBasedValueObjectNeedsEqualityComparer,
48+
DiagnosticsDescriptors.ComplexValueObjectWithStringMembersNeedsDefaultEqualityComparer,
49+
DiagnosticsDescriptors.ExplicitComparerWithoutEqualityComparer,
50+
DiagnosticsDescriptors.ExplicitEqualityComparerWithoutComparer
4951
];
5052

5153
/// <inheritdoc />
@@ -271,10 +273,22 @@ private static void AnalyzeValueObject(OperationAnalysisContext context)
271273
return;
272274

273275
var locationOfFirstDeclaration = type.Locations.IsDefaultOrEmpty ? Location.None : type.Locations[0]; // a representative for all
274-
var assignableMembers = ValidateSharedValueObject(context, type, locationOfFirstDeclaration);
276+
277+
var factory = TypedMemberStateFactoryProvider.GetFactoryOrNull(context.Compilation, _errorLogger);
278+
279+
if (factory is null)
280+
{
281+
context.ReportDiagnostic(Diagnostic.Create(DiagnosticsDescriptors.ErrorDuringCodeAnalysis,
282+
locationOfFirstDeclaration,
283+
type.ToFullyQualifiedDisplayString(),
284+
"Could not fetch type information for analysis of the value object."));
285+
return;
286+
}
287+
288+
var assignableMembers = ValidateSharedValueObject(context, type, locationOfFirstDeclaration, factory);
275289

276290
if (isKeyed)
277-
ValidateKeyedValueObject(context, assignableMembers, type, attrCreation, locationOfFirstDeclaration);
291+
ValidateKeyedValueObject(context, assignableMembers, type, attrCreation, locationOfFirstDeclaration, factory);
278292

279293
if (isComplex && assignableMembers is not null)
280294
ValidateComplexValueObject(context, assignableMembers, attrCreation, locationOfFirstDeclaration);
@@ -370,7 +384,8 @@ private static void ValidateKeyedValueObject(
370384
IReadOnlyList<InstanceMemberInfo>? assignableMembers,
371385
INamedTypeSymbol type,
372386
IObjectCreationOperation attribute,
373-
Location locationOfFirstDeclaration)
387+
Location locationOfFirstDeclaration,
388+
TypedMemberStateFactory factory)
374389
{
375390
var keyType = (attribute.Type as INamedTypeSymbol)?.TypeArguments.FirstOrDefault();
376391

@@ -389,14 +404,16 @@ private static void ValidateKeyedValueObject(
389404
if (attribute.FindSkipKeyMember() == true)
390405
ValidateValueObjectCustomKeyMemberImplementation(context, keyType, assignableMembers, attribute, locationOfFirstDeclaration);
391406

392-
ValidateKeyMemberComparers(context, type, keyType, locationOfFirstDeclaration, true);
407+
ValidateKeyMemberComparers(context, type, keyType, attribute, locationOfFirstDeclaration, factory, true);
393408
}
394409

395410
private static void ValidateKeyMemberComparers(
396411
OperationAnalysisContext context,
397412
INamedTypeSymbol type,
398413
ITypeSymbol keyType,
414+
IObjectCreationOperation attibute,
399415
Location locationOfFirstDeclaration,
416+
TypedMemberStateFactory factory,
400417
bool stringBasedRequiresEqualityComparer)
401418
{
402419
AttributeData? keyMemberComparerAttr = null;
@@ -417,14 +434,32 @@ private static void ValidateKeyMemberComparers(
417434
ValidateComparer(context, keyType, keyMemberComparerAttr);
418435
ValidateComparer(context, keyType, keyMemberEqualityComparerAttr);
419436

420-
if (stringBasedRequiresEqualityComparer
421-
&& keyType.SpecialType == SpecialType.System_String
422-
&& keyMemberEqualityComparerAttr is null)
437+
if (keyMemberComparerAttr is not null && keyMemberEqualityComparerAttr is null)
438+
{
439+
ReportDiagnostic(context,
440+
DiagnosticsDescriptors.ExplicitComparerWithoutEqualityComparer,
441+
locationOfFirstDeclaration,
442+
BuildTypeName(type));
443+
}
444+
else if (stringBasedRequiresEqualityComparer
445+
&& keyType.SpecialType == SpecialType.System_String
446+
&& keyMemberEqualityComparerAttr is null)
423447
{
424448
ReportDiagnostic(context,
425-
DiagnosticsDescriptors.StringBaseValueObjectNeedsEqualityComparer,
449+
DiagnosticsDescriptors.StringBasedValueObjectNeedsEqualityComparer,
426450
locationOfFirstDeclaration);
427451
}
452+
453+
if (keyMemberEqualityComparerAttr is not null
454+
&& keyMemberComparerAttr is null
455+
&& attibute.FindSkipIComparable() != true
456+
&& factory.Create(keyType).IsComparable)
457+
{
458+
ReportDiagnostic(context,
459+
DiagnosticsDescriptors.ExplicitEqualityComparerWithoutComparer,
460+
locationOfFirstDeclaration,
461+
BuildTypeName(type));
462+
}
428463
}
429464

430465
private static void ValidateComparer(OperationAnalysisContext context, ITypeSymbol keyType, AttributeData? keyMemberComparerAttr)
@@ -485,25 +520,15 @@ private static void ValidateCustomKeyMemberImplementation(
485520
private static IReadOnlyList<InstanceMemberInfo>? ValidateSharedValueObject(
486521
OperationAnalysisContext context,
487522
INamedTypeSymbol type,
488-
Location locationOfFirstDeclaration)
523+
Location locationOfFirstDeclaration,
524+
TypedMemberStateFactory factory)
489525
{
490526
if (type.IsRecord || type.TypeKind is not (TypeKind.Class or TypeKind.Struct))
491527
{
492528
ReportDiagnostic(context, DiagnosticsDescriptors.TypeMustBeClassOrStruct, locationOfFirstDeclaration, type);
493529
return null;
494530
}
495531

496-
var factory = TypedMemberStateFactoryProvider.GetFactoryOrNull(context.Compilation, _errorLogger);
497-
498-
if (factory is null)
499-
{
500-
context.ReportDiagnostic(Diagnostic.Create(DiagnosticsDescriptors.ErrorDuringCodeAnalysis,
501-
locationOfFirstDeclaration,
502-
type.ToFullyQualifiedDisplayString(),
503-
"Could not fetch type information for analysis of the value object."));
504-
return null;
505-
}
506-
507532
CheckConstructors(context, type, mustBePrivate: false, canHavePrimaryConstructor: false);
508533
TypeMustBePartial(context, type);
509534
TypeMustNotBeGeneric(context, type, locationOfFirstDeclaration, "Value Object");
@@ -628,14 +653,15 @@ private static void ValidateEnum(
628653

629654
EnumKeyMemberNameMustNotBeItem(context, attribute, locationOfFirstDeclaration);
630655

631-
ValidateKeyedSmartEnum(context, enumType, attribute, locationOfFirstDeclaration);
656+
ValidateKeyedSmartEnum(context, enumType, attribute, locationOfFirstDeclaration, factory);
632657
}
633658

634659
private static void ValidateKeyedSmartEnum(
635660
OperationAnalysisContext context,
636661
INamedTypeSymbol enumType,
637662
IObjectCreationOperation attribute,
638-
Location locationOfFirstDeclaration)
663+
Location locationOfFirstDeclaration,
664+
TypedMemberStateFactory factory)
639665
{
640666
var keyType = (attribute.Type as INamedTypeSymbol)?.TypeArguments.FirstOrDefault();
641667

@@ -662,7 +688,7 @@ private static void ValidateKeyedSmartEnum(
662688
ReportDiagnostic(context, DiagnosticsDescriptors.NonValidatableEnumsMustBeClass, locationOfFirstDeclaration, enumType);
663689
}
664690

665-
ValidateKeyMemberComparers(context, enumType, keyType, locationOfFirstDeclaration, false);
691+
ValidateKeyMemberComparers(context, enumType, keyType, attribute, locationOfFirstDeclaration, factory, false);
666692
}
667693

668694
private static void TypeMustNotBeGeneric(OperationAnalysisContext context, INamedTypeSymbol type, Location locationOfFirstDeclaration, string typeKind)

0 commit comments

Comments
 (0)