Skip to content

Commit 5628af4

Browse files
authored
Merge pull request #15817 from dotnet/merges/main-to-release/dev17.8
2 parents ccd659b + 1f6a5c1 commit 5628af4

File tree

8 files changed

+172
-43
lines changed

8 files changed

+172
-43
lines changed

src/Compiler/Service/FSharpCheckerResults.fs

+50-6
Original file line numberDiff line numberDiff line change
@@ -886,6 +886,7 @@ type internal TypeCheckInfo
886886
| minfo :: _ -> CompletionItemKind.Method minfo.IsExtensionMember
887887
| Item.AnonRecdField _
888888
| Item.RecdField _
889+
| Item.UnionCaseField _
889890
| Item.Property _ -> CompletionItemKind.Property
890891
| Item.Event _ -> CompletionItemKind.Event
891892
| Item.ILField _
@@ -900,7 +901,6 @@ type internal TypeCheckInfo
900901
| Item.TypeVar _
901902
| Item.Types _
902903
| Item.UnionCase _
903-
| Item.UnionCaseField _
904904
| Item.UnqualifiedType _
905905
| Item.NewDef _
906906
| Item.SetterArg _
@@ -1049,6 +1049,25 @@ type internal TypeCheckInfo
10491049
Some(overridableMethods, nenv.DisplayEnv, m)
10501050
| _ -> None)
10511051

1052+
/// Gets all field identifiers of a union case that can be referred to in a pattern.
1053+
let GetUnionCaseFields caseIdRange alreadyReferencedFields =
1054+
sResolutions.CapturedNameResolutions
1055+
|> ResizeArray.tryPick (fun r ->
1056+
match r.Item with
1057+
| Item.UnionCase (uci, _) when equals r.Range caseIdRange ->
1058+
uci.UnionCase.RecdFields
1059+
|> List.indexed
1060+
|> List.choose (fun (index, field) ->
1061+
if List.contains field.LogicalName alreadyReferencedFields then
1062+
None
1063+
else
1064+
Item.UnionCaseField(uci, index)
1065+
|> ItemWithNoInst
1066+
|> CompletionItem ValueNone ValueNone
1067+
|> Some)
1068+
|> Some
1069+
| _ -> None)
1070+
10521071
let getItem (x: ItemWithInst) = x.Item
10531072

10541073
let GetDeclaredItems
@@ -1549,6 +1568,12 @@ type internal TypeCheckInfo
15491568
denv,
15501569
m)
15511570

