Skip to content

Commit

Permalink
Wip
Browse files Browse the repository at this point in the history
  • Loading branch information
smoogipoo committed Dec 6, 2024
1 parent a1ea511 commit 696a997
Show file tree
Hide file tree
Showing 8 changed files with 399 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

namespace osu.Framework.SourceGeneration.Generators.Dependencies
{
[Generator]
public class DependencyInjectionSourceGenerator : AbstractIncrementalGenerator
{
protected override IncrementalSemanticTarget CreateSemanticTarget(ClassDeclarationSyntax node, SemanticModel semanticModel)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using osu.Framework.SourceGeneration.Generators.Dependencies.Emitters;

namespace osu.Framework.SourceGeneration.Generators.Dependencies
{
[Generator]
public class NewDependencyInjectionSourceGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
// All interfaces that have a [Cached] attribute.
// IncrementalValuesProvider<DependencyInjectionSemanticTarget> cachedInterfaces =
// context.SyntaxProvider
// .CreateSyntaxProvider(
// (n, _) => n.IsKind(SyntaxKind.InterfaceDeclaration),
// (ctx, _) => new DependencyInjectionSemanticTarget(ctx))
// .Where(target => target.AnyAttributes());

// Classes containing [Cached], [Resolved], or [BackgroundDependencyLoader] attributes.
IncrementalValuesProvider<ActivatorCandidate> candidates =
context.SyntaxProvider
.CreateSyntaxProvider(
(n, _) => n.IsKind(SyntaxKind.ClassDeclaration),
(ctx, _) => new ActivatorCandidate(ctx))
.Where(candidate => candidate.HasAnyAttributes);

// Classes with semantic information.
IncrementalValuesProvider<DependenciesClassCandidate> semanticCandidates =
candidates.Select((candidate, _) => new DependenciesClassCandidate((ClassDeclarationSyntax)candidate.Context.Node, candidate.Context.SemanticModel));

context.RegisterImplementationSourceOutput(
semanticCandidates,
(ctx, candidate) => new DependenciesFileEmitter(candidate).Emit(ctx.AddSource));
}

private readonly struct ActivatorCandidate : IEquatable<ActivatorCandidate>
{
public readonly GeneratorSyntaxContext Context;

private readonly ClassDeclarationSyntax classSyntax;
private readonly List<CachedMemberInfo> cachedMembers = new List<CachedMemberInfo>();
private readonly List<ResolvedMemberInfo> resolvedMembers = new List<ResolvedMemberInfo>();
private readonly List<LoaderMemberInfo> loaderMembers = new List<LoaderMemberInfo>();

public ActivatorCandidate(GeneratorSyntaxContext context)
{
Context = context;
classSyntax = (ClassDeclarationSyntax)context.Node;

foreach (var attrib in NewSyntaxHelpers.EnumerateNamedAttributes(classSyntax, "Cached"))
cachedMembers.Add(new CachedMemberInfo(attrib, classSyntax));

foreach (var member in classSyntax.Members)
{
switch (member)
{
case PropertyDeclarationSyntax property:
foreach (var attrib in NewSyntaxHelpers.EnumerateNamedAttributes(property, "Cached"))
cachedMembers.Add(new CachedMemberInfo(attrib, property));
foreach (var attrib in NewSyntaxHelpers.EnumerateNamedAttributes(property, "Resolved"))
resolvedMembers.Add(new ResolvedMemberInfo(attrib, property));
break;

case FieldDeclarationSyntax field:
foreach (var attrib in NewSyntaxHelpers.EnumerateNamedAttributes(field, "Cached"))
cachedMembers.Add(new CachedMemberInfo(attrib, field));
break;

case MethodDeclarationSyntax method:
foreach (var attrib in NewSyntaxHelpers.EnumerateNamedAttributes(method, "BackgroundDependencyLoader"))
loaderMembers.Add(new LoaderMemberInfo(attrib, method));
break;
}
}
}

public bool HasAnyAttributes => cachedMembers.Any() || resolvedMembers.Any() || loaderMembers.Any();

public bool Equals(ActivatorCandidate other)
=> classSyntax.Identifier.IsEquivalentTo(other.classSyntax.Identifier)
&& cachedMembers.SequenceEqual(other.cachedMembers)
&& resolvedMembers.SequenceEqual(other.resolvedMembers)
&& loaderMembers.SequenceEqual(other.loaderMembers);
}

private readonly struct CachedMemberInfo : IEquatable<CachedMemberInfo>
{
private readonly AttributeSyntax attributeSyntax;
private readonly MemberDeclarationSyntax memberSyntax;

public CachedMemberInfo(AttributeSyntax attributeSyntax, MemberDeclarationSyntax memberSyntax)
{
this.attributeSyntax = attributeSyntax;
this.memberSyntax = memberSyntax;
}

public bool Equals(CachedMemberInfo other)
=> attributeSyntax.IsEquivalentTo(other.attributeSyntax)
&& memberSyntax switch
{
ClassDeclarationSyntax c => c.Identifier.IsEquivalentTo(((ClassDeclarationSyntax)other.memberSyntax).Identifier),
InterfaceDeclarationSyntax i => i.Identifier.IsEquivalentTo(((InterfaceDeclarationSyntax)other.memberSyntax).Identifier),
PropertyDeclarationSyntax p => p.Identifier.IsEquivalentTo(((PropertyDeclarationSyntax)other.memberSyntax).Identifier),
FieldDeclarationSyntax f => f.Declaration.IsEquivalentTo(((FieldDeclarationSyntax)other.memberSyntax).Declaration),
_ => false
};
}

private readonly struct ResolvedMemberInfo : IEquatable<ResolvedMemberInfo>
{
private readonly AttributeSyntax attributeSyntax;
private readonly PropertyDeclarationSyntax propertySyntax;

public ResolvedMemberInfo(AttributeSyntax attributeSyntax, PropertyDeclarationSyntax propertySyntax)
{
this.attributeSyntax = attributeSyntax;
this.propertySyntax = propertySyntax;
}

public bool Equals(ResolvedMemberInfo other)
=> attributeSyntax.IsEquivalentTo(other.attributeSyntax)
&& propertySyntax.IsEquivalentTo(other.propertySyntax);
}

private readonly struct LoaderMemberInfo : IEquatable<LoaderMemberInfo>
{
private readonly AttributeSyntax attributeSyntax;
private readonly MethodDeclarationSyntax methodSyntax;

public LoaderMemberInfo(AttributeSyntax attributeSyntax, MethodDeclarationSyntax methodSyntax)
{
this.attributeSyntax = attributeSyntax;
this.methodSyntax = methodSyntax;
}

public bool Equals(LoaderMemberInfo other)
=> attributeSyntax.IsEquivalentTo(other.attributeSyntax)
&& methodSyntax.Identifier.IsEquivalentTo(other.methodSyntax.Identifier)
&& methodSyntax.ParameterList.IsEquivalentTo(other.methodSyntax.ParameterList);
}
}

public static class NewSyntaxHelpers
{
public static IdentifierNameSyntax? GetAttributeArgumentName(AttributeArgumentSyntax argument)
{
if (argument.NameColon is NameColonSyntax nameColon)
return nameColon.Name;

if (argument.NameEquals is NameEqualsSyntax nameEquals)
return nameEquals.Name;

return null;
}

public static string GetAttributeArgumentExpression(AttributeArgumentSyntax argument)
{
return argument.Expression switch
{
LiteralExpressionSyntax literal => literal.Token.ValueText,
TypeOfExpressionSyntax type => type.Type switch
{
NameSyntax name => GetUnqualifiedName(name),
_ => type.Type.ToString()
},
_ => argument.Expression.ToString()
};
}

public static string GetUnqualifiedName(NameSyntax name)
{
return name switch
{
IdentifierNameSyntax identifier => identifier.Identifier.ValueText,
AliasQualifiedNameSyntax alias => alias.Name.Identifier.ValueText,
QualifiedNameSyntax qualified => qualified.Right.Identifier.ValueText,
SimpleNameSyntax simple => simple.Identifier.ValueText,
_ => throw new ArgumentException("Unexpected name syntax.", nameof(name))
};
}

public static IEnumerable<AttributeSyntax> EnumerateNamedAttributes(MemberDeclarationSyntax syntax, string name)
{
foreach (var list in syntax.AttributeLists)
{
foreach (var attribute in list.Attributes)
{
string attribName = GetUnqualifiedName(attribute.Name);

// Note that this is somewhat "wide" for brevity, because any time we see "Cached", "Resolved", or "BackgroundDependencyLoader",
// it's generally going to be one of our own attributes (which otherwise cover 95% of classes anyway).
if (attribName.StartsWith(name, StringComparison.Ordinal))
yield return attribute;
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

namespace osu.Framework.SourceGeneration.Generators.HandleInput
{
[Generator]
public class HandleInputSourceGenerator : AbstractIncrementalGenerator
{
protected override IncrementalSemanticTarget CreateSemanticTarget(ClassDeclarationSyntax node, SemanticModel semanticModel)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ namespace osu.Framework.SourceGeneration.Generators
{
public abstract class IncrementalSemanticTarget
{
public readonly ClassDeclarationSyntax ClassSyntax;

public readonly string FullyQualifiedTypeName = string.Empty;
public readonly string GlobalPrefixedTypeName = string.Empty;
public readonly bool NeedsOverride;
Expand All @@ -22,9 +20,7 @@ public abstract class IncrementalSemanticTarget

protected IncrementalSemanticTarget(ClassDeclarationSyntax classSyntax, SemanticModel semanticModel)
{
ClassSyntax = classSyntax;

INamedTypeSymbol symbol = semanticModel.GetDeclaredSymbol(ClassSyntax)!;
INamedTypeSymbol symbol = semanticModel.GetDeclaredSymbol(classSyntax)!;

IsValid = CheckValid(symbol);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

namespace osu.Framework.SourceGeneration.Generators.LongRunningLoad
{
[Generator]
public class LongRunningLoadSourceGenerator : AbstractIncrementalGenerator
{
protected override IncrementalSemanticTarget CreateSemanticTarget(ClassDeclarationSyntax node, SemanticModel semanticModel)
Expand Down
Loading

0 comments on commit 696a997

Please sign in to comment.