/* valalambdaexpression.vala * * Copyright (C) 2006-2010 Jürg Billeter * * 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 */ using GLib; /** * Represents a lambda expression in the source code. * * Lambda expressions are anonymous methods with implicitly typed parameters. */ public class Vala.LambdaExpression : Expression { /** * The expression body of this lambda expression. Only one of * expression_body or statement_body may be set. */ public Expression expression_body { get { return _expression_body; } private set { _expression_body = value; if (_expression_body != null) { _expression_body.parent_node = this; } } } /** * The statement body of this lambda expression. Only one of * expression_body or statement_body may be set. */ public Block statement_body { get { return _statement_body; } private set { _statement_body = value; if (_statement_body != null) { _statement_body.parent_node = this; } } } /** * The generated method. */ public Method method { get; private set; } private List parameters = new ArrayList (); Block _statement_body; Expression _expression_body; /** * Creates a new lambda expression. * * @param expression_body expression body * @param source_reference reference to source code * @return newly created lambda expression */ public LambdaExpression (Expression expression_body, SourceReference? source_reference = null) { this.source_reference = source_reference; this.expression_body = expression_body; } /** * Creates a new lambda expression with statement body. * * @param statement_body statement body * @param source_reference reference to source code * @return newly created lambda expression */ public LambdaExpression.with_statement_body (Block statement_body, SourceReference? source_reference = null) { this.statement_body = statement_body; this.source_reference = source_reference; } /** * Appends implicitly typed parameter. * * @param param parameter name */ public void add_parameter (Parameter param) { parameters.add (param); } /** * Returns the parameter list. * * @return parameter list */ public unowned List get_parameters () { return parameters; } public override void accept (CodeVisitor visitor) { visitor.visit_lambda_expression (this); visitor.visit_expression (this); } public override void accept_children (CodeVisitor visitor) { if (method == null) { if (expression_body != null) { expression_body.accept (visitor); visitor.visit_end_full_expression (expression_body); } else if (statement_body != null) { statement_body.accept (visitor); } } else { method.accept (visitor); } } public override bool is_pure () { return false; } public override bool check (CodeContext context) { if (checked) { return !error; } checked = true; if (!(target_type is DelegateType)) { error = true; if (target_type != null) { Report.error (source_reference, "Cannot convert lambda expression to `%s'", target_type.to_string ()); } else { Report.error (source_reference, "lambda expression not allowed in this context"); } return false; } var cb = (Delegate) ((DelegateType) target_type).delegate_symbol; var return_type = cb.return_type.get_actual_type (target_type, null, this); method = new Method ("@lambda", return_type, source_reference); // track usage for flow analyzer method.used = true; if (return_type is ArrayType) { method.copy_attribute_bool (cb, "CCode", "array_length"); method.copy_attribute_bool (cb, "CCode", "array_null_terminated"); method.copy_attribute_string (cb, "CCode", "array_length_type"); } else if (return_type is DelegateType) { method.copy_attribute_bool (cb, "CCode", "delegate_target"); } if (!cb.has_target || !context.analyzer.is_in_instance_method ()) { method.binding = MemberBinding.STATIC; } else { var sym = context.analyzer.current_symbol; while (method.this_parameter == null) { if (sym is Property) { var prop = (Property) sym; method.this_parameter = prop.this_parameter; } else if (sym is Constructor) { var c = (Constructor) sym; method.this_parameter = c.this_parameter; } else if (sym is Destructor) { var d = (Destructor) sym; method.this_parameter = d.this_parameter; } else if (sym is Method) { var m = (Method) sym; method.this_parameter = m.this_parameter; } sym = sym.parent_symbol; } } method.owner = context.analyzer.current_symbol.scope; method.parent_node = this; var lambda_params = get_parameters (); Iterator lambda_param_it = lambda_params.iterator (); if (cb.sender_type != null && lambda_params.size == cb.get_parameters ().size + 1) { // lambda expression has sender parameter lambda_param_it.next (); Parameter lambda_param = lambda_param_it.get (); lambda_param.variable_type = cb.sender_type; method.add_parameter (lambda_param); } foreach (Parameter cb_param in cb.get_parameters ()) { if (!lambda_param_it.next ()) { /* lambda expressions are allowed to have less parameters */ break; } Parameter lambda_param = lambda_param_it.get (); if (lambda_param.direction != cb_param.direction) { error = true; Report.error (lambda_param.source_reference, "direction of parameter `%s' is incompatible with the target delegate", lambda_param.name); } lambda_param.variable_type = cb_param.variable_type.get_actual_type (target_type, null, this); lambda_param.base_parameter = cb_param; method.add_parameter (lambda_param); } if (lambda_param_it.next ()) { /* lambda expressions may not expect more parameters */ error = true; Report.error (source_reference, "lambda expression: too many parameters"); return false; } var error_types = new ArrayList (); cb.get_error_types (error_types); foreach (var error_type in error_types) { method.add_error_type (error_type.copy ()); } if (expression_body != null) { var block = new Block (source_reference); block.scope.parent_scope = method.scope; if (method.return_type.type_symbol != null) { block.add_statement (new ReturnStatement (expression_body, source_reference)); } else { block.add_statement (new ExpressionStatement (expression_body, source_reference)); } method.body = block; } else { method.body = statement_body; } method.body.owner = method.scope; // support use of generics in closures unowned Method? m = SemanticAnalyzer.find_parent_method (context.analyzer.current_symbol); if (m != null) { foreach (var type_param in m.get_type_parameters ()) { method.add_type_parameter (new TypeParameter (type_param.name, type_param.source_reference)); method.closure = true; m.body.captured = true; } } /* lambda expressions should be usable like MemberAccess of a method */ symbol_reference = method; method.check (context); value_type = new MethodType (method, source_reference); value_type.value_owned = target_type.value_owned; return !error; } public override void emit (CodeGenerator codegen) { codegen.visit_lambda_expression (this); codegen.visit_expression (this); } public override void get_used_variables (Collection collection) { // require captured variables to be initialized if (method != null && method.closure) { method.get_captured_variables ((Collection) collection); } } }