Skip to content

Commit 8805868

Browse files
committed
add generic support to smart enums and keyed value objects
1 parent fb588e3 commit 8805868

105 files changed

Lines changed: 6028 additions & 109 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs

Submodule docs updated from 485d34e to d8dcbc5

samples/AspNetCore.Samples/AspNetCore.Samples.csproj

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,6 @@
2424
<ProjectReference Include="..\..\src\Thinktecture.Runtime.Extensions.Json\Thinktecture.Runtime.Extensions.Json.csproj" />
2525
<ProjectReference Include="..\..\src\Thinktecture.Runtime.Extensions.Swashbuckle\Thinktecture.Runtime.Extensions.Swashbuckle.csproj" />
2626
<ProjectReference Include="..\Basic.Samples\Basic.Samples.csproj" />
27-
<ProjectReference Include="..\..\src\Thinktecture.Runtime.Extensions.SourceGenerator\Thinktecture.Runtime.Extensions.SourceGenerator.csproj"
28-
SetTargetFramework="TargetFramework=netstandard2.0"
29-
ReferenceOutputAssembly="false"
30-
OutputItemType="Analyzer" />
31-
<ProjectReference Include="..\..\src\Thinktecture.Runtime.Extensions.Refactorings\Thinktecture.Runtime.Extensions.Refactorings.csproj"
32-
SetTargetFramework="TargetFramework=netstandard2.0"
33-
ReferenceOutputAssembly="false"
34-
OutputItemType="Analyzer" />
3527
</ItemGroup>
3628

3729
</Project>

samples/Basic.Samples/Basic.Samples.csproj

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,6 @@
99
<ItemGroup>
1010
<ProjectReference Include="..\..\src\Thinktecture.Runtime.Extensions\Thinktecture.Runtime.Extensions.csproj" />
1111
<ProjectReference Include="..\..\src\Thinktecture.Runtime.Extensions.Json\Thinktecture.Runtime.Extensions.Json.csproj" />
12-
<ProjectReference Include="..\..\src\Thinktecture.Runtime.Extensions.SourceGenerator\Thinktecture.Runtime.Extensions.SourceGenerator.csproj"
13-
SetTargetFramework="TargetFramework=netstandard2.0"
14-
ReferenceOutputAssembly="false"
15-
OutputItemType="Analyzer" />
16-
<ProjectReference Include="..\..\src\Thinktecture.Runtime.Extensions.Refactorings\Thinktecture.Runtime.Extensions.Refactorings.csproj"
17-
SetTargetFramework="TargetFramework=netstandard2.0"
18-
ReferenceOutputAssembly="false"
19-
OutputItemType="Analyzer" />
2012
</ItemGroup>
2113
<ItemGroup>
2214
<PackageReference Include="CsvHelper" />

samples/Benchmarking/Benchmarking.csproj

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,6 @@
1818
<ItemGroup>
1919
<ProjectReference Include="..\..\src\Thinktecture.Runtime.Extensions.EntityFrameworkCore9\Thinktecture.Runtime.Extensions.EntityFrameworkCore9.csproj" />
2020
<ProjectReference Include="..\..\src\Thinktecture.Runtime.Extensions.Json\Thinktecture.Runtime.Extensions.Json.csproj" />
21-
<ProjectReference Include="..\..\src\Thinktecture.Runtime.Extensions.SourceGenerator\Thinktecture.Runtime.Extensions.SourceGenerator.csproj"
22-
SetTargetFramework="TargetFramework=netstandard2.0"
23-
ReferenceOutputAssembly="false"
24-
OutputItemType="Analyzer" />
25-
<ProjectReference Include="..\..\src\Thinktecture.Runtime.Extensions.Refactorings\Thinktecture.Runtime.Extensions.Refactorings.csproj"
26-
SetTargetFramework="TargetFramework=netstandard2.0"
27-
ReferenceOutputAssembly="false"
28-
OutputItemType="Analyzer" />
2921
</ItemGroup>
3022

3123
<ItemGroup>

samples/Directory.Build.props

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,19 @@
2626
<CompilerVisibleProperty Include="ThinktectureRuntimeExtensions_SourceGenerator_LogMessageInitialBufferSize" />
2727
</ItemGroup>
2828

