From d26687d0f265beaf7d253123918f803276e078b6 Mon Sep 17 00:00:00 2001 From: Adam Boniecki Date: Mon, 28 Nov 2022 18:11:35 +0100 Subject: [PATCH 1/4] Double curly braces when parsing FormattableString Interpolation expressions have already been replaced by %P() at this point, so it should be safe and should only undo the opposite transformation done by lexer. --- src/Compiler/Checking/CheckFormatStrings.fs | 9 ++++++++- .../Language/InterpolatedStringsTests.fs | 12 +++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/Compiler/Checking/CheckFormatStrings.fs b/src/Compiler/Checking/CheckFormatStrings.fs index 8652e30576..b4f1d84923 100644 --- a/src/Compiler/Checking/CheckFormatStrings.fs +++ b/src/Compiler/Checking/CheckFormatStrings.fs @@ -55,7 +55,7 @@ let parseFormatStringInternal isInterpolated isFormattableString (context: FormatStringCheckContext option) - fmt + (fmt: string) printerArgTy printerResidueTy = @@ -86,6 +86,13 @@ let parseFormatStringInternal // there are no accurate intra-string ranges available for exact error message locations within the string. // The 'm' range passed as an input is however accurate and covers the whole string. // + let fmt = + fmt + // Double all curly braces + // They were stripped away in lexing, but at this point all {expr} are already replaced by %P() + |> Seq.collect (fun x -> if x = '{' || x = '}' then [x;x] else [x]) + |> System.String.Concat + let fmt, fragments = //printfn "--------------------" diff --git a/tests/FSharp.Compiler.ComponentTests/Language/InterpolatedStringsTests.fs b/tests/FSharp.Compiler.ComponentTests/Language/InterpolatedStringsTests.fs index 14dbb4fd0a..26e4b8f08e 100644 --- a/tests/FSharp.Compiler.ComponentTests/Language/InterpolatedStringsTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/Language/InterpolatedStringsTests.fs @@ -30,4 +30,14 @@ let b: System.IComparable = $"string" let c: System.IFormattable = $"string" """ |> compile - |> shouldSucceed \ No newline at end of file + |> shouldSucceed + + [] + let ``Interpolated string literal typed as FormattableString handles double braces correctly`` () = + Fsx """ +let a = $"{{hello}} world" : System.FormattableString +printf $"{a.Format}" + """ + |> compileExeAndRun + |> shouldSucceed + |> withStdOutContains "{{hello}} world" \ No newline at end of file From 551aeacd7369aa46681ea2e24610f4d53709eafa Mon Sep 17 00:00:00 2001 From: Adam Boniecki Date: Wed, 30 Nov 2022 15:23:18 +0100 Subject: [PATCH 2/4] Refactor to make the intention clearer --- src/Compiler/Checking/CheckFormatStrings.fs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Compiler/Checking/CheckFormatStrings.fs b/src/Compiler/Checking/CheckFormatStrings.fs index b4f1d84923..0666b26017 100644 --- a/src/Compiler/Checking/CheckFormatStrings.fs +++ b/src/Compiler/Checking/CheckFormatStrings.fs @@ -48,6 +48,13 @@ let newInfo () = addZeros = false precision = false} +let escapeDotnetFormatString str = + str + // We need to double '{' and '}', because even if they were escaped in the + // original string, extra curly braces were stripped away by the F# lexer. + |> Seq.collect (fun x -> if x = '{' || x = '}' then [x;x] else [x]) + |> System.String.Concat + let parseFormatStringInternal (m: range) (fragRanges: range list) @@ -86,13 +93,6 @@ let parseFormatStringInternal // there are no accurate intra-string ranges available for exact error message locations within the string. // The 'm' range passed as an input is however accurate and covers the whole string. // - let fmt = - fmt - // Double all curly braces - // They were stripped away in lexing, but at this point all {expr} are already replaced by %P() - |> Seq.collect (fun x -> if x = '{' || x = '}' then [x;x] else [x]) - |> System.String.Concat - let fmt, fragments = //printfn "--------------------" @@ -182,7 +182,7 @@ let parseFormatStringInternal | _ -> // Don't muck with the fmt when there is no source code context to go get the original // source code (i.e. when compiling or background checking) - fmt, [ (0, 1, m) ] + escapeDotnetFormatString fmt, [ (0, 1, m) ] let len = fmt.Length From ec6e3cbae53ab3f6feddadb6a6885c42b69effdd Mon Sep 17 00:00:00 2001 From: Adam Boniecki Date: Thu, 1 Dec 2022 13:08:39 +0100 Subject: [PATCH 3/4] Enable the fix only for langpreview --- src/Compiler/Checking/CheckFormatStrings.fs | 4 +++- src/Compiler/FSComp.txt | 1 + src/Compiler/Facilities/LanguageFeatures.fs | 7 +++++-- src/Compiler/Facilities/LanguageFeatures.fsi | 1 + src/Compiler/xlf/FSComp.txt.cs.xlf | 5 +++++ src/Compiler/xlf/FSComp.txt.de.xlf | 5 +++++ src/Compiler/xlf/FSComp.txt.es.xlf | 5 +++++ src/Compiler/xlf/FSComp.txt.fr.xlf | 5 +++++ src/Compiler/xlf/FSComp.txt.it.xlf | 5 +++++ src/Compiler/xlf/FSComp.txt.ja.xlf | 5 +++++ src/Compiler/xlf/FSComp.txt.ko.xlf | 5 +++++ src/Compiler/xlf/FSComp.txt.pl.xlf | 5 +++++ src/Compiler/xlf/FSComp.txt.pt-BR.xlf | 5 +++++ src/Compiler/xlf/FSComp.txt.ru.xlf | 5 +++++ src/Compiler/xlf/FSComp.txt.tr.xlf | 5 +++++ src/Compiler/xlf/FSComp.txt.zh-Hans.xlf | 5 +++++ src/Compiler/xlf/FSComp.txt.zh-Hant.xlf | 5 +++++ 17 files changed, 75 insertions(+), 3 deletions(-) diff --git a/src/Compiler/Checking/CheckFormatStrings.fs b/src/Compiler/Checking/CheckFormatStrings.fs index 0666b26017..8d287145dc 100644 --- a/src/Compiler/Checking/CheckFormatStrings.fs +++ b/src/Compiler/Checking/CheckFormatStrings.fs @@ -93,6 +93,8 @@ let parseFormatStringInternal // there are no accurate intra-string ranges available for exact error message locations within the string. // The 'm' range passed as an input is however accurate and covers the whole string. // + let escapeFormatStringEnabled = g.langVersion.SupportsFeature Features.LanguageFeature.EscapeDotnetFormattableStrings + let fmt, fragments = //printfn "--------------------" @@ -182,7 +184,7 @@ let parseFormatStringInternal | _ -> // Don't muck with the fmt when there is no source code context to go get the original // source code (i.e. when compiling or background checking) - escapeDotnetFormatString fmt, [ (0, 1, m) ] + (if escapeFormatStringEnabled then escapeDotnetFormatString fmt else fmt), [ (0, 1, m) ] let len = fmt.Length diff --git a/src/Compiler/FSComp.txt b/src/Compiler/FSComp.txt index 2fca7afeb6..1f726170df 100644 --- a/src/Compiler/FSComp.txt +++ b/src/Compiler/FSComp.txt @@ -1661,3 +1661,4 @@ reprStateMachineInvalidForm,"The state machine has an unexpected form" 3548,matchNotAllowedForUnionCaseWithNoData,"Pattern discard is not allowed for union case that takes no data." 3549,tcSynTypeOrInvalidInDeclaration,"SynType.Or is not permitted in this declaration" 3550,chkDuplicatedMethodParameter,"Duplicate parameter. The parameter '%s' has been used more that once in this method." +featureEscapeBracesInFormattableString,"Escapes curly braces before calling FormattableStringFactory.Create when interpolated string literal is typed as FormattableString" \ No newline at end of file diff --git a/src/Compiler/Facilities/LanguageFeatures.fs b/src/Compiler/Facilities/LanguageFeatures.fs index 14302b0513..1cc71bf46c 100644 --- a/src/Compiler/Facilities/LanguageFeatures.fs +++ b/src/Compiler/Facilities/LanguageFeatures.fs @@ -57,6 +57,7 @@ type LanguageFeature = | MatchNotAllowedForUnionCaseWithNoData | CSharpExtensionAttributeNotRequired | ErrorForNonVirtualMembersOverrides + | EscapeDotnetFormattableStrings /// LanguageVersion management type LanguageVersion(versionText) = @@ -130,6 +131,7 @@ type LanguageVersion(versionText) = LanguageFeature.MatchNotAllowedForUnionCaseWithNoData, previewVersion LanguageFeature.CSharpExtensionAttributeNotRequired, previewVersion LanguageFeature.ErrorForNonVirtualMembersOverrides, previewVersion + LanguageFeature.EscapeDotnetFormattableStrings, previewVersion ] @@ -238,8 +240,9 @@ type LanguageVersion(versionText) = | LanguageFeature.InterfacesWithAbstractStaticMembers -> FSComp.SR.featureInterfacesWithAbstractStaticMembers () | LanguageFeature.SelfTypeConstraints -> FSComp.SR.featureSelfTypeConstraints () | LanguageFeature.MatchNotAllowedForUnionCaseWithNoData -> FSComp.SR.featureMatchNotAllowedForUnionCaseWithNoData () - | LanguageFeature.CSharpExtensionAttributeNotRequired -> FSComp.SR.featureCSharpExtensionAttributeNotRequired () - | LanguageFeature.ErrorForNonVirtualMembersOverrides -> FSComp.SR.featureErrorForNonVirtualMembersOverrides () + | LanguageFeature.CSharpExtensionAttributeNotRequired -> FSComp.SR.featureCSharpExtensionAttributeNotRequired () + | LanguageFeature.ErrorForNonVirtualMembersOverrides -> FSComp.SR.featureErrorForNonVirtualMembersOverrides () + | LanguageFeature.EscapeDotnetFormattableStrings -> FSComp.SR.featureEscapeBracesInFormattableString () /// Get a version string associated with the given feature. static member GetFeatureVersionString feature = diff --git a/src/Compiler/Facilities/LanguageFeatures.fsi b/src/Compiler/Facilities/LanguageFeatures.fsi index f471f371b9..9c8164fb9c 100644 --- a/src/Compiler/Facilities/LanguageFeatures.fsi +++ b/src/Compiler/Facilities/LanguageFeatures.fsi @@ -47,6 +47,7 @@ type LanguageFeature = | MatchNotAllowedForUnionCaseWithNoData | CSharpExtensionAttributeNotRequired | ErrorForNonVirtualMembersOverrides + | EscapeDotnetFormattableStrings /// LanguageVersion management type LanguageVersion = diff --git a/src/Compiler/xlf/FSComp.txt.cs.xlf b/src/Compiler/xlf/FSComp.txt.cs.xlf index 9f1b557acb..e5f9897690 100644 --- a/src/Compiler/xlf/FSComp.txt.cs.xlf +++ b/src/Compiler/xlf/FSComp.txt.cs.xlf @@ -187,6 +187,11 @@ chyba při zastaralém přístupu konstruktoru s atributem RequireQualifiedAccess + + Escapes curly braces before calling FormattableStringFactory.Create when interpolated string literal is typed as FormattableString + Escapes curly braces before calling FormattableStringFactory.Create when interpolated string literal is typed as FormattableString + + more types support units of measure více typů podporuje měrné jednotky diff --git a/src/Compiler/xlf/FSComp.txt.de.xlf b/src/Compiler/xlf/FSComp.txt.de.xlf index fad70824e3..f64825a850 100644 --- a/src/Compiler/xlf/FSComp.txt.de.xlf +++ b/src/Compiler/xlf/FSComp.txt.de.xlf @@ -187,6 +187,11 @@ Beim veralteten Zugriff auf das Konstrukt mit dem RequireQualifiedAccess-Attribut wird ein Fehler ausgegeben. + + Escapes curly braces before calling FormattableStringFactory.Create when interpolated string literal is typed as FormattableString + Escapes curly braces before calling FormattableStringFactory.Create when interpolated string literal is typed as FormattableString + + more types support units of measure Maßeinheitenunterstützung durch weitere Typen diff --git a/src/Compiler/xlf/FSComp.txt.es.xlf b/src/Compiler/xlf/FSComp.txt.es.xlf index 5402a968af..eab8a9249d 100644 --- a/src/Compiler/xlf/FSComp.txt.es.xlf +++ b/src/Compiler/xlf/FSComp.txt.es.xlf @@ -187,6 +187,11 @@ error en el acceso en desuso de la construcción con el atributo RequireQualifiedAccess + + Escapes curly braces before calling FormattableStringFactory.Create when interpolated string literal is typed as FormattableString + Escapes curly braces before calling FormattableStringFactory.Create when interpolated string literal is typed as FormattableString + + more types support units of measure más tipos admiten las unidades de medida diff --git a/src/Compiler/xlf/FSComp.txt.fr.xlf b/src/Compiler/xlf/FSComp.txt.fr.xlf index aadf83ade0..e61907ceb7 100644 --- a/src/Compiler/xlf/FSComp.txt.fr.xlf +++ b/src/Compiler/xlf/FSComp.txt.fr.xlf @@ -187,6 +187,11 @@ donner une erreur sur l’accès déconseillé de la construction avec l’attribut RequireQualifiedAccess + + Escapes curly braces before calling FormattableStringFactory.Create when interpolated string literal is typed as FormattableString + Escapes curly braces before calling FormattableStringFactory.Create when interpolated string literal is typed as FormattableString + + more types support units of measure d'autres types prennent en charge les unités de mesure diff --git a/src/Compiler/xlf/FSComp.txt.it.xlf b/src/Compiler/xlf/FSComp.txt.it.xlf index 8b681eb4a1..a0ee2e0de9 100644 --- a/src/Compiler/xlf/FSComp.txt.it.xlf +++ b/src/Compiler/xlf/FSComp.txt.it.xlf @@ -187,6 +187,11 @@ errore durante l'accesso deprecato del costrutto con l'attributo RequireQualifiedAccess + + Escapes curly braces before calling FormattableStringFactory.Create when interpolated string literal is typed as FormattableString + Escapes curly braces before calling FormattableStringFactory.Create when interpolated string literal is typed as FormattableString + + more types support units of measure più tipi supportano le unità di misura diff --git a/src/Compiler/xlf/FSComp.txt.ja.xlf b/src/Compiler/xlf/FSComp.txt.ja.xlf index 4b6c944481..8b46d24234 100644 --- a/src/Compiler/xlf/FSComp.txt.ja.xlf +++ b/src/Compiler/xlf/FSComp.txt.ja.xlf @@ -187,6 +187,11 @@ RequireQualifiedAccess 属性を持つコンストラクトの非推奨アクセスでエラーが発生しました + + Escapes curly braces before calling FormattableStringFactory.Create when interpolated string literal is typed as FormattableString + Escapes curly braces before calling FormattableStringFactory.Create when interpolated string literal is typed as FormattableString + + more types support units of measure 単位をサポートするその他の型 diff --git a/src/Compiler/xlf/FSComp.txt.ko.xlf b/src/Compiler/xlf/FSComp.txt.ko.xlf index 40973f9e1a..b100689461 100644 --- a/src/Compiler/xlf/FSComp.txt.ko.xlf +++ b/src/Compiler/xlf/FSComp.txt.ko.xlf @@ -187,6 +187,11 @@ RequireQualifiedAccess 특성을 사용하여 사용되지 않는 구문 액세스에 대한 오류 제공 + + Escapes curly braces before calling FormattableStringFactory.Create when interpolated string literal is typed as FormattableString + Escapes curly braces before calling FormattableStringFactory.Create when interpolated string literal is typed as FormattableString + + more types support units of measure 더 많은 형식이 측정 단위를 지원함 diff --git a/src/Compiler/xlf/FSComp.txt.pl.xlf b/src/Compiler/xlf/FSComp.txt.pl.xlf index 9b4b9dab2e..6762bd3a05 100644 --- a/src/Compiler/xlf/FSComp.txt.pl.xlf +++ b/src/Compiler/xlf/FSComp.txt.pl.xlf @@ -187,6 +187,11 @@ wskazywanie błędu w przypadku przestarzałego dostępu do konstrukcji z atrybutem RequireQualifiedAccess + + Escapes curly braces before calling FormattableStringFactory.Create when interpolated string literal is typed as FormattableString + Escapes curly braces before calling FormattableStringFactory.Create when interpolated string literal is typed as FormattableString + + more types support units of measure więcej typów obsługuje jednostki miary diff --git a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf index 3307d01761..308e07adc3 100644 --- a/src/Compiler/xlf/FSComp.txt.pt-BR.xlf +++ b/src/Compiler/xlf/FSComp.txt.pt-BR.xlf @@ -187,6 +187,11 @@ fornecer erro no acesso preterido do constructo com o atributo RequireQualifiedAccess + + Escapes curly braces before calling FormattableStringFactory.Create when interpolated string literal is typed as FormattableString + Escapes curly braces before calling FormattableStringFactory.Create when interpolated string literal is typed as FormattableString + + more types support units of measure mais tipos dão suporte para unidades de medida diff --git a/src/Compiler/xlf/FSComp.txt.ru.xlf b/src/Compiler/xlf/FSComp.txt.ru.xlf index d9de5f772a..1b9f78c698 100644 --- a/src/Compiler/xlf/FSComp.txt.ru.xlf +++ b/src/Compiler/xlf/FSComp.txt.ru.xlf @@ -187,6 +187,11 @@ выдать ошибку при устаревшем доступе к конструкции с атрибутом RequireQualifiedAccess + + Escapes curly braces before calling FormattableStringFactory.Create when interpolated string literal is typed as FormattableString + Escapes curly braces before calling FormattableStringFactory.Create when interpolated string literal is typed as FormattableString + + more types support units of measure другие типы поддерживают единицы измерения diff --git a/src/Compiler/xlf/FSComp.txt.tr.xlf b/src/Compiler/xlf/FSComp.txt.tr.xlf index 3cd9f827dc..04380c49be 100644 --- a/src/Compiler/xlf/FSComp.txt.tr.xlf +++ b/src/Compiler/xlf/FSComp.txt.tr.xlf @@ -187,6 +187,11 @@ RequireQualifiedAccess özniteliğine sahip yapının kullanım dışı erişiminde hata + + Escapes curly braces before calling FormattableStringFactory.Create when interpolated string literal is typed as FormattableString + Escapes curly braces before calling FormattableStringFactory.Create when interpolated string literal is typed as FormattableString + + more types support units of measure tür daha ölçü birimlerini destekler diff --git a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf index d733d0c103..31cf0b064b 100644 --- a/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf +++ b/src/Compiler/xlf/FSComp.txt.zh-Hans.xlf @@ -187,6 +187,11 @@ 对具有 RequireQualifiedAccess 属性的构造进行弃用的访问时出错 + + Escapes curly braces before calling FormattableStringFactory.Create when interpolated string literal is typed as FormattableString + Escapes curly braces before calling FormattableStringFactory.Create when interpolated string literal is typed as FormattableString + + more types support units of measure 更多类型支持度量单位 diff --git a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf index 1d7aec7be9..69f39b39da 100644 --- a/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf +++ b/src/Compiler/xlf/FSComp.txt.zh-Hant.xlf @@ -187,6 +187,11 @@ 對具有 RequireQualifiedAccess 屬性的建構的已取代存取發出錯誤 + + Escapes curly braces before calling FormattableStringFactory.Create when interpolated string literal is typed as FormattableString + Escapes curly braces before calling FormattableStringFactory.Create when interpolated string literal is typed as FormattableString + + more types support units of measure 更多支援測量單位的類型 From a741a7bc1c73b75052fb3a846b02510131e95a88 Mon Sep 17 00:00:00 2001 From: Adam Boniecki Date: Thu, 1 Dec 2022 13:27:22 +0100 Subject: [PATCH 4/4] Fix test --- .../Language/InterpolatedStringsTests.fs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/FSharp.Compiler.ComponentTests/Language/InterpolatedStringsTests.fs b/tests/FSharp.Compiler.ComponentTests/Language/InterpolatedStringsTests.fs index 26e4b8f08e..a301bc293d 100644 --- a/tests/FSharp.Compiler.ComponentTests/Language/InterpolatedStringsTests.fs +++ b/tests/FSharp.Compiler.ComponentTests/Language/InterpolatedStringsTests.fs @@ -38,6 +38,7 @@ let c: System.IFormattable = $"string" let a = $"{{hello}} world" : System.FormattableString printf $"{a.Format}" """ + |> withLangVersionPreview |> compileExeAndRun |> shouldSucceed |> withStdOutContains "{{hello}} world" \ No newline at end of file