Skip to content

Commit 1ba6b74

Browse files
committed
string interploation implementation
1 parent 97dd7cc commit 1ba6b74

31 files changed

+827
-339
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,3 +130,5 @@ msbuild.binlog
130130
/fcs/FSharp.Compiler.Service.netstandard/*.fsi
131131
/.ionide/
132132
**/.DS_Store
133+
Session.vim
134+
coc-settings.json

src/fsharp/CheckFormatStrings.fs

Lines changed: 73 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ let newInfo () =
4747
addZeros = false
4848
precision = false}
4949

50-
let parseFormatStringInternal (m:range) (g: TcGlobals) (context: FormatStringCheckContext option) fmt bty cty =
50+
let parseFormatStringInternal (m:range) (g: TcGlobals) isInterp (context: FormatStringCheckContext option) fmt bty cty =
5151
// Offset is used to adjust ranges depending on whether input string is regular, verbatim or triple-quote.
5252
// We construct a new 'fmt' string since the current 'fmt' string doesn't distinguish between "\n" and escaped "\\n".
5353
let (offset, fmt) =
@@ -77,7 +77,7 @@ let parseFormatStringInternal (m:range) (g: TcGlobals) (context: FormatStringChe
7777
if acc |> List.forall (fun (p, _) -> p = None) then // without positional specifiers
7878
acc |> List.map snd |> List.rev
7979
else
80-
failwithf "%s" <| FSComp.SR.forPositionalSpecifiersNotPermitted()
80+
raise (Failure (FSComp.SR.forPositionalSpecifiersNotPermitted()))
8181
argtys
8282
elif System.Char.IsSurrogatePair(fmt,i) then
8383
parseLoop acc (i+2, relLine, relCol+2)
@@ -88,65 +88,65 @@ let parseFormatStringInternal (m:range) (g: TcGlobals) (context: FormatStringChe
8888
let startCol = relCol
8989
let relCol = relCol+1
9090
let i = i+1
91-
if i >= len then failwithf "%s" <| FSComp.SR.forMissingFormatSpecifier()
91+
if i >= len then raise (Failure (FSComp.SR.forMissingFormatSpecifier()))
9292
let info = newInfo()
9393

9494
let rec flags i =
95-
if i >= len then failwithf "%s" <| FSComp.SR.forMissingFormatSpecifier()
95+
if i >= len then raise (Failure (FSComp.SR.forMissingFormatSpecifier()))
9696
match fmt.[i] with
9797
| '-' ->
98-
if info.leftJustify then failwithf "%s" <| FSComp.SR.forFlagSetTwice("-")
98+
if info.leftJustify then raise (Failure (FSComp.SR.forFlagSetTwice("-")))
9999
info.leftJustify <- true
100100
flags(i+1)
101101
| '+' ->
102-
if info.numPrefixIfPos <> None then failwithf "%s" <| FSComp.SR.forPrefixFlagSpacePlusSetTwice()
102+
if info.numPrefixIfPos <> None then raise (Failure (FSComp.SR.forPrefixFlagSpacePlusSetTwice()))
103103
info.numPrefixIfPos <- Some '+'
104104
flags(i+1)
105105
| '0' ->
106-
if info.addZeros then failwithf "%s" <| FSComp.SR.forFlagSetTwice("0")
106+
if info.addZeros then raise (Failure (FSComp.SR.forFlagSetTwice("0")))
107107
info.addZeros <- true
108108
flags(i+1)
109109
| ' ' ->
110-
if info.numPrefixIfPos <> None then failwithf "%s" <| FSComp.SR.forPrefixFlagSpacePlusSetTwice()
110+
if info.numPrefixIfPos <> None then raise (Failure (FSComp.SR.forPrefixFlagSpacePlusSetTwice()))
111111
info.numPrefixIfPos <- Some ' '
112112
flags(i+1)
113-
| '#' -> failwithf "%s" <| FSComp.SR.forHashSpecifierIsInvalid()
113+
| '#' -> raise (Failure (FSComp.SR.forHashSpecifierIsInvalid() ))
114114
| _ -> i
115115

116116
let rec digitsPrecision i =
117-
if i >= len then failwithf "%s" <| FSComp.SR.forBadPrecision()
117+
if i >= len then raise (Failure (FSComp.SR.forBadPrecision()))
118118
match fmt.[i] with
119119
| c when System.Char.IsDigit c -> digitsPrecision (i+1)
120120
| _ -> i
121121

122122
let precision i =
123-
if i >= len then failwithf "%s" <| FSComp.SR.forBadWidth()
123+
if i >= len then raise (Failure (FSComp.SR.forBadWidth()))
124124
match fmt.[i] with
125125
| c when System.Char.IsDigit c -> info.precision <- true; false,digitsPrecision (i+1)
126126
| '*' -> info.precision <- true; true,(i+1)
127-
| _ -> failwithf "%s" <| FSComp.SR.forPrecisionMissingAfterDot()
127+
| _ -> raise (Failure (FSComp.SR.forPrecisionMissingAfterDot()))
128128

129129
let optionalDotAndPrecision i =
130-
if i >= len then failwithf "%s" <| FSComp.SR.forBadPrecision()
130+
if i >= len then raise (Failure (FSComp.SR.forBadPrecision()))
131131
match fmt.[i] with
132132
| '.' -> precision (i+1)
133133
| _ -> false,i
134134

135135
let rec digitsWidthAndPrecision i =
136-
if i >= len then failwithf "%s" <| FSComp.SR.forBadPrecision()
136+
if i >= len then raise (Failure (FSComp.SR.forBadPrecision()))
137137
match fmt.[i] with
138138
| c when System.Char.IsDigit c -> digitsWidthAndPrecision (i+1)
139139
| _ -> optionalDotAndPrecision i
140140

141141
let widthAndPrecision i =
142-
if i >= len then failwithf "%s" <| FSComp.SR.forBadPrecision()
142+
if i >= len then raise (Failure (FSComp.SR.forBadPrecision()))
143143
match fmt.[i] with
144144
| c when System.Char.IsDigit c -> false,digitsWidthAndPrecision i
145145
| '*' -> true,optionalDotAndPrecision (i+1)
146146
| _ -> false,optionalDotAndPrecision i
147147

148148
let rec digitsPosition n i =
149-
if i >= len then failwithf "%s" <| FSComp.SR.forBadPrecision()
149+
if i >= len then raise (Failure (FSComp.SR.forBadPrecision()))
150150
match fmt.[i] with
151151
| c when System.Char.IsDigit c -> digitsPosition (n*10 + int c - int '0') (i+1)
152152
| '$' -> Some n, i+1
@@ -171,22 +171,35 @@ let parseFormatStringInternal (m:range) (g: TcGlobals) (context: FormatStringChe
171171
let widthArg,(precisionArg,i) = widthAndPrecision i
172172
let relCol = relCol + i - oldI
173173

174-
if i >= len then failwithf "%s" <| FSComp.SR.forBadPrecision()
174+
if i >= len then raise (Failure (FSComp.SR.forBadPrecision()))
175175

176176
let acc = if precisionArg then (Option.map ((+)1) posi, g.int_ty) :: acc else acc
177177

178178
let acc = if widthArg then (Option.map ((+)1) posi, g.int_ty) :: acc else acc
179179

180-
let checkNoPrecision c = if info.precision then failwithf "%s" <| FSComp.SR.forFormatDoesntSupportPrecision(c.ToString())
181-
let checkNoZeroFlag c = if info.addZeros then failwithf "%s" <| FSComp.SR.forDoesNotSupportZeroFlag(c.ToString())
182-
let checkNoNumericPrefix c = if info.numPrefixIfPos <> None then
183-
failwithf "%s" <| FSComp.SR.forDoesNotSupportPrefixFlag(c.ToString(), (Option.get info.numPrefixIfPos).ToString())
180+
let checkNoPrecision c =
181+
if info.precision then raise (Failure (FSComp.SR.forFormatDoesntSupportPrecision(c.ToString())))
182+
183+
let checkNoZeroFlag c =
184+
if info.addZeros then raise (Failure (FSComp.SR.forDoesNotSupportZeroFlag(c.ToString())))
185+
186+
let checkNoNumericPrefix c =
187+
match info.numPrefixIfPos with
188+
| Some n -> raise (Failure (FSComp.SR.forDoesNotSupportPrefixFlag(c.ToString(), n.ToString())))
189+
| None -> ()
184190

185191
let checkOtherFlags c =
186192
checkNoPrecision c
187193
checkNoZeroFlag c
188194
checkNoNumericPrefix c
189195

196+
let skipInterp i =
197+
// Explicitly typed holes in interpolated strings get '%P' after them as hole place marker
198+
if isInterp then
199+
if i+1 < fmt.Length && fmt.[i] = '%' && fmt.[i+1] = 'P' then i + 2
200+
else raise (Failure (FSComp.SR.forFormatInvalidForInterpolated()))
201+
else i
202+
190203
let collectSpecifierLocation relLine relCol numStdArgs =
191204
let numArgsForSpecifier =
192205
numStdArgs + (if widthArg then 1 else 0) + (if precisionArg then 1 else 0)
@@ -209,94 +222,110 @@ let parseFormatStringInternal (m:range) (g: TcGlobals) (context: FormatStringChe
209222
parseLoop acc (i+1, relLine, relCol+1)
210223

211224
| ('d' | 'i' | 'o' | 'u' | 'x' | 'X') ->
212-
if info.precision then failwithf "%s" <| FSComp.SR.forFormatDoesntSupportPrecision(ch.ToString())
225+
if info.precision then raise (Failure (FSComp.SR.forFormatDoesntSupportPrecision(ch.ToString())))
213226
collectSpecifierLocation relLine relCol 1
214-
parseLoop ((posi, mkFlexibleIntFormatTypar g m) :: acc) (i+1, relLine, relCol+1)
227+
let i = skipInterp (i+1)
228+
parseLoop ((posi, mkFlexibleIntFormatTypar g m) :: acc) (i, relLine, relCol+1)
215229

216230
| ('l' | 'L') ->
217-
if info.precision then failwithf "%s" <| FSComp.SR.forFormatDoesntSupportPrecision(ch.ToString())
231+
if info.precision then raise (Failure (FSComp.SR.forFormatDoesntSupportPrecision(ch.ToString())))
218232
let relCol = relCol+1
219233
let i = i+1
220234

221235
// "bad format specifier ... In F# code you can use %d, %x, %o or %u instead ..."
222236
if i >= len then
223-
failwithf "%s" <| FSComp.SR.forBadFormatSpecifier()
237+
raise (Failure (FSComp.SR.forBadFormatSpecifier()))
224238
// Always error for %l and %Lx
225-
failwithf "%s" <| FSComp.SR.forLIsUnnecessary()
239+
raise (Failure (FSComp.SR.forLIsUnnecessary()))
226240
match fmt.[i] with
227241
| ('d' | 'i' | 'o' | 'u' | 'x' | 'X') ->
228242
collectSpecifierLocation relLine relCol 1
229-
parseLoop ((posi, mkFlexibleIntFormatTypar g m) :: acc) (i+1, relLine, relCol+1)
230-
| _ -> failwithf "%s" <| FSComp.SR.forBadFormatSpecifier()
243+
let i = skipInterp (i+1)
244+
parseLoop ((posi, mkFlexibleIntFormatTypar g m) :: acc) (i, relLine, relCol+1)
245+
| _ -> raise (Failure (FSComp.SR.forBadFormatSpecifier()))
231246

232247
| ('h' | 'H') ->
233-
failwithf "%s" <| FSComp.SR.forHIsUnnecessary()
248+
raise (Failure (FSComp.SR.forHIsUnnecessary()))
234249

235250
| 'M' ->
236251
collectSpecifierLocation relLine relCol 1
237-
parseLoop ((posi, mkFlexibleDecimalFormatTypar g m) :: acc) (i+1, relLine, relCol+1)
252+
let i = skipInterp (i+1)
253+
parseLoop ((posi, mkFlexibleDecimalFormatTypar g m) :: acc) (i, relLine, relCol+1)
238254

239255
| ('f' | 'F' | 'e' | 'E' | 'g' | 'G') ->
240256
collectSpecifierLocation relLine relCol 1
241-
parseLoop ((posi, mkFlexibleFloatFormatTypar g m) :: acc) (i+1, relLine, relCol+1)
257+
let i = skipInterp (i+1)
258+
parseLoop ((posi, mkFlexibleFloatFormatTypar g m) :: acc) (i, relLine, relCol+1)
242259

243260
| 'b' ->
244261
checkOtherFlags ch
245262
collectSpecifierLocation relLine relCol 1
246-
parseLoop ((posi, g.bool_ty) :: acc) (i+1, relLine, relCol+1)
263+
let i = skipInterp (i+1)
264+
parseLoop ((posi, g.bool_ty) :: acc) (i, relLine, relCol+1)
247265

248266
| 'c' ->
249267
checkOtherFlags ch
250268
collectSpecifierLocation relLine relCol 1
251-
parseLoop ((posi, g.char_ty) :: acc) (i+1, relLine, relCol+1)
269+
let i = skipInterp (i+1)
270+
parseLoop ((posi, g.char_ty) :: acc) (i, relLine, relCol+1)
252271

253272
| 's' ->
254273
checkOtherFlags ch
255274
collectSpecifierLocation relLine relCol 1
256-
parseLoop ((posi, g.string_ty) :: acc) (i+1, relLine, relCol+1)
275+
let i = skipInterp (i+1)
276+
parseLoop ((posi, g.string_ty) :: acc) (i, relLine, relCol+1)
257277

258278
| 'O' ->
259279
checkOtherFlags ch
260280
collectSpecifierLocation relLine relCol 1
281+
let i = skipInterp (i+1)
282+
parseLoop ((posi, NewInferenceType ()) :: acc) (i, relLine, relCol+1)
283+
284+
// residue of hole "...{n}..." in interpolated strings
285+
| 'P' when isInterp ->
286+
checkOtherFlags ch
261287
parseLoop ((posi, NewInferenceType ()) :: acc) (i+1, relLine, relCol+1)
262288

263289
| 'A' ->
264290
match info.numPrefixIfPos with
265291
| None // %A has BindingFlags=Public, %+A has BindingFlags=Public | NonPublic
266292
| Some '+' ->
267293
collectSpecifierLocation relLine relCol 1
268-
parseLoop ((posi, NewInferenceType ()) :: acc) (i+1, relLine, relCol+1)
269-
| Some _ -> failwithf "%s" <| FSComp.SR.forDoesNotSupportPrefixFlag(ch.ToString(), (Option.get info.numPrefixIfPos).ToString())
294+
let i = skipInterp (i+1)
295+
parseLoop ((posi, NewInferenceType ()) :: acc) (i, relLine, relCol+1)
296+
| Some n -> raise (Failure (FSComp.SR.forDoesNotSupportPrefixFlag(ch.ToString(), n.ToString())))
270297

271298
| 'a' ->
272299
checkOtherFlags ch
273300
let xty = NewInferenceType ()
274301
let fty = bty --> (xty --> cty)
275302
collectSpecifierLocation relLine relCol 2
276-
parseLoop ((Option.map ((+)1) posi, xty) :: (posi, fty) :: acc) (i+1, relLine, relCol+1)
303+
let i = skipInterp (i+1)
304+
parseLoop ((Option.map ((+)1) posi, xty) :: (posi, fty) :: acc) (i, relLine, relCol+1)
277305

278306
| 't' ->
279307
checkOtherFlags ch
280308
collectSpecifierLocation relLine relCol 1
281-
parseLoop ((posi, bty --> cty) :: acc) (i+1, relLine, relCol+1)
309+
let i = skipInterp (i+1)
310+
parseLoop ((posi, bty --> cty) :: acc) (i, relLine, relCol+1)
282311

283-
| c -> failwithf "%s" <| FSComp.SR.forBadFormatSpecifierGeneral(String.make 1 c)
312+
| c -> raise (Failure (FSComp.SR.forBadFormatSpecifierGeneral(String.make 1 c)))
284313

285314
| '\n' -> parseLoop acc (i+1, relLine+1, 0)
286315
| _ -> parseLoop acc (i+1, relLine, relCol+1)
287316

288317
let results = parseLoop [] (0, 0, m.StartColumn)
289318
results, Seq.toList specifierLocations
290319

291-
let ParseFormatString m g formatStringCheckContext fmt bty cty dty =
292-
let argtys, specifierLocations = parseFormatStringInternal m g formatStringCheckContext fmt bty cty
320+
let ParseFormatString m g isInterp formatStringCheckContext fmt bty cty dty =
321+
let argtys, specifierLocations = parseFormatStringInternal m g isInterp formatStringCheckContext fmt bty cty
293322
let aty = List.foldBack (-->) argtys dty
294323
let ety = mkRefTupledTy g argtys
295-
(aty, ety), specifierLocations
324+
(argtys, aty, ety), specifierLocations
296325

297-
let TryCountFormatStringArguments m g fmt bty cty =
326+
let TryCountFormatStringArguments m g isInterp fmt bty cty =
298327
try
299-
let argtys, _specifierLocations = parseFormatStringInternal m g None fmt bty cty
328+
let argtys, _specifierLocations = parseFormatStringInternal m g isInterp None fmt bty cty
300329
Some argtys.Length
301330
with _ ->
302331
None

src/fsharp/CheckFormatStrings.fsi

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@ module internal FSharp.Compiler.CheckFormatStrings
99

1010
open FSharp.Compiler
1111
open FSharp.Compiler.NameResolution
12+
open FSharp.Compiler.Range
1213
open FSharp.Compiler.TypedTree
1314
open FSharp.Compiler.TcGlobals
1415

15-
val ParseFormatString : Range.range -> TcGlobals -> formatStringCheckContext: FormatStringCheckContext option -> fmt: string -> bty: TType -> cty: TType -> dty: TType -> (TType * TType) * (Range.range * int) list
16+
val ParseFormatString : m: range -> g: TcGlobals -> isInterp: bool -> formatStringCheckContext: FormatStringCheckContext option -> fmt: string -> bty: TType -> cty: TType -> dty: TType -> (TType list * TType * TType) * (range * int) list
1617

17-
val TryCountFormatStringArguments : m:Range.range -> g:TcGlobals -> fmt:string -> bty:TType -> cty:TType -> int option
18+
val TryCountFormatStringArguments : m:Range.range -> g:TcGlobals -> isInterp: bool -> fmt:string -> bty:TType -> cty:TType -> int option

src/fsharp/CompileOps.fs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1165,6 +1165,10 @@ let OutputPhasedErrorR (os: StringBuilder) (err: PhasedDiagnostic) (canSuggestNa
11651165
| Parser.TOKEN_EOF -> getErrorString("Parser.TOKEN.EOF")
11661166
| Parser.TOKEN_CONST -> getErrorString("Parser.TOKEN.CONST")
11671167
| Parser.TOKEN_FIXED -> getErrorString("Parser.TOKEN.FIXED")
1168+
| Parser.TOKEN_INTERP_STRING_BEGIN_END -> getErrorString("Parser.TOKEN.INTERP.STRING.BEGIN.END")
1169+
| Parser.TOKEN_INTERP_STRING_BEGIN_PART -> getErrorString("Parser.TOKEN.INTERP.STRING.BEGIN.PART")
1170+
| Parser.TOKEN_INTERP_STRING_PART -> getErrorString("Parser.TOKEN.INTERP.STRING.PART")
1171+
| Parser.TOKEN_INTERP_STRING_END -> getErrorString("Parser.TOKEN.INTERP.STRING.END")
11681172
| unknown ->
11691173
Debug.Assert(false, "unknown token tag")
11701174
let result = sprintf "%+A" unknown

src/fsharp/ErrorLogger.fs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -688,4 +688,9 @@ let internal tryLanguageFeatureError langVersion langFeature m =
688688
tryLanguageFeatureErrorAux langVersion langFeature m error
689689

690690
let internal tryLanguageFeatureErrorRecover langVersion langFeature m =
691-
tryLanguageFeatureErrorAux langVersion langFeature m errorR
691+
tryLanguageFeatureErrorAux langVersion langFeature m errorR
692+
693+
let internal languageFeatureNotSupportedInLibraryError (langVersion: LanguageVersion) (langFeature: LanguageFeature) (m: range) =
694+
let featureStr = langVersion.GetFeatureString langFeature
695+
let suggestedVersionStr = langVersion.GetFeatureVersionString langFeature
696+
error (Error(FSComp.SR.chkFeatureNotSupportedInLibrary(featureStr, suggestedVersionStr), m))

src/fsharp/FSComp.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1489,6 +1489,8 @@ notAFunctionButMaybeDeclaration,"This value is not a function and cannot be appl
14891489
3350,chkFeatureNotLanguageSupported,"Feature '%s' is not available in F# %s. Please use language version %s or greater."
14901490
3351,chkFeatureNotRuntimeSupported,"Feature '%s' is not supported by target runtime."
14911491
3352,typrelInterfaceMemberNoMostSpecificImplementation,"Interface member '%s' does not have a most specific implementation."
1492+
3353,chkFeatureNotSupportedInLibrary,"Feature '%s' requires the F# library for language version %s or greater."
1493+
3360,lexByteStringMayNotBeInterpolated,"a byte string may not be interpolated"
14921494
useSdkRefs,"Use reference assemblies for .NET framework references when available (Enabled by default)."
14931495
fSharpBannerVersion,"%s for F# %s"
14941496
optsLangVersion,"Display the allowed values for language version, specify language version such as 'latest' or 'preview'"
@@ -1509,3 +1511,6 @@ featureFixedIndexSlice3d4d,"fixed-index slice 3d/4d"
15091511
featureAndBang,"applicative computation expressions"
15101512
featureNullableOptionalInterop,"nullable optional interop"
15111513
featureDefaultInterfaceMemberConsumption,"default interface member consumption"
1514+
featureStringInterpolation,"string interpolation"
1515+
3361,tcInterpolationMixedWithPercent,"Mismatch in interpolated string. Interpolated strings may not use '%%' formats unless each is given an expression, e.g. '%%d{{1+1}}'"
1516+
forFormatInvalidForInterpolated,"Invalid interpolated string. interpolated strings may not use '%%' formats unless each is given an expression, e.g. '%%d{{1+1}}'"

0 commit comments

Comments
 (0)