Skip to content

Commit 1112c11

Browse files
authored
Attempt to make links from single identifier module names. (#16550)
* Add scenarios where parentheses are around module name. * Address problem tighter to nameof usage. * Restore missing commit and inline nameof ident check. * Add release note entry.
1 parent bc62506 commit 1112c11

File tree

6 files changed

+176
-2
lines changed

6 files changed

+176
-2
lines changed

docs/release-notes/.FSharp.Compiler.Service/8.0.300.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
### Fixed
22

33
* Code generated files with > 64K methods and generated symbols crash when loaded. Use infered sequence points for debugging. ([Issue #16399](https://github.com/dotnet/fsharp/issues/16399), [#PR 16514](https://github.com/dotnet/fsharp/pull/16514))
4+
* `nameof Module` expressions and patterns are processed to link files in `--test:GraphBasedChecking`. ([PR #16550](https://github.com/dotnet/fsharp/pull/16550))
45

56
### Added
67

src/Compiler/Driver/GraphChecking/DependencyResolution.fs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
module internal FSharp.Compiler.GraphChecking.DependencyResolution
22

33
open FSharp.Compiler.Syntax
4-
open Internal.Utilities.Library
54

65
/// <summary>Find a path from a starting TrieNode and return the end node or None</summary>
76
let queryTriePartial (trie: TrieNode) (path: LongIdentifier) : TrieNode option =
@@ -118,6 +117,20 @@ let rec processStateEntry (trie: TrieNode) (state: FileContentQueryState) (entry
118117
FoundDependencies = foundDependencies
119118
}
120119

120+
| ModuleName name ->
121+
// We need to check if the module name is a hit in the Trie.
122+
let state' =
123+
let queryResult = queryTrie trie [ name ]
124+
processIdentifier queryResult state
125+
126+
match state.OwnNamespace with
127+
| None -> state'
128+
| Some ns ->
129+
// If there we currently have our own namespace,
130+
// the combination of that namespace + module name should be checked as well.
131+
let queryResult = queryTrieDual trie ns [ name ]
132+
processIdentifier queryResult state'
133+
121134
/// <summary>
122135
/// For a given file's content, collect all missing ("ghost") file dependencies that the core resolution algorithm didn't return,
123136
/// but are required to satisfy the type-checker.

src/Compiler/Driver/GraphChecking/FileContentMapping.fs

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ let longIdentToPath (skipLast: bool) (longId: LongIdent) : LongIdentifier =
1818
let synLongIdentToPath (skipLast: bool) (synLongIdent: SynLongIdent) =
1919
longIdentToPath skipLast synLongIdent.LongIdent
2020

21+
/// In some rare cases we are interested in the name of a single Ident.
22+
/// For example `nameof ModuleName` in expressions or patterns.
23+
let visitIdentAsPotentialModuleName (moduleNameIdent: Ident) =
24+
FileContentEntry.ModuleName moduleNameIdent.idText
25+
2126
let visitSynLongIdent (lid: SynLongIdent) : FileContentEntry list = visitLongIdent lid.LongIdent
2227

2328
let visitLongIdent (lid: LongIdent) =
@@ -302,9 +307,28 @@ let visitSynTypeConstraint (tc: SynTypeConstraint) : FileContentEntry list =
302307
| SynTypeConstraint.WhereTyparIsEnum(typeArgs = typeArgs) -> List.collect visitSynType typeArgs
303308
| SynTypeConstraint.WhereTyparIsDelegate(typeArgs = typeArgs) -> List.collect visitSynType typeArgs
304309

310+
[<return: Struct>]
311+
let inline (|NameofIdent|_|) (ident: Ident) =
312+
if ident.idText = "nameof" then ValueSome() else ValueNone
313+
314+
/// Special case of `nameof Module` type of expression
315+
let (|NameofExpr|_|) (e: SynExpr) =
316+
let rec stripParen (e: SynExpr) =
317+
match e with
318+
| SynExpr.Paren(expr = expr) -> stripParen expr
319+
| _ -> e
320+
321+
match e with
322+
| SynExpr.App(flag = ExprAtomicFlag.NonAtomic; isInfix = false; funcExpr = SynExpr.Ident NameofIdent; argExpr = moduleNameExpr) ->
323+
match stripParen moduleNameExpr with
324+
| SynExpr.Ident moduleNameIdent -> Some moduleNameIdent
325+
| _ -> None
326+
| _ -> None
327+
305328
let visitSynExpr (e: SynExpr) : FileContentEntry list =
306329
let rec visit (e: SynExpr) (continuation: FileContentEntry list -> FileContentEntry list) : FileContentEntry list =
307330
match e with
331+
| NameofExpr moduleNameIdent -> continuation [ visitIdentAsPotentialModuleName moduleNameIdent ]
308332
| SynExpr.Const _ -> continuation []
309333
| SynExpr.Paren(expr = expr) -> visit expr continuation
310334
| SynExpr.Quote(operator = operator; quotedExpr = quotedExpr) ->
@@ -389,7 +413,7 @@ let visitSynExpr (e: SynExpr) : FileContentEntry list =
389413
| SynExpr.IfThenElse(ifExpr = ifExpr; thenExpr = thenExpr; elseExpr = elseExpr) ->
390414
let continuations = List.map visit (ifExpr :: thenExpr :: Option.toList elseExpr)
391415
Continuation.concatenate continuations continuation
392-
| SynExpr.Typar _ -> continuation []
416+
| SynExpr.Typar _
393417
| SynExpr.Ident _ -> continuation []
394418
| SynExpr.LongIdent(longDotId = longDotId) -> continuation (visitSynLongIdent longDotId)
395419
| SynExpr.LongIdentSet(longDotId, expr, _) -> visit expr (fun nodes -> visitSynLongIdent longDotId @ nodes |> continuation)
@@ -517,9 +541,29 @@ let visitSynExpr (e: SynExpr) : FileContentEntry list =
517541

518542
visit e id
519543

544+
/// Special case of `| nameof Module ->` type of pattern
545+
let (|NameofPat|_|) (pat: SynPat) =
546+
let rec stripPats p =
547+
match p with
548+
| SynPat.Paren(pat = pat) -> stripPats pat
549+
| _ -> p
550+
551+
match pat with
552+
| SynPat.LongIdent(longDotId = SynLongIdent(id = [ NameofIdent ]); typarDecls = None; argPats = SynArgPats.Pats [ moduleNamePat ]) ->
553+
match stripPats moduleNamePat with
554+
| SynPat.LongIdent(
555+
longDotId = SynLongIdent.SynLongIdent(id = [ moduleNameIdent ]; dotRanges = []; trivia = [ None ])
556+
extraId = None
557+
typarDecls = None
558+
argPats = SynArgPats.Pats []
559+
accessibility = None) -> Some moduleNameIdent
560+
| _ -> None
561+
| _ -> None
562+
520563
let visitPat (p: SynPat) : FileContentEntry list =
521564
let rec visit (p: SynPat) (continuation: FileContentEntry list -> FileContentEntry list) : FileContentEntry list =
522565
match p with
566+
| NameofPat moduleNameIdent -> continuation [ visitIdentAsPotentialModuleName moduleNameIdent ]
523567
| SynPat.Paren(pat = pat) -> visit pat continuation
524568
| SynPat.Typed(pat = pat; targetType = t) -> visit pat (fun nodes -> nodes @ visitSynType t)
525569
| SynPat.Const _ -> continuation []

src/Compiler/Driver/GraphChecking/Types.fs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,9 @@ type internal FileContentEntry =
7373
/// Being explicit about nested modules allows for easier reasoning what namespaces (paths) are open.
7474
/// We can scope an `OpenStatement` to the everything that is happening inside the nested module.
7575
| NestedModule of name: string * nestedContent: FileContentEntry list
76+
/// A single identifier that could be the name of a module.
77+
/// Example use-case: `let x = nameof Foo` where `Foo` is a module.
78+
| ModuleName of name: Identifier
7679

7780
type internal FileContent =
7881
{

src/Compiler/Driver/GraphChecking/Types.fsi

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ type internal FileContentEntry =
6767
/// Being explicit about nested modules allows for easier reasoning what namespaces (paths) are open.
6868
/// For example we can limit the scope of an `OpenStatement` to symbols defined inside the nested module.
6969
| NestedModule of name: string * nestedContent: FileContentEntry list
70+
/// A single identifier that could be the name of a module.
71+
/// Example use-case: `let x = nameof Foo` where `Foo` is a module.
72+
| ModuleName of name: Identifier
7073

7174
/// File identifiers and its content extract for dependency resolution
7275
type internal FileContent =

tests/FSharp.Compiler.ComponentTests/TypeChecks/Graph/Scenarios.fs

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -800,4 +800,114 @@ printfn "Hello"
800800
"""
801801
Set.empty
802802
]
803+
scenario
804+
"Nameof module with namespace"
805+
[
806+
sourceFile
807+
"A.fs"
808+
"""
809+
namespace X.Y.Z
810+
811+
module Foo =
812+
let x = 2
813+
"""
814+
Set.empty
815+
sourceFile
816+
"B.fs"
817+
"""
818+
namespace X.Y.Z
819+
820+
module Point =
821+
let y = nameof Foo
822+
"""
823+
(set [| 0 |])
824+
]
825+
scenario
826+
"Nameof module without namespace"
827+
[
828+
sourceFile
829+
"A.fs"
830+
"""
831+
module Foo
832+
833+
let x = 2
834+
"""
835+
Set.empty
836+
sourceFile
837+
"B.fs"
838+
"""
839+
module Point
840+
841+
let y = nameof Foo
842+
"""
843+
(set [| 0 |])
844+
]
845+
scenario
846+
"Single module name should always be checked, regardless of own namespace"
847+
[
848+
sourceFile "X.fs" "namespace X.Y" Set.empty
849+
sourceFile
850+
"A.fs"
851+
"""
852+
module Foo
853+
854+
let x = 2
855+
"""
856+
Set.empty
857+
sourceFile
858+
"B.fs"
859+
"""
860+
namespace X.Y
861+
862+
type T() =
863+
let _ = nameof Foo
864+
"""
865+
(set [| 1 |])
866+
]
867+
scenario
868+
"nameof pattern"
869+
[
870+
sourceFile "A.fs" "module Foo" Set.empty
871+
sourceFile
872+
"B.fs"
873+
"""
874+
module Bar
875+
876+
do
877+
match "" with
878+
| nameof Foo -> ()
879+
| _ -> ()
880+
"""
881+
(set [| 0 |])
882+
]
883+
scenario
884+
"parentheses around module name in nameof pattern"
885+
[
886+
sourceFile "A.fs" "module Foo" Set.empty
887+
sourceFile
888+
"B.fs"
889+
"""
890+
module Bar
891+
892+
do
893+
match "" with
894+
| nameof ((Foo)) -> ()
895+
| _ -> ()
896+
"""
897+
(set [| 0 |])
898+
]
899+
900+
scenario
901+
"parentheses around module name in nameof expression"
902+
[
903+
sourceFile "A.fs" "module Foo" Set.empty
904+
sourceFile
905+
"B.fs"
906+
"""
907+
module Bar
908+
909+
let _ = nameof ((Foo))
910+
"""
911+
(set [| 0 |])
912+
]
803913
]

0 commit comments

Comments
 (0)