Enabled Nullable reference types. Added a temporary fix for clonable NRT warnings.

This commit is contained in:
Kwoth
2021-12-28 10:40:59 +01:00
parent 89c2cda9ec
commit 0634470a8a
6 changed files with 320 additions and 6 deletions

View File

@@ -0,0 +1,261 @@
// Code temporarily yeeted from
// https://github.com/mostmand/Cloneable/blob/master/Cloneable/CloneableGenerator.cs
// because of NRT issue
#nullable enable
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Cloneable
{
[Generator]
public class CloneableGenerator : ISourceGenerator
{
private const string PreventDeepCopyKeyString = "PreventDeepCopy";
private const string ExplicitDeclarationKeyString = "ExplicitDeclaration";
private const string CloneableNamespace = "Cloneable";
private const string CloneableAttributeString = "CloneableAttribute";
private const string CloneAttributeString = "CloneAttribute";
private const string IgnoreCloneAttributeString = "IgnoreCloneAttribute";
private const string cloneableAttributeText = @"// <AutoGenerated/>
using System;
namespace " + CloneableNamespace + @"
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = true, AllowMultiple = false)]
public sealed class " + CloneableAttributeString + @" : Attribute
{
public " + CloneableAttributeString + @"()
{
}
public bool " + ExplicitDeclarationKeyString + @" { get; set; }
}
}
";
private const string clonePropertyAttributeText = @"// <AutoGenerated/>
using System;
namespace " + CloneableNamespace + @"
{
[AttributeUsage(AttributeTargets.Property, Inherited = true, AllowMultiple = false)]
public sealed class " + CloneAttributeString + @" : Attribute
{
public " + CloneAttributeString + @"()
{
}
public bool " + PreventDeepCopyKeyString + @" { get; set; }
}
}
";
private const string ignoreClonePropertyAttributeText = @"// <AutoGenerated/>
using System;
namespace " + CloneableNamespace + @"
{
[AttributeUsage(AttributeTargets.Property, Inherited = true, AllowMultiple = false)]
public sealed class " + IgnoreCloneAttributeString + @" : Attribute
{
public " + IgnoreCloneAttributeString + @"()
{
}
}
}
";
private INamedTypeSymbol? cloneableAttribute;
private INamedTypeSymbol? ignoreCloneAttribute;
private INamedTypeSymbol? cloneAttribute;
public void Initialize(GeneratorInitializationContext context)
{
context.RegisterForSyntaxNotifications(() => new SyntaxReceiver());
}
public void Execute(GeneratorExecutionContext context)
{
InjectCloneableAttributes(context);
GenerateCloneMethods(context);
}
private void GenerateCloneMethods(GeneratorExecutionContext context)
{
if (context.SyntaxReceiver is not SyntaxReceiver receiver)
return;
Compilation compilation = GetCompilation(context);
InitAttributes(compilation);
var classSymbols = GetClassSymbols(compilation, receiver);
foreach (var classSymbol in classSymbols)
{
if (!classSymbol.TryGetAttribute(cloneableAttribute!, out var attributes))
continue;
var attribute = attributes.Single();
var isExplicit = (bool?)attribute.NamedArguments.FirstOrDefault(e => e.Key.Equals(ExplicitDeclarationKeyString)).Value.Value ?? false;
context.AddSource($"{classSymbol.Name}_cloneable.g.cs", SourceText.From(CreateCloneableCode(classSymbol, isExplicit), Encoding.UTF8));
}
}
private void InitAttributes(Compilation compilation)
{
cloneableAttribute = compilation.GetTypeByMetadataName($"{CloneableNamespace}.{CloneableAttributeString}")!;
cloneAttribute = compilation.GetTypeByMetadataName($"{CloneableNamespace}.{CloneAttributeString}")!;
ignoreCloneAttribute = compilation.GetTypeByMetadataName($"{CloneableNamespace}.{IgnoreCloneAttributeString}")!;
}
private static Compilation GetCompilation(GeneratorExecutionContext context)
{
var options = context.Compilation.SyntaxTrees.First().Options as CSharpParseOptions;
var compilation = context.Compilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(SourceText.From(cloneableAttributeText, Encoding.UTF8), options)).
AddSyntaxTrees(CSharpSyntaxTree.ParseText(SourceText.From(clonePropertyAttributeText, Encoding.UTF8), options)).
AddSyntaxTrees(CSharpSyntaxTree.ParseText(SourceText.From(ignoreClonePropertyAttributeText, Encoding.UTF8), options));
return compilation;
}
private string CreateCloneableCode(INamedTypeSymbol classSymbol, bool isExplicit)
{
string namespaceName = classSymbol.ContainingNamespace.ToDisplayString();
var fieldAssignmentsCode = GenerateFieldAssignmentsCode(classSymbol, isExplicit);
var fieldAssignmentsCodeSafe = fieldAssignmentsCode.Select(x =>
{
if (x.isCloneable)
return x.line + "Safe(referenceChain)";
return x.line;
});
var fieldAssignmentsCodeFast = fieldAssignmentsCode.Select(x =>
{
if (x.isCloneable)
return x.line + "()";
return x.line;
});
return $@"using System.Collections.Generic;
namespace {namespaceName}
{{
{GetAccessModifier(classSymbol)} partial class {classSymbol.Name}
{{
/// <summary>
/// Creates a copy of {classSymbol.Name} with NO circular reference checking. This method should be used if performance matters.
///
/// <exception cref=""StackOverflowException"">Will occur on any object that has circular references in the hierarchy.</exception>
/// </summary>
public {classSymbol.Name} Clone()
{{
return new {classSymbol.Name}
{{
{string.Join($",{Environment.NewLine}", fieldAssignmentsCodeFast)}
}};
}}
/// <summary>
/// Creates a copy of {classSymbol.Name} with circular reference checking. If a circular reference was detected, only a reference of the leaf object is passed instead of cloning it.
/// </summary>
/// <param name=""referenceChain"">Should only be provided if specific objects should not be cloned but passed by reference instead.</param>
public {classSymbol.Name} CloneSafe(Stack<object> referenceChain = null)
{{
if(referenceChain?.Contains(this) == true)
return this;
referenceChain ??= new Stack<object>();
referenceChain.Push(this);
var result = new {classSymbol.Name}
{{
{string.Join($",{Environment.NewLine}", fieldAssignmentsCodeSafe)}
}};
referenceChain.Pop();
return result;
}}
}}
}}";
}
private IEnumerable<(string line, bool isCloneable)> GenerateFieldAssignmentsCode(INamedTypeSymbol classSymbol, bool isExplicit )
{
var fieldNames = GetCloneableProperties(classSymbol, isExplicit);
var fieldAssignments = fieldNames.Select(field => IsFieldCloneable(field, classSymbol)).
OrderBy(x => x.isCloneable).
Select(x => (GenerateAssignmentCode(x.item.Name, x.isCloneable), x.isCloneable));
return fieldAssignments;
}
private string GenerateAssignmentCode(string name, bool isCloneable)
{
if (isCloneable)
{
return $@" {name} = this.{name}?.Clone";
}
return $@" {name} = this.{name}";
}
private (IPropertySymbol item, bool isCloneable) IsFieldCloneable(IPropertySymbol x, INamedTypeSymbol classSymbol)
{
if (SymbolEqualityComparer.Default.Equals(x.Type, classSymbol))
{
return (x, false);
}
if (!x.Type.TryGetAttribute(cloneableAttribute!, out var attributes))
{
return (x, false);
}
var preventDeepCopy = (bool?)attributes.Single().NamedArguments.FirstOrDefault(e => e.Key.Equals(PreventDeepCopyKeyString)).Value.Value ?? false;
return (item: x, !preventDeepCopy);
}
private string GetAccessModifier(INamedTypeSymbol classSymbol)
{
return classSymbol.DeclaredAccessibility.ToString().ToLowerInvariant();
}
private IEnumerable<IPropertySymbol> GetCloneableProperties(ITypeSymbol classSymbol, bool isExplicit)
{
var targetSymbolMembers = classSymbol.GetMembers().OfType<IPropertySymbol>()
.Where(x => x.SetMethod is not null &&
x.CanBeReferencedByName);
if (isExplicit)
{
return targetSymbolMembers.Where(x => x.HasAttribute(cloneAttribute!));
}
else
{
return targetSymbolMembers.Where(x => !x.HasAttribute(ignoreCloneAttribute!));
}
}
private static IEnumerable<INamedTypeSymbol> GetClassSymbols(Compilation compilation, SyntaxReceiver receiver)
{
return receiver.CandidateClasses.Select(clazz => GetClassSymbol(compilation, clazz));
}
private static INamedTypeSymbol GetClassSymbol(Compilation compilation, ClassDeclarationSyntax clazz)
{
var model = compilation.GetSemanticModel(clazz.SyntaxTree);
var classSymbol = model.GetDeclaredSymbol(clazz)!;
return classSymbol;
}
private static void InjectCloneableAttributes(GeneratorExecutionContext context)
{
context.AddSource(CloneableAttributeString, SourceText.From(cloneableAttributeText, Encoding.UTF8));
context.AddSource(CloneAttributeString, SourceText.From(clonePropertyAttributeText, Encoding.UTF8));
context.AddSource(IgnoreCloneAttributeString, SourceText.From(ignoreClonePropertyAttributeText, Encoding.UTF8));
}
}
}

View File

@@ -0,0 +1,26 @@
// Code temporarily yeeted from
// https://github.com/mostmand/Cloneable/blob/master/Cloneable/CloneableGenerator.cs
// because of NRT issue
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis;
namespace Cloneable
{
internal static class SymbolExtensions
{
public static bool TryGetAttribute(this ISymbol symbol, INamedTypeSymbol attributeType,
out IEnumerable<AttributeData> attributes)
{
attributes = symbol.GetAttributes()
.Where(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, attributeType));
return attributes.Any();
}
public static bool HasAttribute(this ISymbol symbol, INamedTypeSymbol attributeType)
{
return symbol.GetAttributes()
.Any(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, attributeType));
}
}
}

View File

@@ -0,0 +1,27 @@
// Code temporarily yeeted from
// https://github.com/mostmand/Cloneable/blob/master/Cloneable/CloneableGenerator.cs
// because of NRT issue
using System.Collections.Generic;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace Cloneable
{
internal class SyntaxReceiver : ISyntaxReceiver
{
public IList<ClassDeclarationSyntax> CandidateClasses { get; } = new List<ClassDeclarationSyntax>();
/// <summary>
/// Called for every syntax node in the compilation, we can inspect the nodes and save any information useful for generation
/// </summary>
public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
{
// any field with at least one attribute is a candidate for being cloneable
if (syntaxNode is ClassDeclarationSyntax classDeclarationSyntax &&
classDeclarationSyntax.AttributeLists.Count > 0)
{
CandidateClasses.Add(classDeclarationSyntax);
}
}
}
}

