Skip to content

Commit 0a5db80

Browse files
committed
Improve contextual options gen incrementality
1 parent 1dc5b31 commit 0a5db80

File tree

3 files changed

+73
-100
lines changed

3 files changed

+73
-100
lines changed
Lines changed: 15 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
using System.Collections.Generic;
54
using System.Collections.Immutable;
65
using System.Linq;
76
using Microsoft.CodeAnalysis;
@@ -15,50 +14,31 @@ public class ContextualOptionsGenerator : IIncrementalGenerator
1514
{
1615
public void Initialize(IncrementalGeneratorInitializationContext context)
1716
{
18-
IncrementalValuesProvider<SyntaxNode> typeDeclarations = context.SyntaxProvider
17+
IncrementalValuesProvider<OptionsContextType> types = context.SyntaxProvider
1918
.ForAttributeWithMetadataName(
2019
"Microsoft.Extensions.Options.Contextual.OptionsContextAttribute",
21-
(_, _) => true,
22-
(context, _) => context.TargetNode);
20+
(node, _) => node is TypeDeclarationSyntax,
21+
(context, _) => CreateOptionsContextType(context));
2322

24-
IncrementalValueProvider<(Compilation, ImmutableArray<SyntaxNode>)> compilationAndTypes =
25-
context.CompilationProvider.Combine(typeDeclarations.Collect());
23+
IncrementalValueProvider<(Compilation, ImmutableArray<OptionsContextType>)> compilationAndTypes =
24+
context.CompilationProvider.Combine(types.Collect());
2625

27-
context.RegisterSourceOutput(compilationAndTypes, static (spc, source) => HandleAnnotatedTypes(source.Item1, source.Item2, spc));
26+
context.RegisterSourceOutput(types.Collect(), static (spc, source) => HandleAnnotatedTypes(source, spc));
2827
}
2928

30-
private static void HandleAnnotatedTypes(Compilation compilation, IEnumerable<SyntaxNode> nodes, SourceProductionContext context)
29+
private static OptionsContextType CreateOptionsContextType(GeneratorAttributeSyntaxContext context)
3130
{
32-
if (!SymbolLoader.TryLoad(compilation, out var holder))
33-
{
34-
return;
35-
}
36-
37-
var typeDeclarations = nodes.OfType<TypeDeclarationSyntax>()
38-
.ToLookup(declaration => declaration.SyntaxTree)
39-
.SelectMany(declarations => declarations.Select(declaration => (symbol: compilation.GetSemanticModel(declarations.Key).GetDeclaredSymbol(declaration), declaration)))
40-
.Where(_ => _.symbol is INamedTypeSymbol)
41-
.Where(_ => _.symbol!.GetAttributes().Any(attribute => SymbolEqualityComparer.Default.Equals(attribute.AttributeClass, holder!.OptionsContextAttribute)))
42-
.ToLookup(_ => _.symbol, _ => _.declaration, comparer: SymbolEqualityComparer.Default)
43-
.ToDictionary<IGrouping<ISymbol?, TypeDeclarationSyntax>, INamedTypeSymbol, List<TypeDeclarationSyntax>>(
44-
group => (INamedTypeSymbol)group.Key!, group => group.ToList(), comparer: SymbolEqualityComparer.Default);
45-
46-
var list = new List<OptionsContextType>();
47-
foreach (var type in Parser.GetContextualOptionTypes(typeDeclarations))
48-
{
49-
context.CancellationToken.ThrowIfCancellationRequested();
50-
type.Diagnostics.ForEach(context.ReportDiagnostic);
51-
52-
if (type.ShouldEmit)
53-
{
54-
list.Add(type);
55-
}
56-
}
31+
var symbol = (INamedTypeSymbol)context.TargetSymbol;
32+
var node = context.TargetNode;
33+
return Parser.GetContextualOptionTypes(symbol, (TypeDeclarationSyntax)node);
34+
}
5735

