zig/deps/aro/Attribute.zig
Veikka Tuominen 58b07ea14f sync Aro dependency
ref: 482951b0e0eb99ec5dd122e7f893a007586f83f4
2023-10-17 11:55:01 +03:00

1348 lines
44 KiB
Zig
Vendored

const std = @import("std");
const mem = std.mem;
const ZigType = std.builtin.Type;
const CallingConvention = @import("lib.zig").CallingConvention;
const Compilation = @import("Compilation.zig");
const Diagnostics = @import("Diagnostics.zig");
const Parser = @import("Parser.zig");
const Tree = @import("Tree.zig");
const NodeIndex = Tree.NodeIndex;
const TokenIndex = Tree.TokenIndex;
const Type = @import("Type.zig");
const Value = @import("Value.zig");
const Attribute = @This();
tag: Tag,
syntax: Syntax,
args: Arguments,
pub const Syntax = enum {
c2x,
declspec,
gnu,
keyword,
};
pub const Kind = enum {
c2x,
declspec,
gnu,
pub fn toSyntax(kind: Kind) Syntax {
return switch (kind) {
.c2x => .c2x,
.declspec => .declspec,
.gnu => .gnu,
};
}
};
pub const ArgumentType = enum {
string,
identifier,
int,
alignment,
float,
expression,
nullptr_t,
pub fn toString(self: ArgumentType) []const u8 {
return switch (self) {
.string => "a string",
.identifier => "an identifier",
.int, .alignment => "an integer constant",
.nullptr_t => "nullptr",
.float => "a floating point number",
.expression => "an expression",
};
}
fn fromType(comptime T: type) ArgumentType {
return switch (T) {
[]const u8 => .string,
Identifier => .identifier,
u32 => .int,
Alignment => .alignment,
CallingConvention => .identifier,
else => switch (@typeInfo(T)) {
.Enum => if (T.opts.enum_kind == .string) .string else .identifier,
else => unreachable,
},
};
}
fn fromVal(value: Value) ArgumentType {
return switch (value.tag) {
.int => .int,
.bytes => .string,
.unavailable => .expression,
.float => .float,
.nullptr_t => .nullptr_t,
};
}
};
fn getArguments(comptime descriptor: type) []const ZigType.StructField {
return if (@hasDecl(descriptor, "Args")) std.meta.fields(descriptor.Args) else &.{};
}
/// number of required arguments
pub fn requiredArgCount(attr: Tag) u32 {
switch (attr) {
inline else => |tag| {
comptime var needed = 0;
comptime {
const fields = getArguments(@field(attributes, @tagName(tag)));
for (fields) |arg_field| {
if (!mem.eql(u8, arg_field.name, "__name_tok") and @typeInfo(arg_field.type) != .Optional) needed += 1;
}
}
return needed;
},
}
}
/// maximum number of args that can be passed
pub fn maxArgCount(attr: Tag) u32 {
switch (attr) {
inline else => |tag| {
comptime var max = 0;
comptime {
const fields = getArguments(@field(attributes, @tagName(tag)));
for (fields) |arg_field| {
if (!mem.eql(u8, arg_field.name, "__name_tok")) max += 1;
}
}
return max;
},
}
}
fn UnwrapOptional(comptime T: type) type {
return switch (@typeInfo(T)) {
.Optional => |optional| optional.child,
else => T,
};
}
pub const Formatting = struct {
/// The quote char (single or double) to use when printing identifiers/strings corresponding
/// to the enum in the first field of the Args of `attr`. Identifier enums use single quotes, string enums
/// use double quotes
fn quoteChar(attr: Tag) []const u8 {
switch (attr) {
.calling_convention => unreachable,
inline else => |tag| {
const fields = getArguments(@field(attributes, @tagName(tag)));
if (fields.len == 0) unreachable;
const Unwrapped = UnwrapOptional(fields[0].type);
if (@typeInfo(Unwrapped) != .Enum) unreachable;
return if (Unwrapped.opts.enum_kind == .identifier) "'" else "\"";
},
}
}
/// returns a comma-separated string of quoted enum values, representing the valid
/// choices for the string or identifier enum of the first field of the Args of `attr`.
pub fn choices(attr: Tag) []const u8 {
switch (attr) {
.calling_convention => unreachable,
inline else => |tag| {
const fields = getArguments(@field(attributes, @tagName(tag)));
if (fields.len == 0) unreachable;
const Unwrapped = UnwrapOptional(fields[0].type);
if (@typeInfo(Unwrapped) != .Enum) unreachable;
const enum_fields = @typeInfo(Unwrapped).Enum.fields;
@setEvalBranchQuota(3000);
const quote = comptime quoteChar(@enumFromInt(@intFromEnum(tag)));
comptime var values: []const u8 = quote ++ enum_fields[0].name ++ quote;
inline for (enum_fields[1..]) |enum_field| {
values = values ++ ", ";
values = values ++ quote ++ enum_field.name ++ quote;
}
return values;
},
}
}
};
/// Checks if the first argument (if it exists) is an identifier enum
pub fn wantsIdentEnum(attr: Tag) bool {
switch (attr) {
.calling_convention => return false,
inline else => |tag| {
const fields = getArguments(@field(attributes, @tagName(tag)));
if (fields.len == 0) return false;
const Unwrapped = UnwrapOptional(fields[0].type);
if (@typeInfo(Unwrapped) != .Enum) return false;
return Unwrapped.opts.enum_kind == .identifier;
},
}
}
pub fn diagnoseIdent(attr: Tag, arguments: *Arguments, ident: []const u8) ?Diagnostics.Message {
switch (attr) {
inline else => |tag| {
const fields = getArguments(@field(attributes, @tagName(tag)));
if (fields.len == 0) unreachable;
const Unwrapped = UnwrapOptional(fields[0].type);
if (@typeInfo(Unwrapped) != .Enum) unreachable;
if (std.meta.stringToEnum(Unwrapped, normalize(ident))) |enum_val| {
@field(@field(arguments, @tagName(tag)), fields[0].name) = enum_val;
return null;
}
return Diagnostics.Message{
.tag = .unknown_attr_enum,
.extra = .{ .attr_enum = .{ .tag = attr } },
};
},
}
}
pub fn wantsAlignment(attr: Tag, idx: usize) bool {
switch (attr) {
inline else => |tag| {
const fields = getArguments(@field(attributes, @tagName(tag)));
if (fields.len == 0) return false;
return switch (idx) {
inline 0...fields.len - 1 => |i| UnwrapOptional(fields[i].type) == Alignment,
else => false,
};
},
}
}
pub fn diagnoseAlignment(attr: Tag, arguments: *Arguments, arg_idx: u32, val: Value, ty: Type, comp: *Compilation) ?Diagnostics.Message {
switch (attr) {
inline else => |tag| {
const arg_fields = getArguments(@field(attributes, @tagName(tag)));
if (arg_fields.len == 0) unreachable;
switch (arg_idx) {
inline 0...arg_fields.len - 1 => |arg_i| {
if (UnwrapOptional(arg_fields[arg_i].type) != Alignment) unreachable;
if (val.tag != .int) return Diagnostics.Message{ .tag = .alignas_unavailable };
if (val.compare(.lt, Value.int(0), ty, comp)) {
return Diagnostics.Message{ .tag = .negative_alignment, .extra = .{ .signed = val.signExtend(ty, comp) } };
}
const requested = std.math.cast(u29, val.data.int) orelse {
return Diagnostics.Message{ .tag = .maximum_alignment, .extra = .{ .unsigned = val.data.int } };
};
if (!std.mem.isValidAlign(requested)) return Diagnostics.Message{ .tag = .non_pow2_align };
@field(@field(arguments, @tagName(tag)), arg_fields[arg_i].name) = Alignment{ .requested = requested };
return null;
},
else => unreachable,
}
},
}
}
fn diagnoseField(
comptime decl: ZigType.Declaration,
comptime field: ZigType.StructField,
comptime wanted: type,
arguments: *Arguments,
val: Value,
node: Tree.Node,
strings: []const u8,
) ?Diagnostics.Message {
switch (val.tag) {
.int => {
if (@typeInfo(wanted) == .Int) {
@field(@field(arguments, decl.name), field.name) = val.getInt(wanted);
return null;
}
},
.bytes => {
const bytes = val.data.bytes.trim(1); // remove null terminator
if (wanted == Value.ByteRange) {
@field(@field(arguments, decl.name), field.name) = bytes;
return null;
} else if (@typeInfo(wanted) == .Enum and @hasDecl(wanted, "opts") and wanted.opts.enum_kind == .string) {
const str = bytes.slice(strings);
if (std.meta.stringToEnum(wanted, str)) |enum_val| {
@field(@field(arguments, decl.name), field.name) = enum_val;
return null;
} else {
@setEvalBranchQuota(3000);
return Diagnostics.Message{
.tag = .unknown_attr_enum,
.extra = .{ .attr_enum = .{ .tag = std.meta.stringToEnum(Tag, decl.name).? } },
};
}
}
},
else => {
if (wanted == Identifier and node.tag == .decl_ref_expr) {
@field(@field(arguments, decl.name), field.name) = Identifier{ .tok = node.data.decl_ref };
return null;
}
},
}
return Diagnostics.Message{
.tag = .attribute_arg_invalid,
.extra = .{ .attr_arg_type = .{ .expected = ArgumentType.fromType(wanted), .actual = ArgumentType.fromVal(val) } },
};
}
pub fn diagnose(attr: Tag, arguments: *Arguments, arg_idx: u32, val: Value, node: Tree.Node, strings: []const u8) ?Diagnostics.Message {
switch (attr) {
inline else => |tag| {
const decl = @typeInfo(attributes).Struct.decls[@intFromEnum(tag)];
const max_arg_count = comptime maxArgCount(tag);
if (arg_idx >= max_arg_count) return Diagnostics.Message{
.tag = .attribute_too_many_args,
.extra = .{ .attr_arg_count = .{ .attribute = attr, .expected = max_arg_count } },
};
const arg_fields = getArguments(@field(attributes, decl.name));
switch (arg_idx) {
inline 0...arg_fields.len - 1 => |arg_i| {
return diagnoseField(decl, arg_fields[arg_i], UnwrapOptional(arg_fields[arg_i].type), arguments, val, node, strings);
},
else => unreachable,
}
},
}
}
const EnumTypes = enum {
string,
identifier,
};
pub const Alignment = struct {
node: NodeIndex = .none,
requested: u29,
};
pub const Identifier = struct {
tok: TokenIndex = 0,
};
const attributes = struct {
pub const access = struct {
const gnu = "access";
const Args = struct {
access_mode: enum {
read_only,
read_write,
write_only,
none,
const opts = struct {
const enum_kind = .identifier;
};
},
ref_index: u32,
size_index: ?u32 = null,
};
};
pub const alias = struct {
const gnu = "alias";
const Args = struct {
alias: Value.ByteRange,
};
};
pub const aligned = struct {
const gnu = "aligned";
const declspec = "align";
const Args = struct {
alignment: ?Alignment = null,
__name_tok: TokenIndex,
};
};
pub const alloc_align = struct {
const gnu = "alloc_align";
const Args = struct {
position: u32,
};
};
pub const alloc_size = struct {
const gnu = "alloc_size";
const Args = struct {
position_1: u32,
position_2: ?u32 = null,
};
};
pub const allocate = struct {
const declspec = "allocate";
const Args = struct {
segname: Value.ByteRange,
};
};
pub const allocator = struct {
const declspec = "allocator";
};
pub const always_inline = struct {
const gnu = "always_inline";
};
pub const appdomain = struct {
const declspec = "appdomain";
};
pub const artificial = struct {
const gnu = "artificial";
};
pub const assume_aligned = struct {
const gnu = "assume_aligned";
const Args = struct {
alignment: Alignment,
offset: ?u32 = null,
};
};
pub const cleanup = struct {
const gnu = "cleanup";
const Args = struct {
function: Identifier,
};
};
pub const code_seg = struct {
const declspec = "code_seg";
const Args = struct {
segname: Value.ByteRange,
};
};
pub const cold = struct {
const gnu = "cold";
};
pub const common = struct {
const gnu = "common";
};
pub const @"const" = struct {
const gnu = "const";
};
pub const constructor = struct {
const gnu = "constructor";
const Args = struct {
priority: ?u32 = null,
};
};
pub const copy = struct {
const gnu = "copy";
const Args = struct {
function: Identifier,
};
};
pub const deprecated = struct {
const gnu = "deprecated";
const declspec = "deprecated";
const c2x = "deprecated";
const Args = struct {
msg: ?Value.ByteRange = null,
__name_tok: TokenIndex,
};
};
pub const designated_init = struct {
const gnu = "designated_init";
};
pub const destructor = struct {
const gnu = "destructor";
const Args = struct {
priority: ?u32 = null,
};
};
pub const dllexport = struct {
const declspec = "dllexport";
};
pub const dllimport = struct {
const declspec = "dllimport";
};
pub const @"error" = struct {
const gnu = "error";
const Args = struct {
msg: Value.ByteRange,
__name_tok: TokenIndex,
};
};
pub const externally_visible = struct {
const gnu = "externally_visible";
};
pub const fallthrough = struct {
const gnu = "fallthrough";
const c2x = "fallthrough";
};
pub const flatten = struct {
const gnu = "flatten";
};
pub const format = struct {
const gnu = "format";
const Args = struct {
archetype: enum {
printf,
scanf,
strftime,
strfmon,
const opts = struct {
const enum_kind = .identifier;
};
},
string_index: u32,
first_to_check: u32,
};
};
pub const format_arg = struct {
const gnu = "format_arg";
const Args = struct {
string_index: u32,
};
};
pub const gnu_inline = struct {
const gnu = "gnu_inline";
};
pub const hot = struct {
const gnu = "hot";
};
pub const ifunc = struct {
const gnu = "ifunc";
const Args = struct {
resolver: Value.ByteRange,
};
};
pub const interrupt = struct {
const gnu = "interrupt";
};
pub const interrupt_handler = struct {
const gnu = "interrupt_handler";
};
pub const jitintrinsic = struct {
const declspec = "jitintrinsic";
};
pub const leaf = struct {
const gnu = "leaf";
};
pub const malloc = struct {
const gnu = "malloc";
};
pub const may_alias = struct {
const gnu = "may_alias";
};
pub const mode = struct {
const gnu = "mode";
const Args = struct {
mode: enum {
// zig fmt: off
byte, word, pointer,
BI, QI, HI,
PSI, SI, PDI,
DI, TI, OI,
XI, QF, HF,
TQF, SF, DF,
XF, SD, DD,
TD, TF, QQ,
HQ, SQ, DQ,
TQ, UQQ, UHQ,
USQ, UDQ, UTQ,
HA, SA, DA,
TA, UHA, USA,
UDA, UTA, CC,
BLK, VOID, QC,
HC, SC, DC,
XC, TC, CQI,
CHI, CSI, CDI,
CTI, COI, CPSI,
BND32, BND64,
// zig fmt: on
const opts = struct {
const enum_kind = .identifier;
};
},
};
};
pub const naked = struct {
const declspec = "naked";
};
pub const no_address_safety_analysis = struct {
const gnu = "no_address_safety_analysise";
};
pub const no_icf = struct {
const gnu = "no_icf";
};
pub const no_instrument_function = struct {
const gnu = "no_instrument_function";
};
pub const no_profile_instrument_function = struct {
const gnu = "no_profile_instrument_function";
};
pub const no_reorder = struct {
const gnu = "no_reorder";
};
pub const no_sanitize = struct {
const gnu = "no_sanitize";
/// Todo: represent args as union?
const Args = struct {
alignment: Value.ByteRange,
object_size: ?Value.ByteRange = null,
};
};
pub const no_sanitize_address = struct {
const gnu = "no_sanitize_address";
const declspec = "no_sanitize_address";
};
pub const no_sanitize_coverage = struct {
const gnu = "no_sanitize_coverage";
};
pub const no_sanitize_thread = struct {
const gnu = "no_sanitize_thread";
};
pub const no_sanitize_undefined = struct {
const gnu = "no_sanitize_undefined";
};
pub const no_split_stack = struct {
const gnu = "no_split_stack";
};
pub const no_stack_limit = struct {
const gnu = "no_stack_limit";
};
pub const no_stack_protector = struct {
const gnu = "no_stack_protector";
};
pub const @"noalias" = struct {
const declspec = "noalias";
};
pub const noclone = struct {
const gnu = "noclone";
};
pub const nocommon = struct {
const gnu = "nocommon";
};
pub const nodiscard = struct {
const c2x = "nodiscard";
};
pub const noinit = struct {
const gnu = "noinit";
};
pub const @"noinline" = struct {
const gnu = "noinline";
const declspec = "noinline";
};
pub const noipa = struct {
const gnu = "noipa";
};
// TODO: arbitrary number of arguments
// const nonnull = struct {
// const gnu = "nonnull";
// const Args = struct {
// arg_index: []const u32,
// };
// };
pub const nonstring = struct {
const gnu = "nonstring";
};
pub const noplt = struct {
const gnu = "noplt";
};
pub const @"noreturn" = struct {
const gnu = "noreturn";
const c2x = "noreturn";
const declspec = "noreturn";
};
// TODO: union args ?
// const optimize = struct {
// const gnu = "optimize";
// const Args = struct {
// optimize, // u32 | []const u8 -- optimize?
// };
// };
pub const @"packed" = struct {
const gnu = "packed";
};
pub const patchable_function_entry = struct {
const gnu = "patchable_function_entry";
};
pub const persistent = struct {
const gnu = "persistent";
};
pub const process = struct {
const declspec = "process";
};
pub const pure = struct {
const gnu = "pure";
};
pub const reproducible = struct {
const c2x = "reproducible";
};
pub const restrict = struct {
const declspec = "restrict";
};
pub const retain = struct {
const gnu = "retain";
};
pub const returns_nonnull = struct {
const gnu = "returns_nonnull";
};
pub const returns_twice = struct {
const gnu = "returns_twice";
};
pub const safebuffers = struct {
const declspec = "safebuffers";
};
pub const scalar_storage_order = struct {
const gnu = "scalar_storage_order";
const Args = struct {
order: enum {
@"little-endian",
@"big-endian",
const opts = struct {
const enum_kind = .string;
};
},
};
};
pub const section = struct {
const gnu = "section";
const Args = struct {
name: Value.ByteRange,
};
};
pub const selectany = struct {
const declspec = "selectany";
};
pub const sentinel = struct {
const gnu = "sentinel";
const Args = struct {
position: ?u32 = null,
};
};
pub const simd = struct {
const gnu = "simd";
const Args = struct {
mask: ?enum {
notinbranch,
inbranch,
const opts = struct {
const enum_kind = .string;
};
} = null,
};
};
pub const spectre = struct {
const declspec = "spectre";
const Args = struct {
arg: enum {
nomitigation,
const opts = struct {
const enum_kind = .identifier;
};
},
};
};
pub const stack_protect = struct {
const gnu = "stack_protect";
};
pub const symver = struct {
const gnu = "symver";
const Args = struct {
version: Value.ByteRange, // TODO: validate format "name2@nodename"
};
};
pub const target = struct {
const gnu = "target";
const Args = struct {
options: Value.ByteRange, // TODO: multiple arguments
};
};
pub const target_clones = struct {
const gnu = "target_clones";
const Args = struct {
options: Value.ByteRange, // TODO: multiple arguments
};
};
pub const thread = struct {
const declspec = "thread";
};
pub const tls_model = struct {
const gnu = "tls_model";
const Args = struct {
model: enum {
@"global-dynamic",
@"local-dynamic",
@"initial-exec",
@"local-exec",
const opts = struct {
const enum_kind = .string;
};
},
};
};
pub const transparent_union = struct {
const gnu = "transparent_union";
};
pub const unavailable = struct {
const gnu = "unavailable";
const Args = struct {
msg: ?Value.ByteRange = null,
__name_tok: TokenIndex,
};
};
pub const uninitialized = struct {
const gnu = "uninitialized";
};
pub const unsequenced = struct {
const c2x = "unsequenced";
};
pub const unused = struct {
const gnu = "unused";
const c2x = "maybe_unused";
};
pub const used = struct {
const gnu = "used";
};
pub const uuid = struct {
const declspec = "uuid";
const Args = struct {
uuid: Value.ByteRange,
};
};
pub const vector_size = struct {
const gnu = "vector_size";
const Args = struct {
bytes: u32, // TODO: validate "The bytes argument must be a positive power-of-two multiple of the base type size"
};
};
pub const visibility = struct {
const gnu = "visibility";
const Args = struct {
visibility_type: enum {
default,
hidden,
internal,
protected,
const opts = struct {
const enum_kind = .string;
};
},
};
};
pub const warn_if_not_aligned = struct {
const gnu = "warn_if_not_aligned";
const Args = struct {
alignment: Alignment,
};
};
pub const warn_unused_result = struct {
const gnu = "warn_unused_result";
};
pub const warning = struct {
const gnu = "warning";
const Args = struct {
msg: Value.ByteRange,
__name_tok: TokenIndex,
};
};
pub const weak = struct {
const gnu = "weak";
};
pub const weakref = struct {
const gnu = "weakref";
const Args = struct {
target: ?Value.ByteRange = null,
};
};
pub const zero_call_used_regs = struct {
const gnu = "zero_call_used_regs";
const Args = struct {
choice: enum {
skip,
used,
@"used-gpr",
@"used-arg",
@"used-gpr-arg",
all,
@"all-gpr",
@"all-arg",
@"all-gpr-arg",
const opts = struct {
const enum_kind = .string;
};
},
};
};
pub const asm_label = struct {
const Args = struct {
name: Value.ByteRange,
};
};
pub const calling_convention = struct {
const Args = struct {
cc: CallingConvention,
};
};
};
pub const Tag = std.meta.DeclEnum(attributes);
pub const Arguments = blk: {
const decls = @typeInfo(attributes).Struct.decls;
var union_fields: [decls.len]ZigType.UnionField = undefined;
inline for (decls, &union_fields) |decl, *field| {
field.* = .{
.name = decl.name,
.type = if (@hasDecl(@field(attributes, decl.name), "Args")) @field(attributes, decl.name).Args else void,
.alignment = 0,
};
}
break :blk @Type(.{
.Union = .{
.layout = .Auto,
.tag_type = null,
.fields = &union_fields,
.decls = &.{},
},
});
};
pub fn ArgumentsForTag(comptime tag: Tag) type {
const decl = @typeInfo(attributes).Struct.decls[@intFromEnum(tag)];
return if (@hasDecl(@field(attributes, decl.name), "Args")) @field(attributes, decl.name).Args else void;
}
pub fn initArguments(tag: Tag, name_tok: TokenIndex) Arguments {
switch (tag) {
inline else => |arg_tag| {
const union_element = @field(attributes, @tagName(arg_tag));
const has_args = @hasDecl(union_element, "Args");
const init = if (has_args) std.mem.zeroInit(union_element.Args, .{}) else {};
var args = @unionInit(Arguments, @tagName(arg_tag), init);
if (has_args and @hasField(@field(attributes, @tagName(arg_tag)).Args, "__name_tok")) {
@field(args, @tagName(arg_tag)).__name_tok = name_tok;
}
return args;
},
}
}
pub fn fromString(kind: Kind, namespace: ?[]const u8, name: []const u8) ?Tag {
return switch (kind) {
.c2x => fromStringC2X(namespace, name),
.declspec => fromStringDeclspec(name),
.gnu => fromStringGnu(name),
};
}
fn fromStringGnu(name: []const u8) ?Tag {
const normalized = normalize(name);
const decls = @typeInfo(attributes).Struct.decls;
@setEvalBranchQuota(3000);
inline for (decls, 0..) |decl, i| {
if (@hasDecl(@field(attributes, decl.name), "gnu")) {
if (mem.eql(u8, @field(attributes, decl.name).gnu, normalized)) {
return @enumFromInt(i);
}
}
}
return null;
}
fn fromStringC2X(namespace: ?[]const u8, name: []const u8) ?Tag {
const normalized = normalize(name);
if (namespace) |ns| {
const normalized_ns = normalize(ns);
if (mem.eql(u8, normalized_ns, "gnu")) {
return fromStringGnu(normalized);
}
return null;
}
const decls = @typeInfo(attributes).Struct.decls;
inline for (decls, 0..) |decl, i| {
if (@hasDecl(@field(attributes, decl.name), "c2x")) {
if (mem.eql(u8, @field(attributes, decl.name).c2x, normalized)) {
return @enumFromInt(i);
}
}
}
return null;
}
fn fromStringDeclspec(name: []const u8) ?Tag {
const normalized = normalize(name);
const decls = @typeInfo(attributes).Struct.decls;
inline for (decls, 0..) |decl, i| {
if (@hasDecl(@field(attributes, decl.name), "declspec")) {
if (mem.eql(u8, @field(attributes, decl.name).declspec, normalized)) {
return @enumFromInt(i);
}
}
}
return null;
}
fn normalize(name: []const u8) []const u8 {
if (name.len >= 4 and mem.startsWith(u8, name, "__") and mem.endsWith(u8, name, "__")) {
return name[2 .. name.len - 2];
}
return name;
}
fn ignoredAttrErr(p: *Parser, tok: TokenIndex, attr: Attribute.Tag, context: []const u8) !void {
const strings_top = p.strings.items.len;
defer p.strings.items.len = strings_top;
try p.strings.writer().print("attribute '{s}' ignored on {s}", .{ @tagName(attr), context });
const str = try p.comp.diag.arena.allocator().dupe(u8, p.strings.items[strings_top..]);
try p.errStr(.ignored_attribute, tok, str);
}
pub const applyParameterAttributes = applyVariableAttributes;
pub fn applyVariableAttributes(p: *Parser, ty: Type, attr_buf_start: usize, tag: ?Diagnostics.Tag) !Type {
const attrs = p.attr_buf.items(.attr)[attr_buf_start..];
const toks = p.attr_buf.items(.tok)[attr_buf_start..];
p.attr_application_buf.items.len = 0;
var base_ty = ty;
if (base_ty.specifier == .attributed) base_ty = base_ty.data.attributed.base;
var common = false;
var nocommon = false;
for (attrs, toks) |attr, tok| switch (attr.tag) {
// zig fmt: off
.alias, .may_alias, .deprecated, .unavailable, .unused, .warn_if_not_aligned, .weak, .used,
.noinit, .retain, .persistent, .section, .mode, .asm_label,
=> try p.attr_application_buf.append(p.gpa, attr),
// zig fmt: on
.common => if (nocommon) {
try p.errTok(.ignore_common, tok);
} else {
try p.attr_application_buf.append(p.gpa, attr);
common = true;
},
.nocommon => if (common) {
try p.errTok(.ignore_nocommon, tok);
} else {
try p.attr_application_buf.append(p.gpa, attr);
nocommon = true;
},
.vector_size => try attr.applyVectorSize(p, tok, &base_ty),
.aligned => try attr.applyAligned(p, base_ty, tag),
.nonstring => if (!base_ty.isArray() or !(base_ty.is(.char) or base_ty.is(.uchar) or base_ty.is(.schar))) {
try p.errStr(.non_string_ignored, tok, try p.typeStr(ty));
} else {
try p.attr_application_buf.append(p.gpa, attr);
},
.uninitialized => if (p.func.ty == null) {
try p.errStr(.local_variable_attribute, tok, "uninitialized");
} else {
try p.attr_application_buf.append(p.gpa, attr);
},
.cleanup => if (p.func.ty == null) {
try p.errStr(.local_variable_attribute, tok, "cleanup");
} else {
try p.attr_application_buf.append(p.gpa, attr);
},
.alloc_size,
.copy,
.tls_model,
.visibility,
=> std.debug.panic("apply variable attribute {s}", .{@tagName(attr.tag)}),
else => try ignoredAttrErr(p, tok, attr.tag, "variables"),
};
const existing = ty.getAttributes();
if (existing.len == 0 and p.attr_application_buf.items.len == 0) return base_ty;
if (existing.len == 0) return base_ty.withAttributes(p.arena, p.attr_application_buf.items);
const attributed_type = try Type.Attributed.create(p.arena, base_ty, existing, p.attr_application_buf.items);
return Type{ .specifier = .attributed, .data = .{ .attributed = attributed_type } };
}
pub fn applyFieldAttributes(p: *Parser, field_ty: *Type, attr_buf_start: usize) ![]const Attribute {
const attrs = p.attr_buf.items(.attr)[attr_buf_start..];
const toks = p.attr_buf.items(.tok)[attr_buf_start..];
p.attr_application_buf.items.len = 0;
for (attrs, toks) |attr, tok| switch (attr.tag) {
// zig fmt: off
.@"packed", .may_alias, .deprecated, .unavailable, .unused, .warn_if_not_aligned, .mode,
=> try p.attr_application_buf.append(p.gpa, attr),
// zig fmt: on
.vector_size => try attr.applyVectorSize(p, tok, field_ty),
.aligned => try attr.applyAligned(p, field_ty.*, null),
else => try ignoredAttrErr(p, tok, attr.tag, "fields"),
};
if (p.attr_application_buf.items.len == 0) return &[0]Attribute{};
return p.arena.dupe(Attribute, p.attr_application_buf.items);
}
pub fn applyTypeAttributes(p: *Parser, ty: Type, attr_buf_start: usize, tag: ?Diagnostics.Tag) !Type {
const attrs = p.attr_buf.items(.attr)[attr_buf_start..];
const toks = p.attr_buf.items(.tok)[attr_buf_start..];
p.attr_application_buf.items.len = 0;
var base_ty = ty;
if (base_ty.specifier == .attributed) base_ty = base_ty.data.attributed.base;
for (attrs, toks) |attr, tok| switch (attr.tag) {
// zig fmt: off
.@"packed", .may_alias, .deprecated, .unavailable, .unused, .warn_if_not_aligned, .mode,
=> try p.attr_application_buf.append(p.gpa, attr),
// zig fmt: on
.transparent_union => try attr.applyTransparentUnion(p, tok, base_ty),
.vector_size => try attr.applyVectorSize(p, tok, &base_ty),
.aligned => try attr.applyAligned(p, base_ty, tag),
.designated_init => if (base_ty.is(.@"struct")) {
try p.attr_application_buf.append(p.gpa, attr);
} else {
try p.errTok(.designated_init_invalid, tok);
},
.alloc_size,
.copy,
.scalar_storage_order,
.nonstring,
=> std.debug.panic("apply type attribute {s}", .{@tagName(attr.tag)}),
else => try ignoredAttrErr(p, tok, attr.tag, "types"),
};
const existing = ty.getAttributes();
// TODO: the alignment annotation on a type should override
// the decl it refers to. This might not be true for others. Maybe bug.
// if there are annotations on this type def use those.
if (p.attr_application_buf.items.len > 0) {
return try base_ty.withAttributes(p.arena, p.attr_application_buf.items);
} else if (existing.len > 0) {
// else use the ones on the typedef decl we were refering to.
return try base_ty.withAttributes(p.arena, existing);
}
return base_ty;
}
pub fn applyFunctionAttributes(p: *Parser, ty: Type, attr_buf_start: usize) !Type {
const attrs = p.attr_buf.items(.attr)[attr_buf_start..];
const toks = p.attr_buf.items(.tok)[attr_buf_start..];
p.attr_application_buf.items.len = 0;
var base_ty = ty;
if (base_ty.specifier == .attributed) base_ty = base_ty.data.attributed.base;
var hot = false;
var cold = false;
var @"noinline" = false;
var always_inline = false;
for (attrs, toks) |attr, tok| switch (attr.tag) {
// zig fmt: off
.noreturn, .unused, .used, .warning, .deprecated, .unavailable, .weak, .pure, .leaf,
.@"const", .warn_unused_result, .section, .returns_nonnull, .returns_twice, .@"error",
.externally_visible, .retain, .flatten, .gnu_inline, .alias, .asm_label, .nodiscard,
.reproducible, .unsequenced,
=> try p.attr_application_buf.append(p.gpa, attr),
// zig fmt: on
.hot => if (cold) {
try p.errTok(.ignore_hot, tok);
} else {
try p.attr_application_buf.append(p.gpa, attr);
hot = true;
},
.cold => if (hot) {
try p.errTok(.ignore_cold, tok);
} else {
try p.attr_application_buf.append(p.gpa, attr);
cold = true;
},
.always_inline => if (@"noinline") {
try p.errTok(.ignore_always_inline, tok);
} else {
try p.attr_application_buf.append(p.gpa, attr);
always_inline = true;
},
.@"noinline" => if (always_inline) {
try p.errTok(.ignore_noinline, tok);
} else {
try p.attr_application_buf.append(p.gpa, attr);
@"noinline" = true;
},
.aligned => try attr.applyAligned(p, base_ty, null),
.format => try attr.applyFormat(p, base_ty),
.calling_convention => switch (attr.args.calling_convention.cc) {
.C => continue,
.stdcall, .thiscall => switch (p.comp.target.cpu.arch) {
.x86 => try p.attr_application_buf.append(p.gpa, attr),
else => try p.errStr(.callconv_not_supported, tok, p.tok_ids[tok].lexeme().?),
},
.vectorcall => switch (p.comp.target.cpu.arch) {
.x86, .aarch64, .aarch64_be, .aarch64_32 => try p.attr_application_buf.append(p.gpa, attr),
else => try p.errStr(.callconv_not_supported, tok, p.tok_ids[tok].lexeme().?),
},
},
.access,
.alloc_align,
.alloc_size,
.artificial,
.assume_aligned,
.constructor,
.copy,
.destructor,
.format_arg,
.ifunc,
.interrupt,
.interrupt_handler,
.malloc,
.no_address_safety_analysis,
.no_icf,
.no_instrument_function,
.no_profile_instrument_function,
.no_reorder,
.no_sanitize,
.no_sanitize_address,
.no_sanitize_coverage,
.no_sanitize_thread,
.no_sanitize_undefined,
.no_split_stack,
.no_stack_limit,
.no_stack_protector,
.noclone,
.noipa,
// .nonnull,
.noplt,
// .optimize,
.patchable_function_entry,
.sentinel,
.simd,
.stack_protect,
.symver,
.target,
.target_clones,
.visibility,
.weakref,
.zero_call_used_regs,
=> std.debug.panic("apply type attribute {s}", .{@tagName(attr.tag)}),
else => try ignoredAttrErr(p, tok, attr.tag, "functions"),
};
return ty.withAttributes(p.arena, p.attr_application_buf.items);
}
pub fn applyLabelAttributes(p: *Parser, ty: Type, attr_buf_start: usize) !Type {
const attrs = p.attr_buf.items(.attr)[attr_buf_start..];
const toks = p.attr_buf.items(.tok)[attr_buf_start..];
p.attr_application_buf.items.len = 0;
var hot = false;
var cold = false;
for (attrs, toks) |attr, tok| switch (attr.tag) {
.unused => try p.attr_application_buf.append(p.gpa, attr),
.hot => if (cold) {
try p.errTok(.ignore_hot, tok);
} else {
try p.attr_application_buf.append(p.gpa, attr);
hot = true;
},
.cold => if (hot) {
try p.errTok(.ignore_cold, tok);
} else {
try p.attr_application_buf.append(p.gpa, attr);
cold = true;
},
else => try ignoredAttrErr(p, tok, attr.tag, "labels"),
};
return ty.withAttributes(p.arena, p.attr_application_buf.items);
}
pub fn applyStatementAttributes(p: *Parser, ty: Type, expr_start: TokenIndex, attr_buf_start: usize) !Type {
const attrs = p.attr_buf.items(.attr)[attr_buf_start..];
const toks = p.attr_buf.items(.tok)[attr_buf_start..];
p.attr_application_buf.items.len = 0;
for (attrs, toks) |attr, tok| switch (attr.tag) {
.fallthrough => if (p.tok_ids[p.tok_i] != .keyword_case and p.tok_ids[p.tok_i] != .keyword_default) {
// TODO: this condition is not completely correct; the last statement of a compound
// statement is also valid if it precedes a switch label (so intervening '}' are ok,
// but only if they close a compound statement)
try p.errTok(.invalid_fallthrough, expr_start);
} else {
try p.attr_application_buf.append(p.gpa, attr);
},
else => try p.errStr(.cannot_apply_attribute_to_statement, tok, @tagName(attr.tag)),
};
return ty.withAttributes(p.arena, p.attr_application_buf.items);
}
pub fn applyEnumeratorAttributes(p: *Parser, ty: Type, attr_buf_start: usize) !Type {
const attrs = p.attr_buf.items(.attr)[attr_buf_start..];
const toks = p.attr_buf.items(.tok)[attr_buf_start..];
p.attr_application_buf.items.len = 0;
for (attrs, toks) |attr, tok| switch (attr.tag) {
.deprecated, .unavailable => try p.attr_application_buf.append(p.gpa, attr),
else => try ignoredAttrErr(p, tok, attr.tag, "enums"),
};
return ty.withAttributes(p.arena, p.attr_application_buf.items);
}
fn applyAligned(attr: Attribute, p: *Parser, ty: Type, tag: ?Diagnostics.Tag) !void {
const base = ty.canonicalize(.standard);
if (attr.args.aligned.alignment) |alignment| alignas: {
if (attr.syntax != .keyword) break :alignas;
const align_tok = attr.args.aligned.__name_tok;
if (tag) |t| try p.errTok(t, align_tok);
const default_align = base.alignof(p.comp);
if (ty.isFunc()) {
try p.errTok(.alignas_on_func, align_tok);
} else if (alignment.requested < default_align) {
try p.errExtra(.minimum_alignment, align_tok, .{ .unsigned = default_align });
}
}
try p.attr_application_buf.append(p.gpa, attr);
}
fn applyTransparentUnion(attr: Attribute, p: *Parser, tok: TokenIndex, ty: Type) !void {
const union_ty = ty.get(.@"union") orelse {
return p.errTok(.transparent_union_wrong_type, tok);
};
// TODO validate union defined at end
if (union_ty.data.record.isIncomplete()) return;
const fields = union_ty.data.record.fields;
if (fields.len == 0) {
return p.errTok(.transparent_union_one_field, tok);
}
const first_field_size = fields[0].ty.bitSizeof(p.comp).?;
for (fields[1..]) |field| {
const field_size = field.ty.bitSizeof(p.comp).?;
if (field_size == first_field_size) continue;
const mapper = p.comp.string_interner.getSlowTypeMapper();
const str = try std.fmt.allocPrint(p.comp.diag.arena.allocator(), "'{s}' ({d}", .{ mapper.lookup(field.name), field_size });
try p.errStr(.transparent_union_size, field.name_tok, str);
return p.errExtra(.transparent_union_size_note, fields[0].name_tok, .{ .unsigned = first_field_size });
}
try p.attr_application_buf.append(p.gpa, attr);
}
fn applyVectorSize(attr: Attribute, p: *Parser, tok: TokenIndex, ty: *Type) !void {
if (!(ty.isInt() or ty.isFloat()) or !ty.isReal()) {
const orig_ty = try p.typeStr(ty.*);
ty.* = Type.invalid;
return p.errStr(.invalid_vec_elem_ty, tok, orig_ty);
}
const vec_bytes = attr.args.vector_size.bytes;
const ty_size = ty.sizeof(p.comp).?;
if (vec_bytes % ty_size != 0) {
return p.errTok(.vec_size_not_multiple, tok);
}
const vec_size = vec_bytes / ty_size;
const arr_ty = try p.arena.create(Type.Array);
arr_ty.* = .{ .elem = ty.*, .len = vec_size };
ty.* = Type{
.specifier = .vector,
.data = .{ .array = arr_ty },
};
}
fn applyFormat(attr: Attribute, p: *Parser, ty: Type) !void {
// TODO validate
_ = ty;
try p.attr_application_buf.append(p.gpa, attr);
}