Skip to content

[net11.0] Compile x:Reference bindings against resolved element type#34513

Merged
StephaneDelcroix merged 1 commit intonet11.0from
34490-xref-compile-net11
Apr 1, 2026
Merged

[net11.0] Compile x:Reference bindings against resolved element type#34513
StephaneDelcroix merged 1 commit intonet11.0from
34490-xref-compile-net11

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

Follow-up to #34501 (which targets main with a minimal fix).

This PR enhances x:Reference binding compilation for net11.0 by resolving the referenced element's type and compiling the binding against it, instead of skipping compilation entirely.

What it does

When a binding uses Source={x:Reference Name}, the source generator now:

  1. Walks namescopes to find the referenced element's type (e.g., ContentPage for x:Reference PageRoot, Label for x:Reference StatusLabel)
  2. Compiles the binding against the resolved type when the path is fully resolvable (e.g., Path=Text on a Label)
  3. Falls back silently to runtime binding when the path can't be resolved (e.g., Path=BindingContext.SelectItemCommand where BindingContext is object)

Key design decision: out Diagnostic? on TryParsePath

The MAUIG2045 ("property not found") diagnostic is now returned as an out parameter from TryParsePath/TryCompileBinding instead of being emitted directly. This lets the caller decide:

  • x:DataType bindings: emit MAUIG2045 as before (no behavior change)
  • x:Reference bindings: suppress MAUIG2045 and fall back to runtime (these were never compiled before, so a new warning would be a regression)

Tests added

  • BindingWithXReferenceSourceInDataTemplate_DoesNotReportFalsePositive — verifies no false MAUIG2045 for Path=BindingContext.X
  • BindingWithXReferenceToNonRootElement_ResolvesCorrectType — verifies Path=Text against a referenced Label compiles with no warnings
  • Maui34490.xaml — XAML unit test reproducing the original issue
  • Updated Gh3606 test — binding to Content via x:Reference now compiles

Fixes #34490

Copilot AI review requested due to automatic review settings March 17, 2026 12:11
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 17, 2026

🚀 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 -- 34513

Or

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

Copy link
Copy Markdown
Contributor

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

Enhances the XAML source generator on net11.0 to compile Binding expressions that use Source={x:Reference ...} by resolving the referenced element’s type (via namescopes) and using that type for binding-path compilation, while suppressing MAUIG2045 for x:Reference scenarios that can’t be resolved statically.

Changes:

  • Update binding compilation to resolve x:Reference source element types and compile against them (falling back to runtime binding without MAUIG2045 regressions).
  • Refactor compiled-binding diagnostics so MAUIG2045 can be returned to the caller (enabling conditional suppression/reporting).
  • Add/update unit tests covering x:Reference bindings in templates and type-resolution scenarios.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/Controls/src/SourceGen/KnownMarkups.cs Resolves x:Reference source types and conditionally suppresses/report MAUIG2045 based on binding origin.
src/Controls/src/SourceGen/CompiledBindingMarkup.cs Changes compiled-binding path parsing to return (not directly emit) property-not-found diagnostics.
src/Controls/tests/SourceGen.UnitTests/BindingDiagnosticsTests.cs Adds generator-level tests to ensure no false-positive MAUIG2045 and correct referenced-type resolution.
src/Controls/tests/Xaml.UnitTests/Issues/Maui34490.xaml Adds XAML reproduction for the DataTemplate + x:Reference scenario.
src/Controls/tests/Xaml.UnitTests/Issues/Maui34490.xaml.cs Adds XAML unit test asserting diagnostics behavior for the reproduced scenario.
src/Controls/tests/Xaml.UnitTests/Issues/Gh3606.xaml.cs Updates an existing issue test intended to validate x:Reference source-type-based compilation.

