@@ -116,6 +116,14 @@ private sealed class Parser
116
116
117
117
private readonly HashSet < TypeGenerationSpec > _implicitlyRegisteredTypes = new ( ) ;
118
118
119
+ /// <summary>
120
+ /// A list of diagnostics emitted at the type level. This is cached and emitted between the processing of context types
121
+ /// in order to properly retrieve [JsonSerializable] attribute applications for top-level types (since a type might occur
122
+ /// both at top-level and in nested object graphs, with no guarantee of the order that we will see the type).
123
+ /// The element tuple types specifies the serializable type, the kind of diagnostic to emit, and the diagnostic message.
124
+ /// </summary>
125
+ private readonly List < ( Type , DiagnosticDescriptor , string [ ] ) > _typeLevelDiagnostics = new ( ) ;
126
+
119
127
private JsonKnownNamingPolicy _currentContextNamingPolicy ;
120
128
121
129
private static DiagnosticDescriptor ContextClassesMustBePartial { get ; } = new DiagnosticDescriptor (
@@ -244,6 +252,11 @@ public Parser(Compilation compilation, in JsonSourceGenerationContext sourceGene
244
252
245
253
foreach ( ClassDeclarationSyntax classDeclarationSyntax in classDeclarationSyntaxList )
246
254
{
255
+ // Ensure context-scoped metadata caches are empty.
256
+ Debug . Assert ( _typeGenerationSpecCache . Count == 0 ) ;
257
+ Debug . Assert ( _implicitlyRegisteredTypes . Count == 0 ) ;
258
+ Debug . Assert ( _typeLevelDiagnostics . Count == 0 ) ;
259
+
247
260
CompilationUnitSyntax compilationUnitSyntax = classDeclarationSyntax . FirstAncestorOrSelf < CompilationUnitSyntax > ( ) ;
248
261
SemanticModel compilationSemanticModel = compilation . GetSemanticModel ( compilationUnitSyntax . SyntaxTree ) ;
249
262
@@ -285,15 +298,18 @@ public Parser(Compilation compilation, in JsonSourceGenerationContext sourceGene
285
298
INamedTypeSymbol contextTypeSymbol = ( INamedTypeSymbol ) compilationSemanticModel . GetDeclaredSymbol ( classDeclarationSyntax ) ;
286
299
Debug . Assert ( contextTypeSymbol != null ) ;
287
300
301
+ Location contextLocation = contextTypeSymbol . Locations . Length > 0 ? contextTypeSymbol . Locations [ 0 ] : Location . None ;
302
+
288
303
if ( ! TryGetClassDeclarationList ( contextTypeSymbol , out List < string > classDeclarationList ) )
289
304
{
290
305
// Class or one of its containing types is not partial so we can't add to it.
291
- _sourceGenerationContext . ReportDiagnostic ( Diagnostic . Create ( ContextClassesMustBePartial , Location . None , new string [ ] { contextTypeSymbol . Name } ) ) ;
306
+ _sourceGenerationContext . ReportDiagnostic ( Diagnostic . Create ( ContextClassesMustBePartial , contextLocation , new string [ ] { contextTypeSymbol . Name } ) ) ;
292
307
continue ;
293
308
}
294
309
295
310
ContextGenerationSpec contextGenSpec = new ( )
296
311
{
312
+ Location = contextLocation ,
297
313
GenerationOptions = options ?? new JsonSourceGenerationOptionsAttribute ( ) ,
298
314
ContextType = contextTypeSymbol . AsType ( _metadataLoadContext ) ,
299
315
ContextClassDeclarationList = classDeclarationList
@@ -316,14 +332,31 @@ public Parser(Compilation compilation, in JsonSourceGenerationContext sourceGene
316
332
continue ;
317
333
}
318
334
335
+ // Emit type-level diagnostics
336
+ foreach ( ( Type Type , DiagnosticDescriptor Descriptor , string [ ] MessageArgs ) diagnostic in _typeLevelDiagnostics )
337
+ {
338
+ Type type = diagnostic . Type ;
339
+ Location location = type . GetDiagnosticLocation ( ) ;
340
+
341
+ if ( location == null )
342
+ {
343
+ TypeGenerationSpec spec = _typeGenerationSpecCache [ type ] ;
344
+ location = spec . AttributeLocation ;
345
+ }
346
+
347
+ location ??= contextLocation ;
348
+ _sourceGenerationContext . ReportDiagnostic ( Diagnostic . Create ( diagnostic . Descriptor , location , diagnostic . MessageArgs ) ) ;
349
+ }
350
+
319
351
contextGenSpec . ImplicitlyRegisteredTypes . UnionWith ( _implicitlyRegisteredTypes ) ;
320
352
321
353
contextGenSpecList ??= new List < ContextGenerationSpec > ( ) ;
322
354
contextGenSpecList . Add ( contextGenSpec ) ;
323
355
324
- // Clear the cache of generated metadata between the processing of context classes.
356
+ // Clear the caches of generated metadata between the processing of context classes.
325
357
_typeGenerationSpecCache . Clear ( ) ;
326
358
_implicitlyRegisteredTypes . Clear ( ) ;
359
+ _typeLevelDiagnostics . Clear ( ) ;
327
360
}
328
361
329
362
if ( contextGenSpecList == null )
@@ -487,6 +520,8 @@ private static bool TryGetClassDeclarationList(INamedTypeSymbol typeSymbol, [Not
487
520
typeGenerationSpec . GenerationMode = generationMode ;
488
521
}
489
522
523
+ typeGenerationSpec . AttributeLocation = attributeSyntax . GetLocation ( ) ;
524
+
490
525
return typeGenerationSpec ;
491
526
}
492
527
@@ -877,7 +912,7 @@ private TypeGenerationSpec GetOrAddTypeGenerationSpec(Type type, JsonSourceGener
877
912
if ( ! type . TryGetDeserializationConstructor ( useDefaultCtorInAnnotatedStructs , out ConstructorInfo ? constructor ) )
878
913
{
879
914
classType = ClassType . TypeUnsupportedBySourceGen ;
880
- _sourceGenerationContext . ReportDiagnostic ( Diagnostic . Create ( MultipleJsonConstructorAttribute , Location . None , new string [ ] { $ "{ type } " } ) ) ;
915
+ _typeLevelDiagnostics . Add ( ( type , MultipleJsonConstructorAttribute , new string [ ] { $ "{ type } " } ) ) ;
881
916
}
882
917
else
883
918
{
@@ -944,7 +979,7 @@ private TypeGenerationSpec GetOrAddTypeGenerationSpec(Type type, JsonSourceGener
944
979
}
945
980
946
981
spec = GetPropertyGenerationSpec ( propertyInfo , isVirtual , generationMode ) ;
947
- CacheMemberHelper ( ) ;
982
+ CacheMemberHelper ( propertyInfo . GetDiagnosticLocation ( ) ) ;
948
983
}
949
984
950
985
foreach ( FieldInfo fieldInfo in currentType . GetFields ( bindingFlags ) )
@@ -955,10 +990,10 @@ private TypeGenerationSpec GetOrAddTypeGenerationSpec(Type type, JsonSourceGener
955
990
}
956
991
957
992
spec = GetPropertyGenerationSpec ( fieldInfo , isVirtual : false , generationMode ) ;
958
- CacheMemberHelper ( ) ;
993
+ CacheMemberHelper ( fieldInfo . GetDiagnosticLocation ( ) ) ;
959
994
}
960
995
961
- void CacheMemberHelper ( )
996
+ void CacheMemberHelper ( Location memberLocation )
962
997
{
963
998
CacheMember ( spec , ref propGenSpecList , ref ignoredMembers ) ;
964
999
@@ -972,13 +1007,13 @@ void CacheMemberHelper()
972
1007
{
973
1008
if ( dataExtensionPropGenSpec != null )
974
1009
{
975
- _sourceGenerationContext . ReportDiagnostic ( Diagnostic . Create ( MultipleJsonExtensionDataAttribute , Location . None , new string [ ] { type . Name } ) ) ;
1010
+ _typeLevelDiagnostics . Add ( ( type , MultipleJsonExtensionDataAttribute , new string [ ] { type . Name } ) ) ;
976
1011
}
977
1012
978
1013
Type propType = spec . TypeGenerationSpec . Type ;
979
1014
if ( ! IsValidDataExtensionPropertyType ( propType ) )
980
1015
{
981
- _sourceGenerationContext . ReportDiagnostic ( Diagnostic . Create ( DataExtensionPropertyInvalid , Location . None , new string [ ] { type . Name , spec . ClrName } ) ) ;
1016
+ _sourceGenerationContext . ReportDiagnostic ( Diagnostic . Create ( DataExtensionPropertyInvalid , memberLocation , new string [ ] { type . Name , spec . ClrName } ) ) ;
982
1017
}
983
1018
984
1019
dataExtensionPropGenSpec = GetOrAddTypeGenerationSpec ( propType , generationMode ) ;
@@ -987,13 +1022,13 @@ void CacheMemberHelper()
987
1022
988
1023
if ( ! hasInitOnlyProperties && spec . CanUseSetter && spec . IsInitOnlySetter )
989
1024
{
990
- _sourceGenerationContext . ReportDiagnostic ( Diagnostic . Create ( InitOnlyPropertyDeserializationNotSupported , Location . None , new string [ ] { type . Name } ) ) ;
1025
+ _sourceGenerationContext . ReportDiagnostic ( Diagnostic . Create ( InitOnlyPropertyDeserializationNotSupported , memberLocation , new string [ ] { type . Name } ) ) ;
991
1026
hasInitOnlyProperties = true ;
992
1027
}
993
1028
994
1029
if ( spec . HasJsonInclude && ( ! spec . CanUseGetter || ! spec . CanUseSetter || ! spec . IsPublic ) )
995
1030
{
996
- _sourceGenerationContext . ReportDiagnostic ( Diagnostic . Create ( InaccessibleJsonIncludePropertiesNotSupported , Location . None , new string [ ] { type . Name , spec . ClrName } ) ) ;
1031
+ _sourceGenerationContext . ReportDiagnostic ( Diagnostic . Create ( InaccessibleJsonIncludePropertiesNotSupported , memberLocation , new string [ ] { type . Name , spec . ClrName } ) ) ;
997
1032
}
998
1033
}
999
1034
}
0 commit comments