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 73b0abc52e2..193d845ca0a 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/10.0.100.md +++ b/docs/release-notes/.FSharp.Compiler.Service/10.0.100.md @@ -33,6 +33,7 @@ * 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)) * Caches: type subsumption cache key perf regression ([Issue #18925](https://github.com/dotnet/fsharp/issues/18925) [PR #18926](https://github.com/dotnet/fsharp/pull/18926)) +* 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)) diff --git a/src/Compiler/Checking/Expressions/CheckExpressions.fs b/src/Compiler/Checking/Expressions/CheckExpressions.fs index 9aaf8d472a8..ef5eae5c996 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, _, _) 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) | 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/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 -> 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..882e116ceb1 100644 --- a/tests/FSharp.Compiler.ComponentTests/CompilerDirectives/Line.fs +++ b/tests/FSharp.Compiler.ComponentTests/CompilerDirectives/Line.fs @@ -350,3 +350,26 @@ namespace CSharpLib |> compileExeAndRun |> shouldSucceed + let sourceIdSource = """ +#line 100 "/temp/target.fs" +let dir = __SOURCE_DIRECTORY__ +let d = dir[dir.Length - 4 ..] +printf $"{__LINE__} in {__SOURCE_FILE__} in {d}" +""" + + [] + let ``LineDirectivesAreAppliedToSourceIdentifiers`` () = + let result = + sourceIdSource + |> FSharp + |> withFileName "original.fs" + |> compileExeAndRun + |> shouldSucceed + let expected = "102 in target.fs in temp" + match result.RunOutput with + | Some (ExecutionOutput r) -> + Assert.Equal(expected, r.StdOut) + | _ -> + Assert.Fail "unexpected: no execution output" + + \ No newline at end of file diff --git a/tests/FSharp.Compiler.Service.Tests/FsiTests.fs b/tests/FSharp.Compiler.Service.Tests/FsiTests.fs index 208f7a465a6..30f67d97de7 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