Skip to content

Commit 638865c

Browse files
[backport 10.0.1xx-sr5] Fix MAUIG2045 false positive with x:Reference source in DataTemplate (#34525)
<!-- Please let the below note in for people that find this PR --> > [!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](https://github.com/dotnet/maui/wiki/Testing-PR-Builds) from this PR and let us know in a comment if this change resolves your issue. Thank you! ## Description Backport of #34501 to `release/10.0.1xx-sr5`. When a binding uses `Source={x:Reference}`, the source generator was incorrectly validating the binding path against `x:DataType` instead of the referenced element, producing a false MAUIG2045 warning. This fix detects explicit binding sources (both `RelativeSource` and `x:Reference`) and skips compilation, falling back to runtime binding. Fixes #34490 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 55acfe9 commit 638865c

File tree

4 files changed

+141
-19
lines changed

4 files changed

+141
-19
lines changed

src/Controls/src/SourceGen/KnownMarkups.cs

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -341,23 +341,25 @@ private static bool ProvideValueForBindingExtension(ElementNode markupNode, Inde
341341
returnType = context.Compilation.GetTypeByMetadataName("Microsoft.Maui.Controls.BindingBase")!;
342342
ITypeSymbol? dataTypeSymbol = null;
343343

344-
// Check if the binding has a Source property with a RelativeSource.
345-
// In this case, we should NOT compile the binding using x:DataType because
346-
// the source type will be determined at runtime by the RelativeSource, not x:DataType.
347-
bool hasRelativeSource = HasRelativeSourceBinding(markupNode);
344+
// When Source is explicitly set (RelativeSource or x:Reference), x:DataType does not describe
345+
// the actual source — skip compilation and fall back to runtime binding.
346+
bool hasExplicitSource = HasExplicitBindingSource(markupNode);
348347

349348
context.Variables.TryGetValue(markupNode, out ILocalValue? extVariable);
350349

351-
if ( !hasRelativeSource
352-
&& extVariable is not null
353-
&& TryGetXDataType(markupNode, context, out dataTypeSymbol)
354-
&& dataTypeSymbol is not null)
350+
if ( !hasExplicitSource
351+
&& extVariable is not null)
355352
{
356-
var compiledBindingMarkup = new CompiledBindingMarkup(markupNode, GetBindingPath(markupNode), extVariable, context);
357-
if (compiledBindingMarkup.TryCompileBinding(dataTypeSymbol, isTemplateBinding, out string? newBindingExpression) && newBindingExpression is not null)
353+
TryGetXDataType(markupNode, context, out dataTypeSymbol);
354+
355+
if (dataTypeSymbol is not null)
358356
{
359-
value = newBindingExpression;
360-
return true;
357+
var compiledBindingMarkup = new CompiledBindingMarkup(markupNode, GetBindingPath(markupNode), extVariable, context);
358+
if (compiledBindingMarkup.TryCompileBinding(dataTypeSymbol, isTemplateBinding, out string? newBindingExpression) && newBindingExpression is not null)
359+
{
360+
value = newBindingExpression;
361+
return true;
362+
}
361363
}
362364
}
363365

@@ -628,10 +630,10 @@ static bool IsBindingContextBinding(ElementNode node)
628630
&& propertyName.LocalName == "BindingContext";
629631
}
630632

631-
// Checks if the binding has a Source property that is a RelativeSource extension.
632-
// When a binding uses RelativeSource, the source type is determined at runtime,
633+
// Checks if the binding has a Source property set to RelativeSource or x:Reference.
634+
// When Source is explicitly set, x:DataType does not describe the actual binding source,
633635
// so we should NOT compile the binding using x:DataType.
634-
static bool HasRelativeSourceBinding(ElementNode bindingNode)
636+
static bool HasExplicitBindingSource(ElementNode bindingNode)
635637
{
636638
// Check if Source property exists
637639
if (!bindingNode.Properties.TryGetValue(new XmlName("", "Source"), out INode? sourceNode)
@@ -640,12 +642,13 @@ static bool HasRelativeSourceBinding(ElementNode bindingNode)
640642
return false;
641643
}
642644

643-
// Check if the Source is a RelativeSourceExtension
645+
// Check if the Source is a RelativeSourceExtension or ReferenceExtension
644646
if (sourceNode is ElementNode sourceElementNode)
645647
{
646-
// Check if the element is a RelativeSourceExtension
647-
return sourceElementNode.XmlType.Name == "RelativeSourceExtension"
648-
|| sourceElementNode.XmlType.Name == "RelativeSource";
648+
return sourceElementNode.XmlType.Name is "RelativeSourceExtension"
649+
or "RelativeSource"
650+
or "ReferenceExtension"
651+
or "Reference";
649652
}
650653

651654
return false;

src/Controls/tests/SourceGen.UnitTests/BindingDiagnosticsTests.cs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System;
12
using System.Linq;
23
using Microsoft.CodeAnalysis;
34
using Microsoft.Maui.Controls.SourceGen;
@@ -250,6 +251,58 @@ public class ViewModel
250251
Assert.Equal(DiagnosticSeverity.Warning, diagnostic.Severity);
251252
}
252253

254+
[Fact]
255+
public void BindingWithXReferenceSourceInDataTemplate_DoesNotReportFalsePositive()
256+
{
257+
var xaml =
258+
"""
259+
<?xml version="1.0" encoding="UTF-8"?>
260+
<ContentPage
261+
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
262+
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
263+
xmlns:test="clr-namespace:Test"
264+
x:Class="Test.TestPage"
265+
x:Name="PageRoot"
266+
x:DataType="test:ViewModel">
267+
<CollectionView ItemsSource="{Binding Items}">
268+
<CollectionView.ItemTemplate>
269+
<DataTemplate x:DataType="test:ItemModel">
270+
<Button Text="{Binding Name}"
271+
Command="{Binding Source={x:Reference PageRoot}, Path=BindingContext.SelectItemCommand}"
272+
CommandParameter="{Binding .}" />
273+
</DataTemplate>
274+
</CollectionView.ItemTemplate>
275+
</CollectionView>
276+
</ContentPage>
277+
""";
278+
279+
var csharp =
280+
"""
281+
namespace Test;
282+
283+
public partial class TestPage : Microsoft.Maui.Controls.ContentPage { }
284+
285+
public class ViewModel
286+
{
287+
public System.Collections.Generic.List<ItemModel> Items { get; set; }
288+
public Microsoft.Maui.Controls.Command SelectItemCommand { get; set; }
289+
}
290+
291+
public class ItemModel
292+
{
293+
public string Name { get; set; }
294+
}
295+
""";
296+
297+
var compilation = CreateMauiCompilation()
298+
.AddSyntaxTrees(Microsoft.CodeAnalysis.CSharp.CSharpSyntaxTree.ParseText(csharp));
299+
var result = RunGenerator<XamlGenerator>(compilation, new AdditionalXamlFile("Test.xaml", xaml), assertNoCompilationErrors: false);
300+
301+
// x:Reference bindings skip compilation entirely — no MAUIG2045 should be emitted
302+
// for properties on the DataTemplate's x:DataType (ItemModel).
303+
Assert.DoesNotContain(result.Diagnostics, d => d.Id == "MAUIG2045" && d.GetMessage().Contains("ItemModel", StringComparison.Ordinal));
304+
}
305+
253306
[Fact]
254307
public void BindingIndexerTypeUnsupported_ReportsCorrectDiagnostic()
255308
{
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
3+
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
4+
xmlns:local="clr-namespace:Microsoft.Maui.Controls.Xaml.UnitTests"
5+
x:Class="Microsoft.Maui.Controls.Xaml.UnitTests.Maui34490"
6+
x:Name="PageRoot"
7+
x:DataType="local:Maui34490ViewModel">
8+
<CollectionView ItemsSource="{Binding Items}">
9+
<CollectionView.ItemTemplate>
10+
<DataTemplate x:DataType="local:Maui34490ItemModel">
11+
<Button Text="{Binding Name}"
12+
Command="{Binding Source={x:Reference PageRoot}, Path=BindingContext.SelectItemCommand}"
13+
CommandParameter="{Binding .}" />
14+
</DataTemplate>
15+
</CollectionView.ItemTemplate>
16+
</CollectionView>
17+
</ContentPage>
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Windows.Input;
4+
using Microsoft.Maui.ApplicationModel;
5+
using Microsoft.Maui.Controls.Core.UnitTests;
6+
using Xunit;
7+
8+
namespace Microsoft.Maui.Controls.Xaml.UnitTests;
9+
10+
public class Maui34490ViewModel
11+
{
12+
public List<Maui34490ItemModel> Items { get; set; }
13+
public ICommand SelectItemCommand { get; set; }
14+
}
15+
16+
public class Maui34490ItemModel
17+
{
18+
public string Name { get; set; }
19+
}
20+
21+
public partial class Maui34490 : ContentPage
22+
{
23+
public Maui34490() => InitializeComponent();
24+
25+
[Collection("Issue")]
26+
public class Tests : IDisposable
27+
{
28+
public Tests() => AppInfo.SetCurrent(new MockAppInfo());
29+
public void Dispose() => AppInfo.SetCurrent(null);
30+
31+
[Theory]
32+
[XamlInflatorData]
33+
internal void XReferenceSourceInDataTemplateShouldNotWarn(XamlInflator inflator)
34+
{
35+
if (inflator == XamlInflator.SourceGen)
36+
{
37+
var result = MockSourceGenerator.CreateMauiCompilation()
38+
.RunMauiSourceGenerator(typeof(Maui34490));
39+
// The path is resolved against ContentPage (x:Reference target), NOT Maui34490ItemModel (x:DataType).
40+
// BindingContext is 'object' on BindableObject, so SelectItemCommand warning on 'object' is expected,
41+
// but a warning mentioning Maui34490ItemModel would mean it's still resolving against the wrong type.
42+
Assert.DoesNotContain(result.Diagnostics, d => d.Id == "MAUIG2045" && d.GetMessage().Contains("Maui34490ItemModel", StringComparison.Ordinal));
43+
}
44+
45+
var page = new Maui34490(inflator);
46+
Assert.NotNull(page);
47+
}
48+
}
49+
}

0 commit comments

Comments
 (0)