Skip to content

Commit

Permalink
Use MaxBuffer for Suggestions
Browse files Browse the repository at this point in the history
  • Loading branch information
forki committed Jun 26, 2019
1 parent 108428b commit 534d44a
Show file tree
Hide file tree
Showing 12 changed files with 257 additions and 297 deletions.
15 changes: 8 additions & 7 deletions src/fsharp/CompileOps.fs
Original file line number Diff line number Diff line change
Expand Up @@ -826,10 +826,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

let buffer = ErrorResolutionHints.MaxBuffer(ErrorResolutionHints.maxSuggestions)
suggestionsF (ErrorResolutionHints.FilterSuggestions buffer id.idText)
if not buffer.IsEmpty then
os.Append(ErrorResolutionHints.FormatSuggestions DecompileOpName id.idText buffer) |> ignore

| InternalUndefinedItemRef(f, smr, ccuName, s) ->
let _, errs = f(smr, ccuName, s)
Expand Down Expand Up @@ -1367,9 +1367,10 @@ 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
let buffer = ErrorResolutionHints.MaxBuffer(ErrorResolutionHints.maxSuggestions)
suggestionF (ErrorResolutionHints.FilterSuggestions buffer idText)
if not buffer.IsEmpty then
os.Append(ErrorResolutionHints.FormatSuggestions DecompileOpName idText buffer) |> ignore

| NumberedError ((_, s), _) -> os.Append s |> ignore

Expand Down
9 changes: 4 additions & 5 deletions src/fsharp/ConstraintSolver.fs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions src/fsharp/ErrorLogger.fs
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ let rec findOriginalException err =
| WrappedError(err, _) -> findOriginalException err
| _ -> err

type Suggestions = unit -> Collections.Generic.HashSet<string>
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
Expand Down
113 changes: 70 additions & 43 deletions src/fsharp/ErrorResolutionHints.fs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,39 @@ let minThresholdForSuggestions = 0.7
let highConfidenceThreshold = 0.85
let minStringLengthForThreshold = 3

type MaxBuffer<'K,'V when 'K : comparison and 'V : equality>(maxElements:int) =
let data = Array.zeroCreate<System.Collections.Generic.KeyValuePair<'K,'V>>(maxElements)
let mutable elements = 0
let rec backup i j =
if i < j then
data.[i] <- data.[i + 1]
backup (i + 1) j

let isSmaller i k =
if i >= elements then false else
let kvpr : byref<_> = &data.[i]
kvpr.Key < k

member __.Insert (k,v) =
let mutable pos = 0
while pos < maxElements && isSmaller pos k do
pos <- pos + 1

if pos < maxElements then
for i = maxElements-1 downto (pos+1) do
data.[i] <- data.[i-1]
data.[pos] <- System.Collections.Generic.KeyValuePair(k,v)

elements <- elements + 1

member __.IsEmpty = elements = 0
member __.Values() : 'V [] =
[| let hashSet = System.Collections.Generic.HashSet<'V>()
let bound = min (maxElements-1) (elements-1)
for i in 0..bound do
let x = data.[i].Value
if hashSet.Add x then yield x |]

/// We report a candidate if its edit distance is <= the threshold.
/// The threshold is set to about a quarter of the number of characters.
let IsInEditDistanceProximity idText suggestion =
Expand All @@ -23,60 +56,54 @@ let IsInEditDistanceProximity idText suggestion =

editDistance <= threshold

/// Filters predictions based on edit distance to the given unknown identifier.
let FilterPredictions (suggestionF:ErrorLogger.Suggestions) (idText:string) =
let uppercaseText = idText.ToUpperInvariant()
let allSuggestions = suggestionF()
/// Demangles a suggestion
let DemangleSuggestion (nm:string) =
if nm.StartsWithOrdinal("( ") && nm.EndsWithOrdinal(" )") then
let cleanName = nm.[2..nm.Length - 3]
cleanName
else nm

let demangle (nm:string) =
if nm.StartsWithOrdinal("( ") && nm.EndsWithOrdinal(" )") then
let cleanName = nm.[2..nm.Length - 3]
cleanName
else nm
/// Returns `true` if given string is an operator display name, e.g. ( |>> )
let IsOperatorName (name: string) =
if not (name.StartsWithOrdinal("( ") && name.EndsWithOrdinal(" )")) then
false
else
let name = name.[2..name.Length - 3]
name |> Seq.forall (fun c -> c <> ' ')

/// Returns `true` if given string is an operator display name, e.g. ( |>> )
let IsOperatorName (name: string) =
if not (name.StartsWithOrdinal("( ") && name.EndsWithOrdinal(" )")) then
false
else
let name = name.[2..name.Length - 3]
name |> Seq.forall (fun c -> c <> ' ')
/// Filters suggestionss based on edit distance to the given unknown identifier.
let FilterSuggestions (buffer: MaxBuffer<_,string>) (idText:string) =
let uppercaseText = idText.ToUpperInvariant()

if allSuggestions.Contains idText then [] else // some other parsing error occurred
let dotIdText = "." + idText
allSuggestions
|> Seq.choose (fun suggestion ->

fun (suggestion:string) ->
// 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)
if IsOperatorName suggestion || suggestion.StartsWithOrdinal("_") then
()
else
None)
|> Seq.sortByDescending fst
|> Seq.mapi (fun i x -> i, x)
|> Seq.takeWhile (fun (i, _) -> i < maxSuggestions)
|> Seq.map snd
|> Seq.toList
let suggestion:string = DemangleSuggestion suggestion
let suggestedText = suggestion.ToUpperInvariant()
let similarity = EditDistance.JaroWinklerDistance uppercaseText suggestedText
if similarity >= highConfidenceThreshold || suggestion.EndsWithOrdinal(dotIdText) then
buffer.Insert(similarity, suggestion) |> ignore
elif similarity < minThresholdForSuggestions && suggestedText.Length > minStringLengthForThreshold then
()
elif IsInEditDistanceProximity uppercaseText suggestedText then
buffer.Insert(similarity, suggestion) |> ignore

/// Formats the given predictions according to the error style.
let FormatPredictions normalizeF (predictions: (float * string) list) =
match predictions with
| [] -> System.String.Empty
| _ ->
/// Formats the given suggestions according to the error style.
let FormatSuggestions normalizeF (idText:string) (buffer: MaxBuffer<_,string>) =
let values = buffer.Values()
if Array.exists ((=) idText) values then
System.String.Empty
else
let suggestions =
predictions
|> List.map (snd >> normalizeF)
|> List.map (sprintf "%s %s" System.Environment.NewLine)
values
|> Seq.map (fun p -> sprintf "%s %s" System.Environment.NewLine (normalizeF p))
|> String.concat ""

" " + FSComp.SR.undefinedNameSuggestionsIntro() + suggestions
Loading

0 comments on commit 534d44a

Please sign in to comment.