Comment on lines +40 to +42
// BindingContext is 'object' on BindableObject, so SelectItemCommand warning on 'object' is expected,
// but a warning mentioning Maui34490ItemModel would mean it's still resolving against the wrong type.
Assert.DoesNotContain(result.Diagnostics, d => d.Id == "MAUIG2045" && d.GetMessage().Contains("Maui34490ItemModel", StringComparison.Ordinal));
Comment on lines +667 to +711
// When Source={x:Reference Name} is set on a binding, resolves the referenced element's type
// by walking namescopes (same logic as ProvideValueForReferenceExtension).
// Returns null if Source is not an x:Reference or the name cannot be resolved.
static ITypeSymbol? TryResolveXReferenceSourceType(ElementNode bindingNode, SourceGenContext context)
{
if (!bindingNode.Properties.TryGetValue(new XmlName("", "Source"), out INode? sourceNode)
&& !bindingNode.Properties.TryGetValue(new XmlName(null, "Source"), out sourceNode))
return null;

if (sourceNode is not ElementNode refNode)
return null;

if (refNode.XmlType.Name is not "ReferenceExtension" and not "Reference")
return null;

// Extract the Name from the x:Reference markup
if (!refNode.Properties.TryGetValue(new XmlName("", "Name"), out INode? refNameNode)
&& !refNode.Properties.TryGetValue(new XmlName(null, "Name"), out refNameNode))
{
refNameNode = refNode.CollectionItems.Count > 0 ? refNode.CollectionItems[0] : null;
}

if (refNameNode is not ValueNode vn || vn.Value is not string name)
return null;

// Walk namescopes to find the referenced element's type
ElementNode? node = bindingNode;
var currentContext = context;
while (currentContext is not null && node is not null)
{
while (currentContext is not null && !currentContext.Scopes.ContainsKey(node))
currentContext = currentContext.ParentContext;
if (currentContext is null)
break;
var namescope = currentContext.Scopes[node];
if (namescope.namesInScope != null && namescope.namesInScope.ContainsKey(name))
return namescope.namesInScope[name].Type;
INode n = node;
while (n.Parent is ListNode ln)
n = ln.Parent;
node = n.Parent as ElementNode;
}

return null;
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I agree with copilot here

Comment on lines +21 to 30
internal void BindingsWithXReferenceSourceResolveAgainstReferencedType(XamlInflator inflator)
{
// Source={x:Reference page} points to ContentPage, which has a Content property.
// The binding path "Content" resolves against ContentPage, so the source generator
// compiles it instead of falling back to runtime Binding.
var view = new Gh3606(inflator);

var binding = view.Label.GetContext(Label.TextProperty).Bindings.GetValue();
Assert.IsType<Binding>(binding);
Assert.IsAssignableFrom<BindingBase>(binding);
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I agree with copilot here, we should assert that the binding was compiled.

When a binding uses Source={x:Reference}, resolve the referenced element's
type by walking namescopes and compile the binding against it. This enables
compiled bindings for paths like Path=Text on a referenced Label.

When the path can't be fully resolved (e.g. Path=BindingContext.X where
BindingContext is 'object'), fall back silently to runtime binding without
emitting MAUIG2045 — these bindings were never compiled before, so a new
warning would be a regression.

The MAUIG2045 diagnostic is moved from TryParsePath to the caller via an
out parameter, letting the caller decide whether to emit it (only for
x:DataType-sourced bindings, not x:Reference).

Fixes #34490

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@StephaneDelcroix StephaneDelcroix force-pushed the 34490-xref-compile-net11 branch from 893c4b9 to 6df5147 Compare March 30, 2026 07:36
Comment on lines +672 to +673
if (!bindingNode.Properties.TryGetValue(new XmlName("", "Source"), out INode? sourceNode)
&& !bindingNode.Properties.TryGetValue(new XmlName(null, "Source"), out sourceNode))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

One day I'll make copilot an ext method TryGetPropertyNode("Source", out INode? sourceNode) 😆

Copy link
Copy Markdown
Member

@simonrozsival simonrozsival left a comment

Choose a reason for hiding this comment

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

I think we could take this a step further and detect the type of BindingContext on the referenced node and correctly cast the BindingContext in the path. I can look into this in a follow up PR though.

@StephaneDelcroix StephaneDelcroix merged commit 150310c into net11.0 Apr 1, 2026
36 of 39 checks passed
@StephaneDelcroix StephaneDelcroix deleted the 34490-xref-compile-net11 branch April 1, 2026 08:16
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.

3 participants