Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions BitsKit.Benchmarks/BitsKit.Benchmarks.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@
<OutputType>Exe</OutputType>
<TargetFrameworks>net6.0;net7.0;net8.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<StartupObject>BitsKit.Benchmarks.Program</StartupObject>
<Platforms>AnyCPU;x86;x64</Platforms>
</PropertyGroup>

<ItemGroup>
Expand Down
79 changes: 79 additions & 0 deletions BitsKit.Generator/Analysers/BitFieldAnalyser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
using System.Collections.Immutable;
using BitsKit.Generator.Models;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;

namespace BitsKit.Generator.Analysers
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class BitFieldAnalyser : DiagnosticAnalyzer
{
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = [
DiagnosticDescriptors.ConflictingAccessors,
DiagnosticDescriptors.ConflictingSetters,
DiagnosticDescriptors.EnumTypeExpected
];

public override void Initialize(AnalysisContext context)
{
context.EnableConcurrentExecution();
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);

// todo: reintroduce FieldTypeNotDefined
// but i'm not sure how it was triggered

context.RegisterCompilationStartAction(context =>
{
var bitFieldAttribute = context.Compilation.GetTypeByMetadataName(StringConstants.BitFieldAttributeFullName);
if (bitFieldAttribute == null) return;

context.RegisterSymbolAction(context =>
{
var fieldSymbol = (IFieldSymbol)context.Symbol;
if (!fieldSymbol.TryGetAttributesWithBaseType(bitFieldAttribute, out var thisAttributes))
{
return;
}

foreach (var thisAttribute in thisAttributes)
{
// todo: code doesn't read the processor, just stores. so we don't need to give one
var bitField = TypeSymbolProcessor.CreateBitFieldFromAttribute(thisAttribute, null!);
if (bitField == null) continue;

var accessorModifiers = bitField.Modifiers & BitFieldModifiers.AccessorMask;
if ((accessorModifiers & (accessorModifiers - 1)) != 0 &&
accessorModifiers != BitFieldModifiers.ProtectedInternal &&
accessorModifiers != BitFieldModifiers.PrivateProtected)
{
// "protected internal" and "private protected" combos are allowed

context.ReportDiagnostic(
Diagnostic.Create(DiagnosticDescriptors.ConflictingAccessors, thisAttribute.ApplicationSyntaxReference!.GetSyntax().GetLocation(), fieldSymbol.ContainingType.Name, bitField.Name)
);
}

var setterModifiers = bitField.Modifiers & BitFieldModifiers.SetterMask;
if ((setterModifiers & (setterModifiers - 1)) != 0)
{
context.ReportDiagnostic(
Diagnostic.Create(DiagnosticDescriptors.ConflictingSetters, thisAttribute.ApplicationSyntaxReference!.GetSyntax().GetLocation(), fieldSymbol.ContainingType.Name, bitField.Name)
);
}

if (bitField is EnumFieldModel)
{
var enumFieldAttrModel = new EnumFieldAttributeModel(thisAttribute);
if (enumFieldAttrModel.EnumType != null && enumFieldAttrModel.EnumType.EnumUnderlyingType == null)
{
context.ReportDiagnostic(
Diagnostic.Create(DiagnosticDescriptors.EnumTypeExpected, thisAttribute.ApplicationSyntaxReference!.GetSyntax().GetLocation(), fieldSymbol.ContainingType.Name, bitField.Name)
);
}
}
}
}, SymbolKind.Field);
});
}
}
}
59 changes: 59 additions & 0 deletions BitsKit.Generator/Analysers/BitObjectAnalyser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;

