Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support target-typed new #25196

Merged
Merged
Show file tree
Hide file tree
Changes from 35 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
53a9e8f
Start work on target-typed new
alrz Jul 5, 2018
27411ff
Implement the late-filter approach on target-typed new
alrz Jul 5, 2018
e030aa3
Revert code.
alrz Jul 5, 2018
e9df1be
Move error reporting code.
alrz Jul 5, 2018
cba317c
Use the correct error code.
alrz Jul 5, 2018
bdc9514
Formatting.
alrz Jul 5, 2018
8c67f9f
Revert code.
alrz Jul 12, 2018
92cede4
Fixup tests.
alrz Jul 12, 2018
88f739b
Add the feature flag
alrz Jul 12, 2018
fa94c63
Update resources
alrz Jul 12, 2018
32f5ed2
Fixup parsing tests.
alrz Jul 12, 2018
46611a1
Fixup formatting test.
alrz Jul 12, 2018
f5fe1e8
Prevent target-typed new in expression trees.
alrz Jul 12, 2018
baf9ada
Update resources.
alrz Jul 12, 2018
b190286
PR feedback
alrz Jul 13, 2018
7cfbd92
Add lang-version parsing tests
alrz Jul 13, 2018
fb3ce5d
Misc.
alrz Jul 13, 2018
ca76708
Add formatting tests.
alrz Jul 13, 2018
589ad4f
Target nullable underlying type
alrz Jul 13, 2018
141f778
Add test for nullable of a type parameter
alrz Jul 13, 2018
12a97de
Do not cascade errors.
alrz Jul 13, 2018
b256698
Fix typo.
alrz Jul 13, 2018
8dcd4c4
Test expression tree with nullable conversion.
alrz Jul 14, 2018
40ea91c
Add a specialized 'AsSingletone' extension for 'ImmutableArray'
alrz Jul 17, 2018
2276639
Add suggested tests.
alrz Jul 17, 2018
a7fb76f
PR feedback
alrz Jul 17, 2018
44d86ec
Merge remote-tracking branch 'origin/features/target-typed-new' into …
alrz Jul 26, 2018
4f44a62
PR feedback
alrz Jul 26, 2018
be0c0a0
Update resources.
alrz Jul 26, 2018
bccf753
Add suggested tests.
alrz Jul 26, 2018
8120a25
Misc.
alrz Jul 26, 2018
ad519d3
Improve diagnostics for a tuple type in 'new'
alrz Jul 26, 2018
f1ccf96
Extract out helper for error reporting.
alrz Jul 26, 2018
e0a1851
Fix parsing bug with invalid tuples
alrz Jul 26, 2018
9cef7b3
Add IOperation tests.
alrz Jul 26, 2018
df4764a
Reconstruct AnalyzedArguments to avoid binding twice
alrz Aug 2, 2018
aaa3b35
Add additional tests.
alrz Aug 2, 2018
b1ef174
PR feedback
alrz Aug 3, 2018
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
97 changes: 97 additions & 0 deletions src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,11 @@ protected BoundExpression CreateConversion(
return CreateAnonymousFunctionConversion(syntax, source, conversion, isCast, destination, diagnostics);
}

if (conversion.IsNew)
{
return CreateImplicitNewConversion(syntax, source, conversion, isCast, destination, diagnostics);
}

