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

Async CodeGen nesting classes support #382

Merged
merged 1 commit into from Nov 9, 2021
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
80 changes: 73 additions & 7 deletions api/AltV.Net.Async.CodeGen/AsyncEntityGenerator.cs
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.ComponentModel.Design.Serialization;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
Expand Down Expand Up @@ -53,6 +54,48 @@ public void Initialize(GeneratorInitializationContext context)
context.RegisterForSyntaxNotifications(() => new SyntaxTreeReceiver());
}

private static IEnumerable<ISymbol> GetInterfaceMembers(INamedTypeSymbol @interface)
{
var members = @interface.GetMembers().ToList();
foreach (var namedTypeSymbol in @interface.Interfaces)
{
if (namedTypeSymbol.ToString().StartsWith("AltV.Net.Elements.Entities.")) continue;
foreach (var interfaceMember in GetInterfaceMembers(namedTypeSymbol))
{
if (members.All(e => e.Name != interfaceMember.Name)) members.Add(interfaceMember);
}
}

return members;
}

private static IEnumerable<ISymbol> GetClassMembers(INamedTypeSymbol @class)
{
var members = @class.GetMembers().ToList();
if (@class.BaseType is null || @class.BaseType.ToString().StartsWith("AltV.Net.Elements.Entities."))
return members;
foreach (var classMember in GetClassMembers(@class.BaseType))
{
if (members.All(e => e.Name != classMember.Name)) members.Add(classMember);
}

return members;
}

private static IList<INamedTypeSymbol> GetBaseTypes(INamedTypeSymbol @class)
{
var list = new List<INamedTypeSymbol>();
var currentClass = @class;

while (currentClass.BaseType is { } type)
{
list.Add(type);
currentClass = type;
}

return list;
}

public void Execute(GeneratorExecutionContext context)
{
var receiver = (SyntaxTreeReceiver) context.SyntaxReceiver!;
Expand All @@ -66,6 +109,8 @@ public void Execute(GeneratorExecutionContext context)

var attributeSymbol = compilation.GetTypeByMetadataName("AltV.Net.Async.CodeGen.AsyncEntityAttribute");

var validClasses = new List<INamedTypeSymbol>();

foreach (var classSyntax in receiver.Classes)
{
var @class = compilation.GetSemanticModel(classSyntax.SyntaxTree).GetDeclaredSymbol(classSyntax)!;
Expand Down Expand Up @@ -99,22 +144,33 @@ public void Execute(GeneratorExecutionContext context)
}

var baseType = @class.BaseType;
if (baseType is null || !baseType.ToString().StartsWith("AltV.Net.Elements.Entities."))

if (baseType is not null)
while (!baseType!.ToString().StartsWith("AltV.Net.Elements.Entities."))
baseType = baseType.BaseType;

if (baseType is null)
{
context.ReportDiagnostic(Diagnostic.Create(Diagnostics.AsyncEntityShouldImplementEntity,
@class.Locations.FirstOrDefault(), @class.ToString()));
continue;
}

validClasses.Add(@class);

var classBaseDeclaration = @class.BaseType!.ToString().StartsWith("AltV.Net.Elements.Entities.")
? ""
: " : " + @class.BaseType;

var members = new List<string>();

var interfaceMembers = @interface.GetMembers();
var classMembers = @class.GetMembers();
var interfaceMembers = GetInterfaceMembers(@interface);
var classMembers = GetClassMembers(@class).ToArray();

foreach (var member in interfaceMembers)
{
var classMember = classMembers.FirstOrDefault(m => m.Name == member.Name);

// does member hide base class property (new keyword)
var isNew = classMember?.DeclaringSyntaxReferences.Any(s =>
s.GetSyntax() is MemberDeclarationSyntax declarationSyntax &&
Expand Down Expand Up @@ -163,7 +219,7 @@ public void Execute(GeneratorExecutionContext context)
var formattedAttributes =
attributes.Length == 0 ? "" : FormatAttributes(attributes) + "\n";
var @new = isNew ? "new " : "";

members.Add(formattedAttributes +
$"public {@new}{property.Type} {member.Name} {{ {propertyValue}}}");
}
Expand Down Expand Up @@ -219,7 +275,7 @@ public void Execute(GeneratorExecutionContext context)
var attributes = classMethod.GetAttributes();
var formattedAttributes = attributes.Length == 0 ? "" : FormatAttributes(attributes) + "\n";
var @new = isNew ? "new " : "";

members.Add(formattedAttributes +
$"public {@new}{classMethod.ReturnType} {name}({arguments})\n{{\n {returnAction}BaseObject.{name}({callArguments});\n}}");

Expand All @@ -236,7 +292,7 @@ public void Execute(GeneratorExecutionContext context)
interfaceDeclaration = $"namespace {interfaceNamespace} {{\n{Indent(interfaceDeclaration)}\n}}";

var classDeclaration = $@"
public partial class {@class.Name} {{
public partial class {@class.Name}{classBaseDeclaration} {{
public {@interface} ToAsync(AltV.Net.Async.IAsyncContext asyncContext) {{
return new Async(this, asyncContext);
}}
Expand All @@ -257,6 +313,16 @@ private class Async : {asyncEntityType}<{@interface}>, {@interface} {{
sourceBuilder.Append($"\n\n{interfaceDeclaration}\n{classDeclaration}");
}

foreach (var @class in validClasses)
{
var types = GetBaseTypes(@class);
var invalid = types.FirstOrDefault(e => validClasses.Contains(e, SymbolEqualityComparer.Default));
if (invalid is not { } invalidClass) continue;

context.ReportDiagnostic(Diagnostic.Create(Diagnostics.AsyncEntityCannotBeNested,
@class.Locations.FirstOrDefault(), @class.ToString(), invalidClass.ToString()));
}

context.AddSource("GeneratedAsyncEntities.cs", SourceText.From(sourceBuilder.ToString(), Encoding.UTF8));
}
}
Expand Down
10 changes: 10 additions & 0 deletions api/AltV.Net.Async.CodeGen/Diagnostics.cs
Expand Up @@ -85,5 +85,15 @@ public static class Diagnostics
DiagnosticSeverity.Error,
true
);

public static DiagnosticDescriptor AsyncEntityCannotBeNested =
new(
"ALTV0009",
"Invalid async entity",
"Async entity class {0} cannot extend another async entity class {1}, async entities cannot be nested",
"AsyncEntity",
DiagnosticSeverity.Error,
true
);
}
}