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 element type
99 * @param id element variable name
100 * @param col loop body
101 * @param source reference to source code
102 * @return newly created foreach statement
104 public ForeachStatement (DataType? type_reference
, string variable_name
, Expression collection
, Block body
, SourceReference source_reference
) {
105 base (source_reference
);
106 this
.variable_name
= variable_name
;
107 this
.collection
= collection
;
109 this
.type_reference
= type_reference
;
112 public override void accept (CodeVisitor visitor
) {
114 base.accept (visitor
);
118 visitor
.visit_foreach_statement (this
);
121 public override void accept_children (CodeVisitor visitor
) {
123 base.accept_children (visitor
);
127 collection
.accept (visitor
);
128 visitor
.visit_end_full_expression (collection
);
130 if (type_reference
!= null) {
131 type_reference
.accept (visitor
);
134 body
.accept (visitor
);
137 public override void replace_expression (Expression old_node
, Expression new_node
) {
138 if (collection
== old_node
) {
139 collection
= new_node
;
143 public override void replace_type (DataType old_type
, DataType new_type
) {
144 if (type_reference
== old_type
) {
145 type_reference
= new_type
;
149 public override bool check (CodeContext context
) {
156 // analyze collection expression first, used for type inference
157 if (!collection
.check (context
)) {
158 // ignore inner error
161 } else if (collection
.value_type
== null) {
162 Report
.error (collection
.source_reference
, "invalid collection expression");
167 var collection_type
= collection
.value_type
.copy ();
168 collection
.target_type
= collection_type
.copy ();
170 if (context
.profile
!= Profile
.DOVA
&& collection_type
.is_array ()) {
171 var array_type
= (ArrayType
) collection_type
;
173 // can't use inline-allocated array for temporary variable
174 array_type
.inline_allocated
= false;
176 return check_without_iterator (context
, collection_type
, array_type
.element_type
);
177 } else if (context
.profile
== Profile
.GOBJECT
&& (collection_type
.compatible (context
.analyzer
.glist_type
) || collection_type
.compatible (context
.analyzer
.gslist_type
))) {
178 if (collection_type
.get_type_arguments ().size
!= 1) {
180 Report
.error (collection
.source_reference
, "missing type argument for collection");
184 return check_without_iterator (context
, collection_type
, collection_type
.get_type_arguments ().get (0));
185 } else if (context
.profile
== Profile
.GOBJECT
&& collection_type
.compatible (context
.analyzer
.gvaluearray_type
)) {
186 return check_without_iterator (context
, collection_type
, context
.analyzer
.gvalue_type
);
188 return check_with_iterator (context
, collection_type
);
192 bool check_with_index (CodeContext context
, DataType collection_type
) {
193 var get_method
= collection_type
.get_member ("get") as Method
;
194 if (get_method
== null) {
197 if (get_method
.get_parameters ().size
!= 1) {
200 var size_property
= collection_type
.get_member ("size") as Property
;
201 if (size_property
== null) {
205 add_statement (new
DeclarationStatement (new
LocalVariable (null, "_%s_list".printf (variable_name
), collection
, source_reference
), source_reference
));
206 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
));
207 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
));
208 var next
= new
UnaryExpression (UnaryOperator
.INCREMENT
, new MemberAccess
.simple ("_%s_index".printf (variable_name
), source_reference
), source_reference
);
209 var conditional
= new
BinaryExpression (BinaryOperator
.LESS_THAN
, next
, new MemberAccess
.simple ("_%s_size".printf (variable_name
), source_reference
), source_reference
);
210 var loop
= new
WhileStatement (conditional
, body
, source_reference
);
211 add_statement (loop
);
213 var get_call
= new
MethodCall (new
MemberAccess (new MemberAccess
.simple ("_%s_list".printf (variable_name
), source_reference
), "get", source_reference
), source_reference
);
214 get_call
.add_argument (new MemberAccess
.simple ("_%s_index".printf (variable_name
), source_reference
));
215 body
.insert_statement (0, new
DeclarationStatement (new
LocalVariable (type_reference
, variable_name
, get_call
, source_reference
), source_reference
));
218 return base.check (context
);
221 bool check_with_iterator (CodeContext context
, DataType collection_type
) {
224 if (check_with_index (context
, collection_type
)) {
228 var iterator_method
= collection_type
.get_member ("iterator") as Method
;
229 if (iterator_method
== null) {
230 Report
.error (collection
.source_reference
, "`%s' does not have an `iterator' method".printf (collection_type
.to_string ()));
234 if (iterator_method
.get_parameters ().size
!= 0) {
235 Report
.error (collection
.source_reference
, "`%s' must not have any parameters".printf (iterator_method
.get_full_name ()));
239 var iterator_type
= iterator_method
.return_type
.get_actual_type (collection_type
, null, this
);
240 if (iterator_type is VoidType
) {
241 Report
.error (collection
.source_reference
, "`%s' must return an iterator".printf (iterator_method
.get_full_name ()));
246 var iterator_call
= new
MethodCall (new
MemberAccess (collection
, "iterator", source_reference
), source_reference
);
247 add_statement (new
DeclarationStatement (new
LocalVariable (iterator_type
, "_%s_it".printf (variable_name
), iterator_call
, source_reference
), source_reference
));
249 var next_value_method
= iterator_type
.get_member ("next_value") as Method
;
250 var next_method
= iterator_type
.get_member ("next") as Method
;
251 if (next_value_method
!= null) {
252 if (next_value_method
.get_parameters ().size
!= 0) {
253 Report
.error (collection
.source_reference
, "`%s' must not have any parameters".printf (next_value_method
.get_full_name ()));
257 var element_type
= next_value_method
.return_type
.get_actual_type (iterator_type
, null, this
);
258 if (!element_type
.nullable
) {
259 Report
.error (collection
.source_reference
, "return type of `%s' must be nullable".printf (next_value_method
.get_full_name ()));
264 if (!analyze_element_type (element_type
)) {
268 add_statement (new
DeclarationStatement (new
LocalVariable (type_reference
, variable_name
, null, source_reference
), source_reference
));
270 var next_value_call
= new
MethodCall (new
MemberAccess (new MemberAccess
.simple ("_%s_it".printf (variable_name
), source_reference
), "next_value", source_reference
), source_reference
);
271 var assignment
= new
Assignment (new
MemberAccess (null, variable_name
, source_reference
), next_value_call
, AssignmentOperator
.SIMPLE
, source_reference
);
272 var conditional
= new
BinaryExpression (BinaryOperator
.INEQUALITY
, assignment
, new
NullLiteral (source_reference
), source_reference
);
273 var loop
= new
WhileStatement (conditional
, body
, source_reference
);
274 add_statement (loop
);
275 } else if (next_method
!= null) {
276 if (next_method
.get_parameters ().size
!= 0) {
277 Report
.error (collection
.source_reference
, "`%s' must not have any parameters".printf (next_method
.get_full_name ()));
281 if (!next_method
.return_type
.compatible (context
.analyzer
.bool_type
)) {
282 Report
.error (collection
.source_reference
, "`%s' must return a boolean value".printf (next_method
.get_full_name ()));
286 var get_method
= iterator_type
.get_member ("get") as Method
;
287 if (get_method
== null) {
288 Report
.error (collection
.source_reference
, "`%s' does not have a `get' method".printf (iterator_type
.to_string ()));
292 if (get_method
.get_parameters ().size
!= 0) {
293 Report
.error (collection
.source_reference
, "`%s' must not have any parameters".printf (get_method
.get_full_name ()));
297 var element_type
= get_method
.return_type
.get_actual_type (iterator_type
, null, this
);
298 if (element_type is VoidType
) {
299 Report
.error (collection
.source_reference
, "`%s' must return an element".printf (get_method
.get_full_name ()));
304 if (!analyze_element_type (element_type
)) {
308 var next_call
= new
MethodCall (new
MemberAccess (new MemberAccess
.simple ("_%s_it".printf (variable_name
), source_reference
), "next", source_reference
), source_reference
);
309 var loop
= new
WhileStatement (next_call
, body
, source_reference
);
310 add_statement (loop
);
312 var get_call
= new
MethodCall (new
MemberAccess (new MemberAccess
.simple ("_%s_it".printf (variable_name
), source_reference
), "get", source_reference
), source_reference
);
313 body
.insert_statement (0, new
DeclarationStatement (new
LocalVariable (type_reference
, variable_name
, get_call
, source_reference
), source_reference
));
315 Report
.error (collection
.source_reference
, "`%s' does not have a `next_value' or `next' method".printf (iterator_type
.to_string ()));
321 return base.check (context
);
324 bool analyze_element_type (DataType element_type
) {
325 // analyze element type
326 if (type_reference
== null) {
328 type_reference
= element_type
.copy ();
329 } else if (!element_type
.compatible (type_reference
)) {
331 Report
.error (source_reference
, "Foreach: Cannot convert from `%s' to `%s'".printf (element_type
.to_string (), type_reference
.to_string ()));
333 } else if (element_type
.is_disposable () && element_type
.value_owned
&& !type_reference
.value_owned
) {
335 Report
.error (source_reference
, "Foreach: Invalid assignment from owned expression to unowned variable");
342 bool check_without_iterator (CodeContext context
, DataType collection_type
, DataType element_type
) {
343 // analyze element type
344 if (type_reference
== null) {
346 type_reference
= element_type
.copy ();
347 } else if (!element_type
.compatible (type_reference
)) {
349 Report
.error (source_reference
, "Foreach: Cannot convert from `%s' to `%s'".printf (element_type
.to_string (), type_reference
.to_string ()));
353 element_variable
= new
LocalVariable (type_reference
, variable_name
, null, source_reference
);
355 body
.scope
.add (variable_name
, element_variable
);
357 body
.add_local_variable (element_variable
);
358 element_variable
.active
= true;
359 element_variable
.checked
= true;
362 owner
= context
.analyzer
.current_symbol
.scope
;
363 context
.analyzer
.current_symbol
= this
;
365 // call add_local_variable to check for shadowed variable
366 add_local_variable (element_variable
);
367 remove_local_variable (element_variable
);
369 body
.check (context
);
371 foreach (LocalVariable local
in get_local_variables ()) {
372 local
.active
= false;
375 context
.analyzer
.current_symbol
= context
.analyzer
.current_symbol
.parent_symbol
;
377 collection_variable
= new
LocalVariable (collection_type
, "%s_collection".printf (variable_name
));
379 add_local_variable (collection_variable
);
380 collection_variable
.active
= true;
382 add_error_types (collection
.get_error_types ());
383 add_error_types (body
.get_error_types ());
388 public override void emit (CodeGenerator codegen
) {
394 collection
.emit (codegen
);
395 codegen
.visit_end_full_expression (collection
);
397 element_variable
.active
= true;
398 collection_variable
.active
= true;
399 if (iterator_variable
!= null) {
400 iterator_variable
.active
= true;
403 codegen
.visit_foreach_statement (this
);
406 public override void get_defined_variables (Collection
<LocalVariable
> collection
) {
407 collection
.add (element_variable
);