diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/TypedConstantInfo.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/TypedConstantInfo.cs
index e1bd70750..4538afafa 100644
--- a/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/TypedConstantInfo.cs
+++ b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/TypedConstantInfo.cs
@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using System;
+using System.Globalization;
using System.Linq;
using CommunityToolkit.Mvvm.SourceGenerators.Helpers;
using Microsoft.CodeAnalysis;
@@ -89,7 +90,13 @@ public override ExpressionSyntax GetSyntax()
{
byte b => Literal(b),
char c => Literal(c),
- double d => Literal(d),
+
+ // For doubles, we need to manually format it and always add the trailing "D" suffix.
+ // This ensures that the correct type is produced if the expression was assigned to
+ // an object (eg. the literal was used in an attribute object parameter/property).
+ double d => Literal(d.ToString("R", CultureInfo.InvariantCulture) + "D", d),
+
+ // For floats, Roslyn will automatically add the "F" suffix, so no extra work is needed
float f => Literal(f),
int i => Literal(i),
long l => Literal(l),
diff --git a/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Test_SourceGeneratorsCodegen.cs b/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Test_SourceGeneratorsCodegen.cs
index 8cf4b9daa..0a357ffac 100644
--- a/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Test_SourceGeneratorsCodegen.cs
+++ b/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Test_SourceGeneratorsCodegen.cs
@@ -94,6 +94,105 @@ public string Name
VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel.g.cs", result));
}
+ // See https://github.com/CommunityToolkit/dotnet/issues/601
+ [TestMethod]
+ public void ObservablePropertyWithForwardedAttributesWithNumberLiterals_PreservesType()
+ {
+ string source = """
+ using System.ComponentModel;
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ #nullable enable
+
+ namespace MyApp;
+
+ partial class MyViewModel : ObservableObject
+ {
+ const double MyDouble = 3.14;
+ const float MyFloat = 3.14f;
+
+ [ObservableProperty]
+ [property: DefaultValue(0.0)]
+ [property: DefaultValue(1.24)]
+ [property: DefaultValue(0.0f)]
+ [property: DefaultValue(0.0f)]
+ [property: DefaultValue(MyDouble)]
+ [property: DefaultValue(MyFloat)]
+ private object? a;
+ }
+
+ public class DefaultValueAttribute : Attribute
+ {
+ public DefaultValueAttribute(object value)
+ {
+ }
+ }
+ """;
+
+ string result = """
+ //
+ #pragma warning disable
+ #nullable enable
+ namespace MyApp
+ {
+ partial class MyViewModel
+ {
+ ///
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", "8.1.0.0")]
+ [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
+ [global::MyApp.DefaultValueAttribute(0D)]
+ [global::MyApp.DefaultValueAttribute(1.24D)]
+ [global::MyApp.DefaultValueAttribute(0F)]
+ [global::MyApp.DefaultValueAttribute(0F)]
+ [global::MyApp.DefaultValueAttribute(3.14D)]
+ [global::MyApp.DefaultValueAttribute(3.14F)]
+ public object? A
+ {
+ get => a;
+ set
+ {
+ if (!global::System.Collections.Generic.EqualityComparer