Skip to content

feat: add [OptimizedEnumIndex] attribute for generated property lookups#5

Merged
ncipollina merged 4 commits intomainfrom
feat/optimized-enum-index-attribute
Apr 1, 2026
Merged

feat: add [OptimizedEnumIndex] attribute for generated property lookups#5
ncipollina merged 4 commits intomainfrom
feat/optimized-enum-index-attribute

Conversation

@ncipollina
Copy link
Copy Markdown
Contributor

Summary

Adds [OptimizedEnumIndex] — an attribute that can be placed on properties in intermediate OptimizedEnum base classes to generate pre-built From{PropertyName} / TryFrom{PropertyName} dictionary lookups on every concrete subclass. This closes the gap where GetAll() is unavailable on the base class, making hand-written per-property lookups in every concrete type unnecessary.

Also updates the local pack scripts to use a -local.<n> pre-release suffix and bumps VersionPrefix to 1.2.0.

Changes

New API

  • OptimizedEnumIndexAttribute — place on a base class property to opt it into index generation
  • StringComparison property on the attribute controls the StringComparer used for string properties (default: Ordinal); ignored for non-string types

Generator

  • IndexedPropertyInfo model record carrying property name, type, and comparer expression
  • EnumSyntaxProvider.CollectIndexedProperties — walks the base chain, validates IEquatable<T>, resolves the StringComparer expression
  • OptimizedEnum.scriban — emits s_by{PropertyName} dictionary and From{PropertyName} / TryFrom{PropertyName} methods for each indexed property
  • New diagnostic OE0202 (Warning) — emitted when [OptimizedEnumIndex] is applied to a property whose type does not implement IEquatable<T>

Version / tooling

  • VersionPrefix bumped 1.1.11.2.0
  • pack-local.sh and pack-local.ps1 now produce {version}-local.{n} instead of {version}.{n}

Validation

  • 57/57 tests pass across net8.0, net9.0, net10.0
  • Snapshot tests cover: single string index, multiple indexes with mixed StringComparison, and OE0202 for a non-equatable property type

Release Notes

v1.2.0 — New [OptimizedEnumIndex] attribute. Decorate a property on an intermediate base class to generate From{PropertyName} and TryFrom{PropertyName} O(1) dictionary lookups on all concrete subclasses. String properties support a configurable StringComparison; all other IEquatable<T> types are supported. Non-equatable types emit diagnostic OE0202.

🤖 Generated with Claude Code

Introduces OptimizedEnumIndexAttribute, which can be placed on properties
in intermediate OptimizedEnum base classes to generate pre-built dictionary
lookups (From{PropertyName} / TryFrom{PropertyName}) on every concrete
subclass. Supports any IEquatable<T> property type; string properties
respect a configurable StringComparison (default: Ordinal).

Also bumps VersionPrefix to 1.2.0 and updates local pack scripts to use
a -local.<n> pre-release suffix instead of a fourth version segment.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a new opt-in indexing feature for OptimizedEnum-derived types via a new [OptimizedEnumIndex] attribute applied to properties on intermediate base classes, enabling the generator to emit per-property From{PropertyName} / TryFrom{PropertyName} dictionary lookups on concrete subclasses. It also bumps the package version to 1.2.0 and updates generator tests/snapshots accordingly.

Changes:

  • Added OptimizedEnumIndexAttribute (with configurable StringComparison for string keys).
  • Extended the generator pipeline/model/template to collect indexed base properties and emit corresponding dictionaries + lookup methods.
  • Added new diagnostic OE0202 and expanded snapshot/verification tests; bumped VersionPrefix to 1.2.0.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
tests/LayeredCraft.OptimizedEnums.Generator.Tests/Snapshots/GeneratorVerifyTests.Warning_OE0202_IndexProperty_NonEquatableType_IsEmitted#MyApp.Domain.Status.g.verified.cs Snapshot for codegen when OE0202 is produced (index skipped).
tests/LayeredCraft.OptimizedEnums.Generator.Tests/Snapshots/GeneratorVerifyTests.Warning_OE0202_IndexProperty_NonEquatableType_IsEmitted.verified.txt Snapshot for OE0202 diagnostic output.
tests/LayeredCraft.OptimizedEnums.Generator.Tests/Snapshots/GeneratorVerifyTests.IndexedProperty_StringIndex_GeneratesLookupMethods#MyApp.Domain.ForceAlignment.g.verified.cs Snapshot validating generated lookup for a single indexed string property.
tests/LayeredCraft.OptimizedEnums.Generator.Tests/Snapshots/GeneratorVerifyTests.IndexedProperty_MultipleIndexes_GeneratesAllLookupMethods#MyApp.Domain.ForceAlignment.g.verified.cs Snapshot validating multiple indexed properties with differing StringComparison.
tests/LayeredCraft.OptimizedEnums.Generator.Tests/GeneratorVerifyTests.cs Adds verification tests covering indexed-property generation and OE0202.
src/LayeredCraft.OptimizedEnums/OptimizedEnumIndexAttribute.cs New public attribute API for opting properties into index generation.
src/LayeredCraft.OptimizedEnums.Generator/Templates/OptimizedEnum.scriban Emits per-index dictionaries and From*/TryFrom* methods.
src/LayeredCraft.OptimizedEnums.Generator/Providers/EnumSyntaxProvider.cs Collects indexed properties from base chain; emits OE0202; resolves StringComparer.
src/LayeredCraft.OptimizedEnums.Generator/Models/IndexedPropertyInfo.cs New model record describing indexed properties for template emission.
src/LayeredCraft.OptimizedEnums.Generator/Models/EnumInfo.cs Carries indexed properties through the generator pipeline.
src/LayeredCraft.OptimizedEnums.Generator/Emitters/EnumEmitter.cs Passes indexed property model into Scriban template.
src/LayeredCraft.OptimizedEnums.Generator/Diagnostics/DiagnosticDescriptors.cs Adds OE0202 descriptor.
src/LayeredCraft.OptimizedEnums.Generator/AnalyzerReleases.Unshipped.md Records newly added diagnostic OE0202.
Directory.Build.props Bumps VersionPrefix to 1.2.0.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

