Skip to content

Commit 6af9ad1

Browse files
committed
Add EmbeddedAttribute API for source generators
Adds a new API to ensure that Microsoft.CodeAnalysis.EmbeddedAttribute is available in the compilation, and updates the cookbook to include information about using the attribute.
1 parent a5f84af commit 6af9ad1

File tree

9 files changed

+209
-10
lines changed

9 files changed

+209
-10
lines changed

docs/features/incremental-generators.cookbook.md

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,17 @@ wrapper around `StringBuilder` that will keep track of indent level and prepend
124124
[this](https://github.com/dotnet/roslyn/issues/52914#issuecomment-1732680995) conversation on the performance of `NormalizeWhitespace` for more examples, performance
125125
measurements, and discussion on why we don't believe that `SyntaxNode`s are a good abstraction for this use case.
126126

127+
### Put `Microsoft.CodeAnalysis.EmbeddedAttribute` on generated marker types
128+
129+
Users might depend on your generator in multiple projects in the same solution, and these projects will often have `InternalsVisibleTo` applied. This means that your
130+
`internal` marker attributes may be defined in multiple projects, and the compiler will warn about this. While this doesn't block compilation, it can be irritating to
131+
users. To avoid this, mark such attributes with `Microsoft.CodeAnalysis.EmbeddedAttribute`; when the compiler sees this attribute on a type from separate assembly or
132+
project, it will not include that type in lookup results. To ensure that `Microsoft.CodeAnalysis.EmbeddedAttribute` is available in the compilation, call the
133+
`AddEmbeddedAttributeDefinition` helper method in your `RegisterPostInitializationOutput` callback.
134+
135+
Another option is to provide an assembly in your nuget package that defines your marker attributes, but this can be more difficult to author. We recommend the
136+
`EmbeddedAttribute` approach, unless you need to support versions of Roslyn lower than 4.13 (PROTOTYPE, determine when this PR is ready to merge).
137+
127138
## Designs
128139

129140
This section is broken down by user scenarios, with general solutions listed first, and more specific examples later on.
@@ -157,11 +168,14 @@ public class CustomGenerator : IIncrementalGenerator
157168
public void Initialize(IncrementalGeneratorInitializationContext context)
158169
{
159170
context.RegisterPostInitializationOutput(static postInitializationContext => {
171+
postInitializationContext.AddEmbeddedAttributeDefinition();
160172
postInitializationContext.AddSource("myGeneratedFile.cs", SourceText.From("""
161173
using System;
174+
using Microsoft.CodeAnalysis;
162175
163176
namespace GeneratedNamespace
164177
{
178+
[Embedded]
165179
internal sealed class GeneratedAttribute : Attribute
166180
{
167181
}
@@ -235,11 +249,13 @@ public class AugmentingGenerator : IIncrementalGenerator
235249
public void Initialize(IncrementalGeneratorInitializationContext context)
236250
{
237251
context.RegisterPostInitializationOutput(static postInitializationContext =>
252+
postInitializationContext.AddEmbeddedAttributeDefinition();
238253
postInitializationContext.AddSource("myGeneratedFile.cs", SourceText.From("""
239254
using System;
255+
using Microsoft.CodeAnalysis;
240256
namespace GeneratedNamespace
241257
{
242-
[AttributeUsage(AttributeTargets.Method)]
258+
[AttributeUsage(AttributeTargets.Method), Embedded]
243259
internal sealed class GeneratedAttribute : Attribute
244260
{
245261
}
@@ -677,14 +693,16 @@ public class AutoImplementGenerator : IIncrementalGenerator
677693
{
678694
context.RegisterPostInitializationOutput(ctx =>
679695
{
696+
ctx.AddEmbeddedAttributeDefinition();
680697
//Generate the AutoImplementProperties Attribute
681698
const string autoImplementAttributeDeclarationCode = $$"""
682699
// <auto-generated/>
683700
using System;
701+
using Microsoft.CodeAnalysis;
684702
namespace {{AttributeNameSpace}};
685703
686-
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
687-
sealed class {{AttributeClassName}} : Attribute
704+
[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false), Embedded]
705+
internal sealed class {{AttributeClassName}} : Attribute
688706
{
689707
public Type[] InterfacesTypes { get; }
690708
public {{AttributeClassName}}(params Type[] interfacesTypes)

src/Compilers/CSharp/Portable/SourceGeneration/CSharpGeneratorDriver.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,15 @@ internal override SyntaxTree ParseGeneratedSourceText(GeneratedSourceText input,
7676

7777
internal override string SourceExtension => ".cs";
7878

79+
internal override string EmbeddedAttributeDefinition => """
80+
namespace Microsoft.CodeAnalysis
81+
{
82+
internal sealed partial class EmbeddedAttribute : global::System.Attribute
83+
{
84+
}
85+
}
86+
""";
87+
7988
internal override ISyntaxHelper SyntaxHelper => CSharpSyntaxHelper.Instance;
8089
}
8190
}

src/Compilers/CSharp/Test/Semantic/SourceGeneration/GeneratorDriverTests.cs

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2446,6 +2446,82 @@ class C { }
24462446
});
24472447
}
24482448

2449+
[Fact]
2450+
public void IncrementalGenerator_PostInit_AddEmbeddedAttributeSource_Adds()
2451+
{
2452+
var source = @"
2453+
class C { }
2454+
";
2455+
var parseOptions = TestOptions.RegularPreview;
2456+
Compilation compilation = CreateCompilation(source, options: TestOptions.DebugDllThrowing, parseOptions: parseOptions);
2457+
compilation.VerifyDiagnostics();
2458+
2459+
Assert.Single(compilation.SyntaxTrees);
2460+
2461+
var generator = new IncrementalGeneratorWrapper(new PipelineCallbackGenerator((ctx) =>
2462+
{
2463+
ctx.RegisterPostInitializationOutput(c => c.AddEmbeddedAttributeDefinition());
2464+
}));
2465+
2466+
GeneratorDriver driver = CSharpGeneratorDriver.Create([generator], parseOptions: parseOptions, driverOptions: TestOptions.GeneratorDriverOptions);
2467+
driver = driver.RunGenerators(compilation);
2468+
var runResult = driver.GetRunResult().Results[0];
2469+
2470+
Assert.Single(runResult.GeneratedSources);
2471+
2472+
var generatedSource = runResult.GeneratedSources[0];
2473+
2474+
Assert.Equal("""
2475+
namespace Microsoft.CodeAnalysis
2476+
{
2477+
internal sealed partial class EmbeddedAttribute : global::System.Attribute
2478+
{
2479+
}
2480+
}
2481+
""", generatedSource.SourceText.ToString());
2482+
Assert.Equal("Microsoft.CodeAnalysis.EmbeddedAttribute.cs", generatedSource.HintName);
2483+
}
2484+
2485+
[Fact]
2486+
public void IncrementalGenerator_PostInit_AddEmbeddedAttributeSource_DoubleAdd_Throws()
2487+
{
2488+
var source = @"
2489+
class C { }
2490+
";
2491+
var parseOptions = TestOptions.RegularPreview;
2492+
Compilation compilation = CreateCompilation(source, options: TestOptions.DebugDllThrowing, parseOptions: parseOptions);
2493+
compilation.VerifyDiagnostics();
2494+
2495+
Assert.Single(compilation.SyntaxTrees);
2496+
2497+
var generator = new IncrementalGeneratorWrapper(new PipelineCallbackGenerator((ctx) =>
2498+
{
2499+
ctx.RegisterPostInitializationOutput(c =>
2500+
{
2501+
c.AddEmbeddedAttributeDefinition();
2502+
Assert.Throws<ArgumentException>("hintName", () => c.AddEmbeddedAttributeDefinition());
2503+
});
2504+
}));
2505+
2506+
GeneratorDriver driver = CSharpGeneratorDriver.Create([generator], parseOptions: parseOptions, driverOptions: TestOptions.GeneratorDriverOptions);
2507+
driver = driver.RunGenerators(compilation);
2508+
var runResult = driver.GetRunResult().Results[0];
2509+
2510+
Assert.Single(runResult.GeneratedSources);
2511+
2512+
var generatedSource = runResult.GeneratedSources[0];
2513+
2514+
Assert.Equal("""
2515+
namespace Microsoft.CodeAnalysis
2516+
{
2517+
internal sealed partial class EmbeddedAttribute : global::System.Attribute
2518+
{
2519+
}
2520+
}
2521+
""", generatedSource.SourceText.ToString());
2522+
Assert.Equal("Microsoft.CodeAnalysis.EmbeddedAttribute.cs", generatedSource.HintName);
2523+
}
2524+
24492525
[Fact]
24502526
public void Incremental_Generators_Can_Be_Cancelled()
24512527
{

src/Compilers/Core/Portable/PublicAPI.Unshipped.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ Microsoft.CodeAnalysis.GeneratorFilterContext.Generator.get -> Microsoft.CodeAna
1212
Microsoft.CodeAnalysis.GeneratorFilterContext.GeneratorFilterContext() -> void
1313
Microsoft.CodeAnalysis.GeneratorRunResult.HostOutputs.get -> System.Collections.Immutable.ImmutableDictionary<string!, object!>!
1414
Microsoft.CodeAnalysis.IncrementalGeneratorOutputKind.Host = 8 -> Microsoft.CodeAnalysis.IncrementalGeneratorOutputKind
15+
Microsoft.CodeAnalysis.IncrementalGeneratorPostInitializationContext.AddEmbeddedAttributeDefinition() -> void
1516
Microsoft.CodeAnalysis.IPropertySymbol.PartialDefinitionPart.get -> Microsoft.CodeAnalysis.IPropertySymbol?
1617
Microsoft.CodeAnalysis.IPropertySymbol.PartialImplementationPart.get -> Microsoft.CodeAnalysis.IPropertySymbol?
1718
Microsoft.CodeAnalysis.IPropertySymbol.IsPartialDefinition.get -> bool
@@ -25,4 +26,4 @@ Microsoft.CodeAnalysis.Compilation.CreatePreprocessingSymbol(string! name) -> Mi
2526
[RSEXPERIMENTAL004]Microsoft.CodeAnalysis.HostOutputProductionContext.CancellationToken.get -> System.Threading.CancellationToken
2627
[RSEXPERIMENTAL004]Microsoft.CodeAnalysis.HostOutputProductionContext.HostOutputProductionContext() -> void
2728
[RSEXPERIMENTAL004]Microsoft.CodeAnalysis.IncrementalGeneratorInitializationContext.RegisterHostOutput<TSource>(Microsoft.CodeAnalysis.IncrementalValueProvider<TSource> source, System.Action<Microsoft.CodeAnalysis.HostOutputProductionContext, TSource>! action) -> void
28-
[RSEXPERIMENTAL004]Microsoft.CodeAnalysis.IncrementalGeneratorInitializationContext.RegisterHostOutput<TSource>(Microsoft.CodeAnalysis.IncrementalValuesProvider<TSource> source, System.Action<Microsoft.CodeAnalysis.HostOutputProductionContext, TSource>! action) -> void
29+
[RSEXPERIMENTAL004]Microsoft.CodeAnalysis.IncrementalGeneratorInitializationContext.RegisterHostOutput<TSource>(Microsoft.CodeAnalysis.IncrementalValuesProvider<TSource> source, System.Action<Microsoft.CodeAnalysis.HostOutputProductionContext, TSource>! action) -> void

src/Compilers/Core/Portable/SourceGeneration/GeneratorDriver.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ internal GeneratorDriverState RunGeneratorsCore(Compilation compilation, Diagnos
244244
var inputBuilder = ArrayBuilder<SyntaxInputNode>.GetInstance();
245245
var postInitSources = ImmutableArray<GeneratedSyntaxTree>.Empty;
246246
var pipelineContext = new IncrementalGeneratorInitializationContext(
247-
inputBuilder, outputBuilder, this.SyntaxHelper, this.SourceExtension, compilation.CatchAnalyzerExceptions);
247+
inputBuilder, outputBuilder, this.SyntaxHelper, this.SourceExtension, this.EmbeddedAttributeDefinition, compilation.CatchAnalyzerExceptions);
248248

249249
Exception? ex = null;
250250
try
@@ -462,6 +462,8 @@ private static ImmutableArray<IIncrementalGenerator> GetIncrementalGenerators(Im
462462

463463
internal abstract string SourceExtension { get; }
464464

465+
internal abstract string EmbeddedAttributeDefinition { get; }
466+
465467
internal abstract ISyntaxHelper SyntaxHelper { get; }
466468
}
467469
}

src/Compilers/Core/Portable/SourceGeneration/IncrementalContexts.cs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public readonly partial struct IncrementalGeneratorInitializationContext
2727
private readonly ArrayBuilder<SyntaxInputNode> _syntaxInputBuilder;
2828
private readonly ArrayBuilder<IIncrementalGeneratorOutputNode> _outputNodes;
2929
private readonly string _sourceExtension;
30-
30+
private readonly string _embeddedAttributeDefinition;
3131
internal readonly ISyntaxHelper SyntaxHelper;
3232
internal readonly bool CatchAnalyzerExceptions;
3333

@@ -36,12 +36,14 @@ internal IncrementalGeneratorInitializationContext(
3636
ArrayBuilder<IIncrementalGeneratorOutputNode> outputNodes,
3737
ISyntaxHelper syntaxHelper,
3838
string sourceExtension,
39+
string embeddedAttributeDefinition,
3940
bool catchAnalyzerExceptions)
4041
{
4142
_syntaxInputBuilder = syntaxInputBuilder;
4243
_outputNodes = outputNodes;
4344
SyntaxHelper = syntaxHelper;
4445
_sourceExtension = sourceExtension;
46+
_embeddedAttributeDefinition = embeddedAttributeDefinition;
4547
CatchAnalyzerExceptions = catchAnalyzerExceptions;
4648
}
4749

@@ -73,7 +75,7 @@ internal IncrementalValueProvider<CompilationOptions> CompilationOptionsProvider
7375

7476
public void RegisterImplementationSourceOutput<TSource>(IncrementalValuesProvider<TSource> source, Action<SourceProductionContext, TSource> action) => RegisterSourceOutput(source.Node, action, IncrementalGeneratorOutputKind.Implementation, _sourceExtension);
7577

76-
public void RegisterPostInitializationOutput(Action<IncrementalGeneratorPostInitializationContext> callback) => _outputNodes.Add(new PostInitOutputNode(callback.WrapUserAction(CatchAnalyzerExceptions)));
78+
public void RegisterPostInitializationOutput(Action<IncrementalGeneratorPostInitializationContext> callback) => _outputNodes.Add(new PostInitOutputNode(callback.WrapUserAction(CatchAnalyzerExceptions), _embeddedAttributeDefinition));
7779

7880
[Experimental(RoslynExperiments.GeneratorHostOutputs, UrlFormat = RoslynExperiments.GeneratorHostOutputs_Url)]
7981
public void RegisterHostOutput<TSource>(IncrementalValueProvider<TSource> source, Action<HostOutputProductionContext, TSource> action) => source.Node.RegisterOutput(new HostOutputNode<TSource>(source.Node, action.WrapUserAction(CatchAnalyzerExceptions)));
@@ -101,10 +103,12 @@ private void RegisterSourceOutput<TSource>(IIncrementalGeneratorNode<TSource> no
101103
public readonly struct IncrementalGeneratorPostInitializationContext
102104
{
103105
internal readonly AdditionalSourcesCollection AdditionalSources;
106+
private readonly string _embeddedAttributeDefinition;
104107

105-
internal IncrementalGeneratorPostInitializationContext(AdditionalSourcesCollection additionalSources, CancellationToken cancellationToken)
108+
internal IncrementalGeneratorPostInitializationContext(AdditionalSourcesCollection additionalSources, string embeddedAttributeDefinition, CancellationToken cancellationToken)
106109
{
107110
AdditionalSources = additionalSources;
111+
_embeddedAttributeDefinition = embeddedAttributeDefinition;
108112
CancellationToken = cancellationToken;
109113
}
110114

@@ -129,6 +133,16 @@ internal IncrementalGeneratorPostInitializationContext(AdditionalSourcesCollecti
129133
/// Directory separators "/" and "\" are allowed in <paramref name="hintName"/>, they are normalized to "/" regardless of host platform.
130134
/// </remarks>
131135
public void AddSource(string hintName, SourceText sourceText) => AdditionalSources.Add(hintName, sourceText);
136+
137+
/// <summary>
138+
/// Adds a <see cref="SourceText" /> to the compilation containing the definition of <c>Microsoft.CodeAnalysis.EmbeddedAttribute</c>.
139+
/// The source will have a <c>hintName</c> of Microsoft.CodeAnalysis.EmbeddedAttribute.
140+
/// </summary>
141+
/// <remarks>
142+
/// This attribute can be used to mark a type as being only visible to the current assembly. Most commonly, any types provided during this <see cref="IncrementalGeneratorPostInitializationContext"/>
143+
/// should be marked with this attribute to prevent them from being used by other assemblies. The attribute will prevent any downstream assemblies from consuming the type.
144+
/// </remarks>
145+
public void AddEmbeddedAttributeDefinition() => AddSource("Microsoft.CodeAnalysis.EmbeddedAttribute", SourceText.From(_embeddedAttributeDefinition, encoding: Encoding.UTF8));
132146
}
133147

134148
/// <summary>

src/Compilers/Core/Portable/SourceGeneration/Nodes/PostInitOutputNode.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,19 @@ namespace Microsoft.CodeAnalysis
1010
internal sealed class PostInitOutputNode : IIncrementalGeneratorOutputNode
1111
{
1212
private readonly Action<IncrementalGeneratorPostInitializationContext, CancellationToken> _callback;
13+
private readonly string _embeddedAttributeDefinition;
1314

14-
public PostInitOutputNode(Action<IncrementalGeneratorPostInitializationContext, CancellationToken> callback)
15+
public PostInitOutputNode(Action<IncrementalGeneratorPostInitializationContext, CancellationToken> callback, string embeddedAttributeDefinition)
1516
{
1617
_callback = callback;
18+
_embeddedAttributeDefinition = embeddedAttributeDefinition;
1719
}
1820

1921
public IncrementalGeneratorOutputKind Kind => IncrementalGeneratorOutputKind.PostInit;
2022

2123
public void AppendOutputs(IncrementalExecutionContext context, CancellationToken cancellationToken)
2224
{
23-
_callback(new IncrementalGeneratorPostInitializationContext(context.Sources, cancellationToken), cancellationToken);
25+
_callback(new IncrementalGeneratorPostInitializationContext(context.Sources, _embeddedAttributeDefinition, cancellationToken), cancellationToken);
2426
}
2527
}
2628
}

src/Compilers/VisualBasic/Portable/SourceGeneration/VisualBasicGeneratorDriver.vb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,16 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
5353
End Get
5454
End Property
5555

56+
Friend Overrides ReadOnly Property EmbeddedAttributeDefinition As String
57+
Get
58+
Return "Namespace Microsoft.CodeAnalysis
59+
Friend NotInheritable Partial Class EmbeddedAttribute
60+
Inherits Global.System.Attribute
61+
End Class
62+
End Namespace"
63+
End Get
64+
End Property
65+
5666
Friend Overrides ReadOnly Property SyntaxHelper As ISyntaxHelper = VisualBasicSyntaxHelper.Instance
5767
End Class
5868
End Namespace

0 commit comments

Comments
 (0)