diff --git a/docs/features/deconstruction.md b/docs/features/deconstruction.md index 945498dff3667..93d4a303a1837 100644 --- a/docs/features/deconstruction.md +++ b/docs/features/deconstruction.md @@ -41,6 +41,8 @@ It will be updated to support deconstruction-assignment, ie. when the left-hand- - Each item on the left needs to be assignable and needs to be compatible with corresponding position on the right (resulting from previous step). - Needs to handle nesting case such as `(x, (y, z)) = M();`, but note that the second item in the top-level group has no discernable type. +#### Evaluation order + The evaluation order can be summarized as: (1) all the side-effects on the left-hand-side, (2) all the Deconstruct invocations (if not tuple), (3) conversions (if needed), and (4) assignments. In the general case, the lowering for deconstruction-assignment would translate: `(expressionX, expressionY, expressionZ) = expressionRight` into: @@ -100,7 +102,17 @@ evaluate the right-hand-side and do a tuple conversion (using a fake tuple repre assign element-wise from the right to the left ``` -Note that tuples (`System.ValueTuple` and `System.Tuple`) don't need to invoke Deconstruct. +#### Resolution of the Deconstruct method + +The resolution is equivalent to typing `rhs.Deconstruct(out var x1, out var x2, ...);` with the appropriate number of parameters to deconstruct into. +It is based on normal overload resolution. +This implies that `rhs` cannot be dynamic. +Also, the `Deconstruct` method must be an instance method or an extension (but not a static method). + +#### Tuple Deconstruction + +Note that tuples (`System.ValueTuple`) don't need to invoke Deconstruct. +`System.Tuple` are not recognized as tuples, and so will rely on Deconstruct (which will be provided for up to 3 nestings deep, that is 21 elements) ###Deconstruction-declaration (deconstruction into new variables): diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs index 4d1aba2c07219..affbabbbdc339 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs @@ -345,7 +345,7 @@ internal BoundExpression BindValueAllowArgList(ExpressionSyntax node, Diagnostic if (isMemberInitializer) { initializer = initializerBinder.WrapWithVariablesIfAny(initializerOpt, initializer); - } + } return initializer; } @@ -1389,8 +1389,8 @@ private BoundExpression BindNonMethod(SimpleNameSyntax node, Symbol symbol, Diag var parameter = (ParameterSymbol)symbol; if (IsBadLocalOrParameterCapture(parameter, parameter.RefKind)) { - Error(diagnostics, ErrorCode.ERR_AnonDelegateCantUse, node, parameter.Name); - } + Error(diagnostics, ErrorCode.ERR_AnonDelegateCantUse, node, parameter.Name); + } return new BoundParameter(node, parameter, hasErrors: isError); } @@ -1829,7 +1829,7 @@ private BoundExpression BindOriginal(OriginalExpressionSyntax syntax, Diagnostic { var replacedProperty = (PropertySymbol)replaced; Debug.Assert((object)replacedProperty != null); - if (replacedProperty.IsIndexer || replacedProperty.IsIndexedProperty) + if (replacedProperty.IsIndexer || replacedProperty.IsIndexedProperty) { return new BoundPropertyGroup( syntax, @@ -2230,6 +2230,11 @@ private bool RefMustBeObeyed(bool isDelegateCreation, ArgumentSyntax argumentSyn arguments[arg] = CreateConversion(argument.Syntax, argument, kind, false, type, diagnostics); } + else if (argument.Kind == BoundKind.OutDeconstructVarPendingInference) + { + TypeSymbol parameterType = GetCorrespondingParameterType(ref result, parameters, arg); + arguments[arg] = ((OutDeconstructVarPendingInference)argument).SetInferredType(parameterType, success: true); + } } } diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs index 763599635e26d..324632ae2ba6e 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Invocation.cs @@ -114,6 +114,7 @@ private static ImmutableArray GetOriginalMethods(OverloadResolutio var analyzedArguments = AnalyzedArguments.GetInstance(); analyzedArguments.Arguments.AddRange(args); + Debug.Assert(!args.Any(e => e.Kind == BoundKind.OutDeconstructVarPendingInference)); BoundExpression result = BindInvocationExpression( node, node, methodName, boundExpression, analyzedArguments, diagnostics, queryClause, allowUnexpandedForm: allowUnexpandedForm); @@ -329,6 +330,8 @@ private BoundExpression BindArgListOperator(InvocationExpressionSyntax node, Dia private ImmutableArray BuildArgumentsForDynamicInvocation(AnalyzedArguments arguments, DiagnosticBag diagnostics) { + Debug.Assert(!arguments.Arguments.Any(a => a.Kind == BoundKind.OutDeconstructVarPendingInference)); + return arguments.Arguments.ToImmutable(); } @@ -1100,7 +1103,8 @@ private ImmutableArray BuildArgumentsForErrorRecovery(AnalyzedA { BoundKind argumentKind = oldArguments[i].Kind; - if (argumentKind == BoundKind.UnboundLambda && i < parameterCount) + if (argumentKind == BoundKind.OutDeconstructVarPendingInference || + (argumentKind == BoundKind.UnboundLambda && i < parameterCount)) { ArrayBuilder newArguments = ArrayBuilder.GetInstance(argumentCount); newArguments.AddRange(oldArguments); @@ -1120,8 +1124,15 @@ private ImmutableArray BuildArgumentsForErrorRecovery(AnalyzedA newArguments[i] = ((UnboundLambda)oldArgument).Bind(parameterType); } break; + case BoundKind.OutDeconstructVarPendingInference: + newArguments[i] = ((OutDeconstructVarPendingInference)oldArgument).SetInferredType(parameters[i].Type, success: true); + break; } } + else if (oldArgument.Kind == BoundKind.OutDeconstructVarPendingInference) + { + newArguments[i] = ((OutDeconstructVarPendingInference)oldArgument).FailInference(this); + } i++; } diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs index 86ee7db2c0368..a227d1c577d7f 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Statements.cs @@ -1,6 +1,5 @@ // 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 System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; @@ -1831,7 +1830,7 @@ private BoundExpression BindDeconstructionAssignment(AssignmentExpressionSyntax /// This will generate and stack appropriate deconstruction and assignment steps for a non-tuple type. /// Returns null if there was an error (if a suitable Deconstruct method was not found). /// - static private BoundDeconstructionDeconstructStep MakeNonTupleDeconstructStep( + private BoundDeconstructionDeconstructStep MakeNonTupleDeconstructStep( BoundDeconstructValuePlaceholder targetPlaceholder, AssignmentExpressionSyntax syntax, DiagnosticBag diagnostics, @@ -1840,16 +1839,22 @@ private BoundExpression BindDeconstructionAssignment(AssignmentExpressionSyntax ArrayBuilder assignmentSteps) { // symbol and parameters for Deconstruct - MethodSymbol deconstructMethod = FindDeconstruct(variables.Length, targetPlaceholder, syntax, diagnostics); + ImmutableArray outPlaceholders; + var deconstructInvocation = MakeDeconstructInvocationExpression(variables.Length, targetPlaceholder, syntax, diagnostics, out outPlaceholders); - if ((object)deconstructMethod == null) + if (deconstructInvocation.HasAnyErrors) { return null; } - - return new BoundDeconstructionDeconstructStep(syntax, deconstructMethod, targetPlaceholder, deconstructMethod.Parameters.SelectAsArray((p, s) => new BoundDeconstructValuePlaceholder(s, p.Type), syntax)); + else + { + return new BoundDeconstructionDeconstructStep(syntax, deconstructInvocation, targetPlaceholder, outPlaceholders); + } } + /// + /// Holds the variables on the LHS of a deconstruction as a tree of bound expressions. + /// private class DeconstructionVariable { public readonly BoundExpression Single; @@ -1898,7 +1903,7 @@ public DeconstructionVariable(ImmutableArray variables) } else { - var assignment = MakeAssignmentInfo(variable.Single, valuePlaceholder.Type, valuePlaceholder, syntax, diagnostics); + var assignment = MakeDeconstructionAssignmentStep(variable.Single, valuePlaceholder.Type, valuePlaceholder, syntax, diagnostics); assignmentSteps.Add(assignment); } } @@ -1962,7 +1967,9 @@ private ImmutableArray BindDeconstructionVariables(Separ /// /// Figures out how to assign from sourceType into receivingVariable and bundles the information (leaving holes for the actual source and receiver) into an AssignmentInfo. /// - private BoundDeconstructionAssignmentStep MakeAssignmentInfo(BoundExpression receivingVariable, TypeSymbol sourceType, BoundDeconstructValuePlaceholder inputPlaceholder, AssignmentExpressionSyntax node, DiagnosticBag diagnostics) + private BoundDeconstructionAssignmentStep MakeDeconstructionAssignmentStep( + BoundExpression receivingVariable, TypeSymbol sourceType, BoundDeconstructValuePlaceholder inputPlaceholder, + AssignmentExpressionSyntax node, DiagnosticBag diagnostics) { var outputPlaceholder = new BoundDeconstructValuePlaceholder(receivingVariable.Syntax, receivingVariable.Type) { WasCompilerGenerated = true }; @@ -1996,55 +2003,114 @@ static private void FlattenDeconstructVariables(ImmutableArray - /// Find the Deconstruct method for the expression on the right, that will fit the assignable bound expressions on the left. - /// Returns true if the Deconstruct method is found. - /// If so, it outputs the method. + /// Find the Deconstruct method for the expression on the right, that will fit the number of assignable variables on the left. + /// Returns an invocation expression if the Deconstruct method is found. + /// If so, it outputs placeholders that were coerced to the output types of the resolved Deconstruct method. + /// The overload resolution is similar to writing `receiver.Deconstruct(out var x1, out var x2, ...)`. /// - private static MethodSymbol FindDeconstruct(int numCheckedVariables, BoundExpression boundRHS, SyntaxNode node, DiagnosticBag diagnostics) + private BoundExpression MakeDeconstructInvocationExpression( + int numCheckedVariables, BoundExpression receiver, AssignmentExpressionSyntax assignmentSyntax, + DiagnosticBag diagnostics, out ImmutableArray outPlaceholders) { - // find symbol for Deconstruct member - ImmutableArray candidates = boundRHS.Type.GetMembers("Deconstruct"); - switch (candidates.Length) + var receiverSyntax = receiver.Syntax; + + if (receiver.Type.IsDynamic()) { - case 0: - Error(diagnostics, ErrorCode.ERR_MissingDeconstruct, boundRHS.Syntax, boundRHS.Type); - return null; - case 1: - break; - default: - Error(diagnostics, ErrorCode.ERR_AmbiguousDeconstruct, boundRHS.Syntax, boundRHS.Type); - return null; + Error(diagnostics, ErrorCode.ERR_CannotDeconstructDynamic, receiverSyntax); + outPlaceholders = default(ImmutableArray); + + return BadExpression(receiverSyntax, receiver); } - Symbol deconstructMember = candidates[0]; + var analyzedArguments = AnalyzedArguments.GetInstance(); + var outVars = ArrayBuilder.GetInstance(numCheckedVariables); + DiagnosticBag bag = null; - // check that the deconstruct fits - if (deconstructMember.Kind != SymbolKind.Method) + try { - Error(diagnostics, ErrorCode.ERR_MissingDeconstruct, boundRHS.Syntax, boundRHS.Type); - return null; - } + for (int i = 0; i < numCheckedVariables; i++) + { + var variable = new OutDeconstructVarPendingInference(assignmentSyntax); + analyzedArguments.Arguments.Add(variable); + analyzedArguments.RefKinds.Add(RefKind.Out); + outVars.Add(variable); + } - MethodSymbol deconstructMethod = (MethodSymbol)deconstructMember; - if (deconstructMethod.MethodKind != MethodKind.Ordinary) - { - Error(diagnostics, ErrorCode.ERR_MissingDeconstruct, boundRHS.Syntax, boundRHS.Type); - return null; - } + const string methodName = "Deconstruct"; + var memberAccess = BindInstanceMemberAccess( + receiverSyntax, receiverSyntax, receiver, methodName, rightArity: 0, + typeArgumentsSyntax: default(SeparatedSyntaxList), typeArguments: default(ImmutableArray), + invoked: true, diagnostics: diagnostics); - if (deconstructMethod.ParameterCount != numCheckedVariables) - { - Error(diagnostics, ErrorCode.ERR_DeconstructWrongParams, boundRHS.Syntax, deconstructMethod, numCheckedVariables); - return null; - } + memberAccess = CheckValue(memberAccess, BindValueKind.RValueOrMethodGroup, diagnostics); + memberAccess.WasCompilerGenerated = true; + + if (memberAccess.Kind != BoundKind.MethodGroup) + { + return MissingDeconstruct(receiver, assignmentSyntax, numCheckedVariables, diagnostics, out outPlaceholders, receiver); + } + + // After the overload resolution completes, the last step is to coerce the arguments with inferred types. + // That step returns placeholder (of correct type) instead of the outVar nodes that were passed in as arguments. + // So the generated invocation expression will contain placeholders instead of those outVar nodes. + // Those placeholders are also recorded in the outVar for easy access below, by the `SetInferredType` call on the outVar nodes. + bag = DiagnosticBag.GetInstance(); + BoundExpression result = BindMethodGroupInvocation( + receiverSyntax, receiverSyntax, methodName, (BoundMethodGroup)memberAccess, analyzedArguments, bag, queryClause: null, + allowUnexpandedForm: true); + + result.WasCompilerGenerated = true; + diagnostics.AddRange(bag); - if (deconstructMethod.Parameters.Any(p => p.RefKind != RefKind.Out)) + if (bag.HasAnyErrors()) + { + return MissingDeconstruct(receiver, assignmentSyntax, numCheckedVariables, diagnostics, out outPlaceholders, result); + } + + // Verify all the parameters (except "this" for extension methods) are out parameters + if (result.Kind != BoundKind.Call) + { + return MissingDeconstruct(receiver, assignmentSyntax, numCheckedVariables, diagnostics, out outPlaceholders, result); + } + + var deconstructMethod = ((BoundCall)result).Method; + var parameters = deconstructMethod.Parameters; + for (int i = (deconstructMethod.IsExtensionMethod ? 1 : 0); i < parameters.Length; i++) + { + if (parameters[i].RefKind != RefKind.Out) + { + return MissingDeconstruct(receiver, assignmentSyntax, numCheckedVariables, diagnostics, out outPlaceholders, result); + } + } + + if (outVars.Any(v => (object)v.Placeholder == null)) + { + return MissingDeconstruct(receiver, assignmentSyntax, numCheckedVariables, diagnostics, out outPlaceholders, result); + } + + outPlaceholders = outVars.SelectAsArray(v => v.Placeholder); + + return result; + } + finally { - Error(diagnostics, ErrorCode.ERR_DeconstructRequiresOutParams, boundRHS.Syntax, deconstructMethod); - return null; + analyzedArguments.Free(); + outVars.Free(); + + if (bag != null) + { + bag.Free(); + } } - return deconstructMethod; + } + + private BoundExpression MissingDeconstruct(BoundExpression receiver, AssignmentExpressionSyntax syntax, int numParameters, DiagnosticBag diagnostics, out ImmutableArray outPlaceholders, BoundNode childNode) + { + Error(diagnostics, ErrorCode.ERR_MissingDeconstruct, receiver.Syntax, receiver.Type, numParameters); + outPlaceholders = default(ImmutableArray); + + return BadExpression(syntax, childNode); } private BoundAssignmentOperator BindAssignment(AssignmentExpressionSyntax node, BoundExpression op1, BoundExpression op2, DiagnosticBag diagnostics) @@ -2196,6 +2262,10 @@ private BoundExpression CheckValue(BoundExpression expr, BindValueKind valueKind case BoundKind.PropertyGroup: expr = BindIndexedPropertyAccess((BoundPropertyGroup)expr, mustHaveAllOptionalParameters: false, diagnostics: diagnostics); break; + + case BoundKind.OutDeconstructVarPendingInference: + Debug.Assert(valueKind == BindValueKind.RefOrOut); + return expr; } bool hasResolutionErrors = false; diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs index 18179989c79af..0c6d2192ce309 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs @@ -6,7 +6,6 @@ using System.Linq; using Microsoft.CodeAnalysis.CSharp.Symbols; using Roslyn.Utilities; -using System; namespace Microsoft.CodeAnalysis.CSharp { @@ -1230,7 +1229,18 @@ private static TypeSymbol GetParameterType(int argIndex, MemberAnalysisResult re var type2 = GetParameterType(i, m2.Result, m2.LeastOverriddenMember.GetParameters(), out refKind2); bool okToDowngradeToNeither; - var r = BetterConversionFromExpression(arguments[i], + BetterResult r; + + if (argumentKind == BoundKind.OutDeconstructVarPendingInference) + { + // If argument is an out variable that needs type inference, + // neither candidate is better in this argument. + r = BetterResult.Neither; + okToDowngradeToNeither = false; + } + else + { + r = BetterConversionFromExpression(arguments[i], type1, m1.Result.ConversionForArg(i), refKind1, @@ -1240,6 +1250,7 @@ private static TypeSymbol GetParameterType(int argIndex, MemberAnalysisResult re considerRefKinds, ref useSiteDiagnostics, out okToDowngradeToNeither); + } if (r == BetterResult.Neither) { @@ -2906,6 +2917,14 @@ private RefKind GetEffectiveParameterRefKind(ParameterSymbol parameter, RefKind return Conversion.ImplicitDynamic; } + if (argument.Kind == BoundKind.OutDeconstructVarPendingInference) + { + Debug.Assert(argRefKind != RefKind.None); + + // Any parameter type is good, we'll use it for the var local. + return Conversion.Identity; + } + if (argRefKind == RefKind.None) { var conversion = Conversions.ClassifyImplicitConversionFromExpression(argument, parameterType, ref useSiteDiagnostics); diff --git a/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolutionResult.cs b/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolutionResult.cs index 8687ffd0a57ef..5068ec18fea53 100644 --- a/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolutionResult.cs +++ b/src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolutionResult.cs @@ -924,7 +924,7 @@ private static bool HadLambdaConversionError(DiagnosticBag diagnostics, Analyzed // If the expression is untyped because it is a lambda, anonymous method, method group or null // then we never want to report the error "you need a ref on that thing". Rather, we want to // say that you can't convert "null" to "ref int". - if (!argument.HasExpressionType()) + if (!argument.HasExpressionType() && argument.Kind != BoundKind.OutDeconstructVarPendingInference) { // If the problem is that a lambda isn't convertible to the given type, also report why. // The argument and parameter type might match, but may not have same in/out modifiers @@ -971,6 +971,8 @@ private static bool HadLambdaConversionError(DiagnosticBag diagnostics, Analyzed } else { + Debug.Assert(argument.Kind != BoundKind.OutDeconstructVarPendingInference); + TypeSymbol argType = argument.Display as TypeSymbol; Debug.Assert((object)argType != null); diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml index 677404f63ca20..a215dfdf9e6f3 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml @@ -403,7 +403,7 @@ - + @@ -1501,4 +1501,9 @@ + + + + + diff --git a/src/Compilers/CSharp/Portable/BoundTree/Expression.cs b/src/Compilers/CSharp/Portable/BoundTree/Expression.cs index 19fc6c1aa4211..f3d60eb3cc562 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/Expression.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/Expression.cs @@ -5,6 +5,7 @@ using System.Diagnostics; using System.Runtime.CompilerServices; using Microsoft.CodeAnalysis.Semantics; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp { @@ -197,7 +198,7 @@ private static IArgument DeriveArgument(int parameterIndex, int argumentIndex, I return new Argument(ArgumentKind.Named, parameter, argument); }); } - + private static IOperation CreateParamArray(IParameterSymbol parameter, ImmutableArray boundArguments, int firstArgumentElementIndex, SyntaxNode invocationSyntax) { if (parameter.Type.TypeKind == TypeKind.Array) @@ -515,7 +516,7 @@ public override void Accept(OperationVisitor visitor) } } - internal partial class BoundTupleExpression + internal partial class BoundTupleExpression { protected override OperationKind ExpressionKind => OperationKind.None; @@ -1322,7 +1323,7 @@ public override void Accept(OperationVisitor visitor) internal partial class BoundImplicitReceiver : IInstanceReferenceExpression { InstanceReferenceKind IInstanceReferenceExpression.InstanceReferenceKind => InstanceReferenceKind.Implicit; - + protected override OperationKind ExpressionKind => OperationKind.InstanceReferenceExpression; public override void Accept(OperationVisitor visitor) @@ -1951,7 +1952,7 @@ public override void Accept(OperationVisitor visitor) return visitor.VisitNoneOperation(this, argument); } } - + internal partial class BoundDynamicCollectionElementInitializer { protected override OperationKind ExpressionKind => OperationKind.None; @@ -2826,6 +2827,28 @@ internal static BinaryOperationKind DeriveBinaryOperationKind(BinaryOperatorKind } } + /// + /// This node represents an 'out var' parameter to a Deconstruct method. + /// It is only used temporarily during initial binding. + /// + internal partial class OutDeconstructVarPendingInference + { + public override void Accept(OperationVisitor visitor) + { + throw ExceptionUtilities.Unreachable; + } + + public override TResult Accept(OperationVisitor visitor, TArgument argument) + { + throw ExceptionUtilities.Unreachable; + } + + protected override OperationKind ExpressionKind + { + get { throw ExceptionUtilities.Unreachable; } + } + } + partial class BoundIsPatternExpression { public override void Accept(OperationVisitor visitor) diff --git a/src/Compilers/CSharp/Portable/BoundTree/Formatting.cs b/src/Compilers/CSharp/Portable/BoundTree/Formatting.cs index a51da0578be39..803f23c9453c1 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/Formatting.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/Formatting.cs @@ -75,4 +75,12 @@ public override object Display get { throw ExceptionUtilities.Unreachable; } } } + + internal partial class OutDeconstructVarPendingInference + { + public override object Display + { + get { return string.Empty; } + } + } } diff --git a/src/Compilers/CSharp/Portable/BoundTree/OutDeconstructVarPendingInference.cs b/src/Compilers/CSharp/Portable/BoundTree/OutDeconstructVarPendingInference.cs new file mode 100644 index 0000000000000..f4804ebbc8c72 --- /dev/null +++ b/src/Compilers/CSharp/Portable/BoundTree/OutDeconstructVarPendingInference.cs @@ -0,0 +1,26 @@ +// 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 System; +using Microsoft.CodeAnalysis.CSharp.Symbols; +using System.Diagnostics; + +namespace Microsoft.CodeAnalysis.CSharp +{ + internal partial class OutDeconstructVarPendingInference + { + public BoundDeconstructValuePlaceholder Placeholder; + + public BoundDeconstructValuePlaceholder SetInferredType(TypeSymbol type, bool success) + { + Debug.Assert((object)Placeholder == null); + + Placeholder = new BoundDeconstructValuePlaceholder(this.Syntax, type, hasErrors: this.HasErrors || !success); + return Placeholder; + } + + public BoundDeconstructValuePlaceholder FailInference(Binder binder) + { + return SetInferredType(binder.CreateErrorType(), success: false); + } + } +} \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/CSharpCodeAnalysis.csproj b/src/Compilers/CSharp/Portable/CSharpCodeAnalysis.csproj index e12dba4e58b7e..22a43de275fd0 100644 --- a/src/Compilers/CSharp/Portable/CSharpCodeAnalysis.csproj +++ b/src/Compilers/CSharp/Portable/CSharpCodeAnalysis.csproj @@ -200,6 +200,7 @@ + diff --git a/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs b/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs index 49c5e930aca20..bdd7cafaa8466 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs +++ b/src/Compilers/CSharp/Portable/CSharpResources.Designer.cs @@ -394,15 +394,6 @@ internal class CSharpResources { } } - /// - /// Looks up a localized string similar to More than one Deconstruct instance or extension method was found for type '{0}'.. - /// - internal static string ERR_AmbiguousDeconstruct { - get { - return ResourceManager.GetString("ERR_AmbiguousDeconstruct", resourceCulture); - } - } - /// /// Looks up a localized string similar to Cannot use ref or out parameter '{0}' inside an anonymous method, lambda expression, or query expression. /// @@ -2320,6 +2311,15 @@ internal class CSharpResources { } } + /// + /// Looks up a localized string similar to Cannot deconstruct dynamic objects.. + /// + internal static string ERR_CannotDeconstructDynamic { + get { + return ResourceManager.GetString("ERR_CannotDeconstructDynamic", resourceCulture); + } + } + /// /// Looks up a localized string similar to Cannot pass null for friend assembly name. /// @@ -3103,15 +3103,6 @@ internal class CSharpResources { } } - /// - /// Looks up a localized string similar to The Deconstruct method for type '{0}' must have only out parameters.. - /// - internal static string ERR_DeconstructRequiresOutParams { - get { - return ResourceManager.GetString("ERR_DeconstructRequiresOutParams", resourceCulture); - } - } - /// /// Looks up a localized string similar to Cannot deconstruct a tuple of '{0}' elements into '{1}' variables.. /// @@ -3121,15 +3112,6 @@ internal class CSharpResources { } } - /// - /// Looks up a localized string similar to The Deconstruct method for type '{0}' doesn't have the number of parameters ({1}) needed for this deconstruction.. - /// - internal static string ERR_DeconstructWrongParams { - get { - return ResourceManager.GetString("ERR_DeconstructWrongParams", resourceCulture); - } - } - /// /// Looks up a localized string similar to Cannot specify the DefaultMember attribute on a type containing an indexer. /// @@ -5921,7 +5903,7 @@ internal class CSharpResources { } /// - /// Looks up a localized string similar to No Deconstruct instance or extension method was found for type '{0}'.. + /// Looks up a localized string similar to No Deconstruct instance or extension method was found for type '{0}', with {1} out parameters.. /// internal static string ERR_MissingDeconstruct { get { diff --git a/src/Compilers/CSharp/Portable/CSharpResources.resx b/src/Compilers/CSharp/Portable/CSharpResources.resx index cce4a2d44fe35..74bf5c3edab53 100644 --- a/src/Compilers/CSharp/Portable/CSharpResources.resx +++ b/src/Compilers/CSharp/Portable/CSharpResources.resx @@ -4875,17 +4875,8 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ tuples - - More than one Deconstruct instance or extension method was found for type '{0}'. - - - The Deconstruct method for type '{0}' must have only out parameters. - - - The Deconstruct method for type '{0}' doesn't have the number of parameters ({1}) needed for this deconstruction. - - No Deconstruct instance or extension method was found for type '{0}'. + No Deconstruct instance or extension method was found for type '{0}', with {1} out parameters. Deconstruct assignment requires an expression with a type on the right-hand-side. @@ -4902,4 +4893,7 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ Cannot deconstruct a tuple of '{0}' elements into '{1}' variables. + + Cannot deconstruct dynamic objects. + \ No newline at end of file diff --git a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs index cd9f590566a3e..97eae9e0245da 100644 --- a/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs +++ b/src/Compilers/CSharp/Portable/Errors/ErrorCode.cs @@ -1395,10 +1395,8 @@ internal enum ErrorCode ERR_PredefinedTypeMemberNotFoundInAssembly = 8205, ERR_MissingDeconstruct = 8206, - ERR_AmbiguousDeconstruct = 8207, - ERR_DeconstructRequiresOutParams = 8208, - ERR_DeconstructWrongParams = 8209, ERR_DeconstructRequiresExpression = 8210, ERR_DeconstructWrongCardinality = 8211, + ERR_CannotDeconstructDynamic = 8212, } } diff --git a/src/Compilers/CSharp/Portable/FlowAnalysis/PreciseAbstractFlowPass.cs b/src/Compilers/CSharp/Portable/FlowAnalysis/PreciseAbstractFlowPass.cs index aab14c7e3ffdc..5ab45d338ad9a 100644 --- a/src/Compilers/CSharp/Portable/FlowAnalysis/PreciseAbstractFlowPass.cs +++ b/src/Compilers/CSharp/Portable/FlowAnalysis/PreciseAbstractFlowPass.cs @@ -1526,6 +1526,12 @@ public override BoundNode VisitDeconstructionAssignmentOperator(BoundDeconstruct return null; } + public override sealed BoundNode VisitOutDeconstructVarPendingInference(OutDeconstructVarPendingInference node) + { + // OutDeconstructVarPendingInference nodes are only used within initial binding, but don't survive past that stage + throw ExceptionUtilities.Unreachable; + } + public override BoundNode VisitCompoundAssignmentOperator(BoundCompoundAssignmentOperator node) { // TODO: should events be handled specially too? diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs index 0c7fc2f9e592c..1c099f8d8bbc3 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter.cs @@ -8,6 +8,7 @@ using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.RuntimeMembers; +using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp { @@ -293,6 +294,12 @@ private void RemovePlaceholderReplacements(ArrayBuilder private void CallDeconstruct(BoundDeconstructionAssignmentOperator node, BoundDeconstructionDeconstructStep deconstruction, ArrayBuilder temps, ArrayBuilder stores, ArrayBuilder placeholders) { - Debug.Assert((object)deconstruction.DeconstructMemberOpt != null); + Debug.Assert((object)deconstruction.DeconstructInvocationOpt != null); CSharpSyntaxNode syntax = node.Syntax; // prepare out parameters for Deconstruct - var deconstructParameters = deconstruction.DeconstructMemberOpt.Parameters; + var deconstructParameters = deconstruction.OutputPlaceholders; var outParametersBuilder = ArrayBuilder.GetInstance(deconstructParameters.Length); for (var i = 0; i < deconstructParameters.Length; i++) @@ -151,9 +151,8 @@ private void CallDeconstruct(BoundDeconstructionAssignmentOperator node, BoundDe var outParameters = outParametersBuilder.ToImmutableAndFree(); - // invoke Deconstruct - var invokeDeconstruct = MakeCall(syntax, PlaceholderReplacement(deconstruction.TargetPlaceholder), deconstruction.DeconstructMemberOpt, outParameters, deconstruction.DeconstructMemberOpt.ReturnType); - stores.Add(invokeDeconstruct); + // invoke Deconstruct with placeholders replaced by locals + stores.Add(VisitExpression(deconstruction.DeconstructInvocationOpt)); } } } diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenDeconstructTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenDeconstructTests.cs index 460bfb47f52a1..38012f2a26c4a 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenDeconstructTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenDeconstructTests.cs @@ -1,5 +1,6 @@ // 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 System; using System.Linq; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; using Microsoft.CodeAnalysis.Test.Utilities; @@ -117,9 +118,12 @@ static void Main() "; var comp = CreateCompilationWithMscorlib(source, parseOptions: TestOptions.Regular.WithTuplesFeature()); comp.VerifyDiagnostics( - // (8,18): error CS8206: No Deconstruct instance or extension method was found for type 'C'. + // (8,18): error CS1061: 'C' does not contain a definition for 'Deconstruct' and no extension method 'Deconstruct' accepting a first argument of type 'C' could be found (are you missing a using directive or an assembly reference?) + // (x, y) = new C(); + Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "new C()").WithArguments("C", "Deconstruct").WithLocation(8, 18), + // (8,18): error CS8206: No Deconstruct instance or extension method was found for type 'C', with 2 out parameters. // (x, y) = new C(); - Diagnostic(ErrorCode.ERR_MissingDeconstruct, "new C()").WithArguments("C").WithLocation(8, 18) + Diagnostic(ErrorCode.ERR_MissingDeconstruct, "new C()").WithArguments("C", "2").WithLocation(8, 18) ); } @@ -134,6 +138,7 @@ static void Main() long x; string y; (x, y) = new C(); + System.Console.WriteLine(x + "" "" + y); } public void Deconstruct(out int a, out string b) @@ -144,16 +149,12 @@ public void Deconstruct(out int a, out string b) public void Deconstruct(out int a) { - a = 1; + a = 2; } }"; - var comp = CreateCompilationWithMscorlib(source, parseOptions: TestOptions.Regular.WithTuplesFeature()); - comp.VerifyDiagnostics( - // (8,18): error CS8207: More than one Deconstruct instance or extension method was found for type 'C'. - // (x, y) = new C(); - Diagnostic(ErrorCode.ERR_AmbiguousDeconstruct, "new C()").WithArguments("C").WithLocation(8, 18) - ); + var comp = CompileAndVerify(source, expectedOutput: "1 hello", parseOptions: TestOptions.Regular.WithTuplesFeature()); + comp.VerifyDiagnostics(); } [Fact] @@ -168,16 +169,46 @@ static void Main() string y; (x, y) = new C(); } - public void Deconstruct(out int a) + public void Deconstruct(out int a) // too few arguments { a = 1; } }"; var comp = CreateCompilationWithMscorlib(source, parseOptions: TestOptions.Regular.WithTuplesFeature()); comp.VerifyDiagnostics( - // (8,18): error CS8209: The Deconstruct method for type 'C.Deconstruct(out int)' doesn't have the number of parameters (2) needed for this deconstruction. + // (8,18): error CS1501: No overload for method 'Deconstruct' takes 2 arguments + // (x, y) = new C(); + Diagnostic(ErrorCode.ERR_BadArgCount, "new C()").WithArguments("Deconstruct", "2").WithLocation(8, 18), + // (8,18): error CS8206: No Deconstruct instance or extension method was found for type 'C', with 2 out parameters. + // (x, y) = new C(); + Diagnostic(ErrorCode.ERR_MissingDeconstruct, "new C()").WithArguments("C", "2").WithLocation(8, 18) + ); + } + + [Fact] + public void DeconstructWrongParams2() + { + string source = @" +class C +{ + static void Main() + { + long x, y; + (x, y) = new C(); + } + public void Deconstruct(out int a, out int b, out int c) // too many arguments + { + a = b = c = 1; + } +}"; + var comp = CreateCompilationWithMscorlib(source, parseOptions: TestOptions.Regular.WithTuplesFeature()); + comp.VerifyDiagnostics( + // (7,18): error CS7036: There is no argument given that corresponds to the required formal parameter 'c' of 'C.Deconstruct(out int, out int, out int)' // (x, y) = new C(); - Diagnostic(ErrorCode.ERR_DeconstructWrongParams, "new C()").WithArguments("C.Deconstruct(out int)", "2").WithLocation(8, 18) + Diagnostic(ErrorCode.ERR_NoCorrespondingArgument, "new C()").WithArguments("c", "C.Deconstruct(out int, out int, out int)").WithLocation(7, 18), + // (7,18): error CS8206: No Deconstruct instance or extension method was found for type 'C', with 2 out parameters. + // (x, y) = new C(); + Diagnostic(ErrorCode.ERR_MissingDeconstruct, "new C()").WithArguments("C", "2").WithLocation(7, 18) ); } @@ -204,9 +235,12 @@ static void Main() // (8,17): error CS1061: 'string' does not contain a definition for 'g' and no extension method 'g' accepting a first argument of type 'string' could be found (are you missing a using directive or an assembly reference?) // (x.f, y.g) = new C(); Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "g").WithArguments("string", "g").WithLocation(8, 17), - // (8,22): error CS8209: The Deconstruct method for type 'C.Deconstruct()' doesn't have the number of parameters (2) needed for this deconstruction. + // (8,22): error CS1501: No overload for method 'Deconstruct' takes 2 arguments + // (x.f, y.g) = new C(); + Diagnostic(ErrorCode.ERR_BadArgCount, "new C()").WithArguments("Deconstruct", "2").WithLocation(8, 22), + // (8,22): error CS8206: No Deconstruct instance or extension method was found for type 'C', with 2 out parameters. // (x.f, y.g) = new C(); - Diagnostic(ErrorCode.ERR_DeconstructWrongParams, "new C()").WithArguments("C.Deconstruct()", "2").WithLocation(8, 22) + Diagnostic(ErrorCode.ERR_MissingDeconstruct, "new C()").WithArguments("C", "2").WithLocation(8, 22) ); } @@ -227,9 +261,12 @@ static void Main() "; var comp = CreateCompilationWithMscorlib(source, parseOptions: TestOptions.Regular.WithTuplesFeature()); comp.VerifyDiagnostics( - // (8,18): error CS8208: The Deconstruct method for type 'C.Deconstruct(out int, int)' must have only out parameters. + // (8,9): error CS1615: Argument 2 may not be passed with the 'out' keyword // (x, y) = new C(); - Diagnostic(ErrorCode.ERR_DeconstructRequiresOutParams, "new C()").WithArguments("C.Deconstruct(out int, int)").WithLocation(8, 18) + Diagnostic(ErrorCode.ERR_BadArgExtraRef, "(x, y) = new C()").WithArguments("2", "out").WithLocation(8, 9), + // (8,18): error CS8206: No Deconstruct instance or extension method was found for type 'C', with 2 out parameters. + // (x, y) = new C(); + Diagnostic(ErrorCode.ERR_MissingDeconstruct, "new C()").WithArguments("C", "2").WithLocation(8, 18) ); } @@ -250,9 +287,12 @@ static void Main() "; var comp = CreateCompilationWithMscorlib(source, parseOptions: TestOptions.Regular.WithTuplesFeature()); comp.VerifyDiagnostics( - // (8,18): error CS8208: The Deconstruct method for type 'C.Deconstruct(ref int, out int)' must have only out parameters. + // (8,9): error CS1620: Argument 1 must be passed with the 'ref' keyword + // (x, y) = new C(); + Diagnostic(ErrorCode.ERR_BadArgRef, "(x, y) = new C()").WithArguments("1", "ref").WithLocation(8, 9), + // (8,18): error CS8206: No Deconstruct instance or extension method was found for type 'C', with 2 out parameters. // (x, y) = new C(); - Diagnostic(ErrorCode.ERR_DeconstructRequiresOutParams, "new C()").WithArguments("C.Deconstruct(ref int, out int)").WithLocation(8, 18) + Diagnostic(ErrorCode.ERR_MissingDeconstruct, "new C()").WithArguments("C", "2").WithLocation(8, 18) ); } @@ -415,914 +455,1706 @@ public void Deconstruct(out int a, out dynamic b) } [Fact] - public void DifferentStaticVariableKinds() + public void DeconstructInterfaceOnStruct() { string source = @" -class C +interface IDeconstructable { - static int[] ArrayIndexer = new int[1]; - - static string property; - static string Property { set { property = value; } } + void Deconstruct(out int a, out string b); +} - static string AutoProperty { get; set; } +struct C : IDeconstructable +{ + string state; static void Main() { - (C.ArrayIndexer[0], C.Property, C.AutoProperty) = new C(); - System.Console.WriteLine(C.ArrayIndexer[0] + "" "" + C.property + "" "" + C.AutoProperty); + int x; + string y; + IDeconstructable c = new C() { state = ""initial"" }; + System.Console.Write(c); + + (x, y) = c; + System.Console.WriteLine("" "" + c + "" "" + x + "" "" + y); } - public void Deconstruct(out int a, out string b, out string c) + void IDeconstructable.Deconstruct(out int a, out string b) { a = 1; b = ""hello""; - c = ""world""; + state = ""modified""; } + + public override string ToString() { return state; } } "; - var comp = CompileAndVerify(source, expectedOutput: "1 hello world", additionalRefs: new[] { SystemCoreRef }, parseOptions: TestOptions.Regular.WithTuplesFeature()); + var comp = CompileAndVerify(source, expectedOutput: "initial modified 1 hello", additionalRefs: new[] { SystemCoreRef, CSharpRef }, parseOptions: TestOptions.Regular.WithTuplesFeature()); comp.VerifyDiagnostics(); } [Fact] - public void DifferentVariableRefKinds() + public void DeconstructManually() { string source = @" -class C +struct C { static void Main() { - long a = 1; - int b; - C.M(ref a, out b); - System.Console.WriteLine(a + "" "" + b); - } + long x; + string y; + C c = new C(); - static void M(ref long a, out int b) - { - (a, b) = new C(); + c.Deconstruct(out x, out y); // error + (x, y) = c; } - public void Deconstruct(out int x, out byte y) + void Deconstruct(out int a, out string b) { - x = 2; - y = (byte)3; + a = 1; + b = ""hello""; } } "; - var comp = CompileAndVerify(source, expectedOutput: "2 3", additionalRefs: new[] { SystemCoreRef }, parseOptions: TestOptions.Regular.WithTuplesFeature()); - comp.VerifyDiagnostics(); + var comp = CreateCompilationWithMscorlib(source, parseOptions: TestOptions.Regular.WithTuplesFeature()); + comp.VerifyDiagnostics( + // (10,27): error CS1503: Argument 1: cannot convert from 'out long' to 'out int' + // c.Deconstruct(out x, out y); // error + Diagnostic(ErrorCode.ERR_BadArgType, "x").WithArguments("1", "out long", "out int").WithLocation(10, 27) + ); } [Fact] - public void ConversionErrors() + public void DeconstructMethodHasOptionalParam() { string source = @" class C { static void Main() { - byte x; + long x; string y; + (x, y) = new C(); + System.Console.WriteLine(x + "" "" + y); } - public void Deconstruct(out int a, out int b) + public void Deconstruct(out int a, out string b, int c = 42) // not a Deconstruct operator { - a = b = 1; + a = 1; + b = ""hello""; } } "; + var comp = CreateCompilationWithMscorlib(source, parseOptions: TestOptions.Regular.WithTuplesFeature()); comp.VerifyDiagnostics( - // (8,9): error CS0266: Cannot implicitly convert type 'int' to 'byte'. An explicit conversion exists (are you missing a cast?) - // (x, y) = new C(); - Diagnostic(ErrorCode.ERR_NoImplicitConvCast, "(x, y) = new C()").WithArguments("int", "byte").WithLocation(8, 9), - // (8,9): error CS0029: Cannot implicitly convert type 'int' to 'string' + // (9,18): error CS8206: No Deconstruct instance or extension method was found for type 'C', with 2 out parameters. // (x, y) = new C(); - Diagnostic(ErrorCode.ERR_NoImplicitConv, "(x, y) = new C()").WithArguments("int", "string").WithLocation(8, 9) + Diagnostic(ErrorCode.ERR_MissingDeconstruct, "new C()").WithArguments("C", "2").WithLocation(9, 18) ); } [Fact] - public void ExpressionType() + public void BadDeconstructShadowsBaseDeconstruct() { string source = @" -class C +class D +{ + public void Deconstruct(out int a, out string b) { a = 2; b = ""world""; } +} +class C : D { static void Main() { - int x, y; - var type = ((x, y) = new C()).GetType(); + long x; + string y; + + (x, y) = new C(); + System.Console.WriteLine(x + "" "" + y); } - public void Deconstruct(out int a, out int b) + public void Deconstruct(out int a, out string b, int c = 42) // not a Deconstruct operator { - a = b = 1; + a = 1; + b = ""hello""; } } "; var comp = CreateCompilationWithMscorlib(source, parseOptions: TestOptions.Regular.WithTuplesFeature()); comp.VerifyDiagnostics( - // (7,38): error CS0023: Operator '.' cannot be applied to operand of type 'void' - // var type = ((x, y) = new C()).GetType(); - Diagnostic(ErrorCode.ERR_BadUnaryOp, ".").WithArguments(".", "void").WithLocation(7, 38) + // (13,18): error CS8206: No Deconstruct instance or extension method was found for type 'C', with 2 out parameters. + // (x, y) = new C(); + Diagnostic(ErrorCode.ERR_MissingDeconstruct, "new C()").WithArguments("C", "2").WithLocation(13, 18) ); } [Fact] - public void LambdaStillNotValidStatement() + public void DeconstructMethodHasParams() { string source = @" class C { static void Main() { - (a) => a; + long x; + string y; + + (x, y) = new C(); + System.Console.WriteLine(x + "" "" + y); + } + + public void Deconstruct(out int a, out string b, params int[] c) // not a Deconstruct operator + { + a = 1; + b = ""hello""; } } "; var comp = CreateCompilationWithMscorlib(source, parseOptions: TestOptions.Regular.WithTuplesFeature()); comp.VerifyDiagnostics( - // (6,9): error CS0201: Only assignment, call, increment, decrement, and new object expressions can be used as a statement - // (a) => a; - Diagnostic(ErrorCode.ERR_IllegalStatement, "(a) => a").WithLocation(6, 9) + // (9,18): error CS8206: No Deconstruct instance or extension method was found for type 'C', with 2 out parameters. + // (x, y) = new C(); + Diagnostic(ErrorCode.ERR_MissingDeconstruct, "new C()").WithArguments("C", "2").WithLocation(9, 18) ); } [Fact] - public void LambdaWithBodyStillNotValidStatement() + public void DeconstructMethodHasParams2() { string source = @" class C { static void Main() { - (a, b) => { }; + long x; + string y; + + (x, y) = new C(); + System.Console.WriteLine(x + "" "" + y); + } + + public void Deconstruct(out int a, out string b, params int[] c) // not a Deconstruct operator + { + a = 1; + b = ""ignored""; + } + + public void Deconstruct(out int a, out string b) + { + a = 2; + b = ""hello""; } } "; - var comp = CreateCompilationWithMscorlib(source, parseOptions: TestOptions.Regular.WithTuplesFeature()); - comp.VerifyDiagnostics( - // (6,9): error CS0201: Only assignment, call, increment, decrement, and new object expressions can be used as a statement - // (a, b) => { }; - Diagnostic(ErrorCode.ERR_IllegalStatement, "(a, b) => { }").WithLocation(6, 9) - ); + var comp = CompileAndVerify(source, expectedOutput: "2 hello", parseOptions: TestOptions.Regular.WithTuplesFeature()); + comp.VerifyDiagnostics(); } [Fact] - public void CastButNotCast() + public void DeconstructMethodHasArglist() { - // int and string must be types, so (int, string) must be type and ((int, string)) a cast, but then .String() cannot follow a cast... string source = @" class C { static void Main() { - ((int, string)).ToString(); + long x; + string y; + + (x, y) = new C(); + } + + public void Deconstruct(out int a, out string b, __arglist) // not a Deconstruct operator + { + a = 1; + b = ""hello""; } } "; - var comp = CreateCompilationWithMscorlib(source, references: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, parseOptions: TestOptions.Regular.WithTuplesFeature()); + var comp = CreateCompilationWithMscorlib(source, parseOptions: TestOptions.Regular.WithTuplesFeature()); comp.VerifyDiagnostics( - // (6,24): error CS1525: Invalid expression term '.' - // ((int, string)).ToString(); - Diagnostic(ErrorCode.ERR_InvalidExprTerm, ".").WithArguments(".").WithLocation(6, 24) + // (9,18): error CS7036: There is no argument given that corresponds to the required formal parameter '__arglist' of 'C.Deconstruct(out int, out string, __arglist)' + // (x, y) = new C(); + Diagnostic(ErrorCode.ERR_NoCorrespondingArgument, "new C()").WithArguments("__arglist", "C.Deconstruct(out int, out string, __arglist)").WithLocation(9, 18), + // (9,18): error CS8206: No Deconstruct instance or extension method was found for type 'C', with 2 out parameters. + // (x, y) = new C(); + Diagnostic(ErrorCode.ERR_MissingDeconstruct, "new C()").WithArguments("C", "2").WithLocation(9, 18) ); } [Fact] - [CompilerTrait(CompilerFeature.RefLocalsReturns)] - public void RefReturningMethod() + public void DeconstructMethodHasArglist2() { string source = @" class C { - static int i = 0; - static void Main() { - (M(), M()) = new C(); - System.Console.WriteLine($""Final i is {i}""); + long x; + string y; + + (x, y) = new C(); + System.Console.WriteLine(x + "" "" + y); } - static ref int M() + public void Deconstruct(out int a, out string b) { - System.Console.WriteLine($""M (previous i is {i})""); - return ref i; + a = 1; + b = ""hello""; } - void Deconstruct(out int x, out int y) + public void Deconstruct(out int a, out string b, __arglist) // not a Deconstruct operator { - System.Console.WriteLine(""Deconstruct""); - x = 42; - y = 43; + a = 2; + b = ""ignored""; } } -"; - var expected = -@"M (previous i is 0) -M (previous i is 0) -Deconstruct -Final i is 43 "; - var comp = CompileAndVerify(source, expectedOutput: expected, parseOptions: TestOptions.Regular.WithTuplesFeature().WithRefsFeature()); - comp.VerifyDiagnostics( - ); + var comp = CompileAndVerify(source, expectedOutput: "1 hello", additionalRefs: new[] { SystemCoreRef, CSharpRef }, parseOptions: TestOptions.Regular.WithTuplesFeature()); + comp.VerifyDiagnostics(); } [Fact] - [CompilerTrait(CompilerFeature.RefLocalsReturns)] - public void RefReturningProperty() + public void DeconstructDelegate() { string source = @" +public delegate void D1(out int x, out int y); + class C { - static int i = 0; + public D1 Deconstruct; // not a Deconstruct operator static void Main() { - (P, P) = new C(); - System.Console.WriteLine($""Final i is {i}""); - } - - static ref int P - { - get - { - System.Console.WriteLine($""P (previous i is {i})""); - return ref i; - } + int x, y; + (x, y) = new C() { Deconstruct = DeconstructMethod }; } - void Deconstruct(out int x, out int y) - { - System.Console.WriteLine(""Deconstruct""); - x = 42; - y = 43; - } + public static void DeconstructMethod(out int a, out int b) { a = 1; b = 2; } } -"; - var expected = -@"P (previous i is 0) -P (previous i is 0) -Deconstruct -Final i is 43 "; - var comp = CompileAndVerify(source, expectedOutput: expected, parseOptions: TestOptions.Regular.WithTuplesFeature().WithRefsFeature()); + var comp = CreateCompilationWithMscorlib(source, parseOptions: TestOptions.Regular.WithTuplesFeature()); comp.VerifyDiagnostics( + // (11,18): error CS8206: No Deconstruct instance or extension method was found for type 'C', with 2 out parameters. + // (x, y) = new C() { Deconstruct = DeconstructMethod }; + Diagnostic(ErrorCode.ERR_MissingDeconstruct, "new C() { Deconstruct = DeconstructMethod }").WithArguments("C", "2").WithLocation(11, 18) ); } - [Fact(Skip = "PROTOTYPE(tuples)")] - [CompilerTrait(CompilerFeature.RefLocalsReturns)] - public void RefReturningMethod2() + [Fact] + public void DeconstructDelegate2() { string source = @" +public delegate void D1(out int x, out int y); + class C { - static int i; + public D1 Deconstruct; static void Main() { - (M(), M()) = new C(); + int x, y; + (x, y) = new C() { Deconstruct = DeconstructMethod }; } - static ref int M() - { - System.Console.WriteLine(""M""); - return ref i; - } + public static void DeconstructMethod(out int a, out int b) { a = 1; b = 2; } - void Deconstruct(out int i, out int j) - { - i = 42; - j = 43; - } + public void Deconstruct(out int a, out int b) { a = 1; b = 2; } } "; - var comp = CreateCompilationWithMscorlib(source, parseOptions: TestOptions.Regular.WithTuplesFeature().WithRefsFeature()); - comp.VerifyDiagnostics(); - - // This error is wrong - // (4,16): warning CS0649: Field 'C.i' is never assigned to, and will always have its default value 0 - // static int i; - //Diagnostic(ErrorCode.WRN_UnassignedInternalField, "i").WithArguments("C.i", "0").WithLocation(4, 16) - } + var comp = CreateCompilationWithMscorlib(source, parseOptions: TestOptions.Regular.WithTuplesFeature()); + comp.VerifyDiagnostics( + // (16,17): error CS0102: The type 'C' already contains a definition for 'Deconstruct' + // public void Deconstruct(out int a, out int b) { a = 1; b = 2; } + Diagnostic(ErrorCode.ERR_DuplicateNameInClass, "Deconstruct").WithArguments("C", "Deconstruct").WithLocation(16, 17), + // (11,28): error CS1913: Member 'Deconstruct' cannot be initialized. It is not a field or property. + // (x, y) = new C() { Deconstruct = DeconstructMethod }; + Diagnostic(ErrorCode.ERR_MemberCannotBeInitialized, "Deconstruct").WithArguments("Deconstruct").WithLocation(11, 28), + // (6,15): warning CS0649: Field 'C.Deconstruct' is never assigned to, and will always have its default value null + // public D1 Deconstruct; + Diagnostic(ErrorCode.WRN_UnassignedInternalField, "Deconstruct").WithArguments("C.Deconstruct", "null").WithLocation(6, 15) + ); + } [Fact] - [CompilerTrait(CompilerFeature.RefLocalsReturns)] - public void RefReturningMethodFlow() + public void DeconstructEvent() { string source = @" -struct C +public delegate void D1(out int x, out int y); + +class C { - static C i; - static C P { get { System.Console.WriteLine(""getP""); return i; } set { System.Console.WriteLine(""setP""); i = value; } } + public event D1 Deconstruct; // not a Deconstruct operator static void Main() { - (M(), M()) = P; + long x; + int y; + C c = new C(); + c.Deconstruct += DeconstructMethod; + (x, y) = c; } - static ref C M() + public static void DeconstructMethod(out int a, out int b) { - System.Console.WriteLine($""M (previous i is {i})""); - return ref i; + a = 1; + b = 2; } +} +"; - void Deconstruct(out int x, out int y) + var comp = CreateCompilationWithMscorlib(source, parseOptions: TestOptions.Regular.WithTuplesFeature()); + comp.VerifyDiagnostics( + // (14,18): error CS8206: No Deconstruct instance or extension method was found for type 'C', with 2 out parameters. + // (x, y) = c; + Diagnostic(ErrorCode.ERR_MissingDeconstruct, "c").WithArguments("C", "2").WithLocation(14, 18), + // (6,21): warning CS0067: The event 'C.Deconstruct' is never used + // public event D1 Deconstruct; + Diagnostic(ErrorCode.WRN_UnreferencedEvent, "Deconstruct").WithArguments("C.Deconstruct").WithLocation(6, 21) + ); + } + + [Fact] + public void DifferentStaticVariableKinds() + { + string source = @" +class C +{ + static int[] ArrayIndexer = new int[1]; + + static string property; + static string Property { set { property = value; } } + + static string AutoProperty { get; set; } + + static void Main() { - System.Console.WriteLine(""Deconstruct""); - x = 42; - y = 43; + (C.ArrayIndexer[0], C.Property, C.AutoProperty) = new C(); + System.Console.WriteLine(C.ArrayIndexer[0] + "" "" + C.property + "" "" + C.AutoProperty); } - public static implicit operator C(int x) + public void Deconstruct(out int a, out string b, out string c) { - System.Console.WriteLine(""conversion""); - return new C(); + a = 1; + b = ""hello""; + c = ""world""; } } "; - var expected = -@"M (previous i is C) -M (previous i is C) -getP -Deconstruct -conversion -conversion"; - - var comp = CompileAndVerify(source, expectedOutput: expected, parseOptions: TestOptions.Regular.WithTuplesFeature().WithRefsFeature()); + var comp = CompileAndVerify(source, expectedOutput: "1 hello world", additionalRefs: new[] { SystemCoreRef }, parseOptions: TestOptions.Regular.WithTuplesFeature()); comp.VerifyDiagnostics(); } [Fact] - public void UninitializedRight() + public void DifferentVariableRefKinds() { string source = @" class C { static void Main() { - int x; - (x, x) = x; + long a = 1; + int b; + C.M(ref a, out b); + System.Console.WriteLine(a + "" "" + b); + } + + static void M(ref long a, out int b) + { + (a, b) = new C(); + } + + public void Deconstruct(out int x, out byte y) + { + x = 2; + y = (byte)3; } } "; - var comp = CreateCompilationWithMscorlib(source, references: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, parseOptions: TestOptions.Regular.WithTuplesFeature()); - comp.VerifyDiagnostics( - // (7,18): error CS8206: No Deconstruct instance or extension method was found for type 'int'. - // (x, x) = x; - Diagnostic(ErrorCode.ERR_MissingDeconstruct, "x").WithArguments("int").WithLocation(7, 18), - // (7,18): error CS0165: Use of unassigned local variable 'x' - // (x, x) = x; - Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(7, 18) - ); + var comp = CompileAndVerify(source, expectedOutput: "2 3", additionalRefs: new[] { SystemCoreRef }, parseOptions: TestOptions.Regular.WithTuplesFeature()); + comp.VerifyDiagnostics(); } [Fact] - public void Indexers() + public void ConversionErrors() { string source = @" class C { - static SomeArray array; - static void Main() { - int y; - (Foo()[Bar()], y) = new C(); - System.Console.WriteLine($""Final array values[2] {array.values[2]}""); + byte x; + string y; + (x, y) = new C(); } - static SomeArray Foo() + public void Deconstruct(out int a, out int b) { - System.Console.WriteLine($""Foo""); - array = new SomeArray(); - return array; + a = b = 1; } +} +"; + var comp = CreateCompilationWithMscorlib(source, parseOptions: TestOptions.Regular.WithTuplesFeature()); + comp.VerifyDiagnostics( + // (8,9): error CS0266: Cannot implicitly convert type 'int' to 'byte'. An explicit conversion exists (are you missing a cast?) + // (x, y) = new C(); + Diagnostic(ErrorCode.ERR_NoImplicitConvCast, "(x, y) = new C()").WithArguments("int", "byte").WithLocation(8, 9), + // (8,9): error CS0029: Cannot implicitly convert type 'int' to 'string' + // (x, y) = new C(); + Diagnostic(ErrorCode.ERR_NoImplicitConv, "(x, y) = new C()").WithArguments("int", "string").WithLocation(8, 9) + ); + } - static int Bar() + [Fact] + public void ExpressionType() + { + string source = @" +class C +{ + static void Main() { - System.Console.WriteLine($""Bar""); - return 2; + int x, y; + var type = ((x, y) = new C()).GetType(); } - void Deconstruct(out int x, out int y) + public void Deconstruct(out int a, out int b) { - System.Console.WriteLine(""Deconstruct""); - x = 101; - y = 102; - } -} -class SomeArray -{ - public int[] values; - public SomeArray() { values = new [] { 42, 43, 44 }; } - public int this[int index] { - get { System.Console.WriteLine($""indexGet (with value {values[index]})""); return values[index]; } - set { System.Console.WriteLine($""indexSet (with value {value})""); values[index] = value; } + a = b = 1; } } "; - var expected = -@"Foo -Bar -Deconstruct -indexSet (with value 101) -Final array values[2] 101 -"; - var comp = CompileAndVerify(source, expectedOutput: expected, parseOptions: TestOptions.Regular.WithTuplesFeature().WithRefsFeature()); + + var comp = CreateCompilationWithMscorlib(source, parseOptions: TestOptions.Regular.WithTuplesFeature()); comp.VerifyDiagnostics( + // (7,38): error CS0023: Operator '.' cannot be applied to operand of type 'void' + // var type = ((x, y) = new C()).GetType(); + Diagnostic(ErrorCode.ERR_BadUnaryOp, ".").WithArguments(".", "void").WithLocation(7, 38) ); } [Fact] - public void NullRight() + public void LambdaStillNotValidStatement() { string source = @" class C { static void Main() { - int x; - (x, x) = null; + (a) => a; } } "; - var comp = CreateCompilationWithMscorlib(source, parseOptions: TestOptions.Regular.WithTuplesFeature().WithRefsFeature()); + var comp = CreateCompilationWithMscorlib(source, parseOptions: TestOptions.Regular.WithTuplesFeature()); comp.VerifyDiagnostics( - // (7,9): error CS8210: Deconstruct assignment requires an expression with a type on the right-hand-side. - // (x, x) = null; - Diagnostic(ErrorCode.ERR_DeconstructRequiresExpression, "(x, x) = null").WithLocation(7, 9), - // (7,10): error CS0165: Use of unassigned local variable 'x' - // (x, x) = null; - Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(7, 10) + // (6,9): error CS0201: Only assignment, call, increment, decrement, and new object expressions can be used as a statement + // (a) => a; + Diagnostic(ErrorCode.ERR_IllegalStatement, "(a) => a").WithLocation(6, 9) ); } [Fact] - public void VoidRight() + public void LambdaWithBodyStillNotValidStatement() { string source = @" class C { static void Main() { - int x; - (x, x) = M(); + (a, b) => { }; } - static void M() { } } "; - var comp = CreateCompilationWithMscorlib(source, parseOptions: TestOptions.Regular.WithTuplesFeature().WithRefsFeature()); + var comp = CreateCompilationWithMscorlib(source, parseOptions: TestOptions.Regular.WithTuplesFeature()); comp.VerifyDiagnostics( - // (7,18): error CS8206: No Deconstruct instance or extension method was found for type 'void'. - // (x, x) = M(); - Diagnostic(ErrorCode.ERR_MissingDeconstruct, "M()").WithArguments("void").WithLocation(7, 18) + // (6,9): error CS0201: Only assignment, call, increment, decrement, and new object expressions can be used as a statement + // (a, b) => { }; + Diagnostic(ErrorCode.ERR_IllegalStatement, "(a, b) => { }").WithLocation(6, 9) ); } [Fact] - public void AssigningTuple() + public void CastButNotCast() { + // int and string must be types, so (int, string) must be type and ((int, string)) a cast, but then .String() cannot follow a cast... string source = @" class C { static void Main() { - long x; - string y; - - (x, y) = (1, ""hello""); - System.Console.WriteLine(x + "" "" + y); + ((int, string)).ToString(); } } "; - var comp = CompileAndVerify(source, expectedOutput: "1 hello", additionalRefs: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, parseOptions: TestOptions.Regular.WithTuplesFeature()); - comp.VerifyDiagnostics(); + var comp = CreateCompilationWithMscorlib(source, references: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, parseOptions: TestOptions.Regular.WithTuplesFeature()); + comp.VerifyDiagnostics( + // (6,24): error CS1525: Invalid expression term '.' + // ((int, string)).ToString(); + Diagnostic(ErrorCode.ERR_InvalidExprTerm, ".").WithArguments(".").WithLocation(6, 24) + ); } - [Fact(Skip = "PROTOTYPE(tuples)")] - public void AssigningTuple2() + [Fact] + [CompilerTrait(CompilerFeature.RefLocalsReturns)] + public void RefReturningMethod() { string source = @" class C { + static int i = 0; + static void Main() { - long x; - string y; + (M(), M()) = new C(); + System.Console.WriteLine($""Final i is {i}""); + } - (x, y) = M(); - System.Console.WriteLine(x + "" "" + y); + static ref int M() + { + System.Console.WriteLine($""M (previous i is {i})""); + return ref i; } - static System.ValueTuple M() + void Deconstruct(out int x, out int y) { - return (1, ""hello""); + System.Console.WriteLine(""Deconstruct""); + x = 42; + y = 43; } } "; - // Should not give this error: (9,18): error CS0029: Cannot implicitly convert type '(int, string)' to '(long, string)' - var comp = CompileAndVerify(source, expectedOutput: "1 hello", additionalRefs: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, parseOptions: TestOptions.Regular.WithTuplesFeature()); - comp.VerifyDiagnostics(); + var expected = +@"M (previous i is 0) +M (previous i is 0) +Deconstruct +Final i is 43 +"; + + var comp = CompileAndVerify(source, expectedOutput: expected, parseOptions: TestOptions.Regular.WithTuplesFeature().WithRefsFeature()); + comp.VerifyDiagnostics( + ); } [Fact] - public void AssigningLongTuple() + [CompilerTrait(CompilerFeature.RefLocalsReturns)] + public void RefReturningProperty() { string source = @" class C { + static int i = 0; + static void Main() { - long x; - int y; - - (x, x, x, x, x, x, x, x, x, y) = (1, 1, 1, 1, 1, 1, 1, 1, 4, 2); - System.Console.WriteLine(x + "" "" + y); + (P, P) = new C(); + System.Console.WriteLine($""Final i is {i}""); } -} -"; - var comp = CompileAndVerify(source, expectedOutput: "4 2", additionalRefs: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, parseOptions: TestOptions.Regular.WithTuplesFeature()); - comp.VerifyDiagnostics(); + static ref int P + { + get + { + System.Console.WriteLine($""P (previous i is {i})""); + return ref i; + } + } + + void Deconstruct(out int x, out int y) + { + System.Console.WriteLine(""Deconstruct""); + x = 42; + y = 43; + } +} +"; + var expected = +@"P (previous i is 0) +P (previous i is 0) +Deconstruct +Final i is 43 +"; + + var comp = CompileAndVerify(source, expectedOutput: expected, parseOptions: TestOptions.Regular.WithTuplesFeature().WithRefsFeature()); + comp.VerifyDiagnostics(); + } + + [Fact(Skip = "PROTOTYPE(tuples)")] + [CompilerTrait(CompilerFeature.RefLocalsReturns)] + public void RefReturningMethod2() + { + string source = @" +class C +{ + static int i; + + static void Main() + { + (M(), M()) = new C(); + } + + static ref int M() + { + System.Console.WriteLine(""M""); + return ref i; + } + + void Deconstruct(out int i, out int j) + { + i = 42; + j = 43; + } +} +"; + + var comp = CreateCompilationWithMscorlib(source, parseOptions: TestOptions.Regular.WithTuplesFeature().WithRefsFeature()); + comp.VerifyDiagnostics(); + + // This error is wrong + // (4,16): warning CS0649: Field 'C.i' is never assigned to, and will always have its default value 0 + // static int i; + //Diagnostic(ErrorCode.WRN_UnassignedInternalField, "i").WithArguments("C.i", "0").WithLocation(4, 16) + } + + [Fact] + [CompilerTrait(CompilerFeature.RefLocalsReturns)] + public void RefReturningMethodFlow() + { + string source = @" +struct C +{ + static C i; + static C P { get { System.Console.WriteLine(""getP""); return i; } set { System.Console.WriteLine(""setP""); i = value; } } + + static void Main() + { + (M(), M()) = P; + } + + static ref C M() + { + System.Console.WriteLine($""M (previous i is {i})""); + return ref i; + } + + void Deconstruct(out int x, out int y) + { + System.Console.WriteLine(""Deconstruct""); + x = 42; + y = 43; + } + + public static implicit operator C(int x) + { + System.Console.WriteLine(""conversion""); + return new C(); + } +} +"; + + var expected = +@"M (previous i is C) +M (previous i is C) +getP +Deconstruct +conversion +conversion"; + + var comp = CompileAndVerify(source, expectedOutput: expected, parseOptions: TestOptions.Regular.WithTuplesFeature().WithRefsFeature()); + comp.VerifyDiagnostics(); + } + + [Fact] + public void UninitializedRight() + { + string source = @" +class C +{ + static void Main() + { + int x; + (x, x) = x; + } +} +static class D +{ + public static void Deconstruct(this int input, out int output, out int output2) { output = input; output2 = input; } +} +"; + + var comp = CreateCompilationWithMscorlib(source, references: new[] { ValueTupleRef, SystemRuntimeFacadeRef, SystemCoreRef }, parseOptions: TestOptions.Regular.WithTuplesFeature()); + comp.VerifyDiagnostics( + // (7,18): error CS0165: Use of unassigned local variable 'x' + // (x, x) = x; + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(7, 18) + ); + } + + [Fact] + public void Indexers() + { + string source = @" +class C +{ + static SomeArray array; + + static void Main() + { + int y; + (Foo()[Bar()], y) = new C(); + System.Console.WriteLine($""Final array values[2] {array.values[2]}""); + } + + static SomeArray Foo() + { + System.Console.WriteLine($""Foo""); + array = new SomeArray(); + return array; + } + + static int Bar() + { + System.Console.WriteLine($""Bar""); + return 2; + } + + void Deconstruct(out int x, out int y) + { + System.Console.WriteLine(""Deconstruct""); + x = 101; + y = 102; + } +} +class SomeArray +{ + public int[] values; + public SomeArray() { values = new [] { 42, 43, 44 }; } + public int this[int index] { + get { System.Console.WriteLine($""indexGet (with value {values[index]})""); return values[index]; } + set { System.Console.WriteLine($""indexSet (with value {value})""); values[index] = value; } + } +} +"; + var expected = +@"Foo +Bar +Deconstruct +indexSet (with value 101) +Final array values[2] 101 +"; + var comp = CompileAndVerify(source, expectedOutput: expected, parseOptions: TestOptions.Regular.WithTuplesFeature().WithRefsFeature()); + comp.VerifyDiagnostics( + ); + } + + [Fact] + public void NullRight() + { + string source = @" +class C +{ + static void Main() + { + int x; + (x, x) = null; + } +} +"; + + var comp = CreateCompilationWithMscorlib(source, parseOptions: TestOptions.Regular.WithTuplesFeature().WithRefsFeature()); + comp.VerifyDiagnostics( + // (7,9): error CS8210: Deconstruct assignment requires an expression with a type on the right-hand-side. + // (x, x) = null; + Diagnostic(ErrorCode.ERR_DeconstructRequiresExpression, "(x, x) = null").WithLocation(7, 9), + // (7,10): error CS0165: Use of unassigned local variable 'x' + // (x, x) = null; + Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(7, 10) + ); + } + + [Fact] + public void VoidRight() + { + string source = @" +class C +{ + static void Main() + { + int x; + (x, x) = M(); + } + static void M() { } +} +"; + + var comp = CreateCompilationWithMscorlib(source, parseOptions: TestOptions.Regular.WithTuplesFeature().WithRefsFeature()); + comp.VerifyDiagnostics( + // (7,18): error CS1061: 'void' does not contain a definition for 'Deconstruct' and no extension method 'Deconstruct' accepting a first argument of type 'void' could be found (are you missing a using directive or an assembly reference?) + // (x, x) = M(); + Diagnostic(ErrorCode.ERR_NoSuchMemberOrExtension, "M()").WithArguments("void", "Deconstruct").WithLocation(7, 18), + // (7,18): error CS8206: No Deconstruct instance or extension method was found for type 'void', with 2 out parameters. + // (x, x) = M(); + Diagnostic(ErrorCode.ERR_MissingDeconstruct, "M()").WithArguments("void", "2").WithLocation(7, 18) + ); + } + + [Fact] + public void AssigningTuple() + { + string source = @" +class C +{ + static void Main() + { + long x; + string y; + + (x, y) = (1, ""hello""); + System.Console.WriteLine(x + "" "" + y); + } +} +"; + + var comp = CompileAndVerify(source, expectedOutput: "1 hello", additionalRefs: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, parseOptions: TestOptions.Regular.WithTuplesFeature()); + comp.VerifyDiagnostics(); + } + + [Fact] + public void AssigningTupleWithConversion() + { + string source = @" +class C +{ + static void Main() + { + long x; + string y; + + (x, y) = M(); + System.Console.WriteLine(x + "" "" + y); + } + + static System.ValueTuple M() + { + return (1, ""hello""); + } +} +"; + var comp = CompileAndVerify(source, expectedOutput: "1 hello", additionalRefs: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, parseOptions: TestOptions.Regular.WithTuplesFeature()); + comp.VerifyDiagnostics(); + } + + [Fact] + public void AssigningLongTuple() + { + string source = @" +class C +{ + static void Main() + { + long x; + int y; + + (x, x, x, x, x, x, x, x, x, y) = (1, 1, 1, 1, 1, 1, 1, 1, 4, 2); + System.Console.WriteLine(x + "" "" + y); + } +} +"; + + var comp = CompileAndVerify(source, expectedOutput: "4 2", additionalRefs: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, parseOptions: TestOptions.Regular.WithTuplesFeature()); + comp.VerifyDiagnostics(); + comp.VerifyIL("C.Main", @" +{ + // Code size 141 (0x8d) + .maxstack 10 + .locals init (long V_0, //x + int V_1) //y + IL_0000: ldc.i4.1 + IL_0001: ldc.i4.1 + IL_0002: ldc.i4.1 + IL_0003: ldc.i4.1 + IL_0004: ldc.i4.1 + IL_0005: ldc.i4.1 + IL_0006: ldc.i4.1 + IL_0007: ldc.i4.1 + IL_0008: ldc.i4.4 + IL_0009: ldc.i4.2 + IL_000a: newobj ""System.ValueTuple..ctor(int, int, int)"" + IL_000f: newobj ""System.ValueTuple..ctor(int, int, int, int, int, int, int, (int, int, int))"" + IL_0014: dup + IL_0015: ldfld ""int System.ValueTuple.Item1"" + IL_001a: conv.i8 + IL_001b: stloc.0 + IL_001c: dup + IL_001d: ldfld ""int System.ValueTuple.Item2"" + IL_0022: conv.i8 + IL_0023: stloc.0 + IL_0024: dup + IL_0025: ldfld ""int System.ValueTuple.Item3"" + IL_002a: conv.i8 + IL_002b: stloc.0 + IL_002c: dup + IL_002d: ldfld ""int System.ValueTuple.Item4"" + IL_0032: conv.i8 + IL_0033: stloc.0 + IL_0034: dup + IL_0035: ldfld ""int System.ValueTuple.Item5"" + IL_003a: conv.i8 + IL_003b: stloc.0 + IL_003c: dup + IL_003d: ldfld ""int System.ValueTuple.Item6"" + IL_0042: conv.i8 + IL_0043: stloc.0 + IL_0044: dup + IL_0045: ldfld ""int System.ValueTuple.Item7"" + IL_004a: conv.i8 + IL_004b: stloc.0 + IL_004c: dup + IL_004d: ldfld ""(int, int, int) System.ValueTuple.Rest"" + IL_0052: ldfld ""int System.ValueTuple.Item1"" + IL_0057: conv.i8 + IL_0058: stloc.0 + IL_0059: dup + IL_005a: ldfld ""(int, int, int) System.ValueTuple.Rest"" + IL_005f: ldfld ""int System.ValueTuple.Item2"" + IL_0064: conv.i8 + IL_0065: stloc.0 + IL_0066: ldfld ""(int, int, int) System.ValueTuple.Rest"" + IL_006b: ldfld ""int System.ValueTuple.Item3"" + IL_0070: stloc.1 + IL_0071: ldloc.0 + IL_0072: box ""long"" + IL_0077: ldstr "" "" + IL_007c: ldloc.1 + IL_007d: box ""int"" + IL_0082: call ""string string.Concat(object, object, object)"" + IL_0087: call ""void System.Console.WriteLine(string)"" + IL_008c: ret +} +"); + } + + [Fact] + public void AssigningLongTupleWithNames() + { + string source = @" +class C +{ + static void Main() + { + long x; + int y; + + (x, x, x, x, x, x, x, x, x, y) = (a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8, i: 9, j: 10); + System.Console.WriteLine(x + "" "" + y); + } +} +"; + + var comp = CompileAndVerify(source, expectedOutput: "9 10", additionalRefs: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, parseOptions: TestOptions.Regular.WithTuplesFeature()); + comp.VerifyDiagnostics(); + } + + [Fact] + public void AssigningLongTuple2() + { + string source = @" +class C +{ + static void Main() + { + long x; + int y; + + (x, x, x, x, x, x, x, x, x, y) = (1, 1, 1, 1, 1, 1, 1, 1, 4, (byte)2); + System.Console.WriteLine(x + "" "" + y); + } +} +"; + + var comp = CompileAndVerify(source, expectedOutput: "4 2", additionalRefs: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, parseOptions: TestOptions.Regular.WithTuplesFeature()); + comp.VerifyDiagnostics(); + } + + [Fact] + public void AssigningTypelessTuple() + { + string source = @" +class C +{ + static void Main() + { + string x = ""goodbye""; + string y; + + (x, y) = (null, ""hello""); + System.Console.WriteLine($""{x}{y}""); + } +} +"; + + var comp = CompileAndVerify(source, expectedOutput: "hello", additionalRefs: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, parseOptions: TestOptions.Regular.WithTuplesFeature()); + comp.VerifyDiagnostics(); comp.VerifyIL("C.Main", @" { - // Code size 141 (0x8d) - .maxstack 10 - .locals init (long V_0, //x - int V_1) //y - IL_0000: ldc.i4.1 - IL_0001: ldc.i4.1 - IL_0002: ldc.i4.1 - IL_0003: ldc.i4.1 - IL_0004: ldc.i4.1 - IL_0005: ldc.i4.1 - IL_0006: ldc.i4.1 - IL_0007: ldc.i4.1 - IL_0008: ldc.i4.4 - IL_0009: ldc.i4.2 - IL_000a: newobj ""System.ValueTuple..ctor(int, int, int)"" - IL_000f: newobj ""System.ValueTuple..ctor(int, int, int, int, int, int, int, (int, int, int))"" - IL_0014: dup - IL_0015: ldfld ""int System.ValueTuple.Item1"" - IL_001a: conv.i8 - IL_001b: stloc.0 - IL_001c: dup - IL_001d: ldfld ""int System.ValueTuple.Item2"" - IL_0022: conv.i8 - IL_0023: stloc.0 - IL_0024: dup - IL_0025: ldfld ""int System.ValueTuple.Item3"" - IL_002a: conv.i8 - IL_002b: stloc.0 - IL_002c: dup - IL_002d: ldfld ""int System.ValueTuple.Item4"" - IL_0032: conv.i8 - IL_0033: stloc.0 - IL_0034: dup - IL_0035: ldfld ""int System.ValueTuple.Item5"" - IL_003a: conv.i8 - IL_003b: stloc.0 - IL_003c: dup - IL_003d: ldfld ""int System.ValueTuple.Item6"" - IL_0042: conv.i8 - IL_0043: stloc.0 - IL_0044: dup - IL_0045: ldfld ""int System.ValueTuple.Item7"" - IL_004a: conv.i8 - IL_004b: stloc.0 - IL_004c: dup - IL_004d: ldfld ""(int, int, int) System.ValueTuple.Rest"" - IL_0052: ldfld ""int System.ValueTuple.Item1"" - IL_0057: conv.i8 - IL_0058: stloc.0 - IL_0059: dup - IL_005a: ldfld ""(int, int, int) System.ValueTuple.Rest"" - IL_005f: ldfld ""int System.ValueTuple.Item2"" - IL_0064: conv.i8 - IL_0065: stloc.0 - IL_0066: ldfld ""(int, int, int) System.ValueTuple.Rest"" - IL_006b: ldfld ""int System.ValueTuple.Item3"" - IL_0070: stloc.1 - IL_0071: ldloc.0 - IL_0072: box ""long"" - IL_0077: ldstr "" "" - IL_007c: ldloc.1 - IL_007d: box ""int"" - IL_0082: call ""string string.Concat(object, object, object)"" - IL_0087: call ""void System.Console.WriteLine(string)"" - IL_008c: ret + // Code size 48 (0x30) + .maxstack 3 + .locals init (string V_0, //x + string V_1) //y + IL_0000: ldstr ""goodbye"" + IL_0005: stloc.0 + IL_0006: ldnull + IL_0007: ldstr ""hello"" + IL_000c: newobj ""System.ValueTuple..ctor(string, string)"" + IL_0011: dup + IL_0012: ldfld ""string System.ValueTuple.Item1"" + IL_0017: stloc.0 + IL_0018: ldfld ""string System.ValueTuple.Item2"" + IL_001d: stloc.1 + IL_001e: ldstr ""{0}{1}"" + IL_0023: ldloc.0 + IL_0024: ldloc.1 + IL_0025: call ""string string.Format(string, object, object)"" + IL_002a: call ""void System.Console.WriteLine(string)"" + IL_002f: ret +} +"); + } + + [Fact] + public void TupleWithNoConversion() + { + string source = @" +class C +{ + static void Main() + { + byte x; + string y; + + (x, y) = (1, 2); + } +} +"; + var comp = CreateCompilationWithMscorlib(source, references: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, parseOptions: TestOptions.Regular.WithTuplesFeature()); + comp.VerifyDiagnostics( + // (9,9): error CS0266: Cannot implicitly convert type 'int' to 'byte'. An explicit conversion exists (are you missing a cast?) + // (x, y) = (1, 2); + Diagnostic(ErrorCode.ERR_NoImplicitConvCast, "(x, y) = (1, 2)").WithArguments("int", "byte").WithLocation(9, 9), + // (9,9): error CS0029: Cannot implicitly convert type 'int' to 'string' + // (x, y) = (1, 2); + Diagnostic(ErrorCode.ERR_NoImplicitConv, "(x, y) = (1, 2)").WithArguments("int", "string").WithLocation(9, 9) + ); + } + + [Fact] + public void AssigningIntoProperties() + { + string source = @" +class C +{ + static long x { set { System.Console.WriteLine($""setX {value}""); } } + static string y { get; set; } + + static void Main() + { + (x, y) = new C(); + System.Console.WriteLine(y); + } + + public void Deconstruct(out int a, out string b) + { + a = 1; + b = ""hello""; + } +} +"; + string expected = +@"setX 1 +hello"; + var comp = CompileAndVerify(source, expectedOutput: expected, additionalRefs: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, parseOptions: TestOptions.Regular.WithTuplesFeature()); + comp.VerifyDiagnostics(); + } + + [Fact] + public void AssigningTupleIntoProperties() + { + string source = @" +class C +{ + static long x { set { System.Console.WriteLine($""setX {value}""); } } + static string y { get; set; } + + static void Main() + { + (x, y) = (1, ""hello""); + System.Console.WriteLine(y); + } +} +"; + string expected = +@"setX 1 +hello"; + var comp = CompileAndVerify(source, expectedOutput: expected, additionalRefs: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, parseOptions: TestOptions.Regular.WithTuplesFeature()); + comp.VerifyDiagnostics(); + } + + [Fact] + public void Swap() + { + string source = @" +class C +{ + static int x = 2; + static int y = 4; + + static void Main() + { + Swap(); + System.Console.WriteLine(x + "" "" + y); + } + + static void Swap() + { + (x, y) = (y, x); + } +} +"; + + var comp = CompileAndVerify(source, expectedOutput: "4 2", additionalRefs: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, parseOptions: TestOptions.Regular.WithTuplesFeature()); + comp.VerifyDiagnostics(); + comp.VerifyIL("C.Swap", @" +{ + // Code size 37 (0x25) + .maxstack 2 + IL_0000: ldsfld ""int C.y"" + IL_0005: ldsfld ""int C.x"" + IL_000a: newobj ""System.ValueTuple..ctor(int, int)"" + IL_000f: dup + IL_0010: ldfld ""int System.ValueTuple.Item1"" + IL_0015: stsfld ""int C.x"" + IL_001a: ldfld ""int System.ValueTuple.Item2"" + IL_001f: stsfld ""int C.y"" + IL_0024: ret } "); } + + [Fact] + public void TupleWithUseSiteError() + { + string source = @" + +namespace System +{ + public struct ValueTuple + { + public T1 Item1; + + public ValueTuple(T1 item1, T2 item2) + { + this.Item1 = item1; + } + } +} +class C +{ + static void Main() + { + int x; + int y; + + (x, y) = (1, 2); + } +} +"; + + var comp = CreateCompilationWithMscorlib(source, assemblyName: "comp", parseOptions: TestOptions.Regular.WithTuplesFeature()); + comp.VerifyDiagnostics(); + comp.VerifyEmitDiagnostics( + // (22,9): error CS8205: Member 'Item2' was not found on type 'ValueTuple' from assembly 'comp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'. + // (x, y) = (1, 2); + Diagnostic(ErrorCode.ERR_PredefinedTypeMemberNotFoundInAssembly, "(x, y) = (1, 2)").WithArguments("Item2", "System.ValueTuple", "comp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null").WithLocation(22, 9) + ); + } + + [Fact] + public void CircularFlow() + { + string source = @" +class C +{ + static void Main() + { + (object i, object ii) x = (1,2); + object y; + + (x.ii, y) = x; + System.Console.WriteLine(x + "" "" + y); + } +} +"; + + var comp = CompileAndVerify(source, expectedOutput: "(1, 1) 2", additionalRefs: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, parseOptions: TestOptions.Regular.WithTuplesFeature()); + comp.VerifyDiagnostics(); + } + + [Fact] + [CompilerTrait(CompilerFeature.RefLocalsReturns)] + public void CircularFlow2() + { + string source = @" +class C +{ + static void Main() + { + (object i, object ii) x = (1,2); + object y; + + ref var a = ref x; + + (a.ii, y) = x; + System.Console.WriteLine(x + "" "" + y); + } +} +"; + + var comp = CompileAndVerify(source, expectedOutput: "(1, 1) 2", additionalRefs: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, parseOptions: TestOptions.Regular.WithTuplesFeature().WithRefsFeature()); + comp.VerifyDiagnostics(); + } + + [Fact] + public void DeconstructUsingBaseDeconstructMethod() + { + string source = @" +class Base +{ + public void Deconstruct(out int a, out int b) { a = 1; b = 2; } +} +class C : Base +{ + static void Main() + { + int x, y; + (x, y) = new C(); + + System.Console.WriteLine(x + "" "" + y); + } + + public void Deconstruct(out int c) { c = 42; } +} +"; + + var comp = CompileAndVerify(source, expectedOutput: "1 2", additionalRefs: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, parseOptions: TestOptions.Regular.WithTuplesFeature().WithRefsFeature()); + comp.VerifyDiagnostics(); + } + + [Fact] + public void AssignUsingAmbiguousDeconstruction() + { + string source = @" +class Base +{ + public void Deconstruct(out int a, out int b) { a = 1; b = 2; } + public void Deconstruct(out long a, out long b) { a = 1; b = 2; } +} +class C : Base +{ + static void Main() + { + int x, y; + (x, y) = new C(); + + System.Console.WriteLine(x + "" "" + y); + } +} +"; + + var comp = CreateCompilationWithMscorlib(source, parseOptions: TestOptions.Regular.WithTuplesFeature()); + comp.VerifyDiagnostics( + // (12,18): error CS0121: The call is ambiguous between the following methods or properties: 'Base.Deconstruct(out int, out int)' and 'Base.Deconstruct(out long, out long)' + // (x, y) = new C(); + Diagnostic(ErrorCode.ERR_AmbigCall, "new C()").WithArguments("Base.Deconstruct(out int, out int)", "Base.Deconstruct(out long, out long)").WithLocation(12, 18), + // (12,18): error CS8206: No Deconstruct instance or extension method was found for type 'C', with 2 out parameters. + // (x, y) = new C(); + Diagnostic(ErrorCode.ERR_MissingDeconstruct, "new C()").WithArguments("C", "2").WithLocation(12, 18) + ); + } + [Fact] - public void AssigningLongTupleWithNames() + public void DeconstructUsingExtensionMethod() { string source = @" +using System; class C { static void Main() { - long x; - int y; + int x; + string y, z; + (x, (y, z)) = Tuple.Create(1, Tuple.Create(""hello"", ""world"")); - (x, x, x, x, x, x, x, x, x, y) = (a: 1, b: 2, c: 3, d: 4, e: 5, f: 6, g: 7, h: 8, i: 9, j: 10); - System.Console.WriteLine(x + "" "" + y); + System.Console.WriteLine(x + "" "" + y + "" "" + z); } } "; - var comp = CompileAndVerify(source, expectedOutput: "9 10", additionalRefs: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, parseOptions: TestOptions.Regular.WithTuplesFeature()); + var comp = CompileAndVerify(source, expectedOutput: "1 hello world", additionalRefs: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, parseOptions: TestOptions.Regular.WithTuplesFeature().WithRefsFeature()); comp.VerifyDiagnostics(); } [Fact] - public void AssigningLongTuple2() + public void NestedDeconstructUsingExtensionMethod() { string source = @" +using System; class C { static void Main() { - long x; - int y; + int x; + string y, z; + (x, (y, z)) = Tuple.Create(1, Tuple.Create(""hello"", ""world"")); - (x, x, x, x, x, x, x, x, x, y) = (1, 1, 1, 1, 1, 1, 1, 1, 4, (byte)2); - System.Console.WriteLine(x + "" "" + y); + System.Console.WriteLine(x + "" "" + y + "" "" + z); } } "; - var comp = CompileAndVerify(source, expectedOutput: "4 2", additionalRefs: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, parseOptions: TestOptions.Regular.WithTuplesFeature()); + var comp = CompileAndVerify(source, expectedOutput: "1 hello world", additionalRefs: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, parseOptions: TestOptions.Regular.WithTuplesFeature().WithRefsFeature()); comp.VerifyDiagnostics(); } [Fact] - public void AssigningTypelessTuple() + public void DeconstructIsDynamicField() { string source = @" class C { static void Main() { - string x = ""goodbye""; - string y; + int x, y; + (x, y) = new C(); - (x, y) = (null, ""hello""); - System.Console.WriteLine($""{x}{y}""); } + public dynamic Deconstruct = null; } "; - var comp = CompileAndVerify(source, expectedOutput: "hello", additionalRefs: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, parseOptions: TestOptions.Regular.WithTuplesFeature()); - comp.VerifyDiagnostics(); - comp.VerifyIL("C.Main", @" -{ - // Code size 48 (0x30) - .maxstack 3 - .locals init (string V_0, //x - string V_1) //y - IL_0000: ldstr ""goodbye"" - IL_0005: stloc.0 - IL_0006: ldnull - IL_0007: ldstr ""hello"" - IL_000c: newobj ""System.ValueTuple..ctor(string, string)"" - IL_0011: dup - IL_0012: ldfld ""string System.ValueTuple.Item1"" - IL_0017: stloc.0 - IL_0018: ldfld ""string System.ValueTuple.Item2"" - IL_001d: stloc.1 - IL_001e: ldstr ""{0}{1}"" - IL_0023: ldloc.0 - IL_0024: ldloc.1 - IL_0025: call ""string string.Format(string, object, object)"" - IL_002a: call ""void System.Console.WriteLine(string)"" - IL_002f: ret -} -"); + var comp = CreateCompilationWithMscorlib(source, references: new[] { ValueTupleRef, SystemRuntimeFacadeRef, SystemCoreRef, CSharpRef }, parseOptions: TestOptions.Regular.WithTuplesFeature()); + comp.VerifyDiagnostics( + // (7,18): error CS8206: No Deconstruct instance or extension method was found for type 'C', with 2 out parameters. + // (x, y) = new C(); + Diagnostic(ErrorCode.ERR_MissingDeconstruct, "new C()").WithArguments("C", "2").WithLocation(7, 18) + ); } [Fact] - public void TupleWithNoConversion() + public void DeconstructIsField() { string source = @" class C { static void Main() { - byte x; - string y; + int x, y; + (x, y) = new C(); - (x, y) = (1, 2); } + public object Deconstruct = null; } "; + var comp = CreateCompilationWithMscorlib(source, references: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, parseOptions: TestOptions.Regular.WithTuplesFeature()); comp.VerifyDiagnostics( - // (9,9): error CS0266: Cannot implicitly convert type 'int' to 'byte'. An explicit conversion exists (are you missing a cast?) - // (x, y) = (1, 2); - Diagnostic(ErrorCode.ERR_NoImplicitConvCast, "(x, y) = (1, 2)").WithArguments("int", "byte").WithLocation(9, 9), - // (9,9): error CS0029: Cannot implicitly convert type 'int' to 'string' - // (x, y) = (1, 2); - Diagnostic(ErrorCode.ERR_NoImplicitConv, "(x, y) = (1, 2)").WithArguments("int", "string").WithLocation(9, 9) + // (7,18): error CS1955: Non-invocable member 'C.Deconstruct' cannot be used like a method. + // (x, y) = new C(); + Diagnostic(ErrorCode.ERR_NonInvocableMemberCalled, "new C()").WithArguments("C.Deconstruct").WithLocation(7, 18), + // (7,18): error CS8206: No Deconstruct instance or extension method was found for type 'C', with 2 out parameters. + // (x, y) = new C(); + Diagnostic(ErrorCode.ERR_MissingDeconstruct, "new C()").WithArguments("C", "2").WithLocation(7, 18) ); } [Fact] - public void AssigningIntoProperties() + public void OverrideDeconstruct() { string source = @" -class C +class Base +{ + public virtual void Deconstruct(out int a, out string b) { a = 1; b = ""hello""; } +} +class C : Base { - static long x { set { System.Console.WriteLine($""setX {value}""); } } - static string y { get; set; } - static void Main() { + int x; + string y; (x, y) = new C(); - System.Console.WriteLine(y); } + public override void Deconstruct(out int a, out string b) { a = 1; b = ""hello""; System.Console.WriteLine(""override""); } +} +"; - public void Deconstruct(out int a, out string b) + var comp = CompileAndVerify(source, expectedOutput: "override", additionalRefs: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, parseOptions: TestOptions.Regular.WithTuplesFeature().WithRefsFeature()); + comp.VerifyDiagnostics(); + } + + [Fact] + public void DeconstructRefTuple() + { + string template = @" +using System; +class C +{ + static void Main() { - a = 1; - b = ""hello""; + int VARIABLES; // int x1, x2, ... + (VARIABLES) = (TUPLE).ToTuple(); // (x1, x2, ...) = (1, 2, ...).ToTuple(); + + System.Console.WriteLine(OUTPUT); } } "; - string expected = -@"setX 1 -hello"; - var comp = CompileAndVerify(source, expectedOutput: expected, additionalRefs: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, parseOptions: TestOptions.Regular.WithTuplesFeature()); - comp.VerifyDiagnostics(); + for (int i = 2; i <= 21; i++) + { + var tuple = String.Join(", ", Enumerable.Range(1, i).Select(n => n.ToString())); + var variables = String.Join(", ", Enumerable.Range(1, i).Select(n => $"x{n}")); + var output = String.Join(@" + "" "" + ", Enumerable.Range(1, i).Select(n => $"x{n}")); + var expected = String.Join(" ", Enumerable.Range(1, i).Select(n => n)); + + var source = template.Replace("VARIABLES", variables).Replace("TUPLE", tuple).Replace("OUTPUT", output); + var comp = CompileAndVerify(source, expectedOutput: expected, additionalRefs: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, parseOptions: TestOptions.Regular.WithTuplesFeature().WithRefsFeature()); + comp.VerifyDiagnostics(); + } } [Fact] - public void AssigningTupleIntoProperties() + public void CannotDeconstructRefTuple22() { - string source = @" + string template = @" +using System; class C { - static long x { set { System.Console.WriteLine($""setX {value}""); } } - static string y { get; set; } - static void Main() { - (x, y) = (1, ""hello""); - System.Console.WriteLine(y); + int VARIABLES; // int x1, x2, ... + (VARIABLES) = CreateLongRef(1, 2, 3, 4, 5, 6, 7, CreateLongRef(8, 9, 10, 11, 12, 13, 14, Tuple.Create(15, 16, 17, 18, 19, 20, 21, 22))); } + + public static Tuple CreateLongRef(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7, TRest rest) => + new Tuple(item1, item2, item3, item4, item5, item6, item7, rest); } "; - string expected = -@"setX 1 -hello"; - var comp = CompileAndVerify(source, expectedOutput: expected, additionalRefs: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, parseOptions: TestOptions.Regular.WithTuplesFeature()); - comp.VerifyDiagnostics(); + var tuple = String.Join(", ", Enumerable.Range(1, 22).Select(n => n.ToString())); + var variables = String.Join(", ", Enumerable.Range(1, 22).Select(n => $"x{n}")); + + var source = template.Replace("VARIABLES", variables).Replace("TUPLE", tuple); + + var comp = CreateCompilationWithMscorlib(source, references: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, parseOptions: TestOptions.Regular.WithTuplesFeature()); + comp.VerifyDiagnostics( + // (8,113): error CS1501: No overload for method 'Deconstruct' takes 22 arguments + // (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, x21, x22) = CreateLongRef(1, 2, 3, 4, 5, 6, 7, CreateLongRef(8, 9, 10, 11, 12, 13, 14, Tuple.Create(15, 16, 17, 18, 19, 20, 21, 22))); + Diagnostic(ErrorCode.ERR_BadArgCount, "CreateLongRef(1, 2, 3, 4, 5, 6, 7, CreateLongRef(8, 9, 10, 11, 12, 13, 14, Tuple.Create(15, 16, 17, 18, 19, 20, 21, 22)))").WithArguments("Deconstruct", "22").WithLocation(8, 113), + // (8,113): error CS8206: No Deconstruct instance or extension method was found for type 'Tuple>>>', with 22 out parameters. + // (x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, x16, x17, x18, x19, x20, x21, x22) = CreateLongRef(1, 2, 3, 4, 5, 6, 7, CreateLongRef(8, 9, 10, 11, 12, 13, 14, Tuple.Create(15, 16, 17, 18, 19, 20, 21, 22))); + Diagnostic(ErrorCode.ERR_MissingDeconstruct, "CreateLongRef(1, 2, 3, 4, 5, 6, 7, CreateLongRef(8, 9, 10, 11, 12, 13, 14, Tuple.Create(15, 16, 17, 18, 19, 20, 21, 22)))").WithArguments("System.Tuple>>>", "22").WithLocation(8, 113) + ); } [Fact] - public void Swap() + public void DeconstructExtensionMethod() { string source = @" class C { - static int x = 2; - static int y = 4; - static void Main() { - Swap(); + long x; + string y; + + (x, y) = new C(); System.Console.WriteLine(x + "" "" + y); } - - static void Swap() +} +static class D +{ + public static void Deconstruct(this C value, out int a, out string b) { - (x, y) = (y, x); + a = 1; + b = ""hello""; } } "; - var comp = CompileAndVerify(source, expectedOutput: "4 2", additionalRefs: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, parseOptions: TestOptions.Regular.WithTuplesFeature()); + var comp = CompileAndVerify(source, expectedOutput: "1 hello", additionalRefs: new[] { SystemCoreRef }, parseOptions: TestOptions.Regular.WithTuplesFeature()); comp.VerifyDiagnostics(); - comp.VerifyIL("C.Swap", @" -{ - // Code size 37 (0x25) - .maxstack 2 - IL_0000: ldsfld ""int C.y"" - IL_0005: ldsfld ""int C.x"" - IL_000a: newobj ""System.ValueTuple..ctor(int, int)"" - IL_000f: dup - IL_0010: ldfld ""int System.ValueTuple.Item1"" - IL_0015: stsfld ""int C.x"" - IL_001a: ldfld ""int System.ValueTuple.Item2"" - IL_001f: stsfld ""int C.y"" - IL_0024: ret -} -"); } - [Fact] - public void TupleWithUseSiteError() + public void DeconstructGenericExtensionMethod() { string source = @" - -namespace System +class C { - public struct ValueTuple + static void Main() { - public T1 Item1; + long x; + string y; - public ValueTuple(T1 item1, T2 item2) - { - this.Item1 = item1; - } + (x, y) = new C1(); + } +} + +public class C1 { } + +static class Extension +{ + public static void Deconstruct(this C1 value, out int a, out T b) + { + a = 2; + b = default(T); + System.Console.WriteLine(""Deconstructed""); } } +"; + + var comp = CompileAndVerify(source, expectedOutput: "Deconstructed", additionalRefs: new[] { SystemCoreRef }, parseOptions: TestOptions.Regular.WithTuplesFeature()); + comp.VerifyDiagnostics(); + } + + [Fact] + public void DeconstructUsingDynamicMethod() + { + string source = @" class C { static void Main() { int x; - int y; + string y; - (x, y) = (1, 2); + dynamic c = new C(); + (x, y) = c; } + public void Deconstruct(out int a, out string b) { a = 1; b = ""hello""; } } "; - - var comp = CreateCompilationWithMscorlib(source, assemblyName: "comp", parseOptions: TestOptions.Regular.WithTuplesFeature()); - comp.VerifyDiagnostics(); - comp.VerifyEmitDiagnostics( - // (22,9): error CS8205: Member 'Item2' was not found on type 'ValueTuple' from assembly 'comp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'. - // (x, y) = (1, 2); - Diagnostic(ErrorCode.ERR_PredefinedTypeMemberNotFoundInAssembly, "(x, y) = (1, 2)").WithArguments("Item2", "System.ValueTuple", "comp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null").WithLocation(22, 9) + var comp = CreateCompilationWithMscorlib(source, references: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, parseOptions: TestOptions.Regular.WithTuplesFeature()); + comp.VerifyDiagnostics( + // (10,18): error CS8212: Cannot deconstruct dynamic objects. + // (x, y) = c; + Diagnostic(ErrorCode.ERR_CannotDeconstructDynamic, "c").WithLocation(10, 18) ); } [Fact] - public void CircularFlow() + public void DeconstructMethodInaccessible() { string source = @" class C { static void Main() { - (object i, object ii) x = (1,2); - object y; + int x; + string y; - (x.ii, y) = x; - System.Console.WriteLine(x + "" "" + y); + (x, y) = new C1(); } } +class C1 +{ + protected void Deconstruct(out int a, out string b) { a = 1; b = ""hello""; } +} "; + var comp = CreateCompilationWithMscorlib(source, references: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, parseOptions: TestOptions.Regular.WithTuplesFeature()); + comp.VerifyDiagnostics( + // (9,18): error CS0122: 'C1.Deconstruct(out int, out string)' is inaccessible due to its protection level + // (x, y) = new C1(); + Diagnostic(ErrorCode.ERR_BadAccess, "new C1()").WithArguments("C1.Deconstruct(out int, out string)").WithLocation(9, 18), + // (9,18): error CS8206: No Deconstruct instance or extension method was found for type 'C1', with 2 out parameters. + // (x, y) = new C1(); + Diagnostic(ErrorCode.ERR_MissingDeconstruct, "new C1()").WithArguments("C1", "2").WithLocation(9, 18) + ); + } - var comp = CompileAndVerify(source, expectedOutput: "(1, 1) 2", additionalRefs: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, parseOptions: TestOptions.Regular.WithTuplesFeature()); - comp.VerifyDiagnostics(); + [Fact] + public void DeconstructHasUseSiteError() + { + string libMissingSource = @"public class Missing { }"; + + string libSource = @" +public class C +{ + public void Deconstruct(out Missing a, out Missing b) { a = new Missing(); b = new Missing(); } +} +"; + + string source = @" +class C1 +{ + static void Main() + { + object x, y; + (x, y) = new C(); + } +} +"; + var libMissingComp = CreateCompilationWithMscorlib(new string[] { libMissingSource }, assemblyName: "libMissingComp").VerifyDiagnostics(); + var libMissingRef = libMissingComp.EmitToImageReference(); + + var libComp = CreateCompilationWithMscorlib(new string[] { libSource }, references: new[] { libMissingRef }, parseOptions: TestOptions.Regular.WithTuplesFeature()).VerifyDiagnostics(); + var libRef = libComp.EmitToImageReference(); + + var comp = CreateCompilationWithMscorlib(new string[] { source }, references: new[] { libRef }, parseOptions: TestOptions.Regular.WithTuplesFeature()); + comp.VerifyDiagnostics( + // (7,18): error CS0012: The type 'Missing' is defined in an assembly that is not referenced. You must add a reference to assembly 'libMissingComp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'. + // (x, y) = new C(); + Diagnostic(ErrorCode.ERR_NoTypeDef, "new C()").WithArguments("Missing", "libMissingComp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null").WithLocation(7, 18), + // (7,18): error CS8206: No Deconstruct instance or extension method was found for type 'C', with 2 out parameters. + // (x, y) = new C(); + Diagnostic(ErrorCode.ERR_MissingDeconstruct, "new C()").WithArguments("C", "2").WithLocation(7, 18) + ); } [Fact] - [CompilerTrait(CompilerFeature.RefLocalsReturns)] - public void CircularFlow2() + public void StaticDeconstruct() { string source = @" class C { static void Main() { - (object i, object ii) x = (1,2); - object y; - - ref var a = ref x; + int x; + string y; - (a.ii, y) = x; - System.Console.WriteLine(x + "" "" + y); + (x, y) = new C(); } + public static void Deconstruct(out int a, out string b) { a = 1; b = ""hello""; } } "; - - var comp = CompileAndVerify(source, expectedOutput: "(1, 1) 2", additionalRefs: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, parseOptions: TestOptions.Regular.WithTuplesFeature().WithRefsFeature()); - comp.VerifyDiagnostics(); + var comp = CreateCompilationWithMscorlib(source, references: new[] { ValueTupleRef, SystemRuntimeFacadeRef }, parseOptions: TestOptions.Regular.WithTuplesFeature()); + comp.VerifyDiagnostics( + // (9,18): error CS0176: Member 'C.Deconstruct(out int, out string)' cannot be accessed with an instance reference; qualify it with a type name instead + // (x, y) = new C(); + Diagnostic(ErrorCode.ERR_ObjectProhibited, "new C()").WithArguments("C.Deconstruct(out int, out string)").WithLocation(9, 18), + // (9,18): error CS8206: No Deconstruct instance or extension method was found for type 'C', with 2 out parameters. + // (x, y) = new C(); + Diagnostic(ErrorCode.ERR_MissingDeconstruct, "new C()").WithArguments("C", "2").WithLocation(9, 18) + ); } [Fact] @@ -1672,7 +2504,7 @@ public void Deconstruct(out int a, out string b) } } "; - // PROTOTYPE(tuples) we expect "2 hello" instead + // PROTOTYPE(tuples) we expect "2 hello" instead, which means the evaluation order is wrong var comp = CompileAndVerify(source, expectedOutput: "1 hello", parseOptions: TestOptions.Regular.WithTuplesFeature()); comp.VerifyDiagnostics(); } diff --git a/src/Compilers/Test/Resources/Core/NetFX/ValueTuple/System.ValueTuple.dll b/src/Compilers/Test/Resources/Core/NetFX/ValueTuple/System.ValueTuple.dll index bf1b74f27f12f..c20ee6cf2ad23 100644 Binary files a/src/Compilers/Test/Resources/Core/NetFX/ValueTuple/System.ValueTuple.dll and b/src/Compilers/Test/Resources/Core/NetFX/ValueTuple/System.ValueTuple.dll differ