Exemplo n.º 1
0
        public override bool check(CodeContext context)
        {
            if (is_checked)
            {
                return(!error);
            }

            is_checked = true;

            if (!(context.analyzer.current_symbol is Block))
            {
                Report.error(source_reference, "Conditional expressions may only be used in blocks");
                error = true;
                return(false);
            }

            // convert ternary expression into if statement
            // required for flow analysis and exception handling

            string temp_name = get_temp_name();

            true_expression.target_type  = target_type;
            false_expression.target_type = target_type;

            var local = new LocalVariable(null, temp_name, null, source_reference);
            var decl  = new DeclarationStatement(local, source_reference);

            var true_local = new LocalVariable(null, temp_name, true_expression, true_expression.source_reference);
            var true_block = new Block(true_expression.source_reference);
            var true_decl  = new DeclarationStatement(true_local, true_expression.source_reference);

            true_block.add_statement(true_decl);

            var false_local = new LocalVariable(null, temp_name, false_expression, false_expression.source_reference);
            var false_block = new Block(false_expression.source_reference);
            var false_decl  = new DeclarationStatement(false_local, false_expression.source_reference);

            false_block.add_statement(false_decl);

            var if_stmt = new IfStatement(condition, true_block, false_block, source_reference);

            insert_statement(context.analyzer.insert_block, decl);
            insert_statement(context.analyzer.insert_block, if_stmt);

            if (!if_stmt.check(context) || true_expression.error || false_expression.error)
            {
                error = true;
                return(false);
            }

            true_expression  = true_local.initializer;
            false_expression = false_local.initializer;

            true_block.remove_local_variable(true_local);
            false_block.remove_local_variable(false_local);

            if (false_expression.value_type.compatible(true_expression.value_type))
            {
                value_type = true_expression.value_type.copy();
            }
            else if (true_expression.value_type.compatible(false_expression.value_type))
            {
                value_type = false_expression.value_type.copy();
            }
            else
            {
                error = true;
                Report.error(condition.source_reference, "Incompatible expressions");
                return(false);
            }

            value_type.value_owned = (true_expression.value_type.value_owned || false_expression.value_type.value_owned);

            local.variable_type = value_type;
            decl.check(context);

            true_expression.target_type  = value_type;
            false_expression.target_type = value_type;

            var true_stmt = new ExpressionStatement(new Assignment(MemberAccess.simple(local.name, true_expression.source_reference), true_expression, AssignmentOperator.SIMPLE, true_expression.source_reference), true_expression.source_reference);

            true_stmt.check(context);

            var false_stmt = new ExpressionStatement(new Assignment(MemberAccess.simple(local.name, false_expression.source_reference), false_expression, AssignmentOperator.SIMPLE, false_expression.source_reference), false_expression.source_reference);

            false_stmt.check(context);

            true_block.replace_statement(true_decl, true_stmt);
            false_block.replace_statement(false_decl, false_stmt);

            var ma = MemberAccess.simple(local.name, source_reference);

            ma.formal_target_type = formal_target_type;
            ma.target_type        = target_type;
            ma.check(context);

            parent_node.replace_expression(this, ma);

            return(true);
        }