if (conversion.IsStackAlloc)
{
return CreateStackAllocConversion(syntax, source, conversion, isCast, destination, diagnostics);
Expand Down Expand Up @@ -128,6 +133,98 @@ protected BoundExpression CreateConversion(
{ WasCompilerGenerated = wasCompilerGenerated };
}

private BoundExpression CreateImplicitNewConversion(SyntaxNode syntax, BoundExpression source, Conversion conversion, bool isCast, TypeSymbol destination, DiagnosticBag diagnostics)
{
var node = (ObjectCreationExpressionSyntax)source.Syntax;
Copy link
Member

Choose a reason for hiding this comment

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

ObjectCreationExpressionSyntax [](start = 24, length = 30)

As I mentioned in BoundNodes.xml, we should not require a relationship between an UnboundObjectCreationExpression and its syntax node. Please store the initializer (or anything else needed) in the UnboundObjectCreationExpression.

var arguments = ((UnboundObjectCreationExpression)source).AnalyzedArguments;

TypeSymbol type = destination.StrippedType();
BoundObjectInitializerExpressionBase boundInitializerOpt = node.Initializer != null
? BindInitializerExpression(syntax: node.Initializer, type: type, typeSyntax: syntax, diagnostics)
: null;

if (ReportBadTargetType(syntax, type, diagnostics))
{
return MakeBadExpressionForObjectCreation(node, destination, boundInitializerOpt, arguments);
}

BoundExpression result;
switch (type.TypeKind)
Copy link
Member

@jcouv jcouv Jul 31, 2018

Choose a reason for hiding this comment

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

switch [](start = 12, length = 6)

Should we also have a case for TypeKind.Enum? Never mind. I see that it's caught by ReportBadTargetType #Closed

{
case TypeKind.Struct:
case TypeKind.Class:
result = BindClassCreationExpression(
node,
typeName: type.Name,
typeNode: node,
type: (NamedTypeSymbol)type,
arguments,
diagnostics,
boundInitializerOpt,
forTargetTypedNew: true);
break;

case TypeKind.TypeParameter:
result = BindTypeParameterCreationExpression(
node,
typeParameter: (TypeParameterSymbol)type,
arguments,
boundInitializerOpt,
diagnostics);
break;

default:
throw ExceptionUtilities.UnexpectedValue(type.Kind);
}

if (destination.IsNullableType())
Copy link
Member

@jcouv jcouv Jul 31, 2018

Choose a reason for hiding this comment

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

May be good to add a comment on the nullable case to clarify why this is needed: S? x = new(); means S? x = new S(); #Closed

{
result = new BoundConversion(
syntax,
result,
conversion: new Conversion(ConversionKind.ImplicitNullable, Conversion.IdentityUnderlying),
@checked: false,
explicitCastInCode: isCast,
constantValueOpt: null, // A "target-typed new" would never produce a constant.
type: destination);
}
Copy link
Member

@gafter gafter Jul 24, 2018

Choose a reason for hiding this comment

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

It can't be compiler generated and explicit in code at the same time. #Resolved

Copy link
Contributor Author

Choose a reason for hiding this comment

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

what values are expected for explicitCastInCode and WasCompilerGenerated in (S?)new(x)?

I guess the latter should be false.


return result;
}

private bool ReportBadTargetType(SyntaxNode syntax, TypeSymbol type, DiagnosticBag diagnostics)
{
switch (type.TypeKind)
{
case TypeKind.Array:
case TypeKind.Enum:
case TypeKind.Delegate:
case TypeKind.Interface:
Error(diagnostics, ErrorCode.ERR_BadTargetTypeForNew, syntax, type);
return true;

case TypeKind.Pointer:
Error(diagnostics, ErrorCode.ERR_UnsafeTypeInObjectCreation, syntax, type);
return true;

case TypeKind.Dynamic:
Error(diagnostics, ErrorCode.ERR_NoConstructors, syntax, type);
return true;

case TypeKind.Struct when type.IsTupleType:
Error(diagnostics, ErrorCode.ERR_NewWithTupleTypeSyntax, syntax, type);
return true;

case TypeKind.Struct:
case TypeKind.Class:
case TypeKind.TypeParameter:
return false;

default:
return true;
Copy link
Member

@jcouv jcouv Jul 31, 2018

Choose a reason for hiding this comment

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

return true; [](start = 20, length = 12)

Is the default case reachable? If not, let's throw an UnexpectedValue exception. #Closed

Copy link
Contributor Author

@alrz alrz Aug 1, 2018

Choose a reason for hiding this comment

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

Yes, it's reachable, for example TypeKind.Error. I'm not sure if other kinds can possibly get here. either way, we want to return a bad expression from the caller. #Closed

Copy link
Member

Choose a reason for hiding this comment

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

Thanks for the clarification. I think we should make the Error case explicit. Having a complete switch is beneficial, as it catches when we add new cases.


In reply to: 206795071 [](ancestors = 206795071)

Copy link
Contributor Author

@alrz alrz Aug 2, 2018

Choose a reason for hiding this comment

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

Done. #Closed

}
}

protected BoundExpression CreateUserDefinedConversion(SyntaxNode syntax, BoundExpression source, Conversion conversion, bool isCast, TypeSymbol destination, DiagnosticBag diagnostics)
{
if (!conversion.IsValid)
Expand Down
58 changes: 38 additions & 20 deletions src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3555,6 +3555,14 @@ private BoundExpression BindConstructorInitializerCore(

protected BoundExpression BindObjectCreationExpression(ObjectCreationExpressionSyntax node, DiagnosticBag diagnostics)
{
if ((object)node.Type == null)
{
var arguments = new AnalyzedArguments();
BindArgumentsAndNames(node.ArgumentList, diagnostics, arguments, allowArglist: true);

return new UnboundObjectCreationExpression(node, arguments);
}

var type = BindType(node.Type, diagnostics);

BoundObjectInitializerExpressionBase boundInitializerOpt = node.Initializer == null ?
Expand Down Expand Up @@ -4559,7 +4567,8 @@ protected BoundExpression BindClassCreationExpression(
NamedTypeSymbol type,
AnalyzedArguments analyzedArguments,
DiagnosticBag diagnostics,
BoundObjectInitializerExpressionBase boundInitializerOpt = null)
BoundObjectInitializerExpressionBase boundInitializerOpt = null,
bool forTargetTypedNew = false)
{

BoundExpression result = null;
Expand Down Expand Up @@ -4643,9 +4652,9 @@ protected BoundExpression BindClassCreationExpression(
ReportDiagnosticsIfObsolete(diagnostics, method, node, hasBaseReceiver: false);
// NOTE: Use-site diagnostics were reported during overload resolution.

ConstantValue constantValueOpt = (boundInitializerOpt == null && method.IsDefaultValueTypeConstructor()) ?
FoldParameterlessValueTypeConstructor(type) :
null;
ConstantValue constantValueOpt = (boundInitializerOpt == null && !forTargetTypedNew && method.IsDefaultValueTypeConstructor())
? FoldParameterlessValueTypeConstructor(type)
: null;

var arguments = analyzedArguments.Arguments.ToImmutable();
var refKinds = analyzedArguments.RefKinds.ToImmutableOrNull();
Expand All @@ -4662,6 +4671,12 @@ protected BoundExpression BindClassCreationExpression(
argToParams,
this.LocalScopeDepth,
diagnostics);

if (forTargetTypedNew && method.IsDefaultValueTypeConstructor())
{
Error(diagnostics, ErrorCode.ERR_DefaultValueTypeCtorInTargetTypedNew, node, type);
hasError = true;
}
}

result = new BoundObjectCreationExpression(
Expand Down Expand Up @@ -4873,31 +4888,34 @@ private BoundExpression BindTypeParameterCreationExpression(ObjectCreationExpres
AnalyzedArguments analyzedArguments = AnalyzedArguments.GetInstance();
BindArgumentsAndNames(node.ArgumentList, diagnostics, analyzedArguments);

bool hasArguments = analyzedArguments.Arguments.Count > 0;

try
{
if (!typeParameter.HasConstructorConstraint && !typeParameter.IsValueType)
{
diagnostics.Add(ErrorCode.ERR_NoNewTyvar, node.Location, typeParameter);
}
else if (hasArguments)
{
diagnostics.Add(ErrorCode.ERR_NewTyvarWithArgs, node.Location, typeParameter);
}
else
{
return new BoundNewT(node, boundInitializerOpt, typeParameter);
}

return MakeBadExpressionForObjectCreation(node, typeParameter, boundInitializerOpt, analyzedArguments);
return BindTypeParameterCreationExpression(node, typeParameter, analyzedArguments, boundInitializerOpt, diagnostics);
}
finally
{
analyzedArguments.Free();
}
}

private BoundExpression BindTypeParameterCreationExpression(ObjectCreationExpressionSyntax node, TypeParameterSymbol typeParameter, AnalyzedArguments analyzedArguments, BoundObjectInitializerExpressionBase boundInitializerOpt, DiagnosticBag diagnostics)
{
if (!typeParameter.IsInstantiable())
{
diagnostics.Add(ErrorCode.ERR_NoNewTyvar, node.Location, typeParameter);
}
else if (analyzedArguments.Arguments.Count > 0)
{
diagnostics.Add(ErrorCode.ERR_NewTyvarWithArgs, node.Location, typeParameter);
}
else
{
return new BoundNewT(node, boundInitializerOpt, typeParameter);
}

return MakeBadExpressionForObjectCreation(node, typeParameter, boundInitializerOpt, analyzedArguments);
}

/// <summary>
/// Given the type containing constructors, gets the list of candidate instance constructors and uses overload resolution to determine which one should be called.
/// </summary>
Expand Down
8 changes: 8 additions & 0 deletions src/Compilers/CSharp/Portable/Binder/Binder_Operators.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3134,6 +3134,14 @@ private BoundExpression BindAsOperator(BinaryExpressionSyntax node, DiagnosticBa
return new BoundAsOperator(node, operand, typeExpression, Conversion.NoConversion, resultType, hasErrors: true);
}
break;

case BoundKind.UnboundObjectCreationExpression:
if (!operand.HasAnyErrors)
{
Error(diagnostics, ErrorCode.ERR_TypelessNewInAs, node);
}

return new BoundAsOperator(node, operand, typeExpression, Conversion.NoConversion, resultType, hasErrors: true);
}

if (operand.HasAnyErrors || targetTypeKind == TypeKind.Error)
Expand Down
7 changes: 5 additions & 2 deletions src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2891,9 +2891,12 @@ private static bool IsValidStatementExpression(SyntaxNode syntax, BoundExpressio
// is not legal because it is a delegate-creation-expression and not an
// object-creation-expression, but of course we don't know that syntactically.

if (expression.Kind == BoundKind.DelegateCreationExpression || expression.Kind == BoundKind.NameOfOperator)
switch (expression.Kind)
{
return false;
case BoundKind.DelegateCreationExpression:
case BoundKind.NameOfOperator:
case BoundKind.UnboundObjectCreationExpression:
Copy link
Contributor Author

@alrz alrz Jul 26, 2018

Choose a reason for hiding this comment

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

Although we know that this can't be used as a statement expression from the syntax, I didn't modify SyntaxFacts.IsStatementExpression because the grammar allows it.

return false;
}

return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ private static void AssertTrivialConversion(ConversionKind kind)
case ConversionKind.ImplicitReference:
case ConversionKind.ImplicitEnumeration:
case ConversionKind.ImplicitThrow:
case ConversionKind.ImplicitNew:
case ConversionKind.AnonymousFunction:
case ConversionKind.Boxing:
case ConversionKind.DefaultOrNullLiteral:
Expand Down Expand Up @@ -219,6 +220,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 ImplicitNew => new Conversion(ConversionKind.ImplicitNew);
internal static Conversion AnonymousFunction => new Conversion(ConversionKind.AnonymousFunction);
internal static Conversion Boxing => new Conversion(ConversionKind.Boxing);
internal static Conversion DefaultOrNullLiteral => new Conversion(ConversionKind.DefaultOrNullLiteral);
Expand Down Expand Up @@ -513,6 +515,14 @@ public bool IsThrow
}
}

internal bool IsNew
{
get
{
return Kind == ConversionKind.ImplicitNew;
}
}

// TODO: update the language reference section number below.
/// <summary>
/// Returns true if the conversion is an interpolated string conversion.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ internal enum ConversionKind : byte
ImplicitNumeric,
ImplicitEnumeration,
ImplicitThrow,
ImplicitNew,
ImplicitTupleLiteral,
ImplicitTuple,
ExplicitTupleLiteral,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ public static bool IsImplicitConversion(this ConversionKind conversionKind)
case ConversionKind.Deconstruction:
case ConversionKind.StackAllocToPointerType:
case ConversionKind.StackAllocToSpanType:
case ConversionKind.ImplicitNew:
return true;

case ConversionKind.ExplicitNumeric:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -840,6 +840,9 @@ private Conversion ClassifyImplicitBuiltInConversionFromExpression(BoundExpressi

case BoundKind.ThrowExpression:
return Conversion.ImplicitThrow;

case BoundKind.UnboundObjectCreationExpression:
return Conversion.ImplicitNew;
}

return Conversion.NoConversion;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -609,6 +609,9 @@ private static bool IsEncompassingImplicitConversionKind(ConversionKind kind)
case ConversionKind.ImplicitTupleLiteral:
case ConversionKind.ImplicitTuple:
case ConversionKind.ImplicitThrow:

// Added for C# 8.
case ConversionKind.ImplicitNew:
return true;
Copy link
Member

Choose a reason for hiding this comment

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

Do we have a test that exercises this branch?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Looks like it's not reachable for ImplicitThrow as well. My understanding is that since both new and throw are convertible to any type, there's no conversion left for these to encompass. Is that correct?


default:
Expand Down
7 changes: 6 additions & 1 deletion src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1292,7 +1292,12 @@
<!-- BinderOpt is added as a temporary solution for IOperation implementation and should probably be removed in the future -->
<Field Name="BinderOpt" Type="Binder" Null="allow"/>
</Node>


<Node Name="UnboundObjectCreationExpression" Base="BoundExpression">
Copy link
Member

@jcouv jcouv Jul 12, 2018

Choose a reason for hiding this comment

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

UnboundObjectCreationExpression [](start = 14, length = 31)

I assume this node cannot survive initial binding. If that's correct, please add a comment and add something like the following to LocalRewriter:

        public override BoundNode VisitDeconstructionVariablePendingInference(DeconstructionVariablePendingInference node)
        {
            // DeconstructionVariablePendingInference nodes are only used within initial binding, but don't survive past that stage
            throw ExceptionUtilities.Unreachable;
        }
``` #Closed

Copy link
Member

@gafter gafter Aug 3, 2018

Choose a reason for hiding this comment

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

Since the object initializer is logically part of the (un)bound object creation expression, it should be captured here. Probably as an optional InitializerExpressionSyntax. The caller should not assume that the syntax of this bound node is of the proper form, as that would prevent us from using it as an intermediary in translations (of perhaps new, different syntax forms) later.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

Please review the follow-up PR at #29167 after this goes in. thanks.

<!-- Type is not significant for this node type; always null -->
<Field Name="Type" Type="TypeSymbol" Override="true" Null="always"/>
<Field Name="AnalyzedArguments" Type="AnalyzedArguments"/>
Copy link
Member

@jcouv jcouv Jul 31, 2018

Choose a reason for hiding this comment

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

AnalyzedArguments [](start = 17, length = 17)

I don't think we should hold onto a pooled object in the bound tree. It's unclear who's responsible for freeing it... #Closed

Copy link
Contributor Author

@alrz alrz Aug 1, 2018

Choose a reason for hiding this comment

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

So you're suggesting we postpone binding arguments (potentially until we create the conversion)? In that case, we should be responsible for binding the argument list in error cases when there's no destination type to convert to e.g. new(a, b);. #Closed

Copy link
Member

Choose a reason for hiding this comment

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

Some alternatives for discussion:

  1. we could bind arguments twice (once in BindObjectCreationExpression as today, and a second time, in CreateImplicitNewConversion, which would discard diagnostics), but that is somewhat wasteful
  2. we could use a non-pooled instance of AnalyzedArguments, but that is a lot of plumbing/infrastructure change to do cleanly (we'd want to split AnalyzedArguments into a non-poolable base type and a poolable derived type, modify consumers to work with non-poolable one, etc).

I'd suggest we go with (1) for now, with a PROTOTYPE marker.


In reply to: 206795081 [](ancestors = 206795081)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As yet another alternative, I think we could bind the arg list in CreateImplicitNewConversion and another time in some other phase (likely DiagnosticsPass?) when we encounter a UnboundObjectCreationExpression. This way, we only bind it once, for the error cases or otherwise.

</Node>

<!--
Tuple literals can exist in two forms - literal and converted literal.
Expand Down
5 changes: 5 additions & 0 deletions src/Compilers/CSharp/Portable/BoundTree/Formatting.cs
Original file line number Diff line number Diff line change
Expand Up @@ -157,4 +157,9 @@ internal partial class BoundPassByCopy
{
public override object Display => Expression.Display;
}

internal partial class UnboundObjectCreationExpression
{
public override object Display => "new(...)";
}
}
36 changes: 36 additions & 0 deletions src/Compilers/CSharp/Portable/CSharpResources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading