Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 22 additions & 2 deletions src/Controls/src/SourceGen/GeneratorHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -393,14 +393,34 @@ public static (string? source, XamlProjectItemForCB? xamlItem, IList<Diagnostic>

/// <summary>
/// Formats a value as a culture-independent C# literal for source generation.
/// Uses SymbolDisplay.FormatPrimitive to ensure proper handling of special values like NaN and Infinity
/// but also numeric types and makes sure they are formatted correctly.
/// Handles special floating-point values (NaN, Infinity) and uses SymbolDisplay.FormatPrimitive
/// for regular numeric types to ensure they are formatted correctly.
/// </summary>
/// <param name="value">The value to format</param>
/// <param name="quoted">Whether to include quotes around the formatted value</param>
/// <returns>A culture-independent string representation suitable for source generation</returns>
public static string FormatInvariant(object value, bool quoted = false)
{
// Handle special floating-point values that SymbolDisplay.FormatPrimitive doesn't prefix correctly
if (value is double d)
{
if (double.IsNaN(d))
return "double.NaN";
if (double.IsPositiveInfinity(d))
return "double.PositiveInfinity";
if (double.IsNegativeInfinity(d))
return "double.NegativeInfinity";
}
else if (value is float f)
{
if (float.IsNaN(f))
return "float.NaN";
if (float.IsPositiveInfinity(f))
return "float.PositiveInfinity";
if (float.IsNegativeInfinity(f))
return "float.NegativeInfinity";
}

return SymbolDisplay.FormatPrimitive(value, quoteStrings: quoted, useHexadecimalNumbers: false);
}

Expand Down
212 changes: 212 additions & 0 deletions src/Controls/tests/SourceGen.UnitTests/Maui33532Tests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
using System;
using System.Linq;
using Xunit;

namespace Microsoft.Maui.Controls.SourceGen.UnitTests;

/// <summary>
/// Tests for issue #33532: NaN value in XAML generates invalid code
/// When Padding="NaN" is used, the source generator was generating bare "NaN" instead of "double.NaN"
/// </summary>
public class Maui33532Tests : SourceGenXamlInitializeComponentTestBase
{
[Fact]
public void ThicknessWithSingleNaNValue()
{
// Issue #33532: Padding="NaN" generates invalid code with bare "NaN" instead of "double.NaN"
var xaml =
"""
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Test.TestPage">
<Button x:Name="testButton" Padding="NaN" Text="Test"/>
</ContentPage>
""";

var code =
"""
using Microsoft.Maui.Controls;
using Microsoft.Maui.Controls.Xaml;

namespace Test;

[XamlProcessing(XamlInflator.SourceGen)]
public partial class TestPage : ContentPage
{
public TestPage()
{
InitializeComponent();
}
}
""";

var (result, generated) = RunGenerator(xaml, code, targetFramework: "net10.0");
Assert.False(result.Diagnostics.Any());

// Verify that NaN is correctly generated as double.NaN
Assert.Contains("double.NaN", generated, StringComparison.Ordinal);

// Ensure incorrect bare NaN is not generated (would fail with CS0103)
// The pattern "new global::Microsoft.Maui.Thickness(NaN)" would be incorrect
Assert.DoesNotContain("Thickness(NaN)", generated, StringComparison.Ordinal);
}

[Fact]
public void ThicknessWithTwoNaNValues()
{
// Test Padding="NaN,NaN" (horizontal, vertical)
var xaml =
"""
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Test.TestPage">
<Button x:Name="testButton" Padding="NaN,NaN" Text="Test"/>
</ContentPage>
""";

var code =
"""
using Microsoft.Maui.Controls;
using Microsoft.Maui.Controls.Xaml;

namespace Test;

[XamlProcessing(XamlInflator.SourceGen)]
public partial class TestPage : ContentPage
{
public TestPage()
{
InitializeComponent();
}
}
""";

var (result, generated) = RunGenerator(xaml, code, targetFramework: "net10.0");
Assert.False(result.Diagnostics.Any());

// Verify that NaN is correctly generated as double.NaN
Assert.Contains("double.NaN", generated, StringComparison.Ordinal);
}

[Fact]
public void ThicknessWithFourNaNValues()
{
// Test Padding="NaN,NaN,NaN,NaN" (left, top, right, bottom)
var xaml =
"""
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Test.TestPage">
<Button x:Name="testButton" Padding="NaN,NaN,NaN,NaN" Text="Test"/>
</ContentPage>
""";

var code =
"""
using Microsoft.Maui.Controls;
using Microsoft.Maui.Controls.Xaml;

namespace Test;

[XamlProcessing(XamlInflator.SourceGen)]
public partial class TestPage : ContentPage
{
public TestPage()
{
InitializeComponent();
}
}
""";

var (result, generated) = RunGenerator(xaml, code, targetFramework: "net10.0");
Assert.False(result.Diagnostics.Any());

// Verify that NaN is correctly generated as double.NaN (should appear 4 times)
Assert.Contains("double.NaN", generated, StringComparison.Ordinal);
}

[Fact]
public void ThicknessWithMixedNaNAndRegularValues()
{
// Test mixing NaN with regular values: Padding="NaN,10,NaN,20"
var xaml =
"""
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Test.TestPage">
<Button x:Name="testButton" Padding="NaN,10,NaN,20" Text="Test"/>
</ContentPage>
""";

var code =
"""
using Microsoft.Maui.Controls;
using Microsoft.Maui.Controls.Xaml;

namespace Test;

[XamlProcessing(XamlInflator.SourceGen)]
public partial class TestPage : ContentPage
{
public TestPage()
{
InitializeComponent();
}
}
""";

var (result, generated) = RunGenerator(xaml, code, targetFramework: "net10.0");
Assert.False(result.Diagnostics.Any());

// Verify that both NaN and regular values are generated correctly
Assert.Contains("double.NaN", generated, StringComparison.Ordinal);
Assert.Contains("Thickness", generated, StringComparison.Ordinal);
}

[Fact]
public void MarginWithNaNValue()
{
// Test Margin (also uses ThicknessConverter) with NaN
var xaml =
"""
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Test.TestPage">
<Label x:Name="testLabel" Margin="NaN" Text="Test"/>
</ContentPage>
""";

var code =
"""
using Microsoft.Maui.Controls;
using Microsoft.Maui.Controls.Xaml;

namespace Test;

[XamlProcessing(XamlInflator.SourceGen)]
public partial class TestPage : ContentPage
{
public TestPage()
{
InitializeComponent();
}
}
""";

var (result, generated) = RunGenerator(xaml, code, targetFramework: "net10.0");
Assert.False(result.Diagnostics.Any());

// Verify that NaN is correctly generated as double.NaN for Margin
Assert.Contains("double.NaN", generated, StringComparison.Ordinal);
}
}
11 changes: 11 additions & 0 deletions src/Controls/tests/Xaml.UnitTests/Issues/Maui33532.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Microsoft.Maui.Controls.Xaml.UnitTests.Maui33532">
<!-- Test: NaN value in XAML should generate valid code with double.NaN -->
<StackLayout>
<Button x:Name="buttonNaN" Padding="NaN" Text="Padding = NaN"/>
<Button x:Name="buttonNaNComma" Padding="NaN,NaN" Text="Padding = NaN,NaN"/>
<Button x:Name="buttonNaNFull" Padding="NaN,NaN,NaN,NaN" Text="Padding = NaN,NaN,NaN,NaN"/>
</StackLayout>
</ContentPage>
46 changes: 46 additions & 0 deletions src/Controls/tests/Xaml.UnitTests/Issues/Maui33532.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using Xunit;

namespace Microsoft.Maui.Controls.Xaml.UnitTests;

public partial class Maui33532 : ContentPage
{
public Maui33532() => InitializeComponent();

[Collection("Issue")]
public class Tests
{
[Theory]
[XamlInflatorData]
internal void NaNValueInXamlGeneratesValidCode(XamlInflator inflator)
{
// This test reproduces issue #33532:
// When a XAML file contains NaN as a value (e.g., Padding="NaN"),
// the XAML Source Generator was generating invalid C# code using bare "NaN"
// instead of "double.NaN", causing CS0103 compiler error.
var page = new Maui33532(inflator);

Assert.NotNull(page);
Assert.NotNull(page.buttonNaN);
Assert.NotNull(page.buttonNaNComma);
Assert.NotNull(page.buttonNaNFull);

// Verify NaN values were applied correctly (all sides should be NaN)
Assert.True(double.IsNaN(page.buttonNaN.Padding.Left));
Assert.True(double.IsNaN(page.buttonNaN.Padding.Top));
Assert.True(double.IsNaN(page.buttonNaN.Padding.Right));
Assert.True(double.IsNaN(page.buttonNaN.Padding.Bottom));

// Verify 2-value NaN syntax (horizontal, vertical)
Assert.True(double.IsNaN(page.buttonNaNComma.Padding.Left));
Assert.True(double.IsNaN(page.buttonNaNComma.Padding.Top));

// Verify 4-value NaN syntax
Assert.True(double.IsNaN(page.buttonNaNFull.Padding.Left));
Assert.True(double.IsNaN(page.buttonNaNFull.Padding.Top));
Assert.True(double.IsNaN(page.buttonNaNFull.Padding.Right));
Assert.True(double.IsNaN(page.buttonNaNFull.Padding.Bottom));
}
}
}
Loading