From 5a8f454a10f675fc3788b78f90fdc1acedeab606 Mon Sep 17 00:00:00 2001 From: Steffen Forkmann Date: Fri, 28 Jun 2019 21:31:12 +0200 Subject: [PATCH] Use a MaxBuffer for Suggestions (#7060) * Use MaxBuffer for Suggestions * Extract SuggestNames function * Rename Parameter * Apply feedback for formatting --- src/fsharp/CompileOps.fs | 42 ++- src/fsharp/ConstraintSolver.fs | 9 +- src/fsharp/ErrorLogger.fs | 4 +- src/fsharp/ErrorResolutionHints.fs | 133 ++++--- src/fsharp/NameResolution.fs | 342 +++++++----------- src/fsharp/TypeChecker.fs | 33 +- .../service/ServiceErrorResolutionHints.fs | 14 +- .../service/ServiceErrorResolutionHints.fsi | 2 +- .../FSharp.Compiler.UnitTests/EditDistance.fs | 2 - .../FSharp.Compiler.UnitTests.fsproj | 1 + .../SuggestionBuffer.fs | 103 ++++++ tests/fsharp/typecheck/sigs/neg01.bsl | 8 +- tests/fsharp/typecheck/sigs/neg14.bsl | 8 +- tests/fsharp/typecheck/sigs/neg20.bsl | 8 +- tests/fsharp/typecheck/sigs/neg32.bsl | 24 +- tests/fsharp/typecheck/sigs/neg59.bsl | 4 +- tests/fsharp/typecheck/sigs/neg86.vsbsl | 4 +- tests/fsharp/typecheck/sigs/neg96.bsl | 8 +- tests/fsharp/typecheck/sigs/neg97.bsl | 4 +- tests/fsharp/typecheck/sigs/neg97.vsbsl | 4 +- .../CodeFix/ReplaceWithSuggestion.fs | 32 +- 21 files changed, 402 insertions(+), 387 deletions(-) create mode 100644 tests/FSharp.Compiler.UnitTests/SuggestionBuffer.fs diff --git a/src/fsharp/CompileOps.fs b/src/fsharp/CompileOps.fs index f40b700a5f8..85728909cf3 100644 --- a/src/fsharp/CompileOps.fs +++ b/src/fsharp/CompileOps.fs @@ -600,7 +600,20 @@ let getErrorString key = SR.GetString key let (|InvalidArgument|_|) (exn: exn) = match exn with :? ArgumentException as e -> Some e.Message | _ -> None -let OutputPhasedErrorR (os: StringBuilder) (err: PhasedDiagnostic) (suggestNames: bool) = +let OutputPhasedErrorR (os: StringBuilder) (err: PhasedDiagnostic) (canSuggestNames: bool) = + + let suggestNames suggestionsF idText = + if canSuggestNames then + let buffer = ErrorResolutionHints.SuggestionBuffer idText + if not buffer.Disabled then + suggestionsF buffer.Add + if not buffer.IsEmpty then + os.Append " " |> ignore + os.Append(FSComp.SR.undefinedNameSuggestionsIntro()) |> ignore + for value in buffer do + os.AppendLine() |> ignore + os.Append " " |> ignore + os.Append(DecompileOpName value) |> ignore let rec OutputExceptionR (os: StringBuilder) error = @@ -822,14 +835,10 @@ let OutputPhasedErrorR (os: StringBuilder) (err: PhasedDiagnostic) (suggestNames | UndefinedName(_, k, id, suggestionsF) -> os.Append(k (DecompileOpName id.idText)) |> ignore - if suggestNames then - let filtered = ErrorResolutionHints.FilterPredictions suggestionsF id.idText - if List.isEmpty filtered |> not then - os.Append(ErrorResolutionHints.FormatPredictions DecompileOpName filtered) |> ignore - + suggestNames suggestionsF id.idText | InternalUndefinedItemRef(f, smr, ccuName, s) -> - let _, errs = f(smr, ccuName, s) + let _, errs = f(smr, ccuName, s) os.Append errs |> ignore | FieldNotMutable _ -> @@ -1363,10 +1372,7 @@ let OutputPhasedErrorR (os: StringBuilder) (err: PhasedDiagnostic) (suggestNames | ErrorWithSuggestions ((_, s), _, idText, suggestionF) -> os.Append(DecompileOpName s) |> ignore - if suggestNames then - let filtered = ErrorResolutionHints.FilterPredictions suggestionF idText - if List.isEmpty filtered |> not then - os.Append(ErrorResolutionHints.FormatPredictions DecompileOpName filtered) |> ignore + suggestNames suggestionF idText | NumberedError ((_, s), _) -> os.Append s |> ignore @@ -1582,10 +1588,10 @@ let OutputPhasedErrorR (os: StringBuilder) (err: PhasedDiagnostic) (suggestNames // remove any newlines and tabs -let OutputPhasedDiagnostic (os: System.Text.StringBuilder) (err: PhasedDiagnostic) (flattenErrors: bool) (suggestNames: bool) = +let OutputPhasedDiagnostic (os: System.Text.StringBuilder) (err: PhasedDiagnostic) (flattenErrors: bool) (canSuggestNames: bool) = let buf = new System.Text.StringBuilder() - OutputPhasedErrorR buf err suggestNames + OutputPhasedErrorR buf err canSuggestNames let s = if flattenErrors then ErrorLogger.NormalizeErrorString (buf.ToString()) else buf.ToString() os.Append s |> ignore @@ -1635,7 +1641,7 @@ type Diagnostic = | Long of bool * DiagnosticDetailedInfo /// returns sequence that contains Diagnostic for the given error + Diagnostic for all related errors -let CollectDiagnostic (implicitIncludeDir, showFullPaths, flattenErrors, errorStyle, isError, err: PhasedDiagnostic, suggestNames: bool) = +let CollectDiagnostic (implicitIncludeDir, showFullPaths, flattenErrors, errorStyle, isError, err: PhasedDiagnostic, canSuggestNames: bool) = let outputWhere (showFullPaths, errorStyle) m: DiagnosticLocation = if Range.equals m rangeStartup || Range.equals m rangeCmdArgs then { Range = m; TextRepresentation = ""; IsEmpty = true; File = "" } @@ -1708,7 +1714,7 @@ let CollectDiagnostic (implicitIncludeDir, showFullPaths, flattenErrors, errorSt let canonical = OutputCanonicalInformation(err.Subcategory(), GetDiagnosticNumber mainError) let message = let os = System.Text.StringBuilder() - OutputPhasedDiagnostic os mainError flattenErrors suggestNames + OutputPhasedDiagnostic os mainError flattenErrors canSuggestNames os.ToString() let entry: DiagnosticDetailedInfo = { Location = where; Canonical = canonical; Message = message } @@ -1723,7 +1729,7 @@ let CollectDiagnostic (implicitIncludeDir, showFullPaths, flattenErrors, errorSt let relCanonical = OutputCanonicalInformation(err.Subcategory(), GetDiagnosticNumber mainError) // Use main error for code let relMessage = let os = System.Text.StringBuilder() - OutputPhasedDiagnostic os err flattenErrors suggestNames + OutputPhasedDiagnostic os err flattenErrors canSuggestNames os.ToString() let entry: DiagnosticDetailedInfo = { Location = relWhere; Canonical = relCanonical; Message = relMessage} @@ -1731,7 +1737,7 @@ let CollectDiagnostic (implicitIncludeDir, showFullPaths, flattenErrors, errorSt | _ -> let os = System.Text.StringBuilder() - OutputPhasedDiagnostic os err flattenErrors suggestNames + OutputPhasedDiagnostic os err flattenErrors canSuggestNames errors.Add( Diagnostic.Short(isError, os.ToString()) ) relatedErrors |> List.iter OutputRelatedError @@ -1752,7 +1758,7 @@ let CollectDiagnostic (implicitIncludeDir, showFullPaths, flattenErrors, errorSt /// prints error and related errors to the specified StringBuilder let rec OutputDiagnostic (implicitIncludeDir, showFullPaths, flattenErrors, errorStyle, isError) os (err: PhasedDiagnostic) = - // 'true' for "suggestNames" is passed last here because we want to report suggestions in fsc.exe and fsi.exe, just not in regular IDE usage. + // 'true' for "canSuggestNames" is passed last here because we want to report suggestions in fsc.exe and fsi.exe, just not in regular IDE usage. let errors = CollectDiagnostic (implicitIncludeDir, showFullPaths, flattenErrors, errorStyle, isError, err, true) for e in errors do Printf.bprintf os "\n" diff --git a/src/fsharp/ConstraintSolver.fs b/src/fsharp/ConstraintSolver.fs index 74036e1687c..586b91c41b0 100644 --- a/src/fsharp/ConstraintSolver.fs +++ b/src/fsharp/ConstraintSolver.fs @@ -2193,12 +2193,11 @@ and ReportNoCandidatesError (csenv: ConstraintSolverEnv) (nUnnamedCallerArgs, nN match cmeth.UnassignedNamedArgs with | CallerNamedArg(id, _) :: _ -> if minfo.IsConstructor then - let predictFields() = - minfo.DeclaringTyconRef.AllInstanceFieldsAsList - |> List.map (fun p -> p.Name.Replace("@", "")) - |> System.Collections.Generic.HashSet + let suggestFields (addToBuffer: string -> unit) = + for p in minfo.DeclaringTyconRef.AllInstanceFieldsAsList do + addToBuffer(p.Name.Replace("@", "")) - ErrorWithSuggestions((msgNum, FSComp.SR.csCtorHasNoArgumentOrReturnProperty(methodName, id.idText, msgText)), id.idRange, id.idText, predictFields) + ErrorWithSuggestions((msgNum, FSComp.SR.csCtorHasNoArgumentOrReturnProperty(methodName, id.idText, msgText)), id.idRange, id.idText, suggestFields) else Error((msgNum, FSComp.SR.csMemberHasNoArgumentOrReturnProperty(methodName, id.idText, msgText)), id.idRange) | [] -> Error((msgNum, msgText), m) diff --git a/src/fsharp/ErrorLogger.fs b/src/fsharp/ErrorLogger.fs index e84b5c69e4b..2f9c07173b5 100755 --- a/src/fsharp/ErrorLogger.fs +++ b/src/fsharp/ErrorLogger.fs @@ -46,9 +46,9 @@ let rec findOriginalException err = | WrappedError(err, _) -> findOriginalException err | _ -> err -type Suggestions = unit -> Collections.Generic.HashSet +type Suggestions = (string -> unit) -> unit -let NoSuggestions : Suggestions = fun () -> Collections.Generic.HashSet() +let NoSuggestions : Suggestions = ignore /// Thrown when we stop processing the F# Interactive entry or #load. exception StopProcessingExn of exn option with diff --git a/src/fsharp/ErrorResolutionHints.fs b/src/fsharp/ErrorResolutionHints.fs index 02ebc1e1b65..ee80985935e 100644 --- a/src/fsharp/ErrorResolutionHints.fs +++ b/src/fsharp/ErrorResolutionHints.fs @@ -5,11 +5,13 @@ module internal FSharp.Compiler.ErrorResolutionHints open Internal.Utilities open FSharp.Compiler.AbstractIL.Internal.Library +open System.Collections +open System.Collections.Generic let maxSuggestions = 5 let minThresholdForSuggestions = 0.7 let highConfidenceThreshold = 0.85 -let minStringLengthForThreshold = 3 +let minStringLengthForSuggestion = 3 /// We report a candidate if its edit distance is <= the threshold. /// The threshold is set to about a quarter of the number of characters. @@ -23,65 +25,82 @@ let IsInEditDistanceProximity idText suggestion = editDistance <= threshold -/// Filters predictions based on edit distance to the given unknown identifier. -let FilterPredictions (suggestionF:ErrorLogger.Suggestions) (idText:string) = +/// Demangles a suggestion +let DemangleOperator (nm: string) = + if nm.StartsWithOrdinal("( ") && nm.EndsWithOrdinal(" )") then + nm.[2..nm.Length - 3] + else + nm + +type SuggestionBufferEnumerator(tail: int, data: KeyValuePair []) = + let mutable current = data.Length + interface IEnumerator with + member __.Current + with get () = + let kvpr = &data.[current] + kvpr.Value + interface System.Collections.IEnumerator with + member __.Current with get () = box data.[current].Value + member __.MoveNext() = + current <- current - 1 + current > tail || (current = tail && data.[current] <> Unchecked.defaultof<_>) + member __.Reset () = current <- data.Length + interface System.IDisposable with + member __.Dispose () = () + +type SuggestionBuffer(idText: string) = + let data = Array.zeroCreate>(maxSuggestions) + let mutable tail = maxSuggestions - 1 let uppercaseText = idText.ToUpperInvariant() - let allSuggestions = suggestionF() + let dotIdText = "." + idText + let mutable disableSuggestions = idText.Length < minStringLengthForSuggestion - let demangle (nm:string) = - if nm.StartsWithOrdinal("( ") && nm.EndsWithOrdinal(" )") then - let cleanName = nm.[2..nm.Length - 3] - cleanName - else nm + let insert (k,v) = + let mutable pos = tail + while pos < maxSuggestions && (let kv = &data.[pos] in kv.Key < k) do + pos <- pos + 1 - /// Returns `true` if given string is an operator display name, e.g. ( |>> ) - let IsOperatorName (name: string) = - if isNull name || not (name.StartsWithOrdinal("( ") && name.EndsWithOrdinal(" )")) then - false - else - let mutable i = 2 - let mutable isOperator = true - while isOperator && i < name.Length - 3 do - if name.[i] = ' ' then - isOperator <- false - i <- i + 1 - isOperator + if pos > 0 then + if pos >= maxSuggestions || (let kv = &data.[pos] in k <> kv.Key || v <> kv.Value) then + if tail < pos - 1 then + for i = tail to pos - 2 do + data.[i] <- data.[i + 1] + data.[pos - 1] <- KeyValuePair(k,v) + if tail > 0 then tail <- tail - 1 - if allSuggestions.Contains idText then [] else // some other parsing error occurred - let dotIdText = "." + idText - allSuggestions - |> Seq.choose (fun suggestion -> - // Because beginning a name with _ is used both to indicate an unused - // value as well as to formally squelch the associated compiler - // error/warning (FS1182), we remove such names from the suggestions, - // both to prevent accidental usages as well as to encourage good taste - if IsOperatorName suggestion || suggestion.StartsWithOrdinal("_") then None else - let suggestion:string = demangle suggestion - let suggestedText = suggestion.ToUpperInvariant() - let similarity = EditDistance.JaroWinklerDistance uppercaseText suggestedText - if similarity >= highConfidenceThreshold || suggestion.EndsWithOrdinal(dotIdText) then - Some(similarity, suggestion) - elif similarity < minThresholdForSuggestions && suggestedText.Length > minStringLengthForThreshold then - None - elif IsInEditDistanceProximity uppercaseText suggestedText then - Some(similarity, suggestion) - else - None) - |> Seq.sortByDescending fst - |> Seq.mapi (fun i x -> i, x) - |> Seq.takeWhile (fun (i, _) -> i < maxSuggestions) - |> Seq.map snd - |> Seq.toList + member __.Add (suggestion: string) = + if not disableSuggestions then + if suggestion = idText then // some other parse error happened + disableSuggestions <- true + + // Because beginning a name with _ is used both to indicate an unused + // value as well as to formally squelch the associated compiler + // error/warning (FS1182), we remove such names from the suggestions, + // both to prevent accidental usages as well as to encourage good taste + if suggestion.Length >= minStringLengthForSuggestion && not (suggestion.StartsWithOrdinal "_") then + let suggestion:string = DemangleOperator suggestion + let suggestedText = suggestion.ToUpperInvariant() + let similarity = EditDistance.JaroWinklerDistance uppercaseText suggestedText + if similarity >= highConfidenceThreshold || + suggestion.EndsWithOrdinal dotIdText || + (similarity >= minThresholdForSuggestions && IsInEditDistanceProximity uppercaseText suggestedText) + then + insert(similarity, suggestion) |> ignore + + member __.Disabled with get () = disableSuggestions + + member __.IsEmpty with get () = disableSuggestions || (tail = maxSuggestions - 1) -/// Formats the given predictions according to the error style. -let FormatPredictions normalizeF (predictions: (float * string) list) = - match predictions with - | [] -> System.String.Empty - | _ -> - let suggestions = - predictions - |> List.map (snd >> normalizeF) - |> List.map (sprintf "%s %s" System.Environment.NewLine) - |> String.concat "" + interface IEnumerable with + member this.GetEnumerator () = + if this.IsEmpty then + Seq.empty.GetEnumerator() + else + new SuggestionBufferEnumerator(tail, data) :> IEnumerator - " " + FSComp.SR.undefinedNameSuggestionsIntro() + suggestions + interface IEnumerable with + member this.GetEnumerator () = + if this.IsEmpty then + Seq.empty.GetEnumerator() :> IEnumerator + else + new SuggestionBufferEnumerator(tail, data) :> IEnumerator diff --git a/src/fsharp/NameResolution.fs b/src/fsharp/NameResolution.fs index 1809fdaf3b0..6f03bbc1d44 100644 --- a/src/fsharp/NameResolution.fs +++ b/src/fsharp/NameResolution.fs @@ -899,9 +899,7 @@ let AddResults res1 res2 = | Result x, Exception _ -> Result x // If we have error messages for the same symbol, then we can merge suggestions. | Exception (UndefinedName(n1, f, id1, suggestions1)), Exception (UndefinedName(n2, _, id2, suggestions2)) when n1 = n2 && id1.idText = id2.idText && Range.equals id1.idRange id2.idRange -> - let suggestions = HashSet(suggestions1()) - suggestions.UnionWith(suggestions2()) - Exception(UndefinedName(n1, f, id1, fun () -> suggestions)) + Exception(UndefinedName(n1, f, id1, fun addToBuffer -> suggestions1 addToBuffer; suggestions2 addToBuffer)) // This prefers error messages coming from deeper failing long identifier paths | Exception (UndefinedName(n1, _, _, _) as e1), Exception (UndefinedName(n2, _, _, _) as e2) -> if n1 < n2 then Exception e2 else Exception e1 @@ -1840,12 +1838,12 @@ let rec ResolveLongIndentAsModuleOrNamespace sink atMostOne amap m first fullyQu else let moduleOrNamespaces = nenv.ModulesAndNamespaces fullyQualified let namespaceNotFound = lazy( - let suggestModulesAndNamespaces() = - moduleOrNamespaces - |> Seq.collect (fun kv -> kv.Value) - |> Seq.filter (fun modref -> IsEntityAccessible amap m ad modref) - |> Seq.collect (fun e -> [e.DisplayName; e.DemangledModuleOrNamespaceName]) - |> HashSet + let suggestModulesAndNamespaces (addToBuffer: string -> unit) = + for kv in moduleOrNamespaces do + for modref in kv.Value do + if IsEntityAccessible amap m ad modref then + addToBuffer modref.DisplayName + addToBuffer modref.DemangledModuleOrNamespaceName UndefinedName(0, FSComp.SR.undefinedNameNamespaceOrModule, id, suggestModulesAndNamespaces)) @@ -1854,11 +1852,11 @@ let rec ResolveLongIndentAsModuleOrNamespace sink atMostOne amap m first fullyQu match moduleNotFoundErrorCache with | Some (oldId, error) when Range.equals oldId id.idRange -> error | _ -> - let suggestNames() = - mty.ModulesAndNamespacesByDemangledName - |> Seq.filter (fun kv -> IsEntityAccessible amap m ad (modref.NestedTyconRef kv.Value)) - |> Seq.collect (fun e -> [e.Value.DisplayName; e.Value.DemangledModuleOrNamespaceName]) - |> HashSet + let suggestNames (addToBuffer: string -> unit) = + for kv in mty.ModulesAndNamespacesByDemangledName do + if IsEntityAccessible amap m ad (modref.NestedTyconRef kv.Value) then + addToBuffer kv.Value.DisplayName + addToBuffer kv.Value.DemangledModuleOrNamespaceName let error = raze (UndefinedName(depth, FSComp.SR.undefinedNameNamespace, id, suggestNames)) moduleNotFoundErrorCache <- Some(id.idRange, error) @@ -2229,44 +2227,30 @@ let rec ResolveLongIdentInTypePrim (ncenv: NameResolver) nenv lookupKind (resInf match nestedSearchAccessible with | Result res when not (isNil res) -> nestedSearchAccessible | _ -> - let suggestMembers() = - let suggestions1 = - ExtensionPropInfosOfTypeInScope ResultCollectionSettings.AllResults ncenv.InfoReader nenv None ad m ty - |> List.map (fun p -> p.PropertyName) - - let suggestions2 = - ExtensionMethInfosOfTypeInScope ResultCollectionSettings.AllResults ncenv.InfoReader nenv None m ty - |> List.map (fun m -> m.DisplayName) + let suggestMembers (addToBuffer: string -> unit) = + for p in ExtensionPropInfosOfTypeInScope ResultCollectionSettings.AllResults ncenv.InfoReader nenv None ad m ty do + addToBuffer p.PropertyName - let suggestions3 = - GetIntrinsicPropInfosOfType ncenv.InfoReader None ad AllowMultiIntfInstantiations.No findFlag m ty - |> List.map (fun p -> p.PropertyName) + for m in ExtensionMethInfosOfTypeInScope ResultCollectionSettings.AllResults ncenv.InfoReader nenv None m ty do + addToBuffer m.DisplayName - let suggestions4 = - GetIntrinsicMethInfosOfType ncenv.InfoReader None ad AllowMultiIntfInstantiations.No findFlag m ty - |> List.filter (fun m -> not m.IsClassConstructor && not m.IsConstructor) - |> List.map (fun m -> m.DisplayName) + for p in GetIntrinsicPropInfosOfType ncenv.InfoReader None ad AllowMultiIntfInstantiations.No findFlag m ty do + addToBuffer p.PropertyName - let suggestions5 = GetRecordLabelsForType g nenv ty + for m in GetIntrinsicMethInfosOfType ncenv.InfoReader None ad AllowMultiIntfInstantiations.No findFlag m ty do + if not m.IsClassConstructor && not m.IsConstructor then + addToBuffer m.DisplayName - let suggestions6 = - match lookupKind with - | LookupKind.Expr | LookupKind.Pattern -> - if isAppTy g ty then - let tcref = tcrefOfAppTy g ty - tcref.UnionCasesArray - |> Array.map (fun uc -> uc.DisplayName) - else - [||] - | _ -> [||] + for l in GetRecordLabelsForType g nenv ty do + addToBuffer l - [ yield! suggestions1 - yield! suggestions2 - yield! suggestions3 - yield! suggestions4 - yield! suggestions5 - yield! suggestions6 ] - |> HashSet + match lookupKind with + | LookupKind.Expr | LookupKind.Pattern -> + if isAppTy g ty then + let tcref = tcrefOfAppTy g ty + for uc in tcref.UnionCasesArray do + addToBuffer uc.DisplayName + | _ -> () raze (UndefinedName (depth, FSComp.SR.undefinedNameFieldConstructorOrMember, id, suggestMembers)) @@ -2380,43 +2364,28 @@ let rec ResolveExprLongIdentInModuleOrNamespace (ncenv: NameResolver) nenv (type match tyconSearch +++ moduleSearch +++ (fun _ -> unionSearch) with | Result [] -> - let suggestPossibleTypesAndNames() = - let types = - modref.ModuleOrNamespaceType.AllEntities - |> Seq.filter (fun e -> IsEntityAccessible ncenv.amap m ad (modref.NestedTyconRef e)) - |> Seq.map (fun e -> e.DisplayName) - - let submodules = - mty.ModulesAndNamespacesByDemangledName - |> Seq.filter (fun kv -> IsEntityAccessible ncenv.amap m ad (modref.NestedTyconRef kv.Value)) - |> Seq.map (fun e -> e.Value.DisplayName) - - let unions = - modref.ModuleOrNamespaceType.AllEntities - |> Seq.collect (fun tycon -> - let hasRequireQualifiedAccessAttribute = HasFSharpAttribute ncenv.g ncenv.g.attrib_RequireQualifiedAccessAttribute tycon.Attribs - if hasRequireQualifiedAccessAttribute then - [||] - else - tycon.UnionCasesArray) - |> Seq.map (fun uc -> uc.DisplayName) - - let vals = - modref.ModuleOrNamespaceType.AllValsByLogicalName - |> Seq.filter (fun e -> IsValAccessible ad (mkNestedValRef modref e.Value)) - |> Seq.map (fun e -> e.Value.DisplayName) - - let exns = - modref.ModuleOrNamespaceType.ExceptionDefinitionsByDemangledName - |> Seq.filter (fun e -> IsTyconReprAccessible ncenv.amap m ad (modref.NestedTyconRef e.Value)) - |> Seq.map (fun e -> e.Value.DisplayName) - - [ yield! types - yield! submodules - yield! unions - yield! vals - yield! exns ] - |> HashSet + let suggestPossibleTypesAndNames (addToBuffer: string -> unit) = + for e in modref.ModuleOrNamespaceType.AllEntities do + if IsEntityAccessible ncenv.amap m ad (modref.NestedTyconRef e) then + addToBuffer e.DisplayName + + if e.IsUnionTycon then + let hasRequireQualifiedAccessAttribute = HasFSharpAttribute ncenv.g ncenv.g.attrib_RequireQualifiedAccessAttribute e.Attribs + if not hasRequireQualifiedAccessAttribute then + for uc in e.UnionCasesArray do + addToBuffer uc.DisplayName + + for kv in mty.ModulesAndNamespacesByDemangledName do + if IsEntityAccessible ncenv.amap m ad (modref.NestedTyconRef kv.Value) then + addToBuffer kv.Value.DisplayName + + for e in modref.ModuleOrNamespaceType.AllValsByLogicalName do + if IsValAccessible ad (mkNestedValRef modref e.Value) then + addToBuffer e.Value.DisplayName + + for e in modref.ModuleOrNamespaceType.ExceptionDefinitionsByDemangledName do + if IsTyconReprAccessible ncenv.amap m ad (modref.NestedTyconRef e.Value) then + addToBuffer e.Value.DisplayName raze (UndefinedName(depth, FSComp.SR.undefinedNameValueConstructorNamespaceOrType, id, suggestPossibleTypesAndNames)) | results -> AtMostOneResult id.idRange results @@ -2504,41 +2473,26 @@ let rec ResolveExprLongIdentPrim sink (ncenv: NameResolver) first fullyQualified match !typeError with | Some e -> raze e | _ -> - let suggestNamesAndTypes() = - let suggestedNames = - nenv.eUnqualifiedItems - |> Seq.map (fun e -> e.Value.DisplayName) - - let suggestedTypes = - nenv.TyconsByDemangledNameAndArity fullyQualified - |> Seq.filter (fun e -> IsEntityAccessible ncenv.amap m ad e.Value) - |> Seq.map (fun e -> e.Value.DisplayName) - - let suggestedModulesAndNamespaces = - nenv.ModulesAndNamespaces fullyQualified - |> Seq.collect (fun kv -> kv.Value) - |> Seq.filter (fun modref -> IsEntityAccessible ncenv.amap m ad modref) - |> Seq.collect (fun e -> [e.DisplayName; e.DemangledModuleOrNamespaceName]) - - let unions = - // check if the user forgot to use qualified access - nenv.eTyconsByDemangledNameAndArity - |> Seq.choose (fun e -> - let hasRequireQualifiedAccessAttribute = HasFSharpAttribute ncenv.g ncenv.g.attrib_RequireQualifiedAccessAttribute e.Value.Attribs - if not hasRequireQualifiedAccessAttribute then - None - else - if e.Value.IsUnionTycon && e.Value.UnionCasesArray |> Array.exists (fun c -> c.DisplayName = id.idText) then - Some e.Value - else - None) - |> Seq.map (fun t -> t.DisplayName + "." + id.idText) - - [ yield! suggestedNames - yield! suggestedTypes - yield! suggestedModulesAndNamespaces - yield! unions ] - |> HashSet + let suggestNamesAndTypes (addToBuffer: string -> unit) = + for e in nenv.eUnqualifiedItems do + addToBuffer e.Value.DisplayName + + for e in nenv.TyconsByDemangledNameAndArity fullyQualified do + if IsEntityAccessible ncenv.amap m ad e.Value then + addToBuffer e.Value.DisplayName + + for kv in nenv.ModulesAndNamespaces fullyQualified do + for modref in kv.Value do + if IsEntityAccessible ncenv.amap m ad modref then + addToBuffer modref.DisplayName + addToBuffer modref.DemangledModuleOrNamespaceName + + // check if the user forgot to use qualified access + for e in nenv.eTyconsByDemangledNameAndArity do + let hasRequireQualifiedAccessAttribute = HasFSharpAttribute ncenv.g ncenv.g.attrib_RequireQualifiedAccessAttribute e.Value.Attribs + if hasRequireQualifiedAccessAttribute then + if e.Value.IsUnionTycon && e.Value.UnionCasesArray |> Array.exists (fun c -> c.DisplayName = id.idText) then + addToBuffer (e.Value.DisplayName + "." + id.idText) raze (UndefinedName(0, FSComp.SR.undefinedNameValueOfConstructor, id, suggestNamesAndTypes)) ForceRaise failingCase @@ -2605,29 +2559,25 @@ let rec ResolveExprLongIdentPrim sink (ncenv: NameResolver) first fullyQualified | _ -> let innerSearch = search +++ (moduleSearch AccessibleFromSomeFSharpCode) +++ (tyconSearch AccessibleFromSomeFSharpCode) - let suggestEverythingInScope() = - seq { yield! - nenv.ModulesAndNamespaces fullyQualified - |> Seq.collect (fun kv -> kv.Value) - |> Seq.filter (fun modref -> IsEntityAccessible ncenv.amap m ad modref) - |> Seq.collect (fun e -> [e.DisplayName; e.DemangledModuleOrNamespaceName]) + let suggestEverythingInScope (addToBuffer: string -> unit) = + for kv in nenv.ModulesAndNamespaces fullyQualified do + for modref in kv.Value do + if IsEntityAccessible ncenv.amap m ad modref then + addToBuffer modref.DisplayName + addToBuffer modref.DemangledModuleOrNamespaceName - yield! - nenv.TyconsByDemangledNameAndArity fullyQualified - |> Seq.filter (fun e -> IsEntityAccessible ncenv.amap m ad e.Value) - |> Seq.map (fun e -> e.Value.DisplayName) + for e in nenv.TyconsByDemangledNameAndArity fullyQualified do + if IsEntityAccessible ncenv.amap m ad e.Value then + addToBuffer e.Value.DisplayName - yield! - nenv.eUnqualifiedItems - |> Seq.map (fun e -> e.Value.DisplayName) - } |> HashSet + for e in nenv.eUnqualifiedItems do + addToBuffer e.Value.DisplayName match innerSearch with | Exception (UndefinedName(0, _, id1, suggestionsF)) when Range.equals id.idRange id1.idRange -> - let mergeSuggestions() = - let res = suggestEverythingInScope() - res.UnionWith(suggestionsF()) - res + let mergeSuggestions addToBuffer = + suggestionsF addToBuffer + suggestEverythingInScope addToBuffer let failingCase = raze (UndefinedName(0, FSComp.SR.undefinedNameValueNamespaceTypeOrModule, id, mergeSuggestions)) ForceRaise failingCase @@ -2709,20 +2659,15 @@ let rec ResolvePatternLongIdentInModuleOrNamespace (ncenv: NameResolver) nenv nu match tyconSearch +++ ctorSearch +++ moduleSearch with | Result [] -> - let suggestPossibleTypes() = - let submodules = - mty.ModulesAndNamespacesByDemangledName - |> Seq.filter (fun kv -> IsEntityAccessible ncenv.amap m ad (modref.NestedTyconRef kv.Value)) - |> Seq.collect (fun e -> [e.Value.DisplayName; e.Value.DemangledModuleOrNamespaceName]) - - let suggestedTypes = - nenv.TyconsByDemangledNameAndArity FullyQualifiedFlag.OpenQualified - |> Seq.filter (fun e -> IsEntityAccessible ncenv.amap m ad e.Value) - |> Seq.map (fun e -> e.Value.DisplayName) + let suggestPossibleTypes (addToBuffer: string -> unit) = + for kv in mty.ModulesAndNamespacesByDemangledName do + if IsEntityAccessible ncenv.amap m ad (modref.NestedTyconRef kv.Value) then + addToBuffer kv.Value.DisplayName + addToBuffer kv.Value.DemangledModuleOrNamespaceName - [ yield! submodules - yield! suggestedTypes ] - |> HashSet + for e in nenv.TyconsByDemangledNameAndArity FullyQualifiedFlag.OpenQualified do + if IsEntityAccessible ncenv.amap m ad e.Value then + addToBuffer e.Value.DisplayName raze (UndefinedName(depth, FSComp.SR.undefinedNameConstructorModuleOrNamespace, id, suggestPossibleTypes)) | results -> AtMostOneResult id.idRange results @@ -2828,10 +2773,9 @@ let rec ResolveTypeLongIdentInTyconRefPrim (ncenv: NameResolver) (typeNameResInf match tcrefs with | tcref :: _ -> success tcref | [] -> - let suggestTypes() = - tcref.ModuleOrNamespaceType.TypesByDemangledNameAndArity id.idRange - |> Seq.map (fun e -> e.Value.DisplayName) - |> HashSet + let suggestTypes (addToBuffer: string -> unit) = + for e in tcref.ModuleOrNamespaceType.TypesByDemangledNameAndArity id.idRange do + addToBuffer e.Value.DisplayName raze (UndefinedName(depth, FSComp.SR.undefinedNameType, id, suggestTypes)) | id2 :: rest2 -> @@ -2849,10 +2793,9 @@ let rec ResolveTypeLongIdentInTyconRefPrim (ncenv: NameResolver) (typeNameResInf match tcrefs with | _ :: _ -> tcrefs |> CollectAtMostOneResult (fun (resInfo, tcref) -> ResolveTypeLongIdentInTyconRefPrim ncenv typeNameResInfo ad resInfo genOk (depth+1) m tcref id2 rest2) | [] -> - let suggestTypes() = - tcref.ModuleOrNamespaceType.TypesByDemangledNameAndArity id.idRange - |> Seq.map (fun e -> e.Value.DisplayName) - |> HashSet + let suggestTypes (addToBuffer: string -> unit) = + for e in tcref.ModuleOrNamespaceType.TypesByDemangledNameAndArity id.idRange do + addToBuffer e.Value.DisplayName raze (UndefinedName(depth, FSComp.SR.undefinedNameType, id, suggestTypes)) @@ -2873,11 +2816,11 @@ let ResolveTypeLongIdentInTyconRef sink (ncenv: NameResolver) nenv typeNameResIn /// Create an UndefinedName error with details let SuggestTypeLongIdentInModuleOrNamespace depth (modref: ModuleOrNamespaceRef) amap ad m (id: Ident) = - let suggestPossibleTypes() = - modref.ModuleOrNamespaceType.AllEntities - |> Seq.filter (fun e -> IsEntityAccessible amap m ad (modref.NestedTyconRef e)) - |> Seq.collect (fun e -> [e.DisplayName; e.DemangledModuleOrNamespaceName]) - |> HashSet + let suggestPossibleTypes (addToBuffer: string -> unit) = + for e in modref.ModuleOrNamespaceType.AllEntities do + if IsEntityAccessible amap m ad (modref.NestedTyconRef e) then + addToBuffer e.DisplayName + addToBuffer e.DemangledModuleOrNamespaceName let errorTextF s = FSComp.SR.undefinedNameTypeIn(s, fullDisplayTextOfModRef modref) UndefinedName(depth, errorTextF, id, suggestPossibleTypes) @@ -2901,11 +2844,12 @@ let rec private ResolveTypeLongIdentInModuleOrNamespace sink nenv (ncenv: NameRe let resInfo = resInfo.AddEntity(id.idRange, submodref) ResolveTypeLongIdentInModuleOrNamespace sink nenv ncenv typeNameResInfo ad genOk resInfo (depth+1) m submodref submodref.ModuleOrNamespaceType id2 rest2 | _ -> - let suggestPossibleModules() = - modref.ModuleOrNamespaceType.ModulesAndNamespacesByDemangledName - |> Seq.filter (fun kv -> IsEntityAccessible ncenv.amap m ad (modref.NestedTyconRef kv.Value)) - |> Seq.collect (fun e -> [e.Value.DisplayName; e.Value.DemangledModuleOrNamespaceName]) - |> HashSet + let suggestPossibleModules (addToBuffer: string -> unit) = + for kv in modref.ModuleOrNamespaceType.ModulesAndNamespacesByDemangledName do + if IsEntityAccessible ncenv.amap m ad (modref.NestedTyconRef kv.Value) then + addToBuffer kv.Value.DisplayName + addToBuffer kv.Value.DemangledModuleOrNamespaceName + raze (UndefinedName(depth, FSComp.SR.undefinedNameNamespaceOrModule, id, suggestPossibleModules)) let tyconSearch = @@ -2913,10 +2857,9 @@ let rec private ResolveTypeLongIdentInModuleOrNamespace sink nenv (ncenv: NameRe match tcrefs with | _ :: _ -> tcrefs |> CollectResults (fun tcref -> ResolveTypeLongIdentInTyconRefPrim ncenv typeNameResInfo ad resInfo genOk (depth+1) m tcref id2 rest2) | [] -> - let suggestTypes() = - modref.ModuleOrNamespaceType.TypesByDemangledNameAndArity id.idRange - |> Seq.map (fun e -> e.Value.DisplayName) - |> HashSet + let suggestTypes (addToBuffer: string -> unit) = + for e in modref.ModuleOrNamespaceType.TypesByDemangledNameAndArity id.idRange do + addToBuffer e.Value.DisplayName raze (UndefinedName(depth, FSComp.SR.undefinedNameType, id, suggestTypes)) @@ -2948,18 +2891,16 @@ let rec ResolveTypeLongIdentPrim sink (ncenv: NameResolver) occurence first full //CheckForTypeLegitimacyAndMultipleGenericTypeAmbiguities tcref rest typeNameResInfo m success(ResolutionInfo.Empty, tcref) | [] -> - let suggestPossibleTypes() = - nenv.TyconsByDemangledNameAndArity fullyQualified - |> Seq.filter (fun kv -> IsEntityAccessible ncenv.amap m ad kv.Value) - |> Seq.collect (fun e -> - match occurence with - | ItemOccurence.UseInAttribute -> - [yield e.Value.DisplayName - yield e.Value.DemangledModuleOrNamespaceName - if e.Value.DisplayName.EndsWithOrdinal("Attribute") then - yield e.Value.DisplayName.Replace("Attribute", "")] - | _ -> [e.Value.DisplayName; e.Value.DemangledModuleOrNamespaceName]) - |> HashSet + let suggestPossibleTypes (addToBuffer: string -> unit) = + for kv in nenv.TyconsByDemangledNameAndArity fullyQualified do + if IsEntityAccessible ncenv.amap m ad kv.Value then + addToBuffer kv.Value.DisplayName + addToBuffer kv.Value.DemangledModuleOrNamespaceName + match occurence with + | ItemOccurence.UseInAttribute -> + if kv.Value.DisplayName.EndsWithOrdinal("Attribute") then + addToBuffer (kv.Value.DisplayName.Replace("Attribute", "")) + | _ -> () raze (UndefinedName(0, FSComp.SR.undefinedNameType, id, suggestPossibleTypes)) | id2 :: rest2 -> @@ -3084,9 +3025,9 @@ let SuggestOtherLabelsOfSameRecordType g (nenv: NameResolutionEnv) ty (id: Ident labelsOfPossibleRecord let SuggestLabelsOfRelatedRecords g (nenv: NameResolutionEnv) (id: Ident) (allFields: Ident list) = - let suggestLabels() = + let suggestLabels (addToBuffer: string -> unit) = let givenFields = allFields |> List.map (fun fld -> fld.idText) |> List.filter ((<>) id.idText) |> HashSet - let fullyQualfied = + let fullyQualified = if givenFields.Count = 0 then // return labels from all records let result = NameMap.domainL nenv.eFieldLabels |> HashSet @@ -3116,21 +3057,15 @@ let SuggestLabelsOfRelatedRecords g (nenv: NameResolutionEnv) (id: Ident) (allFi labelsOfPossibleRecords.ExceptWith givenFields labelsOfPossibleRecords - if fullyQualfied.Count > 0 then fullyQualfied else - - // check if the user forgot to use qualified access - nenv.eTyconsByDemangledNameAndArity - |> Seq.choose (fun e -> - let hasRequireQualifiedAccessAttribute = HasFSharpAttribute g g.attrib_RequireQualifiedAccessAttribute e.Value.Attribs - if not hasRequireQualifiedAccessAttribute then - None - else - if e.Value.IsRecordTycon && e.Value.AllFieldsArray |> Seq.exists (fun x -> x.Name = id.idText) then - Some e.Value - else - None) - |> Seq.map (fun t -> t.DisplayName + "." + id.idText) - |> HashSet + if fullyQualified.Count > 0 then + fullyQualified |> Seq.iter addToBuffer + else + // check if the user forgot to use qualified access + for e in nenv.eTyconsByDemangledNameAndArity do + let hasRequireQualifiedAccessAttribute = HasFSharpAttribute g g.attrib_RequireQualifiedAccessAttribute e.Value.Attribs + if hasRequireQualifiedAccessAttribute then + if e.Value.IsRecordTycon && e.Value.AllFieldsArray |> Seq.exists (fun x -> x.Name = id.idText) then + addToBuffer (e.Value.DisplayName + "." + id.idText) UndefinedName(0, FSComp.SR.undefinedNameRecordLabel, id, suggestLabels) @@ -3159,7 +3094,10 @@ let ResolveFieldPrim sink (ncenv: NameResolver) nenv ad ty (mp, id: Ident) allFi | _ -> if isRecdTy g ty then // record label doesn't belong to record type -> suggest other labels of same record - let suggestLabels() = SuggestOtherLabelsOfSameRecordType g nenv ty id allFields + let suggestLabels (addToBuffer: string -> unit) = + for label in SuggestOtherLabelsOfSameRecordType g nenv ty id allFields do + addToBuffer label + let typeName = NicePrint.minimalStringOfType nenv.eDisplayEnv ty let errorText = FSComp.SR.nrRecordDoesNotContainSuchLabel(typeName, id.idText) error(ErrorWithSuggestions(errorText, m, id.idText, suggestLabels)) diff --git a/src/fsharp/TypeChecker.fs b/src/fsharp/TypeChecker.fs index 824cb2ac205..ae464f15758 100644 --- a/src/fsharp/TypeChecker.fs +++ b/src/fsharp/TypeChecker.fs @@ -4544,23 +4544,17 @@ and TcTyparOrMeasurePar optKind cenv (env: TcEnv) newOk tpenv (Typar(id, _, _) a | Some res -> checkRes res | None -> if newOk = NoNewTypars then - let predictTypeParameters() = - let predictions1 = - env.eNameResEnv.eTypars - |> Seq.map (fun p -> "'" + p.Key) - - let predictions2 = - match tpenv with - | UnscopedTyparEnv elements -> - elements - |> Seq.map (fun p -> "'" + p.Key) - - [ yield! predictions1 - yield! predictions2 ] - |> HashSet - + let suggestTypeParameters (addToBuffer: string -> unit) = + for p in env.eNameResEnv.eTypars do + addToBuffer ("'" + p.Key) + + match tpenv with + | UnscopedTyparEnv elements -> + for p in elements do + addToBuffer ("'" + p.Key) + let reportedId = Ident("'" + id.idText, id.idRange) - error (UndefinedName(0, FSComp.SR.undefinedNameTypeParameter, reportedId, predictTypeParameters)) + error (UndefinedName(0, FSComp.SR.undefinedNameTypeParameter, reportedId, suggestTypeParameters)) // OK, this is an implicit declaration of a type parameter // The kind defaults to Type @@ -6548,10 +6542,9 @@ and FreshenObjExprAbstractSlot cenv (env: TcEnv) (implty: TType) virtNameAndArit tcref.MembersOfFSharpTyconByName |> Seq.exists (fun kv -> kv.Value |> List.exists (fun valRef -> valRef.DisplayName = bindName)) - let suggestVirtualMembers() = - virtNameAndArityPairs - |> List.map (fst >> fst) - |> HashSet + let suggestVirtualMembers (addToBuffer: string -> unit) = + for ((x,_),_) in virtNameAndArityPairs do + addToBuffer x if containsNonAbstractMemberWithSameName then errorR(ErrorWithSuggestions(FSComp.SR.tcMemberFoundIsNotAbstractOrVirtual(tcref.DisplayName, bindName), mBinding, bindName, suggestVirtualMembers)) diff --git a/src/fsharp/service/ServiceErrorResolutionHints.fs b/src/fsharp/service/ServiceErrorResolutionHints.fs index 37fbc2ea528..5059c2a8979 100644 --- a/src/fsharp/service/ServiceErrorResolutionHints.fs +++ b/src/fsharp/service/ServiceErrorResolutionHints.fs @@ -2,13 +2,13 @@ namespace FSharp.Compiler.SourceCodeServices -open System.Collections.Generic - open FSharp.Compiler.ErrorResolutionHints module ErrorResolutionHints = - let getSuggestedNames (namesToCheck: string[]) (unresolvedIdentifier: string) = - let res = FilterPredictions (fun () -> HashSet(namesToCheck)) unresolvedIdentifier |> List.map snd - match res with - | [] -> None - | _ -> Some res \ No newline at end of file + let getSuggestedNames (suggestionsF: FSharp.Compiler.ErrorLogger.Suggestions) (unresolvedIdentifier: string) = + let buffer = SuggestionBuffer(unresolvedIdentifier) + if buffer.Disabled then + Seq.empty + else + suggestionsF buffer.Add + buffer :> seq \ No newline at end of file diff --git a/src/fsharp/service/ServiceErrorResolutionHints.fsi b/src/fsharp/service/ServiceErrorResolutionHints.fsi index 3677ecd0426..77617a09165 100644 --- a/src/fsharp/service/ServiceErrorResolutionHints.fsi +++ b/src/fsharp/service/ServiceErrorResolutionHints.fsi @@ -6,4 +6,4 @@ namespace FSharp.Compiler.SourceCodeServices module ErrorResolutionHints = /// Given a set of names, uses and a string representing an unresolved identifier, /// returns a list of suggested names if there are any feasible candidates. - val getSuggestedNames: symbolUses: string[] -> unresolvedIdentifier: string -> string list option \ No newline at end of file + val getSuggestedNames: suggestionsF: ((string -> unit) -> unit) -> unresolvedIdentifier: string -> seq diff --git a/tests/FSharp.Compiler.UnitTests/EditDistance.fs b/tests/FSharp.Compiler.UnitTests/EditDistance.fs index fa1f9a4293e..ffcac4bc775 100644 --- a/tests/FSharp.Compiler.UnitTests/EditDistance.fs +++ b/tests/FSharp.Compiler.UnitTests/EditDistance.fs @@ -3,9 +3,7 @@ namespace FSharp.Compiler.UnitTests open System open System.Globalization -open System.Text open NUnit.Framework -open FSharp.Compiler [] module EditDistance = diff --git a/tests/FSharp.Compiler.UnitTests/FSharp.Compiler.UnitTests.fsproj b/tests/FSharp.Compiler.UnitTests/FSharp.Compiler.UnitTests.fsproj index b5b30d90671..27bf5b2d07b 100644 --- a/tests/FSharp.Compiler.UnitTests/FSharp.Compiler.UnitTests.fsproj +++ b/tests/FSharp.Compiler.UnitTests/FSharp.Compiler.UnitTests.fsproj @@ -17,6 +17,7 @@ + diff --git a/tests/FSharp.Compiler.UnitTests/SuggestionBuffer.fs b/tests/FSharp.Compiler.UnitTests/SuggestionBuffer.fs new file mode 100644 index 00000000000..0a0584088c9 --- /dev/null +++ b/tests/FSharp.Compiler.UnitTests/SuggestionBuffer.fs @@ -0,0 +1,103 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. +namespace FSharp.Compiler.UnitTests + +open NUnit.Framework + +[] +module SuggestionBuffer = + open FSharp.Compiler.ErrorResolutionHints + + [] + let NewBufferShouldBeEmpty() = + let buffer = SuggestionBuffer("abdef") + + Assert.IsFalse buffer.Disabled + Assert.IsEmpty buffer + + [] + let BufferShouldOnlyAcceptSimilarElements() = + let buffer = SuggestionBuffer("abcd") + buffer.Add("abce") + buffer.Add("somethingcompletelyunrelated") + + let results = Array.ofSeq buffer + + Assert.areEqual [| "abce" |] results + + [] + let SmallIdentifierShouldBeIgnored() = + let buffer = SuggestionBuffer("ab") + + Assert.IsTrue buffer.Disabled + + buffer.Add("abce") + buffer.Add("somethingcompletelyunrelated") + buffer.Add("abcg") + buffer.Add("abch") + buffer.Add("abcde") + buffer.Add("abci") + buffer.Add("abcj") + + let results = Array.ofSeq buffer + + Assert.IsTrue buffer.Disabled + Assert.areEqual [||] results + + [] + let BufferShouldOnlyTakeTop5Elements() = + let buffer = SuggestionBuffer("abcd") + buffer.Add("abce") + buffer.Add("somethingcompletelyunrelated") + buffer.Add("abcg") + buffer.Add("abch") + buffer.Add("abcde") + buffer.Add("abci") + buffer.Add("abcj") + + let results = Array.ofSeq buffer + + Assert.areEqual [| "abce"; "abcg"; "abch"; "abci"; "abcj"|] results + + [] + let BufferShouldUseEarlierElementsIfTheyHaveSameScore() = + let buffer = SuggestionBuffer("abcd") + buffer.Add("abce") + buffer.Add("abcf") + buffer.Add("abcg") + buffer.Add("abch") + buffer.Add("abci") + buffer.Add("abcj") + + let results = Array.ofSeq buffer + + Assert.areEqual [| "abce"; "abcf"; "abcg"; "abch"; "abci"|] results + + + [] + let BufferShouldDisableItselfIfItSeesTheOriginalIdentifier() = + let buffer = SuggestionBuffer("abcd") + buffer.Add("abce") + buffer.Add("abcf") + buffer.Add("abcg") + buffer.Add("abch") + + Assert.IsFalse buffer.Disabled + Assert.IsNotEmpty buffer + + buffer.Add("abcd") // original Ident + buffer.Add("abcj") + + Assert.IsTrue buffer.Disabled + Assert.IsEmpty buffer + + [] + let BufferShouldIgnoreSmallIdentifiers() = + let buffer = SuggestionBuffer("abd") + buffer.Add("abce") + buffer.Add("abc") + buffer.Add("ab") + buffer.Add("ad") + + let results = Array.ofSeq buffer + + Assert.areEqual [| "abc"; "abce" |] results \ No newline at end of file diff --git a/tests/fsharp/typecheck/sigs/neg01.bsl b/tests/fsharp/typecheck/sigs/neg01.bsl index e2a7c05659e..80b8b22daf3 100644 --- a/tests/fsharp/typecheck/sigs/neg01.bsl +++ b/tests/fsharp/typecheck/sigs/neg01.bsl @@ -3,10 +3,4 @@ neg01a.fsi(24,8,25,7): typecheck error FS0913: Types cannot contain nested type neg01a.fs(22,8,23,7): typecheck error FS0913: Types cannot contain nested type definitions -neg01b.fs(2,13,2,14): typecheck error FS0039: The value, constructor, namespace or type 'X' is not defined. Maybe you want one of the following: - - z - - A - - B +neg01b.fs(2,13,2,14): typecheck error FS0039: The value, constructor, namespace or type 'X' is not defined. diff --git a/tests/fsharp/typecheck/sigs/neg14.bsl b/tests/fsharp/typecheck/sigs/neg14.bsl index f10030ee529..28f4763d71c 100644 --- a/tests/fsharp/typecheck/sigs/neg14.bsl +++ b/tests/fsharp/typecheck/sigs/neg14.bsl @@ -1,10 +1,4 @@ neg14a.fs(9,6,9,33): typecheck error FS0343: The type 'missingInterfaceInSignature' implements 'System.IComparable' explicitly but provides no corresponding override for 'Object.Equals'. An implementation of 'Object.Equals' has been automatically provided, implemented via 'System.IComparable'. Consider implementing the override 'Object.Equals' explicitly -neg14b.fs(2,13,2,14): typecheck error FS0039: The value, constructor, namespace or type 'X' is not defined. Maybe you want one of the following: - - z - - A - - B +neg14b.fs(2,13,2,14): typecheck error FS0039: The value, constructor, namespace or type 'X' is not defined. diff --git a/tests/fsharp/typecheck/sigs/neg20.bsl b/tests/fsharp/typecheck/sigs/neg20.bsl index f9ec6909780..ae9600cdae6 100644 --- a/tests/fsharp/typecheck/sigs/neg20.bsl +++ b/tests/fsharp/typecheck/sigs/neg20.bsl @@ -407,14 +407,10 @@ neg20.fs(373,22,373,41): typecheck error FS1124: Multiple types exist called 'Ov neg20.fs(382,19,382,40): typecheck error FS1124: Multiple types exist called 'OverloadedClassName', taking different numbers of generic parameters. Provide a type instantiation to disambiguate the type resolution, e.g. 'OverloadedClassName<_>'. -neg20.fs(383,39,383,41): typecheck error FS0039: The field, constructor or member 'S2' is not defined. Maybe you want one of the following: - - S +neg20.fs(383,39,383,41): typecheck error FS0039: The field, constructor or member 'S2' is not defined. neg20.fs(428,19,428,38): typecheck error FS1133: No constructors are available for the type 'OverloadedClassName<'a,'b>' neg20.fs(430,22,430,41): typecheck error FS1133: No constructors are available for the type 'OverloadedClassName<'a,'b>' -neg20.fs(444,39,444,41): typecheck error FS0039: The field, constructor or member 'S2' is not defined. Maybe you want one of the following: - - S +neg20.fs(444,39,444,41): typecheck error FS0039: The field, constructor or member 'S2' is not defined. diff --git a/tests/fsharp/typecheck/sigs/neg32.bsl b/tests/fsharp/typecheck/sigs/neg32.bsl index 858ed7330c7..bb849961393 100644 --- a/tests/fsharp/typecheck/sigs/neg32.bsl +++ b/tests/fsharp/typecheck/sigs/neg32.bsl @@ -7,17 +7,11 @@ neg32.fs(39,17,39,19): typecheck error FS0039: The type parameter 'T is not defi neg32.fs(40,4,40,23): typecheck error FS0671: A property cannot have explicit type parameters. Consider using a method instead. -neg32.fs(40,21,40,23): typecheck error FS0039: The type parameter 'U is not defined. Maybe you want one of the following: +neg32.fs(40,21,40,23): typecheck error FS0039: The type parameter 'U is not defined. - 'T +neg32.fs(41,21,41,23): typecheck error FS0039: The type parameter 'U is not defined. -neg32.fs(41,21,41,23): typecheck error FS0039: The type parameter 'U is not defined. Maybe you want one of the following: - - 'T - -neg32.fs(41,27,41,29): typecheck error FS0039: The type parameter 'U is not defined. Maybe you want one of the following: - - 'T +neg32.fs(41,27,41,29): typecheck error FS0039: The type parameter 'U is not defined. neg32.fs(42,18,42,20): typecheck error FS0039: The type parameter 'U is not defined. @@ -27,17 +21,11 @@ neg32.fs(46,17,46,19): typecheck error FS0039: The type parameter 'T is not defi neg32.fs(47,4,47,23): typecheck error FS0671: A property cannot have explicit type parameters. Consider using a method instead. -neg32.fs(47,21,47,23): typecheck error FS0039: The type parameter 'U is not defined. Maybe you want one of the following: - - 'T - -neg32.fs(48,21,48,23): typecheck error FS0039: The type parameter 'U is not defined. Maybe you want one of the following: - - 'T +neg32.fs(47,21,47,23): typecheck error FS0039: The type parameter 'U is not defined. -neg32.fs(48,27,48,29): typecheck error FS0039: The type parameter 'U is not defined. Maybe you want one of the following: +neg32.fs(48,21,48,23): typecheck error FS0039: The type parameter 'U is not defined. - 'T +neg32.fs(48,27,48,29): typecheck error FS0039: The type parameter 'U is not defined. neg32.fs(49,18,49,20): typecheck error FS0039: The type parameter 'U is not defined. diff --git a/tests/fsharp/typecheck/sigs/neg59.bsl b/tests/fsharp/typecheck/sigs/neg59.bsl index 32b9363a076..c4f55d1e06b 100644 --- a/tests/fsharp/typecheck/sigs/neg59.bsl +++ b/tests/fsharp/typecheck/sigs/neg59.bsl @@ -3,9 +3,7 @@ neg59.fs(9,13,9,17): typecheck error FS3096: 'join' must be followed by a variab neg59.fs(9,39,9,40): typecheck error FS0039: The value or constructor 'j' is not defined. -neg59.fs(10,24,10,25): typecheck error FS0039: The value or constructor 'j' is not defined. Maybe you want one of the following: - - i +neg59.fs(10,24,10,25): typecheck error FS0039: The value or constructor 'j' is not defined. neg59.fs(15,13,15,22): typecheck error FS3096: 'groupJoin' must be followed by a variable name. Usage: groupJoin var in collection on (outerKey = innerKey) into group. Note that parentheses are required after 'on'. diff --git a/tests/fsharp/typecheck/sigs/neg86.vsbsl b/tests/fsharp/typecheck/sigs/neg86.vsbsl index 1e189ddee06..638377e4bf9 100644 --- a/tests/fsharp/typecheck/sigs/neg86.vsbsl +++ b/tests/fsharp/typecheck/sigs/neg86.vsbsl @@ -9,9 +9,7 @@ neg86.fsx(8,13,8,17): typecheck error FS3096: 'join' must be followed by a varia neg86.fsx(8,13,8,17): typecheck error FS3167: 'join' must be followed by 'in'. Usage: join var in collection on (outerKey = innerKey). Note that parentheses are required after 'on'. -neg86.fsx(9,21,9,22): typecheck error FS0039: The value or constructor 'e' is not defined. Maybe you want one of the following: - - c +neg86.fsx(9,21,9,22): typecheck error FS0039: The value or constructor 'e' is not defined. neg86.fsx(23,13,23,17): typecheck error FS3097: Incorrect syntax for 'join'. Usage: join var in collection on (outerKey = innerKey). Note that parentheses are required after 'on'. diff --git a/tests/fsharp/typecheck/sigs/neg96.bsl b/tests/fsharp/typecheck/sigs/neg96.bsl index f443fbe6e65..cd87ec8144c 100644 --- a/tests/fsharp/typecheck/sigs/neg96.bsl +++ b/tests/fsharp/typecheck/sigs/neg96.bsl @@ -1,10 +1,6 @@ neg96.fs(11,9,11,21): typecheck error FS1133: No constructors are available for the type 'StructRecord' -neg96.fs(18,10,18,11): typecheck error FS0039: The type 'X' is not defined. Maybe you want one of the following: +neg96.fs(18,10,18,11): typecheck error FS0039: The type 'X' is not defined. - T - -neg96.fs(18,10,18,11): typecheck error FS0039: The type 'X' is not defined. Maybe you want one of the following: - - T +neg96.fs(18,10,18,11): typecheck error FS0039: The type 'X' is not defined. diff --git a/tests/fsharp/typecheck/sigs/neg97.bsl b/tests/fsharp/typecheck/sigs/neg97.bsl index 1e882f98b69..1a4d97f87d9 100644 --- a/tests/fsharp/typecheck/sigs/neg97.bsl +++ b/tests/fsharp/typecheck/sigs/neg97.bsl @@ -23,6 +23,4 @@ neg97.fs(36,20,36,32): typecheck error FS0064: This construct causes code to be neg97.fs(36,12,36,14): typecheck error FS0663: This type parameter has been used in a way that constrains it to always be 'string' -neg97.fs(42,20,42,22): typecheck error FS0039: The type parameter 'T is not defined. Maybe you want one of the following: - - 'U +neg97.fs(42,20,42,22): typecheck error FS0039: The type parameter 'T is not defined. diff --git a/tests/fsharp/typecheck/sigs/neg97.vsbsl b/tests/fsharp/typecheck/sigs/neg97.vsbsl index 1e882f98b69..1a4d97f87d9 100644 --- a/tests/fsharp/typecheck/sigs/neg97.vsbsl +++ b/tests/fsharp/typecheck/sigs/neg97.vsbsl @@ -23,6 +23,4 @@ neg97.fs(36,20,36,32): typecheck error FS0064: This construct causes code to be neg97.fs(36,12,36,14): typecheck error FS0663: This type parameter has been used in a way that constrains it to always be 'string' -neg97.fs(42,20,42,22): typecheck error FS0039: The type parameter 'T is not defined. Maybe you want one of the following: - - 'U +neg97.fs(42,20,42,22): typecheck error FS0039: The type parameter 'T is not defined. diff --git a/vsintegration/src/FSharp.Editor/CodeFix/ReplaceWithSuggestion.fs b/vsintegration/src/FSharp.Editor/CodeFix/ReplaceWithSuggestion.fs index b3f80dc2327..969cfb55a16 100644 --- a/vsintegration/src/FSharp.Editor/CodeFix/ReplaceWithSuggestion.fs +++ b/vsintegration/src/FSharp.Editor/CodeFix/ReplaceWithSuggestion.fs @@ -46,26 +46,24 @@ type internal FSharpReplaceWithSuggestionCodeFixProvider let partialName = QuickParse.GetPartialLongNameEx(caretLine.ToString(), caretLinePos.Character - 1) let! declInfo = checkFileResults.GetDeclarationListInfo(Some parseFileResults, fcsCaretLineNumber, caretLine.ToString(), partialName, userOpName=userOpName) |> liftAsync - let namesToCheck = declInfo.Items |> Array.map (fun item -> item.Name) + let addNames (addToBuffer:string -> unit) = + for item in declInfo.Items do + addToBuffer item.Name - let suggestedNames = ErrorResolutionHints.getSuggestedNames namesToCheck unresolvedIdentifierText - match suggestedNames with - | None -> () - | Some suggestions -> - let diagnostics = - context.Diagnostics - |> Seq.filter (fun x -> fixableDiagnosticIds |> Set.contains x.Id) - |> Seq.toImmutableArray + let diagnostics = + context.Diagnostics + |> Seq.filter (fun x -> fixableDiagnosticIds |> Set.contains x.Id) + |> Seq.toImmutableArray - for suggestion in suggestions do - let replacement = Keywords.QuoteIdentifierIfNeeded suggestion - let codeFix = - CodeFixHelpers.createTextChangeCodeFix( - CompilerDiagnostics.getErrorMessage (ReplaceWithSuggestion suggestion), - context, - (fun () -> asyncMaybe.Return [| TextChange(context.Span, replacement) |])) + for suggestion in ErrorResolutionHints.getSuggestedNames addNames unresolvedIdentifierText do + let replacement = Keywords.QuoteIdentifierIfNeeded suggestion + let codeFix = + CodeFixHelpers.createTextChangeCodeFix( + CompilerDiagnostics.getErrorMessage (ReplaceWithSuggestion suggestion), + context, + (fun () -> asyncMaybe.Return [| TextChange(context.Span, replacement) |])) - context.RegisterCodeFix(codeFix, diagnostics) + context.RegisterCodeFix(codeFix, diagnostics) } |> Async.Ignore |> RoslynHelpers.StartAsyncUnitAsTask(context.CancellationToken)