diff --git a/ChangeLog.md b/ChangeLog.md index cf0c68a786..1b69e018e2 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -31,7 +31,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fix [RCS1154](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1154) ([#1105](https://github.com/JosefPihrt/Roslynator/pull/1105)). - Fix [RCS1211](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1211) ([#1095](https://github.com/JosefPihrt/Roslynator/pull/1095)). - Fix [RCS0005](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS0005) ([#1114](https://github.com/JosefPihrt/Roslynator/pull/1114)). -- Fix [RCS1176](https://github.com/JosefPihrt/Roslynator/blob/main/docs/analyzers/RCS1176.md) ([#1122](https://github.com/JosefPihrt/Roslynator/pull/1122)). +- Fix [RCS1176](https://github.com/JosefPihrt/Roslynator/blob/main/docs/analyzers/RCS1176.md) ([#1122](https://github.com/JosefPihrt/Roslynator/pull/1122), [#1140](https://github.com/JosefPihrt/Roslynator/pull/1140)). - Fix [RCS1085](https://github.com/JosefPihrt/Roslynator/blob/main/docs/analyzers/RCS1085.md) ([#1120](https://github.com/josefpihrt/roslynator/pull/1120)). - Fix [RCS1208](https://github.com/JosefPihrt/Roslynator/blob/main/docs/analyzers/RCS1208.md) ([#1119](https://github.com/JosefPihrt/Roslynator/pull/1119)). - [CLI] Fix member full declaration in generated documentation (command `generate-doc`) ([#1130](https://github.com/josefpihrt/roslynator/pull/1130)). diff --git a/src/CSharp/CSharp/CSharpTypeAnalysis.cs b/src/CSharp/CSharp/CSharpTypeAnalysis.cs index 7f421b0ac6..d0dbf165ff 100644 --- a/src/CSharp/CSharp/CSharpTypeAnalysis.cs +++ b/src/CSharp/CSharp/CSharpTypeAnalysis.cs @@ -1,6 +1,7 @@ // Copyright (c) Josef Pihrt and Contributors. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Immutable; using System.Diagnostics; using System.Threading; using Microsoft.CodeAnalysis; @@ -502,7 +503,68 @@ public static bool IsExplicitThatCanBeImplicit( if (typeSymbol.IsKind(SymbolKind.ErrorType, SymbolKind.DynamicType)) return false; + if (declarationExpression.Parent is ArgumentSyntax argument) + return AnalyzeArgument(argument, semanticModel, cancellationToken); + return true; + + static bool AnalyzeArgument(ArgumentSyntax argument, SemanticModel semanticModel, CancellationToken cancellationToken) + { + IParameterSymbol parameterSymbol = semanticModel.DetermineParameter(argument, cancellationToken: cancellationToken); + + if (parameterSymbol is null) + return false; + + if (SymbolEqualityComparer.Default.Equals(parameterSymbol.Type, parameterSymbol.OriginalDefinition.Type)) + return true; + + if (parameterSymbol.ContainingSymbol is IMethodSymbol methodSymbol) + { + ImmutableArray typeParameterList = methodSymbol.TypeArguments; + + ITypeParameterSymbol typeParameterSymbol = null; + for (int i = 0; i < typeParameterList.Length; i++) + { + if (SymbolEqualityComparer.Default.Equals(typeParameterList[i], parameterSymbol.Type)) + { + typeParameterSymbol = methodSymbol.TypeParameters[i]; + break; + } + } + + if (typeParameterSymbol is not null + && argument.Parent is ArgumentListSyntax argumentList + && argumentList.Parent is InvocationExpressionSyntax invocation) + { + switch (invocation.Expression.Kind()) + { + case SyntaxKind.IdentifierName: + return false; + + case SyntaxKind.GenericName: + return true; + + case SyntaxKind.SimpleMemberAccessExpression: + var memberAccess = (MemberAccessExpressionSyntax)invocation.Expression; + + if (memberAccess.Name.IsKind(SyntaxKind.IdentifierName)) + return false; + + if (memberAccess.Name.IsKind(SyntaxKind.GenericName)) + return true; + + Debug.Fail(memberAccess.Name.Kind().ToString()); + break; + + default: + Debug.Fail(invocation.Expression.Kind().ToString()); + break; + } + } + } + + return false; + } } public static bool IsExplicitThatCanBeImplicit( diff --git a/src/Tests/Analyzers.Tests/RCS1176UseVarInsteadOfExplicitTypeWhenTypeIsNotObviousTests.cs b/src/Tests/Analyzers.Tests/RCS1176UseVarInsteadOfExplicitTypeWhenTypeIsNotObviousTests.cs index cb9a58ecd5..ec2b58db52 100644 --- a/src/Tests/Analyzers.Tests/RCS1176UseVarInsteadOfExplicitTypeWhenTypeIsNotObviousTests.cs +++ b/src/Tests/Analyzers.Tests/RCS1176UseVarInsteadOfExplicitTypeWhenTypeIsNotObviousTests.cs @@ -116,6 +116,46 @@ void M() "); } + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UseVarInsteadOfExplicitTypeWhenTypeIsNotObvious)] + public async Task Test_TryParse_GenericType() + { + await VerifyDiagnosticAndFixAsync(@" +using System; +#nullable enable + +class C +{ + void M() + { + bool TryParse(string? s, out T t) + { + t = default!; + return false; + } + + TryParse(""wasted"", out [|IntPtr|] i); + } +} +", @" +using System; +#nullable enable + +class C +{ + void M() + { + bool TryParse(string? s, out T t) + { + t = default!; + return false; + } + + TryParse(""wasted"", out var i); + } +} +"); + } + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UseVarInsteadOfExplicitTypeWhenTypeIsNotObvious)] public async Task TestNoDiagnostic_ForEach_DeclarationExpression() { @@ -237,6 +277,53 @@ void M() Type? nullableType = type; } } +"); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UseVarInsteadOfExplicitTypeWhenTypeIsNotObvious)] + public async Task TestNoDiagnostic_InferredType_Invocation_IdentifierName() + { + await VerifyNoDiagnosticAsync(@" +using System; +#nullable enable + +class C +{ + void M() + { + bool TryParse(string? s, out T t) + { + t = default!; + return false; + } + + TryParse(""wasted"", out IntPtr i); + } +} +"); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UseVarInsteadOfExplicitTypeWhenTypeIsNotObvious)] + public async Task TestNoDiagnostic_InferredType_Invocation_MemberAccessExpression() + { + await VerifyNoDiagnosticAsync(@" +using System; +#nullable enable + +static class C +{ + static void M() + { + + C.TryParse(""wasted"", out IntPtr i); + } + + static bool TryParse(string? s, out T t) + { + t = default!; + return false; + } +} "); } }