diff --git a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md index dfdf2f14215..d2d59c319a1 100644 --- a/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md +++ b/docs/release-notes/.FSharp.Compiler.Service/11.0.100.md @@ -18,6 +18,7 @@ * Fix `YieldFromFinal`/`ReturnFromFinal` being incorrectly called in non-tail positions (`for`, `use`, `use!`, `try/with` handler). ([Issue #19402](https://github.com/dotnet/fsharp/issues/19402), [PR #19403](https://github.com/dotnet/fsharp/pull/19403)) * Fixed how the source ranges of warn directives are reported (as trivia) in the parser output (by not reporting leading spaces). ([Issue #19405](https://github.com/dotnet/fsharp/issues/19405), [PR #19408]((https://github.com/dotnet/fsharp/pull/19408))) * Fix UoM value type `ToString()` returning garbage values when `--checknulls+` is enabled, caused by double address-taking in codegen. ([Issue #19435](https://github.com/dotnet/fsharp/issues/19435), [PR #19440](https://github.com/dotnet/fsharp/pull/19440)) +* Fix completion inconsistently showing some obsolete members (fields and events) while hiding others (methods and properties). All obsolete members are now consistently hidden by default. ([Issue #13512](https://github.com/dotnet/fsharp/issues/13512), [PR #19506](https://github.com/dotnet/fsharp/pull/19506)) ### Added diff --git a/src/Compiler/Checking/AttributeChecking.fs b/src/Compiler/Checking/AttributeChecking.fs index ba62a69e4be..a7352914720 100755 --- a/src/Compiler/Checking/AttributeChecking.fs +++ b/src/Compiler/Checking/AttributeChecking.fs @@ -639,6 +639,27 @@ let PropInfoIsUnseen _m allowObsolete pinfo = CheckProvidedAttributesForUnseen (pi.PApply((fun st -> (st :> IProvidedCustomAttributeProvider)), m)) m #endif +/// Indicate if an ILFieldInfo has 'Obsolete' attribute. +/// Used to suppress the item in intellisense. +let ILFieldInfoIsUnseen (finfo: ILFieldInfo) = + match finfo with + | ILFieldInfo(_, fdef) -> CheckILAttributesForUnseen fdef.CustomAttrs +#if !NO_TYPEPROVIDERS + | ProvidedField(_amap, fi, m) -> + CheckProvidedAttributesForUnseen (fi.PApply((fun st -> (st :> IProvidedCustomAttributeProvider)), m)) m +#endif + +/// Indicate if an EventInfo has 'Obsolete' or 'CompilerMessageAttribute'. +/// Used to suppress the item in intellisense. +let EventInfoIsUnseen allowObsolete (einfo: EventInfo) = + match einfo with + | ILEvent(ILEventInfo(_, ilEventDef)) -> CheckILAttributesForUnseen ilEventDef.CustomAttrs + | FSEvent(g, _, addValRef, _) -> CheckFSharpAttributesForUnseen g addValRef.Attribs allowObsolete +#if !NO_TYPEPROVIDERS + | ProvidedEvent(_amap, ei, m) -> + CheckProvidedAttributesForUnseen (ei.PApply((fun st -> (st :> IProvidedCustomAttributeProvider)), m)) m +#endif + /// Check the attributes on a union case, returning errors and warnings as data. let CheckUnionCaseAttributes g (x:UnionCaseRef) m = trackErrors { diff --git a/src/Compiler/Checking/AttributeChecking.fsi b/src/Compiler/Checking/AttributeChecking.fsi index c8198e4a985..564957e1bd7 100644 --- a/src/Compiler/Checking/AttributeChecking.fsi +++ b/src/Compiler/Checking/AttributeChecking.fsi @@ -101,6 +101,10 @@ val MethInfoIsUnseen: g: TcGlobals -> m: range -> ty: TType -> minfo: MethInfo - val PropInfoIsUnseen: _m: 'a -> allowObsolete: bool -> pinfo: PropInfo -> bool +val ILFieldInfoIsUnseen: finfo: ILFieldInfo -> bool + +val EventInfoIsUnseen: allowObsolete: bool -> einfo: EventInfo -> bool + val CheckEntityAttributes: g: TcGlobals -> tcref: TyconRef -> m: range -> OperationResult val CheckUnionCaseAttributes: g: TcGlobals -> x: UnionCaseRef -> m: range -> OperationResult diff --git a/src/Compiler/Checking/NameResolution.fs b/src/Compiler/Checking/NameResolution.fs index 55ab3c9219b..07d0ea4743b 100644 --- a/src/Compiler/Checking/NameResolution.fs +++ b/src/Compiler/Checking/NameResolution.fs @@ -4393,6 +4393,8 @@ let ItemIsUnseen ad g amap m allowObsolete item = isUnseenNameOfOperator || IsValUnseen ad g m allowObsolete x | Item.UnionCase(x, _) -> IsUnionCaseUnseen ad g amap m allowObsolete x.UnionCaseRef | Item.ExnCase x -> IsTyconUnseen ad g amap m allowObsolete x + | Item.ILField finfo -> not allowObsolete && ILFieldInfoIsUnseen finfo + | Item.Event einfo -> not allowObsolete && EventInfoIsUnseen allowObsolete einfo | _ -> false let ItemOfTyconRef ncenv m (x: TyconRef) = @@ -4467,7 +4469,8 @@ let ResolveCompletionsInType (ncenv: NameResolver) nenv (completionTargets: Reso ncenv.InfoReader.GetEventInfosOfType(None, ad, m, ty) |> List.filter (fun x -> IsStandardEventInfo ncenv.InfoReader m ad x && - x.IsStatic = statics) + x.IsStatic = statics && + (allowObsolete || not (EventInfoIsUnseen allowObsolete x))) else [] let nestedTypes = @@ -4482,7 +4485,8 @@ let ResolveCompletionsInType (ncenv: NameResolver) nenv (completionTargets: Reso |> List.filter (fun x -> not x.IsSpecialName && x.IsStatic = statics && - IsILFieldInfoAccessible g amap m ad x) + IsILFieldInfoAccessible g amap m ad x && + (allowObsolete || not (ILFieldInfoIsUnseen x))) let qinfos = ncenv.InfoReader.GetTraitInfosInType None ty diff --git a/tests/FSharp.Compiler.Service.Tests/Checker.fs b/tests/FSharp.Compiler.Service.Tests/Checker.fs index 5d16f87783d..22dcf2ad502 100644 --- a/tests/FSharp.Compiler.Service.Tests/Checker.fs +++ b/tests/FSharp.Compiler.Service.Tests/Checker.fs @@ -162,6 +162,11 @@ module Checker = let parseResults, checkResults = getParseAndCheckResults context.Source checkResults.GetCodeCompletionSuggestions(context, parseResults, options) + let getCompletionInfoWithCompilerAndCompletionOptions (compilerOptions: string array) (completionOptions: FSharpCodeCompletionOptions) (markedSource: string) = + let context = getCompletionContext markedSource + let parseResults, checkResults = getParseAndCheckResultsWithOptions compilerOptions context.Source + checkResults.GetCodeCompletionSuggestions(context, parseResults, completionOptions) + let getCompletionInfo markedSource = getCompletionInfoWithOptions FSharpCodeCompletionOptions.Default markedSource diff --git a/tests/FSharp.Compiler.Service.Tests/CompletionTests.fs b/tests/FSharp.Compiler.Service.Tests/CompletionTests.fs index 43ee08b4ee3..e229a6f2b34 100644 --- a/tests/FSharp.Compiler.Service.Tests/CompletionTests.fs +++ b/tests/FSharp.Compiler.Service.Tests/CompletionTests.fs @@ -691,6 +691,86 @@ exception E try () with E{caret} """ + // https://github.com/dotnet/fsharp/issues/13512 + [] + let ``Event - Instance 01`` () = + assertItem "Ev" """ +type T() = + [] + member _.Ev = Event().Publish + +T().{caret} +""" + + // https://github.com/dotnet/fsharp/issues/13512 + [] + let ``Event - Static 01`` () = + assertItem "Ev" """ +type T() = + [] + static member Ev = Event().Publish + +T.{caret} +""" + + /// Helper to assert completion with a reference to the CSharp_Analysis assembly + let private assertCSharpInteropItem name source = + let csharpAssembly = PathRelativeToTestAssembly "CSharp_Analysis.dll" + let compilerOptions = [| $"-r:{csharpAssembly}" |] + [allowObsoleteOptions; disallowObsoleteOptions] + |> List.iter (fun completionOptions -> + let contains = completionOptions.SuggestObsoleteSymbols + let info = Checker.getCompletionInfoWithCompilerAndCompletionOptions compilerOptions completionOptions source + assertItemsWithNames contains [name] info + ) + + // https://github.com/dotnet/fsharp/issues/13512 + [] + let ``CSharp - Obsolete field is hidden`` () = + assertCSharpInteropItem "ObsoleteField" """ +open FSharp.Compiler.Service.Tests +ObsoleteMembersClass.{caret} +""" + + // https://github.com/dotnet/fsharp/issues/13512 + [] + let ``CSharp - Obsolete method is hidden`` () = + assertCSharpInteropItem "ObsoleteMethod" """ +open FSharp.Compiler.Service.Tests +ObsoleteMembersClass.{caret} +""" + + // https://github.com/dotnet/fsharp/issues/13512 + [] + let ``CSharp - Obsolete property is hidden`` () = + assertCSharpInteropItem "ObsoleteProperty" """ +open FSharp.Compiler.Service.Tests +ObsoleteMembersClass.{caret} +""" + + // https://github.com/dotnet/fsharp/issues/13512 + [] + let ``CSharp - Obsolete event is hidden`` () = + assertCSharpInteropItem "ObsoleteEvent" """ +open FSharp.Compiler.Service.Tests +ObsoleteMembersClass.{caret} +""" + + // https://github.com/dotnet/fsharp/issues/13512 + [] + let ``CSharp - Non-obsolete members are always shown`` () = + let csharpAssembly = PathRelativeToTestAssembly "CSharp_Analysis.dll" + let compilerOptions = [| $"-r:{csharpAssembly}" |] + let source = """ +open FSharp.Compiler.Service.Tests +ObsoleteMembersClass.{caret} +""" + [allowObsoleteOptions; disallowObsoleteOptions] + |> List.iter (fun completionOptions -> + let info = Checker.getCompletionInfoWithCompilerAndCompletionOptions compilerOptions completionOptions source + assertItemsWithNames true ["NonObsoleteField"; "NonObsoleteMethod"; "NonObsoleteProperty"; "NonObsoleteEvent"] info + ) + module PatternNameSuggestions = let private suggestPatternNames = { FSharpCodeCompletionOptions.Default with SuggestPatternNames = true } diff --git a/tests/service/data/CSharp_Analysis/CSharpClass.cs b/tests/service/data/CSharp_Analysis/CSharpClass.cs index a8131651b50..e8afc869dc2 100644 --- a/tests/service/data/CSharp_Analysis/CSharpClass.cs +++ b/tests/service/data/CSharp_Analysis/CSharpClass.cs @@ -155,4 +155,25 @@ public class DummyClass { } } + + /// + /// Class with obsolete members for testing completion filtering (issue #13512). + /// + public class ObsoleteMembersClass + { + [Obsolete("Field is obsolete")] public static readonly int ObsoleteField = 1; + + [Obsolete("Method is obsolete")] + public static void ObsoleteMethod() + { + } + + [Obsolete("Property is obsolete")] public static int ObsoleteProperty => 1; + [Obsolete("Event is obsolete")] public static event EventHandler ObsoleteEvent; + + public static readonly int NonObsoleteField = 2; + public static void NonObsoleteMethod() { } + public static int NonObsoleteProperty => 2; + public static event EventHandler NonObsoleteEvent; + } }