Skip to content

Commit

Permalink
detect all DynamicParameters scenarios; suppress parameter warnings (…
Browse files Browse the repository at this point in the history
…also suppress DapperAOT enable advice if actively opted out)
  • Loading branch information
mgravell committed Sep 15, 2023
1 parent 7968293 commit df6d435
Show file tree
Hide file tree
Showing 7 changed files with 95 additions and 14 deletions.
26 changes: 16 additions & 10 deletions src/Dapper.AOT.Analyzers/CodeAnalysis/DapperInterceptorGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ private bool PreFilter(SyntaxNode node, CancellationToken cancellationToken)

// check whether we can use this method
object? diagnostics = null;
bool dapperEnabled = Inspection.IsEnabled(ctx, op, Types.DapperAotAttribute, out _, cancellationToken);
bool dapperEnabled = Inspection.IsEnabled(ctx, op, Types.DapperAotAttribute, out var aotAttribExists, cancellationToken);
if (dapperEnabled)
{
if (methodKind == DapperMethodKind.DapperUnsupported)
Expand All @@ -104,6 +104,7 @@ private bool PreFilter(SyntaxNode node, CancellationToken cancellationToken)
{
// no diagnostic per-item; the generator will emit a single diagnostic if everything is disabled
flags |= OperationFlags.AotNotEnabled | OperationFlags.DoNotGenerate;
if (aotAttribExists) flags |= OperationFlags.AotDisabled; // active opt-out
// but we still process the other bits, as there might be relevant additional things
}

Expand Down Expand Up @@ -573,7 +574,8 @@ select var.Name.StartsWith("@") ? var.Name.Substring(1) : var.Name
else
{
// we can only consider this an error if we're confident in how well we parsed the input
if ((parseFlags & ParseFlags.Reliable) != 0)
// (unless we detected dynamic args, in which case: we don't know what we don't know)
if ((parseFlags & (ParseFlags.Reliable | ParseFlags.DynamicParameters)) == ParseFlags.Reliable)
{
Diagnostics.Add(ref diagnostics, Diagnostic.Create(Diagnostics.SqlParameterNotBound, loc, sqlParamName, CodeWriter.GetTypeName(parameterType)));
}
Expand Down Expand Up @@ -782,10 +784,11 @@ enum OperationFlags
Scalar = 1 << 13,
DoNotGenerate = 1 << 14,
AotNotEnabled = 1 << 15,
BindResultsByName = 1 << 16,
BindTupleParameterByName = 1 << 17,
CacheCommand = 1 << 18,
IncludeLocation = 1 << 19, // include -- SomeFile.cs#40 when possible
AotDisabled = 1 << 16, // actively disabled
BindResultsByName = 1 << 17,
BindTupleParameterByName = 1 << 18,
CacheCommand = 1 << 19,
IncludeLocation = 1 << 20, // include -- SomeFile.cs#40 when possible
}

private static string? GetCommandFactory(Compilation compilation, out bool canConstruct)
Expand Down Expand Up @@ -838,7 +841,7 @@ private bool CheckPrerequisites(SourceProductionContext ctx, (Compilation Compil
enabledCount = 0;
if (!state.Nodes.IsDefaultOrEmpty)
{
int errorCount = 0, disabledCount = 0, doNotGenerateCount = 0;
int errorCount = 0, passiveDisabledCount = 0, doNotGenerateCount = 0;
bool checkParseOptions = true;
foreach (var node in state.Nodes)
{
Expand All @@ -850,9 +853,12 @@ private bool CheckPrerequisites(SourceProductionContext ctx, (Compilation Compil

if (HasAny(node.Flags, OperationFlags.DoNotGenerate)) doNotGenerateCount++;

if ((node.Flags & OperationFlags.AotNotEnabled) != 0)
if ((node.Flags & OperationFlags.AotNotEnabled) != 0) // passive or active disabled
{
disabledCount++;
if ((node.Flags & OperationFlags.AotDisabled) == 0)
{
passiveDisabledCount++;
}
}
else
{
Expand All @@ -876,7 +882,7 @@ private bool CheckPrerequisites(SourceProductionContext ctx, (Compilation Compil
}
}
}
if (disabledCount == state.Nodes.Length && IsDapperAotAvailable(state.Compilation))
if (passiveDisabledCount == state.Nodes.Length && IsDapperAotAvailable(state.Compilation))
{
ctx.ReportDiagnostic(Diagnostic.Create(Diagnostics.DapperAotNotEnabled, null));
errorCount++;
Expand Down
25 changes: 22 additions & 3 deletions src/Dapper.AOT.Analyzers/Internal/DiagnosticTSqlProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
using System;
using System.Data;
using System.Diagnostics;
using static Dapper.SqlAnalysis.TSqlProcessor;

namespace Dapper.Internal;

Expand All @@ -18,13 +17,15 @@ internal class DiagnosticTSqlProcessor : TSqlProcessor
private readonly LiteralExpressionSyntax? _literal;
public object? DiagnosticsObject => _diagnostics;
private readonly ITypeSymbol? _parameterType;
private readonly bool _assumeParameterExists;
public DiagnosticTSqlProcessor(ITypeSymbol? parameterType, ModeFlags flags, object? diagnostics,
Microsoft.CodeAnalysis.Location? location, SyntaxNode? sqlSyntax) : base(flags)
{
_diagnostics = diagnostics;
_location = sqlSyntax?.GetLocation() ?? location;
_parameterType = parameterType;

_assumeParameterExists = Inspection.IsMissingOrObjectOrDynamic(_parameterType) || IsDyanmicParameters(parameterType);
if (_assumeParameterExists) AddFlags(ParseFlags.DynamicParameters);
switch (sqlSyntax)
{
case LiteralExpressionSyntax direct:
Expand All @@ -37,6 +38,24 @@ public DiagnosticTSqlProcessor(ITypeSymbol? parameterType, ModeFlags flags, obje
}
}

public override void Reset()
{
base.Reset();
if (_assumeParameterExists) AddFlags(ParseFlags.DynamicParameters);
}

static bool IsDyanmicParameters(ITypeSymbol? type)
{
if (type is null || type.SpecialType != SpecialType.None) return false;
if (Inspection.IsBasicDapperType(type, Types.DynamicParameters)
|| Inspection.IsNestedSqlMapperType(type, Types.IDynamicParameters, TypeKind.Interface)) return true;
foreach (var i in type.AllInterfaces)
{
if (Inspection.IsNestedSqlMapperType(i, Types.IDynamicParameters, TypeKind.Interface)) return true;
}
return false;
}

private Microsoft.CodeAnalysis.Location? GetLocation(in Location location)
{
if (_literal is not null)
Expand Down Expand Up @@ -143,7 +162,7 @@ protected override bool TryGetParameter(string name, out ParameterDirection dire
return false;
}

if (Inspection.IsMissingOrObjectOrDynamic(_parameterType))
if (_assumeParameterExists) // dynamic, etc
{
// dynamic
direction = ParameterDirection.Input;
Expand Down
16 changes: 16 additions & 0 deletions src/Dapper.AOT.Analyzers/Internal/Inspection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,22 @@ public static bool InvolvesTupleType(this ITypeSymbol? type, out bool hasNames)
return null;
}

public static bool IsBasicDapperType(ITypeSymbol? type, string name, TypeKind kind = TypeKind.Class)
=> type is INamedTypeSymbol
{
Arity: 0, ContainingType: null,
ContainingNamespace: { Name: "Dapper", ContainingNamespace.IsGlobalNamespace: true }
} named && named.TypeKind == kind && named.Name == name;

public static bool IsNestedSqlMapperType(ITypeSymbol? type, string name, TypeKind kind = TypeKind.Class)
=> type is INamedTypeSymbol
{
Arity: 0, ContainingType: {
Name: Types.SqlMapper, TypeKind: TypeKind.Class, IsStatic: true, Arity: 0,
ContainingNamespace: { Name: "Dapper", ContainingNamespace.IsGlobalNamespace: true },
},
} named && named.TypeKind == kind && named.Name == name;

public static ISymbol? GetSymbol(in GeneratorSyntaxContext ctx, IOperation operation, CancellationToken cancellationToken)
{
var method = GetContainingMethodSyntax(operation);
Expand Down
5 changes: 4 additions & 1 deletion src/Dapper.AOT.Analyzers/Internal/Types.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,8 @@ public const string
IncludeLocationAttribute = nameof(IncludeLocationAttribute),
SqlSyntaxAttribute = nameof(SqlSyntaxAttribute),
EstimatedRowCountAttribute = nameof(EstimatedRowCountAttribute),
CommandPropertyAttribute = nameof(CommandPropertyAttribute);
CommandPropertyAttribute = nameof(CommandPropertyAttribute),
DynamicParameters = nameof(DynamicParameters),
IDynamicParameters = nameof(IDynamicParameters),
SqlMapper = nameof(SqlMapper);
}
1 change: 1 addition & 0 deletions src/Dapper.AOT.Analyzers/ParseFlags.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ internal enum ParseFlags
Query = 1 << 3,
Queries = 1 << 4,
MaybeQuery = 1 << 5, // think "exec": we don't know!
DynamicParameters = 1 << 6,
}
1 change: 1 addition & 0 deletions src/Dapper.AOT.Analyzers/SqlAnalysis/TSqlProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ public virtual bool Execute(string sql, ImmutableArray<ElementMember> members =
public IEnumerable<Variable> Variables => _visitor.Variables;

public ParseFlags Flags { get; private set; }
protected void AddFlags(ParseFlags flags) => Flags |= flags;
public virtual void Reset()
{
Flags = ParseFlags.Reliable;
Expand Down
35 changes: 35 additions & 0 deletions test/Dapper.AOT.Test/Interceptors/DynamicParameters.input.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using System;
using System.Data;
using Dapper;
using Microsoft.Data.SqlClient;
[DapperAot(false)]
internal class DynamicParametersTest
{
private SqlConnection connection;

static readonly DateTime From = new DateTime(2023, 6, 1), To = new DateTime(2023, 6, 30);
public void UseTrivialDynamicParameters()
{
var parameters = new DynamicParameters();
parameters.Add("@StartDate", From);
parameters.Add("@EndDate", To);

connection.Execute("insert Reservations ([Start], [End]) values (@StartDate, @EndDate)", parameters);
connection.Execute("insert Reservations ([Start], [End]) values (@StartDate, @EndDate)", (SqlMapper.IDynamicParameters)parameters);
connection.Execute("insert Reservations ([Start], [End]) values (@StartDate, @EndDate)", new Foo());
}
public void UseAnonType()
{
var parameters = new
{
StartDate = From,
EndDate = To,
};

connection.Execute("insert Reservations ([Start], [End]) values (@StartDate, @EndDate)", parameters);
}
class Foo : SqlMapper.IDynamicParameters
{
void SqlMapper.IDynamicParameters.AddParameters(IDbCommand command, SqlMapper.Identity identity) { }
}
}

0 comments on commit df6d435

Please sign in to comment.