diff --git a/VisualFSharp.sln b/VisualFSharp.sln index 0834eca9cac..430af4b9eec 100644 --- a/VisualFSharp.sln +++ b/VisualFSharp.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.26009.0 +VisualStudioVersion = 15.0.26014.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FSharp.Compiler", "src\fsharp\FSharp.Compiler\FSharp.Compiler.fsproj", "{2E4D67B4-522D-4CF7-97E4-BA940F0B18F3}" EndProject diff --git a/src/fsharp/vs/ServiceAssemblyContent.fs b/src/fsharp/vs/ServiceAssemblyContent.fs index 926c78bec82..41f6f09a3c1 100644 --- a/src/fsharp/vs/ServiceAssemblyContent.fs +++ b/src/fsharp/vs/ServiceAssemblyContent.fs @@ -15,6 +15,8 @@ open Microsoft.FSharp.Compiler.Range type internal ShortIdent = string type Idents = ShortIdent[] +type MaybeUnresolvedIdent = { Ident: ShortIdent; Resolved: bool } +type MaybeUnresolvedIdents = MaybeUnresolvedIdent[] type IsAutoOpen = bool [] @@ -458,15 +460,18 @@ module internal Entity = | _ -> candidateNs.Length candidateNs.[0..nsCount - 1] - let tryCreate (targetNamespace: Idents option, targetScope: Idents, partiallyQualifiedName: Idents, - requiresQualifiedAccessParent: Idents option, autoOpenParent: Idents option, - candidateNamespace: Idents option, candidate: Idents) = + let tryCreate (targetNamespace: Idents option, targetScope: Idents, partiallyQualifiedName: MaybeUnresolvedIdents, + requiresQualifiedAccessParent: Idents option, autoOpenParent: Idents option, candidateNamespace: Idents option, candidate: Idents) = match candidate with | [||] -> [||] | _ -> partiallyQualifiedName |> Array.heads - |> Array.choose (fun parts -> + // the last part must be unresolved, otherwise we show false positive suggestions like + // "open System" for `let _ = System.DateTime.Naaaw`. Here only "Naaw" is unresolved. + |> Array.filter (fun x -> not (x.[x.Length - 1].Resolved)) + |> Array.choose (fun parts -> + let parts = parts |> Array.map (fun x -> x.Ident) if not (candidate |> Array.endsWith parts) then None else let identCount = parts.Length @@ -547,29 +552,25 @@ module internal ParsedInput = | SynConstructorArgs.Pats ps -> ps | SynConstructorArgs.NamePatPairs(xs, _) -> List.map snd xs - let internal longIdentToArray (longIdent: LongIdent): Idents = - longIdent |> Seq.map string |> Seq.toArray - - /// Returns all Idents and LongIdents found in an untyped AST. - let internal getLongIdents (input: ParsedInput option) : IDictionary = - let identsByEndPos = Dictionary() + /// Returns all `Ident`s and `LongIdent`s found in an untyped AST. + let internal getLongIdents (input: ParsedInput option) : IDictionary = + let identsByEndPos = Dictionary() let addLongIdent (longIdent: LongIdent) = - let idents = longIdentToArray longIdent for ident in longIdent do - identsByEndPos.[ident.idRange.End] <- idents + identsByEndPos.[ident.idRange.End] <- longIdent let addLongIdentWithDots (LongIdentWithDots (longIdent, lids) as value) = - match longIdentToArray longIdent with - | [||] -> () - | [|_|] as idents -> identsByEndPos.[value.Range.End] <- idents + match longIdent with + | [] -> () + | [_] as idents -> identsByEndPos.[value.Range.End] <- idents | idents -> for dotRange in lids do identsByEndPos.[Range.mkPos dotRange.EndLine (dotRange.EndColumn - 1)] <- idents identsByEndPos.[value.Range.End] <- idents let addIdent (ident: Ident) = - identsByEndPos.[ident.idRange.End] <- [|ident.idText|] + identsByEndPos.[ident.idRange.End] <- [ident] let rec walkImplFileInput (ParsedImplFileInput(_, _, _, _, _, moduleOrNamespaceList, _)) = List.iter walkSynModuleOrNamespace moduleOrNamespaceList @@ -886,7 +887,7 @@ module internal ParsedInput = walkImplFileInput input | _ -> () //debug "%A" idents - identsByEndPos :> _ + upcast identsByEndPos let getLongIdentAt ast pos = let idents = getLongIdents (Some ast) @@ -1003,13 +1004,12 @@ module internal ParsedInput = |> Seq.sortBy (fun (m, _, _) -> -m.Length) |> Seq.toList - fun (partiallyQualifiedName: Idents) (requiresQualifiedAccessParent: Idents option, autoOpenParent: Idents option, - entityNamespace: Idents option, entity: Idents) -> + fun (partiallyQualifiedName: MaybeUnresolvedIdents) + (requiresQualifiedAccessParent: Idents option, autoOpenParent: Idents option, entityNamespace: Idents option, entity: Idents) -> match res with | None -> [||] | Some (scope, ns, pos) -> - Entity.tryCreate(ns, scope.Idents, partiallyQualifiedName, requiresQualifiedAccessParent, - autoOpenParent, entityNamespace, entity) + Entity.tryCreate(ns, scope.Idents, partiallyQualifiedName, requiresQualifiedAccessParent, autoOpenParent, entityNamespace, entity) |> Array.map (fun e -> e, match modules |> List.filter (fun (m, _, _) -> entity |> Array.startsWith m ) with diff --git a/vsintegration/src/FSharp.Editor/CodeFix/AddOpenCodeFixProvider.fs b/vsintegration/src/FSharp.Editor/CodeFix/AddOpenCodeFixProvider.fs index 57923da0763..06068b7cab7 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/AddOpenCodeFixProvider.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/AddOpenCodeFixProvider.fs @@ -178,34 +178,44 @@ type internal FSharpAddOpenCodeFixProvider | None, _ | _, FSharpCheckFileAnswer.Aborted -> () | Some parsedInput, FSharpCheckFileAnswer.Succeeded checkFileResults -> - let textLinePos = sourceText.Lines.GetLinePosition context.Span.Start - let defines = CompilerEnvironment.GetCompilationDefinesForEditing(context.Document.FilePath, options.OtherOptions |> Seq.toList) - let symbol = CommonHelpers.getSymbolAtPosition(context.Document.Id, sourceText, context.Span.Start, context.Document.FilePath, defines, SymbolLookupKind.Fuzzy) - match symbol with - | Some symbol -> - let pos = Pos.fromZ textLinePos.Line textLinePos.Character - let isAttribute = UntypedParseImpl.GetEntityKind(pos, parsedInput) = Some EntityKind.Attribute - let entities = - assemblyContentProvider.GetAllEntitiesInProjectAndReferencedAssemblies checkFileResults - |> List.map (fun e -> - [ yield e.TopRequireQualifiedAccessParent, e.AutoOpenParent, e.Namespace, e.CleanedIdents - if isAttribute then - let lastIdent = e.CleanedIdents.[e.CleanedIdents.Length - 1] - if lastIdent.EndsWith "Attribute" && e.Kind LookupType.Precise = EntityKind.Attribute then - yield - e.TopRequireQualifiedAccessParent, - e.AutoOpenParent, - e.Namespace, - e.CleanedIdents - |> Array.replace (e.CleanedIdents.Length - 1) (lastIdent.Substring(0, lastIdent.Length - 9)) ]) - |> List.concat - - let idents = ParsedInput.getLongIdentAt parsedInput (Range.mkPos pos.Line symbol.RightColumn) - match idents with - | Some idents -> - let createEntity = ParsedInput.tryFindInsertionContext pos.Line parsedInput idents - return entities |> Seq.map createEntity |> Seq.concat |> Seq.toList |> getSuggestions context - | None -> () + let unresolvedIdentRange = + let startLinePos = sourceText.Lines.GetLinePosition context.Span.Start + let startPos = Pos.fromZ startLinePos.Line startLinePos.Character + let endLinePos = sourceText.Lines.GetLinePosition context.Span.End + let endPos = Pos.fromZ endLinePos.Line endLinePos.Character + Range.mkRange context.Document.FilePath startPos endPos + + let isAttribute = UntypedParseImpl.GetEntityKind(unresolvedIdentRange.Start, parsedInput) = Some EntityKind.Attribute + + let entities = + assemblyContentProvider.GetAllEntitiesInProjectAndReferencedAssemblies checkFileResults + |> List.collect (fun e -> + [ yield e.TopRequireQualifiedAccessParent, e.AutoOpenParent, e.Namespace, e.CleanedIdents + if isAttribute then + let lastIdent = e.CleanedIdents.[e.CleanedIdents.Length - 1] + if lastIdent.EndsWith "Attribute" && e.Kind LookupType.Precise = EntityKind.Attribute then + yield + e.TopRequireQualifiedAccessParent, + e.AutoOpenParent, + e.Namespace, + e.CleanedIdents + |> Array.replace (e.CleanedIdents.Length - 1) (lastIdent.Substring(0, lastIdent.Length - 9)) ]) + + let longIdent = ParsedInput.getLongIdentAt parsedInput unresolvedIdentRange.End + + let maybeUnresolvedIdents = + longIdent + |> Option.map (fun longIdent -> + longIdent + |> List.map (fun ident -> + { Ident = ident.idText + Resolved = not (ident.idRange = unresolvedIdentRange) }) + |> List.toArray) + + match maybeUnresolvedIdents with + | Some maybeUnresolvedIdents -> + let createEntity = ParsedInput.tryFindInsertionContext unresolvedIdentRange.StartLine parsedInput maybeUnresolvedIdents + return entities |> Seq.map createEntity |> Seq.concat |> Seq.toList |> getSuggestions context | None -> () | None -> () } |> CommonRoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken)