diff --git a/BitsKit.Benchmarks/BitsKit.Benchmarks.csproj b/BitsKit.Benchmarks/BitsKit.Benchmarks.csproj
index efb4597..d4b0263 100644
--- a/BitsKit.Benchmarks/BitsKit.Benchmarks.csproj
+++ b/BitsKit.Benchmarks/BitsKit.Benchmarks.csproj
@@ -4,9 +4,7 @@
Exe
net6.0;net7.0;net8.0
enable
- enable
BitsKit.Benchmarks.Program
- AnyCPU;x86;x64
diff --git a/BitsKit.Generator/Analysers/BitFieldAnalyser.cs b/BitsKit.Generator/Analysers/BitFieldAnalyser.cs
new file mode 100644
index 0000000..42693c9
--- /dev/null
+++ b/BitsKit.Generator/Analysers/BitFieldAnalyser.cs
@@ -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 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);
+ });
+ }
+ }
+}
\ No newline at end of file
diff --git a/BitsKit.Generator/Analysers/BitObjectAnalyser.cs b/BitsKit.Generator/Analysers/BitObjectAnalyser.cs
new file mode 100644
index 0000000..9827efd
--- /dev/null
+++ b/BitsKit.Generator/Analysers/BitObjectAnalyser.cs
@@ -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 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);
+ });
+ }
+ }
+}
\ No newline at end of file
diff --git a/BitsKit.Generator/BitObjectGenerator.cs b/BitsKit.Generator/BitObjectGenerator.cs
index c6e2f71..f3827ee 100644
--- a/BitsKit.Generator/BitObjectGenerator.cs
+++ b/BitsKit.Generator/BitObjectGenerator.cs
@@ -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)> model = context
- .CompilationProvider
- .Combine(typeDeclarations.Collect());
+ .Where(x => x is not null)
+ .WithTrackingName("Main")!;
+ var model = typeDeclarations.Collect();
context.RegisterSourceOutput(model, GenerateSourceCode);
}
@@ -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 Processors) result)
+ private static void GenerateSourceCode(SourceProductionContext context, ImmutableArray 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)
{
@@ -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);
}
@@ -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
-{
- 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);
-}
diff --git a/BitsKit.Generator/BitsKit.Generator.csproj b/BitsKit.Generator/BitsKit.Generator.csproj
index 7872e93..dc5f7b5 100644
--- a/BitsKit.Generator/BitsKit.Generator.csproj
+++ b/BitsKit.Generator/BitsKit.Generator.csproj
@@ -3,12 +3,7 @@
netstandard2.0
false
- preview
- enable
- true
- Generated
true
- AnyCPU;x86;x64
True
True
@@ -24,8 +19,13 @@
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
diff --git a/BitsKit.Generator/DiagnosticValidator.cs b/BitsKit.Generator/DiagnosticValidator.cs
index ac1d9ed..c03d03b 100644
--- a/BitsKit.Generator/DiagnosticValidator.cs
+++ b/BitsKit.Generator/DiagnosticValidator.cs
@@ -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;
@@ -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(
@@ -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);
- }
+ }*/
}
diff --git a/BitsKit.Generator/EquatableReadOnlyList.cs b/BitsKit.Generator/EquatableReadOnlyList.cs
new file mode 100644
index 0000000..29684b9
--- /dev/null
+++ b/BitsKit.Generator/EquatableReadOnlyList.cs
@@ -0,0 +1,57 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+
+namespace BitsKit.Generator
+{
+ [ExcludeFromCodeCoverage]
+ public static class EquatableReadOnlyList
+ {
+ public static EquatableReadOnlyList ToEquatableReadOnlyList(this IEnumerable enumerable)
+ => new(enumerable is IReadOnlyList l ? l : [.. enumerable]);
+ }
+
+ ///
+ /// A wrapper for IReadOnlyList that provides value equality support for the wrapped list.
+ ///
+ [ExcludeFromCodeCoverage]
+ public readonly struct EquatableReadOnlyList(
+ IReadOnlyList? collection
+ ) : IEquatable>, IReadOnlyList
+ {
+ private IReadOnlyList Collection => collection ?? [];
+
+ public bool Equals(EquatableReadOnlyList other)
+ => this.SequenceEqual(other);
+
+ public override bool Equals(object? obj)
+ => obj is EquatableReadOnlyList other && Equals(other);
+
+ public override int GetHashCode()
+ {
+ var hashCode = new HashCode();
+
+ foreach (var item in Collection)
+ hashCode.Add(item);
+
+ return hashCode.ToHashCode();
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ => Collection.GetEnumerator();
+
+ IEnumerator IEnumerable.GetEnumerator()
+ => Collection.GetEnumerator();
+
+ public int Count => Collection.Count;
+ public T this[int index] => Collection[index];
+
+ public static bool operator ==(EquatableReadOnlyList left, EquatableReadOnlyList right)
+ => left.Equals(right);
+
+ public static bool operator !=(EquatableReadOnlyList left, EquatableReadOnlyList right)
+ => !left.Equals(right);
+ }
+}
\ No newline at end of file
diff --git a/BitsKit.Generator/Models/BackingFieldModel.cs b/BitsKit.Generator/Models/BackingFieldModel.cs
new file mode 100644
index 0000000..2b43ade
--- /dev/null
+++ b/BitsKit.Generator/Models/BackingFieldModel.cs
@@ -0,0 +1,24 @@
+using Microsoft.CodeAnalysis;
+
+namespace BitsKit.Generator.Models
+{
+ internal record BackingFieldModel
+ {
+ public readonly string Name;
+ public readonly string TypeString;
+ public readonly int FixedSize;
+ public readonly bool IsReadOnly;
+
+ public readonly BackingFieldType Type;
+
+ public BackingFieldModel(IFieldSymbol fieldSymbol, BackingFieldType type)
+ {
+ Name = fieldSymbol.Name;
+ TypeString = fieldSymbol.Type.ToDisplayString();
+ FixedSize = fieldSymbol.FixedSize;
+ IsReadOnly = fieldSymbol.IsReadOnly;
+
+ Type = type;
+ }
+ }
+}
\ No newline at end of file
diff --git a/BitsKit.Generator/Models/BitFieldModel.cs b/BitsKit.Generator/Models/BitFieldModel.cs
index abe97ef..b96f2f1 100644
--- a/BitsKit.Generator/Models/BitFieldModel.cs
+++ b/BitsKit.Generator/Models/BitFieldModel.cs
@@ -4,23 +4,31 @@
namespace BitsKit.Generator.Models;
-internal abstract class BitFieldModel
+internal abstract record BitFieldModel
{
public string Name { get; set; } = null!;
public BitFieldType? FieldType { get; set; }
public string? ReturnType { get; set; }
- public IFieldSymbol BackingField { get; set; } = null!;
- public BackingFieldType BackingFieldType { get; set; }
+ public BackingFieldModel BackingField { get; set; } = null!;
+ public BackingFieldType BackingFieldType => BackingField.Type;
public int BitOffset { get; set; }
public int BitCount { get; set; }
public BitOrder BitOrder { get; set; }
public bool ReverseBitOrder { get; }
public BitFieldModifiers Modifiers { get; }
- public TypeSymbolProcessor TypeSymbol { get; }
+
+ private readonly bool _containingTypeIsStruct;
- public BitFieldModel(AttributeData attributeData, TypeSymbolProcessor typeSymbol)
+ public BitFieldModel(AttributeData attributeData, TypeSymbolProcessor? typeSymbol)
{
- TypeSymbol = typeSymbol;
+ if (typeSymbol != null)
+ {
+ // todo: for now, analyser passes null
+ // these fields don't matter for it
+
+ _containingTypeIsStruct = typeSymbol.IsStruct;
+ BitOrder = typeSymbol.DefaultBitOrder;
+ }
for (int i = 0; i < attributeData.NamedArguments.Length; i++)
{
@@ -54,7 +62,7 @@ public void GenerateCSharpSource(StringBuilder sb)
GetPropertyTemplate(),
accessor,
Modifiers.HasFlag(BitFieldModifiers.Required) ? "required" : "",
- ReturnType ?? FieldType.ToString(),
+ ReturnType ?? FieldType?.ToString(),
Name)
.AppendIndentedLine(2, "{");
@@ -64,13 +72,13 @@ public void GenerateCSharpSource(StringBuilder sb)
GetGetterTemplate(),
SupportsReadOnlyGetter() ? "readonly" : "",
"get",
- FieldType!.Value.ToIntegralName(),
+ FieldType?.ToIntegralName(),
BitOrder.ToShortName(),
BackingField.Name,
BitOffset,
BitCount,
BackingField.FixedSize,
- BackingField.Type);
+ BackingField.TypeString);
}
// setter
@@ -80,29 +88,19 @@ public void GenerateCSharpSource(StringBuilder sb)
GetSetterTemplate(),
"",
Modifiers.HasFlag(BitFieldModifiers.InitOnly) ? "init" : "set",
- FieldType!.Value.ToIntegralName(),
+ FieldType?.ToIntegralName(),
BitOrder.ToShortName(),
BackingField.Name,
BitOffset,
BitCount,
BackingField.FixedSize,
- BackingField.Type);
+ BackingField.TypeString);
}
sb.AppendIndentedLine(2, "}")
.AppendLine();
}
- ///
- /// Diagnoses if the field will produce non-compilable or erroneous code
- ///
- public virtual bool HasCompilationIssues(SourceProductionContext context, TypeSymbolProcessor processor)
- {
- return DiagnosticValidator.HasMissingFieldType(context, this, processor.TypeSymbol.Name) |
- DiagnosticValidator.HasConflictingAccessors(context, this, processor.TypeSymbol.Name) |
- DiagnosticValidator.HasConflictingSetters(context, this, processor.TypeSymbol.Name);
- }
-
///
/// Generates a template for the property accessors, type and name
///
@@ -188,7 +186,7 @@ BackingFieldType.Span or
///
protected bool IsReadOnly()
{
- string backingType = BackingField.Type.ToDisplayString();
+ string backingType = BackingField.TypeString;
return BackingField.IsReadOnly ||
backingType == "System.ReadOnlySpan" ||
@@ -202,7 +200,7 @@ protected bool IsReadOnly()
///
private bool SupportsReadOnlyGetter()
{
- return TypeSymbol.TypeDeclaration.IsStruct() &&
+ return _containingTypeIsStruct &&
BackingFieldType != BackingFieldType.Pointer &&
BackingFieldType != BackingFieldType.InlineArray &&
!IsReadOnly();
diff --git a/BitsKit.Generator/Models/BooleanFieldModel.cs b/BitsKit.Generator/Models/BooleanFieldModel.cs
index f09ba99..beda46d 100644
--- a/BitsKit.Generator/Models/BooleanFieldModel.cs
+++ b/BitsKit.Generator/Models/BooleanFieldModel.cs
@@ -5,9 +5,9 @@ namespace BitsKit.Generator.Models;
///
/// A model representing a boolean bit-field
///
-internal sealed class BooleanFieldModel : BitFieldModel
+internal sealed record BooleanFieldModel : BitFieldModel
{
- public BooleanFieldModel(AttributeData attributeData, TypeSymbolProcessor typeSymbol) : base(attributeData, typeSymbol)
+ public BooleanFieldModel(AttributeData attributeData, TypeSymbolProcessor? typeSymbol) : base(attributeData, typeSymbol)
{
switch (attributeData.ConstructorArguments.Length)
{
diff --git a/BitsKit.Generator/Models/EnumFieldModel.cs b/BitsKit.Generator/Models/EnumFieldModel.cs
index 8aea47c..f8f3238 100644
--- a/BitsKit.Generator/Models/EnumFieldModel.cs
+++ b/BitsKit.Generator/Models/EnumFieldModel.cs
@@ -1,15 +1,18 @@
-using Microsoft.CodeAnalysis;
+using System.IO;
+using Microsoft.CodeAnalysis;
namespace BitsKit.Generator.Models;
///
-/// A model representing an enum bit-field
+/// Parsed data from EnumFieldAttribute. Intermediate data only (don't store in incremental pipeline)
///
-internal sealed class EnumFieldModel : BitFieldModel
+internal class EnumFieldAttributeModel
{
+ public string? Name { get; }
public INamedTypeSymbol? EnumType { get; }
-
- public EnumFieldModel(AttributeData attributeData, TypeSymbolProcessor typeSymbol) : base(attributeData, typeSymbol)
+ public int BitCount { get; set; }
+
+ public EnumFieldAttributeModel(AttributeData attributeData)
{
switch(attributeData.ConstructorArguments.Length)
{
@@ -22,22 +25,29 @@ public EnumFieldModel(AttributeData attributeData, TypeSymbolProcessor typeSymbo
EnumType = attributeData.ConstructorArguments[2].Value as INamedTypeSymbol;
break;
default:
- return;
+ throw new InvalidDataException($"unknown number of enum attribute constructor arguments: {attributeData.ConstructorArguments.Length}");
}
+ }
+}
- ReturnType = EnumType?.ToDisplayString();
- FieldType = EnumType?.EnumUnderlyingType?.SpecialType.ToBitFieldType();
+///
+/// A model representing an enum bit-field
+///
+internal sealed record EnumFieldModel : BitFieldModel
+{
+ public EnumFieldModel(AttributeData attributeData, TypeSymbolProcessor? typeSymbol) : base(attributeData, typeSymbol)
+ {
+ var attributeModel = new EnumFieldAttributeModel(attributeData);
+ Name = attributeModel.Name!; // todo: the nullability on this is well.. wrong. padding fields have no name
+ BitCount = attributeModel.BitCount;
+
+ ReturnType = attributeModel.EnumType?.ToDisplayString();
+ FieldType = attributeModel.EnumType?.EnumUnderlyingType?.SpecialType.ToBitFieldType();
if (string.IsNullOrEmpty(Name))
FieldType = BitFieldType.Padding;
}
- public override bool HasCompilationIssues(SourceProductionContext context, TypeSymbolProcessor processor)
- {
- return DiagnosticValidator.IsNotEnumType(context, this, processor.TypeSymbol.Name) |
- base.HasCompilationIssues(context, processor);
- }
-
protected override string GetGetterTemplate()
{
return string.Format(StringConstants.ExplicitGetterTemplate, GetterSource(), ReturnType);
diff --git a/BitsKit.Generator/Models/IntegralFieldModel.cs b/BitsKit.Generator/Models/IntegralFieldModel.cs
index 98f4e90..2358555 100644
--- a/BitsKit.Generator/Models/IntegralFieldModel.cs
+++ b/BitsKit.Generator/Models/IntegralFieldModel.cs
@@ -5,7 +5,7 @@ namespace BitsKit.Generator.Models;
///
/// A model representing an integral bit-field
///
-internal sealed class IntegralFieldModel : BitFieldModel
+internal sealed record IntegralFieldModel : BitFieldModel
{
private bool IsTypeCast => this is
{
@@ -13,7 +13,7 @@ internal sealed class IntegralFieldModel : BitFieldModel
ReturnType.Length: > 0
};
- public IntegralFieldModel(AttributeData attributeData, TypeSymbolProcessor typeSymbol) : base(attributeData, typeSymbol)
+ public IntegralFieldModel(AttributeData attributeData, TypeSymbolProcessor? typeSymbol) : base(attributeData, typeSymbol)
{
switch (attributeData.ConstructorArguments.Length)
{
diff --git a/BitsKit.Generator/Properties/launchSettings.json b/BitsKit.Generator/Properties/launchSettings.json
index 517c167..3fb17af 100644
--- a/BitsKit.Generator/Properties/launchSettings.json
+++ b/BitsKit.Generator/Properties/launchSettings.json
@@ -1,8 +1,8 @@
{
"profiles": {
- "Profile 1": {
+ "Debug Source Generator - on BitsKit.Tests": {
"commandName": "DebugRoslynComponent",
- "targetProject": "..\\BitsKit.Generator.Tests\\BitsKit.Generator.Tests.csproj"
+ "targetProject": "..\\BitsKit.Tests\\BitsKit.Tests.csproj"
}
}
}
\ No newline at end of file
diff --git a/BitsKit.Generator/StringConstants.cs b/BitsKit.Generator/StringConstants.cs
index 1d77a98..96dc573 100644
--- a/BitsKit.Generator/StringConstants.cs
+++ b/BitsKit.Generator/StringConstants.cs
@@ -31,13 +31,11 @@ internal static class StringConstants
///
/// Template for the type declaration
///
- /// {0} = Modifiers
- /// {1} = Keyword
- /// {2} = Record ClassOrStructKeyword
- /// {3} = Identifier
+ /// {0} = Keyword
+ /// {1} = Identifier
///
///
- public const string TypeDeclarationTemplate = "{0} {1} {2} {3}";
+ public const string TypeDeclarationTemplate = "partial {0} {1}";
///
/// Template for a property declaration
diff --git a/BitsKit.Generator/SymbolExtensions.cs b/BitsKit.Generator/SymbolExtensions.cs
new file mode 100644
index 0000000..1cfe880
--- /dev/null
+++ b/BitsKit.Generator/SymbolExtensions.cs
@@ -0,0 +1,49 @@
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using Microsoft.CodeAnalysis;
+
+namespace BitsKit.Generator
+{
+ public static class SymbolExtensions
+ {
+ public static bool TryGetAttributeWithType(this ISymbol symbol, ITypeSymbol typeSymbol, [NotNullWhen(true)] out AttributeData? attributeData)
+ {
+ foreach (AttributeData attribute in symbol.GetAttributes())
+ {
+ if (SymbolEqualityComparer.Default.Equals(attribute.AttributeClass, typeSymbol))
+ {
+ attributeData = attribute;
+
+ return true;
+ }
+ }
+
+ attributeData = null;
+ return false;
+ }
+
+ public static bool TryGetAttributesWithBaseType(this ISymbol symbol, ITypeSymbol typeSymbol, [NotNullWhen(true)] out List? result)
+ {
+ result = null;
+
+ foreach (AttributeData attribute in symbol.GetAttributes())
+ {
+ var attributeClass = attribute.AttributeClass!;
+ do
+ {
+ if (SymbolEqualityComparer.Default.Equals(attributeClass, typeSymbol))
+ {
+ result ??= [];
+ result.Add(attribute);
+ break;
+ }
+
+ attributeClass = attributeClass.BaseType;
+ } while (attributeClass != null);
+
+ }
+
+ return result != null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/BitsKit.Generator/TypeSymbolProcessor.cs b/BitsKit.Generator/TypeSymbolProcessor.cs
index 9ca09be..d5ec8a9 100644
--- a/BitsKit.Generator/TypeSymbolProcessor.cs
+++ b/BitsKit.Generator/TypeSymbolProcessor.cs
@@ -1,5 +1,4 @@
using System.Collections.Generic;
-using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis;
using System.Text;
using BitsKit.Generator.Models;
@@ -7,38 +6,49 @@
namespace BitsKit.Generator;
-internal sealed class TypeSymbolProcessor
+internal sealed record TypeSymbolProcessor
{
- public INamedTypeSymbol TypeSymbol { get; }
- public TypeDeclarationSyntax TypeDeclaration { get; }
- public IReadOnlyList Fields => _fields;
- public BaseNamespaceDeclarationSyntax? Namespace { get; }
+ public EquatableReadOnlyList Fields { get; }
+ public string? Namespace { get; }
+
+ public BitOrder DefaultBitOrder { get; }
+ public bool IsStruct { get; }
public bool IsInlineArray { get; }
+
+ private readonly string _syntaxKeyword;
+ private readonly string _syntaxIdentifier;
- private readonly BitOrder _defaultBitOrder;
- private readonly List _fields = [];
-
- public TypeSymbolProcessor(INamedTypeSymbol typeSymbol, TypeDeclarationSyntax typeDeclaration, AttributeData attribute)
+ public TypeSymbolProcessor(INamedTypeSymbol typeSymbol, AttributeData attribute)
{
- TypeSymbol = typeSymbol;
- TypeDeclaration = typeDeclaration;
- Namespace = TypeDeclaration.Parent as BaseNamespaceDeclarationSyntax;
- IsInlineArray = HasInlineArrayAttribute();
-
- _defaultBitOrder = (BitOrder)attribute.ConstructorArguments[0].Value!;
+ _syntaxKeyword = typeSymbol.TypeKind switch
+ {
+ TypeKind.Struct when typeSymbol.IsRecord => "record struct",
+ TypeKind.Struct => "struct",
+ TypeKind.Interface => "interface",
+ TypeKind.Class when typeSymbol.IsRecord => "record",
+ _ => "class"
+ };
+ _syntaxIdentifier = typeSymbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat);
+
+ Namespace = typeSymbol.ContainingNamespace.ToDisplayString(new SymbolDisplayFormat(typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces));
+ if (string.IsNullOrWhiteSpace(Namespace)) Namespace = null;
+
+ DefaultBitOrder = (BitOrder)attribute.ConstructorArguments[0].Value!;
+ IsStruct = typeSymbol.TypeKind == TypeKind.Struct;
+ IsInlineArray = HasInlineArrayAttribute(typeSymbol);
+
+ Fields = EnumerateFields(typeSymbol);
}
public void GenerateCSharpSource(StringBuilder sb)
{
sb.AppendIndentedLine(1,
StringConstants.TypeDeclarationTemplate,
- TypeDeclaration.Modifiers,
- TypeDeclaration.Keyword.Text,
- (TypeDeclaration as RecordDeclarationSyntax)?.ClassOrStructKeyword.Text,
- TypeDeclaration.Identifier.Text)
+ _syntaxKeyword,
+ _syntaxIdentifier)
.AppendIndentedLine(1, "{");
- foreach (BitFieldModel field in _fields)
+ foreach (BitFieldModel field in Fields)
field.GenerateCSharpSource(sb);
sb.RemoveLastLine()
@@ -46,11 +56,11 @@ public void GenerateCSharpSource(StringBuilder sb)
.AppendLine();
}
- public int EnumerateFields()
+ private EquatableReadOnlyList EnumerateFields(ITypeSymbol typeSymbol)
{
- _fields.Clear();
+ var output = new List();
- foreach (IFieldSymbol field in TypeSymbol.GetMembers().OfType())
+ foreach (IFieldSymbol field in typeSymbol.GetMembers().OfType())
{
if (!IsValidFieldSymbol(field))
continue;
@@ -74,52 +84,26 @@ _ when field.Type.IsSupportedIntegralType() => IsInlineArray ?
if (backingType == BackingFieldType.Invalid)
continue;
- CreateBitFieldModels(field, backingType);
+ var backingModel = new BackingFieldModel(field, backingType);
+ CreateBitFieldModels(output, field, backingModel);
}
- return _fields.Count;
+ return output.ToEquatableReadOnlyList();
}
- public bool ReportCompilationIssues(SourceProductionContext context)
- {
- bool hasCompilationIssues = false;
-
- if (DiagnosticValidator.IsNotPartial(context, TypeDeclaration, TypeSymbol.Name) |
- DiagnosticValidator.IsNested(context, TypeDeclaration, TypeSymbol.Name))
- hasCompilationIssues = true;
-
- foreach (BitFieldModel field in _fields)
- {
- if (field.HasCompilationIssues(context, this))
- hasCompilationIssues = true;
- }
-
- return hasCompilationIssues;
- }
-
- private void CreateBitFieldModels(IFieldSymbol backingField, BackingFieldType backingType)
+ private void CreateBitFieldModels(List output, IFieldSymbol backingField, BackingFieldModel backingModel)
{
int offset = 0;
foreach (AttributeData attribute in backingField.GetAttributes())
{
- string? attributeType = attribute.AttributeClass?.ToDisplayString();
-
- BitFieldModel? bitField = attributeType switch
- {
- StringConstants.BitFieldAttributeFullName => new IntegralFieldModel(attribute, this),
- StringConstants.BooleanFieldAttributeFullName => new BooleanFieldModel(attribute, this),
- StringConstants.EnumFieldAttributeFullName => new EnumFieldModel(attribute, this),
- _ => null
- };
+ BitFieldModel? bitField = CreateBitFieldFromAttribute(attribute, this);
if (bitField == null)
continue;
- bitField.BackingField = backingField;
- bitField.BackingFieldType = backingType;
+ bitField.BackingField = backingModel;
bitField.BitOffset = offset;
- bitField.BitOrder = _defaultBitOrder;
// padding fields are not generated
if (bitField is not { FieldType: BitFieldType.Padding })
@@ -129,24 +113,37 @@ private void CreateBitFieldModels(IFieldSymbol backingField, BackingFieldType ba
bitField.BitOrder ^= BitOrder.MostSignificant;
// integrals inherit their field type from their backing field
- if (backingType == BackingFieldType.Integral)
+ if (backingModel.Type == BackingFieldType.Integral)
bitField.FieldType = backingField.Type.SpecialType.ToBitFieldType();
// allow inline arrays to infer their type
- if (backingType == BackingFieldType.InlineArray)
+ if (backingModel.Type == BackingFieldType.InlineArray)
bitField.FieldType ??= backingField.Type.SpecialType.ToBitFieldType();
// add to list of fields to generate
- _fields.Add(bitField);
+ output.Add(bitField);
}
offset += bitField.BitCount;
}
}
+
+ public static BitFieldModel? CreateBitFieldFromAttribute(AttributeData attribute, TypeSymbolProcessor processor)
+ {
+ string? attributeType = attribute.AttributeClass?.ToDisplayString();
+
+ return attributeType switch
+ {
+ StringConstants.BitFieldAttributeFullName => new IntegralFieldModel(attribute, processor),
+ StringConstants.BooleanFieldAttributeFullName => new BooleanFieldModel(attribute, processor),
+ StringConstants.EnumFieldAttributeFullName => new EnumFieldModel(attribute, processor),
+ _ => null
+ };
+ }
- private bool HasInlineArrayAttribute()
+ private static bool HasInlineArrayAttribute(ITypeSymbol typeSymbol)
{
- return (int?)TypeSymbol
+ return (int?)typeSymbol
.GetAttributes()
.FirstOrDefault(a => a.AttributeClass?.ToDisplayString() == StringConstants.InlineArrayAttributeFullName)?
.ConstructorArguments[0].Value > 0;
diff --git a/BitsKit.Tests/BitsKit.Tests.csproj b/BitsKit.Tests/BitsKit.Tests.csproj
index ca31b2e..da39c34 100644
--- a/BitsKit.Tests/BitsKit.Tests.csproj
+++ b/BitsKit.Tests/BitsKit.Tests.csproj
@@ -2,12 +2,9 @@
net6.0;net7.0;net8.0
- enable
false
- AnyCPU;x86;x64
true
True
- preview
diff --git a/BitsKit.Tests/GeneratorTests.cs b/BitsKit.Tests/GeneratorTests.cs
index 3af0e70..62aa4d4 100644
--- a/BitsKit.Tests/GeneratorTests.cs
+++ b/BitsKit.Tests/GeneratorTests.cs
@@ -242,7 +242,7 @@ public ref partial struct BitFieldReadOnly
";
string expected = @"
- public ref partial struct BitFieldReadOnly
+ partial struct BitFieldReadOnly
{
public Int32 Generated00
{
@@ -261,7 +261,7 @@ public Int32 Generated20
}
";
- string? sourceOutput = GenerateSourceAndTest(source, new BitObjectGenerator());
+ string? sourceOutput = GenerateSourceAndTest(source);
Assert.IsTrue(Helpers.StrEqualExWhiteSpace(sourceOutput, expected));
}
@@ -285,7 +285,7 @@ public readonly ref partial struct BitFieldReadOnly
";
string expected = @"
- public readonly ref partial struct BitFieldReadOnly
+ partial struct BitFieldReadOnly
{
public Int32 Generated00
{
@@ -304,7 +304,7 @@ public Int32 Generated20
}
";
- string? sourceOutput = GenerateSourceAndTest(source, new BitObjectGenerator());
+ string? sourceOutput = GenerateSourceAndTest(source);
Assert.IsTrue(Helpers.StrEqualExWhiteSpace(sourceOutput, expected));
}
@@ -344,7 +344,7 @@ public unsafe partial class BitFieldGeneratorTest
";
string expected = @"
- public unsafe partial class BitFieldGeneratorTest
+ partial class BitFieldGeneratorTest
{
public Int32 Generated01
{
@@ -461,7 +461,7 @@ public UIntPtr Generated19
}
";
- string? sourceOutput = GenerateSourceAndTest(source, new BitObjectGenerator());
+ string? sourceOutput = GenerateSourceAndTest(source);
Assert.IsTrue(Helpers.StrEqualExWhiteSpace(sourceOutput, expected));
}
@@ -490,7 +490,7 @@ public unsafe partial class BitFieldGeneratorTest
";
string expected = @"
- public unsafe partial class BitFieldGeneratorTest
+ partial class BitFieldGeneratorTest
{
public Int32 Generated01
{
@@ -553,7 +553,7 @@ private protected Int32 Generated0A
}
";
- string? sourceOutput = GenerateSourceAndTest(source, new BitObjectGenerator());
+ string? sourceOutput = GenerateSourceAndTest(source);
Assert.IsTrue(Helpers.StrEqualExWhiteSpace(sourceOutput, expected));
#endif
@@ -581,7 +581,7 @@ public unsafe ref partial struct BooleanGeneratorTest
";
string expected = @"
- public unsafe ref partial struct BooleanGeneratorTest
+ partial struct BooleanGeneratorTest
{
public System.Boolean Generated01
{
@@ -608,7 +608,7 @@ public unsafe System.Boolean Generated30
}
";
- string? sourceOutput = GenerateSourceAndTest(source, new BitObjectGenerator());
+ string? sourceOutput = GenerateSourceAndTest(source);
Assert.IsTrue(Helpers.StrEqualExWhiteSpace(sourceOutput, expected));
}
@@ -639,7 +639,7 @@ public unsafe ref partial struct EnumGeneratorTest
";
string expected = @"
- public unsafe ref partial struct EnumGeneratorTest
+ partial struct EnumGeneratorTest
{
public BitsKit.Tests.TestEnum Generated00
{
@@ -689,7 +689,7 @@ public unsafe BitsKit.Tests.TestEnum Generated31
}
";
- string? sourceOutput = GenerateSourceAndTest(source, new BitObjectGenerator());
+ string? sourceOutput = GenerateSourceAndTest(source);
Assert.IsTrue(Helpers.StrEqualExWhiteSpace(sourceOutput, expected));
}
@@ -710,7 +710,7 @@ public ref partial struct BitFieldIntegerConversion
";
string expected = @"
- public ref partial struct BitFieldIntegerConversion
+ partial struct BitFieldIntegerConversion
{
public Byte Generated00
{
@@ -726,7 +726,7 @@ public Int32 Generated10
}
";
- string? sourceOutput = GenerateSourceAndTest(source, new BitObjectGenerator());
+ string? sourceOutput = GenerateSourceAndTest(source);
Assert.IsTrue(Helpers.StrEqualExWhiteSpace(sourceOutput, expected));
}
@@ -750,7 +750,7 @@ public partial struct BitFieldInlineArray
";
string expected = @"
- public partial struct BitFieldInlineArray
+ partial struct BitFieldInlineArray
{
public Int32 Generated00
{
@@ -778,51 +778,93 @@ public System.Boolean Generated03
}
";
- string? sourceOutput = GenerateSourceAndTest(source, new BitObjectGenerator());
+ string? sourceOutput = GenerateSourceAndTest(source);
Assert.IsTrue(Helpers.StrEqualExWhiteSpace(sourceOutput, expected));
}
#endif
- private static string? GenerateSourceAndTest(string source, IIncrementalGenerator generator)
+ private static string? GenerateSourceAndTest(string source)
{
var references = AppDomain.CurrentDomain.GetAssemblies()
.Where(assembly => !assembly.IsDynamic)
.Select(assembly => MetadataReference.CreateFromFile(assembly.Location))
.Cast();
- CSharpCompilation compilation = CSharpCompilation.Create("compilation",
- [CSharpSyntaxTree.ParseText(Helpers.GeneratorTestHeader + source)],
- references,
- new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, allowUnsafe: true));
-
- GeneratorDriver driver = CSharpGeneratorDriver
- .Create(generator)
- .RunGeneratorsAndUpdateCompilation(compilation, out Compilation? outputCompilation, out ImmutableArray diagnostics);
-
- var diag = outputCompilation.GetDiagnostics();
-
- Assert.IsTrue(diagnostics.IsEmpty); // there were no diagnostics created by the generators
- Assert.AreEqual(outputCompilation.SyntaxTrees.Count(), 2); // we have two syntax trees, the original 'user' provided one, and the one added by the generator
- Assert.IsTrue(outputCompilation.GetDiagnostics().IsEmpty); // verify the compilation with the added source has no diagnostics
-
- GeneratorDriverRunResult runResult = driver.GetRunResult();
-
- Assert.AreEqual(runResult.GeneratedTrees.Length, 1);
- Assert.IsTrue(runResult.Diagnostics.IsEmpty);
+ CSharpCompilation compilation = CSharpCompilation.Create(
+ assemblyName: "BitsKit.Tests.InMemory",
+ syntaxTrees: [CSharpSyntaxTree.ParseText(Helpers.GeneratorTestHeader + source)],
+ references: references,
+ options: new CSharpCompilationOptions(
+ OutputKind.DynamicallyLinkedLibrary, allowUnsafe: true
+ )
+ );
+
+ var insignificantEditComp = compilation.Clone()
+ .AddSyntaxTrees(CSharpSyntaxTree.ParseText("// dummy"));
+
+ GeneratorDriver driver = CSharpGeneratorDriver.Create(
+ generators: [new BitObjectGenerator().AsSourceGenerator()],
+ driverOptions: new GeneratorDriverOptions(default, trackIncrementalGeneratorSteps: true));
+
+ var run1Result = RunGenerator(ref driver, compilation);
+ var run2Result = RunGenerator(ref driver, insignificantEditComp);
+
+ foreach (var outputStep in run2Result.Results[0].TrackedOutputSteps)
+ {
+ AssertGeneratorDidntRun(outputStep.Value);
+ }
+ AssertGeneratorDidntRun(run2Result.Results[0].TrackedSteps["Main"]);
+
+ Assert.AreEqual(run1Result.GeneratedTrees.Length, 1);
+ Assert.IsTrue(run1Result.Diagnostics.IsEmpty);
- GeneratorRunResult generatorResult = runResult.Results[0];
+ GeneratorRunResult generatorResult = run1Result.Results[0];
Assert.AreEqual(generatorResult.Generator.GetGeneratorType(), typeof(BitObjectGenerator));
Assert.IsTrue(generatorResult.Diagnostics.IsEmpty);
Assert.AreEqual(generatorResult.GeneratedSources.Length, 1);
Assert.IsTrue(generatorResult.Exception is null);
string sourceOutput = generatorResult.GeneratedSources[0].SourceText.ToString();
-
return TruncateUsings(sourceOutput);
}
-
+
+ private static GeneratorDriverRunResult RunGenerator(
+ ref GeneratorDriver driver,
+ Compilation compilation
+ )
+ {
+ driver = driver
+ .RunGeneratorsAndUpdateCompilation(
+ compilation,
+ out var outputCompilation,
+ out var diagnostics
+ );
+
+ // verify the compilation with the added source has no diagnostics
+ Assert.IsFalse(
+ outputCompilation
+ .GetDiagnostics()
+ .Any(d => d.Severity is DiagnosticSeverity.Error or DiagnosticSeverity.Warning)
+ );
+
+ // there were no diagnostics created by the generators
+ Assert.IsTrue(diagnostics.IsEmpty);
+
+ return driver.GetRunResult();
+ }
+
+ private static void AssertGeneratorDidntRun(ImmutableArray steps)
+ {
+ var outputs = steps.SelectMany(o => o.Outputs);
+ foreach (var output in outputs)
+ {
+ Assert.IsTrue(output.Reason == IncrementalStepRunReason.Unchanged ||
+ output.Reason == IncrementalStepRunReason.Cached);
+ }
+ }
+
private static string? TruncateUsings(string? source)
{
if (string.IsNullOrEmpty(source))
@@ -833,8 +875,8 @@ public System.Boolean Generated03
return source[eol..].TrimStart();
}
-
- /// Loads the BitsKit.Generator assembly into the current AppDomain
- [BitObject(BitOrder.LeastSignificant)]
- private readonly partial struct BitsKitGeneratorStub { }
}
+
+/// Loads the BitsKit.Generator assembly into the current AppDomain
+[BitObject(BitOrder.LeastSignificant)]
+internal readonly partial struct BitsKitGeneratorStub { }
\ No newline at end of file
diff --git a/BitsKit/BitsKit.csproj b/BitsKit/BitsKit.csproj
index 5a78919..d41796d 100644
--- a/BitsKit/BitsKit.csproj
+++ b/BitsKit/BitsKit.csproj
@@ -3,10 +3,7 @@
netstandard2.1;net6.0;net7.0;net8.0
enable
- enable
- AnyCPU;x86;x64
True
- preview
true
diff --git a/Directory.Build.props b/Directory.Build.props
index 2ab06a2..4e725a0 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -22,7 +22,7 @@
enable
- preview
+ 12