Skip to content

v0.6.0

Choose a tag to compare

@koenbeuk koenbeuk released this 27 Apr 01:45
· 121 commits to main since this release
Immutable release. Only release title and notes can be modified.
07cde84

Hot reload support

[MetadataUpdateHandler] now resets the generated ExpressionRegistry and clears resolver/replacer caches when the runtime hot-reloads types. Edit an [Expressive] body in your IDE and the next query rebuilds the lambda from the patched factory IL — no app restart needed. When the runtime reports affected types, only those assemblies are reset; otherwise it falls back to a full scan.

Polyfill generator: massive cold-build / noisy-edit speedup

Two compounding changes to PolyfillInterceptorGenerator:

  1. Per-source-file partial output — instead of one giant generated file, each source file produces its own partial class addition. Reference-equality caching on CompilationUnitSyntax means edits to file A don't re-run the transform for file B.
  2. Skip semantic binding on non-lambda invocations — only invocations with at least one lambda argument enter the SemanticModel. Method-group / Func<>-variable args were never intercepted; they're now filtered syntactically before any GetSymbolInfo.

Measured on PolyfillColdBuildWithNoiseBenchmarks (10 files × 5 real call sites, varying noise):

Scenario Noise=0 Noise=25 Noise=100
Cold E2E baseline 3,580 µs 11,354 µs 155,758 µs
Cold E2E optimized 3,469 µs 3,655 µs 4,150 µs
Speedup 1.0× 3.1× 37.5×

Memory at noise=100: 5,538 KB → 1,965 KB (−64%). At noise=0 everything is within measurement noise, so incremental-edit performance is preserved intact.

[ExpressiveProperty] — synthesized projection-friendly properties

The new [ExpressiveProperty("TargetName")] attribute generates a settable property on a partial type, backed by a private field. Reads evaluate the stub's expression body until a value is materialized (e.g. by EF Core, HotChocolate [UseProjection], AutoMapper ProjectTo), after which the stored value wins.

public partial class User
{
    public string FirstName { get; set; } = "";
    public string LastName  { get; set; } = "";

    [ExpressiveProperty(nameof(FullName))]
    private string FullNameExpression => LastName + ", " + FirstName;
}

This replaces the previous [Expressive(Projectable = true)] story. The MongoDB integration follows along: ExpressiveMongoIgnoreConvention now recognizes [ExpressiveProperty] and [ExpressiveFor] synthesized properties so the generated backing fields aren't persisted to BSON. See the new migration-from-projectables and expressive-property reference.

Simpler [ExpressiveFor] ergonomics

[ExpressiveFor] now accepts a single-argument form when the target is on the same containing type, and works directly on instance properties:

// Before
[ExpressiveFor(typeof(User), nameof(User.FullName))]
static string FullName(User u) => u.LastName + ", " + u.FirstName;

// After
[ExpressiveFor(nameof(FullName))]
private string FullNameExpression => LastName + ", " + FirstName;

Signature matching for static/instance methods and properties was rebuilt with comprehensive coverage (see external-member-mapping recipe and expressive-for reference).

IDE responsiveness — generated expressives moved to implementation output

The polyfill generator now uses RegisterImplementationSourceOutput for the intercepted call-site code, so IDE features that don't need the generated implementations skip them and stay snappy on large solutions.

Bug fixes

  • Block-bodied [Expressive] with early returns — emit as nested Condition expressions instead of failing
  • EXP0013 — no longer warns on members registered via sibling mapping stubs ([ExpressiveFor] / [ExpressiveProperty])
  • Emitter diagnostics & EXP0010 location — restored after the polyfill refactor
  • PolyfillInterceptor compatibility — fixed regression with newly-introduced patterns

Full Changelog: v0.5.1...v0.6.0