diff --git a/src/FSharpVSPowerTools.Core/LanguageService.fs b/src/FSharpVSPowerTools.Core/LanguageService.fs index 867a41ac..d5014781 100644 --- a/src/FSharpVSPowerTools.Core/LanguageService.fs +++ b/src/FSharpVSPowerTools.Core/LanguageService.fs @@ -121,7 +121,7 @@ type WordSpan = Line = r.StartLine StartCol = r.StartColumn EndCol = r.EndColumn } - member x.ToRange() = x.Line, x.StartCol, x.Line, x.EndCol + member x.Range = lazy (x.Line, x.StartCol, x.Line, x.EndCol) [] type LexerBase() = diff --git a/src/FSharpVSPowerTools.Core/TypedAstUtils.fs b/src/FSharpVSPowerTools.Core/TypedAstUtils.fs index 5e1c296c..c817c554 100644 --- a/src/FSharpVSPowerTools.Core/TypedAstUtils.fs +++ b/src/FSharpVSPowerTools.Core/TypedAstUtils.fs @@ -299,28 +299,32 @@ module TypedAstPatterns = module UnusedDeclarations = open System.Collections.Generic - let symbolsComparer = - { new IEqualityComparer with - member __.Equals (x, y) = x.IsEffectivelySameAs y - member __.GetHashCode x = x.GetHashCode() } + let symbolUseComparer = + { new IEqualityComparer with + member __.Equals (x, y) = x.Symbol.IsEffectivelySameAs y.Symbol + member __.GetHashCode x = x.Symbol.GetHashCode() } let getSingleDeclarations (symbolsUses: SymbolUse[]): FSharpSymbol[] = - symbolsUses - |> Seq.groupBy (fun x -> x.SymbolUse.Symbol) - |> Seq.choose (fun (symbol, uses) -> - match symbol with - | UnionCase _ when isSymbolLocalForProject symbol -> Some symbol + let symbols = Dictionary(symbolUseComparer) + + for symbolUse in symbolsUses do + match symbols.TryGetValue symbolUse.SymbolUse with + | true, count -> symbols.[symbolUse.SymbolUse] <- count + 1 + | _ -> symbols.[symbolUse.SymbolUse] <- 1 + + symbols + |> Seq.choose (fun (KeyValue(symbolUse, count)) -> + match symbolUse.Symbol with + | UnionCase _ when isSymbolLocalForProject symbolUse.Symbol -> Some symbolUse.Symbol // Determining that a record, DU or module is used anywhere requires // inspecting all their enclosed entities (fields, cases and func / vals) // for usefulness, which is too expensive to do. Hence we never gray them out. | Entity ((Record | UnionType | Interface | FSharpModule), _, _ | Class) -> None - // FCS returns inconsistent results for override members; we're going to skip these symbols. + // FCS returns inconsistent results for override members; we're skipping these symbols. | MemberFunctionOrValue func when func.IsOverrideOrExplicitInterfaceImplementation -> None // Usage of DU case parameters does not give any meaningful feedback; we never gray them out. | Parameter -> None - | _ -> - match List.ofSeq uses with - | [symbolUse] when symbolUse.SymbolUse.IsFromDefinition && isSymbolLocalForProject symbol -> - Some symbol + | _ when count = 1 && symbolUse.IsFromDefinition && isSymbolLocalForProject symbolUse.Symbol -> + Some symbolUse.Symbol | _ -> None) |> Seq.toArray \ No newline at end of file diff --git a/src/FSharpVSPowerTools.Logic/ClassifierTypes.fs b/src/FSharpVSPowerTools.Logic/ClassifierTypes.fs index 6df338c3..0dc50739 100644 --- a/src/FSharpVSPowerTools.Logic/ClassifierTypes.fs +++ b/src/FSharpVSPowerTools.Logic/ClassifierTypes.fs @@ -17,7 +17,7 @@ type internal CategorizedSnapshotSpan (columnSpan: CategorizedColumnSpan oldSpan |> Option.orTry (fun _ -> - fromRange originalSnapshot (columnSpan.WordSpan.ToRange()) + fromRange originalSnapshot columnSpan.WordSpan.Range.Value |> Option.map (fun span -> { Span = span Line = span.Start.GetContainingLine().LineNumber })) diff --git a/src/FSharpVSPowerTools.Logic/UnusedSymbolClassifier.fs b/src/FSharpVSPowerTools.Logic/UnusedSymbolClassifier.fs index 928aef95..7bf814cc 100644 --- a/src/FSharpVSPowerTools.Logic/UnusedSymbolClassifier.fs +++ b/src/FSharpVSPowerTools.Logic/UnusedSymbolClassifier.fs @@ -114,17 +114,17 @@ type UnusedSymbolClassifier let! openDeclarations = OpenDeclarationGetter.getOpenDeclarations ast entities qualifyOpenDeclarations return - (entities - |> Option.map (fun entities -> - entities - |> Seq.groupBy (fun e -> e.FullName) - |> Seq.map (fun (fullName, entities) -> - fullName, - entities - |> Seq.map (fun e -> e.CleanedIdents) - |> Seq.toList) - |> Dict.ofSeq), - openDeclarations) + entities + |> Option.map (fun entities -> + entities + |> Seq.groupBy (fun e -> e.FullName) + |> Seq.map (fun (fullName, entities) -> + fullName, + entities + |> Seq.map (fun e -> e.CleanedIdents) + |> Seq.toList) + |> Dict.ofSeq), + openDeclarations } let checkAstIsNotEmpty (ast: ParsedInput) = @@ -265,7 +265,7 @@ type UnusedSymbolClassifier match projects |> List.tryFind (fun p -> not p.Checked) with | Some { Options = opts } -> - // there is at least one yet unchecked project, start compilation on it + // there is at least one yet unchecked project, start compiling it vsLanguageService.CheckProjectInBackground opts | None -> // all the needed projects have been checked in background, let's calculate unused symbols @@ -300,7 +300,7 @@ type UnusedSymbolClassifier |> Option.map (fun data -> data.Spans.Spans |> Array.choose (fun wordSpan -> - fromRange data.Snapshot (wordSpan.ColumnSpan.WordSpan.ToRange()) + fromRange data.Snapshot wordSpan.ColumnSpan.WordSpan.Range.Value |> Option.map (fun span -> TagSpan(span, UnusedDeclarationTag()) :> ITagSpan<_>))) |> Option.getOrElse [||]