Skip to content

Commit

Permalink
Implement target-typed-new feature (#39658)
Browse files Browse the repository at this point in the history
* Rebase target-typed-new branch on top of master

* Fix formatting

* Move method

* Adjust baseline

* Revert formatting

* Fix switch label binding

* Revert code

* Clarify diagnostics

* Update resources

* Add comment

* Revert code

* Explicit type parsing mode to avoid superfluous diagnostics

* Bind to natrual type so the node cannot sneak into a later pass

* Clarify comment on binary operator resolution

* Add suggested tests

* Update compiler test plan

* Fix tuple equality

* Fix typo

* Test conditional access

* Renamings

* Fixup merge

* Fixup tests

* Update resources

* Regenerate compiler code

* Fixup merge

* Revert code

* Capture use-site diagnostics

* Tweaks

* Fix formatting

* Address PR feedback on tests

* Undo accidental insertion

* Fix build

* Use Display string for every argument expression

* Update baseline

* Update resources

* Permit throw new() usage

* Add wasTargetTyped flag for object creation node

* PR feedback

* Fix merge

* Revert nullable enable

* Argument list is not optional in new()
  • Loading branch information
alrz authored Mar 6, 2020
1 parent 7804f3d commit a501031
Show file tree
Hide file tree
Showing 63 changed files with 7,803 additions and 602 deletions.
1 change: 1 addition & 0 deletions docs/contributing/Compiler Test Plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ a[e]
x++
x--
new X()
new()
typeof(T)
default(T)
default
Expand Down
66 changes: 66 additions & 0 deletions src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,11 @@ protected BoundExpression CreateConversion(
}
}

if (conversion.IsObjectCreation)
{
return ConvertObjectCreationExpression(syntax, (BoundUnconvertedObjectCreationExpression)source, isCast, destination, diagnostics);
}

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

private BoundExpression ConvertObjectCreationExpression(SyntaxNode syntax, BoundUnconvertedObjectCreationExpression node, bool isCast, TypeSymbol destination, DiagnosticBag diagnostics)
{
var arguments = AnalyzedArguments.GetInstance(node.Arguments, node.ArgumentRefKindsOpt, node.ArgumentNamesOpt);
BoundExpression expr = BindObjectCreationExpression(node, destination.StrippedType(), arguments, diagnostics);
if (destination.IsNullableType())
{
// We manually create an ImplicitNullable conversion
// if the destination is nullable, in which case we
// target the underlying type e.g. `S? x = new();`
// is actually identical to `S? x = new S();`.
HashSet<DiagnosticInfo>? useSiteDiagnostics = null;
var conversion = Conversions.ClassifyStandardConversion(null, expr.Type, destination, ref useSiteDiagnostics);
expr = new BoundConversion(
node.Syntax,
operand: expr,
conversion: conversion,
@checked: false,
explicitCastInCode: isCast,
conversionGroupOpt: new ConversionGroup(conversion),
constantValueOpt: expr.ConstantValue,
type: destination);

diagnostics.Add(syntax, useSiteDiagnostics);
}
arguments.Free();
return expr;
}

private BoundExpression BindObjectCreationExpression(BoundUnconvertedObjectCreationExpression node, TypeSymbol type, AnalyzedArguments arguments, DiagnosticBag diagnostics)
{
var syntax = node.Syntax;
switch (type.TypeKind)
{
case TypeKind.Struct:
case TypeKind.Class when !type.IsAnonymousType: // We don't want to enable object creation with unspeakable types
return BindClassCreationExpression(syntax, type.Name, typeNode: syntax, (NamedTypeSymbol)type, arguments, diagnostics, node.InitializerOpt, wasTargetTyped: true);
case TypeKind.TypeParameter:
return BindTypeParameterCreationExpression(syntax, (TypeParameterSymbol)type, arguments, node.InitializerOpt, typeSyntax: syntax, diagnostics);
case TypeKind.Delegate:
return BindDelegateCreationExpression(syntax, (NamedTypeSymbol)type, arguments, node.InitializerOpt, diagnostics);
case TypeKind.Array:
case TypeKind.Enum:
case TypeKind.Class:
Error(diagnostics, ErrorCode.ERR_TypelessNewIllegalTargetType, syntax, type);
goto case TypeKind.Error;
case TypeKind.Interface:
Error(diagnostics, ErrorCode.ERR_NoNewAbstract, syntax, type);
goto case TypeKind.Error;
case TypeKind.Pointer:
Error(diagnostics, ErrorCode.ERR_UnsafeTypeInObjectCreation, syntax, type);
goto case TypeKind.Error;
case TypeKind.Dynamic:
Error(diagnostics, ErrorCode.ERR_NoConstructors, syntax, type);
goto case TypeKind.Error;
case TypeKind.Error:
return MakeBadExpressionForObjectCreation(syntax, type, arguments, node.InitializerOpt, typeSyntax: syntax, diagnostics);
case var v:
throw ExceptionUtilities.UnexpectedValue(v);
}
}

