Skip to content

Commit

Permalink
Implement target-typed conditional expressions. (#40604)
Browse files Browse the repository at this point in the history
* Implement target-typed conditional expressions.
* Skip broken test (Relates to #41456)
  • Loading branch information
Neal Gafter authored Apr 22, 2020
1 parent ff3d2a7 commit 5fde1bc
Show file tree
Hide file tree
Showing 55 changed files with 991 additions and 306 deletions.
28 changes: 19 additions & 9 deletions src/Compilers/CSharp/Portable/Binder/Binder.ValueChecks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2638,19 +2638,29 @@ internal static bool CheckValEscape(SyntaxNode node, BoundExpression expr, uint
}
return true;

case BoundKind.UnconvertedConditionalOperator:
{
var conditional = (BoundUnconvertedConditionalOperator)expr;
return
CheckValEscape(conditional.Consequence.Syntax, conditional.Consequence, escapeFrom, escapeTo, checkingReceiver: false, diagnostics: diagnostics) &&
CheckValEscape(conditional.Alternative.Syntax, conditional.Alternative, escapeFrom, escapeTo, checkingReceiver: false, diagnostics: diagnostics);
}

case BoundKind.ConditionalOperator:
var conditional = (BoundConditionalOperator)expr;
{
var conditional = (BoundConditionalOperator)expr;

var consValid = CheckValEscape(conditional.Consequence.Syntax, conditional.Consequence, escapeFrom, escapeTo, checkingReceiver: false, diagnostics: diagnostics);
var consValid = CheckValEscape(conditional.Consequence.Syntax, conditional.Consequence, escapeFrom, escapeTo, checkingReceiver: false, diagnostics: diagnostics);

if (!consValid || conditional.IsRef)
{
// ref conditional defers to one operand.
// the other one is the same or we will be reporting errors anyways.
return consValid;
}
if (!consValid || conditional.IsRef)
{
// ref conditional defers to one operand.
// the other one is the same or we will be reporting errors anyways.
return consValid;
}

return CheckValEscape(conditional.Alternative.Syntax, conditional.Alternative, escapeFrom, escapeTo, checkingReceiver: false, diagnostics: diagnostics);
return CheckValEscape(conditional.Alternative.Syntax, conditional.Alternative, escapeFrom, escapeTo, checkingReceiver: false, diagnostics: diagnostics);
}

case BoundKind.NullCoalescingOperator:
var coalescingOp = (BoundNullCoalescingOperator)expr;
Expand Down
49 changes: 48 additions & 1 deletion src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,11 @@ protected BoundExpression CreateConversion(
return ConvertSwitchExpression((BoundUnconvertedSwitchExpression)source, destination, conversionIfTargetTyped: conversion, diagnostics);
}

if (conversion.Kind == ConversionKind.ConditionalExpression)
{
return ConvertConditionalExpression((BoundUnconvertedConditionalOperator)source, destination, conversionIfTargetTyped: conversion, diagnostics);
}

if (source.Kind == BoundKind.UnconvertedSwitchExpression)
{
TypeSymbol? type = source.Type;
Expand All @@ -130,6 +135,23 @@ protected BoundExpression CreateConversion(
}
}

if (source.Kind == BoundKind.UnconvertedConditionalOperator)
{
TypeSymbol? type = source.Type;
if (type is null)
{
Debug.Assert(!conversion.Exists);
type = CreateErrorType();
hasErrors = true;
}

source = ConvertConditionalExpression((BoundUnconvertedConditionalOperator)source, type, conversionIfTargetTyped: null, diagnostics, hasErrors);
if (destination.Equals(type, TypeCompareKind.ConsiderEverything) && wasCompilerGenerated)
{
return source;
}
}

if (conversion.IsUserDefined)
{
// User-defined conversions are likely to be represented as multiple
Expand Down Expand Up @@ -157,12 +179,37 @@ protected BoundExpression CreateConversion(
{ WasCompilerGenerated = wasCompilerGenerated };
}

/// <summary>
/// Rewrite the subexpressions in a conditional expression to convert the whole thing to the destination type.
/// </summary>
private BoundExpression ConvertConditionalExpression(BoundUnconvertedConditionalOperator source, TypeSymbol destination, Conversion? conversionIfTargetTyped, DiagnosticBag diagnostics, bool hasErrors = false)
{
bool targetTyped = conversionIfTargetTyped is { };
Debug.Assert(targetTyped || destination.IsErrorType() || destination.Equals(source.Type, TypeCompareKind.ConsiderEverything));
ImmutableArray<Conversion> underlyingConversions = conversionIfTargetTyped.GetValueOrDefault().UnderlyingConversions;
var condition = source.Condition;
hasErrors |= source.HasErrors || destination.IsErrorType();

var trueExpr =
targetTyped
? CreateConversion(source.Consequence.Syntax, source.Consequence, underlyingConversions[0], isCast: false, conversionGroupOpt: null, destination, diagnostics)
: GenerateConversionForAssignment(destination, source.Consequence, diagnostics);
var falseExpr =
targetTyped
? CreateConversion(source.Alternative.Syntax, source.Alternative, underlyingConversions[1], isCast: false, conversionGroupOpt: null, destination, diagnostics)
: GenerateConversionForAssignment(destination, source.Alternative, diagnostics);
var constantValue = FoldConditionalOperator(condition, trueExpr, falseExpr);
hasErrors |= constantValue?.IsBad == true;
return new BoundConditionalOperator(source.Syntax, isRef: false, condition, trueExpr, falseExpr, constantValue, source.Type, wasTargetTyped: targetTyped, destination, hasErrors)
.WithSuppression(source.IsSuppressed);
}

/// <summary>
/// Rewrite the expressions in the switch expression arms to add a conversion to the destination type.
/// </summary>
private BoundExpression ConvertSwitchExpression(BoundUnconvertedSwitchExpression source, TypeSymbol destination, Conversion? conversionIfTargetTyped, DiagnosticBag diagnostics, bool hasErrors = false)
{
bool targetTyped = conversionIfTargetTyped != null;
bool targetTyped = conversionIfTargetTyped is { };
Debug.Assert(targetTyped || destination.IsErrorType() || destination.Equals(source.Type, TypeCompareKind.ConsiderEverything));
ImmutableArray<Conversion> underlyingConversions = conversionIfTargetTyped.GetValueOrDefault().UnderlyingConversions;
var builder = ArrayBuilder<BoundSwitchExpressionArm>.GetInstance(source.SwitchArms.Length);
Expand Down
44 changes: 38 additions & 6 deletions src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ private BoundBadExpression BadExpression(SyntaxNode syntax, LookupResultKind res
return new BoundBadExpression(syntax,
resultKind,
symbols,
ImmutableArray.Create(childNode),
ImmutableArray.Create(BindToTypeForErrorRecovery(childNode)),
CreateErrorType());
}

Expand Down Expand Up @@ -258,6 +258,9 @@ internal BoundExpression BindToTypeForErrorRecovery(BoundExpression expression,
/// </summary>
internal BoundExpression BindToNaturalType(BoundExpression expression, DiagnosticBag diagnostics, bool reportDefaultMissingType = true)
{
if (!expression.NeedsToBeConverted())
return expression;

BoundExpression result;
switch (expression)
{
Expand All @@ -275,6 +278,32 @@ internal BoundExpression BindToNaturalType(BoundExpression expression, Diagnosti
result = ConvertSwitchExpression(expr, commonType, conversionIfTargetTyped: null, diagnostics, hasErrors);
}
break;
case BoundUnconvertedConditionalOperator op:
{
TypeSymbol type = op.Type;
bool hasErrors = op.HasErrors;
if (type is null)
{
Debug.Assert(op.NoCommonTypeError != 0);
type = CreateErrorType();
hasErrors = true;
object trueArg = op.Consequence.Display;
object falseArg = op.Alternative.Display;
if (op.NoCommonTypeError == ErrorCode.ERR_InvalidQM && trueArg is Symbol trueSymbol && falseArg is Symbol falseSymbol)
{
// ERR_InvalidQM is an error that there is no conversion between the two types. They might be the same
// type name from different assemblies, so we disambiguate the display.
SymbolDistinguisher distinguisher = new SymbolDistinguisher(this.Compilation, trueSymbol, falseSymbol);
trueArg = distinguisher.First;
falseArg = distinguisher.Second;
}

diagnostics.Add(op.NoCommonTypeError, op.Syntax.Location, trueArg, falseArg);
}

result = ConvertConditionalExpression(op, type, conversionIfTargetTyped: null, diagnostics, hasErrors);
}
break;
case BoundTupleLiteral sourceTuple:
{
var boundArgs = ArrayBuilder<BoundExpression>.GetInstance(sourceTuple.Arguments.Length);
Expand Down Expand Up @@ -310,10 +339,12 @@ internal BoundExpression BindToNaturalType(BoundExpression expression, Diagnosti
}
break;
case BoundStackAllocArrayCreation { Type: null } boundStackAlloc:
// This is a context in which the stackalloc could be either a pointer
// or a span. For backward compatibility we treat it as a pointer.
var type = new PointerTypeSymbol(TypeWithAnnotations.Create(boundStackAlloc.ElementType));
result = GenerateConversionForAssignment(type, boundStackAlloc, diagnostics);
{
// This is a context in which the stackalloc could be either a pointer
// or a span. For backward compatibility we treat it as a pointer.
var type = new PointerTypeSymbol(TypeWithAnnotations.Create(boundStackAlloc.ElementType));
result = GenerateConversionForAssignment(type, boundStackAlloc, diagnostics);
}
break;
default:
result = expression;
Expand Down Expand Up @@ -2307,6 +2338,7 @@ private void GenerateExplicitConversionErrors(
Error(diagnostics, ErrorCode.ERR_StackAllocConversionNotPossible, syntax, stackAllocExpression.ElementType, targetType);
return;
}
case BoundKind.UnconvertedConditionalOperator when operand.Type is null:
case BoundKind.UnconvertedSwitchExpression when operand.Type is null:
{
GenerateImplicitConversionError(diagnostics, operand.Syntax, conversion, operand, targetType);
Expand Down Expand Up @@ -4039,7 +4071,7 @@ private BoundExpression BindDelegateCreationExpression(ObjectCreationExpressionS
hasErrors = true;
}

BoundExpression argument = BindToNaturalType(analyzedArguments.Arguments.Count >= 1 ? analyzedArguments.Arguments[0] : null, diagnostics);
BoundExpression argument = analyzedArguments.Arguments.Count >= 1 ? BindToNaturalType(analyzedArguments.Arguments[0], diagnostics) : null;

if (hasErrors)
{
Expand Down
6 changes: 5 additions & 1 deletion src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -963,7 +963,10 @@ private BoundCall BindInvocationExpressionContinued(
_ = BindToNaturalType(argument, diagnostics);
break;
case BoundUnconvertedSwitchExpression { Type: { } naturalType } switchExpr:
_ = ConvertSwitchExpression(switchExpr, naturalType ?? CreateErrorType(), conversionIfTargetTyped: null, diagnostics);
_ = ConvertSwitchExpression(switchExpr, naturalType, conversionIfTargetTyped: null, diagnostics);
break;
case BoundUnconvertedConditionalOperator { Type: { } naturalType } conditionalExpr:
_ = ConvertConditionalExpression(conditionalExpr, naturalType, conversionIfTargetTyped: null, diagnostics);
break;
}
}
Expand Down Expand Up @@ -1536,6 +1539,7 @@ private BoundExpression BindNameofOperatorInternal(InvocationExpressionSyntax no
// We relax the instance-vs-static requirement for top-level member access expressions by creating a NameofBinder binder.
var nameofBinder = new NameofBinder(argument, this);
var boundArgument = nameofBinder.BindExpression(argument, diagnostics);
boundArgument = boundArgument.HasAnyErrors ? BindToTypeForErrorRecovery(boundArgument) : BindToNaturalType(boundArgument, diagnostics);
if (!boundArgument.HasAnyErrors && CheckSyntaxForNameofArgument(argument, out name, diagnostics) && boundArgument.Kind == BoundKind.MethodGroup)
{
var methodGroup = (BoundMethodGroup)boundArgument;
Expand Down
Loading

0 comments on commit 5fde1bc

Please sign in to comment.