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

Added initial support for explicit tuple conversions #12068

Merged
merged 2 commits into from
Jun 18, 2016
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
15 changes: 14 additions & 1 deletion src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,20 @@ private BoundExpression CreateTupleLiteralConversion(CSharpSyntaxNode syntax, Bo
{
var argument = arguments[i];
var destType = targetElementTypes[i];
convertedArguments.Add(CreateConversion(argument, destType, diagnostics));

HashSet<DiagnosticInfo> useSiteDiagnostics = null;
Conversion elementConversion;
if (isCast)
{
elementConversion = this.Conversions.ClassifyConversionForCast(argument, destType, ref useSiteDiagnostics);
Copy link
Contributor

Choose a reason for hiding this comment

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

Could we get conversion classification for elements from nestedConversions?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes. That is the next change that I mentioned in the description. We might not need to be classifying anything at all when lowering conversions.

}
else
{
elementConversion = this.Conversions.ClassifyConversionFromExpression(argument, destType, ref useSiteDiagnostics);
}

diagnostics.Add(syntax, useSiteDiagnostics);
convertedArguments.Add(CreateConversion(argument.Syntax, argument, elementConversion, isCast, destType, diagnostics));
}

BoundExpression result = new BoundConvertedTupleLiteral(
Expand Down
177 changes: 124 additions & 53 deletions src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1716,60 +1716,9 @@ private BoundExpression BindCastCore(ExpressionSyntax node, BoundExpression oper
HashSet<DiagnosticInfo> useSiteDiagnostics = null;
Conversion conversion = this.Conversions.ClassifyConversionForCast(operand, targetType, ref useSiteDiagnostics);
diagnostics.Add(node, useSiteDiagnostics);
bool targetTypeIsStatic = targetType.IsStatic;
if (operand.HasAnyErrors || targetType.IsErrorType() || !conversion.IsValid || targetTypeIsStatic)
if (operand.HasAnyErrors || targetType.IsErrorType() || !conversion.IsValid || targetType.IsStatic)
{
// Make sure that errors within the unbound lambda don't get lost.
if (operand.Kind == BoundKind.UnboundLambda)
{
GenerateAnonymousFunctionConversionError(diagnostics, operand.Syntax, (UnboundLambda)operand, targetType);
}
else if (operand.HasAnyErrors || targetType.IsErrorType())
{
// an error has already been reported elsewhere
}
else if (targetTypeIsStatic)
{
// The specification states in the section titled "Referencing Static
// Class Types" that it is always illegal to have a static class in a
// cast operator.
diagnostics.Add(ErrorCode.ERR_ConvertToStaticClass, node.Location, targetType);
}
else if (!targetType.IsReferenceType && !targetType.IsNullableType() && operand.IsLiteralNull())
{
diagnostics.Add(ErrorCode.ERR_ValueCantBeNull, node.Location, targetType);
}
else if (conversion.ResultKind == LookupResultKind.OverloadResolutionFailure)
{
Debug.Assert(conversion.IsUserDefined);

ImmutableArray<MethodSymbol> originalUserDefinedConversions = conversion.OriginalUserDefinedConversions;
if (originalUserDefinedConversions.Length > 1)
{
diagnostics.Add(ErrorCode.ERR_AmbigUDConv, node.Location, originalUserDefinedConversions[0], originalUserDefinedConversions[1], operand.Type, targetType);
}
else
{
Debug.Assert(originalUserDefinedConversions.Length == 0,
"How can there be exactly one applicable user-defined conversion if the conversion doesn't exist?");
SymbolDistinguisher distinguisher = new SymbolDistinguisher(this.Compilation, operand.Type, targetType);
diagnostics.Add(ErrorCode.ERR_NoExplicitConv, node.Location, distinguisher.First, distinguisher.Second);
}
}
else
{
// TODO: report more specific diagnostics here for failed method group conversions
if (operand.Kind == BoundKind.MethodGroup)
{
diagnostics.Add(ErrorCode.ERR_NoExplicitConv, node.Location, MessageID.IDS_SK_METHOD.Localize(), targetType);
}
else
{
Debug.Assert((object)operand.Type != null);
SymbolDistinguisher distinguisher = new SymbolDistinguisher(this.Compilation, operand.Type, targetType);
diagnostics.Add(ErrorCode.ERR_NoExplicitConv, node.Location, distinguisher.First, distinguisher.Second);
}
}
GenerateExplicitConversionErrors(diagnostics, node, conversion, operand, targetType);

return new BoundConversion(
node,
Expand All @@ -1785,6 +1734,128 @@ private BoundExpression BindCastCore(ExpressionSyntax node, BoundExpression oper
return CreateConversion(node, operand, conversion, isCast: true, wasCompilerGenerated: wasCompilerGenerated, destination: targetType, diagnostics: diagnostics);
}

private void GenerateExplicitConversionErrors(
DiagnosticBag diagnostics,
CSharpSyntaxNode syntax,
Conversion conversion,
BoundExpression operand,
TypeSymbol targetType)
{
// Make sure that errors within the unbound lambda don't get lost.
if (operand.Kind == BoundKind.UnboundLambda)
{
GenerateAnonymousFunctionConversionError(diagnostics, operand.Syntax, (UnboundLambda)operand, targetType);
return;
}

if (operand.HasAnyErrors || targetType.IsErrorType())
{
// an error has already been reported elsewhere
return;
}

if (targetType.IsStatic)
{
// The specification states in the section titled "Referencing Static
Copy link
Member

Choose a reason for hiding this comment

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

If you really wanted to channel Eric you could quote it here :)

Copy link
Member Author

Choose a reason for hiding this comment

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

These comments are actually preexisting ones. I have just moved this code into a helper so that I could call it recursively if needed. Yes, sounds like Eric. :-)

// Class Types" that it is always illegal to have a static class in a
// cast operator.
diagnostics.Add(ErrorCode.ERR_ConvertToStaticClass, syntax.Location, targetType);
return;
}

if (!targetType.IsReferenceType && !targetType.IsNullableType() && operand.IsLiteralNull())
Copy link
Member

Choose a reason for hiding this comment

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

What happens to dynamic here? Does IsReferenceType return false for dynamic?

Copy link
Member Author

Choose a reason for hiding this comment

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

IsReferenceType returns true for dynamic. In this case dynamic can be null so a cast (dynamic)null will succeed. In fact dynamic is implicitly convertable to and from any type so it will always work in this context.
I will add couple scenarios with dynamic, out of curiosity.

Copy link
Member Author

Choose a reason for hiding this comment

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

Interestingly "dynamic" is contextual and is not recognized as a type in a context of a tuple.
#12082

So that blocks this scenario for now.

{
diagnostics.Add(ErrorCode.ERR_ValueCantBeNull, syntax.Location, targetType);
return;
}

if (conversion.ResultKind == LookupResultKind.OverloadResolutionFailure)
{
Debug.Assert(conversion.IsUserDefined);

ImmutableArray<MethodSymbol> originalUserDefinedConversions = conversion.OriginalUserDefinedConversions;
if (originalUserDefinedConversions.Length > 1)
{
diagnostics.Add(ErrorCode.ERR_AmbigUDConv, syntax.Location, originalUserDefinedConversions[0], originalUserDefinedConversions[1], operand.Type, targetType);
}
else
{
Debug.Assert(originalUserDefinedConversions.Length == 0,
"How can there be exactly one applicable user-defined conversion if the conversion doesn't exist?");
SymbolDistinguisher distinguisher1 = new SymbolDistinguisher(this.Compilation, operand.Type, targetType);
diagnostics.Add(ErrorCode.ERR_NoExplicitConv, syntax.Location, distinguisher1.First, distinguisher1.Second);
}

return;
}

// TODO: report more specific diagnostics here for failed method group conversions
if (operand.Kind == BoundKind.MethodGroup)
{
diagnostics.Add(ErrorCode.ERR_NoExplicitConv, syntax.Location, MessageID.IDS_SK_METHOD.Localize(), targetType);
return;
}

if (operand.Kind == BoundKind.TupleLiteral)
{
var tuple = (BoundTupleLiteral)operand;
var targetElementTypes = default(ImmutableArray<TypeSymbol>);

// source does not have a type.
// Such conversions could only happen via explicit tuple literal conversions
// which are currently not supported.
// See: https://github.com/dotnet/roslyn/issues/11804
if ((object)tuple.Type == null)
{
Error(diagnostics, ErrorCode.ERR_NoExplicitConv, syntax, tuple.Display, targetType);
return;
}

// If target is a tuple or compatible type with the same number of elements,
// report errors for tuple arguments that failed to convert, which would be more useful.
if (targetType.TryGetElementTypesIfTupleOrCompatible(out targetElementTypes) &&
targetElementTypes.Length == tuple.Arguments.Length)
{
GenerateExplicitConversionErrorsForTupleLiteralArguments(diagnostics, syntax, tuple.Arguments, targetElementTypes);
return;
}

// Otherwise it is just a regular conversion failure from T1 to T2.
}

Debug.Assert((object)operand.Type != null);
SymbolDistinguisher distinguisher = new SymbolDistinguisher(this.Compilation, operand.Type, targetType);
diagnostics.Add(ErrorCode.ERR_NoExplicitConv, syntax.Location, distinguisher.First, distinguisher.Second);
}

private void GenerateExplicitConversionErrorsForTupleLiteralArguments(
DiagnosticBag diagnostics,
CSharpSyntaxNode syntax,
ImmutableArray<BoundExpression> tupleArguments,
ImmutableArray<TypeSymbol> targetElementTypes)
{
var argLength = tupleArguments.Length;

// report all leaf elements of the tuple literal that failed to convert
// NOTE: we are not responsible for reporting use site errors here, just the failed leaf conversions.
// By the time we get here we have done analysis and know we have failed the cast in general, and diagnostics collected in the process is already in the bag.
// The only thing left is to form a diagnostics about the actually failing conversion(s).
// This whole method does not itself collect any usesite diagnostics. Its only purpose is to produce an error better than "conversion failed here"
HashSet<DiagnosticInfo> usDiagsUnused = null;

for (int i = 0; i < targetElementTypes.Length; i++)
{
var argument = tupleArguments[i];
var targetElementType = targetElementTypes[i];

var elementConversion = Conversions.ClassifyConversionFromType(argument.Type, targetElementType, ref usDiagsUnused);
if (!elementConversion.IsValid)
{
GenerateExplicitConversionErrors(diagnostics, argument.Syntax, elementConversion, argument, targetElementType);
}
}
}

private BoundExpression BindOriginal(OriginalExpressionSyntax syntax, DiagnosticBag diagnostics)
{
var containingMethod = (MethodSymbol)this.ContainingMember();
Expand Down
32 changes: 18 additions & 14 deletions src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2646,31 +2646,35 @@ protected static void GenerateImplicitConversionError(DiagnosticBag diagnostics,
}
}

protected void GenerateImplicitConversionError(DiagnosticBag diagnostics, CSharpSyntaxNode syntax,
Conversion conversion, BoundExpression expression, TypeSymbol targetType)
protected void GenerateImplicitConversionError(
DiagnosticBag diagnostics,
CSharpSyntaxNode syntax,
Conversion conversion,
BoundExpression operand,
TypeSymbol targetType)
{
Debug.Assert(expression != null);
Debug.Assert(operand != null);
Debug.Assert((object)targetType != null);

if (targetType.TypeKind == TypeKind.Error)
{
return;
}

if (expression.Kind == BoundKind.BadExpression)
if (operand.Kind == BoundKind.BadExpression)
{
return;
}

if (expression.Kind == BoundKind.UnboundLambda)
if (operand.Kind == BoundKind.UnboundLambda)
{
GenerateAnonymousFunctionConversionError(diagnostics, syntax, (UnboundLambda)expression, targetType);
GenerateAnonymousFunctionConversionError(diagnostics, syntax, (UnboundLambda)operand, targetType);
return;
}

if (expression.Kind == BoundKind.TupleLiteral)
if (operand.Kind == BoundKind.TupleLiteral)
{
var tuple = (BoundTupleLiteral)expression;
var tuple = (BoundTupleLiteral)operand;
var targetElementTypes = default(ImmutableArray<TypeSymbol>);

// If target is a tuple or compatible type with the same number of elements,
Expand All @@ -2692,14 +2696,14 @@ protected void GenerateImplicitConversionError(DiagnosticBag diagnostics, CSharp
// Otherwise it is just a regular conversion failure from T1 to T2.
}

var sourceType = expression.Type;
var sourceType = operand.Type;
if ((object)sourceType != null)
{
GenerateImplicitConversionError(diagnostics, this.Compilation, syntax, conversion, sourceType, targetType, expression.ConstantValue);
GenerateImplicitConversionError(diagnostics, this.Compilation, syntax, conversion, sourceType, targetType, operand.ConstantValue);
return;
}

if (expression.IsLiteralNull())
if (operand.IsLiteralNull())
{
if (targetType.TypeKind == TypeKind.TypeParameter)
{
Expand All @@ -2713,9 +2717,9 @@ protected void GenerateImplicitConversionError(DiagnosticBag diagnostics, CSharp
}
}

if (expression.Kind == BoundKind.MethodGroup)
if (operand.Kind == BoundKind.MethodGroup)
{
var methodGroup = (BoundMethodGroup)expression;
var methodGroup = (BoundMethodGroup)operand;
if (!Conversions.ReportDelegateMethodGroupDiagnostics(this, methodGroup, targetType, diagnostics))
{
var nodeForSquiggle = syntax;
Expand Down Expand Up @@ -2744,7 +2748,7 @@ protected void GenerateImplicitConversionError(DiagnosticBag diagnostics, CSharp
return;
}

Debug.Assert(expression.HasAnyErrors && expression.Kind != BoundKind.UnboundLambda, "Missing a case in implicit conversion error reporting");
Debug.Assert(operand.HasAnyErrors && operand.Kind != BoundKind.UnboundLambda, "Missing a case in implicit conversion error reporting");
}

private void GenerateImplicitConversionErrorsForTupleLiteralArguments(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using Microsoft.CodeAnalysis.CSharp.Symbols;
using Roslyn.Utilities;
using System;
using System.Collections.Immutable;
using System.Diagnostics;
Copy link
Member

@gafter gafter Jun 17, 2016

Choose a reason for hiding this comment

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

I think the system ones are supposed to go at the top. ¯\_(ツ)_/¯

using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.CSharp
{
Expand Down Expand Up @@ -53,7 +51,10 @@ public struct Conversion : IEquatable<Conversion>
private const byte IsExtensionMethodMask = 1 << 0;
private const byte IsArrayIndexMask = 1 << 1;

private readonly Conversion[] _nestedConversionsOpt;
Copy link
Member Author

Choose a reason for hiding this comment

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

Could not make this ImmutableArray because of a false-circularity bug in CLR loader.
The shape of this type is likely to change as a part of #12067


private Conversion(ConversionKind kind, bool isExtensionMethod, bool isArrayIndex, UserDefinedConversionResult conversionResult, MethodSymbol methodGroupConversionMethod)
: this()
{
_kind = kind;
_conversionResult = conversionResult;
Expand Down Expand Up @@ -81,6 +82,13 @@ internal Conversion(ConversionKind kind)
this._kind = kind;
}

internal Conversion(ConversionKind kind, Conversion[] nestedConversions)
: this()
{
this._kind = kind;
this._nestedConversionsOpt = nestedConversions;
}

internal ConversionKind Kind
{
get
Expand Down Expand Up @@ -128,7 +136,28 @@ internal bool IsValid
{
get
{
return this.Exists && (!this.IsUserDefined || (object)this.Method != null || _conversionResult.Kind == UserDefinedConversionResultKind.Valid);
if (!this.Exists)
{
return false;
}

if (_nestedConversionsOpt != null)
{
foreach (var conv in _nestedConversionsOpt)
{
if (!conv.IsValid)
{
return false;
}
}

Debug.Assert(!this.IsUserDefined);
return true;
}

return !this.IsUserDefined ||
(object)this.Method != null ||
_conversionResult.Kind == UserDefinedConversionResultKind.Valid;
}
}

Expand Down
Loading