<ProjectServicingConfiguration Include="Microsoft.NETCore.App.Ref" PatchVersion="0" />
</ItemGroup>
<PropertyGroup>
- <!-- For source generator support we need to target a pinned version in order to be able to run on older versions of Roslyn -->
- <MicrosoftCodeAnalysisCSharpWorkspacesVersion>3.9.0</MicrosoftCodeAnalysisCSharpWorkspacesVersion>
- <MicrosoftCodeAnalysisVersion>3.9.0</MicrosoftCodeAnalysisVersion>
+ <!-- For source generator support we are targeting the latest version of Roslyn for now, until we can support multi-targeting -->
+ <MicrosoftCodeAnalysisCSharpWorkspacesVersion>4.0.0-3.final</MicrosoftCodeAnalysisCSharpWorkspacesVersion>
+ <MicrosoftCodeAnalysisVersion>4.0.0-3.final</MicrosoftCodeAnalysisVersion>
</PropertyGroup>
<PropertyGroup>
<!-- Code analysis dependencies -->
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "System.Collections.Immutable", "..\System.Collections.Immutable\ref\System.Collections.Immutable.csproj", "{BE27618A-2916-4269-9AD5-6BC5EDC32B30}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Text.Json.SourceGeneration.UnitTests", "tests\System.Text.Json.SourceGeneration.UnitTests\System.Text.Json.SourceGeneration.UnitTests.csproj", "{F6A18EB5-A8CC-4A39-9E85-5FA226019C3D}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Text.Json.SourceGeneration.Unit.Tests", "tests\System.Text.Json.SourceGeneration.Unit.Tests\System.Text.Json.SourceGeneration.Unit.Tests.csproj", "{F6A18EB5-A8CC-4A39-9E85-5FA226019C3D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
defaultSeverity: DiagnosticSeverity.Warning,
isEnabledByDefault: true);
- private readonly GeneratorExecutionContext _executionContext;
+ private readonly SourceProductionContext _sourceProductionContext;
private ContextGenerationSpec _currentContext = null!;
private readonly SourceGenerationSpec _generationSpec = null!;
- public Emitter(in GeneratorExecutionContext executionContext, SourceGenerationSpec generationSpec)
+ private readonly HashSet<string> _emittedPropertyFileNames = new();
+
+ public Emitter(in SourceProductionContext sourceProductionContext, SourceGenerationSpec generationSpec)
{
- _executionContext = executionContext;
+ _sourceProductionContext = sourceProductionContext;
_generationSpec = generationSpec;
}
sb.AppendLine("}");
}
- _executionContext.AddSource(fileName, SourceText.From(sb.ToString(), Encoding.UTF8));
+ _sourceProductionContext.AddSource(fileName, SourceText.From(sb.ToString(), Encoding.UTF8));
}
private void GenerateTypeInfo(TypeGenerationSpec typeGenerationSpec)
break;
case ClassType.TypeUnsupportedBySourceGen:
{
- _executionContext.ReportDiagnostic(
+ _sourceProductionContext.ReportDiagnostic(
Diagnostic.Create(TypeNotSupported, Location.None, new string[] { typeGenerationSpec.TypeRef }));
return;
}
}
}
- try
+ // Don't add a duplicate file, but instead raise a diagnostic to say the duplicate has been skipped.
+ // Workaround https://github.com/dotnet/roslyn/issues/54185 by keeping track of the file names we've used.
+ string propertyFileName = $"{_currentContext.ContextType.Name}.{typeGenerationSpec.TypeInfoPropertyName}.g.cs";
+ if (_emittedPropertyFileNames.Add(propertyFileName))
{
- AddSource($"{_currentContext.ContextType.Name}.{typeGenerationSpec.TypeInfoPropertyName}.g.cs", source);
+ AddSource(propertyFileName, source);
}
- catch (ArgumentException)
+ else
{
- _executionContext.ReportDiagnostic(Diagnostic.Create(DuplicateTypeName, Location.None, new string[] { typeGenerationSpec.TypeInfoPropertyName }));
+ _sourceProductionContext.ReportDiagnostic(Diagnostic.Create(DuplicateTypeName, Location.None, new string[] { typeGenerationSpec.TypeInfoPropertyName }));
}
}
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
+using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
private const string JsonPropertyNameAttributeFullName = "System.Text.Json.Serialization.JsonPropertyNameAttribute";
private const string JsonPropertyOrderAttributeFullName = "System.Text.Json.Serialization.JsonPropertyOrderAttribute";
- private readonly GeneratorExecutionContext _executionContext;
+ private readonly Compilation _compilation;
+ private readonly SourceProductionContext _sourceProductionContext;
private readonly MetadataLoadContextInternal _metadataLoadContext;
private readonly Type _ilistOfTType;
private readonly Type? _dictionaryType;
private readonly Type? _idictionaryOfTKeyTValueType;
private readonly Type? _ireadonlyDictionaryType;
- private readonly Type? _isetType;
+ private readonly Type? _isetType;
private readonly Type? _stackOfTType;
private readonly Type? _queueOfTType;
private readonly Type? _concurrentStackType;
defaultSeverity: DiagnosticSeverity.Error,
isEnabledByDefault: true);
- public Parser(in GeneratorExecutionContext executionContext)
+ public Parser(Compilation compilation, in SourceProductionContext sourceProductionContext)
{
- _executionContext = executionContext;
- _metadataLoadContext = new MetadataLoadContextInternal(executionContext.Compilation);
+ _compilation = compilation;
+ _sourceProductionContext = sourceProductionContext;
+ _metadataLoadContext = new MetadataLoadContextInternal(_compilation);
_ilistOfTType = _metadataLoadContext.Resolve(SpecialType.System_Collections_Generic_IList_T);
_icollectionOfTType = _metadataLoadContext.Resolve(SpecialType.System_Collections_Generic_ICollection_T);
PopulateKnownTypes();
}
- public SourceGenerationSpec? GetGenerationSpec(List<ClassDeclarationSyntax> classDeclarationSyntaxList)
+ public SourceGenerationSpec? GetGenerationSpec(ImmutableArray<ClassDeclarationSyntax> classDeclarationSyntaxList)
{
- Compilation compilation = _executionContext.Compilation;
+ Compilation compilation = _compilation;
INamedTypeSymbol jsonSerializerContextSymbol = compilation.GetTypeByMetadataName("System.Text.Json.Serialization.JsonSerializerContext");
INamedTypeSymbol jsonSerializableAttributeSymbol = compilation.GetTypeByMetadataName("System.Text.Json.Serialization.JsonSerializableAttribute");
INamedTypeSymbol jsonSourceGenerationOptionsAttributeSymbol = compilation.GetTypeByMetadataName("System.Text.Json.Serialization.JsonSourceGenerationOptionsAttribute");
if (!TryGetClassDeclarationList(contextTypeSymbol, out List<string> classDeclarationList))
{
// Class or one of its containing types is not partial so we can't add to it.
- _executionContext.ReportDiagnostic(Diagnostic.Create(ContextClassesMustBePartial, Location.None, new string[] { contextTypeSymbol.Name }));
+ _sourceProductionContext.ReportDiagnostic(Diagnostic.Create(ContextClassesMustBePartial, Location.None, new string[] { contextTypeSymbol.Name }));
continue;
}
return typeGenerationSpec;
}
+ internal static bool IsSyntaxTargetForGeneration(SyntaxNode node) => node is ClassDeclarationSyntax { AttributeLists: { Count: > 0 }, BaseList: { Types : {Count : > 0 } } };
+
+ internal static ClassDeclarationSyntax? GetSemanticTargetForGeneration(GeneratorSyntaxContext context)
+ {
+ var classDeclarationSyntax = (ClassDeclarationSyntax)context.Node;
+
+ foreach (AttributeListSyntax attributeListSyntax in classDeclarationSyntax.AttributeLists)
+ {
+ foreach (AttributeSyntax attributeSyntax in attributeListSyntax.Attributes)
+ {
+ IMethodSymbol attributeSymbol = context.SemanticModel.GetSymbolInfo(attributeSyntax).Symbol as IMethodSymbol;
+ if (attributeSymbol == null)
+ {
+ continue;
+ }
+
+ INamedTypeSymbol attributeContainingTypeSymbol = attributeSymbol.ContainingType;
+ string fullName = attributeContainingTypeSymbol.ToDisplayString();
+
+ if (fullName == "System.Text.Json.Serialization.JsonSerializableAttribute")
+ {
+ return classDeclarationSyntax;
+ }
+ }
+
+ }
+
+ return null;
+ }
+
private static JsonSourceGenerationMode? GetJsonSourceGenerationModeEnumVal(SyntaxNode propertyValueMode)
{
IEnumerable<string> enumTokens = propertyValueMode
if (!type.TryGetDeserializationConstructor(useDefaultCtorInAnnotatedStructs, out ConstructorInfo? constructor))
{
classType = ClassType.TypeUnsupportedBySourceGen;
- _executionContext.ReportDiagnostic(Diagnostic.Create(MultipleJsonConstructorAttribute, Location.None, new string[] { $"{type}" }));
+ _sourceProductionContext.ReportDiagnostic(Diagnostic.Create(MultipleJsonConstructorAttribute, Location.None, new string[] { $"{type}" }));
}
else
{
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+//#define LAUNCH_DEBUGGER
using System.Collections.Generic;
+using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
/// Generates source code to optimize serialization and deserialization with JsonSerializer.
/// </summary>
[Generator]
- public sealed partial class JsonSourceGenerator : ISourceGenerator
+ public sealed partial class JsonSourceGenerator : IIncrementalGenerator
{
- /// <summary>
- /// Registers a syntax resolver to receive compilation units.
- /// </summary>
- /// <param name="context"></param>
- public void Initialize(GeneratorInitializationContext context)
+ public void Initialize(IncrementalGeneratorInitializationContext context)
{
- context.RegisterForSyntaxNotifications(() => new SyntaxReceiver());
+ IncrementalValuesProvider<ClassDeclarationSyntax> classDeclarations = context.SyntaxProvider
+ .CreateSyntaxProvider(static (s, _) => Parser.IsSyntaxTargetForGeneration(s), static (s, _) => Parser.GetSemanticTargetForGeneration(s))
+ .Where(static c => c is not null);
+
+ IncrementalValueProvider<(Compilation, ImmutableArray<ClassDeclarationSyntax>)> compilationAndClasses =
+ context.CompilationProvider.Combine(classDeclarations.Collect());
+
+ context.RegisterSourceOutput(compilationAndClasses, (spc, source) => Execute(source.Item1, source.Item2, spc));
}
- /// <summary>
- /// Generates source code to optimize serialization and deserialization with JsonSerializer.
- /// </summary>
- /// <param name="executionContext"></param>
- public void Execute(GeneratorExecutionContext executionContext)
+ private void Execute(Compilation compilation, ImmutableArray<ClassDeclarationSyntax> contextClasses, SourceProductionContext context)
{
#if LAUNCH_DEBUGGER
if (!Diagnostics.Debugger.IsAttached)
Diagnostics.Debugger.Launch();
}
#endif
- SyntaxReceiver receiver = (SyntaxReceiver)executionContext.SyntaxReceiver;
- List<ClassDeclarationSyntax>? contextClasses = receiver.ClassDeclarationSyntaxList;
- if (contextClasses == null)
+ if (contextClasses.IsDefaultOrEmpty)
{
return;
}
- Parser parser = new(executionContext);
- SourceGenerationSpec? spec = parser.GetGenerationSpec(receiver.ClassDeclarationSyntaxList);
+ Parser parser = new(compilation, context);
+ SourceGenerationSpec? spec = parser.GetGenerationSpec(contextClasses);
if (spec != null)
{
_rootTypes = spec.ContextGenerationSpecList[0].RootSerializableTypes;
- Emitter emitter = new(executionContext, spec);
+ Emitter emitter = new(context, spec);
emitter.Emit();
}
}
- private sealed class SyntaxReceiver : ISyntaxReceiver
- {
- public List<ClassDeclarationSyntax>? ClassDeclarationSyntaxList { get; private set; }
-
- public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
- {
- if (syntaxNode is ClassDeclarationSyntax cds)
- {
- (ClassDeclarationSyntaxList ??= new List<ClassDeclarationSyntax>()).Add(cds);
- }
- }
- }
-
/// <summary>
/// Helper for unit tests.
/// </summary>
using System.Collections.Generic;
using System.Collections.Immutable;
+using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
{
public class CompilationHelper
{
+ private static readonly CSharpParseOptions s_parseOptions =
+ new CSharpParseOptions(kind: SourceCodeKind.Regular, documentationMode: DocumentationMode.Parse)
+ // workaround https://github.com/dotnet/roslyn/pull/55866. We can remove "LangVersion=Preview" when we get a Roslyn build with that change.
+ .WithLanguageVersion(LanguageVersion.Preview);
+
public static Compilation CreateCompilation(
string source,
MetadataReference[] additionalReferences = null,
return CSharpCompilation.Create(
assemblyName,
- syntaxTrees: new[] { CSharpSyntaxTree.ParseText(source) },
+ syntaxTrees: new[] { CSharpSyntaxTree.ParseText(source, s_parseOptions) },
references: references.ToArray(),
options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)
);
}
- private static GeneratorDriver CreateDriver(Compilation compilation, params ISourceGenerator[] generators)
+ private static GeneratorDriver CreateDriver(Compilation compilation, IIncrementalGenerator[] generators)
=> CSharpGeneratorDriver.Create(
- generators: ImmutableArray.Create(generators),
- parseOptions: new CSharpParseOptions(kind: SourceCodeKind.Regular, documentationMode: DocumentationMode.Parse));
+ generators: generators.Select(g => g.AsSourceGenerator()),
+ parseOptions: s_parseOptions);
- public static Compilation RunGenerators(Compilation compilation, out ImmutableArray<Diagnostic> diagnostics, params ISourceGenerator[] generators)
+ public static Compilation RunGenerators(Compilation compilation, out ImmutableArray<Diagnostic> diagnostics, params IIncrementalGenerator[] generators)
{
CreateDriver(compilation, generators).RunGeneratorsAndUpdateCompilation(compilation, out Compilation outCompilation, out diagnostics);
return outCompilation;
Array.Sort(actualMessages);
Array.Sort(expectedMessages);
- Assert.Equal(expectedMessages, actualMessages);
+ if (CultureInfo.CurrentUICulture.Name.StartsWith("en", StringComparison.OrdinalIgnoreCase))
+ {
+ Assert.Equal(expectedMessages, actualMessages);
+ }
+ else
+ {
+ // for non-English runs, just compare the number of messages are the same
+ Assert.Equal(expectedMessages.Length, actualMessages.Length);
+ }
}
}
}
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.CodeAnalysis" Version="$(MicrosoftCodeAnalysisVersion)" PrivateAssets="all" />
+ <PackageReference Include="Microsoft.CodeAnalysis" Version="$(MicrosoftCodeAnalysisVersion)" />
<ProjectReference Include="..\..\src\System.Text.Json.csproj" />
<ProjectReference Include="..\..\gen\System.Text.Json.SourceGeneration.csproj" />
<!-- This OuterLoop test requires browser UI, but the Helix agents are headless -->
<ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Net.WebSockets.Client\tests\wasm\System.Net.WebSockets.Client.Wasm.Tests.csproj" />
+
+ <!-- https://github.com/dotnet/runtime/issues/58226 -->
+ <ProjectExclusions Include="$(MSBuildThisFileDirectory)System.Text.Json\tests\System.Text.Json.SourceGeneration.Unit.Tests\System.Text.Json.SourceGeneration.Unit.Tests.csproj" />
</ItemGroup>
<ItemGroup Condition="'$(TargetOS)' == 'Browser' and '$(BuildAOTTestsOnHelix)' == 'true' and '$(RunDisabledWasmTests)' != 'true'">