1571+
| Some (CompletionContext.Pattern (PatternContext.UnionCaseFieldIdentifier (referencedFields, caseIdRange))) ->
1572+
GetUnionCaseFields caseIdRange referencedFields
1573+
|> Option.map (fun completions ->
1574+
let (nenv, _ad), m = GetBestEnvForPos pos
1575+
completions, nenv.DisplayEnv, m)
1576+
15521577
| Some (CompletionContext.Pattern patternContext) ->
15531578
let declaredItems =
15541579
GetDeclaredItems(
@@ -1573,6 +1598,7 @@ type internal TypeCheckInfo
15731598
| Item.Value v -> v.LiteralValue.IsSome
15741599
| Item.ILField field -> field.LiteralValue.IsSome
15751600
| Item.ActivePatternCase _
1601+
| Item.ExnCase _
15761602
| Item.ModuleOrNamespaces _
15771603
| Item.NewDef _
15781604
| Item.Types _
@@ -1583,19 +1609,37 @@ type internal TypeCheckInfo
15831609

15841610
let indexOrName, caseIdRange =
15851611
match patternContext with
1586-
| PatternContext.PositionalUnionCaseField (index, m) -> Choice1Of2 index, m
1612+
| PatternContext.PositionalUnionCaseField (index, _, m) -> Choice1Of2 index, m
15871613
| PatternContext.NamedUnionCaseField (name, m) -> Choice2Of2 name, m
1614+
| PatternContext.UnionCaseFieldIdentifier _
15881615
| PatternContext.Other -> Choice1Of2 None, range0
15891616

1590-
// No special handling for PatternContext.Other other than filtering out non-literal values
1617+
// No special handling other than filtering out items that may not appear in a pattern
15911618
if equals caseIdRange range0 then
15921619
declaredItems
15931620
else
1594-
GetCapturedNameResolutions caseIdRange.End ResolveOverloads.Yes
1621+
// When the user types `fun (Case (x| )) ->`, we do not yet know whether the intention is to use positional or named arguments,
1622+
// so let's show options for both.
1623+
let fields patternContext (uci: UnionCaseInfo) =
1624+
match patternContext with
1625+
| PatternContext.PositionalUnionCaseField (Some 0, true, _) ->
1626+
uci.UnionCase.RecdFields
1627+
|> List.mapi (fun index _ ->
1628+
Item.UnionCaseField(uci, index)
1629+
|> ItemWithNoInst
1630+
|> CompletionItem ValueNone ValueNone)
1631+
| _ -> []
1632+
1633+
sResolutions.CapturedNameResolutions
15951634
|> ResizeArray.tryPick (fun r ->
15961635
match r.Item with
1597-
| Item.UnionCase (uci, _) ->
1598-
let list = declaredItems |> Option.map p13 |> Option.defaultValue []
1636+
| Item.UnionCase (uci, _) when equals r.Range caseIdRange ->
1637+
let list =
1638+
declaredItems
1639+
|> Option.map p13
1640+
|> Option.defaultValue []
1641+
|> List.append (fields patternContext uci)
1642+
15991643
Some(SuggestNameForUnionCaseFieldPattern g caseIdRange.End pos uci indexOrName list, r.DisplayEnv, r.Range)
16001644
| _ -> None)
16011645
|> Option.orElse declaredItems

src/Compiler/Service/ServiceParsedInputOps.fs

+46-19
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,18 @@ type RecordContext =
5252

5353
[<RequireQualifiedAccess>]
5454
type PatternContext =
55-
/// Completing union case field in a pattern (e.g. fun (Some v|) -> )
56-
/// fieldIndex None signifies that the case identifier is followed by a single field, outside of parentheses
57-
| PositionalUnionCaseField of fieldIndex: int option * caseIdRange: range
55+
/// <summary>Completing union case field pattern (e.g. fun (Some v| ) -> ) or fun (Some (v| )) -> ). In theory, this could also be parameterized active pattern usage.</summary>
56+
/// <param name="fieldIndex">Position in the tuple. <see cref="None">None</see> if there is no tuple, with only one field outside of parentheses - `Some v|`</param>
57+
/// <param name="isTheOnlyField">True when completing the first field in the tuple and no other field is bound - `Case (a|)` but not `Case (a|, b)`</param>
58+
/// <param name="caseIdRange">Range of the case identifier</param>
59+
| PositionalUnionCaseField of fieldIndex: int option * isTheOnlyField: bool * caseIdRange: range
5860

59-
/// Completing union case field in a pattern (e.g. fun (Some (Value = v|) -> )
61+
/// Completing union case field pattern (e.g. fun (Some (Value = v| )) -> )
6062
| NamedUnionCaseField of fieldName: string * caseIdRange: range
6163

64+
/// Completing union case field identifier in a pattern (e.g. fun (Case (field1 = a; fie| )) -> )
65+
| UnionCaseFieldIdentifier of referencedFields: string list * caseIdRange: range
66+
6267
/// Any other position in a pattern that does not need special handling
6368
| Other
6469

@@ -1261,28 +1266,46 @@ module ParsedInput =
12611266
let rec TryGetCompletionContextInPattern suppressIdentifierCompletions (pat: SynPat) previousContext pos =
12621267
match pat with
12631268
| SynPat.LongIdent (longDotId = id) when rangeContainsPos id.Range pos -> Some(CompletionContext.Pattern PatternContext.Other)
1264-
| SynPat.LongIdent (argPats = SynArgPats.NamePatPairs (pats = pats); longDotId = id) ->
1269+
| SynPat.LongIdent (argPats = SynArgPats.NamePatPairs (pats = pats; range = mPairs); longDotId = caseId; range = m) when
1270+
rangeContainsPos m pos
1271+
->
12651272
pats
1266-
|> List.tryPick (fun (patId, _, pat) ->
1267-
if rangeContainsPos patId.idRange pos then
1268-
Some CompletionContext.Invalid
1273+
|> List.tryPick (fun (fieldId, _, pat) ->
1274+
if rangeContainsPos fieldId.idRange pos then
1275+
let referencedFields = pats |> List.map (fun (id, _, _) -> id.idText)
1276+
Some(CompletionContext.Pattern(PatternContext.UnionCaseFieldIdentifier(referencedFields, caseId.Range)))
12691277
else
1270-
let context = Some(PatternContext.NamedUnionCaseField(patId.idText, id.Range))
1278+
let context = Some(PatternContext.NamedUnionCaseField(fieldId.idText, caseId.Range))
12711279
TryGetCompletionContextInPattern suppressIdentifierCompletions pat context pos)
1280+
|> Option.orElseWith (fun () ->
1281+
// Last resort - check for fun (Case (item1 = a; | )) ->
1282+
// That is, pos is after the last pair and still within parentheses
1283+
if rangeBeforePos mPairs pos then
1284+
let referencedFields = pats |> List.map (fun (id, _, _) -> id.idText)
1285+
Some(CompletionContext.Pattern(PatternContext.UnionCaseFieldIdentifier(referencedFields, caseId.Range)))
1286+
else
1287+
None)
12721288
| SynPat.LongIdent (argPats = SynArgPats.Pats pats; longDotId = id; range = m) when rangeContainsPos m pos ->
12731289
match pats with
12741290

12751291
// fun (Some v| ) ->
1276-
| [ SynPat.Named _ ] -> Some(CompletionContext.Pattern(PatternContext.PositionalUnionCaseField(None, id.Range)))
1292+
| [ SynPat.Named _ ] -> Some(CompletionContext.Pattern(PatternContext.PositionalUnionCaseField(None, true, id.Range)))
12771293

12781294
// fun (Case (| )) ->
12791295
| [ SynPat.Paren (SynPat.Const (SynConst.Unit, _), m) ] when rangeContainsPos m pos ->
1280-
Some(CompletionContext.Pattern(PatternContext.PositionalUnionCaseField(Some 0, id.Range)))
1296+
Some(CompletionContext.Pattern(PatternContext.PositionalUnionCaseField(Some 0, true, id.Range)))
1297+
1298+
// fun (Case (a| )) ->
1299+
// This could either be the first positional field pattern or the user might want to use named pairs
1300+
| [ SynPat.Paren (SynPat.Named _, _) ] ->
1301+
Some(CompletionContext.Pattern(PatternContext.PositionalUnionCaseField(Some 0, true, id.Range)))
12811302

12821303
// fun (Case (a| , b)) ->
1283-
| [ SynPat.Paren (SynPat.Tuple _ | SynPat.Named _ as pat, _) ] ->
1284-
TryGetCompletionContextInPattern false pat (Some(PatternContext.PositionalUnionCaseField(Some 0, id.Range))) pos
1285-
|> Option.orElseWith (fun () -> Some CompletionContext.Invalid)
1304+
| [ SynPat.Paren (SynPat.Tuple (elementPats = pats) as pat, _) ] ->
1305+
let context =
1306+
Some(PatternContext.PositionalUnionCaseField(Some 0, pats.Length = 1, id.Range))
1307+
1308+
TryGetCompletionContextInPattern false pat context pos
12861309

12871310
| _ ->
12881311
pats
@@ -1297,21 +1320,25 @@ module ParsedInput =
12971320
|> List.tryPick (fun (i, pat) ->
12981321
let context =
12991322
match previousContext with
1300-
| Some (PatternContext.PositionalUnionCaseField (_, caseIdRange)) ->
1301-
Some(PatternContext.PositionalUnionCaseField(Some i, caseIdRange))
1323+
| Some (PatternContext.PositionalUnionCaseField (_, isTheOnlyField, caseIdRange)) ->
1324+
Some(PatternContext.PositionalUnionCaseField(Some i, isTheOnlyField, caseIdRange))
13021325
| _ ->
13031326
// No preceding LongIdent => this is a tuple deconstruction
13041327
None
13051328

13061329
TryGetCompletionContextInPattern suppressIdentifierCompletions pat context pos)
13071330
|> Option.orElseWith (fun () ->
1308-
// Last resort - check for fun (Case (a, | )) ->
1331+
// Last resort - check for fun (Case (item1 = a, | )) ->
13091332
// That is, pos is after the last comma and before the end of the tuple
13101333
match previousContext, List.tryLast commas with
1311-
| Some (PatternContext.PositionalUnionCaseField (_, caseIdRange)), Some mComma when
1334+
| Some (PatternContext.PositionalUnionCaseField (_, isTheOnlyField, caseIdRange)), Some mComma when
13121335
rangeBeforePos mComma pos && rangeContainsPos m pos
13131336
->
1314-
Some(CompletionContext.Pattern(PatternContext.PositionalUnionCaseField(Some(pats.Length - 1), caseIdRange)))
1337+
Some(
1338+
CompletionContext.Pattern(
1339+
PatternContext.PositionalUnionCaseField(Some(pats.Length - 1), isTheOnlyField, caseIdRange)
1340+
)
1341+
)
13151342
| _ -> None)
13161343
| SynPat.Named (range = m) when rangeContainsPos m pos ->
13171344
if suppressIdentifierCompletions then

src/Compiler/Service/ServiceParsedInputOps.fsi

+9-4
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,18 @@ type public RecordContext =
2424

2525
[<RequireQualifiedAccess>]
2626
type public PatternContext =
27-
/// Completing union case field in a pattern (e.g. fun (Some v|) -> )
28-
/// fieldIndex None signifies that the case identifier is followed by a single field, outside of parentheses
29-
| PositionalUnionCaseField of fieldIndex: int option * caseIdRange: range
27+
/// <summary>Completing union case field pattern (e.g. fun (Some v| ) -> ) or fun (Some (v| )) -> ). In theory, this could also be parameterized active pattern usage.</summary>
28+
/// <param name="fieldIndex">Position in the tuple. <see cref="None">None</see> if there is no tuple, with only one field outside of parentheses - `Some v|`</param>
29+
/// <param name="isTheOnlyField">True when completing the first field in the tuple and no other field is bound - `Case (a|)` but not `Case (a|, b)`</param>
30+
/// <param name="caseIdRange">Range of the case identifier</param>
31+
| PositionalUnionCaseField of fieldIndex: int option * isTheOnlyField: bool * caseIdRange: range
3032

31-
/// Completing union case field in a pattern (e.g. fun (Some (Value = v|) -> )
33+
/// Completing union case field pattern (e.g. fun (Some (Value = v| )) -> )
3234
| NamedUnionCaseField of fieldName: string * caseIdRange: range
3335

36+
/// Completing union case field identifier in a pattern (e.g. fun (Case (field1 = a; fie| )) -> )
37+
| UnionCaseFieldIdentifier of referencedFields: string list * caseIdRange: range
38+
3439
/// Any other position in a pattern that does not need special handling
3540
| Other
3641

src/Compiler/pars.fsy

+5-8
Original file line numberDiff line numberDiff line change
@@ -3480,16 +3480,14 @@ conjPatternElements:
34803480

34813481
namePatPairs:
34823482
| namePatPair opt_seps
3483-
{ [$1], lhs parseState }
3483+
{ [$1] }
34843484

34853485
| namePatPair seps namePatPairs
3486-
{ let rs, _ = $3
3487-
($1 :: rs), lhs parseState }
3486+
{ $1 :: $3 }
34883487

34893488
| namePatPair seps seps namePatPairs
3490-
{ let rs, _ = $4
3491-
reportParseErrorAt (rhs parseState 3) (FSComp.SR.parsExpectingPattern ())
3492-
($1 :: rs), lhs parseState }
3489+
{ reportParseErrorAt (rhs parseState 3) (FSComp.SR.parsExpectingPattern ())
3490+
($1 :: $4) }
34933491

34943492
namePatPair:
34953493
| ident EQUALS parenPattern
@@ -3553,9 +3551,8 @@ constrPattern:
35533551
atomicPatsOrNamePatPairs:
35543552
| LPAREN namePatPairs rparen
35553553
{ let mParen = rhs2 parseState 1 3
3556-
let pats, m = $2
35573554
let trivia = { ParenRange = mParen }
3558-
SynArgPats.NamePatPairs(pats, m, trivia), snd $2 }
3555+
SynArgPats.NamePatPairs($2, rhs parseState 2, trivia), mParen }
35593556

35603557
| atomicPatterns
35613558
{ let mParsed = rhs parseState 1

tests/FSharp.Compiler.Service.Tests/FSharp.Compiler.Service.SurfaceArea.netstandard20.debug.bsl

+12-1
Original file line numberDiff line numberDiff line change
@@ -3577,29 +3577,40 @@ FSharp.Compiler.EditorServices.PatternContext+NamedUnionCaseField: FSharp.Compil
35773577
FSharp.Compiler.EditorServices.PatternContext+NamedUnionCaseField: FSharp.Compiler.Text.Range get_caseIdRange()
35783578
FSharp.Compiler.EditorServices.PatternContext+NamedUnionCaseField: System.String fieldName
35793579
FSharp.Compiler.EditorServices.PatternContext+NamedUnionCaseField: System.String get_fieldName()
3580+
FSharp.Compiler.EditorServices.PatternContext+PositionalUnionCaseField: Boolean get_isTheOnlyField()
3581+
FSharp.Compiler.EditorServices.PatternContext+PositionalUnionCaseField: Boolean isTheOnlyField
35803582
FSharp.Compiler.EditorServices.PatternContext+PositionalUnionCaseField: FSharp.Compiler.Text.Range caseIdRange
35813583
FSharp.Compiler.EditorServices.PatternContext+PositionalUnionCaseField: FSharp.Compiler.Text.Range get_caseIdRange()
35823584
FSharp.Compiler.EditorServices.PatternContext+PositionalUnionCaseField: Microsoft.FSharp.Core.FSharpOption`1[System.Int32] fieldIndex
35833585
FSharp.Compiler.EditorServices.PatternContext+PositionalUnionCaseField: Microsoft.FSharp.Core.FSharpOption`1[System.Int32] get_fieldIndex()
35843586
FSharp.Compiler.EditorServices.PatternContext+Tags: Int32 NamedUnionCaseField
35853587
FSharp.Compiler.EditorServices.PatternContext+Tags: Int32 Other
35863588
FSharp.Compiler.EditorServices.PatternContext+Tags: Int32 PositionalUnionCaseField
3589+
FSharp.Compiler.EditorServices.PatternContext+Tags: Int32 UnionCaseFieldIdentifier
3590+
FSharp.Compiler.EditorServices.PatternContext+UnionCaseFieldIdentifier: FSharp.Compiler.Text.Range caseIdRange
3591+
FSharp.Compiler.EditorServices.PatternContext+UnionCaseFieldIdentifier: FSharp.Compiler.Text.Range get_caseIdRange()
3592+
FSharp.Compiler.EditorServices.PatternContext+UnionCaseFieldIdentifier: Microsoft.FSharp.Collections.FSharpList`1[System.String] get_referencedFields()
3593+
FSharp.Compiler.EditorServices.PatternContext+UnionCaseFieldIdentifier: Microsoft.FSharp.Collections.FSharpList`1[System.String] referencedFields
35873594
FSharp.Compiler.EditorServices.PatternContext: Boolean Equals(FSharp.Compiler.EditorServices.PatternContext)
35883595
FSharp.Compiler.EditorServices.PatternContext: Boolean Equals(System.Object)
35893596
FSharp.Compiler.EditorServices.PatternContext: Boolean Equals(System.Object, System.Collections.IEqualityComparer)
35903597
FSharp.Compiler.EditorServices.PatternContext: Boolean IsNamedUnionCaseField
35913598
FSharp.Compiler.EditorServices.PatternContext: Boolean IsOther
35923599
FSharp.Compiler.EditorServices.PatternContext: Boolean IsPositionalUnionCaseField
3600+
FSharp.Compiler.EditorServices.PatternContext: Boolean IsUnionCaseFieldIdentifier
35933601
FSharp.Compiler.EditorServices.PatternContext: Boolean get_IsNamedUnionCaseField()
35943602
FSharp.Compiler.EditorServices.PatternContext: Boolean get_IsOther()
35953603
FSharp.Compiler.EditorServices.PatternContext: Boolean get_IsPositionalUnionCaseField()
3604+
FSharp.Compiler.EditorServices.PatternContext: Boolean get_IsUnionCaseFieldIdentifier()
35963605
FSharp.Compiler.EditorServices.PatternContext: FSharp.Compiler.EditorServices.PatternContext NewNamedUnionCaseField(System.String, FSharp.Compiler.Text.Range)
3597-
FSharp.Compiler.EditorServices.PatternContext: FSharp.Compiler.EditorServices.PatternContext NewPositionalUnionCaseField(Microsoft.FSharp.Core.FSharpOption`1[System.Int32], FSharp.Compiler.Text.Range)
3606+
FSharp.Compiler.EditorServices.PatternContext: FSharp.Compiler.EditorServices.PatternContext NewPositionalUnionCaseField(Microsoft.FSharp.Core.FSharpOption`1[System.Int32], Boolean, FSharp.Compiler.Text.Range)
3607+
FSharp.Compiler.EditorServices.PatternContext: FSharp.Compiler.EditorServices.PatternContext NewUnionCaseFieldIdentifier(Microsoft.FSharp.Collections.FSharpList`1[System.String], FSharp.Compiler.Text.Range)
35983608
FSharp.Compiler.EditorServices.PatternContext: FSharp.Compiler.EditorServices.PatternContext Other
35993609
FSharp.Compiler.EditorServices.PatternContext: FSharp.Compiler.EditorServices.PatternContext get_Other()
36003610
FSharp.Compiler.EditorServices.PatternContext: FSharp.Compiler.EditorServices.PatternContext+NamedUnionCaseField
36013611
FSharp.Compiler.EditorServices.PatternContext: FSharp.Compiler.EditorServices.PatternContext+PositionalUnionCaseField
36023612
FSharp.Compiler.EditorServices.PatternContext: FSharp.Compiler.EditorServices.PatternContext+Tags
3613+
FSharp.Compiler.EditorServices.PatternContext: FSharp.Compiler.EditorServices.PatternContext+UnionCaseFieldIdentifier
36033614
FSharp.Compiler.EditorServices.PatternContext: Int32 GetHashCode()
36043615
FSharp.Compiler.EditorServices.PatternContext: Int32 GetHashCode(System.Collections.IEqualityComparer)
36053616
FSharp.Compiler.EditorServices.PatternContext: Int32 Tag

0 commit comments

Comments
 (0)