This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
ExpressiveSharp is a Roslyn source generator that enables modern C# syntax (null-conditional ?., switch expressions, pattern matching) in LINQ expression trees, which normally only support a restricted subset of C#. It works by generating Expression<TDelegate> factory code at compile time from [Expressive]-decorated members.
dotnet restore
dotnet build --no-restore -c Release
dotnet test --no-build -c Release
# Run a single test project
dotnet test tests/ExpressiveSharp.Generator.Tests -c Release
# Run a single test by name
dotnet test --filter "FullyQualifiedName~MyTestName" -c Release
# Run tests against a specific target framework
dotnet test -f net8.0 -c Release
# Accept new Verify snapshots (for snapshot tests)
VERIFY_AUTO_APPROVE=true dotnet test tests/ExpressiveSharp.Generator.Tests
# Pack NuGet packages locally
dotnet pack -c Release
# Run benchmarks (all)
dotnet run -c Release --project benchmarks/ExpressiveSharp.Benchmarks/ExpressiveSharp.Benchmarks.csproj -- --filter "*"
# Run benchmarks (specific class)
dotnet run -c Release --project benchmarks/ExpressiveSharp.Benchmarks/ExpressiveSharp.Benchmarks.csproj -- --filter "*GeneratorBenchmarks*"CI targets both .NET 8.0 and .NET 10.0 SDKs.
-
Compile-time (source generation): Two incremental generators in
ExpressiveSharp.Generator(targets netstandard2.0):ExpressiveGenerator— finds[Expressive]members, validates them viaExpressiveInterpreter, emits expression trees viaExpressionTreeEmitter, and builds a runtime registry viaExpressionRegistryEmitterPolyfillInterceptorGenerator— uses C# 13[InterceptsLocation]to rewriteExpressionPolyfill.Create()andIExpressiveQueryable<T>LINQ call sites from delegate form to expression tree form. Supports all standardQueryablemethods, multi-lambda methods (Join, GroupJoin, GroupBy overloads), non-lambda-first methods (Zip, ExceptBy, etc.), and custom target types via[PolyfillTarget](e.g., EF Core'sEntityFrameworkQueryableExtensionsfor async methods)
-
Runtime:
ExpressiveResolverlooks up generated expressions by (DeclaringType, MemberName, ParameterTypes).ExpressiveReplaceris anExpressionVisitorthat substitutes[Expressive]member accesses with the generated expression trees. Transformers (inTransformers/) post-process trees for provider compatibility.
src/ExpressiveSharp.Generator/ExpressiveGenerator.cs— main generator entry pointsrc/ExpressiveSharp.Generator/Emitter/ExpressionTreeEmitter.cs— maps IOperation nodes toExpression.*factory calls (the heart of code generation). UsesvarPrefixto ensure unique local variable names across multi-lambda emitterssrc/ExpressiveSharp.Generator/Interpretation/ExpressiveInterpreter.cs— validates and prepares[Expressive]memberssrc/ExpressiveSharp.Generator/PolyfillInterceptorGenerator.cs— interceptor generation. Dedicated emitters for complex methods (Join, GroupJoin, GroupBy multi-lambda), enhanced generic fallback (EmitGenericSingleLambda) for single-lambda methods with non-lambda arg forwarding,[PolyfillTarget]support for custom target typessrc/ExpressiveSharp/Services/ExpressiveResolver.cs— runtime expression registry lookupsrc/ExpressiveSharp/PolyfillTargetAttribute.cs— specifies a non-Queryabletarget type for interceptor forwarding (e.g.,EntityFrameworkQueryableExtensions)src/ExpressiveSharp/Extensions/ExpressiveQueryableLinqExtensions.cs— delegate-based LINQ stubs onIExpressiveQueryable<T>(~85 intercepted + ~15 passthrough methods)src/ExpressiveSharp.EntityFrameworkCore/Extensions/ExpressiveQueryableEfCoreExtensions.cs— EF Core-specific stubs: chain-continuity (AsNoTracking, TagWith, etc.), Include/ThenInclude, and async lambda methods (AnyAsync, SumAsync, etc.)src/ExpressiveSharp.EntityFrameworkCore/IIncludableExpressiveQueryable.cs— hybrid interface bridgingIIncludableQueryableandIExpressiveQueryablefor Include/ThenInclude chain continuitysrc/ExpressiveSharp.EntityFrameworkCore.RelationalExtensions.Abstractions/WindowFunction.cs— public marker methods for all SQL window functions (ranking, aggregate, navigation). All throw at runtime; translated to SQL by the method call translatorsrc/ExpressiveSharp.EntityFrameworkCore.RelationalExtensions.Abstractions/WindowDefinition.cs— fluent builder types:PartitionedWindowDefinition→OrderedWindowDefinition→FramedWindowDefinition. Type-safe chain ensures ORDER BY before ranking, frame only on aggregates/value functionssrc/ExpressiveSharp.EntityFrameworkCore.RelationalExtensions.Abstractions/WindowFrameBound.cs— frame boundary markers:UnboundedPreceding(property),Preceding(n)(method),CurrentRow(property),Following(n)(method),UnboundedFollowing(property)src/ExpressiveSharp.EntityFrameworkCore.RelationalExtensions/Infrastructure/Internal/WindowFunctionMethodCallTranslator.cs— translatesWindowFunction.*calls toWindowFunctionSqlExpressionorRowNumberExpression. Ranking functions never emit frames; aggregates propagate frames; navigation functions pass through 1–3 argssrc/ExpressiveSharp.EntityFrameworkCore.RelationalExtensions/Infrastructure/Internal/WindowFunctionSqlExpression.cs— self-rendering SQL expression:FUNC(args) OVER(PARTITION BY ... ORDER BY ... [ROWS/RANGE BETWEEN ...]). Provider-agnostic via SQL:2003 standard syntax
ExpressiveSharp.Abstractions (attributes + source generator, net8.0;net10.0)
└── no external deps
Provides: [Expressive], [ExpressiveFor], [ExpressiveForConstructor], [PolyfillTarget],
IExpressionTreeTransformer, source generator + code fixers (as analyzers)
ExpressiveSharp (core runtime, net8.0;net10.0)
└── ExpressiveSharp.Abstractions
Provides: ExpressiveResolver, ExpressiveReplacer, expression transformers,
IExpressiveQueryable<T>, ExpressionPolyfill, .ExpandExpressives(), .AsExpressive()
ExpressiveSharp.Generator (source generator, netstandard2.0)
└── Microsoft.CodeAnalysis.CSharp 5.0.0
ExpressiveSharp.MongoDB (net8.0;net10.0)
├── ExpressiveSharp
├── MongoDB.Driver 3.4.0
└── Provides: IRewritableMongoQueryable<T>, ExpressiveMongoCollection<T>,
ExpressiveMongoQueryProvider (decorating IQueryProvider/IMongoQueryProvider),
async lambda stubs with [PolyfillTarget(typeof(MongoQueryable))]
ExpressiveSharp.EntityFrameworkCore (net8.0;net10.0)
├── ExpressiveSharp
├── EF Core 8.0.25 / 10.0.0
└── Provides: ExpressiveDbSet<T>, IIncludableExpressiveQueryable<T,P>,
chain-continuity stubs, async lambda stubs with [PolyfillTarget]
ExpressiveSharp.EntityFrameworkCore.RelationalExtensions.Abstractions (net8.0;net10.0)
└── no external deps
Provides: Pure marker types for window functions — no EF Core dependency:
WindowFunction (ranking: ROW_NUMBER, RANK, DENSE_RANK, NTILE, PERCENT_RANK, CUME_DIST;
aggregate: SUM, AVG, COUNT, MIN, MAX; navigation: LAG, LEAD, FIRST_VALUE,
LAST_VALUE, NTH_VALUE), Window, OrderedWindowDefinition,
PartitionedWindowDefinition, FramedWindowDefinition, WindowFrameBound
ExpressiveSharp.EntityFrameworkCore.RelationalExtensions (net8.0;net10.0, experimental)
├── ExpressiveSharp.EntityFrameworkCore
├── ExpressiveSharp.EntityFrameworkCore.RelationalExtensions.Abstractions
├── EF Core Relational 8.0.25 / 10.0.0
└── Provides: Window function SQL translation (ranking, aggregate with ROWS/RANGE
frame clauses, navigation with LAG/LEAD/FIRST_VALUE/LAST_VALUE/NTH_VALUE),
indexed Select, activated via UseExpressives(o => o.UseRelationalExtensions()).
Translators: WindowFunctionMethodCallTranslator, WindowSpecMethodCallTranslator,
WindowFrameBoundMemberTranslator (IMemberTranslatorPlugin for property getters).
Note: NTH_VALUE is not supported on SQL Server.
ExpressiveSharp.EntityFrameworkCore.CodeFixers (Roslyn analyzer, netstandard2.0)
└── Microsoft.CodeAnalysis.CSharp.Workspaces 4.12.0
(packed into ExpressiveSharp.EntityFrameworkCore NuGet package)
20 diagnostic codes (EXP0001–EXP0012, EXP0018 in src/ExpressiveSharp.Generator/Infrastructure/Diagnostics.cs, EXP0013 in CodeFixers, EXP0014–EXP0020 for [ExpressiveFor] validation). Key ones: EXP0001 (requires body), EXP0004 (block body requires opt-in), EXP0008 (unsupported operation, default value used), EXP0018 (unsupported operation ignored, e.g. alignment specifiers), EXP0019 ([ExpressiveFor] conflicts with [Expressive]).
| Project | Purpose |
|---|---|
ExpressiveSharp.Generator.Tests |
Snapshot tests (Verify.MSTest) — validates generated C# output |
ExpressiveSharp.Tests |
Unit tests for runtime services, transformers, extensions |
ExpressiveSharp.IntegrationTests |
Compiles expression trees to delegates and runs them against in-memory collections — provider-agnostic correctness for every feature (arithmetic, switch, pattern matching, null-conditional, loops, tuples, [Expressive] expansion). Also hosts the shared Store scenario models + seed data. |
ExpressiveSharp.EntityFrameworkCore.IntegrationTests |
All EF Core integration tests — scenarios, async queryables, window functions, ExecuteUpdate, Include, query filters, conventions. Runs against SQLite by default; containerized SqlServer/Postgres/PomeloMySql/Cosmos via -p:TestDatabase=<provider> |
ExpressiveSharp.Benchmarks |
BenchmarkDotNet performance benchmarks (generator, resolver, replacer, transformers, EF Core) |
- Compilation — generated code compiles without errors
- Diagnostics — no warnings (TreatWarningsAsErrors is enabled globally)
- Behavioral — expression tree is functionally equivalent to the original code
Snapshot tests use GeneratorTestBase.RunExpressiveGenerator() to compile via Roslyn and compare output.
TreatWarningsAsErrors: true— all warnings are errorsNullable: enable— full nullable reference types- C# 12.0 on net8.0, C# 14.0 on net10.0
- Allman brace style, 4-space indentation,
varpreferred - Instance fields:
_camelCase - Expression-bodied members preferred for methods/properties
- Generator targets netstandard2.0 — no modern C# APIs (no spans, no
Index/Range, limited BCL). All generator code must compile against netstandard2.0 even though the rest of the solution uses C# 12+/14. - Snapshot tests —
*.verified.csfiles alongside tests intests/ExpressiveSharp.Generator.Tests/are the expected output. Commit them. UseVERIFY_AUTO_APPROVE=trueto accept new baselines. - Incremental generator caching — never store live Roslyn objects (symbols, syntax nodes) in cached pipeline state. Use immutable data snapshots with custom equality comparers.
- Central Package Management — all package versions are pinned in
Directory.Packages.props. When adding a new dependency, add its version there (not in the individual.csproj).
Expressive_AllowBlockBody— global default for block-bodied[Expressive]members (defaults to false; can also be set per-member via attribute)