1 /* valaforeachstatement.vala
3 * Copyright (C) 2006-2010 Jürg Billeter
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.
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.
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
20 * Jürg Billeter <j@bitron.ch>
25 * Represents a foreach statement in the source code. Foreach statements iterate
26 * over the elements of a collection.
28 public class Vala
.ForeachStatement
: Block
{
30 * Specifies the element type.
32 public DataType? type_reference
{
33 get { return _data_type
; }
36 if (_data_type
!= null) {
37 _data_type
.parent_node
= this
;
43 * Specifies the element variable name.
45 public string variable_name
{ get; set; }
48 * Specifies the container.
50 public Expression collection
{
56 _collection
.parent_node
= this
;
61 * Specifies the loop body.
69 _body
.parent_node
= this
;
73 public bool use_iterator
{ get; private set; }
76 * Specifies the declarator for the generated element variable.
78 public LocalVariable element_variable
{ get; set; }
81 * Specifies the declarator for the generated collection variable.
83 public LocalVariable collection_variable
{ get; set; }
86 * Specifies the declarator for the generated iterator variable.
88 public LocalVariable iterator_variable
{ get; set; }
90 private Expression _collection
;
93 private DataType _data_type
;
96 * Creates a new foreach statement.
98 * @param type_reference element type
99 * @param variable_name element variable name
100 * @param collection container
101 * @param body loop body
102 * @param source_reference reference to source code
103 * @return newly created foreach statement
105 public ForeachStatement (DataType? type_reference
, string variable_name
, Expression collection
, Block body
, SourceReference source_reference
) {
106 base (source_reference
);
107 this
.variable_name
= variable_name
;
108 this
.collection
= collection
;
110 this
.type_reference
= type_reference
;
113 public override void accept (CodeVisitor visitor
) {
115 base.accept (visitor
);
119 visitor
.visit_foreach_statement (this
);
122 public override void accept_children (CodeVisitor visitor
) {
124 base.accept_children (visitor
);
128 collection
.accept (visitor
);
129 visitor
.visit_end_full_expression (collection
);
131 if (type_reference
!= null) {
132 type_reference
.accept (visitor
);
135 body
.accept (visitor
);
138 public override void replace_expression (Expression old_node
, Expression new_node
) {
139 if (collection
== old_node
) {
140 collection
= new_node
;
144 public override void replace_type (DataType old_type
, DataType new_type
) {
145 if (type_reference
== old_type
) {
146 type_reference
= new_type
;
150 public override bool check (CodeContext context
) {
157 // analyze collection expression first, used for type inference
158 if (!collection
.check (context
)) {
159 // ignore inner error
162 } else if (collection
.value_type
== null) {
163 Report
.error (collection
.source_reference
, "invalid collection expression");
168 var collection_type
= collection
.value_type
.copy ();
169 collection
.target_type
= collection_type
.copy ();
171 if (collection_type
.is_array ()) {
172 var array_type
= (ArrayType
) collection_type
;
174 // can't use inline-allocated array for temporary variable
175 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 if (collection_type
.get_type_arguments ().size
!= 1) {
181 Report
.error (collection
.source_reference
, "missing type argument for collection");
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
);
189 return check_with_iterator (context
, collection_type
);
193 bool check_with_index (CodeContext context
, DataType collection_type
) {
194 var get_method
= collection_type
.get_member ("get") as Method
;
195 if (get_method
== null) {
198 if (get_method
.get_parameters ().size
!= 1) {
201 var size_property
= collection_type
.get_member ("size") as Property
;
202 if (size_property
== null) {
206 add_statement (new
DeclarationStatement (new
LocalVariable (null, "_%s_list".printf (variable_name
), collection
, source_reference
), source_reference
));
207 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
));
208 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
));
209 var next
= new
UnaryExpression (UnaryOperator
.INCREMENT
, new MemberAccess
.simple ("_%s_index".printf (variable_name
), source_reference
), source_reference
);
210 var conditional
= new
BinaryExpression (BinaryOperator
.LESS_THAN
, next
, new MemberAccess
.simple ("_%s_size".printf (variable_name
), source_reference
), source_reference
);
211 var loop
= new
WhileStatement (conditional
, body
, source_reference
);
212 add_statement (loop
);
214 var get_call
= new
MethodCall (new
MemberAccess (new MemberAccess
.simple ("_%s_list".printf (variable_name
), source_reference
), "get", source_reference
), source_reference
);
215 get_call
.add_argument (new MemberAccess
.simple ("_%s_index".printf (variable_name
), source_reference
));
216 body
.insert_statement (0, new
DeclarationStatement (new
LocalVariable (type_reference
, variable_name
, get_call
, source_reference
), source_reference
));
219 return base.check (context
);
222 bool check_with_iterator (CodeContext context
, DataType collection_type
) {
225 if (check_with_index (context
, collection_type
)) {
229 var iterator_method
= collection_type
.get_member ("iterator") as Method
;
230 if (iterator_method
== null) {
231 Report
.error (collection
.source_reference
, "`%s' does not have an `iterator' method".printf (collection_type
.to_string ()));
235 if (iterator_method
.get_parameters ().size
!= 0) {
236 Report
.error (collection
.source_reference
, "`%s' must not have any parameters".printf (iterator_method
.get_full_name ()));
240 var iterator_type
= iterator_method
.return_type
.get_actual_type (collection_type
, null, this
);
241 if (iterator_type is VoidType
) {
242 Report
.error (collection
.source_reference
, "`%s' must return an iterator".printf (iterator_method
.get_full_name ()));
247 var iterator_call
= new
MethodCall (new
MemberAccess (collection
, "iterator", source_reference
), source_reference
);
248 add_statement (new
DeclarationStatement (new
LocalVariable (iterator_type
, "_%s_it".printf (variable_name
), iterator_call
, source_reference
), source_reference
));
250 var next_value_method
= iterator_type
.get_member ("next_value") as Method
;
251 var next_method
= iterator_type
.get_member ("next") as Method
;
252 if (next_value_method
!= null) {
253 if (next_value_method
.get_parameters ().size
!= 0) {
254 Report
.error (collection
.source_reference
, "`%s' must not have any parameters".printf (next_value_method
.get_full_name ()));
258 var element_type
= next_value_method
.return_type
.get_actual_type (iterator_type
, null, this
);
259 if (!element_type
.nullable
) {
260 Report
.error (collection
.source_reference
, "return type of `%s' must be nullable".printf (next_value_method
.get_full_name ()));
265 if (!analyze_element_type (element_type
)) {
269 add_statement (new
DeclarationStatement (new
LocalVariable (type_reference
, variable_name
, null, source_reference
), source_reference
));
271 var next_value_call
= new
MethodCall (new
MemberAccess (new MemberAccess
.simple ("_%s_it".printf (variable_name
), source_reference
), "next_value", source_reference
), source_reference
);
272 var assignment
= new
Assignment (new
MemberAccess (null, variable_name
, source_reference
), next_value_call
, AssignmentOperator
.SIMPLE
, source_reference
);
273 var conditional
= new
BinaryExpression (BinaryOperator
.INEQUALITY
, assignment
, new
NullLiteral (source_reference
), source_reference
);
274 var loop
= new
WhileStatement (conditional
, body
, source_reference
);
275 add_statement (loop
);
276 } else if (next_method
!= null) {
277 if (next_method
.get_parameters ().size
!= 0) {
278 Report
.error (collection
.source_reference
, "`%s' must not have any parameters".printf (next_method
.get_full_name ()));
282 if (!next_method
.return_type
.compatible (context
.analyzer
.bool_type
)) {
283 Report
.error (collection
.source_reference
, "`%s' must return a boolean value".printf (next_method
.get_full_name ()));
287 var get_method
= iterator_type
.get_member ("get") as Method
;
288 if (get_method
== null) {
289 Report
.error (collection
.source_reference
, "`%s' does not have a `get' method".printf (iterator_type
.to_string ()));
293 if (get_method
.get_parameters ().size
!= 0) {
294 Report
.error (collection
.source_reference
, "`%s' must not have any parameters".printf (get_method
.get_full_name ()));
298 var element_type
= get_method
.return_type
.get_actual_type (iterator_type
, null, this
);
299 if (element_type is VoidType
) {
300 Report
.error (collection
.source_reference
, "`%s' must return an element".printf (get_method
.get_full_name ()));
305 if (!analyze_element_type (element_type
)) {
309 var next_call
= new
MethodCall (new
MemberAccess (new MemberAccess
.simple ("_%s_it".printf (variable_name
), source_reference
), "next", source_reference
), source_reference
);
310 var loop
= new
WhileStatement (next_call
, body
, source_reference
);
311 add_statement (loop
);
313 var get_call
= new
MethodCall (new
MemberAccess (new MemberAccess
.simple ("_%s_it".printf (variable_name
), source_reference
), "get", source_reference
), source_reference
);
314 body
.insert_statement (0, new
DeclarationStatement (new
LocalVariable (type_reference
, variable_name
, get_call
, source_reference
), source_reference
));
316 Report
.error (collection
.source_reference
, "`%s' does not have a `next_value' or `next' method".printf (iterator_type
.to_string ()));
322 return base.check (context
);
325 bool analyze_element_type (DataType element_type
) {
326 // analyze element type
327 if (type_reference
== null) {
329 type_reference
= element_type
.copy ();
330 } else if (!element_type
.compatible (type_reference
)) {
332 Report
.error (source_reference
, "Foreach: Cannot convert from `%s' to `%s'".printf (element_type
.to_string (), type_reference
.to_string ()));
334 } else if (element_type
.is_disposable () && element_type
.value_owned
&& !type_reference
.value_owned
) {
336 Report
.error (source_reference
, "Foreach: Invalid assignment from owned expression to unowned variable");
343 bool check_without_iterator (CodeContext context
, DataType collection_type
, DataType element_type
) {
344 // analyze element type
345 if (type_reference
== null) {
347 type_reference
= element_type
.copy ();
348 } else if (!element_type
.compatible (type_reference
)) {
350 Report
.error (source_reference
, "Foreach: Cannot convert from `%s' to `%s'".printf (element_type
.to_string (), type_reference
.to_string ()));
354 element_variable
= new
LocalVariable (type_reference
, variable_name
, null, source_reference
);
356 body
.scope
.add (variable_name
, element_variable
);
358 body
.add_local_variable (element_variable
);
359 element_variable
.active
= true;
360 element_variable
.checked
= true;
363 owner
= context
.analyzer
.current_symbol
.scope
;
364 context
.analyzer
.current_symbol
= this
;
366 // call add_local_variable to check for shadowed variable
367 add_local_variable (element_variable
);
368 remove_local_variable (element_variable
);
370 body
.check (context
);
372 foreach (LocalVariable local
in get_local_variables ()) {
373 local
.active
= false;
376 context
.analyzer
.current_symbol
= context
.analyzer
.current_symbol
.parent_symbol
;
378 collection_variable
= new
LocalVariable (collection_type
.copy (), "%s_collection".printf (variable_name
));
380 add_local_variable (collection_variable
);
381 collection_variable
.active
= true;
383 add_error_types (collection
.get_error_types ());
384 add_error_types (body
.get_error_types ());
389 public override void emit (CodeGenerator codegen
) {
395 collection
.emit (codegen
);
396 codegen
.visit_end_full_expression (collection
);
398 element_variable
.active
= true;
399 collection_variable
.active
= true;
400 if (iterator_variable
!= null) {
401 iterator_variable
.active
= true;
404 codegen
.visit_foreach_statement (this
);
407 public override void get_defined_variables (Collection
<Variable
> collection
) {
408 collection
.add (element_variable
);