private static bool IsRequiredCastForReferenceEqualityComparison(ITypeSymbol outerType, CastExpressionSyntax castExpression, SemanticModel semanticModel, out ExpressionSyntax other)
        {
            if (outerType.SpecialType == SpecialType.System_Object)
            {
                var expression = castExpression.WalkUpParentheses();
                var parentNode = expression.Parent;
                if (parentNode.IsKind(SyntaxKind.EqualsExpression) || parentNode.IsKind(SyntaxKind.NotEqualsExpression))
                {
                    // Reference comparison.
                    var binaryExpression = (BinaryExpressionSyntax)parentNode;
                    other = binaryExpression.Left == expression ?
                        binaryExpression.Right :
                        binaryExpression.Left;

                    // Explicit cast not required if we are comparing with type parameter with a class constraint.
                    var otherType = semanticModel.GetTypeInfo(other).Type;
                    if (otherType != null && otherType.TypeKind != TypeKind.TypeParameter)
                    {
                        return !other.WalkDownParentheses().IsKind(SyntaxKind.CastExpression);
                    }
                }
            }

            other = null;
            return false;
        }
        private static bool IsRequiredCastForReferenceEqualityComparison(ITypeSymbol outerType, CastExpressionSyntax castExpression, SemanticModel semanticModel, out ExpressionSyntax other)
        {
            if (outerType.SpecialType == SpecialType.System_Object)
            {
                var expression = castExpression.WalkUpParentheses();
                var parentNode = expression.Parent;
                if (parentNode.IsKind(SyntaxKind.EqualsExpression) || parentNode.IsKind(SyntaxKind.NotEqualsExpression))
                {
                    // Reference comparison.
                    var binaryExpression = (BinaryExpressionSyntax)parentNode;
                    other = binaryExpression.Left == expression ?
                            binaryExpression.Right :
                            binaryExpression.Left;

                    // Explicit cast not required if we are comparing with type parameter with a class constraint.
                    var otherType = semanticModel.GetTypeInfo(other).Type;
                    if (otherType != null && otherType.TypeKind != TypeKind.TypeParameter)
                    {
                        return(!other.WalkDownParentheses().IsKind(SyntaxKind.CastExpression));
                    }
                }
            }

            other = null;
            return(false);
        }
        private static bool PointerCastDefinitelyCantBeRemoved(CastExpressionSyntax cast)
        {
            if (cast.WalkUpParentheses().IsParentKind(SyntaxKind.PointerIndirectionExpression) &&
                cast.Expression.WalkDownParentheses().IsKind(SyntaxKind.NullLiteralExpression))
            {
                return(true);
            }

            return(false);
        }
