Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/release-notes/.FSharp.Compiler.Service/11.0.100.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
### Fixed

* Fix IntelliSense not suggesting later named arguments after the first one is autocompleted, for methods (including overloaded methods like `Task.Factory.StartNew`). ([Issue #19906](https://github.com/dotnet/fsharp/issues/19906), [PR #19940](https://github.com/dotnet/fsharp/pull/19940))
* Fix internal error (FS0193) when calling an indexed property setter with a named argument that matches an indexer parameter. ([Issue #16034](https://github.com/dotnet/fsharp/issues/16034), [PR #19851](https://github.com/dotnet/fsharp/pull/19851))
* Fix missing FS1182 ("unused binding") warning for unused `let` function bindings inside class types. ([Issue #13849](https://github.com/dotnet/fsharp/issues/13849), [PR #19805](https://github.com/dotnet/fsharp/pull/19805))
* Fix inner mutually-recursive `let rec ... and ...` functions under `--realsig+` not being lifted to top-level static methods (TLR), causing `FSharpFunc` closure allocations and loss of `tail.` opcodes — the large struct-mutual-recursion perf regression reported in [Issue #17607](https://github.com/dotnet/fsharp/issues/17607). ([PR #19882](https://github.com/dotnet/fsharp/pull/19882))
Expand Down
121 changes: 87 additions & 34 deletions src/Compiler/Service/FSharpCheckerResults.fs
Original file line number Diff line number Diff line change
Expand Up @@ -676,49 +676,102 @@ type internal TypeCheckInfo
| None -> None)
| _ -> [])

let GetNamedParametersAndSettableFields endOfExprPos allowObsolete =
let GetNamedParametersAndSettableFields endOfExprPos cursorLine (lineStr: string) allowObsolete =
let cnrs =
GetCapturedNameResolutions endOfExprPos ResolveOverloads.No
|> ResizeArray.toList
|> List.rev

let result =
match cnrs with
| CNR(Item.CtorGroup(_, (ctor :: _ as ctors)), _, denv, nenv, ad, m) :: _ ->
let props =
ResolveCompletionsInType
ncenv
nenv
ResolveCompletionTargets.SettablePropertiesAndFields
m
ad
false
ctor.ApparentEnclosingType
allowObsolete

let parameters = CollectParameters ctors amap m
let items = props @ parameters
Some(denv, m, items)
| CNR(Item.MethodGroup(_, methods, _), _, denv, nenv, ad, m) :: _ ->
let props =
methods
|> List.collect (fun meth ->
let retTy = meth.GetFSharpReturnType(amap, m, meth.FormalMethodInst)

ResolveCompletionsInType
// For overloaded calls with a malformed argument list, the type-checker can abandon
// resolution before publishing the Item.MethodGroup CNR. In that case, re-resolve the
// long-ident immediately to the left of endOfExprPos in the best-known name resolution
// environment so named-argument completion still works.
// Only attempt this when endOfExprPos is on the cursor's line, since lineStr is the
// cursor's line - using it to look at columns on a different line of a multi-line call
// site could match an unrelated identifier.
let tryRecoverGroupFromLineText () =
if
endOfExprPos.Line <> cursorLine
|| endOfExprPos.Column <= 0
|| endOfExprPos.Column > lineStr.Length
then
None
else
let plid = QuickParse.GetPartialLongNameEx(lineStr, endOfExprPos.Column - 1)
let lastName = plid.PartialIdent

if String.IsNullOrEmpty lastName then
None
else
let (nenv, ad), m = GetBestEnvForPos endOfExprPos

let items =
ResolvePartialLongIdent
ncenv
nenv
ResolveCompletionTargets.SettablePropertiesAndFields
(ConstraintSolver.IsApplicableMethApprox g amap m)
m
ad
plid.QualifyingIdents
false
retTy
allowObsolete)

let parameters = CollectParameters methods amap m
let items = props @ parameters
Some(denv, m, items)
| _ -> None
items
|> List.tryPick (fun item ->
match item with
| Item.CtorGroup(name, (_ :: _ as ctors)) when name = lastName ->
Some(nenv.DisplayEnv, nenv, ad, m, Choice1Of2 ctors)
| Item.MethodGroup(name, (_ :: _ as methods), _) when name = lastName ->
Some(nenv.DisplayEnv, nenv, ad, m, Choice2Of2 methods)
| _ -> None)

let groupForCtor (ctors: MethInfo list) denv nenv ad m =
let ctor = List.head ctors

let props =
ResolveCompletionsInType
ncenv
nenv
ResolveCompletionTargets.SettablePropertiesAndFields
m
ad
false
ctor.ApparentEnclosingType
allowObsolete

let parameters = CollectParameters ctors amap m
denv, m, props @ parameters

let groupForMethods (methods: MethInfo list) denv nenv ad m =
let props =
methods
|> List.collect (fun meth ->
let retTy = meth.GetFSharpReturnType(amap, m, meth.FormalMethodInst)

ResolveCompletionsInType ncenv nenv ResolveCompletionTargets.SettablePropertiesAndFields m ad false retTy allowObsolete)

let parameters = CollectParameters methods amap m
denv, m, props @ parameters

// Walk the reversed CNR list and pick the first Ctor/MethodGroup, instead of matching the head only.
// Overload refinement or trailing partial arguments can push the original group off the head.
let pickedFromCnrs =
cnrs
|> List.tryPick (fun cnr ->
match cnr.Item with
| Item.CtorGroup(_, (_ :: _ as ctors)) ->
Some(groupForCtor ctors cnr.DisplayEnv cnr.NameResolutionEnv cnr.AccessorDomain cnr.Range)
| Item.MethodGroup(_, (_ :: _ as methods), _) ->
Some(groupForMethods methods cnr.DisplayEnv cnr.NameResolutionEnv cnr.AccessorDomain cnr.Range)
| _ -> None)

let result =
match pickedFromCnrs with
| Some _ -> pickedFromCnrs
| None ->
match tryRecoverGroupFromLineText () with
| Some(denv, nenv, ad, m, Choice1Of2 ctors) -> Some(groupForCtor ctors denv nenv ad m)
| Some(denv, nenv, ad, m, Choice2Of2 methods) -> Some(groupForMethods methods denv nenv ad m)
| None -> None

match result with
| None -> NameResResult.Empty
Expand Down Expand Up @@ -1953,7 +2006,7 @@ type internal TypeCheckInfo
// Completion at ' SomeMethod( ... ) ' or ' [<SomeAttribute( ... )>] ' with named arguments
| Some(CompletionContext.ParameterList(endPos, fields)) ->
let results =
GetNamedParametersAndSettableFields endPos options.SuggestObsoleteSymbols
GetNamedParametersAndSettableFields endPos line lineStr options.SuggestObsoleteSymbols

let declaredItems = getDeclaredItemsNotInRangeOpWithAllSymbols ()

Expand All @@ -1977,7 +2030,7 @@ type internal TypeCheckInfo
})

match declaredItems with
| None -> Some(toCompletionItems (items, denv, m))
| None -> Some(filtered, denv, m)
| Some(declItems, declaredDisplayEnv, declaredRange) -> Some(filtered @ declItems, declaredDisplayEnv, declaredRange)
| _ -> declaredItems

Expand Down
68 changes: 67 additions & 1 deletion tests/FSharp.Compiler.Service.Tests/CompletionTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -860,4 +860,70 @@ let normalize' x = x + 1
normaliz{caret}
"""

assertHasItemWithNames [ "normalize'" ] info
assertHasItemWithNames [ "normalize'" ] info

// Tests for https://github.com/dotnet/fsharp/issues/19906
// Named-argument completion must continue suggesting later named args after the first.

[<Fact>]
let ``Issue 19906 - non-overloaded method - second named arg suggested`` () =
let info = Checker.getCompletionInfo """
type T() = member _.M(apple:int, banana:int, cherry:int) = ()
let t = T()
let _ = t.M(apple=1, b{caret})
"""
assertHasItemWithNames ["banana"; "cherry"] info
assertHasNoItemsWithNames ["apple"] info

[<Fact>]
let ``Issue 19906 - overloaded method - Task.Factory.StartNew second named arg suggested`` () =
let info = Checker.getCompletionInfo """
open System.Threading.Tasks
let _ = Task.Factory.StartNew(action=(fun _ -> ()), s{caret})
"""
assertHasItemWithNames ["state"; "cancellationToken"; "scheduler"; "creationOptions"] info
assertHasNoItemsWithNames ["action"] info

[<Fact>]
let ``Issue 19906 - overloaded method - third named arg suggested`` () =
let info = Checker.getCompletionInfo """
open System.Threading.Tasks
let _ = Task.Factory.StartNew(action=(fun _ -> ()), state=null, c{caret})
"""
assertHasItemWithNames ["cancellationToken"; "creationOptions"] info

[<Fact>]
let ``Issue 19906 - optional args - second optional arg suggested`` () =
let info = Checker.getCompletionInfo """
type T() = static member F(?x:int, ?y:int, ?z:int) = ()
let _ = T.F(?x=1, ?y{caret})
"""
assertHasItemWithNames ["y"; "z"] info

[<Fact>]
let ``Issue 19906 - regression guard - zero-arg ctor with settable props still works`` () =
let info = Checker.getCompletionInfo """
type R() =
member val Apple = "" with get, set
member val Banana = "" with get, set
member val Cherry = "" with get, set
let _ = R(Apple="x", B{caret})
"""
assertHasItemWithNames ["Banana"; "Cherry"] info

[<Fact>]
let ``Issue 19906 - regression guard - positional then partial still works`` () =
let info = Checker.getCompletionInfo """
open System.Threading.Tasks
let _ = Task.Factory.StartNew((fun _ -> ()), s{caret})
"""
assertHasItemWithNames ["state"; "cancellationToken"; "scheduler"; "creationOptions"] info

[<Fact>]
let ``Issue 19906 - regression guard - dotted completion inside named-arg RHS`` () =
let info = Checker.getCompletionInfo """
let s = "x"
let _ = System.Uri(uriString = s.{caret}, kind = System.UriKind.Absolute)
"""
assertHasItemWithNames ["Length"; "Substring"] info
assertHasNoItemsWithNames ["uriString"; "kind"] info
Loading