From 265b366456db37c4de0a37947e52692e34879f49 Mon Sep 17 00:00:00 2001 From: Martin521 <29605222+Martin521@users.noreply.github.com> Date: Thu, 18 Sep 2025 16:51:25 +0000 Subject: [PATCH 1/7] ensure that line directives are applied to source identifiers --- .../Checking/Expressions/CheckExpressions.fs | 13 ++++-- src/Compiler/SyntaxTree/LexHelpers.fs | 29 ++---------- src/Compiler/SyntaxTree/LexHelpers.fsi | 2 - src/Compiler/SyntaxTree/SyntaxTreeOps.fs | 45 +++++++++++++++++-- src/Compiler/SyntaxTree/SyntaxTreeOps.fsi | 7 +++ .../CompilerDirectives/Line.fs | 21 +++++++++ 6 files changed, 83 insertions(+), 34 deletions(-) diff --git a/src/Compiler/Checking/Expressions/CheckExpressions.fs b/src/Compiler/Checking/Expressions/CheckExpressions.fs index 9aaf8d472a8..5714a138545 100644 --- a/src/Compiler/Checking/Expressions/CheckExpressions.fs +++ b/src/Compiler/Checking/Expressions/CheckExpressions.fs @@ -926,10 +926,13 @@ let TcConst (cenv: cenv) (overallTy: TType) m env synConst = | SynConst.Char c -> unif g.char_ty Const.Char c - | SynConst.String (s, _, _) - | SynConst.SourceIdentifier (_, s, _) -> + | SynConst.String (s, _, _) -> unif g.string_ty Const.String s + | SynConst.SourceIdentifier (i, s, m) -> + unif g.string_ty + let s = applyLineDirectivesToSourceIdentifier i s m + Const.String s | SynConst.UserNum _ -> error (InternalError(FSComp.SR.tcUnexpectedBigRationalConstant(), m)) | SynConst.Measure _ -> error (Error(FSComp.SR.tcInvalidTypeForUnitsOfMeasure(), m)) | SynConst.UInt16s _ -> error (InternalError(FSComp.SR.tcUnexpectedConstUint16Array(), m)) @@ -4890,8 +4893,10 @@ and TcStaticConstantParameter (cenv: cenv) (env: TcEnv) tpenv kind (StripParenTy | SynConst.Single n when typeEquiv g g.float32_ty kind -> record(g.float32_ty); box (n: single) | SynConst.Double n when typeEquiv g g.float_ty kind -> record(g.float_ty); box (n: double) | SynConst.Char n when typeEquiv g g.char_ty kind -> record(g.char_ty); box (n: char) - | SynConst.String (s, _, _) - | SynConst.SourceIdentifier (_, s, _) when typeEquiv g g.string_ty kind -> record(g.string_ty); box (s: string) + | SynConst.String (s, _, _) -> record(g.string_ty); box (s: string) + | SynConst.SourceIdentifier (i, s, m) when typeEquiv g g.string_ty kind -> + let s = applyLineDirectivesToSourceIdentifier i s m + record(g.string_ty); box (s: string) | SynConst.Bool b when typeEquiv g g.bool_ty kind -> record(g.bool_ty); box (b: bool) | _ -> fail() v, tpenv diff --git a/src/Compiler/SyntaxTree/LexHelpers.fs b/src/Compiler/SyntaxTree/LexHelpers.fs index dddaaa37a94..a4c222720ac 100644 --- a/src/Compiler/SyntaxTree/LexHelpers.fs +++ b/src/Compiler/SyntaxTree/LexHelpers.fs @@ -16,14 +16,11 @@ open FSharp.Compiler.ParseHelpers open FSharp.Compiler.Parser open FSharp.Compiler.Syntax open FSharp.Compiler.Syntax.PrettyNaming +open FSharp.Compiler.SyntaxTreeOps open FSharp.Compiler.Text open FSharp.Compiler.Text.Range open FSharp.Compiler.UnicodeLexing -/// The "mock" file name used by fsi.exe when reading from stdin. -/// Has special treatment by the lexer, i.e. __SOURCE_DIRECTORY__ becomes GetCurrentDirectory() -let stdinMockFileName = "stdin" - /// Lexer args: status of #light processing. Mutated when a #light /// directive is processed. This alters the behaviour of the lexfilter. [] @@ -472,27 +469,9 @@ module Keywords = v | _ -> match s with - | "__SOURCE_DIRECTORY__" -> - let fileName = FileIndex.fileOfFileIndex lexbuf.StartPos.FileIndex - - let dirname = - if String.IsNullOrWhiteSpace(fileName) then - String.Empty - else if fileName = stdinMockFileName then - System.IO.Directory.GetCurrentDirectory() - else - fileName - |> FileSystem.GetFullPathShim (* asserts that path is already absolute *) - |> System.IO.Path.GetDirectoryName - |> (!!) - - if String.IsNullOrEmpty dirname then - dirname - else - PathMap.applyDir args.pathMap dirname - |> fun dir -> KEYWORD_STRING(s, dir) - | "__SOURCE_FILE__" -> KEYWORD_STRING(s, !!System.IO.Path.GetFileName(FileIndex.fileOfFileIndex lexbuf.StartPos.FileIndex)) - | "__LINE__" -> KEYWORD_STRING(s, string lexbuf.StartPos.Line) + | "__SOURCE_DIRECTORY__" + | "__SOURCE_FILE__" + | "__LINE__" -> KEYWORD_STRING(s, getSourceIdentifierValue args.pathMap s lexbuf.LexemeRange) | _ -> IdentifierToken args lexbuf s /// Arbitrary value diff --git a/src/Compiler/SyntaxTree/LexHelpers.fsi b/src/Compiler/SyntaxTree/LexHelpers.fsi index 63997f08988..2fe067d244a 100644 --- a/src/Compiler/SyntaxTree/LexHelpers.fsi +++ b/src/Compiler/SyntaxTree/LexHelpers.fsi @@ -13,8 +13,6 @@ open FSharp.Compiler.UnicodeLexing open FSharp.Compiler.Parser open FSharp.Compiler.Text -val stdinMockFileName: string - /// Lexer args: status of #light processing. Mutated when a #light /// directive is processed. This alters the behaviour of the lexfilter. [] diff --git a/src/Compiler/SyntaxTree/SyntaxTreeOps.fs b/src/Compiler/SyntaxTree/SyntaxTreeOps.fs index 020dc50bebf..5669b0cd087 100644 --- a/src/Compiler/SyntaxTree/SyntaxTreeOps.fs +++ b/src/Compiler/SyntaxTree/SyntaxTreeOps.fs @@ -2,15 +2,18 @@ module FSharp.Compiler.SyntaxTreeOps +open Internal.Utilities open Internal.Utilities.Library open FSharp.Compiler.DiagnosticsLogger open FSharp.Compiler.Features +open FSharp.Compiler.IO open FSharp.Compiler.Syntax open FSharp.Compiler.SyntaxTrivia open FSharp.Compiler.Syntax.PrettyNaming open FSharp.Compiler.Text open FSharp.Compiler.Text.Range open FSharp.Compiler.Xml +open System /// Generate implicit argument names in parsing type SynArgNameGenerator() = @@ -992,11 +995,47 @@ let rec synExprContainsError inpExpr = let longIdentToString (ident: SynLongIdent) = System.String.Join(".", ident.LongIdent |> List.map (fun ident -> ident.idText.ToString())) +/// The "mock" file name used by fsi.exe when reading from stdin. +/// Has special treatment, i.e. __SOURCE_DIRECTORY__ becomes GetCurrentDirectory() +let stdinMockFileName = "stdin" + +let getSourceIdentifierValue pathMap s (m: range) = + match s with + | "__SOURCE_DIRECTORY__" -> + let fileName = FileIndex.fileOfFileIndex m.FileIndex + + let dirname = + if String.IsNullOrWhiteSpace fileName then + String.Empty + else if fileName = stdinMockFileName then + System.IO.Directory.GetCurrentDirectory() + else + fileName + |> FileSystem.GetFullPathShim (* asserts that path is already absolute *) + |> System.IO.Path.GetDirectoryName + |> (!!) + + if String.IsNullOrEmpty dirname then + dirname + else + PathMap.applyDir pathMap dirname + | "__SOURCE_FILE__" -> !!System.IO.Path.GetFileName(FileIndex.fileOfFileIndex m.FileIndex) + | "__LINE__" -> string m.StartLine + | _ -> failwith "getSourceIdentifierValue: unexpected identifier" + +let applyLineDirectivesToSourceIdentifier s value (m: range) = + let mm = m.ApplyLineDirectives() + + if mm = m then + value // keep the value that was assigned during lexing (when line directives were not yet evaluated) + else + getSourceIdentifierValue PathMap.empty s mm // update because of line directive + let parsedHashDirectiveArguments (input: ParsedHashDirectiveArgument list) (langVersion: LanguageVersion) = List.choose (function | ParsedHashDirectiveArgument.String(s, _, _) -> Some s - | ParsedHashDirectiveArgument.SourceIdentifier(_, v, _) -> Some v + | ParsedHashDirectiveArgument.SourceIdentifier(s, v, m) -> Some(applyLineDirectivesToSourceIdentifier s v m) | ParsedHashDirectiveArgument.Int32(n, m) -> match tryCheckLanguageFeatureAndRecover langVersion LanguageFeature.ParsedHashDirectiveArgumentNonQuotes m with | true -> Some(string n) @@ -1015,7 +1054,7 @@ let parsedHashDirectiveArgumentsNoCheck (input: ParsedHashDirectiveArgument list List.map (function | ParsedHashDirectiveArgument.String(s, _, _) -> s - | ParsedHashDirectiveArgument.SourceIdentifier(_, v, _) -> v + | ParsedHashDirectiveArgument.SourceIdentifier(s, v, m) -> applyLineDirectivesToSourceIdentifier s v m | ParsedHashDirectiveArgument.Int32(n, _) -> string n | ParsedHashDirectiveArgument.Ident(ident, _) -> ident.idText | ParsedHashDirectiveArgument.LongIdent(ident, _) -> longIdentToString ident) @@ -1028,7 +1067,7 @@ let parsedHashDirectiveStringArguments (input: ParsedHashDirectiveArgument list) | ParsedHashDirectiveArgument.Int32(n, m) -> errorR (Error(FSComp.SR.featureParsedHashDirectiveUnexpectedInteger (n), m)) None - | ParsedHashDirectiveArgument.SourceIdentifier(_, v, _) -> Some v + | ParsedHashDirectiveArgument.SourceIdentifier(s, v, m) -> Some(applyLineDirectivesToSourceIdentifier s v m) | ParsedHashDirectiveArgument.Ident(ident, m) -> errorR (Error(FSComp.SR.featureParsedHashDirectiveUnexpectedIdentifier (ident.idText), m)) None diff --git a/src/Compiler/SyntaxTree/SyntaxTreeOps.fsi b/src/Compiler/SyntaxTree/SyntaxTreeOps.fsi index 49190452b4f..3365349bdb0 100644 --- a/src/Compiler/SyntaxTree/SyntaxTreeOps.fsi +++ b/src/Compiler/SyntaxTree/SyntaxTreeOps.fsi @@ -7,6 +7,7 @@ open FSharp.Compiler.Text open FSharp.Compiler.Xml open FSharp.Compiler.Syntax open FSharp.Compiler.SyntaxTrivia +open Internal.Utilities [] type SynArgNameGenerator = @@ -322,6 +323,12 @@ val unionBindingAndMembers: bindings: SynBinding list -> members: SynMemberDefn val synExprContainsError: inpExpr: SynExpr -> bool +val stdinMockFileName: string + +val getSourceIdentifierValue: PathMap -> string -> range -> string + +val applyLineDirectivesToSourceIdentifier: string -> string -> range -> string + val parsedHashDirectiveArguments: ParsedHashDirectiveArgument list -> LanguageVersion -> string list val parsedHashDirectiveArgumentsNoCheck: ParsedHashDirectiveArgument list -> string list diff --git a/tests/FSharp.Compiler.ComponentTests/CompilerDirectives/Line.fs b/tests/FSharp.Compiler.ComponentTests/CompilerDirectives/Line.fs index 334c6985d87..4941a4dfa62 100644 --- a/tests/FSharp.Compiler.ComponentTests/CompilerDirectives/Line.fs +++ b/tests/FSharp.Compiler.ComponentTests/CompilerDirectives/Line.fs @@ -350,3 +350,24 @@ namespace CSharpLib |> compileExeAndRun |> shouldSucceed + let sourceIdSource = """ +#line 100 "/temp/target.fs" +printfn $"{__LINE__} in {__SOURCE_FILE__} in {__SOURCE_DIRECTORY__}" +""" + + [] + let ``LineDirectivesAreAppliedToSourceIdentifiers`` () = + let result = + sourceIdSource + |> FSharp + |> withFileName "original.fs" + |> compileExeAndRun + |> shouldSucceed + let expected = "100 in target.fs in /temp" + match result.RunOutput with + | Some (ExecutionOutput r) -> + Assert.Equal(expected, r.StdOut.Trim()) + | _ -> + Assert.Fail "unexpected: no execution output" + + \ No newline at end of file From 5b73789d7e3e61b691a56e613cc02468382edf85 Mon Sep 17 00:00:00 2001 From: Martin521 <29605222+Martin521@users.noreply.github.com> Date: Thu, 18 Sep 2025 16:55:41 +0000 Subject: [PATCH 2/7] release notes --- docs/release-notes/.FSharp.Compiler.Service/10.0.100.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/release-notes/.FSharp.Compiler.Service/10.0.100.md b/docs/release-notes/.FSharp.Compiler.Service/10.0.100.md index fcd012b8281..5a761654eae 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/10.0.100.md +++ b/docs/release-notes/.FSharp.Compiler.Service/10.0.100.md @@ -30,6 +30,7 @@ * Parser: fix range for computed binding expressions ([PR #18903](https://github.com/dotnet/fsharp/pull/18903)) * Tests: set test source for range debug printing ([PR #18879](https://github.com/dotnet/fsharp/pull/18879)) * Checker: fix declaring type for abbreviated types extensions ([PR #18909](https://github.com/dotnet/fsharp/pull/18909)) +* Ensure that line directives are applied to source identifiers (issue [#18908](https://github.com/dotnet/fsharp/issues/18908), PR [#18918](https://github.com/dotnet/fsharp/pull/18918)) ### Changed * Use `errorR` instead of `error` in `CheckDeclarations.fs` when possible. ([PR #18645](https://github.com/dotnet/fsharp/pull/18645)) From 9daae4e293ec5a95c0c4ac44ea0d5f57be3fa3e3 Mon Sep 17 00:00:00 2001 From: Martin521 <29605222+Martin521@users.noreply.github.com> Date: Thu, 18 Sep 2025 20:22:59 +0000 Subject: [PATCH 3/7] fix test --- .../CompilerDirectives/Line.fs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/FSharp.Compiler.ComponentTests/CompilerDirectives/Line.fs b/tests/FSharp.Compiler.ComponentTests/CompilerDirectives/Line.fs index 4941a4dfa62..882e116ceb1 100644 --- a/tests/FSharp.Compiler.ComponentTests/CompilerDirectives/Line.fs +++ b/tests/FSharp.Compiler.ComponentTests/CompilerDirectives/Line.fs @@ -352,7 +352,9 @@ namespace CSharpLib let sourceIdSource = """ #line 100 "/temp/target.fs" -printfn $"{__LINE__} in {__SOURCE_FILE__} in {__SOURCE_DIRECTORY__}" +let dir = __SOURCE_DIRECTORY__ +let d = dir[dir.Length - 4 ..] +printf $"{__LINE__} in {__SOURCE_FILE__} in {d}" """ [] @@ -363,10 +365,10 @@ printfn $"{__LINE__} in {__SOURCE_FILE__} in {__SOURCE_DIRECTORY__}" |> withFileName "original.fs" |> compileExeAndRun |> shouldSucceed - let expected = "100 in target.fs in /temp" + let expected = "102 in target.fs in temp" match result.RunOutput with | Some (ExecutionOutput r) -> - Assert.Equal(expected, r.StdOut.Trim()) + Assert.Equal(expected, r.StdOut) | _ -> Assert.Fail "unexpected: no execution output" From 5698b1a99238af5f85c31e712e4a0b28db3a7979 Mon Sep 17 00:00:00 2001 From: Martin521 <29605222+Martin521@users.noreply.github.com> Date: Fri, 19 Sep 2025 08:27:39 +0000 Subject: [PATCH 4/7] finish line directive preprocessing also for fsi --- src/Compiler/Driver/ParseAndCheckInputs.fs | 6 +++--- src/Compiler/Driver/ParseAndCheckInputs.fsi | 3 +++ src/Compiler/Interactive/fsi.fs | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Compiler/Driver/ParseAndCheckInputs.fs b/src/Compiler/Driver/ParseAndCheckInputs.fs index fd6eabbf0f9..c6de5e776a1 100644 --- a/src/Compiler/Driver/ParseAndCheckInputs.fs +++ b/src/Compiler/Driver/ParseAndCheckInputs.fs @@ -215,7 +215,7 @@ let PostParseModuleSpec (_i, defaultNamespace, isLastCompiland, fileName, intf) SynModuleOrNamespaceSig(lid, isRecursive, kind, decls, xmlDoc, attributes, None, range, trivia) -let private finishPreprocessing lexbuf diagnosticOptions isScript submoduleRanges = +let FinishPreprocessing lexbuf diagnosticOptions isScript submoduleRanges = WarnScopes.MergeInto diagnosticOptions isScript submoduleRanges lexbuf LineDirectives.add lexbuf.StartPos.FileIndex (LineDirectiveStore.GetLineDirectives lexbuf) @@ -276,7 +276,7 @@ let PostParseModuleImpls let isScript = IsScript fileName - finishPreprocessing lexbuf diagnosticOptions isScript (getImplSubmoduleRanges impls) + FinishPreprocessing lexbuf diagnosticOptions isScript (getImplSubmoduleRanges impls) let trivia = collectParsedInputTrivia lexbuf @@ -310,7 +310,7 @@ let PostParseModuleSpecs identifiers: Set ) = - finishPreprocessing lexbuf diagnosticOptions false (getSpecSubmoduleRanges specs) + FinishPreprocessing lexbuf diagnosticOptions false (getSpecSubmoduleRanges specs) let trivia = collectParsedInputTrivia lexbuf diff --git a/src/Compiler/Driver/ParseAndCheckInputs.fsi b/src/Compiler/Driver/ParseAndCheckInputs.fsi index 281638b5685..36f3493724c 100644 --- a/src/Compiler/Driver/ParseAndCheckInputs.fsi +++ b/src/Compiler/Driver/ParseAndCheckInputs.fsi @@ -136,6 +136,9 @@ val ParseInputFiles: retryLocked: bool -> (ParsedInput * string) list +/// Process collected directives +val FinishPreprocessing: Lexbuf -> FSharpDiagnosticOptions -> bool -> range list -> unit + /// Get the initial type checking environment including the loading of mscorlib/System.Core, FSharp.Core /// applying the InternalsVisibleTo in referenced assemblies and opening 'Checked' if requested. val GetInitialTcEnv: assemblyName: string * range * TcConfig * TcImports * TcGlobals -> TcEnv * OpenDeclaration list diff --git a/src/Compiler/Interactive/fsi.fs b/src/Compiler/Interactive/fsi.fs index 8b09f86fb4d..a4202ddbb62 100644 --- a/src/Compiler/Interactive/fsi.fs +++ b/src/Compiler/Interactive/fsi.fs @@ -3692,7 +3692,7 @@ type FsiInteractionProcessor Parser.interaction lexerWhichSavesLastToken tokenizer.LexBuffer) - WarnScopes.MergeInto diagnosticOptions false [] tokenizer.LexBuffer + FinishPreprocessing tokenizer.LexBuffer diagnosticOptions true [] Some input with e -> From cb53b63b0cb7350f8383efee078713daf2261d16 Mon Sep 17 00:00:00 2001 From: Martin521 <29605222+Martin521@users.noreply.github.com> Date: Fri, 19 Sep 2025 08:39:16 +0000 Subject: [PATCH 5/7] fsi test added --- tests/FSharp.Compiler.Service.Tests/FsiTests.fs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/FSharp.Compiler.Service.Tests/FsiTests.fs b/tests/FSharp.Compiler.Service.Tests/FsiTests.fs index 208f7a465a6..2f1b3f1a25e 100644 --- a/tests/FSharp.Compiler.Service.Tests/FsiTests.fs +++ b/tests/FSharp.Compiler.Service.Tests/FsiTests.fs @@ -662,4 +662,12 @@ module FsiTests = printfn "value: %A" v | Choice2Of2 e -> printfn "exception: %A" e - raise e \ No newline at end of file + raise e + + [] + let ``LineDirectivesWork`` () = + use fsiSession = createFsiSession false + fsiSession.EvalInteraction("""#line 100 + let y = __LINE__""") + let boundValue = fsiSession.GetBoundValues() |> List.exactlyOne + Assert.shouldBe 100 boundValue.Value.ReflectionValue From 84a7661fb104183f5c2668f0d41cea7a7fc6877f Mon Sep 17 00:00:00 2001 From: Martin521 <29605222+Martin521@users.noreply.github.com> Date: Fri, 19 Sep 2025 09:34:37 +0000 Subject: [PATCH 6/7] fixed test --- tests/FSharp.Compiler.Service.Tests/FsiTests.fs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/FSharp.Compiler.Service.Tests/FsiTests.fs b/tests/FSharp.Compiler.Service.Tests/FsiTests.fs index 2f1b3f1a25e..30f67d97de7 100644 --- a/tests/FSharp.Compiler.Service.Tests/FsiTests.fs +++ b/tests/FSharp.Compiler.Service.Tests/FsiTests.fs @@ -670,4 +670,4 @@ module FsiTests = fsiSession.EvalInteraction("""#line 100 let y = __LINE__""") let boundValue = fsiSession.GetBoundValues() |> List.exactlyOne - Assert.shouldBe 100 boundValue.Value.ReflectionValue + Assert.shouldBe "100" boundValue.Value.ReflectionValue From 191ca404b1e5877ff647aff711e51a994614917a Mon Sep 17 00:00:00 2001 From: Martin521 <29605222+Martin521@users.noreply.github.com> Date: Mon, 22 Sep 2025 11:50:43 +0000 Subject: [PATCH 7/7] fix missing match condition --- src/Compiler/Checking/Expressions/CheckExpressions.fs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Compiler/Checking/Expressions/CheckExpressions.fs b/src/Compiler/Checking/Expressions/CheckExpressions.fs index 5714a138545..ef5eae5c996 100644 --- a/src/Compiler/Checking/Expressions/CheckExpressions.fs +++ b/src/Compiler/Checking/Expressions/CheckExpressions.fs @@ -4893,7 +4893,7 @@ and TcStaticConstantParameter (cenv: cenv) (env: TcEnv) tpenv kind (StripParenTy | SynConst.Single n when typeEquiv g g.float32_ty kind -> record(g.float32_ty); box (n: single) | SynConst.Double n when typeEquiv g g.float_ty kind -> record(g.float_ty); box (n: double) | SynConst.Char n when typeEquiv g g.char_ty kind -> record(g.char_ty); box (n: char) - | SynConst.String (s, _, _) -> record(g.string_ty); box (s: string) + | SynConst.String (s, _, _) when typeEquiv g g.string_ty kind -> record(g.string_ty); box (s: string) | SynConst.SourceIdentifier (i, s, m) when typeEquiv g g.string_ty kind -> let s = applyLineDirectivesToSourceIdentifier i s m record(g.string_ty); box (s: string)