-
Notifications
You must be signed in to change notification settings - Fork 4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add support for assembly/module-level [Experimental]
and contextual suppression
#69316
Changes from 1 commit
2b800fb
c8a0fa0
6669f0b
d44dae2
6baacd2
b0ed1cd
afba201
fba843c
c9d41c6
7ad505e
9add30d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,10 +4,12 @@ | |
|
||
#nullable disable | ||
|
||
using System; | ||
using System.Diagnostics; | ||
using System.Reflection.Metadata; | ||
using System.Threading; | ||
using Microsoft.CodeAnalysis.CSharp.Symbols.Metadata.PE; | ||
using Roslyn.Utilities; | ||
|
||
namespace Microsoft.CodeAnalysis.CSharp.Symbols | ||
{ | ||
|
@@ -54,7 +56,7 @@ internal static ObsoleteAttributeData GetObsoleteDataFromMetadata(EntityHandle t | |
/// symbol's Obsoleteness is Unknown. False, if we are certain that no symbol in the parent | ||
/// hierarchy is Obsolete. | ||
/// </returns> | ||
private static ThreeState GetObsoleteContextState(Symbol symbol, bool forceComplete) | ||
private static ThreeState GetObsoleteContextState(Symbol symbol, bool forceComplete, Func<Symbol, ThreeState> getStateFromSymbol) | ||
{ | ||
while ((object)symbol != null) | ||
{ | ||
|
@@ -73,7 +75,7 @@ private static ThreeState GetObsoleteContextState(Symbol symbol, bool forceCompl | |
symbol.ForceCompleteObsoleteAttribute(); | ||
} | ||
|
||
var state = symbol.ObsoleteState; | ||
var state = getStateFromSymbol(symbol); | ||
if (state != ThreeState.False) | ||
{ | ||
return state; | ||
|
@@ -95,13 +97,35 @@ private static ThreeState GetObsoleteContextState(Symbol symbol, bool forceCompl | |
|
||
internal static ObsoleteDiagnosticKind GetObsoleteDiagnosticKind(Symbol symbol, Symbol containingMember, bool forceComplete = false) | ||
{ | ||
if (symbol is NamedTypeSymbol namedTypeSymbol && isExperimentalSymbol(namedTypeSymbol)) | ||
{ | ||
// Skip for System.Diagnostics.CodeAnalysis.ExperimentalAttribute to mitigate cycles | ||
return ObsoleteDiagnosticKind.NotObsolete; | ||
} | ||
|
||
if (symbol is MethodSymbol { MethodKind: MethodKind.Constructor } method && isExperimentalSymbol(method.ContainingType)) | ||
{ | ||
// Skip for constructors of System.Diagnostics.CodeAnalysis.ExperimentalAttribute to mitigate cycles | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We don't need to skip for non-constructor methods? #Resolved |
||
return ObsoleteDiagnosticKind.NotObsolete; | ||
} | ||
|
||
Debug.Assert(symbol.ContainingModule.ObsoleteKind is not ObsoleteAttributeKind.Uninitialized); | ||
Debug.Assert(symbol.ContainingAssembly.ObsoleteKind is not ObsoleteAttributeKind.Uninitialized); | ||
|
||
if (symbol.ContainingModule.ObsoleteKind is ObsoleteAttributeKind.Experimental | ||
|| symbol.ContainingAssembly.ObsoleteKind is ObsoleteAttributeKind.Experimental) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we do this here? Couldn't we check containing member only when this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're right, it's better to put there. That said, I don't think one should use both attributes (Obsolete and Experimental) :-P |
||
{ | ||
return getExperimentalDiagnosticKind(containingMember, forceComplete); | ||
} | ||
|
||
switch (symbol.ObsoleteKind) | ||
{ | ||
case ObsoleteAttributeKind.None: | ||
return ObsoleteDiagnosticKind.NotObsolete; | ||
case ObsoleteAttributeKind.WindowsExperimental: | ||
case ObsoleteAttributeKind.Experimental: | ||
return ObsoleteDiagnosticKind.Diagnostic; | ||
case ObsoleteAttributeKind.Experimental: | ||
return getExperimentalDiagnosticKind(containingMember, forceComplete); | ||
case ObsoleteAttributeKind.Uninitialized: | ||
// If we haven't cracked attributes on the symbol at all or we haven't | ||
// cracked attribute arguments enough to be able to report diagnostics for | ||
|
@@ -110,7 +134,7 @@ internal static ObsoleteDiagnosticKind GetObsoleteDiagnosticKind(Symbol symbol, | |
return ObsoleteDiagnosticKind.Lazy; | ||
} | ||
|
||
switch (GetObsoleteContextState(containingMember, forceComplete)) | ||
switch (GetObsoleteContextState(containingMember, forceComplete, getStateFromSymbol: static (symbol) => symbol.ObsoleteState)) | ||
{ | ||
case ThreeState.False: | ||
return ObsoleteDiagnosticKind.Diagnostic; | ||
|
@@ -123,6 +147,30 @@ internal static ObsoleteDiagnosticKind GetObsoleteDiagnosticKind(Symbol symbol, | |
// later stage | ||
return ObsoleteDiagnosticKind.LazyPotentiallySuppressed; | ||
} | ||
|
||
static bool isExperimentalSymbol(NamedTypeSymbol namedTypeSymbol) | ||
{ | ||
return namedTypeSymbol.Arity == 0 | ||
&& namedTypeSymbol.HasNameQualifier("System.Diagnostics.CodeAnalysis") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
&& namedTypeSymbol.Name.Equals("ExperimentalAttribute", StringComparison.Ordinal); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📝 This is a poor man's cycle avoidance solution. This would treat any type of the right shape as an There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
} | ||
|
||
static ObsoleteDiagnosticKind getExperimentalDiagnosticKind(Symbol containingMember, bool forceComplete) | ||
{ | ||
switch (GetObsoleteContextState(containingMember, forceComplete, getStateFromSymbol: static (symbol) => symbol.ExperimentalState)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📝 although I did not run into that situation, I'm re-using the existing mechanism that we use for delaying Obsolete diagnostics when some information isn't yet resolved. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It looks like the only difference between this
|
||
{ | ||
case ThreeState.False: | ||
return ObsoleteDiagnosticKind.Diagnostic; | ||
case ThreeState.True: | ||
// If we are in a context that is already experimental, there is no point reporting | ||
// more obsolete diagnostics. | ||
return ObsoleteDiagnosticKind.Suppressed; | ||
default: | ||
// If the context is unknown, then store the symbol so that we can do this check at a | ||
// later stage | ||
return ObsoleteDiagnosticKind.LazyPotentiallySuppressed; | ||
} | ||
} | ||
} | ||
|
||
/// <summary> | ||
|
@@ -137,7 +185,7 @@ internal static DiagnosticInfo CreateObsoleteDiagnostic(Symbol symbol, BinderFla | |
|
||
static DiagnosticInfo createObsoleteDiagnostic(Symbol symbol, BinderFlags location) | ||
{ | ||
var data = symbol.ObsoleteAttributeData; | ||
var data = symbol.ObsoleteAttributeData ?? symbol.ContainingModule.ObsoleteAttributeData ?? symbol.ContainingAssembly.ObsoleteAttributeData; | ||
Debug.Assert(data != null); | ||
|
||
if (data == null) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need this cycle avoidance for
ExperimentalAttribute
but not forObsoleteAttribute
? #ResolvedThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for raising this. Turns out that now that
ObsoleteAttributeData
is lazy on assembly/module symbols, we no longer need this extra layer of cycle protection. I'll remove