// Sugar for partial application, which we convert to a lambda. static bool partial_application(pass_opt_t* opt, ast_t** astp) { /* Example that we refer to throughout this function. * ```pony * class C * fun f[T](a: A, b: B = b_default): R * * let recv: T = ... * recv~f[T2](foo) * ``` * * Partial call is converted to: * ```pony * {(b: B = b_default)($0 = recv, a = foo): R => $0.f[T2](a, consume b) } * ``` */ ast_t* ast = *astp; typecheck_t* t = &opt->check; if(!method_application(opt, ast, true)) return false; AST_GET_CHILDREN(ast, positional, namedargs, question, lhs); // LHS must be an application, possibly wrapped in another application // if the method had type parameters for qualification. pony_assert(ast_id(lhs) == TK_FUNAPP || ast_id(lhs) == TK_BEAPP || ast_id(lhs) == TK_NEWAPP); AST_GET_CHILDREN(lhs, receiver, method); ast_t* type_args = NULL; if(ast_id(receiver) == ast_id(lhs)) { type_args = method; AST_GET_CHILDREN_NO_DECL(receiver, receiver, method); } // Look up the original method definition for this method call. ast_t* method_def = lookup(opt, lhs, ast_type(receiver), ast_name(method)); pony_assert(ast_id(method_def) == TK_FUN || ast_id(method_def) == TK_BE || ast_id(method_def) == TK_NEW); // The TK_FUNTYPE of the LHS. ast_t* type = ast_type(lhs); pony_assert(ast_id(type) == TK_FUNTYPE); if(is_typecheck_error(type)) return false; AST_GET_CHILDREN(type, cap, type_params, target_params, result); bool bare = ast_id(cap) == TK_AT; token_id apply_cap = TK_AT; if(!bare) apply_cap = partial_application_cap(opt, type, receiver, positional); token_id can_error = ast_id(ast_childidx(method_def, 5)); const char* recv_name = package_hygienic_id(t); // Build lambda expression. ast_t* call_receiver = NULL; if(bare) { ast_t* arg = ast_child(positional); while(arg != NULL) { if(ast_id(arg) != TK_NONE) { ast_error(opt->check.errors, arg, "the partial application of a bare " "method cannot take arguments"); return false; } arg = ast_sibling(arg); } ast_t* receiver_type = ast_type(receiver); if(is_bare(receiver_type)) { // Partial application on a bare object, simply return the object itself. ast_replace(astp, receiver); return true; } AST_GET_CHILDREN(receiver_type, recv_type_package, recv_type_name); const char* recv_package_str = ast_name(recv_type_package); const char* recv_name_str = ast_name(recv_type_name); ast_t* module = ast_nearest(ast, TK_MODULE); ast_t* package = ast_parent(module); ast_t* pkg_id = package_id(package); const char* pkg_str = ast_name(pkg_id); const char* pkg_alias = NULL; if(recv_package_str != pkg_str) pkg_alias = package_alias_from_id(module, recv_package_str); ast_free_unattached(pkg_id); if(pkg_alias != NULL) { // `package.Type.f` BUILD_NO_DECL(call_receiver, ast, NODE(TK_DOT, NODE(TK_DOT, NODE(TK_REFERENCE, ID(pkg_alias)) ID(recv_name_str)) TREE(method))); } else { // `Type.f` BUILD_NO_DECL(call_receiver, ast, NODE(TK_DOT, NODE(TK_REFERENCE, ID(recv_name_str)) TREE(method))); } } else { // `$0.f` BUILD_NO_DECL(call_receiver, ast, NODE(TK_DOT, NODE(TK_REFERENCE, ID(recv_name)) TREE(method))); } ast_t* captures = NULL; if(bare) { captures = ast_from(receiver, TK_NONE); } else { // Build captures. We always have at least one capture, for receiver. // Capture: `$0 = recv` BUILD_NO_DECL(captures, receiver, NODE(TK_LAMBDACAPTURES, NODE(TK_LAMBDACAPTURE, ID(recv_name) NONE // Infer type. TREE(receiver)))); } // Process arguments. ast_t* target_param = ast_child(target_params); ast_t* lambda_params = ast_from(target_params, TK_NONE); ast_t* lambda_call_args = ast_from(positional, TK_NONE); ast_t* given_arg = ast_child(positional); while(given_arg != NULL) { pony_assert(target_param != NULL); const char* target_p_name = ast_name(ast_child(target_param)); if(ast_id(given_arg) == TK_NONE) { // This argument is not supplied already, must be a lambda parameter. // Like `b` in example above. // Build a new a new TK_PARAM node rather than copying the target one, // since the target has already been processed to expr pass, and we need // a clean one. AST_GET_CHILDREN(target_param, p_id, p_type, p_default); // Parameter: `b: B = b_default` BUILD(lambda_param, target_param, NODE(TK_PARAM, TREE(p_id) TREE(sanitise_type(p_type)) TREE(p_default))); ast_append(lambda_params, lambda_param); ast_setid(lambda_params, TK_PARAMS); // Argument: `consume b` BUILD(target_arg, lambda_param, NODE(TK_SEQ, NODE(TK_CONSUME, NONE NODE(TK_REFERENCE, ID(target_p_name))))); ast_append(lambda_call_args, target_arg); ast_setid(lambda_call_args, TK_POSITIONALARGS); } else { // This argument is supplied to the partial, capture it. // Like `a` in example above. // Capture: `a = foo` BUILD(capture, given_arg, NODE(TK_LAMBDACAPTURE, ID(target_p_name) NONE TREE(given_arg))); ast_append(captures, capture); // Argument: `a` BUILD(target_arg, given_arg, NODE(TK_SEQ, NODE(TK_REFERENCE, ID(target_p_name)))); ast_append(lambda_call_args, target_arg); ast_setid(lambda_call_args, TK_POSITIONALARGS); } given_arg = ast_sibling(given_arg); target_param = ast_sibling(target_param); } pony_assert(target_param == NULL); if(type_args != NULL) { // The partial call has type args, add them to the actual call in apply(). // `$0.f[T2]` BUILD(qualified, type_args, NODE(TK_QUALIFY, TREE(call_receiver) TREE(type_args))); call_receiver = qualified; } REPLACE(astp, NODE((bare ? TK_BARELAMBDA : TK_LAMBDA), NODE(apply_cap) NONE // Lambda function name. NONE // Lambda type params. TREE(lambda_params) TREE(captures) TREE(sanitise_type(result)) NODE(can_error) NODE(TK_SEQ, NODE(TK_CALL, TREE(lambda_call_args) NONE // Named args. NODE(can_error) TREE(call_receiver))) NONE)); // Lambda reference capability. // Need to preserve various lambda children. ast_setflag(ast_childidx(*astp, 2), AST_FLAG_PRESERVE); // Type params. ast_setflag(ast_childidx(*astp, 3), AST_FLAG_PRESERVE); // Parameters. ast_setflag(ast_childidx(*astp, 5), AST_FLAG_PRESERVE); // Return type. ast_setflag(ast_childidx(*astp, 7), AST_FLAG_PRESERVE); // Body. // Catch up to this pass. return ast_passes_subtree(astp, opt, PASS_EXPR); }
// Sugar for partial application, which we convert to a lambda. static bool partial_application(pass_opt_t* opt, ast_t** astp) { /* Example that we refer to throughout this function. * ```pony * class C * fun f[T](a: A, b: B = b_default): R * * let recv: T = ... * recv~f[T2](foo) * ``` * * Partial call is converted to: * ```pony * lambda(b: B = b_default)($0 = recv, a = foo): R => $0.f[T2](a, consume b) * ``` */ ast_t* ast = *astp; typecheck_t* t = &opt->check; if(!method_application(opt, ast, true)) return false; AST_GET_CHILDREN(ast, positional, namedargs, lhs); assert(ast_id(lhs) == TK_FUNAPP || ast_id(lhs) == TK_BEAPP || ast_id(lhs) == TK_NEWAPP); // LHS must be a TK_TILDE, possibly contained in a TK_QUALIFY. AST_GET_CHILDREN(lhs, receiver, method); ast_t* type_args = NULL; switch(ast_id(receiver)) { case TK_NEWAPP: case TK_BEAPP: case TK_FUNAPP: type_args = method; AST_GET_CHILDREN_NO_DECL(receiver, receiver, method); break; default: {} } // The TK_FUNTYPE of the LHS. ast_t* type = ast_type(lhs); if(is_typecheck_error(type)) return false; token_id apply_cap = partial_application_cap(opt, type, receiver, positional); AST_GET_CHILDREN(type, cap, type_params, target_params, result); token_id can_error = ast_canerror(lhs) ? TK_QUESTION : TK_NONE; const char* recv_name = package_hygienic_id(t); // Build captures. We always have at least one capture, for receiver. // Capture: `$0 = recv` BUILD(captures, receiver, NODE(TK_LAMBDACAPTURES, NODE(TK_LAMBDACAPTURE, ID(recv_name) NONE // Infer type. TREE(receiver)))); // Process arguments. ast_t* given_arg = ast_child(positional); ast_t* target_param = ast_child(target_params); ast_t* lambda_params = ast_from(target_params, TK_NONE); ast_t* lambda_call_args = ast_from(positional, TK_NONE); while(given_arg != NULL) { assert(target_param != NULL); const char* target_p_name = ast_name(ast_child(target_param)); if(ast_id(given_arg) == TK_NONE) { // This argument is not supplied already, must be a lambda parameter. // Like `b` in example above. // Build a new a new TK_PARAM node rather than copying the target one, // since the target has already been processed to expr pass, and we need // a clean one. AST_GET_CHILDREN(target_param, p_id, p_type, p_default); // Parameter: `b: B = b_default` BUILD(lambda_param, target_param, NODE(TK_PARAM, TREE(p_id) TREE(sanitise_type(p_type)) TREE(p_default))); ast_append(lambda_params, lambda_param); ast_setid(lambda_params, TK_PARAMS); // Argument: `consume b` BUILD(target_arg, lambda_param, NODE(TK_SEQ, NODE(TK_CONSUME, NONE NODE(TK_REFERENCE, ID(target_p_name))))); ast_append(lambda_call_args, target_arg); ast_setid(lambda_call_args, TK_POSITIONALARGS); } else { // This argument is supplied to the partial, capture it. // Like `a` in example above. // Capture: `a = foo` BUILD(capture, given_arg, NODE(TK_LAMBDACAPTURE, ID(target_p_name) NONE TREE(given_arg))); ast_append(captures, capture); // Argument: `a` BUILD(target_arg, given_arg, NODE(TK_SEQ, NODE(TK_REFERENCE, ID(target_p_name)))); ast_append(lambda_call_args, target_arg); ast_setid(lambda_call_args, TK_POSITIONALARGS); } given_arg = ast_sibling(given_arg); target_param = ast_sibling(target_param); } assert(target_param == NULL); // Build lambda expression. // `$0.f` BUILD(call_receiver, ast, NODE(TK_DOT, NODE(TK_REFERENCE, ID(recv_name)) TREE(method))); if(type_args != NULL) { // The partial call has type args, add them to the actual call in apply(). // `$0.f[T2]` BUILD(qualified, type_args, NODE(TK_QUALIFY, TREE(call_receiver) TREE(type_args))); call_receiver = qualified; } REPLACE(astp, NODE(TK_LAMBDA, NODE(apply_cap) NONE // Lambda function name. NONE // Lambda type params. TREE(lambda_params) TREE(captures) TREE(sanitise_type(result)) NODE(can_error) NODE(TK_SEQ, NODE(TK_CALL, TREE(lambda_call_args) NONE // Named args. TREE(call_receiver))))); // Need to preserve various lambda children. ast_setflag(ast_childidx(*astp, 2), AST_FLAG_PRESERVE); // Type params. ast_setflag(ast_childidx(*astp, 3), AST_FLAG_PRESERVE); // Parameters. ast_setflag(ast_childidx(*astp, 5), AST_FLAG_PRESERVE); // Return type. ast_setflag(ast_childidx(*astp, 7), AST_FLAG_PRESERVE); // Body. // Catch up to this pass. return ast_passes_subtree(astp, opt, PASS_EXPR); }
static bool partial_application(pass_opt_t* opt, ast_t** astp) { ast_t* ast = *astp; typecheck_t* t = &opt->check; if(!method_application(opt, ast, true)) return false; AST_GET_CHILDREN(ast, positional, namedargs, lhs); // LHS must be a TK_TILDE, possibly contained in a TK_QUALIFY. AST_GET_CHILDREN(lhs, receiver, method); switch(ast_id(receiver)) { case TK_NEWAPP: case TK_BEAPP: case TK_FUNAPP: AST_GET_CHILDREN_NO_DECL(receiver, receiver, method); break; default: {} } // The TK_FUNTYPE of the LHS. ast_t* type = ast_type(lhs); if(is_typecheck_error(type)) return false; token_id apply_cap = partial_application_cap(type, receiver, positional); AST_GET_CHILDREN(type, cap, typeparams, params, result); // Create a new anonymous type. ast_t* c_id = ast_from_string(ast, package_hygienic_id(t)); BUILD(def, ast, NODE(TK_CLASS, AST_SCOPE TREE(c_id) NONE NONE NONE NODE(TK_MEMBERS) NONE NONE)); // We will have a create method in the type. BUILD(create, ast, NODE(TK_NEW, AST_SCOPE NONE ID("create") NONE NODE(TK_PARAMS) NONE NONE NODE(TK_SEQ) NONE)); // We will have an apply method in the type. token_id can_error = ast_canerror(lhs) ? TK_QUESTION : TK_NONE; BUILD(apply, ast, NODE(TK_FUN, AST_SCOPE NODE(apply_cap) ID("apply") NONE NODE(TK_PARAMS) TREE(result) NODE(can_error) NODE(TK_SEQ) NONE)); // We will replace partial application with $0.create(...) BUILD(call_receiver, ast, NODE(TK_REFERENCE, TREE(c_id))); BUILD(call_dot, ast, NODE(TK_DOT, TREE(call_receiver) ID("create"))); BUILD(call, ast, NODE(TK_CALL, NONE NODE(TK_NAMEDARGS) TREE(call_dot))); ast_t* class_members = ast_childidx(def, 4); ast_t* create_params = ast_childidx(create, 3); ast_t* create_body = ast_childidx(create, 6); ast_t* apply_params = ast_childidx(apply, 3); ast_t* apply_body = ast_childidx(apply, 6); ast_t* call_namedargs = ast_childidx(call, 1); // Add the receiver to the anonymous type. ast_t* r_id = ast_from_string(receiver, package_hygienic_id(t)); ast_t* r_field_id = ast_from_string(receiver, package_hygienic_id(t)); ast_t* r_type = ast_type(receiver); if(is_typecheck_error(r_type)) return false; // A field in the type. BUILD(r_field, receiver, NODE(TK_FLET, TREE(r_field_id) TREE(r_type) NONE)); // A parameter of the constructor. BUILD(r_ctor_param, receiver, NODE(TK_PARAM, TREE(r_id) TREE(r_type) NONE)); // An assignment in the constructor body. BUILD(r_assign, receiver, NODE(TK_ASSIGN, NODE(TK_CONSUME, NODE(TK_NONE) NODE(TK_REFERENCE, TREE(r_id))) NODE(TK_REFERENCE, TREE(r_field_id)))); // A named argument at the call site. BUILD(r_call_seq, receiver, NODE(TK_SEQ, TREE(receiver))); BUILD(r_call_arg, receiver, NODE(TK_NAMEDARG, TREE(r_id) TREE(r_call_seq))); ast_settype(r_call_seq, r_type); ast_append(class_members, r_field); ast_append(create_params, r_ctor_param); ast_append(create_body, r_assign); ast_append(call_namedargs, r_call_arg); // Add a call to the original method to the apply body. BUILD(apply_call, ast, NODE(TK_CALL, NODE(TK_POSITIONALARGS) NONE NODE(TK_DOT, NODE(TK_REFERENCE, TREE(r_field_id)) TREE(method)))); ast_append(apply_body, apply_call); ast_t* apply_args = ast_child(apply_call); // Add the arguments to the anonymous type. ast_t* arg = ast_child(positional); ast_t* param = ast_child(params); while(arg != NULL) { AST_GET_CHILDREN(param, id, p_type); if(ast_id(arg) == TK_NONE) { // A parameter of the apply method, using the same name, type and default // argument. ast_append(apply_params, param); // An arg in the call to the original method. BUILD(apply_arg, param, NODE(TK_SEQ, NODE(TK_CONSUME, NODE(TK_NONE) NODE(TK_REFERENCE, TREE(id))))); ast_append(apply_args, apply_arg); } else { ast_t* p_id = ast_from_string(id, package_hygienic_id(t)); // A field in the type. BUILD(field, arg, NODE(TK_FLET, TREE(id) TREE(p_type) NONE)); // A parameter of the constructor. BUILD(ctor_param, arg, NODE(TK_PARAM, TREE(p_id) TREE(p_type) NONE)); // An assignment in the constructor body. BUILD(assign, arg, NODE(TK_ASSIGN, NODE(TK_CONSUME, NODE(TK_NONE) NODE(TK_REFERENCE, TREE(p_id))) NODE(TK_REFERENCE, TREE(id)))); // A named argument at the call site. BUILD(call_arg, arg, NODE(TK_NAMEDARG, TREE(p_id) TREE(arg))); // An arg in the call to the original method. BUILD(apply_arg, arg, NODE(TK_SEQ, NODE(TK_REFERENCE, TREE(id)))); ast_append(class_members, field); ast_append(create_params, ctor_param); ast_append(create_body, assign); ast_append(call_namedargs, call_arg); ast_append(apply_args, apply_arg); } arg = ast_sibling(arg); param = ast_sibling(param); } // Add create and apply to the anonymous type. ast_append(class_members, create); ast_append(class_members, apply); // Typecheck the anonymous type. ast_add(t->frame->module, def); if(!type_passes(def, opt)) return false; // Typecheck the create call. if(!expr_reference(opt, &call_receiver)) return false; if(!expr_dot(opt, &call_dot)) return false; if(!expr_call(opt, &call)) return false; // Replace the partial application with the create call. ast_replace(astp, call); return true; }