diff --git a/src/Compiler/Service/FSharpCheckerResults.fs b/src/Compiler/Service/FSharpCheckerResults.fs index 819921750a3..a75c52cf451 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( @@ -1573,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 _ @@ -1583,19 +1609,37 @@ 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.UnionCaseFieldIdentifier _ | PatternContext.Other -> Choice1Of2 None, range0 - // No special handling for PatternContext.Other 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 - 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 89b7e38a764..c6a22626d08 100644 --- a/src/Compiler/Service/ServiceParsedInputOps.fs +++ b/src/Compiler/Service/ServiceParsedInputOps.fs @@ -52,13 +52,18 @@ type RecordContext = [] type PatternContext = - /// Completing union case field in a pattern (e.g. 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 + /// 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 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,28 +1266,46 @@ 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; range = mPairs); 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) + |> Option.orElseWith (fun () -> + // 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))) + else + None) | SynPat.LongIdent (argPats = SynArgPats.Pats pats; longDotId = id; range = m) when rangeContainsPos m pos -> match pats with // fun (Some v| ) -> - | [ SynPat.Named _ ] -> Some(CompletionContext.Pattern(PatternContext.PositionalUnionCaseField(None, id.Range))) + | [ SynPat.Named _ ] -> Some(CompletionContext.Pattern(PatternContext.PositionalUnionCaseField(None, true, id.Range))) // 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 + | [ SynPat.Paren (SynPat.Named _, _) ] -> + Some(CompletionContext.Pattern(PatternContext.PositionalUnionCaseField(Some 0, true, id.Range))) // fun (Case (a| , b)) -> - | [ SynPat.Paren (SynPat.Tuple _ | SynPat.Named _ as pat, _) ] -> - TryGetCompletionContextInPattern false pat (Some(PatternContext.PositionalUnionCaseField(Some 0, id.Range))) pos - |> Option.orElseWith (fun () -> Some CompletionContext.Invalid) + | [ 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 @@ -1297,21 +1320,25 @@ 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 TryGetCompletionContextInPattern suppressIdentifierCompletions pat context pos) |> Option.orElseWith (fun () -> - // Last resort - check for fun (Case (a, | )) -> + // 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 (_, 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/Service/ServiceParsedInputOps.fsi b/src/Compiler/Service/ServiceParsedInputOps.fsi index e94965bb2bd..877ca64a7f3 100644 --- a/src/Compiler/Service/ServiceParsedInputOps.fsi +++ b/src/Compiler/Service/ServiceParsedInputOps.fsi @@ -24,13 +24,18 @@ type public RecordContext = [] type public PatternContext = - /// Completing union case field in a pattern (e.g. 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 + /// 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 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/src/Compiler/pars.fsy b/src/Compiler/pars.fsy index 028416ae796..e83b8c86dbb 100644 --- a/src/Compiler/pars.fsy +++ b/src/Compiler/pars.fsy @@ -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 @@ -3553,9 +3551,8 @@ 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), mParen } | atomicPatterns { let mParsed = rhs parseState 1 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/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 bfcb446a5b0..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`` () = @@ -1757,6 +1762,35 @@ 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 =