/* valamethod.vala * * Copyright (C) 2006-2010 Jürg Billeter * Copyright (C) 2006-2008 Raffaele Sandrini * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * Author: * Jürg Billeter * Raffaele Sandrini */ using GLib; /** * Represents a type or namespace method. */ public class Vala.Method : Subroutine, Callable, GenericSymbol { List type_parameters; /** * The return type of this method. */ public DataType return_type { get { return _return_type; } set { _return_type = value; _return_type.parent_node = this; } } public override bool has_result { get { return !(return_type is VoidType); } } /** * Specifies whether this method may only be called with an instance of * the contained type. */ public MemberBinding binding { get; set; default = MemberBinding.INSTANCE; } /** * Specifies whether this method is abstract. Abstract methods have no * body, may only be specified within abstract classes, and must be * overridden by derived non-abstract classes. */ public bool is_abstract { get; set; } /** * Specifies whether this method is virtual. Virtual methods may be * overridden by derived classes. */ public bool is_virtual { get; set; } /** * Specifies whether this method overrides a virtual or abstract method * of a base type. */ public bool overrides { get; set; } /** * Specifies whether this method should be inlined. */ public bool is_inline { get; set; } public bool returns_floating_reference { get { return get_attribute_bool ("CCode", "returns_floating_reference"); } set { set_attribute_bool ("CCode", "returns_floating_reference", value); } } /* * Specifies whether the C method returns a new instance pointer which * may be different from the previous instance pointer. Only valid for * imported methods. */ public bool returns_modified_pointer { get { return get_attribute ("ReturnsModifiedPointer") != null; } set { set_attribute ("ReturnsModifiedPointer", value); } } /** * Specifies the virtual or abstract method this method overrides. * Reference must be weak as virtual and abstract methods set * base_method to themselves. */ public Method base_method { get { find_base_methods (); return _base_method; } } /** * Specifies the abstract interface method this method implements. */ public Method base_interface_method { get { find_base_methods (); return _base_interface_method; } } /** * Specifies the explicit interface containing the method this method implements. */ public DataType base_interface_type { get { return _base_interface_type; } set { _base_interface_type = value; _base_interface_type.parent_node = this; } } public bool entry_point { get; private set; } public bool is_main_block { get; private set; } /** * Specifies the generated `this` parameter for instance methods. */ public Parameter? this_parameter { get; set; } /** * Specifies whether this method expects printf-style format arguments. */ public bool printf_format { get { return get_attribute ("PrintfFormat") != null; } set { set_attribute ("PrintfFormat", value); } } /** * Specifies whether this method expects scanf-style format arguments. */ public bool scanf_format { get { return get_attribute ("ScanfFormat") != null; } set { set_attribute ("ScanfFormat", value); } } /** * Specifies whether a construct function with a GType parameter is * available. This is only applicable to creation methods. */ public bool has_construct_function { get { return get_attribute_bool ("CCode", "has_construct_function", true); } set { set_attribute_bool ("CCode", "has_construct_function", value); } } public LocalVariable? params_array_var { get; protected set; } public weak Signal signal_reference { get; set; } public bool closure { get; set; } public bool coroutine { get; set; } public bool is_async_callback { get; set; } private List parameters = new ArrayList (); private List? async_begin_parameters; private List? async_end_parameters; private List preconditions; private List postconditions; private DataType _return_type; protected List error_types; private weak Method _base_method; private weak Method _base_interface_method; private DataType _base_interface_type; private bool base_methods_valid; Method? callback_method; Method? end_method; // only valid for closures List captured_variables; static List _empty_expression_list; static List _empty_type_parameter_list; /** * Creates a new method. * * @param name method name * @param return_type method return type * @param source_reference reference to source code * @return newly created method */ public Method (string? name, DataType return_type, SourceReference? source_reference = null, Comment? comment = null) { base (name, source_reference, comment); this.return_type = return_type; } /** * Creates a new main block method. * * @param source_reference reference to source code * @return newly created method */ public Method.main_block (SourceReference? source_reference = null) { base ("main", source_reference, null); return_type = new VoidType (); is_main_block = true; } /** * Appends parameter to this method. * * @param param a formal parameter */ public void add_parameter (Parameter param) { // default C parameter position parameters.add (param); scope.add (param.name, param); } public unowned List get_parameters () { return parameters; } /** * Remove all parameters from this method. */ public void clear_parameters () { foreach (Parameter param in parameters) { if (!param.ellipsis) { scope.remove (param.name); } } parameters.clear (); } public bool is_variadic () { foreach (Parameter param in parameters) { if (param.ellipsis || param.params_array) { return true; } } return false; } public override void accept (CodeVisitor visitor) { visitor.visit_method (this); } public override void accept_children (CodeVisitor visitor) { foreach (TypeParameter p in get_type_parameters ()) { p.accept (visitor); } if (base_interface_type != null) { base_interface_type.accept (visitor); } if (return_type != null) { return_type.accept (visitor); } foreach (Parameter param in parameters) { param.accept (visitor); } if (error_types != null) { foreach (DataType error_type in error_types) { error_type.accept (visitor); } } if (result_var != null) { result_var.accept (visitor); } if (preconditions != null) { foreach (Expression precondition in preconditions) { precondition.accept (visitor); } } if (postconditions != null) { foreach (Expression postcondition in postconditions) { postcondition.accept (visitor); } } if (body != null) { body.accept (visitor); } } /** * Checks whether the parameters and return type of this method are * compatible with the specified method * * @param base_method a method * @param invalid_match error string about which check failed * @return true if the specified method is compatible to this method */ public bool compatible (Method base_method, out string? invalid_match) { return compatible_internal (base_method, out invalid_match, this); } /** * Checks whether the parameters and return type of this method are * compatible with the specified method * * @param base_method a method * @return true if the specified method is compatible to this method */ public bool compatible_no_error (Method base_method) { return compatible_internal (base_method, null, null); } bool compatible_internal (Method base_method, out string? invalid_match, CodeNode? node_reference) { // method is always compatible to itself if (this == base_method) { invalid_match = null; return true; } if (binding != base_method.binding) { invalid_match = "incompatible binding"; return false; } ObjectType object_type = null; if (parent_symbol is ObjectTypeSymbol) { object_type = new ObjectType ((ObjectTypeSymbol) parent_symbol); foreach (TypeParameter type_parameter in object_type.object_type_symbol.get_type_parameters ()) { var type_arg = new GenericType (type_parameter); type_arg.value_owned = true; object_type.add_type_argument (type_arg); } } if (this.get_type_parameters ().size < base_method.get_type_parameters ().size) { invalid_match = "too few type parameters"; return false; } else if (this.get_type_parameters ().size > base_method.get_type_parameters ().size) { invalid_match = "too many type parameters"; return false; } List method_type_args = null; if (this.has_type_parameters ()) { method_type_args = new ArrayList (); foreach (TypeParameter type_parameter in this.get_type_parameters ()) { var type_arg = new GenericType (type_parameter); type_arg.value_owned = true; method_type_args.add (type_arg); } } var return_type = this.return_type.copy (); if (has_attribute_argument ("CCode", "returns_floating_reference")) { return_type.floating_reference = returns_floating_reference; } else { return_type.floating_reference = base_method.returns_floating_reference; } var actual_base_type = base_method.return_type.get_actual_type (object_type, method_type_args, node_reference); if (!return_type.equals (actual_base_type)) { invalid_match = "Base method expected return type `%s', but `%s' was provided".printf (actual_base_type.to_prototype_string (), return_type.to_prototype_string ()); return false; } Iterator method_params_it = parameters.iterator (); int param_index = 1; foreach (Parameter base_param in base_method.parameters) { /* this method may not expect less arguments */ if (!method_params_it.next ()) { invalid_match = "too few parameters"; return false; } var param = method_params_it.get (); if (base_param.ellipsis != param.ellipsis) { invalid_match = "ellipsis parameter mismatch"; return false; } if (base_param.params_array != param.params_array) { invalid_match = "params array parameter mismatch"; return false; } if (!base_param.ellipsis) { if (base_param.direction != param.direction) { invalid_match = "incompatible direction of parameter %d".printf (param_index); return false; } actual_base_type = base_param.variable_type.get_actual_type (object_type, method_type_args, node_reference); if (!actual_base_type.equals (param.variable_type)) { invalid_match = "incompatible type of parameter %d".printf (param_index); return false; } } param_index++; } /* this method may not expect more arguments */ if (method_params_it.next ()) { invalid_match = "too many parameters"; return false; } /* this method may throw less but not more errors than the base method */ var base_method_errors = new ArrayList (); base_method.get_error_types (base_method_errors); if (error_types != null) { foreach (DataType method_error_type in error_types) { bool match = false; foreach (DataType base_method_error_type in base_method_errors) { if (method_error_type.compatible (base_method_error_type)) { match = true; break; } } if (!match) { invalid_match = "incompatible error type `%s'".printf (method_error_type.to_string ()); return false; } } } if (base_method.coroutine != this.coroutine) { invalid_match = "async mismatch"; return false; } invalid_match = null; return true; } /** * Appends the specified parameter to the list of type parameters. * * @param p a type parameter */ public void add_type_parameter (TypeParameter p) { if (type_parameters == null) { type_parameters = new ArrayList (); } type_parameters.add (p); scope.add (p.name, p); } /** * Returns the type parameter list. * * @return list of type parameters */ public unowned List get_type_parameters () { if (type_parameters != null) { return type_parameters; } if (_empty_type_parameter_list == null) { _empty_type_parameter_list = new ArrayList (); } return _empty_type_parameter_list; } public int get_type_parameter_index (string name) { if (type_parameters == null) { return -1; } int i = 0; foreach (TypeParameter parameter in type_parameters) { if (parameter.name == name) { return i; } i++; } return -1; } public bool has_type_parameters () { return (type_parameters != null && type_parameters.size > 0); } /** * Adds a precondition to this method. * * @param precondition a boolean precondition expression */ public void add_precondition (Expression precondition) { if (preconditions == null) { preconditions = new ArrayList (); } preconditions.add (precondition); precondition.parent_node = this; } /** * Returns the list of preconditions of this method. * * @return list of preconditions */ public unowned List get_preconditions () { if (preconditions != null) { return preconditions; } if (_empty_expression_list == null) { _empty_expression_list = new ArrayList (); } return _empty_expression_list; } /** * Adds a postcondition to this method. * * @param postcondition a boolean postcondition expression */ public void add_postcondition (Expression postcondition) { if (postconditions == null) { postconditions = new ArrayList (); } postconditions.add (postcondition); postcondition.parent_node = this; } /** * Returns the list of postconditions of this method. * * @return list of postconditions */ public unowned List get_postconditions () { if (postconditions != null) { return postconditions; } if (_empty_expression_list == null) { _empty_expression_list = new ArrayList (); } return _empty_expression_list; } /** * Adds an error type to the exceptions that can be * thrown by this method. */ public void add_error_type (DataType error_type) { if (error_types == null) { error_types = new ArrayList (); } error_types.add (error_type); error_type.parent_node = this; } public override void get_error_types (Collection collection, SourceReference? source_reference = null) { if (error_types != null) { foreach (var error_type in error_types) { if (source_reference != null) { var type = error_type.copy (); type.source_reference = source_reference; collection.add (type); } else { collection.add (error_type); } } } } public override void replace_expression (Expression old_node, Expression new_node) { if (preconditions != null) { var index = preconditions.index_of (old_node); if (index >= 0) { preconditions[index] = new_node; new_node.parent_node = this; } } if (postconditions != null) { var index = postconditions.index_of (old_node); if (index >= 0) { postconditions[index] = new_node; new_node.parent_node = this; } } } public override void replace_type (DataType old_type, DataType new_type) { if (base_interface_type == old_type) { base_interface_type = new_type; return; } if (return_type == old_type) { return_type = new_type; return; } if (error_types != null) { for (int i = 0; i < error_types.size; i++) { if (error_types[i] == old_type) { error_types[i] = new_type; return; } } } } private void find_base_methods () { if (base_methods_valid) { return; } if (parent_symbol is Class) { if (!(this is CreationMethod)) { find_base_interface_method ((Class) parent_symbol); if (is_virtual || is_abstract || overrides) { find_base_class_method ((Class) parent_symbol); } } } else if (parent_symbol is Interface) { if (is_virtual || is_abstract) { _base_interface_method = this; } } base_methods_valid = true; } private void find_base_class_method (Class cl) { var sym = cl.scope.lookup (name); if (sym is Signal) { unowned Signal sig = (Signal) sym; sym = sig.default_handler; } if (sym is Method) { unowned Method base_method = (Method) sym; if (base_method.is_abstract || base_method.is_virtual) { string invalid_match; if (!compatible (base_method, out invalid_match)) { error = true; var base_method_type = new MethodType (base_method); Report.error (source_reference, "overriding method `%s' is incompatible with base method `%s': %s.", get_full_name (), base_method_type.to_prototype_string (), invalid_match); return; } _base_method = base_method; copy_attribute_double (base_method, "CCode", "instance_pos"); copy_attribute_bool (base_method, "CCode", "returns_floating_reference"); return; } } if (cl.base_class != null) { find_base_class_method (cl.base_class); } } private void find_base_interface_method (Class cl) { Method? base_match = null; string? invalid_error = null; Method? invalid_base_match = null; foreach (DataType type in cl.get_base_types ()) { if (type.type_symbol is Interface) { if (base_interface_type != null && base_interface_type.type_symbol != type.type_symbol) { continue; } var sym = type.type_symbol.scope.lookup (name); if (sym is Signal) { unowned Signal sig = (Signal) sym; sym = sig.default_handler; } if (sym is Method) { unowned Method base_method = (Method) sym; if (base_method.is_abstract || base_method.is_virtual) { if (base_interface_type == null) { // check for existing explicit implementation var has_explicit_implementation = false; foreach (var m in cl.get_methods ()) { if (m.base_interface_type != null && base_method == m.base_interface_method) { has_explicit_implementation = true; break; } } if (has_explicit_implementation) { continue; } } string invalid_match = null; if (!compatible (base_method, out invalid_match)) { invalid_error = invalid_match; invalid_base_match = base_method; } else { base_match = base_method; break; } } } } } if (base_match != null) { _base_interface_method = base_match; copy_attribute_double (base_match, "CCode", "instance_pos"); copy_attribute_bool (base_match, "CCode", "returns_floating_reference"); return; } else if (!hides && invalid_base_match != null) { error = true; var base_method_type = new MethodType (invalid_base_match); Report.error (source_reference, "overriding method `%s' is incompatible with base method `%s': %s.", get_full_name (), base_method_type.to_prototype_string (), invalid_error); return; } if (base_interface_type != null) { Report.error (source_reference, "`%s': no suitable interface method found to implement", get_full_name ()); } } public override bool check (CodeContext context) { if (checked) { return !error; } checked = true; if (this_parameter != null) { this_parameter.check (context); } if (get_attribute ("DestroysInstance") != null) { this_parameter.variable_type.value_owned = true; } if (get_attribute ("NoThrow") != null) { error_types = null; } if (parent_symbol is Class && (is_abstract || is_virtual)) { unowned Class cl = (Class) parent_symbol; if (cl.is_compact && cl.base_class != null) { error = true; Report.error (source_reference, "Abstract and virtual methods may not be declared in derived compact classes"); return false; } if (cl.is_opaque) { error = true; Report.error (source_reference, "Abstract and virtual methods may not be declared in opaque compact classes"); return false; } } if (is_variadic () && (is_abstract || is_virtual)) { error = true; Report.error (source_reference, "Abstract and virtual methods may not be variadic. Use a `va_list' parameter instead of `...' or params-array."); return false; } if (get_attribute ("NoWrapper") != null && !(is_abstract || is_virtual)) { error = true; Report.error (source_reference, "[NoWrapper] methods must be declared abstract or virtual"); return false; } if (is_abstract) { if (parent_symbol is Class) { unowned Class cl = (Class) parent_symbol; if (!cl.is_abstract) { error = true; Report.error (source_reference, "Abstract methods may not be declared in non-abstract classes"); return false; } } else if (!(parent_symbol is Interface)) { error = true; Report.error (source_reference, "Abstract methods may not be declared outside of classes and interfaces"); return false; } } else if (is_virtual) { if (!(parent_symbol is Class) && !(parent_symbol is Interface)) { error = true; Report.error (source_reference, "Virtual methods may not be declared outside of classes and interfaces"); return false; } } else if (overrides) { if (!(parent_symbol is Class)) { error = true; Report.error (source_reference, "Methods may not be overridden outside of classes"); return false; } } else if (access == SymbolAccessibility.PROTECTED) { if (!(parent_symbol is Class) && !(parent_symbol is Interface)) { error = true; Report.error (source_reference, "Protected methods may not be declared outside of classes and interfaces"); return false; } } if (is_abstract && body != null) { error = true; Report.error (source_reference, "Abstract methods cannot have bodies"); } else if ((is_abstract || is_virtual) && is_extern) { error = true; Report.error (source_reference, "Extern methods cannot be abstract or virtual"); } else if (is_extern && body != null) { error = true; Report.error (source_reference, "Extern methods cannot have bodies"); } else if (!is_abstract && !external && source_type == SourceFileType.SOURCE && body == null) { error = true; Report.error (source_reference, "Non-abstract, non-extern methods must have bodies"); } // auto-convert main block to async if a yield expression is used if (is_main_block && body != null) { body.accept (new TraverseVisitor (node => { if (!(node is Statement || node is Expression || node is Variable || node is CatchClause)) { return TraverseStatus.STOP; } if (node is YieldStatement || (node is MethodCall && ((MethodCall) node).is_yield_expression) || (node is ObjectCreationExpression && ((ObjectCreationExpression) node).is_yield_expression)) { coroutine = true; return TraverseStatus.STOP; } return TraverseStatus.CONTINUE; })); } if (coroutine && !external_package && !context.has_package ("gio-2.0")) { error = true; Report.error (source_reference, "gio-2.0 package required for async methods"); return false; } var old_source_file = context.analyzer.current_source_file; var old_symbol = context.analyzer.current_symbol; if (source_reference != null) { context.analyzer.current_source_file = source_reference.file; } context.analyzer.current_symbol = this; return_type.floating_reference = returns_floating_reference; return_type.check (context); if (!external_package) { context.analyzer.check_type (return_type); return_type.check_type_arguments (context, !(return_type is DelegateType)); } if (return_type.type_symbol == context.analyzer.va_list_type.type_symbol) { error = true; Report.error (source_reference, "`%s' not supported as return type", return_type.type_symbol.get_full_name ()); return false; } var init_attr = get_attribute ("ModuleInit"); if (init_attr != null) { source_reference.file.context.module_init_method = this; } if (parameters.size == 1 && parameters[0].ellipsis && body != null && binding != MemberBinding.INSTANCE) { // accept just `...' for external methods and instance methods error = true; Report.error (parameters[0].source_reference, "Named parameter required before `...'"); } if (get_attribute ("Print") != null && (parameters.size != 1 || parameters[0].variable_type.type_symbol != context.analyzer.string_type.type_symbol)) { error = true; Report.error (source_reference, "[Print] methods must have exactly one parameter of type `string'"); } // Collect generic type references // TODO Can this be done in the SymbolResolver? List referenced_generics = new ArrayList (); var traverse = new TraverseVisitor (node => { if (node is GenericType) { referenced_generics.add ((GenericType) node); return TraverseStatus.STOP; } return TraverseStatus.CONTINUE; }); return_type.accept (traverse); var optional_param = false; var params_array_param = false; var ellipsis_param = false; foreach (Parameter param in parameters) { if (!param.check (context)) { error = true; continue; } if (coroutine && param.direction == ParameterDirection.REF) { error = true; Report.error (param.source_reference, "Reference parameters are not supported for async methods"); } if (!external_package && coroutine && (param.ellipsis || param.params_array || param.variable_type.type_symbol == context.analyzer.va_list_type.type_symbol)) { error = true; Report.error (param.source_reference, "Variadic parameters are not supported for async methods"); return false; } param.accept_children (traverse); // TODO: begin and end parameters must be checked separately for coroutines if (coroutine) { continue; } if (optional_param && param.initializer == null && !param.ellipsis) { Report.warning (param.source_reference, "parameter without default follows parameter with default"); } else if (param.initializer != null) { optional_param = true; } // Disallow parameter after params array or ellipsis if (params_array_param) { Report.error (param.source_reference, "parameter follows params-array parameter"); } else if (param.params_array) { params_array_param = true; } if (ellipsis_param) { Report.error (param.source_reference, "parameter follows ellipsis parameter"); } else if (param.ellipsis) { ellipsis_param = true; } // Add local variable to provide access to params arrays which will be constructed out of the given va-args if (param.params_array && body != null) { if (params_array_var != null) { error = true; Report.error (param.source_reference, "Only one params-array parameter is allowed"); continue; } if (!context.experimental) { Report.warning (param.source_reference, "Support of params-arrays is experimental"); } var type = (ArrayType) param.variable_type.copy (); type.element_type.value_owned = type.value_owned; type.value_owned = true; if (type.element_type.is_real_struct_type () && !type.element_type.nullable) { error = true; Report.error (param.source_reference, "Only nullable struct elements are supported in params-array"); } if (type.length != null) { error = true; Report.error (param.source_reference, "Passing length to params-array is not supported yet"); } params_array_var = new LocalVariable (type, param.name, null, param.source_reference); body.insert_statement (0, new DeclarationStatement (params_array_var, param.source_reference)); } } // Check if referenced type-parameters are present // TODO Can this be done in the SymbolResolver? if (binding == MemberBinding.STATIC && parent_symbol is Class && !((Class) parent_symbol).is_compact) { Iterator ref_generics_it = referenced_generics.iterator (); while (ref_generics_it.next ()) { var ref_generics = ref_generics_it.get (); foreach (var type_param in get_type_parameters ()) { if (ref_generics.type_parameter.name == type_param.name) { ref_generics_it.remove (); } } } foreach (var type in referenced_generics) { error = true; Report.error (type.source_reference, "The type name `%s' could not be found", type.type_parameter.name); } } if (coroutine) { // TODO: async methods with out-parameters before in-parameters are not supported bool requires_pointer = false; for (int i = parameters.size - 1; i >= 0; i--) { var param = parameters[i]; if (param.direction == ParameterDirection.IN) { requires_pointer = true; } else if (requires_pointer) { error = true; Report.error (param.source_reference, "Synchronous out-parameters are not supported in async methods"); } } } if (error_types != null) { foreach (DataType error_type in error_types) { if (!(error_type is ErrorType)) { error = true; Report.error (error_type.source_reference, "`%s' is not an error type", error_type.to_string ()); } error_type.check (context); // check whether error type is at least as accessible as the method if (!error_type.is_accessible (this)) { error = true; Report.error (source_reference, "error type `%s' is less accessible than method `%s'", error_type.to_string (), get_full_name ()); return false; } } } if (result_var != null) { result_var.check (context); } if (preconditions != null) { foreach (Expression precondition in preconditions) { precondition.check (context); } } if (postconditions != null) { foreach (Expression postcondition in postconditions) { postcondition.check (context); } } if (body != null) { body.check (context); } if (overrides && base_method == null && base_interface_method != null && base_interface_method.is_abstract) { Report.warning (source_reference, "`override' not required to implement `abstract' interface method `%s'", base_interface_method.get_full_name ()); overrides = false; } else if (!error && overrides && base_method == null && base_interface_method == null) { error = true; Report.error (source_reference, "`%s': no suitable method found to override", get_full_name ()); } else if ((is_abstract || is_virtual || overrides) && access == SymbolAccessibility.PRIVATE) { error = true; Report.error (source_reference, "Private member `%s' cannot be marked as override, virtual, or abstract", get_full_name ()); return false; } if (base_interface_type != null && base_interface_method != null && parent_symbol is Class) { unowned Class cl = (Class) parent_symbol; foreach (var m in cl.get_methods ()) { if (m != this && m.base_interface_method == base_interface_method) { m.checked = true; m.error = true; error = true; Report.error (source_reference, "`%s' already contains an implementation for `%s'", cl.get_full_name (), base_interface_method.get_full_name ()); Report.notice (m.source_reference, "previous implementation of `%s' was here", base_interface_method.get_full_name ()); return false; } } } context.analyzer.current_source_file = old_source_file; context.analyzer.current_symbol = old_symbol; if (!external_package && !overrides && !hides && get_hidden_member () != null) { Report.warning (source_reference, "%s hides inherited method `%s'. Use the `new' keyword if hiding was intentional", get_full_name (), get_hidden_member ().get_full_name ()); } // check whether return type is at least as accessible as the method if (!return_type.is_accessible (this)) { error = true; Report.error (source_reference, "return type `%s' is less accessible than method `%s'", return_type.to_string (), get_full_name ()); return false; } foreach (Expression precondition in get_preconditions ()) { if (precondition.error) { // if there was an error in the precondition, skip this check error = true; return false; } if (!precondition.value_type.compatible (context.analyzer.bool_type)) { error = true; Report.error (precondition.source_reference, "Precondition must be boolean"); return false; } } foreach (Expression postcondition in get_postconditions ()) { if (postcondition.error) { // if there was an error in the postcondition, skip this check error = true; return false; } if (!postcondition.value_type.compatible (context.analyzer.bool_type)) { error = true; Report.error (postcondition.source_reference, "Postcondition must be boolean"); return false; } } // check that all errors that can be thrown in the method body are declared if (body != null && !body.error) { var body_errors = new ArrayList (); body.get_error_types (body_errors); foreach (DataType body_error_type in body_errors) { bool can_propagate_error = false; if (error_types != null) { foreach (DataType method_error_type in error_types) { if (body_error_type.compatible (method_error_type)) { can_propagate_error = true; } } } bool is_dynamic_error = body_error_type is ErrorType && ((ErrorType) body_error_type).dynamic_error; if (!can_propagate_error && !is_dynamic_error) { Report.warning (body_error_type.source_reference, "unhandled error `%s'", body_error_type.to_string()); } } } // check that DBus methods at least throw "GLib.Error" or "GLib.DBusError, GLib.IOError" if (!(this is CreationMethod) && binding == MemberBinding.INSTANCE && !overrides && access == SymbolAccessibility.PUBLIC && parent_symbol is ObjectTypeSymbol && parent_symbol.get_attribute ("DBus") != null) { Attribute? dbus_attr = get_attribute ("DBus"); if (dbus_attr == null || dbus_attr.get_bool ("visible", true)) { bool throws_gerror = false; bool throws_gioerror = false; bool throws_gdbuserror = false; var error_types = new ArrayList (); get_error_types (error_types); foreach (DataType error_type in error_types) { if (!(error_type is ErrorType)) { continue; } unowned ErrorDomain? error_domain = ((ErrorType) error_type).error_domain; if (error_domain == null) { throws_gerror = true; break; } string? full_error_domain = error_domain.get_full_name (); if (full_error_domain == "GLib.IOError") { throws_gioerror = true; } else if (full_error_domain == "GLib.DBusError") { throws_gdbuserror = true; } } if (!throws_gerror && !(throws_gioerror && throws_gdbuserror)) { Report.warning (source_reference, "DBus methods are recommended to throw at least `GLib.Error' or `GLib.DBusError, GLib.IOError'"); } } } if (is_possible_entry_point (context)) { if (context.entry_point != null) { error = true; Report.error (source_reference, "program already has an entry point `%s'", context.entry_point.get_full_name ()); return false; } entry_point = true; context.entry_point = this; if (tree_can_fail) { error = true; Report.error (source_reference, "\"main\" method cannot throw errors"); } if (is_inline) { error = true; Report.error (source_reference, "\"main\" method cannot be inline"); } } if (get_attribute ("GtkCallback") != null) { used = true; } return !error; } bool is_possible_entry_point (CodeContext context) { if (external_package) { return false; } if (context.entry_point_name == null) { if (name == null || name != "main") { // method must be called "main" return false; } } else { // custom entry point name if (get_full_name () != context.entry_point_name) { return false; } } if (binding == MemberBinding.INSTANCE) { // method must be static return false; } if (return_type is VoidType) { } else if (return_type.type_symbol == context.analyzer.int_type.type_symbol) { } else { // return type must be void or int return false; } var params = get_parameters (); if (params.size == 0) { // method may have no parameters return true; } if (params.size > 1) { // method must not have more than one parameter return false; } Iterator params_it = params.iterator (); params_it.next (); var param = params_it.get (); if (param.direction == ParameterDirection.OUT) { // parameter must not be an out parameter return false; } if (!(param.variable_type is ArrayType)) { // parameter must be an array return false; } unowned ArrayType array_type = (ArrayType) param.variable_type; if (array_type.element_type.type_symbol != context.analyzer.string_type.type_symbol) { // parameter must be an array of strings return false; } return true; } public int get_required_arguments () { int n = 0; foreach (var param in parameters) { if (param.initializer != null || param.ellipsis) { // optional argument break; } n++; } return n; } public unowned Method get_end_method () { assert (this.coroutine); if (end_method == null) { end_method = new Method ("end", return_type, source_reference); end_method.access = SymbolAccessibility.PUBLIC; end_method.external = true; end_method.owner = scope; foreach (var param in get_async_end_parameters ()) { end_method.add_parameter (param.copy ()); } foreach (var param in get_type_parameters ()) { end_method.add_type_parameter (param); } end_method.copy_attribute_double (this, "CCode", "async_result_pos"); } return end_method; } public unowned Method get_callback_method () { assert (this.coroutine); if (callback_method == null) { var bool_type = CodeContext.get ().analyzer.bool_type.copy (); bool_type.value_owned = true; callback_method = new Method ("callback", bool_type, source_reference); callback_method.access = SymbolAccessibility.PUBLIC; callback_method.external = true; callback_method.binding = MemberBinding.INSTANCE; callback_method.owner = scope; callback_method.is_async_callback = true; } return callback_method; } public unowned List get_async_begin_parameters () { assert (this.coroutine); if (async_begin_parameters != null) { return async_begin_parameters; } async_begin_parameters = new ArrayList (); var glib_ns = CodeContext.get ().root.scope.lookup ("GLib"); Parameter ellipsis = null; foreach (var param in parameters) { if (param.ellipsis) { ellipsis = param; } else if (param.direction == ParameterDirection.IN) { async_begin_parameters.add (param); } } var callback_type = new DelegateType ((Delegate) glib_ns.scope.lookup ("AsyncReadyCallback"), source_reference); callback_type.nullable = true; callback_type.value_owned = true; callback_type.is_called_once = true; var callback_param = new Parameter ("_callback_", callback_type, source_reference); callback_param.initializer = new NullLiteral (source_reference); callback_param.initializer.target_type = callback_type.copy (); callback_param.set_attribute_double ("CCode", "pos", -1); callback_param.set_attribute_double ("CCode", "delegate_target_pos", -0.9); scope.add (null, callback_param); async_begin_parameters.add (callback_param); if (ellipsis != null) { async_begin_parameters.add (ellipsis); } return async_begin_parameters; } public unowned List get_async_end_parameters () { assert (this.coroutine); if (async_end_parameters != null) { return async_end_parameters; } async_end_parameters = new ArrayList (); var glib_ns = CodeContext.get ().root.scope.lookup ("GLib"); var result_type = new ObjectType ((ObjectTypeSymbol) glib_ns.scope.lookup ("AsyncResult")); var result_param = new Parameter ("_res_", result_type, source_reference); result_param.set_attribute_double ("CCode", "pos", get_attribute_double ("CCode", "async_result_pos", 0.1)); scope.add (null, result_param); async_end_parameters.add (result_param); foreach (var param in parameters) { if (param.direction == ParameterDirection.OUT) { async_end_parameters.add (param); } } return async_end_parameters; } public void add_captured_variable (LocalVariable local) { assert (this.closure); if (captured_variables == null) { captured_variables = new ArrayList (); } captured_variables.add (local); } public void get_captured_variables (Collection variables) { if (captured_variables != null) { foreach (var local in captured_variables) { variables.add (local); } } } public override void get_defined_variables (Collection collection) { if (result_var != null) { collection.add (result_var); } if (params_array_var != null) { collection.add (params_array_var); } // capturing variables is only supported if they are initialized // therefore assume that captured variables are initialized if (closure) { get_captured_variables ((Collection) collection); } } public int get_format_arg_index () { for (int i = 0; i < parameters.size; i++) { if (parameters[i].format_arg) { return i; } } return -1; } public bool has_error_type_parameter () { if (tree_can_fail) { return true; } if (base_method != null && base_method != this && base_method.has_error_type_parameter ()) { return true; } if (base_interface_method != null && base_interface_method != this && base_interface_method.has_error_type_parameter ()) { return true; } return false; } } // vim:sw=8 noet