示例#4
0
        private static bool EnumCastDefinitelyCantBeRemoved(CastExpressionSyntax cast, ITypeSymbol expressionType)
        {
            if (expressionType != null &&
                expressionType.IsEnumType() &&
                cast.WalkUpParentheses().IsParentKind(SyntaxKind.UnaryMinusExpression, SyntaxKind.UnaryPlusExpression))
            {
                return(true);
            }

            return(false);
        }
        private static bool CastPassedToParamsArrayDefinitelyCantBeRemoved(
            CastExpressionSyntax cast,
            ITypeSymbol castType,
            SemanticModel semanticModel,
            CancellationToken cancellationToken)
        {
            // When a casted value is passed as the single argument to a params parameter,
            // we can only remove the cast if it is implicitly convertible to the parameter's type,
            // but not the parameter's element type. Otherwise, we could end up changing the invocation
            // to pass an array rather than an array with a single element.
            //
            // IOW, given the following method...
            //
            // static void Goo(params object[] x) { }
            //
            // ...we should remove this cast...
            //
            // Goo((object[])null);
            //
            // ...but not this cast...
            //
            // Goo((object)null);
            var parent = cast.WalkUpParentheses().Parent;
            if (parent is ArgumentSyntax argument)
            {
                // If there are any arguments to the right, we can assume that this is not a
                // *single* argument passed to a params parameter.
                if (argument.Parent is BaseArgumentListSyntax argumentList)
                {
                    var argumentIndex = argumentList.Arguments.IndexOf(argument);
                    if (argumentIndex < argumentList.Arguments.Count - 1)
                    {
                        return false;
                    }
                }

                var parameter = argument.DetermineParameter(semanticModel, cancellationToken: cancellationToken);
                return ParameterTypeMatchesParamsElementType(parameter, castType, semanticModel);
            }

            if (parent is AttributeArgumentSyntax attributeArgument)
            {
                if (attributeArgument.Parent is AttributeArgumentListSyntax attributeArgumentList)
                {
                    // We don't check the position of the argument because in attributes it is allowed that 
                    // params parameter are positioned in between if named arguments are used.
                    // The *single* argument check above is also broken: https://github.com/dotnet/roslyn/issues/20742
                    var parameter = attributeArgument.DetermineParameter(semanticModel, cancellationToken: cancellationToken);
                    return ParameterTypeMatchesParamsElementType(parameter, castType, semanticModel);
                }
            }

            return false;
        }
        private static bool CastPassedToParamsArrayDefinitelyCantBeRemoved(
            CastExpressionSyntax cast,
            ITypeSymbol castType,
            SemanticModel semanticModel,
            CancellationToken cancellationToken)
        {
            // Special case: When a null literal is cast and passed as the single argument to a params parameter,
            // we can only remove the cast if it is implicitly convertible to the parameter's type,
            // but not the parameter's element type. Otherwise, we could end up changing the invocation
            // to pass a null array rather than an array with a null single element.
            //
            // IOW, given the following method...
            //
            // static void Foo(params object[] x) { }
            //
            // ...we should remove this cast...
            //
            // Foo((object[])null);
            //
            // ...but not this cast...
            //
            // Foo((object)null);

            if (cast.Expression.WalkDownParentheses().IsKind(SyntaxKind.NullLiteralExpression))
            {
                var argument = cast.WalkUpParentheses().Parent as ArgumentSyntax;
                if (argument != null)
                {
                    // If there are any arguments to the right, we can assume that this is not a
                    // *single* argument passed to a params parameter.
                    var argumentList = argument.Parent as BaseArgumentListSyntax;
                    if (argumentList != null)
                    {
                        var argumentIndex = argumentList.Arguments.IndexOf(argument);
                        if (argumentIndex < argumentList.Arguments.Count - 1)
                        {
                            return(false);
                        }
                    }

                    var parameter = argument.DetermineParameter(semanticModel, cancellationToken: cancellationToken);
                    if (parameter != null && parameter.IsParams)
                    {
                        Debug.Assert(parameter.Type is IArrayTypeSymbol);

                        var parameterType = (IArrayTypeSymbol)parameter.Type;

                        var conversion = semanticModel.Compilation.ClassifyConversion(castType, parameterType);
                        if (conversion.Exists &&
                            conversion.IsImplicit)
                        {
                            return(false);
                        }

                        var conversionElementType = semanticModel.Compilation.ClassifyConversion(castType, parameterType.ElementType);
                        if (conversionElementType.Exists &&
                            conversionElementType.IsImplicit)
                        {
                            return(true);
                        }
                    }
                }
            }

            return(false);
        }
