codegen: Use temporary variable for string concatenation
[vala-lang.git] / vala / valabinaryexpression.vala
blob0b461049cb6cadf5e83fce71826424cbaba57501
1 /* valabinaryexpression.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
19 * Author:
20 * Jürg Billeter <j@bitron.ch>
24 /**
25 * Represents an expression with two operands in the source code.
27 * Supports +, -, *, /, %, <<, >>, <, >, <=, >=, ==, !=, &, |, ^, &&, ||, ??.
29 public class Vala.BinaryExpression : Expression {
30 /**
31 * The binary operator.
33 public BinaryOperator operator { get; set; }
35 /**
36 * The left operand.
38 public Expression left {
39 get {
40 return _left;
42 set {
43 _left = value;
44 _left.parent_node = this;
48 /**
49 * The right operand.
51 public Expression right {
52 get {
53 return _right;
55 set {
56 _right = value;
57 _right.parent_node = this;
61 public bool chained;
63 private Expression _left;
64 private Expression _right;
66 /**
67 * Creates a new binary expression.
69 * @param op binary operator
70 * @param left left operand
71 * @param right right operand
72 * @param source reference to source code
73 * @return newly created binary expression
75 public BinaryExpression (BinaryOperator op, Expression _left, Expression _right, SourceReference? source = null) {
76 operator = op;
77 left = _left;
78 right = _right;
79 source_reference = source;
82 public override void accept (CodeVisitor visitor) {
83 visitor.visit_binary_expression (this);
85 visitor.visit_expression (this);
88 public override void accept_children (CodeVisitor visitor) {
89 left.accept (visitor);
90 right.accept (visitor);
93 public override void replace_expression (Expression old_node, Expression new_node) {
94 if (left == old_node) {
95 left = new_node;
97 if (right == old_node) {
98 right = new_node;
102 public string get_operator_string () {
103 switch (_operator) {
104 case BinaryOperator.PLUS: return "+";
105 case BinaryOperator.MINUS: return "-";
106 case BinaryOperator.MUL: return "*";
107 case BinaryOperator.DIV: return "/";
108 case BinaryOperator.MOD: return "%";
109 case BinaryOperator.SHIFT_LEFT: return "<<";
110 case BinaryOperator.SHIFT_RIGHT: return ">>";
111 case BinaryOperator.LESS_THAN: return "<";
112 case BinaryOperator.GREATER_THAN: return ">";
113 case BinaryOperator.LESS_THAN_OR_EQUAL: return "<=";
114 case BinaryOperator.GREATER_THAN_OR_EQUAL: return ">=";
115 case BinaryOperator.EQUALITY: return "==";
116 case BinaryOperator.INEQUALITY: return "!=";
117 case BinaryOperator.BITWISE_AND: return "&";
118 case BinaryOperator.BITWISE_OR: return "|";
119 case BinaryOperator.BITWISE_XOR: return "^";
120 case BinaryOperator.AND: return "&&";
121 case BinaryOperator.OR: return "||";
122 case BinaryOperator.IN: return "in";
123 case BinaryOperator.COALESCE: return "??";
124 default: assert_not_reached ();
128 public override string to_string () {
129 return _left.to_string () + get_operator_string () + _right.to_string ();
132 public override bool is_constant () {
133 return left.is_constant () && right.is_constant ();
136 public override bool is_pure () {
137 return left.is_pure () && right.is_pure ();
140 public override bool is_non_null () {
141 return left.is_non_null () && right.is_non_null ();
144 public override bool check (CodeContext context) {
145 if (checked) {
146 return !error;
149 checked = true;
151 // some expressions are not in a block,
152 // for example, expressions in method contracts
153 if (context.analyzer.current_symbol is Block
154 && (operator == BinaryOperator.AND || operator == BinaryOperator.OR)) {
155 // convert conditional expression into if statement
156 // required for flow analysis and exception handling
158 var local = new LocalVariable (context.analyzer.bool_type.copy (), get_temp_name (), null, source_reference);
159 var decl = new DeclarationStatement (local, source_reference);
160 decl.check (context);
162 var right_stmt = new ExpressionStatement (new Assignment (new MemberAccess.simple (local.name, right.source_reference), right, AssignmentOperator.SIMPLE, right.source_reference), right.source_reference);
164 var stmt = new ExpressionStatement (new Assignment (new MemberAccess.simple (local.name, left.source_reference), new BooleanLiteral ((operator == BinaryOperator.OR), left.source_reference), AssignmentOperator.SIMPLE, left.source_reference), left.source_reference);
166 var true_block = new Block (source_reference);
167 var false_block = new Block (source_reference);
169 if (operator == BinaryOperator.AND) {
170 true_block.add_statement (right_stmt);
171 false_block.add_statement (stmt);
172 } else {
173 true_block.add_statement (stmt);
174 false_block.add_statement (right_stmt);
177 var if_stmt = new IfStatement (left, true_block, false_block, source_reference);
179 insert_statement (context.analyzer.insert_block, decl);
180 insert_statement (context.analyzer.insert_block, if_stmt);
182 if (!if_stmt.check (context)) {
183 error = true;
184 return false;
187 var ma = new MemberAccess.simple (local.name, source_reference);
188 ma.target_type = target_type;
189 ma.check (context);
191 parent_node.replace_expression (this, ma);
193 return true;
196 if (operator == BinaryOperator.COALESCE) {
197 var local = new LocalVariable (null, get_temp_name (), left, source_reference);
198 var decl = new DeclarationStatement (local, source_reference);
199 decl.check (context);
201 var right_stmt = new ExpressionStatement (new Assignment (new MemberAccess.simple (local.name, right.source_reference), right, AssignmentOperator.SIMPLE, right.source_reference), right.source_reference);
203 var true_block = new Block (source_reference);
205 true_block.add_statement (right_stmt);
207 var cond = new BinaryExpression (BinaryOperator.EQUALITY, new MemberAccess.simple (local.name, left.source_reference), new NullLiteral (source_reference), source_reference);
209 var if_stmt = new IfStatement (cond, true_block, null, source_reference);
211 insert_statement (context.analyzer.insert_block, decl);
212 insert_statement (context.analyzer.insert_block, if_stmt);
214 if (!if_stmt.check (context)) {
215 error = true;
216 return false;
219 var ma = new MemberAccess.simple (local.name, source_reference);
220 ma.target_type = target_type;
221 ma.check (context);
223 parent_node.replace_expression (this, ma);
225 return true;
228 if (!left.check (context) || !right.check (context)) {
229 /* if there were any errors in inner expressions, skip type check */
230 error = true;
231 return false;
234 if (left.value_type == null) {
235 Report.error (left.source_reference, "invalid left operand");
236 error = true;
237 return false;
240 if (operator != BinaryOperator.IN && right.value_type == null) {
241 Report.error (right.source_reference, "invalid right operand");
242 error = true;
243 return false;
246 if (left.value_type.data_type == context.analyzer.string_type.data_type
247 && operator == BinaryOperator.PLUS) {
248 // string concatenation
250 if (context.profile == Profile.DOVA) {
251 var concat_call = new MethodCall (new MemberAccess (left, "concat", source_reference), source_reference);
252 concat_call.add_argument (right);
253 concat_call.target_type = target_type;
254 parent_node.replace_expression (this, concat_call);
255 return concat_call.check (context);
258 if (right.value_type == null || right.value_type.data_type != context.analyzer.string_type.data_type) {
259 error = true;
260 Report.error (source_reference, "Operands must be strings");
261 return false;
264 value_type = context.analyzer.string_type.copy ();
265 if (left.is_constant () && right.is_constant ()) {
266 value_type.value_owned = false;
267 } else {
268 value_type.value_owned = true;
270 } else if (context.profile == Profile.DOVA && left.value_type.data_type == context.analyzer.list_type.data_type
271 && operator == BinaryOperator.PLUS) {
272 // list concatenation
274 var concat_call = new MethodCall (new MemberAccess (left, "concat", source_reference), source_reference);
275 concat_call.add_argument (right);
276 concat_call.target_type = target_type;
277 parent_node.replace_expression (this, concat_call);
278 return concat_call.check (context);
279 } else if (context.profile != Profile.DOVA && left.value_type is ArrayType && operator == BinaryOperator.PLUS) {
280 // array concatenation
282 var array_type = (ArrayType) left.value_type;
284 if (right.value_type == null || !right.value_type.compatible (array_type.element_type)) {
285 error = true;
286 Report.error (source_reference, "Incompatible operand");
287 return false;
290 right.target_type = array_type.element_type.copy ();
292 value_type = array_type.copy ();
293 value_type.value_owned = true;
294 } else if (operator == BinaryOperator.PLUS
295 || operator == BinaryOperator.MINUS
296 || operator == BinaryOperator.MUL
297 || operator == BinaryOperator.DIV) {
298 // check for pointer arithmetic
299 if (left.value_type is PointerType) {
300 var pointer_type = (PointerType) left.value_type;
301 if (pointer_type.base_type is VoidType) {
302 error = true;
303 Report.error (source_reference, "Pointer arithmetic not supported for `void*'");
304 return false;
307 var offset_type = right.value_type.data_type as Struct;
308 if (offset_type != null && offset_type.is_integer_type ()) {
309 if (operator == BinaryOperator.PLUS
310 || operator == BinaryOperator.MINUS) {
311 // pointer arithmetic: pointer +/- offset
312 value_type = left.value_type.copy ();
314 } else if (right.value_type is PointerType) {
315 // pointer arithmetic: pointer - pointer
316 if (context.profile == Profile.DOVA) {
317 value_type = context.analyzer.long_type;
318 } else {
319 value_type = context.analyzer.size_t_type;
324 if (value_type == null) {
325 value_type = context.analyzer.get_arithmetic_result_type (left.value_type, right.value_type);
328 if (value_type == null) {
329 error = true;
330 Report.error (source_reference, "Arithmetic operation not supported for types `%s' and `%s'".printf (left.value_type.to_string (), right.value_type.to_string ()));
331 return false;
333 } else if (operator == BinaryOperator.MOD
334 || operator == BinaryOperator.SHIFT_LEFT
335 || operator == BinaryOperator.SHIFT_RIGHT
336 || operator == BinaryOperator.BITWISE_XOR) {
337 value_type = context.analyzer.get_arithmetic_result_type (left.value_type, right.value_type);
339 if (value_type == null) {
340 error = true;
341 Report.error (source_reference, "Arithmetic operation not supported for types `%s' and `%s'".printf (left.value_type.to_string (), right.value_type.to_string ()));
342 return false;
344 } else if (operator == BinaryOperator.LESS_THAN
345 || operator == BinaryOperator.GREATER_THAN
346 || operator == BinaryOperator.LESS_THAN_OR_EQUAL
347 || operator == BinaryOperator.GREATER_THAN_OR_EQUAL) {
348 if (left.value_type.compatible (context.analyzer.string_type)
349 && right.value_type.compatible (context.analyzer.string_type)) {
350 // string comparison
351 } else if (left.value_type is PointerType && right.value_type is PointerType) {
352 // pointer arithmetic
353 } else {
354 DataType resulting_type;
356 if (chained) {
357 var lbe = (BinaryExpression) left;
358 resulting_type = context.analyzer.get_arithmetic_result_type (lbe.right.value_type, right.value_type);
359 } else {
360 resulting_type = context.analyzer.get_arithmetic_result_type (left.value_type, right.value_type);
363 if (resulting_type == null) {
364 error = true;
365 Report.error (source_reference, "Relational operation not supported for types `%s' and `%s'".printf (left.value_type.to_string (), right.value_type.to_string ()));
366 return false;
369 left.target_type = left.value_type.copy ();
370 left.target_type.nullable = false;
371 left.target_type.value_owned = false;
372 right.target_type = right.value_type.copy ();
373 right.target_type.nullable = false;
374 right.target_type.value_owned = false;
377 value_type = context.analyzer.bool_type;
378 } else if (operator == BinaryOperator.EQUALITY
379 || operator == BinaryOperator.INEQUALITY) {
380 /* relational operation */
382 if (!right.value_type.compatible (left.value_type)
383 && !left.value_type.compatible (right.value_type)) {
384 Report.error (source_reference, "Equality operation: `%s' and `%s' are incompatible".printf (right.value_type.to_string (), left.value_type.to_string ()));
385 error = true;
386 return false;
389 left.target_type = left.value_type.copy ();
390 left.target_type.value_owned = false;
391 right.target_type = right.value_type.copy ();
392 right.target_type.value_owned = false;
394 if (left.value_type.nullable != right.value_type.nullable) {
395 // if only one operand is nullable, make sure the other operand is promoted to nullable as well
396 if (!left.value_type.nullable) {
397 left.target_type.nullable = true;
398 } else if (!right.value_type.nullable) {
399 right.target_type.nullable = true;
403 if (left.value_type.compatible (context.analyzer.string_type)
404 && right.value_type.compatible (context.analyzer.string_type)) {
405 // string comparison
406 if (context.profile == Profile.DOVA) {
407 var string_ma = new MemberAccess.simple ("string", source_reference);
408 string_ma.qualified = true;
409 var equals_call = new MethodCall (new MemberAccess (string_ma, "equals", source_reference), source_reference);
410 equals_call.add_argument (left);
411 equals_call.add_argument (right);
412 if (operator == BinaryOperator.EQUALITY) {
413 parent_node.replace_expression (this, equals_call);
414 return equals_call.check (context);
415 } else {
416 var not = new UnaryExpression (UnaryOperator.LOGICAL_NEGATION, equals_call, source_reference);
417 parent_node.replace_expression (this, not);
418 return not.check (context);
423 value_type = context.analyzer.bool_type;
424 } else if (operator == BinaryOperator.BITWISE_AND
425 || operator == BinaryOperator.BITWISE_OR) {
426 // integer type or flags type
428 value_type = left.value_type;
429 } else if (operator == BinaryOperator.AND
430 || operator == BinaryOperator.OR) {
431 if (!left.value_type.compatible (context.analyzer.bool_type) || !right.value_type.compatible (context.analyzer.bool_type)) {
432 error = true;
433 Report.error (source_reference, "Operands must be boolean");
436 value_type = context.analyzer.bool_type;
437 } else if (operator == BinaryOperator.IN) {
438 if (left.value_type.compatible (context.analyzer.int_type)
439 && right.value_type.compatible (context.analyzer.int_type)) {
440 // integers or enums
441 } else if (right.value_type is ArrayType) {
442 if (!left.value_type.compatible (((ArrayType) right.value_type).element_type)) {
443 Report.error (source_reference, "Cannot look for `%s' in `%s'".printf (left.value_type.to_string (), right.value_type.to_string ()));
445 } else {
446 // otherwise require a bool contains () method
447 var contains_method = right.value_type.get_member ("contains") as Method;
448 if (contains_method == null) {
449 Report.error (source_reference, "`%s' does not have a `contains' method".printf (right.value_type.to_string ()));
450 error = true;
451 return false;
453 if (contains_method.get_parameters ().size != 1) {
454 Report.error (source_reference, "`%s' must have one parameter".printf (contains_method.get_full_name ()));
455 error = true;
456 return false;
458 if (!contains_method.return_type.compatible (context.analyzer.bool_type)) {
459 Report.error (source_reference, "`%s' must return a boolean value".printf (contains_method.get_full_name ()));
460 error = true;
461 return false;
464 var contains_call = new MethodCall (new MemberAccess (right, "contains", source_reference), source_reference);
465 contains_call.add_argument (left);
466 parent_node.replace_expression (this, contains_call);
467 return contains_call.check (context);
470 value_type = context.analyzer.bool_type;
472 } else {
473 assert_not_reached ();
476 return !error;
479 public override void emit (CodeGenerator codegen) {
480 left.emit (codegen);
481 right.emit (codegen);
483 codegen.visit_binary_expression (this);
485 codegen.visit_expression (this);
488 public override void get_defined_variables (Collection<LocalVariable> collection) {
489 left.get_defined_variables (collection);
490 right.get_defined_variables (collection);
493 public override void get_used_variables (Collection<LocalVariable> collection) {
494 left.get_used_variables (collection);
495 right.get_used_variables (collection);
499 public enum Vala.BinaryOperator {
500 NONE,
501 PLUS,
502 MINUS,
503 MUL,
504 DIV,
505 MOD,
506 SHIFT_LEFT,
507 SHIFT_RIGHT,
508 LESS_THAN,
509 GREATER_THAN,
510 LESS_THAN_OR_EQUAL,
511 GREATER_THAN_OR_EQUAL,
512 EQUALITY,
513 INEQUALITY,
514 BITWISE_AND,
515 BITWISE_OR,
516 BITWISE_XOR,
517 AND,
520 COALESCE