// Collect the given type parameter static void collect_type_param(ast_t* orig_param, ast_t* params, ast_t* args) { assert(orig_param != NULL); // Get original type parameter info AST_GET_CHILDREN(orig_param, id, constraint, deflt); const char* name = ast_name(id); constraint = sanitise_type(constraint); assert(constraint != NULL); // New type parameter has the same constraint as the old one (sanitised) if(params != NULL) { BUILD(new_param, orig_param, NODE(TK_TYPEPARAM, ID(name) TREE(constraint) NONE)); ast_append(params, new_param); ast_setid(params, TK_TYPEPARAMS); } // New type arguments binds to old type parameter if(args != NULL) { BUILD(new_arg, orig_param, NODE(TK_NOMINAL, NONE // Package ID(name) NONE // Type args NONE // cap NONE)); // ephemeral ast_append(args, new_arg); ast_setid(args, TK_TYPEARGS); } }
// Collect the given type parameter static void collect_type_param(ast_t* orig_param, ast_t* params, ast_t* args) { assert(orig_param != NULL); assert(params != NULL); assert(args != NULL); // Get original type parameter info AST_GET_CHILDREN(orig_param, id, constraint, deflt); const char* name = ast_name(id); constraint = sanitise_type(constraint); assert(constraint != NULL); // New type parameter has the same constraint as the old one (sanitised) BUILD(new_param, orig_param, NODE(TK_TYPEPARAM, ID(name) TREE(constraint) NONE)); ast_append(params, new_param); // New type arguments binds to old type parameter BUILD(new_arg, orig_param, NODE(TK_TYPEPARAMREF, DATA(orig_param) ID(name) NONE // cap NONE)); // ephemeral ast_append(args, new_arg); // Since we have a type parameter the params and args node should not be // TK_NONE ast_setid(params, TK_TYPEPARAMS); ast_setid(args, TK_TYPEARGS); }
// Process the given capture and create the AST for the corresponding field. // Returns the create field AST, which must be freed by the caller. // Returns NULL on error. static ast_t* make_capture_field(pass_opt_t* opt, ast_t* capture) { assert(capture != NULL); AST_GET_CHILDREN(capture, id_node, type, value); const char* name = ast_name(id_node); // There are 3 varieties of capture: // x -> capture variable x, type from defn of x // x = y -> capture expression y, type inferred from expression type // x: T = y -> capture expression y, type T if(ast_id(value) == TK_NONE) { // Variable capture assert(ast_id(type) == TK_NONE); ast_t* def = ast_get(capture, name, NULL); if(def == NULL) { ast_error(opt->check.errors, id_node, "cannot capture \"%s\", variable not defined", name); return NULL; } token_id def_id = ast_id(def); if(def_id != TK_ID && def_id != TK_FVAR && def_id != TK_FLET && def_id != TK_PARAM) { ast_error(opt->check.errors, id_node, "cannot capture \"%s\", can only " "capture fields, parameters and local variables", name); return NULL; } BUILD(capture_rhs, id_node, NODE(TK_REFERENCE, ID(name))); type = alias(ast_type(def)); value = capture_rhs; } else if(ast_id(type) == TK_NONE) { // No type specified, use type of the captured expression type = alias(ast_type(value)); } else { // Type given, infer literals if(!coerce_literals(&value, type, opt)) return NULL; } if(is_typecheck_error(type)) return NULL; type = sanitise_type(type); BUILD(field, id_node, NODE(TK_FVAR, TREE(id_node) TREE(type) TREE(value) NONE)); // Delegate type return field; }
// 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); }
// 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); }
static bool capture_from_reference(pass_opt_t* opt, ast_t* ctx, ast_t* ast, ast_t* captures, ast_t** last_capture) { const char* name = ast_name(ast_child(ast)); if(is_name_dontcare(name)) return true; ast_t* refdef = ast_get(ast, name, NULL); if(refdef != NULL) return true; refdef = ast_get(ctx, name, NULL); if(refdef == NULL) { ast_error(opt->check.errors, ast, "cannot capture \"%s\", variable not defined", name); return false; } if(!def_before_use(opt, refdef, ctx, name)) return false; switch(ast_id(refdef)) { case TK_VAR: case TK_LET: case TK_PARAM: case TK_MATCH_CAPTURE: case TK_FVAR: case TK_FLET: case TK_EMBED: break; default: ast_error(opt->check.errors, ast, "cannot capture \"%s\", can only " "capture fields, parameters and local variables", name); return NULL; } // Check if we've already captured it for(ast_t* p = ast_child(captures); p != NULL; p = ast_sibling(p)) { AST_GET_CHILDREN(p, c_name, c_type); if(name == ast_name(c_name)) return true; } ast_t* type = alias(ast_type(refdef)); if(is_typecheck_error(type)) return false; type = sanitise_type(type); BUILD(field, ast, NODE(TK_FVAR, ID(name) TREE(type) NODE(TK_REFERENCE, ID(name)))); ast_list_append(captures, last_capture, field); return true; }
// Process the given capture and create the AST for the corresponding field. // Returns the create field AST in out_field, which must be freed by the caller, // or NULL if there is no field to create. // Returns false on error. static bool make_capture_field(pass_opt_t* opt, ast_t* capture, ast_t** out_field) { pony_assert(capture != NULL); pony_assert(out_field != NULL); AST_GET_CHILDREN(capture, id_node, type, value); const char* name = ast_name(id_node); bool is_dontcare = is_name_dontcare(name); // There are 3 varieties of capture: // x -> capture variable x, type from defn of x // x = y -> capture expression y, type inferred from expression type // x: T = y -> capture expression y, type T if(ast_id(value) == TK_NONE) { // Variable capture pony_assert(ast_id(type) == TK_NONE); if(is_dontcare) { *out_field = NULL; return true; } ast_t* def = ast_get(capture, name, NULL); if(def == NULL) { ast_error(opt->check.errors, id_node, "cannot capture \"%s\", variable not defined", name); return false; } // lambda captures used before their declaration with their type // not defined are not legal if(!def_before_use(opt, def, capture, name)) return false; switch(ast_id(def)) { case TK_VAR: case TK_LET: case TK_PARAM: case TK_MATCH_CAPTURE: case TK_FVAR: case TK_FLET: case TK_EMBED: break; default: ast_error(opt->check.errors, id_node, "cannot capture \"%s\", can only " "capture fields, parameters and local variables", name); return false; } BUILD(capture_rhs, id_node, NODE(TK_REFERENCE, ID(name))); type = alias(ast_type(def)); value = capture_rhs; } else if(ast_id(type) == TK_NONE) { // No type specified, use type of the captured expression if(ast_type(value) == NULL) return false; type = alias(ast_type(value)); } else { // Type given, infer literals if(!coerce_literals(&value, type, opt)) return false; // Check subtyping now if we're capturing into '_', since the field will be // discarded. if(is_dontcare) { ast_t* v_type = alias(ast_type(value)); errorframe_t info = NULL; if(!is_subtype(v_type, type, &info, opt)) { errorframe_t frame = NULL; ast_error_frame(&frame, value, "argument not a subtype of parameter"); errorframe_append(&frame, &info); errorframe_report(&frame, opt->check.errors); ast_free_unattached(v_type); return false; } ast_free_unattached(v_type); } } if(is_typecheck_error(type)) return false; if(is_dontcare) { *out_field = NULL; return true; } type = sanitise_type(type); BUILD(field, id_node, NODE(TK_FVAR, TREE(id_node) TREE(type) TREE(value))); *out_field = field; return true; }