Awaiting sign-off

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.

Coding agentju26-issue-2283Issue #2283

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();