未验证 提交 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>
<TargetFramework>netcoreapp1.0</TargetFramework>
<DefineConstants>$(DefineConstants);DOTNETCORE;FX_ATLEAST_45;FX_ATLEAST_PORTABLE;FX_NO_RUNTIMEENVIRONMENT;FX_RESHAPED_REFLECTION;TODO_REWORK_ASSEMBLY_LOAD;</DefineConstants>
......@@ -42,6 +42,7 @@
</ItemGroup>
<ItemGroup>
<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="NUnit" Version="3.6.1" />
<PackageReference Include="FSharp.Core" Version="4.1.*" PrivateAssets="All" />
......
......@@ -536,6 +536,12 @@
<Compile Include="$(FSharpSourcesRoot)\fsharp\symbols\Exprs.fs">
<Link>Symbols/Exprs.fs</Link>
</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">
<Link>Service/IncrementalBuild.fsi</Link>
</Compile>
......@@ -632,6 +638,12 @@
<Compile Include="$(FSharpSourcesRoot)\fsharp\vs\ServiceStructure.fs">
<Link>Service/ServiceStructure.fs</Link>
</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">
<Link>Service/fsi.fsi</Link>
</Compile>
......
......@@ -512,6 +512,12 @@
<Compile Include="$(FSharpSourcesRoot)\fsharp\symbols\Exprs.fs">
<Link>Symbols/Exprs.fs</Link>
</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">
<Link>Service/IncrementalBuild.fsi</Link>
</Compile>
......@@ -608,6 +614,12 @@
<Compile Include="$(FSharpSourcesRoot)\fsharp\vs\ServiceStructure.fs">
<Link>Service/ServiceStructure.fs</Link>
</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">
<Link>Service/fsi.fsi</Link>
</Compile>
......
......@@ -492,6 +492,12 @@
<Compile Include="$(FSharpSourcesRoot)\fsharp\symbols\Exprs.fs">
<Link>Symbols/Exprs.fs</Link>
</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 . -->
<Compile Include="$(FSharpSourcesRoot)\fsharp\vs\IncrementalBuild.fsi">
......@@ -590,6 +596,12 @@
<Compile Include="$(FSharpSourcesRoot)\fsharp\vs\ServiceStructure.fs">
<Link>Service/ServiceStructure.fs</Link>
</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 -->
<FsSrGen Include="$(FSharpSourcesRoot)\fsharp\fsi\FSIstrings.txt">
......
......@@ -546,6 +546,12 @@
<Compile Include="..\symbols\Exprs.fs">
<Link>Symbols/Exprs.fs</Link>
</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 . -->
<Compile Include="..\vs\IncrementalBuild.fsi">
......@@ -644,7 +650,13 @@
<Compile Include="..\vs\ServiceStructure.fs">
<Link>Service/ServiceStructure.fs</Link>
</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 -->
<EmbeddedText Include="..\fsi\FSIstrings.txt">
<Link>FSIstrings.txt</Link>
......
......@@ -1222,12 +1222,32 @@ type ItemOccurence =
/// Result gets suppressed over this text range
| 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.
type ITypecheckResultsSink =
abstract NotifyEnvWithScope : range * NameResolutionEnv * AccessorDomain -> 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 NotifyFormatSpecifierLocation : range * int -> unit
abstract NotifyOpenDeclaration : OpenDeclaration -> unit
abstract CurrentSource : string option
let (|ValRefOfProp|_|) (pi : PropInfo) = pi.ArbitraryValRef
......@@ -1460,6 +1480,7 @@ type TcResultsSinkImpl(g, ?source: string) =
member __.GetHashCode((p:pos,i)) = p.Line + 101 * p.Column + hash i
member __.Equals((p1,i1),(p2,i2)) = posEq p1 p2 && i1 = i2 } )
let capturedMethodGroupResolutions = ResizeArray<_>()
let capturedOpenDeclarations = ResizeArray<_>()
let allowedRange (m:range) = not m.IsSynthetic
member this.GetResolutions() =
......@@ -1468,6 +1489,8 @@ type TcResultsSinkImpl(g, ?source: string) =
member this.GetSymbolUses() =
TcSymbolUses(g, capturedNameResolutions, capturedFormatSpecifierLocations.ToArray())
member this.OpenDeclarations = Seq.toList capturedOpenDeclarations
interface ITypecheckResultsSink with
member sink.NotifyEnvWithScope(m,nenv,ad) =
if allowedRange m then
......@@ -1504,6 +1527,9 @@ type TcResultsSinkImpl(g, ?source: string) =
member sink.NotifyFormatSpecifierLocation(m, numArgs) =
capturedFormatSpecifierLocations.Add((m, numArgs))
member sink.NotifyOpenDeclaration(openDeclaration) =
capturedOpenDeclarations.Add(openDeclaration)
member sink.CurrentSource = source
......@@ -1550,6 +1576,11 @@ let CallExprHasTypeSink (sink:TcResultsSink) (m:range,nenv,typ,denv,ad) =
| None -> ()
| 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.
//-------------------------------------------------------------------------
......
......@@ -22,6 +22,9 @@ type NameResolver =
member amap : ImportMap
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>]
/// Represents the item with which a named argument is associated.
type ArgumentContainer =
......@@ -307,7 +310,26 @@ type internal TcSymbolUses =
/// Get the locations of all the printf format specifiers in the file
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
type ITypecheckResultsSink =
......@@ -323,6 +345,9 @@ type ITypecheckResultsSink =
/// Record that a printf format specifier occurred at a specific location in the source
abstract NotifyFormatSpecifierLocation : range * int -> unit
/// Record that an open declaration occured in a given scope range
abstract NotifyOpenDeclaration : OpenDeclaration -> unit
/// Get the current source
abstract CurrentSource : string option
......@@ -337,6 +362,10 @@ type internal TcResultsSinkImpl =
/// Get all the uses of all symbols reported to the sink
member GetSymbolUses : unit -> TcSymbolUses
/// Get all open declarations reported to the sink
member OpenDeclarations : OpenDeclaration list
interface ITypecheckResultsSink
/// 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
/// Report a specific name resolution at a source range
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)
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 =
// 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 =
if isNil mvvs then env else
ModifyNameResEnv (fun nenv -> AddModulesAndNamespacesContentsToNameEnv g amap env.eAccessRights scopem root nenv mvvs) env
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
let AddRootModuleOrNamespaceRefs g amap m env modrefs =
......@@ -691,7 +698,10 @@ let ImplicitlyOpenOwnNamespace tcSink g amap scopem enclosingNamespacePath env =
let ad = env.eAccessRights
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
......@@ -1867,6 +1877,7 @@ let MakeAndPublishSimpleVals cenv env m names mergeNamesInOneNameresEnv =
nameResolutions.Add(pos, item, itemGroup, itemTyparInst, occurence, denv, nenv, ad, m, replacing)
member this.NotifyExprHasType(_, _, _, _, _, _) = assert false // no expr typings in MakeSimpleVals
member this.NotifyFormatSpecifierLocation(_, _) = ()
member this.NotifyOpenDeclaration(_) = ()
member this.CurrentSource = None }
use _h = WithNewTypecheckResultsSink(sink, cenv.tcSink)
......@@ -12099,9 +12110,11 @@ let TcOpenDecl tcSink (g:TcGlobals) amap m scopem env (longId : Ident list) =
if IsPartiallyQualifiedNamespace modref then
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
......@@ -16866,7 +16879,9 @@ let ApplyAssemblyLevelAutoOpenAttributeToTcEnv g amap (ccu: CcuThunk) scopem env
let modref = mkNonLocalTyconRef (mkNonLocalEntityRef ccu (Array.ofList h)) t
match modref.TryDeref with
| 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
let AddCcuToTcEnv(g, amap, scopem, env, assemblyName, ccu, autoOpens, internalsVisible) =
......
module internal Microsoft.VisualStudio.FSharp.Editor.TypedAstUtils
open System
open Microsoft.FSharp.Compiler.SourceCodeServices
open Microsoft.VisualStudio.FSharp.Editor
open System.Text.RegularExpressions
let isAttribute<'T> (attribute: FSharpAttribute) =
// CompiledName throws exception on DataContractAttribute generated by SQLProvider
match Option.attempt (fun _ -> attribute.AttributeType.CompiledName) with
| Some name when name = typeof<'T>.Name -> true
| _ -> false
let tryGetAttribute<'T> (attributes: seq<FSharpAttribute>) =
attributes |> Seq.tryFind isAttribute<'T>
let hasModuleSuffixAttribute (entity: FSharpEntity) =
entity.Attributes
|> tryGetAttribute<CompilationRepresentationAttribute>
|> Option.bind (fun a ->
Option.attempt (fun _ -> a.ConstructorArguments)
|> 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
let isOperator (name: string) =
name.StartsWith "( " && name.EndsWith " )" && name.Length > 4
&& name.Substring (2, name.Length - 4)
|> String.forall (fun c -> c <> ' ' && not (Char.IsLetter c))
let private UnnamedUnionFieldRegex = Regex("^Item(\d+)?$", RegexOptions.Compiled)
// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.
namespace Microsoft.FSharp.Compiler.SourceCodeServices
/// Patterns over FSharpSymbol and derivatives.
[<RequireQualifiedAccess>]
module Symbol =
open System.Text.RegularExpressions
open System
let isAttribute<'T> (attribute: FSharpAttribute) =
// CompiledName throws exception on DataContractAttribute generated by SQLProvider
try attribute.AttributeType.CompiledName = typeof<'T>.Name with _ -> false
let tryGetAttribute<'T> (attributes: seq<FSharpAttribute>) =
attributes |> Seq.tryFind isAttribute<'T>
module Option =
let attempt f = try Some(f()) with _ -> None
let hasModuleSuffixAttribute (entity: FSharpEntity) =
entity.Attributes
|> tryGetAttribute<CompilationRepresentationAttribute>
|> Option.bind (fun a ->
Option.attempt (fun _ -> a.ConstructorArguments)
|> 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
let isOperator (name: string) =
name.StartsWith "( " && name.EndsWith " )" && name.Length > 4
&& 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)
module TypedAstPatterns =
let isUnnamedUnionCaseField (field: FSharpField) = UnnamedUnionFieldRegex.IsMatch(field.Name)
let (|AbbreviatedType|_|) (entity: FSharpEntity) =
if entity.IsFSharpAbbreviation then Some entity.AbbreviatedType
......@@ -76,21 +77,28 @@ module TypedAstPatterns =
match ty with
| None -> false
| Some ty ->
match ty.TryGetFullName() with
| None -> false
| Some fullName ->
fullName = "System.Attribute" || isAttributeType (getBaseType ty)
try ty.FullName = "System.Attribute" || isAttributeType (getBaseType ty)
with _ -> false
isAttributeType (Some entity)
if isAttribute entity then Some() else None
let hasAttribute<'T> (attributes: seq<FSharpAttribute>) =
attributes |> Seq.exists isAttribute<'T>
let (|ValueType|_|) (e: FSharpEntity) =
if e.IsEnum || e.IsValueType || hasAttribute<MeasureAnnotatedAbbreviationAttribute> e.Attributes then Some()
else None
#if EXTENSIONTYPING
let (|Class|_|) (original: FSharpEntity, abbreviated: FSharpEntity, _) =
if abbreviated.IsClass
&& (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
#endif
let (|Record|_|) (e: FSharpEntity) = if e.IsFSharpRecord then Some() else None
let (|UnionType|_|) (e: FSharpEntity) = if e.IsFSharpUnion then Some() else None
......@@ -106,17 +114,21 @@ module TypedAstPatterns =
|| (e.IsFSharp && e.IsOpaque && not e.IsFSharpModule && not e.IsNamespace) then Some()
else None
#if EXTENSIONTYPING
let (|ProvidedType|_|) (e: FSharpEntity) =
if (e.IsProvided || e.IsProvidedAndErased || e.IsProvidedAndGenerated) && e.CompiledName = e.DisplayName then
Some()
else None
#endif
let (|ByRef|_|) (e: FSharpEntity) = if e.IsByRef 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 (|Namespace|_|) (entity: FSharpEntity) = if entity.IsNamespace then Some() else None
#if EXTENSIONTYPING
let (|ProvidedAndErasedType|_|) (entity: FSharpEntity) = if entity.IsProvidedAndErased then Some() else None
#endif
let (|Enum|_|) (entity: FSharpEntity) = if entity.IsEnum then Some() else None
let (|Tuple|_|) (ty: FSharpType option) =
......@@ -190,17 +202,18 @@ module TypedAstPatterns =
/// Constructor (enclosingEntity)
let (|Constructor|_|) (func: FSharpMemberOrFunctionOrValue) =
match func.CompiledName with
| ".ctor" | ".cctor" -> Some func.EnclosingEntity
| ".ctor" | ".cctor" -> func.EnclosingEntity
| _ -> None
let (|Function|_|) excluded (func: FSharpMemberOrFunctionOrValue) =
match func.FullTypeSafe |> Option.map getAbbreviatedType with
| Some typ when typ.IsFunctionType
&& not func.IsPropertyGetterMethod
&& not func.IsPropertySetterMethod
&& not excluded
&& not (isOperator func.DisplayName) -> Some()
| _ -> None
try let typ = func.FullType |> getAbbreviatedType
if typ.IsFunctionType
&& not func.IsPropertyGetterMethod
&& not func.IsPropertySetterMethod
&& not excluded
&& not (isOperator func.DisplayName) then Some()
else None
with _ -> None
let (|ExtensionMember|_|) (func: FSharpMemberOrFunctionOrValue) =
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) =
#else
elif entity.IsTypeAbbrev then None
#endif
elif entity.IsNamespace then Some entity.DemangledModuleOrNamespaceName
elif entity.IsNamespace then Some entity.DemangledModuleOrNamespaceName
else
match entity.CompiledRepresentation with
| CompiledTypeRepr.ILAsmNamed(tref, _, _) -> Some tref.FullName
......@@ -619,6 +619,14 @@ and FSharpEntity(cenv:cenv, entity:EntityRef) =
yield! walkParts parts ]
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) =
box x === other ||
match other with
......@@ -2259,6 +2267,14 @@ type FSharpSymbol with
| :? FSharpMemberFunctionOrValue as x -> Some x.Accessibility
| _ -> None
/// Represents open declaration in F# code.
type FSharpOpenDeclaration =
{ LongId: Ident list
Range: range option
Modules: FSharpEntity list
AppliedScope: range
IsOwnNamespace: bool }
[<Sealed>]
type FSharpSymbolUse(g:TcGlobals, denv: DisplayEnv, symbol:FSharpSymbol, itemOcc, range: range) =
member __.Symbol = symbol
......
......@@ -7,6 +7,7 @@ open Microsoft.FSharp.Compiler
open Microsoft.FSharp.Compiler.AccessibilityLogic
open Microsoft.FSharp.Compiler.CompileOps
open Microsoft.FSharp.Compiler.Range
open Microsoft.FSharp.Compiler.Ast
open Microsoft.FSharp.Compiler.Tast
open Microsoft.FSharp.Compiler.TcGlobals
open Microsoft.FSharp.Compiler.NameResolution
......@@ -329,6 +330,9 @@ and [<Class>] internal FSharpEntity =
/// Get all compilation paths, taking `Module` suffixes into account.
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
#if COMPILER_PUBLIC_API
and [<Class>] FSharpDelegateSignature =
......@@ -1065,7 +1069,26 @@ and [<Class>] internal FSharpAttribute =
/// Format the attribute using the rules of the given display context
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
[<Sealed>]
......
......@@ -1028,6 +1028,7 @@ type TypeCheckAccumulator =
tcEnvAtEndOfFile: TcEnv
tcResolutions: TcResolutions list
tcSymbolUses: TcSymbolUses list
tcOpenDeclarations: OpenDeclaration list
topAttribs:TopAttribs option
typedImplFiles:TypedImplFile list
tcDependencyFiles: string list
......@@ -1102,6 +1103,7 @@ type PartialCheckResults =
Errors: (PhasedDiagnostic * FSharpErrorSeverity) list
TcResolutions: TcResolutions list
TcSymbolUses: TcSymbolUses list
TcOpenDeclarations: OpenDeclaration list
TcDependencyFiles: string list
TopAttribs: TopAttribs option
TimeStamp: System.DateTime
......@@ -1116,6 +1118,7 @@ type PartialCheckResults =
Errors = tcAcc.tcErrors
TcResolutions = tcAcc.tcResolutions
TcSymbolUses = tcAcc.tcSymbolUses
TcOpenDeclarations = tcAcc.tcOpenDeclarations
TcDependencyFiles = tcAcc.tcDependencyFiles
TopAttribs = tcAcc.topAttribs
TimeStamp = timestamp
......@@ -1327,6 +1330,7 @@ type IncrementalBuilder(tcGlobals, frameworkTcImports, nonFrameworkAssemblyInput
tcEnvAtEndOfFile=tcInitial
tcResolutions=[]
tcSymbolUses=[]
tcOpenDeclarations=[]
topAttribs=None
typedImplFiles=[]
tcDependencyFiles=basicDependencies
......@@ -1375,6 +1379,7 @@ type IncrementalBuilder(tcGlobals, frameworkTcImports, nonFrameworkAssemblyInput
typedImplFiles=typedImplFiles
tcResolutions=tcAcc.tcResolutions @ [tcResolutions]
tcSymbolUses=tcAcc.tcSymbolUses @ [tcSymbolUses]
tcOpenDeclarations=tcAcc.tcOpenDeclarations @ sink.OpenDeclarations
tcErrors = tcAcc.tcErrors @ parseErrors @ capturingErrorLogger.GetErrors()
tcDependencyFiles = filename :: tcAcc.tcDependencyFiles }
}
......
......@@ -54,6 +54,9 @@ type internal PartialCheckResults =
/// Represents the collected uses of symbols from type checking
TcSymbolUses: TcSymbolUses list
/// Represents open declarations
TcOpenDeclarations: OpenDeclaration list
TcDependencyFiles: string list
/// 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 =
type FSharpAssemblySignature with
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>]
type LookupType =
| Fuzzy
......@@ -186,7 +155,7 @@ type Parent =
else ident)
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]
if lastIdent.EndsWith "Module" then
idents |> Array.replace (idents.Length - 1) (lastIdent.Substring(0, lastIdent.Length - 6))
......@@ -202,18 +171,6 @@ type Parent =
|> removeGenericParamsCount
|> 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 =
{ FileWriteTime: DateTime
ContentType: AssemblyContentType
......@@ -239,15 +196,15 @@ module AssemblyContentProvider =
Symbol = entity
Kind = fun lookupType ->
match entity, lookupType with
| TypedAstPatterns.FSharpModule, _ ->
| Symbol.FSharpModule, _ ->
EntityKind.Module
{ IsAutoOpen = hasAttribute<AutoOpenAttribute> entity.Attributes
HasModuleSuffix = hasModuleSuffixAttribute entity }
{ IsAutoOpen = Symbol.hasAttribute<AutoOpenAttribute> entity.Attributes
HasModuleSuffix = Symbol.hasModuleSuffixAttribute entity }
| _, LookupType.Fuzzy ->
EntityKind.Type
| _, LookupType.Precise ->
match entity with
| TypedAstPatterns.Attribute -> EntityKind.Attribute
| Symbol.Attribute -> EntityKind.Attribute
| _ -> EntityKind.Type
})
......@@ -297,7 +254,7 @@ module AssemblyContentProvider =
| None -> ()
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
else None
......@@ -305,7 +262,7 @@ module AssemblyContentProvider =
{ ThisRequiresQualifiedAccess = thisRequiresQualifierAccess |> Option.orElse parent.ThisRequiresQualifiedAccess
TopRequiresQualifiedAccess = parent.TopRequiresQualifiedAccess |> Option.orElse thisRequiresQualifierAccess
AutoOpen =
let isAutoOpen = entity.IsFSharpModule && hasAttribute<AutoOpenAttribute> entity.Attributes
let isAutoOpen = entity.IsFSharpModule && Symbol.hasAttribute<AutoOpenAttribute> entity.Attributes
match isAutoOpen, parent.AutoOpen with
// if parent is also AutoOpen, then keep the parent
| true, Some parent -> Some parent
......@@ -315,7 +272,7 @@ module AssemblyContentProvider =
| false, _ -> None
WithModuleSuffix =
if entity.IsFSharpModule && hasModuleSuffixAttribute entity then
if entity.IsFSharpModule && Symbol.hasModuleSuffixAttribute entity then
currentEntity |> Option.map (fun e -> e.CleanedIdents)
else parent.WithModuleSuffix
Namespace = ns }
......
......@@ -255,10 +255,4 @@ module internal Extensions =
type FSharpAssemblySignature with
/// Safe version of `Entities`.
member TryGetEntities : unit -> seq<FSharpEntity>
/// 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
member TryGetEntities : unit -> seq<FSharpEntity>
\ No newline at end of file
......@@ -220,7 +220,7 @@ module internal InterfaceStubGenerator =
let nm, namesWithIndices = normalizeArgName namesWithIndices nm
// 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
(if hasTypeAnnotation && argName <> "()" then
argName + ": " + formatType ctx arg.Type
......@@ -295,7 +295,7 @@ module internal InterfaceStubGenerator =
else displayName
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 getParamArgs (argInfos: FSharpParameter list list) (ctx: Context) (v: FSharpMemberOrFunctionOrValue) =
......@@ -325,7 +325,7 @@ module internal InterfaceStubGenerator =
| _, true, _, name -> name + parArgs
// Ordinary functions or values
| false, _, _, name when
not (hasAttribute<RequireQualifiedAccessAttribute> v.LogicalEnclosingEntity.Attributes) ->
not (Symbol.hasAttribute<RequireQualifiedAccessAttribute> v.LogicalEnclosingEntity.Attributes) ->
name + " " + parArgs
// Ordinary static members or things (?) that require fully qualified access
| _, _, _, name -> name + parArgs
......
......@@ -165,7 +165,8 @@ type TypeCheckInfo
reactorOps : IReactorOperations,
checkAlive : (unit -> bool),
textSnapshotInfo:obj option,
implementationFiles: TypedImplFile list) =
implementationFiles: TypedImplFile list,
openDeclarations: OpenDeclaration list) =
let textSnapshotInfo = defaultArg textSnapshotInfo null
let (|CNR|) (cnr:CapturedNameResolution) =
......@@ -1361,6 +1362,9 @@ type TypeCheckInfo
member __.ImplementationFiles = implementationFiles
/// All open declarations in the file, including auto open modules
member __.OpenDeclarations = openDeclarations
override __.ToString() = "TypeCheckInfo(" + mainInputFileName + ")"
type FSharpParsingOptions =
......@@ -1693,13 +1697,14 @@ module internal Parser =
projectFileName,
mainInputFileName,
sink.GetResolutions(),
sink.GetSymbolUses(),
sink.GetSymbolUses(),
tcEnvAtEnd.NameEnv,
loadClosure,
reactorOps,
checkAlive,
textSnapshotInfo,
typedImplFiles)
typedImplFiles,
sink.OpenDeclarations)
return errors, TypeCheckAborted.No scope
| None ->
return errors, TypeCheckAborted.Yes
......@@ -1990,9 +1995,9 @@ type FSharpCheckFileResults(filename: string, errors: FSharpErrorInfo[], scopeOp
(fun () -> [| |])
(fun scope ->
[| for (item,itemOcc,denv,m) in scope.ScopeSymbolUses.GetAllUsesOfSymbols() do
if itemOcc <> ItemOccurence.RelatedText then
let symbol = FSharpSymbol.Create(scope.TcGlobals, scope.ThisCcu, scope.TcImports, item)
yield FSharpSymbolUse(scope.TcGlobals, denv, symbol, itemOcc, m) |])
if itemOcc <> ItemOccurence.RelatedText then
let symbol = FSharpSymbol.Create(scope.TcGlobals, scope.ThisCcu, scope.TcImports, item)
yield FSharpSymbolUse(scope.TcGlobals, denv, symbol, itemOcc, m) |])
|> async.Return
member info.GetUsesOfSymbolInFile(symbol:FSharpSymbol) =
......@@ -2029,6 +2034,19 @@ type FSharpCheckFileResults(filename: string, errors: FSharpErrorInfo[], scopeOp
let cenv = Impl.cenv(scope.TcGlobals, scope.ThisCcu, scope.TcImports)
[ 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 + ")"
//----------------------------------------------------------------------------
......@@ -2620,7 +2638,8 @@ type BackgroundCompiler(legacyReferenceResolver, projectCacheSize, keepAssemblyC
List.last tcProj.TcSymbolUses,
tcProj.TcEnvAtEnd.NameEnv,
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)
return (parseResults, typedResults)
})
......
......@@ -273,6 +273,9 @@ type internal FSharpCheckFileResults =
/// Represents complete typechecked implementation files, including thier typechecked signatures if any.
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.
[<Sealed>]
#if COMPILER_PUBLIC_API
......
......@@ -111,7 +111,7 @@ let _ = CSharpOuterClass.InnerClass.StaticMember()
|> Async.RunSynchronously
|> Array.map (fun su -> su.Symbol.ToString())
|> shouldEqual
[|"InnerEnum"; "CSharpOuterClass"; "field Case1"; "InnerClass";
[|"Tests"; "InnerEnum"; "CSharpOuterClass"; "field Case1"; "InnerClass";
"CSharpOuterClass"; "member StaticMember"; "NestedEnumClass"|]
[<Test>]
......
......@@ -633,15 +633,18 @@ let _ =
|> Array.map (fun su ->
let r = su.RangeAlternate
su.Symbol.ToString(), (r.StartLine, r.StartColumn, r.EndLine, r.EndColumn))
|> Array.distinct
|> shouldEqual
[|("ConsoleKey", (5, 10, 5, 20))
("field Tab", (5, 10, 5, 24))
("ConsoleKey", (6, 6, 6, 16))
("field OemClear", (6, 6, 6, 25))
("ConsoleKey", (6, 29, 6, 39))
("field A", (6, 29, 6, 41))
("ConsoleKey", (7, 11, 7, 21))
("field B", (7, 11, 7, 23))
// note: these "System" sysbol uses are not duplications because each of them corresponts to different namespaces
[|("System", (2, 5, 2, 11))
("ConsoleKey", (5, 10, 5, 20));
("field Tab", (5, 10, 5, 24));
("ConsoleKey", (6, 6, 6, 16));
("field OemClear", (6, 6, 6, 25));
("ConsoleKey", (6, 29, 6, 39));
("field A", (6, 29, 6, 41));
("ConsoleKey", (7, 11, 7, 21));
("field B", (7, 11, 7, 23));
("Test", (1, 0, 1, 0))|]
[<Test>]
......
......@@ -368,10 +368,11 @@ let ``Test project1 all uses of all signature symbols`` () =
("field DisableFormatting",
[("file2", ((28, 4), (28, 21))); ("file2", ((30, 16), (30, 45)))]);
("M",
[("file1", ((1, 7), (1, 8))); ("file2", ((6, 28), (6, 29)));
("file2", ((9, 28), (9, 29))); ("file2", ((12, 27), (12, 28)));
("file2", ((38, 12), (38, 13))); ("file2", ((38, 22), (38, 23)));
("file2", ((39, 12), (39, 13))); ("file2", ((39, 28), (39, 29)))]);
[("file1", ((1, 7), (1, 8))); ("file2", ((3, 5), (3, 6)));
("file2", ((6, 28), (6, 29))); ("file2", ((9, 28), (9, 29)));
("file2", ((12, 27), (12, 28))); ("file2", ((38, 12), (38, 13)));
("file2", ((38, 22), (38, 23))); ("file2", ((39, 12), (39, 13)));
("file2", ((39, 28), (39, 29)))])
("val xxx",
[("file1", ((6, 4), (6, 7))); ("file1", ((7, 13), (7, 16)));
("file1", ((7, 19), (7, 22))); ("file2", ((6, 28), (6, 33)));
......@@ -420,6 +421,7 @@ let ``Test project1 all uses of all symbols`` () =
("C", "M.C", "file1", ((9, 15), (9, 16)), ["class"]);
("CAbbrev", "M.CAbbrev", "file1", ((9, 5), (9, 12)), ["abbrev"]);
("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"]);
("( .ctor )", "N.D1.( .ctor )", "file2", ((5, 5), (5, 7)),
["member"; "ctor"]);
......@@ -3686,32 +3688,33 @@ let ``Test Project25 symbol uses of type-provided members`` () =
allUses |> shouldEqual
[|("FSharp.Data.XmlProvider", "file1", ((4, 15), (4, 26)),
["class"; "provided"; "erased"]);
("FSharp.Data.XmlProvider", "file1", ((4, 15), (4, 26)),
["class"; "provided"; "erased"]);
("FSharp.Data.XmlProvider", "file1", ((4, 15), (4, 26)),
["class"; "provided"; "erased"]);
("FSharp.Data.XmlProvider", "file1", ((4, 15), (4, 26)),
["class"; "provided"; "erased"]);
("TypeProviderTests.Project", "file1", ((4, 5), (4, 12)), ["abbrev"]);
("TypeProviderTests.Project", "file1", ((5, 8), (5, 15)), ["abbrev"]);
("FSharp.Data.XmlProvider<...>.GetSample", "file1", ((5, 8), (5, 25)),
["member"]);
("Microsoft.FSharp.Core.int", "file1", ((7, 23), (7, 26)), ["abbrev"]);
("Microsoft.FSharp.Core.int", "file1", ((7, 23), (7, 26)), ["abbrev"]);
("TypeProviderTests.Record.Field", "file1", ((7, 16), (7, 21)), ["field"]);
("TypeProviderTests.Record", "file1", ((7, 5), (7, 11)), ["record"]);
("TypeProviderTests.Record", "file1", ((8, 10), (8, 16)), ["record"]);
("TypeProviderTests.Record.Field", "file1", ((8, 17), (8, 22)), ["field"]);
("TypeProviderTests.r", "file1", ((8, 4), (8, 5)), ["val"]);
("FSharp.Data.XmlProvider", "file1", ((10, 8), (10, 19)),
["class"; "provided"; "erased"]);
("FSharp.Data.XmlProvider<...>", "file1", ((10, 8), (10, 68)),
["class"; "provided"; "staticinst"; "erased"]);
("FSharp.Data.XmlProvider<...>.GetSample", "file1", ((10, 8), (10, 78)),
["member"]);
("TypeProviderTests", "file1", ((2, 7), (2, 24)), ["module"])|]
[|("FSharp.Data", "file1", ((3, 5), (3, 16)), ["namespace"; "provided"]);
("Microsoft.FSharp.Data", "file1", ((3, 5), (3, 16)), ["namespace"]);
("FSharp.Data.XmlProvider", "file1", ((4, 15), (4, 26)),
["class"; "provided"; "erased"]);
("FSharp.Data.XmlProvider", "file1", ((4, 15), (4, 26)),
["class"; "provided"; "erased"]);
("FSharp.Data.XmlProvider", "file1", ((4, 15), (4, 26)),
["class"; "provided"; "erased"]);
("FSharp.Data.XmlProvider", "file1", ((4, 15), (4, 26)),
["class"; "provided"; "erased"]);
("TypeProviderTests.Project", "file1", ((4, 5), (4, 12)), ["abbrev"]);
("TypeProviderTests.Project", "file1", ((5, 8), (5, 15)), ["abbrev"]);
("FSharp.Data.XmlProvider<...>.GetSample", "file1", ((5, 8), (5, 25)),
["member"]);
("Microsoft.FSharp.Core.int", "file1", ((7, 23), (7, 26)), ["abbrev"]);
("Microsoft.FSharp.Core.int", "file1", ((7, 23), (7, 26)), ["abbrev"]);
("TypeProviderTests.Record.Field", "file1", ((7, 16), (7, 21)), ["field"]);
("TypeProviderTests.Record", "file1", ((7, 5), (7, 11)), ["record"]);
("TypeProviderTests.Record", "file1", ((8, 10), (8, 16)), ["record"]);
("TypeProviderTests.Record.Field", "file1", ((8, 17), (8, 22)), ["field"]);
("TypeProviderTests.r", "file1", ((8, 4), (8, 5)), ["val"]);
("FSharp.Data.XmlProvider", "file1", ((10, 8), (10, 19)),
["class"; "provided"; "erased"]);
("FSharp.Data.XmlProvider<...>", "file1", ((10, 8), (10, 68)),
["class"; "provided"; "staticinst"; "erased"]);
("FSharp.Data.XmlProvider<...>.GetSample", "file1", ((10, 8), (10, 78)),
["member"]); ("TypeProviderTests", "file1", ((2, 7), (2, 24)), ["module"])|]
let getSampleSymbolUseOpt =
backgroundTypedParse1.GetSymbolUseAtLocation(5,25,"",["GetSample"])
|> Async.RunSynchronously
......
......@@ -35,7 +35,7 @@ type internal UnusedDeclarationsAnalyzer() =
match symbol with
// 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.
| :? 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.
| :? FSharpMemberOrFunctionOrValue as f when
f.IsOverrideOrExplicitInterfaceImplementation ||
......
......@@ -15,129 +15,7 @@ open Microsoft.FSharp.Compiler
open Microsoft.FSharp.Compiler.Ast
open Microsoft.FSharp.Compiler.Range
open Microsoft.FSharp.Compiler.SourceCodeServices
open 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
open Microsoft.VisualStudio.FSharp.Editor.Symbols
[<DiagnosticAnalyzer(FSharpConstants.FSharpLanguageName)>]
type internal UnusedOpensDiagnosticAnalyzer() =
......@@ -160,13 +38,19 @@ type internal UnusedOpensDiagnosticAnalyzer() =
override __.SupportedDiagnostics = ImmutableArray.Create Descriptor
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 {
do! Option.guard Settings.CodeFixes.UnusedOpens
let! sourceText = document.GetTextAsync()
let! _, parsedInput, checkResults = checker.ParseAndCheckDocument(document, options, sourceText = sourceText, allowStaleResults = true, userOpName = userOpName)
let! symbolUses = checkResults.GetAllUsesOfAllSymbolsInFile() |> liftAsync
return UnusedOpens.getUnusedOpens sourceText parsedInput symbolUses
let! _, _, checkResults = checker.ParseAndCheckDocument(document, options, sourceText = sourceText, allowStaleResults = true, userOpName = userOpName)
#if DEBUG
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) =
......@@ -180,10 +64,10 @@ type internal UnusedOpensDiagnosticAnalyzer() =
return
unusedOpens
|> List.map (fun m ->
|> List.map (fun range ->
Diagnostic.Create(
Descriptor,
RoslynHelpers.RangeToLocation(m, sourceText, document.FilePath)))
RoslynHelpers.RangeToLocation(range, sourceText, document.FilePath)))
|> Seq.toImmutableArray
}
|> Async.map (Option.defaultValue ImmutableArray.Empty)
......
......@@ -53,7 +53,6 @@
<Compile Include="Options\EditorOptions.fs" />
<Compile Include="LanguageService\Tokenizer.fs" />
<Compile Include="LanguageService\Symbols.fs" />
<Compile Include="LanguageService\TypedAstUtils.fs" />
<Compile Include="LanguageService\FSharpCheckerExtensions.fs" />
<Compile Include="LanguageService\LanguageService.fs" />
<Compile Include="LanguageService\AssemblyContentProvider.fs" />
......
......@@ -8,7 +8,6 @@ open Microsoft.CodeAnalysis.Text
open Microsoft.FSharp.Compiler
open Microsoft.FSharp.Compiler.Ast
open Microsoft.FSharp.Compiler.SourceCodeServices
open TypedAstUtils
type CheckResults =
| Ready of (FSharpParseFileResults * FSharpCheckFileResults) option
......@@ -108,7 +107,7 @@ type FSharpChecker with
match symbolUse.Symbol with
// Make sure that unsafe manipulation isn't executed if unused opens are disabled
| _ when not checkForUnusedOpens -> None
| TypedAstPatterns.MemberFunctionOrValue func when func.IsExtensionMember ->
| Symbol.MemberFunctionOrValue func when func.IsExtensionMember ->
if func.IsProperty then
let fullNames =
[| if func.HasGetterMethod then
......@@ -125,9 +124,9 @@ type FSharpChecker with
| [||] -> None
| _ -> Some fullNames
else
match func.EnclosingEntity.Value with
match func.EnclosingEntity with
// C# extension method
| TypedAstPatterns.FSharpEntity TypedAstPatterns.Class ->
| Some (Symbol.FSharpEntity Symbol.Class) ->
let fullName = symbolUse.Symbol.FullName.Split '.'
if fullName.Length > 2 then
(* For C# extension methods FCS returns full name including the class name, like:
......@@ -142,9 +141,9 @@ type FSharpChecker with
else None
| _ -> None
// Operators
| TypedAstPatterns.MemberFunctionOrValue func ->
| Symbol.MemberFunctionOrValue func ->
match func with
| TypedAstPatterns.Constructor _ ->
| Symbol.Constructor _ ->
// full name of a constructor looks like "UnusedSymbolClassifierTests.PrivateClass.( .ctor )"
// to make well formed full name parts we cut "( .ctor )" from the tail.
let fullName = func.FullName
......@@ -160,16 +159,16 @@ type FSharpChecker with
| Some idents -> yield String.concat "." idents
| None -> ()
|]
| TypedAstPatterns.FSharpEntity e ->
| Symbol.FSharpEntity e ->
match e with
| e, TypedAstPatterns.Attribute, _ ->
| e, Symbol.Attribute, _ ->
e.TryGetFullName ()
|> Option.map (fun fullName ->
[| fullName; fullName.Substring(0, fullName.Length - "Attribute".Length) |])
| e, _, _ ->
e.TryGetFullName () |> Option.map (fun fullName -> [| fullName |])
| TypedAstPatterns.RecordField _
| TypedAstPatterns.UnionCase _ as symbol ->
| Symbol.RecordField _
| Symbol.UnionCase _ as symbol ->
Some [| let fullName = symbol.FullName
yield fullName
let idents = fullName.Split '.'
......
......@@ -130,218 +130,4 @@ type FSharpEntity with
| _ -> ()
| _ -> ()
]
allBaseTypes x
/// 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
allBaseTypes x
\ 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 @@
<Compile Include="..\..\..\tests\service\ProjectOptionsTests.fs">
<Link>ProjectOptionsTests.fs</Link>
</Compile>
<Compile Include="UnusedOpensTests.fs">
<Link>ServiceAnalysis\UnusedOpensTests.fs</Link>
</Compile>
<Compile Include="ColorizationServiceTests.fs">
<Link>Roslyn\ColorizationServiceTests.fs</Link>
</Compile>
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册