[XAML] SourceGen: Compile RelativeSource AncestorType bindings under AOT - #34408
Conversation
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 34408Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 34408" |
There was a problem hiding this comment.
Pull request overview
This PR fixes a .NET MAUI XAML SourceGen limitation where {RelativeSource AncestorType=...} bindings inside templates were incorrectly routed to string-path Binding(...) creation, which is not trim-safe under AOT. The update enables SourceGen to produce trim-safe TypedBinding when the ancestor type is known at compile time, while preserving the existing fallback behavior for other RelativeSource modes.
Changes:
- Update
KnownMarkups.ProvideValueForBindingExtensionto prefer generating a compiledTypedBindingwhen aRelativeSourcehas a resolvableAncestorType. - Retain the guard that prevents compiling other
RelativeSourcemodes (e.g.,Self,TemplatedParent, and untyped ancestor lookups) using ambientx:DataType. - Add new XAML unit tests (issue #34056) covering both the fixed ancestor-type scenario and the protected
{RelativeSource Self}scenario.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| src/Controls/src/SourceGen/KnownMarkups.cs | Refines SourceGen binding compilation logic to allow compiled bindings for RelativeSource AncestorType when the type is already resolved, avoiding trim-unsafe string-path bindings under AOT. |
| src/Controls/tests/Xaml.UnitTests/Issues/Maui34056.xaml | Adds a minimal repro XAML page covering both RelativeSource AncestorType in a DataTemplate and {RelativeSource Self} in a DataTemplate. |
| src/Controls/tests/Xaml.UnitTests/Issues/Maui34056.xaml.cs | Adds unit tests validating the generated binding types across Runtime/XamlC/SourceGen inflators. |
|
/azp run maui-pr-uitests |
|
Azure Pipelines successfully started running 1 pipeline(s). |
|
/azp run maui-pr-uitests |
|
Azure Pipelines successfully started running 1 pipeline(s). |
|
@simonrozsival @StephaneDelcroix could you please review this one? |
kubaflo
left a comment
There was a problem hiding this comment.
Could you please resolve conflicts?
bb08fe6 to
97c3680
Compare
@kubaflo, I have resolved the merge conflicts in this PR. |
This comment has been minimized.
This comment has been minimized.
I have checked the AI review, and this PR's fix is the winning candidate with 0 errors/0 warnings. |
|
/azp run |
|
Azure Pipelines: Successfully started running 3 pipeline(s). |
This comment has been minimized.
This comment has been minimized.
AI Review Summary
🗂️ Review Sessions — click to expand🚦 Gate — Test Before & After FixGate Result: ✅ PASSEDPlatform: ANDROID · Base: net11.0 · Merge base:
🔴 Without fix — 🧪 BindingDiagnosticsTests: FAIL ✅ · 57sError-relevant lines (filtered from the build log): 🟢 With fix — 🧪 BindingDiagnosticsTests: PASS ✅ · 40s(no coded error found; showing last 1200 chars) 🔴 Without fix — 📄 Maui34056: FAIL ✅ · 135sError-relevant lines (filtered from the build log): 🟢 With fix — 📄 Maui34056: PASS ✅ · 132s(no coded error found; showing last 1200 chars) 🔴 Without fix — 📄 Maui34056PageViewModel: 🔍 NO MATCH · 37s(no coded error found; showing last 1200 chars) 🟢 With fix — 📄 Maui34056PageViewModel: 🔍 NO MATCH · 39s(no coded error found; showing last 1200 chars) 🔴 Without fix — 📄 Maui34056StringAncestorType: FAIL ✅ · 39sError-relevant lines (filtered from the build log): 🟢 With fix — 📄 Maui34056StringAncestorType: PASS ✅ · 39s(no coded error found; showing last 1200 chars) 🔴 Without fix — 📄 Maui34056StringAncestorType.sgen: 🔍 NO MATCH · 35s(no coded error found; showing last 1200 chars) 🟢 With fix — 📄 Maui34056StringAncestorType.sgen: 🔍 NO MATCH · 41s(no coded error found; showing last 1200 chars)
|
| Category | Tests | Snapshot diffs |
|---|---|---|
Button |
71/73 ✓ | 1 diff PNG |
Label |
96/98 ✓ | — |
Layout |
192/195 ✓ | — |
📎 Download drop-deep-uitests artifact (TRX + snapshot diffs) |
🔗 Regression Cross-Reference
🔍 Regression Cross-Reference
✗ Revert risks detected — this PR removes 1 line(s) previously added by labeled bug-fix PRs.
| File | Fix PR | Fixed issue(s) | Risk | Reverted line |
|---|---|---|---|---|
src/Controls/src/SourceGen/KnownMarkups.cs |
#34501 | #34490 | ✗ REVERT | && extVariable is not null) |
Action required: Verify that issues #34490 do not re-regress before merging.
🧪 Regression Tests to Verify
These tests were added by the fix PRs being reverted. They must still pass:
| Fix PR | Type | Test | Filter |
|---|---|---|---|
| #34501 | UnitTest | BindingDiagnosticsTests | BindingDiagnosticsTests |
| #34501 | XamlUnitTest | Maui34490ViewModel | Maui34490ViewModel |
🧪 Regression Test Results
❌ FAILED — 1 passed, 2 failed, 0 skipped
| Fix PR | Test | Type | Result |
|---|---|---|---|
| #34727 | Maui34726 | XamlUnitTest | ❌ FAILED |
| #34501 | BindingDiagnosticsTests | UnitTest | ✅ PASSED |
| #34501 | Maui34490ViewModel | XamlUnitTest | ❌ FAILED |
📋 Pre-Flight — Context & Validation
Issue: #34056 - iOS wrong trimmed Relative bindings under config XamlInflator and AOT
PR: #34408 - [XAML] SourceGen: Compile RelativeSource AncestorType bindings under AOT
Platforms Affected: iOS/MacCatalyst AOT scenario; SourceGen behavior is platform-neutral. Requested test platform: android.
Files Changed: 1 implementation, 5 test
Key Findings
- PR targets SourceGen binding generation for
RelativeSource AncestorTypeso resolvable ancestor sources can use trim-safeTypedBindinginstead of string-pathBinding. - The linked issue reports IL2026 and runtime failure under Release AOT with
MauiXamlInflator; workaround is disabling SourceGen inflator. - Changed implementation file:
src/Controls/src/SourceGen/KnownMarkups.cs. - Added/changed tests cover
Maui34056, stringAncestorType, and SourceGen binding diagnostics. - Gate result supplied by caller: tests fail without the fix and pass with the PR fix; gate was not re-run.
Code Review Summary
Verdict: NEEDS_CHANGES
Confidence: low
Errors: 1 | Warnings: 0 | Suggestions: 0
Key code review findings:
- ✗
src/Controls/src/SourceGen/KnownMarkups.cs:404-408— resolvedAncestorTypecompile failures now emit MAUIG2045, which can break existing SourceGen XAML that expects runtime BindingContext lookup through an element ancestor.
Fix Candidates
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| PR | PR #34408 | Resolve RelativeSource AncestorType and use it as SourceGen compiled-binding source when possible; fall back for Self, TemplatedParent, unresolved ancestor, and x:Reference cases. |
✅ PASSED (Gate) | src/Controls/src/SourceGen/KnownMarkups.cs, tests |
Original PR |
🔬 Code Review — Deep Analysis
Code Review — PR #34408
Independent Assessment
What this changes: SourceGen compiles {Binding Source={RelativeSource AncestorType=...}} into TypedBinding when AncestorType resolves, while keeping runtime Binding fallback for other explicit sources.
Inferred motivation: Avoid trim/AOT-unsafe string-path Binding for known ancestor sources.
Reconciliation with PR Narrative
Author claims: AncestorType sources compile trim-safely; unresolved/Self/TemplatedParent still fall back.
Agreement/disagreement: Mostly matches, but PR narrative says new property-not-found diagnostics are not introduced for AncestorType explicit-source cases; code now emits MAUIG2045 for resolved AncestorType failures, and CI proves this breaks existing samples.
Prior Review Reconciliation
No prior ❌ Error findings found. Earlier conflict-related change requests appear obsolete after the net11 rebase; latest AI review reported 0 code findings.
Blast Radius Assessment
- Runs for all instances: Yes — SourceGen binding compilation affects all source-generated XAML.
- Startup impact: Indirect — can fail builds before app startup.
- Static/shared state: No new shared runtime state.
CI Status
- Required-check result:
gh pr checks --requiredunavailable (ghunauthenticated). Public GitHub/AzDO status shows red checks. - Classification: PR-caused failure ❌
- Action taken: Invoked
azdo-build-investigator;ci-analysisskill was unavailable. Public AzDO logs inspected.
Findings
❌ Error — New MAUIG2045 diagnostics break existing source-generated samples
KnownMarkups.cs now reports BindingPropertyNotFound for resolved AncestorType sources (src/Controls/src/SourceGen/KnownMarkups.cs:404-408). Existing sample XAML binds through AncestorType={x:Type ContentPage} to properties such as SelectedItem/NavigateCommand (CorePage.xaml:49-50). CI fails in maui-pr integration samples with MAUIG2045 on these unchanged sample files. This is PR-caused and blocks merge.
Failure-Mode Probing
- Existing XAML uses
AncestorType=ContentPagebut expects runtime BindingContext lookup: now compiled againstContentPage, causing MAUIG2045. - Self/TemplatedParent paths: guarded by
HasRelativeSourceBinding, still runtime fallback. - Unresolved AncestorType: suppresses MAUIG2045, but resolved broad ancestor types regress.
Verdict: NEEDS_CHANGES
Confidence: low (shared SourceGen build path + red CI), but high confidence in the blocking issue.
Summary: The core AOT direction is sound, but the diagnostic/reporting behavior regresses existing XAML and fails CI. Suppress MAUIG2045 for these AncestorType fallback cases or otherwise preserve existing sample build behavior.
🛠️ Fix — Analysis & Comparison
Fix Candidates
| # | Source | Approach | Test Result | Files Changed | Notes |
|---|---|---|---|---|---|
| 1 | try-fix-1 | Suppress MAUIG2045 for Element-derived AncestorType compile failures while retaining diagnostics for non-Element/ViewModel ancestor types. |
✅ PASS | 4 files | Fixes the code-review regression and preserves non-Element compiled binding diagnostics. Weaker than #2 because failed Element compile fallback still carries the ancestor dataTypeSymbol shape. |
| 2 | try-fix-2 | Treat Element-derived AncestorType compilation as opportunistic: compile if the path resolves on the Element, otherwise clear dataTypeSymbol and fall back to the pre-PR runtime Binding shape without MAUIG2045. |
✅ PASS | 4 files | Best candidate. Preserves successful ContentPage.Title typed binding, avoids false MAUIG2045 for BindingContext paths, and restores pre-PR runtime fallback shape when Element ancestor compilation cannot resolve the path. |
| 3 | try-fix-3 | Hybrid dual fast path: compile against ancestor type first; for Element ancestors with explicit Mode=FindAncestorBindingContext and binding-level x:DataType, retry compilation against that BindingContext type before runtime fallback. |
✅ PASS | 4 files | Valid but more complex. Adds a new BindingContext compile path and helper logic; useful future direction, but higher risk than #2 for this PR. |
| PR | PR #34408 | Resolve RelativeSource AncestorType and use it as the compiled-binding source whenever resolvable; fall back for Self, TemplatedParent, unresolved ancestor, and x:Reference. |
✅ PASSED (Gate) | 6 files | Original PR. Code review found it can report MAUIG2045 for existing Element-ancestor BindingContext patterns. |
Test Results
| Candidate | Primary test | Regression: Maui34726 | Regression: BindingDiagnosticsTests | Regression: Maui34490ViewModel | Result |
|---|---|---|---|---|---|
| 1 | Maui34056 passed; BindingDiagnosticsTests passed |
Passed | Passed | Passed per attempt log | ✅ PASS |
| 2 | Maui34056 passed; BindingDiagnosticsTests passed |
Passed | Passed | Command completed successfully; filter matched no test cases in this checkout | ✅ PASS |
| 3 | Maui34056 passed; BindingDiagnosticsTests passed |
Passed | Passed | Command completed successfully; filter matched no test cases in this checkout | ✅ PASS |
Failure Analysis / Learning
- Candidate 1 proved the core regression fix: Element ancestors must not be treated as fully-known binding sources for diagnostics when the path may target their runtime
BindingContext. - Candidate 2 improved candidate 1 by making Element-ancestor compilation an explicit opportunistic path and clearing
dataTypeSymbolbefore fallback, producing the closest pre-PR runtime Binding shape. - Candidate 3 validated the cross-pollinated hybrid idea, but it adds substantially more branching/helper logic and a new explicit BindingContext typed compile path. It passes tests, but is less surgical than candidate 2.
- All passing candidates needed the same collateral Xaml.UnitTests build unblockers (
MAUIX2017warning demotion and#nullable enableforMaui34998.xaml.cs) to run the mandatory XAML regression project in this checkout; these appear to be pre-existing branch test-build blockers rather than core fix logic.
Cross-Pollination
| Model | Round | New Ideas? | Details |
|---|---|---|---|
| claude-opus-4.6 | 1 | Yes | Candidate 1: suppress MAUIG2045 for Element ancestors, retain diagnostics for non-Element ancestors. |
| claude-opus-4.7 | 1 | Yes | Candidate 2: opportunistic Element-ancestor compile with dataTypeSymbol = null fallback. |
| gpt-5.3-codex | 2 | Yes | Proposed hybrid compiled fast-path with legacy fallback; implemented as candidate 3. |
| gpt-5.5 | 2 | Yes | Proposed compiling Element ancestor BindingContext paths when explicit binding-level x:DataType is available; covered by candidate 3. |
Exhausted: Yes — after candidate 3, remaining alternatives are either trivial restructurings of Element-vs-non-Element fallback handling or broader runtime Binding/TypedBinding infrastructure changes beyond a surgical PR fix.
Selected Fix: Candidate #2 — It passes the primary and mandatory regression commands, addresses the code-review regression in the PR fix, preserves trim-safe compiled binding for non-Element ancestor types and successful Element property paths, restores pre-PR runtime fallback shape when Element ancestor compilation cannot resolve the path, and is materially simpler/lower-risk than candidate #3.
📝 Recommended PR Title & Description
Assessment: ✏️ Recommend updating — the title is good, but the description needs to include the reviewer-required Element-ancestor fallback behavior and remove stale wording that implies all resolvable AncestorType paths are safely diagnosable.
Recommended title
[XAML] SourceGen: Compile RelativeSource AncestorType bindings under AOT
Recommended description
### Root Cause:
The issue occurs because `KnownMarkups.ProvideValueForBindingExtension` had an explicit-source gate that routed `RelativeSource` bindings to `new Binding(string, ...)` — a `[RequiresUnreferencedCode]` constructor that can be trimmed under AOT Release builds.
That runtime fallback was necessary to prevent using parent-scope `x:DataType` as the source type for `{RelativeSource Self}` and other runtime-resolved source bindings. However, it was too broad: when `AncestorType={x:Type PageViewModel}` or a resolvable bare-string `AncestorType` is explicitly set for a non-Element/ViewModel source, the binding source type is known at compile time and a trim-safe `TypedBinding` can be generated.
### Fix Description:
The fix restructures `ProvideValueForBindingExtension` with explicit source-type resolution:
- `TryGetRelativeSourceAncestorType` detects `Source={RelativeSource ...}` with an `AncestorType`. When the ancestor type is resolvable, it can use that type as the compiled binding source and produce a trim-safe `TypedBinding<AncestorType, TProperty>`.
- Non-Element/ViewModel `AncestorType` bindings keep full compile-time diagnostics: if the path cannot be resolved on the known source type, MAUIG2045 is still reported.
- Element-derived `AncestorType` bindings are compiled opportunistically. Paths that resolve on the Element itself, such as `AncestorType=ContentPage` + `Path=Title`, still produce trim-safe `TypedBinding`s. Paths that do not resolve on the Element fall back to the string-based runtime `Binding` without MAUIG2045, preserving valid BindingContext-targeted patterns such as `AncestorType=ContentPage` + `Path=BindingContext.SomeCommand`.
- RelativeSource bindings without a resolvable `AncestorType` (`Self`, `TemplatedParent`, or unresolved ancestor sources) remain on the string-based runtime `Binding` fallback so ambient `x:DataType` is not incorrectly treated as the source type.
- `x:Reference` bindings continue to resolve the referenced element type where possible and avoid new property-not-found diagnostics when they fall back to runtime binding.
- Property-not-found diagnostics are still emitted for normal `x:DataType` bindings and for non-Element `AncestorType` explicit-source cases where the source type is fully known, but are suppressed for runtime-fallback explicit-source cases that previously did not compile.
This enables compiled bindings precisely where the source type is known at compile time while preserving the correct runtime fallback for other explicit source modes. No new type-resolution pipeline is needed — it reuses the `ITypeSymbol` already resolved by the SourceGen pipeline.
### Issues Fixed
Fixes https://github.com/dotnet/maui/issues/34056
### Tested the behaviour in the following platforms
- [x] iOS
- [x] Mac
- [ ] Android
- [ ] Windows
**Note:** NativeAOT scenarios are applicable only to iOS and Mac Catalyst platforms as per the .NET MAUI documentation.
### Output Screenshot
Before Issue Fix | After Issue Fix |
|----------|----------|
|<video width="100" height="100" alt="Before Fix" src="https://github.com/user-attachments/assets/1a28b743-3a42-4a0e-ba53-b76ddc32bd25">|<video width="100" height="100" alt="After Fix" src="https://github.com/user-attachments/assets/2c838fb4-2f57-4a05-ab56-8b299fa097d0">|
🏁 Report — Final Recommendation
Comparative Candidate Report — PR #34408
Candidates
| Candidate | Result | Assessment |
|---|---|---|
pr |
Fixes the AOT/trim scenario by compiling resolvable RelativeSource AncestorType bindings, but regresses valid Element-ancestor BindingContext patterns by emitting MAUIG2045 when a path cannot be proven on the Element type. |
|
pr-plus-reviewer |
✅ Pass-equivalent | Applies the expert review feedback using the try-fix-2 approach: Element ancestors compile opportunistically and fall back to pre-PR runtime Binding semantics on unresolved Element paths, while non-Element ancestor diagnostics remain intact. |
try-fix-1 |
✅ PASS | Suppresses MAUIG2045 for Element ancestors but leaves dataTypeSymbol set to the Element type on fallback, so the runtime Binding shape differs subtly from pre-PR behavior. |
try-fix-2 |
✅ PASS | Best STEP 5a candidate: opportunistic Element-ancestor compilation, exact pre-PR fallback shape on Element path failure, retained diagnostics for non-Element ancestors, and lower complexity than try-fix-3. |
try-fix-3 |
✅ PASS | Valid but more complex: retries Element FindAncestorBindingContext cases against explicit binding-level x:DataType. This may be a useful future enhancement, but adds more branching and surface area than needed for the regression. |
Ranking
pr-plus-reviewer/try-fix-2— Both represent the same winning behavior. They preserve the PR's trim-safeTypedBindingimprovement for resolvable ViewModel ancestors and successful Element property paths, while restoring runtime fallback semantics for Element ancestor BindingContext paths.try-fix-1— Passes tests and fixes the false MAUIG2045, but is slightly weaker because fallback still carries the ElementdataTypeSymbolshape instead of resetting to the pre-PRtypeof=nullruntime fallback.try-fix-3— Passes tests and offers broader typed BindingContext compilation, but its extra mode/type-resolution path is higher risk than necessary for this PR.pr— Ranked below all passing reviewer-adjusted candidates because the expert reviewer found a major regression despite the supplied gate passing.
No candidate from STEP 5a failed regression tests, so regression-test failure did not force any passing try-fix candidate below another. The raw PR is ranked lower because expert review identified an unguarded correctness regression.
Winner
Winner: pr-plus-reviewer
pr-plus-reviewer is the single best candidate because it incorporates the expert review finding directly into the PR fix while matching the empirically strongest STEP 5a approach (try-fix-2). It keeps the intended AOT-safe compiled binding behavior and avoids the new MAUIG2045 false positive for valid Element ancestor BindingContext paths with the smallest behavior-safe change.
🧭 Next Steps — review latest findings
No alternative fix was selected for this run. Review the session findings and CI results before merging.
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!
Root Cause:
The issue occurs because
KnownMarkups.ProvideValueForBindingExtensionhad an explicit-source gate that routedRelativeSourcebindings tonew Binding(string, ...)— a[RequiresUnreferencedCode]constructor that can be trimmed under AOT Release builds.That runtime fallback was necessary to prevent using parent-scope
x:DataTypeas the source type for{RelativeSource Self}and other runtime-resolved source bindings. However, it was too broad: whenAncestorType={x:Type PageViewModel}or a resolvable bare-stringAncestorTypeis explicitly set, the binding source type is known at compile time and a trim-safeTypedBindingcan be generated.Fix Description:
The fix restructures
ProvideValueForBindingExtensionwith explicit source-type resolution:TryGetRelativeSourceAncestorTypedetectsSource={RelativeSource ...}with anAncestorType. When the ancestor type is resolvable, it uses that type as the compiled binding source and produces a trim-safeTypedBinding<AncestorType, TProperty>.AncestorType(Self,TemplatedParent, or unresolved ancestor sources) remain on the string-based runtimeBindingfallback so ambientx:DataTypeis not incorrectly treated as the source type.x:Referencebindings continue to resolve the referenced element type and avoid new property-not-found diagnostics when they fall back to runtime binding.x:DataTypebindings, but not forAncestorTypeorx:Referenceexplicit-source cases that previously did not compile.This enables compiled bindings precisely where the source type is known at compile time while preserving the correct runtime fallback for other explicit source modes.ce bindings. Zero new type resolution is needed — it reuses the ITypeSymbol already resolved by the SourceGen pipeline.
Issues Fixed
Fixes #34056
Tested the behaviour in the following platforms
Note: NativeAOT scenarios are applicable only to iOS and Mac Catalyst platforms as per the .NET MAUI documentation.
Output Screenshot
34056-BeforeFix.mov
34056-AfterFix.mov