Overview​
The BitFieldProperty Parser is responsible for converting a C# PropertyDeclarationSyntax node (annotated with one of the bit-field attributes) into a custom data model (BaseBitFieldPropertyInfo). This model contains all the metadata needed for subsequent BitFieldProperty Aggregation and Property Syntax Generation steps.
Key Components​
IBitFieldPropertyParser- Defines the contract for property parsers. Each parser can decide if a given property + attribute is a �candidate� and perform parsing logic.
- The method
Parse(...)returns aBaseBitFieldPropertyInfoif it recognizes and successfully parses the property.
BaseBitFieldPropertyInfoParser(abstract)- Implements
IBitFieldPropertyParserand provides common logic to parse a property�s attribute data, detect setter/init accessors, and build aBitFieldPropertyInfo. - Subclasses can override
IsCandidate(...)orParseCore(...)to tailor their parsing logic.
- Implements
BitFieldPropertyInfoParser(concrete)- A default implementation of
BaseBitFieldPropertyInfoParser. It calls the base logic to parse the property�s attribute, create aBitFieldPropertyInfo, and store essential details like whether the property has aninit;orset;accessor.
- A default implementation of
BaseBitFieldPropertyInfo- An abstract data holder. The final parser returns an instance of this type�usually a subclass like
BitFieldPropertyInfo. - Stores references to the associated C# symbols (property, containing type), as well as parser results (
IAttributeParsedResult).
- An abstract data holder. The final parser returns an instance of this type�usually a subclass like
PropertyBitPackGeneratorContext.ParseBitFieldProperty(...)- Iterates over all registered
IBitFieldPropertyParsers. - For each parser, calls
Parse(...). - Returns the first non-null
BaseBitFieldPropertyInfo.
- Iterates over all registered
Parsing Flow​
Check if Parser is a Candidate
- Each
IBitFieldPropertyParserrunsIsCandidate(...). It can inspect the property syntax, the attribute data, and other conditions.
- Each
Call
Parse(...)- If
IsCandidateis true, the context callsParse(...). - Default logic in
BaseBitFieldPropertyInfoParser.Parse(...):- Extract Attributes: Calls
Context.TryParseAttribute(...)to retrieve anIAttributeParsedResult. - Extract Accessors: Looks for
set;orinit;accessor modifiers. - Create
BitFieldPropertyInfo: Combines property symbol info and the parsed attribute result.
- Extract Attributes: Calls
- If
Return
BaseBitFieldPropertyInfo- If parsing succeeds,
Parse(...)returns a newBitFieldPropertyInfo; otherwise,nullif it�s not recognized.
- If parsing succeeds,
IBitFieldPropertyParser Interface​
internal interface IBitFieldPropertyParser
{
bool IsCandidate(
PropertyDeclarationSyntax propertyDeclarationSyntax,
AttributeData candidateAttribute,
AttributeSyntax attributeSyntax,
SemanticModel semanticModel);
BaseBitFieldPropertyInfo? Parse(
PropertyDeclarationSyntax propertyDeclarationSyntax,
AttributeData candidateAttribute,
AttributeSyntax attributeSyntax,
SemanticModel semanticModel,
in ImmutableArrayBuilder<Diagnostic> diagnostics);
}
Methods​
IsCandidate(...): Quickly determines if this parser should handle the given property + attribute.Parse(...): Performs the detailed parse (e.g., callsTryParseAttribute(...), reads property symbol details). Returns aBaseBitFieldPropertyInfoif successful.
BaseBitFieldPropertyInfoParser​
BaseBitFieldPropertyInfoParser implements most of IBitFieldPropertyParser and includes the ability to bind a PropertyBitPackGeneratorContext.
internal abstract class BaseBitFieldPropertyInfoParser : IBitFieldPropertyParser, IContextBindable
{
private PropertyBitPackGeneratorContext? _context;
public PropertyBitPackGeneratorContext Context
{
get
{
Debug.Assert(_context is not null);
return _context!;
}
}
void IContextBindable.BindContext(PropertyBitPackGeneratorContext context) => _context = context;
public virtual bool IsCandidate(
PropertyDeclarationSyntax propertyDeclarationSyntax,
AttributeData candidateAttribute,
AttributeSyntax attributeSyntax,
SemanticModel semanticModel
) => true;
public BaseBitFieldPropertyInfo? Parse(
PropertyDeclarationSyntax propertyDeclarationSyntax,
AttributeData candidateAttribute,
AttributeSyntax attributeSyntax,
SemanticModel semanticModel,
in ImmutableArrayBuilder<Diagnostic> diagnostics
)
{
return ParseCore(
propertyDeclarationSyntax,
candidateAttribute,
attributeSyntax,
semanticModel,
diagnostics
);
}
protected virtual BaseBitFieldPropertyInfo? ParseCore(
PropertyDeclarationSyntax propertyDeclarationSyntax,
AttributeData candidateAttribute,
AttributeSyntax attributeSyntax,
SemanticModel semanticModel,
in ImmutableArrayBuilder<Diagnostic> diagnostics
)
{
var setterOrInitModifiers = ExtraxtSetterOrInitModifiers(
propertyDeclarationSyntax,
out var hasInitOrSet,
out var isInit
);
if (!Context.TryParseAttribute(candidateAttribute, attributeSyntax, propertyDeclarationSyntax, semanticModel, diagnostics, out var attributeResult))
{
return null;
}
if (semanticModel.GetDeclaredSymbol(propertyDeclarationSyntax) is not IPropertySymbol propertySymbol)
{
return null;
}
return new BitFieldPropertyInfo(
propertyDeclarationSyntax,
attributeResult,
isInit,
hasInitOrSet,
setterOrInitModifiers,
propertySymbol
);
}
protected virtual SyntaxTokenList ExtraxtSetterOrInitModifiers(
PropertyDeclarationSyntax propertyDeclaration,
out bool hasInitOrSet,
out bool isInit
)
{
hasInitOrSet = false;
isInit = false;
if (propertyDeclaration.AccessorList?.Accessors is not SyntaxList<AccessorDeclarationSyntax> accessors)
{
return default;
}
isInit = accessors.Any(static accessor => accessor.IsKind(SyntaxKind.InitAccessorDeclaration));
hasInitOrSet = isInit || accessors.Any(static accessor => accessor.IsKind(SyntaxKind.SetAccessorDeclaration));
var setterOrInitModifiers = accessors
.Where(static accessor =>
accessor.IsKind(SyntaxKind.InitAccessorDeclaration) ||
accessor.IsKind(SyntaxKind.SetAccessorDeclaration)
)
.Select(static accessor => accessor.Modifiers)
.FirstOrDefault();
return setterOrInitModifiers;
}
}
Notable Details​
Context Binding:
UsesIContextBindable.BindContext(...)to store a reference to thePropertyBitPackGeneratorContext.ParseCore(...):- Calls
Context.TryParseAttribute(...)to get anIAttributeParsedResult. - Attempts to get the declared symbol (
IPropertySymbol) from theSemanticModel. - Builds a
BitFieldPropertyInfo.
- Calls
BitFieldPropertyInfoParser​
A final, concrete parser that subclasses BaseBitFieldPropertyInfoParser. It doesn�t override any logic, so the base functionality is applied directly:
internal sealed class BitFieldPropertyInfoParser : BaseBitFieldPropertyInfoParser
{
}
BaseBitFieldPropertyInfo and BitFieldPropertyInfo​
Parsed properties end up as BaseBitFieldPropertyInfo instances. The default is BitFieldPropertyInfo:
internal abstract class BaseBitFieldPropertyInfo
{
public abstract IAttributeParsedResult AttributeParsedResult { get; }
public abstract bool IsInit { get; }
public abstract bool HasInitOrSet { get; }
public abstract SyntaxTokenList SetterOrInitModifiers { get; }
public abstract IPropertySymbol PropertySymbol { get; }
public abstract PropertyDeclarationSyntax PropertyDeclarationSyntax { get; }
public ITypeSymbol PropertyType => PropertySymbol.Type;
public INamedTypeSymbol Owner => PropertySymbol.ContainingType;
public override string ToString()
{
var setterOrInitter = HasInitOrSet
? IsInit
? "init;"
: "set;"
: string.Empty;
if (HasInitOrSet)
{
setterOrInitter = $"{SetterOrInitModifiers.ToFullString()} {setterOrInitter}";
}
return $"{Owner} => [{AttributeParsedResult}] {PropertySymbol.Type.Name} {PropertySymbol.Name} {{ get; {setterOrInitter} }}";
}
}
internal sealed class BitFieldPropertyInfo(
PropertyDeclarationSyntax propertyDeclarationSyntax,
IAttributeParsedResult attributeParsedResult,
bool isInit,
bool hasInitOrSet,
SyntaxTokenList setterOrInitModifiers,
IPropertySymbol propertySymbol
) : BaseBitFieldPropertyInfo
{
public override PropertyDeclarationSyntax PropertyDeclarationSyntax { get; } = propertyDeclarationSyntax;
public override IAttributeParsedResult AttributeParsedResult { get; } = attributeParsedResult;
public override bool IsInit { get; } = isInit;
public override bool HasInitOrSet { get; } = hasInitOrSet;
public override SyntaxTokenList SetterOrInitModifiers { get; } = setterOrInitModifiers;
public override IPropertySymbol PropertySymbol { get; } = propertySymbol;
}
Properties​
IAttributeParsedResult: The outcome of the attribute parsing phase (contains bits count, field info, etc.).IsInit/HasInitOrSet: Tracks whether the property has aninit;orset;accessor.PropertySymbol: The Roslyn symbol for the property.PropertyDeclarationSyntax: The original syntax node.
How It Fits Together​
PropertyBitPackGeneratorContext.ParseBitFieldProperty(...)- Loops over all
BitFieldPropertyParsersin the context (includingBitFieldPropertyInfoParser). - Calls
Parse(...). The first parser that returns non-null is chosen.
- Loops over all
Parser
- Checks if the property is a candidate.
- Calls the base
ParseCore(...), which usesContext.TryParseAttribute(...).
Generated Model
- A
BitFieldPropertyInfois returned, combining attribute data + property symbol details.
- A
Aggregation & Generation
- The
BitFieldPropertyInfoobjects are aggregated byIBitFieldPropertyAggregators and then turned into final C# source byIPropertiesSyntaxGenerators.
- The
Summary​
- BitField Property Parsing is a crucial step that links attributes to property symbols.
IBitFieldPropertyParserchecks if a property is recognized and constructs a data model for further processing.BaseBitFieldPropertyInfoParserhandles the heavy lifting of calling attribute parsers and extracting relevant property details.- The default
BitFieldPropertyInfoParsersimply defers to the base logic, but you can extend or replace it with custom parsing.
This modular approach keeps PropertyBitPack flexible and allows you to introduce specialized parsers for different use cases or advanced scenarios.