Skip to content
1 change: 1 addition & 0 deletions docs/release-notes/.FSharp.Compiler.Service/10.0.100.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,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))
Expand Down
13 changes: 9 additions & 4 deletions src/Compiler/Checking/Expressions/CheckExpressions.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions src/Compiler/Driver/ParseAndCheckInputs.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -310,7 +310,7 @@ let PostParseModuleSpecs
identifiers: Set<string>
) =

finishPreprocessing lexbuf diagnosticOptions false (getSpecSubmoduleRanges specs)
FinishPreprocessing lexbuf diagnosticOptions false (getSpecSubmoduleRanges specs)

let trivia = collectParsedInputTrivia lexbuf

Expand Down
3 changes: 3 additions & 0 deletions src/Compiler/Driver/ParseAndCheckInputs.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/Compiler/Interactive/fsi.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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 ->
Expand Down
29 changes: 4 additions & 25 deletions src/Compiler/SyntaxTree/LexHelpers.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
[<Sealed>]
Expand Down Expand Up @@ -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
Expand Down
2 changes: 0 additions & 2 deletions src/Compiler/SyntaxTree/LexHelpers.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -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.
[<Sealed>]
Expand Down
45 changes: 42 additions & 3 deletions src/Compiler/SyntaxTree/SyntaxTreeOps.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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() =
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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
Expand Down
7 changes: 7 additions & 0 deletions src/Compiler/SyntaxTree/SyntaxTreeOps.fsi
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ open FSharp.Compiler.Text
open FSharp.Compiler.Xml
open FSharp.Compiler.Syntax
open FSharp.Compiler.SyntaxTrivia
open Internal.Utilities

[<Class>]
type SynArgNameGenerator =
Expand Down Expand Up @@ -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
Expand Down
23 changes: 23 additions & 0 deletions tests/FSharp.Compiler.ComponentTests/CompilerDirectives/Line.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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}"
"""

[<Fact>]
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"


10 changes: 9 additions & 1 deletion tests/FSharp.Compiler.Service.Tests/FsiTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -662,4 +662,12 @@ module FsiTests =
printfn "value: %A" v
| Choice2Of2 e ->
printfn "exception: %A" e
raise e
raise e

[<Fact>]
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
Loading