/* valabinaryexpression.vala * * Copyright (C) 2006-2010 Jürg Billeter * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * Author: * Jürg Billeter */ /** * Represents an expression with two operands in the source code. * * Supports +, -, *, /, %, <<, >>, <, >, <=, >=, ==, !=, &, |, ^, &&, ||, ??. */ public class Vala.BinaryExpression : Expression { /** * The binary operator. */ public BinaryOperator operator { get; private set; } /** * The left operand. */ public Expression left { get { return _left; } private set { _left = value; _left.parent_node = this; } } /** * The right operand. */ public Expression right { get { return _right; } private set { _right = value; _right.parent_node = this; } } public bool is_chained { get; private set; } private Expression _left; private Expression _right; /** * Creates a new binary expression. * * @param op binary operator * @param _left left operand * @param _right right operand * @param source reference to source code * @return newly created binary expression */ public BinaryExpression (BinaryOperator op, Expression _left, Expression _right, SourceReference? source = null) { operator = op; left = _left; right = _right; is_chained = false; source_reference = source; } public BinaryExpression.chained (BinaryOperator op, Expression _left, Expression _right, SourceReference? source = null) { operator = op; left = _left; right = _right; is_chained = true; source_reference = source; } public override void accept (CodeVisitor visitor) { visitor.visit_binary_expression (this); visitor.visit_expression (this); } public override void accept_children (CodeVisitor visitor) { left.accept (visitor); right.accept (visitor); } public override void replace_expression (Expression old_node, Expression new_node) { if (left == old_node) { left = new_node; } if (right == old_node) { right = new_node; } } public override string to_string () { return "(%s %s %s)".printf (_left.to_string (), operator.to_string (), _right.to_string ()); } public override bool is_constant () { return left.is_constant () && right.is_constant (); } public override bool is_pure () { return left.is_pure () && right.is_pure (); } public override bool is_non_null () { return left.is_non_null () && right.is_non_null (); } public override bool is_accessible (Symbol sym) { return left.is_accessible (sym) && right.is_accessible (sym); } public override void get_error_types (Collection collection, SourceReference? source_reference = null) { left.get_error_types (collection, source_reference); right.get_error_types (collection, source_reference); } public override bool check (CodeContext context) { if (checked) { return !error; } 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); var right_stmt = new ExpressionStatement (new Assignment (new MemberAccess.simple (local.name, right.source_reference), right, AssignmentOperator.SIMPLE, right.source_reference), right.source_reference); var stmt = new ExpressionStatement (new Assignment (new 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); decl.check (context); if (!if_stmt.check (context)) { error = true; return false; } var ma = new MemberAccess.simple (local.name, source_reference); ma.target_type = target_type; ma.formal_target_type = formal_target_type; parent_node.replace_expression (this, ma); ma.check (context); return true; } if (operator == BinaryOperator.COALESCE) { if (target_type != null) { left.target_type = target_type.copy (); right.target_type = target_type.copy (); } if (!left.check (context)) { error = true; return false; } string temp_name = get_temp_name (); // right expression is checked under a block (required for short-circuiting) var right_local = new LocalVariable (null, temp_name, right, right.source_reference); var right_decl = new DeclarationStatement (right_local, right.source_reference); var true_block = new Block (source_reference); true_block.add_statement (right_decl); if (!true_block.check (context)) { error = true; return false; } // right expression may have been replaced by the check right = right_local.initializer; 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 (); } if (local_type != null && right.value_type is ValueType && !right.value_type.nullable) { // immediate values in the right expression must always be boxed, // otherwise the local variable may receive a stale pointer to the stack local_type.value_owned = true; } var local = new LocalVariable (local_type, temp_name, left, source_reference); var decl = new DeclarationStatement (local, source_reference); insert_statement (context.analyzer.insert_block, decl); if (!decl.check (context)) { error = true; return false; } // replace the temporary local variable used to compute the type of the right expression by an assignment var right_stmt = new ExpressionStatement (new Assignment (new MemberAccess.simple (local.name, right.source_reference), right, AssignmentOperator.SIMPLE, right.source_reference), right.source_reference); true_block.remove_local_variable (right_local); true_block.replace_statement (right_decl, right_stmt); if (!right_stmt.check (context)) { error = true; return false; } var cond = new BinaryExpression (BinaryOperator.EQUALITY, new 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, if_stmt); 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 = new CastExpression.non_null (replace_expr, source_reference); cast.target_type = replace_expr.target_type.copy (); cast.target_type.nullable = false; replace_expr = cast; } parent_node.replace_expression (this, replace_expr); replace_expr.check (context); return true; } // enum-type inference if (target_type != null && target_type.type_symbol is Enum && (operator == BinaryOperator.BITWISE_AND || operator == BinaryOperator.BITWISE_OR)) { left.target_type = target_type.copy (); right.target_type = target_type.copy (); } else if (operator == BinaryOperator.IN) { right.check (context); if (right.value_type.type_symbol is Enum) { left.target_type = right.value_type.copy (); } } left.check (context); if (left.value_type != null && left.value_type.type_symbol is Enum && (operator == BinaryOperator.EQUALITY || operator == BinaryOperator.INEQUALITY)) { right.target_type = left.value_type.copy (); } right.check (context); if (right.value_type != null && right.value_type.type_symbol is Enum && (operator == BinaryOperator.EQUALITY || operator == BinaryOperator.INEQUALITY)) { left.target_type = right.value_type.copy (); //TODO bug 666035 -- re-check left how? } 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 || left.value_type is PropertyPrototype) { error = true; Report.error (left.source_reference, "Access to instance member `%s' denied", left.symbol_reference.get_full_name ()); return false; } if (right.value_type is FieldPrototype || right.value_type is PropertyPrototype) { error = true; Report.error (right.source_reference, "Access to instance member `%s' denied", 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 (operator == BinaryOperator.PLUS && !(left.value_type is PointerType) && left.value_type.compatible (context.analyzer.string_type)) { // string concatenation if (right.value_type == null || !right.value_type.compatible (context.analyzer.string_type) || left is NullLiteral || right is NullLiteral) { // operands cannot be null 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; } value_type.check (context); return !error; } else if (operator == BinaryOperator.PLUS && left.value_type is ArrayType) { // array concatenation unowned ArrayType array_type = (ArrayType) left.value_type; if (array_type.inline_allocated) { error = true; Report.error (source_reference, "Array concatenation not supported for fixed length arrays"); } 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; value_type.check (context); return !error; } switch (operator) { case BinaryOperator.PLUS: case BinaryOperator.MINUS: case BinaryOperator.MUL: case BinaryOperator.DIV: // check for pointer arithmetic if (left.value_type is PointerType) { unowned PointerType 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; } unowned Struct? offset_type = right.value_type.type_symbol 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'", left.value_type.to_string (), right.value_type.to_string ()); return false; } break; case BinaryOperator.MOD: case BinaryOperator.SHIFT_LEFT: case 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'", left.value_type.to_string (), right.value_type.to_string ()); return false; } break; case BinaryOperator.LESS_THAN: case BinaryOperator.GREATER_THAN: case BinaryOperator.LESS_THAN_OR_EQUAL: case 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 (is_chained) { unowned BinaryExpression lbe = (BinaryExpression) left; if (lbe.right.value_type.compatible (context.analyzer.string_type) && right.value_type.compatible (context.analyzer.string_type)) { value_type = context.analyzer.bool_type; break; } 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; unowned DataType left_type; if (is_chained) { unowned BinaryExpression lbe = (BinaryExpression) left; left_type = lbe.right.value_type; } else { left_type = left.value_type; } Report.error (source_reference, "Relational operation not supported for types `%s' and `%s'", left_type.to_string (), right.value_type.to_string ()); return false; } if (!is_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; break; case BinaryOperator.EQUALITY: case BinaryOperator.INEQUALITY: /* relational operation */ if (context.profile == Profile.GOBJECT) { // Implicit cast for comparison expression of GValue with other type var gvalue_type = context.analyzer.gvalue_type.type_symbol; if ((left.target_type.type_symbol == gvalue_type && right.target_type.type_symbol != gvalue_type) || (left.target_type.type_symbol != gvalue_type && right.target_type.type_symbol == gvalue_type)) { Expression gvalue_expr; DataType target_type; if (left.target_type.type_symbol == gvalue_type) { gvalue_expr = left; target_type = right.target_type; } else { gvalue_expr = right; target_type = left.target_type; } var cast_expr = new CastExpression (gvalue_expr, target_type, gvalue_expr.source_reference); replace_expression (gvalue_expr, cast_expr); checked = false; return check (context); } } DataType resulting_type; if (is_chained) { unowned BinaryExpression lbe = (BinaryExpression) left; resulting_type = context.analyzer.get_arithmetic_result_type (lbe.right.target_type, right.target_type); if (!right.value_type.compatible (lbe.right.value_type) && !lbe.right.value_type.compatible (right.value_type)) { Report.error (source_reference, "Equality operation: `%s' and `%s' are incompatible", right.value_type.to_string (), lbe.right.value_type.to_string ()); error = true; return false; } } else { resulting_type = context.analyzer.get_arithmetic_result_type (left.target_type, right.target_type); 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", right.value_type.to_string (), left.value_type.to_string ()); error = true; return false; } } if (resulting_type != null) { // numeric operation if (!is_chained) { 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; break; case BinaryOperator.BITWISE_AND: case BinaryOperator.BITWISE_OR: case BinaryOperator.BITWISE_XOR: // integer type or flags type left.target_type.nullable = false; right.target_type.nullable = false; // Don't falsely resolve to bool if (left.value_type.compatible (context.analyzer.bool_type) && !right.value_type.compatible (context.analyzer.bool_type)) { value_type = right.target_type.copy (); } else { value_type = left.target_type.copy (); } break; case BinaryOperator.AND: case 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; break; case 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; if (left.value_type.type_symbol is Enum && right.value_type.type_symbol is Enum && left.value_type.type_symbol != right.value_type.type_symbol) { error = true; Report.error (source_reference, "Cannot look for `%s' in `%s'", left.value_type.to_string (), right.value_type.to_string ()); } } else if (right.value_type is ArrayType) { if (!left.value_type.compatible (((ArrayType) right.value_type).element_type)) { error = true; Report.error (source_reference, "Cannot look for `%s' in `%s'", left.value_type.to_string (), right.value_type.to_string ()); } } 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", right.value_type.to_string ()); error = true; return false; } if (contains_method.get_parameters ().size != 1) { Report.error (source_reference, "`%s' must have one parameter", 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", 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; break; default: error = true; Report.error (source_reference, "internal error: unsupported binary operator"); return false; } value_type.check (context); return !error; } public override void emit (CodeGenerator codegen) { left.emit (codegen); right.emit (codegen); codegen.visit_binary_expression (this); codegen.visit_expression (this); } public override void get_defined_variables (Collection collection) { left.get_defined_variables (collection); right.get_defined_variables (collection); } public override void get_used_variables (Collection collection) { left.get_used_variables (collection); right.get_used_variables (collection); } } public enum Vala.BinaryOperator { NONE, PLUS, MINUS, MUL, DIV, MOD, SHIFT_LEFT, SHIFT_RIGHT, LESS_THAN, GREATER_THAN, LESS_THAN_OR_EQUAL, GREATER_THAN_OR_EQUAL, EQUALITY, INEQUALITY, BITWISE_AND, BITWISE_OR, BITWISE_XOR, AND, OR, IN, COALESCE; public unowned string to_string () { switch (this) { case PLUS: return "+"; case MINUS: return "-"; case MUL: return "*"; case DIV: return "/"; case MOD: return "%"; case SHIFT_LEFT: return "<<"; case SHIFT_RIGHT: return ">>"; case LESS_THAN: return "<"; case GREATER_THAN: return ">"; case LESS_THAN_OR_EQUAL: return "<="; case GREATER_THAN_OR_EQUAL: return ">="; case EQUALITY: return "=="; case INEQUALITY: return "!="; case BITWISE_AND: return "&"; case BITWISE_OR: return "|"; case BITWISE_XOR: return "^"; case AND: return "&&"; case OR: return "||"; case IN: return "in"; case COALESCE: return "??"; default: assert_not_reached (); } } }