Skip to content

Commit bd2823e

Browse files
Copilotabonie
andauthored
Fix completion inconsistently filtering obsolete fields and events (#13512) (#19506)
* Fix completion inconsistently filtering obsolete fields and events Add ILFieldInfoIsUnseen and EventInfoIsUnseen functions to filter obsolete IL fields and events from completion, matching existing behavior for methods and properties. Also update ItemIsUnseen to handle ILField and Event items. Fixes #13693 Agent-Logs-Url: https://github.com/dotnet/fsharp/sessions/1d21d452-3f55-4d56-898c-0b50980050b5 Co-authored-by: abonie <20281641+abonie@users.noreply.github.com> * Add C# interop tests for obsolete field/event/method/property filtering Fix issue number to #13512. Add ObsoleteMembersClass to CSharp_Analysis with obsolete and non-obsolete members. Add 6 C# interop completion tests verifying all obsolete member types are consistently hidden. Agent-Logs-Url: https://github.com/dotnet/fsharp/sessions/1d21d452-3f55-4d56-898c-0b50980050b5 Co-authored-by: abonie <20281641+abonie@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: abonie <20281641+abonie@users.noreply.github.com>
1 parent 5bcca43 commit bd2823e

7 files changed

Lines changed: 138 additions & 2 deletions

File tree

docs/release-notes/.FSharp.Compiler.Service/11.0.100.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
* 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))
1919
* 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)))
2020
* 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))
21+
* 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))
2122

2223
### Added
2324

src/Compiler/Checking/AttributeChecking.fs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -639,6 +639,27 @@ let PropInfoIsUnseen _m allowObsolete pinfo =
639639
CheckProvidedAttributesForUnseen (pi.PApply((fun st -> (st :> IProvidedCustomAttributeProvider)), m)) m
640640
#endif
641641

