diff --git a/src/fsharp/service/FSharpCheckerResults.fs b/src/fsharp/service/FSharpCheckerResults.fs index 8c6945d229b..d45c277e5b5 100644 --- a/src/fsharp/service/FSharpCheckerResults.fs +++ b/src/fsharp/service/FSharpCheckerResults.fs @@ -974,6 +974,9 @@ type internal TypeCheckInfo Some(GetClassOrRecordFieldsEnvironmentLookupResolutions(mkPos line loc, [typeName])) |> Option.map toCompletionItems + // No completion at '...: string' + | Some(CompletionContext.RecordField(RecordContext.Declaration true)) -> None + // Completion at ' SomeMethod( ... ) ' with named arguments | Some(CompletionContext.ParameterList (endPos, fields)) -> let results = GetNamedParametersAndSettableFields endPos @@ -1022,7 +1025,13 @@ type internal TypeCheckInfo | _ -> false), denv, m) // Completion at '(x: ...)" - | Some CompletionContext.PatternType -> + | Some CompletionContext.PatternType + // Completion at '| Case1 of ...' + | Some CompletionContext.UnionCaseFieldsDeclaration + // Completion at 'type Long = int6...' or 'type SomeUnion = Abc...' + | Some CompletionContext.TypeAbbreviationOrSingleCaseUnion + // Completion at 'Field1: ...' + | Some(CompletionContext.RecordField(RecordContext.Declaration false)) -> GetDeclaredItems (parseResultsOpt, lineStr, origLongIdentOpt, colAtEndOfNamesAndResidue, residueOpt, lastDotPos, line, loc, filterCtors, resolveOverloads, false, getAllSymbols) |> Option.map (fun (items, denv, m) -> items diff --git a/src/fsharp/service/ServiceParsedInputOps.fs b/src/fsharp/service/ServiceParsedInputOps.fs index 93c6fa25c88..a3605285ae2 100644 --- a/src/fsharp/service/ServiceParsedInputOps.fs +++ b/src/fsharp/service/ServiceParsedInputOps.fs @@ -44,6 +44,7 @@ type RecordContext = | CopyOnUpdate of range: range * path: CompletionPath | Constructor of typeName: string | New of path: CompletionPath + | Declaration of isInIdentifier: bool [] type CompletionContext = @@ -69,6 +70,13 @@ type CompletionContext = /// Completing pattern type (e.g. foo (x: |)) | PatternType + /// Completing union case fields declaration (e.g. 'A of stri|' but not 'B of tex|: string') + | UnionCaseFieldsDeclaration + + /// Completing a type abbreviation (e.g. type Long = int6|) + /// or a single case union without a bar (type SomeUnion = Abc|) + | TypeAbbreviationOrSingleCaseUnion + type ShortIdent = string type ShortIdents = ShortIdent[] @@ -1022,7 +1030,8 @@ module ParsedInput = Some CompletionContext.Invalid | _ -> defaultTraverse synBinding - member _.VisitHashDirective (_path, _directive, range) = + member _.VisitHashDirective (_path, _directive, range) = + // No completions in a directive if rangeContainsPos range pos then Some CompletionContext.Invalid else None @@ -1031,11 +1040,15 @@ module ParsedInput = | Some lastIdent when pos.Line = lastIdent.idRange.EndLine && lastIdent.idRange.EndColumn >= 0 && pos.Column <= lineStr.Length -> let stringBetweenModuleNameAndPos = lineStr.[lastIdent.idRange.EndColumn..pos.Column - 1] if stringBetweenModuleNameAndPos |> Seq.forall (fun x -> x = ' ' || x = '.') then + // No completions in a top level a module or namespace identifier Some CompletionContext.Invalid else None | _ -> None - member _.VisitComponentInfo(_path, SynComponentInfo(range = range)) = + member _.VisitComponentInfo(_path, SynComponentInfo(range = range)) = + // No completions in component info (unless it's within an attribute) + // /// XmlDo| + // type R = class end if rangeContainsPos range pos then Some CompletionContext.Invalid else None @@ -1046,9 +1059,12 @@ module ParsedInput = member _.VisitSimplePats (_path, pats) = pats |> List.tryPick (fun pat -> + // No completions in an identifier or type in a pattern match pat with + // fun x| -> | SynSimplePat.Id(range = range) - | SynSimplePat.Typed(SynSimplePat.Id(range = range), _, _) when rangeContainsPos range pos -> + // fun (x: int|) -> + | SynSimplePat.Typed(SynSimplePat.Id(range = range), _, _) when rangeContainsPos range pos -> Some CompletionContext.Invalid | _ -> None) @@ -1077,6 +1093,35 @@ module ParsedInput = | SynType.LongIdent _ when rangeContainsPos ty.Range pos -> Some CompletionContext.PatternType | _ -> defaultTraverse ty + + member _.VisitRecordDefn(_path, fields, _range) = + fields |> List.tryPick (fun (SynField (idOpt = idOpt; range = fieldRange)) -> + match idOpt with + | Some id when rangeContainsPos id.idRange pos -> Some(CompletionContext.RecordField(RecordContext.Declaration true)) + | _ when rangeContainsPos fieldRange pos -> Some(CompletionContext.RecordField(RecordContext.Declaration false)) + | _ -> None) + + member _.VisitUnionDefn(_path, cases, _range) = + cases |> List.tryPick (fun (SynUnionCase (ident = id; caseType = caseType)) -> + if rangeContainsPos id.idRange pos then + // No completions in a union case identifier + Some CompletionContext.Invalid + else + match caseType with + | SynUnionCaseKind.Fields fieldCases -> + fieldCases |> List.tryPick (fun (SynField (idOpt = fieldIdOpt; range = fieldRange)) -> + match fieldIdOpt with + // No completions in a union case field identifier + | Some id when rangeContainsPos id.idRange pos -> Some CompletionContext.Invalid + | _ -> if rangeContainsPos fieldRange pos then Some CompletionContext.UnionCaseFieldsDeclaration else None) + | _ -> None) + + member _.VisitEnumDefn(_path, _, range) = + // No completions anywhere in an enum + if rangeContainsPos range pos then Some CompletionContext.Invalid else None + + member _.VisitTypeAbbrev(_path, _, range) = + if rangeContainsPos range pos then Some CompletionContext.TypeAbbreviationOrSingleCaseUnion else None } SyntaxTraversal.Traverse(pos, parsedInput, walker) diff --git a/src/fsharp/service/ServiceParsedInputOps.fsi b/src/fsharp/service/ServiceParsedInputOps.fsi index d0c36e03cc9..2180b700416 100644 --- a/src/fsharp/service/ServiceParsedInputOps.fsi +++ b/src/fsharp/service/ServiceParsedInputOps.fsi @@ -19,6 +19,7 @@ type public RecordContext = | CopyOnUpdate of range: range * path: CompletionPath | Constructor of typeName: string | New of path: CompletionPath + | Declaration of isInIdentifier: bool [] type public CompletionContext = @@ -44,6 +45,13 @@ type public CompletionContext = /// Completing pattern type (e.g. foo (x: |)) | PatternType + /// Completing union case fields declaration (e.g. 'A of stri|' but not 'B of tex|: string') + | UnionCaseFieldsDeclaration + + /// Completing a type abbreviation (e.g. type Long = int6|) + /// or a single case union without a bar (type SomeUnion = Abc|) + | TypeAbbreviationOrSingleCaseUnion + type public ModuleKind = { IsAutoOpen: bool HasModuleSuffix: bool } diff --git a/tests/FSharp.Compiler.Service.Tests/FSharp.CompilerService.SurfaceArea.netstandard.expected b/tests/FSharp.Compiler.Service.Tests/FSharp.CompilerService.SurfaceArea.netstandard.expected index 49201d40170..238a7e1432b 100644 --- a/tests/FSharp.Compiler.Service.Tests/FSharp.CompilerService.SurfaceArea.netstandard.expected +++ b/tests/FSharp.Compiler.Service.Tests/FSharp.CompilerService.SurfaceArea.netstandard.expected @@ -2440,6 +2440,8 @@ FSharp.Compiler.EditorServices.CompletionContext+Tags: Int32 ParameterList FSharp.Compiler.EditorServices.CompletionContext+Tags: Int32 PatternType FSharp.Compiler.EditorServices.CompletionContext+Tags: Int32 RangeOperator FSharp.Compiler.EditorServices.CompletionContext+Tags: Int32 RecordField +FSharp.Compiler.EditorServices.CompletionContext+Tags: Int32 TypeAbbreviationOrSingleCaseUnion +FSharp.Compiler.EditorServices.CompletionContext+Tags: Int32 UnionCaseFieldsDeclaration FSharp.Compiler.EditorServices.CompletionContext: Boolean Equals(FSharp.Compiler.EditorServices.CompletionContext) FSharp.Compiler.EditorServices.CompletionContext: Boolean Equals(System.Object) FSharp.Compiler.EditorServices.CompletionContext: Boolean Equals(System.Object, System.Collections.IEqualityComparer) @@ -2451,6 +2453,8 @@ FSharp.Compiler.EditorServices.CompletionContext: Boolean IsParameterList FSharp.Compiler.EditorServices.CompletionContext: Boolean IsPatternType FSharp.Compiler.EditorServices.CompletionContext: Boolean IsRangeOperator FSharp.Compiler.EditorServices.CompletionContext: Boolean IsRecordField +FSharp.Compiler.EditorServices.CompletionContext: Boolean IsTypeAbbreviationOrSingleCaseUnion +FSharp.Compiler.EditorServices.CompletionContext: Boolean IsUnionCaseFieldsDeclaration FSharp.Compiler.EditorServices.CompletionContext: Boolean get_IsAttributeApplication() FSharp.Compiler.EditorServices.CompletionContext: Boolean get_IsInherit() FSharp.Compiler.EditorServices.CompletionContext: Boolean get_IsInvalid() @@ -2459,6 +2463,8 @@ FSharp.Compiler.EditorServices.CompletionContext: Boolean get_IsParameterList() FSharp.Compiler.EditorServices.CompletionContext: Boolean get_IsPatternType() FSharp.Compiler.EditorServices.CompletionContext: Boolean get_IsRangeOperator() FSharp.Compiler.EditorServices.CompletionContext: Boolean get_IsRecordField() +FSharp.Compiler.EditorServices.CompletionContext: Boolean get_IsTypeAbbreviationOrSingleCaseUnion() +FSharp.Compiler.EditorServices.CompletionContext: Boolean get_IsUnionCaseFieldsDeclaration() FSharp.Compiler.EditorServices.CompletionContext: FSharp.Compiler.EditorServices.CompletionContext AttributeApplication FSharp.Compiler.EditorServices.CompletionContext: FSharp.Compiler.EditorServices.CompletionContext Invalid FSharp.Compiler.EditorServices.CompletionContext: FSharp.Compiler.EditorServices.CompletionContext NewInherit(FSharp.Compiler.EditorServices.InheritanceContext, System.Tuple`2[Microsoft.FSharp.Collections.FSharpList`1[System.String],Microsoft.FSharp.Core.FSharpOption`1[System.String]]) @@ -2467,10 +2473,14 @@ FSharp.Compiler.EditorServices.CompletionContext: FSharp.Compiler.EditorServices FSharp.Compiler.EditorServices.CompletionContext: FSharp.Compiler.EditorServices.CompletionContext NewRecordField(FSharp.Compiler.EditorServices.RecordContext) FSharp.Compiler.EditorServices.CompletionContext: FSharp.Compiler.EditorServices.CompletionContext PatternType FSharp.Compiler.EditorServices.CompletionContext: FSharp.Compiler.EditorServices.CompletionContext RangeOperator +FSharp.Compiler.EditorServices.CompletionContext: FSharp.Compiler.EditorServices.CompletionContext TypeAbbreviationOrSingleCaseUnion +FSharp.Compiler.EditorServices.CompletionContext: FSharp.Compiler.EditorServices.CompletionContext UnionCaseFieldsDeclaration FSharp.Compiler.EditorServices.CompletionContext: FSharp.Compiler.EditorServices.CompletionContext get_AttributeApplication() FSharp.Compiler.EditorServices.CompletionContext: FSharp.Compiler.EditorServices.CompletionContext get_Invalid() FSharp.Compiler.EditorServices.CompletionContext: FSharp.Compiler.EditorServices.CompletionContext get_PatternType() FSharp.Compiler.EditorServices.CompletionContext: FSharp.Compiler.EditorServices.CompletionContext get_RangeOperator() +FSharp.Compiler.EditorServices.CompletionContext: FSharp.Compiler.EditorServices.CompletionContext get_TypeAbbreviationOrSingleCaseUnion() +FSharp.Compiler.EditorServices.CompletionContext: FSharp.Compiler.EditorServices.CompletionContext get_UnionCaseFieldsDeclaration() FSharp.Compiler.EditorServices.CompletionContext: FSharp.Compiler.EditorServices.CompletionContext+Inherit FSharp.Compiler.EditorServices.CompletionContext: FSharp.Compiler.EditorServices.CompletionContext+OpenDeclaration FSharp.Compiler.EditorServices.CompletionContext: FSharp.Compiler.EditorServices.CompletionContext+ParameterList @@ -3472,25 +3482,32 @@ FSharp.Compiler.EditorServices.RecordContext+CopyOnUpdate: FSharp.Compiler.Text. FSharp.Compiler.EditorServices.RecordContext+CopyOnUpdate: FSharp.Compiler.Text.Range range FSharp.Compiler.EditorServices.RecordContext+CopyOnUpdate: System.Tuple`2[Microsoft.FSharp.Collections.FSharpList`1[System.String],Microsoft.FSharp.Core.FSharpOption`1[System.String]] get_path() FSharp.Compiler.EditorServices.RecordContext+CopyOnUpdate: System.Tuple`2[Microsoft.FSharp.Collections.FSharpList`1[System.String],Microsoft.FSharp.Core.FSharpOption`1[System.String]] path +FSharp.Compiler.EditorServices.RecordContext+Declaration: Boolean get_isInIdentifier() +FSharp.Compiler.EditorServices.RecordContext+Declaration: Boolean isInIdentifier FSharp.Compiler.EditorServices.RecordContext+New: System.Tuple`2[Microsoft.FSharp.Collections.FSharpList`1[System.String],Microsoft.FSharp.Core.FSharpOption`1[System.String]] get_path() FSharp.Compiler.EditorServices.RecordContext+New: System.Tuple`2[Microsoft.FSharp.Collections.FSharpList`1[System.String],Microsoft.FSharp.Core.FSharpOption`1[System.String]] path FSharp.Compiler.EditorServices.RecordContext+Tags: Int32 Constructor FSharp.Compiler.EditorServices.RecordContext+Tags: Int32 CopyOnUpdate +FSharp.Compiler.EditorServices.RecordContext+Tags: Int32 Declaration FSharp.Compiler.EditorServices.RecordContext+Tags: Int32 New FSharp.Compiler.EditorServices.RecordContext: Boolean Equals(FSharp.Compiler.EditorServices.RecordContext) FSharp.Compiler.EditorServices.RecordContext: Boolean Equals(System.Object) FSharp.Compiler.EditorServices.RecordContext: Boolean Equals(System.Object, System.Collections.IEqualityComparer) FSharp.Compiler.EditorServices.RecordContext: Boolean IsConstructor FSharp.Compiler.EditorServices.RecordContext: Boolean IsCopyOnUpdate +FSharp.Compiler.EditorServices.RecordContext: Boolean IsDeclaration FSharp.Compiler.EditorServices.RecordContext: Boolean IsNew FSharp.Compiler.EditorServices.RecordContext: Boolean get_IsConstructor() FSharp.Compiler.EditorServices.RecordContext: Boolean get_IsCopyOnUpdate() +FSharp.Compiler.EditorServices.RecordContext: Boolean get_IsDeclaration() FSharp.Compiler.EditorServices.RecordContext: Boolean get_IsNew() FSharp.Compiler.EditorServices.RecordContext: FSharp.Compiler.EditorServices.RecordContext NewConstructor(System.String) FSharp.Compiler.EditorServices.RecordContext: FSharp.Compiler.EditorServices.RecordContext NewCopyOnUpdate(FSharp.Compiler.Text.Range, System.Tuple`2[Microsoft.FSharp.Collections.FSharpList`1[System.String],Microsoft.FSharp.Core.FSharpOption`1[System.String]]) +FSharp.Compiler.EditorServices.RecordContext: FSharp.Compiler.EditorServices.RecordContext NewDeclaration(Boolean) FSharp.Compiler.EditorServices.RecordContext: FSharp.Compiler.EditorServices.RecordContext NewNew(System.Tuple`2[Microsoft.FSharp.Collections.FSharpList`1[System.String],Microsoft.FSharp.Core.FSharpOption`1[System.String]]) FSharp.Compiler.EditorServices.RecordContext: FSharp.Compiler.EditorServices.RecordContext+Constructor FSharp.Compiler.EditorServices.RecordContext: FSharp.Compiler.EditorServices.RecordContext+CopyOnUpdate +FSharp.Compiler.EditorServices.RecordContext: FSharp.Compiler.EditorServices.RecordContext+Declaration FSharp.Compiler.EditorServices.RecordContext: FSharp.Compiler.EditorServices.RecordContext+New FSharp.Compiler.EditorServices.RecordContext: FSharp.Compiler.EditorServices.RecordContext+Tags FSharp.Compiler.EditorServices.RecordContext: Int32 GetHashCode() diff --git a/vsintegration/tests/UnitTests/CompletionProviderTests.fs b/vsintegration/tests/UnitTests/CompletionProviderTests.fs index 65f16f53ff8..0f362943d66 100644 --- a/vsintegration/tests/UnitTests/CompletionProviderTests.fs +++ b/vsintegration/tests/UnitTests/CompletionProviderTests.fs @@ -736,6 +736,67 @@ let x = A.``B C`` + D.``E F`` """ VerifyCompletionListSpan(fileContents, "D.``E F``", "``E F``") +[] +let ``No completion on record field identifier at declaration site``() = + let fileContents = """ +type A = { le: string } +""" + VerifyNoCompletionList(fileContents, "le") + +[] +let ``Completion list on record field type at declaration site contains modules and types but not keywords or functions``() = + let fileContents = """ +type A = { Field: l } +""" + VerifyCompletionList(fileContents, "Field: l", ["LanguagePrimitives"; "List"], ["let"; "log"]) + +[] +let ``No completion on union case identifier at declaration site``() = + let fileContents = """ +type A = + | C of string +""" + VerifyNoCompletionList(fileContents, "| C") + +[] +let ``No completion on union case field identifier at declaration site``() = + let fileContents = """ +type A = + | Case of blah: int * str: int +""" + VerifyNoCompletionList(fileContents, "str") + +[] +let ``Completion list on union case type at declaration site contains modules and types but not keywords or functions``() = + let fileContents = """ +type A = + | Case of blah: int * str: l +""" + VerifyCompletionList(fileContents, "str: l", ["LanguagePrimitives"; "List"], ["let"; "log"]) + +[] +let ``Completion list on union case type at declaration site contains modules and types but not keywords or functions2``() = + let fileContents = """ +type A = + | Case of l +""" + VerifyCompletionList(fileContents, "of l", ["LanguagePrimitives"; "List"], ["let"; "log"]) + +[] +let ``Completion list on type alias contains modules and types but not keywords or functions``() = + let fileContents = """ +type A = l +""" + VerifyCompletionList(fileContents, "= l", ["LanguagePrimitives"; "List"], ["let"; "log"]) + +[] +let ``No completion on enum case identifier at declaration site``() = + let fileContents = """ +type A = + | C = 0 +""" + VerifyNoCompletionList(fileContents, "| C") + #if EXE ShouldDisplaySystemNamespace() #endif diff --git a/vsintegration/tests/UnitTests/LegacyLanguageService/Tests.LanguageService.Completion.fs b/vsintegration/tests/UnitTests/LegacyLanguageService/Tests.LanguageService.Completion.fs index 6b9eda12e1a..c59602db4cd 100644 --- a/vsintegration/tests/UnitTests/LegacyLanguageService/Tests.LanguageService.Completion.fs +++ b/vsintegration/tests/UnitTests/LegacyLanguageService/Tests.LanguageService.Completion.fs @@ -4257,38 +4257,6 @@ let x = query { for bbbb in abbbbc(*D0*) do [ "Contains" ] // should contain [ ] // should not contain - [] - member public this.``InDeclaration.Bug3176a``() = - AssertCtrlSpaceCompleteContains - [ "type T<'a> = { aaaa : 'a; bbbb : int } " ] - "aa" // marker - [ "aaaa" ] // should contain - [ "bbbb" ] // should not contain - - [] - member public this.``InDeclaration.Bug3176b``() = - AssertCtrlSpaceCompleteContains - [ "type T<'a> = { aaaa : 'a; bbbb : int } " ] - "bb" // marker - [ "bbbb" ] // should contain - [ "aaaa" ] // should not contain - - [] - member public this.``InDeclaration.Bug3176c``() = - AssertCtrlSpaceCompleteContains - [ "type C ="; - " val aaaa: int" ] - "aa" // move to marker - ["aaaa"] [] // should contain 'aaaa' - - [] - member public this.``InDeclaration.Bug3176d``() = - AssertCtrlSpaceCompleteContains - [ "type DU<'a> ="; - " | DULabel of 'a" ] - "DULab" // move to marker - ["DULabel"] [] // should contain 'DULabel' - [] member public this.``IncompleteIfClause.Bug4594``() = AssertCtrlSpaceCompleteContains