未验证 提交 f087d4f8 编写于 作者: K Kevin Ransom (msft) 提交者: GitHub

Merge pull request #3803 from vasily-kirichenko/typed-open-declarations-2

Typed Unused Opens analyzer
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>netcoreapp1.0</TargetFramework> <TargetFramework>netcoreapp1.0</TargetFramework>
<DefineConstants>$(DefineConstants);DOTNETCORE;FX_ATLEAST_45;FX_ATLEAST_PORTABLE;FX_NO_RUNTIMEENVIRONMENT;FX_RESHAPED_REFLECTION;TODO_REWORK_ASSEMBLY_LOAD;</DefineConstants> <DefineConstants>$(DefineConstants);DOTNETCORE;FX_ATLEAST_45;FX_ATLEAST_PORTABLE;FX_NO_RUNTIMEENVIRONMENT;FX_RESHAPED_REFLECTION;TODO_REWORK_ASSEMBLY_LOAD;</DefineConstants>
...@@ -42,6 +42,7 @@ ...@@ -42,6 +42,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0" />
<PackageReference Include="NUnit3TestAdapter" Version="3.8.0" />
<PackageReference Include="NUnitLite" Version="3.6.1" /> <PackageReference Include="NUnitLite" Version="3.6.1" />
<PackageReference Include="NUnit" Version="3.6.1" /> <PackageReference Include="NUnit" Version="3.6.1" />
<PackageReference Include="FSharp.Core" Version="4.1.*" PrivateAssets="All" /> <PackageReference Include="FSharp.Core" Version="4.1.*" PrivateAssets="All" />
......
...@@ -536,6 +536,12 @@ ...@@ -536,6 +536,12 @@
<Compile Include="$(FSharpSourcesRoot)\fsharp\symbols\Exprs.fs"> <Compile Include="$(FSharpSourcesRoot)\fsharp\symbols\Exprs.fs">
<Link>Symbols/Exprs.fs</Link> <Link>Symbols/Exprs.fs</Link>
</Compile> </Compile>
<Compile Include="$(FSharpSourcesRoot)\fsharp\symbols\SymbolPatterns.fsi">
<Link>Symbols/SymbolPatterns.fsi</Link>
</Compile>
<Compile Include="$(FSharpSourcesRoot)\fsharp\symbols\SymbolPatterns.fs">
<Link>Symbols/SymbolPatterns.fs</Link>
</Compile>
<Compile Include="$(FSharpSourcesRoot)\fsharp\vs\IncrementalBuild.fsi"> <Compile Include="$(FSharpSourcesRoot)\fsharp\vs\IncrementalBuild.fsi">
<Link>Service/IncrementalBuild.fsi</Link> <Link>Service/IncrementalBuild.fsi</Link>
</Compile> </Compile>
...@@ -632,6 +638,12 @@ ...@@ -632,6 +638,12 @@
<Compile Include="$(FSharpSourcesRoot)\fsharp\vs\ServiceStructure.fs"> <Compile Include="$(FSharpSourcesRoot)\fsharp\vs\ServiceStructure.fs">
<Link>Service/ServiceStructure.fs</Link> <Link>Service/ServiceStructure.fs</Link>
</Compile> </Compile>
<Compile Include="$(FSharpSourcesRoot)\fsharp\vs\ServiceAnalysis.fsi">
<Link>Service/ServiceAnalysis.fsi</Link>
</Compile>
<Compile Include="$(FSharpSourcesRoot)\fsharp\vs\ServiceAnalysis.fs">
<Link>Service/ServiceAnalysis.fs</Link>
</Compile>
<Compile Include="$(FSharpSourcesRoot)\fsharp\fsi\fsi.fsi"> <Compile Include="$(FSharpSourcesRoot)\fsharp\fsi\fsi.fsi">
<Link>Service/fsi.fsi</Link> <Link>Service/fsi.fsi</Link>
</Compile> </Compile>
......
...@@ -512,6 +512,12 @@ ...@@ -512,6 +512,12 @@
<Compile Include="$(FSharpSourcesRoot)\fsharp\symbols\Exprs.fs"> <Compile Include="$(FSharpSourcesRoot)\fsharp\symbols\Exprs.fs">
<Link>Symbols/Exprs.fs</Link> <Link>Symbols/Exprs.fs</Link>
</Compile> </Compile>
<Compile Include="$(FSharpSourcesRoot)\fsharp\symbols\SymbolPatterns.fsi">
<Link>Symbols/SymbolPatterns.fsi</Link>
</Compile>
<Compile Include="$(FSharpSourcesRoot)\fsharp\symbols\SymbolPatterns.fs">
<Link>Symbols/SymbolPatterns.fs</Link>
</Compile>
<Compile Include="$(FSharpSourcesRoot)\fsharp\vs\IncrementalBuild.fsi"> <Compile Include="$(FSharpSourcesRoot)\fsharp\vs\IncrementalBuild.fsi">
<Link>Service/IncrementalBuild.fsi</Link> <Link>Service/IncrementalBuild.fsi</Link>
</Compile> </Compile>
...@@ -608,6 +614,12 @@ ...@@ -608,6 +614,12 @@
<Compile Include="$(FSharpSourcesRoot)\fsharp\vs\ServiceStructure.fs"> <Compile Include="$(FSharpSourcesRoot)\fsharp\vs\ServiceStructure.fs">
<Link>Service/ServiceStructure.fs</Link> <Link>Service/ServiceStructure.fs</Link>
</Compile> </Compile>
<Compile Include="$(FSharpSourcesRoot)\fsharp\vs\ServiceAnalysis.fsi">
<Link>Service/ServiceAnalysis.fsi</Link>
</Compile>
<Compile Include="$(FSharpSourcesRoot)\fsharp\vs\ServiceAnalysis.fs">
<Link>Service/ServiceAnalysis.fs</Link>
</Compile>
<Compile Include="$(FSharpSourcesRoot)\fsharp\fsi\fsi.fsi"> <Compile Include="$(FSharpSourcesRoot)\fsharp\fsi\fsi.fsi">
<Link>Service/fsi.fsi</Link> <Link>Service/fsi.fsi</Link>
</Compile> </Compile>
......
...@@ -492,6 +492,12 @@ ...@@ -492,6 +492,12 @@
<Compile Include="$(FSharpSourcesRoot)\fsharp\symbols\Exprs.fs"> <Compile Include="$(FSharpSourcesRoot)\fsharp\symbols\Exprs.fs">
<Link>Symbols/Exprs.fs</Link> <Link>Symbols/Exprs.fs</Link>
</Compile> </Compile>
<Compile Include="$(FSharpSourcesRoot)\fsharp\symbols\SymbolPatterns.fsi">
<Link>Symbols/SymbolPatterns.fsi</Link>
</Compile>
<Compile Include="$(FSharpSourcesRoot)\fsharp\symbols\SymbolPatterns.fs">
<Link>Symbols/SymbolPatterns.fs</Link>
</Compile>
<!-- the incremental builder and service . --> <!-- the incremental builder and service . -->
<Compile Include="$(FSharpSourcesRoot)\fsharp\vs\IncrementalBuild.fsi"> <Compile Include="$(FSharpSourcesRoot)\fsharp\vs\IncrementalBuild.fsi">
...@@ -590,6 +596,12 @@ ...@@ -590,6 +596,12 @@
<Compile Include="$(FSharpSourcesRoot)\fsharp\vs\ServiceStructure.fs"> <Compile Include="$(FSharpSourcesRoot)\fsharp\vs\ServiceStructure.fs">
<Link>Service/ServiceStructure.fs</Link> <Link>Service/ServiceStructure.fs</Link>
</Compile> </Compile>
<Compile Include="$(FSharpSourcesRoot)\fsharp\vs\ServiceAnalysis.fsi">
<Link>Service/ServiceAnalysis.fsi</Link>
</Compile>
<Compile Include="$(FSharpSourcesRoot)\fsharp\vs\ServiceAnalysis.fs">
<Link>Service/ServiceAnalysis.fs</Link>
</Compile>
<!-- the core of the F# Interactive fsi.exe implementation --> <!-- the core of the F# Interactive fsi.exe implementation -->
<FsSrGen Include="$(FSharpSourcesRoot)\fsharp\fsi\FSIstrings.txt"> <FsSrGen Include="$(FSharpSourcesRoot)\fsharp\fsi\FSIstrings.txt">
......
...@@ -546,6 +546,12 @@ ...@@ -546,6 +546,12 @@
<Compile Include="..\symbols\Exprs.fs"> <Compile Include="..\symbols\Exprs.fs">
<Link>Symbols/Exprs.fs</Link> <Link>Symbols/Exprs.fs</Link>
</Compile> </Compile>
<Compile Include="..\symbols\SymbolPatterns.fsi">
<Link>Symbols/SymbolPatterns.fsi</Link>
</Compile>
<Compile Include="..\symbols\SymbolPatterns.fs">
<Link>Symbols/SymbolPatterns.fs</Link>
</Compile>
<!-- the incremental builder and service . --> <!-- the incremental builder and service . -->
<Compile Include="..\vs\IncrementalBuild.fsi"> <Compile Include="..\vs\IncrementalBuild.fsi">
...@@ -644,7 +650,13 @@ ...@@ -644,7 +650,13 @@
<Compile Include="..\vs\ServiceStructure.fs"> <Compile Include="..\vs\ServiceStructure.fs">
<Link>Service/ServiceStructure.fs</Link> <Link>Service/ServiceStructure.fs</Link>
</Compile> </Compile>
<Compile Include="..\vs\ServiceAnalysis.fsi">
<Link>Service/ServiceAnalysis.fsi</Link>
</Compile>
<Compile Include="..\vs\ServiceAnalysis.fs">
<Link>Service/ServiceAnalysis.fs</Link>
</Compile>
<!-- the core of the F# Interactive fsi.exe implementation --> <!-- the core of the F# Interactive fsi.exe implementation -->
<EmbeddedText Include="..\fsi\FSIstrings.txt"> <EmbeddedText Include="..\fsi\FSIstrings.txt">
<Link>FSIstrings.txt</Link> <Link>FSIstrings.txt</Link>
......
...@@ -1222,12 +1222,32 @@ type ItemOccurence = ...@@ -1222,12 +1222,32 @@ type ItemOccurence =
/// Result gets suppressed over this text range /// Result gets suppressed over this text range
| RelatedText | RelatedText
type OpenDeclaration =
{ LongId: Ident list
Range: range option
Modules: ModuleOrNamespaceRef list
AppliedScope: range
IsOwnNamespace: bool }
static member Create(longId: Ident list, modules: ModuleOrNamespaceRef list, appliedScope: range, isOwnNamespace: bool) =
{ LongId = longId
Range =
match longId with
| [] -> None
| first :: rest ->
let last = rest |> List.tryLast |> Option.defaultValue first
Some (mkRange appliedScope.FileName first.idRange.Start last.idRange.End)
Modules = modules
AppliedScope = appliedScope
IsOwnNamespace = isOwnNamespace }
/// An abstract type for reporting the results of name resolution and type checking. /// An abstract type for reporting the results of name resolution and type checking.
type ITypecheckResultsSink = type ITypecheckResultsSink =
abstract NotifyEnvWithScope : range * NameResolutionEnv * AccessorDomain -> unit abstract NotifyEnvWithScope : range * NameResolutionEnv * AccessorDomain -> unit
abstract NotifyExprHasType : pos * TType * Tastops.DisplayEnv * NameResolutionEnv * AccessorDomain * range -> unit abstract NotifyExprHasType : pos * TType * Tastops.DisplayEnv * NameResolutionEnv * AccessorDomain * range -> unit
abstract NotifyNameResolution : pos * Item * Item * TyparInst * ItemOccurence * Tastops.DisplayEnv * NameResolutionEnv * AccessorDomain * range * bool -> unit abstract NotifyNameResolution : pos * Item * Item * TyparInst * ItemOccurence * Tastops.DisplayEnv * NameResolutionEnv * AccessorDomain * range * bool -> unit
abstract NotifyFormatSpecifierLocation : range * int -> unit abstract NotifyFormatSpecifierLocation : range * int -> unit
abstract NotifyOpenDeclaration : OpenDeclaration -> unit
abstract CurrentSource : string option abstract CurrentSource : string option
let (|ValRefOfProp|_|) (pi : PropInfo) = pi.ArbitraryValRef let (|ValRefOfProp|_|) (pi : PropInfo) = pi.ArbitraryValRef
...@@ -1460,6 +1480,7 @@ type TcResultsSinkImpl(g, ?source: string) = ...@@ -1460,6 +1480,7 @@ type TcResultsSinkImpl(g, ?source: string) =
member __.GetHashCode((p:pos,i)) = p.Line + 101 * p.Column + hash i member __.GetHashCode((p:pos,i)) = p.Line + 101 * p.Column + hash i
member __.Equals((p1,i1),(p2,i2)) = posEq p1 p2 && i1 = i2 } ) member __.Equals((p1,i1),(p2,i2)) = posEq p1 p2 && i1 = i2 } )
let capturedMethodGroupResolutions = ResizeArray<_>() let capturedMethodGroupResolutions = ResizeArray<_>()
let capturedOpenDeclarations = ResizeArray<_>()
let allowedRange (m:range) = not m.IsSynthetic let allowedRange (m:range) = not m.IsSynthetic
member this.GetResolutions() = member this.GetResolutions() =
...@@ -1468,6 +1489,8 @@ type TcResultsSinkImpl(g, ?source: string) = ...@@ -1468,6 +1489,8 @@ type TcResultsSinkImpl(g, ?source: string) =
member this.GetSymbolUses() = member this.GetSymbolUses() =
TcSymbolUses(g, capturedNameResolutions, capturedFormatSpecifierLocations.ToArray()) TcSymbolUses(g, capturedNameResolutions, capturedFormatSpecifierLocations.ToArray())
member this.OpenDeclarations = Seq.toList capturedOpenDeclarations
interface ITypecheckResultsSink with interface ITypecheckResultsSink with
member sink.NotifyEnvWithScope(m,nenv,ad) = member sink.NotifyEnvWithScope(m,nenv,ad) =
if allowedRange m then if allowedRange m then
...@@ -1504,6 +1527,9 @@ type TcResultsSinkImpl(g, ?source: string) = ...@@ -1504,6 +1527,9 @@ type TcResultsSinkImpl(g, ?source: string) =
member sink.NotifyFormatSpecifierLocation(m, numArgs) = member sink.NotifyFormatSpecifierLocation(m, numArgs) =
capturedFormatSpecifierLocations.Add((m, numArgs)) capturedFormatSpecifierLocations.Add((m, numArgs))
member sink.NotifyOpenDeclaration(openDeclaration) =
capturedOpenDeclarations.Add(openDeclaration)
member sink.CurrentSource = source member sink.CurrentSource = source
...@@ -1550,6 +1576,11 @@ let CallExprHasTypeSink (sink:TcResultsSink) (m:range,nenv,typ,denv,ad) = ...@@ -1550,6 +1576,11 @@ let CallExprHasTypeSink (sink:TcResultsSink) (m:range,nenv,typ,denv,ad) =
| None -> () | None -> ()
| Some sink -> sink.NotifyExprHasType(m.End,typ,denv,nenv,ad,m) | Some sink -> sink.NotifyExprHasType(m.End,typ,denv,nenv,ad,m)
let CallOpenDeclarationSink (sink:TcResultsSink) (openDeclaration: OpenDeclaration) =
match sink.CurrentSink with
| None -> ()
| Some sink -> sink.NotifyOpenDeclaration(openDeclaration)
//------------------------------------------------------------------------- //-------------------------------------------------------------------------
// Check inferability of type parameters in resolved items. // Check inferability of type parameters in resolved items.
//------------------------------------------------------------------------- //-------------------------------------------------------------------------
......
...@@ -22,6 +22,9 @@ type NameResolver = ...@@ -22,6 +22,9 @@ type NameResolver =
member amap : ImportMap member amap : ImportMap
member g : TcGlobals member g : TcGlobals
/// Get the active pattern elements defined in a module, if any. Cache in the slot in the module type.
val ActivePatternElemsOfModuleOrNamespace : ModuleOrNamespaceRef -> NameMap<ActivePatternElemRef>
[<NoEquality; NoComparison; RequireQualifiedAccess>] [<NoEquality; NoComparison; RequireQualifiedAccess>]
/// Represents the item with which a named argument is associated. /// Represents the item with which a named argument is associated.
type ArgumentContainer = type ArgumentContainer =
...@@ -307,7 +310,26 @@ type internal TcSymbolUses = ...@@ -307,7 +310,26 @@ type internal TcSymbolUses =
/// Get the locations of all the printf format specifiers in the file /// Get the locations of all the printf format specifiers in the file
member GetFormatSpecifierLocationsAndArity : unit -> (range * int)[] member GetFormatSpecifierLocationsAndArity : unit -> (range * int)[]
/// Represents open declaration statement.
type internal OpenDeclaration =
{ /// Long identifier as it's presented in soruce code.
LongId: Ident list
/// Full range of the open declaration.
Range : range option
/// Modules or namespaces which is opened with this declaration.
Modules: ModuleOrNamespaceRef list
/// Scope in which open declaration is visible.
AppliedScope: range
/// If it's `namespace Xxx.Yyy` declaration.
IsOwnNamespace: bool }
/// Create a new instance of OpenDeclaration.
static member Create : longId: Ident list * modules: ModuleOrNamespaceRef list * appliedScope: range * isOwnNamespace: bool -> OpenDeclaration
/// An abstract type for reporting the results of name resolution and type checking /// An abstract type for reporting the results of name resolution and type checking
type ITypecheckResultsSink = type ITypecheckResultsSink =
...@@ -323,6 +345,9 @@ type ITypecheckResultsSink = ...@@ -323,6 +345,9 @@ type ITypecheckResultsSink =
/// Record that a printf format specifier occurred at a specific location in the source /// Record that a printf format specifier occurred at a specific location in the source
abstract NotifyFormatSpecifierLocation : range * int -> unit abstract NotifyFormatSpecifierLocation : range * int -> unit
/// Record that an open declaration occured in a given scope range
abstract NotifyOpenDeclaration : OpenDeclaration -> unit
/// Get the current source /// Get the current source
abstract CurrentSource : string option abstract CurrentSource : string option
...@@ -337,6 +362,10 @@ type internal TcResultsSinkImpl = ...@@ -337,6 +362,10 @@ type internal TcResultsSinkImpl =
/// Get all the uses of all symbols reported to the sink /// Get all the uses of all symbols reported to the sink
member GetSymbolUses : unit -> TcSymbolUses member GetSymbolUses : unit -> TcSymbolUses
/// Get all open declarations reported to the sink
member OpenDeclarations : OpenDeclaration list
interface ITypecheckResultsSink interface ITypecheckResultsSink
/// An abstract type for reporting the results of name resolution and type checking, and which allows /// An abstract type for reporting the results of name resolution and type checking, and which allows
...@@ -364,6 +393,9 @@ val internal CallNameResolutionSinkReplacing : TcResultsSink -> range * Name ...@@ -364,6 +393,9 @@ val internal CallNameResolutionSinkReplacing : TcResultsSink -> range * Name
/// Report a specific name resolution at a source range /// Report a specific name resolution at a source range
val internal CallExprHasTypeSink : TcResultsSink -> range * NameResolutionEnv * TType * DisplayEnv * AccessorDomain -> unit val internal CallExprHasTypeSink : TcResultsSink -> range * NameResolutionEnv * TType * DisplayEnv * AccessorDomain -> unit
/// Report an open declaration
val internal CallOpenDeclarationSink : TcResultsSink -> OpenDeclaration -> unit
/// Get all the available properties of a type (both intrinsic and extension) /// Get all the available properties of a type (both intrinsic and extension)
val internal AllPropInfosOfTypeInScope : InfoReader -> NameResolutionEnv -> string option * AccessorDomain -> FindMemberFlag -> range -> TType -> PropInfo list val internal AllPropInfosOfTypeInScope : InfoReader -> NameResolutionEnv -> string option * AccessorDomain -> FindMemberFlag -> range -> TType -> PropInfo list
......
...@@ -441,11 +441,18 @@ let AddLocalTyconsAndReport tcSink scopem g amap m tycons env = ...@@ -441,11 +441,18 @@ let AddLocalTyconsAndReport tcSink scopem g amap m tycons env =
// Open a structure or an IL namespace // Open a structure or an IL namespace
//------------------------------------------------------------------------- //-------------------------------------------------------------------------
let OpenModulesOrNamespaces tcSink g amap scopem root env mvvs = let OpenModulesOrNamespaces tcSink g amap scopem root env mvvs openDeclaration =
let env = let env =
if isNil mvvs then env else if isNil mvvs then env else
ModifyNameResEnv (fun nenv -> AddModulesAndNamespacesContentsToNameEnv g amap env.eAccessRights scopem root nenv mvvs) env ModifyNameResEnv (fun nenv -> AddModulesAndNamespacesContentsToNameEnv g amap env.eAccessRights scopem root nenv mvvs) env
CallEnvSink tcSink (scopem, env.NameEnv, env.eAccessRights) CallEnvSink tcSink (scopem, env.NameEnv, env.eAccessRights)
CallOpenDeclarationSink tcSink openDeclaration
match openDeclaration.Range with
| None -> ()
| Some range ->
for modul in mvvs do
let item = Item.ModuleOrNamespaces [modul]
CallNameResolutionSink tcSink (range, env.NameEnv, item, item, emptyTyparInst, ItemOccurence.Use, env.DisplayEnv, env.eAccessRights)
env env
let AddRootModuleOrNamespaceRefs g amap m env modrefs = let AddRootModuleOrNamespaceRefs g amap m env modrefs =
...@@ -691,7 +698,10 @@ let ImplicitlyOpenOwnNamespace tcSink g amap scopem enclosingNamespacePath env = ...@@ -691,7 +698,10 @@ let ImplicitlyOpenOwnNamespace tcSink g amap scopem enclosingNamespacePath env =
let ad = env.eAccessRights let ad = env.eAccessRights
match ResolveLongIndentAsModuleOrNamespace ResultCollectionSettings.AllResults amap scopem OpenQualified env.eNameResEnv ad enclosingNamespacePathToOpen with match ResolveLongIndentAsModuleOrNamespace ResultCollectionSettings.AllResults amap scopem OpenQualified env.eNameResEnv ad enclosingNamespacePathToOpen with
| Result modrefs -> OpenModulesOrNamespaces tcSink g amap scopem false env (List.map p23 modrefs) | Result modrefs ->
let modrefs = List.map p23 modrefs
let openDecl = OpenDeclaration.Create (enclosingNamespacePathToOpen, modrefs, scopem, true)
OpenModulesOrNamespaces tcSink g amap scopem false env modrefs openDecl
| Exception _ -> env | Exception _ -> env
...@@ -1867,6 +1877,7 @@ let MakeAndPublishSimpleVals cenv env m names mergeNamesInOneNameresEnv = ...@@ -1867,6 +1877,7 @@ let MakeAndPublishSimpleVals cenv env m names mergeNamesInOneNameresEnv =
nameResolutions.Add(pos, item, itemGroup, itemTyparInst, occurence, denv, nenv, ad, m, replacing) nameResolutions.Add(pos, item, itemGroup, itemTyparInst, occurence, denv, nenv, ad, m, replacing)
member this.NotifyExprHasType(_, _, _, _, _, _) = assert false // no expr typings in MakeSimpleVals member this.NotifyExprHasType(_, _, _, _, _, _) = assert false // no expr typings in MakeSimpleVals
member this.NotifyFormatSpecifierLocation(_, _) = () member this.NotifyFormatSpecifierLocation(_, _) = ()
member this.NotifyOpenDeclaration(_) = ()
member this.CurrentSource = None } member this.CurrentSource = None }
use _h = WithNewTypecheckResultsSink(sink, cenv.tcSink) use _h = WithNewTypecheckResultsSink(sink, cenv.tcSink)
...@@ -12099,9 +12110,11 @@ let TcOpenDecl tcSink (g:TcGlobals) amap m scopem env (longId : Ident list) = ...@@ -12099,9 +12110,11 @@ let TcOpenDecl tcSink (g:TcGlobals) amap m scopem env (longId : Ident list) =
if IsPartiallyQualifiedNamespace modref then if IsPartiallyQualifiedNamespace modref then
errorR(Error(FSComp.SR.tcOpenUsedWithPartiallyQualifiedPath(fullDisplayTextOfModRef modref), m))) errorR(Error(FSComp.SR.tcOpenUsedWithPartiallyQualifiedPath(fullDisplayTextOfModRef modref), m)))
modrefs |> List.iter (fun (_, modref, _) -> CheckEntityAttributes g modref m |> CommitOperationResult) let modrefs = List.map p23 modrefs
modrefs |> List.iter (fun modref -> CheckEntityAttributes g modref m |> CommitOperationResult)
let env = OpenModulesOrNamespaces tcSink g amap scopem false env (List.map p23 modrefs) let openDecl = OpenDeclaration.Create (longId, modrefs, scopem, false)
let env = OpenModulesOrNamespaces tcSink g amap scopem false env modrefs openDecl
env env
...@@ -16866,7 +16879,9 @@ let ApplyAssemblyLevelAutoOpenAttributeToTcEnv g amap (ccu: CcuThunk) scopem env ...@@ -16866,7 +16879,9 @@ let ApplyAssemblyLevelAutoOpenAttributeToTcEnv g amap (ccu: CcuThunk) scopem env
let modref = mkNonLocalTyconRef (mkNonLocalEntityRef ccu (Array.ofList h)) t let modref = mkNonLocalTyconRef (mkNonLocalEntityRef ccu (Array.ofList h)) t
match modref.TryDeref with match modref.TryDeref with
| VNone -> warn() | VNone -> warn()
| VSome _ -> OpenModulesOrNamespaces TcResultsSink.NoSink g amap scopem root env [modref] | VSome _ ->
let openDecl = OpenDeclaration.Create ([], [modref], scopem, false)
OpenModulesOrNamespaces TcResultsSink.NoSink g amap scopem root env [modref] openDecl
// Add the CCU and apply the "AutoOpen" attributes // Add the CCU and apply the "AutoOpen" attributes
let AddCcuToTcEnv(g, amap, scopem, env, assemblyName, ccu, autoOpens, internalsVisible) = let AddCcuToTcEnv(g, amap, scopem, env, assemblyName, ccu, autoOpens, internalsVisible) =
......
module internal Microsoft.VisualStudio.FSharp.Editor.TypedAstUtils // Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.
open System namespace Microsoft.FSharp.Compiler.SourceCodeServices
open Microsoft.FSharp.Compiler.SourceCodeServices
open Microsoft.VisualStudio.FSharp.Editor /// Patterns over FSharpSymbol and derivatives.
[<RequireQualifiedAccess>]
open System.Text.RegularExpressions module Symbol =
open System.Text.RegularExpressions
let isAttribute<'T> (attribute: FSharpAttribute) = open System
// CompiledName throws exception on DataContractAttribute generated by SQLProvider
match Option.attempt (fun _ -> attribute.AttributeType.CompiledName) with let isAttribute<'T> (attribute: FSharpAttribute) =
| Some name when name = typeof<'T>.Name -> true // CompiledName throws exception on DataContractAttribute generated by SQLProvider
| _ -> false try attribute.AttributeType.CompiledName = typeof<'T>.Name with _ -> false
let tryGetAttribute<'T> (attributes: seq<FSharpAttribute>) = let tryGetAttribute<'T> (attributes: seq<FSharpAttribute>) =
attributes |> Seq.tryFind isAttribute<'T> attributes |> Seq.tryFind isAttribute<'T>
let hasModuleSuffixAttribute (entity: FSharpEntity) = module Option =
entity.Attributes let attempt f = try Some(f()) with _ -> None
|> tryGetAttribute<CompilationRepresentationAttribute>
|> Option.bind (fun a -> let hasModuleSuffixAttribute (entity: FSharpEntity) =
Option.attempt (fun _ -> a.ConstructorArguments) entity.Attributes
|> Option.bind (fun args -> args |> Seq.tryPick (fun (_, arg) -> |> tryGetAttribute<CompilationRepresentationAttribute>
let res = |> Option.bind (fun a ->
match arg with Option.attempt (fun _ -> a.ConstructorArguments)
| :? int32 as arg when arg = int CompilationRepresentationFlags.ModuleSuffix -> |> Option.bind (fun args -> args |> Seq.tryPick (fun (_, arg) ->
Some() let res =
| :? CompilationRepresentationFlags as arg when arg = CompilationRepresentationFlags.ModuleSuffix -> match arg with
Some() | :? int32 as arg when arg = int CompilationRepresentationFlags.ModuleSuffix ->
| _ -> Some()
None | :? CompilationRepresentationFlags as arg when arg = CompilationRepresentationFlags.ModuleSuffix ->
res))) Some()
|> Option.isSome | _ ->
None
let isOperator (name: string) = res)))
name.StartsWith "( " && name.EndsWith " )" && name.Length > 4 |> Option.isSome
&& name.Substring (2, name.Length - 4)
|> String.forall (fun c -> c <> ' ' && not (Char.IsLetter c)) let isOperator (name: string) =
name.StartsWith "( " && name.EndsWith " )" && name.Length > 4
let private UnnamedUnionFieldRegex = Regex("^Item(\d+)?$", RegexOptions.Compiled) && name.Substring (2, name.Length - 4)
|> String.forall (fun c -> c <> ' ' && not (Char.IsLetter c))
let UnnamedUnionFieldRegex = Regex("^Item(\d+)?$", RegexOptions.Compiled)
let isUnnamedUnionCaseField (field: FSharpField) = UnnamedUnionFieldRegex.IsMatch(field.Name) let isUnnamedUnionCaseField (field: FSharpField) = UnnamedUnionFieldRegex.IsMatch(field.Name)
module TypedAstPatterns =
let (|AbbreviatedType|_|) (entity: FSharpEntity) = let (|AbbreviatedType|_|) (entity: FSharpEntity) =
if entity.IsFSharpAbbreviation then Some entity.AbbreviatedType if entity.IsFSharpAbbreviation then Some entity.AbbreviatedType
...@@ -76,21 +77,28 @@ module TypedAstPatterns = ...@@ -76,21 +77,28 @@ module TypedAstPatterns =
match ty with match ty with
| None -> false | None -> false
| Some ty -> | Some ty ->
match ty.TryGetFullName() with try ty.FullName = "System.Attribute" || isAttributeType (getBaseType ty)
| None -> false with _ -> false
| Some fullName ->
fullName = "System.Attribute" || isAttributeType (getBaseType ty)
isAttributeType (Some entity) isAttributeType (Some entity)
if isAttribute entity then Some() else None if isAttribute entity then Some() else None
let hasAttribute<'T> (attributes: seq<FSharpAttribute>) =
attributes |> Seq.exists isAttribute<'T>
let (|ValueType|_|) (e: FSharpEntity) = let (|ValueType|_|) (e: FSharpEntity) =
if e.IsEnum || e.IsValueType || hasAttribute<MeasureAnnotatedAbbreviationAttribute> e.Attributes then Some() if e.IsEnum || e.IsValueType || hasAttribute<MeasureAnnotatedAbbreviationAttribute> e.Attributes then Some()
else None else None
#if EXTENSIONTYPING
let (|Class|_|) (original: FSharpEntity, abbreviated: FSharpEntity, _) = let (|Class|_|) (original: FSharpEntity, abbreviated: FSharpEntity, _) =
if abbreviated.IsClass if abbreviated.IsClass
&& (not abbreviated.IsStaticInstantiation || original.IsFSharpAbbreviation) then Some() && (not abbreviated.IsStaticInstantiation || original.IsFSharpAbbreviation) then Some()
else None
#else
let (|Class|_|) (original: FSharpEntity, abbreviated: FSharpEntity, _) =
if abbreviated.IsClass && original.IsFSharpAbbreviation then Some()
else None else None
#endif
let (|Record|_|) (e: FSharpEntity) = if e.IsFSharpRecord then Some() else None let (|Record|_|) (e: FSharpEntity) = if e.IsFSharpRecord then Some() else None
let (|UnionType|_|) (e: FSharpEntity) = if e.IsFSharpUnion then Some() else None let (|UnionType|_|) (e: FSharpEntity) = if e.IsFSharpUnion then Some() else None
...@@ -106,17 +114,21 @@ module TypedAstPatterns = ...@@ -106,17 +114,21 @@ module TypedAstPatterns =
|| (e.IsFSharp && e.IsOpaque && not e.IsFSharpModule && not e.IsNamespace) then Some() || (e.IsFSharp && e.IsOpaque && not e.IsFSharpModule && not e.IsNamespace) then Some()
else None else None
#if EXTENSIONTYPING
let (|ProvidedType|_|) (e: FSharpEntity) = let (|ProvidedType|_|) (e: FSharpEntity) =
if (e.IsProvided || e.IsProvidedAndErased || e.IsProvidedAndGenerated) && e.CompiledName = e.DisplayName then if (e.IsProvided || e.IsProvidedAndErased || e.IsProvidedAndGenerated) && e.CompiledName = e.DisplayName then
Some() Some()
else None else None
#endif
let (|ByRef|_|) (e: FSharpEntity) = if e.IsByRef then Some() else None let (|ByRef|_|) (e: FSharpEntity) = if e.IsByRef then Some() else None
let (|Array|_|) (e: FSharpEntity) = if e.IsArrayType then Some() else None let (|Array|_|) (e: FSharpEntity) = if e.IsArrayType then Some() else None
let (|FSharpModule|_|) (entity: FSharpEntity) = if entity.IsFSharpModule then Some() else None let (|FSharpModule|_|) (entity: FSharpEntity) = if entity.IsFSharpModule then Some() else None
let (|Namespace|_|) (entity: FSharpEntity) = if entity.IsNamespace then Some() else None let (|Namespace|_|) (entity: FSharpEntity) = if entity.IsNamespace then Some() else None
#if EXTENSIONTYPING
let (|ProvidedAndErasedType|_|) (entity: FSharpEntity) = if entity.IsProvidedAndErased then Some() else None let (|ProvidedAndErasedType|_|) (entity: FSharpEntity) = if entity.IsProvidedAndErased then Some() else None
#endif
let (|Enum|_|) (entity: FSharpEntity) = if entity.IsEnum then Some() else None let (|Enum|_|) (entity: FSharpEntity) = if entity.IsEnum then Some() else None
let (|Tuple|_|) (ty: FSharpType option) = let (|Tuple|_|) (ty: FSharpType option) =
...@@ -190,17 +202,18 @@ module TypedAstPatterns = ...@@ -190,17 +202,18 @@ module TypedAstPatterns =
/// Constructor (enclosingEntity) /// Constructor (enclosingEntity)
let (|Constructor|_|) (func: FSharpMemberOrFunctionOrValue) = let (|Constructor|_|) (func: FSharpMemberOrFunctionOrValue) =
match func.CompiledName with match func.CompiledName with
| ".ctor" | ".cctor" -> Some func.EnclosingEntity | ".ctor" | ".cctor" -> func.EnclosingEntity
| _ -> None | _ -> None
let (|Function|_|) excluded (func: FSharpMemberOrFunctionOrValue) = let (|Function|_|) excluded (func: FSharpMemberOrFunctionOrValue) =
match func.FullTypeSafe |> Option.map getAbbreviatedType with try let typ = func.FullType |> getAbbreviatedType
| Some typ when typ.IsFunctionType if typ.IsFunctionType
&& not func.IsPropertyGetterMethod && not func.IsPropertyGetterMethod
&& not func.IsPropertySetterMethod && not func.IsPropertySetterMethod
&& not excluded && not excluded
&& not (isOperator func.DisplayName) -> Some() && not (isOperator func.DisplayName) then Some()
| _ -> None else None
with _ -> None
let (|ExtensionMember|_|) (func: FSharpMemberOrFunctionOrValue) = let (|ExtensionMember|_|) (func: FSharpMemberOrFunctionOrValue) =
if func.IsExtensionMember then Some() else None if func.IsExtensionMember then Some() else None
......
// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.
namespace Microsoft.FSharp.Compiler.SourceCodeServices
[<RequireQualifiedAccess>]
#if COMPILER_PUBLIC_API
module Symbol =
#else
module internal Symbol =
#endif
open System.Text.RegularExpressions
open System
val isAttribute<'T> : FSharpAttribute -> bool
val tryGetAttribute<'T> : seq<FSharpAttribute> -> FSharpAttribute option
val hasModuleSuffixAttribute : FSharpEntity -> bool
val isOperator : name: string -> bool
val isUnnamedUnionCaseField : FSharpField -> bool
val (|AbbreviatedType|_|) : FSharpEntity -> FSharpType option
val (|TypeWithDefinition|_|) : FSharpType -> FSharpEntity option
val getEntityAbbreviatedType : FSharpEntity -> (FSharpEntity * FSharpType option)
val getAbbreviatedType : FSharpType -> FSharpType
val (|Attribute|_|) : FSharpEntity -> unit option
val hasAttribute<'T> : seq<FSharpAttribute> -> bool
val (|ValueType|_|) : FSharpEntity -> unit option
val (|Class|_|) : original: FSharpEntity * abbreviated: FSharpEntity * 'a -> unit option
val (|Record|_|) : FSharpEntity -> unit option
val (|UnionType|_|) : FSharpEntity -> unit option
val (|Delegate|_|) : FSharpEntity -> unit option
val (|FSharpException|_|) : FSharpEntity -> unit option
val (|Interface|_|) : FSharpEntity -> unit option
val (|AbstractClass|_|) : FSharpEntity -> unit option
val (|FSharpType|_|) : FSharpEntity -> unit option
#if EXTENSIONTYPING
val (|ProvidedType|_|) : FSharpEntity -> unit option
#endif
val (|ByRef|_|) : FSharpEntity -> unit option
val (|Array|_|) : FSharpEntity -> unit option
val (|FSharpModule|_|) : FSharpEntity -> unit option
val (|Namespace|_|) : FSharpEntity -> unit option
#if EXTENSIONTYPING
val (|ProvidedAndErasedType|_|) : FSharpEntity -> unit option
#endif
val (|Enum|_|) : FSharpEntity -> unit option
val (|Tuple|_|) : FSharpType option -> unit option
val (|RefCell|_|) : FSharpType -> unit option
val (|FunctionType|_|) : FSharpType -> unit option
val (|Pattern|_|) : FSharpSymbol -> unit option
val (|Field|_|) : FSharpSymbol -> (FSharpField * FSharpType) option
val (|MutableVar|_|) : FSharpSymbol -> unit option
val (|FSharpEntity|_|) : FSharpSymbol -> (FSharpEntity * FSharpEntity * FSharpType option) option
val (|Parameter|_|) : FSharpSymbol -> unit option
val (|UnionCase|_|) : FSharpSymbol -> FSharpUnionCase option
val (|RecordField|_|) : FSharpSymbol -> FSharpField option
val (|ActivePatternCase|_|) : FSharpSymbol -> FSharpActivePatternCase option
val (|MemberFunctionOrValue|_|) : FSharpSymbol -> FSharpMemberOrFunctionOrValue option
val (|Constructor|_|) : FSharpMemberOrFunctionOrValue -> FSharpEntity option
val (|Function|_|) : excluded: bool -> FSharpMemberOrFunctionOrValue -> unit option
val (|ExtensionMember|_|) : FSharpMemberOrFunctionOrValue -> unit option
val (|Event|_|) : FSharpMemberOrFunctionOrValue -> unit option
\ No newline at end of file
...@@ -310,7 +310,7 @@ and FSharpEntity(cenv:cenv, entity:EntityRef) = ...@@ -310,7 +310,7 @@ and FSharpEntity(cenv:cenv, entity:EntityRef) =
#else #else
elif entity.IsTypeAbbrev then None elif entity.IsTypeAbbrev then None
#endif #endif
elif entity.IsNamespace then Some entity.DemangledModuleOrNamespaceName elif entity.IsNamespace then Some entity.DemangledModuleOrNamespaceName
else else
match entity.CompiledRepresentation with match entity.CompiledRepresentation with
| CompiledTypeRepr.ILAsmNamed(tref, _, _) -> Some tref.FullName | CompiledTypeRepr.ILAsmNamed(tref, _, _) -> Some tref.FullName
...@@ -619,6 +619,14 @@ and FSharpEntity(cenv:cenv, entity:EntityRef) = ...@@ -619,6 +619,14 @@ and FSharpEntity(cenv:cenv, entity:EntityRef) =
yield! walkParts parts ] yield! walkParts parts ]
res res
member x.ActivePatternCases =
protect <| fun () ->
ActivePatternElemsOfModuleOrNamespace x.Entity
|> Map.toList
|> List.map (fun (_, apref) ->
let item = Item.ActivePatternCase apref
FSharpActivePatternCase(cenv, apref.ActivePatternInfo, apref.ActivePatternVal.Type, apref.CaseIndex, Some apref.ActivePatternVal, item))
override x.Equals(other: obj) = override x.Equals(other: obj) =
box x === other || box x === other ||
match other with match other with
...@@ -2259,6 +2267,14 @@ type FSharpSymbol with ...@@ -2259,6 +2267,14 @@ type FSharpSymbol with
| :? FSharpMemberFunctionOrValue as x -> Some x.Accessibility | :? FSharpMemberFunctionOrValue as x -> Some x.Accessibility
| _ -> None | _ -> None
/// Represents open declaration in F# code.
type FSharpOpenDeclaration =
{ LongId: Ident list
Range: range option
Modules: FSharpEntity list
AppliedScope: range
IsOwnNamespace: bool }
[<Sealed>] [<Sealed>]
type FSharpSymbolUse(g:TcGlobals, denv: DisplayEnv, symbol:FSharpSymbol, itemOcc, range: range) = type FSharpSymbolUse(g:TcGlobals, denv: DisplayEnv, symbol:FSharpSymbol, itemOcc, range: range) =
member __.Symbol = symbol member __.Symbol = symbol
......
...@@ -7,6 +7,7 @@ open Microsoft.FSharp.Compiler ...@@ -7,6 +7,7 @@ open Microsoft.FSharp.Compiler
open Microsoft.FSharp.Compiler.AccessibilityLogic open Microsoft.FSharp.Compiler.AccessibilityLogic
open Microsoft.FSharp.Compiler.CompileOps open Microsoft.FSharp.Compiler.CompileOps
open Microsoft.FSharp.Compiler.Range open Microsoft.FSharp.Compiler.Range
open Microsoft.FSharp.Compiler.Ast
open Microsoft.FSharp.Compiler.Tast open Microsoft.FSharp.Compiler.Tast
open Microsoft.FSharp.Compiler.TcGlobals open Microsoft.FSharp.Compiler.TcGlobals
open Microsoft.FSharp.Compiler.NameResolution open Microsoft.FSharp.Compiler.NameResolution
...@@ -329,6 +330,9 @@ and [<Class>] internal FSharpEntity = ...@@ -329,6 +330,9 @@ and [<Class>] internal FSharpEntity =
/// Get all compilation paths, taking `Module` suffixes into account. /// Get all compilation paths, taking `Module` suffixes into account.
member AllCompilationPaths : string list member AllCompilationPaths : string list
/// Get all active pattern cases defined in all active patterns in the module.
member ActivePatternCases : FSharpActivePatternCase list
/// Represents a delegate signature in an F# symbol /// Represents a delegate signature in an F# symbol
#if COMPILER_PUBLIC_API #if COMPILER_PUBLIC_API
and [<Class>] FSharpDelegateSignature = and [<Class>] FSharpDelegateSignature =
...@@ -1065,7 +1069,26 @@ and [<Class>] internal FSharpAttribute = ...@@ -1065,7 +1069,26 @@ and [<Class>] internal FSharpAttribute =
/// Format the attribute using the rules of the given display context /// Format the attribute using the rules of the given display context
member Format : context: FSharpDisplayContext -> string member Format : context: FSharpDisplayContext -> string
/// Represents open declaration in F# code.
#if COMPILER_PUBLIC_API
type FSharpOpenDeclaration =
#else
type internal FSharpOpenDeclaration =
#endif
{ /// Idents.
LongId: Ident list
/// Range of the open declaration.
Range: range option
/// Modules or namespaces which is opened with this declaration.
Modules: FSharpEntity list
/// Scope in which open declaration is visible.
AppliedScope: range
/// If it's `namespace Xxx.Yyy` declaration.
IsOwnNamespace: bool }
/// Represents the use of an F# symbol from F# source code /// Represents the use of an F# symbol from F# source code
[<Sealed>] [<Sealed>]
......
...@@ -1028,6 +1028,7 @@ type TypeCheckAccumulator = ...@@ -1028,6 +1028,7 @@ type TypeCheckAccumulator =
tcEnvAtEndOfFile: TcEnv tcEnvAtEndOfFile: TcEnv
tcResolutions: TcResolutions list tcResolutions: TcResolutions list
tcSymbolUses: TcSymbolUses list tcSymbolUses: TcSymbolUses list
tcOpenDeclarations: OpenDeclaration list
topAttribs:TopAttribs option topAttribs:TopAttribs option
typedImplFiles:TypedImplFile list typedImplFiles:TypedImplFile list
tcDependencyFiles: string list tcDependencyFiles: string list
...@@ -1102,6 +1103,7 @@ type PartialCheckResults = ...@@ -1102,6 +1103,7 @@ type PartialCheckResults =
Errors: (PhasedDiagnostic * FSharpErrorSeverity) list Errors: (PhasedDiagnostic * FSharpErrorSeverity) list
TcResolutions: TcResolutions list TcResolutions: TcResolutions list
TcSymbolUses: TcSymbolUses list TcSymbolUses: TcSymbolUses list
TcOpenDeclarations: OpenDeclaration list
TcDependencyFiles: string list TcDependencyFiles: string list
TopAttribs: TopAttribs option TopAttribs: TopAttribs option
TimeStamp: System.DateTime TimeStamp: System.DateTime
...@@ -1116,6 +1118,7 @@ type PartialCheckResults = ...@@ -1116,6 +1118,7 @@ type PartialCheckResults =
Errors = tcAcc.tcErrors Errors = tcAcc.tcErrors
TcResolutions = tcAcc.tcResolutions TcResolutions = tcAcc.tcResolutions
TcSymbolUses = tcAcc.tcSymbolUses TcSymbolUses = tcAcc.tcSymbolUses
TcOpenDeclarations = tcAcc.tcOpenDeclarations
TcDependencyFiles = tcAcc.tcDependencyFiles TcDependencyFiles = tcAcc.tcDependencyFiles
TopAttribs = tcAcc.topAttribs TopAttribs = tcAcc.topAttribs
TimeStamp = timestamp TimeStamp = timestamp
...@@ -1327,6 +1330,7 @@ type IncrementalBuilder(tcGlobals, frameworkTcImports, nonFrameworkAssemblyInput ...@@ -1327,6 +1330,7 @@ type IncrementalBuilder(tcGlobals, frameworkTcImports, nonFrameworkAssemblyInput
tcEnvAtEndOfFile=tcInitial tcEnvAtEndOfFile=tcInitial
tcResolutions=[] tcResolutions=[]
tcSymbolUses=[] tcSymbolUses=[]
tcOpenDeclarations=[]
topAttribs=None topAttribs=None
typedImplFiles=[] typedImplFiles=[]
tcDependencyFiles=basicDependencies tcDependencyFiles=basicDependencies
...@@ -1375,6 +1379,7 @@ type IncrementalBuilder(tcGlobals, frameworkTcImports, nonFrameworkAssemblyInput ...@@ -1375,6 +1379,7 @@ type IncrementalBuilder(tcGlobals, frameworkTcImports, nonFrameworkAssemblyInput
typedImplFiles=typedImplFiles typedImplFiles=typedImplFiles
tcResolutions=tcAcc.tcResolutions @ [tcResolutions] tcResolutions=tcAcc.tcResolutions @ [tcResolutions]
tcSymbolUses=tcAcc.tcSymbolUses @ [tcSymbolUses] tcSymbolUses=tcAcc.tcSymbolUses @ [tcSymbolUses]
tcOpenDeclarations=tcAcc.tcOpenDeclarations @ sink.OpenDeclarations
tcErrors = tcAcc.tcErrors @ parseErrors @ capturingErrorLogger.GetErrors() tcErrors = tcAcc.tcErrors @ parseErrors @ capturingErrorLogger.GetErrors()
tcDependencyFiles = filename :: tcAcc.tcDependencyFiles } tcDependencyFiles = filename :: tcAcc.tcDependencyFiles }
} }
......
...@@ -54,6 +54,9 @@ type internal PartialCheckResults = ...@@ -54,6 +54,9 @@ type internal PartialCheckResults =
/// Represents the collected uses of symbols from type checking /// Represents the collected uses of symbols from type checking
TcSymbolUses: TcSymbolUses list TcSymbolUses: TcSymbolUses list
/// Represents open declarations
TcOpenDeclarations: OpenDeclaration list
TcDependencyFiles: string list TcDependencyFiles: string list
/// Represents the collected attributes to apply to the module of assuembly generates /// Represents the collected attributes to apply to the module of assuembly generates
......
// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.
namespace Microsoft.FSharp.Compiler.SourceCodeServices
open Microsoft.FSharp.Compiler
open Microsoft.FSharp.Compiler.Ast
open Microsoft.FSharp.Compiler.Range
module UnusedOpens =
open Microsoft.FSharp.Compiler.PrettyNaming
open System.Runtime.CompilerServices
type Module =
{ Entity: FSharpEntity
IsNestedAutoOpen: bool }
member this.ChildSymbols =
seq { for ent in this.Entity.NestedEntities do
yield ent :> FSharpSymbol
if ent.IsFSharpRecord then
for rf in ent.FSharpFields do
yield upcast rf
if ent.IsFSharpUnion && not (Symbol.hasAttribute<RequireQualifiedAccessAttribute> ent.Attributes) then
for unionCase in ent.UnionCases do
yield upcast unionCase
if Symbol.hasAttribute<ExtensionAttribute> ent.Attributes then
for fv in ent.MembersFunctionsAndValues do
// fv.IsExtensionMember is always false for C# extension methods returning by `MembersFunctionsAndValues`,
// so we have to check Extension attribute instead.
// (note: fv.IsExtensionMember has proper value for symbols returning by GetAllUsesOfAllSymbolsInFile though)
if Symbol.hasAttribute<ExtensionAttribute> fv.Attributes then
yield upcast fv
for apCase in this.Entity.ActivePatternCases do
yield upcast apCase
for fv in this.Entity.MembersFunctionsAndValues do
yield upcast fv
} |> Seq.cache
type ModuleGroup =
{ Modules: Module list }
static member Create (modul: FSharpEntity) =
let rec getModuleAndItsAutoOpens (isNestedAutoOpen: bool) (modul: FSharpEntity) =
[ yield { Entity = modul; IsNestedAutoOpen = isNestedAutoOpen }
for ent in modul.NestedEntities do
if ent.IsFSharpModule && Symbol.hasAttribute<AutoOpenAttribute> ent.Attributes then
yield! getModuleAndItsAutoOpens true ent ]
{ Modules = getModuleAndItsAutoOpens false modul }
/// Represents single open statement.
type OpenStatement =
{ /// All modules which this open declaration effectively opens, _not_ including auto open ones.
Modules: ModuleGroup list
/// Range of open statement itself.
Range: range
/// Scope on which this open declaration is applied.
AppliedScope: range }
let getOpenStatements (openDeclarations: FSharpOpenDeclaration list) : OpenStatement list =
openDeclarations
|> List.filter (fun x -> not x.IsOwnNamespace)
|> List.choose (fun openDecl ->
match openDecl.LongId, openDecl.Range with
| firstId :: _, Some range ->
if firstId.idText = MangledGlobalName then
None
else
Some { Modules = openDecl.Modules |> List.map ModuleGroup.Create
Range = range
AppliedScope = openDecl.AppliedScope }
| _ -> None)
let filterSymbolUses (getSourceLineStr: int -> string) (symbolUses: FSharpSymbolUse[]) : FSharpSymbolUse[] =
symbolUses
|> Array.filter (fun su ->
match su.Symbol with
| :? FSharpMemberOrFunctionOrValue as fv when fv.IsExtensionMember ->
// extension members should be taken into account even though they have a prefix (as they do most of the time)
true
| _ ->
let partialName = QuickParse.GetPartialLongNameEx (getSourceLineStr su.RangeAlternate.StartLine, su.RangeAlternate.EndColumn - 1)
// for the rest of symbols we pick only those which are the first part of a long idend, because it's they which are
// conteined in opened namespaces / modules. For example, we pick `IO` from long ident `IO.File.OpenWrite` because
// it's `open System` which really brings it into scope.
partialName.QualifyingIdents = [])
type UsedModule =
{ Module: FSharpEntity
AppliedScope: range }
let getUnusedOpens (checkFileResults: FSharpCheckFileResults, getSourceLineStr: int -> string) : Async<range list> =
let filterOpenStatements (openStatements: OpenStatement list) (symbolUses: FSharpSymbolUse[]) : OpenStatement list =
let rec filterInner acc (openStatements: OpenStatement list) (usedModules: UsedModule list) =
let getUsedModules (openStatement: OpenStatement) =
let notAlreadyUsedModuleGroups =
openStatement.Modules
|> List.choose (fun x ->
let notUsedModules =
x.Modules
|> List.filter (fun x ->
not (usedModules
|> List.exists (fun used ->
rangeContainsRange used.AppliedScope openStatement.AppliedScope &&
used.Module.IsEffectivelySameAs x.Entity)))
match notUsedModules with
| [] -> None
| _ when notUsedModules |> List.exists (fun x -> not x.IsNestedAutoOpen) ->
Some { Modules = notUsedModules }
| _ -> None)
match notAlreadyUsedModuleGroups with
| [] -> []
| _ ->
let symbolUsesInScope = symbolUses |> Array.filter (fun symbolUse -> rangeContainsRange openStatement.AppliedScope symbolUse.RangeAlternate)
notAlreadyUsedModuleGroups
|> List.filter (fun modulGroup ->
modulGroup.Modules
|> List.exists (fun modul ->
symbolUsesInScope
|> Array.exists (fun symbolUse ->
let usedByEnclosingEntity =
match symbolUse.Symbol with
| :? FSharpMemberOrFunctionOrValue as f ->
match f.EnclosingEntity with
| Some ent when ent.IsNamespace || ent.IsFSharpModule ->
Some (ent.IsEffectivelySameAs modul.Entity)
| _ -> None
| _ -> None
match usedByEnclosingEntity with
| Some x -> x
| None -> modul.ChildSymbols |> Seq.exists (fun x -> x.IsEffectivelySameAs symbolUse.Symbol)
)))
|> List.collect (fun mg ->
mg.Modules |> List.map (fun x -> { Module = x.Entity; AppliedScope = openStatement.AppliedScope }))
match openStatements with
| os :: xs ->
match getUsedModules os with
| [] -> filterInner (os :: acc) xs usedModules
| um -> filterInner acc xs (um @ usedModules)
| [] -> List.rev acc
filterInner [] openStatements []
async {
let! symbolUses = checkFileResults.GetAllUsesOfAllSymbolsInFile()
let symbolUses = filterSymbolUses getSourceLineStr symbolUses
let openStatements = getOpenStatements checkFileResults.OpenDeclarations
return filterOpenStatements openStatements symbolUses |> List.map (fun os -> os.Range)
}
\ No newline at end of file
// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.
namespace Microsoft.FSharp.Compiler.SourceCodeServices
open Microsoft.FSharp.Compiler.Ast
open Microsoft.FSharp.Compiler.NameResolution
open Microsoft.FSharp.Compiler.Range
#if COMPILER_PUBLIC_API
module UnusedOpens =
#else
module internal UnusedOpens =
#endif
/// Get all unused open declarations in a file
val getUnusedOpens : checkFileResults: FSharpCheckFileResults * getSourceLineStr: (int -> string) -> Async<range list>
\ No newline at end of file
...@@ -97,37 +97,6 @@ module Extensions = ...@@ -97,37 +97,6 @@ module Extensions =
type FSharpAssemblySignature with type FSharpAssemblySignature with
member x.TryGetEntities() = try x.Entities :> _ seq with _ -> Seq.empty member x.TryGetEntities() = try x.Entities :> _ seq with _ -> Seq.empty
[<AutoOpen>]
module Utils =
let isAttribute<'T> (attribute: FSharpAttribute) =
// CompiledName throws exception on DataContractAttribute generated by SQLProvider
match (try Some attribute.AttributeType.CompiledName with _ -> None) with
| Some name when name = typeof<'T>.Name -> true
| _ -> false
let hasAttribute<'T> (attributes: seq<FSharpAttribute>) =
attributes |> Seq.exists isAttribute<'T>
let tryGetAttribute<'T> (attributes: seq<FSharpAttribute>) =
attributes |> Seq.tryFind isAttribute<'T>
let hasModuleSuffixAttribute (entity: FSharpEntity) =
entity.Attributes
|> tryGetAttribute<CompilationRepresentationAttribute>
|> Option.bind (fun a ->
try Some a.ConstructorArguments with _ -> None
|> Option.bind (fun args -> args |> Seq.tryPick (fun (_, arg) ->
let res =
match arg with
| :? int32 as arg when arg = int CompilationRepresentationFlags.ModuleSuffix ->
Some()
| :? CompilationRepresentationFlags as arg when arg = CompilationRepresentationFlags.ModuleSuffix ->
Some()
| _ ->
None
res)))
|> Option.isSome
[<RequireQualifiedAccess>] [<RequireQualifiedAccess>]
type LookupType = type LookupType =
| Fuzzy | Fuzzy
...@@ -186,7 +155,7 @@ type Parent = ...@@ -186,7 +155,7 @@ type Parent =
else ident) else ident)
let removeModuleSuffix (idents: Idents) = let removeModuleSuffix (idents: Idents) =
if entity.IsFSharpModule && idents.Length > 0 && hasModuleSuffixAttribute entity then if entity.IsFSharpModule && idents.Length > 0 && Symbol.hasModuleSuffixAttribute entity then
let lastIdent = idents.[idents.Length - 1] let lastIdent = idents.[idents.Length - 1]
if lastIdent.EndsWith "Module" then if lastIdent.EndsWith "Module" then
idents |> Array.replace (idents.Length - 1) (lastIdent.Substring(0, lastIdent.Length - 6)) idents |> Array.replace (idents.Length - 1) (lastIdent.Substring(0, lastIdent.Length - 6))
...@@ -202,18 +171,6 @@ type Parent = ...@@ -202,18 +171,6 @@ type Parent =
|> removeGenericParamsCount |> removeGenericParamsCount
|> removeModuleSuffix)) |> removeModuleSuffix))
module TypedAstPatterns =
let (|TypeWithDefinition|_|) (ty: FSharpType) =
if ty.HasTypeDefinition then Some ty.TypeDefinition
else None
let (|Attribute|_|) (entity: FSharpEntity) =
let isAttribute (entity: FSharpEntity) =
try entity.IsAttributeType with _ -> false
if isAttribute entity then Some() else None
let (|FSharpModule|_|) (entity: FSharpEntity) = if entity.IsFSharpModule then Some() else None
type AssemblyContentCacheEntry = type AssemblyContentCacheEntry =
{ FileWriteTime: DateTime { FileWriteTime: DateTime
ContentType: AssemblyContentType ContentType: AssemblyContentType
...@@ -239,15 +196,15 @@ module AssemblyContentProvider = ...@@ -239,15 +196,15 @@ module AssemblyContentProvider =
Symbol = entity Symbol = entity
Kind = fun lookupType -> Kind = fun lookupType ->
match entity, lookupType with match entity, lookupType with
| TypedAstPatterns.FSharpModule, _ -> | Symbol.FSharpModule, _ ->
EntityKind.Module EntityKind.Module
{ IsAutoOpen = hasAttribute<AutoOpenAttribute> entity.Attributes { IsAutoOpen = Symbol.hasAttribute<AutoOpenAttribute> entity.Attributes
HasModuleSuffix = hasModuleSuffixAttribute entity } HasModuleSuffix = Symbol.hasModuleSuffixAttribute entity }
| _, LookupType.Fuzzy -> | _, LookupType.Fuzzy ->
EntityKind.Type EntityKind.Type
| _, LookupType.Precise -> | _, LookupType.Precise ->
match entity with match entity with
| TypedAstPatterns.Attribute -> EntityKind.Attribute | Symbol.Attribute -> EntityKind.Attribute
| _ -> EntityKind.Type | _ -> EntityKind.Type
}) })
...@@ -297,7 +254,7 @@ module AssemblyContentProvider = ...@@ -297,7 +254,7 @@ module AssemblyContentProvider =
| None -> () | None -> ()
let thisRequiresQualifierAccess = let thisRequiresQualifierAccess =
if entity.IsFSharp && hasAttribute<RequireQualifiedAccessAttribute> entity.Attributes then if entity.IsFSharp && Symbol.hasAttribute<RequireQualifiedAccessAttribute> entity.Attributes then
parent.FormatEntityFullName entity |> Option.map snd parent.FormatEntityFullName entity |> Option.map snd
else None else None
...@@ -305,7 +262,7 @@ module AssemblyContentProvider = ...@@ -305,7 +262,7 @@ module AssemblyContentProvider =
{ ThisRequiresQualifiedAccess = thisRequiresQualifierAccess |> Option.orElse parent.ThisRequiresQualifiedAccess { ThisRequiresQualifiedAccess = thisRequiresQualifierAccess |> Option.orElse parent.ThisRequiresQualifiedAccess
TopRequiresQualifiedAccess = parent.TopRequiresQualifiedAccess |> Option.orElse thisRequiresQualifierAccess TopRequiresQualifiedAccess = parent.TopRequiresQualifiedAccess |> Option.orElse thisRequiresQualifierAccess
AutoOpen = AutoOpen =
let isAutoOpen = entity.IsFSharpModule && hasAttribute<AutoOpenAttribute> entity.Attributes let isAutoOpen = entity.IsFSharpModule && Symbol.hasAttribute<AutoOpenAttribute> entity.Attributes
match isAutoOpen, parent.AutoOpen with match isAutoOpen, parent.AutoOpen with
// if parent is also AutoOpen, then keep the parent // if parent is also AutoOpen, then keep the parent
| true, Some parent -> Some parent | true, Some parent -> Some parent
...@@ -315,7 +272,7 @@ module AssemblyContentProvider = ...@@ -315,7 +272,7 @@ module AssemblyContentProvider =
| false, _ -> None | false, _ -> None
WithModuleSuffix = WithModuleSuffix =
if entity.IsFSharpModule && hasModuleSuffixAttribute entity then if entity.IsFSharpModule && Symbol.hasModuleSuffixAttribute entity then
currentEntity |> Option.map (fun e -> e.CleanedIdents) currentEntity |> Option.map (fun e -> e.CleanedIdents)
else parent.WithModuleSuffix else parent.WithModuleSuffix
Namespace = ns } Namespace = ns }
......
...@@ -255,10 +255,4 @@ module internal Extensions = ...@@ -255,10 +255,4 @@ module internal Extensions =
type FSharpAssemblySignature with type FSharpAssemblySignature with
/// Safe version of `Entities`. /// Safe version of `Entities`.
member TryGetEntities : unit -> seq<FSharpEntity> member TryGetEntities : unit -> seq<FSharpEntity>
\ No newline at end of file
/// Operations over `FSharpAttribute`.
[<AutoOpen>]
module internal Utils =
/// Returns `true` if a collection of attributes contains one of given type.
val hasAttribute<'T> : attributes: seq<FSharpAttribute> -> bool
...@@ -220,7 +220,7 @@ module internal InterfaceStubGenerator = ...@@ -220,7 +220,7 @@ module internal InterfaceStubGenerator =
let nm, namesWithIndices = normalizeArgName namesWithIndices nm let nm, namesWithIndices = normalizeArgName namesWithIndices nm
// Detect an optional argument // Detect an optional argument
let isOptionalArg = hasAttribute<OptionalArgumentAttribute> arg.Attributes let isOptionalArg = Symbol.hasAttribute<OptionalArgumentAttribute> arg.Attributes
let argName = if isOptionalArg then "?" + nm else nm let argName = if isOptionalArg then "?" + nm else nm
(if hasTypeAnnotation && argName <> "()" then (if hasTypeAnnotation && argName <> "()" then
argName + ": " + formatType ctx arg.Type argName + ": " + formatType ctx arg.Type
...@@ -295,7 +295,7 @@ module internal InterfaceStubGenerator = ...@@ -295,7 +295,7 @@ module internal InterfaceStubGenerator =
else displayName else displayName
let internal isEventMember (m: FSharpMemberOrFunctionOrValue) = let internal isEventMember (m: FSharpMemberOrFunctionOrValue) =
m.IsEvent || hasAttribute<CLIEventAttribute> m.Attributes m.IsEvent || Symbol.hasAttribute<CLIEventAttribute> m.Attributes
let internal formatMember (ctx: Context) m verboseMode = let internal formatMember (ctx: Context) m verboseMode =
let getParamArgs (argInfos: FSharpParameter list list) (ctx: Context) (v: FSharpMemberOrFunctionOrValue) = let getParamArgs (argInfos: FSharpParameter list list) (ctx: Context) (v: FSharpMemberOrFunctionOrValue) =
...@@ -325,7 +325,7 @@ module internal InterfaceStubGenerator = ...@@ -325,7 +325,7 @@ module internal InterfaceStubGenerator =
| _, true, _, name -> name + parArgs | _, true, _, name -> name + parArgs
// Ordinary functions or values // Ordinary functions or values
| false, _, _, name when | false, _, _, name when
not (hasAttribute<RequireQualifiedAccessAttribute> v.LogicalEnclosingEntity.Attributes) -> not (Symbol.hasAttribute<RequireQualifiedAccessAttribute> v.LogicalEnclosingEntity.Attributes) ->
name + " " + parArgs name + " " + parArgs
// Ordinary static members or things (?) that require fully qualified access // Ordinary static members or things (?) that require fully qualified access
| _, _, _, name -> name + parArgs | _, _, _, name -> name + parArgs
......
...@@ -165,7 +165,8 @@ type TypeCheckInfo ...@@ -165,7 +165,8 @@ type TypeCheckInfo
reactorOps : IReactorOperations, reactorOps : IReactorOperations,
checkAlive : (unit -> bool), checkAlive : (unit -> bool),
textSnapshotInfo:obj option, textSnapshotInfo:obj option,
implementationFiles: TypedImplFile list) = implementationFiles: TypedImplFile list,
openDeclarations: OpenDeclaration list) =
let textSnapshotInfo = defaultArg textSnapshotInfo null let textSnapshotInfo = defaultArg textSnapshotInfo null
let (|CNR|) (cnr:CapturedNameResolution) = let (|CNR|) (cnr:CapturedNameResolution) =
...@@ -1361,6 +1362,9 @@ type TypeCheckInfo ...@@ -1361,6 +1362,9 @@ type TypeCheckInfo
member __.ImplementationFiles = implementationFiles member __.ImplementationFiles = implementationFiles
/// All open declarations in the file, including auto open modules
member __.OpenDeclarations = openDeclarations
override __.ToString() = "TypeCheckInfo(" + mainInputFileName + ")" override __.ToString() = "TypeCheckInfo(" + mainInputFileName + ")"
type FSharpParsingOptions = type FSharpParsingOptions =
...@@ -1693,13 +1697,14 @@ module internal Parser = ...@@ -1693,13 +1697,14 @@ module internal Parser =
projectFileName, projectFileName,
mainInputFileName, mainInputFileName,
sink.GetResolutions(), sink.GetResolutions(),
sink.GetSymbolUses(), sink.GetSymbolUses(),
tcEnvAtEnd.NameEnv, tcEnvAtEnd.NameEnv,
loadClosure, loadClosure,
reactorOps, reactorOps,
checkAlive, checkAlive,
textSnapshotInfo, textSnapshotInfo,
typedImplFiles) typedImplFiles,
sink.OpenDeclarations)
return errors, TypeCheckAborted.No scope return errors, TypeCheckAborted.No scope
| None -> | None ->
return errors, TypeCheckAborted.Yes return errors, TypeCheckAborted.Yes
...@@ -1990,9 +1995,9 @@ type FSharpCheckFileResults(filename: string, errors: FSharpErrorInfo[], scopeOp ...@@ -1990,9 +1995,9 @@ type FSharpCheckFileResults(filename: string, errors: FSharpErrorInfo[], scopeOp
(fun () -> [| |]) (fun () -> [| |])
(fun scope -> (fun scope ->
[| for (item,itemOcc,denv,m) in scope.ScopeSymbolUses.GetAllUsesOfSymbols() do [| for (item,itemOcc,denv,m) in scope.ScopeSymbolUses.GetAllUsesOfSymbols() do
if itemOcc <> ItemOccurence.RelatedText then if itemOcc <> ItemOccurence.RelatedText then
let symbol = FSharpSymbol.Create(scope.TcGlobals, scope.ThisCcu, scope.TcImports, item) let symbol = FSharpSymbol.Create(scope.TcGlobals, scope.ThisCcu, scope.TcImports, item)
yield FSharpSymbolUse(scope.TcGlobals, denv, symbol, itemOcc, m) |]) yield FSharpSymbolUse(scope.TcGlobals, denv, symbol, itemOcc, m) |])
|> async.Return |> async.Return
member info.GetUsesOfSymbolInFile(symbol:FSharpSymbol) = member info.GetUsesOfSymbolInFile(symbol:FSharpSymbol) =
...@@ -2029,6 +2034,19 @@ type FSharpCheckFileResults(filename: string, errors: FSharpErrorInfo[], scopeOp ...@@ -2029,6 +2034,19 @@ type FSharpCheckFileResults(filename: string, errors: FSharpErrorInfo[], scopeOp
let cenv = Impl.cenv(scope.TcGlobals, scope.ThisCcu, scope.TcImports) let cenv = Impl.cenv(scope.TcGlobals, scope.ThisCcu, scope.TcImports)
[ for mimpl in scope.ImplementationFiles -> FSharpImplementationFileContents(cenv, mimpl)]) [ for mimpl in scope.ImplementationFiles -> FSharpImplementationFileContents(cenv, mimpl)])
member info.OpenDeclarations =
scopeOptX
|> Option.map (fun scope ->
let cenv = Impl.cenv(scope.TcGlobals, scope.ThisCcu, scope.TcImports)
scope.OpenDeclarations |> List.map (fun x ->
{ LongId = x.LongId
Range = x.Range
Modules = x.Modules |> List.map (fun x -> FSharpEntity(cenv, x))
AppliedScope = x.AppliedScope
IsOwnNamespace = x.IsOwnNamespace }
: FSharpOpenDeclaration ))
|> Option.defaultValue []
override info.ToString() = "FSharpCheckFileResults(" + filename + ")" override info.ToString() = "FSharpCheckFileResults(" + filename + ")"
//---------------------------------------------------------------------------- //----------------------------------------------------------------------------
...@@ -2620,7 +2638,8 @@ type BackgroundCompiler(legacyReferenceResolver, projectCacheSize, keepAssemblyC ...@@ -2620,7 +2638,8 @@ type BackgroundCompiler(legacyReferenceResolver, projectCacheSize, keepAssemblyC
List.last tcProj.TcSymbolUses, List.last tcProj.TcSymbolUses,
tcProj.TcEnvAtEnd.NameEnv, tcProj.TcEnvAtEnd.NameEnv,
loadClosure, reactorOps, (fun () -> builder.IsAlive), None, loadClosure, reactorOps, (fun () -> builder.IsAlive), None,
tcProj.ImplementationFiles) tcProj.ImplementationFiles,
tcProj.TcOpenDeclarations)
let typedResults = MakeCheckFileResults(filename, options, builder, scope, Array.ofList tcProj.TcDependencyFiles, creationErrors, parseResults.Errors, tcErrors) let typedResults = MakeCheckFileResults(filename, options, builder, scope, Array.ofList tcProj.TcDependencyFiles, creationErrors, parseResults.Errors, tcErrors)
return (parseResults, typedResults) return (parseResults, typedResults)
}) })
......
...@@ -273,6 +273,9 @@ type internal FSharpCheckFileResults = ...@@ -273,6 +273,9 @@ type internal FSharpCheckFileResults =
/// Represents complete typechecked implementation files, including thier typechecked signatures if any. /// Represents complete typechecked implementation files, including thier typechecked signatures if any.
member ImplementationFiles: FSharpImplementationFileContents list option member ImplementationFiles: FSharpImplementationFileContents list option
/// Open declarations in the file, including auto open modules.
member OpenDeclarations: FSharpOpenDeclaration list
/// A handle to the results of CheckFileInProject. /// A handle to the results of CheckFileInProject.
[<Sealed>] [<Sealed>]
#if COMPILER_PUBLIC_API #if COMPILER_PUBLIC_API
......
...@@ -111,7 +111,7 @@ let _ = CSharpOuterClass.InnerClass.StaticMember() ...@@ -111,7 +111,7 @@ let _ = CSharpOuterClass.InnerClass.StaticMember()
|> Async.RunSynchronously |> Async.RunSynchronously
|> Array.map (fun su -> su.Symbol.ToString()) |> Array.map (fun su -> su.Symbol.ToString())
|> shouldEqual |> shouldEqual
[|"InnerEnum"; "CSharpOuterClass"; "field Case1"; "InnerClass"; [|"Tests"; "InnerEnum"; "CSharpOuterClass"; "field Case1"; "InnerClass";
"CSharpOuterClass"; "member StaticMember"; "NestedEnumClass"|] "CSharpOuterClass"; "member StaticMember"; "NestedEnumClass"|]
[<Test>] [<Test>]
......
...@@ -633,15 +633,18 @@ let _ = ...@@ -633,15 +633,18 @@ let _ =
|> Array.map (fun su -> |> Array.map (fun su ->
let r = su.RangeAlternate let r = su.RangeAlternate
su.Symbol.ToString(), (r.StartLine, r.StartColumn, r.EndLine, r.EndColumn)) su.Symbol.ToString(), (r.StartLine, r.StartColumn, r.EndLine, r.EndColumn))
|> Array.distinct
|> shouldEqual |> shouldEqual
[|("ConsoleKey", (5, 10, 5, 20)) // note: these "System" sysbol uses are not duplications because each of them corresponts to different namespaces
("field Tab", (5, 10, 5, 24)) [|("System", (2, 5, 2, 11))
("ConsoleKey", (6, 6, 6, 16)) ("ConsoleKey", (5, 10, 5, 20));
("field OemClear", (6, 6, 6, 25)) ("field Tab", (5, 10, 5, 24));
("ConsoleKey", (6, 29, 6, 39)) ("ConsoleKey", (6, 6, 6, 16));
("field A", (6, 29, 6, 41)) ("field OemClear", (6, 6, 6, 25));
("ConsoleKey", (7, 11, 7, 21)) ("ConsoleKey", (6, 29, 6, 39));
("field B", (7, 11, 7, 23)) ("field A", (6, 29, 6, 41));
("ConsoleKey", (7, 11, 7, 21));
("field B", (7, 11, 7, 23));
("Test", (1, 0, 1, 0))|] ("Test", (1, 0, 1, 0))|]
[<Test>] [<Test>]
......
...@@ -368,10 +368,11 @@ let ``Test project1 all uses of all signature symbols`` () = ...@@ -368,10 +368,11 @@ let ``Test project1 all uses of all signature symbols`` () =
("field DisableFormatting", ("field DisableFormatting",
[("file2", ((28, 4), (28, 21))); ("file2", ((30, 16), (30, 45)))]); [("file2", ((28, 4), (28, 21))); ("file2", ((30, 16), (30, 45)))]);
("M", ("M",
[("file1", ((1, 7), (1, 8))); ("file2", ((6, 28), (6, 29))); [("file1", ((1, 7), (1, 8))); ("file2", ((3, 5), (3, 6)));
("file2", ((9, 28), (9, 29))); ("file2", ((12, 27), (12, 28))); ("file2", ((6, 28), (6, 29))); ("file2", ((9, 28), (9, 29)));
("file2", ((38, 12), (38, 13))); ("file2", ((38, 22), (38, 23))); ("file2", ((12, 27), (12, 28))); ("file2", ((38, 12), (38, 13)));
("file2", ((39, 12), (39, 13))); ("file2", ((39, 28), (39, 29)))]); ("file2", ((38, 22), (38, 23))); ("file2", ((39, 12), (39, 13)));
("file2", ((39, 28), (39, 29)))])
("val xxx", ("val xxx",
[("file1", ((6, 4), (6, 7))); ("file1", ((7, 13), (7, 16))); [("file1", ((6, 4), (6, 7))); ("file1", ((7, 13), (7, 16)));
("file1", ((7, 19), (7, 22))); ("file2", ((6, 28), (6, 33))); ("file1", ((7, 19), (7, 22))); ("file2", ((6, 28), (6, 33)));
...@@ -420,6 +421,7 @@ let ``Test project1 all uses of all symbols`` () = ...@@ -420,6 +421,7 @@ let ``Test project1 all uses of all symbols`` () =
("C", "M.C", "file1", ((9, 15), (9, 16)), ["class"]); ("C", "M.C", "file1", ((9, 15), (9, 16)), ["class"]);
("CAbbrev", "M.CAbbrev", "file1", ((9, 5), (9, 12)), ["abbrev"]); ("CAbbrev", "M.CAbbrev", "file1", ((9, 5), (9, 12)), ["abbrev"]);
("M", "M", "file1", ((1, 7), (1, 8)), ["module"]); ("M", "M", "file1", ((1, 7), (1, 8)), ["module"]);
("M", "M", "file2", ((3, 5), (3, 6)), ["module"]);
("D1", "N.D1", "file2", ((5, 5), (5, 7)), ["class"]); ("D1", "N.D1", "file2", ((5, 5), (5, 7)), ["class"]);
("( .ctor )", "N.D1.( .ctor )", "file2", ((5, 5), (5, 7)), ("( .ctor )", "N.D1.( .ctor )", "file2", ((5, 5), (5, 7)),
["member"; "ctor"]); ["member"; "ctor"]);
...@@ -3686,32 +3688,33 @@ let ``Test Project25 symbol uses of type-provided members`` () = ...@@ -3686,32 +3688,33 @@ let ``Test Project25 symbol uses of type-provided members`` () =
allUses |> shouldEqual allUses |> shouldEqual
[|("FSharp.Data.XmlProvider", "file1", ((4, 15), (4, 26)), [|("FSharp.Data", "file1", ((3, 5), (3, 16)), ["namespace"; "provided"]);
["class"; "provided"; "erased"]); ("Microsoft.FSharp.Data", "file1", ((3, 5), (3, 16)), ["namespace"]);
("FSharp.Data.XmlProvider", "file1", ((4, 15), (4, 26)), ("FSharp.Data.XmlProvider", "file1", ((4, 15), (4, 26)),
["class"; "provided"; "erased"]); ["class"; "provided"; "erased"]);
("FSharp.Data.XmlProvider", "file1", ((4, 15), (4, 26)), ("FSharp.Data.XmlProvider", "file1", ((4, 15), (4, 26)),
["class"; "provided"; "erased"]); ["class"; "provided"; "erased"]);
("FSharp.Data.XmlProvider", "file1", ((4, 15), (4, 26)), ("FSharp.Data.XmlProvider", "file1", ((4, 15), (4, 26)),
["class"; "provided"; "erased"]); ["class"; "provided"; "erased"]);
("TypeProviderTests.Project", "file1", ((4, 5), (4, 12)), ["abbrev"]); ("FSharp.Data.XmlProvider", "file1", ((4, 15), (4, 26)),
("TypeProviderTests.Project", "file1", ((5, 8), (5, 15)), ["abbrev"]); ["class"; "provided"; "erased"]);
("FSharp.Data.XmlProvider<...>.GetSample", "file1", ((5, 8), (5, 25)), ("TypeProviderTests.Project", "file1", ((4, 5), (4, 12)), ["abbrev"]);
["member"]); ("TypeProviderTests.Project", "file1", ((5, 8), (5, 15)), ["abbrev"]);
("Microsoft.FSharp.Core.int", "file1", ((7, 23), (7, 26)), ["abbrev"]); ("FSharp.Data.XmlProvider<...>.GetSample", "file1", ((5, 8), (5, 25)),
("Microsoft.FSharp.Core.int", "file1", ((7, 23), (7, 26)), ["abbrev"]); ["member"]);
("TypeProviderTests.Record.Field", "file1", ((7, 16), (7, 21)), ["field"]); ("Microsoft.FSharp.Core.int", "file1", ((7, 23), (7, 26)), ["abbrev"]);
("TypeProviderTests.Record", "file1", ((7, 5), (7, 11)), ["record"]); ("Microsoft.FSharp.Core.int", "file1", ((7, 23), (7, 26)), ["abbrev"]);
("TypeProviderTests.Record", "file1", ((8, 10), (8, 16)), ["record"]); ("TypeProviderTests.Record.Field", "file1", ((7, 16), (7, 21)), ["field"]);
("TypeProviderTests.Record.Field", "file1", ((8, 17), (8, 22)), ["field"]); ("TypeProviderTests.Record", "file1", ((7, 5), (7, 11)), ["record"]);
("TypeProviderTests.r", "file1", ((8, 4), (8, 5)), ["val"]); ("TypeProviderTests.Record", "file1", ((8, 10), (8, 16)), ["record"]);
("FSharp.Data.XmlProvider", "file1", ((10, 8), (10, 19)), ("TypeProviderTests.Record.Field", "file1", ((8, 17), (8, 22)), ["field"]);
["class"; "provided"; "erased"]); ("TypeProviderTests.r", "file1", ((8, 4), (8, 5)), ["val"]);
("FSharp.Data.XmlProvider<...>", "file1", ((10, 8), (10, 68)), ("FSharp.Data.XmlProvider", "file1", ((10, 8), (10, 19)),
["class"; "provided"; "staticinst"; "erased"]); ["class"; "provided"; "erased"]);
("FSharp.Data.XmlProvider<...>.GetSample", "file1", ((10, 8), (10, 78)), ("FSharp.Data.XmlProvider<...>", "file1", ((10, 8), (10, 68)),
["member"]); ["class"; "provided"; "staticinst"; "erased"]);
("TypeProviderTests", "file1", ((2, 7), (2, 24)), ["module"])|] ("FSharp.Data.XmlProvider<...>.GetSample", "file1", ((10, 8), (10, 78)),
["member"]); ("TypeProviderTests", "file1", ((2, 7), (2, 24)), ["module"])|]
let getSampleSymbolUseOpt = let getSampleSymbolUseOpt =
backgroundTypedParse1.GetSymbolUseAtLocation(5,25,"",["GetSample"]) backgroundTypedParse1.GetSymbolUseAtLocation(5,25,"",["GetSample"])
|> Async.RunSynchronously |> Async.RunSynchronously
......
...@@ -35,7 +35,7 @@ type internal UnusedDeclarationsAnalyzer() = ...@@ -35,7 +35,7 @@ type internal UnusedDeclarationsAnalyzer() =
match symbol with match symbol with
// Determining that a record, DU or module is used anywhere requires inspecting all their enclosed entities (fields, cases and func / vals) // Determining that a record, DU or module is used anywhere requires inspecting all their enclosed entities (fields, cases and func / vals)
// for usages, which is too expensive to do. Hence we never gray them out. // for usages, which is too expensive to do. Hence we never gray them out.
| :? FSharpEntity as e when e.IsFSharpRecord || e.IsFSharpUnion || e.IsInterface || e.IsFSharpModule || e.IsClass -> false | :? FSharpEntity as e when e.IsFSharpRecord || e.IsFSharpUnion || e.IsInterface || e.IsFSharpModule || e.IsClass || e.IsNamespace -> false
// FCS returns inconsistent results for override members; we're skipping these symbols. // FCS returns inconsistent results for override members; we're skipping these symbols.
| :? FSharpMemberOrFunctionOrValue as f when | :? FSharpMemberOrFunctionOrValue as f when
f.IsOverrideOrExplicitInterfaceImplementation || f.IsOverrideOrExplicitInterfaceImplementation ||
......
...@@ -15,129 +15,7 @@ open Microsoft.FSharp.Compiler ...@@ -15,129 +15,7 @@ open Microsoft.FSharp.Compiler
open Microsoft.FSharp.Compiler.Ast open Microsoft.FSharp.Compiler.Ast
open Microsoft.FSharp.Compiler.Range open Microsoft.FSharp.Compiler.Range
open Microsoft.FSharp.Compiler.SourceCodeServices open Microsoft.FSharp.Compiler.SourceCodeServices
open Symbols open Microsoft.VisualStudio.FSharp.Editor.Symbols
module private UnusedOpens =
let rec visitSynModuleOrNamespaceDecls (parent: Ast.LongIdent) decls : (Set<string> * range) list =
[ for decl in decls do
match decl with
| SynModuleDecl.Open(LongIdentWithDots.LongIdentWithDots(id = longId), range) ->
yield
set [ yield (longId |> List.map(fun l -> l.idText) |> String.concat ".")
// `open N.M` can open N.M module from parent module as well, if it's non empty
if not (List.isEmpty parent) then
yield (parent @ longId |> List.map(fun l -> l.idText) |> String.concat ".") ], range
| SynModuleDecl.NestedModule(SynComponentInfo.ComponentInfo(longId = longId),_, decls,_,_) ->
yield! visitSynModuleOrNamespaceDecls longId decls
| _ -> () ]
let getOpenStatements = function
| ParsedInput.ImplFile (ParsedImplFileInput(modules = modules)) ->
[ for md in modules do
let SynModuleOrNamespace(longId = longId; decls = decls) = md
yield! visitSynModuleOrNamespaceDecls longId decls ]
| _ -> []
let getAutoOpenAccessPath (ent:FSharpEntity) =
// Some.Namespace+AutoOpenedModule+Entity
// HACK: I can't see a way to get the EnclosingEntity of an Entity
// Some.Namespace + Some.Namespace.AutoOpenedModule are both valid
ent.TryFullName |> Option.bind(fun _ ->
if (not ent.IsNamespace) && ent.QualifiedName.Contains "+" then
Some ent.QualifiedName.[0..ent.QualifiedName.IndexOf "+" - 1]
else
None)
let entityNamespace (entOpt: FSharpEntity option) =
match entOpt with
| Some ent ->
if ent.IsFSharpModule then
[ yield Some ent.QualifiedName
yield Some ent.LogicalName
yield Some ent.AccessPath
yield Some ent.FullName
yield Some ent.DisplayName
yield ent.TryGetFullDisplayName()
if ent.HasFSharpModuleSuffix then
yield Some (ent.AccessPath + "." + ent.DisplayName)]
else
[ yield ent.Namespace
yield Some ent.AccessPath
yield getAutoOpenAccessPath ent
for path in ent.AllCompilationPaths do
yield Some path
]
| None -> []
let symbolIsFullyQualified (sourceText: SourceText) (sym: FSharpSymbolUse) (fullName: string) =
match RoslynHelpers.TryFSharpRangeToTextSpan(sourceText, sym.RangeAlternate) with
| Some span // check that the symbol hasn't provided an invalid span
when sourceText.Length < span.Start
|| sourceText.Length < span.End -> false
| Some span -> sourceText.ToString span = fullName
| None -> false
let getUnusedOpens (sourceText: SourceText) (parsedInput: ParsedInput) (symbolUses: FSharpSymbolUse[]) =
let getPartNamespace (symbolUse: FSharpSymbolUse) (fullName: string) =
// given a symbol range such as `Text.ISegment` and a full name of `MonoDevelop.Core.Text.ISegment`, return `MonoDevelop.Core`
let length = symbolUse.RangeAlternate.EndColumn - symbolUse.RangeAlternate.StartColumn
let lengthDiff = fullName.Length - length - 2
if lengthDiff <= 0 || lengthDiff > fullName.Length - 1 then None
else Some fullName.[0..lengthDiff]
let getPossibleNamespaces (symbolUse: FSharpSymbolUse) : string list =
let isQualified = symbolIsFullyQualified sourceText symbolUse
maybe {
let! fullNames, declaringEntity =
match symbolUse with
| SymbolUse.Entity (ent, cleanFullNames) when not (cleanFullNames |> List.exists isQualified) ->
Some (cleanFullNames, Some ent)
| SymbolUse.Field f when not (isQualified f.FullName) ->
Some ([f.FullName], Some f.DeclaringEntity)
| SymbolUse.MemberFunctionOrValue mfv when not (isQualified mfv.FullName) ->
Some ([mfv.FullName], mfv.EnclosingEntity)
| SymbolUse.Operator op when not (isQualified op.FullName) ->
Some ([op.FullName], op.EnclosingEntity)
| SymbolUse.ActivePattern ap when not (isQualified ap.FullName) ->
Some ([ap.FullName], ap.EnclosingEntity)
| SymbolUse.ActivePatternCase apc when not (isQualified apc.FullName) ->
Some ([apc.FullName], apc.Group.EnclosingEntity)
| SymbolUse.UnionCase uc when not (isQualified uc.FullName) ->
Some ([uc.FullName], Some uc.ReturnType.TypeDefinition)
| SymbolUse.Parameter p when not (isQualified p.FullName) && p.Type.HasTypeDefinition ->
Some ([p.FullName], Some p.Type.TypeDefinition)
| _ -> None
return
[ for name in fullNames do
yield getPartNamespace symbolUse name
yield! entityNamespace declaringEntity ]
} |> Option.toList |> List.concat |> List.choose id
let namespacesInUse =
symbolUses
|> Seq.filter (fun (s: FSharpSymbolUse) -> not s.IsFromDefinition)
|> Seq.collect getPossibleNamespaces
|> Set.ofSeq
let filter list: (Set<string> * range) list =
let rec filterInner acc list (seenNamespaces: Set<string>) =
let notUsed ns = not (namespacesInUse.Contains ns) || seenNamespaces.Contains ns
match list with
| (ns, range) :: xs when ns |> Set.forall notUsed ->
filterInner ((ns, range) :: acc) xs (seenNamespaces |> Set.union ns)
| (ns, _) :: xs ->
filterInner acc xs (seenNamespaces |> Set.union ns)
| [] -> List.rev acc
filterInner [] list Set.empty
let openStatements = getOpenStatements parsedInput
openStatements |> filter |> List.map snd
[<DiagnosticAnalyzer(FSharpConstants.FSharpLanguageName)>] [<DiagnosticAnalyzer(FSharpConstants.FSharpLanguageName)>]
type internal UnusedOpensDiagnosticAnalyzer() = type internal UnusedOpensDiagnosticAnalyzer() =
...@@ -160,13 +38,19 @@ type internal UnusedOpensDiagnosticAnalyzer() = ...@@ -160,13 +38,19 @@ type internal UnusedOpensDiagnosticAnalyzer() =
override __.SupportedDiagnostics = ImmutableArray.Create Descriptor override __.SupportedDiagnostics = ImmutableArray.Create Descriptor
override this.AnalyzeSyntaxAsync(_, _) = Task.FromResult ImmutableArray<Diagnostic>.Empty override this.AnalyzeSyntaxAsync(_, _) = Task.FromResult ImmutableArray<Diagnostic>.Empty
static member GetUnusedOpenRanges(document: Document, options, checker: FSharpChecker) = static member GetUnusedOpenRanges(document: Document, options, checker: FSharpChecker) : Async<Option<range list>> =
asyncMaybe { asyncMaybe {
do! Option.guard Settings.CodeFixes.UnusedOpens do! Option.guard Settings.CodeFixes.UnusedOpens
let! sourceText = document.GetTextAsync() let! sourceText = document.GetTextAsync()
let! _, parsedInput, checkResults = checker.ParseAndCheckDocument(document, options, sourceText = sourceText, allowStaleResults = true, userOpName = userOpName) let! _, _, checkResults = checker.ParseAndCheckDocument(document, options, sourceText = sourceText, allowStaleResults = true, userOpName = userOpName)
let! symbolUses = checkResults.GetAllUsesOfAllSymbolsInFile() |> liftAsync #if DEBUG
return UnusedOpens.getUnusedOpens sourceText parsedInput symbolUses let sw = Stopwatch.StartNew()
#endif
let! unusedOpens = UnusedOpens.getUnusedOpens(checkResults, fun lineNumber -> sourceText.Lines.[Line.toZ lineNumber].ToString()) |> liftAsync
#if DEBUG
Logging.Logging.logInfof "*** Got %d unused opens in %O" unusedOpens.Length sw.Elapsed
#endif
return unusedOpens
} }
override this.AnalyzeSemanticsAsync(document: Document, cancellationToken: CancellationToken) = override this.AnalyzeSemanticsAsync(document: Document, cancellationToken: CancellationToken) =
...@@ -180,10 +64,10 @@ type internal UnusedOpensDiagnosticAnalyzer() = ...@@ -180,10 +64,10 @@ type internal UnusedOpensDiagnosticAnalyzer() =
return return
unusedOpens unusedOpens
|> List.map (fun m -> |> List.map (fun range ->
Diagnostic.Create( Diagnostic.Create(
Descriptor, Descriptor,
RoslynHelpers.RangeToLocation(m, sourceText, document.FilePath))) RoslynHelpers.RangeToLocation(range, sourceText, document.FilePath)))
|> Seq.toImmutableArray |> Seq.toImmutableArray
} }
|> Async.map (Option.defaultValue ImmutableArray.Empty) |> Async.map (Option.defaultValue ImmutableArray.Empty)
......
...@@ -53,7 +53,6 @@ ...@@ -53,7 +53,6 @@
<Compile Include="Options\EditorOptions.fs" /> <Compile Include="Options\EditorOptions.fs" />
<Compile Include="LanguageService\Tokenizer.fs" /> <Compile Include="LanguageService\Tokenizer.fs" />
<Compile Include="LanguageService\Symbols.fs" /> <Compile Include="LanguageService\Symbols.fs" />
<Compile Include="LanguageService\TypedAstUtils.fs" />
<Compile Include="LanguageService\FSharpCheckerExtensions.fs" /> <Compile Include="LanguageService\FSharpCheckerExtensions.fs" />
<Compile Include="LanguageService\LanguageService.fs" /> <Compile Include="LanguageService\LanguageService.fs" />
<Compile Include="LanguageService\AssemblyContentProvider.fs" /> <Compile Include="LanguageService\AssemblyContentProvider.fs" />
......
...@@ -8,7 +8,6 @@ open Microsoft.CodeAnalysis.Text ...@@ -8,7 +8,6 @@ open Microsoft.CodeAnalysis.Text
open Microsoft.FSharp.Compiler open Microsoft.FSharp.Compiler
open Microsoft.FSharp.Compiler.Ast open Microsoft.FSharp.Compiler.Ast
open Microsoft.FSharp.Compiler.SourceCodeServices open Microsoft.FSharp.Compiler.SourceCodeServices
open TypedAstUtils
type CheckResults = type CheckResults =
| Ready of (FSharpParseFileResults * FSharpCheckFileResults) option | Ready of (FSharpParseFileResults * FSharpCheckFileResults) option
...@@ -108,7 +107,7 @@ type FSharpChecker with ...@@ -108,7 +107,7 @@ type FSharpChecker with
match symbolUse.Symbol with match symbolUse.Symbol with
// Make sure that unsafe manipulation isn't executed if unused opens are disabled // Make sure that unsafe manipulation isn't executed if unused opens are disabled
| _ when not checkForUnusedOpens -> None | _ when not checkForUnusedOpens -> None
| TypedAstPatterns.MemberFunctionOrValue func when func.IsExtensionMember -> | Symbol.MemberFunctionOrValue func when func.IsExtensionMember ->
if func.IsProperty then if func.IsProperty then
let fullNames = let fullNames =
[| if func.HasGetterMethod then [| if func.HasGetterMethod then
...@@ -125,9 +124,9 @@ type FSharpChecker with ...@@ -125,9 +124,9 @@ type FSharpChecker with
| [||] -> None | [||] -> None
| _ -> Some fullNames | _ -> Some fullNames
else else
match func.EnclosingEntity.Value with match func.EnclosingEntity with
// C# extension method // C# extension method
| TypedAstPatterns.FSharpEntity TypedAstPatterns.Class -> | Some (Symbol.FSharpEntity Symbol.Class) ->
let fullName = symbolUse.Symbol.FullName.Split '.' let fullName = symbolUse.Symbol.FullName.Split '.'
if fullName.Length > 2 then if fullName.Length > 2 then
(* For C# extension methods FCS returns full name including the class name, like: (* For C# extension methods FCS returns full name including the class name, like:
...@@ -142,9 +141,9 @@ type FSharpChecker with ...@@ -142,9 +141,9 @@ type FSharpChecker with
else None else None
| _ -> None | _ -> None
// Operators // Operators
| TypedAstPatterns.MemberFunctionOrValue func -> | Symbol.MemberFunctionOrValue func ->
match func with match func with
| TypedAstPatterns.Constructor _ -> | Symbol.Constructor _ ->
// full name of a constructor looks like "UnusedSymbolClassifierTests.PrivateClass.( .ctor )" // full name of a constructor looks like "UnusedSymbolClassifierTests.PrivateClass.( .ctor )"
// to make well formed full name parts we cut "( .ctor )" from the tail. // to make well formed full name parts we cut "( .ctor )" from the tail.
let fullName = func.FullName let fullName = func.FullName
...@@ -160,16 +159,16 @@ type FSharpChecker with ...@@ -160,16 +159,16 @@ type FSharpChecker with
| Some idents -> yield String.concat "." idents | Some idents -> yield String.concat "." idents
| None -> () | None -> ()
|] |]
| TypedAstPatterns.FSharpEntity e -> | Symbol.FSharpEntity e ->
match e with match e with
| e, TypedAstPatterns.Attribute, _ -> | e, Symbol.Attribute, _ ->
e.TryGetFullName () e.TryGetFullName ()
|> Option.map (fun fullName -> |> Option.map (fun fullName ->
[| fullName; fullName.Substring(0, fullName.Length - "Attribute".Length) |]) [| fullName; fullName.Substring(0, fullName.Length - "Attribute".Length) |])
| e, _, _ -> | e, _, _ ->
e.TryGetFullName () |> Option.map (fun fullName -> [| fullName |]) e.TryGetFullName () |> Option.map (fun fullName -> [| fullName |])
| TypedAstPatterns.RecordField _ | Symbol.RecordField _
| TypedAstPatterns.UnionCase _ as symbol -> | Symbol.UnionCase _ as symbol ->
Some [| let fullName = symbol.FullName Some [| let fullName = symbol.FullName
yield fullName yield fullName
let idents = fullName.Split '.' let idents = fullName.Split '.'
......
...@@ -130,218 +130,4 @@ type FSharpEntity with ...@@ -130,218 +130,4 @@ type FSharpEntity with
| _ -> () | _ -> ()
| _ -> () | _ -> ()
] ]
allBaseTypes x allBaseTypes x
\ No newline at end of file
/// Active patterns over `FSharpSymbolUse`.
module SymbolUse =
let (|ActivePatternCase|_|) (symbol : FSharpSymbolUse) =
match symbol.Symbol with
| :? FSharpActivePatternCase as ap-> ActivePatternCase(ap) |> Some
| _ -> None
let private attributeSuffixLength = "Attribute".Length
let (|Entity|_|) (symbol : FSharpSymbolUse) : (FSharpEntity * (* cleanFullNames *) string list) option =
match symbol.Symbol with
| :? FSharpEntity as ent ->
// strip generic parameters count suffix (List`1 => List)
let cleanFullName =
// `TryFullName` for type aliases is always `None`, so we have to make one by our own
if ent.IsFSharpAbbreviation then
[ent.AccessPath + "." + ent.DisplayName]
else
ent.TryFullName
|> Option.toList
|> List.map (fun fullName ->
if ent.GenericParameters.Count > 0 && fullName.Length > 2 then
fullName.[0..fullName.Length - 3]
else fullName)
let cleanFullNames =
cleanFullName
|> List.collect (fun cleanFullName ->
if ent.IsAttributeType then
[cleanFullName; cleanFullName.[0..cleanFullName.Length - attributeSuffixLength - 1]]
else [cleanFullName]
)
Some (ent, cleanFullNames)
| _ -> None
let (|Field|_|) (symbol : FSharpSymbolUse) =
match symbol.Symbol with
| :? FSharpField as field-> Some field
| _ -> None
let (|GenericParameter|_|) (symbol: FSharpSymbolUse) =
match symbol.Symbol with
| :? FSharpGenericParameter as gp -> Some gp
| _ -> None
let (|MemberFunctionOrValue|_|) (symbol : FSharpSymbolUse) =
match symbol.Symbol with
| :? FSharpMemberOrFunctionOrValue as func -> Some func
| _ -> None
let (|ActivePattern|_|) = function
| MemberFunctionOrValue m when m.IsActivePattern -> Some m | _ -> None
let (|Parameter|_|) (symbol : FSharpSymbolUse) =
match symbol.Symbol with
| :? FSharpParameter as param -> Some param
| _ -> None
let (|StaticParameter|_|) (symbol : FSharpSymbolUse) =
match symbol.Symbol with
| :? FSharpStaticParameter as sp -> Some sp
| _ -> None
let (|UnionCase|_|) (symbol : FSharpSymbolUse) =
match symbol.Symbol with
| :? FSharpUnionCase as uc-> Some uc
| _ -> None
//let (|Constructor|_|) = function
// | MemberFunctionOrValue func when func.IsConstructor || func.IsImplicitConstructor -> Some func
// | _ -> None
let (|TypeAbbreviation|_|) = function
| Entity (entity, _) when entity.IsFSharpAbbreviation -> Some entity
| _ -> None
let (|Class|_|) = function
| Entity (entity, _) when entity.IsClass -> Some entity
| Entity (entity, _) when entity.IsFSharp &&
entity.IsOpaque &&
not entity.IsFSharpModule &&
not entity.IsNamespace &&
not entity.IsDelegate &&
not entity.IsFSharpUnion &&
not entity.IsFSharpRecord &&
not entity.IsInterface &&
not entity.IsValueType -> Some entity
| _ -> None
let (|Delegate|_|) = function
| Entity (entity, _) when entity.IsDelegate -> Some entity
| _ -> None
let (|Event|_|) = function
| MemberFunctionOrValue symbol when symbol.IsEvent -> Some symbol
| _ -> None
let (|Property|_|) = function
| MemberFunctionOrValue symbol when symbol.IsProperty || symbol.IsPropertyGetterMethod || symbol.IsPropertySetterMethod -> Some symbol
| _ -> None
let inline private notCtorOrProp (symbol:FSharpMemberOrFunctionOrValue) =
not symbol.IsConstructor && not symbol.IsPropertyGetterMethod && not symbol.IsPropertySetterMethod
let (|Method|_|) (symbolUse:FSharpSymbolUse) =
match symbolUse with
| MemberFunctionOrValue symbol when
symbol.IsModuleValueOrMember &&
not symbolUse.IsFromPattern &&
not symbol.IsOperatorOrActivePattern &&
not symbol.IsPropertyGetterMethod &&
not symbol.IsPropertySetterMethod -> Some symbol
| _ -> None
let (|Function|_|) (symbolUse:FSharpSymbolUse) =
match symbolUse with
| MemberFunctionOrValue symbol when
notCtorOrProp symbol &&
symbol.IsModuleValueOrMember &&
not symbol.IsOperatorOrActivePattern &&
not symbolUse.IsFromPattern ->
match symbol.FullTypeSafe with
| Some fullType when fullType.IsFunctionType -> Some symbol
| _ -> None
| _ -> None
let (|Operator|_|) (symbolUse:FSharpSymbolUse) =
match symbolUse with
| MemberFunctionOrValue symbol when
notCtorOrProp symbol &&
not symbolUse.IsFromPattern &&
not symbol.IsActivePattern &&
symbol.IsOperatorOrActivePattern ->
match symbol.FullTypeSafe with
| Some fullType when fullType.IsFunctionType -> Some symbol
| _ -> None
| _ -> None
let (|Pattern|_|) (symbolUse:FSharpSymbolUse) =
match symbolUse with
| MemberFunctionOrValue symbol when
notCtorOrProp symbol &&
not symbol.IsOperatorOrActivePattern &&
symbolUse.IsFromPattern ->
match symbol.FullTypeSafe with
| Some fullType when fullType.IsFunctionType ->Some symbol
| _ -> None
| _ -> None
let (|ClosureOrNestedFunction|_|) = function
| MemberFunctionOrValue symbol when
notCtorOrProp symbol &&
not symbol.IsOperatorOrActivePattern &&
not symbol.IsModuleValueOrMember ->
match symbol.FullTypeSafe with
| Some fullType when fullType.IsFunctionType -> Some symbol
| _ -> None
| _ -> None
let (|Val|_|) = function
| MemberFunctionOrValue symbol when notCtorOrProp symbol &&
not symbol.IsOperatorOrActivePattern ->
match symbol.FullTypeSafe with
| Some _fullType -> Some symbol
| _ -> None
| _ -> None
let (|Enum|_|) = function
| Entity (entity, _) when entity.IsEnum -> Some entity
| _ -> None
let (|Interface|_|) = function
| Entity (entity, _) when entity.IsInterface -> Some entity
| _ -> None
let (|Module|_|) = function
| Entity (entity, _) when entity.IsFSharpModule -> Some entity
| _ -> None
let (|Namespace|_|) = function
| Entity (entity, _) when entity.IsNamespace -> Some entity
| _ -> None
let (|Record|_|) = function
| Entity (entity, _) when entity.IsFSharpRecord -> Some entity
| _ -> None
let (|Union|_|) = function
| Entity (entity, _) when entity.IsFSharpUnion -> Some entity
| _ -> None
let (|ValueType|_|) = function
| Entity (entity, _) when entity.IsValueType && not entity.IsEnum -> Some entity
| _ -> None
let (|ComputationExpression|_|) (symbol:FSharpSymbolUse) =
if symbol.IsFromComputationExpression then Some symbol
else None
let (|Attribute|_|) = function
| Entity (entity, _) when entity.IsAttributeType -> Some entity
| _ -> None
\ No newline at end of file
// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
[<NUnit.Framework.TestFixture>]
module Tests.ServiceAnalysis.UnusedOpens
open System
open NUnit.Framework
open Microsoft.FSharp.Compiler.SourceCodeServices
open Microsoft.FSharp.Compiler.Range
open FsUnit
let private filePath = "C:\\test.fs"
let private projectOptions : FSharpProjectOptions =
{ ProjectFileName = "C:\\test.fsproj"
SourceFiles = [| filePath |]
ReferencedProjects = [| |]
OtherOptions = [| |]
IsIncompleteTypeCheckEnvironment = true
UseScriptResolutionRules = false
LoadTime = DateTime.MaxValue
OriginalLoadReferences = []
UnresolvedReferences = None
ExtraProjectInfo = None
Stamp = None }
let private checker = FSharpChecker.Create()
let (=>) (source: string) (expectedRanges: ((*line*)int * ((*start column*)int * (*end column*)int)) list) =
let sourceLines = source.Split ([|"\r\n"; "\n"; "\r"|], StringSplitOptions.None)
let _, checkFileAnswer = checker.ParseAndCheckFileInProject(filePath, 0, source, projectOptions) |> Async.RunSynchronously
let checkFileResults =
match checkFileAnswer with
| FSharpCheckFileAnswer.Aborted -> failwithf "ParseAndCheckFileInProject aborted"
| FSharpCheckFileAnswer.Succeeded(checkFileResults) -> checkFileResults
let unusedOpenRanges = UnusedOpens.getUnusedOpens (checkFileResults, fun lineNum -> sourceLines.[Line.toZ lineNum]) |> Async.RunSynchronously
unusedOpenRanges
|> List.map (fun x -> x.StartLine, (x.StartColumn, x.EndColumn))
|> shouldEqual expectedRanges
[<Test>]
let ``unused open declaration in top level module``() =
"""
module TopModule
open System
open System.IO
let _ = DateTime.Now
"""
=> [ 4, (5, 14) ]
[<Test>]
let ``unused open declaration in namespace``() =
"""
namespace TopNamespace
open System
open System.IO
module Nested =
let _ = DateTime.Now
"""
=> [ 4, (5, 14) ]
[<Test>]
let ``unused open declaration in nested module``() =
"""
namespace TopNamespace
module Nested =
open System
open System.IO
let _ = DateTime.Now
"""
=> [ 5, (9, 18) ]
[<Test>]
let ``unused open declaration due to partially qualified symbol``() =
"""
module TopModule
open System
open System.IO
let _ = IO.File.Create ""
"""
=> [ 4, (5, 14) ]
[<Test>]
let ``unused parent open declaration due to partially qualified symbol``() =
"""
module TopModule
open System
open System.IO
let _ = File.Create ""
"""
=> [ 3, (5, 11) ]
[<Test>]
let ``open statement duplication in parent module is unused``() =
"""
module TopModule
open System.IO
module Nested =
open System.IO
let _ = File.Create ""
"""
=> [ 5, (9, 18) ]
[<Test>]
let ``open statement duplication in parent module is marked as unused even though it seems to be used in its scope``() =
"""
module TopModule
open System.IO
module Nested =
open System.IO
let _ = File.Create ""
let _ = File.Create ""
"""
=> [ 5, (9, 18) ]
[<Test>]
let ``multiple open declaration in the same line``() =
"""
open System.IO; let _ = File.Create "";; open System.IO
"""
=> [ 2, (46, 55) ]
[<Test>]
let ``open a nested module inside another one is not unused``() =
"""
module Top
module M1 =
let x = ()
module M2 =
open M1
let y = x
"""
=> []
[<Test>]
let ``open a nested module inside another one is not unused, complex hierarchy``() =
"""
module Top =
module M1 =
module M11 =
let x = ()
module M2 =
module M22 =
open M1.M11
let y = x
"""
=> []
[<Test>]
let ``open a nested module inside another one is not unused, even more complex hierarchy``() =
"""
module Top =
module M1 =
module M11 =
module M111 =
module M1111 =
let x = ()
module M2 =
module M22 =
open M1.M11.M111.M1111
let y = x
"""
=> []
[<Test>]
let ``opening auto open module after it's parent module was opened should be marked as unused``() =
"""
module NormalModule =
[<AutoOpen>]
module AutoOpenModule1 =
module NestedNormalModule =
[<AutoOpen>]
module AutoOpenModule2 =
[<AutoOpen>]
module AutoOpenModule3 =
type Class() = class end
open NormalModule.AutoOpenModule1.NestedNormalModule
open NormalModule.AutoOpenModule1.NestedNormalModule.AutoOpenModule2
let _ = Class()
"""
=> [ 13, (5, 68) ]
[<Test>]
let ``opening parent module after one of its auto open module was opened should be marked as unused``() =
"""
module NormalModule =
[<AutoOpen>]
module AutoOpenModule1 =
module NestedNormalModule =
[<AutoOpen>]
module AutoOpenModule2 =
[<AutoOpen>]
module AutoOpenModule3 =
type Class() = class end
open NormalModule.AutoOpenModule1.NestedNormalModule.AutoOpenModule2
open NormalModule.AutoOpenModule1.NestedNormalModule
let _ = Class()
"""
=> [ 13, (5, 52) ]
[<Test>]
let ``open declaration is not marked as unused if there is a shortened attribute symbol from it``() =
"""
open System
[<Serializable>]
type Class() = class end
"""
=> []
[<Test>]
let ``open declaration is not marked as unused if an extension property is used``() =
"""
module Module =
type System.String with
member __.ExtensionProperty = ()
open Module
let _ = "a long string".ExtensionProperty
"""
=> []
[<Test>]
let ``open declaration is marked as unused if an extension property is not used``() =
"""
module Module =
type System.String with
member __.ExtensionProperty = ()
open Module
let _ = "a long string".Trim()
"""
=> [ 5, (5, 11) ]
[<Test>]
let ``open declaration is not marked as unused if an extension method is used``() =
"""
type Class() = class end
module Module =
type Class with
member __.ExtensionMethod() = ()
open Module
let x = Class()
let _ = x.ExtensionMethod()
"""
=> []
[<Test>]
let ``open declaration is marked as unused if an extension method is not used``() =
"""
type Class() = class end
module Module =
type Class with
member __.ExtensionMethod() = ()
open Module
let x = Class()
"""
=> [ 6, (5, 11) ]
[<Test>]
let ``open declaration is not marked as unused if one of its types is used in a constructor signature``() =
"""
module M =
type Class() = class end
open M
type Site (x: Class -> unit) = class end
"""
=> []
[<Test>]
let ``open declaration is marked as unused if nothing from it is used``() =
"""
module M =
type Class() = class end
open M
type Site (x: int -> unit) = class end
"""
=> [ 4, (5, 6) ]
[<Test>]
let ``static extension method applied to a type results that both namespaces /where the type is declared and where the extension is declared/ is not marked as unused``() =
"""
module Extensions =
type System.DateTime with
static member ExtensionMethod() = ()
open System
open Extensions
let _ = DateTime.ExtensionMethod
"""
=> []
[<Test>]
let ``static extension property applied to a type results that both namespaces /where the type is declared and where the extension is declared/ is not marked as unused``() =
"""
module Extensions =
type System.DateTime with
static member ExtensionProperty = ()
open System
open Extensions
let _ = DateTime.ExtensionProperty
"""
=> []
[<Test>]
let ``accessing property on a variable should not force the namespace in which the type is declared to be marked as used``() =
"""
let dt = System.DateTime.Now
module M =
open System
let _ = dt.Hour
"""
=> [4, (9, 15) ]
[<Test>]
let ``either of two open declarations are not marked as unused if symbols from both of them are used``() =
"""
module M1 =
module M2 =
let func1 _ = ()
module M3 =
let func2 _ = ()
open M1.M2.M3
open M1.M2
let _ = func1()
let _ = func2()
"""
=> []
[<Test>]
let ``open module with ModuleSuffix attribute value applied is not marked as unused if a symbol declared in it is used``() =
"""
[<CompilationRepresentation (CompilationRepresentationFlags.ModuleSuffix)>]
module M =
let func _ = ()
open M
let _ = func()
"""
=> []
[<Test>]
let ``open module all of which symbols are used by qualifier is marked as unused``() =
"""
module M =
let func _ = ()
open M
let _ = M.func 1
"""
=> [4, (5, 6) ]
[<Test>]
let ``open module is not marked as unused if a symbol defined in it is used in OCaml-style type annotation``() =
"""
module M =
type Class() = class end
open M
let func (arg: Class list) = ()
"""
=> []
[<Test>]
let ``auto open module``() =
"""
module Top =
[<AutoOpen>]
module M =
let func _ = ()
open Top
let _ = func()
"""
=> []
[<Test>]
let ``auto open module in the middle of hierarchy``() =
"""
namespace Ns
module M1 =
[<AutoOpen>]
module MA1 =
let func _ = ()
open M1
module M2 =
let _ = func()
"""
=> []
[<Test>]
let ``open declaration is not marked as unused if a delegate defined in it is used``() =
"""
open System
let _ = Func<int, int>(fun _ -> 1)
"""
=> []
[<Test>]
let ``open declaration is not marked as unused if a unit of measure defined in it is used``() =
"""
module M =
type [<Measure>] m
module N =
open M
let _ = 1<m>
"""
=> []
[<Test>]
let ``open declaration is not marked as unused if an attribute defined in it is applied on an interface member argument``() =
"""
open System.Runtime.InteropServices
type T = abstract M: [<DefaultParameterValue(null)>] ?x: int -> unit
"""
=> []
[<Test>]
let ``relative module open declaration``() =
"""
module Top =
module Nested =
let x = 1
open Top
open Nested
let _ = x
"""
=> []
[<Test>]
let ``open declaration is used if a symbol defined in it is used in a module top-level do expression``() =
"""
module Top
open System.IO
File.ReadAllLines ""
|> ignore
"""
=> []
[<Test>]
let ``redundant opening a module with ModuleSuffix attribute value is marks as unused``() =
"""
[<CompilationRepresentation (CompilationRepresentationFlags.ModuleSuffix)>]
module InternalModuleWithSuffix =
let func1 _ = ()
module M =
open InternalModuleWithSuffix
let _ = InternalModuleWithSuffix.func1()
"""
=> [ 6, (9, 33) ]
[<Test>]
let ``redundant opening a module is marks as unused``() =
"""
module InternalModuleWithSuffix =
let func1 _ = ()
module M =
open InternalModuleWithSuffix
let _ = InternalModuleWithSuffix.func1()
"""
=> [ 5, (9, 33) ]
[<Test>]
let ``usage of an unqualified union case doesn't make an opening module where it's defined to be marked as unused``() =
"""
module M =
type DU = Case1
open M
let _ = Case1
"""
=> []
[<Test>]
let ``usage of qualified union case doesn't make an opening module where it's defined to be marked as unused``() =
"""
module M =
type DU = Case1
open M
let _ = DU.Case1
"""
=> []
[<Test>]
let ``type with different DisplayName``() =
"""
open Microsoft.FSharp.Quotations
let _ = Expr.Coerce (<@@ 1 @@>, typeof<int>)
"""
=> []
[<Test>]
let ``auto open module with ModuleSuffix attribute value``() =
"""
module Top =
[<AutoOpen; CompilationRepresentation (CompilationRepresentationFlags.ModuleSuffix)>]
module Module =
let func _ = ()
open Top
module Module1 =
let _ = func()
"""
=> []
[<Test>]
let ``a type which has more than one DisplayName causes the namespace it's defined in to be not marked as unused``() =
"""
open System
let _ = IntPtr.Zero
"""
=> []
[<Test>]
let ``usage of an operator makes the module it's defined in to be not marked as unused``() =
"""
module M =
let (++|) x y = ()
open M
let _ = 1 ++| 2
"""
=> []
[<Test>]
let ``usage of an operator makes the module /with Module suffix/ it's defined in to be not marked as unused``() =
"""
[<CompilationRepresentation (CompilationRepresentationFlags.ModuleSuffix)>]
module M =
let (++|) x y = ()
open M
let _ = 1 ++| 2
"""
=> []
[<Test>]
let ``type used in pattern matching with "as" keyword causes the module in which the type is defined to be not marked as unused``() =
"""
module M =
type Class() = class end
open M
let _ = match obj() with
| :? Class as c -> ()
| _ -> ()
"""
=> []
[<Test>]
let ``a function from printf family prevents Printf module from marking as unused``() =
"""
open Microsoft.FSharp.Core.Printf
open System.Text
let _ = bprintf (StringBuilder()) "%A" 1
"""
=> []
[<Test>]
let ``assembly level attribute prevents namespace in which it's defined to be marked as unused``() =
"""
open System
[<assembly: Version("1")>]
()
"""
=> []
[<Test>]
let ``open declaration is not marked as unused if a related type extension is used``() =
"""
module Module =
open System
type String with
member __.Method() = ()
"""
=> []
[<Test>]
let ``open declaration is not marked as unused if a symbol defined in it is used in type do block``() =
"""
open System.IO.Compression
type OutliningHint() as self =
do self.E.Add (fun (e: GZipStream) -> ())
member __.E: IEvent<_> = Unchecked.defaultof<_>
"""
=> []
[<Test>]
let ``should not mark open declaration with global prefix``() =
"""
module Module =
open global.System
let _ = String("")
"""
=> []
[<Test>]
let ``record fields should be taken into account``() =
"""
module M1 =
type Record = { Field: int }
module M2 =
open M1
let x = { Field = 0 }
"""
=> []
[<Test>]
let ``handle type alias``() =
"""
module TypeAlias =
type MyInt = int
module Usage =
open TypeAlias
let f (x:MyInt) = x
"""
=> []
[<Test>]
let ``handle override members``() =
"""
type IInterface =
abstract Property: int
type IClass() =
interface IInterface with
member __.Property = 0
let f (x: IClass) = (x :> IInterface).Property
"""
=> []
[<Test>]
let ``active pattern cases should be taken into account``() =
"""
module M =
let (|Pattern|_|) _ = Some()
open M
let f (Pattern _) = ()
"""
=> []
[<Test>]
let ``active patterns applied as a function should be taken into account``() =
"""
module M =
let (|Pattern|_|) _ = Some()
open M
let _ = (|Pattern|_|) ()
"""
=> []
[<Test>]
let ``not used active pattern does not make the module in which it's defined to not mark as unused``() =
"""
module M =
let (|Pattern|_|) _ = Some()
open M
let _ = 1
"""
=> [ 4, (5, 6) ]
[<Test>]
let ``type in type parameter constraint should be taken into account``() =
"""
open System
let f (x: 'a when 'a :> IDisposable) = ()
"""
=> []
[<Test>]
let ``namespace declaration should never be marked as unused``() =
"""
namespace Library2
type T() = class end
"""
=> []
[<Test>]
let ``auto open module opened before enclosing one is handled correctly``() =
"""
module M =
let x = 1
[<AutoOpen>]
module N =
let y = 2
open M.N
open M
let _ = x
let _ = y
"""
=> []
[<Test>]
let ``single relative open declaration opens two independent modules in different parent modules``() =
"""
module M =
module Xxx =
let x = 1
module N =
module Xxx =
let y = 1
open M
open N
open N.Xxx
open Xxx
let _ = y
let _ = x
"""
=> []
[<Test>]
let ``C# extension methods are taken into account``() =
"""
open System.Linq
module Test =
let xs = []
let _ = xs.ToList()
"""
=> []
[<Test>]
let ``namespace which contains types with C# extension methods is marked as unused if no extension is used``() =
"""
open System.Linq
module Test =
let xs = []
"""
=> [ 2, (5, 16) ]
...@@ -92,6 +92,9 @@ ...@@ -92,6 +92,9 @@
<Compile Include="..\..\..\tests\service\ProjectOptionsTests.fs"> <Compile Include="..\..\..\tests\service\ProjectOptionsTests.fs">
<Link>ProjectOptionsTests.fs</Link> <Link>ProjectOptionsTests.fs</Link>
</Compile> </Compile>
<Compile Include="UnusedOpensTests.fs">
<Link>ServiceAnalysis\UnusedOpensTests.fs</Link>
</Compile>
<Compile Include="ColorizationServiceTests.fs"> <Compile Include="ColorizationServiceTests.fs">
<Link>Roslyn\ColorizationServiceTests.fs</Link> <Link>Roslyn\ColorizationServiceTests.fs</Link>
</Compile> </Compile>
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册