示例#7
0
        public static bool IsUnnecessaryCast(this CastExpressionSyntax cast, SemanticModel semanticModel, CancellationToken cancellationToken)
        {
            var speculationAnalyzer = new SpeculationAnalyzer(cast,
                                                              cast.Expression, semanticModel, cancellationToken,
                                                              skipVerificationForReplacedNode: true, failOnOverloadResolutionFailuresInOriginalCode: true);

            // First, check to see if the node ultimately parenting this cast has any
            // syntax errors. If so, we bail.
            if (speculationAnalyzer.SemanticRootOfOriginalExpression.ContainsDiagnostics)
            {
                return(false);
            }

            var castTypeInfo = semanticModel.GetTypeInfo(cast, cancellationToken);
            var castType     = castTypeInfo.Type;

            // Case:
            // 1 . Console.WriteLine(await (dynamic)task); Any Dynamic Cast will not be removed.
            if (castType == null || castType.Kind == SymbolKind.DynamicType || castType.IsErrorType())
            {
                return(false);
            }

            var expressionTypeInfo = semanticModel.GetTypeInfo(cast.Expression, cancellationToken);
            var expressionType     = expressionTypeInfo.Type;

            if (EnumCastDefinitelyCantBeRemoved(cast, expressionType))
            {
                return(false);
            }

            // We do not remove any cast on
            // 1. Dynamic Expressions
            // 2. If there is any other argument which is dynamic
            // 3. Dynamic Invocation
            // 4. Assignment to dynamic
            if ((expressionType != null &&
                 (expressionType.IsErrorType() ||
                  expressionType.Kind == SymbolKind.DynamicType)) ||
                IsDynamicInvocation(cast, semanticModel, cancellationToken) ||
                IsDynamicAssignment(cast, semanticModel, cancellationToken))
            {
                return(false);
            }

            if (PointerCastDefinitelyCantBeRemoved(cast))
            {
                return(false);
            }

            if (CastPassedToParamsArrayDefinitelyCantBeRemoved(cast, castType, semanticModel, cancellationToken))
            {
                return(false);
            }

            // A casts to object can always be removed from an expression inside of an interpolation, since it'll be converted to object
            // in order to call string.Format(...) anyway.
            if (castType?.SpecialType == SpecialType.System_Object &&
                cast.WalkUpParentheses().IsParentKind(SyntaxKind.Interpolation))
            {
                return(true);
            }

            if (speculationAnalyzer.ReplacementChangesSemantics())
            {
                return(false);
            }

            var expressionToCastType = semanticModel.ClassifyConversion(cast.SpanStart, cast.Expression, castType, isExplicitInSource: true);
            var outerType            = GetOuterCastType(cast, semanticModel, out var parentIsOrAsExpression) ?? castTypeInfo.ConvertedType;

            // Simple case: If the conversion from the inner expression to the cast type is identity,
            // the cast can be removed.
            if (expressionToCastType.IsIdentity)
            {
                // Required explicit cast for reference comparison.
                // Cast removal causes warning CS0252 (Possible unintended reference comparison).
                //      object x = string.Intern("Hi!");
                //      (object)x == "Hi!"
                if (IsRequiredCastForReferenceEqualityComparison(outerType, cast, semanticModel, out var other))
                {
                    var otherToOuterType = semanticModel.ClassifyConversion(other, outerType);
                    if (otherToOuterType.IsImplicit && otherToOuterType.IsReference)
                    {
                        return(false);
                    }
                }

                return(true);
            }
            else if (expressionToCastType.IsExplicit && expressionToCastType.IsReference)
            {
                // Explicit reference conversions can cause an exception or data loss, hence can never be removed.
                return(false);
            }
            else if (expressionToCastType.IsExplicit && expressionToCastType.IsUnboxing)
            {
                // Unboxing conversions can cause a null ref exception, hence can never be removed.
                return(false);
            }
            else if (expressionToCastType.IsExplicit && expressionToCastType.IsNumeric)
            {
                // Don't remove any explicit numeric casts.
                // https://github.com/dotnet/roslyn/issues/2987 tracks improving on this conservative approach.
                return(false);
            }
            else if (expressionToCastType.IsPointer)
            {
                // Don't remove any non-identity pointer conversions.
                // https://github.com/dotnet/roslyn/issues/2987 tracks improving on this conservative approach.
                return(expressionType != null && expressionType.Equals(outerType));
            }

            if (parentIsOrAsExpression)
            {
                // Note: speculationAnalyzer.ReplacementChangesSemantics() ensures that the parenting is or as expression are not broken.
                // Here we just need to ensure that the original cast expression doesn't invoke a user defined operator.
                return(!expressionToCastType.IsUserDefined);
            }

            if (outerType != null)
            {
                var castToOuterType       = semanticModel.ClassifyConversion(cast.SpanStart, cast, outerType);
                var expressionToOuterType = GetSpeculatedExpressionToOuterTypeConversion(speculationAnalyzer.ReplacedExpression, speculationAnalyzer, cancellationToken);

                // CONSIDER: Anonymous function conversions cannot be compared from different semantic models as lambda symbol comparison requires syntax tree equality. Should this be a compiler bug?
                // For now, just revert back to computing expressionToOuterType using the original semantic model.
                if (expressionToOuterType.IsAnonymousFunction)
                {
                    expressionToOuterType = semanticModel.ClassifyConversion(cast.SpanStart, cast.Expression, outerType);
                }

                // If there is an user-defined conversion from the expression to the cast type or the cast
                // to the outer type, we need to make sure that the same user-defined conversion will be
                // called if the cast is removed.
                if (castToOuterType.IsUserDefined || expressionToCastType.IsUserDefined)
                {
                    return(!expressionToOuterType.IsExplicit &&
                           (HaveSameUserDefinedConversion(expressionToCastType, expressionToOuterType) ||
                            HaveSameUserDefinedConversion(castToOuterType, expressionToOuterType)) &&
                           UserDefinedConversionIsAllowed(cast, semanticModel));
                }
                else if (expressionToOuterType.IsUserDefined)
                {
                    return(false);
                }

                if (expressionToCastType.IsExplicit &&
                    expressionToOuterType.IsExplicit)
                {
                    return(false);
                }
                // Required explicit cast for reference comparison.
                // Cast removal causes warning CS0252 (Possible unintended reference comparison).
                //      object x = string.Intern("Hi!");
                //      x == (object)"Hi!"
                if (expressionToCastType.IsImplicit && expressionToCastType.IsReference &&
                    castToOuterType.IsIdentity &&
                    IsRequiredCastForReferenceEqualityComparison(outerType, cast, semanticModel, out var other))
                {
                    return(false);
                }

                // If the conversion from the expression to the cast type is implicit numeric or constant
                // and the conversion from the expression to the outer type is identity, we'll go ahead
                // and remove the cast.
                if (expressionToOuterType.IsIdentity &&
                    expressionToCastType.IsImplicit &&
                    (expressionToCastType.IsNumeric || expressionToCastType.IsConstantExpression))
                {
                    return(true);
                }

                if (!castToOuterType.IsBoxing &&
                    castToOuterType == expressionToOuterType)
                {
                    if (castToOuterType.IsNullable)
                    {
                        // Even though both the nullable conversions (castToOuterType and expressionToOuterType) are equal, we can guarantee no data loss only if there is an
                        // implicit conversion from expression type to cast type and expression type is non-nullable. For example, consider the cast removal "(float?)" for below:

                        // Console.WriteLine((int)(float?)(int?)2147483647); // Prints -2147483648

                        // castToOuterType:         ExplicitNullable
                        // expressionToOuterType:   ExplicitNullable
                        // expressionToCastType:    ImplicitNullable

                        // We should not remove the cast to "float?".
                        // However, cast to "int?" is unnecessary and should be removable.
                        return(expressionToCastType.IsImplicit && !expressionType.IsNullable());
                    }
                    else if (expressionToCastType.IsImplicit && expressionToCastType.IsNumeric && !castToOuterType.IsIdentity)
                    {
                        // Some implicit numeric conversions can cause loss of precision and must not be removed.
                        return(!IsRequiredImplicitNumericConversion(expressionType, castType));
                    }

                    return(true);
                }

                if (castToOuterType.IsIdentity &&
                    !expressionToCastType.IsUnboxing &&
                    expressionToCastType == expressionToOuterType)
                {
                    return(true);
                }

                // Special case: It's possible to have useless casts inside delegate creation expressions.
                // For example: new Func<string, bool>((Predicate<object>)(y => true)).
                if (IsInDelegateCreationExpression(cast, semanticModel))
                {
                    if (expressionToCastType.IsAnonymousFunction && expressionToOuterType.IsAnonymousFunction)
                    {
                        return(!speculationAnalyzer.ReplacementChangesSemanticsOfUnchangedLambda(cast.Expression, speculationAnalyzer.ReplacedExpression));
                    }

                    if (expressionToCastType.IsMethodGroup && expressionToOuterType.IsMethodGroup)
                    {
                        return(true);
                    }
                }

                // Case :
                // 1. IList<object> y = (IList<dynamic>)new List<object>()
                if (expressionToCastType.IsExplicit && castToOuterType.IsExplicit && expressionToOuterType.IsImplicit)
                {
                    // If both expressionToCastType and castToOuterType are numeric, then this is a required cast as one of the conversions leads to loss of precision.
                    // Cast removal can change program behavior.
                    return(!(expressionToCastType.IsNumeric && castToOuterType.IsNumeric));
                }

                // Case :
                // 2. object y = (ValueType)1;
                if (expressionToCastType.IsBoxing && expressionToOuterType.IsBoxing && castToOuterType.IsImplicit)
                {
                    return(true);
                }

                // Case :
                // 3. object y = (NullableValueType)null;
                if ((!castToOuterType.IsBoxing || expressionToCastType.IsNullLiteral) &&
                    castToOuterType.IsImplicit &&
                    expressionToCastType.IsImplicit &&
                    expressionToOuterType.IsImplicit)
                {
                    if (expressionToOuterType.IsAnonymousFunction)
                    {
                        return(expressionToCastType.IsAnonymousFunction &&
                               !speculationAnalyzer.ReplacementChangesSemanticsOfUnchangedLambda(cast.Expression, speculationAnalyzer.ReplacedExpression));
                    }

                    return(true);
                }
            }

            return(false);
        }
        private static bool PointerCastDefinitelyCantBeRemoved(CastExpressionSyntax cast)
        {
            if (cast.WalkUpParentheses().IsParentKind(SyntaxKind.PointerIndirectionExpression) &&
                cast.Expression.WalkDownParentheses().IsKind(SyntaxKind.NullLiteralExpression))
            {
                return true;
            }

            return false;
        }
        private static bool CastPassedToParamsArrayDefinitelyCantBeRemoved(
            CastExpressionSyntax cast,
            ITypeSymbol castType,
            SemanticModel semanticModel,
            CancellationToken cancellationToken)
        {
            // Special case: When a null literal is cast and passed as the single argument to a params parameter,
            // we can only remove the cast if it is implicitly convertible to the parameter's type,
            // but not the parameter's element type. Otherwise, we could end up changing the invocation
            // to pass a null array rather than an array with a null single element.
            //
            // IOW, given the following method...
            //
            // static void Foo(params object[] x) { }
            //
            // ...we should remove this cast...
            //
            // Foo((object[])null);
            //
            // ...but not this cast...
            //
            // Foo((object)null);

            if (cast.Expression.WalkDownParentheses().IsKind(SyntaxKind.NullLiteralExpression))
            {
                var argument = cast.WalkUpParentheses().Parent as ArgumentSyntax;
                if (argument != null)
                {
                    // If there are any arguments to the right, we can assume that this is not a
                    // *single* argument passed to a params parameter.
                    var argumentList = argument.Parent as BaseArgumentListSyntax;
                    if (argumentList != null)
                    {
                        var argumentIndex = argumentList.Arguments.IndexOf(argument);
                        if (argumentIndex < argumentList.Arguments.Count - 1)
                        {
                            return false;
                        }
                    }

                    var parameter = argument.DetermineParameter(semanticModel, cancellationToken: cancellationToken);
                    if (parameter != null && parameter.IsParams)
                    {
                        Debug.Assert(parameter.Type is IArrayTypeSymbol);

                        var parameterType = (IArrayTypeSymbol)parameter.Type;

                        var conversion = semanticModel.Compilation.ClassifyConversion(castType, parameterType);
                        if (conversion.Exists &&
                            conversion.IsImplicit)
                        {
                            return false;
                        }

                        var conversionElementType = semanticModel.Compilation.ClassifyConversion(castType, parameterType.ElementType);
                        if (conversionElementType.Exists &&
                            conversionElementType.IsImplicit)
                        {
                            return true;
                        }
                    }
                }
            }

            return false;
        }