/* valaassignment.vala * * Copyright (C) 2006-2011 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 assignment expression in the source code. * * Supports =, |=, &=, ^=, +=, -=, *=, /=, %=, <<=, >>=. */ public class Vala.Assignment : Expression { /** * Left hand side of the assignment. */ public Expression left { get { return _left; } private set { _left = value; _left.parent_node = this; } } /** * Assignment operator. */ public AssignmentOperator operator { get; private set; } /** * Right hand side of the assignment. */ public Expression right { get { return _right; } private set { _right = value; _right.parent_node = this; } } private Expression _left; private Expression _right; /** * Creates a new assignment. * * @param left left hand side * @param operator assignment operator * @param right right hand side * @param source_reference reference to source code * @return newly created assignment */ public Assignment (Expression left, Expression right, AssignmentOperator operator = AssignmentOperator.SIMPLE, SourceReference? source_reference = null) { this.right = right; this.operator = operator; this.source_reference = source_reference; this.left = left; } public override void accept (CodeVisitor visitor) { visitor.visit_assignment (this); visitor.visit_expression (this); } public override void accept_children (CodeVisitor visitor) { left.accept (visitor); right.accept (visitor); } public override string to_string () { return "(%s %s %s)".printf (_left.to_string (), operator.to_string (), _right.to_string ()); } 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 bool is_pure () { return false; } 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; if (left is Tuple && operator == AssignmentOperator.SIMPLE && parent_node is ExpressionStatement) { unowned Tuple tuple = (Tuple) left; var local = new LocalVariable (null, get_temp_name (), right, right.source_reference); var decl = new DeclarationStatement (local, source_reference); insert_statement (context.analyzer.insert_block, decl); decl.check (context); int i = 0; ExpressionStatement stmt = null; foreach (var expr in tuple.get_expressions ()) { if (stmt != null) { insert_statement (context.analyzer.insert_block, stmt); stmt.check (context); } var temp_access = new MemberAccess.simple (local.name, right.source_reference); var ea = new ElementAccess (temp_access, expr.source_reference); ea.append_index (new IntegerLiteral (i.to_string (), 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) { unowned MemberAccess ma = (MemberAccess) left; check_constant_assignment (ma); if ((!(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", 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.get_attribute ("GtkChild") != null) { error = true; Report.error (source_reference, "Assignment of [GtkChild] `%s' is not allowed", ma.symbol_reference.get_full_name ()); return false; } 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) { unowned ElementAccess ea = (ElementAccess) left; check_constant_assignment (ea.container as MemberAccess); if (ea.container.value_type.type_symbol == context.analyzer.string_type.type_symbol) { error = true; Report.error (ea.source_reference, "strings are immutable"); return false; } 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); } if (operator != AssignmentOperator.SIMPLE && ea.container.value_type.get_member ("get") is Method) { // transform into binary expression inside set call var get_call = new MethodCall (new MemberAccess (ea.container, "get", source_reference), source_reference); foreach (Expression e in ea.get_indices ()) { get_call.add_argument (e); } BinaryOperator bop; switch (operator) { case AssignmentOperator.BITWISE_OR: bop = BinaryOperator.BITWISE_OR; break; case AssignmentOperator.BITWISE_AND: bop = BinaryOperator.BITWISE_AND; break; case AssignmentOperator.BITWISE_XOR: bop = BinaryOperator.BITWISE_XOR; break; case AssignmentOperator.ADD: bop = BinaryOperator.PLUS; break; case AssignmentOperator.SUB: bop = BinaryOperator.MINUS; break; case AssignmentOperator.MUL: bop = BinaryOperator.MUL; break; case AssignmentOperator.DIV: bop = BinaryOperator.DIV; break; case AssignmentOperator.PERCENT: bop = BinaryOperator.MOD; break; case AssignmentOperator.SHIFT_LEFT: bop = BinaryOperator.SHIFT_LEFT; break; case AssignmentOperator.SHIFT_RIGHT: bop = BinaryOperator.SHIFT_RIGHT; break; default: error = true; Report.error (source_reference, "internal error: unsupported assignment operator"); return false; } right = new BinaryExpression (bop, get_call, right, source_reference); } set_call.add_argument (right); parent_node.replace_expression (this, set_call); return set_call.check (context); } else { right.target_type = left.value_type.copy (); } } else if (left is PointerIndirection) { right.target_type = left.value_type.copy (); } else if (left is Literal) { error = true; Report.error (source_reference, "Literals are immutable"); return false; } 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; } unowned MemberAccess? ma = left as MemberAccess; unowned ElementAccess? ea = left as ElementAccess; bool transform_assignment = false; if (ma != null && !(ma.symbol_reference is LocalVariable)) { transform_assignment = true; } else if (left.value_type != null && !left.value_type.is_non_null_simple_type ()) { transform_assignment = true; } else if (ea != null && ea.container.value_type is ArrayType) { // check if the left is an array and its element is non-null simple type unowned ArrayType array_type = (ArrayType) ea.container.value_type; transform_assignment = !array_type.element_type.is_non_null_simple_type (); } if ((operator != AssignmentOperator.SIMPLE) && transform_assignment) { // transform into simple assignment // FIXME: only do this if the backend doesn't support // the assignment natively Expression old_value = null; if (ma != null) { old_value = new MemberAccess (ma.inner, ma.member_name, left.source_reference); } else if (ea !=null) { old_value = new ElementAccess (ea.container, left.source_reference); var indices = ea.get_indices (); foreach (var index in indices) { ((ElementAccess) old_value).append_index (index); } } else { assert_not_reached (); } BinaryOperator bop; switch (operator) { case AssignmentOperator.BITWISE_OR: bop = BinaryOperator.BITWISE_OR; break; case AssignmentOperator.BITWISE_AND: bop = BinaryOperator.BITWISE_AND; break; case AssignmentOperator.BITWISE_XOR: bop = BinaryOperator.BITWISE_XOR; break; case AssignmentOperator.ADD: bop = BinaryOperator.PLUS; break; case AssignmentOperator.SUB: bop = BinaryOperator.MINUS; break; case AssignmentOperator.MUL: bop = BinaryOperator.MUL; break; case AssignmentOperator.DIV: bop = BinaryOperator.DIV; break; case AssignmentOperator.PERCENT: bop = BinaryOperator.MOD; break; case AssignmentOperator.SHIFT_LEFT: bop = BinaryOperator.SHIFT_LEFT; break; case AssignmentOperator.SHIFT_RIGHT: bop = BinaryOperator.SHIFT_RIGHT; break; default: error = true; Report.error (source_reference, "internal error: unsupported assignment operator"); return false; } var bin = new BinaryExpression (bop, old_value, right, source_reference); bin.target_type = right.target_type; right.target_type = right.target_type.copy (); right.target_type.value_owned = false; right = bin; right.check (context); operator = AssignmentOperator.SIMPLE; } if (ma != null) { if (ma.symbol_reference is Property) { unowned Property prop = (Property) ma.symbol_reference; unowned DynamicProperty? 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 (); } } else if (ma.symbol_reference is ArrayLengthField && ((ArrayType) ma.inner.value_type).inline_allocated) { error = true; Report.error (source_reference, "`length' field of fixed length arrays is read-only"); return false; } else if (ma.symbol_reference is Variable && right.value_type is MethodType) { unowned Variable variable = (Variable) ma.symbol_reference; if (variable.variable_type is DelegateType) { /* check whether method matches callback type */ if (!right.value_type.compatible (variable.variable_type)) { unowned Method m = (Method) right.symbol_reference; unowned Delegate cb = ((DelegateType) variable.variable_type).delegate_symbol; error = true; Report.error (source_reference, "Declaration of method `%s' is not compatible with delegate `%s'", m.get_full_name (), cb.get_full_name ()); return false; } } else { error = true; Report.error (source_reference, "Assignment: Invalid assignment attempt"); return false; } } else if (ma.symbol_reference is Variable && right.value_type == null) { error = true; Report.error (source_reference, "Assignment: Invalid assignment attempt"); return false; } else if (ma.symbol_reference is Variable) { unowned Variable variable = (Variable) ma.symbol_reference; unowned ArrayType? variable_array_type = variable.variable_type as ArrayType; if (variable_array_type != null && variable_array_type.inline_allocated && right is ArrayCreationExpression && ((ArrayCreationExpression) right).initializer_list == null) { Report.warning (source_reference, "Inline allocated arrays don't require an explicit instantiation"); ((Block) parent_node.parent_node).replace_statement ((Statement) parent_node, new EmptyStatement (source_reference)); return true; } } 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'", right.value_type.to_string (), left.value_type.to_string ()); return false; } else if (left.value_type is EnumValueType && right.value_type is IntegerType && (!(right is IntegerLiteral) || ((IntegerLiteral) right).value != "0")) { //FIXME This will have to be an error in the future? Report.notice (source_reference, "Assignment: Unsafe conversion from `%s' to `%s'", right.value_type.to_string (), left.value_type.to_string ()); } 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 */ } } } unowned MemberAccess? 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) { unowned Field f = (Field) ma.symbol_reference; if (f.binding == MemberBinding.STATIC) { Report.warning (source_reference, "Assignment to same variable"); } else { unowned MemberAccess? ma_inner = ma.inner as MemberAccess; unowned MemberAccess? 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 (ea != null) { if (!right.value_type.compatible (left.value_type)) { error = true; Report.error (source_reference, "Assignment: Cannot convert from `%s' to `%s'", right.value_type.to_string (), left.value_type.to_string ()); return false; } if (right.value_type.is_disposable ()) { /* rhs transfers ownership of the expression */ DataType element_type; if (ea.container.value_type is ArrayType) { unowned ArrayType array_type = (ArrayType) ea.container.value_type; element_type = array_type.element_type; } else { var args = ea.container.value_type.get_type_arguments (); assert (args.size == 1); element_type = args.get (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; } if (value_type != null) { value_type.check (context); } return !error; } bool is_array_add () { unowned BinaryExpression? binary = right as BinaryExpression; if (binary != null && binary.left.value_type is ArrayType) { if (binary.operator == BinaryOperator.PLUS) { if (left.symbol_reference == binary.left.symbol_reference) { // Allow direct access to array variable binary.left.lvalue = true; return true; } } } return false; } void check_constant_assignment (MemberAccess? inner) { while (inner != null) { if (inner.symbol_reference is Constant) { error = true; Report.error (source_reference, "Assignment to constant after initialization"); break; } if (inner.inner is MemberAccess) { inner = (MemberAccess) inner.inner; } else if (inner.inner is ElementAccess) { inner = ((ElementAccess) inner.inner).container as MemberAccess; } else { inner = null; } } } public override void emit (CodeGenerator codegen) { unowned MemberAccess? ma = left as MemberAccess; unowned ElementAccess? ea = left as ElementAccess; unowned PointerIndirection? pi = left as PointerIndirection; if (ma != null) { unowned LocalVariable? local = ma.symbol_reference as LocalVariable; unowned Parameter? param = ma.symbol_reference as Parameter; unowned Field? field = ma.symbol_reference as Field; unowned Property? property = ma.symbol_reference as Property; bool instance = (field != null && field.binding != MemberBinding.STATIC) || (property != null && property.binding != MemberBinding.STATIC); if (operator == AssignmentOperator.SIMPLE && (local != null || param != null || field != null) && !is_array_add () && !(field is ArrayLengthField) && !(field is DelegateTargetField) && !(field is DelegateDestroyField) && !(left.value_type.is_real_non_null_struct_type () && right is ObjectCreationExpression)) { // visit_assignment not necessary if (instance && ma.inner != null) { ma.inner.emit (codegen); } right.emit (codegen); var new_value = right.target_value; if (local != null) { codegen.store_local (local, new_value, false, source_reference); } else if (param != null) { codegen.store_parameter (param, new_value, false, source_reference); } else if (field != null) { codegen.store_field (field, instance && ma.inner != null ? ma.inner.target_value : null, new_value, false, source_reference); } if (!(parent_node is ExpressionStatement)) { // when load_variable is changed to use temporary // variables, replace following code with this line // target_value = new_value; if (local != null) { target_value = codegen.load_local (local); } else if (param != null) { target_value = codegen.load_parameter (param); } else if (field != null) { target_value = codegen.load_field (field, instance && ma.inner != null ? ma.inner.target_value : null); } } codegen.visit_expression (this); return; } if (instance && ma.inner != null && property != null) { ma.inner.emit (codegen); } else if (property == null) { // always process full lvalue // current codegen depends on it // should be removed when moving codegen from // visit_assignment to emit_store_field/local/param ma.emit (codegen); } } else if (ea != null) { // always process full lvalue // current codegen depends on it // should be removed when moving codegen from // visit_assignment to emit_store_element ea.emit (codegen); } else if (pi != null) { // always process full lvalue // current codegen depends on it // should be removed when moving codegen from // visit_assignment to emit_store_indirectZ pi.emit (codegen); } right.emit (codegen); codegen.visit_assignment (this); codegen.visit_expression (this); } public override void get_defined_variables (Collection collection) { right.get_defined_variables (collection); left.get_defined_variables (collection); unowned LocalVariable? local = left.symbol_reference as LocalVariable; unowned Parameter? param = left.symbol_reference as Parameter; if (local != null) { collection.add (local); } else if (param != null && param.direction == ParameterDirection.OUT) { collection.add (param); } } public override void get_used_variables (Collection collection) { unowned MemberAccess? ma = left as MemberAccess; unowned ElementAccess? ea = left as ElementAccess; if (ma != null && ma.inner != null) { ma.inner.get_used_variables (collection); } else if (ea != null) { ea.get_used_variables (collection); } right.get_used_variables (collection); } } public enum Vala.AssignmentOperator { NONE, SIMPLE, BITWISE_OR, BITWISE_AND, BITWISE_XOR, ADD, SUB, MUL, DIV, PERCENT, SHIFT_LEFT, SHIFT_RIGHT; public unowned string to_string () { switch (this) { case SIMPLE: return "="; case BITWISE_OR: return "|="; case BITWISE_AND: return "&="; case BITWISE_XOR: return "^="; case ADD: return "+="; case SUB: return "-="; case MUL: return "*="; case DIV: return "/="; case PERCENT: return "%="; case SHIFT_LEFT: return "<<="; case SHIFT_RIGHT: return ">>="; default: assert_not_reached (); } } }