- OE0203: emit warning and skip index for static, private, indexer, or
  write-only properties marked [OptimizedEnumIndex]
- OE0204: emit warning and skip index when property name conflicts with
  reserved generated members (Name → s_byName/FromName/TryFromName,
  Value → s_byValue/FromValue/TryFromValue)
- Fix OE0202 message to say "the index will not be generated" rather
  than implying fallback to reference equality
- Add [NotNullWhen(true)] to TryFromName and TryFromValue for
  consistency with TryFrom{PropertyName} methods
- Add tests for OE0203 (private property, static property) and OE0204
  (reserved name collision)
- Update all affected snapshots

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@ncipollina ncipollina requested a review from Copilot April 1, 2026 01:49
…tNullWhen

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 38 out of 38 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

…n compatibility

- Move seenNames.Add() in CollectIndexedProperties to after all validation
  checks so an invalid nearer-base [OptimizedEnumIndex] property cannot
  shadow a valid one higher in the inheritance chain
- Add HasNotNullWhenAttribute to EnumInfo, detected via
  compilation.GetTypeByMetadataName at transform time; template now
  conditionally emits [NotNullWhen(true)] on TryFrom* parameters so
  generated code compiles on netstandard2.0 targets where the attribute
  is absent

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 38 out of 38 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +295 to +299
member.Parameters.Length > 0 ||
member.GetMethod is null ||
member.DeclaredAccessibility == Accessibility.Private ||
member.GetMethod.DeclaredAccessibility == Accessibility.Private)
{
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OE0203 validation only checks for private accessibility. If the indexed property is internal, private protected (ProtectedAndInternal), or otherwise inaccessible from the concrete enum’s assembly/type (e.g., when the intermediate base class comes from a referenced assembly), the generator can still emit s_by{PropertyName} initialization accessing that property, which will fail to compile. Consider validating accessibility using Roslyn’s accessibility APIs (e.g., compilation.IsSymbolAccessibleWithin(...) for the property/getter, potentially with throughType = concrete enum type) rather than only rejecting private.

Copilot uses AI. Check for mistakes.
Comment on lines +13 to +20
/// <see cref="StringComparison"/> to control key comparison.
/// </remarks>
[AttributeUsage(AttributeTargets.Property, Inherited = true, AllowMultiple = false)]
public sealed class OptimizedEnumIndexAttribute : Attribute
{
/// <summary>
/// For <see cref="string"/> properties, specifies the comparison used when building
/// the lookup dictionary. Defaults to <see cref="StringComparison.Ordinal"/>.
Copy link

Copilot AI Apr 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The XML docs use <see cref="StringComparison"/> / <see cref="StringComparison.Ordinal"/> inside a type that also defines a StringComparison property, which can make the cref binding ambiguous or incorrect in generated documentation. Consider fully qualifying the enum type in the cref (e.g., global::System.StringComparison) to ensure the links resolve as intended.

Suggested change
/// <see cref="StringComparison"/> to control key comparison.
/// </remarks>
[AttributeUsage(AttributeTargets.Property, Inherited = true, AllowMultiple = false)]
public sealed class OptimizedEnumIndexAttribute : Attribute
{
/// <summary>
/// For <see cref="string"/> properties, specifies the comparison used when building
/// the lookup dictionary. Defaults to <see cref="StringComparison.Ordinal"/>.
/// <see cref="global::System.StringComparison"/> to control key comparison.
/// </remarks>
[AttributeUsage(AttributeTargets.Property, Inherited = true, AllowMultiple = false)]
public sealed class OptimizedEnumIndexAttribute : Attribute
{
/// <summary>
/// For <see cref="string"/> properties, specifies the comparison used when building
/// the lookup dictionary. Defaults to <see cref="global::System.StringComparison.Ordinal"/>.

Copilot uses AI. Check for mistakes.
@ncipollina ncipollina merged commit e46ef1d into main Apr 1, 2026
5 checks passed
@ncipollina ncipollina deleted the feat/optimized-enum-index-attribute branch April 1, 2026 10:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants