diff --git a/src/Compiler/Service/FSharpCheckerResults.fs b/src/Compiler/Service/FSharpCheckerResults.fs index cab12da5cdf..705f2d02551 100644 --- a/src/Compiler/Service/FSharpCheckerResults.fs +++ b/src/Compiler/Service/FSharpCheckerResults.fs @@ -311,7 +311,7 @@ type FSharpSymbolUse(denv: DisplayEnv, symbol: FSharpSymbol, inst: TyparInstanti /// (Depending on the kind of the items, we may stop processing or continue to find better items) [] type NameResResult = - | Members of (ItemWithInst list * DisplayEnv * range) + | Members of ((ItemWithInst * AssemblySymbol voption) list * DisplayEnv * range) | Cancel of DisplayEnv * range | Empty @@ -325,7 +325,7 @@ type ExprTypingsResult = | NoneBecauseTypecheckIsStaleAndTextChanged | NoneBecauseThereWereTypeErrors | None - | Some of (ItemWithInst list * DisplayEnv * range) * TType + | Some of ((ItemWithInst * AssemblySymbol voption) list * DisplayEnv * range) * TType type Names = string list @@ -443,6 +443,121 @@ type internal TypeCheckInfo items + let getExtMethsOfType m ty getAllSymbols = + /// Checks if the type is used for C# style extension members. + let IsTyconRefUsedForCSharpStyleExtensionMembers g m (tcref: TyconRef) = + // Type must be non-generic and have 'Extension' attribute + isNil (tcref.Typars m) + && TyconRefHasAttribute g m g.attrib_ExtensionAttribute tcref + || g.langVersion.SupportsFeature(LanguageFeature.CSharpExtensionAttributeNotRequired) + + /// A 'plain' method is an extension method not interpreted as an extension method. + let IsMethInfoPlainCSharpStyleExtensionMember g m isEnclExtTy (minfo: MethInfo) = + // Method must be static, have 'Extension' attribute, must not be curried, must have at least one argument + isEnclExtTy + && not minfo.IsInstance + && not minfo.IsExtensionMember + && (match minfo.NumArgs with + | [ x ] when x >= 1 -> true + | _ -> false) + && AttributeChecking.MethInfoHasAttribute g m g.attrib_ExtensionAttribute minfo + + let GetTyconRefForExtensionMembers minfo (deref: Entity) amap m g = + try + let rs = + match metadataOfTycon deref, minfo with + | ILTypeMetadata(TILObjectReprData(scope = scoref)), ILMeth(ilMethInfo = ILMethInfo(ilMethodDef = ilMethod)) -> + match ilMethod.ParameterTypes with + | firstTy :: _ -> + match firstTy with + | ILType.Boxed tspec + | ILType.Value tspec -> + let tref = (tspec |> rescopeILTypeSpec scoref).TypeRef + + if Import.CanImportILTypeRef amap m tref then + let tcref = tref |> Import.ImportILTypeRef amap m + + if isCompiledTupleTyconRef g tcref || tyconRefEq g tcref g.fastFunc_tcr then + None + else + Some tcref + else + None + | _ -> None + | _ -> None + | _ -> + // The results are indexed by the TyconRef of the first 'this' argument, if any. + // So we need to go and crack the type of the 'this' argument. + let thisTy = + minfo + .GetParamTypes(amap, m, generalizeTypars minfo.FormalMethodTypars) + .Head.Head + + match thisTy with + | AppTy g (tcrefOfTypeExtended, _) when not (isByrefTy g thisTy) -> Some tcrefOfTypeExtended + | _ -> None + + Some rs + with RecoverableException e -> // Import of the ILType may fail, if so report the error and skip on + errorRecovery e m + None + + let baseTys = + ty + :: infoReader.GetEntireTypeHierarchy(TypeHierarchy.AllowMultiIntfInstantiations.Yes, m, ty) + |> List.choose (fun ty -> + match tryTcrefOfAppTy g ty with + | ValueSome ty' -> Some(ty, ty') + | _ -> None) + + let checkTy ty2 = + baseTys + |> List.tryPick (fun (ty, ty') -> if tyconRefEq g ty' ty2 then Some ty else None) + + getAllSymbols () + |> List.collect (fun x -> + match x.Symbol.Item with + | Item.MethodGroup(name, meths, un) when not x.Symbol.IsExplicitlySuppressed -> + let meths = + meths + |> List.choose (fun i -> + let isEnclExtTy = + IsTyconRefUsedForCSharpStyleExtensionMembers g m i.DeclaringTyconRef + + if IsMethInfoPlainCSharpStyleExtensionMember g m isEnclExtTy i then + let ty2 = GetTyconRefForExtensionMembers i i.DeclaringTyconRef.Deref amap m g + + match ty2 with + | Some(Some ty2) -> + match checkTy ty2 with + | Some ty -> + match i with + | ILMeth(_, mi, _) -> + Some(MethInfo.CreateILExtensionMeth(amap, m, ty, i.DeclaringTyconRef, Some 0UL, mi.RawMetadata)) + | _ -> Some i + | _ -> None + | _ -> None + else + None) + + if meths.IsEmpty then + [] + else + (ItemWithNoInst(Item.MethodGroup(name, meths, un)), + ValueSome( + { x with + UnresolvedSymbol = + { x.UnresolvedSymbol with + DisplayName = name + } + } + )) + :: [] + | _ -> []) + + let ItemWithNoInstWithNoSymbol (item: ItemWithInst) = + item, (ValueNone: AssemblySymbol voption) + // Filter items to show only valid & return Some if there are any let ReturnItemsOfType (items: ItemWithInst list) g denv (m: range) filterCtors = let items = @@ -452,7 +567,7 @@ type internal TypeCheckInfo |> FilterItemsForCtors filterCtors if not (isNil items) then - NameResResult.Members(items, denv, m) + NameResResult.Members(items |> List.map ItemWithNoInstWithNoSymbol, denv, m) else NameResResult.Empty @@ -477,7 +592,7 @@ type internal TypeCheckInfo /// Looks at the exact name resolutions that occurred during type checking /// If 'membersByResidue' is specified, we look for members of the item obtained /// from the name resolution and filter them by the specified residue (?) - let GetPreciseItemsFromNameResolution (line, colAtEndOfNames, membersByResidue, filterCtors, resolveOverloads) = + let GetPreciseItemsFromNameResolution (line, colAtEndOfNames, membersByResidue, filterCtors, resolveOverloads, getAllSymbols) = let endOfNamesPos = mkPos line colAtEndOfNames // Logic below expects the list to be in reverse order of resolution @@ -543,9 +658,23 @@ type internal TypeCheckInfo let targets = ResolveCompletionTargets.All(ConstraintSolver.IsApplicableMethApprox g amap m) + let globalItems = getExtMethsOfType m ty getAllSymbols let items = ResolveCompletionsInType ncenv nenv targets m ad false ty let items = List.map ItemWithNoInst items - ReturnItemsOfType items g denv m filterCtors + + let items = + items + |> RemoveDuplicateItems g + |> RemoveExplicitlySuppressed g + |> FilterItemsForCtors filterCtors + |> List.map ItemWithNoInstWithNoSymbol + + let items = items @ globalItems + + if not (isNil items) then + NameResResult.Members(items, denv, m) + else + NameResResult.Empty // No residue, so the items are the full resolution of the name | CNR(_, _, denv, _, _, m) :: _, None -> @@ -583,7 +712,7 @@ type internal TypeCheckInfo let CompletionItemWithMoreSetting (ty: TyconRef voption) (assemblySymbol: AssemblySymbol voption) - minorPriority + preferred insertText displayText (item: ItemWithInst) @@ -632,17 +761,75 @@ type internal TypeCheckInfo { ItemWithInst = item - MinorPriority = minorPriority + MinorPriority = 0 Kind = kind IsOwnMember = false Type = ty Unresolved = isUnresolved CustomInsertText = insertText CustomDisplayText = displayText + PreferredType = preferred } let CompletionItem (ty: TyconRef voption) (assemblySymbol: AssemblySymbol voption) (item: ItemWithInst) = - CompletionItemWithMoreSetting ty assemblySymbol 0 ValueNone ValueNone item + CompletionItemWithMoreSetting ty assemblySymbol CompletionPreferType.Normal ValueNone ValueNone item + + let getStaticFieldsOfSameTypeInTheType isInMatch nenv ad m ty = + let targets = + ResolveCompletionTargets.All(ConstraintSolver.IsApplicableMethApprox ncenv.g ncenv.amap m) + + let items = ResolveCompletionsInType ncenv nenv targets m ad true ty + + let items = + items + |> List.filter (function + | Item.UnionCase _ -> true + | Item.Value(valRef) -> typeEquiv g ty valRef.Type && not isInMatch + | Item.ILField(iLFieldInfo) -> + typeEquiv g ty (iLFieldInfo.FieldType(amap, m)) + && (not isInMatch || iLFieldInfo.LiteralValue.IsSome) + | Item.Property(info = pinfo :: _) -> typeEquiv g ty (pinfo.GetPropertyType(amap, m)) && not isInMatch + | _ -> false) + + let items = items |> List.map ItemWithNoInst + let items = items |> RemoveDuplicateItems g + items |> RemoveExplicitlySuppressed g + + let getStaticFieldsOfSameTypeInTheTypeCompletionItem isInMatch (nenv: NameResolutionEnv) ad m shouldInParen shouldUnionCaseInParen ty = + let g = nenv.DisplayEnv.g + let ty = stripTyEqns g ty + + let isAutoOpen = + match ty with + | TType_app(tcref, _, _) -> + (tcref.IsUnionTycon + && (TryFindFSharpBoolAttribute g g.attrib_RequireQualifiedAccessAttribute tcref.Attribs + <> Some true)) + || TryFindFSharpBoolAttribute g g.attrib_AutoOpenAttribute tcref.Attribs = Some true + | _ -> false + + let tyName = + stringOfTy (nenv.DisplayEnv.UseGenericParameterStyle(GenericParameterStyle.Prefix)) ty + + // if the parent namespace/module was opened, and the type was marked as AutoOpen, ignore the typeName + let tyName = + if -1 = tyName.IndexOf '.' && isAutoOpen then + String.Empty + else + tyName + "." + + getStaticFieldsOfSameTypeInTheType isInMatch nenv ad m ty + |> List.map (fun i -> + let name = $"{tyName}{i.Item.DisplayName}" + + let code = + match i.Item with + | Item.UnionCase(ui, _) when not ui.UnionCase.RecdFields.IsEmpty && shouldUnionCaseInParen -> $"({name}())" + | Item.UnionCase(ui, _) when not ui.UnionCase.RecdFields.IsEmpty -> $"{name}()" + | _ when shouldInParen -> $"({name})" + | _ -> name + + CompletionItemWithMoreSetting ValueNone ValueNone CompletionPreferType.Suggested (ValueSome code) (ValueSome name) i) let CollectParameters (methods: MethInfo list) amap m : Item list = methods @@ -656,15 +843,72 @@ type internal TypeCheckInfo | None -> None) | _ -> []) - let GetNamedParametersAndSettableFields endOfExprPos = + let GetNamedParametersAndSettableFields endOfExprPos paramGroupIdx paramIdx paramName caretIsAfterEqualMark isInParen = + let paramGroupIdx = if paramGroupIdx = -1 then 0 else paramGroupIdx + let paramIdx = if paramIdx = -1 then 0 else paramIdx + + let getByIdxInParamListList nenv ad m getTy ls = + let rec loopParamList i ls = + match ls with + | [] -> [] + | x :: t when i = paramIdx -> + getStaticFieldsOfSameTypeInTheTypeCompletionItem + false + nenv + ad + m + (not isInParen && not t.IsEmpty) + (not isInParen) + (getTy x) + | _ :: t -> loopParamList (i + 1) t + + let rec loopParamListList i ls = + match ls with + | [] -> [] + | x :: _ when i = paramGroupIdx -> loopParamList 0 x + | _ :: t -> loopParamListList (i + 1) t + + loopParamListList 0 ls + + let getStaticFieldsOfSameTypeOfTheParameter nenv ad m tinst (methods: MethInfo list) (items: Item list) = + let getStaticFieldsOfSameTypeInTheTypeCompletionItem = + getStaticFieldsOfSameTypeInTheTypeCompletionItem false nenv ad m false false + + match paramName with + | Some name -> + match items |> List.tryFind (fun i -> i.DisplayName = name) with + | Some(Item.OtherName(argType = ty)) -> getStaticFieldsOfSameTypeInTheTypeCompletionItem ty + | Some(Item.Value(valRef)) -> getStaticFieldsOfSameTypeInTheTypeCompletionItem valRef.Type + | Some(Item.ILField(iLFieldInfo)) -> getStaticFieldsOfSameTypeInTheTypeCompletionItem (iLFieldInfo.FieldType(amap, m)) + | Some(Item.Property(info = pinfo :: _)) -> + getStaticFieldsOfSameTypeInTheTypeCompletionItem (pinfo.GetPropertyType(amap, m)) + | _ -> [] + | _ -> + methods + |> List.map (fun meth -> meth.GetParamDatas(amap, m, meth.FormalMethodInst)) + |> List.collect (getByIdxInParamListList nenv ad m (fun (ParamData(ttype = ty)) -> instType tinst ty)) + + let (|CNR|) (cnr: CapturedNameResolution) = + (cnr.ItemWithInst, cnr.DisplayEnv, cnr.NameResolutionEnv, cnr.AccessorDomain, cnr.Range) + let cnrs = - GetCapturedNameResolutions endOfExprPos ResolveOverloads.No + GetCapturedNameResolutions endOfExprPos ResolveOverloads.Yes |> ResizeArray.toList |> List.rev let result = match cnrs with - | CNR(Item.CtorGroup(_, (ctor :: _ as ctors)), _, denv, nenv, ad, m) :: _ -> + | CNR({ + Item = Item.CtorGroup(_, (ctor :: _ as ctors)) + TyparInstantiation = tinst + }, + denv, + nenv, + ad, + m) :: _ -> + + let isCurrying = ctor.NumArgs.Length > 1 + let props = ResolveCompletionsInType ncenv @@ -677,8 +921,26 @@ type internal TypeCheckInfo let parameters = CollectParameters ctors amap m let items = props @ parameters - Some(denv, m, items) - | CNR(Item.MethodGroup(_, methods, _), _, denv, nenv, ad, m) :: _ -> + + let p = getStaticFieldsOfSameTypeOfTheParameter nenv ad m tinst ctors items + + let items = + if isCurrying || caretIsAfterEqualMark then + [] + else + List.map ItemWithNoInst items + + Some(denv, m, items, p) + | CNR({ + Item = Item.MethodGroup(_, (meth :: _ as methods), _) + TyparInstantiation = tinst + }, + denv, + nenv, + ad, + m) :: _ -> + let isCurrying = meth.NumArgs.Length > 1 + let props = methods |> List.collect (fun meth -> @@ -687,14 +949,63 @@ type internal TypeCheckInfo let parameters = CollectParameters methods amap m let items = props @ parameters - Some(denv, m, items) + + let p = getStaticFieldsOfSameTypeOfTheParameter nenv ad m tinst methods items + + let items = + if isCurrying || caretIsAfterEqualMark then + [] + else + List.map ItemWithNoInst items + + Some(denv, m, items, p) + + | CNR({ + Item = Item.Value vref + TyparInstantiation = tinst + }, + denv, + nenv, + ad, + m) :: _ when isForallFunctionTy g vref.Type -> + let ty = + match vref.Type with + | TType.TType_forall(bodyTy = ty) + | ty -> instType tinst ty + + let p = + stripFunTyN g (paramGroupIdx + 1) ty + |> fst + |> List.map (function + | ty when isAnyTupleTy g ty -> destAnyTupleTy g ty |> snd + | ty -> [ ty ]) + |> getByIdxInParamListList nenv ad m id + + if p.IsEmpty then None else Some(denv, m, [], p) + + | CNR({ + Item = Item.UnionCase(uci, _) + TyparInstantiation = tinst + }, + denv, + nenv, + ad, + m) :: _ when not uci.UnionCase.IsNullary -> + let p = + uci.UnionCase.RecdFields + |> List.map (fun field -> + match instType tinst field.FormalType with + | ty when isAnyTupleTy g ty -> destAnyTupleTy g ty |> snd + | ty -> [ ty ]) + |> getByIdxInParamListList nenv ad m id + + if p.IsEmpty then None else Some(denv, m, [], p) + | _ -> None match result with - | None -> NameResResult.Empty - | Some(denv, m, items) -> - let items = List.map ItemWithNoInst items - ReturnItemsOfType items g denv m TypeNameResolutionFlag.ResolveTypeNamesToTypeRefs + | None -> NameResResult.Empty, [] + | Some(denv, m, items, p) -> ReturnItemsOfType items g denv m TypeNameResolutionFlag.ResolveTypeNamesToTypeRefs, p /// finds captured typing for the given position let GetExprTypingForPosition endOfExprPos = @@ -774,7 +1085,7 @@ type internal TypeCheckInfo /// Looks at the exact expression types at the position to the left of the /// residue then the source when it was typechecked. - let GetPreciseCompletionListFromExprTypings (parseResults: FSharpParseFileResults, endOfExprPos, filterCtors) = + let GetPreciseCompletionListFromExprTypings (parseResults: FSharpParseFileResults, endOfExprPos, filterCtors, getAllSymbols) = let thereWereSomeQuals, quals = GetExprTypingForPosition(endOfExprPos) @@ -819,6 +1130,14 @@ type internal TypeCheckInfo let items = items |> RemoveDuplicateItems g let items = items |> RemoveExplicitlySuppressed g let items = items |> FilterItemsForCtors filterCtors + let globalItems = getExtMethsOfType m ty getAllSymbols + + let items = + (items |> List.map (fun i -> i, ValueNone)) @ globalItems + |> IPartialEqualityComparer.partialDistinctBy ( + IPartialEqualityComparer.On (fun (item, _) -> item.Item) (ItemDisplayPartialEquality g) + ) + ExprTypingsResult.Some((items, nenv.DisplayEnv, m), ty) | None -> if textChanged then @@ -975,9 +1294,11 @@ type internal TypeCheckInfo Unresolved = None CustomInsertText = ValueNone CustomDisplayText = ValueNone + PreferredType = CompletionPreferType.Suggested } let getItem (x: ItemWithInst) = x.Item + let getItemTuple2 (x: ItemWithInst, _) = x.Item let getItem2 (x: CompletionItem) = x.Item @@ -1267,7 +1588,7 @@ type internal TypeCheckInfo None ) |> ItemWithNoInst - |> CompletionItemWithMoreSetting ValueNone ValueNone -1 (ValueSome textInCode) (ValueSome name) + |> CompletionItemWithMoreSetting ValueNone ValueNone CompletionPreferType.Suggested (ValueSome textInCode) (ValueSome name) |> Some) let overridableMeths = @@ -1333,20 +1654,27 @@ type internal TypeCheckInfo Item.MethodGroup(name, [ meth ], None) |> ItemWithNoInst - |> CompletionItemWithMoreSetting ValueNone ValueNone -1 (ValueSome textInCode) (ValueSome name) + |> CompletionItemWithMoreSetting ValueNone ValueNone CompletionPreferType.Suggested (ValueSome textInCode) (ValueSome name) |> Some) overridableProps @ overridableMeths let getTyFromTypeNamePos (endPos: pos) = let nameResItems = - GetPreciseItemsFromNameResolution(endPos.Line, endPos.Column, None, ResolveTypeNamesToTypeRefs, ResolveOverloads.Yes) + GetPreciseItemsFromNameResolution( + endPos.Line, + endPos.Column, + None, + ResolveTypeNamesToTypeRefs, + ResolveOverloads.Yes, + fun () -> [] + ) match nameResItems with | NameResResult.Members(ls, _, _) -> ls |> List.tryPick (function - | { Item = Item.Types(_, ty :: _) } -> Some ty + | ({ Item = Item.Types(_, ty :: _) }, _) -> Some ty | _ -> None) | _ -> None @@ -1511,7 +1839,8 @@ type internal TypeCheckInfo // This is based on position (i.e. colAtEndOfNamesAndResidue). This is not used if a residueOpt is given. let nameResItems = match residueOpt with - | None -> GetPreciseItemsFromNameResolution(line, colAtEndOfNamesAndResidue, None, filterCtors, resolveOverloads) + | None -> + GetPreciseItemsFromNameResolution(line, colAtEndOfNamesAndResidue, None, filterCtors, resolveOverloads, allSymbols) | Some residue -> // Deals with cases when we have spaces between dot and\or identifier, like A . $ // if this is our case - then we need to locate end position of the name skipping whitespaces @@ -1525,7 +1854,15 @@ type internal TypeCheckInfo match FindFirstNonWhitespacePosition lineStr (p - 1) with | Some colAtEndOfNames -> let colAtEndOfNames = colAtEndOfNames + 1 // convert 0-based to 1-based - GetPreciseItemsFromNameResolution(line, colAtEndOfNames, Some(residue), filterCtors, resolveOverloads) + + GetPreciseItemsFromNameResolution( + line, + colAtEndOfNames, + Some(residue), + filterCtors, + resolveOverloads, + allSymbols + ) | None -> NameResResult.Empty | _ -> NameResResult.Empty @@ -1565,9 +1902,9 @@ type internal TypeCheckInfo match nameResItems with | NameResResult.Cancel(denv, m) -> Some([], denv, m) - | NameResResult.Members(FilterRelevantItems getItem exactMatchResidueOpt (items, denv, m)) -> + | NameResResult.Members(FilterRelevantItems getItemTuple2 exactMatchResidueOpt (items, denv, m)) -> // lookup based on name resolution results successful - Some(items |> List.map (CompletionItem (getType ()) ValueNone), denv, m) + Some(items |> List.map (fun (item, asm) -> CompletionItem (getType ()) asm item), denv, m) | _ -> match origLongIdentOpt with | None -> None @@ -1592,7 +1929,7 @@ type internal TypeCheckInfo ) match leftOfDot with - | Some(pos, _) -> GetPreciseCompletionListFromExprTypings(parseResults, pos, filterCtors), true + | Some(pos, _) -> GetPreciseCompletionListFromExprTypings(parseResults, pos, filterCtors, allSymbols), true | None -> // Can get here in a case like: if "f xxx yyy" is legal, and we do "f xxx y" // We have no interest in expression typings, those are only useful for dot-completion. We want to fallback @@ -1600,7 +1937,7 @@ type internal TypeCheckInfo ExprTypingsResult.None, false match qualItems, thereIsADotInvolved with - | ExprTypingsResult.Some(FilterRelevantItems getItem exactMatchResidueOpt (items, denv, m), ty), _ when + | ExprTypingsResult.Some(FilterRelevantItems getItemTuple2 exactMatchResidueOpt (items, denv, m), ty), _ when // Initially we only use the expression typings when looking up, e.g. (expr).Nam or (expr).Name1.Nam // These come through as an empty plid and residue "". Otherwise we try an environment lookup // and then return to the qualItems. This is because the expression typings are a little inaccurate, primarily because @@ -1608,7 +1945,12 @@ type internal TypeCheckInfo isNil plid -> // lookup based on expression typings successful - Some(items |> List.map (CompletionItem (tryTcrefOfAppTy g ty) ValueNone), denv, m) + Some( + items + |> List.map (fun (item, asm) -> CompletionItem (tryTcrefOfAppTy g ty) asm item), + denv, + m + ) | ExprTypingsResult.NoneBecauseThereWereTypeErrors, _ -> // There was an error, e.g. we have "." and there is an error determining the type of // In this case, we don't want any of the fallback logic, rather, we want to produce zero results. @@ -1632,7 +1974,7 @@ type internal TypeCheckInfo // First, use unfiltered name resolution items, if they're not empty | NameResResult.Members(items, denv, m), _, _ when not (isNil items) -> // lookup based on name resolution results successful - ValueSome(items |> List.map (CompletionItem (getType ()) ValueNone), denv, m) + ValueSome(items |> List.map (fun (item, asm) -> CompletionItem (getType ()) asm item), denv, m) // If we have nonempty items from environment that were resolved from a type, then use them... // (that's better than the next case - here we'd return 'int' as a type) @@ -1641,8 +1983,13 @@ type internal TypeCheckInfo ValueSome(items |> List.map (CompletionItem (getType ()) ValueNone), denv, m) // Try again with the qualItems - | _, _, ExprTypingsResult.Some(FilterRelevantItems getItem exactMatchResidueOpt (items, denv, m), ty) -> - ValueSome(items |> List.map (CompletionItem (tryTcrefOfAppTy g ty) ValueNone), denv, m) + | _, _, ExprTypingsResult.Some(FilterRelevantItems getItemTuple2 exactMatchResidueOpt (items, denv, m), ty) -> + ValueSome( + items + |> List.map (fun (item, asm) -> CompletionItem (tryTcrefOfAppTy g ty) asm item), + denv, + m + ) | _ -> ValueNone @@ -1868,19 +2215,24 @@ type internal TypeCheckInfo | Some(CompletionContext.RecordField(RecordContext.Declaration true)) -> None // Completion at ' SomeMethod( ... ) ' or ' [] ' with named arguments - | Some(CompletionContext.ParameterList(endPos, fields)) -> - let results = GetNamedParametersAndSettableFields endPos + | Some(CompletionContext.ParameterList(endPos, paramGroupIdx, idx, name, fields, caretIsAfterEqualMark, isInParen)) -> + let results = + GetNamedParametersAndSettableFields endPos paramGroupIdx idx name caretIsAfterEqualMark isInParen let declaredItems = getDeclaredItemsNotInRangeOpWithAllSymbols () match results with - | NameResResult.Members(items, denv, m) -> + | NameResResult.Members(items, denv, m), p -> + let items = items |> List.map fst + let filtered = items |> RemoveDuplicateItems g |> RemoveExplicitlySuppressed g |> List.filter (fun item -> not (fields.Contains item.Item.DisplayName)) |> List.map (fun item -> + let code = item.Item.DisplayName + " = " + { ItemWithInst = item Kind = CompletionItemKind.Argument @@ -1888,14 +2240,19 @@ type internal TypeCheckInfo IsOwnMember = false Type = None Unresolved = None - CustomInsertText = ValueNone - CustomDisplayText = ValueNone + CustomInsertText = ValueSome(if isInParen then code else $"({code})") + CustomDisplayText = ValueSome code + PreferredType = CompletionPreferType.Suggested }) match declaredItems with - | None -> Some(toCompletionItems (items, denv, m)) - | Some(declItems, declaredDisplayEnv, declaredRange) -> Some(filtered @ declItems, declaredDisplayEnv, declaredRange) - | _ -> declaredItems + | None -> Some(p @ (items |> List.map DefaultCompletionItem), denv, m) + | Some(declItems, declaredDisplayEnv, declaredRange) -> + Some(p @ filtered @ declItems, declaredDisplayEnv, declaredRange) + | _, p -> + match declaredItems with + | None -> None + | Some(declItems, declaredDisplayEnv, declaredRange) -> Some(p @ declItems, declaredDisplayEnv, declaredRange) | Some(CompletionContext.AttributeApplication) -> getDeclaredItemsNotInRangeOpWithAllSymbols () @@ -1955,6 +2312,36 @@ type internal TypeCheckInfo | Some(CompletionContext.MethodOverride(ctx, enclosingTypeNameRange, spacesBeforeOverrideKeyword, hasThis, isStatic)) -> GetOverridableMethods pos ctx enclosingTypeNameRange spacesBeforeOverrideKeyword hasThis isStatic + | Some(CompletionContext.CaretAfterOperator(m, isInMatch)) -> + let declaredItems = getDeclaredItemsNotInRangeOpWithAllSymbols () + let _, quals = GetExprTypingForPosition(m.End) + + let bestQual = + quals + |> Array.tryFind (fun (_, _, _, r) -> posEq m.Start r.Start) + |> Option.orElseWith (fun _ -> + GetCapturedNameResolutions m.End ResolveOverloads.No + |> ResizeArray.tryPick (fun (CNR(item, _, _, nenv, ad, r)) -> + if not (posEq m.Start r.Start) then + None + else + match item with + | Item.Value v -> Some(stripFunTy g v.Type |> snd, nenv, ad, r) + | _ -> None)) + + match bestQual with + | Some bestQual -> + let ty, nenv, ad, m = bestQual + let denv = nenv.DisplayEnv + + let items = + getStaticFieldsOfSameTypeInTheTypeCompletionItem isInMatch nenv ad m false false ty + + match declaredItems with + | Some(declaredItems, _, _) -> Some(items @ declaredItems, denv, m) + | None -> Some(items, denv, m) + | None -> declaredItems + // Other completions | cc -> match residueOpt |> Option.bind Seq.tryHead with diff --git a/src/Compiler/Service/ServiceDeclarationLists.fs b/src/Compiler/Service/ServiceDeclarationLists.fs index dd6727a5ba9..1b8605e4da3 100644 --- a/src/Compiler/Service/ServiceDeclarationLists.fs +++ b/src/Compiler/Service/ServiceDeclarationLists.fs @@ -75,6 +75,10 @@ type CompletionItemKind = | CustomOperation | Other +type CompletionPreferType = + | Suggested = 0 + | Normal = 10000 + type UnresolvedSymbol = { FullName: string DisplayName: string @@ -88,7 +92,8 @@ type CompletionItem = Type: TyconRef option Unresolved: UnresolvedSymbol option CustomInsertText: string voption - CustomDisplayText: string voption } + CustomDisplayText: string voption + PreferredType: CompletionPreferType } member x.Item = x.ItemWithInst.Item [] @@ -1010,7 +1015,7 @@ module internal DescriptionListsImpl = /// An intellisense declaration [] type DeclarationListItem(textInDeclList: string, textInCode: string, fullName: string, glyph: FSharpGlyph, info, accessibility: FSharpAccessibility, - kind: CompletionItemKind, isOwnMember: bool, priority: int, isResolved: bool, namespaceToOpen: string option) = + kind: CompletionItemKind, isOwnMember: bool, priority: int, isResolved: bool, namespaceToOpen: string option, preferredType: CompletionPreferType) = member _.Name = textInDeclList @@ -1043,6 +1048,8 @@ type DeclarationListItem(textInDeclList: string, textInCode: string, fullName: s member _.NamespaceToOpen = namespaceToOpen + member _.PreferredType = preferredType + /// A table of declarations for Intellisense completion [] type DeclarationListInfo(declarations: DeclarationListItem[], isForType: bool, isError: bool) = @@ -1252,14 +1259,14 @@ type DeclarationListInfo(declarations: DeclarationListItem[], isForType: bool, i DeclarationListItem( textInDeclList, textInCode, fullName, glyph, Choice1Of2 (items, infoReader, ad, m, denv), getAccessibility item.Item, - item.Kind, item.IsOwnMember, item.MinorPriority, item.Unresolved.IsNone, namespaceToOpen)) + item.Kind, item.IsOwnMember, item.MinorPriority, item.Unresolved.IsNone, namespaceToOpen, item.PreferredType)) DeclarationListInfo(Array.ofList decls, isForType, false) static member Error message = DeclarationListInfo( [| DeclarationListItem("", "", "", FSharpGlyph.Error, Choice2Of2 (ToolTipText [ToolTipElement.CompositionError message]), - FSharpAccessibility(taccessPublic), CompletionItemKind.Other, false, 0, false, None) |], false, true) + FSharpAccessibility(taccessPublic), CompletionItemKind.Other, false, 0, false, None, CompletionPreferType.Suggested) |], false, true) static member Empty = empty diff --git a/src/Compiler/Service/ServiceDeclarationLists.fsi b/src/Compiler/Service/ServiceDeclarationLists.fsi index 3aeaa11112d..b5e41f795ba 100644 --- a/src/Compiler/Service/ServiceDeclarationLists.fsi +++ b/src/Compiler/Service/ServiceDeclarationLists.fsi @@ -69,6 +69,10 @@ type public CompletionItemKind = | CustomOperation | Other +type public CompletionPreferType = + | Suggested = 0 + | Normal = 10000 + type public UnresolvedSymbol = { FullName: string @@ -94,6 +98,7 @@ type internal CompletionItem = CustomInsertText: string voption CustomDisplayText: string voption + PreferredType: CompletionPreferType } member Item: Item @@ -142,6 +147,8 @@ type public DeclarationListItem = member NamespaceToOpen: string option + member PreferredType: CompletionPreferType + /// Represents a set of declarations in F# source code, with information attached ready for display by an editor. /// Returned by GetDeclarations. diff --git a/src/Compiler/Service/ServiceParsedInputOps.fs b/src/Compiler/Service/ServiceParsedInputOps.fs index 0f6f3bc0530..1f9cb956ba5 100644 --- a/src/Compiler/Service/ServiceParsedInputOps.fs +++ b/src/Compiler/Service/ServiceParsedInputOps.fs @@ -90,8 +90,14 @@ type CompletionContext = | RangeOperator /// 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 + | ParameterList of + identEndPos: pos * + paramGroupIdx: int * + paramIdxInGroup: int * + paramName: string option * + settedParam: HashSet * + caretIsAfterEqualMark: bool * + isInParen: bool /// Completing an attribute name, outside of the constructor | AttributeApplication @@ -120,6 +126,9 @@ type CompletionContext = hasThis: bool * isStatic: bool + /// (s =| ) or (s <>| ) + | CaretAfterOperator of mExprBeforeOperator: range * isInMatch: bool + type ShortIdent = string type ShortIdents = ShortIdent[] @@ -1073,7 +1082,7 @@ module ParsedInput = false, SynExpr.App(ExprAtomicFlag.NonAtomic, true, SynExpr.LongIdent(longDotId = SynLongIdent(id = [ ident ])), lhs, _), rhs, - _) when ident.idText = name -> Some(lhs, rhs) + _) when ident.idText = name -> Some(lhs, rhs, ident.idRange) | _ -> None // checks if we are in a range operator @@ -1084,7 +1093,7 @@ module ParsedInput = let (|Setter|_|) e = match e with - | Operator "op_Equality" (SynExpr.Ident id, _) -> Some id + | Operator "op_Equality" (SynExpr.Ident id, _, mEqualMark) -> Some(id, mEqualMark) | _ -> None let posAfterRangeAndBetweenSpaces (lineStr: string) (m: range) pos = @@ -1101,18 +1110,63 @@ module ParsedInput = || posLt pos m.Start || posAfterRangeAndBetweenSpaces lineStr m pos - let findSetters argList = - match argList with - | SynExpr.Paren(SynExpr.Tuple(false, parameters, _, _), _, _, _) -> - let setters = HashSet() + let findSetters identPos (lineStr, pos) currentParamGroup currentParamIdxInGroup argList = + // currying function - found the current parameter + if currentParamIdxInGroup > -1 then + // for currying functions, `=` is not the way to assign value to named parameter, `caretIsAfterEqualMark` is always `false` + CompletionContext.ParameterList(identPos, currentParamGroup, currentParamIdxInGroup, None, emptyStringSet, false, false) + else + match argList with + | SynExpr.Paren(SynExpr.Tuple(false, parameters, _, _), _, _, _) -> + let setters = HashSet() + + let mutable i = 0 + let mutable idx = -1 + let mutable namedParamName = None + let mutable caretIsAfterEqualMark = false - for p in parameters do - match p with - | Setter id -> ignore (setters.Add id.idText) - | _ -> () + for p in parameters do + let isCurrentParam = + rangeContainsPosOrIsSpacesBetweenRangeAndPos lineStr p.Range pos - setters - | _ -> emptyStringSet + if isCurrentParam then + idx <- i + + match p with + | Setter(id, mEqualMark) -> + if isCurrentParam then + namedParamName <- Some id.idText + caretIsAfterEqualMark <- rangeContainsPosOrIsSpacesBetweenRangeAndPos lineStr mEqualMark pos + + ignore (setters.Add id.idText) + | _ -> () + + i <- i + 1 + + CompletionContext.ParameterList(identPos, currentParamGroup, idx, namedParamName, setters, caretIsAfterEqualMark, true) + | SynExpr.Paren(expr = Setter(id, mEqualMark)) -> + let setters = HashSet() + ignore (setters.Add id.idText) + + CompletionContext.ParameterList( + identPos, + currentParamGroup, + 0, + Some id.idText, + setters, + rangeContainsPosOrIsSpacesBetweenRangeAndPos lineStr mEqualMark pos, + true + ) + + | SynExpr.Const(SynConst.Unit, range) -> + let isCurrentParam = rangeContainsPosOrIsSpacesBetweenRangeAndPos lineStr range pos + + if isCurrentParam then + CompletionContext.ParameterList(identPos, currentParamGroup, 0, None, emptyStringSet, false, true) + else + CompletionContext.ParameterList(identPos, currentParamGroup, -1, None, emptyStringSet, false, true) + + | _ -> CompletionContext.ParameterList(identPos, currentParamGroup, -1, None, emptyStringSet, false, false) let endOfLastIdent (lid: SynLongIdent) = let last = List.last lid.LongIdent @@ -1123,37 +1177,68 @@ module ParsedInput = | Some m -> m.End | None -> endOfLastIdent lid - let endOfClosingTokenOrIdent (mClosing: range option) (id: Ident) = - match mClosing with - | Some m -> m.End - | None -> id.idRange.End + //let endOfClosingTokenOrIdent (mClosing: range option) (id: Ident) = + // match mClosing with + // | Some m -> m.End + // | None -> id.idRange.End + + let checkNewObjectOrMethodCall (lineStr, pos) found e = + let rec loop found currentParamGroup currentParamIdxInGroup e = + let currentParamGroup = if found then currentParamGroup + 1 else currentParamGroup + + match e with + | SynExpr.New(_, SynType.LongIdent typeName, arg, _) -> + // new A() + Some(findSetters (endOfLastIdent typeName) (lineStr, pos) currentParamGroup currentParamIdxInGroup arg) + + | SynExpr.New(_, SynType.App(StripParenTypes(SynType.LongIdent typeName), _, _, _, mGreaterThan, _, _), arg, _) -> + // new A<_>() + Some( + findSetters + (endOfClosingTokenOrLastIdent mGreaterThan typeName) + (lineStr, pos) + currentParamGroup + currentParamIdxInGroup + arg + ) + + | SynExpr.App(_, false, SynExpr.Ident id, arg, _) + // A() - let (|NewObjectOrMethodCall|_|) e = - match e with - | SynExpr.New(_, SynType.LongIdent typeName, arg, _) -> - // new A() - Some(endOfLastIdent typeName, findSetters arg) + | SynExpr.App(_, false, SynExpr.TypeApp(SynExpr.Ident id, _, _, _, _, _, _), arg, _) -> + // A<_>() + Some(findSetters (id.idRange.End) (lineStr, pos) currentParamGroup currentParamIdxInGroup arg) + + | SynExpr.App(_, false, SynExpr.LongIdent(_, lid, _, _), arg, _) + | SynExpr.App(_, false, SynExpr.DotGet(longDotId = lid), arg, _) + // A.B() - | SynExpr.New(_, SynType.App(StripParenTypes(SynType.LongIdent typeName), _, _, _, mGreaterThan, _, _), arg, _) -> - // new A<_>() - Some(endOfClosingTokenOrLastIdent mGreaterThan typeName, findSetters arg) + | SynExpr.App(_, false, SynExpr.TypeApp(SynExpr.LongIdent(_, lid, _, _), _, _, _, _, _, _), arg, _) -> + // A.B<_>() + Some(findSetters (endOfLastIdent lid) (lineStr, pos) currentParamGroup currentParamIdxInGroup arg) + + // f 1 2 + | SynExpr.App(_, false, (SynExpr.App _ as expr), arg, _) -> + let currentParamGroup, currentParamIdxInGroup = + if currentParamGroup = 0 then + match findSetters pos0 (lineStr, pos) currentParamGroup currentParamIdxInGroup arg with + | CompletionContext.ParameterList(paramIdxInGroup = idx) -> 0, idx + | _ -> 0, -1 + else + currentParamGroup, currentParamIdxInGroup - | SynExpr.App(_, false, SynExpr.Ident id, arg, _) -> - // A() - Some(id.idRange.End, findSetters arg) + let found = found || currentParamIdxInGroup > -1 + loop found currentParamGroup currentParamIdxInGroup expr - | SynExpr.App(_, false, SynExpr.TypeApp(SynExpr.Ident id, _, _, _, mGreaterThan, _, _), arg, _) -> - // A<_>() - Some(endOfClosingTokenOrIdent mGreaterThan id, findSetters arg) + | _ -> None - | SynExpr.App(_, false, SynExpr.LongIdent(_, lid, _, _), arg, _) -> - // A.B() - Some(endOfLastIdent lid, findSetters arg) + loop found 0 -1 e - | SynExpr.App(_, false, SynExpr.TypeApp(SynExpr.LongIdent(_, lid, _, _), _, _, _, mGreaterThan, _, _), arg, _) -> - // A.B<_>() - Some(endOfClosingTokenOrLastIdent mGreaterThan lid, findSetters arg) - | _ -> None + let (|NewObjectOrMethodCall|_|) (lineStr, pos) e = + checkNewObjectOrMethodCall (lineStr, pos) false e + + let (|NewObjectOrMethodCallFound|_|) (lineStr, pos) e = + checkNewObjectOrMethodCall (lineStr, pos) true e let isOnTheRightOfComma pos (elements: SynExpr list) (commas: range list) current = let rec loop elements (commas: range list) = @@ -1170,11 +1255,13 @@ module ParsedInput = loop elements commas - let (|PartOfParameterList|_|) pos precedingArgument path = + let (|PartOfParameterList|_|) lineStr pos precedingArgument path = match path with - | SyntaxNode.SynExpr(SynExpr.Paren _) :: SyntaxNode.SynExpr(NewObjectOrMethodCall args) :: _ -> - if Option.isSome precedingArgument then None else Some args - | SyntaxNode.SynExpr(SynExpr.Tuple(false, elements, commas, _)) :: SyntaxNode.SynExpr(SynExpr.Paren _) :: SyntaxNode.SynExpr(NewObjectOrMethodCall args) :: _ -> + | SyntaxNode.SynExpr(SynExpr.Paren _) :: SyntaxNode.SynExpr(NewObjectOrMethodCall (lineStr, pos) args) :: _ -> + //if Option.isSome precedingArgument then None else Some args + Some args + | SyntaxNode.SynExpr(SynExpr.Tuple(false, elements, commas, _)) :: SyntaxNode.SynExpr(SynExpr.Paren _) :: SyntaxNode.SynExpr(NewObjectOrMethodCall (lineStr, + pos) args) :: _ -> match precedingArgument with | None -> Some args | Some e -> @@ -1425,28 +1512,39 @@ module ParsedInput = | 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 + let lineStrTrimmed = lineStr.Trim() + match expr with + // new A(1, $) + | SynExpr.Paren(expr = SynExpr.Tuple(range = m)) // new A($) - | SynExpr.Const(SynConst.Unit, m) when rangeContainsPos m pos -> + | SynExpr.Const(SynConst.Unit, m) when rangeContainsPosOrIsSpacesBetweenRangeAndPos lineStr m pos -> match path with - | SyntaxNode.SynExpr(NewObjectOrMethodCall args) :: _ -> Some(CompletionContext.ParameterList args) + | SyntaxNode.SynExpr(NewObjectOrMethodCall (lineStr, pos) args) :: _ -> Some(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) + | PartOfParameterList lineStr pos None args -> Some(args) | _ -> defaultTraverse expr // new (A$ = 1) // new (A = 1, $) - | Setter id when id.idRange.End = pos || rangeBeforePos expr.Range pos -> + | Setter(id, _) when + id.idRange.End = pos + || rangeContainsPosOrIsSpacesBetweenRangeAndPos lineStr 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 + | PartOfParameterList lineStr pos precedingArgument args -> Some(args) + | _ -> + match expr with + | Operator "op_Equality" (l, r, _) when rangeContainsPosOrIsSpacesBetweenRangeAndPos lineStr r.Range pos -> + Some(CompletionContext.CaretAfterOperator(l.Range, false)) + | _ -> defaultTraverse expr | SynExpr.Record(None, None, [], _) -> Some(CompletionContext.RecordField RecordContext.Empty) @@ -1459,12 +1557,50 @@ module ParsedInput = |> List.tryPick (fun pat -> TryGetCompletionContextInPattern true pat None pos) |> Option.orElseWith (fun () -> defaultTraverse expr) + | Operator "op_Equality" (l, r, _) + | Operator "op_Inequality" (l, r, _) + | SynExpr.IfThenElse(ifExpr = Operator "op_Equality" (l, r, _) | Operator "op_Inequality" (l, r, _)) + | SynExpr.Set(l, r, _) when rangeContainsPosOrIsSpacesBetweenRangeAndPos lineStr r.Range pos -> + Some(CompletionContext.CaretAfterOperator(l.Range, false)) + + | SynExpr.LongIdentSet(l, r, _) when rangeContainsPosOrIsSpacesBetweenRangeAndPos lineStr r.Range pos -> + Some(CompletionContext.CaretAfterOperator(l.Range, false)) + + // let a = 1: | + | SynExpr.Typed(targetType = SynType.FromParseError(range = m)) + // let f() = let a: | + | SynExpr.FromParseError( + expr = SynExpr.LetOrUse( + bindings = SynBinding(expr = SynExpr.Typed(expr = SynExpr.ArbitraryAfterError(range = m))) :: _)) when + rangeContainsPosOrIsSpacesBetweenRangeAndPos lineStr m pos + -> + Some CompletionContext.Type + // { new | } | SynExpr.ComputationExpr(expr = SynExpr.ArbitraryAfterError _) when - lineStr.Trim().Split(' ') |> Array.contains "new" + lineStrTrimmed.Split(' ') |> Array.contains "new" -> Some(CompletionContext.Inherit(InheritanceContext.Unknown, ([], None))) + | SynExpr.Match(expr = expr) when lineStrTrimmed.EndsWithOrdinal "|" || lineStrTrimmed.EndsWithOrdinal " with" -> + Some(CompletionContext.CaretAfterOperator(expr.Range, true)) + + | _ when posAfterRangeAndBetweenSpaces lineStr expr.Range pos -> + match expr with + // Some | + | SynExpr.Ident id + | SynExpr.TypeApp(SynExpr.Ident id, _, _, _, _, _, _) -> + Some(CompletionContext.ParameterList(id.idRange.End, 0, 0, None, emptyStringSet, false, false)) + + | SynExpr.LongIdent(_, lid, _, _) + | SynExpr.DotGet(longDotId = lid) + | SynExpr.TypeApp(SynExpr.LongIdent(longDotId = lid), _, _, _, _, _, _) -> + Some(CompletionContext.ParameterList(endOfLastIdent lid, 0, 0, None, emptyStringSet, false, false)) + + | NewObjectOrMethodCallFound (lineStr, pos) args -> Some(args) + + | _ -> defaultTraverse expr + | _ -> defaultTraverse expr member _.VisitRecordField(path, copyOpt, field) = @@ -1519,7 +1655,7 @@ module ParsedInput = ( path, defaultTraverse, - (SynBinding(headPat = headPat; trivia = trivia; returnInfo = returnInfo) as synBinding) + (SynBinding(headPat = headPat; trivia = trivia; returnInfo = returnInfo; expr = expr) as synBinding) ) = let isOverrideOrMember leadingKeyword = @@ -1607,54 +1743,67 @@ module ParsedInput = | Some(SynBindingReturnInfo(range = m)) when rangeContainsPosOrIsSpacesBetweenRangeAndPos lineStr m pos -> Some CompletionContext.Type | _ -> - match headPat with - - // static member | - | SynPat.FromParseError _ when isStaticMember trivia.LeadingKeyword -> - overrideContext path trivia.LeadingKeyword.Range false true false - - // override | - | SynPat.FromParseError _ when isOverrideOrMember trivia.LeadingKeyword && lineStr.[pos.Column - 1] = ' ' -> - overrideContext path trivia.LeadingKeyword.Range false false (isMember trivia.LeadingKeyword) - - // override _.| - | SynPat.FromParseError _ when isOverrideOrMember trivia.LeadingKeyword -> - overrideContext path trivia.LeadingKeyword.Range true false (isMember trivia.LeadingKeyword) - - // override this.| - | SynPat.Named(ident = SynIdent(ident = selfId)) when - isOverrideOrMember trivia.LeadingKeyword && selfId.idRange.End.IsAdjacentTo pos - -> - overrideContext path trivia.LeadingKeyword.Range true false (isMember trivia.LeadingKeyword) + match expr with + // let a: string = | + // member _.M(): string = | + | SynExpr.Typed(expr = SynExpr.ArbitraryAfterError _) -> + let m = + match headPat with + | SynPat.Named(range = m) -> m + | SynPat.LongIdent(longDotId = lid) -> lid.LongIdent |> List.last |> _.idRange + | _ -> headPat.Range - // override this.ToStr| - | SynPat.LongIdent(longDotId = SynLongIdent(id = [ _; methodId ])) when - isOverrideOrMember trivia.LeadingKeyword - && rangeContainsPos methodId.idRange pos - -> - overrideContext path trivia.LeadingKeyword.Range true false (isMember trivia.LeadingKeyword) + Some(CompletionContext.CaretAfterOperator(m, false)) - // static member A| - | SynPat.LongIdent(longDotId = SynLongIdent(id = [ methodId ])) when - isStaticMember trivia.LeadingKeyword && rangeContainsPos methodId.idRange pos - -> - overrideContext path trivia.LeadingKeyword.Range false true false + | _ -> + match headPat with + + // static member | + | SynPat.FromParseError _ when isStaticMember trivia.LeadingKeyword -> + overrideContext path trivia.LeadingKeyword.Range false true false + + // override | + | SynPat.FromParseError _ when isOverrideOrMember trivia.LeadingKeyword && lineStr.[pos.Column - 1] = ' ' -> + overrideContext path trivia.LeadingKeyword.Range false false (isMember trivia.LeadingKeyword) + + // override _.| + | SynPat.FromParseError _ when isOverrideOrMember trivia.LeadingKeyword -> + overrideContext path trivia.LeadingKeyword.Range true false (isMember trivia.LeadingKeyword) + + // override this.| + | SynPat.Named(ident = SynIdent(ident = selfId)) when + isOverrideOrMember trivia.LeadingKeyword && selfId.idRange.End.IsAdjacentTo pos + -> + overrideContext path trivia.LeadingKeyword.Range true false (isMember trivia.LeadingKeyword) + + // override this.ToStr| + | SynPat.LongIdent(longDotId = SynLongIdent(id = [ _; methodId ])) when + isOverrideOrMember trivia.LeadingKeyword + && rangeContainsPos methodId.idRange pos + -> + overrideContext path trivia.LeadingKeyword.Range true false (isMember trivia.LeadingKeyword) + + // static member A| + | SynPat.LongIdent(longDotId = SynLongIdent(id = [ methodId ])) when + isStaticMember trivia.LeadingKeyword && rangeContainsPos methodId.idRange pos + -> + overrideContext path trivia.LeadingKeyword.Range false true false + + | SynPat.LongIdent(longDotId = lidwd; argPats = SynArgPats.Pats pats; range = m) when rangeContainsPos m pos -> + if rangeContainsPos lidwd.Range pos then + // let fo|o x = () + Some CompletionContext.Invalid + else + pats + |> List.tryPick (fun pat -> TryGetCompletionContextInPattern true pat None pos) + |> Option.orElseWith (fun () -> defaultTraverse synBinding) - | SynPat.LongIdent(longDotId = lidwd; argPats = SynArgPats.Pats pats; range = m) when rangeContainsPos m pos -> - if rangeContainsPos lidwd.Range pos then - // let fo|o x = () + | SynPat.Named(range = range) + | SynPat.As(_, SynPat.Named(range = range), _) when rangeContainsPos range pos -> + // let fo|o = 1 Some CompletionContext.Invalid - else - pats - |> List.tryPick (fun pat -> TryGetCompletionContextInPattern true pat None pos) - |> Option.orElseWith (fun () -> 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 + | _ -> defaultTraverse synBinding member _.VisitHashDirective(_, _directive, range) = // No completions in a directive @@ -1823,7 +1972,7 @@ module ParsedInput = Some CompletionContext.AttributeApplication // [] elif rangeContainsPos att.ArgExpr.Range pos then - Some(CompletionContext.ParameterList(att.TypeName.Range.End, findSetters att.ArgExpr)) + Some(findSetters att.TypeName.Range.End (lineStr, pos) 0 -1 att.ArgExpr) else None) diff --git a/src/Compiler/Service/ServiceParsedInputOps.fsi b/src/Compiler/Service/ServiceParsedInputOps.fsi index 427ffda43aa..17b4f2597c4 100644 --- a/src/Compiler/Service/ServiceParsedInputOps.fsi +++ b/src/Compiler/Service/ServiceParsedInputOps.fsi @@ -62,8 +62,14 @@ type public CompletionContext = | RangeOperator /// 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 + | ParameterList of + identEndPos: pos * + paramGroupIdx: int * + paramIdxInGroup: int * + paramName: string option * + settedParam: HashSet * + caretIsAfterEqualMark: bool * + isInParen: bool /// Completing an attribute name, outside of the constructor | AttributeApplication @@ -92,6 +98,9 @@ type public CompletionContext = hasThis: bool * isStatic: bool + /// (s = | ) or (s <> | ) + | CaretAfterOperator of mExprBeforeOperator: range * isInMatch: bool + type public ModuleKind = { IsAutoOpen: bool HasModuleSuffix: bool } diff --git a/vsintegration/src/FSharp.Editor/Completion/CompletionProvider.fs b/vsintegration/src/FSharp.Editor/Completion/CompletionProvider.fs index bfe12f1c7c4..2067dcc83ac 100644 --- a/vsintegration/src/FSharp.Editor/Completion/CompletionProvider.fs +++ b/vsintegration/src/FSharp.Editor/Completion/CompletionProvider.fs @@ -196,29 +196,33 @@ type internal FSharpCompletionProvider Array.sortInPlaceWith (fun (x: DeclarationListItem) (y: DeclarationListItem) -> - let mutable n = (not x.IsResolved).CompareTo(not y.IsResolved) + let mutable n = x.PreferredType.CompareTo(y.PreferredType) if n <> 0 then n else - n <- - (CompletionUtils.getKindPriority x.Kind) - .CompareTo(CompletionUtils.getKindPriority y.Kind) - + n <- (not x.IsResolved).CompareTo(not y.IsResolved) if n <> 0 then n else - n <- (not x.IsOwnMember).CompareTo(not y.IsOwnMember) + n <- + (CompletionUtils.getKindPriority x.Kind) + .CompareTo(CompletionUtils.getKindPriority y.Kind) if n <> 0 then n else - n <- String.Compare(x.NameInList, y.NameInList, StringComparison.OrdinalIgnoreCase) + n <- (not x.IsOwnMember).CompareTo(not y.IsOwnMember) if n <> 0 then n else - x.MinorPriority.CompareTo(y.MinorPriority)) + n <- String.Compare(x.NameInList, y.NameInList, StringComparison.OrdinalIgnoreCase) + + if n <> 0 then + n + else + x.MinorPriority.CompareTo(y.MinorPriority)) declarations.Items declarationItems <- declarations.Items @@ -429,7 +433,6 @@ type internal FSharpCompletionProvider | false, _ -> return CompletionChange.Create(TextChange(item.Span, nameInCode)) | true, ns -> let! sourceText = document.GetTextAsync(cancellationToken) - let! _, checkFileResults = document.GetFSharpParseAndCheckResultsAsync("ProvideCompletionsAsyncAux") let completionInsertRange = diff --git a/vsintegration/tests/FSharp.Editor.Tests/CompletionProviderTests.fs b/vsintegration/tests/FSharp.Editor.Tests/CompletionProviderTests.fs index 09c9d6cefaf..d0562e1ed41 100644 --- a/vsintegration/tests/FSharp.Editor.Tests/CompletionProviderTests.fs +++ b/vsintegration/tests/FSharp.Editor.Tests/CompletionProviderTests.fs @@ -1926,6 +1926,16 @@ type C () = override A1 () = () override x.c override A1 s = () + +type IA = + abstract member A1: unit -> unit + abstract member A1: string -> unit + abstract member A2: unit -> unit + +type TA() = + interface IA with + member this.A1 (arg1: string): unit = () + member thisTA. """ VerifyCompletionListExactly(fileContents, "override _.", [ "Equals (obj: obj): bool"; "Finalize (): unit"; "GetHashCode (): int" ])