Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2251,6 +2251,48 @@ void M(string s)
}
""");

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/77084")]
public async Task TestRuntimeTypeConversion_Assignment3()
{
await VerifyCS.VerifyCodeFixAsync(
"""
class Program
{
void M(string s)
{
object result;

[|switch|] (s)
{
case "a":
result = 1234;
break;
case "b":
result = 3.14;
break;
default:
result = true;
break;
}
}
}
""",
"""
class Program
{
void M(string s)
{
object result = s switch
{
"a" => 1234,
"b" => 3.14,
_ => true,
};
}
}
""");
}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/58636")]
public Task TestRuntimeTypeConversion_Return1()
=> VerifyCS.VerifyCodeFixAsync(
Expand Down Expand Up @@ -2324,6 +2366,41 @@ object M(string s)
}
""");

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/77084")]
public async Task TestRuntimeTypeConversion_Return3()
{
await VerifyCS.VerifyCodeFixAsync(
"""
class Program
{
object M(string s)
{
[|switch|] (s)
{
case "a":
return true;

default:
return false;
}
}
}
""",
"""
class Program
{
object M(string s)
{
return s switch
{
"a" => true,
_ => false,
};
}
}
""");
}

[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/61278")]
public Task TestLeadingTrivia1()
=> new VerifyCS.Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
Expand Down Expand Up @@ -578,6 +579,16 @@ private static bool IsConversionCastSafeToRemove(
return true;
}

// Similarly, we want to support this for:
//
// int? a = b switch { true => (int?)0, false => 1 }
if (IsSwitchExpressionCaseCastSafeToRemove(
castNode, originalSemanticModel,
rewrittenExpression, rewrittenSemanticModel, cancellationToken))
{
return true;
}

// Widening a value before bitwise negation produces the same value as bitwise negation
// followed by the same widening. For example:
//
Expand Down Expand Up @@ -825,17 +836,75 @@ private static bool IsConditionalCastSafeToRemove(
ExpressionSyntax castNode, SemanticModel originalSemanticModel,
ExpressionSyntax rewrittenExpression, SemanticModel rewrittenSemanticModel, CancellationToken cancellationToken)
{
if (castNode is not CastExpressionSyntax castExpression)
return false;
// Defer to common helper to determine if the cast can be removed. This unified processing of `x ? y : z` and
// `x switch { .. => y, .. => z, .. => w, ... }` expressions.
return IsSwitchOrConditionalCastSafeToRemove(
castNode,
originalSemanticModel,
rewrittenExpression,
rewrittenSemanticModel,
static parentExpression => parentExpression.Parent is ConditionalExpressionSyntax conditionalExpression && conditionalExpression.Condition != parentExpression
? conditionalExpression
: null,
static conditionalExpression => [conditionalExpression.WhenTrue, conditionalExpression.WhenFalse],
static (conditionalExpression, armExpression) =>
{
Contract.ThrowIfFalse(conditionalExpression.WhenTrue == armExpression || conditionalExpression.WhenFalse == armExpression);
return armExpression == conditionalExpression.WhenTrue
? conditionalExpression.WhenFalse
: conditionalExpression.WhenTrue;
},
cancellationToken);
}

private static bool IsSwitchExpressionCaseCastSafeToRemove(
ExpressionSyntax castNode, SemanticModel originalSemanticModel,
ExpressionSyntax rewrittenExpression, SemanticModel rewrittenSemanticModel, CancellationToken cancellationToken)
{
// Defer to common helper to determine if the cast can be removed. This unified processing of `x ? y : z` and
// `x switch { .. => y, .. => z, .. => w, ... }` expressions.
return IsSwitchOrConditionalCastSafeToRemove(
castNode,
originalSemanticModel,
rewrittenExpression,
rewrittenSemanticModel,
static parentExpression => parentExpression.Parent is SwitchExpressionArmSyntax { Parent: SwitchExpressionSyntax switchExpression }
? switchExpression
: null,
static switchExpression => switchExpression.Arms.SelectAsArray(a => a.Expression),
static (switchExpression, armExpression) =>
{
if (switchExpression.Arms.Count < 2)
return null;

var arm = switchExpression.Arms.Single(a => a.Expression == armExpression);
var armIndex = switchExpression.Arms.IndexOf(arm);
return armIndex == 0
? switchExpression.Arms[1].Expression
: switchExpression.Arms[armIndex - 1].Expression;
},
cancellationToken);
}

var parent = castExpression.WalkUpParentheses();
if (parent.Parent is not ConditionalExpressionSyntax originalConditionalExpression)
private static bool IsSwitchOrConditionalCastSafeToRemove<TConditionalOrSwitchExpression>(
ExpressionSyntax castNode,
SemanticModel originalSemanticModel,
ExpressionSyntax rewrittenExpression,
SemanticModel rewrittenSemanticModel,
Func<ExpressionSyntax, TConditionalOrSwitchExpression?> getConditionalOrSwitchExpression,
Func<TConditionalOrSwitchExpression, ImmutableArray<ExpressionSyntax>> getArmExpressions,
Func<TConditionalOrSwitchExpression, ExpressionSyntax, ExpressionSyntax?> getAlternativeArm,
CancellationToken cancellationToken)
where TConditionalOrSwitchExpression : ExpressionSyntax
{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all the logic in here is what we had in IsConditionalCastSafeToRemove, but made generic so it can operate on a a?b:c expr vs a a switch { ... } expr, with helper functions to deal with conditional exprs only having two arms, which switches can have many.

if (castNode is not CastExpressionSyntax castExpression)
return false;

// if we were parented by a conditional before, we must be parented by a conditional afterwards.
var rewrittenConditionalExpression = (ConditionalExpressionSyntax)rewrittenExpression.WalkUpParentheses().GetRequiredParent();
var parentExpression = castExpression.WalkUpParentheses();

if (parent != originalConditionalExpression.WhenFalse && parent != originalConditionalExpression.WhenTrue)
var originalConditionalOrSwitchExpression = getConditionalOrSwitchExpression(parentExpression);
var rewrittenConditionalOrSwitchExpression = getConditionalOrSwitchExpression(rewrittenExpression.WalkUpParentheses());
if (originalConditionalOrSwitchExpression is null || rewrittenConditionalOrSwitchExpression is null)
return false;

if (originalSemanticModel.GetOperation(castExpression, cancellationToken) is not IConversionOperation conversionOperation)
Expand All @@ -856,16 +925,16 @@ bool IsConditionalCastSafeToRemoveDueToConversionOfEntireConditionalExpression()
{
// if we have `a ? (int?)b : default` then we can't remove the nullable cast as it changes the
// meaning of `default`.
if (originalConditionalExpression.WhenTrue.WalkDownParentheses().IsKind(SyntaxKind.DefaultLiteralExpression) ||
originalConditionalExpression.WhenFalse.WalkDownParentheses().IsKind(SyntaxKind.DefaultLiteralExpression))
foreach (var armExpression in getArmExpressions(originalConditionalOrSwitchExpression))
{
return false;
if (armExpression.WalkDownParentheses().IsKind(SyntaxKind.DefaultLiteralExpression))
return false;
}
}

var originalCastExpressionTypeInfo = originalSemanticModel.GetTypeInfo(castExpression, cancellationToken);
var originalConditionalTypeInfo = originalSemanticModel.GetTypeInfo(originalConditionalExpression, cancellationToken);
var rewrittenConditionalTypeInfo = rewrittenSemanticModel.GetTypeInfo(rewrittenConditionalExpression, cancellationToken);
var originalConditionalTypeInfo = originalSemanticModel.GetTypeInfo(originalConditionalOrSwitchExpression, cancellationToken);
var rewrittenConditionalTypeInfo = rewrittenSemanticModel.GetTypeInfo(rewrittenConditionalOrSwitchExpression, cancellationToken);

if (IsNullOrErrorType(originalCastExpressionTypeInfo) ||
IsNullOrErrorType(originalConditionalTypeInfo) ||
Expand All @@ -886,13 +955,14 @@ bool IsConditionalCastSafeToRemoveDueToConversionOfEntireConditionalExpression()
if (IsNullOrErrorType(castType))
return false;

if (rewrittenSemanticModel.GetOperation(rewrittenConditionalExpression, cancellationToken) is not IConditionalOperation rewrittenConditionalOperation)
var rewrittenOperation = rewrittenSemanticModel.GetOperation(rewrittenConditionalOrSwitchExpression, cancellationToken);
if (rewrittenOperation is not IConditionalOperation and not ISwitchExpressionOperation)
return false;

if (castType.Equals(rewrittenConditionalOperation.Type, SymbolEqualityComparer.IncludeNullability))
if (castType.Equals(rewrittenOperation.Type, SymbolEqualityComparer.IncludeNullability))
return true;

if (rewrittenConditionalOperation.Parent is IConversionOperation conditionalParentConversion &&
if (rewrittenOperation.Parent is IConversionOperation conditionalParentConversion &&
conditionalParentConversion.GetConversion().IsImplicit &&
castType.Equals(conditionalParentConversion.Type, SymbolEqualityComparer.IncludeNullability))
{
Expand All @@ -911,7 +981,10 @@ bool IsConditionalCastSafeToRemoveDueToConversionToOtherBranch()
if (castExpression.Expression.WalkDownParentheses().IsKind(SyntaxKind.DefaultLiteralExpression))
return false;

var otherSide = parent == originalConditionalExpression.WhenFalse ? originalConditionalExpression.WhenTrue : originalConditionalExpression.WhenFalse;
var otherSide = getAlternativeArm(originalConditionalOrSwitchExpression, parentExpression);
if (otherSide is null)
return false;

var otherSideType = originalSemanticModel.GetTypeInfo(otherSide, cancellationToken).Type;
var thisSideRewrittenType = rewrittenSemanticModel.GetTypeInfo(rewrittenExpression, cancellationToken).Type;

Expand All @@ -925,11 +998,11 @@ bool IsConditionalCastSafeToRemoveDueToConversionToOtherBranch()
// Now check that with the (T) cast removed, that the outer `x ? y : z` is still
// immediately implicitly converted to a 'T'. If so, we can remove this inner (T) cast.

var rewrittenConditionalConvertedType = rewrittenSemanticModel.GetTypeInfo(rewrittenConditionalExpression, cancellationToken).ConvertedType;
var rewrittenConditionalConvertedType = rewrittenSemanticModel.GetTypeInfo(rewrittenConditionalOrSwitchExpression, cancellationToken).ConvertedType;
if (rewrittenConditionalConvertedType is null)
return false;

var outerConversion = rewrittenSemanticModel.GetConversion(rewrittenConditionalExpression, cancellationToken);
var outerConversion = rewrittenSemanticModel.GetConversion(rewrittenConditionalOrSwitchExpression, cancellationToken);
if (!outerConversion.IsImplicit)
return false;

Expand Down
Loading