valaforeachstatement.vala 14.6 KB
Newer Older
1 2
/* valaforeachstatement.vala
 *
3
 * Copyright (C) 2006-2010  Jürg Billeter
4 5 6 7
 *
 * 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
8
 * version 2.1 of the License, or (at your option) any later version.
9 10 11 12 13 14 15 16 17 18 19 20 21 22 23

 * 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 <j@bitron.ch>
 */


24 25 26 27
/**
 * Represents a foreach statement in the source code. Foreach statements iterate
 * over the elements of a collection.
 */
28
public class Vala.ForeachStatement : Block {
29 30 31
	/**
	 * Specifies the element type.
	 */
32
	public DataType? type_reference {
33 34
		get { return _data_type; }
		set {
35 36
			_data_type = value;
			if (_data_type != null) {
37 38
				_data_type.parent_node = this;
			}
39 40
		}
	}
41

42 43 44
	/**
	 * Specifies the element variable name.
	 */
45
	public string variable_name { get; set; }
46

47 48 49
	/**
	 * Specifies the container.
	 */
50
	public Expression collection {
51 52 53
		get {
			return _collection;
		}
54
		set {
55 56 57 58
			_collection = value;
			_collection.parent_node = this;
		}
	}
59

60 61 62
	/**
	 * Specifies the loop body.
	 */
63 64 65 66 67 68 69 70 71 72
	public Block body {
		get {
			return _body;
		}
		set {
			_body = value;
			_body.parent_node = this;
		}
	}

73 74
	public bool use_iterator { get; private set; }

75 76 77
	/**
	 * Specifies the declarator for the generated element variable.
	 */
78
	public LocalVariable element_variable { get; set; }
79

80 81 82
	/**
	 * Specifies the declarator for the generated collection variable.
	 */
83
	public LocalVariable collection_variable { get; set; }
84 85 86 87

