-
Notifications
You must be signed in to change notification settings - Fork 470
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 analyzer for SemanticModel.GetDeclaredSymbol #6779
Conversation
@dotnet/roslyn-compiler for help in reviewing this analyzer. Couple of questions:
|
{ | ||
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); | ||
context.EnableConcurrentExecution(); | ||
context.RegisterCompilationStartAction(static compilationContext => |
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.
I'd rename compilationContext
to context
(intentionally shadowing the outer context
)
|
||
public class Test {{ | ||
public void M(SemanticModel semanticModel, {type} syntax) {{ | ||
var x = [|semanticModel.GetDeclaredSymbol(syntax)|]; |
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.
Consider adding a test with semanticModel.GetDeclaredSymbol()
(which will produce a compilation error)
Codecov Report
@@ Coverage Diff @@
## main #6779 +/- ##
==========================================
- Coverage 96.43% 96.42% -0.01%
==========================================
Files 1410 1412 +2
Lines 336268 336372 +104
Branches 11107 11113 +6
==========================================
+ Hits 324268 324360 +92
- Misses 8896 8905 +9
- Partials 3104 3107 +3 |
if (SymbolEqualityComparer.Default.Equals(invocation.TargetMethod, getDeclaredSymbolMethod)) | ||
{ | ||
var syntaxNodeType = invocation.Arguments[1].Value.WalkDownConversion().Type; | ||
if (syntaxNodeType is not null && syntaxNodeType.GetBaseTypesAndThis().ToSet().Overlaps(new[] { globalStatementSymbol, incompleteMemberSymbol, baseFieldDeclarationSymbol })) |
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.
This allocates a new array for every invocation of GetDeclaredSymbol
. I think this is not necessary.
Also note there is already a DerivesFrom
extension method.
src/Microsoft.CodeAnalysis.Analyzers/Core/CodeAnalysisDiagnosticsResources.resx
Show resolved
Hide resolved
@@ -571,4 +571,13 @@ | |||
<data name="DoNotRegisterCompilerTypesWithBadAssemblyReferenceRuleTitle" xml:space="preserve"> | |||
<value>Compiler extensions should be implemented in assemblies with compiler-provided references</value> | |||
</data> | |||
<data name="SemanticModelGetDeclaredSymbolAlwaysReturnsNullDescription" xml:space="preserve"> | |||
<value>Calling 'SemanticModel.GetDeclaredSymbol' with an argument of type 'GlobalStatementSyntax', 'IncompleteMemberSyntax' or a type inheriting from 'BaseFieldDeclarationSyntax' will always return 'null'.</value> |
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.
It feels like fields should get a specific error message that instructs the user to extract the variable declarators.
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.
Seems like this description needs adjustment as fields now have a separate descriptor/description. Additionally, not sure if we should limit the description to just 'GlobalStatementSyntax' and 'IncompleteMemberSyntax' as the code handles all sub-types of SyntaxNode
that do not have an overload. It's fine to mentioned these two as examples, but not as the only node types being handled.
|| (getDeclaredSymbolMethod = modelExtensions.GetMembers(nameof(ModelExtensions.GetDeclaredSymbol)).FirstOrDefault() as IMethodSymbol) is null | ||
|| !typeProvider.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.MicrosoftCodeAnalysisCSharpSyntaxGlobalStatementSyntax, out var globalStatementSymbol) | ||
|| !typeProvider.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.MicrosoftCodeAnalysisCSharpSyntaxIncompleteMemberSyntax, out var incompleteMemberSymbol) | ||
|| !typeProvider.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.MicrosoftCodeAnalysisCSharpSyntaxBaseFieldDeclarationSyntax, out var baseFieldDeclarationSymbol)) |
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.
I wonder if we could instead look for all the extension GetDeclaredSymbol
methods, and use that a list of types that will return positive results? With maybe a special case for the field decl scenario, since that's somewhat misleading?
If we don't want to try doing that, I think we should at least include VariableDeclarationSyntax
, as that is also a common error.
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.
Thanks for the suggestion, I have implemented it.
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.
LGTM. @mavasani, can you take a look?
var invocation = (IInvocationOperation)context.Operation; | ||
if (SymbolEqualityComparer.Default.Equals(invocation.TargetMethod, getDeclaredSymbolMethod)) | ||
{ | ||
var syntaxNodeDerivingType = invocation.Arguments[1].Value.WalkDownConversion().Type; |
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.
Where do we ensure that getDeclaredSymbolMethod
has at least 2 parameters?
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.
This would also not work as expected if named arguments are used and arguments are out of order. You probably want to use one of the below helper methods:
public static bool TryGetArgumentForParameterAtIndex( |
public static IArgumentOperation GetArgumentForParameterAtIndex( |
Diagnostic? diagnostic = null; | ||
if (syntaxNodeDerivingType.DerivesFrom(baseFieldDeclarationType)) | ||
{ | ||
diagnostic = invocation.CreateDiagnostic(FieldDiagnosticDescriptor, syntaxNodeDerivingType.Name); | ||
} | ||
else if (allowedTypes.All(type => !syntaxNodeDerivingType.DerivesFrom(type, baseTypesOnly: true, checkTypeParameterConstraints: false))) | ||
{ | ||
diagnostic = invocation.CreateDiagnostic(DiagnosticDescriptor, syntaxNodeDerivingType.Name); | ||
} | ||
|
||
if (diagnostic is not null) | ||
{ | ||
context.ReportDiagnostic(diagnostic); | ||
} |
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.
Nit:
Diagnostic? diagnostic = null; | |
if (syntaxNodeDerivingType.DerivesFrom(baseFieldDeclarationType)) | |
{ | |
diagnostic = invocation.CreateDiagnostic(FieldDiagnosticDescriptor, syntaxNodeDerivingType.Name); | |
} | |
else if (allowedTypes.All(type => !syntaxNodeDerivingType.DerivesFrom(type, baseTypesOnly: true, checkTypeParameterConstraints: false))) | |
{ | |
diagnostic = invocation.CreateDiagnostic(DiagnosticDescriptor, syntaxNodeDerivingType.Name); | |
} | |
if (diagnostic is not null) | |
{ | |
context.ReportDiagnostic(diagnostic); | |
} | |
DiagnosticDescriptor descriptor; | |
if (syntaxNodeDerivingType.DerivesFrom(baseFieldDeclarationType)) | |
{ | |
descriptor = FieldDiagnosticDescriptor; | |
} | |
else if (allowedTypes.All(type => !syntaxNodeDerivingType.DerivesFrom(type, baseTypesOnly: true, checkTypeParameterConstraints: false))) | |
{ | |
descriptor = DiagnosticDescriptor; | |
} | |
else | |
{ | |
return; | |
} | |
context.ReportDiagnostic(invocation.CreateDiagnostic(descriptor, syntaxNodeDerivingType.Name)); |
} | ||
} | ||
|
||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(DiagnosticDescriptor, FieldDiagnosticDescriptor); |
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.
Move this to the top of the file above Initialize
method
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.
Overall LGTM. Have a few minor suggestions, I can merge once these are addressed. Thanks for the PR @CollinAlpert!
Done! |
Adds an analyzer to detect usages of
SemanticModel.GetDeclaredSymbol
which always returnsnull
.Fixes #336.