Skip to content

Commit 756141c

Browse files
sbomerjtschuster
andauthored
[ILLink analyzer] Add analyzer support for feature checks (#94944)
Adds support for annotating static boolean properties with `[FeatureCheckAttribute(typeof(RequiresDynamicCodeAttribute))]`, causing the property to be treated as a guard for analyzer warnings about the corresponding `Requires` attribute. Adds two new warnings for: - Invalid use of `FeatureCheckAttribute` applied to a non-static or non-bool property - Implementation of the property doesn't obviously satisfy the "guard property" (it should return false whenever the guarded feature is disabled). See #94625 for notes on the design. See #96859 for the API proposal. --------- Co-authored-by: Jackson Schuster <[email protected]>
1 parent b72ec6c commit 756141c

27 files changed

+986
-74
lines changed

src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlow/FeatureChecksValue.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,15 @@ namespace ILLink.RoslynAnalyzer.DataFlow
1313
// For now, this is only designed to track the built-in "features"/"capabilities"
1414
// like RuntimeFeatures.IsDynamicCodeSupported, where a true return value
1515
// indicates that a feature/capability is available.
16-
public record struct FeatureChecksValue : INegate<FeatureChecksValue>
16+
public record struct FeatureChecksValue : INegate<FeatureChecksValue>, IDeepCopyValue<FeatureChecksValue>
1717
{
1818
public ValueSet<string> EnabledFeatures;
1919
public ValueSet<string> DisabledFeatures;
2020

21+
public static readonly FeatureChecksValue All = new FeatureChecksValue (ValueSet<string>.Unknown, ValueSet<string>.Empty);
22+
23+
public static readonly FeatureChecksValue None = new FeatureChecksValue (ValueSet<string>.Empty, ValueSet<string>.Empty);
24+
2125
public FeatureChecksValue (string enabledFeature)
2226
{
2327
EnabledFeatures = new ValueSet<string> (enabledFeature);
@@ -48,5 +52,10 @@ public FeatureChecksValue Negate ()
4852
{
4953
return new FeatureChecksValue (DisabledFeatures.DeepCopy (), EnabledFeatures.DeepCopy ());
5054
}
55+
56+
public FeatureChecksValue DeepCopy ()
57+
{
58+
return new FeatureChecksValue (EnabledFeatures.DeepCopy (), DisabledFeatures.DeepCopy ());
59+
}
5160
}
5261
}

src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlow/FeatureCheckVisitor.cs renamed to src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlow/FeatureChecksVisitor.cs

Lines changed: 36 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ namespace ILLink.RoslynAnalyzer.DataFlow
2424
// (a set features that are checked to be enabled or disabled).
2525
// The visitor takes a LocalDataFlowState as an argument, allowing for checks that
2626
// depend on the current dataflow state.
27-
public class FeatureChecksVisitor : OperationVisitor<StateValue, FeatureChecksValue?>
27+
public class FeatureChecksVisitor : OperationVisitor<StateValue, FeatureChecksValue>
2828
{
2929
DataFlowAnalyzerContext _dataFlowAnalyzerContext;
3030

@@ -33,32 +33,48 @@ public FeatureChecksVisitor (DataFlowAnalyzerContext dataFlowAnalyzerContext)
3333
_dataFlowAnalyzerContext = dataFlowAnalyzerContext;
3434
}
3535

36-
public override FeatureChecksValue? VisitArgument (IArgumentOperation operation, StateValue state)
36+
public override FeatureChecksValue DefaultVisit (IOperation operation, StateValue state)
37+
{
38+
// Visiting a non-understood pattern should return the empty set of features, which will
39+
// prevent this check from acting as a guard for any feature.
40+
return FeatureChecksValue.None;
41+
}
42+
43+
public override FeatureChecksValue VisitArgument (IArgumentOperation operation, StateValue state)
3744
{
3845
return Visit (operation.Value, state);
3946
}
4047

41-
public override FeatureChecksValue? VisitPropertyReference (IPropertyReferenceOperation operation, StateValue state)
48+
public override FeatureChecksValue VisitPropertyReference (IPropertyReferenceOperation operation, StateValue state)
4249
{
50+
// A single property may serve as a feature check for multiple features.
51+
FeatureChecksValue featureChecks = FeatureChecksValue.None;
4352
foreach (var analyzer in _dataFlowAnalyzerContext.EnabledRequiresAnalyzers) {
44-
if (analyzer.IsRequiresCheck (_dataFlowAnalyzerContext.Compilation, operation.Property)) {
45-
return new FeatureChecksValue (analyzer.FeatureName);
53+
if (analyzer.IsFeatureCheck (operation.Property, _dataFlowAnalyzerContext.Compilation)) {
54+
var featureCheck = new FeatureChecksValue (analyzer.RequiresAttributeFullyQualifiedName);
55+
featureChecks = featureChecks.And (featureCheck);
4656
}
4757
}
4858

49-
return null;
59+
return featureChecks;
5060
}
5161

52-
public override FeatureChecksValue? VisitUnaryOperator (IUnaryOperation operation, StateValue state)
62+
public override FeatureChecksValue VisitUnaryOperator (IUnaryOperation operation, StateValue state)
5363
{
5464
if (operation.OperatorKind is not UnaryOperatorKind.Not)
55-
return null;
65+
return FeatureChecksValue.None;
5666

57-
FeatureChecksValue? context = Visit (operation.Operand, state);
58-
if (context == null)
59-
return null;
67+
FeatureChecksValue context = Visit (operation.Operand, state);
68+
return context.Negate ();
69+
}
6070

61-
return context.Value.Negate ();
71+
public override FeatureChecksValue VisitLiteral (ILiteralOperation operation, StateValue state)
72+
{
73+
// 'false' can guard any feature
74+
if (GetConstantBool (operation.ConstantValue) is false)
75+
return FeatureChecksValue.All;
76+
77+
return FeatureChecksValue.None;
6278
}
6379

6480
public bool? GetLiteralBool (IOperation operation)
@@ -77,7 +93,7 @@ public FeatureChecksVisitor (DataFlowAnalyzerContext dataFlowAnalyzerContext)
7793
return value;
7894
}
7995

80-
public override FeatureChecksValue? VisitBinaryOperator (IBinaryOperation operation, StateValue state)
96+
public override FeatureChecksValue VisitBinaryOperator (IBinaryOperation operation, StateValue state)
8197
{
8298
bool expectEqual;
8399
switch (operation.OperatorKind) {
@@ -88,36 +104,32 @@ public FeatureChecksVisitor (DataFlowAnalyzerContext dataFlowAnalyzerContext)
88104
expectEqual = false;
89105
break;
90106
default:
91-
return null;
107+
return FeatureChecksValue.None;
92108
}
93109

94110
if (GetLiteralBool (operation.LeftOperand) is bool leftBool) {
95-
if (Visit (operation.RightOperand, state) is not FeatureChecksValue rightValue)
96-
return null;
111+
FeatureChecksValue rightValue = Visit (operation.RightOperand, state);
97112
return leftBool == expectEqual
98113
? rightValue
99114
: rightValue.Negate ();
100115
}
101116

102117
if (GetLiteralBool (operation.RightOperand) is bool rightBool) {
103-
if (Visit (operation.LeftOperand, state) is not FeatureChecksValue leftValue)
104-
return null;
118+
FeatureChecksValue leftValue = Visit (operation.LeftOperand, state);
105119
return rightBool == expectEqual
106120
? leftValue
107121
: leftValue.Negate ();
108122
}
109123

110-
return null;
124+
return FeatureChecksValue.None;
111125
}
112126

113-
public override FeatureChecksValue? VisitIsPattern (IIsPatternOperation operation, StateValue state)
127+
public override FeatureChecksValue VisitIsPattern (IIsPatternOperation operation, StateValue state)
114128
{
115129
if (GetExpectedValueFromPattern (operation.Pattern) is not bool patternValue)
116-
return null;
117-
118-
if (Visit (operation.Value, state) is not FeatureChecksValue value)
119-
return null;
130+
return FeatureChecksValue.None;
120131

132+
FeatureChecksValue value = Visit (operation.Value, state);
121133
return patternValue
122134
? value
123135
: value.Negate ();

src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowVisitor.cs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -88,12 +88,12 @@ public LocalDataFlowVisitor (
8888
return null;
8989

9090
var branchValue = Visit (branchValueOperation, state);
91-
91+
TConditionValue conditionValue = GetConditionValue (branchValueOperation, state);
9292
if (block.Block.ConditionKind != ControlFlowConditionKind.None) {
9393
// BranchValue may represent a value used in a conditional branch to the ConditionalSuccessor.
9494
// If so, give the analysis an opportunity to model the checked condition, and return the model
9595
// of the condition back to the generic analysis. It will be applied to the state of each outgoing branch.
96-
return GetConditionValue (branchValueOperation, state);
96+
return conditionValue;
9797
}
9898

9999
// If not, the BranchValue represents a return or throw value associated with the FallThroughSuccessor of this block.
@@ -118,10 +118,13 @@ public LocalDataFlowVisitor (
118118
// We don't want the return operation because this might have multiple possible return values in general.
119119
var current = state.Current;
120120
HandleReturnValue (branchValue, branchValueOperation, in current.Context);
121+
// Must be called for every return value even if it did not return an understood condition,
122+
// because the non-understood conditions will produce warnings for FeatureCheck properties.
123+
HandleReturnConditionValue (conditionValue, branchValueOperation);
121124
return null;
122125
}
123126

124-
public abstract TConditionValue? GetConditionValue (
127+
public abstract TConditionValue GetConditionValue (
125128
IOperation branchValueOperation,
126129
LocalDataFlowState<TValue, TContext, TValueLattice, TContextLattice> state);
127130

@@ -146,6 +149,10 @@ public abstract void HandleReturnValue (
146149
IOperation operation,
147150
in TContext context);
148151

152+
public abstract void HandleReturnConditionValue (
153+
TConditionValue returnConditionValue,
154+
IOperation branchValueOperation);
155+
149156
// This is called for any method call, which includes:
150157
// - Normal invocation operation
151158
// - Accessing property value - which is treated as a call to the getter
@@ -776,9 +783,7 @@ TValue HandleMethodCallHelper (
776783

777784
// Get the condition value that is being asserted. If the attribute is DoesNotReturnIf(true),
778785
// the condition value needs to be negated so that we can assert the false condition.
779-
if (GetConditionValue (argumentOperation, state) is not TConditionValue conditionValue)
780-
continue;
781-
786+
TConditionValue conditionValue = GetConditionValue (argumentOperation, state);
782787
var current = state.Current;
783788
ApplyCondition (
784789
doesNotReturnIfConditionValue == false

src/tools/illink/src/ILLink.RoslynAnalyzer/DataflowAnalyzerContext.cs renamed to src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlowAnalyzerContext.cs

File renamed without changes.

src/tools/illink/src/ILLink.RoslynAnalyzer/DynamicallyAccessedMembersAnalyzer.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ public class DynamicallyAccessedMembersAnalyzer : DiagnosticAnalyzer
2121
internal const string DynamicallyAccessedMembersAttribute = nameof (DynamicallyAccessedMembersAttribute);
2222
public const string attributeArgument = "attributeArgument";
2323
public const string FullyQualifiedDynamicallyAccessedMembersAttribute = "System.Diagnostics.CodeAnalysis." + DynamicallyAccessedMembersAttribute;
24+
public const string FullyQualifiedFeatureCheckAttribute = "System.Diagnostics.CodeAnalysis.FeatureCheckAttribute";
25+
public const string FullyQualifiedFeatureDependsOnAttribute = "System.Diagnostics.CodeAnalysis.FeatureDependsOnAttribute";
2426
public static Lazy<ImmutableArray<RequiresAnalyzerBase>> RequiresAnalyzers { get; } = new Lazy<ImmutableArray<RequiresAnalyzerBase>> (GetRequiresAnalyzers);
2527
static ImmutableArray<RequiresAnalyzerBase> GetRequiresAnalyzers () =>
2628
ImmutableArray.Create<RequiresAnalyzerBase> (
@@ -51,6 +53,8 @@ public static ImmutableArray<DiagnosticDescriptor> GetSupportedDiagnostics ()
5153
diagDescriptorsArrayBuilder.Add (DiagnosticDescriptors.GetDiagnosticDescriptor (DiagnosticId.UnrecognizedTypeNameInTypeGetType));
5254
diagDescriptorsArrayBuilder.Add (DiagnosticDescriptors.GetDiagnosticDescriptor (DiagnosticId.UnrecognizedParameterInMethodCreateInstance));
5355
diagDescriptorsArrayBuilder.Add (DiagnosticDescriptors.GetDiagnosticDescriptor (DiagnosticId.ParametersOfAssemblyCreateInstanceCannotBeAnalyzed));
56+
diagDescriptorsArrayBuilder.Add (DiagnosticDescriptors.GetDiagnosticDescriptor (DiagnosticId.ReturnValueDoesNotMatchFeatureChecks));
57+
diagDescriptorsArrayBuilder.Add (DiagnosticDescriptors.GetDiagnosticDescriptor (DiagnosticId.InvalidFeatureCheck));
5458

5559
foreach (var requiresAnalyzer in RequiresAnalyzers.Value) {
5660
foreach (var diagnosticDescriptor in requiresAnalyzer.SupportedDiagnostics)

src/tools/illink/src/ILLink.RoslynAnalyzer/ISymbolExtensions.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
// Copyright (c) .NET Foundation and contributors. All rights reserved.
22
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
33

4+
using System.Collections.Immutable;
5+
using System.Collections.Generic;
46
using System.Diagnostics.CodeAnalysis;
7+
using System.Linq;
58
using System.Text;
69
using Microsoft.CodeAnalysis;
10+
using ILLink.RoslynAnalyzer.DataFlow;
11+
using ILLink.Shared.DataFlow;
712

813
namespace ILLink.RoslynAnalyzer
914
{
@@ -34,6 +39,14 @@ internal static bool TryGetAttribute (this ISymbol member, string attributeName,
3439
return false;
3540
}
3641

42+
internal static IEnumerable<AttributeData> GetAttributes (this ISymbol member, string attributeName)
43+
{
44+
foreach (var attr in member.GetAttributes ()) {
45+
if (attr.AttributeClass is { } attrClass && attrClass.HasName (attributeName))
46+
yield return attr;
47+
}
48+
}
49+
3750
internal static DynamicallyAccessedMemberTypes GetDynamicallyAccessedMemberTypes (this ISymbol symbol)
3851
{
3952
if (!TryGetAttribute (symbol, DynamicallyAccessedMembersAnalyzer.DynamicallyAccessedMembersAttribute, out var dynamicallyAccessedMembers))
@@ -58,6 +71,28 @@ internal static DynamicallyAccessedMemberTypes GetDynamicallyAccessedMemberTypes
5871
return (DynamicallyAccessedMemberTypes) dynamicallyAccessedMembers.ConstructorArguments[0].Value!;
5972
}
6073

74+
internal static ValueSet<string> GetFeatureCheckAnnotations (this IPropertySymbol propertySymbol)
75+
{
76+
HashSet<string> featureSet = new ();
77+
foreach (var attributeData in propertySymbol.GetAttributes (DynamicallyAccessedMembersAnalyzer.FullyQualifiedFeatureCheckAttribute)) {
78+
if (attributeData.ConstructorArguments is [TypedConstant { Value: INamedTypeSymbol featureType }])
79+
AddFeatures (featureType);
80+
}
81+
return featureSet.Count == 0 ? ValueSet<string>.Empty : new ValueSet<string> (featureSet);
82+
83+
void AddFeatures (INamedTypeSymbol featureType) {
84+
var featureName = featureType.GetDisplayName ();
85+
if (!featureSet.Add (featureName))
86+
return;
87+
88+
// Look at FeatureDependsOn attributes on the feature type.
89+
foreach (var featureTypeAttributeData in featureType.GetAttributes (DynamicallyAccessedMembersAnalyzer.FullyQualifiedFeatureDependsOnAttribute)) {
90+
if (featureTypeAttributeData.ConstructorArguments is [TypedConstant { Value: INamedTypeSymbol featureTypeSymbol }])
91+
AddFeatures (featureTypeSymbol);
92+
}
93+
}
94+
}
95+
6196
internal static bool TryGetReturnAttribute (this IMethodSymbol member, string attributeName, [NotNullWhen (returnValue: true)] out AttributeData? attribute)
6297
{
6398
attribute = null;

src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresAnalyzerBase.cs

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55
using System.Collections.Immutable;
66
using System.Diagnostics.CodeAnalysis;
77
using System.Linq;
8-
using ILLink.Shared;
98
using ILLink.RoslynAnalyzer.DataFlow;
9+
using ILLink.Shared;
10+
using ILLink.Shared.DataFlow;
1011
using Microsoft.CodeAnalysis;
1112
using Microsoft.CodeAnalysis.CSharp;
1213
using Microsoft.CodeAnalysis.CSharp.Syntax;
@@ -19,9 +20,7 @@ public abstract class RequiresAnalyzerBase : DiagnosticAnalyzer
1920
{
2021
private protected abstract string RequiresAttributeName { get; }
2122

22-
internal abstract string FeatureName { get; }
23-
24-
private protected abstract string RequiresAttributeFullyQualifiedName { get; }
23+
internal abstract string RequiresAttributeFullyQualifiedName { get; }
2524

2625
private protected abstract DiagnosticTargets AnalyzerDiagnosticTargets { get; }
2726

@@ -301,7 +300,23 @@ protected virtual bool CreateSpecialIncompatibleMembersDiagnostic (
301300
// - false return value indicating that a feature is supported
302301
// - feature settings supplied by the project
303302
// - custom feature checks defined in library code
304-
internal virtual bool IsRequiresCheck (Compilation compilation, IPropertySymbol propertySymbol) => false;
303+
private protected virtual bool IsRequiresCheck (IPropertySymbol propertySymbol, Compilation compilation) => false;
304+
305+
internal static bool IsAnnotatedFeatureCheck (IPropertySymbol propertySymbol, string featureName)
306+
{
307+
// Only respect FeatureCheckAttribute on static boolean properties.
308+
if (!propertySymbol.IsStatic || propertySymbol.Type.SpecialType != SpecialType.System_Boolean)
309+
return false;
310+
311+
ValueSet<string> featureCheckAnnotations = propertySymbol.GetFeatureCheckAnnotations ();
312+
return featureCheckAnnotations.Contains (featureName);
313+
}
314+
315+
internal bool IsFeatureCheck (IPropertySymbol propertySymbol, Compilation compilation)
316+
{
317+
return IsAnnotatedFeatureCheck (propertySymbol, RequiresAttributeFullyQualifiedName)
318+
|| IsRequiresCheck (propertySymbol, compilation);
319+
}
305320

306321
internal bool CheckAndCreateRequiresDiagnostic (
307322
IOperation operation,
@@ -312,7 +327,7 @@ internal bool CheckAndCreateRequiresDiagnostic (
312327
[NotNullWhen (true)] out Diagnostic? diagnostic)
313328
{
314329
// Warnings are not emitted if the featureContext says the feature is available.
315-
if (featureContext.IsEnabled (FeatureName)) {
330+
if (featureContext.IsEnabled (RequiresAttributeFullyQualifiedName)) {
316331
diagnostic = null;
317332
return false;
318333
}

src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresAssemblyFilesAnalyzer.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,7 @@ public sealed class RequiresAssemblyFilesAnalyzer : RequiresAnalyzerBase
3636

3737
private protected override string RequiresAttributeName => RequiresAssemblyFilesAttribute;
3838

39-
internal override string FeatureName => "AssemblyFiles";
40-
41-
private protected override string RequiresAttributeFullyQualifiedName => RequiresAssemblyFilesAttributeFullyQualifiedName;
39+
internal override string RequiresAttributeFullyQualifiedName => RequiresAssemblyFilesAttributeFullyQualifiedName;
4240

4341
private protected override DiagnosticTargets AnalyzerDiagnosticTargets => DiagnosticTargets.MethodOrConstructor | DiagnosticTargets.Property | DiagnosticTargets.Event;
4442

@@ -61,7 +59,7 @@ internal override bool IsAnalyzerEnabled (AnalyzerOptions options)
6159
return true;
6260
}
6361

64-
internal override bool IsRequiresCheck (Compilation compilation, IPropertySymbol propertySymbol)
62+
private protected override bool IsRequiresCheck (IPropertySymbol propertySymbol, Compilation compilation)
6563
{
6664
// "IsAssemblyFilesSupported" is treated as a requires check for testing purposes only, and
6765
// is not officially-supported product behavior.

src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresDynamicCodeAnalyzer.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,7 @@ public sealed class RequiresDynamicCodeAnalyzer : RequiresAnalyzerBase
2525

2626
private protected override string RequiresAttributeName => RequiresDynamicCodeAttribute;
2727

28-
internal override string FeatureName => "DynamicCode";
29-
30-
private protected override string RequiresAttributeFullyQualifiedName => FullyQualifiedRequiresDynamicCodeAttribute;
28+
internal override string RequiresAttributeFullyQualifiedName => FullyQualifiedRequiresDynamicCodeAttribute;
3129

3230
private protected override DiagnosticTargets AnalyzerDiagnosticTargets => DiagnosticTargets.MethodOrConstructor | DiagnosticTargets.Class;
3331

@@ -40,7 +38,7 @@ public sealed class RequiresDynamicCodeAnalyzer : RequiresAnalyzerBase
4038
internal override bool IsAnalyzerEnabled (AnalyzerOptions options) =>
4139
options.IsMSBuildPropertyValueTrue (MSBuildPropertyOptionNames.EnableAotAnalyzer);
4240

43-
internal override bool IsRequiresCheck (Compilation compilation, IPropertySymbol propertySymbol) {
41+
private protected override bool IsRequiresCheck (IPropertySymbol propertySymbol, Compilation compilation) {
4442
var runtimeFeaturesType = compilation.GetTypeByMetadataName ("System.Runtime.CompilerServices.RuntimeFeature");
4543
if (runtimeFeaturesType == null)
4644
return false;

src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresUnreferencedCodeAnalyzer.cs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,7 @@ private Action<SymbolAnalysisContext> typeDerivesFromRucBase {
4949

5050
private protected override string RequiresAttributeName => RequiresUnreferencedCodeAttribute;
5151

52-
public const string UnreferencedCode = nameof (UnreferencedCode);
53-
54-
internal override string FeatureName => UnreferencedCode;
55-
56-
private protected override string RequiresAttributeFullyQualifiedName => FullyQualifiedRequiresUnreferencedCodeAttribute;
52+
internal override string RequiresAttributeFullyQualifiedName => FullyQualifiedRequiresUnreferencedCodeAttribute;
5753

5854
private protected override DiagnosticTargets AnalyzerDiagnosticTargets => DiagnosticTargets.MethodOrConstructor | DiagnosticTargets.Class;
5955

@@ -66,7 +62,7 @@ private Action<SymbolAnalysisContext> typeDerivesFromRucBase {
6662
internal override bool IsAnalyzerEnabled (AnalyzerOptions options) =>
6763
options.IsMSBuildPropertyValueTrue (MSBuildPropertyOptionNames.EnableTrimAnalyzer);
6864

69-
internal override bool IsRequiresCheck (Compilation compilation, IPropertySymbol propertySymbol)
65+
private protected override bool IsRequiresCheck (IPropertySymbol propertySymbol, Compilation compilation)
7066
{
7167
// "IsUnreferencedCodeSupported" is treated as a requires check for testing purposes only, and
7268
// is not officially-supported product behavior.

0 commit comments

Comments
 (0)