1/* valaccodedelegatemodule.vala
2 *
3 * Copyright (C) 2006-2010  Jürg Billeter
4 * Copyright (C) 2006-2008  Raffaele Sandrini
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 * Lesser General Public License for more details.
15
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
19 *
20 * Author:
21 * 	Jürg Billeter <j@bitron.ch>
22 *	Raffaele Sandrini <raffaele@sandrini.ch>
23 */
24
25
26/**
27 * The link between an assignment and generated code.
28 */
29public class Vala.CCodeDelegateModule : CCodeArrayModule {
30	public override void generate_delegate_declaration (Delegate d, CCodeFile decl_space) {
31		if (add_symbol_declaration (decl_space, d, get_ccode_name (d))) {
32			return;
33		}
34
35		// internally generated delegates don't require a typedef
36		if (d.sender_type != null) {
37			return;
38		}
39
40		var creturn_type = get_callable_creturn_type (d);
41		if (creturn_type is DelegateType && ((DelegateType) creturn_type).delegate_symbol == d) {
42			// recursive delegate
43			creturn_type = new DelegateType ((Delegate) context.root.scope.lookup ("GLib").scope.lookup ("Callback"));
44		}
45
46		generate_type_declaration (creturn_type, decl_space);
47
48		var cparam_map = new HashMap<int,CCodeParameter> (direct_hash, direct_equal);
49
50		var cfundecl = new CCodeFunctionDeclarator (get_ccode_name (d));
51		foreach (Parameter param in d.get_parameters ()) {
52			generate_parameter (param, decl_space, cparam_map, null);
53		}
54
55		// FIXME partial code duplication with CCodeMethodModule.generate_cparameters
56
57		if (d.return_type.is_real_non_null_struct_type ()) {
58			// structs are returned via out parameter
59			var cparam = new CCodeParameter ("result", get_ccode_name (d.return_type) + "*");
60			cparam_map.set (get_param_pos (-3), cparam);
61		} else if (get_ccode_array_length (d) && d.return_type is ArrayType) {
62			// return array length if appropriate
63			var array_type = (ArrayType) d.return_type;
64			var length_ctype = get_ccode_array_length_type (d) + "*";
65
66			for (int dim = 1; dim <= array_type.rank; dim++) {
67				var cparam = new CCodeParameter (get_array_length_cname ("result", dim), length_ctype);
68				cparam_map.set (get_param_pos (get_ccode_array_length_pos (d) + 0.01 * dim), cparam);
69			}
70		} else if (get_ccode_delegate_target (d) && d.return_type is DelegateType) {
71			// return delegate target if appropriate
72			var deleg_type = (DelegateType) d.return_type;
73			if (deleg_type.delegate_symbol.has_target) {
74				generate_type_declaration (delegate_target_type, decl_space);
75				var cparam = new CCodeParameter (get_delegate_target_cname ("result"), get_ccode_name (delegate_target_type) + "*");
76				cparam_map.set (get_param_pos (get_ccode_delegate_target_pos (d)), cparam);
77				if (deleg_type.is_disposable ()) {
78					generate_type_declaration (delegate_target_destroy_type, decl_space);
79					cparam = new CCodeParameter (get_delegate_target_destroy_notify_cname ("result"), get_ccode_name (delegate_target_destroy_type) + "*");
80					cparam_map.set (get_param_pos (get_ccode_destroy_notify_pos (d)), cparam);
81				}
82			}
83		}
84
85		if (d.has_target) {
86			generate_type_declaration (delegate_target_type, decl_space);
87			var cparam = new CCodeParameter ("user_data", get_ccode_name (delegate_target_type));
88			cparam_map.set (get_param_pos (get_ccode_instance_pos (d)), cparam);
89		}
90		if (d.tree_can_fail) {
91			generate_type_declaration (gerror_type, decl_space);
92			var cparam = new CCodeParameter ("error", "GError**");
93			cparam_map.set (get_param_pos (get_ccode_error_pos (d)), cparam);
94		}
95
96		// append C parameters in the right order
97		int last_pos = -1;
98		int min_pos;
99		while (true) {
100			min_pos = -1;
101			foreach (int pos in cparam_map.get_keys ()) {
102				if (pos > last_pos && (min_pos == -1 || pos < min_pos)) {
103					min_pos = pos;
104				}
105			}
106			if (min_pos == -1) {
107				break;
108			}
109			cfundecl.add_parameter (cparam_map.get (min_pos));
110			last_pos = min_pos;
111		}
112
113		var ctypedef = new CCodeTypeDefinition (get_ccode_name (creturn_type), cfundecl);
114
115		if (d.version.deprecated) {
116			if (context.profile == Profile.GOBJECT) {
117				decl_space.add_include ("glib.h");
118			}
119			ctypedef.modifiers |= CCodeModifiers.DEPRECATED;
120		}
121
122		decl_space.add_type_declaration (ctypedef);
123	}
124
125	public override void visit_delegate (Delegate d) {
126		generate_delegate_declaration (d, cfile);
127
128		if (!d.is_internal_symbol ()) {
129			generate_delegate_declaration (d, header_file);
130		}
131		if (!d.is_private_symbol ()) {
132			generate_delegate_declaration (d, internal_header_file);
133		}
134
135		d.accept_children (this);
136	}
137
138	public override string get_delegate_target_cname (string delegate_cname) {
139		return "%s_target".printf (delegate_cname);
140	}
141
142	public override CCodeExpression get_delegate_target_cexpression (Expression delegate_expr, out CCodeExpression delegate_target_destroy_notify) {
143		delegate_target_destroy_notify = get_delegate_target_destroy_notify_cvalue (delegate_expr.target_value);
144		return get_delegate_target_cvalue (delegate_expr.target_value);
145	}
146
147	public override CCodeExpression get_delegate_target_cvalue (TargetValue value) {
148		return ((GLibValue) value).delegate_target_cvalue;
149	}
150
151	public override CCodeExpression get_delegate_target_destroy_notify_cvalue (TargetValue value) {
152		return ((GLibValue) value).delegate_target_destroy_notify_cvalue;
153	}
154
155	public override string get_delegate_target_destroy_notify_cname (string delegate_cname) {
156		return "%s_target_destroy_notify".printf (delegate_cname);
157	}
158
159	public override CCodeExpression get_implicit_cast_expression (CCodeExpression source_cexpr, DataType? expression_type, DataType? target_type, CodeNode? node) {
160		if (target_type is DelegateType && expression_type is MethodType) {
161			var dt = (DelegateType) target_type;
162			var mt = (MethodType) expression_type;
163
164			var method = mt.method_symbol;
165			if (method.base_method != null) {
166				method = method.base_method;
167			} else if (method.base_interface_method != null) {
168				method = method.base_interface_method;
169			}
170
171			return new CCodeIdentifier (generate_delegate_wrapper (method, dt, node));
172		}
173
174		return base.get_implicit_cast_expression (source_cexpr, expression_type, target_type, node);
175	}
176
177	public string generate_delegate_wrapper (Method m, DelegateType dt, CodeNode? node) {
178		var d = dt.delegate_symbol;
179		string delegate_name;
180		var sig = d.parent_symbol as Signal;
181		var dynamic_sig = sig as DynamicSignal;
182		if (dynamic_sig != null) {
183			delegate_name = get_dynamic_signal_cname (dynamic_sig);
184		} else if (sig != null) {
185			delegate_name = get_ccode_lower_case_prefix (sig.parent_symbol) + get_ccode_lower_case_name (sig);
186		} else {
187			delegate_name = Symbol.camel_case_to_lower_case (get_ccode_name (d));
188		}
189
190		string wrapper_name = "_%s_%s".printf (get_ccode_name (m), delegate_name);
191
192		if (!add_wrapper (wrapper_name)) {
193			// wrapper already defined
194			return wrapper_name;
195		}
196
197		// declaration
198		var creturn_type = get_callable_creturn_type (d);
199
200		var function = new CCodeFunction (wrapper_name, get_ccode_name (creturn_type));
201		function.modifiers = CCodeModifiers.STATIC;
202
203		push_function (function);
204
205		var cparam_map = new HashMap<int,CCodeParameter> (direct_hash, direct_equal);
206
207		if (d.has_target) {
208			var cparam = new CCodeParameter ("self", get_ccode_name (delegate_target_type));
209			cparam_map.set (get_param_pos (get_ccode_instance_pos (d)), cparam);
210		}
211
212		if (d.sender_type != null) {
213			var param = new Parameter ("_sender", d.sender_type);
214			generate_parameter (param, cfile, cparam_map, null);
215		}
216
217		var d_params = d.get_parameters ();
218		foreach (Parameter param in d_params) {
219			if (dynamic_sig != null
220			    && param.variable_type is ArrayType
221			    && ((ArrayType) param.variable_type).element_type.type_symbol == string_type.type_symbol) {
222				// use null-terminated string arrays for dynamic signals for compatibility reasons
223				param.set_attribute_bool ("CCode", "array_length", false);
224				param.set_attribute_bool ("CCode", "array_null_terminated", true);
225			}
226
227			generate_parameter (param, cfile, cparam_map, null);
228		}
229		if (get_ccode_array_length (d) && d.return_type is ArrayType) {
230			// return array length if appropriate
231			var array_type = (ArrayType) d.return_type;
232			var length_ctype = get_ccode_array_length_type (d) + "*";
233
234			for (int dim = 1; dim <= array_type.rank; dim++) {
235				var cparam = new CCodeParameter (get_array_length_cname ("result", dim), length_ctype);
236				cparam_map.set (get_param_pos (get_ccode_array_length_pos (d) + 0.01 * dim), cparam);
237			}
238		} else if (d.return_type is DelegateType) {
239			// return delegate target if appropriate
240			var deleg_type = (DelegateType) d.return_type;
241
242			if (get_ccode_delegate_target (d) && deleg_type.delegate_symbol.has_target) {
243				var cparam = new CCodeParameter (get_delegate_target_cname ("result"), get_ccode_name (delegate_target_type) + "*");
244				cparam_map.set (get_param_pos (get_ccode_delegate_target_pos (d)), cparam);
245				if (deleg_type.is_disposable ()) {
246					cparam = new CCodeParameter (get_delegate_target_destroy_notify_cname ("result"), get_ccode_name (delegate_target_destroy_type) + "*");
247					cparam_map.set (get_param_pos (get_ccode_destroy_notify_pos (d)), cparam);
248				}
249			}
250		} else if (d.return_type.is_real_non_null_struct_type ()) {
251			var cparam = new CCodeParameter ("result", "%s*".printf (get_ccode_name (d.return_type)));
252			cparam_map.set (get_param_pos (-3), cparam);
253		}
254
255		if (m.tree_can_fail) {
256			var cparam = new CCodeParameter ("error", "GError**");
257			cparam_map.set (get_param_pos (get_ccode_error_pos (d)), cparam);
258		}
259
260		// append C parameters in the right order
261		int last_pos = -1;
262		int min_pos;
263		while (true) {
264			min_pos = -1;
265			foreach (int pos in cparam_map.get_keys ()) {
266				if (pos > last_pos && (min_pos == -1 || pos < min_pos)) {
267					min_pos = pos;
268				}
269			}
270			if (min_pos == -1) {
271				break;
272			}
273			function.add_parameter (cparam_map.get (min_pos));
274			last_pos = min_pos;
275		}
276
277
278		// definition
279
280		var carg_map = new HashMap<int,CCodeExpression> (direct_hash, direct_equal);
281
282		int i = 0;
283		if (m.binding == MemberBinding.INSTANCE || m.closure) {
284			CCodeExpression arg;
285			if (d.has_target) {
286				arg = new CCodeIdentifier ("self");
287				if (!m.closure && m.this_parameter != null) {
288					arg = convert_from_generic_pointer (arg, m.this_parameter.variable_type);
289				}
290			} else {
291				// use first delegate parameter as instance
292				if (d_params.size == 0 || m.closure) {
293					Report.error (node != null ? node.source_reference : null, "internal: Cannot create delegate wrapper");
294					arg = new CCodeInvalidExpression ();
295				} else {
296					arg = new CCodeIdentifier (get_ccode_name (d_params.get (0)));
297					i = 1;
298				}
299			}
300			carg_map.set (get_param_pos (get_ccode_instance_pos (m)), arg);
301		}
302
303		bool first = true;
304
305		foreach (Parameter param in m.get_parameters ()) {
306			if (first && d.sender_type != null && m.get_parameters ().size == d.get_parameters ().size + 1) {
307				// sender parameter
308				carg_map.set (get_param_pos (get_ccode_pos (param)), new CCodeIdentifier ("_sender"));
309
310				first = false;
311				continue;
312			}
313
314			CCodeExpression arg;
315			arg = new CCodeIdentifier (get_ccode_name (d_params.get (i)));
316			if (d_params.get (i).variable_type is GenericType) {
317				arg = convert_from_generic_pointer (arg, param.variable_type);
318			}
319			carg_map.set (get_param_pos (get_ccode_pos (param)), arg);
320
321			// handle array arguments
322			if (get_ccode_array_length (param) && param.variable_type is ArrayType) {
323				var array_type = (ArrayType) param.variable_type;
324				for (int dim = 1; dim <= array_type.rank; dim++) {
325					CCodeExpression clength;
326					if (get_ccode_array_null_terminated (d_params.get (i))) {
327						requires_array_length = true;
328						var len_call = new CCodeFunctionCall (new CCodeIdentifier ("_vala_array_length"));
329						len_call.add_argument (new CCodeIdentifier (d_params.get (i).name));
330						clength = len_call;
331					} else if (!get_ccode_array_length (d_params.get (i))) {
332						clength = new CCodeConstant ("-1");
333					} else {
334						clength = new CCodeIdentifier (get_variable_array_length_cname (d_params.get (i), dim));
335					}
336					carg_map.set (get_param_pos (get_ccode_array_length_pos (param) + 0.01 * dim), clength);
337				}
338			} else if (get_ccode_delegate_target (param) && param.variable_type is DelegateType) {
339				var deleg_type = (DelegateType) param.variable_type;
340
341				if (deleg_type.delegate_symbol.has_target) {
342					var ctarget = new CCodeIdentifier (get_ccode_delegate_target_name (d_params.get (i)));
343					carg_map.set (get_param_pos (get_ccode_delegate_target_pos (param)), ctarget);
344					if (deleg_type.is_disposable ()) {
345						var ctarget_destroy_notify = new CCodeIdentifier (get_ccode_delegate_target_destroy_notify_name (d_params.get (i)));
346						carg_map.set (get_param_pos (get_ccode_destroy_notify_pos (m)), ctarget_destroy_notify);
347					}
348				}
349			}
350
351			i++;
352		}
353		if (get_ccode_array_length (m) && m.return_type is ArrayType) {
354			var array_type = (ArrayType) m.return_type;
355			for (int dim = 1; dim <= array_type.rank; dim++) {
356				CCodeExpression clength;
357				if (!get_ccode_array_length (d)) {
358					clength = new CCodeConstant ("NULL");
359				} else {
360					clength = new CCodeIdentifier (get_array_length_cname ("result", dim));
361				}
362				carg_map.set (get_param_pos (get_ccode_array_length_pos (m) + 0.01 * dim), clength);
363			}
364		} else if (get_ccode_delegate_target (m) && m.return_type is DelegateType) {
365			var deleg_type = (DelegateType) m.return_type;
366
367			if (deleg_type.delegate_symbol.has_target) {
368				var ctarget = new CCodeIdentifier (get_delegate_target_cname ("result"));
369				carg_map.set (get_param_pos (get_ccode_delegate_target_pos (m)), ctarget);
370				if (deleg_type.is_disposable ()) {
371					var ctarget_destroy_notify = new CCodeIdentifier (get_delegate_target_destroy_notify_cname ("result"));
372					carg_map.set (get_param_pos (get_ccode_destroy_notify_pos (m)), ctarget_destroy_notify);
373				}
374			}
375		} else if (m.return_type.is_real_non_null_struct_type ()) {
376			carg_map.set (get_param_pos (-3), new CCodeIdentifier ("result"));
377		}
378
379		if (m.tree_can_fail) {
380			carg_map.set (get_param_pos (get_ccode_error_pos (m)), new CCodeIdentifier ("error"));
381		}
382
383		var ccall = new CCodeFunctionCall (new CCodeIdentifier (get_ccode_name (m)));
384
385		// append C arguments in the right order
386		last_pos = -1;
387		while (true) {
388			min_pos = -1;
389			foreach (int pos in carg_map.get_keys ()) {
390				if (pos > last_pos && (min_pos == -1 || pos < min_pos)) {
391					min_pos = pos;
392				}
393			}
394			if (min_pos == -1) {
395				break;
396			}
397			ccall.add_argument (carg_map.get (min_pos));
398			last_pos = min_pos;
399		}
400
401		if (m.coroutine) {
402			ccall.add_argument (new CCodeConstant ("NULL"));
403			ccall.add_argument (new CCodeConstant ("NULL"));
404		}
405
406		if (m.return_type is VoidType || m.return_type.is_real_non_null_struct_type ()) {
407			ccode.add_expression (ccall);
408			if (!(d.return_type is VoidType || d.return_type.is_real_non_null_struct_type ())) {
409				// return a default value
410				ccode.add_declaration (get_ccode_name (creturn_type), new CCodeVariableDeclarator ("result", default_value_for_type (d.return_type, true)));
411			}
412		} else {
413			CCodeExpression result = ccall;
414			if (d.return_type is GenericType) {
415				result = convert_to_generic_pointer (result, m.return_type);
416			}
417			ccode.add_declaration (get_ccode_name (creturn_type), new CCodeVariableDeclarator ("result", result));
418		}
419
420		if (d.has_target /* TODO: && dt.value_owned */ && dt.is_called_once) {
421			// destroy notify "self" after the call
422			CCodeExpression? destroy_notify = null;
423			if (m.closure) {
424				int block_id = get_block_id (current_closure_block);
425				destroy_notify = new CCodeIdentifier ("block%d_data_unref".printf (block_id));
426			} else if (get_this_type () != null && m.binding != MemberBinding.STATIC && !m.is_async_callback && is_reference_counting (m.this_parameter.variable_type.type_symbol)) {
427				destroy_notify = get_destroy_func_expression (m.this_parameter.variable_type);
428			}
429
430			if (destroy_notify != null) {
431				var unref_call = new CCodeFunctionCall (destroy_notify);
432				unref_call.add_argument (new CCodeIdentifier ("self"));
433				ccode.add_expression (unref_call);
434			}
435		}
436
437		if (!(m.return_type is VoidType || m.return_type.is_real_non_null_struct_type ()) ||
438			!(d.return_type is VoidType || d.return_type.is_real_non_null_struct_type ())) {
439			ccode.add_return (new CCodeIdentifier ("result"));
440		}
441
442		pop_function ();
443
444		// append to file
445		cfile.add_function_declaration (function);
446		cfile.add_function (function);
447
448		return wrapper_name;
449	}
450
451	public override CCodeParameter generate_parameter (Parameter param, CCodeFile decl_space, Map<int,CCodeParameter> cparam_map, Map<int,CCodeExpression>? carg_map) {
452		if (!(param.variable_type is DelegateType || param.variable_type is MethodType)) {
453			return base.generate_parameter (param, decl_space, cparam_map, carg_map);
454		}
455
456		var param_type = param.variable_type;
457		if (param_type is DelegateType && ((DelegateType) param_type).delegate_symbol == param.parent_symbol) {
458			// recursive delegate
459			param_type = new DelegateType ((Delegate) context.root.scope.lookup ("GLib").scope.lookup ("Callback"));
460		}
461
462		generate_type_declaration (param_type, decl_space);
463
464		string ctypename = get_ccode_name (param_type);
465		string target_ctypename = get_ccode_name (delegate_target_type);
466		string target_destroy_notify_ctypename = get_ccode_name (delegate_target_destroy_type);
467
468		if (param.direction != ParameterDirection.IN) {
469			ctypename += "*";
470			target_ctypename += "*";
471			target_destroy_notify_ctypename += "*";
472		}
473
474		var main_cparam = new CCodeParameter (get_ccode_name (param), ctypename);
475
476		cparam_map.set (get_param_pos (get_ccode_pos (param)), main_cparam);
477		if (carg_map != null) {
478			carg_map.set (get_param_pos (get_ccode_pos (param)), get_parameter_cexpression (param));
479		}
480
481		if (param_type is DelegateType) {
482			unowned DelegateType deleg_type = (DelegateType) param_type;
483			if (get_ccode_delegate_target (param) && deleg_type.delegate_symbol.has_target) {
484				var cparam = new CCodeParameter (get_ccode_delegate_target_name (param), target_ctypename);
485				cparam_map.set (get_param_pos (get_ccode_delegate_target_pos (param)), cparam);
486				if (carg_map != null) {
487					carg_map.set (get_param_pos (get_ccode_delegate_target_pos (param)), get_cexpression (cparam.name));
488				}
489				if (deleg_type.is_disposable ()) {
490					cparam = new CCodeParameter (get_ccode_delegate_target_destroy_notify_name (param), target_destroy_notify_ctypename);
491					cparam_map.set (get_param_pos (get_ccode_destroy_notify_pos (param)), cparam);
492					if (carg_map != null) {
493						carg_map.set (get_param_pos (get_ccode_destroy_notify_pos (param)), get_cexpression (cparam.name));
494					}
495				}
496			}
497		} else if (param_type is MethodType) {
498			var cparam = new CCodeParameter (get_ccode_delegate_target_name (param), target_ctypename);
499			cparam_map.set (get_param_pos (get_ccode_delegate_target_pos (param)), cparam);
500			if (carg_map != null) {
501				carg_map.set (get_param_pos (get_ccode_delegate_target_pos (param)), get_cexpression (cparam.name));
502			}
503		}
504
505		return main_cparam;
506	}
507}
508