Line data Source code
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 : */
29 702 : public class Vala.ForeachStatement : Block {
30 : /**
31 : * Specifies the element type.
32 : */
33 : public DataType? type_reference {
34 3967 : get { return _data_type; }
35 1196 : private set {
36 3128 : _data_type = value;
37 1196 : if (_data_type != null) {
38 1196 : _data_type.parent_node = this;
39 : }
40 : }
41 : }
42 :
43 : /**
44 : * Specifies the element variable name.
45 : */
46 2126 : public string variable_name { get; private set; }
47 :
48 : /**
49 : * Specifies the container.
50 : */
51 : public Expression collection {
52 7012 : get {
53 7012 : return _collection;
54 : }
55 656 : private set {
56 1312 : _collection = value;
57 656 : _collection.parent_node = this;
58 : }
59 : }
60 :
61 : /**
62 : * Specifies the loop body.
63 : */
64 : public Block body {
65 3236 : get {
66 3236 : return _body;
67 : }
68 653 : private set {
69 1306 : _body = value;
70 653 : _body.parent_node = this;
71 : }
72 : }
73 :
74 483 : public bool use_iterator { get; private set; }
75 :
76 : /**
77 : * Specifies the declarator for the generated element variable.
78 : */
79 1334 : public LocalVariable element_variable { get; private set; }
80 :
81 : /**
82 : * Specifies the declarator for the generated collection variable.
83 : */
84 1154 : public LocalVariable collection_variable { get; private set; }
85 :
86 : /**
87 : * Specifies the declarator for the generated iterator variable.
88 : */
89 653 : public LocalVariable iterator_variable { get; private set; }
90 :
91 653 : private Expression _collection;
92 653 : private Block _body;
93 :
94 653 : 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 1959 : public ForeachStatement (DataType? type_reference, string variable_name, Expression collection, Block body, SourceReference? source_reference = null) {
107 653 : base (source_reference);
108 653 : this.variable_name = variable_name;
109 653 : this.collection = collection;
110 653 : this.body = body;
111 653 : this.type_reference = type_reference;
112 : }
113 :
114 1288 : public override void accept (CodeVisitor visitor) {
115 1288 : if (use_iterator) {
116 468 : base.accept (visitor);
117 468 : return;
118 : }
119 :
120 820 : visitor.visit_foreach_statement (this);
121 : }
122 :
123 1121 : public override void accept_children (CodeVisitor visitor) {
124 1121 : if (use_iterator) {
125 468 : base.accept_children (visitor);
126 468 : return;
127 : }
128 :
129 653 : collection.accept (visitor);
130 653 : visitor.visit_end_full_expression (collection);
131 :
132 653 : if (type_reference != null) {
133 653 : type_reference.accept (visitor);
134 : }
135 :
136 653 : body.accept (visitor);
137 : }
138 :
139 3 : public override void replace_expression (Expression old_node, Expression new_node) {
140 3 : if (collection == old_node) {
141 3 : collection = new_node;
142 : }
143 : }
144 :
145 496 : public override void replace_type (DataType old_type, DataType new_type) {
146 496 : if (type_reference == old_type) {
147 496 : type_reference = new_type;
148 : }
149 : }
150 :
151 653 : public override bool check (CodeContext context) {
152 653 : if (checked) {
153 0 : return !error;
154 : }
155 :
156 653 : checked = true;
157 :
158 653 : if (type_reference == null) {
159 0 : type_reference = new VarType ();
160 : }
161 :
162 : // analyze collection expression first, used for type inference
163 653 : if (!collection.check (context)) {
164 : // ignore inner error
165 0 : error = true;
166 0 : return false;
167 653 : } else if (collection.value_type == null) {
168 0 : Report.error (collection.source_reference, "invalid collection expression");
169 0 : error = true;
170 0 : return false;
171 : }
172 :
173 653 : var collection_type = collection.value_type.copy ();
174 653 : collection.target_type = collection_type.copy ();
175 :
176 653 : if (collection_type is ArrayType) {
177 122 : var array_type = (ArrayType) collection_type;
178 :
179 : // can't use inline-allocated array for temporary variable
180 122 : array_type.inline_allocated = false;
181 :
182 122 : return check_without_iterator (context, collection_type, array_type.element_type);
183 2509 : } else if (context.profile == Profile.GOBJECT && (collection_type.compatible (context.analyzer.glist_type)
184 500 : || collection_type.compatible (context.analyzer.gslist_type)
185 499 : || collection_type.compatible (context.analyzer.genericarray_type)
186 492 : || collection_type.compatible (context.analyzer.garray_type)
187 487 : || collection_type.compatible (context.analyzer.gsequence_type))) {
188 47 : if (collection_type.get_type_arguments ().size != 1) {
189 1 : error = true;
190 1 : Report.error (collection.source_reference, "missing type argument for collection");
191 1 : return false;
192 : }
193 :
194 46 : return check_without_iterator (context, collection_type, collection_type.get_type_arguments ().get (0));
195 484 : } else if (context.profile == Profile.GOBJECT && collection_type.compatible (context.analyzer.gvaluearray_type)) {
196 2 : return check_without_iterator (context, collection_type, context.analyzer.gvalue_type);
197 482 : } else if (context.profile == Profile.GOBJECT && collection_type.compatible (context.analyzer.string_type)) {
198 1 : return check_without_iterator (context, collection_type, context.analyzer.unichar_type);
199 : } else {
200 481 : return check_with_iterator (context, collection_type);
201 : }
202 : }
203 :
204 481 : bool check_with_index (CodeContext context, DataType collection_type) {
205 481 : var get_method = collection_type.get_member ("get") as Method;
206 481 : if (get_method == null) {
207 60 : return false;
208 : }
209 421 : unowned List<Parameter> parameters = get_method.get_parameters ();
210 421 : if (parameters.size != 1 || !(parameters[0].variable_type is IntegerType)) {
211 2 : return false;
212 : }
213 419 : var size_property = collection_type.get_member ("size") as Property;
214 419 : if (size_property == null) {
215 0 : return false;
216 : }
217 :
218 419 : var list_type = collection_type.copy ();
219 419 : if (collection.symbol_reference is Variable) {
220 160 : list_type.value_owned = false;
221 : }
222 419 : add_statement (new DeclarationStatement (new LocalVariable (list_type, "_%s_list".printf (variable_name), collection, source_reference), source_reference));
223 419 : 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));
224 419 : 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));
225 419 : var next = new UnaryExpression (UnaryOperator.INCREMENT, new MemberAccess.simple ("_%s_index".printf (variable_name), source_reference), source_reference);
226 419 : var conditional = new BinaryExpression (BinaryOperator.LESS_THAN, next, new MemberAccess.simple ("_%s_size".printf (variable_name), source_reference), source_reference);
227 419 : var loop = new WhileStatement (conditional, body, source_reference);
228 419 : add_statement (loop);
229 :
230 419 : var get_call = new MethodCall (new MemberAccess (new MemberAccess.simple ("_%s_list".printf (variable_name), source_reference), "get", source_reference), source_reference);
231 419 : get_call.add_argument (new MemberAccess.simple ("_%s_index".printf (variable_name), source_reference));
232 419 : body.insert_statement (0, new DeclarationStatement (new LocalVariable (type_reference, variable_name, get_call, source_reference), source_reference));
233 :
234 419 : checked = false;
235 419 : return base.check (context);
236 : }
237 :
238 481 : bool check_with_iterator (CodeContext context, DataType collection_type) {
239 481 : use_iterator = true;
240 :
241 481 : if (check_with_index (context, collection_type)) {
242 481 : return true;
243 : }
244 :
245 62 : var iterator_method = collection_type.get_member ("iterator") as Method;
246 1 : if (iterator_method == null) {
247 1 : Report.error (collection.source_reference, "`%s' does not have an `iterator' method", collection_type.to_string ());
248 1 : error = true;
249 1 : return false;
250 : }
251 61 : if (iterator_method.get_parameters ().size != 0) {
252 1 : Report.error (collection.source_reference, "`%s' must not have any parameters", iterator_method.get_full_name ());
253 1 : error = true;
254 1 : return false;
255 : }
256 60 : var iterator_type = iterator_method.return_type.get_actual_type (collection_type, null, this);
257 60 : if (iterator_type is VoidType) {
258 1 : Report.error (collection.source_reference, "`%s' must return an iterator", iterator_method.get_full_name ());
259 1 : error = true;
260 1 : return false;
261 : }
262 :
263 59 : var iterator_call = new MethodCall (new MemberAccess (collection, "iterator", source_reference), source_reference);
264 59 : add_statement (new DeclarationStatement (new LocalVariable (iterator_type, "_%s_it".printf (variable_name), iterator_call, source_reference), source_reference));
265 :
266 59 : var next_value_method = iterator_type.get_member ("next_value") as Method;
267 59 : var next_method = iterator_type.get_member ("next") as Method;
268 61 : if (next_value_method != null) {
269 4 : if (next_value_method.get_parameters ().size != 0) {
270 1 : Report.error (collection.source_reference, "`%s' must not have any parameters", next_value_method.get_full_name ());
271 1 : error = true;
272 1 : return false;
273 : }
274 3 : var element_type = next_value_method.return_type.get_actual_type (iterator_type, null, this);
275 3 : if (!element_type.nullable) {
276 1 : Report.error (collection.source_reference, "return type of `%s' must be nullable", next_value_method.get_full_name ());
277 1 : error = true;
278 1 : return false;
279 : }
280 :
281 2 : if (!analyze_element_type (element_type)) {
282 0 : return false;
283 : }
284 :
285 2 : add_statement (new DeclarationStatement (new LocalVariable (type_reference, variable_name, null, source_reference), source_reference));
286 :
287 2 : var next_value_call = new MethodCall (new MemberAccess (new MemberAccess.simple ("_%s_it".printf (variable_name), source_reference), "next_value", source_reference), source_reference);
288 2 : var assignment = new Assignment (new MemberAccess (null, variable_name, source_reference), next_value_call, AssignmentOperator.SIMPLE, source_reference);
289 2 : var conditional = new BinaryExpression (BinaryOperator.INEQUALITY, assignment, new NullLiteral (source_reference), source_reference);
290 2 : var loop = new WhileStatement (conditional, body, source_reference);
291 2 : add_statement (loop);
292 102 : } else if (next_method != null) {
293 54 : if (next_method.get_parameters ().size != 0) {
294 1 : Report.error (collection.source_reference, "`%s' must not have any parameters", next_method.get_full_name ());
295 1 : error = true;
296 1 : return false;
297 : }
298 53 : if (!next_method.return_type.compatible (context.analyzer.bool_type)) {
299 1 : Report.error (collection.source_reference, "`%s' must return a boolean value", next_method.get_full_name ());
300 1 : error = true;
301 1 : return false;
302 : }
303 52 : var get_method = iterator_type.get_member ("get") as Method;
304 1 : if (get_method == null) {
305 1 : Report.error (collection.source_reference, "`%s' does not have a `get' method", iterator_type.to_string ());
306 1 : error = true;
307 1 : return false;
308 : }
309 51 : if (get_method.get_parameters ().size != 0) {
310 1 : Report.error (collection.source_reference, "`%s' must not have any parameters", get_method.get_full_name ());
311 1 : error = true;
312 1 : return false;
313 : }
314 50 : var element_type = get_method.return_type.get_actual_type (iterator_type, null, this);
315 50 : if (element_type is VoidType) {
316 1 : Report.error (collection.source_reference, "`%s' must return an element", get_method.get_full_name ());
317 1 : error = true;
318 1 : return false;
319 : }
320 :
321 49 : if (!analyze_element_type (element_type)) {
322 2 : return false;
323 : }
324 :
325 47 : var next_call = new MethodCall (new MemberAccess (new MemberAccess.simple ("_%s_it".printf (variable_name), source_reference), "next", source_reference), source_reference);
326 47 : var loop = new WhileStatement (next_call, body, source_reference);
327 47 : add_statement (loop);
328 :
329 47 : var get_call = new MethodCall (new MemberAccess (new MemberAccess.simple ("_%s_it".printf (variable_name), source_reference), "get", source_reference), source_reference);
330 47 : body.insert_statement (0, new DeclarationStatement (new LocalVariable (type_reference, variable_name, get_call, source_reference), source_reference));
331 : } else {
332 1 : Report.error (collection.source_reference, "`%s' does not have a `next_value' or `next' method", iterator_type.to_string ());
333 1 : error = true;
334 1 : return false;
335 : }
336 :
337 49 : checked = false;
338 49 : return base.check (context);
339 : }
340 :
341 51 : bool analyze_element_type (DataType element_type) {
342 : // analyze element type
343 51 : if (type_reference is VarType) {
344 : // var type
345 14 : bool nullable = type_reference.nullable;
346 14 : bool value_owned = type_reference.value_owned;
347 14 : bool is_dynamic = type_reference.is_dynamic;
348 14 : type_reference = element_type.copy ();
349 : // FIXME Only follows "unowned var" otherwise inherit ownership of element-type
350 14 : if (!value_owned) {
351 0 : type_reference.value_owned = false;
352 : }
353 14 : if (nullable) {
354 0 : type_reference.nullable = true;
355 : }
356 14 : if (is_dynamic) {
357 0 : type_reference.is_dynamic = true;
358 : }
359 37 : } else if (!element_type.compatible (type_reference)) {
360 1 : error = true;
361 1 : Report.error (source_reference, "Foreach: Cannot convert from `%s' to `%s'", element_type.to_string (), type_reference.to_string ());
362 1 : return false;
363 36 : } else if (element_type.is_disposable () && element_type.value_owned && !type_reference.value_owned) {
364 1 : error = true;
365 1 : Report.error (source_reference, "Foreach: Invalid assignment from owned expression to unowned variable");
366 1 : return false;
367 : }
368 :
369 51 : return true;
370 : }
371 :
372 171 : bool check_without_iterator (CodeContext context, DataType collection_type, DataType element_type) {
373 : // analyze element type
374 171 : if (type_reference is VarType) {
375 : // var type
376 33 : bool nullable = type_reference.nullable;
377 33 : bool value_owned = type_reference.value_owned;
378 33 : bool is_dynamic = type_reference.is_dynamic;
379 33 : type_reference = element_type.copy ();
380 : // FIXME Only follows "unowned var" otherwise inherit ownership of element-type
381 33 : if (!value_owned) {
382 5 : type_reference.value_owned = false;
383 : }
384 33 : if (nullable) {
385 5 : type_reference.nullable = true;
386 : }
387 33 : if (is_dynamic) {
388 4 : type_reference.is_dynamic = true;
389 : }
390 138 : } else if (!element_type.compatible (type_reference)) {
391 4 : error = true;
392 4 : Report.error (source_reference, "Foreach: Cannot convert from `%s' to `%s'", element_type.to_string (), type_reference.to_string ());
393 4 : return false;
394 : }
395 :
396 167 : element_variable = new LocalVariable (type_reference, variable_name, null, source_reference);
397 :
398 167 : body.scope.add (variable_name, element_variable);
399 :
400 167 : body.add_local_variable (element_variable);
401 167 : element_variable.active = true;
402 167 : element_variable.checked = true;
403 :
404 : // analyze body
405 167 : var old_symbol = context.analyzer.current_symbol;
406 167 : owner = context.analyzer.current_symbol.scope;
407 167 : context.analyzer.current_symbol = this;
408 :
409 : // call add_local_variable to check for shadowed variable
410 167 : add_local_variable (element_variable);
411 167 : remove_local_variable (element_variable);
412 :
413 167 : body.check (context);
414 :
415 167 : foreach (LocalVariable local in get_local_variables ()) {
416 0 : local.active = false;
417 : }
418 :
419 167 : context.analyzer.current_symbol = old_symbol;
420 :
421 167 : collection_variable = new LocalVariable (collection_type.copy (), "%s_collection".printf (variable_name), null, source_reference);
422 :
423 167 : add_local_variable (collection_variable);
424 167 : collection_variable.active = true;
425 :
426 167 : return !error;
427 : }
428 :
429 814 : public override void get_error_types (Collection<DataType> collection, SourceReference? source_reference = null) {
430 814 : if (source_reference == null) {
431 771 : source_reference = this.source_reference;
432 : }
433 814 : this.collection.get_error_types (collection, source_reference);
434 814 : body.get_error_types (collection, source_reference);
435 : }
436 :
437 635 : public override void emit (CodeGenerator codegen) {
438 635 : if (use_iterator) {
439 468 : base.emit (codegen);
440 468 : return;
441 : }
442 :
443 167 : collection.emit (codegen);
444 167 : codegen.visit_end_full_expression (collection);
445 :
446 167 : element_variable.active = true;
447 167 : collection_variable.active = true;
448 167 : if (iterator_variable != null) {
449 0 : iterator_variable.active = true;
450 : }
451 :
452 167 : codegen.visit_foreach_statement (this);
453 : }
454 :
455 501 : public override void get_defined_variables (Collection<Variable> collection) {
456 501 : if (element_variable != null) {
457 501 : collection.add (element_variable);
458 : }
459 : }
460 : }
|