58-
if (list.Count > 0)
36+
private static void HandleAnnotatedTypes(ImmutableArray<OptionsContextType> types, SourceProductionContext context)
37+
{
38+
if (types.Length > 0)
5939
{
6040
var emitter = new Emitter();
61-
context.AddSource($"ContextualOptions.g.cs", emitter.Emit(list.OrderBy(x => x.Namespace + "." + x.Name)));
41+
context.AddSource($"ContextualOptions.g.cs", emitter.Emit(types.OrderBy(x => x.Namespace + "." + x.Name)));
6242
}
6343
}
6444
}
Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,30 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
using System.Collections.Generic;
54
using System.Collections.Immutable;
65
using Microsoft.CodeAnalysis;
76
using Microsoft.CodeAnalysis.CSharp.Syntax;
87

98
namespace Microsoft.Gen.ContextualOptions.Model;
109

10+
// TODO: Equality
1111
internal sealed class OptionsContextType
1212
{
13-
public readonly List<Diagnostic> Diagnostics = [];
14-
public readonly INamedTypeSymbol Symbol;
15-
public readonly ImmutableArray<TypeDeclarationSyntax> Definitions;
1613
public readonly ImmutableArray<string> OptionsContextProperties;
17-
public string Keyword => Definitions[0].Keyword.Text;
18-
public string? Namespace => Symbol.ContainingNamespace.IsGlobalNamespace ? null : Symbol.ContainingNamespace.ToString();
19-
public string Name => Symbol.Name;
20-
21-
public bool ShouldEmit => Diagnostics.TrueForAll(diag => diag.Severity != DiagnosticSeverity.Error);
22-
23-
public string HintName => $"{Namespace}.{Name}";
14+
public string Keyword;
15+
public string? Namespace;
16+
public string Name;
2417

2518
public OptionsContextType(
2619
INamedTypeSymbol symbol,
27-
ImmutableArray<TypeDeclarationSyntax> definitions,
20+
TypeDeclarationSyntax typeDeclarationSyntax,
2821
ImmutableArray<string> optionsContextProperties)
2922
{
30-
Symbol = symbol;
31-
Definitions = definitions;
23+
// NOTE: NEVER store INamedTypeSymbol in OptionsContextType.
24+
// This is better for source generator incrementality.
25+
Name = symbol.Name;
26+
Namespace = symbol.ContainingNamespace.IsGlobalNamespace ? null : symbol.ContainingNamespace.ToString();
27+
Keyword = typeDeclarationSyntax.Keyword.Text;
3228
OptionsContextProperties = optionsContextProperties;
3329
}
3430
}

src/Generators/Microsoft.Gen.ContextualOptions/Parser.cs

Lines changed: 48 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,72 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
using System.Collections.Generic;
54
using System.Collections.Immutable;
65
using System.Linq;
76
using Microsoft.CodeAnalysis;
8-
using Microsoft.CodeAnalysis.CSharp;
97
using Microsoft.CodeAnalysis.CSharp.Syntax;
108
using Microsoft.Gen.ContextualOptions.Model;
119

1210
namespace Microsoft.Gen.ContextualOptions;
1311

