From d0631ba9bbbb55d1bd3d552f3abd6c1d6f07b5e3 Mon Sep 17 00:00:00 2001 From: Adam Boniecki Date: Tue, 16 Jan 2024 16:33:21 +0100 Subject: [PATCH 1/4] Add unit tests for interp strings with string expr Sanity check that lowering to concat does not break these simple cases --- .../Language/InterpolatedStringsTests.fs | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/tests/FSharp.Compiler.ComponentTests/Language/InterpolatedStringsTests.fs b/tests/FSharp.Compiler.ComponentTests/Language/InterpolatedStringsTests.fs index c8f2eafa937..28e3456deb2 100644 --- a/tests/FSharp.Compiler.ComponentTests/Language/InterpolatedStringsTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/Language/InterpolatedStringsTests.fs @@ -128,4 +128,31 @@ type Foo () = x """ |> compile - |> shouldSucceed \ No newline at end of file + |> shouldSucceed + + [] + // Test different number of interpolated string parts + [] + [] + [] + let ``Interpolated expressions are strings`` (strToPrint: string, expectedOut: string) = + Fsx $""" +let x = {strToPrint} +printfn "%%s" x + """ + |> compileExeAndRun + |> shouldSucceed + |> withStdOutContains expectedOut + + [] + [] + [] + [] + let ``In FormattableString, interpolated expressions are strings`` (formattableStr: string, expectedOut: string) = + Fsx $""" +let x = {formattableStr} : System.FormattableString +printfn "%%s" (System.Globalization.CultureInfo "en-US" |> x.ToString) + """ + |> compileExeAndRun + |> shouldSucceed + |> withStdOutContains expectedOut \ No newline at end of file From 47451656461f321eafbd6c7ebe7b52731445b511 Mon Sep 17 00:00:00 2001 From: Adam Boniecki Date: Tue, 16 Jan 2024 17:38:00 +0100 Subject: [PATCH 2/4] Add tests for FormattableString --- .../Language/InterpolatedStringsTests.fs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/FSharp.Compiler.ComponentTests/Language/InterpolatedStringsTests.fs b/tests/FSharp.Compiler.ComponentTests/Language/InterpolatedStringsTests.fs index 28e3456deb2..d0f7b316e36 100644 --- a/tests/FSharp.Compiler.ComponentTests/Language/InterpolatedStringsTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/Language/InterpolatedStringsTests.fs @@ -145,12 +145,13 @@ printfn "%%s" x |> withStdOutContains expectedOut [] - [] - [] - [] - let ``In FormattableString, interpolated expressions are strings`` (formattableStr: string, expectedOut: string) = + [] + [] + [] + let ``In FormattableString, interpolated expressions are strings`` (formattableStr: string, expectedOut: string, argCount: int) = Fsx $""" let x = {formattableStr} : System.FormattableString +assert(x.ArgumentCount = {argCount}) printfn "%%s" (System.Globalization.CultureInfo "en-US" |> x.ToString) """ |> compileExeAndRun From 686a4aee369983469b63c4443514a69deb2bb8c0 Mon Sep 17 00:00:00 2001 From: Adam Boniecki Date: Tue, 16 Jan 2024 15:20:34 +0100 Subject: [PATCH 3/4] Add optimization in CheckExpressions Initial attempt with many TODOs, also not sure whether it should be done in checking, but it seems that later we would have to again parse the string (since CheckExpressions is going from AST version of an interpolated string to a sprintf call basically) --- src/Compiler/Checking/CheckExpressions.fs | 34 +++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/Compiler/Checking/CheckExpressions.fs b/src/Compiler/Checking/CheckExpressions.fs index d72e8324300..3c34f105d42 100644 --- a/src/Compiler/Checking/CheckExpressions.fs +++ b/src/Compiler/Checking/CheckExpressions.fs @@ -7332,6 +7332,40 @@ and TcInterpolatedStringExpr cenv (overallTy: OverallTy) env m tpenv (parts: Syn // Type check the expressions filling the holes let fillExprs, tpenv = TcExprsNoFlexes cenv env m tpenv argTys synFillExprs + // True iff whole expression and all interpolation expressions are strings + let fillExprsAreString = + isString && + (argTys, synFillExprs) + ||> List.forall2 (fun ttype expr -> + AddCxTypeEqualsTypeUndoIfFailedOrWarnings env.DisplayEnv cenv.css expr.Range ttype g.string_ty) + + // If all fill expressions are strings and there is less then 5 parts of the interpolated string total + // then we can use System.String.Concat instead of a sprintf call + if fillExprsAreString && parts.Length < 5 then + let rec f xs ys acc = + match xs with + | SynInterpolatedStringPart.String(s, m)::xs -> + f xs ys ((mkString g m s)::acc) + | SynInterpolatedStringPart.FillExpr(_, _)::xs -> + match ys with + | y::ys -> f xs ys (y::acc) + | _ -> error(Error((0, "FOOBAR"), m)) // TODO XXX wrong error + | _ -> acc + + let args = f parts fillExprs [] |> List.rev + assert (args.Length = parts.Length) + if args.Length = 4 then + (mkStaticCall_String_Concat4 g m args[0] args[1] args[2] args[3], tpenv) + elif args.Length = 3 then + (mkStaticCall_String_Concat3 g m args[0] args[1] args[2], tpenv) + elif args.Length = 2 then + (mkStaticCall_String_Concat2 g m args[0] args[1], tpenv) + else + // Throw some error + error(Error((0, "FOOBAR2"), m)) // TODO XXX wrong error + else // TODO XXX messed up indentation below + + let fillExprsBoxed = (argTys, fillExprs) ||> List.map2 (mkCallBox g m) let argsExpr = mkArray (g.obj_ty, fillExprsBoxed, m) From 961b66de93c368f482188222f588a70e48a5dc61 Mon Sep 17 00:00:00 2001 From: Adam Boniecki Date: Tue, 16 Jan 2024 18:41:50 +0100 Subject: [PATCH 4/4] Add IL test for lowering to concat --- .../EmittedIL/StringFormatAndInterpolation.fs | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/StringFormatAndInterpolation.fs b/tests/FSharp.Compiler.ComponentTests/EmittedIL/StringFormatAndInterpolation.fs index 0ef7d23422e..4a0e2c6062d 100644 --- a/tests/FSharp.Compiler.ComponentTests/EmittedIL/StringFormatAndInterpolation.fs +++ b/tests/FSharp.Compiler.ComponentTests/EmittedIL/StringFormatAndInterpolation.fs @@ -5,8 +5,8 @@ namespace EmittedIL open Xunit open FSharp.Test.Compiler -#if !DEBUG // sensitive to debug-level code coming across from debug FSharp.Core module ``StringFormatAndInterpolation`` = +#if !DEBUG // sensitive to debug-level code coming across from debug FSharp.Core [] let ``Interpolated string with no holes is reduced to a string or simple format when used in printf``() = FSharp """ @@ -34,3 +34,21 @@ IL_0017: ret"""] #endif + [] + let ``Interpolated string with 3 parts consisting only of strings is lowered to concat`` () = + let str = "$\"\"\"ab{\"c\"}d\"\"\"" + FSharp $""" +module StringFormatAndInterpolation + +let str () = {str} + """ + |> compile + |> shouldSucceed + |> verifyIL [""" +IL_0000: ldstr "ab" +IL_0005: ldstr "c" +IL_000a: ldstr "d" +IL_000f: call string [runtime]System.String::Concat(string, + string, + string) +IL_0014: ret"""] \ No newline at end of file