* Make analyzer stop flagging methods with well-known unsupported types for conversion
* Make fixer put attribute arguments in preferred order
using System.Collections.Generic;
using System.Collections.Immutable;
-using System.Diagnostics;
-using System.Runtime.InteropServices;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
{
private const string Category = "Interoperability";
+ private static readonly string[] s_unsupportedTypeNames = new string[]
+ {
+ "System.Runtime.InteropServices.CriticalHandle",
+ "System.Runtime.InteropServices.HandleRef",
+ "System.Text.StringBuilder"
+ };
+
public static readonly DiagnosticDescriptor ConvertToGeneratedDllImport =
new DiagnosticDescriptor(
Ids.ConvertToGeneratedDllImport,
if (generatedDllImportAttrType == null)
return;
- INamedTypeSymbol? dllImportAttrType = compilationContext.Compilation.GetTypeByMetadataName(typeof(DllImportAttribute).FullName);
- if (dllImportAttrType == null)
- return;
+ var knownUnsupportedTypes = new List<ITypeSymbol>(s_unsupportedTypeNames.Length);
+ foreach (string typeName in s_unsupportedTypeNames)
+ {
+ INamedTypeSymbol? unsupportedType = compilationContext.Compilation.GetTypeByMetadataName(typeName);
+ if (unsupportedType != null)
+ {
+ knownUnsupportedTypes.Add(unsupportedType);
+ }
+ }
- compilationContext.RegisterSymbolAction(symbolContext => AnalyzeSymbol(symbolContext, dllImportAttrType), SymbolKind.Method);
+ compilationContext.RegisterSymbolAction(symbolContext => AnalyzeSymbol(symbolContext, knownUnsupportedTypes), SymbolKind.Method);
});
}
- private static void AnalyzeSymbol(SymbolAnalysisContext context, INamedTypeSymbol dllImportAttrType)
+ private static void AnalyzeSymbol(SymbolAnalysisContext context, List<ITypeSymbol> knownUnsupportedTypes)
{
var method = (IMethodSymbol)context.Symbol;
if (dllImportData.ModuleName == "QCall")
return;
+ // Ignore methods with unsupported parameters
+ foreach (IParameterSymbol parameter in method.Parameters)
+ {
+ if (knownUnsupportedTypes.Contains(parameter.Type))
+ {
+ return;
+ }
+ }
+
+ // Ignore methods with unsupported returns
+ if (method.ReturnsByRef || method.ReturnsByRefReadonly || knownUnsupportedTypes.Contains(method.ReturnType))
+ return;
+
context.ReportDiagnostic(method.CreateDiagnostic(ConvertToGeneratedDllImport, method.Name));
}
}
using System.Collections.Generic;
using System.Collections.Immutable;
+using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
public const string NoPreprocessorDefinesKey = "ConvertToGeneratedDllImport";
public const string WithPreprocessorDefinesKey = "ConvertToGeneratedDllImportPreprocessor";
+ private static readonly string[] s_preferredAttributeArgumentOrder =
+ {
+ nameof(DllImportAttribute.EntryPoint),
+ nameof(DllImportAttribute.BestFitMapping),
+ nameof(DllImportAttribute.CallingConvention),
+ nameof(DllImportAttribute.CharSet),
+ nameof(DllImportAttribute.ExactSpelling),
+ nameof(DllImportAttribute.PreserveSig),
+ nameof(DllImportAttribute.SetLastError),
+ nameof(DllImportAttribute.ThrowOnUnmappableChar)
+ };
+
public override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
// Get the syntax root and semantic model
SyntaxFactory.ElasticMarker
}));
+ // Sort attribute arguments so that GeneratedDllImport and DllImport match
+ MethodDeclarationSyntax updatedDeclaration = (MethodDeclarationSyntax)generator.ReplaceNode(methodSyntax, dllImportSyntax, SortDllImportAttributeArguments(dllImportSyntax, generator));
+
// Remove existing leading trivia - it will be on the GeneratedDllImport method
- MethodDeclarationSyntax updatedDeclaration = methodSyntax.WithLeadingTrivia();
+ updatedDeclaration = updatedDeclaration.WithLeadingTrivia();
// #endif
updatedDeclaration = updatedDeclaration.WithTrailingTrivia(
}
}
- return generator.RemoveNodes(generatedDllImportSyntax, argumentsToRemove);
+ generatedDllImportSyntax = generator.RemoveNodes(generatedDllImportSyntax, argumentsToRemove);
+ return SortDllImportAttributeArguments((AttributeSyntax)generatedDllImportSyntax, generator);
+ }
+
+ private static SyntaxNode SortDllImportAttributeArguments(AttributeSyntax attribute, SyntaxGenerator generator)
+ {
+ AttributeArgumentListSyntax updatedArgList = attribute.ArgumentList.WithArguments(
+ SyntaxFactory.SeparatedList(
+ attribute.ArgumentList.Arguments.OrderBy(arg =>
+ {
+ // Unnamed arguments first
+ if (arg.NameEquals == null)
+ return -1;
+
+ // Named arguments in specified order, followed by any named arguments with no preferred order
+ string name = arg.NameEquals.Name.Identifier.Text;
+ int index = System.Array.IndexOf(s_preferredAttributeArgumentOrder, name);
+ return index == -1 ? int.MaxValue : index;
+ })));
+ return generator.ReplaceNode(attribute, attribute.ArgumentList, updatedArgList);
}
private bool TryCreateUnmanagedCallConvAttributeToEmit(
new object[] { typeof(ConsoleKey) }, // enum
};
+ public static IEnumerable<object[]> UnsupportedTypes() => new[]
+ {
+ new object[] { typeof(System.Runtime.InteropServices.CriticalHandle) },
+ new object[] { typeof(System.Runtime.InteropServices.HandleRef) },
+ new object[] { typeof(System.Text.StringBuilder) },
+ };
+
[ConditionalTheory]
[MemberData(nameof(MarshallingRequiredTypes))]
[MemberData(nameof(NoMarshallingRequiredTypes))]
.WithArguments("Method2"));
}
+ [ConditionalTheory]
+ [MemberData(nameof(UnsupportedTypes))]
+ public async Task UnsupportedType_NoDiagnostic(Type type)
+ {
+ string source = DllImportWithType(type.FullName!);
+ await VerifyCS.VerifyAnalyzerAsync(source);
+ }
+
[ConditionalFact]
public async Task NotDllImport_NoDiagnostic()
{
[GeneratedDllImport(""DoesNotExist"", EntryPoint = ""Entry"")]
public static partial int {{|CS8795:Method1|}}(out int ret);
#else
- [DllImport(""DoesNotExist"", BestFitMapping = false, EntryPoint = ""Entry"")]
+ [DllImport(""DoesNotExist"", EntryPoint = ""Entry"", BestFitMapping = false)]
public static extern int Method1(out int ret);
#endif
[GeneratedDllImport(""DoesNotExist"", EntryPoint = ""Entry"")]
public static partial int {{|CS8795:Method1|}}(out int ret);
#else
- [DllImport(""DoesNotExist"", CallingConvention = CallingConvention.Winapi, EntryPoint = ""Entry"")]
+ [DllImport(""DoesNotExist"", EntryPoint = ""Entry"", CallingConvention = CallingConvention.Winapi)]
public static extern int Method1(out int ret);
#endif
}}" : @$"
[UnmanagedCallConv(CallConvs = new System.Type[] {{ typeof({callConvType.FullName}) }})]
public static partial int {{|CS8795:Method1|}}(out int ret);
#else
- [DllImport(""DoesNotExist"", CallingConvention = CallingConvention.{callConv}, EntryPoint = ""Entry"")]
+ [DllImport(""DoesNotExist"", EntryPoint = ""Entry"", CallingConvention = CallingConvention.{callConv})]
public static extern int Method1(out int ret);
#endif
}}" : @$"
fixedSource,
usePreprocessorDefines ? WithPreprocessorDefinesKey : NoPreprocessorDefinesKey);
}
+
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public async Task PreferredAttributeOrder(bool usePreprocessorDefines)
+ {
+ string source = @$"
+using System.Runtime.InteropServices;
+partial class Test
+{{
+ [DllImport(""DoesNotExist"", SetLastError = true, EntryPoint = ""Entry"", ExactSpelling = true, CharSet = CharSet.Unicode)]
+ public static extern int [|Method|](out int ret);
+}}";
+ // Fixed source will have CS8795 (Partial method must have an implementation) without generator run
+ string fixedSource = usePreprocessorDefines
+ ? @$"
+using System.Runtime.InteropServices;
+partial class Test
+{{
+#if DLLIMPORTGENERATOR_ENABLED
+ [GeneratedDllImport(""DoesNotExist"", EntryPoint = ""Entry"", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
+ public static partial int {{|CS8795:Method|}}(out int ret);
+#else
+ [DllImport(""DoesNotExist"", EntryPoint = ""Entry"", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
+ public static extern int Method(out int ret);
+#endif
+}}" : @$"
+using System.Runtime.InteropServices;
+partial class Test
+{{
+ [GeneratedDllImport(""DoesNotExist"", EntryPoint = ""Entry"", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
+ public static partial int {{|CS8795:Method|}}(out int ret);
+}}";
+ await VerifyCS.VerifyCodeFixAsync(
+ source,
+ fixedSource,
+ usePreprocessorDefines ? WithPreprocessorDefinesKey : NoPreprocessorDefinesKey);
+ }
}
}