mirror of
https://github.com/ziglang/zig.git
synced 2026-02-13 04:48:20 +00:00
Sema: validate function parameter types and return type
This commit is contained in:
parent
d851b24180
commit
8feb398760
@ -2439,6 +2439,7 @@ pub const SrcLoc = struct {
|
||||
|
||||
.node_offset_fn_type_ret_ty => |node_off| {
|
||||
const tree = try src_loc.file_scope.getTree(gpa);
|
||||
const node_datas = tree.nodes.items(.data);
|
||||
const node_tags = tree.nodes.items(.tag);
|
||||
const node = src_loc.declRelativeToNodeIndex(node_off);
|
||||
var params: [1]Ast.Node.Index = undefined;
|
||||
@ -2447,6 +2448,16 @@ pub const SrcLoc = struct {
|
||||
.fn_proto_multi => tree.fnProtoMulti(node),
|
||||
.fn_proto_one => tree.fnProtoOne(¶ms, node),
|
||||
.fn_proto => tree.fnProto(node),
|
||||
.fn_decl => blk: {
|
||||
const fn_proto = node_datas[node].lhs;
|
||||
break :blk switch (node_tags[fn_proto]) {
|
||||
.fn_proto_simple => tree.fnProtoSimple(¶ms, fn_proto),
|
||||
.fn_proto_multi => tree.fnProtoMulti(fn_proto),
|
||||
.fn_proto_one => tree.fnProtoOne(¶ms, fn_proto),
|
||||
.fn_proto => tree.fnProto(fn_proto),
|
||||
else => unreachable,
|
||||
};
|
||||
},
|
||||
else => unreachable,
|
||||
};
|
||||
return nodeToSpan(tree, full.ast.return_type);
|
||||
|
||||
98
src/Sema.zig
98
src/Sema.zig
@ -7243,7 +7243,10 @@ fn funcCommon(
|
||||
break :new_func new_func;
|
||||
}
|
||||
destroy_fn_on_error = true;
|
||||
break :new_func try sema.gpa.create(Module.Fn);
|
||||
const new_func = try sema.gpa.create(Module.Fn);
|
||||
// Set this here so that the inferred return type can be printed correctly if it appears in an error.
|
||||
new_func.owner_decl = sema.owner_decl_index;
|
||||
break :new_func new_func;
|
||||
};
|
||||
errdefer if (destroy_fn_on_error) sema.gpa.destroy(new_func);
|
||||
|
||||
@ -7277,18 +7280,67 @@ fn funcCommon(
|
||||
}
|
||||
}
|
||||
|
||||
// These locals are pulled out from the init expression below to work around
|
||||
// a stage1 compiler bug.
|
||||
// In the case of generic calling convention, or generic alignment, we use
|
||||
// default values which are only meaningful for the generic function, *not*
|
||||
// the instantiation, which can depend on comptime parameters.
|
||||
// Related proposal: https://github.com/ziglang/zig/issues/11834
|
||||
const cc_workaround = cc orelse .Unspecified;
|
||||
const align_workaround = alignment orelse 0;
|
||||
|
||||
const param_types = try sema.arena.alloc(Type, block.params.items.len);
|
||||
const comptime_params = try sema.arena.alloc(bool, block.params.items.len);
|
||||
for (block.params.items) |param, i| {
|
||||
const param_src = LazySrcLoc.nodeOffset(src_node_offset); // TODO better soruce location
|
||||
param_types[i] = param.ty;
|
||||
comptime_params[i] = param.is_comptime or
|
||||
try sema.typeRequiresComptime(block, param_src, param.ty);
|
||||
const requires_comptime = try sema.typeRequiresComptime(block, param_src, param.ty);
|
||||
comptime_params[i] = param.is_comptime or requires_comptime;
|
||||
is_generic = is_generic or comptime_params[i] or param.ty.tag() == .generic_poison;
|
||||
if (is_extern and is_generic) {
|
||||
// TODO add note: function is generic because of this parameter
|
||||
return sema.fail(block, param_src, "extern function cannot be generic", .{});
|
||||
}
|
||||
if (!param.ty.isValidParamType()) {
|
||||
const opaque_str = if (param.ty.zigTypeTag() == .Opaque) "opaque " else "";
|
||||
const msg = msg: {
|
||||
const msg = try sema.errMsg(block, param_src, "parameter of {s}type '{}' not allowed", .{
|
||||
opaque_str, param.ty.fmt(sema.mod),
|
||||
});
|
||||
errdefer msg.destroy(sema.gpa);
|
||||
|
||||
try sema.addDeclaredHereNote(msg, param.ty);
|
||||
break :msg msg;
|
||||
};
|
||||
return sema.failWithOwnedErrorMsg(block, msg);
|
||||
}
|
||||
if (!Type.fnCallingConventionAllowsZigTypes(cc_workaround) and !(try sema.validateExternType(param.ty, .param_ty))) {
|
||||
const msg = msg: {
|
||||
const msg = try sema.errMsg(block, param_src, "parameter of type '{}' not allowed in function with calling convention '{s}'", .{
|
||||
param.ty.fmt(sema.mod), @tagName(cc_workaround),
|
||||
});
|
||||
errdefer msg.destroy(sema.gpa);
|
||||
|
||||
const src_decl = sema.mod.declPtr(block.src_decl);
|
||||
try sema.explainWhyTypeIsNotExtern(block, param_src, msg, param_src.toSrcLoc(src_decl), param.ty, .param_ty);
|
||||
|
||||
try sema.addDeclaredHereNote(msg, param.ty);
|
||||
break :msg msg;
|
||||
};
|
||||
return sema.failWithOwnedErrorMsg(block, msg);
|
||||
}
|
||||
if (requires_comptime and !param.is_comptime) {
|
||||
const msg = msg: {
|
||||
const msg = try sema.errMsg(block, param_src, "parametter of type '{}' must be declared comptime", .{
|
||||
param.ty.fmt(sema.mod),
|
||||
});
|
||||
errdefer msg.destroy(sema.gpa);
|
||||
|
||||
try sema.addDeclaredHereNote(msg, param.ty);
|
||||
break :msg msg;
|
||||
};
|
||||
return sema.failWithOwnedErrorMsg(block, msg);
|
||||
}
|
||||
}
|
||||
|
||||
const ret_poison = if (!is_generic) rp: {
|
||||
@ -7318,14 +7370,34 @@ fn funcCommon(
|
||||
});
|
||||
};
|
||||
|
||||
// These locals are pulled out from the init expression below to work around
|
||||
// a stage1 compiler bug.
|
||||
// In the case of generic calling convention, or generic alignment, we use
|
||||
// default values which are only meaningful for the generic function, *not*
|
||||
// the instantiation, which can depend on comptime parameters.
|
||||
// Related proposal: https://github.com/ziglang/zig/issues/11834
|
||||
const cc_workaround = cc orelse .Unspecified;
|
||||
const align_workaround = alignment orelse 0;
|
||||
if (!bare_return_type.isValidReturnType()) {
|
||||
const opaque_str = if (bare_return_type.zigTypeTag() == .Opaque) "opaque " else "";
|
||||
const msg = msg: {
|
||||
const msg = try sema.errMsg(block, ret_ty_src, "{s}return type '{}' not allowed", .{
|
||||
opaque_str, bare_return_type.fmt(sema.mod),
|
||||
});
|
||||
errdefer msg.destroy(sema.gpa);
|
||||
|
||||
try sema.addDeclaredHereNote(msg, bare_return_type);
|
||||
break :msg msg;
|
||||
};
|
||||
return sema.failWithOwnedErrorMsg(block, msg);
|
||||
}
|
||||
if (!Type.fnCallingConventionAllowsZigTypes(cc_workaround) and !(try sema.validateExternType(return_type, .ret_ty))) {
|
||||
const msg = msg: {
|
||||
const msg = try sema.errMsg(block, ret_ty_src, "return type '{}' not allowed in function with calling convention '{s}'", .{
|
||||
return_type.fmt(sema.mod), @tagName(cc_workaround),
|
||||
});
|
||||
errdefer msg.destroy(sema.gpa);
|
||||
|
||||
const src_decl = sema.mod.declPtr(block.src_decl);
|
||||
try sema.explainWhyTypeIsNotExtern(block, ret_ty_src, msg, ret_ty_src.toSrcLoc(src_decl), return_type, .ret_ty);
|
||||
|
||||
try sema.addDeclaredHereNote(msg, return_type);
|
||||
break :msg msg;
|
||||
};
|
||||
return sema.failWithOwnedErrorMsg(block, msg);
|
||||
}
|
||||
|
||||
const arch = sema.mod.getTarget().cpu.arch;
|
||||
if (switch (cc_workaround) {
|
||||
@ -18399,7 +18471,7 @@ fn validateExternType(sema: *Sema, ty: Type, position: ExternPosition) CompileEr
|
||||
.BoundFn,
|
||||
.Frame,
|
||||
=> return false,
|
||||
.Void => return position == .union_field,
|
||||
.Void => return position == .union_field or position == .ret_ty,
|
||||
.NoReturn => return position == .ret_ty,
|
||||
.Opaque,
|
||||
.Bool,
|
||||
@ -18411,7 +18483,7 @@ fn validateExternType(sema: *Sema, ty: Type, position: ExternPosition) CompileEr
|
||||
8, 16, 32, 64, 128 => return true,
|
||||
else => return false,
|
||||
},
|
||||
.Fn => return !ty.fnCallingConventionAllowsZigTypes(),
|
||||
.Fn => return !Type.fnCallingConventionAllowsZigTypes(ty.fnCallingConvention()),
|
||||
.Enum => {
|
||||
var buf: Type.Payload.Bits = undefined;
|
||||
return sema.validateExternType(ty.intTagType(&buf), position);
|
||||
|
||||
22
src/type.zig
22
src/type.zig
@ -4643,13 +4643,27 @@ pub const Type = extern union {
|
||||
}
|
||||
|
||||
/// Asserts the type is a function.
|
||||
pub fn fnCallingConventionAllowsZigTypes(self: Type) bool {
|
||||
return switch (self.fnCallingConvention()) {
|
||||
pub fn fnCallingConventionAllowsZigTypes(cc: std.builtin.CallingConvention) bool {
|
||||
return switch (cc) {
|
||||
.Unspecified, .Async, .Inline, .PtxKernel => true,
|
||||
else => false,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn isValidParamType(self: Type) bool {
|
||||
return switch (self.zigTypeTagOrPoison() catch return true) {
|
||||
.Undefined, .Null, .Opaque, .NoReturn => false,
|
||||
else => true,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn isValidReturnType(self: Type) bool {
|
||||
return switch (self.zigTypeTagOrPoison() catch return true) {
|
||||
.Undefined, .Null, .Opaque => false,
|
||||
else => true,
|
||||
};
|
||||
}
|
||||
|
||||
/// Asserts the type is a function.
|
||||
pub fn fnIsVarArgs(self: Type) bool {
|
||||
return switch (self.tag()) {
|
||||
@ -5650,6 +5664,10 @@ pub const Type = extern union {
|
||||
const union_obj = ty.cast(Payload.Union).?.data;
|
||||
return union_obj.srcLoc(mod);
|
||||
},
|
||||
.@"opaque" => {
|
||||
const opaque_obj = ty.cast(Payload.Opaque).?.data;
|
||||
return opaque_obj.srcLoc(mod);
|
||||
},
|
||||
.atomic_order,
|
||||
.atomic_rmw_op,
|
||||
.calling_convention,
|
||||
|
||||
@ -20,10 +20,11 @@ export fn entry4() void {
|
||||
}
|
||||
|
||||
// error
|
||||
// backend=stage1
|
||||
// backend=stage2
|
||||
// target=native
|
||||
//
|
||||
// tmp.zig:3:28: error: parameter of opaque type 'FooType' not allowed
|
||||
// tmp.zig:8:28: error: parameter of type '@Type(.Null)' not allowed
|
||||
// tmp.zig:12:11: error: parameter of opaque type 'FooType' not allowed
|
||||
// tmp.zig:17:11: error: parameter of type '@Type(.Null)' not allowed
|
||||
// :3:24: error: parameter of opaque type 'tmp.FooType' not allowed
|
||||
// :1:17: note: opaque declared here
|
||||
// :8:24: error: parameter of type '@TypeOf(null)' not allowed
|
||||
// :12:1: error: parameter of opaque type 'tmp.FooType' not allowed
|
||||
// :17:1: error: parameter of type '@TypeOf(null)' not allowed
|
||||
19
test/cases/compile_errors/function_returning_opaque_type.zig
Normal file
19
test/cases/compile_errors/function_returning_opaque_type.zig
Normal file
@ -0,0 +1,19 @@
|
||||
const FooType = opaque {};
|
||||
export fn bar() !FooType {
|
||||
return error.InvalidValue;
|
||||
}
|
||||
export fn bav() !@TypeOf(null) {
|
||||
return error.InvalidValue;
|
||||
}
|
||||
export fn baz() !@TypeOf(undefined) {
|
||||
return error.InvalidValue;
|
||||
}
|
||||
|
||||
// error
|
||||
// backend=stage2
|
||||
// target=native
|
||||
//
|
||||
// :2:18: error: opaque return type 'tmp.FooType' not allowed
|
||||
// :1:17: note: opaque declared here
|
||||
// :5:18: error: return type '@TypeOf(null)' not allowed
|
||||
// :8:18: error: return type '@TypeOf(undefined)' not allowed
|
||||
@ -0,0 +1,11 @@
|
||||
const Foo = enum { A, B, C };
|
||||
export fn entry(foo: Foo) void { _ = foo; }
|
||||
|
||||
// error
|
||||
// backend=stage2
|
||||
// target=native
|
||||
//
|
||||
// :2:8: error: parameter of type 'tmp.Foo' not allowed in function with calling convention 'C'
|
||||
// :2:8: note: enum tag type 'u2' is not extern compatible
|
||||
// :2:8: note: only integers with power of two bits are extern compatible
|
||||
// :1:13: note: enum declared here
|
||||
@ -0,0 +1,14 @@
|
||||
const Foo = struct {
|
||||
A: i32,
|
||||
B: f32,
|
||||
C: bool,
|
||||
};
|
||||
export fn entry(foo: Foo) void { _ = foo; }
|
||||
|
||||
// error
|
||||
// backend=stage2
|
||||
// target=native
|
||||
//
|
||||
// :6:8: error: parameter of type 'tmp.Foo' not allowed in function with calling convention 'C'
|
||||
// :6:8: note: only structs with packed or extern layout are extern compatible
|
||||
// :1:13: note: struct declared here
|
||||
@ -0,0 +1,14 @@
|
||||
const Foo = union {
|
||||
A: i32,
|
||||
B: f32,
|
||||
C: bool,
|
||||
};
|
||||
export fn entry(foo: Foo) void { _ = foo; }
|
||||
|
||||
// error
|
||||
// backend=stage2
|
||||
// target=native
|
||||
//
|
||||
// :6:8: error: parameter of type 'tmp.Foo' not allowed in function with calling convention 'C'
|
||||
// :6:8: note: only unions with packed or extern layout are extern compatible
|
||||
// :1:13: note: union declared here
|
||||
@ -1,19 +0,0 @@
|
||||
const FooType = opaque {};
|
||||
export fn bar() !FooType {
|
||||
return error.InvalidValue;
|
||||
}
|
||||
export fn bav() !@TypeOf(null) {
|
||||
return error.InvalidValue;
|
||||
}
|
||||
export fn baz() !@TypeOf(undefined) {
|
||||
return error.InvalidValue;
|
||||
}
|
||||
|
||||
// error
|
||||
// backend=stage1
|
||||
// target=native
|
||||
//
|
||||
// tmp.zig:2:18: error: Opaque return type 'FooType' not allowed
|
||||
// tmp.zig:1:1: note: type declared here
|
||||
// tmp.zig:5:18: error: Null return type '@Type(.Null)' not allowed
|
||||
// tmp.zig:8:18: error: Undefined return type '@Type(.Undefined)' not allowed
|
||||
@ -1,8 +0,0 @@
|
||||
const Foo = enum { A, B, C };
|
||||
export fn entry(foo: Foo) void { _ = foo; }
|
||||
|
||||
// error
|
||||
// backend=stage1
|
||||
// target=native
|
||||
//
|
||||
// tmp.zig:2:22: error: parameter of type 'Foo' not allowed in function with calling convention 'C'
|
||||
@ -1,12 +0,0 @@
|
||||
const Foo = struct {
|
||||
A: i32,
|
||||
B: f32,
|
||||
C: bool,
|
||||
};
|
||||
export fn entry(foo: Foo) void { _ = foo; }
|
||||
|
||||
// error
|
||||
// backend=stage1
|
||||
// target=native
|
||||
//
|
||||
// tmp.zig:6:22: error: parameter of type 'Foo' not allowed in function with calling convention 'C'
|
||||
@ -1,12 +0,0 @@
|
||||
const Foo = union {
|
||||
A: i32,
|
||||
B: f32,
|
||||
C: bool,
|
||||
};
|
||||
export fn entry(foo: Foo) void { _ = foo; }
|
||||
|
||||
// error
|
||||
// backend=stage1
|
||||
// target=native
|
||||
//
|
||||
// tmp.zig:6:22: error: parameter of type 'Foo' not allowed in function with calling convention 'C'
|
||||
Loading…
x
Reference in New Issue
Block a user