diff --git a/src/Neo.SmartContract.Analyzer/CollectionTypesUsageAnalyzer.cs b/src/Neo.SmartContract.Analyzer/CollectionTypesUsageAnalyzer.cs index 46c2afca8..3a3685d6c 100644 --- a/src/Neo.SmartContract.Analyzer/CollectionTypesUsageAnalyzer.cs +++ b/src/Neo.SmartContract.Analyzer/CollectionTypesUsageAnalyzer.cs @@ -14,6 +14,7 @@ using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Operations; using System.Collections.Immutable; +using System.Collections.Generic; using System.Linq; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -132,10 +133,61 @@ private void ReportIfUnsupportedCollectionType(SyntaxNodeAnalysisContext context private string? GetUnsupportedCollectionType(ITypeSymbol? type) { + return GetUnsupportedCollectionType( + type, + new HashSet(SymbolEqualityComparer.Default)); + } + + private string? GetUnsupportedCollectionType( + ITypeSymbol? type, + HashSet visitedTypes) + { + if (type is null || !visitedTypes.Add(type)) return null; + + if (type is IArrayTypeSymbol arrayType) + return GetUnsupportedCollectionType(arrayType.ElementType, visitedTypes); + + if (type is IPointerTypeSymbol pointerType) + return GetUnsupportedCollectionType(pointerType.PointedAtType, visitedTypes); + + if (type is IFunctionPointerTypeSymbol functionPointerType) + { + var returnType = GetUnsupportedCollectionType( + functionPointerType.Signature.ReturnType, + visitedTypes); + if (returnType is not null) return returnType; + + foreach (var parameter in functionPointerType.Signature.Parameters) + { + var parameterType = GetUnsupportedCollectionType(parameter.Type, visitedTypes); + if (parameterType is not null) return parameterType; + } + + return null; + } + if (type is not INamedTypeSymbol namedType) return null; - var originalType = namedType.OriginalDefinition.ToString() ?? throw new ArgumentNullException("originalType is null"); - return _unsupportedCollectionTypes.Contains(originalType) ? originalType : null; + var originalType = namedType.OriginalDefinition.ToString() ?? + throw new ArgumentNullException(nameof(type)); + if (_unsupportedCollectionTypes.Contains(originalType)) + return originalType; + + if (namedType.ContainingType is not null) + { + var containingType = GetUnsupportedCollectionType( + namedType.ContainingType, + visitedTypes); + if (containingType is not null) return containingType; + } + + foreach (var typeArgument in namedType.TypeArguments) + { + var unsupportedType = GetUnsupportedCollectionType(typeArgument, visitedTypes); + if (unsupportedType is not null) return unsupportedType; + } + + return null; } private static string GetSuggestedType(string originalType) diff --git a/tests/Neo.SmartContract.Analyzer.UnitTests/CollectionTypesUsageAnalyzerUnitTests.cs b/tests/Neo.SmartContract.Analyzer.UnitTests/CollectionTypesUsageAnalyzerUnitTests.cs index 03670cd4e..1960852b1 100644 --- a/tests/Neo.SmartContract.Analyzer.UnitTests/CollectionTypesUsageAnalyzerUnitTests.cs +++ b/tests/Neo.SmartContract.Analyzer.UnitTests/CollectionTypesUsageAnalyzerUnitTests.cs @@ -143,6 +143,42 @@ class TestClass await VerifyCS.VerifyAnalyzerAsync(test, returnDiagnostic, parameterDiagnostic, propertyDiagnostic); } + [TestMethod] + public async Task NestedUnsupportedCollectionTypes_ShouldReportDiagnostics() + { + var test = TestNamespace + """ + + class TestClass + { + public {|#0:System.Tuple>|} GetItems() => null; + public void SetItems({|#1:System.Collections.Generic.Stack[]|} values) { } + } + """; + + var returnDiagnostic = VerifyCS.Diagnostic(CollectionTypesUsageAnalyzer.DiagnosticId) + .WithLocation(0) + .WithArguments("System.Collections.Generic.List", "List"); + var parameterDiagnostic = VerifyCS.Diagnostic(CollectionTypesUsageAnalyzer.DiagnosticId) + .WithLocation(1) + .WithArguments("System.Collections.Generic.Stack", "List"); + + await VerifyCS.VerifyAnalyzerAsync(test, returnDiagnostic, parameterDiagnostic); + } + + [TestMethod] + public async Task NestedSupportedCollectionTypes_ShouldNotReportDiagnostics() + { + var test = TestNamespace + """ + + class TestClass + { + public System.Tuple> GetItems() => null; + } + """; + + await VerifyCS.VerifyAnalyzerAsync(test); + } + [TestMethod] public void CollectionDiagnostic_ShouldNotOfferAutomaticCodeFixes() {