29+
<ItemGroup>
30+
<ProjectReference Include="..\..\src\Thinktecture.Runtime.Extensions.SourceGenerator\Thinktecture.Runtime.Extensions.SourceGenerator.csproj"
31+
SetTargetFramework="TargetFramework=netstandard2.0"
32+
ReferenceOutputAssembly="false"
33+
OutputItemType="Analyzer" />
34+
<ProjectReference Include="..\..\src\Thinktecture.Runtime.Extensions.Analyzers\Thinktecture.Runtime.Extensions.Analyzers.csproj"
35+
SetTargetFramework="TargetFramework=netstandard2.0"
36+
ReferenceOutputAssembly="false"
37+
OutputItemType="Analyzer" />
38+
<ProjectReference Include="..\..\src\Thinktecture.Runtime.Extensions.Refactorings\Thinktecture.Runtime.Extensions.Refactorings.csproj"
39+
SetTargetFramework="TargetFramework=netstandard2.0"
40+
ReferenceOutputAssembly="false"
41+
OutputItemType="Analyzer" />
42+
</ItemGroup>
43+
2944
</Project>

samples/EntityFrameworkCore.Samples/EntityFrameworkCore.Samples.csproj

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,6 @@
66
<ItemGroup>
77
<ProjectReference Include="..\..\src\Thinktecture.Runtime.Extensions.EntityFrameworkCore8\Thinktecture.Runtime.Extensions.EntityFrameworkCore8.csproj" />
88
<ProjectReference Include="..\Basic.Samples\Basic.Samples.csproj" />
9-
<ProjectReference Include="..\..\src\Thinktecture.Runtime.Extensions.SourceGenerator\Thinktecture.Runtime.Extensions.SourceGenerator.csproj"
10-
SetTargetFramework="TargetFramework=netstandard2.0"
11-
ReferenceOutputAssembly="false"
12-
OutputItemType="Analyzer" />
13-
<ProjectReference Include="..\..\src\Thinktecture.Runtime.Extensions.Refactorings\Thinktecture.Runtime.Extensions.Refactorings.csproj"
14-
SetTargetFramework="TargetFramework=netstandard2.0"
15-
ReferenceOutputAssembly="false"
16-
OutputItemType="Analyzer" />
179
</ItemGroup>
1810
<ItemGroup>
1911
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" />

samples/MessagePack.Samples/MessagePack.Samples.csproj

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,5 @@
55
<ItemGroup>
66
<ProjectReference Include="..\..\src\Thinktecture.Runtime.Extensions.MessagePack\Thinktecture.Runtime.Extensions.MessagePack.csproj" />
77
<ProjectReference Include="..\Basic.Samples\Basic.Samples.csproj" />
8-
<ProjectReference Include="..\..\src\Thinktecture.Runtime.Extensions.SourceGenerator\Thinktecture.Runtime.Extensions.SourceGenerator.csproj"
9-
SetTargetFramework="TargetFramework=netstandard2.0"
10-
ReferenceOutputAssembly="false"
11-
OutputItemType="Analyzer" />
12-
<ProjectReference Include="..\..\src\Thinktecture.Runtime.Extensions.Refactorings\Thinktecture.Runtime.Extensions.Refactorings.csproj"
13-
SetTargetFramework="TargetFramework=netstandard2.0"
14-
ReferenceOutputAssembly="false"
15-
OutputItemType="Analyzer" />
168
</ItemGroup>
179
</Project>

samples/Newtonsoft.Json.AspNetCore.Samples/Newtonsoft.Samples.csproj

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,5 @@
1212
<ProjectReference Include="..\..\src\Thinktecture.Runtime.Extensions.AspNetCore\Thinktecture.Runtime.Extensions.AspNetCore.csproj" />
1313
<ProjectReference Include="..\..\src\Thinktecture.Runtime.Extensions.Newtonsoft.Json\Thinktecture.Runtime.Extensions.Newtonsoft.Json.csproj" />
1414
<ProjectReference Include="..\Basic.Samples\Basic.Samples.csproj" />
15-
<ProjectReference Include="..\..\src\Thinktecture.Runtime.Extensions.SourceGenerator\Thinktecture.Runtime.Extensions.SourceGenerator.csproj"
16-
SetTargetFramework="TargetFramework=netstandard2.0"
17-
ReferenceOutputAssembly="false"
18-
OutputItemType="Analyzer" />
19-
<ProjectReference Include="..\..\src\Thinktecture.Runtime.Extensions.Refactorings\Thinktecture.Runtime.Extensions.Refactorings.csproj"
20-
SetTargetFramework="TargetFramework=netstandard2.0"
21-
ReferenceOutputAssembly="false"
22-
OutputItemType="Analyzer" />
2315
</ItemGroup>
2416
</Project>

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

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ public sealed class ThinktectureRuntimeExtensionsCodeFixProvider : CodeFixProvid
2424
private const string _GENERATE_VALIDATE_METHOD = "Generate Validate method";
2525
private const string _GENERATE_TO_VALUE_METHOD = "Generate ToValue method";
2626
private const string _MAKE_LAMBDA_STATIC = "Make lambdas static";
27+
private const string _ADD_NOTNULL_CONSTRAINT = "Add 'notnull' constraint";
2728