View File

@@ -3,10 +3,8 @@ using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
using Newtonsoft.Json;
namespace NadekoBot.Generators
@@ -20,7 +18,7 @@ namespace NadekoBot.Generators
[Generator]
public class LocalizedStringsGenerator : ISourceGenerator
{
private const string LocStrSource = @"namespace NadekoBot
private const string LOC_STR_SOURCE = @"namespace NadekoBot
{
public readonly struct LocStr
{
@@ -89,10 +87,10 @@ namespace NadekoBot.Generators
sw.Flush();
context.AddSource("strs.cs", stringWriter.ToString());
context.AddSource("strs.g.cs", stringWriter.ToString());
}
context.AddSource("LocStr.cs", LocStrSource);
context.AddSource("LocStr.g.cs", LOC_STR_SOURCE);
}
private List<TranslationPair> GetFields(string dataText)

View File

@@ -2,6 +2,7 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>9</LangVersion>
<IncludeBuildOutput>false</IncludeBuildOutput>
</PropertyGroup>

View File

@@ -3,6 +3,7 @@
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<LangVersion>10.0</LangVersion>
<Nullable>enable</Nullable>
<EnablePreviewFeatures>True</EnablePreviewFeatures>
<ImplicitUsings>true</ImplicitUsings>
<RunWorkingDirectory>$(MSBuildProjectDirectory)</RunWorkingDirectory>
@@ -13,7 +14,7 @@
<ItemGroup>
<PackageReference Include="AngleSharp" Version="0.16.1" />
<PackageReference Include="AWSSDK.S3" Version="3.7.7.5" />
<PackageReference Include="Cloneable" Version="1.3.0" />
<!-- <PackageReference Include="Cloneable" Version="1.3.0" />-->
<PackageReference Include="CodeHollow.FeedReader" Version="1.2.2" />
<PackageReference Include="CommandLineParser" Version="2.8.0" />
<PackageReference Include="Discord.Net" Version="3.0.0" />