提交 ea54e1e1 编写于 作者: V VSadov

Explicit tuple literal conversions

上级 818ae18e
......@@ -90,8 +90,8 @@ internal partial class Binder
return CreateAnonymousFunctionConversion(syntax, source, conversion, isCast, destination, diagnostics);
}
if (conversion.IsTupleLiteral ||
(conversion.Kind == ConversionKind.ImplicitNullable && source.Kind == BoundKind.TupleLiteral))
if (conversion.IsTupleLiteralConversion ||
(conversion.Kind == ConversionKind.ImplicitNullable && conversion.UnderlyingConversions[0].IsTupleLiteralConversion))
{
return CreateTupleLiteralConversion(syntax, (BoundTupleLiteral)source, conversion, isCast, destination, diagnostics);
}
......@@ -335,16 +335,20 @@ private BoundExpression CreateTupleLiteralConversion(CSharpSyntaxNode syntax, Bo
{
// We have a successful tuple conversion; rather than producing a separate conversion node
// which is a conversion on top of a tuple literal, tuple conversion is an element-wise conversion of arguments.
Debug.Assert(conversion.Kind == ConversionKind.ImplicitTupleLiteral || conversion.Kind == ConversionKind.ImplicitNullable);
Debug.Assert((conversion.Kind == ConversionKind.ImplicitNullable) == destination.IsNullableType());
TypeSymbol destinationWithoutNullable = conversion.Kind == ConversionKind.ImplicitNullable ?
destinationWithoutNullable = destination.GetNullableUnderlyingType() :
destination;
var destinationWithoutNullable = destination;
var conversionWithoutNullable = conversion;
NamedTypeSymbol targetType = (NamedTypeSymbol)destinationWithoutNullable;
if (conversion.Kind == ConversionKind.ImplicitNullable)
{
destinationWithoutNullable = destination.GetNullableUnderlyingType();
conversionWithoutNullable = conversion.UnderlyingConversions[0];
}
Debug.Assert(conversionWithoutNullable.IsTupleLiteralConversion);
NamedTypeSymbol targetType = (NamedTypeSymbol)destinationWithoutNullable;
if (targetType.IsTupleType)
{
var destTupleType = (TupleTypeSymbol)targetType;
......@@ -365,18 +369,7 @@ private BoundExpression CreateTupleLiteralConversion(CSharpSyntaxNode syntax, Bo
var argument = arguments[i];
var destType = targetElementTypes[i];
HashSet<DiagnosticInfo> useSiteDiagnostics = null;
Conversion elementConversion;
if (isCast)
{
elementConversion = this.Conversions.ClassifyConversionForCast(argument, destType, ref useSiteDiagnostics);
}
else
{
elementConversion = this.Conversions.ClassifyConversionFromExpression(argument, destType, ref useSiteDiagnostics);
}
diagnostics.Add(syntax, useSiteDiagnostics);
Conversion elementConversion = conversionWithoutNullable.UnderlyingConversions[i];
convertedArguments.Add(CreateConversion(argument.Syntax, argument, elementConversion, isCast, destType, diagnostics));
}
......@@ -386,20 +379,23 @@ private BoundExpression CreateTupleLiteralConversion(CSharpSyntaxNode syntax, Bo
convertedArguments.ToImmutableAndFree(),
targetType);
// We need to preserve any conversion that changes the type (even identity conversions),
// or that was explicitly written in code (so that GetSemanticInfo can find the syntax in the bound tree).
if (!isCast && targetType == destination)
if (sourceTuple.Type != destination)
{
return result;
// literal cast is applied to the literal
result = new BoundConversion(
sourceTuple.Syntax,
result,
conversion,
@checked: false,
explicitCastInCode: isCast,
constantValueOpt: ConstantValue.NotAvailable,
type: destination);
}
// if we have a nullable cast combined with a name/dynamic cast
// name/dynamic cast must happen before converting to nullable
if (conversion.Kind == ConversionKind.ImplicitNullable &&
destinationWithoutNullable != targetType)
// If we had a cast in the code, keep conversion in the tree.
// even though the literal is already converted to the target type.
if (isCast)
{
Debug.Assert(destinationWithoutNullable.Equals(targetType, ignoreDynamic: true));
result = new BoundConversion(
syntax,
result,
......@@ -407,19 +403,10 @@ private BoundExpression CreateTupleLiteralConversion(CSharpSyntaxNode syntax, Bo
@checked: false,
explicitCastInCode: isCast,
constantValueOpt: ConstantValue.NotAvailable,
type: destinationWithoutNullable)
{ WasCompilerGenerated = sourceTuple.WasCompilerGenerated };
type: destination);
}
return new BoundConversion(
syntax,
result,
conversion,
@checked: false,
explicitCastInCode: isCast,
constantValueOpt: ConstantValue.NotAvailable,
type: destination)
{ WasCompilerGenerated = sourceTuple.WasCompilerGenerated };
return result;
}
private static bool IsMethodGroupWithTypeOrValueReceiver(BoundNode node)
......
......@@ -6177,7 +6177,7 @@ private BoundExpression TryImplicitConversionToArrayIndex(BoundExpression expr,
return null;
}
BoundExpression result = CreateConversion(expr.Syntax, expr, conversion.ToArrayIndexConversion(), isCast: false, destination: type, diagnostics: attemptDiagnostics); // UNDONE: was cast?
BoundExpression result = CreateConversion(expr.Syntax, expr, conversion.WithArrayIndexConversion(true), isCast: false, destination: type, diagnostics: attemptDiagnostics); // UNDONE: was cast?
Debug.Assert(result != null); // If this ever fails (it shouldn't), then put a null-check around the diagnostics update.
diagnostics.AddRange(attemptDiagnostics);
......
......@@ -3322,7 +3322,7 @@ private BoundExpression BindNullCoalescingOperator(BinaryExpressionSyntax node,
// CreateConversion here to generate diagnostics.
if (isLeftNullable)
{
var conversion = new Conversion(ConversionKind.ExplicitNullable, new Conversion[] { leftConversion });
var conversion = Conversion.MakeNullableConversion(ConversionKind.ExplicitNullable, leftConversion);
var strippedLeftOperand = CreateConversion(leftOperand, conversion, optLeftType0, diagnostics);
leftOperand = CreateConversion(strippedLeftOperand, leftConversion, optRightType, diagnostics);
}
......
......@@ -14,15 +14,18 @@ namespace Microsoft.CodeAnalysis.CSharp
/// </summary>
public struct Conversion : IEquatable<Conversion>
{
private readonly MethodSymbol _conversionMethod;
private readonly UserDefinedConversionResult _conversionResult; //no effect on Equals/GetHashCode
private readonly ConversionKind _kind;
private readonly byte _flags;
private readonly MethodSymbol _conversionMethod;
private readonly Conversion[] _nestedConversionsOpt;
//no effect on Equals/GetHashCode
private readonly UserDefinedConversionResult _conversionResult;
private readonly byte _flags;
private const byte IsExtensionMethodMask = 1 << 0;
private const byte IsArrayIndexMask = 1 << 1;
private readonly Conversion[] _nestedConversionsOpt;
private Conversion(
ConversionKind kind,
......@@ -44,12 +47,6 @@ public struct Conversion : IEquatable<Conversion>
}
}
private Conversion(ConversionKind kind, bool isExtensionMethod, bool isArrayIndex, UserDefinedConversionResult conversionResult, MethodSymbol methodGroupConversionMethod)
: this(kind, isExtensionMethod, isArrayIndex, conversionResult, methodGroupConversionMethod, nestedConversions: null)
{
}
internal Conversion(UserDefinedConversionResult conversionResult, bool isImplicit)
: this()
{
......@@ -59,6 +56,18 @@ internal Conversion(UserDefinedConversionResult conversionResult, bool isImplici
_conversionResult = conversionResult;
}
// For the method group, lambda and anonymous method conversions
internal Conversion(ConversionKind kind, MethodSymbol methodGroupConversionMethod, bool isExtensionMethod)
: this()
{
this._kind = kind;
_conversionMethod = methodGroupConversionMethod;
if (isExtensionMethod)
{
_flags = IsExtensionMethodMask;
}
}
internal Conversion(ConversionKind kind, Conversion[] nestedConversions)
: this()
{
......@@ -66,15 +75,27 @@ internal Conversion(ConversionKind kind, Conversion[] nestedConversions)
this._nestedConversionsOpt = nestedConversions;
}
private Conversion(ConversionKind kind)
: this()
{
Debug.Assert(kind != ConversionKind.ImplicitNullable, "nullable conversion must have an underlying one");
Debug.Assert(kind != ConversionKind.ExplicitNullable, "nullable conversion must have an underlying one");
Debug.Assert(kind != ConversionKind.ImplicitTuple, "tuple conversion must have underlying conversions");
Debug.Assert(kind != ConversionKind.ImplicitTupleLiteral, "tuple conversion must have underlying conversions");
Debug.Assert(kind != ConversionKind.ExplicitTuple, "tuple conversion must have underlying conversions");
Debug.Assert(kind != ConversionKind.ExplicitTupleLiteral, "tuple conversion must have underlying conversions");
this._kind = kind;
}
internal Conversion WithConversionMethod(MethodSymbol methodGroupConversionMethod)
{
return new CSharp.Conversion(this.Kind, this.IsExtensionMethod, this.IsArrayIndex, this._conversionResult, methodGroupConversionMethod, this._nestedConversionsOpt);
}
private Conversion(ConversionKind kind)
: this()
internal Conversion WithArrayIndexConversion(bool isArrayIndex)
{
this._kind = kind;
return new Conversion(this.Kind, this.IsExtensionMethod, isArrayIndex, _conversionResult, _conversionMethod, _nestedConversionsOpt);
}
internal static Conversion GetTrivialConversion(ConversionKind kind)
......@@ -87,7 +108,6 @@ internal static Conversion GetTrivialConversion(ConversionKind kind)
case ConversionKind.ImplicitNumeric: return Conversion.ImplicitNumeric;
case ConversionKind.ImplicitReference: return Conversion.ImplicitReference;
case ConversionKind.ImplicitEnumeration: return Conversion.ImplicitEnumeration;
case ConversionKind.ImplicitTupleLiteral: return Conversion.ImplicitTupleLiteral;
case ConversionKind.AnonymousFunction: return Conversion.AnonymousFunction;
case ConversionKind.Boxing: return Conversion.Boxing;
case ConversionKind.NullLiteral: return Conversion.NullLiteral;
......@@ -115,7 +135,6 @@ internal static Conversion GetTrivialConversion(ConversionKind kind)
internal static Conversion ImplicitNumeric => new Conversion(ConversionKind.ImplicitNumeric);
internal static Conversion ImplicitReference => new Conversion(ConversionKind.ImplicitReference);
internal static Conversion ImplicitEnumeration => new Conversion(ConversionKind.ImplicitEnumeration);
internal static Conversion ImplicitTupleLiteral => new Conversion(ConversionKind.ImplicitTupleLiteral);
internal static Conversion AnonymousFunction => new Conversion(ConversionKind.AnonymousFunction);
internal static Conversion Boxing => new Conversion(ConversionKind.Boxing);
internal static Conversion NullLiteral => new Conversion(ConversionKind.NullLiteral);
......@@ -133,6 +152,49 @@ internal static Conversion GetTrivialConversion(ConversionKind kind)
internal static Conversion ExplicitDynamic => new Conversion(ConversionKind.ExplicitDynamic);
internal static Conversion InterpolatedString => new Conversion(ConversionKind.InterpolatedString);
// trivial conversions that could be underlying in nullable conversion
// NOTE: tuple conversions can be underlying as well, but they are not trivial
internal static Conversion[] IdentityUnderlying = new Conversion[] { Identity };
internal static Conversion[] ImplicitConstantUnderlying = new Conversion[] { ImplicitConstant };
internal static Conversion[] ImplicitNumericUnderlying = new Conversion[] { ImplicitNumeric };
internal static Conversion[] ExplicitNumericUnderlying = new Conversion[] { ExplicitNumeric };
internal static Conversion[] ExplicitEnumerationUnderlying = new Conversion[] { ExplicitEnumeration };
internal static Conversion[] PointerToIntegerUnderlying = new Conversion[] { PointerToInteger };
internal static Conversion MakeNullableConversion(ConversionKind kind, Conversion nestedConversion)
{
Debug.Assert(kind == ConversionKind.ImplicitNullable || kind == ConversionKind.ExplicitNullable);
Conversion[] nested;
switch (nestedConversion.Kind)
{
case ConversionKind.Identity:
nested = IdentityUnderlying;
break;
case ConversionKind.ImplicitConstant:
nested = ImplicitConstantUnderlying;
break;
case ConversionKind.ImplicitNumeric:
nested = ImplicitNumericUnderlying;
break;
case ConversionKind.ExplicitNumeric:
nested = ExplicitNumericUnderlying;
break;
case ConversionKind.ExplicitEnumeration:
nested = ExplicitEnumerationUnderlying;
break;
case ConversionKind.PointerToInteger:
nested = PointerToIntegerUnderlying;
break;
default:
Debug.Assert(false, "missing singleton for a nullable underlying conversion kind:" + nestedConversion.Kind);
nested = new Conversion[] { nestedConversion };
break;
}
return new Conversion(kind, nested);
}
internal ConversionKind Kind
{
get
......@@ -157,22 +219,19 @@ internal bool IsArrayIndex
}
}
internal Conversion[] UnderlyingConversions => _nestedConversionsOpt;
internal Conversion ToArrayIndexConversion()
internal Conversion[] UnderlyingConversions
{
return new Conversion(this.Kind, this.IsExtensionMethod, true, _conversionResult, _conversionMethod);
get
{
return _nestedConversionsOpt;
}
}
// For the method group, lambda and anonymous method conversions
internal Conversion(ConversionKind kind, MethodSymbol methodGroupConversionMethod, bool isExtensionMethod)
: this()
internal MethodSymbol Method
{
this._kind = kind;
_conversionMethod = methodGroupConversionMethod;
if (isExtensionMethod)
get
{
_flags = IsExtensionMethodMask;
return _conversionMethod ?? UserDefinedConversion;
}
}
......@@ -321,6 +380,28 @@ public bool IsNullable
}
}
/// <summary>
/// Returns true if the conversion is an implicit tuple literal conversion or explicit tuple literal conversion.
/// </summary>
public bool IsTupleLiteralConversion
{
get
{
return Kind == ConversionKind.ImplicitTupleLiteral || Kind == ConversionKind.ExplicitTupleLiteral;
}
}
/// <summary>
/// Returns true if the conversion is an implicit tuple conversion or explicit tuple conversion.
/// </summary>
public bool IsTupleConversion
{
get
{
return Kind == ConversionKind.ImplicitTuple || Kind == ConversionKind.ExplicitTuple;
}
}
/// <summary>
/// Returns true if the conversion is an implicit reference conversion or explicit reference conversion.
/// </summary>
......@@ -447,17 +528,6 @@ public bool IsMethodGroup
}
}
/// <summary>
/// Returns true if the conversion is an implicit tuple conversion.
/// </summary>
public bool IsTupleLiteral
{
get
{
return Kind == ConversionKind.ImplicitTupleLiteral;
}
}
/// <summary>
/// Returns true if the conversion is a pointer conversion
/// </summary>
......@@ -503,14 +573,6 @@ public bool IsIntPtr
}
}
internal MethodSymbol Method
{
get
{
return _conversionMethod ?? UserDefinedConversion;
}
}
/// <summary>
/// Returns the method used to create the delegate for a method group conversion if <see cref="IsMethodGroup"/> is true
/// or the method used to perform the conversion for a user-defined conversion if <see cref="IsUserDefined"/> is true.
......
......@@ -14,6 +14,7 @@ internal enum ConversionKind : byte
ImplicitEnumeration,
ImplicitTupleLiteral,
ImplicitTuple,
ExplicitTupleLiteral,
ExplicitTuple,
ImplicitNullable,
NullLiteral,
......
......@@ -44,6 +44,7 @@ public static bool IsImplicitConversion(this ConversionKind conversionKind)
case ConversionKind.ExplicitNumeric:
case ConversionKind.ExplicitTuple:
case ConversionKind.ExplicitTupleLiteral:
case ConversionKind.ExplicitEnumeration:
case ConversionKind.ExplicitNullable:
case ConversionKind.ExplicitReference:
......
......@@ -119,6 +119,7 @@ private static bool ExplicitConversionMayDifferFromImplicit(Conversion implicitC
case ConversionKind.ImplicitUserDefined:
case ConversionKind.ImplicitDynamic:
case ConversionKind.ImplicitTuple:
case ConversionKind.ImplicitTupleLiteral:
case ConversionKind.ImplicitNullable:
return true;
......@@ -167,7 +168,7 @@ private Conversion ClassifyImplicitBuiltInConversionFromExpression(BoundExpressi
break;
case BoundKind.TupleLiteral:
var tupleConversion = ClassifyImplicitTupleLiteralConversion(sourceExpression, destination, ref useSiteDiagnostics);
var tupleConversion = ClassifyImplicitTupleLiteralConversion((BoundTupleLiteral)sourceExpression, destination, ref useSiteDiagnostics);
if (tupleConversion.Exists)
{
return tupleConversion;
......@@ -308,15 +309,14 @@ private static Conversion ClassifyImplicitConstantExpressionConversion(BoundExpr
if (nt.OriginalDefinition.GetSpecialTypeSafe() == SpecialType.System_Nullable_T &&
HasImplicitConstantExpressionConversion(source, nt.TypeArgumentsNoUseSiteDiagnostics[0]))
{
//TODO: vsadov singleton arrays.
return new Conversion(ConversionKind.ImplicitNullable, new Conversion[] { Conversion.ImplicitConstant });
return new Conversion(ConversionKind.ImplicitNullable, Conversion.ImplicitConstantUnderlying);
}
}
return Conversion.NoConversion;
}
private Conversion ClassifyImplicitTupleLiteralConversion(BoundExpression source, TypeSymbol destination, ref HashSet<DiagnosticInfo> useSiteDiagnostics)
private Conversion ClassifyImplicitTupleLiteralConversion(BoundTupleLiteral source, TypeSymbol destination, ref HashSet<DiagnosticInfo> useSiteDiagnostics)
{
var tupleConversion = GetImplicitTupleLiteralConversion(source, destination, ref useSiteDiagnostics);
if (tupleConversion.Exists)
......@@ -345,6 +345,35 @@ private Conversion ClassifyImplicitTupleLiteralConversion(BoundExpression source
return Conversion.NoConversion;
}
private Conversion ClassifyExplicitTupleLiteralConversion(BoundTupleLiteral source, TypeSymbol destination, ref HashSet<DiagnosticInfo> useSiteDiagnostics, bool forCast)
{
var tupleConversion = GetExplicitTupleLiteralConversion(source, destination, ref useSiteDiagnostics, forCast);
if (tupleConversion.Exists)
{
return tupleConversion;
}
// strip nullable from the destination
//
// the following should work and it is an ExplicitNullable conversion
// var x = ((byte, string)?)(1,2);
if (destination.Kind == SymbolKind.NamedType)
{
var nt = (NamedTypeSymbol)destination;
if (nt.OriginalDefinition.GetSpecialTypeSafe() == SpecialType.System_Nullable_T)
{
var underlyingTupleConversion = GetExplicitTupleLiteralConversion(source, nt.TypeArgumentsNoUseSiteDiagnostics[0], ref useSiteDiagnostics, forCast);
if (underlyingTupleConversion.Exists)
{
return new Conversion(ConversionKind.ExplicitNullable, new Conversion[] { underlyingTupleConversion });
}
}
}
return Conversion.NoConversion;
}
internal static bool HasImplicitConstantExpressionConversion(BoundExpression source, TypeSymbol destination)
{
var constantValue = source.ConstantValue;
......@@ -404,6 +433,18 @@ internal static bool HasImplicitConstantExpressionConversion(BoundExpression sou
Debug.Assert(sourceExpression == null || (object)sourceExpression.Type == (object)source);
Debug.Assert((object)destination != null);
// NB: need to check for explicit tuple literal conversion before checking for explicit conversion from type
// The same literal may have both explicit typle conversion and explicit tuple literal conversion to the target type.
// They are, however, observably different conversions via the order of argument evaluations and element-wise conversions
if (sourceExpression?.Kind == BoundKind.TupleLiteral)
{
Conversion tupleConversion = ClassifyExplicitTupleLiteralConversion((BoundTupleLiteral)sourceExpression, destination, ref useSiteDiagnostics, forCast);
if (tupleConversion.Exists)
{
return tupleConversion;
}
}
if ((object)source != null)
{
// Try using the short-circuit "fast-conversion" path.
......@@ -414,7 +455,7 @@ internal static bool HasImplicitConstantExpressionConversion(BoundExpression sou
}
else
{
Conversion conversion = ClassifyExplicitBuiltInOnlyConversion(source, destination, ref useSiteDiagnostics, forCast);
var conversion = ClassifyExplicitBuiltInOnlyConversion(source, destination, ref useSiteDiagnostics, forCast);
if (conversion.Exists)
{
return conversion;
......@@ -719,16 +760,40 @@ public override Conversion GetMethodGroupConversion(BoundMethodGroup source, Typ
return conversion;
}
protected override Conversion GetImplicitTupleLiteralConversion(BoundExpression source, TypeSymbol destination, ref HashSet<DiagnosticInfo> useSiteDiagnostics)
protected override Conversion GetImplicitTupleLiteralConversion(BoundTupleLiteral source, TypeSymbol destination, ref HashSet<DiagnosticInfo> useSiteDiagnostics)
{
if (source.Kind != BoundKind.TupleLiteral)
var arguments = source.Arguments;
// check if the type is actually compatible type for a tuple of given cardinality
if (!destination.IsTupleOrCompatibleWithTupleOfCardinality(arguments.Length))
{
// source must be a tuple literal with no conversions
return Conversion.NoConversion;
}
var tupleExpression = (BoundTupleLiteral)source;
var arguments = tupleExpression.Arguments;
ImmutableArray<TypeSymbol> targetElementTypes = destination.GetElementTypesOfTupleOrCompatible();
Debug.Assert(arguments.Length == targetElementTypes.Length);
// check arguments against flattened list of target element types
var argumentConversions = ArrayBuilder<Conversion>.GetInstance(arguments.Length);
for (int i = 0; i < arguments.Length; i++)
{
var argument = arguments[i];
var result = ClassifyImplicitConversionFromExpression(argument, targetElementTypes[i], ref useSiteDiagnostics);
if (!result.Exists)
{
argumentConversions.Free();
return Conversion.NoConversion;
}
argumentConversions.Add(result);
}
return new Conversion(ConversionKind.ImplicitTupleLiteral, argumentConversions.ToArrayAndFree());
}
protected override Conversion GetExplicitTupleLiteralConversion(BoundTupleLiteral source, TypeSymbol destination, ref HashSet<DiagnosticInfo> useSiteDiagnostics, bool forCast)
{
var arguments = source.Arguments;
// check if the type is actually compatible type for a tuple of given cardinality
if (!destination.IsTupleOrCompatibleWithTupleOfCardinality(arguments.Length))
......@@ -744,7 +809,10 @@ protected override Conversion GetImplicitTupleLiteralConversion(BoundExpression
for (int i = 0; i < arguments.Length; i++)
{
var argument = arguments[i];
var result = ClassifyImplicitConversionFromExpression(argument, targetElementTypes[i], ref useSiteDiagnostics);
var result = forCast ?
ClassifyConversionForCast(argument, targetElementTypes[i], ref useSiteDiagnostics) :
ClassifyConversionFromExpression(argument, targetElementTypes[i], ref useSiteDiagnostics);
if (!result.Exists)
{
argumentConversions.Free();
......@@ -754,7 +822,7 @@ protected override Conversion GetImplicitTupleLiteralConversion(BoundExpression
argumentConversions.Add(result);
}
return new Conversion(ConversionKind.ImplicitTupleLiteral, argumentConversions.ToArrayAndFree());
return new Conversion(ConversionKind.ExplicitTupleLiteral, argumentConversions.ToArrayAndFree());
}
protected override Conversion GetInterpolatedStringConversion(BoundInterpolatedString source, TypeSymbol destination, ref HashSet<DiagnosticInfo> useSiteDiagnostics)
......
......@@ -31,7 +31,9 @@ protected ConversionsBase(AssemblySymbol corLibrary, int currentRecursionDepth)
protected abstract Conversion GetInterpolatedStringConversion(BoundInterpolatedString source, TypeSymbol destination, ref HashSet<DiagnosticInfo> useSiteDiagnostics);
protected abstract Conversion GetImplicitTupleLiteralConversion(BoundExpression source, TypeSymbol destination, ref HashSet<DiagnosticInfo> useSiteDiagnostics);
protected abstract Conversion GetImplicitTupleLiteralConversion(BoundTupleLiteral source, TypeSymbol destination, ref HashSet<DiagnosticInfo> useSiteDiagnostics);
protected abstract Conversion GetExplicitTupleLiteralConversion(BoundTupleLiteral source, TypeSymbol destination, ref HashSet<DiagnosticInfo> useSiteDiagnostics, bool forCast);
/// <summary>
/// Attempt a quick classification of builtin conversions. As result of "no conversion"
......@@ -46,8 +48,7 @@ public static Conversion FastClassifyConversion(TypeSymbol source, TypeSymbol ta
return Conversion.GetTrivialConversion(convKind);
}
// TODO: vsadov singleton trivial conv arrays
return new Conversion(convKind, new Conversion[] { FastClassifyConversion(source.StrippedType(), target.StrippedType())});
return Conversion.MakeNullableConversion(convKind, FastClassifyConversion(source.StrippedType(), target.StrippedType()));
}
/// <summary>
......@@ -323,7 +324,7 @@ private Conversion DeriveStandardExplicitFromOppositeStandardImplicitConversion(
// the opposite underlying conversion may not exist
// for example if underlying conversion is implicit tuple
impliedExplicitConversion = underlyingConversion.Exists ?
new Conversion(ConversionKind.ExplicitNullable, new Conversion[] { underlyingConversion }) :
Conversion.MakeNullableConversion(ConversionKind.ExplicitNullable, underlyingConversion) :
Conversion.NoConversion;
break;
......@@ -953,14 +954,12 @@ private Conversion ClassifyImplicitNullableConversion(TypeSymbol source, TypeSym
if (HasIdentityConversion(unwrappedSource, unwrappedDestination))
{
//TODO: vsadov array singletons
return new Conversion(ConversionKind.ImplicitNullable, new Conversion[] {Conversion.Identity});
return new Conversion(ConversionKind.ImplicitNullable, Conversion.IdentityUnderlying);
}
if (HasImplicitNumericConversion(unwrappedSource, unwrappedDestination))
{
//TODO: vsadov array singletons
return new Conversion(ConversionKind.ImplicitNullable, new Conversion[] { Conversion.ImplicitNumeric });
return new Conversion(ConversionKind.ImplicitNullable, Conversion.ImplicitNumericUnderlying);
}
var tupleConversion = ClassifyImplicitTupleConversion(unwrappedSource, unwrappedDestination, ref useSiteDiagnostics);
......@@ -1054,20 +1053,17 @@ private Conversion ClassifyExplicitNullableConversion(TypeSymbol source, TypeSym
if (HasIdentityConversion(unwrappedSource, unwrappedDestination))
{
//TODO: vsadov singleton arrays
return new Conversion(ConversionKind.ExplicitNullable, new[] { Conversion.Identity });
return new Conversion(ConversionKind.ExplicitNullable, Conversion.IdentityUnderlying);
}
if (HasImplicitNumericConversion(unwrappedSource, unwrappedDestination))
{
//TODO: vsadov singleton arrays
return new Conversion(ConversionKind.ExplicitNullable, new[] { Conversion.ImplicitNumeric });
return new Conversion(ConversionKind.ExplicitNullable, Conversion.ImplicitNumericUnderlying);
}
if (HasExplicitNumericConversion(unwrappedSource, unwrappedDestination))
{
//TODO: vsadov singleton arrays
return new Conversion(ConversionKind.ExplicitNullable, new[] { Conversion.ExplicitNumeric });
return new Conversion(ConversionKind.ExplicitNullable, Conversion.ExplicitNumericUnderlying);
}
var tupleConversion = ClassifyExplicitTupleConversion(unwrappedSource, unwrappedDestination, ref useSiteDiagnostics, forCast);
......@@ -1078,14 +1074,12 @@ private Conversion ClassifyExplicitNullableConversion(TypeSymbol source, TypeSym
if (HasExplicitEnumerationConversion(unwrappedSource, unwrappedDestination))
{
//TODO: vsadov singleton arrays
return new Conversion(ConversionKind.ExplicitNullable, new[] { Conversion.ExplicitEnumeration });
return new Conversion(ConversionKind.ExplicitNullable, Conversion.ExplicitEnumerationUnderlying);
}
if (HasPointerToIntegerConversion(unwrappedSource, unwrappedDestination))
{
//TODO: vsadov singleton arrays
return new Conversion(ConversionKind.ExplicitNullable, new[] { Conversion.PointerToInteger });
return new Conversion(ConversionKind.ExplicitNullable, Conversion.PointerToIntegerUnderlying);
}
return Conversion.NoConversion;
......
......@@ -38,7 +38,13 @@ protected override Conversion GetInterpolatedStringConversion(BoundInterpolatedS
throw ExceptionUtilities.Unreachable;
}
protected override Conversion GetImplicitTupleLiteralConversion(BoundExpression source, TypeSymbol destination, ref HashSet<DiagnosticInfo> useSiteDiagnostics)
protected override Conversion GetImplicitTupleLiteralConversion(BoundTupleLiteral source, TypeSymbol destination, ref HashSet<DiagnosticInfo> useSiteDiagnostics)
{
// Tuple conversions require a Binder, recursively
throw ExceptionUtilities.Unreachable;
}
protected override Conversion GetExplicitTupleLiteralConversion(BoundTupleLiteral source, TypeSymbol destination, ref HashSet<DiagnosticInfo> useSiteDiagnostics, bool forCast)
{
// Tuple conversions require a Binder, recursively
throw ExceptionUtilities.Unreachable;
......
......@@ -602,6 +602,7 @@ private static bool IsEncompassingImplicitConversionKind(ConversionKind kind)
case ConversionKind.ImplicitTuple:
return true;
case ConversionKind.ExplicitTupleLiteral:
case ConversionKind.ExplicitTuple:
return false;
......
......@@ -710,6 +710,7 @@ Semantics.ConversionKind IConversionExpression.ConversionKind
case CSharp.ConversionKind.ImplicitEnumeration:
case CSharp.ConversionKind.ImplicitTupleLiteral:
case CSharp.ConversionKind.ImplicitTuple:
case CSharp.ConversionKind.ExplicitTupleLiteral:
case CSharp.ConversionKind.ExplicitTuple:
case CSharp.ConversionKind.ExplicitNullable:
case CSharp.ConversionKind.ImplicitNullable:
......
......@@ -71,6 +71,7 @@ private void EmitConversion(BoundConversion conversion)
case ConversionKind.MethodGroup:
case ConversionKind.ImplicitTupleLiteral:
case ConversionKind.ImplicitTuple:
case ConversionKind.ExplicitTupleLiteral:
case ConversionKind.ExplicitTuple:
case ConversionKind.ImplicitDynamic:
case ConversionKind.ExplicitDynamic:
......
......@@ -1852,18 +1852,13 @@ private static bool IsUserDefinedTrueOrFalse(BoundUnaryOperator @operator)
type = null;
conversion = new Conversion(ConversionKind.AnonymousFunction, lambda.Symbol, false);
}
else if (highestBoundExpr?.Kind == BoundKind.ConvertedTupleLiteral)
else if ((highestBoundExpr as BoundConversion)?.Conversion.IsTupleLiteralConversion == true)
{
Debug.Assert(highestBoundExpr == boundExpr);
var convertedLiteral = (BoundConvertedTupleLiteral)highestBoundExpr;
// The bound tree always fully binds tuple literals. From the language point of
// view, however, converted tuple literals represent tuple conversions
// from tuple literal expressions which may or may not have types
type = convertedLiteral.NaturalTypeOpt;
convertedType = convertedLiteral.Type;
conversion = convertedType.Equals(type, ignoreDynamic: true) ?
Conversion.Identity :
Conversion.ImplicitTupleLiteral;
var tupleLiteralConversion = (BoundConversion)highestBoundExpr;
var convertedTuple = (BoundConvertedTupleLiteral)tupleLiteralConversion.Operand;
type = convertedTuple.NaturalTypeOpt;
convertedType = tupleLiteralConversion.Type;
conversion = tupleLiteralConversion.Conversion;
}
else if (highestBoundExpr != null && highestBoundExpr != boundExpr && highestBoundExpr.HasExpressionType())
{
......
......@@ -227,14 +227,12 @@ private static bool IsFloatPointExpressionOfUnknownPrecision(BoundExpression rew
break;
case ConversionKind.ImplicitTupleLiteral:
case ConversionKind.ExplicitTupleLiteral:
{
// we keep ImplicitTuple conversions in the tree
// for the purpose of semantic model (for example when they are casts in the source)
// we keep tuple literal conversions in the tree for the purpose of semantic model (for example when they are casts in the source)
// for the purpose of lowering/codegeneration thay are identity conversions.
Debug.Assert(rewrittenOperand.Type.Equals(rewrittenType, ignoreDynamic: true));
conversion = Conversion.Identity;
break;
return rewrittenOperand;
}
case ConversionKind.ImplicitEnumeration:
......@@ -253,8 +251,7 @@ private static bool IsFloatPointExpressionOfUnknownPrecision(BoundExpression rew
constantValueOpt,
rewrittenType.GetNullableUnderlyingType());
//TODO: vsadov singletons
var outerConversion = new Conversion(ConversionKind.ImplicitNullable, new Conversion[] { Conversion.Identity });
var outerConversion = new Conversion(ConversionKind.ImplicitNullable, Conversion.IdentityUnderlying);
return MakeConversionNode(
oldNode,
syntax,
......
Microsoft.CodeAnalysis.CSharp.CSharpParseOptions.CSharpParseOptions(Microsoft.CodeAnalysis.CSharp.LanguageVersion languageVersion = Microsoft.CodeAnalysis.CSharp.LanguageVersion.CSharp7, Microsoft.CodeAnalysis.DocumentationMode documentationMode = Microsoft.CodeAnalysis.DocumentationMode.Parse, Microsoft.CodeAnalysis.SourceCodeKind kind = Microsoft.CodeAnalysis.SourceCodeKind.Regular, System.Collections.Generic.IEnumerable<string> preprocessorSymbols = null) -> void
Microsoft.CodeAnalysis.CSharp.Conversion.IsTupleLiteral.get -> bool
Microsoft.CodeAnalysis.CSharp.Conversion.IsTupleConversion.get -> bool
Microsoft.CodeAnalysis.CSharp.Conversion.IsTupleLiteralConversion.get -> bool
Microsoft.CodeAnalysis.CSharp.LanguageVersion.CSharp7 = 7 -> Microsoft.CodeAnalysis.CSharp.LanguageVersion
Microsoft.CodeAnalysis.CSharp.Syntax.ArgumentSyntax.Declaration.get -> Microsoft.CodeAnalysis.CSharp.Syntax.VariableDeclarationSyntax
Microsoft.CodeAnalysis.CSharp.Syntax.ArgumentSyntax.Update(Microsoft.CodeAnalysis.CSharp.Syntax.NameColonSyntax nameColon, Microsoft.CodeAnalysis.SyntaxToken outKeyword, Microsoft.CodeAnalysis.CSharp.Syntax.VariableDeclarationSyntax declaration) -> Microsoft.CodeAnalysis.CSharp.Syntax.ArgumentSyntax
......
......@@ -4426,21 +4426,21 @@ static void Main()
comp.VerifyIL("C.Main()",
@"
{
// Code size 40 (0x28)
.maxstack 2
// Code size 41 (0x29)
.maxstack 3
.locals init (System.ValueTuple<byte, string> V_0)
IL_0000: ldc.i4.1
IL_0001: ldstr ""hello""
IL_0006: newobj ""System.ValueTuple<byte, string>..ctor(byte, string)""
IL_000b: stloc.0
IL_000c: ldloc.0
IL_000d: ldfld ""byte System.ValueTuple<byte, string>.Item1""
IL_0012: ldloc.0
IL_0013: ldfld ""string System.ValueTuple<byte, string>.Item2""
IL_0018: newobj ""System.ValueTuple<short, string>..ctor(short, string)""
IL_001d: box ""System.ValueTuple<short, string>""
IL_0022: call ""void System.Console.WriteLine(object)""
IL_0027: ret
IL_0000: ldloca.s V_0
IL_0002: ldc.i4.1
IL_0003: ldstr ""hello""
IL_0008: call ""System.ValueTuple<byte, string>..ctor(byte, string)""
IL_000d: ldloc.0
IL_000e: ldfld ""byte System.ValueTuple<byte, string>.Item1""
IL_0013: ldloc.0
IL_0014: ldfld ""string System.ValueTuple<byte, string>.Item2""
IL_0019: newobj ""System.ValueTuple<short, string>..ctor(short, string)""
IL_001e: box ""System.ValueTuple<short, string>""
IL_0023: call ""void System.Console.WriteLine(object)""
IL_0028: ret
}
"); ;
}
......@@ -5343,8 +5343,8 @@ static void Main()
Assert.Equal(@"(e: 1, f: ""hello"")", node.ToString());
Assert.Equal("(System.Int32 e, System.String f)", model.GetTypeInfo(node).Type.ToTestDisplayString());
Assert.Equal("(System.Int16 e, System.String f)", model.GetTypeInfo(node).ConvertedType.ToTestDisplayString());
Assert.Equal(Conversion.ImplicitTupleLiteral, model.GetConversion(node));
Assert.Equal("(System.Int16 c, System.String d)", model.GetTypeInfo(node).ConvertedType.ToTestDisplayString());
Assert.Equal(ConversionKind.ExplicitTupleLiteral, model.GetConversion(node).Kind);
// semantic model returns topmost conversion from the sequence of conversions for
// ((short c, string d))(e: 1, f: ""hello"")
......@@ -5605,7 +5605,7 @@ static void Main()
Assert.Equal(@"(e: 1, f: ""hello"")", node.ToString());
Assert.Equal("(System.Int32 e, System.String f)", model.GetTypeInfo(node).Type.ToTestDisplayString());
Assert.Equal("(System.Int16 a, System.String b)", model.GetTypeInfo(node).ConvertedType.ToTestDisplayString());
Assert.Equal(Conversion.ImplicitTupleLiteral, model.GetConversion(node));
Assert.Equal(ConversionKind.ImplicitTupleLiteral, model.GetConversion(node).Kind);
var x = nodes.OfType<VariableDeclaratorSyntax>().First();
Assert.Equal("(System.Int16 a, System.String b) x", model.GetDeclaredSymbol(x).ToTestDisplayString());
......@@ -5642,8 +5642,8 @@ static void Main()
Assert.Equal(@"(e: 1, f: ""hello"")", node.ToString());
Assert.Equal("(System.Int32 e, System.String f)", model.GetTypeInfo(node).Type.ToTestDisplayString());
Assert.Equal("(System.Int16 e, System.String f)", model.GetTypeInfo(node).ConvertedType.ToTestDisplayString());
Assert.Equal(Conversion.ImplicitTupleLiteral, model.GetConversion(node));
Assert.Equal("(System.Int16 c, System.String d)", model.GetTypeInfo(node).ConvertedType.ToTestDisplayString());
Assert.Equal(ConversionKind.ExplicitTupleLiteral, model.GetConversion(node).Kind);
// semantic model returns topmost conversion from the sequence of conversions for
// ((short c, string d))(e: 1, f: ""hello"")
......@@ -5678,7 +5678,7 @@ static void Main()
Assert.Equal(@"(e: 1, f: null)", node.ToString());
Assert.Null(model.GetTypeInfo(node).Type);
Assert.Equal("(System.Int16 a, System.String b)", model.GetTypeInfo(node).ConvertedType.ToTestDisplayString());
Assert.Equal(Conversion.ImplicitTupleLiteral, model.GetConversion(node));
Assert.Equal(ConversionKind.ImplicitTupleLiteral, model.GetConversion(node).Kind);
var x = nodes.OfType<VariableDeclaratorSyntax>().First();
Assert.Equal("(System.Int16 a, System.String b) x", model.GetDeclaredSymbol(x).ToTestDisplayString());
......@@ -5715,8 +5715,8 @@ static void Main()
Assert.Equal(@"(e: 1, f: null)", node.ToString());
Assert.Null(model.GetTypeInfo(node).Type);
Assert.Equal("(System.Int16 e, System.String f)", model.GetTypeInfo(node).ConvertedType.ToTestDisplayString());
Assert.Equal(Conversion.ImplicitTupleLiteral, model.GetConversion(node));
Assert.Equal("(System.Int16 c, System.String d)", model.GetTypeInfo(node).ConvertedType.ToTestDisplayString());
Assert.Equal(ConversionKind.ExplicitTupleLiteral, model.GetConversion(node).Kind);
// semantic model returns topmost conversion from the sequence of conversions for
// ((short c, string d))(e: 1, f: null)
......@@ -5767,7 +5767,7 @@ public C1(string arg)
Assert.Equal(@"(e: 1, f: new C1(""qq""))", node.ToString());
Assert.Equal("(System.Int32 e, C.C1 f)", model.GetTypeInfo(node).Type.ToTestDisplayString());
Assert.Equal("(System.Int16 a, System.String b)", model.GetTypeInfo(node).ConvertedType.ToTestDisplayString());
Assert.Equal(Conversion.ImplicitTupleLiteral, model.GetConversion(node));
Assert.Equal(ConversionKind.ImplicitTupleLiteral, model.GetConversion(node).Kind);
var x = nodes.OfType<VariableDeclaratorSyntax>().First();
Assert.Equal("(System.Int16 a, System.String b) x", model.GetDeclaredSymbol(x).ToTestDisplayString());
......@@ -5813,8 +5813,8 @@ public C1(string arg)
Assert.Equal(@"(e: 1, f: new C1(""qq""))", node.ToString());
Assert.Equal("(System.Int32 e, C.C1 f)", model.GetTypeInfo(node).Type.ToTestDisplayString());
Assert.Equal("(System.Int16 e, System.String f)", model.GetTypeInfo(node).ConvertedType.ToTestDisplayString());
Assert.Equal(Conversion.ImplicitTupleLiteral, model.GetConversion(node));
Assert.Equal("(System.Int16 c, System.String d)", model.GetTypeInfo(node).ConvertedType.ToTestDisplayString());
Assert.Equal(ConversionKind.ExplicitTupleLiteral, model.GetConversion(node).Kind);
// semantic model returns topmost conversion from the sequence of conversions for
// ((short c, string d))(e: 1, f: null)
......@@ -11923,14 +11923,14 @@ static void Main()
Assert.Equal(@"(1, ""hello"")", n1.ToString());
Assert.Equal("(System.Int32, System.String)", model.GetTypeInfo(n1).Type.ToTestDisplayString());
Assert.Equal("(System.Int16, System.String)", model.GetTypeInfo(n1).ConvertedType.ToTestDisplayString());
Assert.Equal(Conversion.ImplicitTupleLiteral, model.GetConversion(n1));
Assert.Equal(ConversionKind.ImplicitTupleLiteral, model.GetConversion(n1).Kind);
var n2 = nodes.OfType<TupleExpressionSyntax>().ElementAt(1);
Assert.Equal(@"(2, null)", n2.ToString());
Assert.Null(model.GetTypeInfo(n2).Type);
Assert.Equal("(System.Int16, System.String)", model.GetTypeInfo(n2).ConvertedType.ToTestDisplayString());
Assert.Equal(Conversion.ImplicitTupleLiteral, model.GetConversion(n2));
Assert.Equal(ConversionKind.ImplicitTupleLiteral, model.GetConversion(n2).Kind);
var n3 = nodes.OfType<LiteralExpressionSyntax>().ElementAt(4);
......@@ -12852,10 +12852,9 @@ static void Main()
comp.VerifyIL("C.Main", @"
{
// Code size 86 (0x56)
.maxstack 3
.locals init (System.ValueTuple<int, int> V_0,
System.ValueTuple<long, object> V_1)
// Code size 65 (0x41)
.maxstack 2
.locals init (System.ValueTuple<int, int> V_0)
IL_0000: ldc.i4.1
IL_0001: ldc.i4.2
IL_0002: newobj ""System.ValueTuple<int, int>..ctor(int, int)""
......@@ -12869,22 +12868,14 @@ .maxstack 3
IL_0016: newobj ""System.ValueTuple<byte, byte>..ctor(byte, byte)""
IL_001b: box ""System.ValueTuple<byte, byte>""
IL_0020: call ""void System.Console.WriteLine(object)""
IL_0025: ldloca.s V_1
IL_0027: ldc.i4.3
IL_0028: conv.i8
IL_0029: ldc.i4.4
IL_002a: box ""int""
IL_002f: call ""System.ValueTuple<long, object>..ctor(long, object)""
IL_0034: ldloc.1
IL_0035: ldfld ""long System.ValueTuple<long, object>.Item1""
IL_003a: conv.i4
IL_003b: ldloc.1
IL_003c: ldfld ""object System.ValueTuple<long, object>.Item2""
IL_0041: unbox.any ""int""
IL_0046: newobj ""System.ValueTuple<int, int>..ctor(int, int)""
IL_004b: box ""System.ValueTuple<int, int>""
IL_0050: call ""void System.Console.WriteLine(object)""
IL_0055: ret
IL_0025: ldc.i4.3
IL_0026: ldc.i4.4
IL_0027: box ""int""
IL_002c: unbox.any ""int""
IL_0031: newobj ""System.ValueTuple<int, int>..ctor(int, int)""
IL_0036: box ""System.ValueTuple<int, int>""
IL_003b: call ""void System.Console.WriteLine(object)""
IL_0040: ret
}
"); ;
}
......@@ -12931,10 +12922,9 @@ public override string ToString()
comp.VerifyIL("C.Main", @"
{
// Code size 92 (0x5c)
.maxstack 3
.locals init (System.ValueTuple<int, int> V_0,
System.ValueTuple<object, int> V_1)
// Code size 68 (0x44)
.maxstack 2
.locals init (System.ValueTuple<int, int> V_0)
IL_0000: ldc.i4.1
IL_0001: ldc.i4.2
IL_0002: newobj ""System.ValueTuple<int, int>..ctor(int, int)""
......@@ -12948,20 +12938,13 @@ .maxstack 3
IL_001e: newobj ""System.ValueTuple<C.C2, C.C2>..ctor(C.C2, C.C2)""
IL_0023: box ""System.ValueTuple<C.C2, C.C2>""
IL_0028: call ""void System.Console.WriteLine(object)""
IL_002d: ldloca.s V_1
IL_002f: ldnull
IL_0030: ldc.i4.4
IL_0031: call ""System.ValueTuple<object, int>..ctor(object, int)""
IL_0036: ldloc.1
IL_0037: ldfld ""object System.ValueTuple<object, int>.Item1""
IL_003c: castclass ""C.C2""
IL_0041: ldloc.1
IL_0042: ldfld ""int System.ValueTuple<object, int>.Item2""
IL_0047: call ""C.C2 C.C2.op_Explicit(int)""
IL_004c: newobj ""System.ValueTuple<C.C2, C.C2>..ctor(C.C2, C.C2)""
IL_0051: box ""System.ValueTuple<C.C2, C.C2>""
IL_0056: call ""void System.Console.WriteLine(object)""
IL_005b: ret
IL_002d: ldnull
IL_002e: ldc.i4.4
IL_002f: call ""C.C2 C.C2.op_Explicit(int)""
IL_0034: newobj ""System.ValueTuple<C.C2, C.C2>..ctor(C.C2, C.C2)""
IL_0039: box ""System.ValueTuple<C.C2, C.C2>""
IL_003e: call ""void System.Console.WriteLine(object)""
IL_0043: ret
}
"); ;
}
......@@ -12971,7 +12954,6 @@ .maxstack 3
public void ExplicitConversionsSimple02Expr()
{
var source = @"
using System;
class C
{
static void Main()
......@@ -12980,8 +12962,6 @@ static void Main()
var y = ((C2, C2))x;
System.Console.WriteLine(y);
// this is likely to become legal upon resolution of https://github.com/dotnet/roslyn/issues/11804
// for now just check that we can handle this case.
var z = ((C2, C2))(null, 4);
System.Console.WriteLine(z);
......@@ -13006,13 +12986,37 @@ public override string ToString()
}
" + trivial2uple;
var comp = CreateCompilationWithMscorlib(source, parseOptions: TestOptions.Regular, assemblyName: "ImplicitConversions06Err");
comp.VerifyEmitDiagnostics(
// (13,17): error CS0030: Cannot convert type '(<null>, int)' to '(C.C2, C.C2)'
// var z = ((C2, C2))(null, 4);
Diagnostic(ErrorCode.ERR_NoExplicitConv, "((C2, C2))(null, 4)").WithArguments("(<null>, int)", "(C.C2, C.C2)").WithLocation(13, 17)
var comp = CompileAndVerify(source, parseOptions: TestOptions.Regular, expectedOutput: @"
{2, 3}
{, 5}");
);
comp.VerifyIL("C.Main", @"
{
// Code size 68 (0x44)
.maxstack 2
.locals init (System.ValueTuple<int, int> V_0)
IL_0000: ldc.i4.1
IL_0001: ldc.i4.2
IL_0002: newobj ""System.ValueTuple<int, int>..ctor(int, int)""
IL_0007: stloc.0
IL_0008: ldloc.0
IL_0009: ldfld ""int System.ValueTuple<int, int>.Item1""
IL_000e: call ""C.C2 C.C2.op_Explicit(int)""
IL_0013: ldloc.0
IL_0014: ldfld ""int System.ValueTuple<int, int>.Item2""
IL_0019: call ""C.C2 C.C2.op_Explicit(int)""
IL_001e: newobj ""System.ValueTuple<C.C2, C.C2>..ctor(C.C2, C.C2)""
IL_0023: box ""System.ValueTuple<C.C2, C.C2>""
IL_0028: call ""void System.Console.WriteLine(object)""
IL_002d: ldnull
IL_002e: ldc.i4.4
IL_002f: call ""C.C2 C.C2.op_Explicit(int)""
IL_0034: newobj ""System.ValueTuple<C.C2, C.C2>..ctor(C.C2, C.C2)""
IL_0039: box ""System.ValueTuple<C.C2, C.C2>""
IL_003e: call ""void System.Console.WriteLine(object)""
IL_0043: ret
}
");
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册