diff --git a/docs/release-notes/.FSharp.Compiler.Service/8.0.202.md b/docs/release-notes/.FSharp.Compiler.Service/8.0.202.md index a34e8e6442c..0780d447b5b 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/8.0.202.md +++ b/docs/release-notes/.FSharp.Compiler.Service/8.0.202.md @@ -1,3 +1,3 @@ ### Fixed -* `nameof Module` expressions and patterns are processed to link files in `--test:GraphBasedChecking`. ([PR #16570](https://github.com/dotnet/fsharp/pull/16570)) +`nameof Module` expressions and patterns are processed to link files in `--test:GraphBasedChecking`. ([PR #16570](https://github.com/dotnet/fsharp/pull/16570), [PR #16747](https://github.com/dotnet/fsharp/pull/16747)) diff --git a/src/Compiler/Driver/GraphChecking/FileContentMapping.fs b/src/Compiler/Driver/GraphChecking/FileContentMapping.fs index f86298308a9..2bd9e730926 100644 --- a/src/Compiler/Driver/GraphChecking/FileContentMapping.fs +++ b/src/Compiler/Driver/GraphChecking/FileContentMapping.fs @@ -311,8 +311,27 @@ let visitSynTypeConstraint (tc: SynTypeConstraint) : FileContentEntry list = let inline (|NameofIdent|_|) (ident: Ident) = if ident.idText = "nameof" then ValueSome() else ValueNone +/// nameof X.Y.Z can be used in expressions and patterns +[] +type NameofResult = + /// Example: nameof X + /// Where X is a module name + | SingleIdent of potentialModuleName: Ident + /// Example: nameof X.Y.Z + /// Where Z is either a module name or something from inside module or namespace Y. + /// Both options need to be explored. + | LongIdent of longIdent: LongIdent + +let visitNameofResult (nameofResult: NameofResult) : FileContentEntry = + match nameofResult with + | NameofResult.SingleIdent moduleName -> visitIdentAsPotentialModuleName moduleName + | NameofResult.LongIdent longIdent -> + // In this case the last part of the LongIdent could be a module name. + // So we should not cut off the last part. + FileContentEntry.PrefixedIdentifier(longIdentToPath false longIdent) + /// Special case of `nameof Module` type of expression -let (|NameofExpr|_|) (e: SynExpr) = +let (|NameofExpr|_|) (e: SynExpr) : NameofResult option = let rec stripParen (e: SynExpr) = match e with | SynExpr.Paren(expr = expr) -> stripParen expr @@ -321,14 +340,20 @@ let (|NameofExpr|_|) (e: SynExpr) = match e with | SynExpr.App(flag = ExprAtomicFlag.NonAtomic; isInfix = false; funcExpr = SynExpr.Ident NameofIdent; argExpr = moduleNameExpr) -> match stripParen moduleNameExpr with - | SynExpr.Ident moduleNameIdent -> Some moduleNameIdent + | SynExpr.Ident moduleNameIdent -> Some(NameofResult.SingleIdent moduleNameIdent) + | SynExpr.LongIdent(longDotId = longIdent) -> + match longIdent.LongIdent with + | [] -> None + // This is highly unlikely to be produced by the parser + | [ moduleNameIdent ] -> Some(NameofResult.SingleIdent moduleNameIdent) + | lid -> Some(NameofResult.LongIdent(lid)) | _ -> None | _ -> None let visitSynExpr (e: SynExpr) : FileContentEntry list = let rec visit (e: SynExpr) (continuation: FileContentEntry list -> FileContentEntry list) : FileContentEntry list = match e with - | NameofExpr moduleNameIdent -> continuation [ visitIdentAsPotentialModuleName moduleNameIdent ] + | NameofExpr nameofResult -> continuation [ visitNameofResult nameofResult ] | SynExpr.Const _ -> continuation [] | SynExpr.Paren(expr = expr) -> visit expr continuation | SynExpr.Quote(operator = operator; quotedExpr = quotedExpr) -> @@ -552,18 +577,22 @@ let (|NameofPat|_|) (pat: SynPat) = | 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 ]) + longDotId = SynLongIdent.SynLongIdent(id = longIdent) extraId = None typarDecls = None argPats = SynArgPats.Pats [] - accessibility = None) -> Some moduleNameIdent + accessibility = None) -> + match longIdent with + | [] -> None + | [ moduleNameIdent ] -> Some(NameofResult.SingleIdent moduleNameIdent) + | lid -> Some(NameofResult.LongIdent lid) | _ -> None | _ -> None let visitPat (p: SynPat) : FileContentEntry list = let rec visit (p: SynPat) (continuation: FileContentEntry list -> FileContentEntry list) : FileContentEntry list = match p with - | NameofPat moduleNameIdent -> continuation [ visitIdentAsPotentialModuleName moduleNameIdent ] + | NameofPat moduleNameIdent -> continuation [ visitNameofResult moduleNameIdent ] | SynPat.Paren(pat = pat) -> visit pat continuation | SynPat.Typed(pat = pat; targetType = t) -> visit pat (fun nodes -> nodes @ visitSynType t) | SynPat.Const _ -> continuation [] diff --git a/tests/FSharp.Compiler.ComponentTests/TypeChecks/Graph/Scenarios.fs b/tests/FSharp.Compiler.ComponentTests/TypeChecks/Graph/Scenarios.fs index 80f7caecafb..524556dae0a 100644 --- a/tests/FSharp.Compiler.ComponentTests/TypeChecks/Graph/Scenarios.fs +++ b/tests/FSharp.Compiler.ComponentTests/TypeChecks/Graph/Scenarios.fs @@ -796,6 +796,8 @@ type Foo = class end sourceFile "Program" """ +module Program + printfn "Hello" """ Set.empty @@ -907,6 +909,35 @@ do module Bar let _ = nameof ((Foo)) +""" + (set [| 0 |]) + ] + scenario + "prefixed module name in nameof expression" + [ + sourceFile "A.fs" "module X.Y.Z" Set.empty + sourceFile + "B.fs" + """ +module B + +open System.ComponentModel + +[] +let v = 2 +""" + (set [| 0 |]) + ] + scenario + "prefixed module name in nameof pattern" + [ + sourceFile "A.fs" "module X.Y.Z" Set.empty + sourceFile + "B.fs" + """ +module B + +do ignore (match "" with | nameof X.Y.Z -> () | _ -> ()) """ (set [| 0 |]) ]