compiler: Add support for method values.
From-SVN: r200379
This commit is contained in:
parent
39953c7972
commit
571d3f918f
@ -1090,6 +1090,15 @@ Set_and_use_temporary_expression::do_type()
|
||||
return this->statement_->type();
|
||||
}
|
||||
|
||||
// Determine the type of the expression.
|
||||
|
||||
void
|
||||
Set_and_use_temporary_expression::do_determine_type(
|
||||
const Type_context* context)
|
||||
{
|
||||
this->expr_->determine_type(context);
|
||||
}
|
||||
|
||||
// Take the address.
|
||||
|
||||
void
|
||||
@ -6626,20 +6635,49 @@ Bound_method_expression::do_traverse(Traverse* traverse)
|
||||
return Expression::traverse(&this->expr_, traverse);
|
||||
}
|
||||
|
||||
// Lower the expression. If this is a method value rather than being
|
||||
// called, and the method is accessed via a pointer, we may need to
|
||||
// add nil checks. Introduce a temporary variable so that those nil
|
||||
// checks do not cause multiple evaluation.
|
||||
|
||||
Expression*
|
||||
Bound_method_expression::do_lower(Gogo*, Named_object*,
|
||||
Statement_inserter* inserter, int)
|
||||
{
|
||||
// For simplicity we use a temporary for every call to an embedded
|
||||
// method, even though some of them might be pure value methods and
|
||||
// not require a temporary.
|
||||
if (this->expr_->var_expression() == NULL
|
||||
&& this->expr_->temporary_reference_expression() == NULL
|
||||
&& this->expr_->set_and_use_temporary_expression() == NULL
|
||||
&& (this->method_->field_indexes() != NULL
|
||||
|| (this->method_->is_value_method()
|
||||
&& this->expr_->type()->points_to() != NULL)))
|
||||
{
|
||||
Temporary_statement* temp =
|
||||
Statement::make_temporary(this->expr_->type(), NULL, this->location());
|
||||
inserter->insert(temp);
|
||||
this->expr_ = Expression::make_set_and_use_temporary(temp, this->expr_,
|
||||
this->location());
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
// Return the type of a bound method expression. The type of this
|
||||
// object is really the type of the method with no receiver. We
|
||||
// should be able to get away with just returning the type of the
|
||||
// method.
|
||||
// object is simply the type of the method with no receiver.
|
||||
|
||||
Type*
|
||||
Bound_method_expression::do_type()
|
||||
{
|
||||
if (this->method_->is_function())
|
||||
return this->method_->func_value()->type();
|
||||
else if (this->method_->is_function_declaration())
|
||||
return this->method_->func_declaration_value()->type();
|
||||
Named_object* fn = this->method_->named_object();
|
||||
Function_type* fntype;
|
||||
if (fn->is_function())
|
||||
fntype = fn->func_value()->type();
|
||||
else if (fn->is_function_declaration())
|
||||
fntype = fn->func_declaration_value()->type();
|
||||
else
|
||||
return Type::make_error_type();
|
||||
return fntype->copy_without_receiver();
|
||||
}
|
||||
|
||||
// Determine the types of a method expression.
|
||||
@ -6647,7 +6685,14 @@ Bound_method_expression::do_type()
|
||||
void
|
||||
Bound_method_expression::do_determine_type(const Type_context*)
|
||||
{
|
||||
Function_type* fntype = this->type()->function_type();
|
||||
Named_object* fn = this->method_->named_object();
|
||||
Function_type* fntype;
|
||||
if (fn->is_function())
|
||||
fntype = fn->func_value()->type();
|
||||
else if (fn->is_function_declaration())
|
||||
fntype = fn->func_declaration_value()->type();
|
||||
else
|
||||
fntype = NULL;
|
||||
if (fntype == NULL || !fntype->is_method())
|
||||
this->expr_->determine_type_no_context();
|
||||
else
|
||||
@ -6662,31 +6707,278 @@ Bound_method_expression::do_determine_type(const Type_context*)
|
||||
void
|
||||
Bound_method_expression::do_check_types(Gogo*)
|
||||
{
|
||||
if (!this->method_->is_function()
|
||||
&& !this->method_->is_function_declaration())
|
||||
this->report_error(_("object is not a method"));
|
||||
else
|
||||
Named_object* fn = this->method_->named_object();
|
||||
if (!fn->is_function() && !fn->is_function_declaration())
|
||||
{
|
||||
Type* rtype = this->type()->function_type()->receiver()->type()->deref();
|
||||
Type* etype = (this->expr_type_ != NULL
|
||||
? this->expr_type_
|
||||
: this->expr_->type());
|
||||
etype = etype->deref();
|
||||
if (!Type::are_identical(rtype, etype, true, NULL))
|
||||
this->report_error(_("method type does not match object type"));
|
||||
this->report_error(_("object is not a method"));
|
||||
return;
|
||||
}
|
||||
|
||||
Function_type* fntype;
|
||||
if (fn->is_function())
|
||||
fntype = fn->func_value()->type();
|
||||
else if (fn->is_function_declaration())
|
||||
fntype = fn->func_declaration_value()->type();
|
||||
else
|
||||
go_unreachable();
|
||||
Type* rtype = fntype->receiver()->type()->deref();
|
||||
Type* etype = (this->expr_type_ != NULL
|
||||
? this->expr_type_
|
||||
: this->expr_->type());
|
||||
etype = etype->deref();
|
||||
if (!Type::are_identical(rtype, etype, true, NULL))
|
||||
this->report_error(_("method type does not match object type"));
|
||||
}
|
||||
|
||||
// Get the tree for a method expression. There is no standard tree
|
||||
// representation for this. The only places it may currently be used
|
||||
// are in a Call_expression or a Go_statement, which will take it
|
||||
// apart directly. So this has nothing to do at present.
|
||||
// If a bound method expression is not simply called, then it is
|
||||
// represented as a closure. The closure will hold a single variable,
|
||||
// the receiver to pass to the method. The function will be a simple
|
||||
// thunk that pulls that value from the closure and calls the method
|
||||
// with the remaining arguments.
|
||||
//
|
||||
// Because method values are not common, we don't build all thunks for
|
||||
// every methods, but instead only build them as we need them. In
|
||||
// particular, we even build them on demand for methods defined in
|
||||
// other packages.
|
||||
|
||||
Bound_method_expression::Method_value_thunks
|
||||
Bound_method_expression::method_value_thunks;
|
||||
|
||||
// Find or create the thunk for METHOD.
|
||||
|
||||
Named_object*
|
||||
Bound_method_expression::create_thunk(Gogo* gogo, const Method* method,
|
||||
Named_object* fn)
|
||||
{
|
||||
std::pair<Named_object*, Named_object*> val(fn, NULL);
|
||||
std::pair<Method_value_thunks::iterator, bool> ins =
|
||||
Bound_method_expression::method_value_thunks.insert(val);
|
||||
if (!ins.second)
|
||||
{
|
||||
// We have seen this method before.
|
||||
go_assert(ins.first->second != NULL);
|
||||
return ins.first->second;
|
||||
}
|
||||
|
||||
Location loc = fn->location();
|
||||
|
||||
Function_type* orig_fntype;
|
||||
if (fn->is_function())
|
||||
orig_fntype = fn->func_value()->type();
|
||||
else if (fn->is_function_declaration())
|
||||
orig_fntype = fn->func_declaration_value()->type();
|
||||
else
|
||||
orig_fntype = NULL;
|
||||
|
||||
if (orig_fntype == NULL || !orig_fntype->is_method())
|
||||
{
|
||||
ins.first->second = Named_object::make_erroneous_name(Gogo::thunk_name());
|
||||
return ins.first->second;
|
||||
}
|
||||
|
||||
Struct_field_list* sfl = new Struct_field_list();
|
||||
// The type here is wrong--it should be new_fntype. But we don't
|
||||
// have new_fntype yet, and it doesn't really matter.
|
||||
Type* vt = Type::make_pointer_type(Type::make_void_type());
|
||||
sfl->push_back(Struct_field(Typed_identifier("fn.0", vt, loc)));
|
||||
sfl->push_back(Struct_field(Typed_identifier("val.1",
|
||||
orig_fntype->receiver()->type(),
|
||||
loc)));
|
||||
Type* closure_type = Type::make_struct_type(sfl, loc);
|
||||
closure_type = Type::make_pointer_type(closure_type);
|
||||
|
||||
Function_type* new_fntype = orig_fntype->copy_with_closure(closure_type);
|
||||
|
||||
Named_object* new_no = gogo->start_function(Gogo::thunk_name(), new_fntype,
|
||||
false, loc);
|
||||
|
||||
gogo->start_block(loc);
|
||||
|
||||
Named_object* cp = gogo->lookup("closure.0", NULL);
|
||||
go_assert(cp != NULL
|
||||
&& cp->is_variable()
|
||||
&& cp->var_value()->is_parameter());
|
||||
|
||||
// Field 0 of the closure is the function code pointer, field 1 is
|
||||
// the value on which to invoke the method.
|
||||
Expression* arg = Expression::make_var_reference(cp, loc);
|
||||
arg = Expression::make_unary(OPERATOR_MULT, arg, loc);
|
||||
arg = Expression::make_field_reference(arg, 1, loc);
|
||||
|
||||
Expression* bme = Expression::make_bound_method(arg, method, fn, loc);
|
||||
|
||||
const Typed_identifier_list* orig_params = orig_fntype->parameters();
|
||||
Expression_list* args;
|
||||
if (orig_params == NULL || orig_params->empty())
|
||||
args = NULL;
|
||||
else
|
||||
{
|
||||
const Typed_identifier_list* new_params = new_fntype->parameters();
|
||||
args = new Expression_list();
|
||||
for (Typed_identifier_list::const_iterator p = new_params->begin();
|
||||
p + 1 != new_params->end();
|
||||
++p)
|
||||
{
|
||||
Named_object* p_no = gogo->lookup(p->name(), NULL);
|
||||
go_assert(p_no != NULL
|
||||
&& p_no->is_variable()
|
||||
&& p_no->var_value()->is_parameter());
|
||||
args->push_back(Expression::make_var_reference(p_no, loc));
|
||||
}
|
||||
}
|
||||
|
||||
Call_expression* call = Expression::make_call(bme, args,
|
||||
orig_fntype->is_varargs(),
|
||||
loc);
|
||||
call->set_varargs_are_lowered();
|
||||
|
||||
Statement* s = Statement::make_return_from_call(call, loc);
|
||||
gogo->add_statement(s);
|
||||
Block* b = gogo->finish_block(loc);
|
||||
gogo->add_block(b, loc);
|
||||
gogo->lower_block(new_no, b);
|
||||
gogo->finish_function(loc);
|
||||
|
||||
ins.first->second = new_no;
|
||||
return new_no;
|
||||
}
|
||||
|
||||
// Return an expression to check *REF for nil while dereferencing
|
||||
// according to FIELD_INDEXES. Update *REF to build up the field
|
||||
// reference. This is a static function so that we don't have to
|
||||
// worry about declaring Field_indexes in expressions.h.
|
||||
|
||||
static Expression*
|
||||
bme_check_nil(const Method::Field_indexes* field_indexes, Location loc,
|
||||
Expression** ref)
|
||||
{
|
||||
if (field_indexes == NULL)
|
||||
return Expression::make_boolean(false, loc);
|
||||
Expression* cond = bme_check_nil(field_indexes->next, loc, ref);
|
||||
Struct_type* stype = (*ref)->type()->deref()->struct_type();
|
||||
go_assert(stype != NULL
|
||||
&& field_indexes->field_index < stype->field_count());
|
||||
if ((*ref)->type()->struct_type() == NULL)
|
||||
{
|
||||
go_assert((*ref)->type()->points_to() != NULL);
|
||||
Expression* n = Expression::make_binary(OPERATOR_EQEQ, *ref,
|
||||
Expression::make_nil(loc),
|
||||
loc);
|
||||
cond = Expression::make_binary(OPERATOR_OROR, cond, n, loc);
|
||||
*ref = Expression::make_unary(OPERATOR_MULT, *ref, loc);
|
||||
go_assert((*ref)->type()->struct_type() == stype);
|
||||
}
|
||||
*ref = Expression::make_field_reference(*ref, field_indexes->field_index,
|
||||
loc);
|
||||
return cond;
|
||||
}
|
||||
|
||||
// Get the tree for a method value.
|
||||
|
||||
tree
|
||||
Bound_method_expression::do_get_tree(Translate_context*)
|
||||
Bound_method_expression::do_get_tree(Translate_context* context)
|
||||
{
|
||||
error_at(this->location(), "reference to method other than calling it");
|
||||
return error_mark_node;
|
||||
Named_object* thunk = Bound_method_expression::create_thunk(context->gogo(),
|
||||
this->method_,
|
||||
this->function_);
|
||||
if (thunk->is_erroneous())
|
||||
{
|
||||
go_assert(saw_errors());
|
||||
return error_mark_node;
|
||||
}
|
||||
|
||||
// FIXME: We should lower this earlier, but we can't lower it in the
|
||||
// lowering pass because at that point we don't know whether we need
|
||||
// to create the thunk or not. If the expression is called, we
|
||||
// don't need the thunk.
|
||||
|
||||
Location loc = this->location();
|
||||
|
||||
// If the method expects a value, and we have a pointer, we need to
|
||||
// dereference the pointer.
|
||||
|
||||
Named_object* fn = this->method_->named_object();
|
||||
Function_type* fntype;
|
||||
if (fn->is_function())
|
||||
fntype = fn->func_value()->type();
|
||||
else if (fn->is_function_declaration())
|
||||
fntype = fn->func_declaration_value()->type();
|
||||
else
|
||||
go_unreachable();
|
||||
|
||||
Expression* val = this->expr_;
|
||||
if (fntype->receiver()->type()->points_to() == NULL
|
||||
&& val->type()->points_to() != NULL)
|
||||
val = Expression::make_unary(OPERATOR_MULT, val, loc);
|
||||
|
||||
// Note that we are ignoring this->expr_type_ here. The thunk will
|
||||
// expect a closure whose second field has type this->expr_type_ (if
|
||||
// that is not NULL). We are going to pass it a closure whose
|
||||
// second field has type this->expr_->type(). Since
|
||||
// this->expr_type_ is only not-NULL for pointer types, we can get
|
||||
// away with this.
|
||||
|
||||
Struct_field_list* fields = new Struct_field_list();
|
||||
fields->push_back(Struct_field(Typed_identifier("fn.0",
|
||||
thunk->func_value()->type(),
|
||||
loc)));
|
||||
fields->push_back(Struct_field(Typed_identifier("val.1", val->type(), loc)));
|
||||
Struct_type* st = Type::make_struct_type(fields, loc);
|
||||
|
||||
Expression_list* vals = new Expression_list();
|
||||
vals->push_back(Expression::make_func_code_reference(thunk, loc));
|
||||
vals->push_back(val);
|
||||
|
||||
Expression* ret = Expression::make_struct_composite_literal(st, vals, loc);
|
||||
ret = Expression::make_heap_composite(ret, loc);
|
||||
|
||||
tree ret_tree = ret->get_tree(context);
|
||||
|
||||
Expression* nil_check = NULL;
|
||||
|
||||
// See whether the expression or any embedded pointers are nil.
|
||||
|
||||
Expression* expr = this->expr_;
|
||||
if (this->method_->field_indexes() != NULL)
|
||||
{
|
||||
// Note that we are evaluating this->expr_ twice, but that is OK
|
||||
// because in the lowering pass we forced it into a temporary
|
||||
// variable.
|
||||
Expression* ref = expr;
|
||||
nil_check = bme_check_nil(this->method_->field_indexes(), loc, &ref);
|
||||
expr = ref;
|
||||
}
|
||||
|
||||
if (this->method_->is_value_method() && expr->type()->points_to() != NULL)
|
||||
{
|
||||
Expression* n = Expression::make_binary(OPERATOR_EQEQ, expr,
|
||||
Expression::make_nil(loc),
|
||||
loc);
|
||||
if (nil_check == NULL)
|
||||
nil_check = n;
|
||||
else
|
||||
nil_check = Expression::make_binary(OPERATOR_OROR, nil_check, n, loc);
|
||||
}
|
||||
|
||||
if (nil_check != NULL)
|
||||
{
|
||||
tree nil_check_tree = nil_check->get_tree(context);
|
||||
tree crash =
|
||||
context->gogo()->runtime_error(RUNTIME_ERROR_NIL_DEREFERENCE, loc);
|
||||
if (ret_tree == error_mark_node
|
||||
|| nil_check_tree == error_mark_node
|
||||
|| crash == error_mark_node)
|
||||
return error_mark_node;
|
||||
|
||||
ret_tree = fold_build2_loc(loc.gcc_location(), COMPOUND_EXPR,
|
||||
TREE_TYPE(ret_tree),
|
||||
build3_loc(loc.gcc_location(), COND_EXPR,
|
||||
void_type_node, nil_check_tree,
|
||||
crash, NULL_TREE),
|
||||
ret_tree);
|
||||
}
|
||||
|
||||
return ret_tree;
|
||||
}
|
||||
|
||||
// Dump ast representation of a bound method expression.
|
||||
@ -6705,16 +6997,16 @@ Bound_method_expression::do_dump_expression(Ast_dump_context* ast_dump_context)
|
||||
ast_dump_context->ostream() << ")";
|
||||
}
|
||||
|
||||
ast_dump_context->ostream() << "." << this->method_->name();
|
||||
ast_dump_context->ostream() << "." << this->function_->name();
|
||||
}
|
||||
|
||||
// Make a method expression.
|
||||
|
||||
Bound_method_expression*
|
||||
Expression::make_bound_method(Expression* expr, Named_object* method,
|
||||
Location location)
|
||||
Expression::make_bound_method(Expression* expr, const Method* method,
|
||||
Named_object* function, Location location)
|
||||
{
|
||||
return new Bound_method_expression(expr, method, location);
|
||||
return new Bound_method_expression(expr, method, function, location);
|
||||
}
|
||||
|
||||
// Class Builtin_call_expression. This is used for a call to a
|
||||
@ -8921,7 +9213,7 @@ Call_expression::do_lower(Gogo* gogo, Named_object* function,
|
||||
Bound_method_expression* bme = this->fn_->bound_method_expression();
|
||||
if (bme != NULL)
|
||||
{
|
||||
Named_object* method = bme->method();
|
||||
Named_object* methodfn = bme->function();
|
||||
Expression* first_arg = bme->first_argument();
|
||||
|
||||
// We always pass a pointer when calling a method.
|
||||
@ -8962,7 +9254,7 @@ Call_expression::do_lower(Gogo* gogo, Named_object* function,
|
||||
// old arguments, because we may be traversing them up in some
|
||||
// caller. FIXME.
|
||||
this->args_ = new_args;
|
||||
this->fn_ = Expression::make_func_reference(method, NULL,
|
||||
this->fn_ = Expression::make_func_reference(methodfn, NULL,
|
||||
bme->location());
|
||||
}
|
||||
|
||||
@ -11158,6 +11450,28 @@ Interface_field_reference_expression::do_traverse(Traverse* traverse)
|
||||
return Expression::traverse(&this->expr_, traverse);
|
||||
}
|
||||
|
||||
// Lower the expression. If this expression is not called, we need to
|
||||
// evaluate the expression twice when converting to the backend
|
||||
// interface. So introduce a temporary variable if necessary.
|
||||
|
||||
Expression*
|
||||
Interface_field_reference_expression::do_lower(Gogo*, Named_object*,
|
||||
Statement_inserter* inserter,
|
||||
int)
|
||||
{
|
||||
if (this->expr_->var_expression() == NULL
|
||||
&& this->expr_->temporary_reference_expression() == NULL
|
||||
&& this->expr_->set_and_use_temporary_expression() == NULL)
|
||||
{
|
||||
Temporary_statement* temp =
|
||||
Statement::make_temporary(this->expr_->type(), NULL, this->location());
|
||||
inserter->insert(temp);
|
||||
this->expr_ = Expression::make_set_and_use_temporary(temp, this->expr_,
|
||||
this->location());
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
// Return the type of an interface field reference.
|
||||
|
||||
Type*
|
||||
@ -11218,18 +11532,188 @@ Interface_field_reference_expression::do_check_types(Gogo*)
|
||||
}
|
||||
}
|
||||
|
||||
// Get a tree for a reference to a field in an interface. There is no
|
||||
// standard tree type representation for this: it's a function
|
||||
// attached to its first argument, like a Bound_method_expression.
|
||||
// The only places it may currently be used are in a Call_expression
|
||||
// or a Go_statement, which will take it apart directly. So this has
|
||||
// nothing to do at present.
|
||||
// If an interface field reference is not simply called, then it is
|
||||
// represented as a closure. The closure will hold a single variable,
|
||||
// the value of the interface on which the method should be called.
|
||||
// The function will be a simple thunk that pulls the value from the
|
||||
// closure and calls the method with the remaining arguments.
|
||||
|
||||
// Because method values are not common, we don't build all thunks for
|
||||
// all possible interface methods, but instead only build them as we
|
||||
// need them. In particular, we even build them on demand for
|
||||
// interface methods defined in other packages.
|
||||
|
||||
Interface_field_reference_expression::Interface_method_thunks
|
||||
Interface_field_reference_expression::interface_method_thunks;
|
||||
|
||||
// Find or create the thunk to call method NAME on TYPE.
|
||||
|
||||
Named_object*
|
||||
Interface_field_reference_expression::create_thunk(Gogo* gogo,
|
||||
Interface_type* type,
|
||||
const std::string& name)
|
||||
{
|
||||
std::pair<Interface_type*, Method_thunks*> val(type, NULL);
|
||||
std::pair<Interface_method_thunks::iterator, bool> ins =
|
||||
Interface_field_reference_expression::interface_method_thunks.insert(val);
|
||||
if (ins.second)
|
||||
{
|
||||
// This is the first time we have seen this interface.
|
||||
ins.first->second = new Method_thunks();
|
||||
}
|
||||
|
||||
for (Method_thunks::const_iterator p = ins.first->second->begin();
|
||||
p != ins.first->second->end();
|
||||
p++)
|
||||
if (p->first == name)
|
||||
return p->second;
|
||||
|
||||
Location loc = type->location();
|
||||
|
||||
const Typed_identifier* method_id = type->find_method(name);
|
||||
if (method_id == NULL)
|
||||
return Named_object::make_erroneous_name(Gogo::thunk_name());
|
||||
|
||||
Function_type* orig_fntype = method_id->type()->function_type();
|
||||
if (orig_fntype == NULL)
|
||||
return Named_object::make_erroneous_name(Gogo::thunk_name());
|
||||
|
||||
Struct_field_list* sfl = new Struct_field_list();
|
||||
// The type here is wrong--it should be new_fntype. But we don't
|
||||
// have new_fntype yet, and it doesn't really matter.
|
||||
Type* vt = Type::make_pointer_type(Type::make_void_type());
|
||||
sfl->push_back(Struct_field(Typed_identifier("fn.0", vt, loc)));
|
||||
sfl->push_back(Struct_field(Typed_identifier("val.1", type, loc)));
|
||||
Type* closure_type = Type::make_struct_type(sfl, loc);
|
||||
closure_type = Type::make_pointer_type(closure_type);
|
||||
|
||||
Function_type* new_fntype = orig_fntype->copy_with_closure(closure_type);
|
||||
|
||||
Named_object* new_no = gogo->start_function(Gogo::thunk_name(), new_fntype,
|
||||
false, loc);
|
||||
|
||||
gogo->start_block(loc);
|
||||
|
||||
Named_object* cp = gogo->lookup("closure.0", NULL);
|
||||
go_assert(cp != NULL
|
||||
&& cp->is_variable()
|
||||
&& cp->var_value()->is_parameter());
|
||||
|
||||
// Field 0 of the closure is the function code pointer, field 1 is
|
||||
// the value on which to invoke the method.
|
||||
Expression* arg = Expression::make_var_reference(cp, loc);
|
||||
arg = Expression::make_unary(OPERATOR_MULT, arg, loc);
|
||||
arg = Expression::make_field_reference(arg, 1, loc);
|
||||
|
||||
Expression *ifre = Expression::make_interface_field_reference(arg, name,
|
||||
loc);
|
||||
|
||||
const Typed_identifier_list* orig_params = orig_fntype->parameters();
|
||||
Expression_list* args;
|
||||
if (orig_params == NULL || orig_params->empty())
|
||||
args = NULL;
|
||||
else
|
||||
{
|
||||
const Typed_identifier_list* new_params = new_fntype->parameters();
|
||||
args = new Expression_list();
|
||||
for (Typed_identifier_list::const_iterator p = new_params->begin();
|
||||
p + 1 != new_params->end();
|
||||
++p)
|
||||
{
|
||||
Named_object* p_no = gogo->lookup(p->name(), NULL);
|
||||
go_assert(p_no != NULL
|
||||
&& p_no->is_variable()
|
||||
&& p_no->var_value()->is_parameter());
|
||||
args->push_back(Expression::make_var_reference(p_no, loc));
|
||||
}
|
||||
}
|
||||
|
||||
Call_expression* call = Expression::make_call(ifre, args,
|
||||
orig_fntype->is_varargs(),
|
||||
loc);
|
||||
call->set_varargs_are_lowered();
|
||||
|
||||
Statement* s = Statement::make_return_from_call(call, loc);
|
||||
gogo->add_statement(s);
|
||||
Block* b = gogo->finish_block(loc);
|
||||
gogo->add_block(b, loc);
|
||||
gogo->lower_block(new_no, b);
|
||||
gogo->finish_function(loc);
|
||||
|
||||
ins.first->second->push_back(std::make_pair(name, new_no));
|
||||
return new_no;
|
||||
}
|
||||
|
||||
// Get a tree for a method value.
|
||||
|
||||
tree
|
||||
Interface_field_reference_expression::do_get_tree(Translate_context*)
|
||||
Interface_field_reference_expression::do_get_tree(Translate_context* context)
|
||||
{
|
||||
error_at(this->location(), "reference to method other than calling it");
|
||||
return error_mark_node;
|
||||
Interface_type* type = this->expr_->type()->interface_type();
|
||||
if (type == NULL)
|
||||
{
|
||||
go_assert(saw_errors());
|
||||
return error_mark_node;
|
||||
}
|
||||
|
||||
Named_object* thunk =
|
||||
Interface_field_reference_expression::create_thunk(context->gogo(),
|
||||
type, this->name_);
|
||||
if (thunk->is_erroneous())
|
||||
{
|
||||
go_assert(saw_errors());
|
||||
return error_mark_node;
|
||||
}
|
||||
|
||||
// FIXME: We should lower this earlier, but we can't it lower it in
|
||||
// the lowering pass because at that point we don't know whether we
|
||||
// need to create the thunk or not. If the expression is called, we
|
||||
// don't need the thunk.
|
||||
|
||||
Location loc = this->location();
|
||||
|
||||
Struct_field_list* fields = new Struct_field_list();
|
||||
fields->push_back(Struct_field(Typed_identifier("fn.0",
|
||||
thunk->func_value()->type(),
|
||||
loc)));
|
||||
fields->push_back(Struct_field(Typed_identifier("val.1",
|
||||
this->expr_->type(),
|
||||
loc)));
|
||||
Struct_type* st = Type::make_struct_type(fields, loc);
|
||||
|
||||
Expression_list* vals = new Expression_list();
|
||||
vals->push_back(Expression::make_func_code_reference(thunk, loc));
|
||||
vals->push_back(this->expr_);
|
||||
|
||||
Expression* expr = Expression::make_struct_composite_literal(st, vals, loc);
|
||||
expr = Expression::make_heap_composite(expr, loc);
|
||||
|
||||
tree closure_tree = expr->get_tree(context);
|
||||
|
||||
// Note that we are evaluating this->expr_ twice, but that is OK
|
||||
// because in the lowering pass we forced it into a temporary
|
||||
// variable.
|
||||
tree expr_tree = this->expr_->get_tree(context);
|
||||
tree nil_check_tree = Expression::comparison_tree(context,
|
||||
Type::lookup_bool_type(),
|
||||
OPERATOR_EQEQ,
|
||||
this->expr_->type(),
|
||||
expr_tree,
|
||||
Type::make_nil_type(),
|
||||
null_pointer_node,
|
||||
loc);
|
||||
tree crash = context->gogo()->runtime_error(RUNTIME_ERROR_NIL_DEREFERENCE,
|
||||
loc);
|
||||
if (closure_tree == error_mark_node
|
||||
|| nil_check_tree == error_mark_node
|
||||
|| crash == error_mark_node)
|
||||
return error_mark_node;
|
||||
return fold_build2_loc(loc.gcc_location(), COMPOUND_EXPR,
|
||||
TREE_TYPE(closure_tree),
|
||||
build3_loc(loc.gcc_location(), COND_EXPR,
|
||||
void_type_node, nil_check_tree, crash,
|
||||
NULL_TREE),
|
||||
closure_tree);
|
||||
}
|
||||
|
||||
// Dump ast representation for an interface field reference.
|
||||
@ -11485,22 +11969,7 @@ Selector_expression::lower_method_expression(Gogo* gogo)
|
||||
method_type->is_varargs(),
|
||||
location);
|
||||
|
||||
size_t count = call->result_count();
|
||||
Statement* s;
|
||||
if (count == 0)
|
||||
s = Statement::make_statement(call, true);
|
||||
else
|
||||
{
|
||||
Expression_list* retvals = new Expression_list();
|
||||
if (count <= 1)
|
||||
retvals->push_back(call);
|
||||
else
|
||||
{
|
||||
for (size_t i = 0; i < count; ++i)
|
||||
retvals->push_back(Expression::make_call_result(call, i));
|
||||
}
|
||||
s = Statement::make_return_statement(retvals, location);
|
||||
}
|
||||
Statement* s = Statement::make_return_from_call(call, location);
|
||||
gogo->add_statement(s);
|
||||
|
||||
Block* b = gogo->finish_block(location);
|
||||
|
@ -16,6 +16,7 @@ class Translate_context;
|
||||
class Traverse;
|
||||
class Statement_inserter;
|
||||
class Type;
|
||||
class Method;
|
||||
struct Type_context;
|
||||
class Integer_type;
|
||||
class Float_type;
|
||||
@ -224,9 +225,11 @@ class Expression
|
||||
make_call_result(Call_expression*, unsigned int index);
|
||||
|
||||
// Make an expression which is a method bound to its first
|
||||
// parameter.
|
||||
// parameter. METHOD is the method being called, FUNCTION is the
|
||||
// function to call.
|
||||
static Bound_method_expression*
|
||||
make_bound_method(Expression* object, Named_object* method, Location);
|
||||
make_bound_method(Expression* object, const Method* method,
|
||||
Named_object* function, Location);
|
||||
|
||||
// Make an index or slice expression. This is a parser expression
|
||||
// which represents LEFT[START:END]. END may be NULL, meaning an
|
||||
@ -1079,8 +1082,7 @@ class Set_and_use_temporary_expression : public Expression
|
||||
do_type();
|
||||
|
||||
void
|
||||
do_determine_type(const Type_context*)
|
||||
{ }
|
||||
do_determine_type(const Type_context*);
|
||||
|
||||
Expression*
|
||||
do_copy()
|
||||
@ -1852,10 +1854,10 @@ class Map_index_expression : public Expression
|
||||
class Bound_method_expression : public Expression
|
||||
{
|
||||
public:
|
||||
Bound_method_expression(Expression* expr, Named_object* method,
|
||||
Location location)
|
||||
Bound_method_expression(Expression* expr, const Method *method,
|
||||
Named_object* function, Location location)
|
||||
: Expression(EXPRESSION_BOUND_METHOD, location),
|
||||
expr_(expr), expr_type_(NULL), method_(method)
|
||||
expr_(expr), expr_type_(NULL), method_(method), function_(function)
|
||||
{ }
|
||||
|
||||
// Return the object which is the first argument.
|
||||
@ -1870,20 +1872,33 @@ class Bound_method_expression : public Expression
|
||||
first_argument_type() const
|
||||
{ return this->expr_type_; }
|
||||
|
||||
// Return the method function.
|
||||
Named_object*
|
||||
method()
|
||||
// Return the method.
|
||||
const Method*
|
||||
method() const
|
||||
{ return this->method_; }
|
||||
|
||||
// Return the function to call.
|
||||
Named_object*
|
||||
function() const
|
||||
{ return this->function_; }
|
||||
|
||||
// Set the implicit type of the expression.
|
||||
void
|
||||
set_first_argument_type(Type* type)
|
||||
{ this->expr_type_ = type; }
|
||||
|
||||
// Create a thunk to call FUNCTION, for METHOD, when it is used as
|
||||
// part of a method value.
|
||||
static Named_object*
|
||||
create_thunk(Gogo*, const Method* method, Named_object* function);
|
||||
|
||||
protected:
|
||||
int
|
||||
do_traverse(Traverse*);
|
||||
|
||||
Expression*
|
||||
do_lower(Gogo*, Named_object*, Statement_inserter*, int);
|
||||
|
||||
Type*
|
||||
do_type();
|
||||
|
||||
@ -1897,7 +1912,7 @@ class Bound_method_expression : public Expression
|
||||
do_copy()
|
||||
{
|
||||
return new Bound_method_expression(this->expr_->copy(), this->method_,
|
||||
this->location());
|
||||
this->function_, this->location());
|
||||
}
|
||||
|
||||
tree
|
||||
@ -1907,6 +1922,11 @@ class Bound_method_expression : public Expression
|
||||
do_dump_expression(Ast_dump_context*) const;
|
||||
|
||||
private:
|
||||
// A mapping from method functions to the thunks we have created for
|
||||
// them.
|
||||
typedef Unordered_map(Named_object*, Named_object*) Method_value_thunks;
|
||||
static Method_value_thunks method_value_thunks;
|
||||
|
||||
// The object used to find the method. This is passed to the method
|
||||
// as the first argument.
|
||||
Expression* expr_;
|
||||
@ -1914,8 +1934,12 @@ class Bound_method_expression : public Expression
|
||||
// NULL in the normal case, non-NULL when using a method from an
|
||||
// anonymous field which does not require a stub.
|
||||
Type* expr_type_;
|
||||
// The method itself.
|
||||
Named_object* method_;
|
||||
// The method.
|
||||
const Method* method_;
|
||||
// The function to call. This is not the same as
|
||||
// method_->named_object() when the method has a stub. This will be
|
||||
// the real function rather than the stub.
|
||||
Named_object* function_;
|
||||
};
|
||||
|
||||
// A reference to a field in a struct.
|
||||
@ -2031,6 +2055,11 @@ class Interface_field_reference_expression : public Expression
|
||||
name() const
|
||||
{ return this->name_; }
|
||||
|
||||
// Create a thunk to call the method NAME in TYPE when it is used as
|
||||
// part of a method value.
|
||||
static Named_object*
|
||||
create_thunk(Gogo*, Interface_type* type, const std::string& name);
|
||||
|
||||
// Return a tree for the pointer to the function to call, given a
|
||||
// tree for the expression.
|
||||
tree
|
||||
@ -2046,6 +2075,9 @@ class Interface_field_reference_expression : public Expression
|
||||
int
|
||||
do_traverse(Traverse* traverse);
|
||||
|
||||
Expression*
|
||||
do_lower(Gogo*, Named_object*, Statement_inserter*, int);
|
||||
|
||||
Type*
|
||||
do_type();
|
||||
|
||||
@ -2070,6 +2102,13 @@ class Interface_field_reference_expression : public Expression
|
||||
do_dump_expression(Ast_dump_context*) const;
|
||||
|
||||
private:
|
||||
// A mapping from interface types to a list of thunks we have
|
||||
// created for methods.
|
||||
typedef std::vector<std::pair<std::string, Named_object*> > Method_thunks;
|
||||
typedef Unordered_map(Interface_type*, Method_thunks*)
|
||||
Interface_method_thunks;
|
||||
static Interface_method_thunks interface_method_thunks;
|
||||
|
||||
// The expression for the interface object. This should have a type
|
||||
// of interface or pointer to interface.
|
||||
Expression* expr_;
|
||||
|
@ -1795,11 +1795,36 @@ Create_function_descriptors::expression(Expression** pexpr)
|
||||
return TRAVERSE_CONTINUE;
|
||||
}
|
||||
|
||||
Bound_method_expression* bme = expr->bound_method_expression();
|
||||
if (bme != NULL)
|
||||
{
|
||||
// We would not get here for a call to this method, so this is a
|
||||
// method value. We need to create a thunk.
|
||||
Bound_method_expression::create_thunk(this->gogo_, bme->method(),
|
||||
bme->function());
|
||||
return TRAVERSE_CONTINUE;
|
||||
}
|
||||
|
||||
Interface_field_reference_expression* ifre =
|
||||
expr->interface_field_reference_expression();
|
||||
if (ifre != NULL)
|
||||
{
|
||||
// We would not get here for a call to this interface method, so
|
||||
// this is a method value. We need to create a thunk.
|
||||
Interface_type* type = ifre->expr()->type()->interface_type();
|
||||
if (type != NULL)
|
||||
Interface_field_reference_expression::create_thunk(this->gogo_, type,
|
||||
ifre->name());
|
||||
return TRAVERSE_CONTINUE;
|
||||
}
|
||||
|
||||
Call_expression* ce = expr->call_expression();
|
||||
if (ce != NULL)
|
||||
{
|
||||
Expression* fn = ce->fn();
|
||||
if (fn->func_expression() != NULL)
|
||||
if (fn->func_expression() != NULL
|
||||
|| fn->bound_method_expression() != NULL
|
||||
|| fn->interface_field_reference_expression() != NULL)
|
||||
{
|
||||
// Traverse the arguments but not the function.
|
||||
Expression_list* args = ce->args();
|
||||
@ -2806,22 +2831,7 @@ Build_recover_thunks::function(Named_object* orig_no)
|
||||
// Any varargs call has already been lowered.
|
||||
call->set_varargs_are_lowered();
|
||||
|
||||
Statement* s;
|
||||
if (orig_fntype->results() == NULL || orig_fntype->results()->empty())
|
||||
s = Statement::make_statement(call, true);
|
||||
else
|
||||
{
|
||||
Expression_list* vals = new Expression_list();
|
||||
size_t rc = orig_fntype->results()->size();
|
||||
if (rc == 1)
|
||||
vals->push_back(call);
|
||||
else
|
||||
{
|
||||
for (size_t i = 0; i < rc; ++i)
|
||||
vals->push_back(Expression::make_call_result(call, i));
|
||||
}
|
||||
s = Statement::make_return_statement(vals, location);
|
||||
}
|
||||
Statement* s = Statement::make_return_from_call(call, location);
|
||||
s->determine_types();
|
||||
gogo->add_statement(s);
|
||||
|
||||
@ -3557,42 +3567,8 @@ Function::make_descriptor_wrapper(Gogo* gogo, Named_object* no,
|
||||
{
|
||||
Location loc = no->location();
|
||||
|
||||
Typed_identifier_list* new_params = new Typed_identifier_list();
|
||||
const Typed_identifier_list* orig_params = orig_fntype->parameters();
|
||||
if (orig_params != NULL && !orig_params->empty())
|
||||
{
|
||||
static int count;
|
||||
char buf[50];
|
||||
for (Typed_identifier_list::const_iterator p = orig_params->begin();
|
||||
p != orig_params->end();
|
||||
++p)
|
||||
{
|
||||
snprintf(buf, sizeof buf, "pt.%u", count);
|
||||
++count;
|
||||
new_params->push_back(Typed_identifier(buf, p->type(),
|
||||
p->location()));
|
||||
}
|
||||
}
|
||||
Type* vt = Type::make_pointer_type(Type::make_void_type());
|
||||
new_params->push_back(Typed_identifier("closure.0", vt, loc));
|
||||
|
||||
const Typed_identifier_list* orig_results = orig_fntype->results();
|
||||
Typed_identifier_list* new_results;
|
||||
if (orig_results == NULL || orig_results->empty())
|
||||
new_results = NULL;
|
||||
else
|
||||
{
|
||||
new_results = new Typed_identifier_list();
|
||||
for (Typed_identifier_list::const_iterator p = orig_results->begin();
|
||||
p != orig_results->end();
|
||||
++p)
|
||||
new_results->push_back(Typed_identifier("", p->type(),
|
||||
p->location()));
|
||||
}
|
||||
|
||||
Function_type* new_fntype = Type::make_function_type(NULL, new_params,
|
||||
new_results,
|
||||
loc);
|
||||
Function_type* new_fntype = orig_fntype->copy_with_closure(vt);
|
||||
|
||||
std::string name = no->name() + "$descriptorfn";
|
||||
Named_object* dno = gogo->start_function(name, new_fntype, false, loc);
|
||||
@ -3602,13 +3578,16 @@ Function::make_descriptor_wrapper(Gogo* gogo, Named_object* no,
|
||||
|
||||
Expression* fn = Expression::make_func_reference(no, NULL, loc);
|
||||
|
||||
// Call the wrapper function, passing all of the arguments except
|
||||
// for the last one (the last argument is the ignored closure).
|
||||
// Call the function begin wrapped, passing all of the arguments
|
||||
// except for the last one (the last argument is the ignored
|
||||
// closure).
|
||||
const Typed_identifier_list* orig_params = orig_fntype->parameters();
|
||||
Expression_list* args;
|
||||
if (orig_params == NULL || orig_params->empty())
|
||||
args = NULL;
|
||||
else
|
||||
{
|
||||
const Typed_identifier_list* new_params = new_fntype->parameters();
|
||||
args = new Expression_list();
|
||||
for (Typed_identifier_list::const_iterator p = new_params->begin();
|
||||
p + 1 != new_params->end();
|
||||
@ -3627,23 +3606,7 @@ Function::make_descriptor_wrapper(Gogo* gogo, Named_object* no,
|
||||
loc);
|
||||
call->set_varargs_are_lowered();
|
||||
|
||||
Statement* s;
|
||||
if (orig_results == NULL || orig_results->empty())
|
||||
s = Statement::make_statement(call, true);
|
||||
else
|
||||
{
|
||||
Expression_list* vals = new Expression_list();
|
||||
size_t rc = orig_results->size();
|
||||
if (rc == 1)
|
||||
vals->push_back(call);
|
||||
else
|
||||
{
|
||||
for (size_t i = 0; i < rc; ++i)
|
||||
vals->push_back(Expression::make_call_result(call, i));
|
||||
}
|
||||
s = Statement::make_return_statement(vals, loc);
|
||||
}
|
||||
|
||||
Statement* s = Statement::make_return_from_call(call, loc);
|
||||
gogo->add_statement(s);
|
||||
Block* b = gogo->finish_block(loc);
|
||||
gogo->add_block(b, loc);
|
||||
|
@ -2815,6 +2815,28 @@ Statement::make_return_statement(Expression_list* vals,
|
||||
return new Return_statement(vals, location);
|
||||
}
|
||||
|
||||
// Make a statement that returns the result of a call expression.
|
||||
|
||||
Statement*
|
||||
Statement::make_return_from_call(Call_expression* call, Location location)
|
||||
{
|
||||
size_t rc = call->result_count();
|
||||
if (rc == 0)
|
||||
return Statement::make_statement(call, true);
|
||||
else
|
||||
{
|
||||
Expression_list* vals = new Expression_list();
|
||||
if (rc == 1)
|
||||
vals->push_back(call);
|
||||
else
|
||||
{
|
||||
for (size_t i = 0; i < rc; ++i)
|
||||
vals->push_back(Expression::make_call_result(call, i));
|
||||
}
|
||||
return Statement::make_return_statement(vals, location);
|
||||
}
|
||||
}
|
||||
|
||||
// A break or continue statement.
|
||||
|
||||
class Bc_statement : public Statement
|
||||
|
@ -207,6 +207,13 @@ class Statement
|
||||
static Return_statement*
|
||||
make_return_statement(Expression_list*, Location);
|
||||
|
||||
// Make a statement that returns the result of a call expression.
|
||||
// If the call does not return any results, this just returns the
|
||||
// call expression as a statement, assuming that the function will
|
||||
// end immediately afterward.
|
||||
static Statement*
|
||||
make_return_from_call(Call_expression*, Location);
|
||||
|
||||
// Make a break statement.
|
||||
static Statement*
|
||||
make_break_statement(Unnamed_label* label, Location);
|
||||
|
@ -3396,7 +3396,8 @@ Function_type::do_get_backend(Gogo* gogo)
|
||||
// passed when invoking the function indirectly, via the struct.
|
||||
|
||||
Location loc = this->location();
|
||||
Btype* struct_type = gogo->backend()->placeholder_struct_type("", loc);
|
||||
Btype* struct_type =
|
||||
gogo->backend()->placeholder_struct_type("__go_descriptor", loc);
|
||||
Btype* ptr_struct_type = gogo->backend()->pointer_type(struct_type);
|
||||
|
||||
Backend::Btyped_identifier breceiver;
|
||||
@ -3835,6 +3836,49 @@ Function_type::copy_with_receiver(Type* receiver_type) const
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Make a copy of a function type ignoring any receiver and adding a
|
||||
// closure parameter.
|
||||
|
||||
Function_type*
|
||||
Function_type::copy_with_closure(Type* closure_type) const
|
||||
{
|
||||
Typed_identifier_list* new_params = new Typed_identifier_list();
|
||||
const Typed_identifier_list* orig_params = this->parameters_;
|
||||
if (orig_params != NULL && !orig_params->empty())
|
||||
{
|
||||
static int count;
|
||||
char buf[50];
|
||||
for (Typed_identifier_list::const_iterator p = orig_params->begin();
|
||||
p != orig_params->end();
|
||||
++p)
|
||||
{
|
||||
snprintf(buf, sizeof buf, "pt.%u", count);
|
||||
++count;
|
||||
new_params->push_back(Typed_identifier(buf, p->type(),
|
||||
p->location()));
|
||||
}
|
||||
}
|
||||
new_params->push_back(Typed_identifier("closure.0", closure_type,
|
||||
this->location_));
|
||||
|
||||
const Typed_identifier_list* orig_results = this->results_;
|
||||
Typed_identifier_list* new_results;
|
||||
if (orig_results == NULL || orig_results->empty())
|
||||
new_results = NULL;
|
||||
else
|
||||
{
|
||||
new_results = new Typed_identifier_list();
|
||||
for (Typed_identifier_list::const_iterator p = orig_results->begin();
|
||||
p != orig_results->end();
|
||||
++p)
|
||||
new_results->push_back(Typed_identifier("", p->type(),
|
||||
p->location()));
|
||||
}
|
||||
|
||||
return Type::make_function_type(NULL, new_params, new_results,
|
||||
this->location());
|
||||
}
|
||||
|
||||
// Make a function type.
|
||||
|
||||
Function_type*
|
||||
@ -7580,7 +7624,7 @@ Method::bind_method(Expression* expr, Location location) const
|
||||
// the child class.
|
||||
return this->do_bind_method(expr, location);
|
||||
}
|
||||
return Expression::make_bound_method(expr, this->stub_, location);
|
||||
return Expression::make_bound_method(expr, this, this->stub_, location);
|
||||
}
|
||||
|
||||
// Return the named object associated with a method. This may only be
|
||||
@ -7623,8 +7667,8 @@ Expression*
|
||||
Named_method::do_bind_method(Expression* expr, Location location) const
|
||||
{
|
||||
Named_object* no = this->named_object_;
|
||||
Bound_method_expression* bme = Expression::make_bound_method(expr, no,
|
||||
location);
|
||||
Bound_method_expression* bme = Expression::make_bound_method(expr, this,
|
||||
no, location);
|
||||
// If this is not a local method, and it does not use a stub, then
|
||||
// the real method expects a different type. We need to cast the
|
||||
// first argument.
|
||||
@ -9002,28 +9046,16 @@ Type::build_one_stub_method(Gogo* gogo, Method* method,
|
||||
Call_expression* call = Expression::make_call(func, arguments, is_varargs,
|
||||
location);
|
||||
call->set_hidden_fields_are_ok();
|
||||
size_t count = call->result_count();
|
||||
if (count == 0)
|
||||
gogo->add_statement(Statement::make_statement(call, true));
|
||||
else
|
||||
{
|
||||
Expression_list* retvals = new Expression_list();
|
||||
if (count <= 1)
|
||||
retvals->push_back(call);
|
||||
else
|
||||
{
|
||||
for (size_t i = 0; i < count; ++i)
|
||||
retvals->push_back(Expression::make_call_result(call, i));
|
||||
}
|
||||
Return_statement* retstat = Statement::make_return_statement(retvals,
|
||||
location);
|
||||
|
||||
Statement* s = Statement::make_return_from_call(call, location);
|
||||
Return_statement* retstat = s->return_statement();
|
||||
if (retstat != NULL)
|
||||
{
|
||||
// We can return values with hidden fields from a stub. This is
|
||||
// necessary if the method is itself hidden.
|
||||
retstat->set_hidden_fields_are_ok();
|
||||
|
||||
gogo->add_statement(retstat);
|
||||
}
|
||||
gogo->add_statement(s);
|
||||
}
|
||||
|
||||
// Apply FIELD_INDEXES to EXPR. The field indexes have to be applied
|
||||
|
@ -1789,6 +1789,12 @@ class Function_type : public Type
|
||||
Function_type*
|
||||
copy_with_receiver(Type*) const;
|
||||
|
||||
// Return a copy of this type ignoring any receiver and adding a
|
||||
// final closure parameter of type CLOSURE_TYPE. This is used when
|
||||
// creating descriptors.
|
||||
Function_type*
|
||||
copy_with_closure(Type* closure_type) const;
|
||||
|
||||
static Type*
|
||||
make_function_type_descriptor_type();
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user