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