diff --git a/src/Compiler/Driver/GraphChecking/DependencyResolution.fs b/src/Compiler/Driver/GraphChecking/DependencyResolution.fs
index 9300385b4839..11ba984ca511 100644
--- a/src/Compiler/Driver/GraphChecking/DependencyResolution.fs
+++ b/src/Compiler/Driver/GraphChecking/DependencyResolution.fs
@@ -1,7 +1,6 @@
module internal FSharp.Compiler.GraphChecking.DependencyResolution
open FSharp.Compiler.Syntax
-open Internal.Utilities.Library
/// Find a path from a starting TrieNode and return the end node or None
let queryTriePartial (trie: TrieNode) (path: LongIdentifier) : TrieNode option =
@@ -118,6 +117,20 @@ let rec processStateEntry (trie: TrieNode) (state: FileContentQueryState) (entry
FoundDependencies = foundDependencies
}
+ | ModuleName name ->
+ // We need to check if the module name is a hit in the Trie.
+ let state' =
+ let queryResult = queryTrie trie [ name ]
+ processIdentifier queryResult state
+
+ match state.OwnNamespace with
+ | None -> state'
+ | Some ns ->
+ // If there we currently have our own namespace,
+ // the combination of that namespace + module name should be checked as well.
+ let queryResult = queryTrieDual trie ns [ name ]
+ processIdentifier queryResult state'
+
///
/// For a given file's content, collect all missing ("ghost") file dependencies that the core resolution algorithm didn't return,
/// but are required to satisfy the type-checker.
diff --git a/src/Compiler/Driver/GraphChecking/FileContentMapping.fs b/src/Compiler/Driver/GraphChecking/FileContentMapping.fs
index ed3b76b49854..f86298308a9a 100644
--- a/src/Compiler/Driver/GraphChecking/FileContentMapping.fs
+++ b/src/Compiler/Driver/GraphChecking/FileContentMapping.fs
@@ -18,6 +18,11 @@ let longIdentToPath (skipLast: bool) (longId: LongIdent) : LongIdentifier =
let synLongIdentToPath (skipLast: bool) (synLongIdent: SynLongIdent) =
longIdentToPath skipLast synLongIdent.LongIdent
+/// In some rare cases we are interested in the name of a single Ident.
+/// For example `nameof ModuleName` in expressions or patterns.
+let visitIdentAsPotentialModuleName (moduleNameIdent: Ident) =
+ FileContentEntry.ModuleName moduleNameIdent.idText
+
let visitSynLongIdent (lid: SynLongIdent) : FileContentEntry list = visitLongIdent lid.LongIdent
let visitLongIdent (lid: LongIdent) =
@@ -302,6 +307,10 @@ let visitSynTypeConstraint (tc: SynTypeConstraint) : FileContentEntry list =
| SynTypeConstraint.WhereTyparIsEnum(typeArgs = typeArgs) -> List.collect visitSynType typeArgs
| SynTypeConstraint.WhereTyparIsDelegate(typeArgs = typeArgs) -> List.collect visitSynType typeArgs
+[]
+let inline (|NameofIdent|_|) (ident: Ident) =
+ if ident.idText = "nameof" then ValueSome() else ValueNone
+
/// Special case of `nameof Module` type of expression
let (|NameofExpr|_|) (e: SynExpr) =
let rec stripParen (e: SynExpr) =
@@ -310,13 +319,10 @@ let (|NameofExpr|_|) (e: SynExpr) =
| _ -> e
match e with
- | SynExpr.App(flag = ExprAtomicFlag.NonAtomic; isInfix = false; funcExpr = SynExpr.Ident nameofIdent; argExpr = moduleNameExpr) ->
- if nameofIdent.idText <> "nameof" then
- None
- else
- match stripParen moduleNameExpr with
- | SynExpr.Ident moduleNameIdent -> Some moduleNameIdent
- | _ -> None
+ | SynExpr.App(flag = ExprAtomicFlag.NonAtomic; isInfix = false; funcExpr = SynExpr.Ident NameofIdent; argExpr = moduleNameExpr) ->
+ match stripParen moduleNameExpr with
+ | SynExpr.Ident moduleNameIdent -> Some moduleNameIdent
+ | _ -> None
| _ -> None
let visitSynExpr (e: SynExpr) : FileContentEntry list =
@@ -543,18 +549,15 @@ let (|NameofPat|_|) (pat: SynPat) =
| _ -> p
match pat with
- | SynPat.LongIdent(longDotId = SynLongIdent(id = [ nameofIdent ]); typarDecls = None; argPats = SynArgPats.Pats [ moduleNamePat ]) ->
- if nameofIdent.idText <> "nameof" then
- None
- else
- match stripPats moduleNamePat with
- | SynPat.LongIdent(
- longDotId = SynLongIdent.SynLongIdent(id = [ moduleNameIdent ]; dotRanges = []; trivia = [ None ])
- extraId = None
- typarDecls = None
- argPats = SynArgPats.Pats []
- accessibility = None) -> Some moduleNameIdent
- | _ -> None
+ | SynPat.LongIdent(longDotId = SynLongIdent(id = [ NameofIdent ]); typarDecls = None; argPats = SynArgPats.Pats [ moduleNamePat ]) ->
+ match stripPats moduleNamePat with
+ | SynPat.LongIdent(
+ longDotId = SynLongIdent.SynLongIdent(id = [ moduleNameIdent ]; dotRanges = []; trivia = [ None ])
+ extraId = None
+ typarDecls = None
+ argPats = SynArgPats.Pats []
+ accessibility = None) -> Some moduleNameIdent
+ | _ -> None
| _ -> None
let visitPat (p: SynPat) : FileContentEntry list =
diff --git a/src/Compiler/Driver/GraphChecking/Types.fs b/src/Compiler/Driver/GraphChecking/Types.fs
index 00538b6e599f..c667a573f69f 100644
--- a/src/Compiler/Driver/GraphChecking/Types.fs
+++ b/src/Compiler/Driver/GraphChecking/Types.fs
@@ -73,6 +73,9 @@ type internal FileContentEntry =
/// Being explicit about nested modules allows for easier reasoning what namespaces (paths) are open.
/// We can scope an `OpenStatement` to the everything that is happening inside the nested module.
| NestedModule of name: string * nestedContent: FileContentEntry list
+ /// A single identifier that could be the name of a module.
+ /// Example use-case: `let x = nameof Foo` where `Foo` is a module.
+ | ModuleName of name: Identifier
type internal FileContent =
{
diff --git a/src/Compiler/Driver/GraphChecking/Types.fsi b/src/Compiler/Driver/GraphChecking/Types.fsi
index 468ef65889cf..096719b6be73 100644
--- a/src/Compiler/Driver/GraphChecking/Types.fsi
+++ b/src/Compiler/Driver/GraphChecking/Types.fsi
@@ -67,6 +67,9 @@ type internal FileContentEntry =
/// Being explicit about nested modules allows for easier reasoning what namespaces (paths) are open.
/// For example we can limit the scope of an `OpenStatement` to symbols defined inside the nested module.
| NestedModule of name: string * nestedContent: FileContentEntry list
+ /// A single identifier that could be the name of a module.
+ /// Example use-case: `let x = nameof Foo` where `Foo` is a module.
+ | ModuleName of name: Identifier
/// File identifiers and its content extract for dependency resolution
type internal FileContent =
diff --git a/tests/FSharp.Compiler.ComponentTests/TypeChecks/Graph/Scenarios.fs b/tests/FSharp.Compiler.ComponentTests/TypeChecks/Graph/Scenarios.fs
index 6063366d4f7d..80f7caecafb2 100644
--- a/tests/FSharp.Compiler.ComponentTests/TypeChecks/Graph/Scenarios.fs
+++ b/tests/FSharp.Compiler.ComponentTests/TypeChecks/Graph/Scenarios.fs
@@ -800,6 +800,86 @@ printfn "Hello"
"""
Set.empty
]
+ scenario
+ "Nameof module with namespace"
+ [
+ sourceFile
+ "A.fs"
+ """
+namespace X.Y.Z
+
+module Foo =
+ let x = 2
+"""
+ Set.empty
+ sourceFile
+ "B.fs"
+ """
+namespace X.Y.Z
+
+module Point =
+ let y = nameof Foo
+"""
+ (set [| 0 |])
+ ]
+ scenario
+ "Nameof module without namespace"
+ [
+ sourceFile
+ "A.fs"
+ """
+module Foo
+
+let x = 2
+"""
+ Set.empty
+ sourceFile
+ "B.fs"
+ """
+module Point
+
+let y = nameof Foo
+"""
+ (set [| 0 |])
+ ]
+ scenario
+ "Single module name should always be checked, regardless of own namespace"
+ [
+ sourceFile "X.fs" "namespace X.Y" Set.empty
+ sourceFile
+ "A.fs"
+ """
+module Foo
+
+let x = 2
+"""
+ Set.empty
+ sourceFile
+ "B.fs"
+ """
+namespace X.Y
+
+type T() =
+ let _ = nameof Foo
+"""
+ (set [| 1 |])
+ ]
+ scenario
+ "nameof pattern"
+ [
+ sourceFile "A.fs" "module Foo" Set.empty
+ sourceFile
+ "B.fs"
+ """
+module Bar
+
+do
+ match "" with
+ | nameof Foo -> ()
+ | _ -> ()
+"""
+ (set [| 0 |])
+ ]
scenario
"parentheses around module name in nameof pattern"
[