private BoundNode VisitBinaryOperatorBase(BoundBinaryOperatorBase binaryOperator) { // Use an explicit stack to avoid blowing the managed stack when visiting deeply-recursive // binary nodes var stack = ArrayBuilder <BoundBinaryOperatorBase> .GetInstance(); BoundBinaryOperatorBase?currentBinary = binaryOperator; do { stack.Push(currentBinary); currentBinary = currentBinary.Left as BoundBinaryOperatorBase; }while (currentBinary is object); Debug.Assert(stack.Count > 0); var leftChild = (BoundExpression)Visit(stack.Peek().Left); do { currentBinary = stack.Pop(); bool foundInfo = _updatedNullabilities.TryGetValue(currentBinary, out (NullabilityInfo Info, TypeSymbol? Type)infoAndType); var right = (BoundExpression)Visit(currentBinary.Right); var type = foundInfo ? infoAndType.Type : currentBinary.Type; currentBinary = currentBinary switch { BoundBinaryOperator binary => binary.Update( binary.OperatorKind, BoundBinaryOperator.UncommonData.CreateIfNeeded(binary.ConstantValue, GetUpdatedSymbol(binary, binary.Method), binary.ConstrainedToType, binary.OriginalUserDefinedOperatorsOpt), binary.ResultKind, leftChild, right, type !), // https://github.com/dotnet/roslyn/issues/35031: We'll need to update logical.LogicalOperator BoundUserDefinedConditionalLogicalOperator logical => logical.Update(logical.OperatorKind, logical.LogicalOperator, logical.TrueOperator, logical.FalseOperator, logical.ConstrainedToTypeOpt, logical.ResultKind, logical.OriginalUserDefinedOperatorsOpt, leftChild, right, type !), _ => throw ExceptionUtilities.UnexpectedValue(currentBinary.Kind), }; if (foundInfo) { currentBinary.TopLevelNullability = infoAndType.Info; } leftChild = currentBinary; }while (stack.Count > 0); Debug.Assert(currentBinary != null); return(currentBinary !); }
private BoundNode VisitBinaryOperatorBase(BoundBinaryOperatorBase binaryOperator) { // Use an explicit stack to avoid blowing the managed stack when visiting deeply-recursive // binary nodes var stack = ArrayBuilder <BoundBinaryOperatorBase> .GetInstance(); BoundBinaryOperatorBase?currentBinary = binaryOperator; do { stack.Push(currentBinary); currentBinary = currentBinary.Left as BoundBinaryOperatorBase; }while (currentBinary is object); Debug.Assert(stack.Count > 0); var leftChild = (BoundExpression)Visit(stack.Peek().Left); do { currentBinary = stack.Pop(); bool foundInfo = _updatedNullabilities.TryGetValue(currentBinary, out (NullabilityInfo Info, TypeSymbol Type)infoAndType); var right = (BoundExpression)Visit(currentBinary.Right); var type = foundInfo ? infoAndType.Type : currentBinary.Type; #pragma warning disable IDE0055 // Fix formatting // https://github.com/dotnet/roslyn/issues/35031: We'll need to update the symbols for the internal methods/operators used in the binary operators currentBinary = currentBinary switch { BoundBinaryOperator binary => (BoundBinaryOperatorBase)binary.Update(binary.OperatorKind, binary.ConstantValueOpt, binary.MethodOpt, binary.ResultKind, leftChild, right, type), BoundUserDefinedConditionalLogicalOperator logical => logical.Update(logical.OperatorKind, logical.LogicalOperator, logical.TrueOperator, logical.FalseOperator, logical.ResultKind, leftChild, right, type), _ => throw ExceptionUtilities.UnexpectedValue(currentBinary.Kind), }; #pragma warning restore IDE0055 // Fix formatting if (foundInfo) { currentBinary.TopLevelNullability = infoAndType.Info; } leftChild = currentBinary; }while (stack.Count > 0); Debug.Assert(currentBinary != null); return(currentBinary); }
public override BoundNode VisitUserDefinedConditionalLogicalOperator(BoundUserDefinedConditionalLogicalOperator node) { // Yes, we could have a lifted, logical, user-defined operator: // // struct C { // public static C operator &(C x, C y) {...} // public static bool operator true(C? c) { ... } // public static bool operator false(C? c) { ... } // } // // If we have C? q, r and we say q && r then this gets bound as // C? tempQ = q ; // C.false(tempQ) ? // tempQ : // ( // C? tempR = r ; // tempQ.HasValue & tempR.HasValue ? // new C?(C.&(tempQ.GetValueOrDefault(), tempR.GetValueOrDefault())) : // default C?() // ) // // Note that the native compiler does not allow q && r. However, the native compiler // *does* allow q && r if C is defined as: // // struct C { // public static C? operator &(C? x, C? y) {...} // public static bool operator true(C? c) { ... } // public static bool operator false(C? c) { ... } // } // // It seems unusual and wrong that an & operator should be allowed to become // a && operator if there is a "manually lifted" operator in source, but not // if there is a "synthesized" lifted operator. Roslyn fixes this bug. // // Anyway, in this case we must lower this to its non-logical form, and then // lower the interior of that to its non-lifted form. // See comments in method IsValidUserDefinedConditionalLogicalOperator for information // on some subtle aspects of this lowering. // We generate one of: // // x || y --> temp = x; T.true(temp) ? temp : T.|(temp, y); // x && y --> temp = x; T.false(temp) ? temp : T.&(temp, y); // // For the ease of naming locals, we'll assume we're doing an &&. // TODO: We generate every one of these as "temp = x; T.false(temp) ? temp : T.&(temp, y)" even // TODO: when x has no side effects. We can optimize away the temporary if there are no side effects. var syntax = node.Syntax; var operatorKind = node.OperatorKind; var type = node.Type; BoundExpression loweredLeft = VisitExpression(node.Left); BoundExpression loweredRight = VisitExpression(node.Right); if (_inExpressionLambda) { return node.Update(operatorKind, loweredLeft, loweredRight, node.LogicalOperator, node.TrueOperator, node.FalseOperator, node.ResultKind, type); } BoundAssignmentOperator tempAssignment; var boundTemp = _factory.StoreToTemp(loweredLeft, out tempAssignment); // T.false(temp) var falseOperatorCall = BoundCall.Synthesized(syntax, null, operatorKind.Operator() == BinaryOperatorKind.And ? node.FalseOperator : node.TrueOperator, boundTemp); // T.&(temp, y) var andOperatorCall = LowerUserDefinedBinaryOperator(syntax, operatorKind & ~BinaryOperatorKind.Logical, boundTemp, loweredRight, type, node.LogicalOperator); // T.false(temp) ? temp : T.&(temp, y) BoundExpression conditionalExpression = RewriteConditionalOperator( syntax: syntax, rewrittenCondition: falseOperatorCall, rewrittenConsequence: boundTemp, rewrittenAlternative: andOperatorCall, constantValueOpt: null, rewrittenType: type); // temp = x; T.false(temp) ? temp : T.&(temp, y) return new BoundSequence( syntax: syntax, locals: ImmutableArray.Create(boundTemp.LocalSymbol), sideEffects: ImmutableArray.Create<BoundExpression>(tempAssignment), value: conditionalExpression, type: type); }