diff --git a/src/Compiler/Driver/CompilerConfig.fs b/src/Compiler/Driver/CompilerConfig.fs index 2786e30e9a3..e406af4bd5c 100644 --- a/src/Compiler/Driver/CompilerConfig.fs +++ b/src/Compiler/Driver/CompilerConfig.fs @@ -565,6 +565,8 @@ type TcConfigBuilder = mutable noConditionalErasure: bool + mutable applyLineDirectives: bool + mutable pathMap: PathMap mutable langVersion: LanguageVersion @@ -739,6 +741,7 @@ type TcConfigBuilder = internalTestSpanStackReferring = false noConditionalErasure = false pathMap = PathMap.empty + applyLineDirectives = true langVersion = LanguageVersion.Default implicitIncludeDir = implicitIncludeDir defaultFSharpBinariesDir = defaultFSharpBinariesDir @@ -1326,6 +1329,7 @@ type TcConfig private (data: TcConfigBuilder, validate: bool) = member _.tryGetMetadataSnapshot = data.tryGetMetadataSnapshot member _.internalTestSpanStackReferring = data.internalTestSpanStackReferring member _.noConditionalErasure = data.noConditionalErasure + member _.applyLineDirectives = data.applyLineDirectives member _.xmlDocInfoLoader = data.xmlDocInfoLoader static member Create(builder, validate) = diff --git a/src/Compiler/Driver/CompilerConfig.fsi b/src/Compiler/Driver/CompilerConfig.fsi index a11f1c6c77d..51a8b7d09cf 100644 --- a/src/Compiler/Driver/CompilerConfig.fsi +++ b/src/Compiler/Driver/CompilerConfig.fsi @@ -466,6 +466,9 @@ type TcConfigBuilder = /// Prevent erasure of conditional attributes and methods so tooling is able analyse them. mutable noConditionalErasure: bool + /// Take '#line' into account? Defaults to true + mutable applyLineDirectives: bool + mutable pathMap: PathMap mutable langVersion: LanguageVersion @@ -800,6 +803,9 @@ type TcConfig = /// Prevent erasure of conditional attributes and methods so tooling is able analyse them. member noConditionalErasure: bool + /// Take '#line' into account? Defaults to true + member applyLineDirectives: bool + /// if true - 'let mutable x = Span.Empty', the value 'x' is a stack referring span. Used for internal testing purposes only until we get true stack spans. member internalTestSpanStackReferring: bool diff --git a/src/Compiler/Driver/CompilerOptions.fs b/src/Compiler/Driver/CompilerOptions.fs index b8fbfbe4f91..1059c0ff6bf 100644 --- a/src/Compiler/Driver/CompilerOptions.fs +++ b/src/Compiler/Driver/CompilerOptions.fs @@ -1397,6 +1397,7 @@ let editorSpecificFlags (tcConfigB: TcConfigBuilder) = CompilerOption("exename", tagNone, OptionString(fun s -> tcConfigB.exename <- Some s), None, None) CompilerOption("maxerrors", tagInt, OptionInt(fun n -> tcConfigB.maxErrors <- n), None, None) CompilerOption("noconditionalerasure", tagNone, OptionUnit(fun () -> tcConfigB.noConditionalErasure <- true), None, None) + CompilerOption("ignorelinedirectives", tagNone, OptionUnit(fun () -> tcConfigB.applyLineDirectives <- false), None, None) ] let internalFlags (tcConfigB: TcConfigBuilder) = diff --git a/src/Compiler/Driver/ParseAndCheckInputs.fs b/src/Compiler/Driver/ParseAndCheckInputs.fs index 6d764f264dd..b0c820c6c67 100644 --- a/src/Compiler/Driver/ParseAndCheckInputs.fs +++ b/src/Compiler/Driver/ParseAndCheckInputs.fs @@ -563,7 +563,15 @@ let ParseOneInputLexbuf (tcConfig: TcConfig, lexResourceManager, lexbuf, fileNam // Set up the initial lexer arguments let lexargs = - mkLexargs (tcConfig.conditionalDefines, indentationSyntaxStatus, lexResourceManager, [], diagnosticsLogger, tcConfig.pathMap) + mkLexargs ( + tcConfig.conditionalDefines, + indentationSyntaxStatus, + lexResourceManager, + [], + diagnosticsLogger, + tcConfig.pathMap, + tcConfig.applyLineDirectives + ) // Set up the initial lexer arguments let shortFilename = SanitizeFileName fileName tcConfig.implicitIncludeDir diff --git a/src/Compiler/Interactive/fsi.fs b/src/Compiler/Interactive/fsi.fs index 5d8966fd9a1..92bcc810a34 100644 --- a/src/Compiler/Interactive/fsi.fs +++ b/src/Compiler/Interactive/fsi.fs @@ -2524,8 +2524,8 @@ type FsiStdinLexerProvider resetLexbufPos sourceFileName lexbuf let skip = true // don't report whitespace from lexer - let defines = tcConfigB.conditionalDefines - let lexargs = mkLexargs (defines, indentationSyntaxStatus, lexResourceManager, [], diagnosticsLogger, PathMap.empty) + let applyLineDirectives = true + let lexargs = mkLexargs (tcConfigB.conditionalDefines, indentationSyntaxStatus, lexResourceManager, [], diagnosticsLogger, PathMap.empty, applyLineDirectives) let tokenizer = LexFilter.LexFilter(indentationSyntaxStatus, tcConfigB.compilingFSharpCore, Lexer.token lexargs skip, lexbuf) tokenizer diff --git a/src/Compiler/Service/FSharpCheckerResults.fs b/src/Compiler/Service/FSharpCheckerResults.fs index 48649d1835a..6f84923f6c7 100644 --- a/src/Compiler/Service/FSharpCheckerResults.fs +++ b/src/Compiler/Service/FSharpCheckerResults.fs @@ -2035,6 +2035,7 @@ type internal TypeCheckInfo type FSharpParsingOptions = { SourceFiles: string[] + ApplyLineDirectives: bool ConditionalDefines: string list DiagnosticOptions: FSharpDiagnosticOptions LangVersionText: string @@ -2051,6 +2052,7 @@ type FSharpParsingOptions = static member Default = { SourceFiles = Array.empty + ApplyLineDirectives = false ConditionalDefines = [] DiagnosticOptions = FSharpDiagnosticOptions.Default LangVersionText = LanguageVersion.Default.VersionText @@ -2063,6 +2065,7 @@ type FSharpParsingOptions = static member FromTcConfig(tcConfig: TcConfig, sourceFiles, isInteractive: bool) = { SourceFiles = sourceFiles + ApplyLineDirectives = tcConfig.applyLineDirectives ConditionalDefines = tcConfig.conditionalDefines DiagnosticOptions = tcConfig.diagnosticsOptions LangVersionText = tcConfig.langVersion.VersionText @@ -2075,6 +2078,7 @@ type FSharpParsingOptions = static member FromTcConfigBuilder(tcConfigB: TcConfigBuilder, sourceFiles, isInteractive: bool) = { SourceFiles = sourceFiles + ApplyLineDirectives = tcConfigB.applyLineDirectives ConditionalDefines = tcConfigB.conditionalDefines DiagnosticOptions = tcConfigB.diagnosticsOptions LangVersionText = tcConfigB.langVersion.VersionText @@ -2183,12 +2187,15 @@ module internal ParseAndCheckFile = // When analyzing files using ParseOneFile, i.e. for the use of editing clients, we do not apply line directives. // TODO(pathmap): expose PathMap on the service API, and thread it through here let lexargs = - mkLexargs (conditionalDefines, indentationSyntaxStatus, lexResourceManager, [], errHandler.DiagnosticsLogger, PathMap.empty) - - let lexargs = - { lexargs with - applyLineDirectives = false - } + mkLexargs ( + conditionalDefines, + indentationSyntaxStatus, + lexResourceManager, + [], + errHandler.DiagnosticsLogger, + PathMap.empty, + options.ApplyLineDirectives + ) let tokenizer = LexFilter.LexFilter(indentationSyntaxStatus, options.CompilingFSharpCore, Lexer.token lexargs true, lexbuf) diff --git a/src/Compiler/Service/FSharpCheckerResults.fsi b/src/Compiler/Service/FSharpCheckerResults.fsi index dd8a4aee65a..dff418f5438 100644 --- a/src/Compiler/Service/FSharpCheckerResults.fsi +++ b/src/Compiler/Service/FSharpCheckerResults.fsi @@ -200,14 +200,30 @@ type public FSharpProjectContext = /// Options used to determine active --define conditionals and other options relevant to parsing files in a project type public FSharpParsingOptions = - { SourceFiles: string[] - ConditionalDefines: string list - DiagnosticOptions: FSharpDiagnosticOptions - LangVersionText: string - IsInteractive: bool - IndentationAwareSyntax: bool option - CompilingFSharpCore: bool - IsExe: bool } + { + SourceFiles: string[] + + /// Indicates if the ranges returned by parsing should have '#line' directives applied to them. + /// When compiling code, this should usually be 'true'. For editing tools, this is usually 'false. + /// The default for FSharpParsingOptions.ApplyLineDirectives is 'false'. The default for + /// FSharpParsingOptions arising from FSharpProjectOptions will be 'true' unless '--ignorelinedirectives' is used in the + /// parameters from which these are derived. + ApplyLineDirectives: bool + + ConditionalDefines: string list + + DiagnosticOptions: FSharpDiagnosticOptions + + LangVersionText: string + + IsInteractive: bool + + IndentationAwareSyntax: bool option + + CompilingFSharpCore: bool + + IsExe: bool + } static member Default: FSharpParsingOptions diff --git a/src/Compiler/Service/ServiceLexing.fs b/src/Compiler/Service/ServiceLexing.fs index 9f14d717af2..ba2490c3f9d 100644 --- a/src/Compiler/Service/ServiceLexing.fs +++ b/src/Compiler/Service/ServiceLexing.fs @@ -1183,14 +1183,18 @@ type FSharpSourceTokenizer(conditionalDefines: string list, fileName: string opt let lexResourceManager = LexResourceManager() + let applyLineDirectives = false + let indentationSyntaxStatus = IndentationAwareSyntaxStatus(true, false) + let lexargs = mkLexargs ( conditionalDefines, - IndentationAwareSyntaxStatus(true, false), + indentationSyntaxStatus, lexResourceManager, [], DiscardErrorsLogger, - PathMap.empty + PathMap.empty, + applyLineDirectives ) member _.CreateLineTokenizer(lineText: string) = @@ -1813,14 +1817,18 @@ module FSharpLexerImpl = UnicodeLexing.SourceTextAsLexbuf(reportLibraryOnlyFeatures, langVersion, text) let indentationSyntaxStatus = IndentationAwareSyntaxStatus(isLightSyntaxOn, true) + let applyLineDirectives = isCompiling let lexargs = - mkLexargs (conditionalDefines, indentationSyntaxStatus, LexResourceManager(0), [], diagnosticsLogger, pathMap) - - let lexargs = - { lexargs with - applyLineDirectives = isCompiling - } + mkLexargs ( + conditionalDefines, + indentationSyntaxStatus, + LexResourceManager(0), + [], + diagnosticsLogger, + pathMap, + applyLineDirectives + ) let getNextToken = let lexer = Lexer.token lexargs canSkipTrivia diff --git a/src/Compiler/SyntaxTree/LexHelpers.fs b/src/Compiler/SyntaxTree/LexHelpers.fs index 67f6c78f558..80fc0c21246 100644 --- a/src/Compiler/SyntaxTree/LexHelpers.fs +++ b/src/Compiler/SyntaxTree/LexHelpers.fs @@ -74,14 +74,23 @@ type LongUnicodeLexResult = | SingleChar of uint16 | Invalid -let mkLexargs (conditionalDefines, indentationSyntaxStatus, resourceManager, ifdefStack, diagnosticsLogger, pathMap: PathMap) = +let mkLexargs + ( + conditionalDefines, + indentationSyntaxStatus, + resourceManager, + ifdefStack, + diagnosticsLogger, + pathMap: PathMap, + applyLineDirectives + ) = { conditionalDefines = conditionalDefines ifdefStack = ifdefStack indentationSyntaxStatus = indentationSyntaxStatus resourceManager = resourceManager diagnosticsLogger = diagnosticsLogger - applyLineDirectives = true + applyLineDirectives = applyLineDirectives stringNest = [] pathMap = pathMap } diff --git a/src/Compiler/SyntaxTree/LexHelpers.fsi b/src/Compiler/SyntaxTree/LexHelpers.fsi index 0f88933dac1..49f73abbabf 100644 --- a/src/Compiler/SyntaxTree/LexHelpers.fsi +++ b/src/Compiler/SyntaxTree/LexHelpers.fsi @@ -52,7 +52,8 @@ val mkLexargs: resourceManager: LexResourceManager * ifdefStack: LexerIfdefStack * diagnosticsLogger: DiagnosticsLogger * - pathMap: PathMap -> + pathMap: PathMap * + applyLineDirectives: bool -> LexArgs val reusingLexbufForParsing: Lexbuf -> (unit -> 'a) -> 'a diff --git a/tests/FSharp.Compiler.Service.Tests/FSharp.CompilerService.SurfaceArea.netstandard.expected b/tests/FSharp.Compiler.Service.Tests/FSharp.CompilerService.SurfaceArea.netstandard.expected index 090d64e02c7..d35d8849d33 100644 --- a/tests/FSharp.Compiler.Service.Tests/FSharp.CompilerService.SurfaceArea.netstandard.expected +++ b/tests/FSharp.Compiler.Service.Tests/FSharp.CompilerService.SurfaceArea.netstandard.expected @@ -2108,7 +2108,9 @@ FSharp.Compiler.CodeAnalysis.FSharpParsingOptions: System.String ToString() FSharp.Compiler.CodeAnalysis.FSharpParsingOptions: System.String get_LangVersionText() FSharp.Compiler.CodeAnalysis.FSharpParsingOptions: System.String[] SourceFiles FSharp.Compiler.CodeAnalysis.FSharpParsingOptions: System.String[] get_SourceFiles() -FSharp.Compiler.CodeAnalysis.FSharpParsingOptions: Void .ctor(System.String[], Microsoft.FSharp.Collections.FSharpList`1[System.String], FSharp.Compiler.Diagnostics.FSharpDiagnosticOptions, System.String, Boolean, Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Boolean, Boolean) +FSharp.Compiler.CodeAnalysis.FSharpParsingOptions: Boolean ApplyLineDirectives +FSharp.Compiler.CodeAnalysis.FSharpParsingOptions: Boolean get_ApplyLineDirectives() +FSharp.Compiler.CodeAnalysis.FSharpParsingOptions: Void .ctor(System.String[], Boolean, Microsoft.FSharp.Collections.FSharpList`1[System.String], FSharp.Compiler.Diagnostics.FSharpDiagnosticOptions, System.String, Boolean, Microsoft.FSharp.Core.FSharpOption`1[System.Boolean], Boolean, Boolean) FSharp.Compiler.CodeAnalysis.FSharpProjectContext FSharp.Compiler.CodeAnalysis.FSharpProjectContext: FSharp.Compiler.CodeAnalysis.FSharpProjectOptions ProjectOptions FSharp.Compiler.CodeAnalysis.FSharpProjectContext: FSharp.Compiler.CodeAnalysis.FSharpProjectOptions get_ProjectOptions() diff --git a/tests/FSharp.Compiler.UnitTests/HashIfExpression.fs b/tests/FSharp.Compiler.UnitTests/HashIfExpression.fs index 9af4a412632..0d81eff6cd5 100644 --- a/tests/FSharp.Compiler.UnitTests/HashIfExpression.fs +++ b/tests/FSharp.Compiler.UnitTests/HashIfExpression.fs @@ -57,11 +57,12 @@ type public HashIfExpression() = member _.ErrorCount = errors.Count } - let lightSyntax = IndentationAwareSyntaxStatus(true, false) + let indentationSyntaxStatus = IndentationAwareSyntaxStatus(true, false) let resourceManager = LexResourceManager () - let defines= [] + let defines = [] + let applyLineDirectives = true let startPos = Position.Empty - let args = mkLexargs (defines, lightSyntax, resourceManager, [], diagnosticsLogger, PathMap.empty) + let args = mkLexargs (defines, indentationSyntaxStatus, resourceManager, [], diagnosticsLogger, PathMap.empty, applyLineDirectives) DiagnosticsThreadStatics.DiagnosticsLogger <- diagnosticsLogger diff --git a/tests/benchmarks/FCSBenchmarks/CompilerServiceBenchmarks/Benchmarks.fs b/tests/benchmarks/FCSBenchmarks/CompilerServiceBenchmarks/Benchmarks.fs index 5b244d4481a..6a48e688b4e 100644 --- a/tests/benchmarks/FCSBenchmarks/CompilerServiceBenchmarks/Benchmarks.fs +++ b/tests/benchmarks/FCSBenchmarks/CompilerServiceBenchmarks/Benchmarks.fs @@ -136,6 +136,7 @@ type CompilerService() = DiagnosticOptions = FSharpDiagnosticOptions.Default LangVersionText = "default" IsInteractive = false + ApplyLineDirectives = false IndentationAwareSyntax = None CompilingFSharpCore = false IsExe = false diff --git a/tests/service/ProjectAnalysisTests.fs b/tests/service/ProjectAnalysisTests.fs index 3318cf40cf4..d552caf10aa 100644 --- a/tests/service/ProjectAnalysisTests.fs +++ b/tests/service/ProjectAnalysisTests.fs @@ -5333,7 +5333,7 @@ let x = (1 = 3.0) let options = checker.GetProjectOptionsFromCommandLineArgs (projFileName, args) [] -let ``Test line directives in foreground analysis`` () = // see https://github.com/dotnet/fsharp/issues/3317 +let ``Test diagnostics with line directives active`` () = // In background analysis and normal compiler checking, the errors are reported w.r.t. the line directives let wholeProjectResults = checker.ParseAndCheckProject(ProjectLineDirectives.options) |> Async.RunImmediate @@ -5342,17 +5342,39 @@ let ``Test line directives in foreground analysis`` () = // see https://github.c [ for e in wholeProjectResults.Diagnostics -> e.Range.StartLine, e.Range.EndLine, e.Range.FileName ] |> shouldEqual [(10, 10, "Test.fsy")] - // In foreground analysis routines, used by visual editing tools, the errors are reported w.r.t. the source - // file, which is assumed to be in the editor, not the other files referred to by line directives. - let checkResults1 = + let checkResults = checker.ParseAndCheckFileInProject(ProjectLineDirectives.fileName1, 0, ProjectLineDirectives.fileSource1, ProjectLineDirectives.options) |> Async.RunImmediate |> function _,FSharpCheckFileAnswer.Succeeded x -> x | _ -> failwith "unexpected aborted" - for e in checkResults1.Diagnostics do + for e in checkResults.Diagnostics do printfn "ProjectLineDirectives checkResults1 error file: <<<%s>>>" e.Range.FileName - [ for e in checkResults1.Diagnostics -> e.Range.StartLine, e.Range.EndLine, e.Range.FileName ] |> shouldEqual [(5, 5, ProjectLineDirectives.fileName1)] + // No diagnostics for logical location "Test.fsy" are reported when checking the source since the filenames don't match. You need to pass --ignorelinedirectives to get them + [ for e in checkResults.Diagnostics -> e.Range.StartLine, e.Range.EndLine, e.Range.FileName ] |> shouldEqual [ ] + +[] +let ``Test diagnostics with line directives ignored`` () = + + // If you pass hidden IDE flag --ignorelinedirectives, the diagnostics are reported w.r.t. the source + // file, not the files referred to by line directives. + let options = { ProjectLineDirectives.options with OtherOptions = (Array.append ProjectLineDirectives.options.OtherOptions [| "--ignorelinedirectives" |]) } + + let wholeProjectResults = checker.ParseAndCheckProject(options) |> Async.RunImmediate + for e in wholeProjectResults.Diagnostics do + printfn "ProjectLineDirectives wholeProjectResults error file: <<<%s>>>" e.Range.FileName + + [ for e in wholeProjectResults.Diagnostics -> e.Range.StartLine, e.Range.EndLine, e.Range.FileName ] |> shouldEqual [(5, 5, ProjectLineDirectives.fileName1)] + + let checkResults = + checker.ParseAndCheckFileInProject(ProjectLineDirectives.fileName1, 0, ProjectLineDirectives.fileSource1, options) + |> Async.RunImmediate + |> function _,FSharpCheckFileAnswer.Succeeded x -> x | _ -> failwith "unexpected aborted" + + for e in checkResults.Diagnostics do + printfn "ProjectLineDirectives checkResults error file: <<<%s>>>" e.Range.FileName + + [ for e in checkResults.Diagnostics -> e.Range.StartLine, e.Range.EndLine, e.Range.FileName ] |> shouldEqual [(5, 5, ProjectLineDirectives.fileName1)] //------------------------------------------------------ diff --git a/vsintegration/src/FSharp.Editor/LanguageService/FSharpProjectOptionsManager.fs b/vsintegration/src/FSharp.Editor/LanguageService/FSharpProjectOptionsManager.fs index cb8c6e71b82..53dbbad2338 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/FSharpProjectOptionsManager.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/FSharpProjectOptionsManager.fs @@ -289,6 +289,11 @@ type private FSharpProjectOptionsReactor (checker: FSharpChecker) = for x in project.ProjectReferences do "-r:" + project.Solution.GetProject(x.ProjectId).OutputFilePath + // In the IDE we always ignore all #line directives for all purposes. This means + // IDE features work correctly within generated source files, but diagnostics are + // reported in the IDE with respect to the generated source, and will not unify with + // diagnostics from the build. + "--ignorelinedirectives" |] let! ver = project.GetDependentVersionAsync(ct) |> Async.AwaitTask @@ -509,6 +514,7 @@ type internal FSharpProjectOptionsManager | Some (_, parsingOptions, _) -> parsingOptions | _ -> { FSharpParsingOptions.Default with + ApplyLineDirectives = false IsInteractive = CompilerEnvironment.IsScriptFile document.Name } CompilerEnvironment.GetConditionalDefinesForEditing parsingOptions