Fix the closed generic struct crash
The source generator crashed on generic structs from other assemblies and silently dropped the resolver, so NativeAOT apps failed at runtime.
Loading the stored receiptsilvekoski/junction26-messagepack-csharp#1 · check trust-receipt
You read 25 of 228 changed lines. Tests and probes cover the rest.
src/MessagePack.SourceGenerator/CodeAnalysis/TypeCollector.csHuman reads
@@ -236,7 +236,7 @@ public class TypeCollector
236 private TypeCollector(Compilation compilation, AnalyzerOptions options, ReferenceSymbols referenceSymbols, ITypeSymbol targetType, Action<Diagnostic>? reportAnalyzerDiagnostic, CancellationToken cancellationToken)
237 {
238 this.typeReferences = referenceSymbols;
239− this.reportDiagnostic = reportAnalyzerDiagnostic;
Human reads
Wraps every analyzer diagnostic in a filter. The blast radius is all MsgPack00x rules.
239+ this.reportDiagnostic = reportAnalyzerDiagnostic is null ? null : diagnostic => ReportIfLocatedInCompilation(reportAnalyzerDiagnostic, diagnostic);
240 this.options = options;
241 this.compilation = compilation;
242 this.cancellationToken = cancellationToken;
@@ -982,7 +982,7 @@ public class TypeCollector
982 var stringKey = key is { Value: string stringKeyValue } ? stringKeyValue : default;
983 if (intKey == null && stringKey == null)
984 {
985− this.reportDiagnostic?.Invoke(Diagnostic.Create(MsgPack00xMessagePackAnalyzer.BothStringAndIntKeyAreNull, ((PropertyDeclarationSyntax)item.DeclaringSyntaxReferences[0].GetSyntax()).Identifier.GetLocation(), formattedType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), item.Name));
Check evidence
The crash fix. The red-green run proves it. Check that the tests exercise it.
985+ this.reportDiagnostic?.Invoke(Diagnostic.Create(MsgPack00xMessagePackAnalyzer.BothStringAndIntKeyAreNull, GetPropertyIdentifierLocation(item), formattedType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), item.Name));
986 }
987
988 // A property with an init accessor and an initializer has a default that will be discarded by the deserializer.
@@ -1074,7 +1074,7 @@ public class TypeCollector
1074 {
1075 Diagnostic diagnostic = derivedKeyAttribute is not null && suppressSourceGeneration is not true
1076 ? Diagnostic.Create(MsgPack00xMessagePackAnalyzer.AOTDerivedKeyAttribute, derivedKeyAttribute.GetSyntax(this.cancellationToken).GetLocation())
1077− : Diagnostic.Create(MsgPack00xMessagePackAnalyzer.MemberNeedsKey, item.DeclaringSyntaxReferences[0].GetSyntax().GetLocation(), formattedType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), item.Name);
Check evidence
The same pattern as line 985, made safe for metadata.
1077+ : Diagnostic.Create(MsgPack00xMessagePackAnalyzer.MemberNeedsKey, item.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax().GetLocation(), formattedType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat), item.Name);
1078 if (nonPublicMembersAreSerialized || (item.DeclaredAccessibility & Accessibility.Public) == Accessibility.Public)
1079 {
1080 this.reportDiagnostic?.Invoke(diagnostic);
@@ -1426,6 +1426,29 @@ public class TypeCollector
1426
1427 private static Location? GetIdentifierLocation(INamedTypeSymbol type) => ((BaseTypeDeclarationSyntax?)type.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax())?.Identifier.GetLocation();
1428
Human reads
The filter itself. One line decides which diagnostics a user never sees. Read the if statement only.
1429+ /// <summary>
1430+ /// Gets the location of the identifier of a property, if it is declared in source.
1431+ /// </summary>
1432+ /// <param name="property">The property symbol. It may be declared in metadata, in which case <see langword="null" /> is returned.</param>
1433+ /// <returns>The location of the identifier, or <see langword="null" /> if the property has no declaration in source.</returns>
1434+ private static Location? GetPropertyIdentifierLocation(IPropertySymbol property) => (property.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax() as PropertyDeclarationSyntax)?.Identifier.GetLocation();
1435+
1436+ /// <summary>
1437+ /// Reports a diagnostic only when its location belongs to the compilation being analyzed.
1438+ /// </summary>
1439+ /// <remarks>
1440+ /// Types and members that come from metadata (or from another compilation) have no location in the compilation being analyzed.
1441+ /// Their problems are reported by the compilation that defines them, so they are dropped here rather than being reported
1442+ /// against a location that does not exist, which analyzers are not permitted to do.
1443+ /// </remarks>
1444+ private void ReportIfLocatedInCompilation(Action<Diagnostic> report, Diagnostic diagnostic)
1445+ {
1446+ if (diagnostic.Location.IsInSource && this.compilation.ContainsSyntaxTree(diagnostic.Location.SourceTree))
1447+ {
1448+ report(diagnostic);
1449+ }
1450+ }
1451+
1452 private static Location? GetLocation(IParameterSymbol parameter) => parameter.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax().GetLocation();
1453
1454 private static Location? GetParameterListLocation(IMethodSymbol? method) => ((BaseMethodDeclarationSyntax?)method?.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax())?.ParameterList.GetLocation();