mirror of
https://github.com/ziglang/zig.git
synced 2025-12-27 00:23:22 +00:00
commit
d57370d3ca
@ -754,8 +754,9 @@ pub const Node = struct {
|
||||
};
|
||||
|
||||
pub const ContainerField = struct {
|
||||
base: Node,
|
||||
base: Node = Node{ .id = .ContainerField },
|
||||
doc_comments: ?*DocComment,
|
||||
comptime_token: ?TokenIndex,
|
||||
name_token: TokenIndex,
|
||||
type_expr: ?*Node,
|
||||
value_expr: ?*Node,
|
||||
@ -778,7 +779,7 @@ pub const Node = struct {
|
||||
}
|
||||
|
||||
pub fn firstToken(self: *const ContainerField) TokenIndex {
|
||||
return self.name_token;
|
||||
return self.comptime_token orelse self.name_token;
|
||||
}
|
||||
|
||||
pub fn lastToken(self: *const ContainerField) TokenIndex {
|
||||
|
||||
@ -206,6 +206,11 @@ fn parseTestDecl(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
||||
/// TopLevelComptime <- KEYWORD_comptime BlockExpr
|
||||
fn parseTopLevelComptime(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
||||
const tok = eatToken(it, .Keyword_comptime) orelse return null;
|
||||
const lbrace = eatToken(it, .LBrace) orelse {
|
||||
putBackToken(it, tok);
|
||||
return null;
|
||||
};
|
||||
putBackToken(it, lbrace);
|
||||
const block_node = try expectNode(arena, it, tree, parseBlockExpr, AstError{
|
||||
.ExpectedLabelOrLBrace = AstError.ExpectedLabelOrLBrace{ .token = it.index },
|
||||
});
|
||||
@ -403,9 +408,13 @@ fn parseVarDecl(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
||||
return &node.base;
|
||||
}
|
||||
|
||||
/// ContainerField <- IDENTIFIER (COLON TypeExpr ByteAlign?)? (EQUAL Expr)?
|
||||
/// ContainerField <- KEYWORD_comptime? IDENTIFIER (COLON TypeExpr ByteAlign?)? (EQUAL Expr)?
|
||||
fn parseContainerField(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*Node {
|
||||
const name_token = eatToken(it, .Identifier) orelse return null;
|
||||
const comptime_token = eatToken(it, .Keyword_comptime);
|
||||
const name_token = eatToken(it, .Identifier) orelse {
|
||||
if (comptime_token) |t| putBackToken(it, t);
|
||||
return null;
|
||||
};
|
||||
|
||||
var align_expr: ?*Node = null;
|
||||
var type_expr: ?*Node = null;
|
||||
@ -431,8 +440,8 @@ fn parseContainerField(arena: *Allocator, it: *TokenIterator, tree: *Tree) !?*No
|
||||
|
||||
const node = try arena.create(Node.ContainerField);
|
||||
node.* = Node.ContainerField{
|
||||
.base = Node{ .id = .ContainerField },
|
||||
.doc_comments = null,
|
||||
.comptime_token = comptime_token,
|
||||
.name_token = name_token,
|
||||
.type_expr = type_expr,
|
||||
.value_expr = value_expr,
|
||||
|
||||
@ -1,3 +1,13 @@
|
||||
test "zig fmt: comptime struct field" {
|
||||
try testCanonical(
|
||||
\\const Foo = struct {
|
||||
\\ a: i32,
|
||||
\\ comptime b: i32 = 1234,
|
||||
\\};
|
||||
\\
|
||||
);
|
||||
}
|
||||
|
||||
test "zig fmt: c pointer type" {
|
||||
try testCanonical(
|
||||
\\pub extern fn repro() [*c]const u8;
|
||||
|
||||
@ -251,6 +251,9 @@ fn renderTopLevelDecl(allocator: *mem.Allocator, stream: var, tree: *ast.Tree, i
|
||||
const field = @fieldParentPtr(ast.Node.ContainerField, "base", decl);
|
||||
|
||||
try renderDocComments(tree, stream, field, indent, start_col);
|
||||
if (field.comptime_token) |t| {
|
||||
try renderToken(tree, stream, t, indent, start_col, Space.Space); // comptime
|
||||
}
|
||||
|
||||
if (field.type_expr == null and field.value_expr == null) {
|
||||
return renderToken(tree, stream, field.name_token, indent, start_col, Space.Comma); // name,
|
||||
|
||||
@ -1006,6 +1006,7 @@ struct AstNodeStructField {
|
||||
// populated if the "align(A)" is present
|
||||
AstNode *align_expr;
|
||||
Buf doc_comments;
|
||||
Token *comptime_token;
|
||||
};
|
||||
|
||||
struct AstNodeStringLiteral {
|
||||
@ -1263,6 +1264,7 @@ struct TypeStructField {
|
||||
uint32_t bit_offset_in_host; // offset from the memory at gen_index
|
||||
uint32_t host_int_bytes; // size of host integer
|
||||
uint32_t align;
|
||||
bool is_comptime;
|
||||
};
|
||||
|
||||
enum ResolveStatus {
|
||||
|
||||
@ -2736,6 +2736,16 @@ static Error resolve_struct_zero_bits(CodeGen *g, ZigType *struct_type) {
|
||||
field_node = decl_node->data.container_decl.fields.at(i);
|
||||
type_struct_field->name = field_node->data.struct_field.name;
|
||||
type_struct_field->decl_node = field_node;
|
||||
if (field_node->data.struct_field.comptime_token != nullptr) {
|
||||
if (field_node->data.struct_field.value == nullptr) {
|
||||
add_token_error(g, field_node->owner,
|
||||
field_node->data.struct_field.comptime_token,
|
||||
buf_sprintf("comptime struct field missing initialization value"));
|
||||
struct_type->data.structure.resolve_status = ResolveStatusInvalid;
|
||||
return ErrorSemanticAnalyzeFail;
|
||||
}
|
||||
type_struct_field->is_comptime = true;
|
||||
}
|
||||
|
||||
if (field_node->data.struct_field.type == nullptr) {
|
||||
add_node_error(g, field_node, buf_sprintf("struct field missing type"));
|
||||
@ -2788,6 +2798,9 @@ static Error resolve_struct_zero_bits(CodeGen *g, ZigType *struct_type) {
|
||||
type_struct_field->src_index = i;
|
||||
type_struct_field->gen_index = SIZE_MAX;
|
||||
|
||||
if (type_struct_field->is_comptime)
|
||||
continue;
|
||||
|
||||
switch (type_val_resolve_requires_comptime(g, field_type_val)) {
|
||||
case ReqCompTimeYes:
|
||||
struct_type->data.structure.requires_comptime = true;
|
||||
@ -7987,8 +8000,9 @@ static void resolve_llvm_types_struct(CodeGen *g, ZigType *struct_type, ResolveS
|
||||
// inserting padding bytes where LLVM would do it automatically.
|
||||
size_t llvm_struct_abi_align = 0;
|
||||
for (size_t i = 0; i < field_count; i += 1) {
|
||||
ZigType *field_type = struct_type->data.structure.fields[i]->type_entry;
|
||||
if (!type_has_bits(field_type))
|
||||
TypeStructField *field = struct_type->data.structure.fields[i];
|
||||
ZigType *field_type = field->type_entry;
|
||||
if (field->is_comptime || !type_has_bits(field_type))
|
||||
continue;
|
||||
LLVMTypeRef field_llvm_type = get_llvm_type(g, field_type);
|
||||
size_t llvm_field_abi_align = LLVMABIAlignmentOfType(g->target_data_ref, field_llvm_type);
|
||||
@ -7999,7 +8013,7 @@ static void resolve_llvm_types_struct(CodeGen *g, ZigType *struct_type, ResolveS
|
||||
TypeStructField *field = struct_type->data.structure.fields[i];
|
||||
ZigType *field_type = field->type_entry;
|
||||
|
||||
if (!type_has_bits(field_type)) {
|
||||
if (field->is_comptime || !type_has_bits(field_type)) {
|
||||
field->gen_index = SIZE_MAX;
|
||||
continue;
|
||||
}
|
||||
|
||||
56
src/ir.cpp
56
src/ir.cpp
@ -655,6 +655,10 @@ static ZigValue *const_ptr_pointee_unchecked(CodeGen *g, ZigValue *const_val) {
|
||||
if (isf != nullptr) {
|
||||
TypeStructField *field = find_struct_type_field(isf->inferred_struct_type, isf->field_name);
|
||||
assert(field != nullptr);
|
||||
if (field->is_comptime) {
|
||||
assert(field->init_val != nullptr);
|
||||
return field->init_val;
|
||||
}
|
||||
assert(const_val->data.x_ptr.special == ConstPtrSpecialRef);
|
||||
ZigValue *struct_val = const_val->data.x_ptr.data.ref.pointee;
|
||||
return struct_val->data.x_struct.fields[field->src_index];
|
||||
@ -17373,6 +17377,13 @@ static IrInstruction *ir_analyze_store_ptr(IrAnalyze *ira, IrInstruction *source
|
||||
field->type_val = create_const_type(ira->codegen, field->type_entry);
|
||||
field->src_index = old_field_count;
|
||||
field->decl_node = uncasted_value->source_node;
|
||||
if (instr_is_comptime(uncasted_value)) {
|
||||
ZigValue *uncasted_val = ir_resolve_const(ira, uncasted_value, UndefOk);
|
||||
field->is_comptime = true;
|
||||
field->init_val = create_const_vals(1);
|
||||
copy_const_val(field->init_val, uncasted_val);
|
||||
return ir_const_void(ira, source_instr);
|
||||
}
|
||||
|
||||
ZigType *struct_ptr_type = get_pointer_to_type(ira->codegen, isf->inferred_struct_type, false);
|
||||
IrInstruction *casted_ptr;
|
||||
@ -19503,6 +19514,18 @@ static IrInstruction *ir_analyze_container_member_access_inner(IrAnalyze *ira,
|
||||
return ira->codegen->invalid_instruction;
|
||||
}
|
||||
|
||||
static void memoize_field_init_val(CodeGen *codegen, ZigType *container_type, TypeStructField *field) {
|
||||
if (field->init_val != nullptr) return;
|
||||
if (field->decl_node->type != NodeTypeStructField) return;
|
||||
AstNode *init_node = field->decl_node->data.struct_field.value;
|
||||
if (init_node == nullptr) return;
|
||||
// scope is not the scope of the struct init, it's the scope of the struct type decl
|
||||
Scope *analyze_scope = &get_container_scope(container_type)->base;
|
||||
// memoize it
|
||||
field->init_val = analyze_const_value(codegen, analyze_scope, init_node,
|
||||
field->type_entry, nullptr, UndefOk);
|
||||
}
|
||||
|
||||
static IrInstruction *ir_analyze_struct_field_ptr(IrAnalyze *ira, IrInstruction *source_instr,
|
||||
TypeStructField *field, IrInstruction *struct_ptr, ZigType *struct_type, bool initializing)
|
||||
{
|
||||
@ -19510,6 +19533,12 @@ static IrInstruction *ir_analyze_struct_field_ptr(IrAnalyze *ira, IrInstruction
|
||||
ZigType *field_type = resolve_struct_field_type(ira->codegen, field);
|
||||
if (field_type == nullptr)
|
||||
return ira->codegen->invalid_instruction;
|
||||
if (field->is_comptime) {
|
||||
IrInstruction *elem = ir_const(ira, source_instr, field_type);
|
||||
memoize_field_init_val(ira->codegen, struct_type, field);
|
||||
copy_const_val(elem->value, field->init_val);
|
||||
return ir_get_ref(ira, source_instr, elem, true, false);
|
||||
}
|
||||
switch (type_has_one_possible_value(ira->codegen, field_type)) {
|
||||
case OnePossibleValueInvalid:
|
||||
return ira->codegen->invalid_instruction;
|
||||
@ -21540,25 +21569,12 @@ static IrInstruction *ir_analyze_container_init_fields(IrAnalyze *ira, IrInstruc
|
||||
|
||||
// look for a default field value
|
||||
TypeStructField *field = container_type->data.structure.fields[i];
|
||||
memoize_field_init_val(ira->codegen, container_type, field);
|
||||
if (field->init_val == nullptr) {
|
||||
// it's not memoized. time to go analyze it
|
||||
AstNode *init_node;
|
||||
if (field->decl_node->type == NodeTypeStructField) {
|
||||
init_node = field->decl_node->data.struct_field.value;
|
||||
} else {
|
||||
init_node = nullptr;
|
||||
}
|
||||
if (init_node == nullptr) {
|
||||
ir_add_error_node(ira, instruction->source_node,
|
||||
buf_sprintf("missing field: '%s'", buf_ptr(container_type->data.structure.fields[i]->name)));
|
||||
any_missing = true;
|
||||
continue;
|
||||
}
|
||||
// scope is not the scope of the struct init, it's the scope of the struct type decl
|
||||
Scope *analyze_scope = &get_container_scope(container_type)->base;
|
||||
// memoize it
|
||||
field->init_val = analyze_const_value(ira->codegen, analyze_scope, init_node,
|
||||
field->type_entry, nullptr, UndefOk);
|
||||
ir_add_error_node(ira, instruction->source_node,
|
||||
buf_sprintf("missing field: '%s'", buf_ptr(container_type->data.structure.fields[i]->name)));
|
||||
any_missing = true;
|
||||
continue;
|
||||
}
|
||||
if (type_is_invalid(field->init_val->type))
|
||||
return ira->codegen->invalid_instruction;
|
||||
@ -21716,6 +21732,10 @@ static IrInstruction *ir_analyze_instruction_container_init_list(IrAnalyze *ira,
|
||||
for (size_t i = 0; i < const_ptrs.length; i += 1) {
|
||||
IrInstruction *elem_result_loc = const_ptrs.at(i);
|
||||
assert(elem_result_loc->value->special == ConstValSpecialStatic);
|
||||
if (elem_result_loc->value->type->data.pointer.inferred_struct_field != nullptr) {
|
||||
// This field will be generated comptime; no need to do this.
|
||||
continue;
|
||||
}
|
||||
IrInstruction *deref = ir_get_deref(ira, elem_result_loc, elem_result_loc, nullptr);
|
||||
elem_result_loc->value->special = ConstValSpecialRuntime;
|
||||
ir_analyze_store_ptr(ira, elem_result_loc, elem_result_loc, deref, false);
|
||||
|
||||
@ -536,8 +536,8 @@ static void ast_parse_container_doc_comments(ParseContext *pc, Buf *buf) {
|
||||
// <- TestDecl ContainerMembers
|
||||
// / TopLevelComptime ContainerMembers
|
||||
// / KEYWORD_pub? TopLevelDecl ContainerMembers
|
||||
// / ContainerField COMMA ContainerMembers
|
||||
// / ContainerField
|
||||
// / KEYWORD_comptime? ContainerField COMMA ContainerMembers
|
||||
// / KEYWORD_comptime? ContainerField
|
||||
// /
|
||||
static AstNodeContainerDecl ast_parse_container_members(ParseContext *pc) {
|
||||
AstNodeContainerDecl res = {};
|
||||
@ -574,10 +574,13 @@ static AstNodeContainerDecl ast_parse_container_members(ParseContext *pc) {
|
||||
ast_error(pc, peek_token(pc), "expected function or variable declaration after pub");
|
||||
}
|
||||
|
||||
Token *comptime_token = eat_token_if(pc, TokenIdKeywordCompTime);
|
||||
|
||||
AstNode *container_field = ast_parse_container_field(pc);
|
||||
if (container_field != nullptr) {
|
||||
assert(container_field->type == NodeTypeStructField);
|
||||
container_field->data.struct_field.doc_comments = doc_comment_buf;
|
||||
container_field->data.struct_field.comptime_token = comptime_token;
|
||||
res.fields.append(container_field);
|
||||
if (eat_token_if(pc, TokenIdComma) != nullptr) {
|
||||
continue;
|
||||
@ -612,6 +615,13 @@ static AstNode *ast_parse_top_level_comptime(ParseContext *pc) {
|
||||
if (comptime == nullptr)
|
||||
return nullptr;
|
||||
|
||||
// 1 token lookahead because it could be a comptime struct field
|
||||
Token *lbrace = peek_token(pc);
|
||||
if (lbrace->id != TokenIdLBrace) {
|
||||
put_back_token(pc);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AstNode *block = ast_expect(pc, ast_parse_block_expr);
|
||||
AstNode *res = ast_create_node(pc, NodeTypeCompTime, comptime);
|
||||
res->data.comptime_expr.expr = block;
|
||||
|
||||
@ -2,6 +2,15 @@ const tests = @import("tests.zig");
|
||||
const builtin = @import("builtin");
|
||||
|
||||
pub fn addCases(cases: *tests.CompileErrorContext) void {
|
||||
cases.add("comptime struct field, no init value",
|
||||
\\const Foo = struct {
|
||||
\\ comptime b: i32,
|
||||
\\};
|
||||
\\export fn entry() void {
|
||||
\\ var f: Foo = undefined;
|
||||
\\}
|
||||
, "tmp.zig:2:5: error: comptime struct field missing initialization value");
|
||||
|
||||
cases.add(
|
||||
"bad usage of @call",
|
||||
\\export fn entry1() void {
|
||||
@ -32,7 +41,7 @@ pub fn addCases(cases: *tests.CompileErrorContext) void {
|
||||
"tmp.zig:15:43: error: unable to evaluate constant expression",
|
||||
);
|
||||
|
||||
cases.add(
|
||||
cases.add("exported async function",
|
||||
\\export async fn foo() void {}
|
||||
, "tmp.zig:1:1: error: exported function cannot be async");
|
||||
|
||||
|
||||
@ -37,12 +37,12 @@ test "tuple parameters" {
|
||||
comptime expect(@call(.{}, add, .{ 12, 34 }) == 46);
|
||||
{
|
||||
const separate_args0 = .{ a, b };
|
||||
//TODO const separate_args1 = .{ a, 34 };
|
||||
const separate_args1 = .{ a, 34 };
|
||||
const separate_args2 = .{ 12, 34 };
|
||||
//TODO const separate_args3 = .{ 12, b };
|
||||
const separate_args3 = .{ 12, b };
|
||||
expect(@call(.{ .modifier = .always_inline }, add, separate_args0) == 46);
|
||||
// TODO expect(@call(.{ .modifier = .always_inline }, add, separate_args1) == 46);
|
||||
expect(@call(.{ .modifier = .always_inline }, add, separate_args1) == 46);
|
||||
expect(@call(.{ .modifier = .always_inline }, add, separate_args2) == 46);
|
||||
// TODO expect(@call(.{ .modifier = .always_inline }, add, separate_args3) == 46);
|
||||
expect(@call(.{ .modifier = .always_inline }, add, separate_args3) == 46);
|
||||
}
|
||||
}
|
||||
|
||||
@ -775,8 +775,6 @@ test "anonymous struct literal assigned to variable" {
|
||||
expect(vec.@"0" == 22);
|
||||
expect(vec.@"1" == 55);
|
||||
expect(vec.@"2" == 99);
|
||||
vec.@"1" += 1;
|
||||
expect(vec.@"1" == 56);
|
||||
}
|
||||
|
||||
test "struct with var field" {
|
||||
@ -791,3 +789,13 @@ test "struct with var field" {
|
||||
expect(pt.x == 1);
|
||||
expect(pt.y == 2);
|
||||
}
|
||||
|
||||
test "comptime struct field" {
|
||||
const T = struct {
|
||||
a: i32,
|
||||
comptime b: i32 = 1234,
|
||||
};
|
||||
|
||||
var foo: T = undefined;
|
||||
comptime expect(foo.b == 1234);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user