From e80f39ace272048571283fd91cb6e67cede9148f Mon Sep 17 00:00:00 2001 From: kerams Date: Thu, 3 Aug 2023 18:14:38 +0200 Subject: [PATCH 1/8] Implement completions for union case fields in patterns --- src/Compiler/Service/FSharpCheckerResults.fs | 56 ++++++++++++++++--- src/Compiler/Service/ServiceParsedInputOps.fs | 46 ++++++++++----- .../Service/ServiceParsedInputOps.fsi | 9 ++- ...ervice.SurfaceArea.netstandard20.debug.bsl | 13 ++++- ...vice.SurfaceArea.netstandard20.release.bsl | 13 ++++- .../CompletionProviderTests.fs | 24 ++++++++ 6 files changed, 134 insertions(+), 27 deletions(-) diff --git a/src/Compiler/Service/FSharpCheckerResults.fs b/src/Compiler/Service/FSharpCheckerResults.fs index 819921750a3..c397fb6b4fa 100644 --- a/src/Compiler/Service/FSharpCheckerResults.fs +++ b/src/Compiler/Service/FSharpCheckerResults.fs @@ -886,6 +886,7 @@ type internal TypeCheckInfo | minfo :: _ -> CompletionItemKind.Method minfo.IsExtensionMember | Item.AnonRecdField _ | Item.RecdField _ + | Item.UnionCaseField _ | Item.Property _ -> CompletionItemKind.Property | Item.Event _ -> CompletionItemKind.Event | Item.ILField _ @@ -900,7 +901,6 @@ type internal TypeCheckInfo | Item.TypeVar _ | Item.Types _ | Item.UnionCase _ - | Item.UnionCaseField _ | Item.UnqualifiedType _ | Item.NewDef _ | Item.SetterArg _ @@ -1049,6 +1049,25 @@ type internal TypeCheckInfo Some(overridableMethods, nenv.DisplayEnv, m) | _ -> None) + /// Gets all field identifiers of a union case that can be referred to in a pattern. + let GetUnionCaseFields caseIdRange alreadyReferencedFields = + sResolutions.CapturedNameResolutions + |> ResizeArray.tryPick (fun r -> + match r.Item with + | Item.UnionCase (uci, _) when equals r.Range caseIdRange -> + uci.UnionCase.RecdFields + |> List.indexed + |> List.choose (fun (index, field) -> + if List.contains field.LogicalName alreadyReferencedFields then + None + else + Item.UnionCaseField(uci, index) + |> ItemWithNoInst + |> CompletionItem ValueNone ValueNone + |> Some) + |> Some + | _ -> None) + let getItem (x: ItemWithInst) = x.Item let GetDeclaredItems @@ -1549,6 +1568,12 @@ type internal TypeCheckInfo denv, m) + | Some (CompletionContext.Pattern (PatternContext.UnionCaseFieldIdentifier (referencedFields, caseIdRange))) -> + GetUnionCaseFields caseIdRange referencedFields + |> Option.map (fun completions -> + let (nenv, _ad), m = GetBestEnvForPos pos + completions, nenv.DisplayEnv, m) + | Some (CompletionContext.Pattern patternContext) -> let declaredItems = GetDeclaredItems( @@ -1583,19 +1608,36 @@ type internal TypeCheckInfo let indexOrName, caseIdRange = match patternContext with - | PatternContext.PositionalUnionCaseField (index, m) -> Choice1Of2 index, m + | PatternContext.PositionalUnionCaseField (index, _, m) -> Choice1Of2 index, m | PatternContext.NamedUnionCaseField (name, m) -> Choice2Of2 name, m - | PatternContext.Other -> Choice1Of2 None, range0 + | _ -> Choice1Of2 None, range0 - // No special handling for PatternContext.Other other than filtering out non-literal values + // No special handling other than filtering out non-literal values if equals caseIdRange range0 then declaredItems else - GetCapturedNameResolutions caseIdRange.End ResolveOverloads.Yes + // When the user types `fun (Case (x| )) ->`, we do not yet know whether the intention is to use positional or named arguments, + // so let's show options for both. + let fields patternContext (uci: UnionCaseInfo) = + match patternContext with + | PatternContext.PositionalUnionCaseField (Some 0, true, _) -> + uci.UnionCase.RecdFields + |> List.mapi (fun index _ -> + Item.UnionCaseField(uci, index) + |> ItemWithNoInst + |> CompletionItem ValueNone ValueNone) + | _ -> [] + + sResolutions.CapturedNameResolutions |> ResizeArray.tryPick (fun r -> match r.Item with - | Item.UnionCase (uci, _) -> - let list = declaredItems |> Option.map p13 |> Option.defaultValue [] + | Item.UnionCase (uci, _) when equals r.Range caseIdRange -> + let list = + declaredItems + |> Option.map p13 + |> Option.defaultValue [] + |> List.append (fields patternContext uci) + Some(SuggestNameForUnionCaseFieldPattern g caseIdRange.End pos uci indexOrName list, r.DisplayEnv, r.Range) | _ -> None) |> Option.orElse declaredItems diff --git a/src/Compiler/Service/ServiceParsedInputOps.fs b/src/Compiler/Service/ServiceParsedInputOps.fs index cb939cf3cdc..c2f12543e7c 100644 --- a/src/Compiler/Service/ServiceParsedInputOps.fs +++ b/src/Compiler/Service/ServiceParsedInputOps.fs @@ -52,13 +52,16 @@ type RecordContext = [] type PatternContext = - /// Completing union case field in a pattern (e.g. fun (Some v|) -> ) + /// Completing union case field pattern (e.g. fun (Some v| ) -> ) or fun (Some (v| )) -> ) /// fieldIndex None signifies that the case identifier is followed by a single field, outside of parentheses - | PositionalUnionCaseField of fieldIndex: int option * caseIdRange: range + | PositionalUnionCaseField of fieldIndex: int option * isTheOnlyField: bool * caseIdRange: range - /// Completing union case field in a pattern (e.g. fun (Some (Value = v|) -> ) + /// Completing union case field pattern (e.g. fun (Some (Value = v| )) -> ) | NamedUnionCaseField of fieldName: string * caseIdRange: range + /// Completing union case field identifier in a pattern (e.g. fun (Case (field1 = a; fie| )) -> ) + | UnionCaseFieldIdentifier of referencedFields: string list * caseIdRange: range + /// Any other position in a pattern that does not need special handling | Other @@ -1261,20 +1264,33 @@ module ParsedInput = let rec TryGetCompletionContextInPattern suppressIdentifierCompletions (pat: SynPat) previousContext pos = match pat with | SynPat.LongIdent (longDotId = id) when rangeContainsPos id.Range pos -> Some(CompletionContext.Pattern PatternContext.Other) - | SynPat.LongIdent (argPats = SynArgPats.NamePatPairs (pats = pats); longDotId = id) -> + | SynPat.LongIdent (argPats = SynArgPats.NamePatPairs (pats = pats); longDotId = caseId; range = m) when rangeContainsPos m pos -> pats - |> List.tryPick (fun (patId, _, pat) -> - if rangeContainsPos patId.idRange pos then - Some CompletionContext.Invalid + |> List.tryPick (fun (fieldId, _, pat) -> + if rangeContainsPos fieldId.idRange pos then + let referencedFields = pats |> List.map (fun (id, _, _) -> id.idText) + Some(CompletionContext.Pattern(PatternContext.UnionCaseFieldIdentifier(referencedFields, caseId.Range))) else - let context = Some(PatternContext.NamedUnionCaseField(patId.idText, id.Range)) + let context = Some(PatternContext.NamedUnionCaseField(fieldId.idText, caseId.Range)) TryGetCompletionContextInPattern suppressIdentifierCompletions pat context pos) - | SynPat.LongIdent (argPats = SynArgPats.Pats pats; longDotId = id) -> + | SynPat.LongIdent (argPats = SynArgPats.Pats pats; longDotId = id; range = m) when rangeContainsPos m pos -> match pats with - | [ SynPat.Named _ as pat ] -> - TryGetCompletionContextInPattern false pat (Some(PatternContext.PositionalUnionCaseField(None, id.Range))) pos - | [ SynPat.Paren (SynPat.Tuple _ | SynPat.Named _ as pat, _) ] -> - TryGetCompletionContextInPattern false pat (Some(PatternContext.PositionalUnionCaseField(Some 0, id.Range))) pos + + // fun (Some v| ) -> + | [ SynPat.Named _ ] -> Some(CompletionContext.Pattern(PatternContext.PositionalUnionCaseField(None, true, id.Range))) + + // fun (Case (a| )) -> + // This could either be the first positional field pattern or the user might want to use named pairs + | [ SynPat.Paren (SynPat.Named _, _) ] -> + Some(CompletionContext.Pattern(PatternContext.PositionalUnionCaseField(Some 0, true, id.Range))) + + // fun (Case (a| , b)) -> + | [ SynPat.Paren (SynPat.Tuple (elementPats = pats) as pat, _) ] -> + let context = + Some(PatternContext.PositionalUnionCaseField(Some 0, pats.Length = 1, id.Range)) + + TryGetCompletionContextInPattern false pat context pos + | _ -> pats |> List.tryPick (fun pat -> TryGetCompletionContextInPattern false pat None pos) @@ -1288,8 +1304,8 @@ module ParsedInput = |> List.tryPick (fun (i, pat) -> let context = match previousContext with - | Some (PatternContext.PositionalUnionCaseField (_, caseIdRange)) -> - Some(PatternContext.PositionalUnionCaseField(Some i, caseIdRange)) + | Some (PatternContext.PositionalUnionCaseField (_, isTheOnlyField, caseIdRange)) -> + Some(PatternContext.PositionalUnionCaseField(Some i, isTheOnlyField, caseIdRange)) | _ -> // No preceding LongIdent => this is a tuple deconstruction None diff --git a/src/Compiler/Service/ServiceParsedInputOps.fsi b/src/Compiler/Service/ServiceParsedInputOps.fsi index e94965bb2bd..1f9470379fc 100644 --- a/src/Compiler/Service/ServiceParsedInputOps.fsi +++ b/src/Compiler/Service/ServiceParsedInputOps.fsi @@ -24,13 +24,16 @@ type public RecordContext = [] type public PatternContext = - /// Completing union case field in a pattern (e.g. fun (Some v|) -> ) + /// Completing union case field pattern (e.g. fun (Some v| ) -> ) or fun (Some (v| )) -> ) /// fieldIndex None signifies that the case identifier is followed by a single field, outside of parentheses - | PositionalUnionCaseField of fieldIndex: int option * caseIdRange: range + | PositionalUnionCaseField of fieldIndex: int option * isTheOnlyField: bool * caseIdRange: range - /// Completing union case field in a pattern (e.g. fun (Some (Value = v|) -> ) + /// Completing union case field pattern (e.g. fun (Some (Value = v| )) -> ) | NamedUnionCaseField of fieldName: string * caseIdRange: range + /// Completing union case field identifier in a pattern (e.g. fun (Case (field1 = a; fie| )) -> ) + | UnionCaseFieldIdentifier of referencedFields: string list * caseIdRange: range + /// Any other position in a pattern that does not need special handling | Other diff --git a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.debug.bsl b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.debug.bsl index 384a2dd66fc..28d90e3f467 100644 --- a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.debug.bsl +++ b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.debug.bsl @@ -3577,6 +3577,8 @@ FSharp.Compiler.EditorServices.PatternContext+NamedUnionCaseField: FSharp.Compil FSharp.Compiler.EditorServices.PatternContext+NamedUnionCaseField: FSharp.Compiler.Text.Range get_caseIdRange() FSharp.Compiler.EditorServices.PatternContext+NamedUnionCaseField: System.String fieldName FSharp.Compiler.EditorServices.PatternContext+NamedUnionCaseField: System.String get_fieldName() +FSharp.Compiler.EditorServices.PatternContext+PositionalUnionCaseField: Boolean get_isTheOnlyField() +FSharp.Compiler.EditorServices.PatternContext+PositionalUnionCaseField: Boolean isTheOnlyField FSharp.Compiler.EditorServices.PatternContext+PositionalUnionCaseField: FSharp.Compiler.Text.Range caseIdRange FSharp.Compiler.EditorServices.PatternContext+PositionalUnionCaseField: FSharp.Compiler.Text.Range get_caseIdRange() FSharp.Compiler.EditorServices.PatternContext+PositionalUnionCaseField: Microsoft.FSharp.Core.FSharpOption`1[System.Int32] fieldIndex @@ -3584,22 +3586,31 @@ FSharp.Compiler.EditorServices.PatternContext+PositionalUnionCaseField: Microsof FSharp.Compiler.EditorServices.PatternContext+Tags: Int32 NamedUnionCaseField FSharp.Compiler.EditorServices.PatternContext+Tags: Int32 Other FSharp.Compiler.EditorServices.PatternContext+Tags: Int32 PositionalUnionCaseField +FSharp.Compiler.EditorServices.PatternContext+Tags: Int32 UnionCaseFieldIdentifier +FSharp.Compiler.EditorServices.PatternContext+UnionCaseFieldIdentifier: FSharp.Compiler.Text.Range caseIdRange +FSharp.Compiler.EditorServices.PatternContext+UnionCaseFieldIdentifier: FSharp.Compiler.Text.Range get_caseIdRange() +FSharp.Compiler.EditorServices.PatternContext+UnionCaseFieldIdentifier: Microsoft.FSharp.Collections.FSharpList`1[System.String] get_referencedFields() +FSharp.Compiler.EditorServices.PatternContext+UnionCaseFieldIdentifier: Microsoft.FSharp.Collections.FSharpList`1[System.String] referencedFields FSharp.Compiler.EditorServices.PatternContext: Boolean Equals(FSharp.Compiler.EditorServices.PatternContext) FSharp.Compiler.EditorServices.PatternContext: Boolean Equals(System.Object) FSharp.Compiler.EditorServices.PatternContext: Boolean Equals(System.Object, System.Collections.IEqualityComparer) FSharp.Compiler.EditorServices.PatternContext: Boolean IsNamedUnionCaseField FSharp.Compiler.EditorServices.PatternContext: Boolean IsOther FSharp.Compiler.EditorServices.PatternContext: Boolean IsPositionalUnionCaseField +FSharp.Compiler.EditorServices.PatternContext: Boolean IsUnionCaseFieldIdentifier FSharp.Compiler.EditorServices.PatternContext: Boolean get_IsNamedUnionCaseField() FSharp.Compiler.EditorServices.PatternContext: Boolean get_IsOther() FSharp.Compiler.EditorServices.PatternContext: Boolean get_IsPositionalUnionCaseField() +FSharp.Compiler.EditorServices.PatternContext: Boolean get_IsUnionCaseFieldIdentifier() FSharp.Compiler.EditorServices.PatternContext: FSharp.Compiler.EditorServices.PatternContext NewNamedUnionCaseField(System.String, FSharp.Compiler.Text.Range) -FSharp.Compiler.EditorServices.PatternContext: FSharp.Compiler.EditorServices.PatternContext NewPositionalUnionCaseField(Microsoft.FSharp.Core.FSharpOption`1[System.Int32], FSharp.Compiler.Text.Range) +FSharp.Compiler.EditorServices.PatternContext: FSharp.Compiler.EditorServices.PatternContext NewPositionalUnionCaseField(Microsoft.FSharp.Core.FSharpOption`1[System.Int32], Boolean, FSharp.Compiler.Text.Range) +FSharp.Compiler.EditorServices.PatternContext: FSharp.Compiler.EditorServices.PatternContext NewUnionCaseFieldIdentifier(Microsoft.FSharp.Collections.FSharpList`1[System.String], FSharp.Compiler.Text.Range) FSharp.Compiler.EditorServices.PatternContext: FSharp.Compiler.EditorServices.PatternContext Other FSharp.Compiler.EditorServices.PatternContext: FSharp.Compiler.EditorServices.PatternContext get_Other() FSharp.Compiler.EditorServices.PatternContext: FSharp.Compiler.EditorServices.PatternContext+NamedUnionCaseField FSharp.Compiler.EditorServices.PatternContext: FSharp.Compiler.EditorServices.PatternContext+PositionalUnionCaseField FSharp.Compiler.EditorServices.PatternContext: FSharp.Compiler.EditorServices.PatternContext+Tags +FSharp.Compiler.EditorServices.PatternContext: FSharp.Compiler.EditorServices.PatternContext+UnionCaseFieldIdentifier FSharp.Compiler.EditorServices.PatternContext: Int32 GetHashCode() FSharp.Compiler.EditorServices.PatternContext: Int32 GetHashCode(System.Collections.IEqualityComparer) FSharp.Compiler.EditorServices.PatternContext: Int32 Tag diff --git a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.release.bsl b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.release.bsl index 384a2dd66fc..28d90e3f467 100644 --- a/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.release.bsl +++ b/tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.release.bsl @@ -3577,6 +3577,8 @@ FSharp.Compiler.EditorServices.PatternContext+NamedUnionCaseField: FSharp.Compil FSharp.Compiler.EditorServices.PatternContext+NamedUnionCaseField: FSharp.Compiler.Text.Range get_caseIdRange() FSharp.Compiler.EditorServices.PatternContext+NamedUnionCaseField: System.String fieldName FSharp.Compiler.EditorServices.PatternContext+NamedUnionCaseField: System.String get_fieldName() +FSharp.Compiler.EditorServices.PatternContext+PositionalUnionCaseField: Boolean get_isTheOnlyField() +FSharp.Compiler.EditorServices.PatternContext+PositionalUnionCaseField: Boolean isTheOnlyField FSharp.Compiler.EditorServices.PatternContext+PositionalUnionCaseField: FSharp.Compiler.Text.Range caseIdRange FSharp.Compiler.EditorServices.PatternContext+PositionalUnionCaseField: FSharp.Compiler.Text.Range get_caseIdRange() FSharp.Compiler.EditorServices.PatternContext+PositionalUnionCaseField: Microsoft.FSharp.Core.FSharpOption`1[System.Int32] fieldIndex @@ -3584,22 +3586,31 @@ FSharp.Compiler.EditorServices.PatternContext+PositionalUnionCaseField: Microsof FSharp.Compiler.EditorServices.PatternContext+Tags: Int32 NamedUnionCaseField FSharp.Compiler.EditorServices.PatternContext+Tags: Int32 Other FSharp.Compiler.EditorServices.PatternContext+Tags: Int32 PositionalUnionCaseField +FSharp.Compiler.EditorServices.PatternContext+Tags: Int32 UnionCaseFieldIdentifier +FSharp.Compiler.EditorServices.PatternContext+UnionCaseFieldIdentifier: FSharp.Compiler.Text.Range caseIdRange +FSharp.Compiler.EditorServices.PatternContext+UnionCaseFieldIdentifier: FSharp.Compiler.Text.Range get_caseIdRange() +FSharp.Compiler.EditorServices.PatternContext+UnionCaseFieldIdentifier: Microsoft.FSharp.Collections.FSharpList`1[System.String] get_referencedFields() +FSharp.Compiler.EditorServices.PatternContext+UnionCaseFieldIdentifier: Microsoft.FSharp.Collections.FSharpList`1[System.String] referencedFields FSharp.Compiler.EditorServices.PatternContext: Boolean Equals(FSharp.Compiler.EditorServices.PatternContext) FSharp.Compiler.EditorServices.PatternContext: Boolean Equals(System.Object) FSharp.Compiler.EditorServices.PatternContext: Boolean Equals(System.Object, System.Collections.IEqualityComparer) FSharp.Compiler.EditorServices.PatternContext: Boolean IsNamedUnionCaseField FSharp.Compiler.EditorServices.PatternContext: Boolean IsOther FSharp.Compiler.EditorServices.PatternContext: Boolean IsPositionalUnionCaseField +FSharp.Compiler.EditorServices.PatternContext: Boolean IsUnionCaseFieldIdentifier FSharp.Compiler.EditorServices.PatternContext: Boolean get_IsNamedUnionCaseField() FSharp.Compiler.EditorServices.PatternContext: Boolean get_IsOther() FSharp.Compiler.EditorServices.PatternContext: Boolean get_IsPositionalUnionCaseField() +FSharp.Compiler.EditorServices.PatternContext: Boolean get_IsUnionCaseFieldIdentifier() FSharp.Compiler.EditorServices.PatternContext: FSharp.Compiler.EditorServices.PatternContext NewNamedUnionCaseField(System.String, FSharp.Compiler.Text.Range) -FSharp.Compiler.EditorServices.PatternContext: FSharp.Compiler.EditorServices.PatternContext NewPositionalUnionCaseField(Microsoft.FSharp.Core.FSharpOption`1[System.Int32], FSharp.Compiler.Text.Range) +FSharp.Compiler.EditorServices.PatternContext: FSharp.Compiler.EditorServices.PatternContext NewPositionalUnionCaseField(Microsoft.FSharp.Core.FSharpOption`1[System.Int32], Boolean, FSharp.Compiler.Text.Range) +FSharp.Compiler.EditorServices.PatternContext: FSharp.Compiler.EditorServices.PatternContext NewUnionCaseFieldIdentifier(Microsoft.FSharp.Collections.FSharpList`1[System.String], FSharp.Compiler.Text.Range) FSharp.Compiler.EditorServices.PatternContext: FSharp.Compiler.EditorServices.PatternContext Other FSharp.Compiler.EditorServices.PatternContext: FSharp.Compiler.EditorServices.PatternContext get_Other() FSharp.Compiler.EditorServices.PatternContext: FSharp.Compiler.EditorServices.PatternContext+NamedUnionCaseField FSharp.Compiler.EditorServices.PatternContext: FSharp.Compiler.EditorServices.PatternContext+PositionalUnionCaseField FSharp.Compiler.EditorServices.PatternContext: FSharp.Compiler.EditorServices.PatternContext+Tags +FSharp.Compiler.EditorServices.PatternContext: FSharp.Compiler.EditorServices.PatternContext+UnionCaseFieldIdentifier FSharp.Compiler.EditorServices.PatternContext: Int32 GetHashCode() FSharp.Compiler.EditorServices.PatternContext: Int32 GetHashCode(System.Collections.IEqualityComparer) FSharp.Compiler.EditorServices.PatternContext: Int32 Tag diff --git a/vsintegration/tests/FSharp.Editor.Tests/CompletionProviderTests.fs b/vsintegration/tests/FSharp.Editor.Tests/CompletionProviderTests.fs index 5c7f3b9fa83..55f459d2d08 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/CompletionProviderTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/CompletionProviderTests.fs @@ -1722,6 +1722,30 @@ match U1 (1, A) with VerifyCompletionList(fileContents, "| U1 (x", [ "xxx"; "num" ], [ "tab"; "yyy"; "fff" ]) VerifyCompletionList(fileContents, "| U1 (x, y", [ "yyy"; "tab" ], [ "xxx"; "num"; "fff" ]) + [] + let ``Completion list for union case field identifier contains available fields`` () = + let fileContents = + """ +type PatternContext = + | PositionalUnionCaseField of fieldIndex: int option * isTheOnlyField: bool * caseIdRange: range + | NamedUnionCaseField of fieldName: string * caseIdRange: range + | UnionCaseFieldIdentifier of referencedFields: string list * caseIdRange: range + | Other + +match PositionalUnionCaseField (None, 0, range0) with +| PositionalUnionCaseField (fieldIndex = _; a) +| NamedUnionCaseField (fieldName = a; z) +| NamedUnionCaseField (x) +""" + + VerifyCompletionListExactly(fileContents, "PositionalUnionCaseField (fieldIndex = _; a", [ "caseIdRange"; "isTheOnlyField" ]) + VerifyCompletionListExactly(fileContents, "NamedUnionCaseField (fieldName = a; z", [ "caseIdRange" ]) + + // This has the potential to become either a positional field pattern or a named field identifier, so we want to see completions for both: + // - suggested name based on the first field's identifier and a suggested name based on the first field's type + // - names of all fields + VerifyCompletionList(fileContents, "NamedUnionCaseField (x", [ "string"; "fieldName"; "caseIdRange" ], [ "range"; "fieldIndex"; "referencedFields"; "isTheOnlyField" ]) + [] let ``Completion list does not contain methods and non-literals when dotting into a type or module in a pattern`` () = let fileContents = From d53a0333ca1c81fdda7b82d39d269b4ec6c8fd7e Mon Sep 17 00:00:00 2001 From: kerams Date: Thu, 3 Aug 2023 18:21:48 +0200 Subject: [PATCH 2/8] . --- .../tests/FSharp.Editor.Tests/CompletionProviderTests.fs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/vsintegration/tests/FSharp.Editor.Tests/CompletionProviderTests.fs b/vsintegration/tests/FSharp.Editor.Tests/CompletionProviderTests.fs index 55f459d2d08..2fc70cfb924 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/CompletionProviderTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/CompletionProviderTests.fs @@ -1744,7 +1744,12 @@ match PositionalUnionCaseField (None, 0, range0) with // This has the potential to become either a positional field pattern or a named field identifier, so we want to see completions for both: // - suggested name based on the first field's identifier and a suggested name based on the first field's type // - names of all fields - VerifyCompletionList(fileContents, "NamedUnionCaseField (x", [ "string"; "fieldName"; "caseIdRange" ], [ "range"; "fieldIndex"; "referencedFields"; "isTheOnlyField" ]) + VerifyCompletionList( + fileContents, + "NamedUnionCaseField (x", + [ "string"; "fieldName"; "caseIdRange" ], + [ "range"; "fieldIndex"; "referencedFields"; "isTheOnlyField" ] + ) [] let ``Completion list does not contain methods and non-literals when dotting into a type or module in a pattern`` () = From d5a3262307e5a61e309f3f2d106885591ffbeec2 Mon Sep 17 00:00:00 2001 From: kerams Date: Tue, 8 Aug 2023 18:19:24 +0200 Subject: [PATCH 3/8] Fix SynPat.LongIdent range with SynArgPats.NamePatPairs --- src/Compiler/Service/ServiceParsedInputOps.fs | 16 ++++-- src/Compiler/pars.fsy | 49 +++++++------------ .../SyntaxTree/Pattern/Named field 04.fs.bsl | 2 +- .../CompletionProviderTests.fs | 11 +++-- 4 files changed, 40 insertions(+), 38 deletions(-) diff --git a/src/Compiler/Service/ServiceParsedInputOps.fs b/src/Compiler/Service/ServiceParsedInputOps.fs index 875700375dc..e52d110d4b0 100644 --- a/src/Compiler/Service/ServiceParsedInputOps.fs +++ b/src/Compiler/Service/ServiceParsedInputOps.fs @@ -1264,7 +1264,7 @@ module ParsedInput = let rec TryGetCompletionContextInPattern suppressIdentifierCompletions (pat: SynPat) previousContext pos = match pat with | SynPat.LongIdent (longDotId = id) when rangeContainsPos id.Range pos -> Some(CompletionContext.Pattern PatternContext.Other) - | SynPat.LongIdent (argPats = SynArgPats.NamePatPairs (pats = pats); longDotId = caseId; range = m) when rangeContainsPos m pos -> + | SynPat.LongIdent (argPats = SynArgPats.NamePatPairs (pats = pats; range = mPairs); longDotId = caseId; range = m) when rangeContainsPos m pos -> pats |> List.tryPick (fun (fieldId, _, pat) -> if rangeContainsPos fieldId.idRange pos then @@ -1273,6 +1273,14 @@ module ParsedInput = else let context = Some(PatternContext.NamedUnionCaseField(fieldId.idText, caseId.Range)) TryGetCompletionContextInPattern suppressIdentifierCompletions pat context pos) + |> Option.orElseWith (fun () -> + // Last resort - check for fun (Case (item1 = a, | )) -> + // That is, pos is after the last semicolon and before the end of the tuple + if rangeBeforePos mPairs pos then + let referencedFields = pats |> List.map (fun (id, _, _) -> id.idText) + Some(CompletionContext.Pattern(PatternContext.UnionCaseFieldIdentifier(referencedFields, caseId.Range))) + else + None) | SynPat.LongIdent (argPats = SynArgPats.Pats pats; longDotId = id; range = m) when rangeContainsPos m pos -> match pats with @@ -1281,7 +1289,7 @@ module ParsedInput = // fun (Case (| )) -> | [ SynPat.Paren (SynPat.Const (SynConst.Unit, _), m) ] when rangeContainsPos m pos -> - Some(CompletionContext.Pattern(PatternContext.PositionalUnionCaseField(Some 0, id.Range))) + Some(CompletionContext.Pattern(PatternContext.PositionalUnionCaseField(Some 0, true, id.Range))) // fun (Case (a| )) -> // This could either be the first positional field pattern or the user might want to use named pairs @@ -1319,10 +1327,10 @@ module ParsedInput = // Last resort - check for fun (Case (a, | )) -> // That is, pos is after the last comma and before the end of the tuple match previousContext, List.tryLast commas with - | Some (PatternContext.PositionalUnionCaseField (_, caseIdRange)), Some mComma when + | Some (PatternContext.PositionalUnionCaseField (_, isTheOnlyField, caseIdRange)), Some mComma when rangeBeforePos mComma pos && rangeContainsPos m pos -> - Some(CompletionContext.Pattern(PatternContext.PositionalUnionCaseField(Some(pats.Length - 1), caseIdRange))) + Some(CompletionContext.Pattern(PatternContext.PositionalUnionCaseField(Some(pats.Length - 1), isTheOnlyField, caseIdRange))) | _ -> None) | SynPat.Named (range = m) when rangeContainsPos m pos -> if suppressIdentifierCompletions then diff --git a/src/Compiler/pars.fsy b/src/Compiler/pars.fsy index 028416ae796..89286fa8dfc 100644 --- a/src/Compiler/pars.fsy +++ b/src/Compiler/pars.fsy @@ -158,7 +158,7 @@ let parse_error_rich = Some(fun (ctxt: ParseErrorContext<_>) -> %type ident %type typ typEOF %type tyconSpfnList -%type atomicPatsOrNamePatPairs +%type atomicPatsOrNamePatPairs %type atomicPatterns %type patternResult %type declExpr @@ -3480,16 +3480,14 @@ conjPatternElements: namePatPairs: | namePatPair opt_seps - { [$1], lhs parseState } + { [$1] } | namePatPair seps namePatPairs - { let rs, _ = $3 - ($1 :: rs), lhs parseState } + { $1 :: $3 } | namePatPair seps seps namePatPairs - { let rs, _ = $4 - reportParseErrorAt (rhs parseState 3) (FSComp.SR.parsExpectingPattern ()) - ($1 :: rs), lhs parseState } + { reportParseErrorAt (rhs parseState 3) (FSComp.SR.parsExpectingPattern ()) + ($1 :: $4) } namePatPair: | ident EQUALS parenPattern @@ -3510,39 +3508,33 @@ constrPattern: | atomicPatternLongIdent explicitValTyparDecls atomicPatsOrNamePatPairs %prec pat_app { let vis, lid = $1 - let args, argsM = $3 - let m = unionRanges (rhs2 parseState 1 2) argsM - SynPat.LongIdent(lid, None, Some $2, args, vis, m) } + let m = lhs parseState + SynPat.LongIdent(lid, None, Some $2, $3, vis, m) } | atomicPatternLongIdent explicitValTyparDecls HIGH_PRECEDENCE_PAREN_APP atomicPatsOrNamePatPairs { let vis, lid = $1 - let args, argsM = $4 - let m = unionRanges (rhs2 parseState 1 2) argsM - SynPat.LongIdent(lid, None, Some $2, args, vis, m) } + let m = lhs parseState + SynPat.LongIdent(lid, None, Some $2, $4, vis, m) } | atomicPatternLongIdent explicitValTyparDecls HIGH_PRECEDENCE_BRACK_APP atomicPatsOrNamePatPairs { let vis, lid = $1 - let args, argsM = $4 - let m = unionRanges (rhs2 parseState 1 2) argsM - SynPat.LongIdent(lid, None, Some $2, args, vis, m) } + let m = lhs parseState + SynPat.LongIdent(lid, None, Some $2, $4, vis, m) } | atomicPatternLongIdent atomicPatsOrNamePatPairs %prec pat_app { let vis, lid = $1 - let args, argsM = $2 - let m = unionRanges (rhs parseState 1) argsM - SynPat.LongIdent(lid, None, None, args, vis, m) } + let m = rhs2 parseState 1 2 + SynPat.LongIdent(lid, None, None, $2, vis, m) } | atomicPatternLongIdent HIGH_PRECEDENCE_PAREN_APP atomicPatsOrNamePatPairs { let vis, lid = $1 - let args, argsM = $3 - let m = unionRanges (rhs parseState 1) argsM - SynPat.LongIdent(lid, None, None, args, vis, m) } + let m = lhs parseState + SynPat.LongIdent(lid, None, None, $3, vis, m) } | atomicPatternLongIdent HIGH_PRECEDENCE_BRACK_APP atomicPatsOrNamePatPairs { let vis, lid = $1 - let args, argsM = $3 - let m = unionRanges (rhs parseState 1) argsM - SynPat.LongIdent(lid, None, None, args, vis, m) } + let m = lhs parseState + SynPat.LongIdent(lid, None, None, $3, vis, m) } | COLON_QMARK atomTypeOrAnonRecdType %prec pat_isinst { SynPat.IsInst($2, lhs parseState) } @@ -3553,14 +3545,11 @@ constrPattern: atomicPatsOrNamePatPairs: | LPAREN namePatPairs rparen { let mParen = rhs2 parseState 1 3 - let pats, m = $2 let trivia = { ParenRange = mParen } - SynArgPats.NamePatPairs(pats, m, trivia), snd $2 } + SynArgPats.NamePatPairs($2, rhs parseState 2, trivia) } | atomicPatterns - { let mParsed = rhs parseState 1 - let mAll = (mParsed.StartRange, $1) ||> unionRangeWithListBy (fun p -> p.Range) - SynArgPats.Pats $1, mAll } + { SynArgPats.Pats $1 } atomicPatterns: | atomicPattern atomicPatterns %prec pat_args diff --git a/tests/service/data/SyntaxTree/Pattern/Named field 04.fs.bsl b/tests/service/data/SyntaxTree/Pattern/Named field 04.fs.bsl index 6909e6602d0..33a958c138e 100644 --- a/tests/service/data/SyntaxTree/Pattern/Named field 04.fs.bsl +++ b/tests/service/data/SyntaxTree/Pattern/Named field 04.fs.bsl @@ -12,7 +12,7 @@ ImplFile (SynLongIdent ([A], [], [None]), None, None, NamePatPairs ([(a, Some (4,6--4,7), Wild (4,8--4,9))], (4,4--4,10), - { ParenRange = (4,3--4,11) }), None, (4,2--4,10)), + { ParenRange = (4,3--4,11) }), None, (4,2--4,11)), None, Const (Int32 2, (4,15--4,16)), (4,2--4,16), Yes, { ArrowRange = Some (4,12--4,14) BarRange = Some (4,0--4,1) })], (3,0--4,16), diff --git a/vsintegration/tests/FSharp.Editor.Tests/CompletionProviderTests.fs b/vsintegration/tests/FSharp.Editor.Tests/CompletionProviderTests.fs index 6bb12034999..e8294caff85 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/CompletionProviderTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/CompletionProviderTests.fs @@ -1684,11 +1684,16 @@ type Du = let x du = match du with | C () -> () - | C (first, ) -> () + | C (ff, ) -> () + | C (first = f;) -> () """ - VerifyCompletionList(fileContents, "| C (", [ "first"; "du" ], [ "second"; "result" ]) - VerifyCompletionList(fileContents, "| C (first, ", [ "second"; "result" ], [ "first"; "du" ]) + // This has the potential to become either a positional field pattern or a named field identifier, so we want to see completions for both: + // - suggested name based on the first field's identifier and a suggested name based on the first field's type + // - names of all fields + VerifyCompletionList(fileContents, "| C (", [ "first"; "du"; "second" ], [ "result" ]) + VerifyCompletionList(fileContents, "| C (ff, ", [ "second"; "result" ], [ "first"; "du" ]) + VerifyCompletionListExactly(fileContents, "| C (first = f;", [ "second" ]) [] let ``Completion list contains suggested names for union case field pattern in a let binding, lambda and member`` () = From e720f6712d41406ecda5c6cf82f5b03bab4e19ed Mon Sep 17 00:00:00 2001 From: kerams Date: Tue, 8 Aug 2023 18:25:56 +0200 Subject: [PATCH 4/8] . --- src/Compiler/Service/ServiceParsedInputOps.fs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Compiler/Service/ServiceParsedInputOps.fs b/src/Compiler/Service/ServiceParsedInputOps.fs index e52d110d4b0..055fad52a0f 100644 --- a/src/Compiler/Service/ServiceParsedInputOps.fs +++ b/src/Compiler/Service/ServiceParsedInputOps.fs @@ -1264,7 +1264,9 @@ module ParsedInput = let rec TryGetCompletionContextInPattern suppressIdentifierCompletions (pat: SynPat) previousContext pos = match pat with | SynPat.LongIdent (longDotId = id) when rangeContainsPos id.Range pos -> Some(CompletionContext.Pattern PatternContext.Other) - | SynPat.LongIdent (argPats = SynArgPats.NamePatPairs (pats = pats; range = mPairs); longDotId = caseId; range = m) when rangeContainsPos m pos -> + | SynPat.LongIdent (argPats = SynArgPats.NamePatPairs (pats = pats; range = mPairs); longDotId = caseId; range = m) when + rangeContainsPos m pos + -> pats |> List.tryPick (fun (fieldId, _, pat) -> if rangeContainsPos fieldId.idRange pos then @@ -1330,7 +1332,11 @@ module ParsedInput = | Some (PatternContext.PositionalUnionCaseField (_, isTheOnlyField, caseIdRange)), Some mComma when rangeBeforePos mComma pos && rangeContainsPos m pos -> - Some(CompletionContext.Pattern(PatternContext.PositionalUnionCaseField(Some(pats.Length - 1), isTheOnlyField, caseIdRange))) + Some( + CompletionContext.Pattern( + PatternContext.PositionalUnionCaseField(Some(pats.Length - 1), isTheOnlyField, caseIdRange) + ) + ) | _ -> None) | SynPat.Named (range = m) when rangeContainsPos m pos -> if suppressIdentifierCompletions then From 6b22adc5206c98270f62d57e2a38767d162f9106 Mon Sep 17 00:00:00 2001 From: kerams Date: Thu, 10 Aug 2023 21:07:56 +0200 Subject: [PATCH 5/8] Improve docs --- src/Compiler/Service/FSharpCheckerResults.fs | 2 +- src/Compiler/Service/ServiceParsedInputOps.fs | 10 ++++++---- src/Compiler/Service/ServiceParsedInputOps.fsi | 6 ++++-- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/Compiler/Service/FSharpCheckerResults.fs b/src/Compiler/Service/FSharpCheckerResults.fs index c397fb6b4fa..2242574c1ee 100644 --- a/src/Compiler/Service/FSharpCheckerResults.fs +++ b/src/Compiler/Service/FSharpCheckerResults.fs @@ -1612,7 +1612,7 @@ type internal TypeCheckInfo | PatternContext.NamedUnionCaseField (name, m) -> Choice2Of2 name, m | _ -> Choice1Of2 None, range0 - // No special handling other than filtering out non-literal values + // No special handling other than filtering out items that may not appear in a pattern if equals caseIdRange range0 then declaredItems else diff --git a/src/Compiler/Service/ServiceParsedInputOps.fs b/src/Compiler/Service/ServiceParsedInputOps.fs index 055fad52a0f..8721a730a0a 100644 --- a/src/Compiler/Service/ServiceParsedInputOps.fs +++ b/src/Compiler/Service/ServiceParsedInputOps.fs @@ -52,8 +52,10 @@ type RecordContext = [] type PatternContext = - /// Completing union case field pattern (e.g. fun (Some v| ) -> ) or fun (Some (v| )) -> ) - /// fieldIndex None signifies that the case identifier is followed by a single field, outside of parentheses + /// Completing union case field pattern (e.g. fun (Some v| ) -> ) or fun (Some (v| )) -> ). In theory, this could also be parameterized active pattern usage. + /// Position in the tuple. None if there is no tuple, with only one field outside of parentheses - `Some v|` + /// True when completing the first field in the tuple and no other field is bound - `Case (a|)` but not `Case (a|, b)` + /// Range of the case identifier | PositionalUnionCaseField of fieldIndex: int option * isTheOnlyField: bool * caseIdRange: range /// Completing union case field pattern (e.g. fun (Some (Value = v| )) -> ) @@ -1326,8 +1328,8 @@ module ParsedInput = TryGetCompletionContextInPattern suppressIdentifierCompletions pat context pos) |> Option.orElseWith (fun () -> - // Last resort - check for fun (Case (a, | )) -> - // That is, pos is after the last comma and before the end of the tuple + // Last resort - check for fun (Case (item1 = a; | )) -> + // That is, pos is after the last pair and still within parentheses match previousContext, List.tryLast commas with | Some (PatternContext.PositionalUnionCaseField (_, isTheOnlyField, caseIdRange)), Some mComma when rangeBeforePos mComma pos && rangeContainsPos m pos diff --git a/src/Compiler/Service/ServiceParsedInputOps.fsi b/src/Compiler/Service/ServiceParsedInputOps.fsi index 1f9470379fc..877ca64a7f3 100644 --- a/src/Compiler/Service/ServiceParsedInputOps.fsi +++ b/src/Compiler/Service/ServiceParsedInputOps.fsi @@ -24,8 +24,10 @@ type public RecordContext = [] type public PatternContext = - /// Completing union case field pattern (e.g. fun (Some v| ) -> ) or fun (Some (v| )) -> ) - /// fieldIndex None signifies that the case identifier is followed by a single field, outside of parentheses + /// Completing union case field pattern (e.g. fun (Some v| ) -> ) or fun (Some (v| )) -> ). In theory, this could also be parameterized active pattern usage. + /// Position in the tuple. None if there is no tuple, with only one field outside of parentheses - `Some v|` + /// True when completing the first field in the tuple and no other field is bound - `Case (a|)` but not `Case (a|, b)` + /// Range of the case identifier | PositionalUnionCaseField of fieldIndex: int option * isTheOnlyField: bool * caseIdRange: range /// Completing union case field pattern (e.g. fun (Some (Value = v| )) -> ) From 598f2f679bf045312cc037e5ba8cb115317e721c Mon Sep 17 00:00:00 2001 From: kerams Date: Thu, 10 Aug 2023 21:30:00 +0200 Subject: [PATCH 6/8] pars.fsy --- src/Compiler/pars.fsy | 38 +++++++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/src/Compiler/pars.fsy b/src/Compiler/pars.fsy index 89286fa8dfc..e83b8c86dbb 100644 --- a/src/Compiler/pars.fsy +++ b/src/Compiler/pars.fsy @@ -158,7 +158,7 @@ let parse_error_rich = Some(fun (ctxt: ParseErrorContext<_>) -> %type ident %type typ typEOF %type tyconSpfnList -%type atomicPatsOrNamePatPairs +%type atomicPatsOrNamePatPairs %type atomicPatterns %type patternResult %type declExpr @@ -3508,33 +3508,39 @@ constrPattern: | atomicPatternLongIdent explicitValTyparDecls atomicPatsOrNamePatPairs %prec pat_app { let vis, lid = $1 - let m = lhs parseState - SynPat.LongIdent(lid, None, Some $2, $3, vis, m) } + let args, argsM = $3 + let m = unionRanges (rhs2 parseState 1 2) argsM + SynPat.LongIdent(lid, None, Some $2, args, vis, m) } | atomicPatternLongIdent explicitValTyparDecls HIGH_PRECEDENCE_PAREN_APP atomicPatsOrNamePatPairs { let vis, lid = $1 - let m = lhs parseState - SynPat.LongIdent(lid, None, Some $2, $4, vis, m) } + let args, argsM = $4 + let m = unionRanges (rhs2 parseState 1 2) argsM + SynPat.LongIdent(lid, None, Some $2, args, vis, m) } | atomicPatternLongIdent explicitValTyparDecls HIGH_PRECEDENCE_BRACK_APP atomicPatsOrNamePatPairs { let vis, lid = $1 - let m = lhs parseState - SynPat.LongIdent(lid, None, Some $2, $4, vis, m) } + let args, argsM = $4 + let m = unionRanges (rhs2 parseState 1 2) argsM + SynPat.LongIdent(lid, None, Some $2, args, vis, m) } | atomicPatternLongIdent atomicPatsOrNamePatPairs %prec pat_app { let vis, lid = $1 - let m = rhs2 parseState 1 2 - SynPat.LongIdent(lid, None, None, $2, vis, m) } + let args, argsM = $2 + let m = unionRanges (rhs parseState 1) argsM + SynPat.LongIdent(lid, None, None, args, vis, m) } | atomicPatternLongIdent HIGH_PRECEDENCE_PAREN_APP atomicPatsOrNamePatPairs { let vis, lid = $1 - let m = lhs parseState - SynPat.LongIdent(lid, None, None, $3, vis, m) } + let args, argsM = $3 + let m = unionRanges (rhs parseState 1) argsM + SynPat.LongIdent(lid, None, None, args, vis, m) } | atomicPatternLongIdent HIGH_PRECEDENCE_BRACK_APP atomicPatsOrNamePatPairs { let vis, lid = $1 - let m = lhs parseState - SynPat.LongIdent(lid, None, None, $3, vis, m) } + let args, argsM = $3 + let m = unionRanges (rhs parseState 1) argsM + SynPat.LongIdent(lid, None, None, args, vis, m) } | COLON_QMARK atomTypeOrAnonRecdType %prec pat_isinst { SynPat.IsInst($2, lhs parseState) } @@ -3546,10 +3552,12 @@ atomicPatsOrNamePatPairs: | LPAREN namePatPairs rparen { let mParen = rhs2 parseState 1 3 let trivia = { ParenRange = mParen } - SynArgPats.NamePatPairs($2, rhs parseState 2, trivia) } + SynArgPats.NamePatPairs($2, rhs parseState 2, trivia), mParen } | atomicPatterns - { SynArgPats.Pats $1 } + { let mParsed = rhs parseState 1 + let mAll = (mParsed.StartRange, $1) ||> unionRangeWithListBy (fun p -> p.Range) + SynArgPats.Pats $1, mAll } atomicPatterns: | atomicPattern atomicPatterns %prec pat_args From 63ca1958c2fb4d066b64fd07d23743ef63092156 Mon Sep 17 00:00:00 2001 From: kerams Date: Sat, 12 Aug 2023 23:37:05 +0200 Subject: [PATCH 7/8] Update FSharpCheckerResults.fs --- src/Compiler/Service/FSharpCheckerResults.fs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Compiler/Service/FSharpCheckerResults.fs b/src/Compiler/Service/FSharpCheckerResults.fs index 2242574c1ee..c6b86e733af 100644 --- a/src/Compiler/Service/FSharpCheckerResults.fs +++ b/src/Compiler/Service/FSharpCheckerResults.fs @@ -1598,6 +1598,7 @@ type internal TypeCheckInfo | Item.Value v -> v.LiteralValue.IsSome | Item.ILField field -> field.LiteralValue.IsSome | Item.ActivePatternCase _ + | Item.ExnCase _ | Item.ModuleOrNamespaces _ | Item.NewDef _ | Item.Types _ From f492fd3ab7370531ba91f41e158b2b8dfa15384a Mon Sep 17 00:00:00 2001 From: kerams Date: Wed, 16 Aug 2023 15:25:12 +0200 Subject: [PATCH 8/8] Refactor --- src/Compiler/Service/FSharpCheckerResults.fs | 3 ++- src/Compiler/Service/ServiceParsedInputOps.fs | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Compiler/Service/FSharpCheckerResults.fs b/src/Compiler/Service/FSharpCheckerResults.fs index c6b86e733af..a75c52cf451 100644 --- a/src/Compiler/Service/FSharpCheckerResults.fs +++ b/src/Compiler/Service/FSharpCheckerResults.fs @@ -1611,7 +1611,8 @@ type internal TypeCheckInfo match patternContext with | PatternContext.PositionalUnionCaseField (index, _, m) -> Choice1Of2 index, m | PatternContext.NamedUnionCaseField (name, m) -> Choice2Of2 name, m - | _ -> Choice1Of2 None, range0 + | PatternContext.UnionCaseFieldIdentifier _ + | PatternContext.Other -> Choice1Of2 None, range0 // No special handling other than filtering out items that may not appear in a pattern if equals caseIdRange range0 then diff --git a/src/Compiler/Service/ServiceParsedInputOps.fs b/src/Compiler/Service/ServiceParsedInputOps.fs index 8721a730a0a..c6a22626d08 100644 --- a/src/Compiler/Service/ServiceParsedInputOps.fs +++ b/src/Compiler/Service/ServiceParsedInputOps.fs @@ -1278,8 +1278,8 @@ module ParsedInput = let context = Some(PatternContext.NamedUnionCaseField(fieldId.idText, caseId.Range)) TryGetCompletionContextInPattern suppressIdentifierCompletions pat context pos) |> Option.orElseWith (fun () -> - // Last resort - check for fun (Case (item1 = a, | )) -> - // That is, pos is after the last semicolon and before the end of the tuple + // Last resort - check for fun (Case (item1 = a; | )) -> + // That is, pos is after the last pair and still within parentheses if rangeBeforePos mPairs pos then let referencedFields = pats |> List.map (fun (id, _, _) -> id.idText) Some(CompletionContext.Pattern(PatternContext.UnionCaseFieldIdentifier(referencedFields, caseId.Range))) @@ -1328,8 +1328,8 @@ module ParsedInput = TryGetCompletionContextInPattern suppressIdentifierCompletions pat context pos) |> Option.orElseWith (fun () -> - // Last resort - check for fun (Case (item1 = a; | )) -> - // That is, pos is after the last pair and still within parentheses + // Last resort - check for fun (Case (item1 = a, | )) -> + // That is, pos is after the last comma and before the end of the tuple match previousContext, List.tryLast commas with | Some (PatternContext.PositionalUnionCaseField (_, isTheOnlyField, caseIdRange)), Some mComma when rangeBeforePos mComma pos && rangeContainsPos m pos