2829
/// <inheritdoc />
2930
public override ImmutableArray<string> FixableDiagnosticIds { get; } =
@@ -47,6 +48,7 @@ public sealed class ThinktectureRuntimeExtensionsCodeFixProvider : CodeFixProvid
4748
DiagnosticsDescriptors.ObjectFactoryMustImplementStaticValidateMethod.Id,
4849
DiagnosticsDescriptors.ObjectFactoryMustImplementToValueMethod.Id,
4950
DiagnosticsDescriptors.UseSwitchMapWithStaticLambda.Id,
51+
DiagnosticsDescriptors.TypeParamRefRequiresNotnullConstraint.Id,
5052
];
5153

5254
/// <inheritdoc />
@@ -163,6 +165,13 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context)
163165
context.RegisterCodeFix(CodeAction.Create(_MAKE_LAMBDA_STATIC, _ => MakeAllLambdasStaticAsync(context.Document, root, invocation), _MAKE_LAMBDA_STATIC), diagnostic);
164166
}
165167
}
168+
else if (diagnostic.Id == DiagnosticsDescriptors.TypeParamRefRequiresNotnullConstraint.Id)
169+
{
170+
if (diagnostic.Properties.TryGetValue("TypeParamName", out var typeParamName) && typeParamName is not null)
171+
{
172+
context.RegisterCodeFix(CodeAction.Create(_ADD_NOTNULL_CONSTRAINT, _ => AddNotnullConstraintAsync(context.Document, root, GetCodeFixesContext().TypeDeclaration, typeParamName), _ADD_NOTNULL_CONSTRAINT), diagnostic);
173+
}
174+
}
166175
}
167176
}
168177

@@ -991,6 +1000,44 @@ private static LambdaExpressionSyntax TransformLambdaForStateOverload(
9911000
return transformedLambda;
9921001
}
9931002

1003+
private static Task<Document> AddNotnullConstraintAsync(
1004+
Document document,
1005+
SyntaxNode root,
1006+
TypeDeclarationSyntax? typeDeclaration,
1007+
string typeParamName)
1008+
{
1009+
if (typeDeclaration is null)
1010+
return Task.FromResult(document);
1011+
1012+
var notnullConstraint = SyntaxFactory.TypeConstraint(SyntaxFactory.IdentifierName("notnull"));
1013+
1014+
var existingClause = typeDeclaration.ConstraintClauses
1015+
.FirstOrDefault(c => c.Name.Identifier.Text == typeParamName);
1016+
1017+
TypeDeclarationSyntax newTypeDeclaration;
1018+
1019+
if (existingClause is not null)
1020+
{
1021+
var newConstraints = existingClause.Constraints.Insert(0, notnullConstraint);
1022+
var newClause = existingClause.WithConstraints(newConstraints);
1023+
newTypeDeclaration = typeDeclaration.ReplaceNode(existingClause, newClause);
1024+
}
1025+
else
1026+
{
1027+
var constraintClause = SyntaxFactory.TypeParameterConstraintClause(
1028+
SyntaxFactory.IdentifierName(typeParamName))
1029+
.WithConstraints(SyntaxFactory.SingletonSeparatedList<TypeParameterConstraintSyntax>(notnullConstraint))
1030+
.WithWhereKeyword(SyntaxFactory.Token(SyntaxKind.WhereKeyword).WithTrailingTrivia(SyntaxFactory.Space))
1031+
.WithColonToken(SyntaxFactory.Token(SyntaxKind.ColonToken).WithTrailingTrivia(SyntaxFactory.Space));
1032+
1033+
newTypeDeclaration = typeDeclaration.AddConstraintClauses(constraintClause);
1034+
}
1035+
1036+
var newRoot = root.ReplaceNode(typeDeclaration, newTypeDeclaration);
1037+
1038+
return Task.FromResult(document.WithSyntaxRoot(newRoot));
1039+
}
1040+
9941041
private sealed class CodeFixesContext(Diagnostic diagnostic, SyntaxNode root)
9951042
{
9961043
public TypeDeclarationSyntax? TypeDeclaration => field ??= GetDeclaration<TypeDeclarationSyntax>();

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

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ public sealed class ThinktectureRuntimeExtensionsAnalyzer : DiagnosticAnalyzer
6363
DiagnosticsDescriptors.AdHocUnionMustHaveAtLeastTwoMemberTypes,
6464
DiagnosticsDescriptors.ComparisonAndEqualityOperatorsMismatch,
6565
DiagnosticsDescriptors.UseSwitchMapWithStaticLambda,
66+
DiagnosticsDescriptors.TypeParamRefRequiresNotnullConstraint,
6667
];
6768

6869
/// <inheritdoc />
@@ -604,6 +605,9 @@ private static void ValidateKeyedValueObject(
604605
if (keyType.TypeKind == TypeKind.Error)
605606
return;
606607

608+
if (ReportIfTypeParamRefMissingNotnullConstraint(context, keyType, type, tdsLocation))
609+
return;
610+
607611
if (keyType.NullableAnnotation == NullableAnnotation.Annotated || keyType.SpecialType == SpecialType.System_Nullable_T)
608612
{
609613
ReportDiagnostic(
@@ -1091,6 +1095,9 @@ private static void ValidateKeyedSmartEnum(
10911095
if (keyType.TypeKind == TypeKind.Error)
10921096
return;
10931097

1098+
if (ReportIfTypeParamRefMissingNotnullConstraint(context, keyType, enumType, tdsLocation))
1099+
return;
1100+
10941101
if (keyType.NullableAnnotation == NullableAnnotation.Annotated || keyType.SpecialType == SpecialType.System_Nullable_T)
10951102
{
10961103
ReportDiagnostic(context, DiagnosticsDescriptors.SmartEnumKeyShouldNotBeNullable, tdsLocation);
@@ -1386,6 +1393,41 @@ private static void ReportDiagnostic(SymbolAnalysisContext context, DiagnosticDe
13861393
context.ReportDiagnostic(Diagnostic.Create(descriptor, location));
13871394
}
13881395

1396+
private static bool ReportIfTypeParamRefMissingNotnullConstraint(
1397+
in SymbolAnalysisContext context,
1398+
ITypeSymbol keyType,
1399+
INamedTypeSymbol type,
1400+
Location tdsLocation)
1401+
{
1402+
var maxTypeParamRefIndex = keyType.GetMaxTypeParamRefIndex();
1403+
1404+
if (maxTypeParamRefIndex <= 0 || type.Arity == 0 || maxTypeParamRefIndex > type.Arity)
1405+
return false;
1406+
1407+
var (resolved, _) = keyType.ResolveTypeParamRefs(type.TypeParameters, context.Compilation);
1408+
1409+
if (resolved is ITypeParameterSymbol { HasNotNullConstraint: false, HasReferenceTypeConstraint: false, HasValueTypeConstraint: false } resolvedTypeParam
1410+
&& !HasNonNullableTypeConstraint(resolvedTypeParam))
1411+
{
1412+
var properties = ImmutableDictionary.Create<string, string?>().Add("TypeParamName", resolvedTypeParam.Name);
1413+
ReportDiagnostic(context, DiagnosticsDescriptors.TypeParamRefRequiresNotnullConstraint, tdsLocation, properties, resolvedTypeParam.Name, type.ToMinimallyQualifiedDisplayString());
1414+
return true;
1415+
}
1416+
1417+
return false;
1418+
}
1419+
1420+
private static bool HasNonNullableTypeConstraint(ITypeParameterSymbol typeParam)
1421+
{
1422+
foreach (var constraintType in typeParam.ConstraintTypes)
1423+
{
1424+
if (constraintType.NullableAnnotation == NullableAnnotation.NotAnnotated)
1425+
return true;
1426+
}
1427+
1428+
return false;
1429+
}
1430+
13891431
private static void ReportDiagnostic(OperationAnalysisContext context, DiagnosticDescriptor descriptor, Location location, string arg0)
13901432
{
13911433
context.ReportDiagnostic(Diagnostic.Create(descriptor, location, arg0));
@@ -1401,6 +1443,11 @@ private static void ReportDiagnostic(in SymbolAnalysisContext context, Diagnosti
14011443
context.ReportDiagnostic(Diagnostic.Create(descriptor, location, arg0, arg1));
14021444
}
14031445

1446+
private static void ReportDiagnostic(in SymbolAnalysisContext context, DiagnosticDescriptor descriptor, Location location, ImmutableDictionary<string, string?> properties, string arg0, string arg1)
1447+
{
1448+
context.ReportDiagnostic(Diagnostic.Create(descriptor, location, properties, arg0, arg1));
1449+
}
1450+
14041451
private static void ReportDiagnostic(SymbolAnalysisContext context, DiagnosticDescriptor descriptor, Location location, string arg0, string arg1, string arg2)
14051452
{
14061453
context.ReportDiagnostic(Diagnostic.Create(descriptor, location, arg0, arg1, arg2));

0 commit comments

Comments
 (0)