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 is_accessible (Symbol sym
) {
101 return left
.is_accessible (sym
) && right
.is_accessible (sym
);
104 public override bool check (CodeContext context
) {
111 if (left is Tuple
&& operator
== AssignmentOperator
.SIMPLE
&& parent_node is ExpressionStatement
) {
112 var tuple
= (Tuple
) left
;
114 var local
= new
LocalVariable (null, get_temp_name (), right
, right
.source_reference
);
115 var decl
= new
DeclarationStatement (local
, source_reference
);
116 decl
.check (context
);
117 insert_statement (context
.analyzer
.insert_block
, decl
);
120 ExpressionStatement stmt
= null;
121 foreach (var expr
in tuple
.get_expressions ()) {
123 stmt
.check (context
);
124 insert_statement (context
.analyzer
.insert_block
, stmt
);
127 var temp_access
= new MemberAccess
.simple (local
.name
, right
.source_reference
);
128 var ea
= new
ElementAccess (temp_access
, expr
.source_reference
);
129 ea
.append_index (new
IntegerLiteral (i
.to_string (), expr
.source_reference
));
130 var assign
= new
Assignment (expr
, ea
, operator
, expr
.source_reference
);
131 stmt
= new
ExpressionStatement (assign
, expr
.source_reference
);
136 context
.analyzer
.replaced_nodes
.add (this
);
137 parent_node
.replace_expression (this
, stmt
.expression
);
138 return stmt
.expression
.check (context
);
143 if (!left
.check (context
)) {
144 // skip on error in inner expression
149 if (left is MemberAccess
) {
150 var ma
= (MemberAccess
) left
;
152 if (ma
.symbol_reference is Constant
) {
154 Report
.error (source_reference
, "Assignment to constant after initialization");
158 if ((!(ma
.symbol_reference is Signal
|| ma
.symbol_reference is DynamicProperty
) && ma
.value_type
== null) ||
159 (ma
.inner
== null && ma
.member_name
== "this" && context
.analyzer
.is_in_instance_method ())) {
161 Report
.error (source_reference
, "unsupported lvalue in assignment");
164 if (ma
.prototype_access
) {
166 Report
.error (source_reference
, "Access to instance member `%s' denied".printf (ma
.symbol_reference
.get_full_name ()));
170 if (ma
.error
|| ma
.symbol_reference
== null) {
172 /* if no symbol found, skip this check */
176 if (ma
.symbol_reference is DynamicSignal
) {
177 // target_type not available for dynamic signals
178 if (!context
.deprecated
) {
179 Report
.warning (source_reference
, "deprecated syntax, use `connect' method instead");
181 } else if (ma
.symbol_reference is Signal
) {
182 if (!context
.deprecated
) {
183 Report
.warning (source_reference
, "deprecated syntax, use `connect' method instead");
185 var sig
= (Signal
) ma
.symbol_reference
;
186 right
.target_type
= new
DelegateType (sig
.get_delegate (ma
.inner
.value_type
, this
));
187 } else if (ma
.symbol_reference is DynamicProperty
) {
188 // target_type not available for dynamic properties
190 right
.formal_target_type
= ma
.formal_value_type
.copy ();
191 right
.target_type
= ma
.value_type
.copy ();
193 } else if (left is ElementAccess
) {
194 var ea
= (ElementAccess
) left
;
196 if (ea
.container
.value_type
.data_type
== context
.analyzer
.string_type
.data_type
) {
198 Report
.error (ea
.source_reference
, "strings are immutable");
200 } else if (ea
.container is MemberAccess
&& ea
.container
.symbol_reference is Signal
) {
201 var ma
= (MemberAccess
) ea
.container
;
202 var sig
= (Signal
) ea
.container
.symbol_reference
;
203 right
.target_type
= new
DelegateType (sig
.get_delegate (ma
.inner
.value_type
, this
));
204 } else if (ea
.container
.value_type
.get_member ("set") is Method
) {
205 var set_call
= new
MethodCall (new
MemberAccess (ea
.container
, "set", source_reference
), source_reference
);
206 foreach (Expression e
in ea
.get_indices ()) {
207 set_call
.add_argument (e
);
209 set_call
.add_argument (right
);
210 parent_node
.replace_expression (this
, set_call
);
211 return set_call
.check (context
);
213 right
.target_type
= left
.value_type
;
215 } else if (left is PointerIndirection
) {
216 right
.target_type
= left
.value_type
;
219 Report
.error (source_reference
, "unsupported lvalue in assignment");
223 if (!right
.check (context
)) {
224 // skip on error in inner expression
229 if (operator
!= AssignmentOperator
.SIMPLE
&& left is MemberAccess
) {
230 // transform into simple assignment
231 // FIXME: only do this if the backend doesn't support
232 // the assignment natively
234 var ma
= (MemberAccess
) left
;
236 if (!(ma
.symbol_reference is Signal
)) {
237 var old_value
= new
MemberAccess (ma
.inner
, ma
.member_name
);
239 var bin
= new
BinaryExpression (BinaryOperator
.PLUS
, old_value
, right
, source_reference
);
240 bin
.target_type
= right
.target_type
;
241 right
.target_type
= right
.target_type
.copy ();
242 right
.target_type
.value_owned
= false;
245 case AssignmentOperator
.BITWISE_OR
: bin
.operator
= BinaryOperator
.BITWISE_OR
; break;
246 case AssignmentOperator
.BITWISE_AND
: bin
.operator
= BinaryOperator
.BITWISE_AND
; break;
247 case AssignmentOperator
.BITWISE_XOR
: bin
.operator
= BinaryOperator
.BITWISE_XOR
; break;
248 case AssignmentOperator
.ADD
: bin
.operator
= BinaryOperator
.PLUS
; break;
249 case AssignmentOperator
.SUB
: bin
.operator
= BinaryOperator
.MINUS
; break;
250 case AssignmentOperator
.MUL
: bin
.operator
= BinaryOperator
.MUL
; break;
251 case AssignmentOperator
.DIV
: bin
.operator
= BinaryOperator
.DIV
; break;
252 case AssignmentOperator
.PERCENT
: bin
.operator
= BinaryOperator
.MOD
; break;
253 case AssignmentOperator
.SHIFT_LEFT
: bin
.operator
= BinaryOperator
.SHIFT_LEFT
; break;
254 case AssignmentOperator
.SHIFT_RIGHT
: bin
.operator
= BinaryOperator
.SHIFT_RIGHT
; break;
258 right
.check (context
);
260 operator
= AssignmentOperator
.SIMPLE
;
264 if (left
.symbol_reference is Signal
) {
265 var sig
= (Signal
) left
.symbol_reference
;
267 var m
= right
.symbol_reference as Method
;
271 Report
.error (right
.source_reference
, "unsupported expression for signal handler");
275 var dynamic_sig
= sig as DynamicSignal
;
276 var right_ma
= right as MemberAccess
;
277 if (dynamic_sig
!= null) {
279 foreach (Parameter param
in dynamic_sig
.handler
.value_type
.get_parameters ()) {
281 // skip sender parameter
284 dynamic_sig
.add_parameter (param
.copy ());
287 right
.target_type
= new
DelegateType (sig
.get_delegate (new
ObjectType ((ObjectTypeSymbol
) sig
.parent_symbol
), this
));
288 } else if (!right
.value_type
.compatible (right
.target_type
)) {
289 var delegate_type
= (DelegateType
) right
.target_type
;
292 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
.to_prototype_string (m
.name
)));
294 } else if (right_ma
!= null && right_ma
.prototype_access
) {
296 Report
.error (right
.source_reference
, "Access to instance member `%s' denied".printf (m
.get_full_name ()));
299 } else if (left is MemberAccess
) {
300 var ma
= (MemberAccess
) left
;
302 if (ma
.symbol_reference is Property
) {
303 var prop
= (Property
) ma
.symbol_reference
;
305 var dynamic_prop
= prop as DynamicProperty
;
306 if (dynamic_prop
!= null) {
307 dynamic_prop
.property_type
= right
.value_type
.copy ();
308 left
.value_type
= dynamic_prop
.property_type
.copy ();
311 if (prop
.set_accessor
== null
312 || (!prop
.set_accessor
.writable
&& !(context
.analyzer
.find_current_method () is CreationMethod
|| context
.analyzer
.is_in_constructor ()))) {
314 Report
.error (ma
.source_reference
, "Property `%s' is read-only".printf (prop
.get_full_name ()));
316 } else if (!context
.deprecated
317 && !prop
.set_accessor
.writable
318 && context
.analyzer
.find_current_method () is CreationMethod
) {
319 if (ma
.inner
.symbol_reference
!= context
.analyzer
.find_current_method ().this_parameter
) {
320 // trying to set construct-only property in creation method for foreign instance
321 Report
.error (ma
.source_reference
, "Property `%s' is read-only".printf (prop
.get_full_name ()));
324 Report
.error (ma
.source_reference
, "Cannot assign to construct-only properties, use Object (property: value) constructor chain up");
328 } else if (ma
.symbol_reference is Variable
&& right
.value_type
== null) {
329 var variable
= (Variable
) ma
.symbol_reference
;
331 if (right
.symbol_reference is Method
&&
332 variable
.variable_type is DelegateType
) {
333 var m
= (Method
) right
.symbol_reference
;
334 var dt
= (DelegateType
) variable
.variable_type
;
335 var cb
= dt
.delegate_symbol
;
337 /* check whether method matches callback type */
338 if (!cb
.matches_method (m
, dt
)) {
340 Report
.error (source_reference
, "declaration of method `%s' doesn't match declaration of callback `%s'".printf (m
.get_full_name (), cb
.get_full_name ()));
344 right
.value_type
= variable
.variable_type
;
347 Report
.error (source_reference
, "Assignment: Invalid assignment attempt");
352 if (left
.value_type
!= null && right
.value_type
!= null) {
353 /* if there was an error on either side,
354 * i.e. {left|right}.value_type == null, skip type check */
356 if (!right
.value_type
.compatible (left
.value_type
)) {
358 Report
.error (source_reference
, "Assignment: Cannot convert from `%s' to `%s'".printf (right
.value_type
.to_string (), left
.value_type
.to_string ()));
362 if (!(ma
.symbol_reference is Property
)) {
363 if (right
.value_type
.is_disposable ()) {
364 /* rhs transfers ownership of the expression */
365 if (!(left
.value_type is PointerType
) && !left
.value_type
.value_owned
) {
366 /* lhs doesn't own the value */
368 Report
.error (source_reference
, "Invalid assignment from owned expression to unowned variable");
370 } else if (left
.value_type
.value_owned
) {
371 /* lhs wants to own the value
372 * rhs doesn't transfer the ownership
373 * code generator needs to add reference
379 var right_ma
= right as MemberAccess
;
380 if (right_ma
!= null && ma
.symbol_reference
== right_ma
.symbol_reference
) {
381 if (ma
.symbol_reference is LocalVariable
|| ma
.symbol_reference is Parameter
) {
382 Report
.warning (source_reference
, "Assignment to same variable");
383 } else if (ma
.symbol_reference is Field
) {
384 var f
= (Field
) ma
.symbol_reference
;
385 if (f
.binding
== MemberBinding
.STATIC
) {
386 Report
.warning (source_reference
, "Assignment to same variable");
388 var ma_inner
= ma
.inner as MemberAccess
;
389 var right_ma_inner
= right_ma
.inner as MemberAccess
;
390 if (ma_inner
!= null && ma_inner
.member_name
== "this" && ma_inner
.inner
== null &&
391 right_ma_inner
!= null && right_ma_inner
.member_name
== "this" && right_ma_inner
.inner
== null) {
392 Report
.warning (source_reference
, "Assignment to same variable");
397 } else if (left is ElementAccess
) {
398 var ea
= (ElementAccess
) left
;
400 if (!right
.value_type
.compatible (left
.value_type
)) {
402 Report
.error (source_reference
, "Assignment: Cannot convert from `%s' to `%s'".printf (right
.value_type
.to_string (), left
.value_type
.to_string ()));
406 if (right
.value_type
.is_disposable ()) {
407 /* rhs transfers ownership of the expression */
409 DataType element_type
;
411 if (ea
.container
.value_type is ArrayType
) {
412 var array_type
= (ArrayType
) ea
.container
.value_type
;
413 element_type
= array_type
.element_type
;
415 var args
= ea
.container
.value_type
.get_type_arguments ();
416 assert (args
.size
== 1);
417 element_type
= args
.get (0);
420 if (!(element_type is PointerType
) && !element_type
.value_owned
) {
421 /* lhs doesn't own the value */
423 Report
.error (source_reference
, "Invalid assignment from owned expression to unowned variable");
426 } else if (left
.value_type
.value_owned
) {
427 /* lhs wants to own the value
428 * rhs doesn't transfer the ownership
429 * code generator needs to add reference
436 if (left
.value_type
!= null) {
437 value_type
= left
.value_type
.copy ();
438 value_type
.value_owned
= false;
443 add_error_types (left
.get_error_types ());
444 add_error_types (right
.get_error_types ());
449 bool is_array_add () {
450 var binary
= right as BinaryExpression
;
451 if (binary
!= null && binary
.left
.value_type is ArrayType
) {
452 if (binary
.operator
== BinaryOperator
.PLUS
) {
453 if (left
.symbol_reference
== binary
.left
.symbol_reference
) {
462 public override void emit (CodeGenerator codegen
) {
463 var ma
= left as MemberAccess
;
464 var ea
= left as ElementAccess
;
465 var pi
= left as PointerIndirection
;
467 var local
= ma
.symbol_reference as LocalVariable
;
468 var param
= ma
.symbol_reference as Parameter
;
469 var field
= ma
.symbol_reference as Field
;
470 var property
= ma
.symbol_reference as Property
;
472 bool instance
= (field
!= null && field
.binding
!= MemberBinding
.STATIC
)
473 || (property
!= null && property
.binding
!= MemberBinding
.STATIC
);
475 if (operator
== AssignmentOperator
.SIMPLE
&&
476 (local
!= null || param
!= null || field
!= null) &&
478 !(field is ArrayLengthField
) &&
479 !(left
.value_type
.is_real_non_null_struct_type () && right is ObjectCreationExpression
)) {
480 // visit_assignment not necessary
481 if (instance
&& ma
.inner
!= null) {
482 ma
.inner
.emit (codegen
);
485 right
.emit (codegen
);
486 var new_value
= right
.target_value
;
489 codegen
.store_local (local
, new_value
, false, source_reference
);
490 } else if (param
!= null) {
491 codegen
.store_parameter (param
, new_value
, false, source_reference
);
492 } else if (field
!= null) {
493 codegen
.store_field (field
, instance
&& ma
.inner
!= null ? ma
.inner
.target_value
: null, new_value
, source_reference
);
496 if (!(parent_node is ExpressionStatement
)) {
497 // when load_variable is changed to use temporary
498 // variables, replace following code with this line
499 // target_value = new_value;
501 target_value
= codegen
.load_local (local
);
502 } else if (param
!= null) {
503 target_value
= codegen
.load_parameter (param
);
504 } else if (field
!= null) {
505 target_value
= codegen
.load_field (field
, instance
&& ma
.inner
!= null ? ma
.inner
.target_value
: null);
509 codegen
.visit_expression (this
);
513 if (instance
&& ma
.inner
!= null && property
!= null) {
514 ma
.inner
.emit (codegen
);
516 // always process full lvalue
517 // current codegen depends on it
518 // should be removed when moving codegen from
519 // visit_assignment to emit_store_field/local/param
522 } else if (ea
!= 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_element
528 } else if (pi
!= null) {
529 // always process full lvalue
530 // current codegen depends on it
531 // should be removed when moving codegen from
532 // visit_assignment to emit_store_indirectZ
536 right
.emit (codegen
);
538 codegen
.visit_assignment (this
);
540 codegen
.visit_expression (this
);
543 public override void get_defined_variables (Collection
<Variable
> collection
) {
544 right
.get_defined_variables (collection
);
545 left
.get_defined_variables (collection
);
546 var local
= left
.symbol_reference as LocalVariable
;
547 var param
= left
.symbol_reference as Parameter
;
549 collection
.add (local
);
550 } else if (param
!= null && param
.direction
== ParameterDirection
.OUT
) {
551 collection
.add (param
);
555 public override void get_used_variables (Collection
<Variable
> collection
) {
556 var ma
= left as MemberAccess
;
557 var ea
= left as ElementAccess
;
558 if (ma
!= null && ma
.inner
!= null) {
559 ma
.inner
.get_used_variables (collection
);
560 } else if (ea
!= null) {
561 ea
.get_used_variables (collection
);
563 right
.get_used_variables (collection
);
567 public enum Vala
.AssignmentOperator
{