dotnet / roslyn

The Roslyn .NET compiler provides C# and Visual Basic languages with rich code analysis APIs.
https://docs.microsoft.com/dotnet/csharp/roslyn-sdk/
MIT License
19.11k stars 4.04k forks source link

ToCSharpString outputs floats without a type suffix #76061

Open DanJBower opened 4 days ago

DanJBower commented 4 days ago

Version Used:

\ \

Steps to Reproduce:

  1. Use ToCSharpString on any TypedConstant that represents a float

Expected Behavior:

It produces a string that represents the float and could be used to initialise a new float in C#, i.e., 0.5f

Actual Behavior:

It produces a string without a type suffix, i.e., 0.5

Use case:

I'm creating a source generator that generates source code based on NamedArguments in AttributeData. When the named argument's value is retrieved, I use arg[x].Value.ToCSharpString() to turn the value into a string to place in the generated code and the string being generated for floats is causing compile errors

CyrusNajmabadi commented 3 days ago

ToCSharpString doesn't guarantee the natural type in the expression matches the original. It just represents legal c# syntax.

DanJBower commented 3 days ago

I wouldn't expect it to match the original (like if the original expression was complex where multiple floats were added together or something), but I was expecting it to produce compilable code. Would 0.5 be considered legal C# syntax for a typed constant representing a float, as wouldn't it always be interpreted as a double?

jcouv commented 1 day ago

This would need API review (since changing the behavior of a public API), but I took a quick look. This could be a good first time contribution. Below is a code change that should work, but tests would need to be added as multiple kinds of literals would be affected (uint, long, ulong, double, float and decimal). It may even be possible to make the change to FormatPrimitive unconditional, but that would require more investigation/discussion, as that is a public API too and it is also used in SymbolDisplayVisitor so might break some things.

diff --git a/src/Compilers/CSharp/Portable/SymbolDisplay/SymbolDisplay.cs b/src/Compilers/CSharp/Portable/SymbolDisplay/SymbolDisplay.cs
index dddb063e87b..03f6b8180a4 100644
--- a/src/Compilers/CSharp/Portable/SymbolDisplay/SymbolDisplay.cs
+++ b/src/Compilers/CSharp/Portable/SymbolDisplay/SymbolDisplay.cs
@@ -298,6 +298,11 @@ private static ArrayBuilder<SymbolDisplayPart> PopulateDisplayParts(
         /// and <c>null</c>.
         /// </remarks>
         public static string FormatPrimitive(object obj, bool quoteStrings, bool useHexadecimalNumbers)
+        {
+            return FormatPrimitive(obj, quoteStrings, useHexadecimalNumbers, includeTypeSuffix: false);
+        }
+
+        internal static string FormatPrimitive(object obj, bool quoteStrings, bool useHexadecimalNumbers, bool includeTypeSuffix = false)
         {
             var options = ObjectDisplayOptions.EscapeNonPrintableCharacters;
             if (quoteStrings)
@@ -308,6 +313,10 @@ public static string FormatPrimitive(object obj, bool quoteStrings, bool useHexa
             {
                 options |= ObjectDisplayOptions.UseHexadecimalNumbers;
             }
+            if (includeTypeSuffix)
+            {
+                options |= ObjectDisplayOptions.IncludeTypeSuffix;
+            }
             return ObjectDisplay.FormatPrimitive(obj, options);
         }

diff --git a/src/Compilers/CSharp/Portable/Symbols/TypedConstantExtensions.cs b/src/Compilers/CSharp/Portable/Symbols/TypedConstantExtensions.cs
index 5b8ba5f5ca4..4e1ea9c485a 100644
--- a/src/Compilers/CSharp/Portable/Symbols/TypedConstantExtensions.cs
+++ b/src/Compilers/CSharp/Portable/Symbols/TypedConstantExtensions.cs
@@ -40,7 +40,7 @@ public static string ToCSharpString(this TypedConstant constant)
             }

             Debug.Assert(constant.ValueInternal is object);
-            return SymbolDisplay.FormatPrimitive(constant.ValueInternal, quoteStrings: true, useHexadecimalNumbers: false);
+            return SymbolDisplay.FormatPrimitive(constant.ValueInternal, quoteStrings: true, useHexadecimalNumbers: false, includeTypeSuffix: true);
         }

         // Decode the value of enum constant