From ae8d26a6a00a528bdf555689c2a93cb35a3287f2 Mon Sep 17 00:00:00 2001 From: antlilja Date: Sat, 6 Aug 2022 22:32:00 +0200 Subject: [PATCH] Sema: add error for non-comptime param in comptime func Adds error for taking a non comptime parameter in a function returning a comptime-only type but not when that type is dependent on a parameter. Co-authored-by: Veikka Tuominen --- lib/std/c.zig | 2 +- lib/std/elf.zig | 4 +- lib/std/io/bit_reader.zig | 2 +- lib/std/io/bit_writer.zig | 2 +- lib/std/meta.zig | 2 +- lib/std/multi_array_list.zig | 2 +- src/Sema.zig | 61 ++++++++++++++----- test/behavior/union.zig | 2 +- .../explain_why_fn_is_called_at_comptime.zig | 8 +-- ...on_comptime_param_in_comptime_function.zig | 36 +++++++++++ 10 files changed, 95 insertions(+), 26 deletions(-) create mode 100644 test/cases/compile_errors/non_comptime_param_in_comptime_function.zig diff --git a/lib/std/c.zig b/lib/std/c.zig index f01f580083..7b018f62d5 100644 --- a/lib/std/c.zig +++ b/lib/std/c.zig @@ -20,7 +20,7 @@ pub const Tokenizer = tokenizer.Tokenizer; /// If linking gnu libc (glibc), the `ok` value will be true if the target /// version is greater than or equal to `glibc_version`. /// If linking a libc other than these, returns `false`. -pub fn versionCheck(glibc_version: std.builtin.Version) type { +pub fn versionCheck(comptime glibc_version: std.builtin.Version) type { return struct { pub const ok = blk: { if (!builtin.link_libc) break :blk false; diff --git a/lib/std/elf.zig b/lib/std/elf.zig index cc43e11a7d..4a9b7a498f 100644 --- a/lib/std/elf.zig +++ b/lib/std/elf.zig @@ -406,7 +406,7 @@ pub const Header = struct { } }; -pub fn ProgramHeaderIterator(ParseSource: anytype) type { +pub fn ProgramHeaderIterator(comptime ParseSource: anytype) type { return struct { elf_header: Header, parse_source: ParseSource, @@ -456,7 +456,7 @@ pub fn ProgramHeaderIterator(ParseSource: anytype) type { }; } -pub fn SectionHeaderIterator(ParseSource: anytype) type { +pub fn SectionHeaderIterator(comptime ParseSource: anytype) type { return struct { elf_header: Header, parse_source: ParseSource, diff --git a/lib/std/io/bit_reader.zig b/lib/std/io/bit_reader.zig index 15262f67a2..e897850b83 100644 --- a/lib/std/io/bit_reader.zig +++ b/lib/std/io/bit_reader.zig @@ -7,7 +7,7 @@ const meta = std.meta; const math = std.math; /// Creates a stream which allows for reading bit fields from another stream -pub fn BitReader(endian: std.builtin.Endian, comptime ReaderType: type) type { +pub fn BitReader(comptime endian: std.builtin.Endian, comptime ReaderType: type) type { return struct { forward_reader: ReaderType, bit_buffer: u7, diff --git a/lib/std/io/bit_writer.zig b/lib/std/io/bit_writer.zig index d71afe5fe5..0be2e7ab08 100644 --- a/lib/std/io/bit_writer.zig +++ b/lib/std/io/bit_writer.zig @@ -7,7 +7,7 @@ const meta = std.meta; const math = std.math; /// Creates a stream which allows for writing bit fields to another stream -pub fn BitWriter(endian: std.builtin.Endian, comptime WriterType: type) type { +pub fn BitWriter(comptime endian: std.builtin.Endian, comptime WriterType: type) type { return struct { forward_writer: WriterType, bit_buffer: u8, diff --git a/lib/std/meta.zig b/lib/std/meta.zig index 937b1998ff..7e64eff49f 100644 --- a/lib/std/meta.zig +++ b/lib/std/meta.zig @@ -764,7 +764,7 @@ const TagPayloadType = TagPayload; ///Given a tagged union type, and an enum, return the type of the union /// field corresponding to the enum tag. -pub fn TagPayload(comptime U: type, tag: Tag(U)) type { +pub fn TagPayload(comptime U: type, comptime tag: Tag(U)) type { comptime debug.assert(trait.is(.Union)(U)); const info = @typeInfo(U).Union; diff --git a/lib/std/multi_array_list.zig b/lib/std/multi_array_list.zig index 48dddf636d..11aec707ab 100644 --- a/lib/std/multi_array_list.zig +++ b/lib/std/multi_array_list.zig @@ -459,7 +459,7 @@ pub fn MultiArrayList(comptime S: type) type { return self.bytes[0..capacityInBytes(self.capacity)]; } - fn FieldType(field: Field) type { + fn FieldType(comptime field: Field) type { return meta.fieldInfo(S, field).field_type; } diff --git a/src/Sema.zig b/src/Sema.zig index 2907999c25..047d3f84c2 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -7816,19 +7816,17 @@ fn funcCommon( }; } - var is_comptime_ret = false; - const ret_poison = if (!is_generic) rp: { - if (sema.typeRequiresComptime(block, ret_ty_src, bare_return_type)) |ret_comptime| { - is_comptime_ret = ret_comptime; - break :rp bare_return_type.tag() == .generic_poison; - } else |err| switch (err) { - error.GenericPoison => { - is_generic = true; - break :rp true; - }, - else => |e| return e, - } - } else bare_return_type.tag() == .generic_poison; + var ret_ty_requires_comptime = false; + const ret_poison = if (sema.typeRequiresComptime(block, ret_ty_src, bare_return_type)) |ret_comptime| rp: { + ret_ty_requires_comptime = ret_comptime; + break :rp bare_return_type.tag() == .generic_poison; + } else |err| switch (err) { + error.GenericPoison => rp: { + is_generic = true; + break :rp true; + }, + else => |e| return e, + }; const return_type = if (!inferred_error_set or ret_poison) bare_return_type @@ -7873,6 +7871,41 @@ fn funcCommon( return sema.failWithOwnedErrorMsg(msg); } + // If the return type is comptime only but not dependent on parameters then all parameter types also need to be comptime + if (!sema.is_generic_instantiation and has_body and ret_ty_requires_comptime) comptime_check: { + for (block.params.items) |param| { + if (!param.is_comptime) break; + } else break :comptime_check; + + const msg = try sema.errMsg( + block, + ret_ty_src, + "function with comptime only return type '{}' requires all parameters to be comptime", + .{return_type.fmt(sema.mod)}, + ); + try sema.explainWhyTypeIsComptime(block, ret_ty_src, msg, ret_ty_src.toSrcLoc(sema.owner_decl), return_type); + + const tags = sema.code.instructions.items(.tag); + const data = sema.code.instructions.items(.data); + const param_body = sema.code.getParamBody(func_inst); + for (block.params.items) |param, i| { + if (!param.is_comptime) { + const param_index = param_body[i]; + const param_src = switch (tags[param_index]) { + .param => data[param_index].pl_tok.src(), + .param_anytype => data[param_index].str_tok.src(), + else => unreachable, + }; + if (param.name.len != 0) { + try sema.errNote(block, param_src, msg, "param '{s}' is required to be comptime", .{param.name}); + } else { + try sema.errNote(block, param_src, msg, "param is required to be comptime", .{}); + } + } + } + return sema.failWithOwnedErrorMsg(msg); + } + const arch = sema.mod.getTarget().cpu.arch; if (switch (cc_workaround) { .Unspecified, .C, .Naked, .Async, .Inline => null, @@ -7917,7 +7950,7 @@ fn funcCommon( } if (is_generic and sema.no_partial_func_ty) return error.GenericPoison; for (comptime_params) |ct| is_generic = is_generic or ct; - is_generic = is_generic or is_comptime_ret; + is_generic = is_generic or ret_ty_requires_comptime; break :fn_ty try Type.Tag.function.create(sema.arena, .{ .param_types = param_types, diff --git a/test/behavior/union.zig b/test/behavior/union.zig index e2078c66df..5d6b084be5 100644 --- a/test/behavior/union.zig +++ b/test/behavior/union.zig @@ -745,7 +745,7 @@ fn setAttribute(attr: Attribute) void { _ = attr; } -fn Setter(attr: Attribute) type { +fn Setter(comptime attr: Attribute) type { return struct { fn set() void { setAttribute(attr); diff --git a/test/cases/compile_errors/explain_why_fn_is_called_at_comptime.zig b/test/cases/compile_errors/explain_why_fn_is_called_at_comptime.zig index 7ec539dcd1..04f64c2303 100644 --- a/test/cases/compile_errors/explain_why_fn_is_called_at_comptime.zig +++ b/test/cases/compile_errors/explain_why_fn_is_called_at_comptime.zig @@ -4,12 +4,12 @@ const S = struct { }; fn bar() void {} -fn foo(a: u8) S { - return .{ .fnPtr = bar, .a = a }; +fn foo(comptime a: *u8) S { + return .{ .fnPtr = bar, .a = a.* }; } pub export fn entry() void { var a: u8 = 1; - _ = foo(a); + _ = foo(&a); } // error @@ -18,6 +18,6 @@ pub export fn entry() void { // // :12:13: error: unable to resolve comptime value // :12:13: note: argument to function being called at comptime must be comptime known -// :7:15: note: function is being called at comptime because it returns a comptime only type 'tmp.S' +// :7:25: note: function is being called at comptime because it returns a comptime only type 'tmp.S' // :2:12: note: struct requires comptime because of this field // :2:12: note: use '*const fn() void' for a function pointer type diff --git a/test/cases/compile_errors/non_comptime_param_in_comptime_function.zig b/test/cases/compile_errors/non_comptime_param_in_comptime_function.zig new file mode 100644 index 0000000000..758166dd7f --- /dev/null +++ b/test/cases/compile_errors/non_comptime_param_in_comptime_function.zig @@ -0,0 +1,36 @@ +fn F(val: anytype) type { + _ = val; + return struct {}; +} +export fn entry() void { + _ = F(void{}); +} +const S = struct { + foo: fn () void, +}; +fn bar(_: u32) S { + return undefined; +} +export fn entry1() void { + _ = bar(); +} +// prioritize other return type errors +fn foo(a: u32) callconv(.C) comptime_int { + return a; +} +export fn entry2() void { + _ = foo(1); +} + +// error +// backend=stage2 +// target=native +// +// :1:20: error: function with comptime only return type 'type' requires all parameters to be comptime +// :1:20: note: types are not available at runtime +// :1:6: note: param 'val' is required to be comptime +// :11:16: error: function with comptime only return type 'tmp.S' requires all parameters to be comptime +// :9:10: note: struct requires comptime because of this field +// :9:10: note: use '*const fn() void' for a function pointer type +// :11:8: note: param is required to be comptime +// :18:29: error: return type 'comptime_int' not allowed in function with calling convention 'C'