	/**
	 * Specifies the declarator for the generated iterator variable.
	 */
88
	public LocalVariable iterator_variable { get; set; }
89

90
	private Expression _collection;
91
	private Block _body;
92

93 94
	private DataType _data_type;

95 96 97
	/**
	 * Creates a new foreach statement.
	 *
98 99 100 101 102 103
	 * @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
104
	 */
105
	public ForeachStatement (DataType? type_reference, string variable_name, Expression collection, Block body, SourceReference source_reference) {
106
		base (source_reference);
107 108 109 110
		this.variable_name = variable_name;
		this.collection = collection;
		this.body = body;
		this.type_reference = type_reference;
111
	}
112

113
	public override void accept (CodeVisitor visitor) {
114 115 116 117 118
		if (use_iterator) {
			base.accept (visitor);
			return;
		}

119 120
		visitor.visit_foreach_statement (this);
	}
121

122
	public override void accept_children (CodeVisitor visitor) {
123 124 125 126 127
		if (use_iterator) {
			base.accept_children (visitor);
			return;
		}

128
		collection.accept (visitor);
129 130
		visitor.visit_end_full_expression (collection);

131 132 133 134
		if (type_reference != null) {
			type_reference.accept (visitor);
		}

135
		body.accept (visitor);
136
	}
137

138
	public override void replace_expression (Expression old_node, Expression new_node) {
139
		if (collection == old_node) {
140
			collection = new_node;
141 142
		}
	}
143

144
	public override void replace_type (DataType old_type, DataType new_type) {
145 146 147 148
		if (type_reference == old_type) {
			type_reference = new_type;
		}
	}
149

150
	public override bool check (CodeContext context) {
151 152 153 154 155 156 157
		if (checked) {
			return !error;
		}

		checked = true;

		// analyze collection expression first, used for type inference
158
		if (!collection.check (context)) {
159 160 161 162 163 164 165 166 167 168 169
			// 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 ();
170

171
		if (collection_type is ArrayType) {
172
			var array_type = (ArrayType) collection_type;
173

174 175 176
			// can't use inline-allocated array for temporary variable
			array_type.inline_allocated = false;

177
			return check_without_iterator (context, collection_type, array_type.element_type);
178
		} else if (context.profile == Profile.GOBJECT && (collection_type.compatible (context.analyzer.glist_type) || collection_type.compatible (context.analyzer.gslist_type))) {
179 180 181 182
			if (collection_type.get_type_arguments ().size != 1) {
				error = true;
				Report.error (collection.source_reference, "missing type argument for collection");
				return false;
183 184
			}

185
			return check_without_iterator (context, collection_type, collection_type.get_type_arguments ().get (0));
186
		} else if (context.profile == Profile.GOBJECT && collection_type.compatible (context.analyzer.gvaluearray_type)) {
187
			return check_without_iterator (context, collection_type, context.analyzer.gvalue_type);
188
		} else {
189
			return check_with_iterator (context, collection_type);
190 191 192
		}
	}

193
	bool check_with_index (CodeContext context, DataType collection_type) {
194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218
		var get_method = collection_type.get_member ("get") as Method;
		if (get_method == null) {
			return false;
		}
		if (get_method.get_parameters ().size != 1) {
			return false;
		}
		var size_property = collection_type.get_member ("size") as Property;
		if (size_property == null) {
			return false;
		}

		add_statement (new DeclarationStatement (new LocalVariable (null, "_%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;
219
		return base.check (context);
220 221
	}

222
	bool check_with_iterator (CodeContext context, DataType collection_type) {
223 224
		use_iterator = true;

225
		if (check_with_index (context, collection_type)) {
226 227 228
			return true;
		}

229 230 231
		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".printf (collection_type.to_string ()));
232 233 234
			error = true;
			return false;
		}
235 236 237 238 239
		if (iterator_method.get_parameters ().size != 0) {
			Report.error (collection.source_reference, "`%s' must not have any parameters".printf (iterator_method.get_full_name ()));
			error = true;
			return false;
		}
240
		var iterator_type = iterator_method.return_type.get_actual_type (collection_type, null, this);
241 242 243 244 245
		if (iterator_type is VoidType) {
			Report.error (collection.source_reference, "`%s' must return an iterator".printf (iterator_method.get_full_name ()));
			error = true;
			return false;
		}
246

247
		var iterator_call = new MethodCall (new MemberAccess (collection, "iterator", source_reference), source_reference);
248 249 250
		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;
251
		var next_method = iterator_type.get_member ("next") as Method;
252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281
		if (next_value_method != null) {
			if (next_value_method.get_parameters ().size != 0) {
				Report.error (collection.source_reference, "`%s' must not have any parameters".printf (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".printf (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".printf (next_method.get_full_name ()));
				error = true;
				return false;
			}
282
			if (!next_method.return_type.compatible (context.analyzer.bool_type)) {
283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316
				Report.error (collection.source_reference, "`%s' must return a boolean value".printf (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".printf (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".printf (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".printf (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".printf (iterator_type.to_string ()));
317 318 319 320
			error = true;
			return false;
		}

321
		checked = false;
322
		return base.check (context);
323 324 325
	}

	bool analyze_element_type (DataType element_type) {
326 327 328
		// analyze element type
		if (type_reference == null) {
			// var type
329 330
			type_reference = element_type.copy ();
		} else if (!element_type.compatible (type_reference)) {
331
			error = true;
332
			Report.error (source_reference, "Foreach: Cannot convert from `%s' to `%s'".printf (element_type.to_string (), type_reference.to_string ()));
333
			return false;
334
		} else if (element_type.is_disposable () && element_type.value_owned && !type_reference.value_owned) {
335 336 337 338
			error = true;
			Report.error (source_reference, "Foreach: Invalid assignment from owned expression to unowned variable");
			return false;
		}
339

340
		return true;
341 342
	}

343
	bool check_without_iterator (CodeContext context, DataType collection_type, DataType element_type) {
344 345 346 347 348 349 350 351 352 353
		// analyze element type
		if (type_reference == null) {
			// var type
			type_reference = element_type.copy ();
		} else if (!element_type.compatible (type_reference)) {
			error = true;
			Report.error (source_reference, "Foreach: Cannot convert from `%s' to `%s'".printf (element_type.to_string (), type_reference.to_string ()));
			return false;
		}

354
		element_variable = new LocalVariable (type_reference, variable_name, null, source_reference);
355 356 357 358 359

		body.scope.add (variable_name, element_variable);

		body.add_local_variable (element_variable);
		element_variable.active = true;
360
		element_variable.checked = true;
361 362

		// analyze body
363 364
		owner = context.analyzer.current_symbol.scope;
		context.analyzer.current_symbol = this;
365

366 367 368 369
		// call add_local_variable to check for shadowed variable
		add_local_variable (element_variable);
		remove_local_variable (element_variable);

370
		body.check (context);
371 372 373 374 375

		foreach (LocalVariable local in get_local_variables ()) {
			local.active = false;
		}

376
		context.analyzer.current_symbol = context.analyzer.current_symbol.parent_symbol;
377

378
		collection_variable = new LocalVariable (collection_type.copy (), "%s_collection".printf (variable_name));
379 380 381 382 383 384

		add_local_variable (collection_variable);
		collection_variable.active = true;

		return !error;
	}
385

386 387 388 389 390 391 392 393
	public override void get_error_types (Collection<DataType> 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);
	}

394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411
	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);
	}

412
	public override void get_defined_variables (Collection<Variable> collection) {
413 414
		collection.add (element_variable);
	}
415
}