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