Exemplo n.º 2
0
        public override bool check(CodeContext context)
        {
            if (is_checked)
            {
                return(!error);
            }

            is_checked = true;

            if (left is ValaTuple && Operator == AssignmentOperator.SIMPLE && parent_node is ExpressionStatement)
            {
                var tuple = (ValaTuple)left;

                var local = new LocalVariable(null, get_temp_name(), right, right.source_reference);
                var decl  = new DeclarationStatement(local, source_reference);
                decl.check(context);
                insert_statement(context.analyzer.insert_block, decl);

                int i = 0;
                ExpressionStatement stmt = null;
                foreach (var expr in tuple.get_expressions())
                {
                    if (stmt != null)
                    {
                        stmt.check(context);
                        insert_statement(context.analyzer.insert_block, stmt);
                    }

                    var temp_access = MemberAccess.simple(local.name, right.source_reference);
                    var ea          = new ElementAccess(temp_access, expr.source_reference);
                    ea.append_index(new IntegerLiteral(i.ToString(), expr.source_reference));
                    var assign = new Assignment(expr, ea, Operator, expr.source_reference);
                    stmt = new ExpressionStatement(assign, expr.source_reference);

                    i++;
                }

                context.analyzer.replaced_nodes.Add(this);
                parent_node.replace_expression(this, stmt.expression);
                return(stmt.expression.check(context));
            }

            left.lvalue = true;

            if (!left.check(context))
            {
                // skip on error in inner expression
                error = true;
                return(false);
            }

            if (left is MemberAccess)
            {
                var ma = (MemberAccess)left;

                if (ma.symbol_reference is Constant)
                {
                    error = true;
                    Report.error(source_reference, "Assignment to constant after initialization");
                    return(false);
                }

                if ((!(ma.symbol_reference is Signal || ma.symbol_reference is DynamicProperty) && ma.value_type == null) ||
                    (ma.inner == null && ma.member_name == "this" && context.analyzer.is_in_instance_method()))
                {
                    error = true;
                    Report.error(source_reference, "unsupported lvalue in assignment");
                    return(false);
                }
                if (ma.prototype_access)
                {
                    error = true;
                    Report.error(source_reference, "Access to instance member `%s' denied".printf(ma.symbol_reference.get_full_name()));
                    return(false);
                }

                if (ma.error || ma.symbol_reference == null)
                {
                    error = true;
                    /* if no symbol found, skip this check */
                    return(false);
                }

                if (ma.symbol_reference is DynamicSignal)
                {
                    // target_type not available for dynamic signals
                    if (!context.deprecated)
                    {
                        Report.warning(source_reference, "deprecated syntax, use `connect' method instead");
                    }
                }
                else if (ma.symbol_reference is Signal)
                {
                    if (!context.deprecated)
                    {
                        Report.warning(source_reference, "deprecated syntax, use `connect' method instead");
                    }
                    var sig = (Signal)ma.symbol_reference;
                    right.target_type = new DelegateType(sig.get_delegate(ma.inner.value_type, this));
                }
                else if (ma.symbol_reference is DynamicProperty)
                {
                    // target_type not available for dynamic properties
                }
                else
                {
                    right.formal_target_type = ma.formal_value_type.copy();
                    right.target_type        = ma.value_type.copy();
                }
            }
            else if (left is ElementAccess)
            {
                var ea = (ElementAccess)left;

                if (ea.container.value_type.data_type == context.analyzer.string_type.data_type)
                {
                    error = true;
                    Report.error(ea.source_reference, "strings are immutable");
                    return(false);
                }
                else if (ea.container is MemberAccess && ea.container.symbol_reference is Signal)
                {
                    var ma  = (MemberAccess)ea.container;
                    var sig = (Signal)ea.container.symbol_reference;
                    right.target_type = new DelegateType(sig.get_delegate(ma.inner.value_type, this));
                }
                else if (ea.container.value_type.get_member("set") is Method)
                {
                    var set_call = new MethodCall(new MemberAccess(ea.container, "set", source_reference), source_reference);
                    foreach (Expression e in ea.get_indices())
                    {
                        set_call.add_argument(e);
                    }
                    set_call.add_argument(right);
                    parent_node.replace_expression(this, set_call);
                    return(set_call.check(context));
                }
                else
                {
                    right.target_type = left.value_type;
                }
            }
            else if (left is PointerIndirection)
            {
                right.target_type = left.value_type;
            }
            else
            {
                error = true;
                Report.error(source_reference, "unsupported lvalue in assignment");
                return(false);
            }

            if (!right.check(context))
            {
                // skip on error in inner expression
                error = true;
                return(false);
            }

            if (Operator != AssignmentOperator.SIMPLE && left is MemberAccess)
            {
                // transform into simple assignment
                // FIXME: only do this if the backend doesn't support
                // the assignment natively

                var ma = (MemberAccess)left;

                if (!(ma.symbol_reference is Signal))
                {
                    var old_value = new MemberAccess(ma.inner, ma.member_name);

                    var bin = new BinaryExpression(BinaryOperator.PLUS, old_value, right, source_reference);
                    bin.target_type               = right.target_type;
                    right.target_type             = right.target_type.copy();
                    right.target_type.value_owned = false;

                    if (Operator == AssignmentOperator.BITWISE_OR)
                    {
                        bin.Operator = BinaryOperator.BITWISE_OR;
                    }
                    else if (Operator == AssignmentOperator.BITWISE_AND)
                    {
                        bin.Operator = BinaryOperator.BITWISE_AND;
                    }
                    else if (Operator == AssignmentOperator.BITWISE_XOR)
                    {
                        bin.Operator = BinaryOperator.BITWISE_XOR;
                    }
                    else if (Operator == AssignmentOperator.ADD)
                    {
                        bin.Operator = BinaryOperator.PLUS;
                    }
                    else if (Operator == AssignmentOperator.SUB)
                    {
                        bin.Operator = BinaryOperator.MINUS;
                    }
                    else if (Operator == AssignmentOperator.MUL)
                    {
                        bin.Operator = BinaryOperator.MUL;
                    }
                    else if (Operator == AssignmentOperator.DIV)
                    {
                        bin.Operator = BinaryOperator.DIV;
                    }
                    else if (Operator == AssignmentOperator.PERCENT)
                    {
                        bin.Operator = BinaryOperator.MOD;
                    }
                    else if (Operator == AssignmentOperator.SHIFT_LEFT)
                    {
                        bin.Operator = BinaryOperator.SHIFT_LEFT;
                    }
                    else if (Operator == AssignmentOperator.SHIFT_RIGHT)
                    {
                        bin.Operator = BinaryOperator.SHIFT_RIGHT;
                    }

                    right = bin;
                    right.check(context);

                    Operator = AssignmentOperator.SIMPLE;
                }
            }

            if (left.symbol_reference is Signal)
            {
                var sig = (Signal)left.symbol_reference;

                var m = right.symbol_reference as Method;

                if (m == null)
                {
                    error = true;
                    Report.error(right.source_reference, "unsupported expression for signal handler");
                    return(false);
                }

                var dynamic_sig = sig as DynamicSignal;
                var right_ma    = right as MemberAccess;
                if (dynamic_sig != null)
                {
                    bool first = true;
                    foreach (Parameter param in dynamic_sig.handler.value_type.get_parameters())
                    {
                        if (first)
                        {
                            // skip sender parameter
                            first = false;
                        }
                        else
                        {
                            dynamic_sig.add_parameter(param.copy());
                        }
                    }
                    right.target_type = new DelegateType(sig.get_delegate(new ObjectType((ObjectTypeSymbol)sig.parent_symbol), this));
                }
                else if (!right.value_type.compatible(right.target_type))
                {
                    var delegate_type = (DelegateType)right.target_type;

                    error = true;
                    Report.error(right.source_reference, "method `%s' is incompatible with signal `%s', expected `%s'".printf(right.value_type.ToString(), right.target_type.ToString(), delegate_type.delegate_symbol.get_prototype_string(m.name)));
                    return(false);
                }
                else if (right_ma != null && right_ma.prototype_access)
                {
                    error = true;
                    Report.error(right.source_reference, "Access to instance member `%s' denied".printf(m.get_full_name()));
                    return(false);
                }
            }
            else if (left is MemberAccess)
            {
                var ma = (MemberAccess)left;

                if (ma.symbol_reference is Property)
                {
                    var prop = (Property)ma.symbol_reference;

                    var dynamic_prop = prop as DynamicProperty;
                    if (dynamic_prop != null)
                    {
                        dynamic_prop.property_type = right.value_type.copy();
                        left.value_type            = dynamic_prop.property_type.copy();
                    }

                    if (prop.set_accessor == null ||
                        (!prop.set_accessor.writable && !(context.analyzer.find_current_method() is CreationMethod || context.analyzer.is_in_constructor())))
                    {
                        ma.error = true;
                        Report.error(ma.source_reference, "Property `%s' is read-only".printf(prop.get_full_name()));
                        return(false);
                    }
                    else if (!context.deprecated &&
                             !prop.set_accessor.writable &&
                             context.analyzer.find_current_method() is CreationMethod)
                    {
                        if (ma.inner.symbol_reference != context.analyzer.find_current_method().this_parameter)
                        {
                            // trying to set construct-only property in creation method for foreign instance
                            Report.error(ma.source_reference, "Property `%s' is read-only".printf(prop.get_full_name()));
                            return(false);
                        }
                        else
                        {
                            Report.error(ma.source_reference, "Cannot assign to construct-only properties, use Object (property: value) constructor chain up");
                            return(false);
                        }
                    }
                }
                else if (ma.symbol_reference is Variable && right.value_type == null)
                {
                    var variable = (Variable)ma.symbol_reference;

                    if (right.symbol_reference is Method &&
                        variable.variable_type is DelegateType)
                    {
                        var m  = (Method)right.symbol_reference;
                        var dt = (DelegateType)variable.variable_type;
                        var cb = dt.delegate_symbol;

                        /* check whether method matches callback type */
                        if (!cb.matches_method(m, dt))
                        {
                            error = true;
                            Report.error(source_reference, "declaration of method `%s' doesn't match declaration of callback `%s'".printf(m.get_full_name(), cb.get_full_name()));
                            return(false);
                        }

                        right.value_type = variable.variable_type;
                    }
                    else
                    {
                        error = true;
                        Report.error(source_reference, "Assignment: Invalid assignment attempt");
                        return(false);
                    }
                }

                if (left.value_type != null && right.value_type != null)
                {
                    /* if there was an error on either side,
                     * i.e. {left|right}.value_type == null, skip type check */

                    if (!right.value_type.compatible(left.value_type))
                    {
                        error = true;
                        Report.error(source_reference, "Assignment: Cannot convert from `%s' to `%s'".printf(right.value_type.ToString(), left.value_type.ToString()));
                        return(false);
                    }

                    if (!(ma.symbol_reference is Property))
                    {
                        if (right.value_type.is_disposable())
                        {
                            /* rhs transfers ownership of the expression */
                            if (!(left.value_type is PointerType) && !left.value_type.value_owned)
                            {
                                /* lhs doesn't own the value */
                                error = true;
                                Report.error(source_reference, "Invalid assignment from owned expression to unowned variable");
                            }
                        }
                        else if (left.value_type.value_owned)
                        {
                            /* lhs wants to own the value
                             * rhs doesn't transfer the ownership
                             * code generator needs to add reference
                             * increment calls */
                        }
                    }
                }

                var right_ma = right as MemberAccess;
                if (right_ma != null && ma.symbol_reference == right_ma.symbol_reference)
                {
                    if (ma.symbol_reference is LocalVariable || ma.symbol_reference is Parameter)
                    {
                        Report.warning(source_reference, "Assignment to same variable");
                    }
                    else if (ma.symbol_reference is Field)
                    {
                        var f = (Field)ma.symbol_reference;
                        if (f.binding == MemberBinding.STATIC)
                        {
                            Report.warning(source_reference, "Assignment to same variable");
                        }
                        else
                        {
                            var ma_inner       = ma.inner as MemberAccess;
                            var right_ma_inner = right_ma.inner as MemberAccess;
                            if (ma_inner != null && ma_inner.member_name == "this" && ma_inner.inner == null &&
                                right_ma_inner != null && right_ma_inner.member_name == "this" && right_ma_inner.inner == null)
                            {
                                Report.warning(source_reference, "Assignment to same variable");
                            }
                        }
                    }
                }
            }
            else if (left is ElementAccess)
            {
                var ea = (ElementAccess)left;

                if (!right.value_type.compatible(left.value_type))
                {
                    error = true;
                    Report.error(source_reference, "Assignment: Cannot convert from `%s' to `%s'".printf(right.value_type.ToString(), left.value_type.ToString()));
                    return(false);
                }

                if (right.value_type.is_disposable())
                {
                    /* rhs transfers ownership of the expression */

                    DataType element_type;

                    if (ea.container.value_type is ArrayType)
                    {
                        var array_type = (ArrayType)ea.container.value_type;
                        element_type = array_type.element_type;
                    }
                    else
                    {
                        var args = ea.container.value_type.get_type_arguments();
                        Debug.Assert(args.Count == 1);
                        element_type = args[0];
                    }

                    if (!(element_type is PointerType) && !element_type.value_owned)
                    {
                        /* lhs doesn't own the value */
                        error = true;
                        Report.error(source_reference, "Invalid assignment from owned expression to unowned variable");
                        return(false);
                    }
                }
                else if (left.value_type.value_owned)
                {
                    /* lhs wants to own the value
                     * rhs doesn't transfer the ownership
                     * code generator needs to add reference
                     * increment calls */
                }
            }
            else
            {
                return(true);
            }

            if (left.value_type != null)
            {
                value_type             = left.value_type.copy();
                value_type.value_owned = false;
            }
            else
            {
                value_type = null;
            }

            add_error_types(left.get_error_types());
            add_error_types(right.get_error_types());

            return(!error);
        }
