Skip to content

Commit 8805868

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

File tree

105 files changed

+6028
-109
lines changed

Some content is hidden

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

105 files changed

+6028
-109
lines changed

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)