Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add preliminary work for type-based src generators #12303

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Compilers/CSharp/Portable/CSharpCodeAnalysis.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@
<Compile Include="Compilation\AttributeSemanticModel.cs" />
<Compile Include="Compilation\AwaitExpressionInfo.cs" />
<Compile Include="Compilation\BuiltInOperators.cs" />
<Compile Include="Compilation\CSharpCompilation.SourceGenerator.cs" />
<Compile Include="Compilation\CSharpScriptCompilationInfo.cs" />
<Compile Include="Compilation\SyntaxAndDeclarationManager.cs" />
<Compile Include="Compilation\SyntaxAndDeclarationManager.LazyState.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using Microsoft.CodeAnalysis.CSharp.Syntax;
using System;
using System.Collections.Immutable;
using System.Linq;

namespace Microsoft.CodeAnalysis.CSharp
{
public sealed partial class CSharpCompilation
{
private ImmutableArray<SyntaxNode> GetTypesWithAttributeName(string attributeName)
{
var typeDecls = ArrayBuilder<TypeDeclarationSyntax>.GetInstance();
// Only descend into nodes that can contain type delcarations
Func<SyntaxNode, bool> shouldDescend = node => node is CompilationUnitSyntax ||
node is NamespaceDeclarationSyntax ||
node is TypeDeclarationSyntax;

foreach (var tree in SyntaxTrees)
{
var nodes = tree.GetRoot().DescendantNodes(shouldDescend);
foreach (var node in nodes)
{
var typeDecl = node as TypeDeclarationSyntax;
if (typeDecl != null)
{
var attrs = typeDecl.AttributeLists.SelectMany(list => list.Attributes);
foreach (var attr in attrs)
{
if (attr.Name.GetUnqualifiedName().ToString() == attributeName)
{
typeDecls.Add(typeDecl);
}
}
}
}
}

return ImmutableArray<SyntaxNode>.CastUp(typeDecls.ToImmutableOrEmptyAndFree());
}

internal override SourceGeneratorTypeContext GetSourceGeneratorTypeContext(
ArrayBuilder<SyntaxTree> builder,
string attributeName,
string path,
bool writeToDisk)
{
var matchingTypes = GetTypesWithAttributeName(attributeName);
var parseOptions = CommonParseOptions(matchingTypes);

return new Context(builder,
matchingTypes,
this,
path,
parseOptions,
writeToDisk);
}

private sealed class Context : SourceGeneratorTypeContext
{
public Context(ArrayBuilder<SyntaxTree> builder,
ImmutableArray<SyntaxNode> matchingTypes,
Compilation compilation,
string path,
ParseOptions parseOptions,
bool writeToDisk)
: base(builder,
matchingTypes,
compilation,
path,
parseOptions,
writeToDisk) { }

internal override SyntaxTree CreateSyntaxTree(string source,
ParseOptions options,
string path) =>
SyntaxFactory.ParseSyntaxTree(source, options, path);
}
}
}
36 changes: 36 additions & 0 deletions src/Compilers/Core/CodeAnalysisTest/SourceGeneratorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,42 @@ namespace Microsoft.CodeAnalysis.UnitTests
[CompilerTrait(CompilerFeature.SourceGenerators)]
public class SourceGeneratorTests : TestBase
{
private sealed class TypeBasedSourceGenerator : ITypeBasedSourceGenerator
{
private readonly Action<SourceGeneratorTypeContext> _execute;
public TypeBasedSourceGenerator(Action<SourceGeneratorTypeContext> execute)
{
_execute = execute;
}

public void Execute(SourceGeneratorTypeContext context) => _execute(context);
}

[Fact]
public void TrivialTypeBasedGenerator()
{
var comp = CSharpCompilation.Create(GetUniqueName(),
new[] { CSharpSyntaxTree.ParseText(@"[DoNothingGenerator]class C {}") },
new[] { MscorlibRef });
const string generatedSrc = @"
partial class C
{
public void DoNothing() {}
}";
var generator = new TypeBasedSourceGenerator(ctx =>
{
ctx.AddCompilationUnit("C.DoNothing", generatedSrc);
});

var dir = Temp.CreateDirectory();
var generators = ImmutableArray.Create<ITypeBasedSourceGenerator>(generator);
comp.GenerateSource(generators, "DoNothingGenerator", dir.Path, writeToDisk: true);

var generatedPath = Path.Combine(dir.Path, "C.DoNothing.cs");
Assert.True(File.Exists(generatedPath));
Assert.Equal(generatedSrc, File.ReadAllText(generatedPath));
}

[Fact]
public void NoGenerators()
{
Expand Down
7 changes: 5 additions & 2 deletions src/Compilers/Core/Portable/CodeAnalysis.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,6 @@
<Compile Include="PEWriter\NativeResourceWriter.cs" />
<Compile Include="RealParser.cs" />
<Compile Include="ReferenceManager\MergedAliases.cs" />
<Compile Include="SourceGenerator\SourceGeneratorExtensions.cs" />
<Compile Include="StrongName\CryptoBlobParser.cs" />
<Compile Include="Symbols\ISourceAssemblySymbol.cs" />
<Compile Include="Symbols\IAssemblySymbolInternal.cs" />
Expand Down Expand Up @@ -533,6 +532,10 @@
<Compile Include="Serialization\SimpleRecordingObjectBinder.cs" />
<Compile Include="SignatureComparer.cs" />
<Compile Include="SourceCodeKind.cs" />
<Compile Include="SourceGenerator\Compilation.SourceGenerator.cs" />
<Compile Include="SourceGenerator\ITypeBasedSourceGenerator.cs" />
<Compile Include="SourceGenerator\SourceGeneratorExtensions.cs" />
<Compile Include="SourceGenerator\SourceGeneratorTypeContext.cs" />
<Compile Include="SourceGenerator\SourceGenerator.cs" />
<Compile Include="SourceGenerator\SourceGeneratorAttribute.cs" />
<Compile Include="SourceGenerator\SourceGeneratorContext.cs" />
Expand Down Expand Up @@ -799,4 +802,4 @@
<ImportGroup Label="Targets">
<Import Project="..\..\..\..\build\Targets\VSL.Imports.targets" />
</ImportGroup>
</Project>
</Project>
8 changes: 8 additions & 0 deletions src/Compilers/Core/Portable/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
Microsoft.CodeAnalysis.Compilation.CreateTupleTypeSymbol(Microsoft.CodeAnalysis.INamedTypeSymbol underlyingType, System.Collections.Immutable.ImmutableArray<string> elementNames = default(System.Collections.Immutable.ImmutableArray<string>)) -> Microsoft.CodeAnalysis.INamedTypeSymbol
Microsoft.CodeAnalysis.Compilation.CreateTupleTypeSymbol(System.Collections.Immutable.ImmutableArray<Microsoft.CodeAnalysis.ITypeSymbol> elementTypes, System.Collections.Immutable.ImmutableArray<string> elementNames = default(System.Collections.Immutable.ImmutableArray<string>)) -> Microsoft.CodeAnalysis.INamedTypeSymbol
Microsoft.CodeAnalysis.Compilation.GenerateSource(System.Collections.Immutable.ImmutableArray<Microsoft.CodeAnalysis.ITypeBasedSourceGenerator> generators, string attributeName, string path, bool writeToDisk) -> System.Collections.Immutable.ImmutableArray<Microsoft.CodeAnalysis.SyntaxTree>
Microsoft.CodeAnalysis.Diagnostics.AnalysisContext.RegisterOperationAction(System.Action<Microsoft.CodeAnalysis.Diagnostics.OperationAnalysisContext> action, params Microsoft.CodeAnalysis.OperationKind[] operationKinds) -> void
Microsoft.CodeAnalysis.Diagnostics.AnalysisResult
Microsoft.CodeAnalysis.Diagnostics.AnalysisResult.AnalyzerTelemetryInfo.get -> System.Collections.Immutable.ImmutableDictionary<Microsoft.CodeAnalysis.Diagnostics.DiagnosticAnalyzer, Microsoft.CodeAnalysis.Diagnostics.Telemetry.AnalyzerTelemetryInfo>
Expand Down Expand Up @@ -55,6 +56,8 @@ Microsoft.CodeAnalysis.IOperation.Type.get -> Microsoft.CodeAnalysis.ITypeSymbol
Microsoft.CodeAnalysis.IPropertySymbol.ReturnsByRef.get -> bool
Microsoft.CodeAnalysis.ISourceAssemblySymbol
Microsoft.CodeAnalysis.ISourceAssemblySymbol.Compilation.get -> Microsoft.CodeAnalysis.Compilation
Microsoft.CodeAnalysis.ITypeBasedSourceGenerator
Microsoft.CodeAnalysis.ITypeBasedSourceGenerator.Execute(Microsoft.CodeAnalysis.SourceGeneratorTypeContext context) -> void
Microsoft.CodeAnalysis.ITypeSymbol.IsTupleType.get -> bool
Microsoft.CodeAnalysis.MethodKind.LocalFunction = 17 -> Microsoft.CodeAnalysis.MethodKind
Microsoft.CodeAnalysis.ModuleMetadata.GetMetadataReader() -> System.Reflection.Metadata.MetadataReader
Expand Down Expand Up @@ -679,6 +682,11 @@ Microsoft.CodeAnalysis.SourceGeneratorAttribute.SourceGeneratorAttribute(string
Microsoft.CodeAnalysis.SourceGeneratorContext
Microsoft.CodeAnalysis.SourceGeneratorContext.SourceGeneratorContext() -> void
Microsoft.CodeAnalysis.SourceGeneratorExtensions
Microsoft.CodeAnalysis.SourceGeneratorTypeContext
Microsoft.CodeAnalysis.SourceGeneratorTypeContext.AddCompilationUnit(string name, string source) -> void
Microsoft.CodeAnalysis.SourceGeneratorTypeContext.Compilation.get -> Microsoft.CodeAnalysis.Compilation
Microsoft.CodeAnalysis.SourceGeneratorTypeContext.MatchingTypes.get -> System.Collections.Immutable.ImmutableArray<Microsoft.CodeAnalysis.SyntaxNode>
Microsoft.CodeAnalysis.SourceGeneratorTypeContext.ReportDiagnostic(Microsoft.CodeAnalysis.Diagnostic diagnostic) -> void
abstract Microsoft.CodeAnalysis.Diagnostics.AnalyzerReference.GetSourceGenerators(string language) -> System.Collections.Immutable.ImmutableArray<Microsoft.CodeAnalysis.SourceGenerator>
abstract Microsoft.CodeAnalysis.Diagnostics.OperationBlockStartAnalysisContext.RegisterOperationAction(System.Action<Microsoft.CodeAnalysis.Diagnostics.OperationAnalysisContext> action, System.Collections.Immutable.ImmutableArray<Microsoft.CodeAnalysis.OperationKind> operationKinds) -> void
abstract Microsoft.CodeAnalysis.Diagnostics.OperationBlockStartAnalysisContext.RegisterOperationBlockEndAction(System.Action<Microsoft.CodeAnalysis.Diagnostics.OperationBlockAnalysisContext> action) -> void
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Immutable;
using System.Diagnostics;

namespace Microsoft.CodeAnalysis
{
public abstract partial class Compilation
{
internal abstract SourceGeneratorTypeContext GetSourceGeneratorTypeContext(
ArrayBuilder<SyntaxTree> builder,
string attributeName,
string path,
bool writeToDisk);

public ImmutableArray<SyntaxTree> GenerateSource(
ImmutableArray<ITypeBasedSourceGenerator> generators,
string attributeName,
string path,
bool writeToDisk)
{
if (generators.IsDefault)
{
throw new ArgumentException(nameof(generators));
}

if (path == null)
{
throw new ArgumentNullException(nameof(path));
}

var builder = ArrayBuilder<SyntaxTree>.GetInstance();
var context = GetSourceGeneratorTypeContext(builder,
attributeName,
path,
writeToDisk);
foreach (var generator in generators)
{
generator.Execute(context);
}
return builder.ToImmutableAndFree();
}

internal static ParseOptions CommonParseOptions(ImmutableArray<SyntaxNode> matchingTypes)
{
Debug.Assert(!matchingTypes.IsDefaultOrEmpty);

ParseOptions common = null;
foreach (var type in matchingTypes)
{
if (common == null)
{
common = type.SyntaxTree.Options;
}
else if (common != type.SyntaxTree.Options)
{
// TODO: Report diagnostic
}
}
return common;
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

namespace Microsoft.CodeAnalysis
{
public interface ITypeBasedSourceGenerator
{
void Execute(SourceGeneratorTypeContext context);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using Microsoft.CodeAnalysis.Collections;
using Roslyn.Utilities;
using System;
using System.Collections.Immutable;
using System.Text;

namespace Microsoft.CodeAnalysis
{
public abstract class SourceGeneratorTypeContext
{
private readonly ArrayBuilder<SyntaxTree> _builder;
private readonly string _path;
private readonly bool _writeToDisk;
private readonly ParseOptions _parseOptions;

internal SourceGeneratorTypeContext(ArrayBuilder<SyntaxTree> builder,
ImmutableArray<SyntaxNode> matchingTypes,
Compilation compilation,
string path,
ParseOptions parseOptions,
bool writeToDisk)
{
_builder = builder;
MatchingTypes = matchingTypes;
Compilation = compilation;
_path = path;
_parseOptions = parseOptions;
_writeToDisk = writeToDisk;
}

internal abstract SyntaxTree CreateSyntaxTree(string source, ParseOptions options, string path);

public Compilation Compilation { get; }

public ImmutableArray<SyntaxNode> MatchingTypes { get; }

public void ReportDiagnostic(Diagnostic diagnostic)
{
throw new NotImplementedException();
}

/// <summary>
/// Add the generated source.
/// </summary>
/// <param name="name">
/// Name of the generated source. This name must be unique across
/// all source generated from this <see cref="ITypeBasedSourceGenerator"/> and
/// <see cref="CodeAnalysis.Compilation"/>. If the host persists the source to disk,
/// the file will have this name, with a location determined by the host.
/// (<see cref="SyntaxTree.FilePath"/> is ignored.)
/// </param>
/// <param name="source">Generated source.</param>
public void AddCompilationUnit(string name, string source)
{
var ext = (Compilation.Language == LanguageNames.VisualBasic) ? ".vb" : ".cs";
var fileName = $"{FixUpName(name)}{ext}";
var path = PathUtilities.CombinePossiblyRelativeAndRelativePaths(_path, fileName);

if (_writeToDisk)
{
PortableShim.File.WriteAllText(path, source, Encoding.UTF8);
}

var tree = CreateSyntaxTree(source, _parseOptions, path);
_builder.Add(tree);
}

// Remove any characters from name other than [0-9a-zA-Z._]
// so the name can be used as a file name. It's possible
// the resulting name is the empty string.
private static string FixUpName(string name)
{
var pooledBuilder = PooledStringBuilder.GetInstance();
var builder = pooledBuilder.Builder;
foreach (var c in name)
{
if (char.IsLetterOrDigit(c) || c == '.' || c == '_')
{
builder.Append(c);
}
}
return pooledBuilder.ToStringAndFree();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -943,6 +943,7 @@
<Compile Include="Compilation\ModuleCompilationState.vb" />
<PublicAPI Include="PublicAPI.Shipped.txt" />
<PublicAPI Include="PublicAPI.Unshipped.txt" />
<Compile Include="Compilation\VisualBasicCompilation.SourceGenerator.vb" />
<Content Include="Symbols\SymbolsAndNoPia.docx" />
<ErrorCode Include="Errors\Errors.vb" />
<SyntaxDefinition Include="Syntax\Syntax.xml">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

Namespace Microsoft.CodeAnalysis.VisualBasic

Partial Public NotInheritable Class VisualBasicCompilation

Friend Overrides Function GetSourceGeneratorTypeContext(builder As ArrayBuilder(Of SyntaxTree),
attributeName As String,
path As String,
writeToDisk As Boolean) As SourceGeneratorTypeContext
Throw New NotImplementedException()
End Function

End Class

End Namespace
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
''' compilation from scratch, as the new compilation can share information from the old
''' compilation.
''' </summary>
Public NotInheritable Class VisualBasicCompilation
Partial Public NotInheritable Class VisualBasicCompilation
Inherits Compilation

' !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
Expand Down