Skip to content

Commit 13c55c5

Browse files
StephaneDelcroixPureWeen
authored andcommitted
[XC] better support for nullable props and BPs (#28550)
1 parent 20ad946 commit 13c55c5

File tree

4 files changed

+82
-37
lines changed

4 files changed

+82
-37
lines changed

src/Controls/src/Build.Tasks/SetPropertiesVisitor.cs

Lines changed: 43 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1410,6 +1410,25 @@ static IEnumerable<Instruction> SetBinding(VariableDefinition parent, FieldRefer
14101410

14111411
static bool CanSetValue(FieldReference bpRef, bool attached, INode node, IXmlLineInfo iXmlLineInfo, ILContext context)
14121412
{
1413+
static bool CanSetValue (TypeReference bpTypeRef, VariableDefinition varValue, ILContext context, IXmlLineInfo iXmlLineInfo)
1414+
{
1415+
// If it's an attached BP, there's no second chance to handle IMarkupExtensions, so we try here.
1416+
// Worst case scenario ? InvalidCastException at runtime
1417+
if (varValue.VariableType.FullName == "System.Object")
1418+
return true;
1419+
var implicitOperator = varValue.VariableType.GetImplicitOperatorTo(context.Cache, bpTypeRef, context.Body.Method.Module);
1420+
if (implicitOperator != null)
1421+
return true;
1422+
1423+
//as we're in the SetValue Scenario, we can accept value types, they'll be boxed
1424+
if (varValue.VariableType.IsValueType && bpTypeRef.FullName == "System.Object")
1425+
return true;
1426+
1427+
if (varValue.VariableType.InheritsFromOrImplements(context.Cache, bpTypeRef) || varValue.VariableType.FullName == "System.Object")
1428+
return true;
1429+
return false;
1430+
}
1431+
14131432
var module = context.Body.Method.Module;
14141433

14151434
if (bpRef == null)
@@ -1424,22 +1443,23 @@ static bool CanSetValue(FieldReference bpRef, bool attached, INode node, IXmlLin
14241443
if (!context.Variables.TryGetValue(elementNode, out VariableDefinition varValue))
14251444
return false;
14261445

1446+
14271447
var bpTypeRef = bpRef.GetBindablePropertyType(context.Cache, iXmlLineInfo, module);
1428-
// If it's an attached BP, there's no second chance to handle IMarkupExtensions, so we try here.
1429-
// Worst case scenario ? InvalidCastException at runtime
1430-
if (varValue.VariableType.FullName == "System.Object")
1431-
return true;
1432-
var implicitOperator = varValue.VariableType.GetImplicitOperatorTo(context.Cache, bpTypeRef, module);
1433-
if (implicitOperator != null)
1434-
return true;
14351448

1436-
//as we're in the SetValue Scenario, we can accept value types, they'll be boxed
1437-
if (varValue.VariableType.IsValueType && bpTypeRef.FullName == "System.Object")
1449+
if (CanSetValue(bpTypeRef, varValue, context, iXmlLineInfo))
14381450
return true;
14391451

1440-
return varValue.VariableType.InheritsFromOrImplements(context.Cache, bpTypeRef) || varValue.VariableType.FullName == "System.Object";
1452+
if (bpTypeRef.ResolveCached(context.Cache).FullName == "System.Nullable`1")
1453+
{
1454+
bpTypeRef = ((GenericInstanceType)bpTypeRef).GenericArguments[0];
1455+
if (CanSetValue(bpTypeRef, varValue, context, iXmlLineInfo))
1456+
return true;
1457+
}
1458+
1459+
return false;
14411460
}
14421461

1462+
14431463
static bool CanGetValue(VariableDefinition parent, FieldReference bpRef, bool attached, IXmlLineInfo iXmlLineInfo, ILContext context, out TypeReference propertyType)
14441464
{
14451465
var module = context.Body.Method.Module;
@@ -1483,32 +1503,37 @@ static IEnumerable<Instruction> SetValue(VariableDefinition parent, FieldReferen
14831503
var @else = Create(OpCodes.Nop);
14841504
var endif = Create(OpCodes.Nop);
14851505

1486-
if (context.Variables[elementNode].VariableType.FullName == "System.Object")
1506+
1507+
var varValue = context.Variables[elementNode];
1508+
if (varValue.VariableType.FullName == "System.Object")
14871509
{
14881510
//if(value != null && value.GetType().IsAssignableFrom(typeof(BindingBase)))
1489-
yield return Create(Ldloc, context.Variables[elementNode]);
1511+
yield return Create(Ldloc, varValue);
14901512
yield return Create(Brfalse, @else);
1491-
14921513
yield return Create(Ldtoken, module.ImportReference(context.Cache, ("Microsoft.Maui.Controls", "Microsoft.Maui.Controls", "BindingBase")));
14931514
yield return Create(Call, module.ImportMethodReference(context.Cache, ("mscorlib", "System", "Type"), methodName: "GetTypeFromHandle", parameterTypes: new[] { ("mscorlib", "System", "RuntimeTypeHandle") }, isStatic: true));
1494-
yield return Create(Ldloc, context.Variables[elementNode]);
1515+
yield return Create(Ldloc, varValue);
14951516
yield return Create(Callvirt, module.ImportMethodReference(context.Cache, ("mscorlib", "System", "Object"), methodName: "GetType", paramCount: 0));
14961517
yield return Create(Callvirt, module.ImportMethodReference(context.Cache, ("mscorlib", "System", "Type"), methodName: "IsAssignableFrom", parameterTypes: new[] { ("mscorlib", "System", "Type") }));
14971518
yield return Create(Brfalse, @else);
14981519
//then
1499-
yield return Create(Ldloc, context.Variables[elementNode]);
1520+
yield return Create(Ldloc, varValue);
15001521
yield return Create(Br, endif);
15011522
//else
15021523
yield return @else;
15031524
}
15041525
var bpTypeRef = bpRef.GetBindablePropertyType(context.Cache, iXmlLineInfo, module);
1505-
foreach (var instruction in context.Variables[elementNode].LoadAs(context.Cache, bpTypeRef, module))
1526+
foreach (var instruction in varValue.LoadAs(context.Cache, bpTypeRef, module))
15061527
yield return instruction;
15071528
if (bpTypeRef.IsValueType)
1529+
{
1530+
if ( bpTypeRef.ResolveCached(context.Cache).FullName == "System.Nullable`1"
1531+
&& TypeRefComparer.Default.Equals(varValue.VariableType, ((GenericInstanceType)bpTypeRef).GenericArguments[0]))
1532+
bpTypeRef = ((GenericInstanceType)bpTypeRef).GenericArguments[0];
15081533
yield return Create(Box, module.ImportReference(bpTypeRef));
1509-
1534+
}
15101535
//endif
1511-
if (context.Variables[elementNode].VariableType.FullName == "System.Object")
1536+
if (varValue.VariableType.FullName == "System.Object")
15121537
yield return endif;
15131538

15141539
}

src/Controls/tests/Xaml.UnitTests/Issues/Bz24910.xaml.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ public void ConversionForNullable(bool useCompiledXaml)
4747
[TestCase(true), TestCase(false)]
4848
public void AllowNull(bool useCompiledXaml)
4949
{
50+
if (useCompiledXaml)
51+
MockCompiler.Compile(typeof(Bz24910));
52+
5053
var page = new Bz24910(useCompiledXaml);
5154
var control = page.control2;
5255
Assert.Null(control.NullableInt);
@@ -85,7 +88,7 @@ public void AllowNonBindableNullable(bool useCompiledXaml)
8588
public class Bz24910Control : Button
8689
{
8790
public static readonly BindableProperty NullableIntProperty =
88-
BindableProperty.Create("NullableInt", typeof(int?), typeof(Bz24910Control), default(int?));
91+
BindableProperty.Create(nameof(NullableInt), typeof(int?), typeof(Bz24910Control), default(int?));
8992

9093
public int? NullableInt
9194
{
@@ -94,7 +97,7 @@ public int? NullableInt
9497
}
9598

9699
public static readonly BindableProperty NullableDoubleProperty =
97-
BindableProperty.Create("NullableDouble", typeof(double?), typeof(Bz24910Control), default(double?));
100+
BindableProperty.Create(nameof(NullableDouble), typeof(double?), typeof(Bz24910Control), default(double?));
98101

99102
public double? NullableDouble
100103
{

src/Controls/tests/Xaml.UnitTests/Issues/Unreported008.xaml

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,26 @@
22
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
33
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
44
xmlns:sys="clr-namespace:System;assembly=mscorlib"
5+
xmlns:local="clr-namespace:Microsoft.Maui.Controls.Xaml.UnitTests"
56
x:Class="Microsoft.Maui.Controls.Xaml.UnitTests.Unreported008">
6-
<DatePicker Date="{x:Static sys:DateTime.Now}" x:Name="picker0">
7-
<DatePicker.Format>yyyy-MM-dd</DatePicker.Format>
8-
<DatePicker.MinimumDate>
9-
<sys:DateTime x:FactoryMethod="Parse">
10-
<x:Arguments>
11-
<x:String>Jan 1 2000</x:String>
12-
</x:Arguments>
13-
</sys:DateTime>
14-
</DatePicker.MinimumDate>
15-
<DatePicker.MaximumDate>
16-
<sys:DateTime x:FactoryMethod="Parse">
17-
<x:Arguments>
18-
<x:String>Dec 31 2050</x:String>
19-
</x:Arguments>
20-
</sys:DateTime>
21-
</DatePicker.MaximumDate>
22-
</DatePicker>
7+
<StackLayout>
8+
<DatePicker Date="{x:Static sys:DateTime.Now}" x:Name="picker0">
9+
<DatePicker.Format>yyyy-MM-dd</DatePicker.Format>
10+
<DatePicker.MinimumDate>
11+
<sys:DateTime x:FactoryMethod="Parse">
12+
<x:Arguments>
13+
<x:String>Jan 1 2000</x:String>
14+
</x:Arguments>
15+
</sys:DateTime>
16+
</DatePicker.MinimumDate>
17+
<DatePicker.MaximumDate>
18+
<sys:DateTime x:FactoryMethod="Parse">
19+
<x:Arguments>
20+
<x:String>Dec 31 2050</x:String>
21+
</x:Arguments>
22+
</sys:DateTime>
23+
</DatePicker.MaximumDate>
24+
</DatePicker>
25+
<local:Unreported008View Date="{x:Static sys:DateTime.Now}" x:Name="view0"/>
26+
</StackLayout>
2327
</ContentPage>

src/Controls/tests/Xaml.UnitTests/Issues/Unreported008.xaml.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,17 @@
44

55
namespace Microsoft.Maui.Controls.Xaml.UnitTests
66
{
7+
public class Unreported008View : ContentView
8+
{
9+
public static readonly BindableProperty DateProperty = BindableProperty.Create(nameof(Date), typeof(DateTime?), typeof(Unreported008View), null);
10+
11+
public DateTime? Date
12+
{
13+
get { return (DateTime?)GetValue(DateProperty); }
14+
set { SetValue(DateProperty, value); }
15+
}
16+
}
17+
718
public partial class Unreported008 : ContentPage
819
{
920
public Unreported008()
@@ -27,6 +38,8 @@ public void PickerDateTimesAndXamlC(bool useCompiledXaml)
2738
Assert.AreEqual(DateTime.Today, picker.Date.Date);
2839
Assert.AreEqual(new DateTime(2000, 1, 1), picker.MinimumDate);
2940
Assert.AreEqual(new DateTime(2050, 12, 31), picker.MaximumDate);
41+
42+
Assert.AreEqual(DateTime.Today, page.view0.Date.Value.Date);
3043
}
3144
}
3245
}

0 commit comments

Comments
 (0)