Skip to content

[SourceGen] Don't emit INPC handlers for static type references in C# XAML expressions#35922

Open
StephaneDelcroix wants to merge 1 commit into
net11.0from
sde/fix-35900-static-type-handlers
Open

[SourceGen] Don't emit INPC handlers for static type references in C# XAML expressions#35922
StephaneDelcroix wants to merge 1 commit into
net11.0from
sde/fix-35900-static-type-handlers

Conversation

@StephaneDelcroix

Copy link
Copy Markdown
Contributor

Note

Are you waiting for the changes in this PR to be merged?
It would be very helpful if you could test the resulting artifacts from this PR and let us know in a comment if this change resolves your issue. Thank you!

Description of Change

Fixes #35900.

The XAML C# expression source generator was emitting INPC subscription handlers for every MemberAccess chain in an expression, including chains rooted at a static type reachable via global using such as Colors.Goldenrod, DateTime.Now, or Math.Max(...).

The existing filter in ExpressionAnalyzer.Analyze correctly dropped the top-level handler when the root identifier (Colors, DateTime, Math) was not a member of the x:DataType, but it always kept the nested handlers for the same chain. That produced invalid getter lambdas like:

new(static __source => __source.Colors, "Goldenrod")
new(static __source => __source.DateTime, "Now")
new(static __source => __source.Math,     "Max")

…which then failed compilation with CS1061: 'MainViewModel' does not contain a definition for 'Colors' (and similar for DateTime/Math). This is step 4 of the XamlCSharpExpressions name-resolution spec: an identifier that doesn't resolve as a BindingContext member, code-behind member, or lambda parameter must fall through to a static type reference — it must never produce an INPC subscription against a non-existent member of the DataType.

The fix tightens the handler filter so a nested handler is kept only when the root identifier of its parent expression (after the __source. prefix) is an actual member of the DataType. The getter-body rewriter (TransformRootIdentifiers) already handled the static-type case correctly, so the lambda body — e.g. __source => (__source.IsVip ? Colors.Goldenrod : Colors.Gray, true) — compiles unchanged once the handler array is clean.

Issues Fixed

Fixes #35900

Tests

Added a parameterized test in CSharpExpressionDiagnosticsTests that exercises all four scenarios from the issue report:

  • TextColor="{IsVip ? Colors.Goldenrod : Colors.Gray}"
  • Text="{$'Now: {DateTime.Now:t}'}"
  • Text="{$'Max = {Math.Max(Price, Quantity):F2}'}"
  • Text="{$'{Name} - {DateTime.Now:d}'}"

The test asserts the generated .xsg.cs contains no __source.Colors/__source.DateTime/__source.Math handler subscriptions and that the resulting compilation has no errors. All 205 existing SourceGen.UnitTests continue to pass.

… XAML expressions

Fixes #35900

The XAML C# expression source generator was extracting binding handlers
for every MemberAccess chain in an expression, including chains rooted
at static type names like 'Colors.Goldenrod', 'DateTime.Now' or
'Math.Max(...)'. Top-level handlers were filtered out when the root
identifier wasn't a member of the x:DataType, but nested handlers in
the chain were always kept. That produced invalid getter lambdas such
as 'static __source => __source.Colors' which broke compilation with
'CS1061: <DataType> does not contain a definition for Colors'.

Per the XamlCSharpExpressions spec step 4 (Static Invocation), an
identifier that doesn't resolve as a BindingContext member, code-behind
member, or local lambda parameter must fall through to a static type
reference - it must not subscribe to a non-existent property on the
DataType.

The fix tightens the handler filter in ExpressionAnalyzer.Analyze so a
nested handler is kept only when the root identifier of its parent
expression (after the source parameter prefix) is an actual member of
the DataType.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 35922

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 35922"

@github-actions github-actions Bot added the area-xaml XAML, CSS, Triggers, Behaviors label Jun 15, 2026
@kubaflo

kubaflo commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

NEEDS_DISCUSSION (confidence: high) — multi-model review (2 LGTM, 2 ND); code unanimously judged correct, 0 actionable findings.

The source-gen fix is correct and minimal: it tightens the handler filter so a nested member-access handler is kept only when its chain-root identifier is a real property/field of the x:DataType. This drops both handlers of static-type chains (e.g. Colors.Goldenrod → the invalid static __source => __source.Colors getter that caused CS1061) while preserving genuine nested chains like User.Address.City. The new gate is consistent with the body rewriter TransformRootIdentifiers, and the test lives in SourceGen.UnitTests with assertNoCompilationErrors.

The only reason this isn't a clean LGTM: a required CI leg (RunOniOS_MauiReleaseTrimFull_CoreCLR ARM64) is failing while maui-pr is pending. It looks flaky/unrelated to a source-gen filter change — please confirm a green re-run before merge.

Multi-model review (gpt-5.5 · opus-4.8 · opus-4.6 · gemini-3.1-pro). Comments only — not a formal approval.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-xaml XAML, CSS, Triggers, Behaviors

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants