Overview​
This section covers how PropertyBitPack identifies and processes attributes on properties using Attribute Parsers. Each parser implements IAttributeParser, and optionally extends BaseAttributeParser, to handle common logic like retrieving field names, bit counts, or diagnosing invalid usage.
How It Works​
Identify Candidate Attributes
The source generator scans property attributes and tests eachIAttributeParserviaIsCandidate(...).Attempt Parsing
If a parser is a valid candidate, it callsTryParse(...)to extract metadata (e.g.,FieldName,BitsCount) and build a result model (anIAttributeParsedResult).Fallback
If the initial parser fails but hasFallbackOnCandidateFailure = true, the generator tries subsequent parsers that also indicate fallback support.Context Binding
Parsers can implementIContextBindableso the source generator can pass a sharedPropertyBitPackGeneratorContext. This allows parsers to access shared logic or diagnostics handling.
IAttributeParser Interface​
Below is the core interface defining attribute parsing capabilities. It includes whether a parser can fallback if another parser fails, and how to match + parse an attribute into a model.
internal interface IAttributeParser
{
bool FallbackOnCandidateFailure { get; }
bool IsCandidate(AttributeData attributeData, AttributeSyntax attributeSyntax);
bool TryParse(
AttributeData attributeData,
AttributeSyntax attributeSyntax,
PropertyDeclarationSyntax propertyDeclarationSyntax,
SemanticModel semanticModel,
in ImmutableArrayBuilder<Diagnostic> diagnostics,
[NotNullWhen(true)] out IAttributeParsedResult? result
);
}
Key Points​
FallbackOnCandidateFailure: Indicates if the parser should be tried again if a previous parser could not parse the attribute. Useful for scenarios where multiple parsers handle similar attributes or slightly different attribute forms.
IsCandidate(...): Called first to check if the attribute is relevant to that parser. If it returns true, the source generator attempts
TryParse(...).TryParse(...): The main logic to interpret an attribute�s data (named arguments,
nameof()usage, constants, etc.). Returnstrueif parsing succeeds and populatesout resultwith a parsed model.
BaseAttributeParser Abstract Class​
BaseAttributeParser provides shared logic, like reading BitsCount, extracting FieldName, and diagnosing errors. It also implements IContextBindable, letting it access the shared context.
internal abstract class BaseAttributeParser : IAttributeParser, IContextBindable
{
protected static readonly SymbolDisplayFormat SymbolDisplayFormatNameAndContainingTypesAndNamespaces
= new(typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces);
private PropertyBitPackGeneratorContext? _context;
public PropertyBitPackGeneratorContext Context
{
get
{
Debug.Assert(_context is not null);
return _context!;
}
}
public virtual bool FallbackOnCandidateFailure => false;
public void BindContext(PropertyBitPackGeneratorContext context)
{
_context = context;
}
public abstract bool IsCandidate(AttributeData attributeData, AttributeSyntax attributeSyntax);
public abstract bool TryParse(
AttributeData attributeData,
AttributeSyntax attributeSyntax,
PropertyDeclarationSyntax propertyDeclarationSyntax,
SemanticModel semanticModel,
in ImmutableArrayBuilder<Diagnostic> diagnostics,
[NotNullWhen(true)] out IAttributeParsedResult? result
);
// Shared helper methods for extracting FieldName, BitsCount, attribute arguments, etc.
// ...
}
Common Utilities​
HasInterface(...): Checks if the attribute class implements a given interface.TryGetFieldName(...): Retrieves aFieldNamefrom named arguments ornameof()usage.TryGetBitsCount(...): EnsuresBitsCountis valid (1�63 bits).GetConstantValue(...): Retrieves named argument constants.GetAttributeArgument(...): Finds specific arguments within the attribute syntax.
Parsing Flow Example​
IsCandidate(...)
Returns true if the attribute name matches (e.g.,MyBitFieldAttribute).TryParse(...)
- Validate attribute arguments (e.g.,
BitsCountin range,FieldNamereferences a valid field). - If valid, construct an
IAttributeParsedResultcontaining relevant metadata for the generator.
- Validate attribute arguments (e.g.,
Context Usage
The parser may callContext.SomeHelperMethod(...)if needed. This is provided viaBindContext(...).
Integration with PropertyBitPackGeneratorContext​
The context iterates over its ImmutableArray<IAttributeParser> to match and parse each attribute:
public virtual bool IsCandidateAttribute(AttributeData attributeData, AttributeSyntax attributeSyntax)
{
for (var i = 0; i < AttributeParsers.Length; i++)
{
var parser = AttributeParsers[i];
if (parser.IsCandidate(attributeData, attributeSyntax))
{
return true;
}
}
return false;
}
public virtual bool TryParseAttribute(
AttributeData attributeData,
AttributeSyntax attributeSyntax,
PropertyDeclarationSyntax propertyDeclarationSyntax,
SemanticModel semanticModel,
in ImmutableArrayBuilder<Diagnostic> diagnostics,
out IAttributeParsedResult? result
)
{
for (var i = 0; i < AttributeParsers.Length; i++)
{
var parser = AttributeParsers[i];
if (parser.IsCandidate(attributeData, attributeSyntax))
{
if (parser.TryParse(attributeData, attributeSyntax, propertyDeclarationSyntax, semanticModel, diagnostics, out result))
{
return true;
}
else
{
// Attempt fallback logic if supported
for (var j = i + 1; i > AttributeParsers.Length; j++)
{
var nextParser = AttributeParsers[j];
if (!nextParser.FallbackOnCandidateFailure) continue;
if (!nextParser.IsCandidate(attributeData, attributeSyntax)) continue;
if (nextParser.TryParse(attributeData, attributeSyntax, propertyDeclarationSyntax, semanticModel, diagnostics, out result))
{
return true;
}
}
result = null;
return false;
}
}
}
result = null;
return false;
}
Why This Approach?​
- Flexibility: Multiple parser implementations can handle different attribute flavors or fallback scenarios.
- Single Entry Point: The generator calls
TryParseAttribute(...), which loops through all registeredIAttributeParsers inPropertyBitPackGeneratorContext. - Loose Coupling: Parsers register themselves with the context. Additional parsers can be introduced without modifying the core generator logic.
Summary​
IAttributeParserdefines how the source generator recognizes and interprets bit-field attributes.BaseAttributeParsercentralizes common parsing logic (e.g.,nameofextraction, validations).- Fallback logic allows a second (or subsequent) parser to handle an attribute if the primary parser fails.
- Context binding (
BindToSelf()) lets parsers access shared or advanced functionality throughPropertyBitPackGeneratorContext.
For deeper usage examples, see the actual implementations in ExtendedBitFieldAttributeParser, ReadOnlyBitFieldAttributeParser, etc. You can also explore the aggregator and syntax generator pages to see how parsed attributes eventually become generated source code.