From 17cb85dfb837949cd3a559fe8e99dee1f72463a4 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 23 Jan 2017 16:40:17 -0500 Subject: [PATCH] basic support for functions with variable length arguments See #77 --- doc/langref.md | 2 +- src/all_types.hpp | 11 +++ src/analyze.cpp | 62 ++++++++++++++--- src/analyze.hpp | 3 + src/ast_render.cpp | 12 ++-- src/codegen.cpp | 8 +++ src/ir.cpp | 151 ++++++++++++++++++++++++++++++++-------- src/parser.cpp | 25 ++++--- test/cases/var_args.zig | 16 +++++ test/run_tests.cpp | 31 +++++++-- test/self_hosted.zig | 1 + 11 files changed, 258 insertions(+), 64 deletions(-) create mode 100644 test/cases/var_args.zig diff --git a/doc/langref.md b/doc/langref.md index 94ef5cc97..5df990ad6 100644 --- a/doc/langref.md +++ b/doc/langref.md @@ -33,7 +33,7 @@ FnDef = option("inline" | "extern") FnProto Block ParamDeclList = "(" list(ParamDecl, ",") ")" -ParamDecl = option("noalias" | "comptime") option(Symbol ":") TypeExpr | "..." +ParamDecl = option("noalias" | "comptime") option(Symbol ":") (TypeExpr | "...") Block = "{" list(option(Statement), ";") "}" diff --git a/src/all_types.hpp b/src/all_types.hpp index a12444eb4..76e759a1f 100644 --- a/src/all_types.hpp +++ b/src/all_types.hpp @@ -122,6 +122,11 @@ struct ConstBoundFnValue { IrInstruction *first_arg; }; +struct ConstArgTuple { + size_t start_index; + size_t end_index; +}; + enum ConstValSpecial { ConstValSpecialRuntime, ConstValSpecialStatic, @@ -163,6 +168,7 @@ struct ConstExprValue { ConstPtrValue x_ptr; ImportTableEntry *x_import; Scope *x_block; + ConstArgTuple x_arg_tuple; // populated if special == ConstValSpecialRuntime RuntimeHintErrorUnion rh_error_union; @@ -325,6 +331,7 @@ struct AstNodeParamDecl { AstNode *type; bool is_noalias; bool is_inline; + bool is_var_args; }; struct AstNodeBlock { @@ -936,6 +943,7 @@ enum TypeTableEntryId { TypeTableEntryIdNamespace, TypeTableEntryIdBlock, TypeTableEntryIdBoundFn, + TypeTableEntryIdArgTuple, }; struct TypeTableEntry { @@ -1034,6 +1042,8 @@ struct FnTableEntry { ZigList alloca_list; ZigList variable_list; + + VariableTableEntry *var_args_var; }; uint32_t fn_table_entry_hash(FnTableEntry*); @@ -1155,6 +1165,7 @@ struct CodeGen { TypeTableEntry *entry_environ_enum; TypeTableEntry *entry_oformat_enum; TypeTableEntry *entry_atomic_order_enum; + TypeTableEntry *entry_arg_tuple; } builtin_types; ZigTarget zig_target; diff --git a/src/analyze.cpp b/src/analyze.cpp index 1e83a51b5..aeeefb3ee 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -208,6 +208,7 @@ bool type_is_complete(TypeTableEntry *type_entry) { case TypeTableEntryIdBlock: case TypeTableEntryIdBoundFn: case TypeTableEntryIdEnumTag: + case TypeTableEntryIdArgTuple: return true; } zig_unreachable(); @@ -245,6 +246,7 @@ bool type_has_zero_bits_known(TypeTableEntry *type_entry) { case TypeTableEntryIdBlock: case TypeTableEntryIdBoundFn: case TypeTableEntryIdEnumTag: + case TypeTableEntryIdArgTuple: return true; } zig_unreachable(); @@ -921,6 +923,7 @@ static TypeTableEntry *analyze_fn_type(CodeGen *g, AstNode *proto_node, Scope *c assert(param_node->type == NodeTypeParamDecl); bool param_is_inline = param_node->data.param_decl.is_inline; + bool param_is_var_args = param_node->data.param_decl.is_var_args; if (param_is_inline) { if (fn_type_id.is_extern) { @@ -929,6 +932,13 @@ static TypeTableEntry *analyze_fn_type(CodeGen *g, AstNode *proto_node, Scope *c return g->builtin_types.entry_invalid; } return get_generic_fn_type(g, &fn_type_id); + } else if (param_is_var_args) { + if (fn_type_id.is_extern) { + fn_type_id.param_count = fn_type_id.next_param_index; + continue; + } else { + return get_generic_fn_type(g, &fn_type_id); + } } TypeTableEntry *type_entry = analyze_type_expr(g, child_scope, param_node->data.param_decl.type); @@ -939,6 +949,7 @@ static TypeTableEntry *analyze_fn_type(CodeGen *g, AstNode *proto_node, Scope *c case TypeTableEntryIdUnreachable: case TypeTableEntryIdUndefLit: case TypeTableEntryIdNullLit: + case TypeTableEntryIdArgTuple: add_node_error(g, param_node->data.param_decl.type, buf_sprintf("parameter of type '%s' not allowed", buf_ptr(&type_entry->name))); return g->builtin_types.entry_invalid; @@ -989,6 +1000,7 @@ static TypeTableEntry *analyze_fn_type(CodeGen *g, AstNode *proto_node, Scope *c case TypeTableEntryIdUndefLit: case TypeTableEntryIdNullLit: + case TypeTableEntryIdArgTuple: add_node_error(g, fn_proto->return_type, buf_sprintf("return type '%s' not allowed", buf_ptr(&fn_type_id.return_type->name))); return g->builtin_types.entry_invalid; @@ -1558,13 +1570,6 @@ static void resolve_decl_fn(CodeGen *g, TldFn *tld_fn) { AstNode *fn_def_node = fn_proto->fn_def_node; - if (fn_def_node && fn_proto->is_var_args) { - add_node_error(g, proto_node, - buf_sprintf("variadic arguments only allowed in extern function declarations")); - tld_fn->base.resolution = TldResolutionInvalid; - return; - } - FnTableEntry *fn_table_entry = create_fn(tld_fn->base.source_node); get_fully_qualified_decl_name(&fn_table_entry->symbol_name, &tld_fn->base, '_'); @@ -1799,6 +1804,7 @@ TypeTableEntry *validate_var_type(CodeGen *g, AstNode *source_node, TypeTableEnt case TypeTableEntryIdUndefLit: case TypeTableEntryIdNullLit: case TypeTableEntryIdBlock: + case TypeTableEntryIdArgTuple: add_node_error(g, source_node, buf_sprintf("variable of type '%s' not allowed", buf_ptr(&underlying_type->name))); return g->builtin_types.entry_invalid; @@ -2210,6 +2216,7 @@ static bool is_container(TypeTableEntry *type_entry) { case TypeTableEntryIdBlock: case TypeTableEntryIdBoundFn: case TypeTableEntryIdEnumTag: + case TypeTableEntryIdArgTuple: return false; } zig_unreachable(); @@ -2260,6 +2267,7 @@ void resolve_container_type(CodeGen *g, TypeTableEntry *type_entry) { case TypeTableEntryIdInvalid: case TypeTableEntryIdVar: case TypeTableEntryIdEnumTag: + case TypeTableEntryIdArgTuple: zig_unreachable(); } } @@ -2290,7 +2298,13 @@ void define_local_param_variables(CodeGen *g, FnTableEntry *fn_table_entry, Vari for (size_t i = 0; i < fn_type_id->param_count; i += 1) { FnTypeParamInfo *param_info = &fn_type_id->param_info[i]; AstNode *param_decl_node = get_param_decl_node(fn_table_entry, i); - Buf *param_name = param_decl_node ? param_decl_node->data.param_decl.name : buf_sprintf("arg%zu", i); + Buf *param_name; + bool is_var_args = param_decl_node && param_decl_node->data.param_decl.is_var_args; + if (param_decl_node && !is_var_args) { + param_name = param_decl_node->data.param_decl.name; + } else { + param_name = buf_sprintf("arg%zu", i); + } TypeTableEntry *param_type = param_info->type; bool is_noalias = param_info->is_noalias; @@ -2308,6 +2322,7 @@ void define_local_param_variables(CodeGen *g, FnTableEntry *fn_table_entry, Vari param_name, true, create_const_runtime(param_type)); var->src_arg_index = i; fn_table_entry->child_scope = var->child_scope; + var->shadowable = var->shadowable || is_var_args; if (type_has_bits(param_type)) { fn_table_entry->variable_list.append(var); @@ -2627,6 +2642,7 @@ bool handle_is_ptr(TypeTableEntry *type_entry) { case TypeTableEntryIdBlock: case TypeTableEntryIdBoundFn: case TypeTableEntryIdVar: + case TypeTableEntryIdArgTuple: zig_unreachable(); case TypeTableEntryIdUnreachable: case TypeTableEntryIdVoid: @@ -2742,6 +2758,9 @@ static uint32_t hash_const_val(ConstExprValue *const_val) { case TypeTableEntryIdFloat: case TypeTableEntryIdNumLitFloat: return const_val->data.x_bignum.data.x_float * UINT32_MAX; + case TypeTableEntryIdArgTuple: + return const_val->data.x_arg_tuple.start_index * 281907309 + + const_val->data.x_arg_tuple.end_index * 2290442768; case TypeTableEntryIdPointer: return hash_ptr(const_val->data.x_ptr.base_ptr) + hash_size(const_val->data.x_ptr.index); case TypeTableEntryIdUndefLit: @@ -2805,7 +2824,7 @@ uint32_t generic_fn_type_id_hash(GenericFnTypeId *id) { bool generic_fn_type_id_eql(GenericFnTypeId *a, GenericFnTypeId *b) { assert(a->fn_entry); if (a->fn_entry != b->fn_entry) return false; - assert(a->param_count == b->param_count); + if (a->param_count != b->param_count) return false; for (size_t i = 0; i < a->param_count; i += 1) { ConstExprValue *a_val = &a->params[i]; ConstExprValue *b_val = &b->params[i]; @@ -2904,6 +2923,7 @@ static TypeTableEntry *type_of_first_thing_in_memory(TypeTableEntry *type_entry) case TypeTableEntryIdBlock: case TypeTableEntryIdBoundFn: case TypeTableEntryIdVar: + case TypeTableEntryIdArgTuple: zig_unreachable(); case TypeTableEntryIdArray: return type_of_first_thing_in_memory(type_entry->data.array.child_type); @@ -2946,6 +2966,7 @@ bool type_requires_comptime(TypeTableEntry *type_entry) { case TypeTableEntryIdNamespace: case TypeTableEntryIdBlock: case TypeTableEntryIdBoundFn: + case TypeTableEntryIdArgTuple: return true; case TypeTableEntryIdArray: case TypeTableEntryIdStruct: @@ -3155,6 +3176,20 @@ ConstExprValue *create_const_ptr(CodeGen *g, ConstExprValue *base_ptr, size_t in return const_val; } +void init_const_arg_tuple(CodeGen *g, ConstExprValue *const_val, size_t arg_index_start, size_t arg_index_end) { + const_val->special = ConstValSpecialStatic; + const_val->type = g->builtin_types.entry_arg_tuple; + const_val->data.x_arg_tuple.start_index = arg_index_start; + const_val->data.x_arg_tuple.end_index = arg_index_end; +} + +ConstExprValue *create_const_arg_tuple(CodeGen *g, size_t arg_index_start, size_t arg_index_end) { + ConstExprValue *const_val = allocate(1); + init_const_arg_tuple(g, const_val, arg_index_start, arg_index_end); + return const_val; +} + + void ensure_complete_type(CodeGen *g, TypeTableEntry *type_entry) { if (type_entry->id == TypeTableEntryIdStruct) { if (!type_entry->data.structure.complete) @@ -3245,6 +3280,9 @@ bool const_values_equal(ConstExprValue *a, ConstExprValue *b) { return a->data.x_import == b->data.x_import; case TypeTableEntryIdBlock: return a->data.x_block == b->data.x_block; + case TypeTableEntryIdArgTuple: + return a->data.x_arg_tuple.start_index == b->data.x_arg_tuple.start_index && + a->data.x_arg_tuple.end_index == b->data.x_arg_tuple.end_index; case TypeTableEntryIdBoundFn: case TypeTableEntryIdInvalid: case TypeTableEntryIdUnreachable: @@ -3506,7 +3544,11 @@ void render_const_value(Buf *buf, ConstExprValue *const_val) { buf_appendf(buf, "%s.%s", buf_ptr(&enum_type->name), buf_ptr(field->name)); return; } + case TypeTableEntryIdArgTuple: + { + buf_appendf(buf, "(args value)"); + return; + } } zig_unreachable(); } - diff --git a/src/analyze.hpp b/src/analyze.hpp index ad51caa2b..b74f91a04 100644 --- a/src/analyze.hpp +++ b/src/analyze.hpp @@ -133,4 +133,7 @@ void init_const_slice(CodeGen *g, ConstExprValue *const_val, ConstExprValue *arr size_t start, size_t len, bool is_const); ConstExprValue *create_const_slice(CodeGen *g, ConstExprValue *array_val, size_t start, size_t len, bool is_const); +void init_const_arg_tuple(CodeGen *g, ConstExprValue *const_val, size_t arg_index_start, size_t arg_index_end); +ConstExprValue *create_const_arg_tuple(CodeGen *g, size_t arg_index_start, size_t arg_index_end); + #endif diff --git a/src/ast_render.cpp b/src/ast_render.cpp index 20fcf955c..2166d68cd 100644 --- a/src/ast_render.cpp +++ b/src/ast_render.cpp @@ -393,7 +393,6 @@ static void render_node_extra(AstRender *ar, AstNode *node, bool grouped) { print_symbol(ar, node->data.fn_proto.name); fprintf(ar->f, "("); int arg_count = node->data.fn_proto.params.length; - bool is_var_args = node->data.fn_proto.is_var_args; for (int arg_i = 0; arg_i < arg_count; arg_i += 1) { AstNode *param_decl = node->data.fn_proto.params.at(arg_i); assert(param_decl->type == NodeTypeParamDecl); @@ -404,15 +403,16 @@ static void render_node_extra(AstRender *ar, AstNode *node, bool grouped) { print_symbol(ar, param_decl->data.param_decl.name); fprintf(ar->f, ": "); } - render_node_grouped(ar, param_decl->data.param_decl.type); + if (param_decl->data.param_decl.is_var_args) { + fprintf(ar->f, "..."); + } else { + render_node_grouped(ar, param_decl->data.param_decl.type); + } - if (arg_i + 1 < arg_count || is_var_args) { + if (arg_i + 1 < arg_count) { fprintf(ar->f, ", "); } } - if (is_var_args) { - fprintf(ar->f, "..."); - } fprintf(ar->f, ")"); AstNode *return_type_node = node->data.fn_proto.return_type; diff --git a/src/codegen.cpp b/src/codegen.cpp index 6d67eec83..29508a326 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -2636,6 +2636,7 @@ static LLVMValueRef gen_const_val(CodeGen *g, ConstExprValue *const_val) { case TypeTableEntryIdBlock: case TypeTableEntryIdBoundFn: case TypeTableEntryIdVar: + case TypeTableEntryIdArgTuple: zig_unreachable(); } @@ -3179,6 +3180,12 @@ static void define_builtin_types(CodeGen *g) { buf_init_from_str(&entry->name, "(var)"); g->builtin_types.entry_var = entry; } + { + TypeTableEntry *entry = new_type_table_entry(TypeTableEntryIdArgTuple); + buf_init_from_str(&entry->name, "(args)"); + entry->zero_bits = true; + g->builtin_types.entry_arg_tuple = entry; + } for (size_t int_size_i = 0; int_size_i < array_length(int_sizes_in_bits); int_size_i += 1) { size_t size_in_bits = int_sizes_in_bits[int_size_i]; @@ -3947,6 +3954,7 @@ static void get_c_type(CodeGen *g, TypeTableEntry *type_entry, Buf *out_buf) { case TypeTableEntryIdUndefLit: case TypeTableEntryIdNullLit: case TypeTableEntryIdVar: + case TypeTableEntryIdArgTuple: zig_unreachable(); } } diff --git a/src/ir.cpp b/src/ir.cpp index f516da8c1..e3ab55b72 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -4347,8 +4347,7 @@ static IrInstruction *ir_gen_for_expr(IrBuilder *irb, Scope *parent_scope, AstNo IrInstruction *array_val = ir_build_load_ptr(irb, parent_scope, array_node, array_val_ptr); - IrInstruction *array_type = ir_build_typeof(irb, parent_scope, array_node, array_val); - IrInstruction *pointer_type = ir_build_to_ptr_type(irb, parent_scope, array_node, array_type); + IrInstruction *pointer_type = ir_build_to_ptr_type(irb, parent_scope, array_node, array_val); IrInstruction *elem_var_type; if (node->data.for_expr.elem_is_ptr) { elem_var_type = pointer_type; @@ -6887,6 +6886,7 @@ static TypeTableEntry *ir_analyze_bin_op_cmp(IrAnalyze *ira, IrInstructionBinOp case TypeTableEntryIdNamespace: case TypeTableEntryIdBlock: case TypeTableEntryIdBoundFn: + case TypeTableEntryIdArgTuple: if (!is_equality_cmp) { ir_add_error_node(ira, source_node, buf_sprintf("operator not allowed for type '%s'", buf_ptr(&resolved_type->name))); @@ -7449,6 +7449,7 @@ static TypeTableEntry *ir_analyze_instruction_decl_var(IrAnalyze *ira, IrInstruc case TypeTableEntryIdFn: case TypeTableEntryIdBoundFn: case TypeTableEntryIdEnumTag: + case TypeTableEntryIdArgTuple: // OK break; } @@ -7520,21 +7521,35 @@ static bool ir_analyze_fn_call_generic_arg(IrAnalyze *ira, AstNode *fn_proto_nod { AstNode *param_decl_node = fn_proto_node->data.fn_proto.params.at(*next_proto_i); assert(param_decl_node->type == NodeTypeParamDecl); - AstNode *param_type_node = param_decl_node->data.param_decl.type; - TypeTableEntry *param_type = analyze_type_expr(ira->codegen, *child_scope, param_type_node); - if (param_type->id == TypeTableEntryIdInvalid) - return false; + bool is_var_args = param_decl_node->data.param_decl.is_var_args; + bool arg_part_of_generic_id = false; + IrInstruction *casted_arg; + if (is_var_args) { + arg_part_of_generic_id = true; + casted_arg = arg; + } else { + AstNode *param_type_node = param_decl_node->data.param_decl.type; + TypeTableEntry *param_type = analyze_type_expr(ira->codegen, *child_scope, param_type_node); + if (param_type->id == TypeTableEntryIdInvalid) + return false; - IrInstruction *casted_arg = ir_implicit_cast(ira, arg, param_type); - if (casted_arg->value.type->id == TypeTableEntryIdInvalid) - return false; + bool is_var_type = (param_type->id == TypeTableEntryIdVar); + if (is_var_type) { + arg_part_of_generic_id = true; + casted_arg = arg; + } else { + casted_arg = ir_implicit_cast(ira, arg, param_type); + if (casted_arg->value.type->id == TypeTableEntryIdInvalid) + return false; + } + } - bool inline_arg = param_decl_node->data.param_decl.is_inline; - bool is_var_type = (param_type->id == TypeTableEntryIdVar); + bool comptime_arg = param_decl_node->data.param_decl.is_inline; ConstExprValue *arg_val; - if (inline_arg) { + if (comptime_arg) { + arg_part_of_generic_id = true; arg_val = ir_resolve_const(ira, casted_arg, UndefBad); if (!arg_val) return false; @@ -7544,26 +7559,41 @@ static bool ir_analyze_fn_call_generic_arg(IrAnalyze *ira, AstNode *fn_proto_nod } else { arg_val = create_const_runtime(casted_arg->value.type); } - - Buf *param_name = param_decl_node->data.param_decl.name; - VariableTableEntry *var = add_variable(ira->codegen, param_decl_node, - *child_scope, param_name, true, arg_val); - var->value.depends_on_compile_var = true; - *child_scope = var->child_scope; - - if (inline_arg || is_var_type) { + if (arg_part_of_generic_id) { generic_id->params[generic_id->param_count] = *arg_val; generic_id->param_count += 1; } - if (!inline_arg) { - if (type_requires_comptime(var->value.type)) { - ir_add_error(ira, arg, - buf_sprintf("parameter of type '%s' not allowed", buf_ptr(&var->value.type->name))); + + Buf *param_name = param_decl_node->data.param_decl.name; + if (is_var_args) { + if (!impl_fn->var_args_var) { + ConstExprValue *var_args_val = create_const_arg_tuple(ira->codegen, + fn_type_id->param_count, fn_type_id->param_count + 1); + VariableTableEntry *var = add_variable(ira->codegen, param_decl_node, + *child_scope, param_name, true, var_args_val); + var->value.depends_on_compile_var = true; + *child_scope = var->child_scope; + impl_fn->var_args_var = var; + } + impl_fn->var_args_var->value.data.x_arg_tuple.end_index = fn_type_id->param_count + 1; + + } else { + VariableTableEntry *var = add_variable(ira->codegen, param_decl_node, + *child_scope, param_name, true, arg_val); + var->value.depends_on_compile_var = true; + *child_scope = var->child_scope; + var->shadowable = !comptime_arg; + + *next_proto_i += 1; + } + + if (!comptime_arg) { + if (type_requires_comptime(casted_arg->value.type)) { + ir_add_error(ira, casted_arg, + buf_sprintf("parameter of type '%s' requires comptime", buf_ptr(&casted_arg->value.type->name))); return false; } - var->shadowable = true; - casted_args[fn_type_id->param_count] = casted_arg; FnTypeParamInfo *param_info = &fn_type_id->param_info[fn_type_id->param_count]; param_info->type = casted_arg->value.type; @@ -7571,7 +7601,7 @@ static bool ir_analyze_fn_call_generic_arg(IrAnalyze *ira, AstNode *fn_proto_nod impl_fn->param_source_nodes[fn_type_id->param_count] = param_decl_node; fn_type_id->param_count += 1; } - *next_proto_i += 1; + return true; } @@ -7682,6 +7712,7 @@ static TypeTableEntry *ir_analyze_fn_call(IrAnalyze *ira, IrInstructionCall *cal FnTypeId inst_fn_type_id = {0}; init_fn_type_id(&inst_fn_type_id, fn_proto_node); inst_fn_type_id.param_count = 0; + inst_fn_type_id.is_var_args = false; // TODO maybe GenericFnTypeId can be replaced with using the child_scope directly // as the key in generic_table @@ -7918,6 +7949,7 @@ static TypeTableEntry *ir_analyze_unary_prefix_op_err(IrAnalyze *ira, IrInstruct case TypeTableEntryIdBlock: case TypeTableEntryIdUnreachable: case TypeTableEntryIdVar: + case TypeTableEntryIdArgTuple: ir_add_error_node(ira, un_op_instruction->base.source_node, buf_sprintf("unable to wrap type '%s' in error type", buf_ptr(&meta_type->name))); // TODO if meta_type is type decl, add note pointing to type decl declaration @@ -7990,6 +8022,7 @@ static TypeTableEntry *ir_analyze_maybe(IrAnalyze *ira, IrInstructionUnOp *un_op case TypeTableEntryIdBlock: case TypeTableEntryIdBoundFn: case TypeTableEntryIdEnumTag: + case TypeTableEntryIdArgTuple: { ConstExprValue *out_val = ir_build_const_from(ira, &un_op_instruction->base, value->value.depends_on_compile_var); @@ -8327,6 +8360,30 @@ static TypeTableEntry *ir_analyze_instruction_elem_ptr(IrAnalyze *ira, IrInstruc return_type = array_type; } else if (is_slice(array_type)) { return_type = array_type->data.structure.fields[0].type_entry; + } else if (array_type->id == TypeTableEntryIdArgTuple) { + ConstExprValue *ptr_val = ir_resolve_const(ira, array_ptr, UndefBad); + if (!ptr_val) + return ira->codegen->builtin_types.entry_invalid; + ConstExprValue *args_val = const_ptr_pointee(ptr_val); + size_t start = args_val->data.x_arg_tuple.start_index; + size_t end = args_val->data.x_arg_tuple.end_index; + ConstExprValue *elem_index_val = ir_resolve_const(ira, elem_index, UndefBad); + if (!elem_index_val) + return ira->codegen->builtin_types.entry_invalid; + size_t index = bignum_to_twos_complement(&elem_index_val->data.x_bignum); + size_t len = end - start; + if (index >= len) { + ir_add_error(ira, &elem_ptr_instruction->base, + buf_sprintf("index %zu outside argument list of size %zu", index, len)); + return ira->codegen->builtin_types.entry_invalid; + } + size_t abs_index = start + index; + FnTableEntry *fn_entry = exec_fn_entry(ira->new_irb.exec); + assert(fn_entry); + VariableTableEntry *var = fn_entry->variable_list.at(abs_index); + bool depends_on_compile_var = array_ptr->value.depends_on_compile_var || + elem_index->value.depends_on_compile_var; + return ir_analyze_var_ptr(ira, &elem_ptr_instruction->base, var, true, depends_on_compile_var); } else { ir_add_error_node(ira, elem_ptr_instruction->base.source_node, buf_sprintf("array access of non-array type '%s'", buf_ptr(&array_type->name))); @@ -8572,6 +8629,29 @@ static TypeTableEntry *ir_analyze_instruction_field_ptr(IrAnalyze *ira, IrInstru ConstExprValue *len_val = allocate(1); init_const_usize(ira->codegen, len_val, container_type->data.array.len); + TypeTableEntry *usize = ira->codegen->builtin_types.entry_usize; + bool ptr_is_const = true; + return ir_analyze_const_ptr(ira, &field_ptr_instruction->base, len_val, + usize, false, ConstPtrSpecialNone, ptr_is_const); + } else { + ir_add_error_node(ira, source_node, + buf_sprintf("no member named '%s' in '%s'", buf_ptr(field_name), + buf_ptr(&container_type->name))); + return ira->codegen->builtin_types.entry_invalid; + } + } else if (container_type->id == TypeTableEntryIdArgTuple) { + ConstExprValue *container_ptr_val = ir_resolve_const(ira, container_ptr, UndefBad); + if (!container_ptr_val) + return ira->codegen->builtin_types.entry_invalid; + + assert(container_ptr->value.type->id == TypeTableEntryIdPointer); + ConstExprValue *child_val = const_ptr_pointee(container_ptr_val); + + if (buf_eql_str(field_name, "len")) { + ConstExprValue *len_val = allocate(1); + size_t len = child_val->data.x_arg_tuple.end_index - child_val->data.x_arg_tuple.start_index; + init_const_usize(ira->codegen, len_val, len); + TypeTableEntry *usize = ira->codegen->builtin_types.entry_usize; bool ptr_is_const = true; return ir_analyze_const_ptr(ira, &field_ptr_instruction->base, len_val, @@ -8830,6 +8910,7 @@ static TypeTableEntry *ir_analyze_instruction_typeof(IrAnalyze *ira, IrInstructi case TypeTableEntryIdFn: case TypeTableEntryIdTypeDecl: case TypeTableEntryIdEnumTag: + case TypeTableEntryIdArgTuple: { ConstExprValue *out_val = ir_build_const_from(ira, &typeof_instruction->base, false); // TODO depends_on_compile_var should be set based on whether the type of the expression @@ -8847,8 +8928,8 @@ static TypeTableEntry *ir_analyze_instruction_typeof(IrAnalyze *ira, IrInstructi static TypeTableEntry *ir_analyze_instruction_to_ptr_type(IrAnalyze *ira, IrInstructionToPtrType *to_ptr_type_instruction) { - IrInstruction *type_value = to_ptr_type_instruction->value->other; - TypeTableEntry *type_entry = ir_resolve_type(ira, type_value); + IrInstruction *value = to_ptr_type_instruction->value->other; + TypeTableEntry *type_entry = value->value.type; if (type_entry->id == TypeTableEntryIdInvalid) return type_entry; @@ -8857,6 +8938,11 @@ static TypeTableEntry *ir_analyze_instruction_to_ptr_type(IrAnalyze *ira, ptr_type = get_pointer_to_type(ira->codegen, type_entry->data.array.child_type, false); } else if (is_slice(type_entry)) { ptr_type = type_entry->data.structure.fields[0].type_entry; + } else if (type_entry->id == TypeTableEntryIdArgTuple) { + ConstExprValue *arg_tuple_val = ir_resolve_const(ira, value, UndefBad); + if (!arg_tuple_val) + return ira->codegen->builtin_types.entry_invalid; + zig_panic("TODO for loop on var args"); } else { ir_add_error_node(ira, to_ptr_type_instruction->base.source_node, buf_sprintf("expected array type, found '%s'", buf_ptr(&type_entry->name))); @@ -8864,7 +8950,7 @@ static TypeTableEntry *ir_analyze_instruction_to_ptr_type(IrAnalyze *ira, } ConstExprValue *out_val = ir_build_const_from(ira, &to_ptr_type_instruction->base, - type_value->value.depends_on_compile_var); + value->value.depends_on_compile_var); out_val->data.x_type = ptr_type; return ira->codegen->builtin_types.entry_type; } @@ -9027,6 +9113,7 @@ static TypeTableEntry *ir_analyze_instruction_slice_type(IrAnalyze *ira, case TypeTableEntryIdUndefLit: case TypeTableEntryIdNullLit: case TypeTableEntryIdBlock: + case TypeTableEntryIdArgTuple: ir_add_error_node(ira, slice_type_instruction->base.source_node, buf_sprintf("slice of type '%s' not allowed", buf_ptr(&resolved_child_type->name))); // TODO if this is a typedecl, add error note showing the declaration of the type decl @@ -9118,6 +9205,7 @@ static TypeTableEntry *ir_analyze_instruction_array_type(IrAnalyze *ira, case TypeTableEntryIdUndefLit: case TypeTableEntryIdNullLit: case TypeTableEntryIdBlock: + case TypeTableEntryIdArgTuple: ir_add_error_node(ira, array_type_instruction->base.source_node, buf_sprintf("array of type '%s' not allowed", buf_ptr(&child_type->name))); // TODO if this is a typedecl, add error note showing the declaration of the type decl @@ -9214,6 +9302,7 @@ static TypeTableEntry *ir_analyze_instruction_size_of(IrAnalyze *ira, case TypeTableEntryIdMetaType: case TypeTableEntryIdFn: case TypeTableEntryIdNamespace: + case TypeTableEntryIdArgTuple: ir_add_error_node(ira, size_of_instruction->base.source_node, buf_sprintf("no size available for type '%s'", buf_ptr(&type_entry->name))); // TODO if this is a typedecl, add error note showing the declaration of the type decl @@ -9559,6 +9648,7 @@ static TypeTableEntry *ir_analyze_instruction_switch_target(IrAnalyze *ira, case TypeTableEntryIdUnion: case TypeTableEntryIdBlock: case TypeTableEntryIdBoundFn: + case TypeTableEntryIdArgTuple: ir_add_error(ira, &switch_target_instruction->base, buf_sprintf("invalid switch target type '%s'", buf_ptr(&target_type->name))); // TODO if this is a typedecl, add error note showing the declaration of the type decl @@ -10075,6 +10165,7 @@ static TypeTableEntry *ir_analyze_min_max(IrAnalyze *ira, IrInstruction *source_ case TypeTableEntryIdNamespace: case TypeTableEntryIdBlock: case TypeTableEntryIdBoundFn: + case TypeTableEntryIdArgTuple: { const char *err_format = is_max ? "no max value available for type '%s'" : diff --git a/src/parser.cpp b/src/parser.cpp index dbb6836e6..04c31fcfe 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -250,16 +250,11 @@ static AstNode *ast_parse_type_expr(ParseContext *pc, size_t *token_index, bool } /* -ParamDecl = option("noalias" | "comptime") option(Symbol ":") TypeExpr | "..." +ParamDecl = option("noalias" | "comptime") option(Symbol ":") (TypeExpr | "...") */ static AstNode *ast_parse_param_decl(ParseContext *pc, size_t *token_index) { Token *token = &pc->tokens->at(*token_index); - if (token->id == TokenIdEllipsis) { - *token_index += 1; - return nullptr; - } - AstNode *node = ast_create_node(pc, NodeTypeParamDecl, token); if (token->id == TokenIdKeywordNoAlias) { @@ -282,7 +277,13 @@ static AstNode *ast_parse_param_decl(ParseContext *pc, size_t *token_index) { } } - node->data.param_decl.type = ast_parse_type_expr(pc, token_index, true); + Token *ellipsis_tok = &pc->tokens->at(*token_index); + if (ellipsis_tok->id == TokenIdEllipsis) { + *token_index += 1; + node->data.param_decl.is_var_args = true; + } else { + node->data.param_decl.type = ast_parse_type_expr(pc, token_index, true); + } return node; } @@ -304,12 +305,10 @@ static void ast_parse_param_decl_list(ParseContext *pc, size_t *token_index, for (;;) { AstNode *param_decl_node = ast_parse_param_decl(pc, token_index); bool expect_end = false; - if (param_decl_node) { - params->append(param_decl_node); - } else { - *is_var_args = true; - expect_end = true; - } + assert(param_decl_node); + params->append(param_decl_node); + expect_end = param_decl_node->data.param_decl.is_var_args; + *is_var_args = expect_end; Token *token = &pc->tokens->at(*token_index); *token_index += 1; diff --git a/test/cases/var_args.zig b/test/cases/var_args.zig new file mode 100644 index 000000000..6b30edd25 --- /dev/null +++ b/test/cases/var_args.zig @@ -0,0 +1,16 @@ +const assert = @import("std").debug.assert; + +fn add(args: ...) -> i32 { + var sum = i32(0); + {comptime var i: usize = 0; inline while (i < args.len; i += 1) { + sum += args[i]; + }} + return sum; +} + +fn testAddArbitraryArgs() { + @setFnTest(this); + + assert(add(i32(1), i32(2), i32(3), i32(4)) == 10); + assert(add(i32(1234)) == 1234); +} diff --git a/test/run_tests.cpp b/test/run_tests.cpp index 19d36d256..a359bcdea 100644 --- a/test/run_tests.cpp +++ b/test/run_tests.cpp @@ -832,10 +832,6 @@ fn f() { )SOURCE", 2, ".tmp_source.zig:5:11: error: expected type 'usize', found 'bool'", ".tmp_source.zig:5:24: error: expected type 'usize', found 'bool'"); - add_compile_fail_case("variadic functions only allowed in extern", R"SOURCE( -fn f(...) {} - )SOURCE", 1, ".tmp_source.zig:2:1: error: variadic arguments only allowed in extern function declarations"); - add_compile_fail_case("write to const global variable", R"SOURCE( const x : i32 = 99; fn f() { @@ -1633,6 +1629,33 @@ pub fn maybeInt() -> ?i32 { return 0; } )SOURCE", 1, ".tmp_source.zig:5:11: error: cannot return from defer expression"); + + add_compile_fail_case("attempt to access var args out of bounds", R"SOURCE( +fn add(args: ...) -> i32 { + args[0] + args[1] +} + +fn foo() -> i32 { + add(i32(1234)) +} + )SOURCE", 2, + ".tmp_source.zig:3:19: error: index 1 outside argument list of size 1", + ".tmp_source.zig:7:8: note: called from here"); + + add_compile_fail_case("pass integer literal to var args", R"SOURCE( +fn add(args: ...) -> i32 { + var sum = i32(0); + {comptime var i: usize = 0; inline while (i < args.len; i += 1) { + sum += args[i]; + }} + return sum; +} + +fn bar() -> i32 { + add(1, 2, 3, 4) +} + )SOURCE", 1, ".tmp_source.zig:11:9: error: parameter of type '(integer literal)' requires comptime"); + } ////////////////////////////////////////////////////////////////////////////// diff --git a/test/self_hosted.zig b/test/self_hosted.zig index a636f87cc..a6a2f4127 100644 --- a/test/self_hosted.zig +++ b/test/self_hosted.zig @@ -28,4 +28,5 @@ const test_switch = @import("cases/switch.zig"); const test_switch_prong_err_enum = @import("cases/switch_prong_err_enum.zig"); const test_switch_prong_implicit_cast = @import("cases/switch_prong_implicit_cast.zig"); const test_this = @import("cases/this.zig"); +const test_var_args = @import("cases/var_args.zig"); const test_while = @import("cases/while.zig");