namespace BitsKit.Generator.Analysers
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class BitObjectAnalyser : DiagnosticAnalyzer
{
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = [
DiagnosticDescriptors.MustBePartial,
DiagnosticDescriptors.NestedNotAllowed
];

public override void Initialize(AnalysisContext context)
{
context.EnableConcurrentExecution();
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);

context.RegisterCompilationStartAction(context =>
{
var bitObjectAttribute = context.Compilation.GetTypeByMetadataName(StringConstants.BitObjectAttributeFullName);
if (bitObjectAttribute == null) return;

context.RegisterSymbolAction(context =>
{
var type = (INamedTypeSymbol)context.Symbol;

if (!type.TryGetAttributeWithType(bitObjectAttribute, out _))
{
return;
}

if (type.DeclaringSyntaxReferences[0].GetSyntax() is not TypeDeclarationSyntax typeDeclarationSyntax)
{
return;
}

if (!typeDeclarationSyntax.Modifiers.Any(m => m.IsKind(SyntaxKind.PartialKeyword)))
{
context.ReportDiagnostic(
Diagnostic.Create(DiagnosticDescriptors.MustBePartial, typeDeclarationSyntax.GetLocation(), type.Name)
);
}

if (type.ContainingType != null)
{
context.ReportDiagnostic(
Diagnostic.Create(DiagnosticDescriptors.NestedNotAllowed, typeDeclarationSyntax.GetLocation(), type.Name)
);
}
}, SymbolKind.NamedType);
});
}
}
}
39 changes: 8 additions & 31 deletions BitsKit.Generator/BitObjectGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,10 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
StringConstants.BitObjectAttributeFullName,
predicate: IsValidTypeDeclaration,
transform: ProcessSyntaxNode)
.WithComparer(TypeSymbolProcessorComparer.Default)
.Where(x => x is not null)!;

IncrementalValueProvider<(Compilation, ImmutableArray<TypeSymbolProcessor>)> model = context
.CompilationProvider
.Combine(typeDeclarations.Collect());
.Where(x => x is not null)
.WithTrackingName("Main")!;

var model = typeDeclarations.Collect();
context.RegisterSourceOutput(model, GenerateSourceCode);
}

Expand All @@ -43,18 +40,18 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
.GetAttributes()
.Single(a => a.AttributeClass?.ToDisplayString() == StringConstants.BitObjectAttributeFullName);

return new(typeSymbol, typeDeclaration, attribute);
return new(typeSymbol, attribute);
}

private static void GenerateSourceCode(SourceProductionContext context, (Compilation _, ImmutableArray<TypeSymbolProcessor> Processors) result)
private static void GenerateSourceCode(SourceProductionContext context, ImmutableArray<TypeSymbolProcessor> processors)
{
if (result.Processors.Length == 0)
if (processors.Length == 0)
return;

StringBuilder stringBuilder = new(StringConstants.Header);

// group the objects by their respective namespace
var namespaceGroups = result.Processors.GroupBy(x => x.Namespace);
var namespaceGroups = processors.GroupBy(x => x.Namespace);

foreach (var namespaceGroup in namespaceGroups)
{
Expand All @@ -63,20 +60,11 @@ private static void GenerateSourceCode(SourceProductionContext context, (Compila
// print the current namespace
if (namespaceGroup.Key is not null)
stringBuilder
.AppendLine($"namespace {namespaceGroup.Key.Name.ToFullString()}")
.AppendLine($"namespace {namespaceGroup.Key}")
.AppendLine("{");

foreach (TypeSymbolProcessor processor in namespaceGroup)
{
// evaluate if there are actually any valid fields
if (processor.EnumerateFields() == 0)
continue;

// check and report any compilation issues and prevent
// code generation for this type if there are
if (processor.ReportCompilationIssues(context))
continue;

processor.GenerateCSharpSource(stringBuilder);
}

Expand All @@ -94,14 +82,3 @@ private static void GenerateSourceCode(SourceProductionContext context, (Compila
private static bool IsValidTypeDeclaration(SyntaxNode node, CancellationToken _) =>
node is ClassDeclarationSyntax or StructDeclarationSyntax or RecordDeclarationSyntax;
}

file class TypeSymbolProcessorComparer : IEqualityComparer<TypeSymbolProcessor?>
{
public static TypeSymbolProcessorComparer Default { get; } = new();

public bool Equals(TypeSymbolProcessor? x, TypeSymbolProcessor? y) =>
SymbolEqualityComparer.Default.Equals(x?.TypeSymbol, y?.TypeSymbol);

public int GetHashCode(TypeSymbolProcessor? obj) =>
SymbolEqualityComparer.Default.GetHashCode(obj?.TypeSymbol);
}
10 changes: 5 additions & 5 deletions BitsKit.Generator/BitsKit.Generator.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,7 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<IncludeBuildOutput>false</IncludeBuildOutput>
<LangVersion>preview</LangVersion>
<Nullable>enable</Nullable>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>Generated</CompilerGeneratedFilesOutputPath>
<IsRoslynComponent>true</IsRoslynComponent>
<Platforms>AnyCPU;x86;x64</Platforms>
<EnforceCodeStyleInBuild>True</EnforceCodeStyleInBuild>
<EnforceExtendedAnalyzerRules>True</EnforceExtendedAnalyzerRules>
</PropertyGroup>
Expand All @@ -24,8 +19,13 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Bcl.HashCode" Version="6.0.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.10.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" PrivateAssets="all" />
<PackageReference Include="PolySharp" Version="1.15.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
Expand Down
70 changes: 3 additions & 67 deletions BitsKit.Generator/DiagnosticValidator.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis;
using BitsKit.Generator.Models;

namespace BitsKit.Generator;
Expand All @@ -19,27 +16,7 @@ public static bool ReportDiagnostic(SourceProductionContext context, DiagnosticD
return descriptor is { DefaultSeverity: DiagnosticSeverity.Error };
}

public static bool IsNotPartial(SourceProductionContext context, TypeDeclarationSyntax typeDeclaration, string typeName)
{
return !typeDeclaration.Modifiers.Any(m => m.IsKind(SyntaxKind.PartialKeyword))
&& ReportDiagnostic(
context,
DiagnosticDescriptors.MustBePartial,
typeDeclaration.GetLocation(),
typeName);
}

public static bool IsNested(SourceProductionContext context, TypeDeclarationSyntax typeDeclaration, string typeName)
{
return typeDeclaration.Parent is TypeDeclarationSyntax
&& ReportDiagnostic(
context,
DiagnosticDescriptors.NestedNotAllowed,
typeDeclaration.GetLocation(),
typeName);
}

public static bool HasMissingFieldType(SourceProductionContext context, BitFieldModel bitField, string typeName)
/*public static bool HasMissingFieldType(SourceProductionContext context, BitFieldModel bitField, string typeName)
{
return bitField.FieldType is null
&& ReportDiagnostic(
Expand All @@ -48,46 +25,5 @@ public static bool HasMissingFieldType(SourceProductionContext context, BitField
bitField.BackingField.Locations[0],
typeName,
bitField.Name);
}

public static bool HasConflictingAccessors(SourceProductionContext context, BitFieldModel bitField, string typeName)
{
BitFieldModifiers modifiers = bitField.Modifiers & BitFieldModifiers.AccessorMask;

// "protected internal" and "private protected" combos are allowed
if (modifiers is BitFieldModifiers.ProtectedInternal or BitFieldModifiers.PrivateProtected)
return false;

return (modifiers & (modifiers - 1)) != 0
&& ReportDiagnostic(
context,
DiagnosticDescriptors.ConflictingAccessors,
bitField.BackingField.Locations[0],
typeName,
bitField.Name);
}

public static bool HasConflictingSetters(SourceProductionContext context, BitFieldModel bitField, string typeName)
{
BitFieldModifiers modifiers = bitField.Modifiers & BitFieldModifiers.SetterMask;

return (modifiers & (modifiers - 1)) != 0
&& ReportDiagnostic(
context,
DiagnosticDescriptors.ConflictingSetters,
bitField.BackingField.Locations[0],
typeName,
bitField.Name);
}

public static bool IsNotEnumType(SourceProductionContext context, EnumFieldModel enumField, string typeName)
{
return enumField.EnumType is not { EnumUnderlyingType: { } }
&& ReportDiagnostic(
context,
DiagnosticDescriptors.EnumTypeExpected,
enumField.BackingField.Locations[0],
typeName,
enumField.Name);
}
}*/
}
Loading