Skip to content
13 changes: 11 additions & 2 deletions src/Compiler/Checking/CheckFormatStrings.fs
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,21 @@ 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)
(g: TcGlobals)
isInterpolated
isFormattableString
(context: FormatStringCheckContext option)
fmt
(fmt: string)
printerArgTy
printerResidueTy =

Expand Down Expand Up @@ -86,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 "--------------------"
Expand Down Expand Up @@ -175,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)
fmt, [ (0, 1, m) ]
(if escapeFormatStringEnabled then escapeDotnetFormatString fmt else fmt), [ (0, 1, m) ]

let len = fmt.Length

Expand Down
1 change: 1 addition & 0 deletions src/Compiler/FSComp.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1662,3 +1662,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"
3 changes: 3 additions & 0 deletions src/Compiler/Facilities/LanguageFeatures.fs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ type LanguageFeature =
| MatchNotAllowedForUnionCaseWithNoData
| CSharpExtensionAttributeNotRequired
| ErrorForNonVirtualMembersOverrides
| EscapeDotnetFormattableStrings
| ArithmeticInLiterals

/// LanguageVersion management
Expand Down Expand Up @@ -131,6 +132,7 @@ type LanguageVersion(versionText) =
LanguageFeature.MatchNotAllowedForUnionCaseWithNoData, previewVersion
LanguageFeature.CSharpExtensionAttributeNotRequired, previewVersion
LanguageFeature.ErrorForNonVirtualMembersOverrides, previewVersion
LanguageFeature.EscapeDotnetFormattableStrings, previewVersion
LanguageFeature.ArithmeticInLiterals, previewVersion

]
Expand Down Expand Up @@ -242,6 +244,7 @@ type LanguageVersion(versionText) =
| LanguageFeature.MatchNotAllowedForUnionCaseWithNoData -> FSComp.SR.featureMatchNotAllowedForUnionCaseWithNoData ()
| LanguageFeature.CSharpExtensionAttributeNotRequired -> FSComp.SR.featureCSharpExtensionAttributeNotRequired ()
| LanguageFeature.ErrorForNonVirtualMembersOverrides -> FSComp.SR.featureErrorForNonVirtualMembersOverrides ()
| LanguageFeature.EscapeDotnetFormattableStrings -> FSComp.SR.featureEscapeBracesInFormattableString ()
| LanguageFeature.ArithmeticInLiterals -> FSComp.SR.featureArithmeticInLiterals ()

/// Get a version string associated with the given feature.
Expand Down
1 change: 1 addition & 0 deletions src/Compiler/Facilities/LanguageFeatures.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ type LanguageFeature =
| MatchNotAllowedForUnionCaseWithNoData
| CSharpExtensionAttributeNotRequired
| ErrorForNonVirtualMembersOverrides
| EscapeDotnetFormattableStrings
| ArithmeticInLiterals

