Skip to content

Commit 7903428

Browse files
authored
fix line directive application for caller info (#18829)
1 parent 22815df commit 7903428

File tree

13 files changed

+225
-39
lines changed

13 files changed

+225
-39
lines changed

docs/release-notes/.FSharp.Compiler.Service/10.0.100.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
* `SynExprRecordField` now includes a `range` field ([PR #18617](https://github.com/dotnet/fsharp/pull/18617))
3131
* Mark `Range.Zero` as obsolete in favor of `Range.range0` ([PR #18664](https://github.com/dotnet/fsharp/pull/18664))
3232
* Use `Synbinding` to model `and!` ([PR #18805](https://github.com/dotnet/fsharp/pull/18805))
33-
* Redesign #line processing. The original positions (unaffected by #line directives) are now kept in the AST, and `__LINE__` and `__SOURCE_LINE__` show the original line numbers / file names. However, all diagnostics and debug information stays the same (shows the position transformed by the #line directives). ([Issue #18553](https://github.com/dotnet/fsharp/issues/18553), [PR #18699](https://github.com/dotnet/fsharp/pull/18699))
33+
* Redesign #line processing. The original positions (unaffected by #line directives) are now kept in the AST, and `__LINE__` and `__SOURCE_LINE__` show the original line numbers / file names. However, all diagnostics and debug information stays the same (shows the position transformed by the #line directives). ([Issue #18553](https://github.com/dotnet/fsharp/issues/18553), [PR #18699](https://github.com/dotnet/fsharp/pull/18699), [PR 18828](https://github.com/dotnet/fsharp/pull/18828), [PR 18829](https://github.com/dotnet/fsharp/pull/18829))
3434
* Unify `let`, `let!`, `use` and `use!` AST representation. ([PR #18825](https://github.com/dotnet/fsharp/pull/18825))[^1]
3535

3636
### Migration Guidance for AST Users

src/Compiler/Checking/MethodCalls.fs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1464,11 +1464,12 @@ let rec GetDefaultExpressionForCallerSideOptionalArg tcFieldInit g (calledArg: C
14641464
| ByrefTy g inst ->
14651465
GetDefaultExpressionForCallerSideOptionalArg tcFieldInit g calledArg inst (PassByRef(inst, currDfltVal)) eCallerMemberName mMethExpr
14661466
| _ ->
1467+
let mLineDirectivesApplied = mMethExpr.ApplyLineDirectives()
14671468
match calledArg.CallerInfo, eCallerMemberName with
14681469
| CallerLineNumber, _ when typeEquiv g currCalledArgTy g.int_ty ->
1469-
emptyPreBinder, Expr.Const (Const.Int32(mMethExpr.StartLine), mMethExpr, currCalledArgTy)
1470+
emptyPreBinder, Expr.Const (Const.Int32(mLineDirectivesApplied.StartLine), mMethExpr, currCalledArgTy)
14701471
| CallerFilePath, _ when typeEquiv g currCalledArgTy g.string_ty ->
1471-
let fileName = mMethExpr.FileName |> FileSystem.GetFullPathShim |> PathMap.apply g.pathMap
1472+
let fileName = mLineDirectivesApplied.FileName |> FileSystem.GetFullPathShim |> PathMap.apply g.pathMap
14721473
emptyPreBinder, Expr.Const (Const.String fileName, mMethExpr, currCalledArgTy)
14731474
| CallerMemberName, Some callerName when (typeEquiv g currCalledArgTy g.string_ty) ->
14741475
emptyPreBinder, Expr.Const (Const.String callerName, mMethExpr, currCalledArgTy)
@@ -1504,13 +1505,14 @@ let rec GetDefaultExpressionForCallerSideOptionalArg tcFieldInit g (calledArg: C
15041505
let GetDefaultExpressionForCalleeSideOptionalArg g (calledArg: CalledArg) eCallerMemberName (mMethExpr: range) =
15051506
let calledArgTy = calledArg.CalledArgumentType
15061507
let calledNonOptTy = tryDestOptionalTy g calledArgTy
1508+
let mLineDirectivesApplied = mMethExpr.ApplyLineDirectives()
15071509

15081510
match calledArg.CallerInfo, eCallerMemberName with
15091511
| CallerLineNumber, _ when typeEquiv g calledNonOptTy g.int_ty ->
1510-
let lineExpr = Expr.Const(Const.Int32 mMethExpr.StartLine, mMethExpr, calledNonOptTy)
1512+
let lineExpr = Expr.Const(Const.Int32 mLineDirectivesApplied.StartLine, mMethExpr, calledNonOptTy)
15111513
mkOptionalSome g calledArgTy calledNonOptTy lineExpr mMethExpr
15121514
| CallerFilePath, _ when typeEquiv g calledNonOptTy g.string_ty ->
1513-
let fileName = mMethExpr.FileName |> FileSystem.GetFullPathShim |> PathMap.apply g.pathMap
1515+
let fileName = mLineDirectivesApplied.FileName |> FileSystem.GetFullPathShim |> PathMap.apply g.pathMap
15141516
let filePathExpr = Expr.Const (Const.String(fileName), mMethExpr, calledNonOptTy)
15151517
mkOptionalSome g calledArgTy calledNonOptTy filePathExpr mMethExpr
15161518
| CallerMemberName, Some(callerName) when typeEquiv g calledNonOptTy g.string_ty ->

src/Compiler/Utilities/range.fs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -268,9 +268,10 @@ module internal LineDirectives =
268268
// The key is the index of the original file. Each line directive is represented
269269
// by the line number of the directive and the file index and line number of the target.
270270
let mutable store: Map<FileIndex, (int * (FileIndex * int)) list> = Map.empty
271+
let storeLock = obj ()
271272

272-
let add originalFileIndex lineDirectives =
273-
lock store <| fun () -> store <- store.Add(originalFileIndex, lineDirectives)
273+
let add fileIndex lineDirectives =
274+
lock storeLock <| fun () -> store <- store.Add(fileIndex, lineDirectives)
274275

275276
[<Struct; CustomEquality; NoComparison>]
276277
[<System.Diagnostics.DebuggerDisplay("({StartLine},{StartColumn}-{EndLine},{EndColumn}) {ShortFileName} -> {DebugCode}")>]

src/Compiler/Utilities/range.fsi

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -191,9 +191,9 @@ module internal FileIndex =
191191
[<RequireQualifiedAccess>]
192192
module internal LineDirectives =
193193

194-
/// Add the line directive data of the source file with originalFileIndex. Each line directive is represented
194+
/// Add the line directive data of the source file of fileIndex. Each line directive is represented
195195
/// by the line number of the directive and the file index and line number of the target.
196-
val add: originalFileIndex: FileIndex -> lineDirectives: (int * (FileIndex * int)) list -> unit
196+
val add: fileIndex: FileIndex -> lineDirectives: (int * (FileIndex * int)) list -> unit
197197

198198
module Range =
199199

src/Compiler/lex.fsl

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -812,13 +812,12 @@ rule token (args: LexArgs) (skip: bool) = parse
812812
| '"' -> String.sub s start (n-start)
813813
| _ -> parseFile start (n+1)
814814

815-
// Call the parser
816815
let line, file = parseLeadingDirective 1
817816

818-
// Construct the new position
819817
if args.applyLineDirectives then
820818
let fileIndex = match file with Some f -> FileIndex.fileIndexOfFile f | None -> pos.FileIndex
821819
LineDirectiveStore.SaveLineDirective(lexbuf, fileIndex, line)
820+
822821
// we consumed a newline getting here
823822
incrLine lexbuf
824823

tests/FSharp.Compiler.ComponentTests/CompilerDirectives/Line.fs

Lines changed: 200 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,13 @@ open FSharp.Compiler.Syntax
1212
open FSharp.Compiler.Text
1313
open FSharp.Compiler.UnicodeLexing
1414
open FSharp.Test.Compiler
15+
open System.IO
1516

1617
module Line =
1718

18-
let parse (source: string) =
19+
let parse (source: string) sourceFileName =
1920
let checker = FSharpChecker.Create()
2021
let langVersion = "preview"
21-
let sourceFileName = "Line.fs"
2222
let parsingOptions =
2323
{ FSharpParsingOptions.Default with
2424
SourceFiles = [| sourceFileName |]
@@ -30,7 +30,7 @@ module Line =
3030

3131
[<Literal>]
3232
let private case1 = """module A
33-
#line 1 "xyz.fs"
33+
#line 1 "xyz1.fs"
3434
(
3535
printfn ""
3636
)
@@ -39,24 +39,24 @@ printfn ""
3939
[<Literal>]
4040
let private case2 = """module A
4141
(
42-
#line 1 "xyz.fs"
42+
#line 1 "xyz2.fs"
4343
printfn ""
4444
)
4545
"""
4646

4747
[<Literal>]
4848
let private case3 = """module A
4949
(
50-
#line 1 "xyz.fs"
50+
#line 1 "xyz3.fs"
5151
)
5252
"""
5353

5454
[<Theory>]
55-
[<InlineData(1, case1, "xyz.fs:(1,0--3,1)")>]
56-
[<InlineData(2, case2, "Line.fs:(2,0--5,1)")>]
57-
[<InlineData(3, case3, "Line.fs:(2,0--4,1)")>]
55+
[<InlineData(1, case1, "xyz1.fs:(1,0--3,1)")>]
56+
[<InlineData(2, case2, "Line2.fs:(2,0--5,1)")>]
57+
[<InlineData(3, case3, "Line3.fs:(2,0--4,1)")>]
5858
let ``check expr range interacting with line directive`` (case, source, expectedRange) =
59-
let parseResults = parse source
59+
let parseResults = parse source $"Line{case}.fs"
6060
if parseResults.ParseHadErrors then failwith "unexpected: parse error"
6161
let exprRange =
6262
match parseResults.ParseTree with
@@ -111,12 +111,12 @@ printfn ""
111111
let errors =
112112
source
113113
|> FSharp
114-
|> withFileName "test.fs"
114+
|> withFileName "testn.fs"
115115
|> compile
116116
|> shouldFail
117117
|> fun cr -> cr.Output.PerFileErrors |> List.map (fun (fn, d) -> fn, d.Range.StartLine)
118118
let expectedErrors =
119-
[("test.fs", 3); ("test.fs", 4)]
119+
[("testn.fs", 3); ("testn.fs", 4)]
120120
Assert.True(
121121
List.forall2 (fun (e_fn, e_line) (fn, line) -> e_fn = fn && e_line = line) expectedErrors errors,
122122
sprintf "Expected: %A, Found: %A" expectedErrors errors
@@ -137,7 +137,7 @@ printfn ""
137137
true
138138
)
139139
let lexbuf = StringAsLexbuf(true, langVersion, None, sourceText)
140-
resetLexbufPos "test.fs" lexbuf
140+
resetLexbufPos "testt.fs" lexbuf
141141
let tokenizer _ =
142142
let t = Lexer.token lexargs true lexbuf
143143
let p = lexbuf.StartPos
@@ -149,14 +149,14 @@ printfn ""
149149
1
150150
#line 5 "other.fs"
151151
2
152-
#line 10 "test.fs"
152+
#line 10 "testt.fs"
153153
3
154154
"""
155155

156156
let private expected = [
157-
"test.fs", 2
158-
"test.fs", 4
159-
"test.fs", 6
157+
"testt.fs", 2
158+
"testt.fs", 4
159+
"testt.fs", 6
160160
]
161161

162162
[<Fact>]
@@ -166,3 +166,187 @@ printfn ""
166166
for ((e_idx, e_line), (_, idx, line)) in List.zip expected tokens do
167167
Assert.Equal(e_idx, idx)
168168
Assert.Equal(e_line, line)
169+
170+
let callerInfoSource = """
171+
open System.Runtime.CompilerServices
172+
open System.Runtime.InteropServices
173+
174+
type C() =
175+
static member M (
176+
[<CallerLineNumber; Optional; DefaultParameterValue 0>]c: int,
177+
[<CallerFilePath; Optional; DefaultParameterValue "no value">]d: string) =
178+
c, d
179+
180+
#line 1 "file1.fs"
181+
let line1, file1 = C.M()
182+
#line 551 "file2.fs"
183+
let line2, file2 = C.M()
184+
185+
printfn $"{file1} {line1} {file2} {line2}"
186+
"""
187+
188+
[<Fact>]
189+
let ``CallerLineNumber and CallerFilePath work with line directives`` () =
190+
let results =
191+
callerInfoSource
192+
|> FSharp
193+
|> withFileName "CallerInfo.fs"
194+
|> withLangVersion "preview"
195+
|> compileExeAndRun
196+
match results.RunOutput with
197+
| Some (ExecutionOutput output) ->
198+
let words = output.StdOut.Trim().Split(' ')
199+
let file1 = Path.GetFileName(words.[0])
200+
let line1 = int words.[1]
201+
let file2 = Path.GetFileName(words.[2])
202+
let line2 = int words.[3]
203+
Assert.Equal("file1.fs", file1)
204+
Assert.Equal(1, line1)
205+
Assert.Equal("file2.fs", file2)
206+
Assert.Equal(551, line2)
207+
| _ -> failwith "Unexpected: no run output"
208+
209+
210+
211+
let csharpLibSource = """
212+
using System;
213+
using System.Reflection;
214+
using System.Runtime.CompilerServices;
215+
216+
namespace CSharpLib
217+
{
218+
public class CallerInfoTest
219+
{
220+
public static int LineNumber([CallerLineNumber] int line = 777)
221+
{
222+
return line;
223+
}
224+
225+
public static string FilePath([CallerFilePath] string filePath = "dummy1")
226+
{
227+
return filePath;
228+
}
229+
230+
public static string MemberName([CallerMemberName] string memberName = "dummy1")
231+
{
232+
return memberName;
233+
}
234+
235+
public static Tuple<string, int, string> AllInfo(int normalArg, [CallerFilePath] string filePath = "dummy2", [CallerLineNumber] int line = 778, [CallerMemberName] string memberName = "dummy3")
236+
{
237+
return new Tuple<string, int, string>(filePath, line, memberName);
238+
}
239+
}
240+
241+
public class MyCallerInfoAttribute : Attribute
242+
{
243+
public int LineNumber { get; set; }
244+
245+
public MyCallerInfoAttribute([CallerLineNumber] int lineNumber = -1)
246+
{
247+
LineNumber = lineNumber;
248+
}
249+
}
250+
251+
public class MyCallerMemberNameAttribute : Attribute
252+
{
253+
public string MemberName { get; set; }
254+
255+
public MyCallerMemberNameAttribute([CallerMemberName] string member = "dflt")
256+
{
257+
MemberName = member;
258+
}
259+
}
260+
}
261+
"""
262+
263+
let fsharpSource = """
264+
265+
open System.Runtime.CompilerServices
266+
open CSharpLib
267+
268+
type MyTy([<CallerFilePath>] ?p0 : string) =
269+
let mutable p = p0
270+
271+
member x.Path with get() = p
272+
273+
static member GetCallerFilePath([<CallerFilePath>] ?path : string) =
274+
path
275+
276+
module Program =
277+
let doubleSeparator = "##".Replace('#', System.IO.Path.DirectorySeparatorChar)
278+
let sameDirectory = "#.#".Replace('#', System.IO.Path.DirectorySeparatorChar)
279+
let parentDirectory = ".."
280+
let matchesPath (path : string) (s : string) =
281+
s.EndsWith(path.Replace('#', System.IO.Path.DirectorySeparatorChar))
282+
&& not (s.Contains(doubleSeparator))
283+
&& not (s.Contains(sameDirectory))
284+
&& not (s.Contains(parentDirectory))
285+
286+
287+
[<EntryPoint>]
288+
let main (_:string[]) =
289+
printfn "starting main"
290+
let o = MyTy()
291+
let o1 = MyTy("42")
292+
293+
match o.Path with
294+
| Some(path) when matchesPath "CallerInfo.fs" path -> ()
295+
| Some(path) -> failwithf "Unexpected (1): %s" path
296+
| None -> failwith "Unexpected (1): None"
297+
298+
match o1.Path with
299+
| Some(path) when matchesPath "42" path -> ()
300+
| Some(path) -> failwithf "Unexpected (2): %s" path
301+
| None -> failwith "Unexpected (2): None"
302+
303+
match MyTy.GetCallerFilePath() with
304+
| Some(path) when matchesPath "CallerInfo.fs" path -> ()
305+
| Some(path) -> failwithf "Unexpected (3): %s" path
306+
| None -> failwith "Unexpected (3): None"
307+
308+
match MyTy.GetCallerFilePath("42") with
309+
| Some("42") -> ()
310+
| Some(path) -> failwithf "Unexpected (4): %s" path
311+
| None -> failwith "Unexpected (4): None"
312+
313+
match CallerInfoTest.FilePath() with
314+
| path when matchesPath "CallerInfo.fs" path -> ()
315+
| path -> failwithf "Unexpected (5): %s" path
316+
317+
match CallerInfoTest.FilePath("xyz") with
318+
| "xyz" -> ()
319+
| path -> failwithf "Unexpected (6): %s" path
320+
321+
match CallerInfoTest.AllInfo(21) with
322+
| (path, _, _) when matchesPath "CallerInfo.fs" path -> ()
323+
| (path, _, _) -> failwithf "Unexpected (7): %s" path
324+
325+
# 345 "qwerty.fsy"
326+
match CallerInfoTest.AllInfo(123) with
327+
| (path, _, _) when matchesPath "qwerty.fsy" path -> ()
328+
| (path, _, _) -> failwithf "Unexpected (8): %s" path
329+
330+
# 456 "qwerty.fsl"
331+
match CallerInfoTest.AllInfo(123) with
332+
| (path, _, _) when matchesPath "qwerty.fsl" path -> ()
333+
| (path, _, _) -> failwithf "Unexpected (9): %s" path
334+
335+
0
336+
"""
337+
338+
[<Fact>]
339+
let ``C# CallerLineNumber and CallerFilePath work with line directives`` () =
340+
let csharp =
341+
csharpLibSource
342+
|> CSharp
343+
|> withFileName "CallerInfoLib.cs"
344+
345+
fsharpSource
346+
|> FSharp
347+
|> withFileName "CallerInfo.fs"
348+
|> withLangVersion "preview"
349+
|> withReferences [csharp]
350+
|> compileExeAndRun
351+
|> shouldSucceed
352+

0 commit comments

Comments
 (0)