diff --git a/fcs/FSharp.Compiler.Service.Tests.netcore/FSharp.Compiler.Service.Tests.netcore.fsproj b/fcs/FSharp.Compiler.Service.Tests.netcore/FSharp.Compiler.Service.Tests.netcore.fsproj index 058a83e551e36bf9de90f213722742865e44532b..71f5341bb71c1a38c44aef36e2f25bb2ac99e843 100644 --- a/fcs/FSharp.Compiler.Service.Tests.netcore/FSharp.Compiler.Service.Tests.netcore.fsproj +++ b/fcs/FSharp.Compiler.Service.Tests.netcore/FSharp.Compiler.Service.Tests.netcore.fsproj @@ -1,4 +1,4 @@ - + netcoreapp1.0 $(DefineConstants);DOTNETCORE;FX_ATLEAST_45;FX_ATLEAST_PORTABLE;FX_NO_RUNTIMEENVIRONMENT;FX_RESHAPED_REFLECTION;TODO_REWORK_ASSEMBLY_LOAD; @@ -42,6 +42,7 @@ + diff --git a/fcs/FSharp.Compiler.Service.netstandard/FSharp.Compiler.Service.netstandard.fsproj b/fcs/FSharp.Compiler.Service.netstandard/FSharp.Compiler.Service.netstandard.fsproj index cbf108a68e408823de6f2483d484bc1c7d2608f0..3b8ed6361e6f3178b29d7132547179bf57c6ac37 100644 --- a/fcs/FSharp.Compiler.Service.netstandard/FSharp.Compiler.Service.netstandard.fsproj +++ b/fcs/FSharp.Compiler.Service.netstandard/FSharp.Compiler.Service.netstandard.fsproj @@ -536,6 +536,12 @@ Symbols/Exprs.fs + + Symbols/SymbolPatterns.fsi + + + Symbols/SymbolPatterns.fs + Service/IncrementalBuild.fsi @@ -632,6 +638,12 @@ Service/ServiceStructure.fs + + Service/ServiceAnalysis.fsi + + + Service/ServiceAnalysis.fs + Service/fsi.fsi diff --git a/fcs/FSharp.Compiler.Service/FSharp.Compiler.Service.fsproj b/fcs/FSharp.Compiler.Service/FSharp.Compiler.Service.fsproj index fa10b3664ea142301347561e3e73a278183eb808..1d5057aae1c53b41006c051de2c7f4ff6bb652d8 100644 --- a/fcs/FSharp.Compiler.Service/FSharp.Compiler.Service.fsproj +++ b/fcs/FSharp.Compiler.Service/FSharp.Compiler.Service.fsproj @@ -512,6 +512,12 @@ Symbols/Exprs.fs + + Symbols/SymbolPatterns.fsi + + + Symbols/SymbolPatterns.fs + Service/IncrementalBuild.fsi @@ -608,6 +614,12 @@ Service/ServiceStructure.fs + + Service/ServiceAnalysis.fsi + + + Service/ServiceAnalysis.fs + Service/fsi.fsi diff --git a/src/buildfromsource/FSharp.Compiler.Private/FSharp.Compiler.Private.fsproj b/src/buildfromsource/FSharp.Compiler.Private/FSharp.Compiler.Private.fsproj index 4d5189148f01cbbee7164c425d6783f9903d60dc..c8eec300d75774ece8c0857a1262007faf888788 100644 --- a/src/buildfromsource/FSharp.Compiler.Private/FSharp.Compiler.Private.fsproj +++ b/src/buildfromsource/FSharp.Compiler.Private/FSharp.Compiler.Private.fsproj @@ -492,6 +492,12 @@ Symbols/Exprs.fs + + Symbols/SymbolPatterns.fsi + + + Symbols/SymbolPatterns.fs + @@ -590,6 +596,12 @@ Service/ServiceStructure.fs + + Service/ServiceAnalysis.fsi + + + Service/ServiceAnalysis.fs + diff --git a/src/fsharp/FSharp.Compiler.Private/FSharp.Compiler.Private.fsproj b/src/fsharp/FSharp.Compiler.Private/FSharp.Compiler.Private.fsproj index 4b72bcf00f0c28920a299ddf461eea69490a19ba..24f3ea8f5767be71781d3c37b34eb9cf1e892535 100644 --- a/src/fsharp/FSharp.Compiler.Private/FSharp.Compiler.Private.fsproj +++ b/src/fsharp/FSharp.Compiler.Private/FSharp.Compiler.Private.fsproj @@ -546,6 +546,12 @@ Symbols/Exprs.fs + + Symbols/SymbolPatterns.fsi + + + Symbols/SymbolPatterns.fs + @@ -644,7 +650,13 @@ Service/ServiceStructure.fs - + + Service/ServiceAnalysis.fsi + + + Service/ServiceAnalysis.fs + + FSIstrings.txt diff --git a/src/fsharp/NameResolution.fs b/src/fsharp/NameResolution.fs index cc50d5bb15d6b159896c34dc15d23c916c3e6601..7150c4d99d38db0ce5973eed4c3eb69203f0c329 100644 --- a/src/fsharp/NameResolution.fs +++ b/src/fsharp/NameResolution.fs @@ -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. //------------------------------------------------------------------------- diff --git a/src/fsharp/NameResolution.fsi b/src/fsharp/NameResolution.fsi index 60ea7a0e6603a54e8435fab590bf7e1f5529f03b..93f0009ff8bfc607cdf960911f09175102617d1a 100755 --- a/src/fsharp/NameResolution.fsi +++ b/src/fsharp/NameResolution.fsi @@ -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 + [] /// 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 diff --git a/src/fsharp/TypeChecker.fs b/src/fsharp/TypeChecker.fs index 5606e93014087d1ec128a2c648453bbae9ffd7d6..966a5f188062382776b655de6d56db6ec8682c27 100755 --- a/src/fsharp/TypeChecker.fs +++ b/src/fsharp/TypeChecker.fs @@ -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) = diff --git a/vsintegration/src/FSharp.Editor/LanguageService/TypedAstUtils.fs b/src/fsharp/symbols/SymbolPatterns.fs similarity index 68% rename from vsintegration/src/FSharp.Editor/LanguageService/TypedAstUtils.fs rename to src/fsharp/symbols/SymbolPatterns.fs index b857ac6c58608cd341ec66004e17bd1707f0e032..aa4be5a32f8b07fcea7cf347b2e1da945c7414d2 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/TypedAstUtils.fs +++ b/src/fsharp/symbols/SymbolPatterns.fs @@ -1,47 +1,48 @@ -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) = - attributes |> Seq.tryFind isAttribute<'T> - -let hasModuleSuffixAttribute (entity: FSharpEntity) = - entity.Attributes - |> tryGetAttribute - |> 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. +[] +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) = + attributes |> Seq.tryFind isAttribute<'T> + + module Option = + let attempt f = try Some(f()) with _ -> None + + let hasModuleSuffixAttribute (entity: FSharpEntity) = + entity.Attributes + |> tryGetAttribute + |> 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) = + attributes |> Seq.exists isAttribute<'T> + let (|ValueType|_|) (e: FSharpEntity) = if e.IsEnum || e.IsValueType || hasAttribute 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 diff --git a/src/fsharp/symbols/SymbolPatterns.fsi b/src/fsharp/symbols/SymbolPatterns.fsi new file mode 100644 index 0000000000000000000000000000000000000000..e2c00c80ee7b36d77509336ec450b10b2b8f7b68 --- /dev/null +++ b/src/fsharp/symbols/SymbolPatterns.fsi @@ -0,0 +1,60 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +namespace Microsoft.FSharp.Compiler.SourceCodeServices + +[] +#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 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 -> 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 diff --git a/src/fsharp/symbols/Symbols.fs b/src/fsharp/symbols/Symbols.fs index db5f96b3d1758b86d31fb109584d60c4068ab473..dd6c04451b424bec24d5528441620cca00c0f957 100644 --- a/src/fsharp/symbols/Symbols.fs +++ b/src/fsharp/symbols/Symbols.fs @@ -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 } + [] type FSharpSymbolUse(g:TcGlobals, denv: DisplayEnv, symbol:FSharpSymbol, itemOcc, range: range) = member __.Symbol = symbol diff --git a/src/fsharp/symbols/Symbols.fsi b/src/fsharp/symbols/Symbols.fsi index 8cc1c0ed06beb38b1583776225024685c44cf1c5..e01c3d994223d6b6a1437c51d8b220657d9ed2d3 100644 --- a/src/fsharp/symbols/Symbols.fsi +++ b/src/fsharp/symbols/Symbols.fsi @@ -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 [] 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 [] FSharpDelegateSignature = @@ -1065,7 +1069,26 @@ and [] 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 [] diff --git a/src/fsharp/vs/IncrementalBuild.fs b/src/fsharp/vs/IncrementalBuild.fs index 44056bea56d1c8cd1a1fd164e3fac044f4344131..56f7113c37726d067b2112ff508543a3d1d47b35 100755 --- a/src/fsharp/vs/IncrementalBuild.fs +++ b/src/fsharp/vs/IncrementalBuild.fs @@ -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 } } diff --git a/src/fsharp/vs/IncrementalBuild.fsi b/src/fsharp/vs/IncrementalBuild.fsi index 47ec0a7b624be3b8077c521b6f0121c85929e537..e29f3408c6b8032fce7e3bfe59a3392db691ecde 100755 --- a/src/fsharp/vs/IncrementalBuild.fsi +++ b/src/fsharp/vs/IncrementalBuild.fsi @@ -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 diff --git a/src/fsharp/vs/ServiceAnalysis.fs b/src/fsharp/vs/ServiceAnalysis.fs new file mode 100644 index 0000000000000000000000000000000000000000..5923459c0311f681cf90c807748c985426958612 --- /dev/null +++ b/src/fsharp/vs/ServiceAnalysis.fs @@ -0,0 +1,159 @@ +// 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 ent.Attributes) then + for unionCase in ent.UnionCases do + yield upcast unionCase + + if Symbol.hasAttribute 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 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 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 = + + 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 diff --git a/src/fsharp/vs/ServiceAnalysis.fsi b/src/fsharp/vs/ServiceAnalysis.fsi new file mode 100644 index 0000000000000000000000000000000000000000..6290c5d43e5d15c4ab98f6d71648889856ddb451 --- /dev/null +++ b/src/fsharp/vs/ServiceAnalysis.fsi @@ -0,0 +1,15 @@ +// 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 \ No newline at end of file diff --git a/src/fsharp/vs/ServiceAssemblyContent.fs b/src/fsharp/vs/ServiceAssemblyContent.fs index 414098d21668e576f1bad2e3d5d15526917296e2..38a29545c5c851340ef538be0b3b2cd2ecd6e4fd 100644 --- a/src/fsharp/vs/ServiceAssemblyContent.fs +++ b/src/fsharp/vs/ServiceAssemblyContent.fs @@ -97,37 +97,6 @@ module Extensions = type FSharpAssemblySignature with member x.TryGetEntities() = try x.Entities :> _ seq with _ -> Seq.empty -[] -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) = - attributes |> Seq.exists isAttribute<'T> - - let tryGetAttribute<'T> (attributes: seq) = - attributes |> Seq.tryFind isAttribute<'T> - - let hasModuleSuffixAttribute (entity: FSharpEntity) = - entity.Attributes - |> tryGetAttribute - |> 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 - [] 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 entity.Attributes - HasModuleSuffix = hasModuleSuffixAttribute entity } + { IsAutoOpen = Symbol.hasAttribute 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 entity.Attributes then + if entity.IsFSharp && Symbol.hasAttribute 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 entity.Attributes + let isAutoOpen = entity.IsFSharpModule && Symbol.hasAttribute 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 } diff --git a/src/fsharp/vs/ServiceAssemblyContent.fsi b/src/fsharp/vs/ServiceAssemblyContent.fsi index c92c741cfbc30d48c9d0dd9c9403ed7ccc310e28..03e8ccfa81a4f588d72e7a5c6d507d74879c8d7c 100644 --- a/src/fsharp/vs/ServiceAssemblyContent.fsi +++ b/src/fsharp/vs/ServiceAssemblyContent.fsi @@ -255,10 +255,4 @@ module internal Extensions = type FSharpAssemblySignature with /// Safe version of `Entities`. - member TryGetEntities : unit -> seq - -/// Operations over `FSharpAttribute`. -[] -module internal Utils = - /// Returns `true` if a collection of attributes contains one of given type. - val hasAttribute<'T> : attributes: seq -> bool + member TryGetEntities : unit -> seq \ No newline at end of file diff --git a/src/fsharp/vs/ServiceInterfaceStubGenerator.fs b/src/fsharp/vs/ServiceInterfaceStubGenerator.fs index e6ccfffbab326db3ccbbdd69048f3ac31bf27553..4f62581f1bf33e6cfdfa90b6c80be96942ee5c1f 100644 --- a/src/fsharp/vs/ServiceInterfaceStubGenerator.fs +++ b/src/fsharp/vs/ServiceInterfaceStubGenerator.fs @@ -220,7 +220,7 @@ module internal InterfaceStubGenerator = let nm, namesWithIndices = normalizeArgName namesWithIndices nm // Detect an optional argument - let isOptionalArg = hasAttribute arg.Attributes + let isOptionalArg = Symbol.hasAttribute 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 m.Attributes + m.IsEvent || Symbol.hasAttribute 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 v.LogicalEnclosingEntity.Attributes) -> + not (Symbol.hasAttribute v.LogicalEnclosingEntity.Attributes) -> name + " " + parArgs // Ordinary static members or things (?) that require fully qualified access | _, _, _, name -> name + parArgs diff --git a/src/fsharp/vs/service.fs b/src/fsharp/vs/service.fs index 3d86ffa63cff1d1317bae30621722a1decf79fbd..f96d560b763a2aee62940e78b3c7d452d09ddc72 100644 --- a/src/fsharp/vs/service.fs +++ b/src/fsharp/vs/service.fs @@ -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) }) diff --git a/src/fsharp/vs/service.fsi b/src/fsharp/vs/service.fsi index e6471120f5f3012209fcd0732ddecc47c7654a25..1e7005bff08e12de0e90140b377317e742f81c3c 100755 --- a/src/fsharp/vs/service.fsi +++ b/src/fsharp/vs/service.fsi @@ -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. [] #if COMPILER_PUBLIC_API diff --git a/tests/service/CSharpProjectAnalysis.fs b/tests/service/CSharpProjectAnalysis.fs index 188d733cd1827a7a892038c13609d992ec0d200e..7acf06c7111aa3a4f65515e0ba9fb7a7ac65bc6d 100644 --- a/tests/service/CSharpProjectAnalysis.fs +++ b/tests/service/CSharpProjectAnalysis.fs @@ -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"|] [] diff --git a/tests/service/EditorTests.fs b/tests/service/EditorTests.fs index e935737511eb90881ed5e634e1c9a553c1c4ecec..2e050abe327034271148fcf3f57b5f22c7c7b60e 100644 --- a/tests/service/EditorTests.fs +++ b/tests/service/EditorTests.fs @@ -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))|] [] diff --git a/tests/service/ProjectAnalysisTests.fs b/tests/service/ProjectAnalysisTests.fs index c09d8d4ca153c9c17ef6303a3d6be0d933a32a8f..1a345c4a76b27999694e0b3f79bb9fab3a60da0d 100644 --- a/tests/service/ProjectAnalysisTests.fs +++ b/tests/service/ProjectAnalysisTests.fs @@ -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 diff --git a/vsintegration/src/FSharp.Editor/Diagnostics/UnusedDeclarationsAnalyzer.fs b/vsintegration/src/FSharp.Editor/Diagnostics/UnusedDeclarationsAnalyzer.fs index cb541cac322846ff218097867c4e898b34963b07..071f0aadbe13e9a4d5d4280439121c7abc87374c 100644 --- a/vsintegration/src/FSharp.Editor/Diagnostics/UnusedDeclarationsAnalyzer.fs +++ b/vsintegration/src/FSharp.Editor/Diagnostics/UnusedDeclarationsAnalyzer.fs @@ -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 || diff --git a/vsintegration/src/FSharp.Editor/Diagnostics/UnusedOpensDiagnosticAnalyzer.fs b/vsintegration/src/FSharp.Editor/Diagnostics/UnusedOpensDiagnosticAnalyzer.fs index 0dcb5c9da5d66c4a6958643be4ff0caeaf4aa4bb..88568b4a2a237fabe10a758aa62ddf966dccda22 100644 --- a/vsintegration/src/FSharp.Editor/Diagnostics/UnusedOpensDiagnosticAnalyzer.fs +++ b/vsintegration/src/FSharp.Editor/Diagnostics/UnusedOpensDiagnosticAnalyzer.fs @@ -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 * 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 * range) list = - let rec filterInner acc list (seenNamespaces: Set) = - 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 [] type internal UnusedOpensDiagnosticAnalyzer() = @@ -160,13 +38,19 @@ type internal UnusedOpensDiagnosticAnalyzer() = override __.SupportedDiagnostics = ImmutableArray.Create Descriptor override this.AnalyzeSyntaxAsync(_, _) = Task.FromResult ImmutableArray.Empty - static member GetUnusedOpenRanges(document: Document, options, checker: FSharpChecker) = + static member GetUnusedOpenRanges(document: Document, options, checker: FSharpChecker) : Async> = 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) diff --git a/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj b/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj index 5bb2b7d613ce99b1ae62ff655222618151efa8c6..8bc852eb2900d12818ab9cff28698863a9b9e95b 100644 --- a/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj +++ b/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj @@ -53,7 +53,6 @@ - diff --git a/vsintegration/src/FSharp.Editor/LanguageService/FSharpCheckerExtensions.fs b/vsintegration/src/FSharp.Editor/LanguageService/FSharpCheckerExtensions.fs index 2d6b877fee9ef81023212913ccc8df3b769a4f06..14719b17d231e9599e930cb0fc67c5c704f39041 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/FSharpCheckerExtensions.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/FSharpCheckerExtensions.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 '.' diff --git a/vsintegration/src/FSharp.Editor/LanguageService/Symbols.fs b/vsintegration/src/FSharp.Editor/LanguageService/Symbols.fs index 1ed87724e3f1db4b80bb0c1bcfa1899d327608a2..13de16c081ca791e0c0b73335d01a52bebf236ba 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/Symbols.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/Symbols.fs @@ -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 diff --git a/vsintegration/tests/unittests/UnusedOpensTests.fs b/vsintegration/tests/unittests/UnusedOpensTests.fs new file mode 100644 index 0000000000000000000000000000000000000000..bb64f920faad125fdad77dd82b10540aca56e410 --- /dev/null +++ b/vsintegration/tests/unittests/UnusedOpensTests.fs @@ -0,0 +1,725 @@ +// 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. +[] +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 + +[] +let ``unused open declaration in top level module``() = + """ +module TopModule +open System +open System.IO +let _ = DateTime.Now +""" + => [ 4, (5, 14) ] + +[] +let ``unused open declaration in namespace``() = + """ +namespace TopNamespace +open System +open System.IO +module Nested = + let _ = DateTime.Now +""" + => [ 4, (5, 14) ] + +[] +let ``unused open declaration in nested module``() = + """ +namespace TopNamespace +module Nested = + open System + open System.IO + let _ = DateTime.Now +""" + => [ 5, (9, 18) ] + +[] +let ``unused open declaration due to partially qualified symbol``() = + """ +module TopModule +open System +open System.IO +let _ = IO.File.Create "" +""" + => [ 4, (5, 14) ] + +[] +let ``unused parent open declaration due to partially qualified symbol``() = + """ +module TopModule +open System +open System.IO +let _ = File.Create "" +""" + => [ 3, (5, 11) ] + +[] +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) ] + +[] +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) ] + +[] +let ``multiple open declaration in the same line``() = + """ +open System.IO; let _ = File.Create "";; open System.IO +""" + => [ 2, (46, 55) ] + +[] +let ``open a nested module inside another one is not unused``() = + """ +module Top +module M1 = + let x = () +module M2 = + open M1 + let y = x +""" + => [] + +[] +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 +""" + => [] + +[] +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 +""" + => [] + +[] +let ``opening auto open module after it's parent module was opened should be marked as unused``() = + """ +module NormalModule = + [] + module AutoOpenModule1 = + module NestedNormalModule = + [] + module AutoOpenModule2 = + [] + module AutoOpenModule3 = + type Class() = class end + +open NormalModule.AutoOpenModule1.NestedNormalModule +open NormalModule.AutoOpenModule1.NestedNormalModule.AutoOpenModule2 +let _ = Class() +""" + => [ 13, (5, 68) ] + +[] +let ``opening parent module after one of its auto open module was opened should be marked as unused``() = + """ +module NormalModule = + [] + module AutoOpenModule1 = + module NestedNormalModule = + [] + module AutoOpenModule2 = + [] + module AutoOpenModule3 = + type Class() = class end + +open NormalModule.AutoOpenModule1.NestedNormalModule.AutoOpenModule2 +open NormalModule.AutoOpenModule1.NestedNormalModule +let _ = Class() +""" + => [ 13, (5, 52) ] + +[] +let ``open declaration is not marked as unused if there is a shortened attribute symbol from it``() = + """ +open System +[] +type Class() = class end +""" + => [] + +[] +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 +""" + => [] + +[] +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) ] + +[] +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() +""" + => [] + +[] +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) ] + +[] +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 +""" + => [] + +[] +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) ] + +[] +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 +""" + => [] + +[] +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 +""" + => [] + +[] +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) ] + +[] +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() +""" + => [] + +[] +let ``open module with ModuleSuffix attribute value applied is not marked as unused if a symbol declared in it is used``() = + """ +[] +module M = + let func _ = () +open M +let _ = func() +""" + => [] + +[] +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) ] + +[] +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) = () +""" + => [] + +[] +let ``auto open module``() = + """ +module Top = + [] + module M = + let func _ = () +open Top +let _ = func() +""" + => [] + +[] +let ``auto open module in the middle of hierarchy``() = + """ +namespace Ns +module M1 = + [] + module MA1 = + let func _ = () +open M1 +module M2 = + let _ = func() +""" + => [] + +[] +let ``open declaration is not marked as unused if a delegate defined in it is used``() = + """ +open System +let _ = Func(fun _ -> 1) +""" + => [] + +[] +let ``open declaration is not marked as unused if a unit of measure defined in it is used``() = + """ +module M = + type [] m +module N = + open M + let _ = 1 +""" + => [] + +[] +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: [] ?x: int -> unit +""" + => [] + +[] +let ``relative module open declaration``() = + """ +module Top = + module Nested = + let x = 1 +open Top +open Nested +let _ = x +""" + => [] + +[] +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 +""" + => [] + +[] +let ``redundant opening a module with ModuleSuffix attribute value is marks as unused``() = + """ +[] +module InternalModuleWithSuffix = + let func1 _ = () +module M = + open InternalModuleWithSuffix + let _ = InternalModuleWithSuffix.func1() +""" + => [ 6, (9, 33) ] + +[] +let ``redundant opening a module is marks as unused``() = + """ +module InternalModuleWithSuffix = + let func1 _ = () +module M = + open InternalModuleWithSuffix + let _ = InternalModuleWithSuffix.func1() +""" + => [ 5, (9, 33) ] + +[] +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 +""" + => [] + +[] +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 +""" + => [] + +[] +let ``type with different DisplayName``() = + """ +open Microsoft.FSharp.Quotations +let _ = Expr.Coerce (<@@ 1 @@>, typeof) +""" + => [] + +[] +let ``auto open module with ModuleSuffix attribute value``() = + """ +module Top = + [] + module Module = + let func _ = () +open Top +module Module1 = + let _ = func() +""" + => [] + +[] +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 +""" + => [] + +[] +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 +""" + => [] + +[] +let ``usage of an operator makes the module /with Module suffix/ it's defined in to be not marked as unused``() = + """ +[] +module M = + let (++|) x y = () +open M +let _ = 1 ++| 2 +""" + => [] + +[] +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 -> () + | _ -> () +""" + => [] + +[] +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 +""" + => [] + +[] +let ``assembly level attribute prevents namespace in which it's defined to be marked as unused``() = + """ +open System +[] +() +""" + => [] + +[] +let ``open declaration is not marked as unused if a related type extension is used``() = + """ +module Module = + open System + type String with + member __.Method() = () +""" + => [] + +[] +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<_> +""" + => [] + +[] +let ``should not mark open declaration with global prefix``() = + """ +module Module = + open global.System + let _ = String("") +""" + => [] + +[] +let ``record fields should be taken into account``() = + """ +module M1 = + type Record = { Field: int } +module M2 = + open M1 + let x = { Field = 0 } +""" + => [] + +[] +let ``handle type alias``() = + """ +module TypeAlias = + type MyInt = int +module Usage = + open TypeAlias + let f (x:MyInt) = x +""" + => [] + +[] +let ``handle override members``() = + """ +type IInterface = + abstract Property: int + +type IClass() = + interface IInterface with + member __.Property = 0 + +let f (x: IClass) = (x :> IInterface).Property +""" + => [] + +[] +let ``active pattern cases should be taken into account``() = + """ +module M = + let (|Pattern|_|) _ = Some() +open M +let f (Pattern _) = () +""" + => [] + +[] +let ``active patterns applied as a function should be taken into account``() = + """ +module M = + let (|Pattern|_|) _ = Some() +open M +let _ = (|Pattern|_|) () +""" + => [] + +[] +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) ] + +[] +let ``type in type parameter constraint should be taken into account``() = + """ +open System +let f (x: 'a when 'a :> IDisposable) = () +""" + => [] + +[] +let ``namespace declaration should never be marked as unused``() = + """ +namespace Library2 +type T() = class end +""" + => [] + +[] +let ``auto open module opened before enclosing one is handled correctly``() = + """ +module M = + let x = 1 + [] + module N = + let y = 2 +open M.N +open M +let _ = x +let _ = y +""" + => [] + +[] +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 +""" + => [] + +[] +let ``C# extension methods are taken into account``() = + """ +open System.Linq + +module Test = + let xs = [] + let _ = xs.ToList() +""" + => [] + +[] +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) ] diff --git a/vsintegration/tests/unittests/VisualFSharp.Unittests.fsproj b/vsintegration/tests/unittests/VisualFSharp.Unittests.fsproj index 5603dd8058794a258884768b3e62b462d0d1fcae..177beb3bbd2279efb31c2cc640400fffb6870006 100644 --- a/vsintegration/tests/unittests/VisualFSharp.Unittests.fsproj +++ b/vsintegration/tests/unittests/VisualFSharp.Unittests.fsproj @@ -92,6 +92,9 @@ ProjectOptionsTests.fs + + ServiceAnalysis\UnusedOpensTests.fs + Roslyn\ColorizationServiceTests.fs