3 * Copyright (C) 2006-2011 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 an assignment expression in the source code.
27 * Supports =, |=, &=, ^=, +=, -=, *=, /=, %=, <<=, >>=.
29 public class Vala
.Assignment
: Expression
{
31 * Left hand side of the assignment.
33 public Expression left
{
37 _left
.parent_node
= this
;
42 * Assignment operator.
44 public AssignmentOperator operator
{ get; set; }
47 * Right hand side of the assignment.
49 public Expression right
{
50 get { return _right
; }
53 _right
.parent_node
= this
;
57 private Expression _left
;
58 private Expression _right
;
61 * Creates a new assignment.
63 * @param left left hand side
64 * @param operator assignment operator
65 * @param right right hand side
66 * @param source_reference reference to source code
67 * @return newly created assignment
69 public Assignment (Expression left
, Expression right
, AssignmentOperator operator
= AssignmentOperator
.SIMPLE
, SourceReference? source_reference
= null) {
71 this
.operator
= operator
;
72 this
.source_reference
= source_reference
;
76 public override void accept (CodeVisitor visitor
) {
77 visitor
.visit_assignment (this
);
79 visitor
.visit_expression (this
);
82 public override void accept_children (CodeVisitor visitor
) {
83 left
.accept (visitor
);
84 right
.accept (visitor
);
87 public override void replace_expression (Expression old_node
, Expression new_node
) {
88 if (left
== old_node
) {
91 if (right
== old_node
) {
96 public override bool is_pure () {
100 public override bool check (CodeContext context
) {
107 if (left is Tuple
&& operator
== AssignmentOperator
.SIMPLE
&& parent_node is ExpressionStatement
) {
108 var tuple
= (Tuple
) left
;
110 var local
= new
LocalVariable (null, get_temp_name (), right
, right
.source_reference
);
111 var decl
= new
DeclarationStatement (local
, source_reference
);
112 decl
.check (context
);
113 insert_statement (context
.analyzer
.insert_block
, decl
);
116 ExpressionStatement stmt
= null;
117 foreach (var expr
in tuple
.get_expressions ()) {
119 stmt
.check (context
);
120 insert_statement (context
.analyzer
.insert_block
, stmt
);
123 var temp_access
= new MemberAccess
.simple (local
.name
, right
.source_reference
);
124 var ea
= new
ElementAccess (temp_access
, expr
.source_reference
);
125 ea
.append_index (new
IntegerLiteral (i
.to_string (), expr
.source_reference
));
126 var assign
= new
Assignment (expr
, ea
, operator
, expr
.source_reference
);
127 stmt
= new
ExpressionStatement (assign
, expr
.source_reference
);
132 context
.analyzer
.replaced_nodes
.add (this
);
133 parent_node
.replace_expression (this
, stmt
.expression
);
134 return stmt
.expression
.check (context
);
139 if (!left
.check (context
)) {
140 // skip on error in inner expression
145 if (left is MemberAccess
) {
146 var ma
= (MemberAccess
) left
;
148 if ((!(ma
.symbol_reference is Signal
|| ma
.symbol_reference is DynamicProperty
) && ma
.value_type
== null) ||
149 (ma
.inner
== null && ma
.member_name
== "this" && context
.analyzer
.is_in_instance_method ())) {
151 Report
.error (source_reference
, "unsupported lvalue in assignment");
154 if (ma
.prototype_access
) {
156 Report
.error (source_reference
, "Access to instance member `%s' denied".printf (ma
.symbol_reference
.get_full_name ()));
160 if (ma
.error
|| ma
.symbol_reference
== null) {
162 /* if no symbol found, skip this check */
166 if (ma
.symbol_reference is DynamicSignal
) {
167 // target_type not available for dynamic signals
168 if (!context
.deprecated
) {
169 Report
.warning (source_reference
, "deprecated syntax, use `connect' method instead");
171 } else if (ma
.symbol_reference is Signal
) {
172 if (!context
.deprecated
) {
173 Report
.warning (source_reference
, "deprecated syntax, use `connect' method instead");
175 var sig
= (Signal
) ma
.symbol_reference
;
176 right
.target_type
= new
DelegateType (sig
.get_delegate (ma
.inner
.value_type
, this
));
178 right
.formal_target_type
= ma
.formal_value_type
;
179 right
.target_type
= ma
.value_type
;
181 } else if (left is ElementAccess
) {
182 var ea
= (ElementAccess
) left
;
184 if (ea
.container
.value_type
.data_type
== context
.analyzer
.string_type
.data_type
) {
186 Report
.error (ea
.source_reference
, "strings are immutable");
188 } else if (ea
.container is MemberAccess
&& ea
.container
.symbol_reference is Signal
) {
189 var ma
= (MemberAccess
) ea
.container
;
190 var sig
= (Signal
) ea
.container
.symbol_reference
;
191 right
.target_type
= new
DelegateType (sig
.get_delegate (ma
.inner
.value_type
, this
));
192 } else if (ea
.container
.value_type
.get_member ("set") is Method
) {
193 var set_call
= new
MethodCall (new
MemberAccess (ea
.container
, "set", source_reference
), source_reference
);
194 foreach (Expression e
in ea
.get_indices ()) {
195 set_call
.add_argument (e
);
197 set_call
.add_argument (right
);
198 parent_node
.replace_expression (this
, set_call
);
199 return set_call
.check (context
);
201 right
.target_type
= left
.value_type
;
203 } else if (left is PointerIndirection
) {
204 right
.target_type
= left
.value_type
;
207 Report
.error (source_reference
, "unsupported lvalue in assignment");
211 if (!right
.check (context
)) {
212 // skip on error in inner expression
217 if (operator
!= AssignmentOperator
.SIMPLE
&& left is MemberAccess
) {
218 // transform into simple assignment
219 // FIXME: only do this if the backend doesn't support
220 // the assignment natively
222 var ma
= (MemberAccess
) left
;
224 if (!(ma
.symbol_reference is Signal
)) {
225 var old_value
= new
MemberAccess (ma
.inner
, ma
.member_name
);
227 var bin
= new
BinaryExpression (BinaryOperator
.PLUS
, old_value
, right
, source_reference
);
228 bin
.target_type
= right
.target_type
;
229 right
.target_type
= right
.target_type
.copy ();
230 right
.target_type
.value_owned
= false;
232 if (operator
== AssignmentOperator
.BITWISE_OR
) {
233 bin
.operator
= BinaryOperator
.BITWISE_OR
;
234 } else if (operator
== AssignmentOperator
.BITWISE_AND
) {
235 bin
.operator
= BinaryOperator
.BITWISE_AND
;
236 } else if (operator
== AssignmentOperator
.BITWISE_XOR
) {
237 bin
.operator
= BinaryOperator
.BITWISE_XOR
;
238 } else if (operator
== AssignmentOperator
.ADD
) {
239 bin
.operator
= BinaryOperator
.PLUS
;
240 } else if (operator
== AssignmentOperator
.SUB
) {
241 bin
.operator
= BinaryOperator
.MINUS
;
242 } else if (operator
== AssignmentOperator
.MUL
) {
243 bin
.operator
= BinaryOperator
.MUL
;
244 } else if (operator
== AssignmentOperator
.DIV
) {
245 bin
.operator
= BinaryOperator
.DIV
;
246 } else if (operator
== AssignmentOperator
.PERCENT
) {
247 bin
.operator
= BinaryOperator
.MOD
;
248 } else if (operator
== AssignmentOperator
.SHIFT_LEFT
) {
249 bin
.operator
= BinaryOperator
.SHIFT_LEFT
;
250 } else if (operator
== AssignmentOperator
.SHIFT_RIGHT
) {
251 bin
.operator
= BinaryOperator
.SHIFT_RIGHT
;
255 right
.check (context
);
257 operator
= AssignmentOperator
.SIMPLE
;
261 if (left
.symbol_reference is Signal
) {
262 var sig
= (Signal
) left
.symbol_reference
;
264 var m
= right
.symbol_reference as Method
;
268 Report
.error (right
.source_reference
, "unsupported expression for signal handler");
272 var dynamic_sig
= sig as DynamicSignal
;
273 var right_ma
= right as MemberAccess
;
274 if (dynamic_sig
!= null) {
276 foreach (Parameter param
in dynamic_sig
.handler
.value_type
.get_parameters ()) {
278 // skip sender parameter
281 dynamic_sig
.add_parameter (param
.copy ());
284 right
.target_type
= new
DelegateType (sig
.get_delegate (new
ObjectType ((ObjectTypeSymbol
) sig
.parent_symbol
), this
));
285 } else if (!right
.value_type
.compatible (right
.target_type
)) {
286 var delegate_type
= (DelegateType
) right
.target_type
;
289 Report
.error (right
.source_reference
, "method `%s' is incompatible with signal `%s', expected `%s'".printf (right
.value_type
.to_string (), right
.target_type
.to_string (), delegate_type
.delegate_symbol
.get_prototype_string (m
.name
)));
291 } else if (right_ma
!= null && right_ma
.prototype_access
) {
293 Report
.error (right
.source_reference
, "Access to instance member `%s' denied".printf (m
.get_full_name ()));
296 } else if (left is MemberAccess
) {
297 var ma
= (MemberAccess
) left
;
299 if (ma
.symbol_reference is Property
) {
300 var prop
= (Property
) ma
.symbol_reference
;
302 var dynamic_prop
= prop as DynamicProperty
;
303 if (dynamic_prop
!= null) {
304 dynamic_prop
.property_type
= right
.value_type
.copy ();
305 left
.value_type
= dynamic_prop
.property_type
.copy ();
308 if (prop
.set_accessor
== null
309 || (!prop
.set_accessor
.writable
&& !(context
.analyzer
.find_current_method () is CreationMethod
|| context
.analyzer
.is_in_constructor ()))) {
311 Report
.error (ma
.source_reference
, "Property `%s' is read-only".printf (prop
.get_full_name ()));
313 } else if (!context
.deprecated
314 && !prop
.set_accessor
.writable
315 && context
.analyzer
.find_current_method () is CreationMethod
) {
316 if (ma
.inner
.symbol_reference
!= context
.analyzer
.find_current_method ().this_parameter
) {
317 // trying to set construct-only property in creation method for foreign instance
318 Report
.error (ma
.source_reference
, "Property `%s' is read-only".printf (prop
.get_full_name ()));
321 Report
.error (ma
.source_reference
, "Cannot assign to construct-only properties, use Object (property: value) constructor chain up");
325 } else if (ma
.symbol_reference is Variable
&& right
.value_type
== null) {
326 var variable
= (Variable
) ma
.symbol_reference
;
328 if (right
.symbol_reference is Method
&&
329 variable
.variable_type is DelegateType
) {
330 var m
= (Method
) right
.symbol_reference
;
331 var dt
= (DelegateType
) variable
.variable_type
;
332 var cb
= dt
.delegate_symbol
;
334 /* check whether method matches callback type */
335 if (!cb
.matches_method (m
, dt
)) {
337 Report
.error (source_reference
, "declaration of method `%s' doesn't match declaration of callback `%s'".printf (m
.get_full_name (), cb
.get_full_name ()));
341 right
.value_type
= variable
.variable_type
;
344 Report
.error (source_reference
, "Assignment: Invalid assignment attempt");
349 if (left
.value_type
!= null && right
.value_type
!= null) {
350 /* if there was an error on either side,
351 * i.e. {left|right}.value_type == null, skip type check */
353 if (!right
.value_type
.compatible (left
.value_type
)) {
355 Report
.error (source_reference
, "Assignment: Cannot convert from `%s' to `%s'".printf (right
.value_type
.to_string (), left
.value_type
.to_string ()));
359 if (!(ma
.symbol_reference is Property
)) {
360 if (right
.value_type
.is_disposable ()) {
361 /* rhs transfers ownership of the expression */
362 if (!(left
.value_type is PointerType
) && !left
.value_type
.value_owned
) {
363 /* lhs doesn't own the value */
365 Report
.error (source_reference
, "Invalid assignment from owned expression to unowned variable");
367 } else if (left
.value_type
.value_owned
) {
368 /* lhs wants to own the value
369 * rhs doesn't transfer the ownership
370 * code generator needs to add reference
376 var right_ma
= right as MemberAccess
;
377 if (right_ma
!= null && ma
.symbol_reference
== right_ma
.symbol_reference
) {
378 if (ma
.symbol_reference is LocalVariable
|| ma
.symbol_reference is Parameter
) {
379 Report
.warning (source_reference
, "Assignment to same variable");
380 } else if (ma
.symbol_reference is Field
) {
381 var f
= (Field
) ma
.symbol_reference
;
382 if (f
.binding
== MemberBinding
.STATIC
) {
383 Report
.warning (source_reference
, "Assignment to same variable");
385 var ma_inner
= ma
.inner as MemberAccess
;
386 var right_ma_inner
= right_ma
.inner as MemberAccess
;
387 if (ma_inner
!= null && ma_inner
.member_name
== "this" && ma_inner
.inner
== null &&
388 right_ma_inner
!= null && right_ma_inner
.member_name
== "this" && right_ma_inner
.inner
== null) {
389 Report
.warning (source_reference
, "Assignment to same variable");
394 } else if (left is ElementAccess
) {
395 var ea
= (ElementAccess
) left
;
397 if (!right
.value_type
.compatible (left
.value_type
)) {
399 Report
.error (source_reference
, "Assignment: Cannot convert from `%s' to `%s'".printf (right
.value_type
.to_string (), left
.value_type
.to_string ()));
403 if (right
.value_type
.is_disposable ()) {
404 /* rhs transfers ownership of the expression */
406 DataType element_type
;
408 if (ea
.container
.value_type is ArrayType
) {
409 var array_type
= (ArrayType
) ea
.container
.value_type
;
410 element_type
= array_type
.element_type
;
412 var args
= ea
.container
.value_type
.get_type_arguments ();
413 assert (args
.size
== 1);
414 element_type
= args
.get (0);
417 if (!(element_type is PointerType
) && !element_type
.value_owned
) {
418 /* lhs doesn't own the value */
420 Report
.error (source_reference
, "Invalid assignment from owned expression to unowned variable");
423 } else if (left
.value_type
.value_owned
) {
424 /* lhs wants to own the value
425 * rhs doesn't transfer the ownership
426 * code generator needs to add reference
433 if (left
.value_type
!= null) {
434 value_type
= left
.value_type
.copy ();
435 value_type
.value_owned
= false;
440 add_error_types (left
.get_error_types ());
441 add_error_types (right
.get_error_types ());
446 bool is_array_add () {
447 var binary
= right as BinaryExpression
;
448 if (binary
!= null && binary
.left
.value_type is ArrayType
) {
449 if (binary
.operator
== BinaryOperator
.PLUS
) {
450 if (left
.symbol_reference
== binary
.left
.symbol_reference
) {
459 public override void emit (CodeGenerator codegen
) {
460 var ma
= left as MemberAccess
;
461 var ea
= left as ElementAccess
;
462 var pi
= left as PointerIndirection
;
464 var local
= ma
.symbol_reference as LocalVariable
;
465 var param
= ma
.symbol_reference as Parameter
;
466 var field
= ma
.symbol_reference as Field
;
467 var property
= ma
.symbol_reference as Property
;
469 bool instance
= (field
!= null && field
.binding
!= MemberBinding
.STATIC
)
470 || (property
!= null && property
.binding
!= MemberBinding
.STATIC
);
472 if (operator
== AssignmentOperator
.SIMPLE
&&
473 (local
!= null || param
!= null || field
!= null) &&
475 !(field is ArrayLengthField
)) {
476 // visit_assignment not necessary
477 if (instance
&& ma
.inner
!= null) {
478 ma
.inner
.emit (codegen
);
481 right
.emit (codegen
);
482 var new_value
= right
.target_value
;
485 codegen
.store_local (local
, new_value
, false);
486 } else if (param
!= null) {
487 codegen
.store_parameter (param
, new_value
);
488 } else if (field
!= null) {
489 codegen
.store_field (field
, instance
&& ma
.inner
!= null ? ma
.inner
.target_value
: null, new_value
);
492 // when load_variable is changed to use temporary
493 // variables, replace following code with this line
494 // target_value = new_value;
496 target_value
= codegen
.load_local (local
);
497 } else if (param
!= null) {
498 target_value
= codegen
.load_parameter (param
);
499 } else if (field
!= null) {
500 target_value
= codegen
.load_field (field
, instance
&& ma
.inner
!= null ? ma
.inner
.target_value
: null);
503 codegen
.visit_expression (this
);
508 // always process full lvalue
509 // current codegen depends on it
510 // should be removed when moving codegen from
511 // visit_assignment to emit_store_field
513 } else if (instance
&& ma
.inner
!= null) {
514 ma
.inner
.emit (codegen
);
516 } else if (ea
!= null) {
517 // always process full lvalue
518 // current codegen depends on it
519 // should be removed when moving codegen from
520 // visit_assignment to emit_store_element
522 } else if (pi
!= null) {
523 // always process full lvalue
524 // current codegen depends on it
525 // should be removed when moving codegen from
526 // visit_assignment to emit_store_indirectZ
530 right
.emit (codegen
);
532 codegen
.visit_assignment (this
);
534 codegen
.visit_expression (this
);
537 public override void get_defined_variables (Collection
<LocalVariable
> collection
) {
538 right
.get_defined_variables (collection
);
539 left
.get_defined_variables (collection
);
540 var local
= left
.symbol_reference as LocalVariable
;
542 collection
.add (local
);
546 public override void get_used_variables (Collection
<LocalVariable
> collection
) {
547 var ma
= left as MemberAccess
;
548 var ea
= left as ElementAccess
;
549 if (ma
!= null && ma
.inner
!= null) {
550 ma
.inner
.get_used_variables (collection
);
551 } else if (ea
!= null) {
552 ea
.get_used_variables (collection
);
554 right
.get_used_variables (collection
);
558 public enum Vala
.AssignmentOperator
{