-
Notifications
You must be signed in to change notification settings - Fork 5.5k
Don't attach code-fix locations from outside the analyzed compilation #131828
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
base: main
Are you sure you want to change the base?
Changes from all commits
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 | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -223,7 +223,7 @@ private static void VerifyDamOnMethodsMatch(SymbolAnalysisContext context, IMeth | |||||||
| Location attributableSymbolLocation = GetPrimaryLocation(attributableMethod.Locations); | ||||||||
|
|
||||||||
| // code fix does not support merging multiple attributes. If an attribute is present or the method is not in source, do not provide args for code fix. | ||||||||
| (Location[]? sourceLocation, Dictionary<string, string?>? DAMArgs) = (!attributableSymbolLocation.IsInSource | ||||||||
| (Location[]? sourceLocation, Dictionary<string, string?>? DAMArgs) = (!CanOfferCodeFixAt(attributableSymbolLocation, context.Compilation) | ||||||||
| || (overrideMethod.TryGetReturnAttribute(DynamicallyAccessedMembersAnalyzer.DynamicallyAccessedMembersAttribute, out var _) | ||||||||
| && baseMethod.TryGetReturnAttribute(DynamicallyAccessedMembersAnalyzer.DynamicallyAccessedMembersAttribute, out var _)) | ||||||||
| ) ? (null, null) : CreateArguments(attributableSymbolLocation, missingAttribute); | ||||||||
|
|
@@ -247,7 +247,7 @@ private static void VerifyDamOnMethodsMatch(SymbolAnalysisContext context, IMeth | |||||||
| Location attributableSymbolLocation = attributableMethod.GetParameter(overrideParam.Index).Location!; | ||||||||
|
|
||||||||
| // code fix does not support merging multiple attributes. If an attribute is present or the method is not in source, do not provide args for code fix. | ||||||||
| (Location[]? sourceLocation, Dictionary<string, string?>? DAMArgs) = (!attributableSymbolLocation.IsInSource | ||||||||
| (Location[]? sourceLocation, Dictionary<string, string?>? DAMArgs) = (!CanOfferCodeFixAt(attributableSymbolLocation, context.Compilation) | ||||||||
| || (overrideParam.ParameterSymbol!.TryGetAttribute(DynamicallyAccessedMembersAnalyzer.DynamicallyAccessedMembersAttribute, out var _) | ||||||||
| && baseParam.ParameterSymbol!.TryGetAttribute(DynamicallyAccessedMembersAnalyzer.DynamicallyAccessedMembersAttribute, out var _)) | ||||||||
| ) ? (null, null) : CreateArguments(attributableSymbolLocation, missingAttribute); | ||||||||
|
|
@@ -273,7 +273,7 @@ private static void VerifyDamOnMethodsMatch(SymbolAnalysisContext context, IMeth | |||||||
| Location attributableSymbolLocation = GetPrimaryLocation(attributableSymbol.Locations); | ||||||||
|
|
||||||||
| // code fix does not support merging multiple attributes. If an attribute is present or the method is not in source, do not provide args for code fix. | ||||||||
| (Location[]? sourceLocation, Dictionary<string, string?>? DAMArgs) = (!attributableSymbolLocation.IsInSource | ||||||||
| (Location[]? sourceLocation, Dictionary<string, string?>? DAMArgs) = (!CanOfferCodeFixAt(attributableSymbolLocation, context.Compilation) | ||||||||
| || (overrideMethod.TypeParameters[i].TryGetAttribute(DynamicallyAccessedMembersAnalyzer.DynamicallyAccessedMembersAttribute, out var _) | ||||||||
| && baseMethod.TypeParameters[i].TryGetAttribute(DynamicallyAccessedMembersAnalyzer.DynamicallyAccessedMembersAttribute, out var _)) | ||||||||
| ) ? (null, null) : CreateArguments(attributableSymbolLocation, missingAttribute); | ||||||||
|
|
@@ -351,8 +351,16 @@ private static void VerifyDamOnPropertyAndAccessorMatch(SymbolAnalysisContext co | |||||||
| } | ||||||||
| } | ||||||||
|
|
||||||||
| private static (IMethodSymbol Method, DynamicallyAccessedMemberTypes Requirements) GetTargetAndRequirements(IMethodSymbol method, IMethodSymbol overriddenMethod, DynamicallyAccessedMemberTypes methodAnnotation, DynamicallyAccessedMemberTypes overriddenMethodAnnotation) | ||||||||
| { | ||||||||
| /// <summary> | ||||||||
| /// Determines whether a code fix location can be attached to a diagnostic. The location must point into the | ||||||||
| /// compilation being analyzed, otherwise Roslyn rejects the reported diagnostic. Note that a location can be in | ||||||||
| /// source and still belong to another project: the IDE models project-to-project references as compilation | ||||||||
| /// references, which expose source symbols whose syntax trees belong to a different compilation. | ||||||||
| /// </summary> | ||||||||
| private static bool CanOfferCodeFixAt(Location location, Compilation compilation) | ||||||||
| => location.SourceTree is { } sourceTree && compilation.ContainsSyntaxTree(sourceTree); | ||||||||
|
Comment on lines
+360
to
+361
Member
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. I'm slightly concerned that this and |
||||||||
|
|
||||||||
| private static (IMethodSymbol Method, DynamicallyAccessedMemberTypes Requirements) GetTargetAndRequirements(IMethodSymbol method, IMethodSymbol overriddenMethod, DynamicallyAccessedMemberTypes methodAnnotation, DynamicallyAccessedMemberTypes overriddenMethodAnnotation) { | ||||||||
|
Member
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.
Suggested change
|
||||||||
| DynamicallyAccessedMemberTypes mismatchedArgument; | ||||||||
| IMethodSymbol paramNeedsAttributes; | ||||||||
| if (methodAnnotation == DynamicallyAccessedMemberTypes.None) | ||||||||
|
|
||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,11 +14,14 @@ public readonly partial struct DiagnosticContext | |
| { | ||
| public readonly Location Location { get; } | ||
|
|
||
| private readonly Compilation _compilation; | ||
|
|
||
| private readonly Action<Diagnostic>? _reportDiagnostic; | ||
|
|
||
| public DiagnosticContext(Location location, Action<Diagnostic>? reportDiagnostic) | ||
| public DiagnosticContext(Location location, Compilation compilation, Action<Diagnostic>? reportDiagnostic) | ||
| { | ||
| Location = location; | ||
| _compilation = compilation; | ||
| _reportDiagnostic = reportDiagnostic; | ||
| } | ||
|
|
||
|
|
@@ -68,7 +71,7 @@ private Diagnostic CreateDiagnostic(DiagnosticId id, ValueWithDynamicallyAccesse | |
| Dictionary<string, string?>? DAMArgument = new Dictionary<string, string?>(); | ||
|
|
||
| // not supporting merging differing attributes, check to make sure symbol has no other attributes | ||
| if (symbol.DeclaringSyntaxReferences.Length == 0 | ||
| if (!CanOfferCodeFixOn(symbol) | ||
| || (actualValue is not MethodReturnValue | ||
| && symbol.TryGetAttribute(DynamicallyAccessedMembersAnalyzer.DynamicallyAccessedMembersAttribute, out var _)) | ||
| || (actualValue is MethodReturnValue | ||
|
|
@@ -88,5 +91,20 @@ private Diagnostic CreateDiagnostic(DiagnosticId id, ValueWithDynamicallyAccesse | |
|
|
||
| return Diagnostic.Create(DiagnosticDescriptors.GetDiagnosticDescriptor(id), Location, sourceLocation, DAMArgument?.ToImmutableDictionary(), args); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Determines whether a code fix location can be attached to a diagnostic for <paramref name="symbol"/>. | ||
| /// The symbol must be declared in source that is part of the compilation being analyzed, otherwise Roslyn | ||
| /// rejects the reported diagnostic. Note that a symbol from another project can still have declaring syntax | ||
| /// references: the IDE models project-to-project references as compilation references, which expose source | ||
| /// symbols whose syntax trees belong to a different compilation. | ||
| /// </summary> | ||
| private bool CanOfferCodeFixOn(ISymbol symbol) | ||
|
Member
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. Actually, I was taking a different approach. This is approaching the problem from trying to fix things in many different scenarios and then catching mistakes. I think a more robust solution would be to use the inherent structure of the program to decide when a code fix should be offered. So, an example: in a base/override scenario, we can always offer the code fixer on the override, and just ignore fixes that on the base entirely. We actually don't have to know about the compilation at all to know that the override always belongs to the current compilation. |
||
| { | ||
| if (symbol.DeclaringSyntaxReferences.Length == 0) | ||
|
Member
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. If we set things up right I think this should actually be impossible to hit. |
||
| return false; | ||
|
|
||
| return _compilation.ContainsSyntaxTree(symbol.DeclaringSyntaxReferences[0].SyntaxTree); | ||
| } | ||
| } | ||
| } | ||
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 think we can stop attaching additional source locations here entirely. Symbols don't need locations because the code fixer can completely hydrate all necessary information without looking at locations at all.
The only place we might need auxiliary information is in data flow.
Same is true for DAM annotations -- on symbols this info is trivial to reconstruct in the fixer. It's only helpful to attach that info for flow analysis, where the fixer would have to re-do all of flow analysis to find the right data.