/* valaforeachstatement.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 a foreach statement in the source code. * * Foreach statements iterate over the elements of a collection. */ public class Vala.ForeachStatement : Block { /** * Specifies the element type. */ public DataType? type_reference { get { return _data_type; } private set { _data_type = value; if (_data_type != null) { _data_type.parent_node = this; } } } /** * Specifies the element variable name. */ public string variable_name { get; private set; } /** * Specifies the container. */ public Expression collection { get { return _collection; } private set { _collection = value; _collection.parent_node = this; } } /** * Specifies the loop body. */ public Block body { get { return _body; } private set { _body = value; _body.parent_node = this; } } public bool use_iterator { get; private set; } /** * Specifies the declarator for the generated element variable. */ public LocalVariable element_variable { get; private set; } /** * Specifies the declarator for the generated collection variable. */ public LocalVariable collection_variable { get; private set; } /** * Specifies the declarator for the generated iterator variable. */ public LocalVariable iterator_variable { get; private set; } private Expression _collection; private Block _body; private DataType _data_type; /** * Creates a new foreach statement. * * @param type_reference element type * @param variable_name element variable name * @param collection container * @param body loop body * @param source_reference reference to source code * @return newly created foreach statement */ public ForeachStatement (DataType? type_reference, string variable_name, Expression collection, Block body, SourceReference? source_reference = null) { base (source_reference); this.variable_name = variable_name; this.collection = collection; this.body = body; this.type_reference = type_reference; } public override void accept (CodeVisitor visitor) { if (use_iterator) { base.accept (visitor); return; } visitor.visit_foreach_statement (this); } public override void accept_children (CodeVisitor visitor) { if (use_iterator) { base.accept_children (visitor); return; } collection.accept (visitor); visitor.visit_end_full_expression (collection); if (type_reference != null) { type_reference.accept (visitor); } body.accept (visitor); } public override void replace_expression (Expression old_node, Expression new_node) { if (collection == old_node) { collection = new_node; } } public override void replace_type (DataType old_type, DataType new_type) { if (type_reference == old_type) { type_reference = new_type; } } public override bool check (CodeContext context) { if (checked) { return !error; } checked = true; if (type_reference == null) { type_reference = new VarType (); } // analyze collection expression first, used for type inference if (!collection.check (context)) { // ignore inner error error = true; return false; } else if (collection.value_type == null) { Report.error (collection.source_reference, "invalid collection expression"); error = true; return false; } var collection_type = collection.value_type.copy (); collection.target_type = collection_type.copy (); if (collection_type is ArrayType) { var array_type = (ArrayType) collection_type; // can't use inline-allocated array for temporary variable array_type.inline_allocated = false; return check_without_iterator (context, collection_type, array_type.element_type); } else if (context.profile == Profile.GOBJECT && (collection_type.compatible (context.analyzer.glist_type) || collection_type.compatible (context.analyzer.gslist_type) || collection_type.compatible (context.analyzer.genericarray_type) || collection_type.compatible (context.analyzer.garray_type) || collection_type.compatible (context.analyzer.gsequence_type))) { if (collection_type.get_type_arguments ().size != 1) { error = true; Report.error (collection.source_reference, "missing type argument for collection"); return false; } return check_without_iterator (context, collection_type, collection_type.get_type_arguments ().get (0)); } else if (context.profile == Profile.GOBJECT && collection_type.compatible (context.analyzer.gvaluearray_type)) { return check_without_iterator (context, collection_type, context.analyzer.gvalue_type); } else if (context.profile == Profile.GOBJECT && collection_type.compatible (context.analyzer.string_type)) { return check_without_iterator (context, collection_type, context.analyzer.unichar_type); } else { return check_with_iterator (context, collection_type); } } bool check_with_index (CodeContext context, DataType collection_type) { var get_method = collection_type.get_member ("get") as Method; if (get_method == null) { return false; } unowned List parameters = get_method.get_parameters (); if (parameters.size != 1 || !(parameters[0].variable_type is IntegerType)) { return false; } var size_property = collection_type.get_member ("size") as Property; if (size_property == null) { return false; } var list_type = collection_type.copy (); if (collection.symbol_reference is Variable) { list_type.value_owned = false; } add_statement (new DeclarationStatement (new LocalVariable (list_type, "_%s_list".printf (variable_name), collection, source_reference), source_reference)); add_statement (new DeclarationStatement (new LocalVariable (null, "_%s_size".printf (variable_name), new MemberAccess (new MemberAccess.simple ("_%s_list".printf (variable_name), source_reference), "size", source_reference), source_reference), source_reference)); add_statement (new DeclarationStatement (new LocalVariable (null, "_%s_index".printf (variable_name), new UnaryExpression (UnaryOperator.MINUS, new IntegerLiteral ("1", source_reference), source_reference), source_reference), source_reference)); var next = new UnaryExpression (UnaryOperator.INCREMENT, new MemberAccess.simple ("_%s_index".printf (variable_name), source_reference), source_reference); var conditional = new BinaryExpression (BinaryOperator.LESS_THAN, next, new MemberAccess.simple ("_%s_size".printf (variable_name), source_reference), source_reference); var loop = new WhileStatement (conditional, body, source_reference); add_statement (loop); var get_call = new MethodCall (new MemberAccess (new MemberAccess.simple ("_%s_list".printf (variable_name), source_reference), "get", source_reference), source_reference); get_call.add_argument (new MemberAccess.simple ("_%s_index".printf (variable_name), source_reference)); body.insert_statement (0, new DeclarationStatement (new LocalVariable (type_reference, variable_name, get_call, source_reference), source_reference)); checked = false; return base.check (context); } bool check_with_iterator (CodeContext context, DataType collection_type) { use_iterator = true; if (check_with_index (context, collection_type)) { return true; } var iterator_method = collection_type.get_member ("iterator") as Method; if (iterator_method == null) { Report.error (collection.source_reference, "`%s' does not have an `iterator' method", collection_type.to_string ()); error = true; return false; } if (iterator_method.get_parameters ().size != 0) { Report.error (collection.source_reference, "`%s' must not have any parameters", iterator_method.get_full_name ()); error = true; return false; } var iterator_type = iterator_method.return_type.get_actual_type (collection_type, null, this); if (iterator_type is VoidType) { Report.error (collection.source_reference, "`%s' must return an iterator", iterator_method.get_full_name ()); error = true; return false; } var iterator_call = new MethodCall (new MemberAccess (collection, "iterator", source_reference), source_reference); add_statement (new DeclarationStatement (new LocalVariable (iterator_type, "_%s_it".printf (variable_name), iterator_call, source_reference), source_reference)); var next_value_method = iterator_type.get_member ("next_value") as Method; var next_method = iterator_type.get_member ("next") as Method; if (next_value_method != null) { if (next_value_method.get_parameters ().size != 0) { Report.error (collection.source_reference, "`%s' must not have any parameters", next_value_method.get_full_name ()); error = true; return false; } var element_type = next_value_method.return_type.get_actual_type (iterator_type, null, this); if (!element_type.nullable) { Report.error (collection.source_reference, "return type of `%s' must be nullable", next_value_method.get_full_name ()); error = true; return false; } if (!analyze_element_type (element_type)) { return false; } add_statement (new DeclarationStatement (new LocalVariable (type_reference, variable_name, null, source_reference), source_reference)); var next_value_call = new MethodCall (new MemberAccess (new MemberAccess.simple ("_%s_it".printf (variable_name), source_reference), "next_value", source_reference), source_reference); var assignment = new Assignment (new MemberAccess (null, variable_name, source_reference), next_value_call, AssignmentOperator.SIMPLE, source_reference); var conditional = new BinaryExpression (BinaryOperator.INEQUALITY, assignment, new NullLiteral (source_reference), source_reference); var loop = new WhileStatement (conditional, body, source_reference); add_statement (loop); } else if (next_method != null) { if (next_method.get_parameters ().size != 0) { Report.error (collection.source_reference, "`%s' must not have any parameters", next_method.get_full_name ()); error = true; return false; } if (!next_method.return_type.compatible (context.analyzer.bool_type)) { Report.error (collection.source_reference, "`%s' must return a boolean value", next_method.get_full_name ()); error = true; return false; } var get_method = iterator_type.get_member ("get") as Method; if (get_method == null) { Report.error (collection.source_reference, "`%s' does not have a `get' method", iterator_type.to_string ()); error = true; return false; } if (get_method.get_parameters ().size != 0) { Report.error (collection.source_reference, "`%s' must not have any parameters", get_method.get_full_name ()); error = true; return false; } var element_type = get_method.return_type.get_actual_type (iterator_type, null, this); if (element_type is VoidType) { Report.error (collection.source_reference, "`%s' must return an element", get_method.get_full_name ()); error = true; return false; } if (!analyze_element_type (element_type)) { return false; } var next_call = new MethodCall (new MemberAccess (new MemberAccess.simple ("_%s_it".printf (variable_name), source_reference), "next", source_reference), source_reference); var loop = new WhileStatement (next_call, body, source_reference); add_statement (loop); var get_call = new MethodCall (new MemberAccess (new MemberAccess.simple ("_%s_it".printf (variable_name), source_reference), "get", source_reference), source_reference); body.insert_statement (0, new DeclarationStatement (new LocalVariable (type_reference, variable_name, get_call, source_reference), source_reference)); } else { Report.error (collection.source_reference, "`%s' does not have a `next_value' or `next' method", iterator_type.to_string ()); error = true; return false; } checked = false; return base.check (context); } bool analyze_element_type (DataType element_type) { // analyze element type if (type_reference is VarType) { // var type bool nullable = type_reference.nullable; bool value_owned = type_reference.value_owned; bool is_dynamic = type_reference.is_dynamic; type_reference = element_type.copy (); // FIXME Only follows "unowned var" otherwise inherit ownership of element-type if (!value_owned) { type_reference.value_owned = false; } if (nullable) { type_reference.nullable = true; } if (is_dynamic) { type_reference.is_dynamic = true; } } else if (!element_type.compatible (type_reference)) { error = true; Report.error (source_reference, "Foreach: Cannot convert from `%s' to `%s'", element_type.to_string (), type_reference.to_string ()); return false; } else if (element_type.is_disposable () && element_type.value_owned && !type_reference.value_owned) { error = true; Report.error (source_reference, "Foreach: Invalid assignment from owned expression to unowned variable"); return false; } return true; } bool check_without_iterator (CodeContext context, DataType collection_type, DataType element_type) { // analyze element type if (type_reference is VarType) { // var type bool nullable = type_reference.nullable; bool value_owned = type_reference.value_owned; bool is_dynamic = type_reference.is_dynamic; type_reference = element_type.copy (); // FIXME Only follows "unowned var" otherwise inherit ownership of element-type if (!value_owned) { type_reference.value_owned = false; } if (nullable) { type_reference.nullable = true; } if (is_dynamic) { type_reference.is_dynamic = true; } } else if (!element_type.compatible (type_reference)) { error = true; Report.error (source_reference, "Foreach: Cannot convert from `%s' to `%s'", element_type.to_string (), type_reference.to_string ()); return false; } element_variable = new LocalVariable (type_reference, variable_name, null, source_reference); body.scope.add (variable_name, element_variable); body.add_local_variable (element_variable); element_variable.active = true; element_variable.checked = true; // analyze body var old_symbol = context.analyzer.current_symbol; owner = context.analyzer.current_symbol.scope; context.analyzer.current_symbol = this; // call add_local_variable to check for shadowed variable add_local_variable (element_variable); remove_local_variable (element_variable); body.check (context); foreach (LocalVariable local in get_local_variables ()) { local.active = false; } context.analyzer.current_symbol = old_symbol; collection_variable = new LocalVariable (collection_type.copy (), "%s_collection".printf (variable_name), null, source_reference); add_local_variable (collection_variable); collection_variable.active = true; return !error; } public override void get_error_types (Collection collection, SourceReference? source_reference = null) { if (source_reference == null) { source_reference = this.source_reference; } this.collection.get_error_types (collection, source_reference); body.get_error_types (collection, source_reference); } public override void emit (CodeGenerator codegen) { if (use_iterator) { base.emit (codegen); return; } collection.emit (codegen); codegen.visit_end_full_expression (collection); element_variable.active = true; collection_variable.active = true; if (iterator_variable != null) { iterator_variable.active = true; } codegen.visit_foreach_statement (this); } public override void get_defined_variables (Collection collection) { if (element_variable != null) { collection.add (element_variable); } } }