/// LanguageVersion management
Expand Down
5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.cs.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,11 @@
<target state="translated">chyba při zastaralém přístupu konstruktoru s atributem RequireQualifiedAccess</target>
<note />
</trans-unit>
<trans-unit id="featureEscapeBracesInFormattableString">
<source>Escapes curly braces before calling FormattableStringFactory.Create when interpolated string literal is typed as FormattableString</source>
<target state="new">Escapes curly braces before calling FormattableStringFactory.Create when interpolated string literal is typed as FormattableString</target>
<note />
</trans-unit>
<trans-unit id="featureExpandedMeasurables">
<source>more types support units of measure</source>
<target state="translated">více typů podporuje měrné jednotky</target>
Expand Down
5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.de.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,11 @@
<target state="translated">Beim veralteten Zugriff auf das Konstrukt mit dem RequireQualifiedAccess-Attribut wird ein Fehler ausgegeben.</target>
<note />
</trans-unit>
<trans-unit id="featureEscapeBracesInFormattableString">
<source>Escapes curly braces before calling FormattableStringFactory.Create when interpolated string literal is typed as FormattableString</source>
<target state="new">Escapes curly braces before calling FormattableStringFactory.Create when interpolated string literal is typed as FormattableString</target>
<note />
</trans-unit>
<trans-unit id="featureExpandedMeasurables">
<source>more types support units of measure</source>
<target state="translated">Maßeinheitenunterstützung durch weitere Typen</target>
Expand Down
5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.es.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,11 @@
<target state="translated">error en el acceso en desuso de la construcción con el atributo RequireQualifiedAccess</target>
<note />
</trans-unit>
<trans-unit id="featureEscapeBracesInFormattableString">
<source>Escapes curly braces before calling FormattableStringFactory.Create when interpolated string literal is typed as FormattableString</source>
<target state="new">Escapes curly braces before calling FormattableStringFactory.Create when interpolated string literal is typed as FormattableString</target>
<note />
</trans-unit>
<trans-unit id="featureExpandedMeasurables">
<source>more types support units of measure</source>
<target state="translated">más tipos admiten las unidades de medida</target>
Expand Down
5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.fr.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,11 @@
<target state="translated">donner une erreur sur l’accès déconseillé de la construction avec l’attribut RequireQualifiedAccess</target>
<note />
</trans-unit>
<trans-unit id="featureEscapeBracesInFormattableString">
<source>Escapes curly braces before calling FormattableStringFactory.Create when interpolated string literal is typed as FormattableString</source>
<target state="new">Escapes curly braces before calling FormattableStringFactory.Create when interpolated string literal is typed as FormattableString</target>
<note />
</trans-unit>
<trans-unit id="featureExpandedMeasurables">
<source>more types support units of measure</source>
<target state="translated">d'autres types prennent en charge les unités de mesure</target>
Expand Down
5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.it.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,11 @@
<target state="translated">errore durante l'accesso deprecato del costrutto con l'attributo RequireQualifiedAccess</target>
<note />
</trans-unit>
<trans-unit id="featureEscapeBracesInFormattableString">
<source>Escapes curly braces before calling FormattableStringFactory.Create when interpolated string literal is typed as FormattableString</source>
<target state="new">Escapes curly braces before calling FormattableStringFactory.Create when interpolated string literal is typed as FormattableString</target>
<note />
</trans-unit>
<trans-unit id="featureExpandedMeasurables">
<source>more types support units of measure</source>
<target state="translated">più tipi supportano le unità di misura</target>
Expand Down
5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.ja.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,11 @@
<target state="translated">RequireQualifiedAccess 属性を持つコンストラクトの非推奨アクセスでエラーが発生しました</target>
<note />
</trans-unit>
<trans-unit id="featureEscapeBracesInFormattableString">
<source>Escapes curly braces before calling FormattableStringFactory.Create when interpolated string literal is typed as FormattableString</source>
<target state="new">Escapes curly braces before calling FormattableStringFactory.Create when interpolated string literal is typed as FormattableString</target>
<note />
</trans-unit>
<trans-unit id="featureExpandedMeasurables">
<source>more types support units of measure</source>
<target state="translated">単位をサポートするその他の型</target>
Expand Down
5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.ko.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,11 @@
<target state="translated">RequireQualifiedAccess 특성을 사용하여 사용되지 않는 구문 액세스에 대한 오류 제공</target>
<note />
</trans-unit>
<trans-unit id="featureEscapeBracesInFormattableString">
<source>Escapes curly braces before calling FormattableStringFactory.Create when interpolated string literal is typed as FormattableString</source>
<target state="new">Escapes curly braces before calling FormattableStringFactory.Create when interpolated string literal is typed as FormattableString</target>
<note />
</trans-unit>
<trans-unit id="featureExpandedMeasurables">
<source>more types support units of measure</source>
<target state="translated">더 많은 형식이 측정 단위를 지원함</target>
Expand Down
5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.pl.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,11 @@
<target state="translated">wskazywanie błędu w przypadku przestarzałego dostępu do konstrukcji z atrybutem RequireQualifiedAccess</target>
<note />
</trans-unit>
<trans-unit id="featureEscapeBracesInFormattableString">
<source>Escapes curly braces before calling FormattableStringFactory.Create when interpolated string literal is typed as FormattableString</source>
<target state="new">Escapes curly braces before calling FormattableStringFactory.Create when interpolated string literal is typed as FormattableString</target>
<note />
</trans-unit>
<trans-unit id="featureExpandedMeasurables">
<source>more types support units of measure</source>
<target state="translated">więcej typów obsługuje jednostki miary</target>
Expand Down
5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.pt-BR.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,11 @@
<target state="translated">fornecer erro no acesso preterido do constructo com o atributo RequireQualifiedAccess</target>
<note />
</trans-unit>
<trans-unit id="featureEscapeBracesInFormattableString">
<source>Escapes curly braces before calling FormattableStringFactory.Create when interpolated string literal is typed as FormattableString</source>
<target state="new">Escapes curly braces before calling FormattableStringFactory.Create when interpolated string literal is typed as FormattableString</target>
<note />
</trans-unit>
<trans-unit id="featureExpandedMeasurables">
<source>more types support units of measure</source>
<target state="translated">mais tipos dão suporte para unidades de medida</target>
Expand Down
5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.ru.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,11 @@
<target state="translated">выдать ошибку при устаревшем доступе к конструкции с атрибутом RequireQualifiedAccess</target>
<note />
</trans-unit>
<trans-unit id="featureEscapeBracesInFormattableString">
<source>Escapes curly braces before calling FormattableStringFactory.Create when interpolated string literal is typed as FormattableString</source>
<target state="new">Escapes curly braces before calling FormattableStringFactory.Create when interpolated string literal is typed as FormattableString</target>
<note />
</trans-unit>
<trans-unit id="featureExpandedMeasurables">
<source>more types support units of measure</source>
<target state="translated">другие типы поддерживают единицы измерения</target>
Expand Down
5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.tr.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,11 @@
<target state="translated">RequireQualifiedAccess özniteliğine sahip yapının kullanım dışı erişiminde hata</target>
<note />
</trans-unit>
<trans-unit id="featureEscapeBracesInFormattableString">
<source>Escapes curly braces before calling FormattableStringFactory.Create when interpolated string literal is typed as FormattableString</source>
<target state="new">Escapes curly braces before calling FormattableStringFactory.Create when interpolated string literal is typed as FormattableString</target>
<note />
</trans-unit>
<trans-unit id="featureExpandedMeasurables">
<source>more types support units of measure</source>
<target state="translated">tür daha ölçü birimlerini destekler</target>
Expand Down
5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.zh-Hans.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,11 @@
<target state="translated">对具有 RequireQualifiedAccess 属性的构造进行弃用的访问时出错</target>
<note />
</trans-unit>
<trans-unit id="featureEscapeBracesInFormattableString">
<source>Escapes curly braces before calling FormattableStringFactory.Create when interpolated string literal is typed as FormattableString</source>
<target state="new">Escapes curly braces before calling FormattableStringFactory.Create when interpolated string literal is typed as FormattableString</target>
<note />
</trans-unit>
<trans-unit id="featureExpandedMeasurables">
<source>more types support units of measure</source>
<target state="translated">更多类型支持度量单位</target>
Expand Down
5 changes: 5 additions & 0 deletions src/Compiler/xlf/FSComp.txt.zh-Hant.xlf
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,11 @@
<target state="translated">對具有 RequireQualifiedAccess 屬性的建構的已取代存取發出錯誤</target>
<note />
</trans-unit>
<trans-unit id="featureEscapeBracesInFormattableString">
<source>Escapes curly braces before calling FormattableStringFactory.Create when interpolated string literal is typed as FormattableString</source>
<target state="new">Escapes curly braces before calling FormattableStringFactory.Create when interpolated string literal is typed as FormattableString</target>
<note />
</trans-unit>
<trans-unit id="featureExpandedMeasurables">
<source>more types support units of measure</source>
<target state="translated">更多支援測量單位的類型</target>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,19 @@ let c: System.IFormattable = $"string"
|> compile
|> shouldSucceed

[<Fact>]
let ``Interpolated string literal typed as FormattableString handles double braces correctly`` () =
Fsx """
let a = $"{{hello}} world" : System.FormattableString
printf $"{a.Format}"
"""
|> withLangVersionPreview
|> compileExeAndRun
|> shouldSucceed
|> withStdOutContains "{{hello}} world"

[<Fact>]
let ``Percent sign characters in interpolated strings`` () =
Assert.Equal("%", $"%%")
Assert.Equal("42%", $"{42}%%")
Assert.Equal("% 42", $"%%%3d{42}")
Assert.Equal("% 42", $"%%%3d{42}")