in src/Workspaces/CSharp/Portable/Extensions/CastExpressionSyntaxExtensions.cs [299:561]
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)
{
// Simple case: Is this an identity cast to another cast? If so, we're safe to remove it.
if (cast.Expression.WalkDownParentheses().IsKind(SyntaxKind.CastExpression))
{
return true;
}
// 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;
}