diff --git a/src/Compiler/SyntaxTree/ParseHelpers.fs b/src/Compiler/SyntaxTree/ParseHelpers.fs
index f3e3fcdfdaf..6aa973d275e 100644
--- a/src/Compiler/SyntaxTree/ParseHelpers.fs
+++ b/src/Compiler/SyntaxTree/ParseHelpers.fs
@@ -468,6 +468,7 @@ let mkSynMemberDefnGetSet
{
LetKeyword = None
EqualsRange = mEquals
+ ExternKeyword = None
}
let binding =
@@ -542,6 +543,7 @@ let mkSynMemberDefnGetSet
{
LetKeyword = None
EqualsRange = mEquals
+ ExternKeyword = None
}
let binding =
@@ -629,6 +631,7 @@ let mkSynMemberDefnGetSet
{
LetKeyword = None
EqualsRange = mEquals
+ ExternKeyword = None
}
let bindingOuter =
diff --git a/src/Compiler/SyntaxTree/SyntaxTrivia.fs b/src/Compiler/SyntaxTree/SyntaxTrivia.fs
index e4b58c85c00..fa4ebc784c8 100644
--- a/src/Compiler/SyntaxTree/SyntaxTrivia.fs
+++ b/src/Compiler/SyntaxTree/SyntaxTrivia.fs
@@ -159,12 +159,14 @@ type SynTypeDefnSigTrivia =
type SynBindingTrivia =
{
LetKeyword: range option
+ ExternKeyword: range option
EqualsRange: range option
}
static member Zero: SynBindingTrivia =
{
LetKeyword = None
+ ExternKeyword = None
EqualsRange = None
}
diff --git a/src/Compiler/SyntaxTree/SyntaxTrivia.fsi b/src/Compiler/SyntaxTree/SyntaxTrivia.fsi
index 3e0dea3db0b..b6f0532d73c 100644
--- a/src/Compiler/SyntaxTree/SyntaxTrivia.fsi
+++ b/src/Compiler/SyntaxTree/SyntaxTrivia.fsi
@@ -246,6 +246,9 @@ type SynBindingTrivia =
/// The syntax range of the `let` keyword.
LetKeyword: range option
+ /// The syntax range of the `extern` keyword.
+ ExternKeyword: range option
+
/// The syntax range of the `=` token.
EqualsRange: range option
}
diff --git a/src/Compiler/pars.fsy b/src/Compiler/pars.fsy
index 94df75aada1..b732754d7fc 100644
--- a/src/Compiler/pars.fsy
+++ b/src/Compiler/pars.fsy
@@ -1868,7 +1868,7 @@ memberCore:
let xmlDoc = grabXmlDocAtRangeStart(parseState, attrs, rangeStart)
let memberFlags = Some (memFlagsBuilder SynMemberKind.Member)
let mWholeBindLhs = (mBindLhs, attrs) ||> unionRangeWithListBy (fun (a: SynAttributeList) -> a.Range)
- let trivia: SynBindingTrivia = { LetKeyword = None; EqualsRange = Some mEquals }
+ let trivia: SynBindingTrivia = { LetKeyword = None; EqualsRange = Some mEquals; ExternKeyword = None }
let binding = mkSynBinding (xmlDoc, bindingPat) (vis, $1, false, mWholeBindLhs, DebugPointAtBinding.NoneAtInvisible, optReturnType, $5, mRhs, [], attrs, memberFlags, trivia)
let memberRange = unionRanges rangeStart mRhs |> unionRangeWithXmlDoc xmlDoc
[ SynMemberDefn.Member (binding, memberRange) ]) }
@@ -1985,7 +1985,7 @@ classDefnMember:
let declPat = SynPat.LongIdent (SynLongIdent([mkSynId (rhs parseState 3) "new"], [], [None]), None, Some noInferredTypars, SynArgPats.Pats [$4], vis, rhs parseState 3)
// Check that 'SynPatForConstructorDecl' matches this correctly
assert (match declPat with SynPatForConstructorDecl _ -> true | _ -> false)
- let synBindingTrivia: SynBindingTrivia = { LetKeyword = None; EqualsRange = Some mEquals }
+ let synBindingTrivia: SynBindingTrivia = { LetKeyword = None; EqualsRange = Some mEquals; ExternKeyword = None }
[ SynMemberDefn.Member(SynBinding (None, SynBindingKind.Normal, false, false, $1, xmlDoc, valSynData, declPat, None, expr, mWholeBindLhs, DebugPointAtBinding.NoneAtInvisible, synBindingTrivia), m) ] }
| opt_attributes opt_declVisibility STATIC typeKeyword tyconDefn
@@ -2743,7 +2743,8 @@ hardwhiteDefnBindingsTerminator:
/* An 'extern' DllImport function definition in C-style syntax */
cPrototype:
| EXTERN cRetType opt_access ident opt_HIGH_PRECEDENCE_APP LPAREN externArgs rparen
- { let rty, vis, nm, args = $2, $3, $4, $7
+ { let mExtern = rhs parseState 1
+ let rty, vis, nm, args = $2, $3, $4, $7
let nmm = rhs parseState 3
let argsm = rhs parseState 6
let mBindLhs = lhs parseState
@@ -2760,10 +2761,11 @@ cPrototype:
let bindingPat = SynPat.LongIdent (SynLongIdent([nm], [], [None]), None, Some noInferredTypars, SynArgPats.Pats [SynPat.Tuple(false, args, argsm)], vis, nmm)
let mWholeBindLhs = (mBindLhs, attrs) ||> unionRangeWithListBy (fun (a: SynAttributeList) -> a.Range)
let xmlDoc = grabXmlDoc(parseState, attrs, 1)
+ let trivia = { LetKeyword = None; ExternKeyword = Some mExtern; EqualsRange = None }
let binding =
mkSynBinding
(xmlDoc, bindingPat)
- (vis, false, false, mWholeBindLhs, DebugPointAtBinding.NoneAtInvisible, Some rty, rhsExpr, mRhs, [], attrs, None, SynBindingTrivia.Zero)
+ (vis, false, false, mWholeBindLhs, DebugPointAtBinding.NoneAtInvisible, Some rty, rhsExpr, mRhs, [], attrs, None, trivia)
[], [binding]) }
/* A list of arguments in an 'extern' DllImport function definition */
@@ -2882,7 +2884,7 @@ localBinding:
let mWhole = (unionRanges mLetKwd mRhs, attrs) ||> unionRangeWithListBy (fun (a: SynAttributeList) -> a.Range)
let spBind = if IsDebugPointBinding bindingPat expr then DebugPointAtBinding.Yes mWhole else DebugPointAtBinding.NoneAtLet
let mWholeBindLhs = (mBindLhs, attrs) ||> unionRangeWithListBy (fun (a: SynAttributeList) -> a.Range)
- let trivia: SynBindingTrivia = { LetKeyword = Some mLetKwd; EqualsRange = Some mEquals }
+ let trivia: SynBindingTrivia = { LetKeyword = Some mLetKwd; EqualsRange = Some mEquals; ExternKeyword = None }
mkSynBinding (xmlDoc, bindingPat) (vis, $1, $2, mWholeBindLhs, spBind, optReturnType, expr, mRhs, opts, attrs, None, trivia))
localBindingRange, localBindingBuilder }
@@ -2897,7 +2899,7 @@ localBinding:
let zeroWidthAtEnd = mEquals.EndRange
let rhsExpr = arbExpr("localBinding1", zeroWidthAtEnd)
let spBind = if IsDebugPointBinding bindingPat rhsExpr then DebugPointAtBinding.Yes mWhole else DebugPointAtBinding.NoneAtLet
- let trivia: SynBindingTrivia = { LetKeyword = Some mLetKwd; EqualsRange = Some mEquals }
+ let trivia: SynBindingTrivia = { LetKeyword = Some mLetKwd; EqualsRange = Some mEquals; ExternKeyword = None }
mkSynBinding (xmlDoc, bindingPat) (vis, $1, $2, mBindLhs, spBind, optReturnType, rhsExpr, mRhs, [], attrs, None, trivia))
mWhole, localBindingBuilder }
@@ -2910,7 +2912,7 @@ localBinding:
let localBindingBuilder =
(fun xmlDoc attrs vis mLetKwd ->
let spBind = DebugPointAtBinding.Yes (unionRanges mLetKwd mRhs)
- let trivia = { LetKeyword = Some mLetKwd; EqualsRange = None }
+ let trivia = { LetKeyword = Some mLetKwd; EqualsRange = None; ExternKeyword = None }
let rhsExpr = arbExpr("localBinding2", mRhs)
mkSynBinding (xmlDoc, bindingPat) (vis, $1, $2, mBindLhs, spBind, optReturnType, rhsExpr, mRhs, [], attrs, None, trivia))
mWhole, localBindingBuilder }
diff --git a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.Tests.fsproj b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.Tests.fsproj
index d6db862529d..202bd49532e 100644
--- a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.Tests.fsproj
+++ b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.Tests.fsproj
@@ -122,6 +122,9 @@
SyntaxTree\AttributeTests.fs
+
+ SyntaxTree\ExternTests.fs
+
FileSystemTests.fs
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 a6713fdbc26..1d2367fb0a6 100644
--- a/tests/FSharp.Compiler.Service.Tests/FSharp.CompilerService.SurfaceArea.netstandard.expected
+++ b/tests/FSharp.Compiler.Service.Tests/FSharp.CompilerService.SurfaceArea.netstandard.expected
@@ -9419,11 +9419,13 @@ FSharp.Compiler.SyntaxTrivia.SynBindingTrivia
FSharp.Compiler.SyntaxTrivia.SynBindingTrivia: FSharp.Compiler.SyntaxTrivia.SynBindingTrivia Zero
FSharp.Compiler.SyntaxTrivia.SynBindingTrivia: FSharp.Compiler.SyntaxTrivia.SynBindingTrivia get_Zero()
FSharp.Compiler.SyntaxTrivia.SynBindingTrivia: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.Range] EqualsRange
+FSharp.Compiler.SyntaxTrivia.SynBindingTrivia: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.Range] ExternKeyword
FSharp.Compiler.SyntaxTrivia.SynBindingTrivia: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.Range] LetKeyword
FSharp.Compiler.SyntaxTrivia.SynBindingTrivia: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.Range] get_EqualsRange()
+FSharp.Compiler.SyntaxTrivia.SynBindingTrivia: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.Range] get_ExternKeyword()
FSharp.Compiler.SyntaxTrivia.SynBindingTrivia: Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.Range] get_LetKeyword()
FSharp.Compiler.SyntaxTrivia.SynBindingTrivia: System.String ToString()
-FSharp.Compiler.SyntaxTrivia.SynBindingTrivia: Void .ctor(Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.Range], Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.Range])
+FSharp.Compiler.SyntaxTrivia.SynBindingTrivia: Void .ctor(Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.Range], Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.Range], Microsoft.FSharp.Core.FSharpOption`1[FSharp.Compiler.Text.Range])
FSharp.Compiler.SyntaxTrivia.SynEnumCaseTrivia
FSharp.Compiler.SyntaxTrivia.SynEnumCaseTrivia: FSharp.Compiler.Text.Range EqualsRange
FSharp.Compiler.SyntaxTrivia.SynEnumCaseTrivia: FSharp.Compiler.Text.Range get_EqualsRange()
diff --git a/tests/service/SyntaxTreeTests/ExternTests.fs b/tests/service/SyntaxTreeTests/ExternTests.fs
new file mode 100644
index 00000000000..0599307d994
--- /dev/null
+++ b/tests/service/SyntaxTreeTests/ExternTests.fs
@@ -0,0 +1,21 @@
+module FSharp.Compiler.Service.Tests.SyntaxTreeTests.ExternTests
+
+open FSharp.Compiler.Service.Tests.Common
+open FSharp.Compiler.Syntax
+open FSharp.Compiler.SyntaxTrivia
+open NUnit.Framework
+
+[]
+let ``extern keyword is present in trivia`` () =
+ let parseResults = getParseResults "extern void GetProcessHeap()"
+
+ match parseResults with
+ | ParsedInput.ImplFile(ParsedImplFileInput(contents = [
+ SynModuleOrNamespace.SynModuleOrNamespace(decls = [
+ SynModuleDecl.Let(bindings = [
+ SynBinding(trivia = { ExternKeyword = Some mExtern })
+ ])
+ ])
+ ])) ->
+ assertRange (1, 0) (1, 6) mExtern
+ | _ -> Assert.Fail $"Could not get valid AST, got {parseResults}"