/// <summary>
/// Rewrite the expressions in the switch expression arms to add a conversion to the destination type.
/// </summary>
Expand Down
384 changes: 209 additions & 175 deletions src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -958,6 +958,7 @@ private BoundCall BindInvocationExpressionContinued(
var boundWithErrors = unboundLambda.BindForErrorRecovery();
diagnostics.AddRange(boundWithErrors.Diagnostics);
break;
case BoundUnconvertedObjectCreationExpression _:
case BoundTupleLiteral _:
// Tuple literals can contain unbound lambdas or switch expressions.
_ = BindToNaturalType(argument, diagnostics);
Expand Down Expand Up @@ -1550,6 +1551,7 @@ private BoundExpression BindNameofOperatorInternal(InvocationExpressionSyntax no
}
}

boundArgument = BindToNaturalType(boundArgument, diagnostics, reportNoTargetType: false);
return new BoundNameOfOperator(node, boundArgument, ConstantValue.Create(name), Compilation.GetSpecialType(SpecialType.System_String));
}

Expand Down
69 changes: 43 additions & 26 deletions src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs
Original file line number Diff line number Diff line change
Expand Up @@ -589,8 +589,8 @@ private BoundExpression BindSimpleBinaryOperator(BinaryExpressionSyntax node, Di
{
// If we found an operator, we'll have given the `default` literal a type.
// Otherwise, we'll have reported the problem in ReportBinaryOperatorError.
resultLeft = BindToNaturalType(resultLeft, diagnostics, reportDefaultMissingType: false);
resultRight = BindToNaturalType(resultRight, diagnostics, reportDefaultMissingType: false);
resultLeft = BindToNaturalType(resultLeft, diagnostics, reportNoTargetType: false);
resultRight = BindToNaturalType(resultRight, diagnostics, reportNoTargetType: false);
}

hasErrors = hasErrors || resultConstant != null && resultConstant.IsBad;
Expand Down Expand Up @@ -655,8 +655,8 @@ private bool BindSimpleBinaryOperatorParts(BinaryExpressionSyntax node, Diagnost
{
resultSignature = signature;
HashSet<DiagnosticInfo> useSiteDiagnostics = null;
bool leftDefault = left.IsLiteralDefault();
bool rightDefault = right.IsLiteralDefault();
bool leftDefault = left.IsLiteralDefaultOrTypelessNew();
bool rightDefault = right.IsLiteralDefaultOrTypelessNew();
foundOperator = !isObjectEquality || BuiltInOperators.IsValidObjectEquality(Conversions, leftType, leftNull, leftDefault, rightType, rightNull, rightDefault, ref useSiteDiagnostics);
diagnostics.Add(node, useSiteDiagnostics);
}
Expand Down Expand Up @@ -701,30 +701,32 @@ private static void ReportBinaryOperatorError(ExpressionSyntax node, DiagnosticB
{
bool leftDefault = left.IsLiteralDefault();
bool rightDefault = right.IsLiteralDefault();
if ((operatorToken.Kind() == SyntaxKind.EqualsEqualsToken || operatorToken.Kind() == SyntaxKind.ExclamationEqualsToken))
if (operatorToken.Kind() != SyntaxKind.EqualsEqualsToken && operatorToken.Kind() != SyntaxKind.ExclamationEqualsToken)
{
if (leftDefault && rightDefault)
if (leftDefault || rightDefault)
{
Error(diagnostics, ErrorCode.ERR_AmbigBinaryOpsOnDefault, node, operatorToken.Text);
return;
}
else if (leftDefault && right.Type is TypeParameterSymbol)
{
Debug.Assert(!right.Type.IsReferenceType);
Error(diagnostics, ErrorCode.ERR_AmbigBinaryOpsOnUnconstrainedDefault, node, operatorToken.Text, right.Type);
return;
}
else if (rightDefault && left.Type is TypeParameterSymbol)
{
Debug.Assert(!left.Type.IsReferenceType);
Error(diagnostics, ErrorCode.ERR_AmbigBinaryOpsOnUnconstrainedDefault, node, operatorToken.Text, left.Type);
// other than == and !=, binary operators are disallowed on `default` literal
Error(diagnostics, ErrorCode.ERR_BadOpOnNullOrDefaultOrNew, node, operatorToken.Text, "default");
return;
}
}
else if (leftDefault || rightDefault)

if ((leftDefault || left.IsTypelessNew()) &&
(rightDefault || right.IsTypelessNew()))
{
Error(diagnostics, ErrorCode.ERR_AmbigBinaryOpsOnDefaultOrNew, node, operatorToken.Text, left.Display, right.Display);
return;
}
else if (leftDefault && right.Type is TypeParameterSymbol)
{
// other than == and !=, binary operators are disallowed on `default` literal
Error(diagnostics, ErrorCode.ERR_BadOpOnNullOrDefault, node, operatorToken.Text, "default");
Debug.Assert(!right.Type.IsReferenceType);
Error(diagnostics, ErrorCode.ERR_AmbigBinaryOpsOnUnconstrainedDefault, node, operatorToken.Text, right.Type);
return;
}
else if (rightDefault && left.Type is TypeParameterSymbol)
{
Debug.Assert(!left.Type.IsReferenceType);
Error(diagnostics, ErrorCode.ERR_AmbigBinaryOpsOnUnconstrainedDefault, node, operatorToken.Text, left.Type);
return;
}

Expand Down Expand Up @@ -1127,7 +1129,7 @@ private TypeSymbol GetBinaryOperatorErrorType(BinaryOperatorKind kind, Diagnosti

private BinaryOperatorAnalysisResult BinaryOperatorOverloadResolution(BinaryOperatorKind kind, BoundExpression left, BoundExpression right, CSharpSyntaxNode node, DiagnosticBag diagnostics, out LookupResultKind resultKind, out ImmutableArray<MethodSymbol> originalUserDefinedOperators)
{
if (!IsDefaultLiteralAllowedInBinaryOperator(kind, left, right))
if (!IsTypelessExpressionAllowedInBinaryOperator(kind, left, right))
{
resultKind = LookupResultKind.OverloadResolutionFailure;
originalUserDefinedOperators = default(ImmutableArray<MethodSymbol>);
Expand Down Expand Up @@ -1196,8 +1198,21 @@ private void ReportObsoleteAndFeatureAvailabilityDiagnostics(MethodSymbol operat
}
}

private bool IsDefaultLiteralAllowedInBinaryOperator(BinaryOperatorKind kind, BoundExpression left, BoundExpression right)
private bool IsTypelessExpressionAllowedInBinaryOperator(BinaryOperatorKind kind, BoundExpression left, BoundExpression right)
{
// The default literal is only allowed with equality operators and both operands cannot be typeless at the same time.
// Note: we only need to restrict expressions that can be converted to *any* type, in which case the resolution could always succeed.

if (left.IsTypelessNew())
{
return !right.IsLiteralDefaultOrTypelessNew();
}

if (right.IsTypelessNew())
{
return !left.IsLiteralDefault();
}

bool isEquality = kind == BinaryOperatorKind.Equal || kind == BinaryOperatorKind.NotEqual;
if (isEquality)
{
Expand Down Expand Up @@ -2327,7 +2342,7 @@ private BoundExpression BindUnaryOperatorCore(CSharpSyntaxNode node, string oper
{
// Dev10 does not allow unary prefix operators to be applied to the null literal
// (or other typeless expressions).
Error(diagnostics, ErrorCode.ERR_BadOpOnNullOrDefault, node, operatorText, operand.Display);
Error(diagnostics, ErrorCode.ERR_BadOpOnNullOrDefaultOrNew, node, operatorText, operand.Display);
}

// If the operand is bad, avoid generating cascading errors.
Expand Down Expand Up @@ -3434,7 +3449,7 @@ private BoundExpression BindNullCoalescingOperator(BinaryExpressionSyntax node,
// The specification does not permit the left hand side to be a default literal
if (leftOperand.IsLiteralDefault())
{
Error(diagnostics, ErrorCode.ERR_BadOpOnNullOrDefault, node, node.OperatorToken.Text, "default");
Error(diagnostics, ErrorCode.ERR_BadOpOnNullOrDefaultOrNew, node, node.OperatorToken.Text, "default");

return new BoundNullCoalescingOperator(node, leftOperand, rightOperand,
Conversion.NoConversion, BoundNullCoalescingOperatorResultKind.NoCommonType, CreateErrorType(), hasErrors: true);
Expand Down Expand Up @@ -3876,6 +3891,8 @@ private BoundExpression BindConditionalOperator(ConditionalExpressionSyntax node
hasErrors = constantValue != null && constantValue.IsBad;
}

trueExpr = BindToNaturalType(trueExpr, diagnostics, reportNoTargetType: false);
falseExpr = BindToNaturalType(falseExpr, diagnostics, reportNoTargetType: false);
return new BoundConditionalOperator(node, isRef, condition, trueExpr, falseExpr, constantValue, type, hasErrors);
}

Expand Down
4 changes: 4 additions & 0 deletions src/Compilers/CSharp/Portable/Binder/Binder_Query.cs
Original file line number Diff line number Diff line change
Expand Up @@ -737,6 +737,10 @@ protected BoundCall MakeQueryInvocation(CSharpSyntaxNode node, BoundExpression r
{
diagnostics.Add(ErrorCode.ERR_DefaultLiteralNotValid, node.Location);
}
else if (ultimateReceiver.IsTypelessNew())
{
diagnostics.Add(ErrorCode.ERR_TypelessNewNotValid, node.Location);
}
else if (ultimateReceiver.Kind == BoundKind.NamespaceExpression)
{
diagnostics.Add(ErrorCode.ERR_BadSKunknown, ultimateReceiver.Syntax.Location, ultimateReceiver.Syntax, MessageID.IDS_SK_NAMESPACE.Localize());
Expand Down
39 changes: 24 additions & 15 deletions src/Compilers/CSharp/Portable/Binder/Binder_TupleOperators.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ private BoundExpression ApplyConvertedTypes(BoundExpression expr, TupleBinaryOpe
var multiple = (TupleBinaryOperatorInfo.Multiple)@operator;
if (multiple.Operators.Length == 0)
{
return BindToNaturalType(expr, diagnostics, reportDefaultMissingType: false);
return BindToNaturalType(expr, diagnostics, reportNoTargetType: false);
}

ImmutableArray<BoundExpression> arguments = tuple.Arguments;
Expand All @@ -64,7 +64,7 @@ private BoundExpression ApplyConvertedTypes(BoundExpression expr, TupleBinaryOpe
}

// This element isn't getting a converted type
return BindToNaturalType(expr, diagnostics, reportDefaultMissingType: false);
return BindToNaturalType(expr, diagnostics, reportNoTargetType: false);
}

// We were able to determine a converted type (for this tuple literal or element), we can just convert to it
Expand Down Expand Up @@ -193,11 +193,11 @@ private TupleBinaryOperatorInfo BindTupleDynamicBinaryOperatorSingleInfo(BinaryE
private TupleBinaryOperatorInfo.Multiple BindTupleBinaryOperatorNestedInfo(BinaryExpressionSyntax node, BinaryOperatorKind kind,
BoundExpression left, BoundExpression right, DiagnosticBag diagnostics)
{
left = GiveTupleTypeToDefaultLiteralIfNeeded(left, right.Type);
right = GiveTupleTypeToDefaultLiteralIfNeeded(right, left.Type);
left = GiveTupleTypeToTypelessExpressionIfNeeded(left, right.Type, diagnostics);
right = GiveTupleTypeToTypelessExpressionIfNeeded(right, left.Type, diagnostics);

if ((left.Type is null && left.IsLiteralDefault()) ||
(right.Type is null && right.IsLiteralDefault()))
if ((left.Type is null && left.IsLiteralDefaultOrTypelessNew()) ||
(right.Type is null && right.IsLiteralDefaultOrTypelessNew()))
{
Error(diagnostics, ErrorCode.ERR_AmbigBinaryOps, node, node.OperatorToken.Text, left.Display, right.Display);
return TupleBinaryOperatorInfo.Multiple.ErrorInstance;
Expand Down Expand Up @@ -313,27 +313,36 @@ private static void ReportNamesMismatchesIfAny(BoundExpression left, BoundExpres
}
}

internal static BoundExpression GiveTupleTypeToDefaultLiteralIfNeeded(BoundExpression expr, TypeSymbol targetType)
internal BoundExpression GiveTupleTypeToTypelessExpressionIfNeeded(BoundExpression expr, TypeSymbol targetType, DiagnosticBag diagnostics)
{
if (!expr.IsLiteralDefault() || targetType is null)
if (targetType is object)
{
return expr;
if (expr.IsLiteralDefault())
{
Debug.Assert(targetType.StrippedType().IsTupleType);
return new BoundDefaultExpression(expr.Syntax, targetType);
}

if (expr is BoundUnconvertedObjectCreationExpression objectCreation)
{
return ConvertObjectCreationExpression(expr.Syntax, objectCreation, isCast: false, targetType, diagnostics);
}
}

Debug.Assert(targetType.StrippedType().IsTupleType);
return new BoundDefaultExpression(expr.Syntax, targetType);
return expr;
}

private static bool IsTupleBinaryOperation(BoundExpression left, BoundExpression right)
{
bool leftDefault = left.IsLiteralDefault();
bool rightDefault = right.IsLiteralDefault();
if (leftDefault && rightDefault)
bool leftDefaultOrNew = left.IsLiteralDefaultOrTypelessNew();
bool rightDefaultOrNew = right.IsLiteralDefaultOrTypelessNew();
if (leftDefaultOrNew && rightDefaultOrNew)
{
return false;
}

return (GetTupleCardinality(left) > 1 || leftDefault) && (GetTupleCardinality(right) > 1 || rightDefault);
return (GetTupleCardinality(left) > 1 || leftDefaultOrNew) &&
(GetTupleCardinality(right) > 1 || rightDefaultOrNew);
}

private static int GetTupleCardinality(BoundExpression expr)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ internal static Conversion GetTrivialConversion(ConversionKind kind)
internal static Conversion ImplicitReference => new Conversion(ConversionKind.ImplicitReference);
internal static Conversion ImplicitEnumeration => new Conversion(ConversionKind.ImplicitEnumeration);
internal static Conversion ImplicitThrow => new Conversion(ConversionKind.ImplicitThrow);
internal static Conversion ObjectCreation => new Conversion(ConversionKind.ObjectCreation);
internal static Conversion AnonymousFunction => new Conversion(ConversionKind.AnonymousFunction);
internal static Conversion Boxing => new Conversion(ConversionKind.Boxing);
internal static Conversion NullLiteral => new Conversion(ConversionKind.NullLiteral);
Expand Down Expand Up @@ -523,6 +524,17 @@ public bool IsThrow
}
}

/// <summary>
/// Returns true if the conversion is an implicit object creation expression conversion.
/// </summary>
internal bool IsObjectCreation
{
get
{
return Kind == ConversionKind.ObjectCreation;
}
}

/// <summary>
/// Returns true if the conversion is an implicit switch expression conversion.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,5 +56,6 @@ internal enum ConversionKind : byte
PinnedObjectToPointer,

DefaultLiteral, // a conversion from a `default` literal to any type
ObjectCreation, // a conversion from a `new()` expression to any type
}
}
Loading

0 comments on commit a501031

Please sign in to comment.