642+
/// Indicate if an ILFieldInfo has 'Obsolete' attribute.
643+
/// Used to suppress the item in intellisense.
644+
let ILFieldInfoIsUnseen (finfo: ILFieldInfo) =
645+
match finfo with
646+
| ILFieldInfo(_, fdef) -> CheckILAttributesForUnseen fdef.CustomAttrs
647+
#if !NO_TYPEPROVIDERS
648+
| ProvidedField(_amap, fi, m) ->
649+
CheckProvidedAttributesForUnseen (fi.PApply((fun st -> (st :> IProvidedCustomAttributeProvider)), m)) m
650+
#endif
651+
652+
/// Indicate if an EventInfo has 'Obsolete' or 'CompilerMessageAttribute'.
653+
/// Used to suppress the item in intellisense.
654+
let EventInfoIsUnseen allowObsolete (einfo: EventInfo) =
655+
match einfo with
656+
| ILEvent(ILEventInfo(_, ilEventDef)) -> CheckILAttributesForUnseen ilEventDef.CustomAttrs
657+
| FSEvent(g, _, addValRef, _) -> CheckFSharpAttributesForUnseen g addValRef.Attribs allowObsolete
658+
#if !NO_TYPEPROVIDERS
659+
| ProvidedEvent(_amap, ei, m) ->
660+
CheckProvidedAttributesForUnseen (ei.PApply((fun st -> (st :> IProvidedCustomAttributeProvider)), m)) m
661+
#endif
662+
642663
/// Check the attributes on a union case, returning errors and warnings as data.
643664
let CheckUnionCaseAttributes g (x:UnionCaseRef) m =
644665
trackErrors {

src/Compiler/Checking/AttributeChecking.fsi

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,10 @@ val MethInfoIsUnseen: g: TcGlobals -> m: range -> ty: TType -> minfo: MethInfo -
101101

102102
val PropInfoIsUnseen: _m: 'a -> allowObsolete: bool -> pinfo: PropInfo -> bool
103103

104+
val ILFieldInfoIsUnseen: finfo: ILFieldInfo -> bool
105+
106+
val EventInfoIsUnseen: allowObsolete: bool -> einfo: EventInfo -> bool
107+
104108
val CheckEntityAttributes: g: TcGlobals -> tcref: TyconRef -> m: range -> OperationResult<unit>
105109

106110
val CheckUnionCaseAttributes: g: TcGlobals -> x: UnionCaseRef -> m: range -> OperationResult<unit>

src/Compiler/Checking/NameResolution.fs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4393,6 +4393,8 @@ let ItemIsUnseen ad g amap m allowObsolete item =
43934393
isUnseenNameOfOperator || IsValUnseen ad g m allowObsolete x
43944394
| Item.UnionCase(x, _) -> IsUnionCaseUnseen ad g amap m allowObsolete x.UnionCaseRef
43954395
| Item.ExnCase x -> IsTyconUnseen ad g amap m allowObsolete x
4396+
| Item.ILField finfo -> not allowObsolete && ILFieldInfoIsUnseen finfo
4397+
| Item.Event einfo -> not allowObsolete && EventInfoIsUnseen allowObsolete einfo
43964398
| _ -> false
43974399

43984400
let ItemOfTyconRef ncenv m (x: TyconRef) =
@@ -4467,7 +4469,8 @@ let ResolveCompletionsInType (ncenv: NameResolver) nenv (completionTargets: Reso
44674469
ncenv.InfoReader.GetEventInfosOfType(None, ad, m, ty)
44684470
|> List.filter (fun x ->
44694471
IsStandardEventInfo ncenv.InfoReader m ad x &&
4470-
x.IsStatic = statics)
4472+
x.IsStatic = statics &&
4473+
(allowObsolete || not (EventInfoIsUnseen allowObsolete x)))
44714474
else []
44724475

44734476
let nestedTypes =
@@ -4482,7 +4485,8 @@ let ResolveCompletionsInType (ncenv: NameResolver) nenv (completionTargets: Reso
44824485
|> List.filter (fun x ->
44834486
not x.IsSpecialName &&
44844487
x.IsStatic = statics &&
4485-
IsILFieldInfoAccessible g amap m ad x)
4488+
IsILFieldInfoAccessible g amap m ad x &&
4489+
(allowObsolete || not (ILFieldInfoIsUnseen x)))
44864490

44874491
let qinfos =
44884492
ncenv.InfoReader.GetTraitInfosInType None ty

tests/FSharp.Compiler.Service.Tests/Checker.fs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,11 @@ module Checker =
162162
let parseResults, checkResults = getParseAndCheckResults context.Source
163163
checkResults.GetCodeCompletionSuggestions(context, parseResults, options)
164164

165+
let getCompletionInfoWithCompilerAndCompletionOptions (compilerOptions: string array) (completionOptions: FSharpCodeCompletionOptions) (markedSource: string) =
166+
let context = getCompletionContext markedSource
167+
let parseResults, checkResults = getParseAndCheckResultsWithOptions compilerOptions context.Source
168+
checkResults.GetCodeCompletionSuggestions(context, parseResults, completionOptions)
169+
165170
let getCompletionInfo markedSource =
166171
getCompletionInfoWithOptions FSharpCodeCompletionOptions.Default markedSource
167172

tests/FSharp.Compiler.Service.Tests/CompletionTests.fs

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -691,6 +691,86 @@ exception E
691691
try () with E{caret}
692692
"""
693693

694+
// https://github.com/dotnet/fsharp/issues/13512
695+
[<Fact>]
696+
let ``Event - Instance 01`` () =
697+
assertItem "Ev" """
698+
type T() =
699+
[<System.Obsolete; CLIEvent>]
700+
member _.Ev = Event<System.EventHandler, _>().Publish
701+
702+
T().{caret}
703+
"""
704+
705+
// https://github.com/dotnet/fsharp/issues/13512
706+
[<Fact>]
707+
let ``Event - Static 01`` () =
708+
assertItem "Ev" """
709+
type T() =
710+
[<System.Obsolete; CLIEvent>]
711+
static member Ev = Event<System.EventHandler, _>().Publish
712+
713+
T.{caret}
714+
"""
715+
716+
/// Helper to assert completion with a reference to the CSharp_Analysis assembly
717+
let private assertCSharpInteropItem name source =
718+
let csharpAssembly = PathRelativeToTestAssembly "CSharp_Analysis.dll"
719+
let compilerOptions = [| $"-r:{csharpAssembly}" |]
720+
[allowObsoleteOptions; disallowObsoleteOptions]
721+
|> List.iter (fun completionOptions ->
722+
let contains = completionOptions.SuggestObsoleteSymbols
723+
let info = Checker.getCompletionInfoWithCompilerAndCompletionOptions compilerOptions completionOptions source
724+
assertItemsWithNames contains [name] info
725+
)
726+
727+
// https://github.com/dotnet/fsharp/issues/13512
728+
[<Fact>]
729+
let ``CSharp - Obsolete field is hidden`` () =
730+
assertCSharpInteropItem "ObsoleteField" """
731+
open FSharp.Compiler.Service.Tests
732+
ObsoleteMembersClass.{caret}
733+
"""
734+
735+
// https://github.com/dotnet/fsharp/issues/13512
736+
[<Fact>]
737+
let ``CSharp - Obsolete method is hidden`` () =
738+
assertCSharpInteropItem "ObsoleteMethod" """
739+
open FSharp.Compiler.Service.Tests
740+
ObsoleteMembersClass.{caret}
741+
"""
742+
743+
// https://github.com/dotnet/fsharp/issues/13512
744+
[<Fact>]
745+
let ``CSharp - Obsolete property is hidden`` () =
746+
assertCSharpInteropItem "ObsoleteProperty" """
747+
open FSharp.Compiler.Service.Tests
748+
ObsoleteMembersClass.{caret}
749+
"""
750+
751+
// https://github.com/dotnet/fsharp/issues/13512
752+
[<Fact>]
753+
let ``CSharp - Obsolete event is hidden`` () =
754+
assertCSharpInteropItem "ObsoleteEvent" """
755+
open FSharp.Compiler.Service.Tests
756+
ObsoleteMembersClass.{caret}
757+
"""
758+
759+
// https://github.com/dotnet/fsharp/issues/13512
760+
[<Fact>]
761+
let ``CSharp - Non-obsolete members are always shown`` () =
762+
let csharpAssembly = PathRelativeToTestAssembly "CSharp_Analysis.dll"
763+
let compilerOptions = [| $"-r:{csharpAssembly}" |]
764+
let source = """
765+
open FSharp.Compiler.Service.Tests
766+
ObsoleteMembersClass.{caret}
767+
"""
768+
[allowObsoleteOptions; disallowObsoleteOptions]
769+
|> List.iter (fun completionOptions ->
770+
let info = Checker.getCompletionInfoWithCompilerAndCompletionOptions compilerOptions completionOptions source
771+
assertItemsWithNames true ["NonObsoleteField"; "NonObsoleteMethod"; "NonObsoleteProperty"; "NonObsoleteEvent"] info
772+
)
773+
694774

695775
module PatternNameSuggestions =
696776
let private suggestPatternNames = { FSharpCodeCompletionOptions.Default with SuggestPatternNames = true }

tests/service/data/CSharp_Analysis/CSharpClass.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,4 +155,25 @@ public class DummyClass
155155
{
156156
}
157157
}
158+
159+
/// <summary>
160+
/// Class with obsolete members for testing completion filtering (issue #13512).
161+
/// </summary>
162+
public class ObsoleteMembersClass
163+
{
164+
[Obsolete("Field is obsolete")] public static readonly int ObsoleteField = 1;
165+
166+
[Obsolete("Method is obsolete")]
167+
public static void ObsoleteMethod()
168+
{
169+
}
170+
171+
[Obsolete("Property is obsolete")] public static int ObsoleteProperty => 1;
172+
[Obsolete("Event is obsolete")] public static event EventHandler ObsoleteEvent;
173+
174+
public static readonly int NonObsoleteField = 2;
175+
public static void NonObsoleteMethod() { }
176+
public static int NonObsoleteProperty => 2;
177+
public static event EventHandler NonObsoleteEvent;
178+
}
158179
}

0 commit comments

Comments
 (0)