Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Aligning constructor and type for Rename + FindAllReferences #14350

Merged
Merged
1 change: 1 addition & 0 deletions src/Compiler/Service/ItemKey.fs
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,7 @@ and [<Sealed>] 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@
<Compile Include="Signatures\SigGenerationRoundTripTests.fs" />
<Compile Include="FSharpChecker\CommonWorkflows.fs" />
<Compile Include="FSharpChecker\SymbolUse.fs" />
<Compile Include="FSharpChecker\FindReferences.fs" />
<None Include="**\*.cs;**\*.fs;**\*.fsx;**\*.fsi" Exclude="@(Compile)">
<Link>%(RelativeDir)\TestSource\%(Filename)%(Extension)</Link>
</None>
Expand Down
Original file line number Diff line number Diff line change
@@ -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())

[<Fact>]
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) )
}


[<Fact>]
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<FSharp.Compiler.Text.range>) ->
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) )

}

[<Fact>]
let ``Finding usage of type via FindReference works across files`` () =
T-Gro marked this conversation as resolved.
Show resolved Hide resolved
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<FSharp.Compiler.Text.range>) ->
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) )

}

46 changes: 43 additions & 3 deletions tests/FSharp.Test.Utilities/ProjectGeneration.fs
Original file line number Diff line number Diff line change
Expand Up @@ -356,11 +356,19 @@ module ProjectOperations =

type WorkflowContext =
{ Project: SyntheticProject
Signatures: Map<string, string> }
Signatures: Map<string, string>
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 {
Expand Down Expand Up @@ -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<WorkflowContext>) =
Expand Down Expand Up @@ -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
[<CustomOperation "placeCursor">]
member this.PlaceCursor(workflow: Async<WorkflowContext>, 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}
}

T-Gro marked this conversation as resolved.
Show resolved Hide resolved


/// Find all references within a single file, results are provided to the 'processResults' function
[<CustomOperation "findAllReferences">]
member this.FindAllReferences(workflow: Async<WorkflowContext>, fileId: string, processResults) =
T-Gro marked this conversation as resolved.
Show resolved Hide resolved
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 placeCursor first")
T-Gro marked this conversation as resolved.
Show resolved Hide resolved
let file = ctx.Project.Find fileId
let absFileName = ctx.Project.ProjectDir ++ file.FileName
let! results = checker.FindBackgroundReferencesInFile(absFileName,po, s.Symbol)
T-Gro marked this conversation as resolved.
Show resolved Hide resolved

processResults (results |> Seq.toList)

return ctx
}

/// Parse and type check given file and process the results using `processResults` function.
[<CustomOperation "checkFile">]
member this.CheckFile(workflow: Async<WorkflowContext>, fileId: string, processResults) =
Expand Down