Skip to content

Commit 06e4d56

Browse files
committed
Fix the issue with format specifiers
Change parsing format specifiers so that it first checks whether it sees escaped `%` before it starts parsing flags/width/precision.
1 parent 820f6ae commit 06e4d56

File tree

1 file changed

+155
-149
lines changed

1 file changed

+155
-149
lines changed

src/Compiler/Checking/CheckFormatStrings.fs

+155-149
Original file line numberDiff line numberDiff line change
@@ -313,169 +313,175 @@ let parseFormatStringInternal
313313
and parseSpecifier acc (i, fragLine, fragCol) fragments =
314314
let startFragCol = fragCol
315315
let fragCol = fragCol+1
316-
let i = i+1
317-
if i >= len then failwith (FSComp.SR.forMissingFormatSpecifier())
318-
let info = newInfo()
319-
320-
let oldI = i
321-
let posi, i = Parsing.position fmt i
322-
let fragCol = fragCol + i - oldI
323-
324-
let oldI = i
325-
let i = Parsing.flags info fmt i
326-
let fragCol = fragCol + i - oldI
327-
328-
let oldI = i
329-
let widthArg,(widthValue, (precisionArg,i)) = Parsing.widthAndPrecision info fmt i
330-
let fragCol = fragCol + i - oldI
331-
332-
if i >= len then failwith (FSComp.SR.forBadPrecision())
333-
334-
let acc = if precisionArg then (Option.map ((+)1) posi, g.int_ty) :: acc else acc
335-
336-
let acc = if widthArg then (Option.map ((+)1) posi, g.int_ty) :: acc else acc
337-
338-
let checkOtherFlags c =
339-
if info.precision then failwith (FSComp.SR.forFormatDoesntSupportPrecision(c.ToString()))
340-
if info.addZeros then failwith (FSComp.SR.forDoesNotSupportZeroFlag(c.ToString()))
341-
match info.numPrefixIfPos with
342-
| Some n -> failwith (FSComp.SR.forDoesNotSupportPrefixFlag(c.ToString(), n.ToString()))
343-
| None -> ()
344-
345-
let skipPossibleInterpolationHole pos = Parsing.skipPossibleInterpolationHole isInterpolated isFormattableString fmt pos
346-
347-
// Implicitly typed holes in interpolated strings are translated to '... %P(...)...' in the
348-
// type checker. They should always have '(...)' after for format string.
349-
let requireAndSkipInterpolationHoleFormat i =
350-
if i < len && fmt[i] = '(' then
351-
let i2 = fmt.IndexOf(")", i+1)
352-
if i2 = -1 then
353-
failwith (FSComp.SR.forFormatInvalidForInterpolated3())
354-
else
355-
let dotnetAlignment = match widthValue with None -> "" | Some w -> "," + (if info.leftJustify then "-" else "") + string w
356-
let dotnetNumberFormat = match fmt[i+1..i2-1] with "" -> "" | s -> ":" + s
357-
appendToDotnetFormatString ("{" + string dotnetFormatStringInterpolationHoleCount + dotnetAlignment + dotnetNumberFormat + "}")
358-
dotnetFormatStringInterpolationHoleCount <- dotnetFormatStringInterpolationHoleCount + 1
359-
i2+1
360-
else
361-
failwith (FSComp.SR.forFormatInvalidForInterpolated3())
362-
363-
let collectSpecifierLocation fragLine fragCol numStdArgs =
316+
if fmt[i..(i+1)] = "%%" then
364317
match context with
365318
| Some _ ->
366-
let numArgsForSpecifier =
367-
numStdArgs + (if widthArg then 1 else 0) + (if precisionArg then 1 else 0)
368319
specifierLocations.Add(
369320
(Range.mkFileIndexRange m.FileIndex
370321
(Position.mkPos fragLine startFragCol)
371-
(Position.mkPos fragLine (fragCol + 1))), numArgsForSpecifier)
322+
(Position.mkPos fragLine (fragCol + 1))), 0)
372323
| None -> ()
373-
374-
let ch = fmt[i]
375-
match ch with
376-
| '%' ->
377-
collectSpecifierLocation fragLine fragCol 0
378324
appendToDotnetFormatString "%"
379-
parseLoop acc (i+1, fragLine, fragCol+1) fragments
380-
381-
| 'd' | 'i' | 'u' | 'B' | 'o' | 'x' | 'X' ->
382-
if ch = 'B' then DiagnosticsLogger.checkLanguageFeatureError g.langVersion Features.LanguageFeature.PrintfBinaryFormat m
383-
if info.precision then failwith (FSComp.SR.forFormatDoesntSupportPrecision(ch.ToString()))
384-
collectSpecifierLocation fragLine fragCol 1
385-
let i = skipPossibleInterpolationHole (i+1)
386-
parseLoop ((posi, mkFlexibleIntFormatTypar g m) :: acc) (i, fragLine, fragCol+1) fragments
387-
388-
| 'l' | 'L' ->
389-
if info.precision then failwith (FSComp.SR.forFormatDoesntSupportPrecision(ch.ToString()))
390-
let fragCol = fragCol+1
325+
parseLoop acc (i+2, fragLine, fragCol+1) fragments
326+
else
391327
let i = i+1
328+
if i >= len then failwith (FSComp.SR.forMissingFormatSpecifier())
329+
let info = newInfo()
330+
331+
let oldI = i
332+
let posi, i = Parsing.position fmt i
333+
let fragCol = fragCol + i - oldI
334+
335+
let oldI = i
336+
let i = Parsing.flags info fmt i
337+
let fragCol = fragCol + i - oldI
338+
339+
let oldI = i
340+
let widthArg,(widthValue, (precisionArg,i)) = Parsing.widthAndPrecision info fmt i
341+
let fragCol = fragCol + i - oldI
342+
343+
if i >= len then failwith (FSComp.SR.forBadPrecision())
344+
345+
let acc = if precisionArg then (Option.map ((+)1) posi, g.int_ty) :: acc else acc
346+
347+
let acc = if widthArg then (Option.map ((+)1) posi, g.int_ty) :: acc else acc
348+
349+
let checkOtherFlags c =
350+
if info.precision then failwith (FSComp.SR.forFormatDoesntSupportPrecision(c.ToString()))
351+
if info.addZeros then failwith (FSComp.SR.forDoesNotSupportZeroFlag(c.ToString()))
352+
match info.numPrefixIfPos with
353+
| Some n -> failwith (FSComp.SR.forDoesNotSupportPrefixFlag(c.ToString(), n.ToString()))
354+
| None -> ()
355+
356+
let skipPossibleInterpolationHole pos = Parsing.skipPossibleInterpolationHole isInterpolated isFormattableString fmt pos
357+
358+
// Implicitly typed holes in interpolated strings are translated to '... %P(...)...' in the
359+
// type checker. They should always have '(...)' after for format string.
360+
let requireAndSkipInterpolationHoleFormat i =
361+
if i < len && fmt[i] = '(' then
362+
let i2 = fmt.IndexOf(")", i+1)
363+
if i2 = -1 then
364+
failwith (FSComp.SR.forFormatInvalidForInterpolated3())
365+
else
366+
let dotnetAlignment = match widthValue with None -> "" | Some w -> "," + (if info.leftJustify then "-" else "") + string w
367+
let dotnetNumberFormat = match fmt[i+1..i2-1] with "" -> "" | s -> ":" + s
368+
appendToDotnetFormatString ("{" + string dotnetFormatStringInterpolationHoleCount + dotnetAlignment + dotnetNumberFormat + "}")
369+
dotnetFormatStringInterpolationHoleCount <- dotnetFormatStringInterpolationHoleCount + 1
370+
i2+1
371+
else
372+
failwith (FSComp.SR.forFormatInvalidForInterpolated3())
392373