1412
internal static class Parser
1513
{
16-
public static IEnumerable<OptionsContextType> GetContextualOptionTypes(Dictionary<INamedTypeSymbol, List<TypeDeclarationSyntax>> types) =>
17-
types
18-
.Select(type => new OptionsContextType(type.Key, type.Value.ToImmutableArray(), GetContextProperties(type.Key)))
19-
.Select(CheckInstantiable)
20-
.Select(CheckPartial)
21-
.Select(CheckRefLikeType)
22-
.Select(CheckHasProperties);
14+
public static OptionsContextType GetContextualOptionTypes(INamedTypeSymbol symbol, TypeDeclarationSyntax typeDeclarationSyntax)
15+
=> new OptionsContextType(symbol, typeDeclarationSyntax, GetContextProperties(symbol));
2316

24-
private static OptionsContextType CheckInstantiable(OptionsContextType type)
25-
{
26-
if (type.Symbol.IsStatic)
27-
{
28-
type.Diagnostics.AddRange(
29-
type.Definitions
30-
.SelectMany(def => def.Modifiers)
31-
.Where(modifier => modifier.IsKind(SyntaxKind.StaticKeyword))
32-
.Select(modifier => Diagnostic.Create(DiagDescriptors.ContextCannotBeStatic, modifier.GetLocation(), type.Name)));
33-
}
17+
// TODO: Separate analyzer for diagnostics.
18+
//private static OptionsContextType CheckInstantiable(OptionsContextType type)
19+
//{
20+
// if (type.Symbol.IsStatic)
21+
// {
22+
// type.Diagnostics.AddRange(
23+
// type.Definitions
24+
// .SelectMany(def => def.Modifiers)
25+
// .Where(modifier => modifier.IsKind(SyntaxKind.StaticKeyword))
26+
// .Select(modifier => Diagnostic.Create(DiagDescriptors.ContextCannotBeStatic, modifier.GetLocation(), type.Name)));
27+
// }
3428

35-
return type;
36-
}
29+
// return type;
30+
//}
3731

38-
private static OptionsContextType CheckRefLikeType(OptionsContextType type)
39-
{
40-
if (type.Symbol.IsRefLikeType)
41-
{
42-
type.Diagnostics.AddRange(
43-
type.Definitions
44-
.SelectMany(def => def.Modifiers)
45-
.Where(modifier => modifier.IsKind(SyntaxKind.RefKeyword))
46-
.Select(modifier => Diagnostic.Create(DiagDescriptors.ContextCannotBeRefLike, modifier.GetLocation(), type.Name)));
47-
}
32+
// TODO: Separate analyzer for diagnostics.
33+
//private static OptionsContextType CheckRefLikeType(OptionsContextType type)
34+
//{
35+
// if (type.Symbol.IsRefLikeType)
36+
// {
37+
// type.Diagnostics.AddRange(
38+
// type.Definitions
39+
// .SelectMany(def => def.Modifiers)
40+
// .Where(modifier => modifier.IsKind(SyntaxKind.RefKeyword))
41+
// .Select(modifier => Diagnostic.Create(DiagDescriptors.ContextCannotBeRefLike, modifier.GetLocation(), type.Name)));
42+
// }
4843

49-
return type;
50-
}
44+
// return type;
45+
//}
5146

52-
private static OptionsContextType CheckPartial(OptionsContextType type)
53-
{
54-
if (!type.Definitions.Any(def => def.Modifiers.Any(static token => token.IsKind(SyntaxKind.PartialKeyword))))
55-
{
56-
type.Diagnostics.AddRange(
57-
type.Definitions.Select(def => Diagnostic.Create(DiagDescriptors.ContextMustBePartial, def.Identifier.GetLocation(), type.Name)));
58-
}
47+
// TODO: Separate analyzer for diagnostics.
48+
//private static OptionsContextType CheckPartial(OptionsContextType type)
49+
//{
50+
// if (!type.Definitions.Any(def => def.Modifiers.Any(static token => token.IsKind(SyntaxKind.PartialKeyword))))
51+
// {
52+
// type.Diagnostics.AddRange(
53+
// type.Definitions.Select(def => Diagnostic.Create(DiagDescriptors.ContextMustBePartial, def.Identifier.GetLocation(), type.Name)));
54+
// }
5955

60-
return type;
61-
}
56+
// return type;
57+
//}
6258

63-
private static OptionsContextType CheckHasProperties(OptionsContextType type)
64-
{
65-
if (type.OptionsContextProperties.IsEmpty)
66-
{
67-
type.Diagnostics.AddRange(
68-
type.Definitions.Select(def => Diagnostic.Create(DiagDescriptors.ContextDoesNotHaveValidProperties, def.Identifier.GetLocation(), type.Name)));
69-
}
59+
// TODO: Separate analyzer for diagnostics.
60+
//private static OptionsContextType CheckHasProperties(OptionsContextType type)
61+
//{
62+
// if (type.OptionsContextProperties.IsEmpty)
63+
// {
64+
// type.Diagnostics.AddRange(
65+
// type.Definitions.Select(def => Diagnostic.Create(DiagDescriptors.ContextDoesNotHaveValidProperties, def.Identifier.GetLocation(), type.Name)));
66+
// }
7067

71-
return type;
72-
}
68+
// return type;
69+
//}
7370

7471
private static ImmutableArray<string> GetContextProperties(INamedTypeSymbol symbol)
7572
{

0 commit comments

Comments
 (0)