// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Linq; using System.Threading; using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Shared.Extensions; #if CODE_STYLE using OptionSet = Microsoft.CodeAnalysis.Diagnostics.AnalyzerConfigOptions; #else using OptionSet = Microsoft.CodeAnalysis.Options.OptionSet; #endif namespace Microsoft.CodeAnalysis.CSharp.CodeStyle.TypeStyle { internal static class TypeStyleHelper { public static bool IsBuiltInType(ITypeSymbol type) => type?.IsSpecialType() == true; /// /// Analyzes if type information is obvious to the reader by simply looking at the assignment expression. /// /// /// accepts null, to be able to cater to codegen features /// that are about to generate a local declaration and do not have this information to pass in. /// Things (like analyzers) that do have a local declaration already, should pass this in. /// public static bool IsTypeApparentInAssignmentExpression( UseVarPreference stylePreferences, ExpressionSyntax initializerExpression, SemanticModel semanticModel, ITypeSymbol typeInDeclaration, CancellationToken cancellationToken) { // tuple literals if (initializerExpression.IsKind(SyntaxKind.TupleExpression, out TupleExpressionSyntax tuple)) { if (typeInDeclaration == null || !typeInDeclaration.IsTupleType) { return false; } var tupleType = (INamedTypeSymbol)typeInDeclaration; if (tupleType.TupleElements.Length != tuple.Arguments.Count) { return false; } for (int i = 0, n = tuple.Arguments.Count; i < n; i++) { var argument = tuple.Arguments[i]; var tupleElementType = tupleType.TupleElements[i].Type; if (!IsTypeApparentInAssignmentExpression( stylePreferences, argument.Expression, semanticModel, tupleElementType, cancellationToken)) { return false; } } return true; } // default(type) if (initializerExpression.IsKind(SyntaxKind.DefaultExpression)) { return true; } // literals, use var if options allow usage here. if (initializerExpression.IsAnyLiteralExpression()) { return stylePreferences.HasFlag(UseVarPreference.ForBuiltInTypes); } // constructor invocations cases: // = new type(); if (initializerExpression.IsKind(SyntaxKind.ObjectCreationExpression, SyntaxKind.ArrayCreationExpression) && !initializerExpression.IsKind(SyntaxKind.AnonymousObjectCreationExpression)) { return true; } // explicit conversion cases: // (type)expr, expr is type, expr as type if (initializerExpression.IsKind(SyntaxKind.CastExpression) || initializerExpression.IsKind(SyntaxKind.IsExpression) || initializerExpression.IsKind(SyntaxKind.AsExpression)) { return true; } // other Conversion cases: // a. conversion with helpers like: int.Parse methods // b. types that implement IConvertible and then invoking .ToType() // c. System.Convert.ToType() var memberName = GetRightmostInvocationExpression(initializerExpression).GetRightmostName(); if (memberName == null) { return false; } if (!(semanticModel.GetSymbolInfo(memberName, cancellationToken).Symbol is IMethodSymbol methodSymbol)) { return false; } if (memberName.IsRightSideOfDot()) { var containingTypeName = memberName.GetLeftSideOfDot(); return IsPossibleCreationOrConversionMethod(methodSymbol, typeInDeclaration, semanticModel, containingTypeName, cancellationToken); } return false; } private static bool IsPossibleCreationOrConversionMethod(IMethodSymbol methodSymbol, ITypeSymbol typeInDeclaration, SemanticModel semanticModel, ExpressionSyntax containingTypeName, CancellationToken cancellationToken) { if (methodSymbol.ReturnsVoid) { return false; } var containingType = semanticModel.GetTypeInfo(containingTypeName, cancellationToken).Type; return IsPossibleCreationMethod(methodSymbol, typeInDeclaration, containingType) || IsPossibleConversionMethod(methodSymbol); } /// /// Looks for types that have static methods that return the same type as the container. /// e.g: int.Parse, XElement.Load, Tuple.Create etc. /// private static bool IsPossibleCreationMethod(IMethodSymbol methodSymbol, ITypeSymbol typeInDeclaration, ITypeSymbol containingType) { if (!methodSymbol.IsStatic) { return false; } return IsContainerTypeEqualToReturnType(methodSymbol, typeInDeclaration, containingType); } /// /// If we have a method ToXXX and its return type is also XXX, then type name is apparent /// e.g: Convert.ToString. /// private static bool IsPossibleConversionMethod(IMethodSymbol methodSymbol) { var returnType = methodSymbol.ReturnType; var returnTypeName = returnType.IsNullable() ? returnType.GetTypeArguments().First().Name : returnType.Name; return methodSymbol.Name.Equals("To" + returnTypeName, StringComparison.Ordinal); } /// /// If there are type arguments on either side of assignment, we match type names instead of type equality /// to account for inferred generic type arguments. /// e.g: Tuple.Create(0, true) returns Tuple<X,y> which isn't the same as type Tuple. /// otherwise, we match for type equivalence /// private static bool IsContainerTypeEqualToReturnType(IMethodSymbol methodSymbol, ITypeSymbol typeInDeclaration, ITypeSymbol containingType) { var returnType = UnwrapTupleType(methodSymbol.ReturnType); if (UnwrapTupleType(typeInDeclaration)?.GetTypeArguments().Length > 0 || containingType.GetTypeArguments().Length > 0) { return UnwrapTupleType(containingType).Name.Equals(returnType.Name); } else { return UnwrapTupleType(containingType).Equals(returnType); } } private static ITypeSymbol UnwrapTupleType(ITypeSymbol symbol) { if (symbol is null) return null; if (!(symbol is INamedTypeSymbol namedTypeSymbol)) return symbol; return namedTypeSymbol.TupleUnderlyingType ?? symbol; } private static ExpressionSyntax GetRightmostInvocationExpression(ExpressionSyntax node) { if (node is AwaitExpressionSyntax awaitExpression && awaitExpression.Expression != null) { return GetRightmostInvocationExpression(awaitExpression.Expression); } if (node is InvocationExpressionSyntax invocationExpression && invocationExpression.Expression != null) { return GetRightmostInvocationExpression(invocationExpression.Expression); } if (node is ConditionalAccessExpressionSyntax conditional) { return GetRightmostInvocationExpression(conditional.WhenNotNull); } return node; } private static UseVarPreference GetCurrentTypeStylePreferences(OptionSet optionSet) { var stylePreferences = UseVarPreference.None; var styleForIntrinsicTypes = optionSet.GetOption(CSharpCodeStyleOptions.VarForBuiltInTypes); var styleForApparent = optionSet.GetOption(CSharpCodeStyleOptions.VarWhenTypeIsApparent); var styleForElsewhere = optionSet.GetOption(CSharpCodeStyleOptions.VarElsewhere); if (styleForIntrinsicTypes.Value) { stylePreferences |= UseVarPreference.ForBuiltInTypes; } if (styleForApparent.Value) { stylePreferences |= UseVarPreference.WhenTypeIsApparent; } if (styleForElsewhere.Value) { stylePreferences |= UseVarPreference.Elsewhere; } return stylePreferences; } public static bool IsPredefinedType(TypeSyntax type) { return type is PredefinedTypeSyntax predefinedType ? SyntaxFacts.IsPredefinedType(predefinedType.Keyword.Kind()) : false; } } }