Skip to content

Fix cross-file navigation features when #line is used #12963

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 20 commits into from
Jul 13, 2022
Merged
4 changes: 4 additions & 0 deletions src/Compiler/Driver/CompilerConfig.fs
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,8 @@ type TcConfigBuilder =

mutable noConditionalErasure: bool

mutable applyLineDirectives: bool

mutable pathMap: PathMap

mutable langVersion: LanguageVersion
Expand Down Expand Up @@ -739,6 +741,7 @@ type TcConfigBuilder =
internalTestSpanStackReferring = false
noConditionalErasure = false
pathMap = PathMap.empty
applyLineDirectives = true
langVersion = LanguageVersion.Default
implicitIncludeDir = implicitIncludeDir
defaultFSharpBinariesDir = defaultFSharpBinariesDir
Expand Down Expand Up @@ -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) =
Expand Down
6 changes: 6 additions & 0 deletions src/Compiler/Driver/CompilerConfig.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions src/Compiler/Driver/CompilerOptions.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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) =
Expand Down
10 changes: 9 additions & 1 deletion src/Compiler/Driver/ParseAndCheckInputs.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions src/Compiler/Interactive/fsi.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
19 changes: 13 additions & 6 deletions src/Compiler/Service/FSharpCheckerResults.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2035,6 +2035,7 @@ type internal TypeCheckInfo
type FSharpParsingOptions =
{
SourceFiles: string[]
ApplyLineDirectives: bool
ConditionalDefines: string list
DiagnosticOptions: FSharpDiagnosticOptions
LangVersionText: string
Expand All @@ -2051,6 +2052,7 @@ type FSharpParsingOptions =
static member Default =
{
SourceFiles = Array.empty
ApplyLineDirectives = false
ConditionalDefines = []
DiagnosticOptions = FSharpDiagnosticOptions.Default
LangVersionText = LanguageVersion.Default.VersionText
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand Down
32 changes: 24 additions & 8 deletions src/Compiler/Service/FSharpCheckerResults.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
24 changes: 16 additions & 8 deletions src/Compiler/Service/ServiceLexing.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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) =
Expand Down Expand Up @@ -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
Expand Down
13 changes: 11 additions & 2 deletions src/Compiler/SyntaxTree/LexHelpers.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
3 changes: 2 additions & 1 deletion src/Compiler/SyntaxTree/LexHelpers.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
7 changes: 4 additions & 3 deletions tests/FSharp.Compiler.UnitTests/HashIfExpression.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ type CompilerService() =
DiagnosticOptions = FSharpDiagnosticOptions.Default
LangVersionText = "default"
IsInteractive = false
ApplyLineDirectives = false
IndentationAwareSyntax = None
CompilingFSharpCore = false
IsExe = false
Expand Down
34 changes: 28 additions & 6 deletions tests/service/ProjectAnalysisTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -5333,7 +5333,7 @@ let x = (1 = 3.0)
let options = checker.GetProjectOptionsFromCommandLineArgs (projFileName, args)

[<Test>]
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
Expand All @@ -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 [ ]

[<Test>]
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)]

//------------------------------------------------------

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -509,6 +514,7 @@ type internal FSharpProjectOptionsManager
| Some (_, parsingOptions, _) -> parsingOptions
| _ ->
{ FSharpParsingOptions.Default with
ApplyLineDirectives = false
IsInteractive = CompilerEnvironment.IsScriptFile document.Name
}
CompilerEnvironment.GetConditionalDefinesForEditing parsingOptions
Expand Down