diff --git a/src/absil/il.fs b/src/absil/il.fs index 363b1d6fb35..726ac147674 100644 --- a/src/absil/il.fs +++ b/src/absil/il.fs @@ -1990,6 +1990,8 @@ type ILTypeDef(name: string, attributes: TypeAttributes, layout: ILTypeDefLayout extends: ILType option, methods: ILMethodDefs, nestedTypes: ILTypeDefs, fields: ILFieldDefs, methodImpls: ILMethodImplDefs, events: ILEventDefs, properties: ILPropertyDefs, securityDeclsStored: ILSecurityDeclsStored, customAttrsStored: ILAttributesStored, metadataIndex: int32) = + let mutable customAttrsStored = customAttrsStored + new (name, attributes, layout, implements, genericParams, extends, methods, nestedTypes, fields, methodImpls, events, properties, securityDecls, customAttrs) = ILTypeDef (name, attributes, layout, implements, genericParams, extends, methods, nestedTypes, fields, methodImpls, events, properties, storeILSecurityDecls securityDecls, storeILCustomAttrs customAttrs, NoMetadataIdx) @@ -2025,7 +2027,15 @@ type ILTypeDef(name: string, attributes: TypeAttributes, layout: ILTypeDefLayout properties = defaultArg properties x.Properties, customAttrs = defaultArg customAttrs x.CustomAttrs) - member x.CustomAttrs = customAttrsStored.GetCustomAttrs x.MetadataIndex + member x.CustomAttrs = + match customAttrsStored with + | ILAttributesStored.Reader f -> + let res = ILAttributes(f x.MetadataIndex) + customAttrsStored <- ILAttributesStored.Given res + res + | ILAttributesStored.Given res -> + res + member x.SecurityDecls = x.SecurityDeclsStored.GetSecurityDecls x.MetadataIndex member x.IsClass = (typeKindOfFlags x.Name x.Methods x.Fields x.Extends (int x.Attributes)) = ILTypeDefKind.Class diff --git a/src/fsharp/PrettyNaming.fs b/src/fsharp/PrettyNaming.fs index 46ab87d59c9..bb784e0e007 100755 --- a/src/fsharp/PrettyNaming.fs +++ b/src/fsharp/PrettyNaming.fs @@ -138,7 +138,7 @@ module public Microsoft.FSharp.Compiler.PrettyNaming let IsOperatorName (name: string) = let name = if name.StartsWith "( " && name.EndsWith " )" then name.[2..name.Length - 3] else name // there is single operator containing a space - range operator with step: `.. ..` - let res = name = ".. .." || name |> Seq.forall (fun c -> opCharSet.Contains c && c <> ' ') + let res = name = ".. .." || name |> Seq.forall (fun c -> c <> ' ' && opCharSet.Contains c) res let IsMangledOpName (n:string) = diff --git a/src/fsharp/service/ServiceAssemblyContent.fs b/src/fsharp/service/ServiceAssemblyContent.fs index 8f0a7dbc59b..b5278b439f2 100644 --- a/src/fsharp/service/ServiceAssemblyContent.fs +++ b/src/fsharp/service/ServiceAssemblyContent.fs @@ -111,7 +111,8 @@ type AssemblySymbol = TopRequireQualifiedAccessParent: Idents option AutoOpenParent: Idents option Symbol: FSharpSymbol - Kind: LookupType -> EntityKind } + Kind: LookupType -> EntityKind + UnresolvedSymbol: UnresolvedSymbol } override x.ToString() = sprintf "%A" x type AssemblyPath = string @@ -186,14 +187,33 @@ type IAssemblyContentCache = module AssemblyContentProvider = open System.IO - let private createEntity ns (parent: Parent) (entity: FSharpEntity) = + let unresolvedSymbol (topRequireQualifiedAccessParent: Idents option) (cleanedIdents: Idents) (fullName: string) = + let getNamespace (idents: Idents) = + if idents.Length > 1 then Some idents.[..idents.Length - 2] else None + + let ns = + topRequireQualifiedAccessParent + |> Option.bind getNamespace + |> Option.orElseWith (fun () -> getNamespace cleanedIdents) + |> Option.defaultValue [||] + + let displayName = + let nameIdents = if cleanedIdents.Length > ns.Length then cleanedIdents |> Array.skip ns.Length else cleanedIdents + nameIdents |> String.concat "." + + { FullName = fullName + DisplayName = displayName + Namespace = ns } + + let createEntity ns (parent: Parent) (entity: FSharpEntity) = parent.FormatEntityFullName entity |> Option.map (fun (fullName, cleanIdents) -> + let topRequireQualifiedAccessParent = parent.TopRequiresQualifiedAccess false |> Option.map parent.FixParentModuleSuffix { FullName = fullName CleanedIdents = cleanIdents Namespace = ns NearestRequireQualifiedAccessParent = parent.ThisRequiresQualifiedAccess false |> Option.map parent.FixParentModuleSuffix - TopRequireQualifiedAccessParent = parent.TopRequiresQualifiedAccess false |> Option.map parent.FixParentModuleSuffix + TopRequireQualifiedAccessParent = topRequireQualifiedAccessParent AutoOpenParent = parent.AutoOpen |> Option.map parent.FixParentModuleSuffix Symbol = entity Kind = fun lookupType -> @@ -208,21 +228,26 @@ module AssemblyContentProvider = match entity with | Symbol.Attribute -> EntityKind.Attribute | _ -> EntityKind.Type + UnresolvedSymbol = unresolvedSymbol topRequireQualifiedAccessParent cleanIdents fullName }) - let private traverseMemberFunctionAndValues ns (parent: Parent) (membersFunctionsAndValues: seq) = + let traverseMemberFunctionAndValues ns (parent: Parent) (membersFunctionsAndValues: seq) = + let topRequireQualifiedAccessParent = parent.TopRequiresQualifiedAccess true |> Option.map parent.FixParentModuleSuffix + let autoOpenParent = parent.AutoOpen |> Option.map parent.FixParentModuleSuffix membersFunctionsAndValues |> Seq.filter (fun x -> not x.IsInstanceMember && not x.IsPropertyGetterMethod && not x.IsPropertySetterMethod) |> Seq.collect (fun func -> let processIdents fullName idents = + let cleanedIdentes = parent.FixParentModuleSuffix idents { FullName = fullName - CleanedIdents = parent.FixParentModuleSuffix idents + CleanedIdents = cleanedIdentes Namespace = ns NearestRequireQualifiedAccessParent = parent.ThisRequiresQualifiedAccess true |> Option.map parent.FixParentModuleSuffix - TopRequireQualifiedAccessParent = parent.TopRequiresQualifiedAccess true |> Option.map parent.FixParentModuleSuffix - AutoOpenParent = parent.AutoOpen |> Option.map parent.FixParentModuleSuffix + TopRequireQualifiedAccessParent = topRequireQualifiedAccessParent + AutoOpenParent = autoOpenParent Symbol = func - Kind = fun _ -> EntityKind.FunctionOrValue func.IsActivePattern } + Kind = fun _ -> EntityKind.FunctionOrValue func.IsActivePattern + UnresolvedSymbol = unresolvedSymbol topRequireQualifiedAccessParent cleanedIdentes fullName } [ yield! func.TryGetFullDisplayName() |> Option.map (fun fullDisplayName -> processIdents func.FullName (fullDisplayName.Split '.')) @@ -241,7 +266,7 @@ module AssemblyContentProvider = processIdents (fullCompiledIdents |> String.concat ".") fullCompiledIdents) |> Option.toList ]) - let rec private traverseEntity contentType (parent: Parent) (entity: FSharpEntity) = + let rec traverseEntity contentType (parent: Parent) (entity: FSharpEntity) = seq { #if !NO_EXTENSIONTYPING @@ -308,7 +333,7 @@ module AssemblyContentProvider = |> Seq.distinctBy (fun {FullName = fullName; CleanedIdents = cleanIdents} -> (fullName, cleanIdents)) |> Seq.toList - let private getAssemblySignaturesContent contentType (assemblies: FSharpAssembly list) = + let getAssemblySignaturesContent contentType (assemblies: FSharpAssembly list) = assemblies |> List.collect (fun asm -> getAssemblySignatureContent contentType asm.Contents) let getAssemblyContent (withCache: (IAssemblyContentCache -> _) -> _) contentType (fileName: string option) (assemblies: FSharpAssembly list) = diff --git a/src/fsharp/service/ServiceAssemblyContent.fsi b/src/fsharp/service/ServiceAssemblyContent.fsi index ce08cb8eb63..10c9dc4baa3 100644 --- a/src/fsharp/service/ServiceAssemblyContent.fsi +++ b/src/fsharp/service/ServiceAssemblyContent.fsi @@ -57,7 +57,9 @@ type public AssemblySymbol = AutoOpenParent: Idents option Symbol: FSharpSymbol /// Function that returns `EntityKind` based of given `LookupKind`. - Kind: LookupType -> EntityKind } + Kind: LookupType -> EntityKind + /// Cache display name and namespace, used for completion. + UnresolvedSymbol: UnresolvedSymbol } /// `RawEntity` list retrieved from an assembly. type internal AssemblyContentCacheEntry = diff --git a/src/fsharp/service/ServiceDeclarationLists.fs b/src/fsharp/service/ServiceDeclarationLists.fs index c3976e60abf..5abeacf0998 100644 --- a/src/fsharp/service/ServiceDeclarationLists.fs +++ b/src/fsharp/service/ServiceDeclarationLists.fs @@ -548,6 +548,8 @@ type FSharpDeclarationListItem(name: string, nameInCode: string, fullName: strin /// A table of declarations for Intellisense completion [] type FSharpDeclarationListInfo(declarations: FSharpDeclarationListItem[], isForType: bool, isError: bool) = + static let fsharpNamespace = [|"Microsoft"; "FSharp"|] + member __.Items = declarations member __.IsForType = isForType member __.IsError = isError @@ -650,22 +652,26 @@ type FSharpDeclarationListInfo(declarations: FSharpDeclarationListItem[], isForT | Some _ -> displayName | None -> Lexhelp.Keywords.QuoteIdentifierIfNeeded displayName - let isAttribute = SymbolHelpers.IsAttribute infoReader item.Item - + let isAttributeItem = lazy (SymbolHelpers.IsAttribute infoReader item.Item) + let cutAttributeSuffix (name: string) = - if isAttributeApplicationContext && isAttribute && name <> "Attribute" && name.EndsWith "Attribute" then + if isAttributeApplicationContext && name <> "Attribute" && name.EndsWith "Attribute" && isAttributeItem.Value then name.[0..name.Length - "Attribute".Length - 1] else name let name = cutAttributeSuffix name let nameInCode = cutAttributeSuffix nameInCode - let fullName = SymbolHelpers.FullNameOfItem g item.Item + + let fullName = + match item.Unresolved with + | Some x -> x.FullName + | None -> SymbolHelpers.FullNameOfItem g item.Item let namespaceToOpen = item.Unresolved |> Option.map (fun x -> x.Namespace) |> Option.bind (fun ns -> - if ns |> Array.startsWith [|"Microsoft"; "FSharp"|] then None + if ns |> Array.startsWith fsharpNamespace then None else Some ns) |> Option.map (fun ns -> match currentNamespaceOrModule with @@ -676,7 +682,7 @@ type FSharpDeclarationListInfo(declarations: FSharpDeclarationListItem[], isForT | None -> ns) |> Option.bind (function | [||] -> None - | ns -> Some (ns |> String.concat ".")) + | ns -> Some (System.String.Join(".", ns))) FSharpDeclarationListItem( name, nameInCode, fullName, glyph, Choice1Of2 (items, infoReader, m, denv, reactor, checkAlive), getAccessibility item.Item, diff --git a/src/fsharp/service/service.fs b/src/fsharp/service/service.fs index dac30bf30e0..8c686bc4f8f 100644 --- a/src/fsharp/service/service.fs +++ b/src/fsharp/service/service.fs @@ -568,7 +568,7 @@ type TypeCheckInfo p <- p - 1 if p >= 0 then Some p else None - let CompletionItem (ty: TyconRef option) (unresolvedEntity: AssemblySymbol option) (item: ItemWithInst) = + let CompletionItem (ty: TyconRef option) (assemblySymbol: AssemblySymbol option) (item: ItemWithInst) = let kind = match item.Item with | Item.MethodGroup (_, minfo :: _, _) -> CompletionItemKind.Method minfo.IsExtensionMember @@ -579,29 +579,12 @@ type TypeCheckInfo | Item.Value _ -> CompletionItemKind.Field | _ -> CompletionItemKind.Other - let getNamespace (idents: Idents) = - if idents.Length > 1 then Some idents.[..idents.Length - 2] else None - - let unresolved = - unresolvedEntity - |> Option.map (fun x -> - let ns = - x.TopRequireQualifiedAccessParent - |> Option.bind getNamespace - |> Option.orElseWith (fun () -> getNamespace x.CleanedIdents) - |> Option.defaultValue [||] - - let displayName = x.CleanedIdents |> Array.skip ns.Length |> String.concat "." - - { DisplayName = displayName - Namespace = ns }) - { ItemWithInst = item MinorPriority = 0 Kind = kind IsOwnMember = false Type = ty - Unresolved = unresolved } + Unresolved = assemblySymbol |> Option.map (fun x -> x.UnresolvedSymbol) } let DefaultCompletionItem item = CompletionItem None None item diff --git a/src/fsharp/symbols/SymbolHelpers.fs b/src/fsharp/symbols/SymbolHelpers.fs index 273154fc275..f7450623ec2 100644 --- a/src/fsharp/symbols/SymbolHelpers.fs +++ b/src/fsharp/symbols/SymbolHelpers.fs @@ -278,7 +278,8 @@ type CompletionItemKind = | Other type UnresolvedSymbol = - { DisplayName: string + { FullName: string + DisplayName: string Namespace: string[] } type CompletionItem = @@ -825,16 +826,16 @@ module internal SymbolHelpers = protectAssemblyExploration true (fun () -> match item with | Item.Types(it, [ty]) -> + isAppTy g ty && g.suppressed_types |> List.exists (fun supp -> - if isAppTy g ty && isAppTy g (generalizedTyconRef supp) then - // check if they are the same logical type (after removing all abbreviations) - let tcr1 = tcrefOfAppTy g ty - let tcr2 = tcrefOfAppTy g (generalizedTyconRef supp) - tyconRefEq g tcr1 tcr2 && - // check the display name is precisely the one we're suppressing - it = supp.DisplayName - else false) + let generalizedSupp = generalizedTyconRef supp + // check the display name is precisely the one we're suppressing + isAppTy g generalizedSupp && it = supp.DisplayName && + // check if they are the same logical type (after removing all abbreviations) + let tcr1 = tcrefOfAppTy g ty + let tcr2 = tcrefOfAppTy g generalizedSupp + tyconRefEq g tcr1 tcr2) | _ -> false) /// Filter types that are explicitly suppressed from the IntelliSense (such as uppercase "FSharpList", "Option", etc.) diff --git a/src/fsharp/symbols/SymbolHelpers.fsi b/src/fsharp/symbols/SymbolHelpers.fsi index 065c28335d4..03d62d4aec7 100755 --- a/src/fsharp/symbols/SymbolHelpers.fsi +++ b/src/fsharp/symbols/SymbolHelpers.fsi @@ -115,8 +115,9 @@ type public CompletionItemKind = | Argument | Other -type internal UnresolvedSymbol = - { DisplayName: string +type UnresolvedSymbol = + { FullName: string + DisplayName: string Namespace: string[] } type internal CompletionItem = diff --git a/vsintegration/src/FSharp.Editor/Completion/CompletionProvider.fs b/vsintegration/src/FSharp.Editor/Completion/CompletionProvider.fs index 4ff90efcacd..1ec9696d55d 100644 --- a/vsintegration/src/FSharp.Editor/Completion/CompletionProvider.fs +++ b/vsintegration/src/FSharp.Editor/Completion/CompletionProvider.fs @@ -39,12 +39,13 @@ type internal FSharpCompletionProvider static let userOpName = "CompletionProvider" // Save the backing data in a cache, we need to save for at least the length of the completion session // See https://github.com/Microsoft/visualfsharp/issues/4714 - static let declarationItemsData = new ConcurrentDictionary() + static let mutable declarationItems: FSharpDeclarationListItem[] = [||] static let [] NameInCodePropName = "NameInCode" static let [] FullNamePropName = "FullName" static let [] IsExtensionMemberPropName = "IsExtensionMember" static let [] NamespaceToOpenPropName = "NamespaceToOpen" static let [] IsKeywordPropName = "IsKeyword" + static let [] IndexPropName = "Index" static let keywordCompletionItems = Lexhelp.Keywords.keywordsWithDescription @@ -111,40 +112,31 @@ type internal FSharpCompletionProvider let caretLineColumn = caretLinePos.Character let partialName = QuickParse.GetPartialLongNameEx(caretLine.ToString(), caretLineColumn - 1) - let getAllSymbols() = - getAllSymbols checkFileResults - |> List.filter (fun entity -> entity.FullName.Contains "." && not (PrettyNaming.IsOperatorName entity.Symbol.DisplayName)) + let getAllSymbols() = + getAllSymbols checkFileResults + |> List.filter (fun assemblySymbol -> + assemblySymbol.FullName.Contains "." && not (PrettyNaming.IsOperatorName assemblySymbol.Symbol.DisplayName)) let! declarations = checkFileResults.GetDeclarationListInfo(Some(parseResults), fcsCaretLineNumber, caretLine.ToString(), partialName, getAllSymbols, userOpName=userOpName) |> liftAsync let results = List() - let getKindPriority = function - | CompletionItemKind.Property -> 0 - | CompletionItemKind.Field -> 1 - | CompletionItemKind.Method (isExtension = false) -> 2 - | CompletionItemKind.Event -> 3 - | CompletionItemKind.Argument -> 4 - | CompletionItemKind.Other -> 5 - | CompletionItemKind.Method (isExtension = true) -> 6 - - let sortedDeclItems = + declarationItems <- declarations.Items |> Array.sortWith (fun x y -> let mutable n = (not x.IsResolved).CompareTo(not y.IsResolved) if n <> 0 then n else - n <- (getKindPriority x.Kind).CompareTo(getKindPriority y.Kind) + n <- (CompletionUtils.getKindPriority x.Kind).CompareTo(CompletionUtils.getKindPriority y.Kind) if n <> 0 then n else n <- (not x.IsOwnMember).CompareTo(not y.IsOwnMember) if n <> 0 then n else - n <- StringComparer.OrdinalIgnoreCase.Compare(x.Name, y.Name) + n <- String.Compare(x.Name, y.Name, StringComparison.OrdinalIgnoreCase) if n <> 0 then n else x.MinorPriority.CompareTo(y.MinorPriority)) let maxHints = if mruItems.Values.Count = 0 then 0 else Seq.max mruItems.Values - declarationItemsData.Clear() - sortedDeclItems |> Array.iteri (fun number declarationItem -> + declarationItems |> Array.iteri (fun number declarationItem -> let glyph = Tokenizer.FSharpGlyphToRoslynGlyph (declarationItem.Glyph, declarationItem.Accessibility) let name = match declarationItem.NamespaceToOpen with @@ -166,7 +158,7 @@ type internal FSharpCompletionProvider let completionItem = match declarationItem.Kind with | CompletionItemKind.Method (isExtension = true) -> - completionItem.AddProperty(IsExtensionMemberPropName, "") + completionItem.AddProperty(IsExtensionMemberPropName, "") | _ -> completionItem let completionItem = @@ -179,17 +171,15 @@ type internal FSharpCompletionProvider | Some ns -> completionItem.AddProperty(NamespaceToOpenPropName, ns) | None -> completionItem + let completionItem = completionItem.AddProperty(IndexPropName, string number) + let priority = match mruItems.TryGetValue declarationItem.FullName with | true, hints -> maxHints - hints | _ -> number + maxHints + 1 - let sortText = sprintf "%06d" priority - + let sortText = priority.ToString("D6") let completionItem = completionItem.WithSortText(sortText) - - let key = completionItem.DisplayText - declarationItemsData.TryAdd(key, declarationItem) |> ignore results.Add(completionItem)) if results.Count > 0 && not declarations.IsForType && not declarations.IsError && List.isEmpty partialName.QualifyingIdents then @@ -242,15 +232,19 @@ type internal FSharpCompletionProvider override this.GetDescriptionAsync(document: Document, completionItem: Completion.CompletionItem, cancellationToken: CancellationToken): Task = async { use _logBlock = Logger.LogBlockMessage document.Name LogEditorFunctionId.Completion_GetDescriptionAsync - - match declarationItemsData.TryGetValue(completionItem.DisplayText) with - | true, declarationItem -> - let! description = declarationItem.StructuredDescriptionTextAsync - let documentation = List() - let collector = RoslynHelpers.CollectTaggedText documentation - // mix main description and xmldoc by using one collector - XmlDocumentation.BuildDataTipText(documentationBuilder, collector, collector, collector, collector, collector, description) - return CompletionDescription.Create(documentation.ToImmutableArray()) + match completionItem.Properties.TryGetValue IndexPropName with + | true, completionItemIndexStr -> + let completionItemIndex = int completionItemIndexStr + if completionItemIndex < declarationItems.Length then + let declarationItem = declarationItems.[completionItemIndex] + let! description = declarationItem.StructuredDescriptionTextAsync + let documentation = List() + let collector = RoslynHelpers.CollectTaggedText documentation + // mix main description and xmldoc by using one collector + XmlDocumentation.BuildDataTipText(documentationBuilder, collector, collector, collector, collector, collector, description) + return CompletionDescription.Create(documentation.ToImmutableArray()) + else + return CompletionDescription.Empty | _ -> return CompletionDescription.Empty } |> RoslynHelpers.StartAsyncAsTask cancellationToken diff --git a/vsintegration/src/FSharp.Editor/Completion/CompletionUtils.fs b/vsintegration/src/FSharp.Editor/Completion/CompletionUtils.fs index 0acd4db9325..2abba274acd 100644 --- a/vsintegration/src/FSharp.Editor/Completion/CompletionUtils.fs +++ b/vsintegration/src/FSharp.Editor/Completion/CompletionUtils.fs @@ -9,6 +9,7 @@ open Microsoft.CodeAnalysis.Classification open Microsoft.CodeAnalysis.Text open Microsoft.CodeAnalysis.Completion open System.Globalization +open Microsoft.FSharp.Compiler.SourceCodeServices module internal CompletionUtils = @@ -97,4 +98,14 @@ module internal CompletionUtils = | ClassificationTypeNames.Operator | ClassificationTypeNames.NumericLiteral -> false | _ -> true // anything else is a valid classification type - )) \ No newline at end of file + )) + + let inline getKindPriority kind = + match kind with + | CompletionItemKind.Property -> 0 + | CompletionItemKind.Field -> 1 + | CompletionItemKind.Method (isExtension = false) -> 2 + | CompletionItemKind.Event -> 3 + | CompletionItemKind.Argument -> 4 + | CompletionItemKind.Other -> 5 + | CompletionItemKind.Method (isExtension = true) -> 6 \ No newline at end of file