Skip to content

Commit

Permalink
Matching Input and Output Type Activity Analyzer (#316)
Browse files Browse the repository at this point in the history
This new analyzer will search for `TaskOrchestrationContext.CallActivityAsync` invocations and check if the provided input and output types matches the related Activity definition. The activity can be either an Azure Function activity, a Durable Task Activity or a Durable Task Func/Action. If there is a mismatch between the types, the analyzer reports `DURABLE2001` (input mismatch) or `DURABLE2002` (output mismatch).

The code uses activity name to correlate the invocations and their definitions, which must be constant in compile time. For instance, they can be defined using `nameof` operator, string `const` fields or locals, and string literals. If the invocation name can't be determined (because it is not constant a compile time), the code ignores the checking to avoid a false positive report.
  • Loading branch information
allantargino authored May 25, 2024
1 parent 23eb257 commit cd92074
Show file tree
Hide file tree
Showing 8 changed files with 826 additions and 1 deletion.
352 changes: 352 additions & 0 deletions src/Analyzers/Activities/MatchingInputOutputTypeActivityAnalyzer.cs

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion src/Analyzers/AnalyzerReleases.Unshipped.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,6 @@ DURABLE0007 | Orchestration | Warning | CancellationTokenOrchestrationAnalyzer
DURABLE0008 | Orchestration | Warning | OtherBindingsOrchestrationAnalyzer
DURABLE1001 | Attribute Binding | Error | OrchestrationTriggerBindingAnalyzer
DURABLE1002 | Attribute Binding | Error | DurableClientBindingAnalyzer
DURABLE1003 | Attribute Binding | Error | EntityTriggerBindingAnalyzer
DURABLE1003 | Attribute Binding | Error | EntityTriggerBindingAnalyzer
DURABLE2001 | Activity | Warning | MatchingInputOutputTypeActivityAnalyzer
DURABLE2002 | Activity | Warning | MatchingInputOutputTypeActivityAnalyzer
5 changes: 5 additions & 0 deletions src/Analyzers/AnalyzersCategories.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,9 @@ static class AnalyzersCategories
/// The category for the attribute binding related analyzers.
/// </summary>
public const string AttributeBinding = "Attribute Binding";

/// <summary>
/// The category for the activity related analyzers.
/// </summary>
public const string Activity = "Activity";
}
6 changes: 6 additions & 0 deletions src/Analyzers/KnownTypeSymbols.Durable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ namespace Microsoft.DurableTask.Analyzers;
public sealed partial class KnownTypeSymbols
{
INamedTypeSymbol? taskOrchestratorInterface;
INamedTypeSymbol? taskActivityBase;
INamedTypeSymbol? durableTaskRegistry;
INamedTypeSymbol? taskOrchestrationContext;
INamedTypeSymbol? durableTaskClient;
Expand All @@ -23,6 +24,11 @@ public sealed partial class KnownTypeSymbols
/// </summary>
public INamedTypeSymbol? TaskOrchestratorInterface => this.GetOrResolveFullyQualifiedType("Microsoft.DurableTask.ITaskOrchestrator", ref this.taskOrchestratorInterface);

/// <summary>
/// Gets a TaskActivity&lt;TInput,TOutput&gt; type symbol.
/// </summary>
public INamedTypeSymbol? TaskActivityBase => this.GetOrResolveFullyQualifiedType("Microsoft.DurableTask.TaskActivity`2", ref this.taskActivityBase);

/// <summary>
/// Gets a DurableTaskRegistry type symbol.
/// </summary>
Expand Down
6 changes: 6 additions & 0 deletions src/Analyzers/KnownTypeSymbols.Functions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public sealed partial class KnownTypeSymbols
INamedTypeSymbol? functionOrchestrationAttribute;
INamedTypeSymbol? functionNameAttribute;
INamedTypeSymbol? durableClientAttribute;
INamedTypeSymbol? activityTriggerAttribute;
INamedTypeSymbol? entityTriggerAttribute;
INamedTypeSymbol? taskEntityDispatcher;

Expand All @@ -34,6 +35,11 @@ public sealed partial class KnownTypeSymbols
/// </summary>
public INamedTypeSymbol? DurableClientAttribute => this.GetOrResolveFullyQualifiedType("Microsoft.Azure.Functions.Worker.DurableClientAttribute", ref this.durableClientAttribute);

/// <summary>
/// Gets an ActivityTriggerAttribute type symbol.
/// </summary>
public INamedTypeSymbol? ActivityTriggerAttribute => this.GetOrResolveFullyQualifiedType("Microsoft.Azure.Functions.Worker.ActivityTriggerAttribute", ref this.activityTriggerAttribute);

/// <summary>
/// Gets an EntityTriggerAttribute type symbol.
/// </summary>
Expand Down
12 changes: 12 additions & 0 deletions src/Analyzers/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -183,4 +183,16 @@
<data name="ThreadTaskOrchestrationAnalyzerTitle" xml:space="preserve">
<value>Thread and Task calls must be deterministic inside an orchestrator function</value>
</data>
<data name="InputArgumentTypeMismatchAnalyzerMessageFormat" xml:space="preserve">
<value>CallActivityAsync is passing the incorrect type '{0}' instead of '{1}' to the activity function '{2}'</value>
</data>
<data name="InputArgumentTypeMismatchAnalyzerTitle" xml:space="preserve">
<value>Activity function calls use the wrong argument type</value>
</data>
<data name="OutputArgumentTypeMismatchAnalyzerMessageFormat" xml:space="preserve">
<value>CallActivityAsync is expecting the return type '{0}' and that does not match the return type '{1}' of the activity function '{2}'</value>
</data>
<data name="OutputArgumentTypeMismatchAnalyzerTitle" xml:space="preserve">
<value>Activity function call return type doesn't match the function definition return type</value>
</data>
</root>
23 changes: 23 additions & 0 deletions src/Analyzers/RoslynExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT License.

using System.Collections.Immutable;
using System.Diagnostics;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
Expand Down Expand Up @@ -85,6 +86,28 @@ public static bool BaseTypeIsConstructedFrom(this INamedTypeSymbol symbol, IType
return methods.FirstOrDefault(m => m.OverriddenMethod != null && m.OverriddenMethod.OriginalDefinition.Equals(methodSymbol, SymbolEqualityComparer.Default));
}

/// <summary>
/// Gets the type argument of a method by its parameter name.
/// </summary>
/// <param name="method">Method symbol.</param>
/// <param name="parameterName">Type argument name.</param>
/// <returns>The type argument symbol.</returns>
public static ITypeSymbol? GetTypeArgumentByParameterName(this IMethodSymbol method, string parameterName)
{
(ITypeParameterSymbol param, int idx) = method.TypeParameters
.Where(t => t.Name == parameterName)
.Select((t, i) => (t, i))
.SingleOrDefault();

if (param != null)
{
Debug.Assert(idx >= 0, "parameter index is not negative");
return method.TypeArguments[idx];
}

return null;
}

/// <summary>
/// Gets the syntax nodes of a method symbol.
/// </summary>
Expand Down
Loading

0 comments on commit cd92074

Please sign in to comment.