Fix FunctionContext Check and Polymorphic Type Conversions in Activity Analyzer#506
Fix FunctionContext Check and Polymorphic Type Conversions in Activity Analyzer#506
Conversation
There was a problem hiding this comment.
Pull Request Overview
This PR fixes issue #453 by improving the Activity analyzer to correctly handle FunctionContext parameters and support polymorphic type conversions.
- Adds special handling to skip validation for activities that use FunctionContext with [ActivityTrigger], preventing false positive warnings
- Replaces exact type matching with type compatibility checking using Roslyn's ClassifyConversion API and custom collection compatibility logic
- Adds comprehensive test coverage for FunctionContext handling, polymorphism, collection type compatibility, and incompatible type detection
Reviewed Changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| src/Analyzers/Activities/MatchingInputOutputTypeActivityAnalyzer.cs | Adds FunctionContext detection to skip DI parameter validation; implements type compatibility checking with support for polymorphism, inheritance, and collection interface implementations (List to IReadOnlyList) |
| test/Analyzers.Tests/Activities/MatchingInputOutputTypeActivityAnalyzerTests.cs | Adds 6 new test cases covering FunctionContext scenarios, polymorphic input/output types, collection type compatibility, and incompatible type detection |
| foreach (INamedTypeSymbol @interface in sourceType.AllInterfaces) | ||
| { | ||
| if (SymbolEqualityComparer.Default.Equals(@interface.OriginalDefinition, targetInterface.OriginalDefinition)) | ||
| { | ||
| return true; | ||
| } | ||
| } |
There was a problem hiding this comment.
This foreach loop implicitly filters its target sequence - consider filtering the sequence explicitly using '.Where(...)'.
bachuv
left a comment
There was a problem hiding this comment.
Thanks for opening this PR!
Can you add more details in the PR description?
| } | ||
|
|
||
| // One is null, the other isn't = not compatible | ||
| if (sourceType == null || targetType == null) |
There was a problem hiding this comment.
Just checking - does this logic support passing null as input to a nullable function parameter? That was also an issue with this analyzer previously
| // Array implements: IEnumerable<T>, ICollection<T>, IList<T>, IReadOnlyCollection<T>, IReadOnlyList<T> | ||
| string targetName = targetInterface.OriginalDefinition.ToDisplayString(); | ||
| return targetName == "System.Collections.Generic.IEnumerable<T>" || | ||
| targetName == "System.Collections.Generic.ICollection<T>" || | ||
| targetName == "System.Collections.Generic.IList<T>" || | ||
| targetName == "System.Collections.Generic.IReadOnlyCollection<T>" || | ||
| targetName == "System.Collections.Generic.IReadOnlyList<T>"; |
There was a problem hiding this comment.
[nitpick] The array interface compatibility check uses string comparison of type names, which may not be robust across different compilation contexts or type forwarding scenarios. Consider using GetTypeByMetadataName to get reference symbols for the standard collection interfaces and comparing using SymbolEqualityComparer.Default.Equals instead of string comparison. This would be more reliable and less prone to edge cases with type representation differences.
| if (sourceType == null && targetType != null) | ||
| { | ||
| // Check if target is a nullable reference type (NullableAnnotation.Annotated) | ||
| // or a nullable value type (Nullable<T>) | ||
| if (targetType.NullableAnnotation == NullableAnnotation.Annotated || | ||
| targetType.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T) | ||
| { | ||
| return true; | ||
| } | ||
| } |
There was a problem hiding this comment.
The null handling logic may incorrectly reject null values passed to reference type parameters when nullable reference types are disabled. When sourceType is null and targetType is a reference type (like string), the code checks for NullableAnnotation.Annotated (line 369), which will be false when nullable reference types are disabled. However, in such projects, all reference types can accept null. Consider adding || targetType.IsReferenceType to line 369 to allow null for all reference types, or checking if nullable context is enabled before applying strict nullable validation.
There was a problem hiding this comment.
turns out this is not right
src/Analyzers/Activities/MatchingInputOutputTypeActivityAnalyzer.cs
Outdated
Show resolved
Hide resolved
c47ebea to
ec21a2a
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated 8 comments.
Comments suppressed due to low confidence (1)
src/Analyzers/Activities/MatchingInputOutputTypeActivityAnalyzer.cs:374
- These 'if' statements can be combined.
if (sourceType == null && targetType != null)
{
// Check if target is a nullable reference type (NullableAnnotation.Annotated)
// or a nullable value type (Nullable<T>)
if (targetType.NullableAnnotation == NullableAnnotation.Annotated ||
targetType.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T)
{
return true;
}
}
src/Analyzers/Activities/MatchingInputOutputTypeActivityAnalyzer.cs
Outdated
Show resolved
Hide resolved
test/Analyzers.Tests/Activities/MatchingInputOutputTypeActivityAnalyzerTests.cs
Outdated
Show resolved
Hide resolved
test/Analyzers.Tests/Activities/MatchingInputOutputTypeActivityAnalyzerTests.cs
Outdated
Show resolved
Hide resolved
test/Analyzers.Tests/Activities/MatchingInputOutputTypeActivityAnalyzerTests.cs
Outdated
Show resolved
Hide resolved
test/Analyzers.Tests/Activities/MatchingInputOutputTypeActivityAnalyzerTests.cs
Outdated
Show resolved
Hide resolved
test/Analyzers.Tests/Activities/MatchingInputOutputTypeActivityAnalyzerTests.cs
Outdated
Show resolved
Hide resolved
test/Analyzers.Tests/Activities/MatchingInputOutputTypeActivityAnalyzerTests.cs
Outdated
Show resolved
Hide resolved
test/Analyzers.Tests/Activities/MatchingInputOutputTypeActivityAnalyzerTests.cs
Outdated
Show resolved
Hide resolved
| // If the parameter is FunctionContext, skip validation for this activity (it's a DI parameter, not real input) | ||
| if (functionContextSymbol != null && SymbolEqualityComparer.Default.Equals(inputParam.Type, functionContextSymbol)) | ||
| { | ||
| return; | ||
| } |
There was a problem hiding this comment.
The FunctionContext check (lines 167-171) lacks test coverage. Consider adding a test case that verifies an activity method with a FunctionContext parameter marked with [ActivityTrigger] doesn't generate diagnostics, confirming that DI parameters are properly excluded from input type validation.
| // Check if type arguments are compatible (could be different but compatible types) | ||
| for (int i = 0; i < sourceNamed.TypeArguments.Length; i++) | ||
| { | ||
| if (!SymbolEqualityComparer.Default.Equals(sourceNamed.TypeArguments[i], targetNamedType.TypeArguments[i])) | ||
| { | ||
| // Type arguments must match exactly for collections (we don't support covariance/contravariance here) | ||
| return false; | ||
| } | ||
| } |
There was a problem hiding this comment.
The comment on line 436 "Check if type arguments are compatible (could be different but compatible types)" is misleading, as the implementation (lines 437-444) requires exact type argument equality, not just compatibility. Consider updating the comment to: "Check if type arguments match exactly (we don't support covariance/contravariance for collections)" to better reflect the actual behavior.


Fix #453