1/* valaforeachstatement.vala 2 * 3 * Copyright (C) 2006-2010 Jürg Billeter 4 * 5 * This library is free software; you can redistribute it and/or 6 * modify it under the terms of the GNU Lesser General Public 7 * License as published by the Free Software Foundation; either 8 * version 2.1 of the License, or (at your option) any later version. 9 10 * This library is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 * Lesser General Public License for more details. 14 15 * You should have received a copy of the GNU Lesser General Public 16 * License along with this library; if not, write to the Free Software 17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 18 * 19 * Author: 20 * Jürg Billeter <j@bitron.ch> 21 */ 22 23 24/** 25 * Represents a foreach statement in the source code. 26 * 27 * Foreach statements iterate over the elements of a collection. 28 */ 29public class Vala.ForeachStatement : Block { 30 /** 31 * Specifies the element type. 32 */ 33 public DataType? type_reference { 34 get { return _data_type; } 35 set { 36 _data_type = value; 37 if (_data_type != null) { 38 _data_type.parent_node = this; 39 } 40 } 41 } 42 43 /** 44 * Specifies the element variable name. 45 */ 46 public string variable_name { get; set; } 47 48 /** 49 * Specifies the container. 50 */ 51 public Expression collection { 52 get { 53 return _collection; 54 } 55 set { 56 _collection = value; 57 _collection.parent_node = this; 58 } 59 } 60 61 /** 62 * Specifies the loop body. 63 */ 64 public Block body { 65 get { 66 return _body; 67 } 68 set { 69 _body = value; 70 _body.parent_node = this; 71 } 72 } 73 74 public bool use_iterator { get; private set; } 75 76 /** 77 * Specifies the declarator for the generated element variable. 78 */ 79 public LocalVariable element_variable { get; set; } 80 81 /** 82 * Specifies the declarator for the generated collection variable. 83 */ 84 public LocalVariable collection_variable { get; set; } 85 86 /** 87 * Specifies the declarator for the generated iterator variable. 88 */ 89 public LocalVariable iterator_variable { get; set; } 90 91 private Expression _collection; 92 private Block _body; 93 94 private DataType _data_type; 95 96 /** 97 * Creates a new foreach statement. 98 * 99 * @param type_reference element type 100 * @param variable_name element variable name 101 * @param collection container 102 * @param body loop body 103 * @param source_reference reference to source code 104 * @return newly created foreach statement 105 */ 106 public ForeachStatement (DataType? type_reference, string variable_name, Expression collection, Block body, SourceReference? source_reference = null) { 107 base (source_reference); 108 this.variable_name = variable_name; 109 this.collection = collection; 110 this.body = body; 111 this.type_reference = type_reference; 112 } 113 114 public override void accept (CodeVisitor visitor) { 115 if (use_iterator) { 116 base.accept (visitor); 117 return; 118 } 119 120 visitor.visit_foreach_statement (this); 121 } 122 123 public override void accept_children (CodeVisitor visitor) { 124 if (use_iterator) { 125 base.accept_children (visitor); 126 return; 127 } 128 129 collection.accept (visitor); 130 visitor.visit_end_full_expression (collection); 131 132 if (type_reference != null) { 133 type_reference.accept (visitor); 134 } 135 136 body.accept (visitor); 137 } 138 139 public override void replace_expression (Expression old_node, Expression new_node) { 140 if (collection == old_node) { 141 collection = new_node; 142 } 143 } 144 145 public override void replace_type (DataType old_type, DataType new_type) { 146 if (type_reference == old_type) { 147 type_reference = new_type; 148 } 149 } 150 151 public override bool check (CodeContext context) { 152 if (checked) { 153 return !error; 154 } 155 156 checked = true; 157 158 if (type_reference == null) { 159 type_reference = new VarType (); 160 } 161 162 // analyze collection expression first, used for type inference 163 if (!collection.check (context)) { 164 // ignore inner error 165 error = true; 166 return false; 167 } else if (collection.value_type == null) { 168 Report.error (collection.source_reference, "invalid collection expression"); 169 error = true; 170 return false; 171 } 172 173 var collection_type = collection.value_type.copy (); 174 collection.target_type = collection_type.copy (); 175 176 if (collection_type is ArrayType) { 177 var array_type = (ArrayType) collection_type; 178 179 // can't use inline-allocated array for temporary variable 180 array_type.inline_allocated = false; 181 182 return check_without_iterator (context, collection_type, array_type.element_type); 183 } else if (context.profile == Profile.GOBJECT && (collection_type.compatible (context.analyzer.glist_type) || collection_type.compatible (context.analyzer.gslist_type))) { 184 if (collection_type.get_type_arguments ().size != 1) { 185 error = true; 186 Report.error (collection.source_reference, "missing type argument for collection"); 187 return false; 188 } 189 190 return check_without_iterator (context, collection_type, collection_type.get_type_arguments ().get (0)); 191 } else if (context.profile == Profile.GOBJECT && collection_type.compatible (context.analyzer.gvaluearray_type)) { 192 return check_without_iterator (context, collection_type, context.analyzer.gvalue_type); 193 } else { 194 return check_with_iterator (context, collection_type); 195 } 196 } 197 198 bool check_with_index (CodeContext context, DataType collection_type) { 199 var get_method = collection_type.get_member ("get") as Method; 200 if (get_method == null) { 201 return false; 202 } 203 unowned List<Parameter> parameters = get_method.get_parameters (); 204 if (parameters.size != 1 || !(parameters[0].variable_type is IntegerType)) { 205 return false; 206 } 207 var size_property = collection_type.get_member ("size") as Property; 208 if (size_property == null) { 209 return false; 210 } 211 212 add_statement (new DeclarationStatement (new LocalVariable (null, "_%s_list".printf (variable_name), collection, source_reference), source_reference)); 213 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)); 214 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)); 215 var next = new UnaryExpression (UnaryOperator.INCREMENT, new MemberAccess.simple ("_%s_index".printf (variable_name), source_reference), source_reference); 216 var conditional = new BinaryExpression (BinaryOperator.LESS_THAN, next, new MemberAccess.simple ("_%s_size".printf (variable_name), source_reference), source_reference); 217 var loop = new WhileStatement (conditional, body, source_reference); 218 add_statement (loop); 219 220 var get_call = new MethodCall (new MemberAccess (new MemberAccess.simple ("_%s_list".printf (variable_name), source_reference), "get", source_reference), source_reference); 221 get_call.add_argument (new MemberAccess.simple ("_%s_index".printf (variable_name), source_reference)); 222 body.insert_statement (0, new DeclarationStatement (new LocalVariable (type_reference, variable_name, get_call, source_reference), source_reference)); 223 224 checked = false; 225 return base.check (context); 226 } 227 228 bool check_with_iterator (CodeContext context, DataType collection_type) { 229 use_iterator = true; 230 231 if (check_with_index (context, collection_type)) { 232 return true; 233 } 234 235 var iterator_method = collection_type.get_member ("iterator") as Method; 236 if (iterator_method == null) { 237 Report.error (collection.source_reference, "`%s' does not have an `iterator' method".printf (collection_type.to_string ())); 238 error = true; 239 return false; 240 } 241 if (iterator_method.get_parameters ().size != 0) { 242 Report.error (collection.source_reference, "`%s' must not have any parameters".printf (iterator_method.get_full_name ())); 243 error = true; 244 return false; 245 } 246 var iterator_type = iterator_method.return_type.get_actual_type (collection_type, null, this); 247 if (iterator_type is VoidType) { 248 Report.error (collection.source_reference, "`%s' must return an iterator".printf (iterator_method.get_full_name ())); 249 error = true; 250 return false; 251 } 252 253 var iterator_call = new MethodCall (new MemberAccess (collection, "iterator", source_reference), source_reference); 254 add_statement (new DeclarationStatement (new LocalVariable (iterator_type, "_%s_it".printf (variable_name), iterator_call, source_reference), source_reference)); 255 256 var next_value_method = iterator_type.get_member ("next_value") as Method; 257 var next_method = iterator_type.get_member ("next") as Method; 258 if (next_value_method != null) { 259 if (next_value_method.get_parameters ().size != 0) { 260 Report.error (collection.source_reference, "`%s' must not have any parameters".printf (next_value_method.get_full_name ())); 261 error = true; 262 return false; 263 } 264 var element_type = next_value_method.return_type.get_actual_type (iterator_type, null, this); 265 if (!element_type.nullable) { 266 Report.error (collection.source_reference, "return type of `%s' must be nullable".printf (next_value_method.get_full_name ())); 267 error = true; 268 return false; 269 } 270 271 if (!analyze_element_type (element_type)) { 272 return false; 273 } 274 275 add_statement (new DeclarationStatement (new LocalVariable (type_reference, variable_name, null, source_reference), source_reference)); 276 277 var next_value_call = new MethodCall (new MemberAccess (new MemberAccess.simple ("_%s_it".printf (variable_name), source_reference), "next_value", source_reference), source_reference); 278 var assignment = new Assignment (new MemberAccess (null, variable_name, source_reference), next_value_call, AssignmentOperator.SIMPLE, source_reference); 279 var conditional = new BinaryExpression (BinaryOperator.INEQUALITY, assignment, new NullLiteral (source_reference), source_reference); 280 var loop = new WhileStatement (conditional, body, source_reference); 281 add_statement (loop); 282 } else if (next_method != null) { 283 if (next_method.get_parameters ().size != 0) { 284 Report.error (collection.source_reference, "`%s' must not have any parameters".printf (next_method.get_full_name ())); 285 error = true; 286 return false; 287 } 288 if (!next_method.return_type.compatible (context.analyzer.bool_type)) { 289 Report.error (collection.source_reference, "`%s' must return a boolean value".printf (next_method.get_full_name ())); 290 error = true; 291 return false; 292 } 293 var get_method = iterator_type.get_member ("get") as Method; 294 if (get_method == null) { 295 Report.error (collection.source_reference, "`%s' does not have a `get' method".printf (iterator_type.to_string ())); 296 error = true; 297 return false; 298 } 299 if (get_method.get_parameters ().size != 0) { 300 Report.error (collection.source_reference, "`%s' must not have any parameters".printf (get_method.get_full_name ())); 301 error = true; 302 return false; 303 } 304 var element_type = get_method.return_type.get_actual_type (iterator_type, null, this); 305 if (element_type is VoidType) { 306 Report.error (collection.source_reference, "`%s' must return an element".printf (get_method.get_full_name ())); 307 error = true; 308 return false; 309 } 310 311 if (!analyze_element_type (element_type)) { 312 return false; 313 } 314 315 var next_call = new MethodCall (new MemberAccess (new MemberAccess.simple ("_%s_it".printf (variable_name), source_reference), "next", source_reference), source_reference); 316 var loop = new WhileStatement (next_call, body, source_reference); 317 add_statement (loop); 318 319 var get_call = new MethodCall (new MemberAccess (new MemberAccess.simple ("_%s_it".printf (variable_name), source_reference), "get", source_reference), source_reference); 320 body.insert_statement (0, new DeclarationStatement (new LocalVariable (type_reference, variable_name, get_call, source_reference), source_reference)); 321 } else { 322 Report.error (collection.source_reference, "`%s' does not have a `next_value' or `next' method".printf (iterator_type.to_string ())); 323 error = true; 324 return false; 325 } 326 327 checked = false; 328 return base.check (context); 329 } 330 331 bool analyze_element_type (DataType element_type) { 332 // analyze element type 333 if (type_reference is VarType) { 334 // var type 335 bool value_owned = type_reference.value_owned; 336 type_reference = element_type.copy (); 337 // FIXME Only follows "unowned var" otherwise inherit ownership of element-type 338 if (!value_owned) { 339 type_reference.value_owned = false; 340 } 341 } else if (!element_type.compatible (type_reference)) { 342 error = true; 343 Report.error (source_reference, "Foreach: Cannot convert from `%s' to `%s'".printf (element_type.to_string (), type_reference.to_string ())); 344 return false; 345 } else if (element_type.is_disposable () && element_type.value_owned && !type_reference.value_owned) { 346 error = true; 347 Report.error (source_reference, "Foreach: Invalid assignment from owned expression to unowned variable"); 348 return false; 349 } 350 351 return true; 352 } 353 354 bool check_without_iterator (CodeContext context, DataType collection_type, DataType element_type) { 355 // analyze element type 356 if (type_reference is VarType) { 357 // var type 358 bool value_owned = type_reference.value_owned; 359 type_reference = element_type.copy (); 360 // FIXME Only follows "unowned var" otherwise inherit ownership of element-type 361 if (!value_owned) { 362 type_reference.value_owned = false; 363 } 364 } else if (!element_type.compatible (type_reference)) { 365 error = true; 366 Report.error (source_reference, "Foreach: Cannot convert from `%s' to `%s'".printf (element_type.to_string (), type_reference.to_string ())); 367 return false; 368 } 369 370 element_variable = new LocalVariable (type_reference, variable_name, null, source_reference); 371 372 body.scope.add (variable_name, element_variable); 373 374 body.add_local_variable (element_variable); 375 element_variable.active = true; 376 element_variable.checked = true; 377 378 // analyze body 379 owner = context.analyzer.current_symbol.scope; 380 context.analyzer.current_symbol = this; 381 382 // call add_local_variable to check for shadowed variable 383 add_local_variable (element_variable); 384 remove_local_variable (element_variable); 385 386 body.check (context); 387 388 foreach (LocalVariable local in get_local_variables ()) { 389 local.active = false; 390 } 391 392 context.analyzer.current_symbol = context.analyzer.current_symbol.parent_symbol; 393 394 collection_variable = new LocalVariable (collection_type.copy (), "%s_collection".printf (variable_name)); 395 396 add_local_variable (collection_variable); 397 collection_variable.active = true; 398 399 return !error; 400 } 401 402 public override void get_error_types (Collection<DataType> collection, SourceReference? source_reference = null) { 403 if (source_reference == null) { 404 source_reference = this.source_reference; 405 } 406 this.collection.get_error_types (collection, source_reference); 407 body.get_error_types (collection, source_reference); 408 } 409 410 public override void emit (CodeGenerator codegen) { 411 if (use_iterator) { 412 base.emit (codegen); 413 return; 414 } 415 416 collection.emit (codegen); 417 codegen.visit_end_full_expression (collection); 418 419 element_variable.active = true; 420 collection_variable.active = true; 421 if (iterator_variable != null) { 422 iterator_variable.active = true; 423 } 424 425 codegen.visit_foreach_statement (this); 426 } 427 428 public override void get_defined_variables (Collection<Variable> collection) { 429 if (element_variable != null) { 430 collection.add (element_variable); 431 } 432 } 433} 434