diff --git a/src/Compiler/Service/ItemKey.fs b/src/Compiler/Service/ItemKey.fs index ef769874226..9609ccf9c38 100644 --- a/src/Compiler/Service/ItemKey.fs +++ b/src/Compiler/Service/ItemKey.fs @@ -390,6 +390,7 @@ and [] ItemKeyStoreBuilder() = | Item.MethodGroup (_, [ info ], _) | Item.CtorGroup (_, [ info ]) -> match info with + | FSMeth (_, ty, vref, _) when vref.IsConstructor -> writeType true ty | FSMeth (_, _, vref, _) -> writeValRef vref | ILMeth (_, info, _) -> info.ILMethodRef.ArgTypes |> List.iter writeILType diff --git a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj index 85239d5a3c0..476069e622d 100644 --- a/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj +++ b/tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj @@ -210,6 +210,7 @@ + %(RelativeDir)\TestSource\%(Filename)%(Extension) diff --git a/tests/FSharp.Compiler.ComponentTests/FSharpChecker/FindReferences.fs b/tests/FSharp.Compiler.ComponentTests/FSharpChecker/FindReferences.fs new file mode 100644 index 00000000000..48cfdf9168a --- /dev/null +++ b/tests/FSharp.Compiler.ComponentTests/FSharpChecker/FindReferences.fs @@ -0,0 +1,98 @@ +module FSharp.Compiler.ComponentTests.FSharpChecker.FindReferences + +open FSharp.Compiler.CodeAnalysis +open Xunit +open FSharp.Test.ProjectGeneration + +type Occurence = Definition | InType | Use + +let deriveOccurence (su:FSharpSymbolUse) = + if su.IsFromDefinition + then Definition + elif su.IsFromType + then InType + elif su.IsFromUse + then Use + else failwith $"Unexpected type of occurence (for this test), symbolUse = {su}" + +/// https://github.com/dotnet/fsharp/issues/13199 +let reproSourceCode = """ +type MyType() = + member x.DoNothing(d:MyType) = () + +let a = MyType() +let b = new MyType() +a.DoNothing(b) +""" +let impFile() = { sourceFile "First" [] with ExtraSource = reproSourceCode } +let createProject() = SyntheticProject.Create(impFile()) + +[] +let ``Finding usage of type via GetUsesOfSymbolInFile should also find it's constructors`` () = + createProject().Workflow + { + checkFile "First" (fun (typeCheckResult: FSharpCheckFileResults) -> + + let symbolUse = typeCheckResult.GetSymbolUseAtLocation(7, 11, "type MyType() =", ["MyType"]).Value + let references = + typeCheckResult.GetUsesOfSymbolInFile(symbolUse.Symbol) + |> Array.sortBy (fun su -> su.Range.StartLine) + |> Array.map (fun su -> su.Range.StartLine, su.Range.StartColumn, su.Range.EndColumn, deriveOccurence su) + + Assert.Equal<(int*int*int*Occurence)>( + [| 7,5,11,Definition + 8,25,31,InType + 10,8,14,Use + 11,12,18,Use + |],references) ) + } + + +[] +let ``Finding usage of type via FindReference should also find it's constructors`` () = + createProject().Workflow + { + placeCursor "First" 7 11 "type MyType() =" ["MyType"] + findAllReferences "First" (fun (ranges:list) -> + let ranges = + ranges + |> List.sortBy (fun r -> r.StartLine) + |> List.map (fun r -> r.StartLine, r.StartColumn, r.EndColumn) + |> Array.ofSeq + + Assert.Equal<(int*int*int)>( + [| 7,5,11 // Typedef itself + 8,25,31 // Usage within type + 10,8,14 // "a= ..." constructor + 11,12,18 // "b= ..." constructor + |],ranges) ) + + } + +[] +let ``Finding usage of type via FindReference works across files`` () = + let secondFile = { sourceFile "Second" ["First"] with ExtraSource = """ +open ModuleFirst +let secondA = MyType() +let secondB = new MyType() +secondA.DoNothing(secondB) + """} + let original = createProject() + let project = {original with SourceFiles = original.SourceFiles @ [secondFile]} + project.Workflow + { + placeCursor "First" 7 11 "type MyType() =" ["MyType"] + findAllReferences "Second" (fun (ranges:list) -> + let ranges = + ranges + |> List.sortBy (fun r -> r.StartLine) + |> List.map (fun r -> r.StartLine, r.StartColumn, r.EndColumn) + |> Array.ofSeq + + Assert.Equal<(int*int*int)>( + [| 9,14,20 // "secondA = ..." constructor + 10,18,24 // "secondB = ..." constructor + |],ranges) ) + + } + diff --git a/tests/FSharp.Test.Utilities/ProjectGeneration.fs b/tests/FSharp.Test.Utilities/ProjectGeneration.fs index 7dd50e28d03..c565ca9b779 100644 --- a/tests/FSharp.Test.Utilities/ProjectGeneration.fs +++ b/tests/FSharp.Test.Utilities/ProjectGeneration.fs @@ -356,11 +356,19 @@ module ProjectOperations = type WorkflowContext = { Project: SyntheticProject - Signatures: Map } + Signatures: Map + Cursor : FSharp.Compiler.CodeAnalysis.FSharpSymbolUse option } type ProjectWorkflowBuilder(initialProject: SyntheticProject, ?checker: FSharpChecker) = - let checker = defaultArg checker (FSharpChecker.Create()) + let checker = + defaultArg + checker + (FSharpChecker.Create( + keepAllBackgroundSymbolUses = false, + enableBackgroundItemKeyStoreAndSemanticClassification = true, + enablePartialTypeChecking = true + )) let mapProject f workflow = async { @@ -392,7 +400,8 @@ type ProjectWorkflowBuilder(initialProject: SyntheticProject, ?checker: FSharpCh return { Project = initialProject - Signatures = Map signatures } + Signatures = Map signatures + Cursor = None } } member this.Run(workflow: Async) = @@ -442,6 +451,37 @@ type ProjectWorkflowBuilder(initialProject: SyntheticProject, ?checker: FSharpCh return { ctx with Signatures = ctx.Signatures.Add(fileId, newSignature) } } + /// Find a symbol using the provided range, mimicing placing a cursor on it in IDE scenarios + [] + member this.PlaceCursor(workflow: Async, fileId, line, colAtEndOfNames, fullLine, symbolNames) = + async { + let! ctx = workflow + let! results = checkFile fileId ctx.Project checker + let typeCheckResults = getTypeCheckResult results + + let su = typeCheckResults.GetSymbolUseAtLocation(line,colAtEndOfNames,fullLine,symbolNames) + + return {ctx with Cursor = su} + } + + + + /// Find all references within a single file, results are provided to the 'processResults' function + [] + member this.FindAllReferences(workflow: Async, fileId: string, processResults) = + async{ + let! ctx = workflow + let po = ctx.Project.GetProjectOptions checker + let s = ctx.Cursor |> Option.defaultWith (fun () -> failwith $"Please place cursor at a valid location via {nameof(this.PlaceCursor)} first") + let file = ctx.Project.Find fileId + let absFileName = ctx.Project.ProjectDir ++ file.FileName + let! results = checker.FindBackgroundReferencesInFile(absFileName,po, s.Symbol) + + processResults (results |> Seq.toList) + + return ctx + } + /// Parse and type check given file and process the results using `processResults` function. [] member this.CheckFile(workflow: Async, fileId: string, processResults) =