393-
// "bad format specifier ... In F# code you can use %d, %x, %o or %u instead ..."
394-
if i >= len then
395-
failwith (FSComp.SR.forBadFormatSpecifier())
396-
// Always error for %l and %Lx
397-
failwith (FSComp.SR.forLIsUnnecessary())
398-
match fmt[i] with
399-
| 'd' | 'i' | 'o' | 'u' | 'x' | 'X' ->
374+
let collectSpecifierLocation fragLine fragCol numStdArgs =
375+
match context with
376+
| Some _ ->
377+
let numArgsForSpecifier =
378+
numStdArgs + (if widthArg then 1 else 0) + (if precisionArg then 1 else 0)
379+
specifierLocations.Add(
380+
(Range.mkFileIndexRange m.FileIndex
381+
(Position.mkPos fragLine startFragCol)
382+
(Position.mkPos fragLine (fragCol + 1))), numArgsForSpecifier)
383+
| None -> ()
384+
385+
let ch = fmt[i]
386+
match ch with
387+
| 'd' | 'i' | 'u' | 'B' | 'o' | 'x' | 'X' ->
388+
if ch = 'B' then DiagnosticsLogger.checkLanguageFeatureError g.langVersion Features.LanguageFeature.PrintfBinaryFormat m
389+
if info.precision then failwith (FSComp.SR.forFormatDoesntSupportPrecision(ch.ToString()))
400390
collectSpecifierLocation fragLine fragCol 1
401391
let i = skipPossibleInterpolationHole (i+1)
402-
parseLoop ((posi, mkFlexibleIntFormatTypar g m) :: acc) (i, fragLine, fragCol+1) fragments
403-
| _ -> failwith (FSComp.SR.forBadFormatSpecifier())
404-
405-
| 'h' | 'H' ->
406-
failwith (FSComp.SR.forHIsUnnecessary())
407-
408-
| 'M' ->
409-
collectSpecifierLocation fragLine fragCol 1
410-
let i = skipPossibleInterpolationHole (i+1)
411-
parseLoop ((posi, mkFlexibleDecimalFormatTypar g m) :: acc) (i, fragLine, fragCol+1) fragments
412-
413-
| 'f' | 'F' | 'e' | 'E' | 'g' | 'G' ->
414-
collectSpecifierLocation fragLine fragCol 1
415-
let i = skipPossibleInterpolationHole (i+1)
416-
parseLoop ((posi, mkFlexibleFloatFormatTypar g m) :: acc) (i, fragLine, fragCol+1) fragments
417-
418-
| 'b' ->
419-
checkOtherFlags ch
420-
collectSpecifierLocation fragLine fragCol 1
421-
let i = skipPossibleInterpolationHole (i+1)
422-
parseLoop ((posi, g.bool_ty) :: acc) (i, fragLine, fragCol+1) fragments
423-
424-
| 'c' ->
425-
checkOtherFlags ch
426-
collectSpecifierLocation fragLine fragCol 1
427-
let i = skipPossibleInterpolationHole (i+1)
428-
parseLoop ((posi, g.char_ty) :: acc) (i, fragLine, fragCol+1) fragments
429-
430-
| 's' ->
431-
checkOtherFlags ch
432-
collectSpecifierLocation fragLine fragCol 1
433-
let i = skipPossibleInterpolationHole (i+1)
434-
parseLoop ((posi, g.string_ty) :: acc) (i, fragLine, fragCol+1) fragments
435-
436-
| 'O' ->
437-
checkOtherFlags ch
438-
collectSpecifierLocation fragLine fragCol 1
439-
let i = skipPossibleInterpolationHole (i+1)
440-
parseLoop ((posi, NewInferenceType g) :: acc) (i, fragLine, fragCol+1) fragments
441-
442-
// residue of hole "...{n}..." in interpolated strings become %P(...)
443-
| 'P' when isInterpolated ->
444-
checkOtherFlags ch
445-
let i = requireAndSkipInterpolationHoleFormat (i+1)
446-
// Note, the fragCol doesn't advance at all as these are magically inserted.
447-
parseLoop ((posi, NewInferenceType g) :: acc) (i, fragLine, startFragCol) fragments
448-
449-
| 'A' ->
450-
if g.useReflectionFreeCodeGen then
451-
failwith (FSComp.SR.forPercentAInReflectionFreeCode())
452-
453-
match info.numPrefixIfPos with
454-
| None // %A has BindingFlags=Public, %+A has BindingFlags=Public | NonPublic
455-
| Some '+' ->
392+
parseLoop ((posi, mkFlexibleIntFormatTypar g m) :: acc) (i, fragLine, fragCol+1) fragments
393+
394+
| 'l' | 'L' ->
395+
if info.precision then failwith (FSComp.SR.forFormatDoesntSupportPrecision(ch.ToString()))
396+
let fragCol = fragCol+1
397+
let i = i+1
398+
399+
// "bad format specifier ... In F# code you can use %d, %x, %o or %u instead ..."
400+
if i >= len then
401+
failwith (FSComp.SR.forBadFormatSpecifier())
402+
// Always error for %l and %Lx
403+
failwith (FSComp.SR.forLIsUnnecessary())
404+
match fmt[i] with
405+
| 'd' | 'i' | 'o' | 'u' | 'x' | 'X' ->
406+
collectSpecifierLocation fragLine fragCol 1
407+
let i = skipPossibleInterpolationHole (i+1)
408+
parseLoop ((posi, mkFlexibleIntFormatTypar g m) :: acc) (i, fragLine, fragCol+1) fragments
409+
| _ -> failwith (FSComp.SR.forBadFormatSpecifier())
410+
411+
| 'h' | 'H' ->
412+
failwith (FSComp.SR.forHIsUnnecessary())
413+
414+
| 'M' ->
456415
collectSpecifierLocation fragLine fragCol 1
457416
let i = skipPossibleInterpolationHole (i+1)
417+
parseLoop ((posi, mkFlexibleDecimalFormatTypar g m) :: acc) (i, fragLine, fragCol+1) fragments
418+
419+
| 'f' | 'F' | 'e' | 'E' | 'g' | 'G' ->
420+
collectSpecifierLocation fragLine fragCol 1
421+
let i = skipPossibleInterpolationHole (i+1)
422+
parseLoop ((posi, mkFlexibleFloatFormatTypar g m) :: acc) (i, fragLine, fragCol+1) fragments
423+
424+
| 'b' ->
425+
checkOtherFlags ch
426+
collectSpecifierLocation fragLine fragCol 1
427+
let i = skipPossibleInterpolationHole (i+1)
428+
parseLoop ((posi, g.bool_ty) :: acc) (i, fragLine, fragCol+1) fragments
429+
430+
| 'c' ->
431+
checkOtherFlags ch
432+
collectSpecifierLocation fragLine fragCol 1
433+
let i = skipPossibleInterpolationHole (i+1)
434+
parseLoop ((posi, g.char_ty) :: acc) (i, fragLine, fragCol+1) fragments
435+
436+
| 's' ->
437+
checkOtherFlags ch
438+
collectSpecifierLocation fragLine fragCol 1
439+
let i = skipPossibleInterpolationHole (i+1)
440+
parseLoop ((posi, g.string_ty) :: acc) (i, fragLine, fragCol+1) fragments
441+
442+
| 'O' ->
443+
checkOtherFlags ch
444+
collectSpecifierLocation fragLine fragCol 1
445+
let i = skipPossibleInterpolationHole (i+1)
446+
parseLoop ((posi, NewInferenceType g) :: acc) (i, fragLine, fragCol+1) fragments
447+
448+
// residue of hole "...{n}..." in interpolated strings become %P(...)
449+
| 'P' when isInterpolated ->
450+
checkOtherFlags ch
451+
let i = requireAndSkipInterpolationHoleFormat (i+1)
452+
// Note, the fragCol doesn't advance at all as these are magically inserted.
453+
parseLoop ((posi, NewInferenceType g) :: acc) (i, fragLine, startFragCol) fragments
454+
455+
| 'A' ->
456+
if g.useReflectionFreeCodeGen then
457+
failwith (FSComp.SR.forPercentAInReflectionFreeCode())
458+
459+
match info.numPrefixIfPos with
460+
| None // %A has BindingFlags=Public, %+A has BindingFlags=Public | NonPublic
461+
| Some '+' ->
462+
collectSpecifierLocation fragLine fragCol 1
463+
let i = skipPossibleInterpolationHole (i+1)
464+
let aTy = NewInferenceType g
465+
percentATys.Add(aTy)
466+
parseLoop ((posi, aTy) :: acc) (i, fragLine, fragCol+1) fragments
467+
| Some n ->
468+
failwith (FSComp.SR.forDoesNotSupportPrefixFlag(ch.ToString(), n.ToString()))
469+
470+
| 'a' ->
471+
checkOtherFlags ch
458472
let aTy = NewInferenceType g
459-
percentATys.Add(aTy)
460-
parseLoop ((posi, aTy) :: acc) (i, fragLine, fragCol+1) fragments
461-
| Some n ->
462-
failwith (FSComp.SR.forDoesNotSupportPrefixFlag(ch.ToString(), n.ToString()))
463-
464-
| 'a' ->
465-
checkOtherFlags ch
466-
let aTy = NewInferenceType g
467-
let fTy = mkFunTy g printerArgTy (mkFunTy g aTy printerResidueTy)
468-
collectSpecifierLocation fragLine fragCol 2
469-
let i = skipPossibleInterpolationHole (i+1)
470-
parseLoop ((Option.map ((+)1) posi, aTy) :: (posi, fTy) :: acc) (i, fragLine, fragCol+1) fragments
471-
472-
| 't' ->
473-
checkOtherFlags ch
474-
collectSpecifierLocation fragLine fragCol 1
475-
let i = skipPossibleInterpolationHole (i+1)
476-
parseLoop ((posi, mkFunTy g printerArgTy printerResidueTy) :: acc) (i, fragLine, fragCol+1) fragments
477-
478-
| c -> failwith (FSComp.SR.forBadFormatSpecifierGeneral(String.make 1 c))
473+
let fTy = mkFunTy g printerArgTy (mkFunTy g aTy printerResidueTy)
474+
collectSpecifierLocation fragLine fragCol 2
475+
let i = skipPossibleInterpolationHole (i+1)
476+
parseLoop ((Option.map ((+)1) posi, aTy) :: (posi, fTy) :: acc) (i, fragLine, fragCol+1) fragments
477+
478+
| 't' ->
479+
checkOtherFlags ch
480+
collectSpecifierLocation fragLine fragCol 1
481+
let i = skipPossibleInterpolationHole (i+1)
482+
parseLoop ((posi, mkFunTy g printerArgTy printerResidueTy) :: acc) (i, fragLine, fragCol+1) fragments
483+
484+
| c -> failwith (FSComp.SR.forBadFormatSpecifierGeneral(String.make 1 c))
479485

480486
let results = parseLoop [] (0, 0, m.StartColumn) fragments
481487
results, Seq.toList specifierLocations, dotnetFormatString.ToString(), percentATys.ToArray()

0 commit comments

Comments
 (0)