@@ -8,6 +8,7 @@ open Microsoft.FSharp.Compiler.AbstractIL
88open Microsoft.FSharp .Compiler .AbstractIL .Internal
99open Microsoft.FSharp .Compiler .AbstractIL .Internal .Library
1010open Microsoft.FSharp .Compiler .Ast
11+ open Microsoft.FSharp .Compiler .Range
1112open Microsoft.FSharp .Compiler .ErrorLogger
1213open Microsoft.FSharp .Compiler .Tast
1314open Microsoft.FSharp .Compiler .Tastops
@@ -24,7 +25,7 @@ let lowestDefaultPriority = 0 (* See comment on TyparConstraint.DefaultsTo *)
2425
2526let mkFlexibleFormatTypar m tys dflt =
2627 let tp = NewTypar ( TyparKind.Type, TyparRigidity.Rigid, Typar( mkSynId m " fmt" , HeadTypeStaticReq, true ), false , TyparDynamicReq.Yes,[], false , false )
27- tp.FixupConstraints [ TyparConstraint.SimpleChoice ( tys, m); TyparConstraint.DefaultsTo ( lowestDefaultPriority, dflt, m)];
28+ tp.FixupConstraints [ TyparConstraint.SimpleChoice ( tys, m); TyparConstraint.DefaultsTo ( lowestDefaultPriority, dflt, m)]
2829 copyAndFixupFormatTypar m tp
2930
3031let mkFlexibleIntFormatTypar g m =
@@ -37,21 +38,46 @@ let mkFlexibleFloatFormatTypar g m =
3738let isDigit c = ( '0' <= c && c <= '9' )
3839
3940type FormatInfoRegister =
40- { mutable leftJustify : bool ;
41- mutable numPrefixIfPos : char option ;
42- mutable addZeros : bool ;
41+ { mutable leftJustify : bool
42+ mutable numPrefixIfPos : char option
43+ mutable addZeros : bool
4344 mutable precision : bool }
4445
4546let newInfo ()=
46- { leftJustify = false ;
47- numPrefixIfPos = None;
48- addZeros = false ;
47+ { leftJustify = false
48+ numPrefixIfPos = None
49+ addZeros = false
4950 precision = false }
5051
51- let parseFormatStringInternal m g fmt bty cty =
52+ let parseFormatStringInternal ( m : range ) g ( source : string option ) fmt bty cty =
53+ // Offset is used to adjust ranges depending on whether input string is regular, verbatim or triple-quote.
54+ // We construct a new 'fmt' string since the current 'fmt' string doesn't distinguish between "\n" and escaped "\\n".
55+ let ( offset , fmt ) =
56+ match source with
57+ | Some source ->
58+ let source = source.Replace( " \r\n " , " \n " ) .Replace( " \r " , " \n " )
59+ let positions =
60+ source.Split( '\n' )
61+ |> Seq.map ( fun s -> String.length s + 1 )
62+ |> Seq.scan (+) 0
63+ |> Seq.toArray
64+ let length = source.Length
65+ if m.EndLine < positions.Length then
66+ let startIndex = positions.[ m.StartLine-1 ] + m.StartColumn
67+ let endIndex = positions.[ m.EndLine-1 ] + m.EndColumn - 1
68+ if startIndex < length-3 && source.[ startIndex.. startIndex+ 2 ] = " \"\"\" " then
69+ ( 3 , source.[ startIndex+ 3 .. endIndex-3 ])
70+ elif startIndex < length-2 && source.[ startIndex.. startIndex+ 1 ] = " @\" " then
71+ ( 2 , source.[ startIndex+ 2 .. endIndex-1 ])
72+ else ( 1 , source.[ startIndex+ 1 .. endIndex-1 ])
73+ else ( 1 , fmt)
74+ | None -> ( 1 , fmt)
75+
5276 let len = String.length fmt
5377
54- let rec parseLoop acc i =
78+ let specifierLocations = ResizeArray()
79+
80+ let rec parseLoop acc ( i , relLine , relCol ) =
5581 if i >= len then
5682 let argtys =
5783 if acc |> List.forall ( fun ( p , _ ) -> p = None) then // without positional specifiers
@@ -60,11 +86,13 @@ let parseFormatStringInternal m g fmt bty cty =
6086 failwithf " %s " <| FSComp.SR.forPositionalSpecifiersNotPermitted()
6187 argtys
6288 elif System.Char.IsSurrogatePair( fmt, i) then
63- parseLoop acc ( i+ 2 )
89+ parseLoop acc ( i+ 2 , relLine , relCol + 2 )
6490 else
6591 let c = fmt.[ i]
6692 match c with
6793 | '%' ->
94+ let startCol = relCol
95+ let relCol = relCol+ 1
6896 let i = i+ 1
6997 if i >= len then failwithf " %s " <| FSComp.SR.forMissingFormatSpecifier()
7098 let info = newInfo()
@@ -73,58 +101,58 @@ let parseFormatStringInternal m g fmt bty cty =
73101 if i >= len then failwithf " %s " <| FSComp.SR.forMissingFormatSpecifier()
74102 match fmt.[ i] with
75103 | '-' ->
76- if info.leftJustify then failwithf " %s " <| FSComp.SR.forFlagSetTwice( " -" );
77- info.leftJustify <- true ;
104+ if info.leftJustify then failwithf " %s " <| FSComp.SR.forFlagSetTwice( " -" )
105+ info.leftJustify <- true
78106 flags( i+ 1 )
79107 | '+' ->
80108 if info.numPrefixIfPos <> None then failwithf " %s " <| FSComp.SR.forPrefixFlagSpacePlusSetTwice()
81- info.numPrefixIfPos <- Some '+' ;
109+ info.numPrefixIfPos <- Some '+'
82110 flags( i+ 1 )
83111 | '0' ->
84- if info.addZeros then failwithf " %s " <| FSComp.SR.forFlagSetTwice( " 0" );
85- info.addZeros <- true ;
112+ if info.addZeros then failwithf " %s " <| FSComp.SR.forFlagSetTwice( " 0" )
113+ info.addZeros <- true
86114 flags( i+ 1 )
87115 | ' ' ->
88- if info.numPrefixIfPos <> None then failwithf " %s " <| FSComp.SR.forPrefixFlagSpacePlusSetTwice();
89- info.numPrefixIfPos <- Some ' ' ;
116+ if info.numPrefixIfPos <> None then failwithf " %s " <| FSComp.SR.forPrefixFlagSpacePlusSetTwice()
117+ info.numPrefixIfPos <- Some ' '
90118 flags( i+ 1 )
91- | '#' -> failwithf " %s " <| FSComp.SR.forHashSpecifierIsInvalid();
119+ | '#' -> failwithf " %s " <| FSComp.SR.forHashSpecifierIsInvalid()
92120 | _ -> i
93121
94122 let rec digitsPrecision i =
95- if i >= len then failwithf " %s " <| FSComp.SR.forBadPrecision();
123+ if i >= len then failwithf " %s " <| FSComp.SR.forBadPrecision()
96124 match fmt.[ i] with
97125 | c when isDigit c -> digitsPrecision ( i+ 1 )
98126 | _ -> i
99127
100128 let precision i =
101- if i >= len then failwithf " %s " <| FSComp.SR.forBadWidth();
129+ if i >= len then failwithf " %s " <| FSComp.SR.forBadWidth()
102130 match fmt.[ i] with
103131 | c when isDigit c -> info.precision <- true ; false , digitsPrecision ( i+ 1 )
104132 | '*' -> info.precision <- true ; true ,( i+ 1 )
105133 | _ -> failwithf " %s " <| FSComp.SR.forPrecisionMissingAfterDot()
106134
107135 let optionalDotAndPrecision i =
108- if i >= len then failwithf " %s " <| FSComp.SR.forBadPrecision();
136+ if i >= len then failwithf " %s " <| FSComp.SR.forBadPrecision()
109137 match fmt.[ i] with
110138 | '.' -> precision ( i+ 1 )
111139 | _ -> false , i
112140
113141 let rec digitsWidthAndPrecision i =
114- if i >= len then failwithf " %s " <| FSComp.SR.forBadPrecision();
142+ if i >= len then failwithf " %s " <| FSComp.SR.forBadPrecision()
115143 match fmt.[ i] with
116144 | c when isDigit c -> digitsWidthAndPrecision ( i+ 1 )
117145 | _ -> optionalDotAndPrecision i
118146
119147 let widthAndPrecision i =
120- if i >= len then failwithf " %s " <| FSComp.SR.forBadPrecision();
148+ if i >= len then failwithf " %s " <| FSComp.SR.forBadPrecision()
121149 match fmt.[ i] with
122150 | c when isDigit c -> false , digitsWidthAndPrecision i
123151 | '*' -> true , optionalDotAndPrecision ( i+ 1 )
124152 | _ -> false , optionalDotAndPrecision i
125153
126154 let rec digitsPosition n i =
127- if i >= len then failwithf " %s " <| FSComp.SR.forBadPrecision();
155+ if i >= len then failwithf " %s " <| FSComp.SR.forBadPrecision()
128156 match fmt.[ i] with
129157 | c when isDigit c -> digitsPosition ( n* 10 + int c - int '0' ) ( i+ 1 )
130158 | '$' -> Some n, i+ 1
@@ -136,14 +164,20 @@ let parseFormatStringInternal m g fmt bty cty =
136164 let p , i' = digitsPosition ( int c - int '0' ) ( i+ 1 )
137165 if p = None then None, i else p, i'
138166 | _ -> None, i
139-
167+
168+ let oldI = i
140169 let posi , i = position i
170+ let relCol = relCol + i - oldI
141171
172+ let oldI = i
142173 let i = flags i
174+ let relCol = relCol + i - oldI
143175
176+ let oldI = i
144177 let widthArg ,( precisionArg , i ) = widthAndPrecision i
178+ let relCol = relCol + i - oldI
145179
146- if i >= len then failwithf " %s " <| FSComp.SR.forBadPrecision();
180+ if i >= len then failwithf " %s " <| FSComp.SR.forBadPrecision()
147181
148182 let acc = if precisionArg then ( Option.map ((+) 1 ) posi, g.int_ ty) :: acc else acc
149183
@@ -155,20 +189,36 @@ let parseFormatStringInternal m g fmt bty cty =
155189 failwithf " %s " <| FSComp.SR.forDoesNotSupportPrefixFlag( c.ToString(), ( Option.get info.numPrefixIfPos) .ToString())
156190
157191 let checkOtherFlags c =
158- checkNoPrecision c;
159- checkNoZeroFlag c;
192+ checkNoPrecision c
193+ checkNoZeroFlag c
160194 checkNoNumericPrefix c
161195
196+ let collectSpecifierLocation relLine relCol =
197+ match relLine with
198+ | 0 ->
199+ specifierLocations.Add(
200+ Range.mkFileIndexRange m.FileIndex
201+ ( Range.mkPos m.StartLine ( startCol + offset))
202+ ( Range.mkPos m.StartLine ( relCol + offset)))
203+ | _ ->
204+ specifierLocations.Add(
205+ Range.mkFileIndexRange m.FileIndex
206+ ( Range.mkPos ( m.StartLine + relLine) startCol)
207+ ( Range.mkPos ( m.StartLine + relLine) relCol))
208+
162209 let ch = fmt.[ i]
163210 match ch with
164- | '%' -> parseLoop acc ( i+ 1 )
211+ | '%' ->
212+ parseLoop acc ( i+ 1 , relLine, relCol+ 1 )
165213
166214 | ( 'd' | 'i' | 'o' | 'u' | 'x' | 'X' ) ->
167- if info.precision then failwithf " %s " <| FSComp.SR.forFormatDoesntSupportPrecision( ch.ToString());
168- parseLoop (( posi, mkFlexibleIntFormatTypar g m) :: acc) ( i+ 1 )
215+ if info.precision then failwithf " %s " <| FSComp.SR.forFormatDoesntSupportPrecision( ch.ToString())
216+ collectSpecifierLocation relLine relCol
217+ parseLoop (( posi, mkFlexibleIntFormatTypar g m) :: acc) ( i+ 1 , relLine, relCol+ 1 )
169218
170219 | ( 'l' | 'L' ) ->
171- if info.precision then failwithf " %s " <| FSComp.SR.forFormatDoesntSupportPrecision( ch.ToString());
220+ if info.precision then failwithf " %s " <| FSComp.SR.forFormatDoesntSupportPrecision( ch.ToString())
221+ let relCol = relCol+ 1
172222 let i = i+ 1
173223
174224 // "bad format specifier ... In F# code you can use %d, %x, %o or %u instead ..."
@@ -178,65 +228,79 @@ let parseFormatStringInternal m g fmt bty cty =
178228 failwithf " %s " <| FSComp.SR.forLIsUnnecessary()
179229 match fmt.[ i] with
180230 | ( 'd' | 'i' | 'o' | 'u' | 'x' | 'X' ) ->
181- parseLoop (( posi, mkFlexibleIntFormatTypar g m) :: acc) ( i+ 1 )
231+ collectSpecifierLocation relLine relCol
232+ parseLoop (( posi, mkFlexibleIntFormatTypar g m) :: acc) ( i+ 1 , relLine, relCol+ 1 )
182233 | _ -> failwithf " %s " <| FSComp.SR.forBadFormatSpecifier()
183234
184235 | ( 'h' | 'H' ) ->
185236 failwithf " %s " <| FSComp.SR.forHIsUnnecessary()
186237
187238 | 'M' ->
188- parseLoop (( posi, g.decimal_ ty) :: acc) ( i+ 1 )
239+ collectSpecifierLocation relLine relCol
240+ parseLoop (( posi, g.decimal_ ty) :: acc) ( i+ 1 , relLine, relCol+ 1 )
189241
190- | ( 'f' | 'F' | 'e' | 'E' | 'g' | 'G' ) ->
191- parseLoop (( posi, mkFlexibleFloatFormatTypar g m) :: acc) ( i+ 1 )
242+ | ( 'f' | 'F' | 'e' | 'E' | 'g' | 'G' ) ->
243+ collectSpecifierLocation relLine relCol
244+ parseLoop (( posi, mkFlexibleFloatFormatTypar g m) :: acc) ( i+ 1 , relLine, relCol+ 1 )
192245
193246 | 'b' ->
194- checkOtherFlags ch;
195- parseLoop (( posi, g.bool_ ty) :: acc) ( i+ 1 )
247+ checkOtherFlags ch
248+ collectSpecifierLocation relLine relCol
249+ parseLoop (( posi, g.bool_ ty) :: acc) ( i+ 1 , relLine, relCol+ 1 )
196250
197251 | 'c' ->
198- checkOtherFlags ch;
199- parseLoop (( posi, g.char_ ty) :: acc) ( i+ 1 )
252+ checkOtherFlags ch
253+ collectSpecifierLocation relLine relCol
254+ parseLoop (( posi, g.char_ ty) :: acc) ( i+ 1 , relLine, relCol+ 1 )
200255
201256 | 's' ->
202- checkOtherFlags ch;
203- parseLoop (( posi, g.string_ ty) :: acc) ( i+ 1 )
257+ checkOtherFlags ch
258+ collectSpecifierLocation relLine relCol
259+ parseLoop (( posi, g.string_ ty) :: acc) ( i+ 1 , relLine, relCol+ 1 )
204260
205261 | 'O' ->
206- checkOtherFlags ch;
207- parseLoop (( posi, NewInferenceType ()) :: acc) ( i+ 1 )
262+ checkOtherFlags ch
263+ collectSpecifierLocation relLine relCol
264+ parseLoop (( posi, NewInferenceType ()) :: acc) ( i+ 1 , relLine, relCol+ 1 )
208265
209266 | 'A' ->
210267 match info.numPrefixIfPos with
211268 | None // %A has BindingFlags=Public, %+A has BindingFlags=Public | NonPublic
212- | Some '+' -> parseLoop (( posi, NewInferenceType ()) :: acc) ( i+ 1 )
269+ | Some '+' ->
270+ collectSpecifierLocation relLine relCol
271+ parseLoop (( posi, NewInferenceType ()) :: acc) ( i+ 1 , relLine, relCol+ 1 )
213272 | Some _ -> failwithf " %s " <| FSComp.SR.forDoesNotSupportPrefixFlag( ch.ToString(), ( Option.get info.numPrefixIfPos) .ToString())
214273
215274 | 'a' ->
216- checkOtherFlags ch;
275+ checkOtherFlags ch
217276 let xty = NewInferenceType ()
218277 let fty = bty - -> ( xty - -> cty)
219- parseLoop (( Option.map ((+) 1 ) posi, xty) :: ( posi, fty) :: acc) ( i+ 1 )
278+ collectSpecifierLocation relLine relCol
279+ parseLoop (( Option.map ((+) 1 ) posi, xty) :: ( posi, fty) :: acc) ( i+ 1 , relLine, relCol+ 1 )
220280
221281 | 't' ->
222- checkOtherFlags ch;
223- parseLoop (( posi, bty - -> cty) :: acc) ( i+ 1 )
282+ checkOtherFlags ch
283+ collectSpecifierLocation relLine relCol
284+ parseLoop (( posi, bty - -> cty) :: acc) ( i+ 1 , relLine, relCol+ 1 )
224285
225286 | c -> failwithf " %s " <| FSComp.SR.forBadFormatSpecifierGeneral( String.make 1 c)
226-
227- | _ -> parseLoop acc ( i+ 1 )
228- parseLoop [] 0
229-
230- let ParseFormatString m g fmt bty cty dty =
231- let argtys = parseFormatStringInternal m g fmt bty cty
287+
288+ | '\n' -> parseLoop acc ( i+ 1 , relLine+ 1 , 0 )
289+ | _ -> parseLoop acc ( i+ 1 , relLine, relCol+ 1 )
290+
291+ let results = parseLoop [] ( 0 , 0 , m.StartColumn)
292+ results, Seq.toList specifierLocations
293+
294+ let ParseFormatString m g source fmt bty cty dty =
295+ let argtys , specifierLocations = parseFormatStringInternal m g source fmt bty cty
232296 let aty = List.foldBack (- -> ) argtys dty
233297 let ety = mkTupledTy g argtys
234- aty, ety
298+ ( aty, ety), specifierLocations
235299
236300let TryCountFormatStringArguments m g fmt bty cty =
237301 try
238- parseFormatStringInternal m g fmt bty cty
239- |> List.length
240- |> Some
302+ let argtys , _specifierLocations = parseFormatStringInternal m g None fmt bty cty
303+ Some argtys.Length
241304 with _ ->
242- None
305+ None
306+
0 commit comments