diff --git a/src/tools/illink/src/ILLink.CodeFix/DynamicallyAccessedMembersCodeFixProvider.cs b/src/tools/illink/src/ILLink.CodeFix/DynamicallyAccessedMembersCodeFixProvider.cs index f644fec90ea3be..0cb09009e6a1ad 100644 --- a/src/tools/illink/src/ILLink.CodeFix/DynamicallyAccessedMembersCodeFixProvider.cs +++ b/src/tools/illink/src/ILLink.CodeFix/DynamicallyAccessedMembersCodeFixProvider.cs @@ -94,21 +94,26 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) { var document = context.Document; var diagnostic = context.Diagnostics[0]; - var codeFixTitle = CodeFixTitle.ToString(); - - if (await document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false) is not { } root) - return; if (diagnostic.AdditionalLocations.Count == 0) return; - if (root.FindNode(diagnostic.AdditionalLocations[0].SourceSpan, getInnermostNodeForTie: true) is not SyntaxNode targetNode) + if (diagnostic.AdditionalLocations[0].SourceTree is not { } targetTree) + return; + if (document.Project.Solution.GetDocument(targetTree) is not { } targetDocument) + return; + if (await targetDocument.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false) is not { } targetRoot) + return; + if (targetRoot.FindNode(diagnostic.AdditionalLocations[0].SourceSpan, getInnermostNodeForTie: true) is not SyntaxNode targetNode) return; - if (diagnostic.Properties["attributeArgument"] is not string stringArgs || stringArgs.Contains(",")) + if (!diagnostic.Properties.TryGetValue(DynamicallyAccessedMembersAnalyzer.attributeArgument, out string? stringArgs) + || stringArgs is null + || stringArgs.Contains(",")) return; + string codeFixTitle = CodeFixTitle.ToString(); context.RegisterCodeFix(CodeAction.Create( - title: CodeFixTitle.ToString(), + title: codeFixTitle, createChangedDocument: ct => AddAttributeAsync( - document, + targetDocument, targetNode, stringArgs, addAsReturnAttribute: AttributeOnReturn.Contains(diagnostic.Id), diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/DynamicallyAccessedMembersAnalyzer.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/DynamicallyAccessedMembersAnalyzer.cs index 5aea7858179e71..ed50545ff35439 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/DynamicallyAccessedMembersAnalyzer.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/DynamicallyAccessedMembersAnalyzer.cs @@ -216,22 +216,22 @@ private static void VerifyDamOnMethodsMatch(SymbolAnalysisContext context, IMeth var baseMethodReturnAnnotation = FlowAnnotations.GetMethodReturnValueAnnotation(baseMethod); if (overrideMethodReturnAnnotation != baseMethodReturnAnnotation) { + Location[]? additionalLocations = null; + ImmutableDictionary? properties = null; + if (overrideMethodReturnAnnotation == DynamicallyAccessedMemberTypes.None + && !overrideMethod.TryGetReturnAttribute(DynamicallyAccessedMembersAttribute, out _)) + { + (additionalLocations, properties) = CreateCodeFixArguments( + context.Compilation, + GetPrimaryLocation(overrideMethod.Locations), + baseMethodReturnAnnotation); + } - (IMethodSymbol attributableMethod, DynamicallyAccessedMemberTypes missingAttribute) = GetTargetAndRequirements(overrideMethod, - baseMethod, overrideMethodReturnAnnotation, baseMethodReturnAnnotation); - - 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? DAMArgs) = (!attributableSymbolLocation.IsInSource - || (overrideMethod.TryGetReturnAttribute(DynamicallyAccessedMembersAnalyzer.DynamicallyAccessedMembersAttribute, out var _) - && baseMethod.TryGetReturnAttribute(DynamicallyAccessedMembersAnalyzer.DynamicallyAccessedMembersAttribute, out var _)) - ) ? (null, null) : CreateArguments(attributableSymbolLocation, missingAttribute); - - var returnOrigin = origin ??= overrideMethod; + var returnOrigin = origin ?? overrideMethod; context.ReportDiagnostic(Diagnostic.Create( DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.DynamicallyAccessedMembersMismatchOnMethodReturnValueBetweenOverrides), - GetPrimaryLocation(returnOrigin.Locations), sourceLocation, DAMArgs?.ToImmutableDictionary(), overrideMethod.GetDisplayName(), baseMethod.GetDisplayName())); + GetPrimaryLocation(returnOrigin.Locations), additionalLocations, properties, + overrideMethod.GetDisplayName(), baseMethod.GetDisplayName())); } foreach (var overrideParam in overrideMethod.GetMetadataParameters()) @@ -241,21 +241,21 @@ private static void VerifyDamOnMethodsMatch(SymbolAnalysisContext context, IMeth var overrideParameterAnnotation = FlowAnnotations.GetMethodParameterAnnotation(overrideParam); if (overrideParameterAnnotation != baseParameterAnnotation) { - (IMethodSymbol attributableMethod, DynamicallyAccessedMemberTypes missingAttribute) = GetTargetAndRequirements(overrideMethod, - baseMethod, overrideParameterAnnotation, baseParameterAnnotation); - - 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? DAMArgs) = (!attributableSymbolLocation.IsInSource - || (overrideParam.ParameterSymbol!.TryGetAttribute(DynamicallyAccessedMembersAnalyzer.DynamicallyAccessedMembersAttribute, out var _) - && baseParam.ParameterSymbol!.TryGetAttribute(DynamicallyAccessedMembersAnalyzer.DynamicallyAccessedMembersAttribute, out var _)) - ) ? (null, null) : CreateArguments(attributableSymbolLocation, missingAttribute); + Location[]? additionalLocations = null; + ImmutableDictionary? properties = null; + if (overrideParameterAnnotation == DynamicallyAccessedMemberTypes.None + && !overrideParam.ParameterSymbol!.TryGetAttribute(DynamicallyAccessedMembersAttribute, out _)) + { + (additionalLocations, properties) = CreateCodeFixArguments( + context.Compilation, + overrideParam.Location!, + baseParameterAnnotation); + } var parameterOrigin = origin ?? overrideParam.ParameterSymbol; context.ReportDiagnostic(Diagnostic.Create( DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.DynamicallyAccessedMembersMismatchOnMethodParameterBetweenOverrides), - GetPrimaryLocation(parameterOrigin?.Locations), sourceLocation, DAMArgs?.ToImmutableDictionary(), + GetPrimaryLocation(parameterOrigin?.Locations), additionalLocations, properties, overrideParam.GetDisplayName(), overrideMethod.GetDisplayName(), baseParam.GetDisplayName(), baseMethod.GetDisplayName())); } } @@ -266,22 +266,10 @@ private static void VerifyDamOnMethodsMatch(SymbolAnalysisContext context, IMeth var overriddenMethodTypeParameterAnnotation = baseMethod.TypeParameters[i].GetDynamicallyAccessedMemberTypes(); if (methodTypeParameterAnnotation != overriddenMethodTypeParameterAnnotation) { - - (IMethodSymbol attributableMethod, DynamicallyAccessedMemberTypes missingAttribute) = GetTargetAndRequirements(overrideMethod, baseMethod, methodTypeParameterAnnotation, overriddenMethodTypeParameterAnnotation); - - var attributableSymbol = attributableMethod.TypeParameters[i]; - 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? DAMArgs) = (!attributableSymbolLocation.IsInSource - || (overrideMethod.TypeParameters[i].TryGetAttribute(DynamicallyAccessedMembersAnalyzer.DynamicallyAccessedMembersAttribute, out var _) - && baseMethod.TypeParameters[i].TryGetAttribute(DynamicallyAccessedMembersAnalyzer.DynamicallyAccessedMembersAttribute, out var _)) - ) ? (null, null) : CreateArguments(attributableSymbolLocation, missingAttribute); - var typeParameterOrigin = origin ?? overrideMethod.TypeParameters[i]; context.ReportDiagnostic(Diagnostic.Create( DiagnosticDescriptors.GetDiagnosticDescriptor(DiagnosticId.DynamicallyAccessedMembersMismatchOnGenericParameterBetweenOverrides), - GetPrimaryLocation(typeParameterOrigin.Locations), sourceLocation, DAMArgs?.ToImmutableDictionary(), + GetPrimaryLocation(typeParameterOrigin.Locations), overrideMethod.TypeParameters[i].GetDisplayName(), overrideMethod.GetDisplayName(), baseMethod.TypeParameters[i].GetDisplayName(), baseMethod.GetDisplayName())); } @@ -308,7 +296,7 @@ private static void VerifyDamOnInterfaceAndImplementationMethodsMatch(SymbolAnal { if (implementationMember is IMethodSymbol implementationMethod && interfaceMember is IMethodSymbol interfaceMethod) { - ISymbol origin = implementationMethod; + ISymbol? origin = null; INamedTypeSymbol implementationType = implementationMethod.ContainingType; // If this type implements an interface method through a base class, the origin of the warning is this type, @@ -351,29 +339,24 @@ private static void VerifyDamOnPropertyAndAccessorMatch(SymbolAnalysisContext co } } - private static (IMethodSymbol Method, DynamicallyAccessedMemberTypes Requirements) GetTargetAndRequirements(IMethodSymbol method, IMethodSymbol overriddenMethod, DynamicallyAccessedMemberTypes methodAnnotation, DynamicallyAccessedMemberTypes overriddenMethodAnnotation) + private static (Location[]?, ImmutableDictionary?) CreateCodeFixArguments( + Compilation compilation, + Location targetLocation, + DynamicallyAccessedMemberTypes annotation) { - DynamicallyAccessedMemberTypes mismatchedArgument; - IMethodSymbol paramNeedsAttributes; - if (methodAnnotation == DynamicallyAccessedMemberTypes.None) - { - mismatchedArgument = overriddenMethodAnnotation; - paramNeedsAttributes = method; - } - else + if (targetLocation.SourceTree is not { } syntaxTree + || !compilation.ContainsSyntaxTree(syntaxTree)) { - mismatchedArgument = methodAnnotation; - paramNeedsAttributes = overriddenMethod; + return default; } - return (paramNeedsAttributes, mismatchedArgument); - } - private static (Location[]?, Dictionary?) CreateArguments(Location attributableSymbolLocation, DynamicallyAccessedMemberTypes mismatchedArgument) - { - Dictionary? DAMArgument = new(); - Location[]? sourceLocation = new Location[] { attributableSymbolLocation }; - DAMArgument.Add(DynamicallyAccessedMembersAnalyzer.attributeArgument, mismatchedArgument.ToString()); - return (sourceLocation, DAMArgument); + return ( + [targetLocation], + new Dictionary + { + [attributeArgument] = annotation.ToString() + }.ToImmutableDictionary()); } + } } diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresAnalyzerBase.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresAnalyzerBase.cs index d811bb8de6afe0..0b068c950b5ba2 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresAnalyzerBase.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/RequiresAnalyzerBase.cs @@ -53,7 +53,7 @@ public virtual void ProcessGenericInstantiation( if (publicParameterlessConstructor != null) { - var diagnosticContext = new DiagnosticContext(location, reportDiagnostic); + var diagnosticContext = new DiagnosticContext(location, reportDiagnostic, typeNameResolver.Compilation); CheckAndCreateRequiresDiagnostic( publicParameterlessConstructor, owningSymbol, @@ -201,7 +201,8 @@ private void AnalyzeImplicitBaseCtor(SymbolAnalysisContext context) var diagnosticContext = new DiagnosticContext( typeSymbol.Locations[0], - context.ReportDiagnostic); + context.ReportDiagnostic, + context.Compilation); CheckAndCreateRequiresDiagnostic( baseCtor, diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/DAM-code-fix.md b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/DAM-code-fix.md index 8f48b146bbed34..058caac6bbb331 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/DAM-code-fix.md +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/DAM-code-fix.md @@ -6,12 +6,14 @@ The DAM warning pattern can be annotated in a way that makes the reflection usag Once initialized, the analyzer walks the compiler-generated AST of the program to determine coherent use of DAM attributes and where they may be necessary. This is achieved by considering uses of annotated fields, methods, and parameters. If an inconsistent use is detected, the analyzer will trigger a warning and report a diagnostic. ### How information passes from Analyzer to Code Fix -The DAM Analyzer reports diagnostics that contain information about the specific warning, including the warning ID (`descriptor`), the location of the warning (`location`), the location where a code fix may be applied (`additionalLocations`), the argument to be included in the DAM attribute to be applied (`properties`), and any additional arguments (`messageArgs`). These diagnostics are then unpacked by the Code Fixer. +The DAM Analyzer reports diagnostics that carry the warning ID (`descriptor`), a primary source location (`location`), and message arguments (`messageArgs`). +Data-flow diagnostics also carry the propagated DAM requirement in `properties["attributeArgument"]` and the declaration of the symbol that needs the attribute as an additional location. The declaration location is included only when its syntax tree belongs to the current compilation. This prevents diagnostics from containing source locations from a referenced compilation. +Override and interface diagnostics use the same guarded location and property format when the local implementation is missing an attribute that is present on the related contract. They do not offer fixes that remove or replace an existing attribute. ### How the Code Fix changes the file -The Code Fix uses `SyntaxGenerator` to create the DAM attribute to add from the DAM argument passed through the `properties` dictionary. The Syntax Node that the attribute is applied to is found from the `additionalLocations` of the diagnostic. `SyntaxEditor` applies the attribute to the location specified and update the original document. +For data-flow diagnostics, the Code Fix resolves the document containing the additional location. `SyntaxGenerator` builds the DAM attribute from `properties["attributeArgument"]`, and `SyntaxEditor` applies it to that declaration. If the analyzer did not provide a local declaration location, no fix is offered. ## Future Work -1. **Multiple Arguments:** The Code Fix does not support the case where there are multiple arguments present on a node (i.e. `DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.PublicFields)`). +1. **Multiple Arguments:** The Code Fix does not support adding an attribute with multiple arguments (i.e. `DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.PublicFields`). 2. **Merging Arguments:** When there are two differing DAM attributes on nodes that should have the same attribute, we do not provide a Code Fix. However, we could read which attributes are present, merge them, and replace the attributes in both locations. 3. **Replace Checks in `DAMCodeFixProvider.AddAttributeAsync()`:** Changes to `AddAttribute()` and `AddReturnAttribute()` were made that should be updated in the `DAMCodeFixProvider` once the new Roslyn package is published and the repo uses the new package. We can remove the `addGenericParameterAttribute` check from `DAMCodeFixProvider.AddReturnAttribute()` entirely as the API will support adding a generic parameter using `AddAttribute()`. Additionally, we can replace the lambda function in the return attribute check with `AddReturnAttribute()`. diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/DiagnosticContext.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/DiagnosticContext.cs index 335f845d9e4800..27571a04ab1ac1 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/DiagnosticContext.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/DiagnosticContext.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; -using System.Diagnostics; using ILLink.RoslynAnalyzer; using Microsoft.CodeAnalysis; @@ -15,11 +14,13 @@ public readonly partial struct DiagnosticContext public readonly Location Location { get; } private readonly Action? _reportDiagnostic; + private readonly Compilation _compilation; - public DiagnosticContext(Location location, Action? reportDiagnostic) + public DiagnosticContext(Location location, Action? reportDiagnostic, Compilation compilation) { Location = location; _reportDiagnostic = reportDiagnostic; + _compilation = compilation; } private Diagnostic CreateDiagnostic(DiagnosticId id, params string[] args) @@ -45,8 +46,6 @@ public partial void AddDiagnostic(DiagnosticId id, ValueWithDynamicallyAccessedM private Diagnostic CreateDiagnostic(DiagnosticId id, ValueWithDynamicallyAccessedMembers actualValue, ValueWithDynamicallyAccessedMembers expectedAnnotationsValue, params string[] args) { - Debug.Assert(Location != null); - actualValue = actualValue switch { NullableValueWithDynamicallyAccessedMembers nv => nv.UnderlyingTypeValue, @@ -57,36 +56,48 @@ private Diagnostic CreateDiagnostic(DiagnosticId id, ValueWithDynamicallyAccesse ISymbol symbol = actualValue switch { FieldValue field => field.FieldSymbol, - MethodParameterValue maybeThisParameter when maybeThisParameter.Parameter.IsImplicitThis => maybeThisParameter.MethodSymbol, - MethodParameterValue methodParameter => methodParameter.Parameter.ParameterSymbol!, - MethodReturnValue mrv => mrv.MethodSymbol, - GenericParameterValue gpv => gpv.GenericParameter.TypeParameterSymbol, + MethodParameterValue { Parameter.IsImplicitThis: true } thisParameter => thisParameter.MethodSymbol, + MethodParameterValue { Parameter.ParameterSymbol: { } parameterSymbol } => parameterSymbol, + MethodReturnValue methodReturnValue => methodReturnValue.MethodSymbol, + GenericParameterValue genericParameter => genericParameter.GenericParameter.TypeParameterSymbol, _ => throw new InvalidOperationException() }; - Location[]? sourceLocation; - Dictionary? DAMArgument = new Dictionary(); + bool hasAttribute = actualValue is MethodReturnValue + ? ((IMethodSymbol)symbol).TryGetReturnAttribute( + DynamicallyAccessedMembersAnalyzer.DynamicallyAccessedMembersAttribute, + out _) + : symbol.TryGetAttribute( + DynamicallyAccessedMembersAnalyzer.DynamicallyAccessedMembersAttribute, + out _); - // not supporting merging differing attributes, check to make sure symbol has no other attributes - if (symbol.DeclaringSyntaxReferences.Length == 0 - || (actualValue is not MethodReturnValue - && symbol.TryGetAttribute(DynamicallyAccessedMembersAnalyzer.DynamicallyAccessedMembersAttribute, out var _)) - || (actualValue is MethodReturnValue - && symbol is IMethodSymbol method - && method.TryGetReturnAttribute(DynamicallyAccessedMembersAnalyzer.DynamicallyAccessedMembersAttribute, out var _))) + Dictionary? properties = null; + Location[]? additionalLocations = null; + if (!hasAttribute && TryGetLocalDeclarationLocation(symbol, out Location declarationLocation)) { - sourceLocation = null; - DAMArgument = null; + properties = new Dictionary + { + ["attributeArgument"] = expectedAnnotationsValue.DynamicallyAccessedMemberTypes.ToString(), + }; + additionalLocations = [declarationLocation]; } - else + + return Diagnostic.Create(DiagnosticDescriptors.GetDiagnosticDescriptor(id), Location, additionalLocations, properties?.ToImmutableDictionary(), args); + } + + private bool TryGetLocalDeclarationLocation(ISymbol symbol, out Location location) + { + foreach (SyntaxReference syntaxReference in symbol.DeclaringSyntaxReferences) { - Location symbolLocation; - symbolLocation = symbol.DeclaringSyntaxReferences[0].GetSyntax().GetLocation(); - DAMArgument.Add("attributeArgument", expectedAnnotationsValue.DynamicallyAccessedMemberTypes.ToString()); - sourceLocation = new Location[] { symbolLocation }; + if (_compilation.ContainsSyntaxTree(syntaxReference.SyntaxTree)) + { + location = syntaxReference.GetSyntax().GetLocation(); + return true; + } } - return Diagnostic.Create(DiagnosticDescriptors.GetDiagnosticDescriptor(id), Location, sourceLocation, DAMArgument?.ToImmutableDictionary(), args); + location = null!; + return false; } } } diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/FeatureCheckReturnValuePattern.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/FeatureCheckReturnValuePattern.cs index ed69b463b7a67f..03bf249b948941 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/FeatureCheckReturnValuePattern.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/FeatureCheckReturnValuePattern.cs @@ -32,7 +32,7 @@ public FeatureCheckReturnValuePattern( public void ReportDiagnostics(DataFlowAnalyzerContext context, Action reportDiagnostic) { - var diagnosticContext = new DiagnosticContext(Operation.Syntax.GetLocation(), reportDiagnostic); + var diagnosticContext = new DiagnosticContext(Operation.Syntax.GetLocation(), reportDiagnostic, context.Compilation); // For now, feature check validation is enabled only when trim analysis is enabled. if (context.TrimAnalyzer is null) return; diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/HandleCallAction.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/HandleCallAction.cs index 3c691e00b3af0a..6d9e717f5cfe64 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/HandleCallAction.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/HandleCallAction.cs @@ -41,7 +41,7 @@ public HandleCallAction( _owningSymbol = owningSymbol; _operation = operation; _isNewObj = operation.Kind == OperationKind.ObjectCreation; - _diagnosticContext = new DiagnosticContext(location, reportDiagnostic); + _diagnosticContext = new DiagnosticContext(location, reportDiagnostic, typeNameResolver.Compilation); _annotations = FlowAnnotations.Instance; _reflectionAccessAnalyzer = new(reportDiagnostic, typeNameResolver, typeHierarchyType: null); _typeNameResolver = typeNameResolver; diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/ReflectionAccessAnalyzer.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/ReflectionAccessAnalyzer.cs index e27c79d03506cd..510590119da30c 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/ReflectionAccessAnalyzer.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/ReflectionAccessAnalyzer.cs @@ -96,7 +96,7 @@ private void ReportRequiresUnreferencedCodeDiagnostic(Location location, Attribu { var message = RequiresUnreferencedCodeUtils.GetMessageFromAttribute(requiresAttributeData); var url = RequiresAnalyzerBase.GetUrlFromAttribute(requiresAttributeData); - var diagnosticContext = new DiagnosticContext(location, _reportDiagnostic); + var diagnosticContext = new DiagnosticContext(location, _reportDiagnostic, _typeNameResolver.Compilation); diagnosticContext.AddDiagnostic(DiagnosticId.RequiresUnreferencedCode, member.GetDisplayName(), message, url); } @@ -146,7 +146,7 @@ static bool IsDeclaredWithinType(ISymbol member, INamedTypeSymbol type) if (reportOnMember) location = DynamicallyAccessedMembersAnalyzer.GetPrimaryLocation(member.Locations); - var diagnosticContext = new DiagnosticContext(location, _reportDiagnostic); + var diagnosticContext = new DiagnosticContext(location, _reportDiagnostic, _typeNameResolver.Compilation); if (member.IsInRequiresUnreferencedCodeAttributeScope(out AttributeData? requiresUnreferencedCodeAttribute)) { @@ -166,7 +166,7 @@ static bool IsDeclaredWithinType(ISymbol member, INamedTypeSymbol type) internal void GetDiagnosticsForReflectionAccessToDAMOnMethod(Location location, IMethodSymbol methodSymbol) { - var diagnosticContext = new DiagnosticContext(location, _reportDiagnostic); + var diagnosticContext = new DiagnosticContext(location, _reportDiagnostic, _typeNameResolver.Compilation); if (methodSymbol.IsVirtual && FlowAnnotations.GetMethodReturnValueAnnotation(methodSymbol) != DynamicallyAccessedMemberTypes.None) { diagnosticContext.AddDiagnostic(DiagnosticId.DynamicallyAccessedMembersMethodAccessedViaReflection, methodSymbol.GetDisplayName()); @@ -215,7 +215,7 @@ private void GetDiagnosticsForField(Location location, IFieldSymbol fieldSymbol) if (FlowAnnotations.GetFieldAnnotation(fieldSymbol) != DynamicallyAccessedMemberTypes.None) { - var diagnosticContext = new DiagnosticContext(location, _reportDiagnostic); + var diagnosticContext = new DiagnosticContext(location, _reportDiagnostic, _typeNameResolver.Compilation); diagnosticContext.AddDiagnostic(DiagnosticId.DynamicallyAccessedMembersFieldAccessedViaReflection, fieldSymbol.GetDisplayName()); } } diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/RequireDynamicallyAccessedMembersAction.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/RequireDynamicallyAccessedMembersAction.cs index 3a7ff369c0df8f..10302442b74f05 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/RequireDynamicallyAccessedMembersAction.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/RequireDynamicallyAccessedMembersAction.cs @@ -42,12 +42,12 @@ public RequireDynamicallyAccessedMembersAction( _reportDiagnostic = reportDiagnostic; _reflectionAccessAnalyzer = reflectionAccessAnalyzer; _owningSymbol = owningSymbol; - _diagnosticContext = new(location, reportDiagnostic); + _diagnosticContext = new(location, reportDiagnostic, typeNameResolver.Compilation); } public partial bool TryResolveTypeNameAndMark(string typeName, bool needsAssemblyName, out TypeProxy type) { - var diagnosticContext = new DiagnosticContext(_location, _reportDiagnostic); + var diagnosticContext = new DiagnosticContext(_location, _reportDiagnostic, _typeNameResolver.Compilation); if (_reflectionAccessAnalyzer.TryResolveTypeNameAndMark(typeName, diagnosticContext, needsAssemblyName, out ITypeSymbol? foundType)) { if (foundType is INamedTypeSymbol namedType && namedType.IsGenericType) diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisBackingFieldAccessPattern.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisBackingFieldAccessPattern.cs index 7ad0d048f50125..bd3ad1dd45b74a 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisBackingFieldAccessPattern.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisBackingFieldAccessPattern.cs @@ -47,7 +47,7 @@ public TrimAnalysisBackingFieldAccessPattern Merge( public void ReportDiagnostics(DataFlowAnalyzerContext context, Action reportDiagnostic) { - DiagnosticContext diagnosticContext = new(Operation.Syntax.GetLocation(), reportDiagnostic); + DiagnosticContext diagnosticContext = new(Operation.Syntax.GetLocation(), reportDiagnostic, context.Compilation); foreach (var requiresAnalyzer in context.EnabledRequiresAnalyzers) requiresAnalyzer.CheckAndCreateRequiresDiagnostic(Operation, Property, OwningSymbol, context, FeatureContext, in diagnosticContext); } diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisFieldAccessPattern.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisFieldAccessPattern.cs index 35ad06cef57f4c..a4a18a9be10e79 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisFieldAccessPattern.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisFieldAccessPattern.cs @@ -46,7 +46,7 @@ public TrimAnalysisFieldAccessPattern Merge( public void ReportDiagnostics(DataFlowAnalyzerContext context, Action reportDiagnostic) { - DiagnosticContext diagnosticContext = new(Operation.Syntax.GetLocation(), reportDiagnostic); + DiagnosticContext diagnosticContext = new(Operation.Syntax.GetLocation(), reportDiagnostic, context.Compilation); foreach (var requiresAnalyzer in context.EnabledRequiresAnalyzers) requiresAnalyzer.CheckAndCreateRequiresDiagnostic(Operation, Field, OwningSymbol, context, FeatureContext, in diagnosticContext); } diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisMethodCallPattern.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisMethodCallPattern.cs index 69d70a1ef8c74e..148094d37c2688 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisMethodCallPattern.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisMethodCallPattern.cs @@ -92,7 +92,7 @@ public void ReportDiagnostics(DataFlowAnalyzerContext context, Action invocationSyntax.Expression.GetLocation(), _ => location }; - var diagnosticContext = new DiagnosticContext(location, reportDiagnostic); + var diagnosticContext = new DiagnosticContext(location, reportDiagnostic, context.Compilation); foreach (var requiresAnalyzer in context.EnabledRequiresAnalyzers) { if (!requiresAnalyzer.IsIntrinsicallyHandled(CalledMethod, Instance, Arguments)) diff --git a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisReflectionAccessPattern.cs b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisReflectionAccessPattern.cs index 281e93e06f542f..2281e7a53aefdd 100644 --- a/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisReflectionAccessPattern.cs +++ b/src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisReflectionAccessPattern.cs @@ -55,7 +55,7 @@ public void ReportDiagnostics(DataFlowAnalyzerContext context, Action _compilation; + static readonly TypeNameParseOptions s_typeNameParseOptions = new() { MaxNodes = int.MaxValue }; public TypeNameResolver(Compilation compilation) diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/DynamicallyAccessedMembersAnalyzerTests.cs b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/DynamicallyAccessedMembersAnalyzerTests.cs index 2db6ae9f47800f..a3ce266bffb575 100644 --- a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/DynamicallyAccessedMembersAnalyzerTests.cs +++ b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/DynamicallyAccessedMembersAnalyzerTests.cs @@ -18,6 +18,7 @@ static Task VerifyDynamicallyAccessedMembersAnalyzer( bool consoleApplication, params DiagnosticResult[] expected) { + IgnoreAdditionalLocations(expected); return VerifyCS.VerifyAnalyzerAsync( source, consoleApplication, @@ -25,6 +26,12 @@ static Task VerifyDynamicallyAccessedMembersAnalyzer( expected: expected); } + private static void IgnoreAdditionalLocations(DiagnosticResult[] diagnostics) + { + for (int i = 0; i < diagnostics.Length; i++) + diagnostics[i] = diagnostics[i].WithOptions(DiagnosticOptions.IgnoreAdditionalLocations); + } + [Fact] public Task NoWarningsIfAnalyzerIsNotEnabled() { @@ -92,7 +99,6 @@ private static void M(Type type) return VerifyDynamicallyAccessedMembersAnalyzer(TargetParameterWithAnnotations, consoleApplication: false, VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchParameterTargetsParameter) .WithSpan(21, 9, 21, 44) - .WithSpan(19, 27, 19, 36) .WithArguments("parameter", "C.NeedsPublicMethodsOnParameter(Type)", "type", @@ -132,7 +138,6 @@ private static Type M(Type type) return VerifyDynamicallyAccessedMembersAnalyzer(TargetMethodReturnTypeWithAnnotations, consoleApplication: false, VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchParameterTargetsMethodReturnType) .WithSpan(18, 16, 18, 20) - .WithSpan(16, 27, 16, 36) .WithArguments("C.M(Type)", "type", "C.M(Type)", @@ -173,7 +178,6 @@ private static void M(Type type) return VerifyDynamicallyAccessedMembersAnalyzer(TargetFieldWithAnnotations, consoleApplication: false, VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchParameterTargetsField) .WithSpan(17, 9, 17, 17) - .WithSpan(15, 27, 15, 36) .WithArguments("C.f", "type", "C.M(Type)", @@ -211,7 +215,6 @@ private static void M(Type type) return VerifyDynamicallyAccessedMembersAnalyzer(TargetMethodWithAnnotations, consoleApplication: false, VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchParameterTargetsThisParameter) .WithSpan(16, 9, 16, 30) - .WithSpan(14, 27, 14, 36) .WithArguments("System.Type.GetMethod(String)", "type", "C.M(Type)", @@ -254,7 +257,6 @@ private static Type GetT() return VerifyDynamicallyAccessedMembersAnalyzer(TargetParameterWithAnnotations, consoleApplication: false, VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchMethodReturnTypeTargetsParameter) .WithSpan(12, 9, 12, 46) - .WithSpan(20, 5, 23, 6) .WithArguments("type", "C.NeedsPublicMethodsOnParameter(Type)", "C.GetT()", "'DynamicallyAccessedMemberTypes.PublicMethods'")); } @@ -293,7 +295,6 @@ private static Type GetFoo() return VerifyDynamicallyAccessedMembersAnalyzer(TargetMethodReturnTypeWithAnnotations, consoleApplication: false, VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchMethodReturnTypeTargetsMethodReturnType) .WithSpan(18, 16, 18, 24) - .WithSpan(21, 5, 24, 6) .WithArguments("C.M()", "C.GetFoo()", "'DynamicallyAccessedMemberTypes.PublicMethods'")); } @@ -329,7 +330,6 @@ private static Type M() return VerifyDynamicallyAccessedMembersAnalyzer(TargetFieldWithAnnotations, consoleApplication: false, VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchMethodReturnTypeTargetsField) .WithSpan(12, 9, 12, 16) - .WithSpan(15, 5, 18, 6) .WithArguments("C.f", "C.M()", "'DynamicallyAccessedMemberTypes.PublicMethods'")); @@ -365,10 +365,46 @@ private static Type GetFoo() return VerifyDynamicallyAccessedMembersAnalyzer(TargetMethodWithAnnotations, consoleApplication: false, VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchMethodReturnTypeTargetsThisParameter) .WithSpan(11, 9, 11, 34) - .WithSpan(15, 5, 18, 6) .WithArguments("System.Type.GetMethod(String)", "C.GetFoo()", "'DynamicallyAccessedMemberTypes.PublicMethods'")); } + [Fact] + public async Task SourceMethodReturnTypeFromCompilationReferenceDoesNotUseForeignLocation() + { + var referenceSource = """ + using System; + + public static class External + { + public static Type GetType() => typeof(object); + } + """; + var source = """ + using System; + + class C + { + void M() + { + External.GetType().GetMethod("M"); + } + } + """; + + var test = ReferenceCompatibilityTestUtils.CreateTestWithCompilationReference( + source, referenceSource); + test.TestState.AnalyzerConfigFiles.Add(("/.editorconfig", Microsoft.CodeAnalysis.Text.SourceText.From($""" + is_global = true + build_property.{MSBuildPropertyOptionNames.EnableTrimAnalyzer} = true + """))); + test.ExpectedDiagnostics.Add( + VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchMethodReturnTypeTargetsThisParameter) + .WithSpan(7, 9, 7, 42) + .WithArguments("System.Type.GetMethod(String)", "External.GetType()", "'DynamicallyAccessedMemberTypes.PublicMethods'")); + + await test.RunAsync(); + } + #endregion #region SourceField @@ -405,7 +441,6 @@ private static void NeedsPublicMethods( return VerifyDynamicallyAccessedMembersAnalyzer(TargetParameterWithAnnotations, consoleApplication: false, VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchFieldTargetsParameter) .WithSpan(14, 9, 14, 30) - .WithSpan(10, 25, 10, 40) .WithArguments("type", "C.NeedsPublicMethods(Type)", "C.f", @@ -446,7 +481,6 @@ private static Type M() return VerifyDynamicallyAccessedMembersAnalyzer(TargetMethodReturnTypeWithAnnotations, consoleApplication: false, VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchFieldTargetsMethodReturnType) .WithSpan(20, 16, 20, 17) - .WithSpan(10, 25, 10, 40) .WithArguments("C.M()", "C.f", "'DynamicallyAccessedMemberTypes.PublicMethods'")); } @@ -481,7 +515,6 @@ public static void Main() return VerifyDynamicallyAccessedMembersAnalyzer(TargetFieldWithAnnotations, consoleApplication: false, VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchFieldTargetsField) .WithSpan(17, 9, 17, 16) - .WithSpan(10, 25, 10, 41) .WithArguments("C.f2", "C.f1", "'DynamicallyAccessedMemberTypes.PublicMethods'")); @@ -515,7 +548,6 @@ public static void Main() return VerifyDynamicallyAccessedMembersAnalyzer(TargetMethodWithAnnotations, consoleApplication: false, VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchFieldTargetsThisParameter) .WithSpan(13, 9, 13, 27) - .WithSpan(9, 25, 9, 40) .WithArguments("System.Type.GetMethod(String)", "C.f", "'DynamicallyAccessedMemberTypes.PublicMethods'")); @@ -750,7 +782,6 @@ private static void M2( return VerifyDynamicallyAccessedMembersAnalyzer(string.Concat(GetSystemTypeBase(), TargetParameterWithAnnotations), consoleApplication: false, VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchThisParameterTargetsParameter) .WithSpan(198, 13, 198, 21) - .WithSpan(196, 9, 199, 10) .WithArguments("type", "System.C.M2(Type)", "System.C.M1()", "'DynamicallyAccessedMemberTypes.PublicMethods'")); } @@ -790,7 +821,6 @@ private static void M2( // (203,13): warning IL2072: 'type' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicMethods' in call to 'System.C.M2(Type)'. The return value of method 'System.ConvertsToType.implicit operator Type(ConvertsToType)' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to. VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchMethodReturnTypeTargetsParameter) .WithSpan(203, 13, 203, 37) - .WithSpan(191, 9, 191, 94) .WithArguments("type", "System.C.M2(Type)", "System.ConvertsToType.implicit operator Type(ConvertsToType)", "'DynamicallyAccessedMemberTypes.PublicMethods'")); } @@ -902,7 +932,6 @@ private Type M() return VerifyDynamicallyAccessedMembersAnalyzer(string.Concat(GetSystemTypeBase(), TargetMethodReturnTypeWithAnnotations), consoleApplication: false, VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchThisParameterTargetsMethodReturnType) .WithSpan(200, 20, 200, 24) - .WithSpan(196, 9, 201, 10) .WithArguments("System.C.M()", "System.C.M()", "'DynamicallyAccessedMemberTypes.PublicMethods'")); } @@ -937,7 +966,6 @@ private void M() return VerifyDynamicallyAccessedMembersAnalyzer(string.Concat(GetSystemTypeBase(), TargetFieldWithAnnotations), consoleApplication: false, VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchThisParameterTargetsField) .WithSpan(198, 13, 198, 21) - .WithSpan(196, 9, 199, 10) .WithArguments("System.C.f", "System.C.M()", "'DynamicallyAccessedMemberTypes.PublicMethods'")); @@ -970,7 +998,6 @@ private void M() return VerifyDynamicallyAccessedMembersAnalyzer(string.Concat(GetSystemTypeBase(), TargetMethodWithAnnotations), consoleApplication: false, VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchThisParameterTargetsThisParameter) .WithSpan(198, 13, 198, 30) - .WithSpan(196, 9, 199, 10) .WithArguments("System.Type.GetMethods()", "System.C.M()", "'DynamicallyAccessedMemberTypes.PublicMethods'")); } #endregion @@ -1007,7 +1034,6 @@ private static void M2() return VerifyDynamicallyAccessedMembersAnalyzer(TargetParameterWithAnnotations, consoleApplication: false, VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchTypeArgumentTargetsParameter) .WithSpan(18, 9, 18, 22) - .WithSpan(16, 28, 16, 29) .WithArguments("type", "C.M1(Type)", "T", "C.M2()", "'DynamicallyAccessedMemberTypes.PublicMethods'")); } @@ -1039,7 +1065,6 @@ private static Type M() return VerifyDynamicallyAccessedMembersAnalyzer(TargetMethodReturnTypeWithAnnotations, consoleApplication: false, VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchTypeArgumentTargetsMethodReturnType) .WithSpan(14, 16, 14, 25) - .WithSpan(12, 27, 12, 28) .WithArguments("C.M()", "T", "C.M()", "'DynamicallyAccessedMemberTypes.PublicConstructors'")); } @@ -1073,7 +1098,6 @@ private static void M() return VerifyDynamicallyAccessedMembersAnalyzer(TargetFieldWithAnnotations, consoleApplication: false, VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchTypeArgumentTargetsField) .WithSpan(16, 9, 16, 22) - .WithSpan(14, 27, 14, 28) .WithArguments("C.f", "T", "C.M()", @@ -1110,7 +1134,6 @@ private static void M2() return VerifyDynamicallyAccessedMembersAnalyzer(TargetGenericParameterWithAnnotations, consoleApplication: false, VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchTypeArgumentTargetsGenericParameter) .WithSpan(16, 9, 16, 16) - .WithSpan(14, 28, 14, 29) .WithArguments("T", "C.M1()", "S", "C.M2()", "'DynamicallyAccessedMemberTypes.PublicMethods'")); } @@ -1427,7 +1450,7 @@ class CRequires<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Publi // (10,60): error CS1519: Invalid token ')' in a member declaration DiagnosticResult.CompilerError("CS1519").WithSpan(10, 60, 10, 61).WithArguments(")"), // (10,39): warning IL2091: 'TInner' generic argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicMethods' in 'CRequires'. The generic parameter 'TOuter' of 'C' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to. - VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchTypeArgumentTargetsGenericParameter).WithSpan(10, 39, 10, 60).WithSpan(3, 9, 3, 15).WithArguments("TInner", "CRequires", "TOuter", "C", "'DynamicallyAccessedMemberTypes.PublicMethods'")); + VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchTypeArgumentTargetsGenericParameter).WithSpan(10, 39, 10, 60).WithArguments("TInner", "CRequires", "TOuter", "C", "'DynamicallyAccessedMemberTypes.PublicMethods'")); } [Fact] diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/DynamicallyAccessedMembersCodeFixTests.cs b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/DynamicallyAccessedMembersCodeFixTests.cs index 6d40c1cb536f05..59dd9b355c9cc1 100644 --- a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/DynamicallyAccessedMembersCodeFixTests.cs +++ b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/DynamicallyAccessedMembersCodeFixTests.cs @@ -27,6 +27,8 @@ static Task VerifyDynamicallyAccessedMembersCodeFix( TestCode = source, FixedCode = fixedSource }; + IgnoreAdditionalLocations(baselineExpected); + IgnoreAdditionalLocations(fixedExpected); test.ExpectedDiagnostics.AddRange(baselineExpected); test.TestState.AnalyzerConfigFiles.Add( ("/.editorconfig", SourceText.From(@$" @@ -37,7 +39,36 @@ static Task VerifyDynamicallyAccessedMembersCodeFix( test.NumberOfIncrementalIterations = numberOfIterations; test.NumberOfFixAllIterations = numberOfIterations; } - test.FixedState.ExpectedDiagnostics.AddRange(fixedExpected); + test.FixedState.ExpectedDiagnostics.AddRange( + fixedSource == source && fixedExpected.Length == 0 ? baselineExpected : fixedExpected); + return test.RunAsync(); + } + + private static void IgnoreAdditionalLocations(DiagnosticResult[] diagnostics) + { + for (int i = 0; i < diagnostics.Length; i++) + diagnostics[i] = diagnostics[i].WithOptions(DiagnosticOptions.IgnoreAdditionalLocations); + } + + static Task VerifyDynamicallyAccessedMembersCodeFixWithReference( + string source, + string fixedSource, + string referenceSource, + DiagnosticResult expected) + { + var test = ReferenceCompatibilityTestUtils.CreateTestWithReference< + DynamicallyAccessedMembersAnalyzer, + ILLink.CodeFix.DynamicallyAccessedMembersCodeFixProvider>(source, referenceSource); + test.FixedCode = fixedSource; + expected = expected.WithOptions(DiagnosticOptions.IgnoreAdditionalLocations); + test.ExpectedDiagnostics.Add(expected); + if (fixedSource == source) + test.FixedState.ExpectedDiagnostics.Add(expected); + test.TestState.AnalyzerConfigFiles.Add( + ("/.editorconfig", SourceText.From($""" + is_global = true + build_property.{MSBuildPropertyOptionNames.EnableTrimAnalyzer} = true + """))); return test.RunAsync(); } @@ -81,7 +112,6 @@ await VerifyDynamicallyAccessedMembersCodeFix( // The source value must declare at least the same requirements as those declared on the target location it is assigned to. VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchParameterTargetsParameter) .WithSpan(7, 9, 7, 14) - .WithSpan(6, 19, 6, 25) .WithArguments("t", "C.M2(Type)", "t", @@ -91,6 +121,88 @@ await VerifyDynamicallyAccessedMembersCodeFix( fixedExpected: Array.Empty()); } + [Fact] + public Task CodeFix_IL2067_TargetsMatchingParameterWithMultipleArguments() + { + var source = """ + using System; + using System.Diagnostics.CodeAnalysis; + + class C + { + static void M(Type p1, Type p2) => M2(p1, p2); + + static void M2(Type a, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type b) {} + } + """; + var fixedSource = """ + using System; + using System.Diagnostics.CodeAnalysis; + + class C + { + static void M(Type p1, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type p2) => M2(p1, p2); + + static void M2(Type a, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type b) {} + } + """; + + return VerifyDynamicallyAccessedMembersCodeFix( + source, + fixedSource, + [ + VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchParameterTargetsParameter) + .WithSpan(6, 40, 6, 50) + .WithArguments("b", "C.M2(Type, Type)", "p2", "C.M(Type, Type)", "'DynamicallyAccessedMemberTypes.PublicMethods'") + ], + []); + } + + [Fact] + public Task CodeFix_IL2067_ResolvesParameterThroughLocal() + { + var source = """ + using System; + using System.Diagnostics.CodeAnalysis; + + class C + { + static void M(Type p) + { + Type local = p; + {|#0:M2(local)|}; + } + + static void M2([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type t) {} + } + """; + var fixedSource = """ + using System; + using System.Diagnostics.CodeAnalysis; + + class C + { + static void M([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type p) + { + Type local = p; + M2(local); + } + + static void M2([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type t) {} + } + """; + + return VerifyDynamicallyAccessedMembersCodeFix( + source, + fixedSource, + [ + VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchParameterTargetsParameter) + .WithLocation(0) + .WithArguments("t", "C.M2(Type)", "p", "C.M(Type)", "'DynamicallyAccessedMemberTypes.PublicMethods'") + ], + []); + } + [Fact] public async Task CodeFix_IL2067_MismatchParamTargetsParam_WithReturn() { @@ -135,7 +247,6 @@ await VerifyDynamicallyAccessedMembersCodeFix( // The source value must declare at least the same requirements as those declared on the target location it is assigned to. VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchParameterTargetsParameter) .WithSpan(8, 9, 8, 14) - .WithSpan(7, 21, 7, 27) .WithArguments("t", "C.M2(Type)", "t", @@ -167,7 +278,6 @@ static void M2([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Public // The source value must declare at least the same requirements as those declared on the target location it is assigned to. VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchParameterTargetsParameter) .WithSpan(7, 9, 7, 14) - .WithSpan(6, 19, 6, 25) .WithArguments("t", "C.M2(Type)", "t", @@ -247,7 +357,6 @@ await VerifyDynamicallyAccessedMembersCodeFix( // /0/Test0.cs(8,10): warning IL2068: 'C.M(Type)' method return value does not satisfy 'DynamicallyAccessedMemberTypes.All' requirements. The parameter 't' of method 'C.M(Type)' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to. VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchParameterTargetsMethodReturnType) .WithSpan(8, 16, 8, 17) - .WithSpan(7, 12, 7, 18) .WithArguments("C.M(Type)", "t", "C.M(Type)", @@ -336,7 +445,6 @@ await VerifyDynamicallyAccessedMembersCodeFix( //The source value must declare at least the same requirements as those declared on the target location it is assigned to. VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchParameterTargetsField) .WithSpan(13, 9, 13, 17) - .WithSpan(11, 27, 11, 36) .WithArguments("C.f", "type", "C.M(Type)", @@ -387,7 +495,6 @@ await VerifyDynamicallyAccessedMembersCodeFix( // /0/Test0.cs(12,3): warning IL2070: 'this' argument does not satisfy 'DynamicallyAccessedMemberTypes.PublicMethods' in call to 'System.Type.GetMethods()'. The parameter 't' of method 'C.M(Type)' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to. VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchParameterTargetsThisParameter) .WithSpan(12, 9, 12, 23) - .WithSpan(10, 19, 10, 25) .WithArguments("System.Type.GetMethods()", "t", "C.M(Type)", @@ -443,7 +550,6 @@ await VerifyDynamicallyAccessedMembersCodeFix( // The source value must declare at least the same requirements as those declared on the target location it is assigned to. VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchParameterTargetsThisParameter) .WithSpan(13, 9, 13, 45) - .WithSpan(11, 19, 11, 25) .WithArguments("System.Type.GetMethods(BindingFlags)", "t", "C.M(Type)", @@ -510,7 +616,6 @@ await VerifyDynamicallyAccessedMembersCodeFix( // The source value must declare at least the same requirements as those declared on the target location it is assigned to. VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchParameterTargetsThisParameter) .WithSpan(10, 36, 10, 50) - .WithSpan(8, 26, 8, 32) .WithArguments("System.Type.GetMethods()", "t", "System.C.Main(Type)", @@ -578,7 +683,6 @@ await VerifyDynamicallyAccessedMembersCodeFix( // The source value must declare at least the same requirements as those declared on the target location it is assigned to. VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchMethodReturnTypeTargetsParameter) .WithSpan(8, 9, 8, 46) - .WithSpan(16, 5, 19, 6) .WithArguments("type", "C.NeedsPublicMethodsOnParameter(Type)", "C.GetC()", @@ -587,6 +691,66 @@ await VerifyDynamicallyAccessedMembersCodeFix( fixedExpected: Array.Empty()); } + [Fact] + public Task CodeFix_IL2072_UserDefinedConversionReturn() + { + var source = """ + using System; + using System.Diagnostics.CodeAnalysis; + + class ConvertsToType + { + public static implicit operator Type(ConvertsToType value) => typeof(ConvertsToType); + } + + class C + { + static void M() + { + {|#0:NeedsPublicMethods(new ConvertsToType())|}; + } + + static void NeedsPublicMethods( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type) {} + } + """; + var fixedSource = """ + using System; + using System.Diagnostics.CodeAnalysis; + + class ConvertsToType + { + [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] + public static implicit operator Type(ConvertsToType value) => typeof(ConvertsToType); + } + + class C + { + static void M() + { + NeedsPublicMethods(new ConvertsToType()); + } + + static void NeedsPublicMethods( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type) {} + } + """; + + return VerifyDynamicallyAccessedMembersCodeFix( + source, + fixedSource, + [ + VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchMethodReturnTypeTargetsParameter) + .WithLocation(0) + .WithArguments( + "type", + "C.NeedsPublicMethods(Type)", + "ConvertsToType.implicit operator Type(ConvertsToType)", + "'DynamicallyAccessedMemberTypes.PublicMethods'") + ], + []); + } + [Fact] public async Task CodeFix_IL2072_MismatchMethodReturnTargetsParam_WithAttributes() { @@ -646,7 +810,6 @@ await VerifyDynamicallyAccessedMembersCodeFix( // The source value must declare at least the same requirements as those declared on the target location it is assigned to. VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchMethodReturnTypeTargetsParameter) .WithSpan(8, 9, 8, 55) - .WithSpan(16, 5, 19, 6) .WithArguments("t", "C.NeedsPublicMethodsOnParameter(Type)", "C.GetC(Type)", @@ -778,7 +941,6 @@ await VerifyDynamicallyAccessedMembersCodeFix( // The source value must declare at least the same requirements as those declared on the target location it is assigned to. VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchMethodReturnTypeTargetsMethodReturnType) .WithSpan(11, 16, 11, 31) - .WithSpan(5, 5, 7, 6) .WithArguments("C.M()", "C.Main(Type)", "'DynamicallyAccessedMemberTypes.PublicMethods'") @@ -829,7 +991,6 @@ await VerifyDynamicallyAccessedMembersCodeFix( // The source value must declare at least the same requirements as those declared on the target location it is assigned to. VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchMethodReturnTypeTargetsMethodReturnType) .WithSpan(11, 16, 11, 23) - .WithSpan(5, 5, 7, 6) .WithArguments("C.M(Type)", "C.Main(Type)", "'DynamicallyAccessedMemberTypes.PublicMethods'") @@ -890,7 +1051,6 @@ await VerifyDynamicallyAccessedMembersCodeFix( // The source value must declare at least the same requirements as those declared on the target location it is assigned to. VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchMethodReturnTypeTargetsField) .WithSpan(8, 9, 8, 16) - .WithSpan(11, 5, 14, 6) .WithArguments("C.f", "C.M()", "'DynamicallyAccessedMemberTypes.PublicMethods'") @@ -946,7 +1106,6 @@ await VerifyDynamicallyAccessedMembersCodeFix( //The source value must declare at least the same requirements as those declared on the target location it is assigned to. VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchMethodReturnTypeTargetsThisParameter) .WithSpan(8, 9, 8, 32) - .WithSpan(11, 5, 14, 6) .WithArguments("System.Type.GetMethod(String)", "C.GetC()", "'DynamicallyAccessedMemberTypes.PublicMethods'") @@ -1003,7 +1162,6 @@ await VerifyDynamicallyAccessedMembersCodeFix( //The source value must declare at least the same requirements as those declared on the target location it is assigned to. VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchMethodReturnTypeTargetsThisParameter) .WithSpan(194, 13, 194, 36) - .WithSpan(197, 9, 200, 10) .WithArguments("System.Type.GetMethod(String)", "System.C.GetC()", "'DynamicallyAccessedMemberTypes.PublicMethods'") @@ -1060,7 +1218,6 @@ await VerifyDynamicallyAccessedMembersCodeFix( //The source value must declare at least the same requirements as those declared on the target location it is assigned to. VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchMethodReturnTypeTargetsThisParameter) .WithSpan(193, 13, 193, 36) - .WithSpan(196, 9, 200, 10) .WithArguments("System.Type.GetMethod(String)", "System.C.GetC()", "'DynamicallyAccessedMemberTypes.PublicMethods'") @@ -1119,7 +1276,6 @@ await VerifyDynamicallyAccessedMembersCodeFix( //The source value must declare at least the same requirements as those declared on the target location it is assigned to. VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchMethodReturnTypeTargetsThisParameter) .WithSpan(194, 13, 194, 36) - .WithSpan(198, 9, 201, 10) .WithArguments("System.Type.GetMethod(String)", "System.C.GetC()", "'DynamicallyAccessedMemberTypes.PublicMethods'") @@ -1178,7 +1334,6 @@ await VerifyDynamicallyAccessedMembersCodeFix( // The source value must declare at least the same requirements as those declared on the target location it is assigned to. VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchFieldTargetsParameter) .WithSpan(10, 9, 10, 30) - .WithSpan(6, 25, 6, 38) .WithArguments("type", "C.NeedsPublicMethods(Type)", "C.f", @@ -1266,7 +1421,6 @@ await VerifyDynamicallyAccessedMembersCodeFix( // The source value must declare at least the same requirements as those declared on the target location it is assigned to. VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchFieldTargetsMethodReturnType) .WithSpan(9, 16, 9, 17) - .WithSpan(12, 25, 12, 26) .WithArguments("C.Main()", "C.f", "'DynamicallyAccessedMemberTypes.PublicMethods'") @@ -1351,7 +1505,6 @@ await VerifyDynamicallyAccessedMembersCodeFix( // The source value must declare at least the same requirements as those declared on the target location it is assigned to. VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchFieldTargetsField) .WithSpan(13, 9, 13, 16) - .WithSpan(6, 25, 6, 39) .WithArguments("C.f2", "C.f1", "'DynamicallyAccessedMemberTypes.PublicMethods'") @@ -1434,7 +1587,6 @@ await VerifyDynamicallyAccessedMembersCodeFix( // The source value must declare at least the same requirements as those declared on the target location it is assigned to. VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchFieldTargetsThisParameter) .WithSpan(10, 9, 10, 27) - .WithSpan(6, 25, 6, 38) .WithArguments("System.Type.GetMethod(String)", "C.f", "'DynamicallyAccessedMemberTypes.PublicMethods'") @@ -1483,7 +1635,6 @@ await VerifyDynamicallyAccessedMembersCodeFix( // The source value must declare at least the same requirements as those declared on the target location it is assigned to. VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchFieldTargetsThisParameter) .WithSpan(10, 9, 10, 27) - .WithSpan(6, 24, 6, 37) .WithArguments("System.Type.GetMethod(String)", "C.f", "'DynamicallyAccessedMemberTypes.PublicMethods'") @@ -1578,7 +1729,6 @@ await VerifyDynamicallyAccessedMembersCodeFix( // The source value must declare at least the same requirements as those declared on the target location it is assigned to. VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchThisParameterTargetsParameter) .WithSpan(198, 13, 198, 21) - .WithSpan(196, 9, 199, 10) .WithArguments("t", "System.C.M2(Type)", "System.C.M1()", @@ -1653,7 +1803,6 @@ await VerifyDynamicallyAccessedMembersCodeFix( // The source value must declare at least the same requirements as those declared on the target location it is assigned to. VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchThisParameterTargetsParameter) .WithSpan(199, 13, 199, 21) - .WithSpan(196, 9, 201, 10) .WithArguments("t", "System.C.M2(Type)", "System.C.M1()", @@ -1728,7 +1877,6 @@ await VerifyDynamicallyAccessedMembersCodeFix( // The source value must declare at least the same requirements as those declared on the target location it is assigned to. VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchThisParameterTargetsParameter) .WithSpan(199, 13, 199, 21) - .WithSpan(196, 9, 201, 10) .WithArguments("t", "System.C.M2(Type)", "System.C.M1(String)", @@ -1836,7 +1984,6 @@ await VerifyDynamicallyAccessedMembersCodeFix( // /0/Test0.cs(199,11): warning IL2083: 'System.C.M1()' method return value does not satisfy 'DynamicallyAccessedMemberTypes.PublicMethods' requirements. The implicit 'this' argument of method 'System.C.M1()' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to. VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchThisParameterTargetsMethodReturnType) .WithSpan(199, 20, 199, 24) - .WithSpan(196, 9, 200, 10) .WithArguments("System.C.M1()", "System.C.M1()", "'DynamicallyAccessedMemberTypes.PublicMethods'") @@ -1900,7 +2047,6 @@ await VerifyDynamicallyAccessedMembersCodeFix( // /0/Test0.cs(199,11): warning IL2083: 'System.C.M1()' method return value does not satisfy 'DynamicallyAccessedMemberTypes.PublicMethods' requirements. The implicit 'this' argument of method 'System.C.M1()' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to. VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchThisParameterTargetsMethodReturnType) .WithSpan(200, 20, 200, 24) - .WithSpan(196, 9, 201, 10) .WithArguments("System.C.M1(String)", "System.C.M1(String)", "'DynamicallyAccessedMemberTypes.PublicMethods'") @@ -2008,7 +2154,6 @@ await VerifyDynamicallyAccessedMembersCodeFix( // The source value must declare at least the same requirements as those declared on the target location it is assigned to. VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchThisParameterTargetsField) .WithSpan(198, 13, 198, 21) - .WithSpan(196, 9, 199, 10) .WithArguments("System.C.f", "System.C.M()", "'DynamicallyAccessedMemberTypes.PublicMethods'") @@ -2080,7 +2225,6 @@ await VerifyDynamicallyAccessedMembersCodeFix( // The source value must declare at least the same requirements as those declared on the target location it is assigned to. VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchThisParameterTargetsThisParameter) .WithSpan(198, 13, 198, 22) - .WithSpan(196, 9, 199, 10) .WithArguments("System.C.M2()", "System.C.M1()", "'DynamicallyAccessedMemberTypes.PublicMethods'") @@ -2195,7 +2339,6 @@ await VerifyDynamicallyAccessedMembersCodeFix( // The source value must declare at least the same requirements as those declared on the target location it is assigned to. VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchTypeArgumentTargetsParameter) .WithSpan(18, 9, 18, 22) - .WithSpan(16, 28, 16, 29) .WithArguments("type", "C.M1(Type)", "T", @@ -2253,7 +2396,6 @@ await VerifyDynamicallyAccessedMembersCodeFix( // The source value must declare at least the same requirements as those declared on the target location it is assigned to. VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchTypeArgumentTargetsMethodReturnType) .WithSpan(14, 16, 14, 25) - .WithSpan(12, 27, 12, 28) .WithArguments("C.M()", "T", "C.M()", @@ -2337,7 +2479,6 @@ await VerifyDynamicallyAccessedMembersCodeFix( // /0/Test0.cs(8,3): warning IL2089: value stored in field 'C.f' does not satisfy 'DynamicallyAccessedMemberTypes.PublicMethods' requirements. The generic parameter 'T' of 'C.Main()' does not have matching annotations. The source value must declare at least the same requirements as those declared on the target location it is assigned to. VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchTypeArgumentTargetsField) .WithSpan(8, 9, 8, 22) - .WithSpan(6, 29, 6, 30) .WithArguments("C.f", "T", "C.Main()", @@ -2413,7 +2554,6 @@ await VerifyDynamicallyAccessedMembersCodeFix( // The source value must declare at least the same requirements as those declared on the target location it is assigned to. VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchTypeArgumentTargetsThisParameter) .WithSpan(8, 9, 8, 31) - .WithSpan(4, 9, 4, 10) .WithArguments("System.Type.GetMethods()", "T", "C", @@ -2531,7 +2671,6 @@ await VerifyDynamicallyAccessedMembersCodeFix( // The source value must declare at least the same requirements as those declared on the target location it is assigned to. VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchTypeArgumentTargetsGenericParameter) .WithSpan(16, 9, 16, 16) - .WithSpan(14, 28, 14, 29) .WithArguments("T", "C.M1()", "S", @@ -2541,6 +2680,70 @@ await VerifyDynamicallyAccessedMembersCodeFix( fixedExpected: Array.Empty()); } + [Fact] + public Task CodeFix_IL2091_TargetsMatchingTypeParameterWithMultipleArguments() + { + var source = """ + using System.Diagnostics.CodeAnalysis; + + class C + { + static void M1() {} + + static void M2() => M1(); + } + """; + var fixedSource = """ + using System.Diagnostics.CodeAnalysis; + + class C + { + static void M1() {} + + static void M2() => M1(); + } + """; + + return VerifyDynamicallyAccessedMembersCodeFix( + source, + fixedSource, + [ + VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchTypeArgumentTargetsGenericParameter) + .WithSpan(7, 33, 7, 45) + .WithArguments("T2", "C.M1()", "S2", "C.M2()", "'DynamicallyAccessedMemberTypes.PublicMethods'") + ], + []); + } + + [Fact] + public Task CodeFix_IL2091_ResolvesTypeParameterFromBaseList() + { + var source = """ + using System.Diagnostics.CodeAnalysis; + + interface I<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] T> {} + + class {|#0:C|} : I {} + """; + var fixedSource = """ + using System.Diagnostics.CodeAnalysis; + + interface I<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] T> {} + + class C<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] T> : I {} + """; + + return VerifyDynamicallyAccessedMembersCodeFix( + source, + fixedSource, + [ + VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchTypeArgumentTargetsGenericParameter) + .WithLocation(0) + .WithArguments("T", "I", "T", "C", "'DynamicallyAccessedMemberTypes.PublicMethods'") + ], + []); + } + [Fact] public async Task CodeFix_IL2091_AttributeTurnsOffCodeFix() { @@ -2622,7 +2825,6 @@ await VerifyDynamicallyAccessedMembersCodeFix(test, fixtest, new[] { // All overridden members must have the same 'DynamicallyAccessedMembersAttribute' usage. VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchOnMethodParameterBetweenOverrides) .WithSpan(11, 33, 11, 34) - .WithSpan(11, 33, 11, 34) .WithArguments("t", "C.M(Type)", "t", @@ -2631,33 +2833,88 @@ await VerifyDynamicallyAccessedMembersCodeFix(test, fixtest, new[] { } [Fact] - public async Task CodeFix_IL2092_MismatchMethodParamBtOverride_NonPublicMethods_Reverse() + public Task CodeFix_IL2092_AddsAttributeToInterfaceImplementationParameter() { - var test = $$""" - using System; - using System.Diagnostics.CodeAnalysis; + var source = """ + using System; + using System.Diagnostics.CodeAnalysis; - public class Base - { - public virtual void M(Type t) {} - } + interface I + { + void M([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type t); + } - public class C : Base - { - public override void M([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.NonPublicMethods)] Type t) {} + class C : I + { + public void M(Type {|#0:t|}) {} + } + """; + var fixedSource = """ + using System; + using System.Diagnostics.CodeAnalysis; - public static void Main() { + interface I + { + void M([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type t); + } + class C : I + { + public void M([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type t) {} } - } - """; - var fixtest = $$""" + """; + + return VerifyDynamicallyAccessedMembersCodeFix( + source, + fixedSource, + [ + VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchOnMethodParameterBetweenOverrides) + .WithLocation(0) + .WithArguments("t", "C.M(Type)", "t", "I.M(Type)") + ], + []); + } + + [Fact] + public Task CodeFix_IL2092_DoesNotRemoveAttributeFromInterfaceImplementationParameter() + { + var source = """ + using System; + using System.Diagnostics.CodeAnalysis; + + interface I + { + void M(Type t); + } + + class C : I + { + public void M([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type {|#0:t|}) {} + } + """; + var fixedSource = source; + + return VerifyDynamicallyAccessedMembersCodeFix( + source, + fixedSource, + [ + VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchOnMethodParameterBetweenOverrides) + .WithLocation(0) + .WithArguments("t", "C.M(Type)", "t", "I.M(Type)") + ], + []); + } + + [Fact] + public async Task CodeFix_IL2092_MismatchMethodParamBtOverride_NonPublicMethods_Reverse() + { + var test = $$""" using System; using System.Diagnostics.CodeAnalysis; public class Base { - public virtual void M([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.NonPublicMethods)] Type t) {} + public virtual void M(Type t) {} } public class C : Base @@ -2669,6 +2926,7 @@ public static void Main() { } } """; + var fixtest = test; await VerifyDynamicallyAccessedMembersCodeFix( source: test, fixedSource: fixtest, @@ -2678,7 +2936,6 @@ await VerifyDynamicallyAccessedMembersCodeFix( // All overridden members must have the same 'DynamicallyAccessedMembersAttribute' usage. VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchOnMethodParameterBetweenOverrides) .WithSpan(11, 111, 11, 112) - .WithSpan(6, 32, 6, 33) .WithArguments("t", "C.M(Type)", "t", @@ -2687,6 +2944,77 @@ await VerifyDynamicallyAccessedMembersCodeFix( fixedExpected: Array.Empty()); } + [Fact] + public Task CodeFix_IL2092_AddsAttributeToOverrideOfMetadataMethod() + { + var referenceSource = """ + using System; + using System.Diagnostics.CodeAnalysis; + + public class Base + { + public virtual void M([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.NonPublicMethods)] Type t) {} + } + """; + var source = """ + using System; + using System.Diagnostics.CodeAnalysis; + + public class C : Base + { + public override void M(Type {|#0:t|}) {} + } + """; + var fixedSource = """ + using System; + using System.Diagnostics.CodeAnalysis; + + public class C : Base + { + public override void M([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.NonPublicMethods)] Type t) {} + } + """; + + return VerifyDynamicallyAccessedMembersCodeFixWithReference( + source, + fixedSource, + referenceSource, + VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchOnMethodParameterBetweenOverrides) + .WithLocation(0) + .WithArguments("t", "C.M(Type)", "t", "Base.M(Type)")); + } + + [Fact] + public Task CodeFix_IL2092_DoesNotRemoveAttributeFromOverrideOfMetadataMethod() + { + var referenceSource = """ + using System; + + public class Base + { + public virtual void M(Type t) {} + } + """; + var source = """ + using System; + using System.Diagnostics.CodeAnalysis; + + public class C : Base + { + public override void M([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.PublicFields)] Type {|#0:t|}) {} + } + """; + var fixedSource = source; + + return VerifyDynamicallyAccessedMembersCodeFixWithReference( + source, + fixedSource, + referenceSource, + VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchOnMethodParameterBetweenOverrides) + .WithLocation(0) + .WithArguments("t", "C.M(Type)", "t", "Base.M(Type)")); + } + [Fact] public async Task CodeFix_IL2092_BothAttributesTurnOffCodeFix() { @@ -2749,7 +3077,6 @@ public static void Main() { // All overridden members must have the same 'DynamicallyAccessedMembersAttribute' usage. VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchOnMethodParameterBetweenOverrides) .WithSpan(11, 33, 11, 34) - .WithSpan(11, 33, 11, 34) .WithArguments("t", "C.M(Type)", "t", @@ -2849,7 +3176,6 @@ await VerifyDynamicallyAccessedMembersCodeFix( // don't match overridden return value of method 'Base.M(Type)'. // All overridden members must have the same 'DynamicallyAccessedMembersAttribute' usage. VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchOnMethodReturnValueBetweenOverrides) - .WithSpan(14, 26, 14, 27) .WithSpan(14, 26, 14, 27) .WithArguments("C.M(Type)", "Base.M(Type)") @@ -2882,29 +3208,7 @@ public static void Main() { } } """; - var fixtest = $$""" - using System; - using System.Diagnostics.CodeAnalysis; - - public class Base - { - [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.NonPublicMethods)] - public virtual Type M([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.NonPublicMethods)] Type t) { - return t; - } - } - - public class C : Base - { - [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.NonPublicMethods)] - public override Type M([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.NonPublicMethods)] Type t) { - return t; - } - - public static void Main() { - } - } - """; + var fixtest = test; await VerifyDynamicallyAccessedMembersCodeFix( source: test, fixedSource: fixtest, @@ -2914,13 +3218,86 @@ await VerifyDynamicallyAccessedMembersCodeFix( // All overridden members must have the same 'DynamicallyAccessedMembersAttribute' usage. VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchOnMethodReturnValueBetweenOverrides) .WithSpan(14, 26, 14, 27) - .WithSpan(6, 25, 6, 26) .WithArguments("C.M(Type)", "Base.M(Type)") }, fixedExpected: Array.Empty()); } + [Fact] + public Task CodeFix_IL2093_AddsAttributeToOverrideOfMetadataMethod() + { + var referenceSource = """ + using System; + using System.Diagnostics.CodeAnalysis; + + public class Base + { + [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.NonPublicMethods)] + public virtual Type M() => typeof(object); + } + """; + var source = """ + using System; + using System.Diagnostics.CodeAnalysis; + + public class C : Base + { + public override Type {|#0:M|}() => typeof(object); + } + """; + var fixedSource = """ + using System; + using System.Diagnostics.CodeAnalysis; + + public class C : Base + { + [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.NonPublicMethods)] + public override Type M() => typeof(object); + } + """; + + return VerifyDynamicallyAccessedMembersCodeFixWithReference( + source, + fixedSource, + referenceSource, + VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchOnMethodReturnValueBetweenOverrides) + .WithLocation(0) + .WithArguments("C.M()", "Base.M()")); + } + + [Fact] + public Task CodeFix_IL2093_DoesNotRemoveAttributeFromOverrideOfMetadataMethod() + { + var referenceSource = """ + using System; + + public class Base + { + public virtual Type M() => typeof(object); + } + """; + var source = """ + using System; + using System.Diagnostics.CodeAnalysis; + + public class C : Base + { + [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.NonPublicMethods)] + public override Type {|#0:M|}() => typeof(object); + } + """; + var fixedSource = source; + + return VerifyDynamicallyAccessedMembersCodeFixWithReference( + source, + fixedSource, + referenceSource, + VerifyCS.Diagnostic(DiagnosticId.DynamicallyAccessedMembersMismatchOnMethodReturnValueBetweenOverrides) + .WithLocation(0) + .WithArguments("C.M()", "Base.M()")); + } + [Fact] public async Task CodeFix_IL2093_BothAttributesTurnOffCodeFix() { diff --git a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/ReferenceCompatibilityTestUtils.cs b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/ReferenceCompatibilityTestUtils.cs index ac901c6ce109a8..51364dcfcabd63 100644 --- a/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/ReferenceCompatibilityTestUtils.cs +++ b/src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/ReferenceCompatibilityTestUtils.cs @@ -13,6 +13,7 @@ using System.IO; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; namespace ILLink.RoslynAnalyzer.Tests { @@ -51,5 +52,34 @@ static MetadataReference CreateReferencedMetadata(string referencedSource) return MetadataReference.CreateFromStream(referencedImage); } } + + public static CSharpCodeFixVerifier.Test CreateTestWithCompilationReference(string mainSource, string referenceSource) + where TAnalyzer : DiagnosticAnalyzer, new() + where TCodeFix : Microsoft.CodeAnalysis.CodeFixes.CodeFixProvider, new() + { + var test = new CSharpCodeFixVerifier.Test + { + TestCode = mainSource + }; + test.SolutionTransforms.Add((solution, projectId) => + { + ProjectId referencedProjectId = ProjectId.CreateNewId(); + solution = solution + .AddProject(referencedProjectId, "ReferencedAssembly", "ReferencedAssembly", LanguageNames.CSharp) + .WithProjectParseOptions(referencedProjectId, new CSharpParseOptions(languageVersion: LanguageVersion.Preview)) + .WithProjectCompilationOptions(referencedProjectId, new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); + + Project referencedProject = solution.GetProject(referencedProjectId)!; + solution = referencedProject + .AddMetadataReferences(SourceGenerators.Tests.LiveReferencePack.GetMetadataReferences()) + .AddDocument("ReferencedAssembly.cs", SourceText.From(referenceSource)) + .Project + .Solution; + + Project project = solution.GetProject(projectId)!; + return project.AddProjectReference(new ProjectReference(referencedProjectId)).Solution; + }); + return test; + } } }