diff --git a/src/Compiler/Service/FSharpCheckerResults.fs b/src/Compiler/Service/FSharpCheckerResults.fs index c66bb3e5142..4ace2384ffc 100644 --- a/src/Compiler/Service/FSharpCheckerResults.fs +++ b/src/Compiler/Service/FSharpCheckerResults.fs @@ -1262,7 +1262,7 @@ type internal TypeCheckInfo // No completion at '...: string' | Some (CompletionContext.RecordField (RecordContext.Declaration true)) -> None - // Completion at ' SomeMethod( ... ) ' with named arguments + // Completion at ' SomeMethod( ... ) ' or ' [] ' with named arguments | Some (CompletionContext.ParameterList (endPos, fields)) -> let results = GetNamedParametersAndSettableFields endPos diff --git a/src/Compiler/Service/ServiceParseTreeWalk.fs b/src/Compiler/Service/ServiceParseTreeWalk.fs index 7e932bdb83f..b938c8a2254 100644 --- a/src/Compiler/Service/ServiceParseTreeWalk.fs +++ b/src/Compiler/Service/ServiceParseTreeWalk.fs @@ -178,6 +178,12 @@ type SyntaxVisitorBase<'T>() = ignore path defaultTraverse synType + abstract VisitAttributeApplication: path: SyntaxVisitorPath * attributes: SynAttributeList -> 'T option + + default _.VisitAttributeApplication(path, attributes) = + ignore (path, attributes) + None + /// A range of utility functions to assist with traversing an AST module SyntaxTraversal = @@ -278,9 +284,10 @@ module SyntaxTraversal = match m with | SynModuleDecl.ModuleAbbrev (_ident, _longIdent, _range) -> None - | SynModuleDecl.NestedModule (decls = synModuleDecls) -> + | SynModuleDecl.NestedModule (decls = synModuleDecls; moduleInfo = SynComponentInfo (attributes = attributes)) -> synModuleDecls |> List.map (fun x -> dive x x.Range (traverseSynModuleDecl path)) + |> List.append (attributeApplicationDives path attributes) |> pick decl | SynModuleDecl.Let (isRecursive, synBindingList, range) -> match visitor.VisitLetOrUse(path, isRecursive, traverseSynBinding path, synBindingList, range) with @@ -296,7 +303,7 @@ module SyntaxTraversal = |> pick decl | SynModuleDecl.Exception (_synExceptionDefn, _range) -> None | SynModuleDecl.Open (_target, _range) -> None - | SynModuleDecl.Attributes (_synAttributes, _range) -> None + | SynModuleDecl.Attributes (attributes, _) -> attributeApplicationDives path attributes |> pick decl | SynModuleDecl.HashDirective (parsedHashDirective, range) -> visitor.VisitHashDirective(path, parsedHashDirective, range) | SynModuleDecl.NamespaceFragment (synModuleOrNamespace) -> traverseSynModuleOrNamespace path synModuleOrNamespace @@ -549,7 +556,7 @@ module SyntaxTraversal = | SynExpr.Lambda (args = synSimplePats; body = synExpr) -> match synSimplePats with | SynSimplePats.SimplePats (pats, _) -> - match visitor.VisitSimplePats(path, pats) with + match traverseSynSimplePats path pats with | None -> traverseSynExpr synExpr | x -> x | _ -> traverseSynExpr synExpr @@ -792,7 +799,10 @@ module SyntaxTraversal = | SynPat.Ands (ps, _) | SynPat.Tuple (_, ps, _) | SynPat.ArrayOrList (_, ps, _) -> ps |> List.tryPick (traversePat path) - | SynPat.Attrib (p, _, _) -> traversePat path p + | SynPat.Attrib (p, attributes, m) -> + match traversePat path p with + | None -> attributeApplicationDives path attributes |> pick m attributes + | x -> x | SynPat.LongIdent (argPats = args) -> match args with | SynArgPats.Pats ps -> ps |> List.tryPick (traversePat path) @@ -805,6 +815,17 @@ module SyntaxTraversal = visitor.VisitPat(origPath, defaultTraverse, pat) + and traverseSynSimplePats origPath (pats: SynSimplePat list) = + match visitor.VisitSimplePats(origPath, pats) with + | None -> + pats + |> List.tryPick (fun pat -> + match pat with + | SynSimplePat.Attrib (attributes = attributes; range = m) -> + attributeApplicationDives origPath attributes |> pick m attributes + | _ -> None) + | x -> x + and traverseSynType origPath (StripParenTypes ty) = let defaultTraverse ty = let path = SyntaxNode.SynType ty :: origPath @@ -854,36 +875,64 @@ module SyntaxTraversal = match visitor.VisitComponentInfo(origPath, synComponentInfo) with | Some x -> Some x | None -> - [ - match synTypeDefnRepr with - | SynTypeDefnRepr.Exception _ -> - // This node is generated in CheckExpressions.fs, not in the AST. - // But note exception declarations are missing from this tree walk. - () - | SynTypeDefnRepr.ObjectModel (synTypeDefnKind, synMemberDefns, _oRange) -> - // traverse inherit function is used to capture type specific data required for processing Inherit part - let traverseInherit (synType: SynType, range: range) = - visitor.VisitInheritSynMemberDefn(path, synComponentInfo, synTypeDefnKind, synType, synMemberDefns, range) + match synComponentInfo with + | SynComponentInfo (attributes = attributes) -> + [ + yield! attributeApplicationDives path attributes + + match synTypeDefnRepr with + | SynTypeDefnRepr.Exception _ -> + // This node is generated in CheckExpressions.fs, not in the AST. + // But note exception declarations are missing from this tree walk. + () + | SynTypeDefnRepr.ObjectModel (synTypeDefnKind, synMemberDefns, _oRange) -> + // traverse inherit function is used to capture type specific data required for processing Inherit part + let traverseInherit (synType: SynType, range: range) = + visitor.VisitInheritSynMemberDefn(path, synComponentInfo, synTypeDefnKind, synType, synMemberDefns, range) + yield! + synMemberDefns + |> normalizeMembersToDealWithPeculiaritiesOfGettersAndSetters path traverseInherit + | SynTypeDefnRepr.Simple (synTypeDefnSimpleRepr, _range) -> + match synTypeDefnSimpleRepr with + | SynTypeDefnSimpleRepr.Record (_synAccessOption, fields, m) -> + yield dive () synTypeDefnRepr.Range (fun () -> traverseRecordDefn path fields m) + | SynTypeDefnSimpleRepr.Union (_synAccessOption, cases, m) -> + yield dive () synTypeDefnRepr.Range (fun () -> traverseUnionDefn path cases m) + | SynTypeDefnSimpleRepr.Enum (cases, m) -> + yield dive () synTypeDefnRepr.Range (fun () -> traverseEnumDefn path cases m) + | SynTypeDefnSimpleRepr.TypeAbbrev (_, synType, m) -> + yield dive synTypeDefnRepr synTypeDefnRepr.Range (fun _ -> visitor.VisitTypeAbbrev(path, synType, m)) + | _ -> () yield! synMemberDefns - |> normalizeMembersToDealWithPeculiaritiesOfGettersAndSetters path traverseInherit - | SynTypeDefnRepr.Simple (synTypeDefnSimpleRepr, _range) -> - match synTypeDefnSimpleRepr with - | SynTypeDefnSimpleRepr.Record (_synAccessOption, fields, m) -> - yield dive () synTypeDefnRepr.Range (fun () -> visitor.VisitRecordDefn(path, fields, m)) - | SynTypeDefnSimpleRepr.Union (_synAccessOption, cases, m) -> - yield dive () synTypeDefnRepr.Range (fun () -> visitor.VisitUnionDefn(path, cases, m)) - | SynTypeDefnSimpleRepr.Enum (cases, m) -> - yield dive () synTypeDefnRepr.Range (fun () -> visitor.VisitEnumDefn(path, cases, m)) - | SynTypeDefnSimpleRepr.TypeAbbrev (_, synType, m) -> - yield dive synTypeDefnRepr synTypeDefnRepr.Range (fun _ -> visitor.VisitTypeAbbrev(path, synType, m)) - | _ -> () - yield! - synMemberDefns - |> normalizeMembersToDealWithPeculiaritiesOfGettersAndSetters path (fun _ -> None) - ] - |> pick tRange tydef + |> normalizeMembersToDealWithPeculiaritiesOfGettersAndSetters path (fun _ -> None) + ] + |> pick tRange tydef + + and traverseRecordDefn path fields m = + fields + |> List.tryPick (fun (SynField (attributes = attributes)) -> attributeApplicationDives path attributes |> pick m attributes) + |> Option.orElseWith (fun () -> visitor.VisitRecordDefn(path, fields, m)) + + and traverseEnumDefn path cases m = + cases + |> List.tryPick (fun (SynEnumCase (attributes = attributes)) -> attributeApplicationDives path attributes |> pick m attributes) + |> Option.orElseWith (fun () -> visitor.VisitEnumDefn(path, cases, m)) + + and traverseUnionDefn path cases m = + cases + |> List.tryPick (fun (SynUnionCase (attributes = attributes; caseType = caseType)) -> + match attributeApplicationDives path attributes |> pick m attributes with + | None -> + match caseType with + | SynUnionCaseKind.Fields fields -> + fields + |> List.tryPick (fun (SynField (attributes = attributes)) -> + attributeApplicationDives path attributes |> pick m attributes) + | _ -> None + | x -> x) + |> Option.orElseWith (fun () -> visitor.VisitUnionDefn(path, cases, m)) and traverseSynMemberDefn path traverseInherit (m: SynMemberDefn) = let pick (debugObj: obj) = pick m.Range debugObj @@ -903,7 +952,7 @@ module SyntaxTraversal = | SynMemberDefn.ImplicitCtor (ctorArgs = simplePats) -> match simplePats with - | SynSimplePats.SimplePats (simplePats, _) -> visitor.VisitSimplePats(path, simplePats) + | SynSimplePats.SimplePats (simplePats, _) -> traverseSynSimplePats path simplePats | _ -> None | SynMemberDefn.ImplicitInherit (synType, synExpr, _identOption, range) -> [ @@ -914,7 +963,10 @@ module SyntaxTraversal = dive () synExpr.Range (fun () -> visitor.VisitImplicitInherit(path, traverseSynExpr path, synType, synExpr, range)) ] |> pick m - | SynMemberDefn.AutoProperty (synExpr = synExpr) -> traverseSynExpr path synExpr + | SynMemberDefn.AutoProperty (synExpr = synExpr; attributes = attributes) -> + match traverseSynExpr path synExpr with + | None -> attributeApplicationDives path attributes |> pick attributes + | x -> x | SynMemberDefn.LetBindings (synBindingList, isRecursive, _, range) -> match visitor.VisitLetOrUse(path, isRecursive, traverseSynBinding path, synBindingList, range) with | None -> @@ -922,7 +974,10 @@ module SyntaxTraversal = |> List.map (fun x -> dive x x.RangeOfBindingWithRhs (traverseSynBinding path)) |> pick m | x -> x - | SynMemberDefn.AbstractSlot(slotSig = SynValSig (synType = synType)) -> traverseSynType path synType + | SynMemberDefn.AbstractSlot(slotSig = SynValSig (synType = synType; attributes = attributes)) -> + match traverseSynType path synType with + | None -> attributeApplicationDives path attributes |> pick attributes + | x -> x | SynMemberDefn.Interface (interfaceType = synType; members = synMemberDefnsOption) -> match visitor.VisitInterfaceSynMemberDefnType(path, synType) with | None -> @@ -963,13 +1018,20 @@ module SyntaxTraversal = let path = SyntaxNode.SynBinding b :: origPath match b with - | SynBinding (headPat = synPat; expr = synExpr) -> - match traversePat path synPat with - | None -> traverseSynExpr path synExpr - | x -> x + | SynBinding (headPat = synPat; expr = synExpr; attributes = attributes; range = m) -> + [ + yield! attributeApplicationDives path attributes + dive synPat synPat.Range (traversePat path) + dive synExpr synExpr.Range (traverseSynExpr path) + ] + |> pick m b visitor.VisitBinding(origPath, defaultTraverse, b) + and attributeApplicationDives origPath attributes = + attributes + |> List.map (fun attributes -> dive () attributes.Range (fun () -> visitor.VisitAttributeApplication(origPath, attributes))) + match parseTree with | ParsedInput.ImplFile file -> let l = file.Contents diff --git a/src/Compiler/Service/ServiceParseTreeWalk.fsi b/src/Compiler/Service/ServiceParseTreeWalk.fsi index 2b9819def4e..d073ca1e73c 100644 --- a/src/Compiler/Service/ServiceParseTreeWalk.fsi +++ b/src/Compiler/Service/ServiceParseTreeWalk.fsi @@ -148,6 +148,9 @@ type SyntaxVisitorBase<'T> = abstract VisitTypeAbbrev: path: SyntaxVisitorPath * synType: SynType * range: range -> 'T option default VisitTypeAbbrev: path: SyntaxVisitorPath * synType: SynType * range: range -> 'T option + abstract VisitAttributeApplication: path: SyntaxVisitorPath * attributes: SynAttributeList -> 'T option + default VisitAttributeApplication: path: SyntaxVisitorPath * attributes: SynAttributeList -> 'T option + module public SyntaxTraversal = val internal rangeContainsPosLeftEdgeInclusive: m1: range -> p: pos -> bool diff --git a/src/Compiler/Service/ServiceParsedInputOps.fs b/src/Compiler/Service/ServiceParsedInputOps.fs index b4739fe73e2..292686fe6c9 100644 --- a/src/Compiler/Service/ServiceParsedInputOps.fs +++ b/src/Compiler/Service/ServiceParsedInputOps.fs @@ -62,10 +62,11 @@ type CompletionContext = | RangeOperator - /// Completing named parameters\setters in parameter list of constructor\method calls + /// Completing named parameters\setters in parameter list of attributes\constructor\method calls /// end of name ast node * list of properties\parameters that were already set | ParameterList of pos * HashSet + /// Completing an attribute name, outside of the constructor | AttributeApplication | OpenDeclaration of isOpenType: bool @@ -1267,278 +1268,279 @@ module ParsedInput = /// Try to determine completion context for the given pair (row, columns) let TryGetCompletionContext (pos, parsedInput: ParsedInput, lineStr: string) : CompletionContext option = - match GetEntityKind(pos, parsedInput) with - | Some EntityKind.Attribute -> Some CompletionContext.AttributeApplication - | _ -> + let visitor = + { new SyntaxVisitorBase<_>() with + member _.VisitExpr(path, _, defaultTraverse, expr) = - let visitor = - { new SyntaxVisitorBase<_>() with - member _.VisitExpr(path, _, defaultTraverse, expr) = + if isAtRangeOp path then + match defaultTraverse expr with + | None -> Some CompletionContext.RangeOperator // nothing was found - report that we were in the context of range operator + | x -> x // ok, we found something - return it + else + match expr with + // new A($) + | SynExpr.Const (SynConst.Unit, m) when rangeContainsPos m pos -> + match path with + | SyntaxNode.SynExpr (NewObjectOrMethodCall args) :: _ -> Some(CompletionContext.ParameterList args) + | _ -> defaultTraverse expr - if isAtRangeOp path then - match defaultTraverse expr with - | None -> Some CompletionContext.RangeOperator // nothing was found - report that we were in the context of range operator - | x -> x // ok, we found something - return it - else - match expr with - // new A($) - | SynExpr.Const (SynConst.Unit, m) when rangeContainsPos m pos -> - match path with - | SyntaxNode.SynExpr (NewObjectOrMethodCall args) :: _ -> Some(CompletionContext.ParameterList args) - | _ -> defaultTraverse expr - - // new (... A$) - | SynExpr.Ident id - | SynExpr.LongIdent(longDotId = SynLongIdent ([ id ], [], [ Some _ ])) when id.idRange.End = pos -> - match path with - | PartOfParameterList pos None args -> Some(CompletionContext.ParameterList args) - | _ -> defaultTraverse expr - - // new (A$ = 1) - // new (A = 1, $) - | Setter id when id.idRange.End = pos || rangeBeforePos expr.Range pos -> - let precedingArgument = if id.idRange.End = pos then None else Some expr - - match path with - | PartOfParameterList pos precedingArgument args -> Some(CompletionContext.ParameterList args) - | _ -> defaultTraverse expr - - | SynExpr.Record (None, None, [], _) -> Some(CompletionContext.RecordField RecordContext.Empty) - - // Unchecked.defaultof - | SynExpr.TypeApp (typeArgsRange = range) when rangeContainsPos range pos -> Some CompletionContext.PatternType + // new (... A$) + | SynExpr.Ident id + | SynExpr.LongIdent(longDotId = SynLongIdent ([ id ], [], [ Some _ ])) when id.idRange.End = pos -> + match path with + | PartOfParameterList pos None args -> Some(CompletionContext.ParameterList args) | _ -> defaultTraverse expr - member _.VisitRecordField(path, copyOpt, field) = - let contextFromTreePath completionPath = - // detect records usage in constructor + // new (A$ = 1) + // new (A = 1, $) + | Setter id when id.idRange.End = pos || rangeBeforePos expr.Range pos -> + let precedingArgument = if id.idRange.End = pos then None else Some expr + match path with - | SyntaxNode.SynExpr _ :: SyntaxNode.SynBinding _ :: SyntaxNode.SynMemberDefn _ :: SyntaxNode.SynTypeDefn (SynTypeDefn(typeInfo = SynComponentInfo(longId = [ id ]))) :: _ -> - RecordContext.Constructor(id.idText) + | PartOfParameterList pos precedingArgument args -> Some(CompletionContext.ParameterList args) + | _ -> defaultTraverse expr - | SyntaxNode.SynExpr (SynExpr.Record (None, _, fields, _)) :: _ -> - let isFirstField = - match field, fields with - | Some contextLid, SynExprRecordField(fieldName = lid, _) :: _ -> contextLid.Range = lid.Range - | _ -> false + | SynExpr.Record (None, None, [], _) -> Some(CompletionContext.RecordField RecordContext.Empty) - RecordContext.New(completionPath, isFirstField) + // Unchecked.defaultof + | SynExpr.TypeApp (typeArgsRange = range) when rangeContainsPos range pos -> Some CompletionContext.PatternType + | _ -> defaultTraverse expr - // Unfinished `{ xxx }` expression considered a record field by the tree visitor. - | SyntaxNode.SynExpr (SynExpr.ComputationExpr _) :: _ -> RecordContext.New(completionPath, true) + member _.VisitRecordField(path, copyOpt, field) = + let contextFromTreePath completionPath = + // detect records usage in constructor + match path with + | SyntaxNode.SynExpr _ :: SyntaxNode.SynBinding _ :: SyntaxNode.SynMemberDefn _ :: SyntaxNode.SynTypeDefn (SynTypeDefn(typeInfo = SynComponentInfo(longId = [ id ]))) :: _ -> + RecordContext.Constructor(id.idText) - | _ -> RecordContext.New(completionPath, false) + | SyntaxNode.SynExpr (SynExpr.Record (None, _, fields, _)) :: _ -> + let isFirstField = + match field, fields with + | Some contextLid, SynExprRecordField(fieldName = lid, _) :: _ -> contextLid.Range = lid.Range + | _ -> false - match field with - | Some field -> - match parseLid pos field with - | Some completionPath -> - let recordContext = - match copyOpt with - | Some (s: SynExpr) -> RecordContext.CopyOnUpdate(s.Range, completionPath) - | None -> contextFromTreePath completionPath + RecordContext.New(completionPath, isFirstField) - Some(CompletionContext.RecordField recordContext) - | None -> None - | None -> + // Unfinished `{ xxx }` expression considered a record field by the tree visitor. + | SyntaxNode.SynExpr (SynExpr.ComputationExpr _) :: _ -> RecordContext.New(completionPath, true) + + | _ -> RecordContext.New(completionPath, false) + + match field with + | Some field -> + match parseLid pos field with + | Some completionPath -> let recordContext = match copyOpt with - | Some s -> RecordContext.CopyOnUpdate(s.Range, ([], None)) - | None -> contextFromTreePath ([], None) + | Some (s: SynExpr) -> RecordContext.CopyOnUpdate(s.Range, completionPath) + | None -> contextFromTreePath completionPath Some(CompletionContext.RecordField recordContext) + | None -> None + | None -> + let recordContext = + match copyOpt with + | Some s -> RecordContext.CopyOnUpdate(s.Range, ([], None)) + | None -> contextFromTreePath ([], None) - member _.VisitInheritSynMemberDefn(_, componentInfo, typeDefnKind, synType, _, _) = - match synType with - | SynType.LongIdent lidwd -> - match parseLid pos lidwd with - | Some completionPath -> GetCompletionContextForInheritSynMember(componentInfo, typeDefnKind, completionPath) - | None -> Some CompletionContext.Invalid // A $ .B -> no completion list + Some(CompletionContext.RecordField recordContext) - | _ -> None + member _.VisitInheritSynMemberDefn(_, componentInfo, typeDefnKind, synType, _, _) = + match synType with + | SynType.LongIdent lidwd -> + match parseLid pos lidwd with + | Some completionPath -> GetCompletionContextForInheritSynMember(componentInfo, typeDefnKind, completionPath) + | None -> Some CompletionContext.Invalid // A $ .B -> no completion list - member _.VisitBinding(_, defaultTraverse, (SynBinding (headPat = headPat) as synBinding)) = + | _ -> None - let visitParam (SkipFromParseErrorPat pat) = - match pat with - | SynPat.Named (range = range) - | SynPat.As (_, SynPat.Named (range = range), _) when rangeContainsPos range pos -> - // parameter without type hint, no completion - Some CompletionContext.Invalid - | SynPat.Typed (SynPat.Named (_, _, _, range), _, _) when rangeContainsPos range pos -> - // parameter with type hint, but we are on its name, no completion - Some CompletionContext.Invalid - | _ -> defaultTraverse synBinding + member _.VisitBinding(_, defaultTraverse, (SynBinding (headPat = headPat) as synBinding)) = - match headPat with - | SynPat.LongIdent (longDotId = lidwd) when rangeContainsPos lidwd.Range pos -> - // let fo|o x = () - Some CompletionContext.Invalid - | SynPat.LongIdent (argPats = ctorArgs) -> - match ctorArgs with - | SynArgPats.Pats pats -> - pats - |> List.tryPick (fun (SkipFromParseErrorPat pat) -> - match pat with - | SynPat.Paren (pat, _) -> - match pat with - | SynPat.Tuple (_, pats, _) -> pats |> List.tryPick visitParam - | _ -> visitParam pat - | SynPat.Wild range - | SynPat.FromParseError (SynPat.Named _, range) when rangeContainsPos range pos -> - // let foo (x| - Some CompletionContext.Invalid - | _ -> visitParam pat) - | _ -> defaultTraverse synBinding + let visitParam (SkipFromParseErrorPat pat) = + match pat with | SynPat.Named (range = range) | SynPat.As (_, SynPat.Named (range = range), _) when rangeContainsPos range pos -> - // let fo|o = 1 + // parameter without type hint, no completion + Some CompletionContext.Invalid + | SynPat.Typed (SynPat.Named (_, _, _, range), _, _) when rangeContainsPos range pos -> + // parameter with type hint, but we are on its name, no completion Some CompletionContext.Invalid | _ -> defaultTraverse synBinding - member _.VisitHashDirective(_, _directive, range) = - // No completions in a directive - if rangeContainsPos range pos then + match headPat with + | SynPat.LongIdent (longDotId = lidwd) when rangeContainsPos lidwd.Range pos -> + // let fo|o x = () + Some CompletionContext.Invalid + | SynPat.LongIdent (argPats = ctorArgs; range = range) when rangeContainsPos range pos -> + match ctorArgs with + | SynArgPats.Pats pats -> + pats + |> List.tryPick (fun (SkipFromParseErrorPat pat) -> + match pat with + | SynPat.Paren (pat, _) -> + match pat with + | SynPat.Tuple (_, pats, _) -> pats |> List.tryPick visitParam + | _ -> visitParam pat + | SynPat.Wild range + | SynPat.FromParseError (SynPat.Named _, range) when rangeContainsPos range pos -> + // let foo (x| + Some CompletionContext.Invalid + | _ -> visitParam pat) + | _ -> defaultTraverse synBinding + | SynPat.Named (range = range) + | SynPat.As (_, SynPat.Named (range = range), _) when rangeContainsPos range pos -> + // let fo|o = 1 + Some CompletionContext.Invalid + | _ -> defaultTraverse synBinding + + member _.VisitHashDirective(_, _directive, range) = + // No completions in a directive + if rangeContainsPos range pos then + Some CompletionContext.Invalid + else + None + + member _.VisitModuleOrNamespace(_, SynModuleOrNamespace (longId = idents)) = + match List.tryLast idents with + | 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 _.VisitModuleOrNamespace(_, SynModuleOrNamespace (longId = idents)) = - match List.tryLast idents with - | 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] + member _.VisitComponentInfo(_, 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 + + member _.VisitLetOrUse(_, _, _, bindings, range) = + match bindings with + | [] when range.StartLine = pos.Line -> Some CompletionContext.Invalid + | _ -> None - if stringBetweenModuleNameAndPos |> Seq.forall (fun x -> x = ' ' || x = '.') then - // No completions in a top level a module or namespace identifier + member _.VisitSimplePats(_, pats) = + pats + |> List.tryPick (fun pat -> + // No completions in an identifier in a pattern + match pat with + // fun x| -> + | SynSimplePat.Id (range = range) when rangeContainsPos range pos -> Some CompletionContext.Invalid + | SynSimplePat.Typed (SynSimplePat.Id (range = idRange), synType, _) -> + // fun (x|: int) -> + if rangeContainsPos idRange pos then Some CompletionContext.Invalid + // fun (x: int|) -> + elif rangeContainsPos synType.Range pos then + Some CompletionContext.PatternType else None - | _ -> None - - member _.VisitComponentInfo(_, 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 + | _ -> None) + + member _.VisitPat(_, defaultTraverse, pat) = + match pat with + | SynPat.IsInst (_, range) when rangeContainsPos range pos -> Some CompletionContext.PatternType + | _ -> defaultTraverse pat + + member _.VisitModuleDecl(_, defaultTraverse, decl) = + match decl with + | SynModuleDecl.Open (target, m) -> + // in theory, this means we're "in an open" + // in practice, because the parse tree/visitors do not handle attributes well yet, need extra check below to ensure not e.g. $here$ + // open System + // [ true + | SynOpenDeclTarget.ModuleOrNamespace _ -> false + + Some(CompletionContext.OpenDeclaration isOpenType) else None + | _ -> defaultTraverse decl - member _.VisitLetOrUse(_, _, _, bindings, range) = - match bindings with - | [] when range.StartLine = pos.Line -> Some CompletionContext.Invalid - | _ -> None - - member _.VisitSimplePats(_, pats) = - pats - |> List.tryPick (fun pat -> - // No completions in an identifier in a pattern - match pat with - // fun x| -> - | SynSimplePat.Id (range = range) when rangeContainsPos range pos -> Some CompletionContext.Invalid - | SynSimplePat.Typed (SynSimplePat.Id (range = idRange), synType, _) -> - // fun (x|: int) -> - if rangeContainsPos idRange pos then - Some CompletionContext.Invalid - // fun (x: int|) -> - elif rangeContainsPos synType.Range pos then - Some CompletionContext.PatternType - else - None - | _ -> None) - - member _.VisitPat(_, defaultTraverse, pat) = - match pat with - | SynPat.IsInst (_, range) when rangeContainsPos range pos -> Some CompletionContext.PatternType - | _ -> defaultTraverse pat - - member _.VisitModuleDecl(_, defaultTraverse, decl) = - match decl with - | SynModuleDecl.Open (target, m) -> - // in theory, this means we're "in an open" - // in practice, because the parse tree/visitors do not handle attributes well yet, need extra check below to ensure not e.g. $here$ - // open System - // [ true - | SynOpenDeclTarget.ModuleOrNamespace _ -> false - - Some(CompletionContext.OpenDeclaration isOpenType) - else - None - | _ -> defaultTraverse decl - - member _.VisitType(_, defaultTraverse, ty) = - match ty with - | SynType.LongIdent _ when rangeContainsPos ty.Range pos -> Some CompletionContext.PatternType - | _ -> defaultTraverse ty - - member _.VisitRecordDefn(_, 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) - // No completions in a record outside of all fields - |> Option.orElseWith (fun () -> - if rangeContainsPos range pos then - Some CompletionContext.Invalid - else - None) + member _.VisitType(_, defaultTraverse, ty) = + match ty with + | SynType.LongIdent _ when rangeContainsPos ty.Range pos -> Some CompletionContext.PatternType + | _ -> defaultTraverse ty - member _.VisitUnionDefn(_, cases, _) = - cases - |> List.tryPick (fun (SynUnionCase (ident = SynIdent (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(_, _, range) = - // No completions anywhere in an enum - if rangeContainsPos range pos then + member _.VisitRecordDefn(_, fields, _) = + 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) + // No completions in a record outside of all fields, except in attributes, which is established earlier in VisitAttributeApplication + |> Option.orElse (Some CompletionContext.Invalid) + + member _.VisitUnionDefn(_, cases, _) = + cases + |> List.tryPick (fun (SynUnionCase (ident = SynIdent (id, _); caseType = caseType)) -> + if rangeContainsPos id.idRange pos then + // No completions in a union case identifier Some CompletionContext.Invalid else - None + 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(_, _, _) = + // No completions anywhere in an enum, except in attributes, which is established earlier in VisitAttributeApplication + Some CompletionContext.Invalid - member _.VisitTypeAbbrev(_, _, range) = - if rangeContainsPos range pos then - Some CompletionContext.TypeAbbreviationOrSingleCaseUnion + member _.VisitTypeAbbrev(_, _, range) = + if rangeContainsPos range pos then + Some CompletionContext.TypeAbbreviationOrSingleCaseUnion + else + None + + member _.VisitAttributeApplication(_, attributes) = + attributes.Attributes + |> List.tryPick (fun att -> + // [] + if rangeContainsPos att.TypeName.Range pos then + Some CompletionContext.AttributeApplication + // [] + elif rangeContainsPos att.ArgExpr.Range pos then + Some(CompletionContext.ParameterList(att.TypeName.Range.End, findSetters att.ArgExpr)) else - None - } + None) + } - let ctxt = SyntaxTraversal.Traverse(pos, parsedInput, visitor) + let ctxt = SyntaxTraversal.Traverse(pos, parsedInput, visitor) - match ctxt with - | Some _ -> ctxt - | _ -> TryGetCompletionContextOfAttributes(pos, lineStr) + match ctxt with + | Some _ -> ctxt + | _ -> TryGetCompletionContextOfAttributes(pos, lineStr) //-------------------------------------------------------------------------------------------- // TryGetInsertionContext diff --git a/src/Compiler/Service/ServiceParsedInputOps.fsi b/src/Compiler/Service/ServiceParsedInputOps.fsi index f0542957759..5fb5e85d8cd 100644 --- a/src/Compiler/Service/ServiceParsedInputOps.fsi +++ b/src/Compiler/Service/ServiceParsedInputOps.fsi @@ -35,10 +35,11 @@ type public CompletionContext = | RangeOperator - /// Completing named parameters\setters in parameter list of constructor\method calls + /// Completing named parameters\setters in parameter list of attributes\constructor\method calls /// end of name ast node * list of properties\parameters that were already set | ParameterList of pos * HashSet + /// Completing an attribute name, outside of the constructor | AttributeApplication | OpenDeclaration of isOpenType: bool diff --git a/tests/FSharp.Compiler.Private.Scripting.UnitTests/CompletionTests.fs b/tests/FSharp.Compiler.Private.Scripting.UnitTests/CompletionTests.fs index 8bc7fa581a1..01dd539eab7 100644 --- a/tests/FSharp.Compiler.Private.Scripting.UnitTests/CompletionTests.fs +++ b/tests/FSharp.Compiler.Private.Scripting.UnitTests/CompletionTests.fs @@ -157,4 +157,13 @@ type CompletionTests() = Assert.Equal(1, matchingCompletions.Length) Assert.Equal("A", matchingCompletions.[0].NameInCode) - + [] + member _.``Completions for record field with backticks`` () = + use script = new FSharpScript() + let lines = [ "type MyRec = { ``field.field`` : string }" + "let a = {``field.field`` = \"\"}" + "a." ] + let completions = script.GetCompletionItems(String.Join("\n", lines), 3, 2) |> Async.RunSynchronously + let matchingCompletions = completions |> Array.filter (fun d -> d.NameInList = "field.field") + Assert.Equal(1, matchingCompletions.Length) + Assert.Equal("``field.field``", matchingCompletions.[0].NameInCode) \ No newline at end of file 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 1bce6c61d8d..c80440dc2ac 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 @@ -9102,6 +9102,7 @@ FSharp.Compiler.Syntax.SyntaxNode: Int32 Tag FSharp.Compiler.Syntax.SyntaxNode: Int32 get_Tag() FSharp.Compiler.Syntax.SyntaxNode: System.String ToString() FSharp.Compiler.Syntax.SyntaxTraversal: Microsoft.FSharp.Core.FSharpOption`1[T] Traverse[T](FSharp.Compiler.Text.Position, FSharp.Compiler.Syntax.ParsedInput, FSharp.Compiler.Syntax.SyntaxVisitorBase`1[T]) +FSharp.Compiler.Syntax.SyntaxVisitorBase`1[T]: Microsoft.FSharp.Core.FSharpOption`1[T] VisitAttributeApplication(Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.Syntax.SyntaxNode], FSharp.Compiler.Syntax.SynAttributeList) FSharp.Compiler.Syntax.SyntaxVisitorBase`1[T]: Microsoft.FSharp.Core.FSharpOption`1[T] VisitBinding(Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.Syntax.SyntaxNode], Microsoft.FSharp.Core.FSharpFunc`2[FSharp.Compiler.Syntax.SynBinding,Microsoft.FSharp.Core.FSharpOption`1[T]], FSharp.Compiler.Syntax.SynBinding) FSharp.Compiler.Syntax.SyntaxVisitorBase`1[T]: Microsoft.FSharp.Core.FSharpOption`1[T] VisitComponentInfo(Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.Syntax.SyntaxNode], FSharp.Compiler.Syntax.SynComponentInfo) FSharp.Compiler.Syntax.SyntaxVisitorBase`1[T]: Microsoft.FSharp.Core.FSharpOption`1[T] VisitEnumDefn(Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.Syntax.SyntaxNode], Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.Syntax.SynEnumCase], FSharp.Compiler.Text.Range) 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 1bce6c61d8d..c80440dc2ac 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 @@ -9102,6 +9102,7 @@ FSharp.Compiler.Syntax.SyntaxNode: Int32 Tag FSharp.Compiler.Syntax.SyntaxNode: Int32 get_Tag() FSharp.Compiler.Syntax.SyntaxNode: System.String ToString() FSharp.Compiler.Syntax.SyntaxTraversal: Microsoft.FSharp.Core.FSharpOption`1[T] Traverse[T](FSharp.Compiler.Text.Position, FSharp.Compiler.Syntax.ParsedInput, FSharp.Compiler.Syntax.SyntaxVisitorBase`1[T]) +FSharp.Compiler.Syntax.SyntaxVisitorBase`1[T]: Microsoft.FSharp.Core.FSharpOption`1[T] VisitAttributeApplication(Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.Syntax.SyntaxNode], FSharp.Compiler.Syntax.SynAttributeList) FSharp.Compiler.Syntax.SyntaxVisitorBase`1[T]: Microsoft.FSharp.Core.FSharpOption`1[T] VisitBinding(Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.Syntax.SyntaxNode], Microsoft.FSharp.Core.FSharpFunc`2[FSharp.Compiler.Syntax.SynBinding,Microsoft.FSharp.Core.FSharpOption`1[T]], FSharp.Compiler.Syntax.SynBinding) FSharp.Compiler.Syntax.SyntaxVisitorBase`1[T]: Microsoft.FSharp.Core.FSharpOption`1[T] VisitComponentInfo(Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.Syntax.SyntaxNode], FSharp.Compiler.Syntax.SynComponentInfo) FSharp.Compiler.Syntax.SyntaxVisitorBase`1[T]: Microsoft.FSharp.Core.FSharpOption`1[T] VisitEnumDefn(Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.Syntax.SyntaxNode], Microsoft.FSharp.Collections.FSharpList`1[FSharp.Compiler.Syntax.SynEnumCase], FSharp.Compiler.Text.Range) diff --git a/tests/service/ServiceUntypedParseTests.fs b/tests/service/ServiceUntypedParseTests.fs index 86b3bd83d2a..ea2591b8bf9 100644 --- a/tests/service/ServiceUntypedParseTests.fs +++ b/tests/service/ServiceUntypedParseTests.fs @@ -19,7 +19,7 @@ open NUnit.Framework let [] private Marker = "(* marker *)" -let private (=>) (source: string) (expected: CompletionContext option) = +let private assertCompletionContext (checker: CompletionContext option -> bool) (source: string) = let lines = use reader = new StringReader(source) @@ -43,10 +43,10 @@ let private (=>) (source: string) (expected: CompletionContext option) = | Some markerPos -> let parseTree = parseSourceCode("C:\\test.fs", source) let actual = ParsedInput.TryGetCompletionContext(markerPos, parseTree, lines[Line.toZ markerPos.Line]) - try Assert.AreEqual(expected, actual) - with e -> + + if not (checker actual) then printfn "ParseTree: %A" parseTree - reraise() + failwithf "Completion context '%A' was not expected" actual module AttributeCompletion = [] @@ -54,7 +54,7 @@ module AttributeCompletion = """ [<(* marker *) """ - => Some CompletionContext.AttributeApplication + |> assertCompletionContext (fun x -> x = Some CompletionContext.AttributeApplication) [] [] @@ -65,17 +65,13 @@ module AttributeCompletion = [] [][<(* marker *)", true)>] [][< (* marker *)", true)>] - [] - [] - [] - [][] - [] let ``incomplete``(lineStr: string, expectAttributeApplicationContext: bool) = - (sprintf """ -%s + let code = $""" +{lineStr} type T = - { F: int } -""" lineStr) => (if expectAttributeApplicationContext then Some CompletionContext.AttributeApplication else None) + {{ F: int }} +""" + code |> assertCompletionContext (fun x -> x = (if expectAttributeApplicationContext then Some CompletionContext.AttributeApplication else None)) []", true)>] []", true)>] @@ -87,20 +83,40 @@ type T = []", true)>] []", true)>] [][]", true)>] - []", false)>] []", false)>] - [][]", false)>] - []", false)>] - []", false)>] - [][]", false)>] let ``complete``(lineStr: string, expectAttributeApplicationContext: bool) = - (sprintf """ -%s + let code = $""" +{lineStr} type T = - { F: int } -""" lineStr) => (if expectAttributeApplicationContext then Some CompletionContext.AttributeApplication else None) - - + {{ F: int }} +""" + code |> assertCompletionContext (fun x -> x = (if expectAttributeApplicationContext then Some CompletionContext.AttributeApplication else None)) + +module AttributeConstructorCompletion = + [] + [] + [][] + [] + let ``incomplete``(lineStr: string) = + let code = $""" +{lineStr} +type T = + {{ F: int }} +""" + code |> assertCompletionContext (fun x -> match x with Some (CompletionContext.ParameterList _) -> true | _ -> false) + + []")>] + [][]")>] + []")>] + []")>] + [][]")>] + let ``complete``(lineStr: string) = + let code = $""" +{lineStr} +type T = + {{ F: int }} +""" + code |> assertCompletionContext (fun x -> match x with Some (CompletionContext.ParameterList _) -> true | _ -> false) [] let ``Attribute lists`` () = diff --git a/vsintegration/tests/FSharp.Editor.Tests/CompletionProviderTests.fs b/vsintegration/tests/FSharp.Editor.Tests/CompletionProviderTests.fs index 2be760394f9..b5b6685138c 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/CompletionProviderTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/CompletionProviderTests.fs @@ -1266,3 +1266,47 @@ let inline f_StaticProperty_SRTP<'T when 'T : (static member StaticProperty: 'T) """ VerifyCompletionListWithOptions(fileContents, "'T.", [ "StaticProperty" ], [], [| "/langversion:preview" |]) + + [] + let ``Completion list for attribute application contains settable members and ctor parameters`` () = + let fileContents = + """ +type LangAttribute (langParam: int) = + inherit System.Attribute () + + member val LangMember1 = 0 with get, set + member val LangMember2 = 0 with get, set + +[] +module X = + [< Lang(2, LangMember1 = 2)>] + let a = () + +[< Lang(3, LangMember1 = 3, L)>] +type B () = + [< Lang(la)>] + member _.M = "" + +type G = { [] f: string } + +type A = + | [] A = 1 +""" + + // Attribute on module, completing attribute name - settable properties omitted + VerifyCompletionList(fileContents, "[] - member this.``AutoCompletion.escaped with backticks`` () = - let code = """ -type MyRec = { - ``field.field`` : string -} - -let a = {``field.field`` = ""} -a. - """ - this.AssertCtrlSpaceCompletion( - [code] - , "a." - , (fun completions -> - completions - |> Seq.tryFind (fun (CompletionItem(_,name,nameInCode,_,_)) -> - name = "field.field" - && nameInCode = "``field.field``" - ) - |> function - | Some _ -> () - | None -> Assert.Fail "expected ``field.field`` to be present" - - )) - [] member this.``AutoCompletion.BeforeThis``() = let code =