diff --git a/src/Compiler/Checking/CheckExpressions.fs b/src/Compiler/Checking/CheckExpressions.fs index d72e832430..3c34f105d4 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) diff --git a/tests/FSharp.Compiler.ComponentTests/EmittedIL/StringFormatAndInterpolation.fs b/tests/FSharp.Compiler.ComponentTests/EmittedIL/StringFormatAndInterpolation.fs index 0ef7d23422..4a0e2c6062 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 diff --git a/tests/FSharp.Compiler.ComponentTests/Language/InterpolatedStringsTests.fs b/tests/FSharp.Compiler.ComponentTests/Language/InterpolatedStringsTests.fs index c8f2eafa93..d0f7b316e3 100644 --- a/tests/FSharp.Compiler.ComponentTests/Language/InterpolatedStringsTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/Language/InterpolatedStringsTests.fs @@ -128,4 +128,32 @@ 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, argCount: int) = + Fsx $""" +let x = {formattableStr} : System.FormattableString +assert(x.ArgumentCount = {argCount}) +printfn "%%s" (System.Globalization.CultureInfo "en-US" |> x.ToString) + """ + |> compileExeAndRun + |> shouldSucceed + |> withStdOutContains expectedOut \ No newline at end of file