Exemplo n.º 3
0
        public override bool check(CodeContext context)
        {
            if (is_checked)
            {
                return(!error);
            }

            is_checked = true;

            // some expressions are not in a block,
            // for example, expressions in method contracts
            if (context.analyzer.current_symbol is Block &&
                (Operator == BinaryOperator.AND || Operator == BinaryOperator.OR))
            {
                // convert conditional expression into if statement
                // required for flow analysis and exception handling

                var local = new LocalVariable(context.analyzer.bool_type.copy(), get_temp_name(), null, source_reference);
                var decl  = new DeclarationStatement(local, source_reference);
                decl.check(context);

                var right_stmt = new ExpressionStatement(
                    new Assignment(
                        MemberAccess.simple(local.name, right.source_reference),
                        right,
                        AssignmentOperator.SIMPLE,
                        right.source_reference
                        ), right.source_reference
                    );

                var stmt = new ExpressionStatement(
                    new Assignment(
                        MemberAccess.simple(local.name, left.source_reference),
                        new BooleanLiteral((Operator == BinaryOperator.OR), left.source_reference),
                        AssignmentOperator.SIMPLE, left.source_reference
                        ), left.source_reference
                    );

                var true_block  = new Block(source_reference);
                var false_block = new Block(source_reference);

                if (Operator == BinaryOperator.AND)
                {
                    true_block.add_statement(right_stmt);
                    false_block.add_statement(stmt);
                }
                else
                {
                    true_block.add_statement(stmt);
                    false_block.add_statement(right_stmt);
                }

                var if_stmt = new IfStatement(left, true_block, false_block, source_reference);

                insert_statement(context.analyzer.insert_block, decl);
                insert_statement(context.analyzer.insert_block, if_stmt);

                if (!if_stmt.check(context))
                {
                    error = true;
                    return(false);
                }

                var ma = MemberAccess.simple(local.name, source_reference);
                ma.target_type        = target_type;
                ma.formal_target_type = formal_target_type;
                ma.check(context);

                parent_node.replace_expression(this, ma);

                return(true);
            }

            if (Operator == BinaryOperator.COALESCE)
            {
                if (!left.check(context))
                {
                    error = true;
                    return(false);
                }

                if (!right.check(context))
                {
                    error = true;
                    return(false);
                }

                DataType local_type    = null;
                bool     cast_non_null = false;
                if (left.value_type is NullType && right.value_type != null)
                {
                    Report.warning(left.source_reference, "left operand is always null");
                    local_type          = right.value_type.copy();
                    local_type.nullable = true;
                    if (!right.value_type.nullable)
                    {
                        cast_non_null = true;
                    }
                }
                else if (left.value_type != null)
                {
                    local_type = left.value_type.copy();
                    if (right.value_type != null && right.value_type.value_owned)
                    {
                        // value owned if either left or right is owned
                        local_type.value_owned = true;
                    }
                    if (context.experimental_non_null)
                    {
                        if (!local_type.nullable)
                        {
                            Report.warning(left.source_reference, "left operand is never null");
                            if (right.value_type != null && right.value_type.nullable)
                            {
                                local_type.nullable = true;
                                cast_non_null       = true;
                            }
                        }
                        else if (right.value_type != null && !right.value_type.nullable)
                        {
                            cast_non_null = true;
                        }
                    }
                }
                else if (right.value_type != null)
                {
                    local_type = right.value_type.copy();
                }

                var local = new LocalVariable(local_type, get_temp_name(), left, source_reference);
                var decl  = new DeclarationStatement(local, source_reference);

                var right_stmt = new ExpressionStatement(new Assignment(MemberAccess.simple(local.name, right.source_reference), right, AssignmentOperator.SIMPLE, right.source_reference), right.source_reference);

                var true_block = new Block(source_reference);

                true_block.add_statement(right_stmt);

                var cond = new BinaryExpression(BinaryOperator.EQUALITY, MemberAccess.simple(local.name, left.source_reference), new NullLiteral(source_reference), source_reference);

                var if_stmt = new IfStatement(cond, true_block, null, source_reference);

                insert_statement(context.analyzer.insert_block, decl);
                insert_statement(context.analyzer.insert_block, if_stmt);

                if (!decl.check(context))
                {
                    error = true;
                    return(false);
                }

                if (!if_stmt.check(context))
                {
                    error = true;
                    return(false);
                }

                var replace_expr = SemanticAnalyzer.create_temp_access(local, target_type);
                if (cast_non_null && replace_expr.target_type != null)
                {
                    var cast = CastExpression.non_null(replace_expr, source_reference);
                    cast.target_type          = replace_expr.target_type.copy();
                    cast.target_type.nullable = false;
                    replace_expr = cast;
                }
                replace_expr.check(context);

                parent_node.replace_expression(this, replace_expr);

                return(true);
            }

            if (!left.check(context) || !right.check(context))
            {
                /* if there were any errors in inner expressions, skip type check */
                error = true;
                return(false);
            }

            if (left.value_type == null)
            {
                Report.error(left.source_reference, "invalid left operand");
                error = true;
                return(false);
            }

            if (Operator != BinaryOperator.IN && right.value_type == null)
            {
                Report.error(right.source_reference, "invalid right operand");
                error = true;
                return(false);
            }

            if (left.value_type is FieldPrototype)
            {
                error = true;
                Report.error(left.source_reference, "Access to instance member `%s' denied".printf(left.symbol_reference.get_full_name()));
                return(false);
            }
            if (right.value_type is FieldPrototype)
            {
                error = true;
                Report.error(right.source_reference, "Access to instance member `%s' denied".printf(right.symbol_reference.get_full_name()));
                return(false);
            }

            left.target_type              = left.value_type.copy();
            left.target_type.value_owned  = false;
            right.target_type             = right.value_type.copy();
            right.target_type.value_owned = false;

            if (left.value_type.data_type == context.analyzer.string_type.data_type &&
                Operator == BinaryOperator.PLUS)
            {
                // string concatenation

                if (right.value_type == null || right.value_type.data_type != context.analyzer.string_type.data_type)
                {
                    error = true;
                    Report.error(source_reference, "Operands must be strings");
                    return(false);
                }

                value_type = context.analyzer.string_type.copy();
                if (left.is_constant() && right.is_constant())
                {
                    value_type.value_owned = false;
                }
                else
                {
                    value_type.value_owned = true;
                }
            }
            else if (left.value_type is ArrayType && Operator == BinaryOperator.PLUS)
            {
                // array concatenation

                var array_type = (ArrayType)left.value_type;

                if (right.value_type == null || !right.value_type.compatible(array_type.element_type))
                {
                    error = true;
                    Report.error(source_reference, "Incompatible operand");
                    return(false);
                }

                right.target_type = array_type.element_type.copy();

                value_type             = array_type.copy();
                value_type.value_owned = true;
            }
            else if (
                Operator == BinaryOperator.PLUS ||
                Operator == BinaryOperator.MINUS ||
                Operator == BinaryOperator.MUL ||
                Operator == BinaryOperator.DIV
                )
            {
                // check for pointer arithmetic
                if (left.value_type is PointerType)
                {
                    var pointer_type = (PointerType)left.value_type;
                    if (pointer_type.base_type is VoidType)
                    {
                        error = true;
                        Report.error(source_reference, "Pointer arithmetic not supported for `void*'");
                        return(false);
                    }

                    var offset_type = right.value_type.data_type as Struct;
                    if (offset_type != null && offset_type.is_integer_type())
                    {
                        if (Operator == BinaryOperator.PLUS ||
                            Operator == BinaryOperator.MINUS)
                        {
                            // pointer arithmetic: pointer +/- offset
                            value_type = left.value_type.copy();
                        }
                    }
                    else if (right.value_type is PointerType)
                    {
                        // pointer arithmetic: pointer - pointer
                        value_type = context.analyzer.size_t_type;
                    }
                }
                else
                {
                    left.target_type.nullable  = false;
                    right.target_type.nullable = false;
                }

                if (value_type == null)
                {
                    value_type = context.analyzer.get_arithmetic_result_type(left.target_type, right.target_type);
                }

                if (value_type == null)
                {
                    error = true;
                    Report.error(source_reference, "Arithmetic operation not supported for types `%s' and `%s'".printf(left.value_type.ToString(), right.value_type.ToString()));
                    return(false);
                }
            }
            else if (
                Operator == BinaryOperator.MOD ||
                Operator == BinaryOperator.SHIFT_LEFT ||
                Operator == BinaryOperator.SHIFT_RIGHT
                )
            {
                left.target_type.nullable  = false;
                right.target_type.nullable = false;

                value_type = context.analyzer.get_arithmetic_result_type(left.target_type, right.target_type);

                if (value_type == null)
                {
                    error = true;
                    Report.error(source_reference, "Arithmetic operation not supported for types `%s' and `%s'".printf(left.value_type.ToString(), right.value_type.ToString()));
                    return(false);
                }
            }
            else if (Operator == BinaryOperator.LESS_THAN ||
                     Operator == BinaryOperator.GREATER_THAN ||
                     Operator == BinaryOperator.LESS_THAN_OR_EQUAL ||
                     Operator == BinaryOperator.GREATER_THAN_OR_EQUAL
                     )
            {
                if (left.value_type.compatible(context.analyzer.string_type) &&
                    right.value_type.compatible(context.analyzer.string_type))
                {
                    // string comparison
                }
                else if (left.value_type is PointerType && right.value_type is PointerType)
                {
                    // pointer arithmetic
                }
                else
                {
                    DataType resulting_type;

                    if (chained)
                    {
                        var lbe = (BinaryExpression)left;
                        resulting_type = context.analyzer.get_arithmetic_result_type(lbe.right.target_type, right.target_type);
                    }
                    else
                    {
                        resulting_type = context.analyzer.get_arithmetic_result_type(left.target_type, right.target_type);
                    }

                    if (resulting_type == null)
                    {
                        error = true;
                        Report.error(source_reference, "Relational operation not supported for types `%s' and `%s'".printf(left.value_type.ToString(), right.value_type.ToString()));
                        return(false);
                    }

                    if (!chained)
                    {
                        left.target_type = resulting_type.copy();
                    }
                    right.target_type          = resulting_type.copy();
                    left.target_type.nullable  = false;
                    right.target_type.nullable = false;
                }

                value_type = context.analyzer.bool_type;
            }
            else if (
                Operator == BinaryOperator.EQUALITY ||
                Operator == BinaryOperator.INEQUALITY
                )
            {
                /* relational operation */

                if (!right.value_type.compatible(left.value_type) &&
                    !left.value_type.compatible(right.value_type))
                {
                    Report.error(source_reference, "Equality operation: `%s' and `%s' are incompatible".printf(right.value_type.ToString(), left.value_type.ToString()));
                    error = true;
                    return(false);
                }

                var resulting_type = context.analyzer.get_arithmetic_result_type(left.target_type, right.target_type);
                if (resulting_type != null)
                {
                    // numeric operation
                    left.target_type  = resulting_type.copy();
                    right.target_type = resulting_type.copy();
                }

                left.target_type.value_owned  = false;
                right.target_type.value_owned = false;

                if (left.value_type.nullable != right.value_type.nullable)
                {
                    // if only one operand is nullable, make sure the other
                    // operand is promoted to nullable as well,
                    // reassign both, as get_arithmetic_result_type doesn't
                    // take nullability into account
                    left.target_type.nullable  = true;
                    right.target_type.nullable = true;
                }

                value_type = context.analyzer.bool_type;
            }
            else if (
                Operator == BinaryOperator.BITWISE_AND ||
                Operator == BinaryOperator.BITWISE_OR ||
                Operator == BinaryOperator.BITWISE_XOR
                )
            {
                // integer type or flags type
                left.target_type.nullable  = false;
                right.target_type.nullable = false;

                value_type = left.target_type.copy();
            }
            else if (
                Operator == BinaryOperator.AND ||
                Operator == BinaryOperator.OR
                )
            {
                if (!left.value_type.compatible(context.analyzer.bool_type) || !right.value_type.compatible(context.analyzer.bool_type))
                {
                    error = true;
                    Report.error(source_reference, "Operands must be boolean");
                }
                left.target_type.nullable  = false;
                right.target_type.nullable = false;

                value_type = context.analyzer.bool_type;
            }
            else if (Operator == BinaryOperator.IN)
            {
                if (left.value_type.compatible(context.analyzer.int_type) &&
                    right.value_type.compatible(context.analyzer.int_type))
                {
                    // integers or enums
                    left.target_type.nullable  = false;
                    right.target_type.nullable = false;
                }
                else if (right.value_type is ArrayType)
                {
                    if (!left.value_type.compatible(((ArrayType)right.value_type).element_type))
                    {
                        Report.error(source_reference, "Cannot look for `%s' in `%s'".printf(left.value_type.ToString(), right.value_type.ToString()));
                    }
                }
                else
                {
                    // otherwise require a bool contains () method
                    var contains_method = right.value_type.get_member("contains") as Method;
                    if (contains_method == null)
                    {
                        Report.error(source_reference, "`%s' does not have a `contains' method".printf(right.value_type.ToString()));
                        error = true;
                        return(false);
                    }
                    if (contains_method.get_parameters().Count != 1)
                    {
                        Report.error(source_reference, "`%s' must have one parameter".printf(contains_method.get_full_name()));
                        error = true;
                        return(false);
                    }
                    if (!contains_method.return_type.compatible(context.analyzer.bool_type))
                    {
                        Report.error(source_reference, "`%s' must return a boolean value".printf(contains_method.get_full_name()));
                        error = true;
                        return(false);
                    }

                    var contains_call = new MethodCall(new MemberAccess(right, "contains", source_reference), source_reference);
                    contains_call.add_argument(left);
                    parent_node.replace_expression(this, contains_call);
                    return(contains_call.check(context));
                }

                value_type = context.analyzer.bool_type;
            }
            else
            {
                assert_not_reached();
            }

            return(!error);
        }