Skip to content

Commit

Permalink
fixup remaining tests (#58)
Browse files Browse the repository at this point in the history
* - fault handling
- DAP037 (needs test+docs)

* DAP037 tests; docs are no longer optional

* args

* fix rowcount change
  • Loading branch information
mgravell authored Sep 25, 2023
1 parent 739dfd2 commit c5f7392
Show file tree
Hide file tree
Showing 62 changed files with 967 additions and 548 deletions.
11 changes: 11 additions & 0 deletions docs/rules/DAP037.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# DAP037

When materializing results, Dapper needs to be able to assign values. It can do this in a range
of ways, including:

- accessible fields that are not `readonly`
- accessible `set` accessors
- accessible `init` accessors
- constructors (using `[ExplicitConstructor]` to select if ambiguous)

If there are *no* accessible members: you're not going to get very interesting results, hence this warning!
58 changes: 29 additions & 29 deletions src/Dapper.AOT.Analyzers/CodeAnalysis/DapperAnalyzer.Diagnostics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,44 +9,44 @@ internal sealed class Diagnostics : DiagnosticsBase
public static readonly DiagnosticDescriptor
// general usage
// 000: InterceptorsGenerated
UnsupportedMethod = LibraryInfo("DAP001", "Unsupported method", "The Dapper method '{0}' is not currently supported by Dapper.AOT", true),
UnsupportedMethod = LibraryInfo("DAP001", "Unsupported method", "The Dapper method '{0}' is not currently supported by Dapper.AOT"),
// unused: DAP002
// 003: InterceptorsNotEnabled
// 004: LanguageVersionTooLow
DapperAotNotEnabled = LibraryInfo("DAP005", "Dapper.AOT not enabled", "{0} candidate Dapper methods detected, but none have Dapper.AOT enabled", true),
DapperLegacyTupleParameter = LibraryWarning("DAP006", "Dapper tuple-type parameter", "Dapper (original) does not work well with tuple-type parameters as name information is inaccessible", true),
UnexpectedCommandType = LibraryInfo("DAP007", "Unexpected command type", "The command type specified is not understood", true),
DapperAotNotEnabled = LibraryInfo("DAP005", "Dapper.AOT not enabled", "{0} candidate Dapper methods detected, but none have Dapper.AOT enabled"),
DapperLegacyTupleParameter = LibraryWarning("DAP006", "Dapper tuple-type parameter", "Dapper (original) does not work well with tuple-type parameters as name information is inaccessible"),
UnexpectedCommandType = LibraryInfo("DAP007", "Unexpected command type", "The command type specified is not understood"),
// unused: DAP008
UnexpectedArgument = LibraryInfo("DAP009", "Unexpected parameter", "The parameter '{0}' is not understood", true),
UnexpectedArgument = LibraryInfo("DAP009", "Unexpected parameter", "The parameter '{0}' is not understood"),
// unused: DAP010
DapperLegacyBindNameTupleResults = LibraryWarning("DAP011", "Named-tuple results", "Dapper (original) does not support tuple results with bind-by-name semantics", true),
DapperAotAddBindTupleByName = LibraryWarning("DAP012", "Add BindTupleByName", "Because of differences in how Dapper and Dapper.AOT can process tuple-types, please add '[BindTupleByName({true|false})]' to clarify your intent", true),
DapperAotTupleResults = LibraryInfo("DAP013", "Tuple-type results", "Tuple-type results are not currently supported", true),
DapperAotTupleParameter = LibraryInfo("DAP014", "Tuple-type parameter", "Tuple-type parameters are not currently supported", true),
UntypedParameter = LibraryInfo("DAP015", "Untyped parameter", "The parameter type could not be resolved", true),
GenericTypeParameter = LibraryInfo("DAP016", "Generic type parameter", "Generic type parameters ({0}) are not currently supported", true),
NonPublicType = LibraryInfo("DAP017", "Non-accessible type", "Type '{0}' is not accessible; {1} types are not currently supported", true),
SqlParametersNotDetected = SqlWarning("DAP018", "SQL parameters not detected", "Parameters are being supplied, but no parameters were detected in the command", true),
DapperLegacyBindNameTupleResults = LibraryWarning("DAP011", "Named-tuple results", "Dapper (original) does not support tuple results with bind-by-name semantics"),
DapperAotAddBindTupleByName = LibraryWarning("DAP012", "Add BindTupleByName", "Because of differences in how Dapper and Dapper.AOT can process tuple-types, please add '[BindTupleByName({true|false})]' to clarify your intent"),
DapperAotTupleResults = LibraryInfo("DAP013", "Tuple-type results", "Tuple-type results are not currently supported"),
DapperAotTupleParameter = LibraryInfo("DAP014", "Tuple-type parameter", "Tuple-type parameters are not currently supported"),
UntypedParameter = LibraryInfo("DAP015", "Untyped parameter", "The parameter type could not be resolved"),
GenericTypeParameter = LibraryInfo("DAP016", "Generic type parameter", "Generic type parameters ({0}) are not currently supported"),
NonPublicType = LibraryInfo("DAP017", "Non-accessible type", "Type '{0}' is not accessible; {1} types are not currently supported"),
SqlParametersNotDetected = SqlWarning("DAP018", "SQL parameters not detected", "Parameters are being supplied, but no parameters were detected in the command"),
// unused: DAP019
// unused: DAP020
DuplicateParameter = LibraryWarning("DAP021", "Duplicate parameter", "Members '{0}' and '{1}' both have the database name '{2}'; '{1}' will be ignored", true),
DuplicateReturn = LibraryWarning("DAP022", "Duplicate return parameter", "Members '{0}' and '{1}' are both designated as return values; '{1}' will be ignored", true),
DuplicateRowCount = LibraryWarning("DAP023", "Duplicate row-count member", "Members '{0}' and '{1}' are both marked [RowCount]", true),
RowCountDbValue = LibraryWarning("DAP024", "Member is both row-count and mapped value", "Member '{0}' is marked both [RowCount] and [DbValue]; [DbValue] will be ignored", true),
ExecuteCommandWithQuery = SqlWarning("DAP025", "Execute command with query", "The command has a query that will be ignored", true),
QueryCommandMissingQuery = SqlError("DAP026", "Query/scalar command lacks query", "The command lacks a query", true),
UseSingleRowQuery = PerformanceWarning("DAP027", "Use single-row query", "Use {0}() instead of Query(...).{1}()", true),
UseQueryAsList = PerformanceWarning("DAP028", "Use AsList instead of ToList", "Use Query(...).AsList() instead of Query(...).ToList()", true),
RowCountHintRedundant = LibraryInfo("DAP029", "Row-count hint redundant", "The method-level [RowCountHint] will be ignored due to parameter member '{0}'", true),
RowCountHintInvalidValue = LibraryError("DAP030", "Row-count hint invalid value", "The [RowCountHint] parameters are invalid; a positive integer must be supplied", true),
RowCountHintShouldNotSpecifyValue = LibraryError("DAP031", "Row-count hint should not specify value", "The [RowCountHint] parameters are invalid; no parameter should be supplied", true),
RowCountHintDuplicated = LibraryError("DAP032", "Row-count hint duplicated", "Only a single member or parameter should be marked [RowCountHint]; '{0} will be ignored", true),
DuplicateParameter = LibraryWarning("DAP021", "Duplicate parameter", "Members '{0}' and '{1}' both have the database name '{2}'; '{1}' will be ignored"),
DuplicateReturn = LibraryWarning("DAP022", "Duplicate return parameter", "Members '{0}' and '{1}' are both designated as return values; '{1}' will be ignored"),
DuplicateRowCount = LibraryWarning("DAP023", "Duplicate row-count member", "Members '{0}' and '{1}' are both marked [RowCount]"),
RowCountDbValue = LibraryWarning("DAP024", "Member is both row-count and mapped value", "Member '{0}' is marked both [RowCount] and [DbValue]; [DbValue] will be ignored"),
ExecuteCommandWithQuery = SqlWarning("DAP025", "Execute command with query", "The command has a query that will be ignored"),
QueryCommandMissingQuery = SqlError("DAP026", "Query/scalar command lacks query", "The command lacks a query"),
UseSingleRowQuery = PerformanceWarning("DAP027", "Use single-row query", "Use {0}() instead of Query(...).{1}()"),
UseQueryAsList = PerformanceWarning("DAP028", "Use AsList instead of ToList", "Use Query(...).AsList() instead of Query(...).ToList()"),
RowCountHintRedundant = LibraryInfo("DAP029", "Row-count hint redundant", "The method-level [RowCountHint] will be ignored due to parameter member '{0}'"),
RowCountHintInvalidValue = LibraryError("DAP030", "Row-count hint invalid value", "The [RowCountHint] parameters are invalid; a positive integer must be supplied"),
RowCountHintShouldNotSpecifyValue = LibraryError("DAP031", "Row-count hint should not specify value", "The [RowCountHint] parameters are invalid; no parameter should be supplied"),
RowCountHintDuplicated = LibraryError("DAP032", "Row-count hint duplicated", "Only a single member or parameter should be marked [RowCountHint]; '{0} will be ignored"),
// 033: CommandPropertyNotFound
// 034: CommandPropertyReserved
ConstructorMultipleExplicit = LibraryError("DAP035", "Multiple explicit constructors", "Only one constructor should be marked [ExplicitConstructor] for type '{0}'", true),
ConstructorAmbiguous = LibraryError("DAP036", "Ambiguous constructors", "Type '{0}' has more than 1 constructor; mark one constructor with [ExplicitConstructor] or reduce constructors", true),
// 037: UserTypeNoSettableMembersFound
ValueTypeSingleFirstOrDefaultUsage = LibraryWarning("DAP038", "Value-type single row 'OrDefault' usage", "Type '{0}' is a value-type; it will not be trivial to identify missing rows from {1}", true),
ConstructorMultipleExplicit = LibraryError("DAP035", "Multiple explicit constructors", "Only one constructor should be marked [ExplicitConstructor] for type '{0}'"),
ConstructorAmbiguous = LibraryError("DAP036", "Ambiguous constructors", "Type '{0}' has more than 1 constructor; mark one constructor with [ExplicitConstructor] or reduce constructors"),
UserTypeNoSettableMembersFound = LibraryError("DAP037", "No settable members exist for user type", "Type '{0}' has no settable fields or properties"),
ValueTypeSingleFirstOrDefaultUsage = LibraryWarning("DAP038", "Value-type single row 'OrDefault' usage", "Type '{0}' is a value-type; it will not be trivial to identify missing rows from {1}"),

// SQL parse specific
GeneralSqlError = SqlWarning("DAP200", "SQL error", "SQL error: {0}"),
Expand Down
73 changes: 45 additions & 28 deletions src/Dapper.AOT.Analyzers/CodeAnalysis/DapperAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,21 +83,28 @@ private void OnDapperAotMiss(Location location)

internal void OnCompilationEndAction(CompilationAnalysisContext ctx)
{
lock (_missedOpportunities)
try
{
var count = _missedOpportunities.Count;
if (count != 0)
lock (_missedOpportunities)
{
var location = _missedOpportunities[0];
Location[]? additionalLocations = null;
if (count > 1)
var count = _missedOpportunities.Count;
if (count != 0)
{
additionalLocations = new Location[count - 1];
_missedOpportunities.CopyTo(1, additionalLocations, 0, count - 1);
var location = _missedOpportunities[0];
Location[]? additionalLocations = null;
if (count > 1)
{
additionalLocations = new Location[count - 1];
_missedOpportunities.CopyTo(1, additionalLocations, 0, count - 1);
}
ctx.ReportDiagnostic(Diagnostic.Create(Diagnostics.DapperAotNotEnabled, location, additionalLocations, count));
}
ctx.ReportDiagnostic(Diagnostic.Create(Diagnostics.DapperAotNotEnabled, location, additionalLocations, count));
}
}
catch (Exception ex)
{
ctx.ReportDiagnostic(Diagnostic.Create(DiagnosticsBase.UnknownError, null, ex.Message, ex.StackTrace));
}
}

public void OnOperation(OperationAnalysisContext ctx)
Expand Down Expand Up @@ -185,31 +192,41 @@ private void ValidateDapperMethod(in OperationAnalysisContext ctx, IOperation sq
}

// check the types
var resultMap = MemberMap.Create(invoke.GetResultType(flags), location);
if (resultMap is not null)
var resultType = invoke.GetResultType(flags);
if (resultType is not null && IdentifyDbType(resultType, out _) is null) // don't warn if handled as an inbuilt
{
// check for single-row value-type usage
if (flags.HasAny(OperationFlags.SingleRow) && !flags.HasAny(OperationFlags.AtLeastOne)
&& resultMap.ElementType.IsValueType && !CouldBeNullable(resultMap.ElementType))
var resultMap = MemberMap.CreateForResults(resultType, location);
if (resultMap is not null)
{
ctx.ReportDiagnostic(Diagnostic.Create(Diagnostics.ValueTypeSingleFirstOrDefaultUsage, location, resultMap.ElementType.Name, invoke.TargetMethod.Name));
}
// check for single-row value-type usage
if (flags.HasAny(OperationFlags.SingleRow) && !flags.HasAny(OperationFlags.AtLeastOne)
&& resultMap.ElementType.IsValueType && !CouldBeNullable(resultMap.ElementType))
{
ctx.ReportDiagnostic(Diagnostic.Create(Diagnostics.ValueTypeSingleFirstOrDefaultUsage, location, resultMap.ElementType.Name, invoke.TargetMethod.Name));
}

// check for constructors on the materialized type
DiagnosticDescriptor? ctorFault = ChooseConstructor(resultMap.ElementType, out var ctor) switch
{
ConstructorResult.FailMultipleExplicit => Diagnostics.ConstructorMultipleExplicit,
ConstructorResult.FailMultipleImplicit when aotEnabled => Diagnostics.ConstructorAmbiguous,
_ => null,
};
if (ctorFault is not null)
{
var loc = ctor?.Locations.FirstOrDefault() ?? resultMap.ElementType.Locations.FirstOrDefault();
ctx.ReportDiagnostic(Diagnostic.Create(ctorFault, loc, resultMap.ElementType.GetDisplayString()));
// check for constructors on the materialized type
DiagnosticDescriptor? ctorFault = ChooseConstructor(resultMap.ElementType, out var ctor) switch
{
ConstructorResult.FailMultipleExplicit => Diagnostics.ConstructorMultipleExplicit,
ConstructorResult.FailMultipleImplicit when aotEnabled => Diagnostics.ConstructorAmbiguous,
_ => null,
};
if (ctorFault is not null)
{
var loc = ctor?.Locations.FirstOrDefault() ?? resultMap.ElementType.Locations.FirstOrDefault();
ctx.ReportDiagnostic(Diagnostic.Create(ctorFault, loc, resultMap.ElementType.GetDisplayString()));
}
else if (resultMap.Members.IsDefaultOrEmpty && IsPublicOrAssemblyLocal(resultType, parseState, out _))
{
// there are so settable members + there is no constructor to use
// (note: generator uses Inspection.GetMembers(type, dapperAotConstructor: constructor)
ctx.ReportDiagnostic(Diagnostic.Create(Diagnostics.UserTypeNoSettableMembersFound, resultMap.Location, resultType.Name));
}
}
}

var parameters = MemberMap.Create(argExpression);
var parameters = MemberMap.CreateForParameters(argExpression);
if (aotEnabled)
{
_ = AdditionalCommandState.Parse(GetSymbol(parseState, invoke), parameters, onDiagnostic);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,13 @@ internal sealed class Diagnostics : DiagnosticsBase
{

internal static readonly DiagnosticDescriptor
InterceptorsGenerated = LibraryHidden("DAP000", "Interceptors generated", "Dapper.AOT handled {0} of {1} possible call-sites using {2} interceptors, {3} commands and {4} readers", true),
InterceptorsGenerated = LibraryHidden("DAP000", "Interceptors generated", "Dapper.AOT handled {0} of {1} possible call-sites using {2} interceptors, {3} commands and {4} readers"),
InterceptorsNotEnabled = LibraryWarning("DAP003", "Interceptors not enabled",
"Interceptors need to be enabled", true),
LanguageVersionTooLow = LibraryWarning("DAP004", "Language version too low", "Interceptors require at least C# version 11", true),
"Interceptors need to be enabled"),
LanguageVersionTooLow = LibraryWarning("DAP004", "Language version too low", "Interceptors require at least C# version 11"),


CommandPropertyNotFound = LibraryWarning("DAP033", "Command property not found", "Command property {0}.{1} was not found or was not valid; attribute will be ignored",true),
CommandPropertyReserved = LibraryWarning("DAP034", "Command property reserved", "Command property {1} is reserved for internal usage; attribute will be ignored", true),

UserTypeNoSettableMembersFound = LibraryError("DAP037", "No settable members exist for user type", "Type '{0}' has no settable members (fields or properties)");
CommandPropertyNotFound = LibraryWarning("DAP033", "Command property not found", "Command property {0}.{1} was not found or was not valid; attribute will be ignored"),
CommandPropertyReserved = LibraryWarning("DAP034", "Command property reserved", "Command property {1} is reserved for internal usage; attribute will be ignored");
}
}
Loading

0 comments on commit c5f7392

Please sign in to comment.