diff --git a/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperAnalyzer.cs b/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperAnalyzer.cs index 332017f2..ef300aac 100644 --- a/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperAnalyzer.cs +++ b/src/Dapper.AOT.Analyzers/CodeAnalysis/DapperAnalyzer.cs @@ -683,7 +683,7 @@ internal static Location SharedParseArgsAndFlags(in ParseState ctx, IInvocationO flags |= OperationFlags.DoNotGenerate; reportDiagnostic?.Invoke(Diagnostic.Create(Diagnostics.GenericTypeParameter, argLocation, paramType!.ToDisplayString())); } - else if (IsMissingOrObjectOrDynamic(paramType) || IsDynamicParameters(paramType)) + else if (IsMissingOrObjectOrDynamic(paramType) || IsDynamicParameters(paramType, out _)) { flags |= OperationFlags.DoNotGenerate; reportDiagnostic?.Invoke(Diagnostic.Create(Diagnostics.UntypedParameter, argLocation)); diff --git a/src/Dapper.AOT.Analyzers/Internal/Inspection.cs b/src/Dapper.AOT.Analyzers/Internal/Inspection.cs index 5578bde7..ea5f5f1b 100644 --- a/src/Dapper.AOT.Analyzers/Internal/Inspection.cs +++ b/src/Dapper.AOT.Analyzers/Internal/Inspection.cs @@ -159,8 +159,9 @@ public static bool IsDapperAttribute(AttributeData attrib) public static bool IsMissingOrObjectOrDynamic(ITypeSymbol? type) => type is null || type.SpecialType == SpecialType.System_Object || type.TypeKind == TypeKind.Dynamic; - internal static bool IsDynamicParameters(ITypeSymbol? type) + internal static bool IsDynamicParameters(ITypeSymbol? type, out bool needsConstruction) { + needsConstruction = false; if (type is null || type.SpecialType != SpecialType.None) return false; if (IsBasicDapperType(type, Types.DynamicParameters) || IsNestedSqlMapperType(type, Types.IDynamicParameters, TypeKind.Interface)) return true; @@ -168,6 +169,32 @@ internal static bool IsDynamicParameters(ITypeSymbol? type) { if (IsNestedSqlMapperType(i, Types.IDynamicParameters, TypeKind.Interface)) return true; } + + // Dapper also treats anything IEnumerable as dynamic parameters + if (type.ImplementsIEnumerable(out var found) && found is INamedTypeSymbol { Arity: 1 } iet + && iet.TypeArguments[0] is INamedTypeSymbol + { + Name: "KeyValuePair", Arity: 2, ContainingType: null, + ContainingNamespace: + { + Name: "Generic", + ContainingNamespace: + { + Name: "Collections", + ContainingNamespace: + { + Name: "System", + ContainingNamespace.IsGlobalNamespace: true + } + } + } + } kvp + && kvp.TypeArguments[0].SpecialType == SpecialType.System_String + && kvp.TypeArguments[1].SpecialType == SpecialType.System_Object) + { + needsConstruction = true; + return true; + } return false; } @@ -419,7 +446,7 @@ public readonly struct ElementMember /// Order of member in constructor parameter list (starts from 0). /// public int? ConstructorParameterOrder { get; } - + /// /// Order of member in factory method parameter list (starts from 0). /// @@ -452,10 +479,10 @@ public ElementMember( { _dbValue = dbValue; _flags = flags; - + Member = member; Kind = kind; - + ConstructorParameterOrder = constructorParameterOrder; FactoryMethodParameterOrder = factoryMethodParameterOrder; } @@ -493,7 +520,7 @@ public enum ConstructorResult FailMultipleExplicit, FailMultipleImplicit } - + public enum FactoryMethodResult { // note that implicit isn't a thing here @@ -513,12 +540,12 @@ internal static FactoryMethodResult ChooseFactoryMethod(ITypeSymbol? typeSymbol, { return FactoryMethodResult.NoneFound; } - + var staticMethods = typeSymbol - .GetMethods(method => + .GetMethods(method => method.IsStatic && SymbolEqualityComparer.Default.Equals(method.ReturnType, typeSymbol) && - method.DeclaredAccessibility == Accessibility.Public + method.DeclaredAccessibility == Accessibility.Public ) ?.ToArray(); if (staticMethods?.Length == 0) @@ -541,7 +568,7 @@ internal static FactoryMethodResult ChooseFactoryMethod(ITypeSymbol? typeSymbol, return factoryMethod is null ? FactoryMethodResult.NoneFound : FactoryMethodResult.SuccessSingleExplicit; } - + /// /// Builds a collection of type constructors, which are NOT: /// a) parameterless @@ -613,7 +640,8 @@ internal static ConstructorResult ChooseConstructor(ITypeSymbol? typeSymbol, out ContainingNamespace: { Name: "Runtime", - ContainingNamespace: { + ContainingNamespace: + { Name: "System", ContainingNamespace.IsGlobalNamespace: true } @@ -630,7 +658,7 @@ internal static ConstructorResult ChooseConstructor(ITypeSymbol? typeSymbol, out // ruled out by signature continue; } - + if (constructor is not null) { return ConstructorResult.FailMultipleImplicit; @@ -704,7 +732,7 @@ internal static ImmutableArray GetMembers(bool forParameters, ITy int? constructorParameterOrder = constructorParameters?.TryGetValue(member.Name, out var constructorParameter) == true ? constructorParameter.Order : null; - + int? factoryMethodParamOrder = factoryMethodParameters?.TryGetValue(member.Name, out var factoryMethodParam) == true ? factoryMethodParam.Order : null; @@ -1018,12 +1046,12 @@ public static bool TryGetConstantValueWithSyntax(IOperation val, out T? value case StringSyntaxKind.ConcatenatedString: case StringSyntaxKind.InterpolatedString: case StringSyntaxKind.FormatString: - { - value = default!; - syntax = null; - syntaxKind = stringSyntaxKind; - return false; - } + { + value = default!; + syntax = null; + syntaxKind = stringSyntaxKind; + return false; + } } } @@ -1096,25 +1124,25 @@ internal static bool IsCommand(INamedTypeSymbol type) while (type.SpecialType != SpecialType.System_Object) { if (type is - { - Name: nameof(DbCommand), - ContainingType: null, - Arity: 0, - IsGenericType: false, - ContainingNamespace: { - Name: "Common", + Name: nameof(DbCommand), + ContainingType: null, + Arity: 0, + IsGenericType: false, ContainingNamespace: { - Name: "Data", + Name: "Common", ContainingNamespace: { - Name: "System", - ContainingNamespace.IsGlobalNamespace: true, + Name: "Data", + ContainingNamespace: + { + Name: "System", + ContainingNamespace.IsGlobalNamespace: true, + } } } - } - }) + }) { return true; } diff --git a/src/Dapper.AOT.Analyzers/Internal/Roslyn/TypeSymbolExtensions.cs b/src/Dapper.AOT.Analyzers/Internal/Roslyn/TypeSymbolExtensions.cs index 1b1583ca..7d9e70a3 100644 --- a/src/Dapper.AOT.Analyzers/Internal/Roslyn/TypeSymbolExtensions.cs +++ b/src/Dapper.AOT.Analyzers/Internal/Roslyn/TypeSymbolExtensions.cs @@ -208,17 +208,17 @@ public static bool ImplementsIReadOnlyList(this ITypeSymbol? typeSymbol, out ITy private static bool ImplementsInterface( this ITypeSymbol? typeSymbol, SpecialType interfaceType, - out ITypeSymbol? searchedInterface, + out ITypeSymbol? found, bool searchFromStart = true) { if (typeSymbol is null) { - searchedInterface = null; + found = null; return false; } if (typeSymbol.SpecialType == interfaceType || typeSymbol.OriginalDefinition?.SpecialType == interfaceType) { - searchedInterface = typeSymbol; + found = typeSymbol; return true; } @@ -231,7 +231,7 @@ private static bool ImplementsInterface( if (currentSymbol.SpecialType == interfaceType || currentSymbol.OriginalDefinition?.SpecialType == interfaceType) { - searchedInterface = currentSymbol; + found = currentSymbol; return true; } } @@ -245,13 +245,13 @@ private static bool ImplementsInterface( if (currentSymbol.SpecialType == interfaceType || currentSymbol.OriginalDefinition?.SpecialType == interfaceType) { - searchedInterface = currentSymbol; + found = currentSymbol; return true; } } } - searchedInterface = null; + found = null; return false; } } \ No newline at end of file diff --git a/test/Dapper.AOT.Test/Interceptors/Techempower.input.cs b/test/Dapper.AOT.Test/Interceptors/Techempower.input.cs index d2954839..63852499 100644 --- a/test/Dapper.AOT.Test/Interceptors/Techempower.input.cs +++ b/test/Dapper.AOT.Test/Interceptors/Techempower.input.cs @@ -8,7 +8,7 @@ using System.Threading.Tasks; using System.Linq; -[module: DapperAot] +[module:DapperAot] var connectionString = new SqlConnectionStringBuilder { @@ -32,7 +32,6 @@ public BenchRunner(string connectionString, DbProviderFactory dbProviderFactory) _connectionString = connectionString; } - [DapperAot] public void Create() { using var conn = _dbProviderFactory.CreateConnection(); @@ -50,7 +49,6 @@ static IEnumerable Invent(int count) } } - [DapperAot(false)] // dictionary usage isn't going to work today public async Task LoadMultipleUpdatesRows(int count) { count = Clamp(count, 1, 500); diff --git a/test/Dapper.AOT.Test/Interceptors/Techempower.output.cs b/test/Dapper.AOT.Test/Interceptors/Techempower.output.cs index e2d2aa19..ab4d1182 100644 --- a/test/Dapper.AOT.Test/Interceptors/Techempower.output.cs +++ b/test/Dapper.AOT.Test/Interceptors/Techempower.output.cs @@ -3,9 +3,9 @@ namespace Dapper.AOT // interceptors must be in a known namespace { file static class DapperGeneratedInterceptors { - [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\Techempower.input.cs", 41, 20)] + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\Techempower.input.cs", 40, 20)] + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\Techempower.input.cs", 41, 14)] [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\Techempower.input.cs", 42, 14)] - [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\Techempower.input.cs", 43, 14)] internal static int Execute0(this global::System.Data.IDbConnection cnn, string sql, object? param, global::System.Data.IDbTransaction? transaction, int? commandTimeout, global::System.Data.CommandType? commandType) { // Execute, Text @@ -17,7 +17,7 @@ internal static int Execute0(this global::System.Data.IDbConnection cnn, string } - [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\Techempower.input.cs", 44, 14)] + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\Techempower.input.cs", 43, 14)] internal static int Execute1(this global::System.Data.IDbConnection cnn, string sql, object? param, global::System.Data.IDbTransaction? transaction, int? commandTimeout, global::System.Data.CommandType? commandType) { // Execute, HasParameters, Text, KnownParameters @@ -31,7 +31,7 @@ internal static int Execute1(this global::System.Data.IDbConnection cnn, string } - [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\Techempower.input.cs", 95, 19)] + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\Techempower.input.cs", 93, 19)] internal static global::System.Threading.Tasks.Task QueryFirstOrDefaultAsync2(this global::System.Data.IDbConnection cnn, string sql, object? param, global::System.Data.IDbTransaction? transaction, int? commandTimeout, global::System.Data.CommandType? commandType) { // Query, Async, TypedResult, HasParameters, SingleRow, Text, BindResultsByName, KnownParameters diff --git a/test/Dapper.AOT.Test/Interceptors/Techempower.output.netfx.cs b/test/Dapper.AOT.Test/Interceptors/Techempower.output.netfx.cs index e2d2aa19..ab4d1182 100644 --- a/test/Dapper.AOT.Test/Interceptors/Techempower.output.netfx.cs +++ b/test/Dapper.AOT.Test/Interceptors/Techempower.output.netfx.cs @@ -3,9 +3,9 @@ namespace Dapper.AOT // interceptors must be in a known namespace { file static class DapperGeneratedInterceptors { - [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\Techempower.input.cs", 41, 20)] + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\Techempower.input.cs", 40, 20)] + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\Techempower.input.cs", 41, 14)] [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\Techempower.input.cs", 42, 14)] - [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\Techempower.input.cs", 43, 14)] internal static int Execute0(this global::System.Data.IDbConnection cnn, string sql, object? param, global::System.Data.IDbTransaction? transaction, int? commandTimeout, global::System.Data.CommandType? commandType) { // Execute, Text @@ -17,7 +17,7 @@ internal static int Execute0(this global::System.Data.IDbConnection cnn, string } - [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\Techempower.input.cs", 44, 14)] + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\Techempower.input.cs", 43, 14)] internal static int Execute1(this global::System.Data.IDbConnection cnn, string sql, object? param, global::System.Data.IDbTransaction? transaction, int? commandTimeout, global::System.Data.CommandType? commandType) { // Execute, HasParameters, Text, KnownParameters @@ -31,7 +31,7 @@ internal static int Execute1(this global::System.Data.IDbConnection cnn, string } - [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\Techempower.input.cs", 95, 19)] + [global::System.Runtime.CompilerServices.InterceptsLocationAttribute("Interceptors\\Techempower.input.cs", 93, 19)] internal static global::System.Threading.Tasks.Task QueryFirstOrDefaultAsync2(this global::System.Data.IDbConnection cnn, string sql, object? param, global::System.Data.IDbTransaction? transaction, int? commandTimeout, global::System.Data.CommandType? commandType) { // Query, Async, TypedResult, HasParameters, SingleRow, Text, BindResultsByName, KnownParameters