/// <summary> /// 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, ...)`. /// </summary> private BoundExpression MakeDeconstructInvocationExpression( int numCheckedVariables, BoundExpression receiver, CSharpSyntaxNode syntax, DiagnosticBag diagnostics, out ImmutableArray <BoundDeconstructValuePlaceholder> outPlaceholders) { var receiverSyntax = receiver.Syntax; if (receiver.Type.IsDynamic()) { Error(diagnostics, ErrorCode.ERR_CannotDeconstructDynamic, receiverSyntax); outPlaceholders = default(ImmutableArray <BoundDeconstructValuePlaceholder>); return(BadExpression(receiverSyntax, receiver)); } var analyzedArguments = AnalyzedArguments.GetInstance(); var outVars = ArrayBuilder <OutDeconstructVarPendingInference> .GetInstance(numCheckedVariables); DiagnosticBag bag = null; try { for (int i = 0; i < numCheckedVariables; i++) { var variable = new OutDeconstructVarPendingInference(syntax); analyzedArguments.Arguments.Add(variable); analyzedArguments.RefKinds.Add(RefKind.Out); outVars.Add(variable); } const string methodName = "Deconstruct"; var memberAccess = BindInstanceMemberAccess( receiverSyntax, receiverSyntax, receiver, methodName, rightArity: 0, typeArgumentsSyntax: default(SeparatedSyntaxList <TypeSyntax>), typeArguments: default(ImmutableArray <TypeSymbol>), invoked: true, diagnostics: diagnostics); memberAccess = CheckValue(memberAccess, BindValueKind.RValueOrMethodGroup, diagnostics); memberAccess.WasCompilerGenerated = true; if (memberAccess.Kind != BoundKind.MethodGroup) { return(MissingDeconstruct(receiver, syntax, 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 (bag.HasAnyErrors()) { return(MissingDeconstruct(receiver, syntax, 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, syntax, 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, syntax, numCheckedVariables, diagnostics, out outPlaceholders, result)); } } if (outVars.Any(v => (object)v.Placeholder == null)) { return(MissingDeconstruct(receiver, syntax, numCheckedVariables, diagnostics, out outPlaceholders, result)); } outPlaceholders = outVars.SelectAsArray(v => v.Placeholder); return(result); } finally { analyzedArguments.Free(); outVars.Free(); if (bag != null) { bag.Free(); } } }
/// <summary> /// 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, ...)`. /// </summary> private BoundExpression MakeDeconstructInvocationExpression( int numCheckedVariables, BoundExpression receiver, CSharpSyntaxNode syntax, DiagnosticBag diagnostics, out ImmutableArray<BoundDeconstructValuePlaceholder> outPlaceholders) { var receiverSyntax = receiver.Syntax; if (numCheckedVariables < 2) { Error(diagnostics, ErrorCode.ERR_DeconstructTooFewElements, receiverSyntax); outPlaceholders = default(ImmutableArray<BoundDeconstructValuePlaceholder>); return BadExpression(receiverSyntax, receiver); } if (receiver.Type.IsDynamic()) { Error(diagnostics, ErrorCode.ERR_CannotDeconstructDynamic, receiverSyntax); outPlaceholders = default(ImmutableArray<BoundDeconstructValuePlaceholder>); return BadExpression(receiverSyntax, receiver); } var analyzedArguments = AnalyzedArguments.GetInstance(); var outVars = ArrayBuilder<OutDeconstructVarPendingInference>.GetInstance(numCheckedVariables); try { for (int i = 0; i < numCheckedVariables; i++) { var variable = new OutDeconstructVarPendingInference(syntax); analyzedArguments.Arguments.Add(variable); analyzedArguments.RefKinds.Add(RefKind.Out); outVars.Add(variable); } const string methodName = "Deconstruct"; var memberAccess = BindInstanceMemberAccess( receiverSyntax, receiverSyntax, receiver, methodName, rightArity: 0, typeArgumentsSyntax: default(SeparatedSyntaxList<TypeSyntax>), typeArguments: default(ImmutableArray<TypeSymbol>), invoked: true, diagnostics: diagnostics); memberAccess = CheckValue(memberAccess, BindValueKind.RValueOrMethodGroup, diagnostics); memberAccess.WasCompilerGenerated = true; if (memberAccess.Kind != BoundKind.MethodGroup) { return MissingDeconstruct(receiver, syntax, 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. BoundExpression result = BindMethodGroupInvocation( receiverSyntax, receiverSyntax, methodName, (BoundMethodGroup)memberAccess, analyzedArguments, diagnostics, queryClause: null, allowUnexpandedForm: true); result.WasCompilerGenerated = true; if (result.HasErrors && !receiver.HasAnyErrors) { return MissingDeconstruct(receiver, syntax, 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, syntax, 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, syntax, numCheckedVariables, diagnostics, out outPlaceholders, result); } } if (deconstructMethod.ReturnType.GetSpecialTypeSafe() != SpecialType.System_Void) { return MissingDeconstruct(receiver, syntax, numCheckedVariables, diagnostics, out outPlaceholders, result); } if (outVars.Any(v => (object)v.Placeholder == null)) { return MissingDeconstruct(receiver, syntax, numCheckedVariables, diagnostics, out outPlaceholders, result); } outPlaceholders = outVars.SelectAsArray(v => v.Placeholder); return result; } finally { analyzedArguments.Free(); outVars.Free(); } }
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; }