diff --git a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md index 27f2c0070d7..6753d0ee080 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md +++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md @@ -150,6 +150,7 @@ * IL: fix leaking binary view ([PR #20250](https://github.com/dotnet/fsharp/pull/20250)) ### Added +* `NavigableItem` carries `ParameterCount` and `TypeParameterCount`, the counts C# and VB give Navigate To to order matches that are otherwise equal. ([PR #20531](https://github.com/dotnet/fsharp/pull/20531)) * Added a "most concrete" tiebreaker for overload resolution (`--langversion:preview`). ([RFC FS-1340](https://github.com/fsharp/fslang-design/pull/834), [PR #19277](https://github.com/dotnet/fsharp/pull/19277)) * Added support for `OverloadResolutionPriorityAttribute` in overload resolution (`--langversion:preview`). ([RFC FS-1338](https://github.com/fsharp/fslang-design/pull/828), [PR #19277](https://github.com/dotnet/fsharp/pull/19277)) diff --git a/docs/release-notes/.VisualStudio/18.vNext.md b/docs/release-notes/.VisualStudio/18.vNext.md index ba03f663967..06f29a9f23b 100644 --- a/docs/release-notes/.VisualStudio/18.vNext.md +++ b/docs/release-notes/.VisualStudio/18.vNext.md @@ -5,6 +5,8 @@ ### Fixed +* Navigate To (Ctrl+T) orders F# matches of the same kind as it orders C# and Visual Basic ones: a match in the file being edited first, then by how far its folder is from that file, then by parameter and type parameter count and name. ([PR #20532](https://github.com/dotnet/fsharp/pull/20532)) + * Improve Find All References performance by throttling parallel typechecks. ([PR #20128](https://github.com/dotnet/fsharp/pull/20128)) * Fixed Rename incorrectly renaming `get` and `set` keywords for properties with explicit accessors. ([Issue #18270](https://github.com/dotnet/fsharp/issues/18270), [PR #19252](https://github.com/dotnet/fsharp/pull/19252)) * Fixed Find All References crash when F# project contains non-F# files like `.cshtml`. ([Issue #16394](https://github.com/dotnet/fsharp/issues/16394), [PR #19252](https://github.com/dotnet/fsharp/pull/19252)) diff --git a/src/Compiler/Service/ServiceNavigation.fs b/src/Compiler/Service/ServiceNavigation.fs index 41d565ebed4..3d2233690e4 100755 --- a/src/Compiler/Service/ServiceNavigation.fs +++ b/src/Compiler/Service/ServiceNavigation.fs @@ -762,10 +762,37 @@ type NavigableItem = IsSignature: bool Kind: NavigableItemKind Container: NavigableContainer + ParameterCount: int + TypeParameterCount: int } [] module NavigateTo = + let private typeParameterCountOf (typars: SynTyparDecls option) = + match typars with + | Some typars -> typars.TyparDecls.Length + | None -> 0 + + let rec private isUnitType synType = + match synType with + | SynType.LongIdent(SynLongIdent([ id ], _, _)) -> id.idText = "unit" + | SynType.Paren(innerType, _) + | SynType.WithGlobalConstraints(innerType, _, _) -> isUnitType innerType + | _ -> false + + /// The parameters of the compiled method: the parser leaves a solitary unit argument of a signature in its arity, + /// where a binding has already dropped it. + let private parameterCountOfSignature (SynValInfo(curriedArgInfos, _)) (synType: SynType) = + match curriedArgInfos, synType with + | [ [ _ ] ], SynType.Fun(argType = argType) when isUnitType argType -> 0 + | [ [ _ ] ], SynType.WithGlobalConstraints(SynType.Fun(argType = argType), _, _) when isUnitType argType -> 0 + | _ -> List.sumBy List.length curriedArgInfos + + let private parameterCountOfBinding (SynValData(memberFlags = memberFlags; valInfo = SynValInfo(curriedArgInfos, _))) = + match memberFlags, curriedArgInfos with + | Some memberFlags, _self :: argInfos when memberFlags.IsInstance -> List.sumBy List.length argInfos + | _ -> List.sumBy List.length curriedArgInfos + let GetNavigableItems (parsedInput: ParsedInput) : NavigableItem[] = let convertToDisplayName name = @@ -776,7 +803,7 @@ module NavigateTo = let result = ResizeArray() - let addLongIdent kind (lid: LongIdent) (isSignature: bool) (container: NavigableContainer) = + let addLongIdent kind (lid: LongIdent) (isSignature: bool) (container: NavigableContainer) typeParameterCount = if not lid.IsEmpty then let name = textOfLid lid @@ -787,10 +814,12 @@ module NavigateTo = IsSignature = isSignature Kind = kind Container = container + ParameterCount = 0 + TypeParameterCount = typeParameterCount } |> result.Add - let addIdent kind (id: Ident) (isSignature: bool) (container: NavigableContainer) = + let addIdentWithArity kind (id: Ident) (isSignature: bool) (container: NavigableContainer) parameterCount typeParameterCount = if not (String.IsNullOrEmpty id.idText) then let name = convertToDisplayName id.idText @@ -801,11 +830,16 @@ module NavigateTo = IsSignature = isSignature Kind = kind Container = container + ParameterCount = parameterCount + TypeParameterCount = typeParameterCount } |> result.Add + let addIdent kind id isSignature container = + addIdentWithArity kind id isSignature container 0 0 + let addModule lid isSig container = - addLongIdent NavigableItemKind.Module lid isSig container + addLongIdent NavigableItemKind.Module lid isSig container 0 let addModuleAbbreviation (id: Ident) isSig container = addIdent NavigableItemKind.ModuleAbbreviation id isSig container @@ -816,14 +850,17 @@ module NavigateTo = NavigableContainer.Container(NavigableContainerType.Exception, [ id.idText ], container) let addComponentInfo containerType kind (info: SynComponentInfo) isSig container = + let (SynComponentInfo(typeParams = typeParams)) = info let lid = info.LongIdent - addLongIdent kind lid isSig container + addLongIdent kind lid isSig container (typeParameterCountOf typeParams) NavigableContainer.Container(containerType, pathOfLid lid, container) let addValSig kind synValSig isSig container = - let (SynValSig(ident = SynIdent(id, _))) = synValSig - addIdent kind id isSig container + let (SynValSig(ident = SynIdent(id, _); explicitTypeParams = SynValTyparDecls(typars, _); synType = synType; arity = arity)) = + synValSig + + addIdentWithArity kind id isSig container (parameterCountOfSignature arity synType) (typeParameterCountOf typars) let addField synField isSig container = let (SynField(idOpt = id)) = synField @@ -861,17 +898,25 @@ module NavigateTo = | Some mf -> mapMemberKind mf.MemberKind | _ -> NavigableItemKind.ModuleValue + let typeParameterCount = + match headPat with + | SynPat.LongIdent(typarDecls = Some(SynValTyparDecls(typars, _))) -> typeParameterCountOf typars + | _ -> 0 + + let addBindingIdent id = + addIdentWithArity kind id false container (parameterCountOfBinding valData) typeParameterCount + match headPat with | SynPat.LongIdent(longDotId = SynLongIdent([ _; id ], _, _)) -> // instance members - addIdent kind id false container + addBindingIdent id | SynPat.LongIdent(longDotId = SynLongIdent([ id ], _, _)) -> // functions - addIdent kind id false container + addBindingIdent id | SynPat.Named(SynIdent(id, _), _, _, _) | SynPat.As(_, SynPat.Named(SynIdent(id, _), _, _, _), _) -> // values - addIdent kind id false container + addBindingIdent id | _ -> () let addMember valSig (memberFlags: SynMemberFlags) isSig container = diff --git a/src/Compiler/Service/ServiceNavigation.fsi b/src/Compiler/Service/ServiceNavigation.fsi index cfccd6ef20c..49475c7f93b 100755 --- a/src/Compiler/Service/ServiceNavigation.fsi +++ b/src/Compiler/Service/ServiceNavigation.fsi @@ -112,12 +112,20 @@ type NavigableContainer = member Name: string type NavigableItem = - { Name: string - NeedsBackticks: bool - Range: range - IsSignature: bool - Kind: NavigableItemKind - Container: NavigableContainer } + { + Name: string + NeedsBackticks: bool + Range: range + IsSignature: bool + Kind: NavigableItemKind + Container: NavigableContainer + /// The number of parameters of the method the declaration compiles to, as C# and VB count them to order + /// equally good Navigate To matches: every curried and tupled argument, without the instance and without a + /// solitary unit argument. + ParameterCount: int + /// The number of explicitly declared type parameters. + TypeParameterCount: int + } [] module public NavigateTo = diff --git a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.bsl b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.bsl index 5c9c346b613..719aa266360 100644 --- a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.bsl +++ b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.bsl @@ -3946,10 +3946,14 @@ FSharp.Compiler.EditorServices.NavigableItem: FSharp.Compiler.Text.Range Range FSharp.Compiler.EditorServices.NavigableItem: FSharp.Compiler.Text.Range get_Range() FSharp.Compiler.EditorServices.NavigableItem: Int32 GetHashCode() FSharp.Compiler.EditorServices.NavigableItem: Int32 GetHashCode(System.Collections.IEqualityComparer) +FSharp.Compiler.EditorServices.NavigableItem: Int32 ParameterCount +FSharp.Compiler.EditorServices.NavigableItem: Int32 TypeParameterCount +FSharp.Compiler.EditorServices.NavigableItem: Int32 get_ParameterCount() +FSharp.Compiler.EditorServices.NavigableItem: Int32 get_TypeParameterCount() FSharp.Compiler.EditorServices.NavigableItem: System.String Name FSharp.Compiler.EditorServices.NavigableItem: System.String ToString() FSharp.Compiler.EditorServices.NavigableItem: System.String get_Name() -FSharp.Compiler.EditorServices.NavigableItem: Void .ctor(System.String, Boolean, FSharp.Compiler.Text.Range, Boolean, FSharp.Compiler.EditorServices.NavigableItemKind, FSharp.Compiler.EditorServices.NavigableContainer) +FSharp.Compiler.EditorServices.NavigableItem: Void .ctor(System.String, Boolean, FSharp.Compiler.Text.Range, Boolean, FSharp.Compiler.EditorServices.NavigableItemKind, FSharp.Compiler.EditorServices.NavigableContainer, Int32, Int32) FSharp.Compiler.EditorServices.NavigableItemKind+Tags: Int32 Constructor FSharp.Compiler.EditorServices.NavigableItemKind+Tags: Int32 EnumCase FSharp.Compiler.EditorServices.NavigableItemKind+Tags: Int32 Exception diff --git a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.Tests.fsproj b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.Tests.fsproj index e043d8554ad..f6fb240a665 100644 --- a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.Tests.fsproj +++ b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.Tests.fsproj @@ -51,6 +51,7 @@ + diff --git a/tests/FSharp.Compiler.Service.Tests/NavigateToTests.fs b/tests/FSharp.Compiler.Service.Tests/NavigateToTests.fs new file mode 100644 index 00000000000..cf67e20c870 --- /dev/null +++ b/tests/FSharp.Compiler.Service.Tests/NavigateToTests.fs @@ -0,0 +1,63 @@ +module FSharp.Compiler.Service.Tests.NavigateToTests + +open FSharp.Compiler.EditorServices +open FSharp.Compiler.Service.Tests.Common +open FSharp.Compiler.Syntax +open Xunit + +let private implementation = + """ +let value = 1 +let curried a b = a + b +let tupledAndCurried (a, b) c = a + b + c +let takesUnit () = 1 +let generic<'T> (x: 'T) = x + +type C<'T, 'U>() = + member _.Method(a: int, b: int) = a + b + member _.Property = 1 + static member Static x y = x + y +""" + +let private signature = + """ +module M + +val curried: int -> int -> int +val takesUnit: unit -> int +val generic<'T> : 'T * 'T -> 'T + +type C<'T> = + member Method: a: int * b: int -> int + abstract Abstract: unit -> unit +""" + +let private arityOf (parseTree: ParsedInput) name = + let item = + NavigateTo.GetNavigableItems parseTree + |> Array.find (fun item -> item.Name = name) + + item.ParameterCount, item.TypeParameterCount + +[] +[] +[] +[] +[] +[] +[] +[] +[] +[] +let ``A declaration in an implementation file counts the parameters it compiles to`` (name: string, parameterCount: int, typeParameterCount: int) = + Assert.Equal((parameterCount, typeParameterCount), arityOf (getParseResults implementation) name) + +[] +[] +[] +[] +[] +[] +[] +let ``A declaration in a signature file counts the parameters it compiles to`` (name: string, parameterCount: int, typeParameterCount: int) = + Assert.Equal((parameterCount, typeParameterCount), arityOf (getParseResultsOfSignatureFile signature) name) diff --git a/vsintegration/src/FSharp.Editor/Navigation/NavigateToSearchService.fs b/vsintegration/src/FSharp.Editor/Navigation/NavigateToSearchService.fs index 546b00e1b16..56a18f876db 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/NavigateToSearchService.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/NavigateToSearchService.fs @@ -179,7 +179,9 @@ type internal FSharpNavigateToSearchService ImmutableArray.Create(TaggedText(TextTags.Text, item.Name)), document, sourceSpan - ) + ), + item.ParameterCount, + item.TypeParameterCount ) | _ -> () |] diff --git a/vsintegration/tests/FSharp.Editor.Tests/NavigateToSearchServiceTests.fs b/vsintegration/tests/FSharp.Editor.Tests/NavigateToSearchServiceTests.fs index 08816ea6191..ccd9525c2aa 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/NavigateToSearchServiceTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/NavigateToSearchServiceTests.fs @@ -72,3 +72,8 @@ module HeyHo = [] let ``nested containers`` () = assertResultsContain "hh.a.b.g.d" "Delta" + + [] + let ``results carry the counts Navigate To sorts equal matches by`` () = + let result = navigateToSearch "+>" |> Seq.find (fun i -> i.Name = "+>") + Assert.Equal((2, 0), (result.ParameterCount, result.TypeParameterCount))