const std = @import("std"); const Tree = @import("Tree.zig"); const TokenIndex = Tree.TokenIndex; const NodeIndex = Tree.NodeIndex; const Parser = @import("Parser.zig"); const Compilation = @import("Compilation.zig"); const Attribute = @import("Attribute.zig"); const StringInterner = @import("StringInterner.zig"); const StringId = StringInterner.StringId; const target_util = @import("target.zig"); const LangOpts = @import("LangOpts.zig"); const BuiltinFunction = @import("builtins/BuiltinFunction.zig"); const Type = @This(); pub const Qualifiers = packed struct { @"const": bool = false, atomic: bool = false, @"volatile": bool = false, restrict: bool = false, // for function parameters only, stored here since it fits in the padding register: bool = false, pub fn any(quals: Qualifiers) bool { return quals.@"const" or quals.restrict or quals.@"volatile" or quals.atomic; } pub fn dump(quals: Qualifiers, w: anytype) !void { if (quals.@"const") try w.writeAll("const "); if (quals.atomic) try w.writeAll("_Atomic "); if (quals.@"volatile") try w.writeAll("volatile "); if (quals.restrict) try w.writeAll("restrict "); if (quals.register) try w.writeAll("register "); } /// Merge the const/volatile qualifiers, used by type resolution /// of the conditional operator pub fn mergeCV(a: Qualifiers, b: Qualifiers) Qualifiers { return .{ .@"const" = a.@"const" or b.@"const", .@"volatile" = a.@"volatile" or b.@"volatile", }; } /// Merge all qualifiers, used by typeof() fn mergeAll(a: Qualifiers, b: Qualifiers) Qualifiers { return .{ .@"const" = a.@"const" or b.@"const", .atomic = a.atomic or b.atomic, .@"volatile" = a.@"volatile" or b.@"volatile", .restrict = a.restrict or b.restrict, .register = a.register or b.register, }; } /// Checks if a has all the qualifiers of b pub fn hasQuals(a: Qualifiers, b: Qualifiers) bool { if (b.@"const" and !a.@"const") return false; if (b.@"volatile" and !a.@"volatile") return false; if (b.atomic and !a.atomic) return false; return true; } /// register is a storage class and not actually a qualifier /// so it is not preserved by typeof() pub fn inheritFromTypeof(quals: Qualifiers) Qualifiers { var res = quals; res.register = false; return res; } pub const Builder = struct { @"const": ?TokenIndex = null, atomic: ?TokenIndex = null, @"volatile": ?TokenIndex = null, restrict: ?TokenIndex = null, pub fn finish(b: Qualifiers.Builder, p: *Parser, ty: *Type) !void { if (ty.specifier != .pointer and b.restrict != null) { try p.errStr(.restrict_non_pointer, b.restrict.?, try p.typeStr(ty.*)); } if (b.atomic) |some| { if (ty.isArray()) try p.errStr(.atomic_array, some, try p.typeStr(ty.*)); if (ty.isFunc()) try p.errStr(.atomic_func, some, try p.typeStr(ty.*)); if (ty.hasIncompleteSize()) try p.errStr(.atomic_incomplete, some, try p.typeStr(ty.*)); } if (b.@"const" != null) ty.qual.@"const" = true; if (b.atomic != null) ty.qual.atomic = true; if (b.@"volatile" != null) ty.qual.@"volatile" = true; if (b.restrict != null) ty.qual.restrict = true; } }; }; // TODO improve memory usage pub const Func = struct { return_type: Type, params: []Param, pub const Param = struct { ty: Type, name: StringId, name_tok: TokenIndex, }; }; pub const Array = struct { len: u64, elem: Type, }; pub const Expr = struct { node: NodeIndex, ty: Type, }; pub const Attributed = struct { attributes: []Attribute, base: Type, pub fn create(allocator: std.mem.Allocator, base: Type, existing_attributes: []const Attribute, attributes: []const Attribute) !*Attributed { var attributed_type = try allocator.create(Attributed); errdefer allocator.destroy(attributed_type); var all_attrs = try allocator.alloc(Attribute, existing_attributes.len + attributes.len); std.mem.copy(Attribute, all_attrs, existing_attributes); std.mem.copy(Attribute, all_attrs[existing_attributes.len..], attributes); attributed_type.* = .{ .attributes = all_attrs, .base = base, }; return attributed_type; } }; // TODO improve memory usage pub const Enum = struct { fields: []Field, tag_ty: Type, name: StringId, fixed: bool, pub const Field = struct { ty: Type, name: StringId, name_tok: TokenIndex, node: NodeIndex, }; pub fn isIncomplete(e: Enum) bool { return e.fields.len == std.math.maxInt(usize); } pub fn create(allocator: std.mem.Allocator, name: StringId, fixed_ty: ?Type) !*Enum { var e = try allocator.create(Enum); e.name = name; e.fields.len = std.math.maxInt(usize); if (fixed_ty) |some| e.tag_ty = some; e.fixed = fixed_ty != null; return e; } }; // might not need all 4 of these when finished, // but currently it helps having all 4 when diff-ing // the rust code. pub const TypeLayout = struct { /// The size of the type in bits. /// /// This is the value returned by `sizeof` and C and `std::mem::size_of` in Rust /// (but in bits instead of bytes). This is a multiple of `pointer_alignment_bits`. size_bits: u64, /// The alignment of the type, in bits, when used as a field in a record. /// /// This is usually the value returned by `_Alignof` in C, but there are some edge /// cases in GCC where `_Alignof` returns a smaller value. field_alignment_bits: u32, /// The alignment, in bits, of valid pointers to this type. /// /// This is the value returned by `std::mem::align_of` in Rust /// (but in bits instead of bytes). `size_bits` is a multiple of this value. pointer_alignment_bits: u32, /// The required alignment of the type in bits. /// /// This value is only used by MSVC targets. It is 8 on all other /// targets. On MSVC targets, this value restricts the effects of `#pragma pack` except /// in some cases involving bit-fields. required_alignment_bits: u32, }; pub const FieldLayout = struct { /// `offset_bits` and `size_bits` should both be INVALID if and only if the field /// is an unnamed bitfield. There is no way to reference an unnamed bitfield in C, so /// there should be no way to observe these values. If it is used, this value will /// maximize the chance that a safety-checked overflow will occur. const INVALID = std.math.maxInt(u64); /// The offset of the field, in bits, from the start of the struct. offset_bits: u64 = INVALID, /// The size, in bits, of the field. /// /// For bit-fields, this is the width of the field. size_bits: u64 = INVALID, pub fn isUnnamed(self: FieldLayout) bool { return self.offset_bits == INVALID and self.size_bits == INVALID; } }; // TODO improve memory usage pub const Record = struct { fields: []Field, type_layout: TypeLayout, /// If this is null, none of the fields have attributes /// Otherwise, it's a pointer to N items (where N == number of fields) /// and the item at index i is the attributes for the field at index i field_attributes: ?[*][]const Attribute, name: StringId, pub const Field = struct { ty: Type, name: StringId, /// zero for anonymous fields name_tok: TokenIndex = 0, bit_width: ?u32 = null, layout: FieldLayout = .{ .offset_bits = 0, .size_bits = 0, }, pub fn isNamed(f: *const Field) bool { return f.name_tok != 0; } pub fn isAnonymousRecord(f: Field) bool { return !f.isNamed() and f.ty.isRecord(); } /// false for bitfields pub fn isRegularField(f: *const Field) bool { return f.bit_width == null; } /// bit width as specified in the C source. Asserts that `f` is a bitfield. pub fn specifiedBitWidth(f: *const Field) u32 { return f.bit_width.?; } }; pub fn isIncomplete(r: Record) bool { return r.fields.len == std.math.maxInt(usize); } pub fn create(allocator: std.mem.Allocator, name: StringId) !*Record { var r = try allocator.create(Record); r.name = name; r.fields.len = std.math.maxInt(usize); r.field_attributes = null; r.type_layout = .{ .size_bits = 8, .field_alignment_bits = 8, .pointer_alignment_bits = 8, .required_alignment_bits = 8, }; return r; } pub fn hasFieldOfType(self: *const Record, ty: Type, comp: *const Compilation) bool { if (self.isIncomplete()) return false; for (self.fields) |f| { if (ty.eql(f.ty, comp, false)) return true; } return false; } }; pub const Specifier = enum { /// A NaN-like poison value invalid, /// GNU auto type /// This is a placeholder specifier - it must be replaced by the actual type specifier (determined by the initializer) auto_type, void, bool, // integers char, schar, uchar, short, ushort, int, uint, long, ulong, long_long, ulong_long, int128, uint128, complex_char, complex_schar, complex_uchar, complex_short, complex_ushort, complex_int, complex_uint, complex_long, complex_ulong, complex_long_long, complex_ulong_long, complex_int128, complex_uint128, // data.int bit_int, complex_bit_int, // floating point numbers fp16, float16, float, double, long_double, float80, float128, complex_float, complex_double, complex_long_double, complex_float80, complex_float128, // data.sub_type pointer, unspecified_variable_len_array, decayed_unspecified_variable_len_array, // data.func /// int foo(int bar, char baz) and int (void) func, /// int foo(int bar, char baz, ...) var_args_func, /// int foo(bar, baz) and int foo() /// is also var args, but we can give warnings about incorrect amounts of parameters old_style_func, // data.array array, decayed_array, static_array, decayed_static_array, incomplete_array, decayed_incomplete_array, vector, // data.expr variable_len_array, decayed_variable_len_array, // data.record @"struct", @"union", // data.enum @"enum", /// typeof(type-name) typeof_type, /// decayed array created with typeof(type-name) decayed_typeof_type, /// typeof(expression) typeof_expr, /// decayed array created with typeof(expression) decayed_typeof_expr, /// data.attributed attributed, /// C23 nullptr_t nullptr_t, }; /// All fields of Type except data may be mutated data: union { sub_type: *Type, func: *Func, array: *Array, expr: *Expr, @"enum": *Enum, record: *Record, attributed: *Attributed, none: void, int: struct { bits: u8, signedness: std.builtin.Signedness, }, } = .{ .none = {} }, specifier: Specifier, qual: Qualifiers = .{}, pub const int = Type{ .specifier = .int }; pub const invalid = Type{ .specifier = .invalid }; /// Determine if type matches the given specifier, recursing into typeof /// types if necessary. pub fn is(ty: Type, specifier: Specifier) bool { std.debug.assert(specifier != .typeof_type and specifier != .typeof_expr); return ty.get(specifier) != null; } pub fn withAttributes(self: Type, allocator: std.mem.Allocator, attributes: []const Attribute) !Type { if (attributes.len == 0) return self; const attributed_type = try Type.Attributed.create(allocator, self, self.getAttributes(), attributes); return Type{ .specifier = .attributed, .data = .{ .attributed = attributed_type } }; } pub fn isCallable(ty: Type) ?Type { return switch (ty.specifier) { .func, .var_args_func, .old_style_func => ty, .pointer => if (ty.data.sub_type.isFunc()) ty.data.sub_type.* else null, .typeof_type => ty.data.sub_type.isCallable(), .typeof_expr => ty.data.expr.ty.isCallable(), .attributed => ty.data.attributed.base.isCallable(), else => null, }; } pub fn isFunc(ty: Type) bool { return switch (ty.specifier) { .func, .var_args_func, .old_style_func => true, .typeof_type => ty.data.sub_type.isFunc(), .typeof_expr => ty.data.expr.ty.isFunc(), .attributed => ty.data.attributed.base.isFunc(), else => false, }; } pub fn isArray(ty: Type) bool { return switch (ty.specifier) { .array, .static_array, .incomplete_array, .variable_len_array, .unspecified_variable_len_array => true, .typeof_type => ty.data.sub_type.isArray(), .typeof_expr => ty.data.expr.ty.isArray(), .attributed => ty.data.attributed.base.isArray(), else => false, }; } pub fn isScalar(ty: Type) bool { return ty.isInt() or ty.isScalarNonInt(); } /// To avoid calling isInt() twice for allowable loop/if controlling expressions pub fn isScalarNonInt(ty: Type) bool { return ty.isFloat() or ty.isPtr() or ty.is(.nullptr_t); } pub fn isDecayed(ty: Type) bool { const decayed = switch (ty.specifier) { .decayed_array, .decayed_static_array, .decayed_incomplete_array, .decayed_variable_len_array, .decayed_unspecified_variable_len_array, .decayed_typeof_type, .decayed_typeof_expr, => true, else => false, }; std.debug.assert(decayed or !std.mem.startsWith(u8, @tagName(ty.specifier), "decayed")); return decayed; } pub fn isPtr(ty: Type) bool { return switch (ty.specifier) { .pointer, .decayed_array, .decayed_static_array, .decayed_incomplete_array, .decayed_variable_len_array, .decayed_unspecified_variable_len_array, .decayed_typeof_type, .decayed_typeof_expr, => true, .typeof_type => ty.data.sub_type.isPtr(), .typeof_expr => ty.data.expr.ty.isPtr(), .attributed => ty.data.attributed.base.isPtr(), else => false, }; } pub fn isInt(ty: Type) bool { return switch (ty.specifier) { // zig fmt: off .@"enum", .bool, .char, .schar, .uchar, .short, .ushort, .int, .uint, .long, .ulong, .long_long, .ulong_long, .int128, .uint128, .complex_char, .complex_schar, .complex_uchar, .complex_short, .complex_ushort, .complex_int, .complex_uint, .complex_long, .complex_ulong, .complex_long_long, .complex_ulong_long, .complex_int128, .complex_uint128, .bit_int, .complex_bit_int => true, // zig fmt: on .typeof_type => ty.data.sub_type.isInt(), .typeof_expr => ty.data.expr.ty.isInt(), .attributed => ty.data.attributed.base.isInt(), else => false, }; } pub fn isFloat(ty: Type) bool { return switch (ty.specifier) { // zig fmt: off .float, .double, .long_double, .complex_float, .complex_double, .complex_long_double, .fp16, .float16, .float80, .float128, .complex_float80, .complex_float128 => true, // zig fmt: on .typeof_type => ty.data.sub_type.isFloat(), .typeof_expr => ty.data.expr.ty.isFloat(), .attributed => ty.data.attributed.base.isFloat(), else => false, }; } pub fn isReal(ty: Type) bool { return switch (ty.specifier) { // zig fmt: off .complex_float, .complex_double, .complex_long_double, .complex_float80, .complex_float128, .complex_char, .complex_schar, .complex_uchar, .complex_short, .complex_ushort, .complex_int, .complex_uint, .complex_long, .complex_ulong, .complex_long_long, .complex_ulong_long, .complex_int128, .complex_uint128, .complex_bit_int => false, // zig fmt: on .typeof_type => ty.data.sub_type.isReal(), .typeof_expr => ty.data.expr.ty.isReal(), .attributed => ty.data.attributed.base.isReal(), else => true, }; } pub fn isComplex(ty: Type) bool { return switch (ty.specifier) { // zig fmt: off .complex_float, .complex_double, .complex_long_double, .complex_float80, .complex_float128, .complex_char, .complex_schar, .complex_uchar, .complex_short, .complex_ushort, .complex_int, .complex_uint, .complex_long, .complex_ulong, .complex_long_long, .complex_ulong_long, .complex_int128, .complex_uint128, .complex_bit_int => true, // zig fmt: on .typeof_type => ty.data.sub_type.isComplex(), .typeof_expr => ty.data.expr.ty.isComplex(), .attributed => ty.data.attributed.base.isComplex(), else => false, }; } pub fn isVoidStar(ty: Type) bool { return switch (ty.specifier) { .pointer => ty.data.sub_type.specifier == .void, .typeof_type => ty.data.sub_type.isVoidStar(), .typeof_expr => ty.data.expr.ty.isVoidStar(), .attributed => ty.data.attributed.base.isVoidStar(), else => false, }; } pub fn isTypeof(ty: Type) bool { return switch (ty.specifier) { .typeof_type, .typeof_expr, .decayed_typeof_type, .decayed_typeof_expr => true, else => false, }; } pub fn isConst(ty: Type) bool { return switch (ty.specifier) { .typeof_type, .decayed_typeof_type => ty.qual.@"const" or ty.data.sub_type.isConst(), .typeof_expr, .decayed_typeof_expr => ty.qual.@"const" or ty.data.expr.ty.isConst(), .attributed => ty.data.attributed.base.isConst(), else => ty.qual.@"const", }; } pub fn isUnsignedInt(ty: Type, comp: *const Compilation) bool { return switch (ty.specifier) { // zig fmt: off .char, .complex_char => return comp.getCharSignedness() == .unsigned, .uchar, .ushort, .uint, .ulong, .ulong_long, .bool, .complex_uchar, .complex_ushort, .complex_uint, .complex_ulong, .complex_ulong_long, .complex_uint128 => true, // zig fmt: on .bit_int, .complex_bit_int => return ty.data.int.signedness == .unsigned, .typeof_type => ty.data.sub_type.isUnsignedInt(comp), .typeof_expr => ty.data.expr.ty.isUnsignedInt(comp), .attributed => ty.data.attributed.base.isUnsignedInt(comp), else => false, }; } pub fn isEnumOrRecord(ty: Type) bool { return switch (ty.specifier) { .@"enum", .@"struct", .@"union" => true, .typeof_type => ty.data.sub_type.isEnumOrRecord(), .typeof_expr => ty.data.expr.ty.isEnumOrRecord(), .attributed => ty.data.attributed.base.isEnumOrRecord(), else => false, }; } pub fn isRecord(ty: Type) bool { return switch (ty.specifier) { .@"struct", .@"union" => true, .typeof_type => ty.data.sub_type.isRecord(), .typeof_expr => ty.data.expr.ty.isRecord(), .attributed => ty.data.attributed.base.isRecord(), else => false, }; } pub fn isAnonymousRecord(ty: Type, comp: *const Compilation) bool { return switch (ty.specifier) { // anonymous records can be recognized by their names which are in // the format "(anonymous TAG at path:line:col)". .@"struct", .@"union" => { const mapper = comp.string_interner.getSlowTypeMapper(); return mapper.lookup(ty.data.record.name)[0] == '('; }, .typeof_type => ty.data.sub_type.isAnonymousRecord(comp), .typeof_expr => ty.data.expr.ty.isAnonymousRecord(comp), .attributed => ty.data.attributed.base.isAnonymousRecord(comp), else => false, }; } pub fn elemType(ty: Type) Type { return switch (ty.specifier) { .pointer, .unspecified_variable_len_array, .decayed_unspecified_variable_len_array => ty.data.sub_type.*, .array, .static_array, .incomplete_array, .decayed_array, .decayed_static_array, .decayed_incomplete_array, .vector => ty.data.array.elem, .variable_len_array, .decayed_variable_len_array => ty.data.expr.ty, .typeof_type, .decayed_typeof_type, .typeof_expr, .decayed_typeof_expr => { const unwrapped = ty.canonicalize(.preserve_quals); var elem = unwrapped.elemType(); elem.qual = elem.qual.mergeAll(unwrapped.qual); return elem; }, .attributed => ty.data.attributed.base, .invalid => Type.invalid, // zig fmt: off .complex_float, .complex_double, .complex_long_double, .complex_float80, .complex_float128, .complex_char, .complex_schar, .complex_uchar, .complex_short, .complex_ushort, .complex_int, .complex_uint, .complex_long, .complex_ulong, .complex_long_long, .complex_ulong_long, .complex_int128, .complex_uint128, .complex_bit_int => ty.makeReal(), // zig fmt: on else => unreachable, }; } pub fn returnType(ty: Type) Type { return switch (ty.specifier) { .func, .var_args_func, .old_style_func => ty.data.func.return_type, .typeof_type, .decayed_typeof_type => ty.data.sub_type.returnType(), .typeof_expr, .decayed_typeof_expr => ty.data.expr.ty.returnType(), .attributed => ty.data.attributed.base.returnType(), .invalid => Type.invalid, else => unreachable, }; } pub fn params(ty: Type) []Func.Param { return switch (ty.specifier) { .func, .var_args_func, .old_style_func => ty.data.func.params, .typeof_type, .decayed_typeof_type => ty.data.sub_type.params(), .typeof_expr, .decayed_typeof_expr => ty.data.expr.ty.params(), .attributed => ty.data.attributed.base.params(), .invalid => &.{}, else => unreachable, }; } pub fn arrayLen(ty: Type) ?u64 { return switch (ty.specifier) { .array, .static_array, .decayed_array, .decayed_static_array => ty.data.array.len, .typeof_type, .decayed_typeof_type => ty.data.sub_type.arrayLen(), .typeof_expr, .decayed_typeof_expr => ty.data.expr.ty.arrayLen(), .attributed => ty.data.attributed.base.arrayLen(), else => null, }; } /// Complex numbers are scalars but they can be initialized with a 2-element initList pub fn expectedInitListSize(ty: Type) ?u64 { return if (ty.isComplex()) 2 else ty.arrayLen(); } pub fn anyQual(ty: Type) bool { return switch (ty.specifier) { .typeof_type => ty.qual.any() or ty.data.sub_type.anyQual(), .typeof_expr => ty.qual.any() or ty.data.expr.ty.anyQual(), else => ty.qual.any(), }; } pub fn getAttributes(ty: Type) []const Attribute { return switch (ty.specifier) { .attributed => ty.data.attributed.attributes, .typeof_type, .decayed_typeof_type => ty.data.sub_type.getAttributes(), .typeof_expr, .decayed_typeof_expr => ty.data.expr.ty.getAttributes(), else => &.{}, }; } pub fn getRecord(ty: Type) ?*const Type.Record { return switch (ty.specifier) { .attributed => ty.data.attributed.base.getRecord(), .typeof_type, .decayed_typeof_type => ty.data.sub_type.getRecord(), .typeof_expr, .decayed_typeof_expr => ty.data.expr.ty.getRecord(), .@"struct", .@"union" => ty.data.record, else => null, }; } fn compareIntegerRanks(a: Type, b: Type, comp: *const Compilation) std.math.Order { std.debug.assert(a.isInt() and b.isInt()); if (a.eql(b, comp, false)) return .eq; const a_unsigned = a.isUnsignedInt(comp); const b_unsigned = b.isUnsignedInt(comp); const a_rank = a.integerRank(comp); const b_rank = b.integerRank(comp); if (a_unsigned == b_unsigned) { return std.math.order(a_rank, b_rank); } if (a_unsigned) { if (a_rank >= b_rank) return .gt; return .lt; } std.debug.assert(b_unsigned); if (b_rank >= a_rank) return .lt; return .gt; } fn realIntegerConversion(a: Type, b: Type, comp: *const Compilation) Type { std.debug.assert(a.isReal() and b.isReal()); const type_order = a.compareIntegerRanks(b, comp); const a_signed = !a.isUnsignedInt(comp); const b_signed = !b.isUnsignedInt(comp); if (a_signed == b_signed) { // If both have the same sign, use higher-rank type. return switch (type_order) { .lt => b, .eq, .gt => a, }; } else if (type_order != if (a_signed) std.math.Order.gt else std.math.Order.lt) { // Only one is signed; and the unsigned type has rank >= the signed type // Use the unsigned type return if (b_signed) a else b; } else if (a.bitSizeof(comp).? != b.bitSizeof(comp).?) { // Signed type is higher rank and sizes are not equal // Use the signed type return if (a_signed) a else b; } else { // Signed type is higher rank but same size as unsigned type // e.g. `long` and `unsigned` on x86-linux-gnu // Use unsigned version of the signed type return if (a_signed) a.makeIntegerUnsigned() else b.makeIntegerUnsigned(); } } pub fn makeIntegerUnsigned(ty: Type) Type { // TODO discards attributed/typeof var base = ty.canonicalize(.standard); switch (base.specifier) { // zig fmt: off .uchar, .ushort, .uint, .ulong, .ulong_long, .uint128, .complex_uchar, .complex_ushort, .complex_uint, .complex_ulong, .complex_ulong_long, .complex_uint128, => return ty, // zig fmt: on .char, .complex_char => { base.specifier = @enumFromInt(@intFromEnum(base.specifier) + 2); return base; }, // zig fmt: off .schar, .short, .int, .long, .long_long, .int128, .complex_schar, .complex_short, .complex_int, .complex_long, .complex_long_long, .complex_int128 => { base.specifier = @enumFromInt(@intFromEnum(base.specifier) + 1); return base; }, // zig fmt: on .bit_int, .complex_bit_int => { base.data.int.signedness = .unsigned; return base; }, else => unreachable, } } /// Find the common type of a and b for binary operations pub fn integerConversion(a: Type, b: Type, comp: *const Compilation) Type { const a_real = a.isReal(); const b_real = b.isReal(); const target_ty = a.makeReal().realIntegerConversion(b.makeReal(), comp); return if (a_real and b_real) target_ty else target_ty.makeComplex(); } pub fn integerPromotion(ty: Type, comp: *Compilation) Type { var specifier = ty.specifier; switch (specifier) { .@"enum" => { if (ty.hasIncompleteSize()) return .{ .specifier = .int }; specifier = ty.data.@"enum".tag_ty.specifier; }, .bit_int, .complex_bit_int => return .{ .specifier = specifier, .data = ty.data }, else => {}, } return switch (specifier) { else => .{ .specifier = switch (specifier) { // zig fmt: off .bool, .char, .schar, .uchar, .short => .int, .ushort => if (ty.sizeof(comp).? == sizeof(.{ .specifier = .int }, comp)) Specifier.uint else .int, .int, .uint, .long, .ulong, .long_long, .ulong_long, .int128, .uint128, .complex_char, .complex_schar, .complex_uchar, .complex_short, .complex_ushort, .complex_int, .complex_uint, .complex_long, .complex_ulong, .complex_long_long, .complex_ulong_long, .complex_int128, .complex_uint128 => specifier, // zig fmt: on .typeof_type => return ty.data.sub_type.integerPromotion(comp), .typeof_expr => return ty.data.expr.ty.integerPromotion(comp), .attributed => return ty.data.attributed.base.integerPromotion(comp), .invalid => .invalid, else => unreachable, // _BitInt, or not an integer type }, }, }; } /// Promote a bitfield. If `int` can hold all the values of the underlying field, /// promote to int. Otherwise, promote to unsigned int /// Returns null if no promotion is necessary pub fn bitfieldPromotion(ty: Type, comp: *Compilation, width: u32) ?Type { const type_size_bits = ty.bitSizeof(comp).?; // Note: GCC and clang will promote `long: 3` to int even though the C standard does not allow this if (width < type_size_bits) { return int; } if (width == type_size_bits) { return if (ty.isUnsignedInt(comp)) .{ .specifier = .uint } else int; } return null; } pub fn hasIncompleteSize(ty: Type) bool { return switch (ty.specifier) { .void, .incomplete_array, .invalid => true, .@"enum" => ty.data.@"enum".isIncomplete() and !ty.data.@"enum".fixed, .@"struct", .@"union" => ty.data.record.isIncomplete(), .array, .static_array => ty.data.array.elem.hasIncompleteSize(), .typeof_type => ty.data.sub_type.hasIncompleteSize(), .typeof_expr => ty.data.expr.ty.hasIncompleteSize(), .attributed => ty.data.attributed.base.hasIncompleteSize(), else => false, }; } pub fn hasUnboundVLA(ty: Type) bool { var cur = ty; while (true) { switch (cur.specifier) { .unspecified_variable_len_array, .decayed_unspecified_variable_len_array, => return true, .array, .static_array, .incomplete_array, .variable_len_array, .decayed_array, .decayed_static_array, .decayed_incomplete_array, .decayed_variable_len_array, => cur = cur.elemType(), .typeof_type, .decayed_typeof_type => cur = cur.data.sub_type.*, .typeof_expr, .decayed_typeof_expr => cur = cur.data.expr.ty, .attributed => cur = cur.data.attributed.base, else => return false, } } } pub fn hasField(ty: Type, name: StringId) bool { switch (ty.specifier) { .@"struct" => { std.debug.assert(!ty.data.record.isIncomplete()); for (ty.data.record.fields) |f| { if (f.isAnonymousRecord() and f.ty.hasField(name)) return true; if (name == f.name) return true; } }, .@"union" => { std.debug.assert(!ty.data.record.isIncomplete()); for (ty.data.record.fields) |f| { if (f.isAnonymousRecord() and f.ty.hasField(name)) return true; if (name == f.name) return true; } }, .typeof_type => return ty.data.sub_type.hasField(name), .typeof_expr => return ty.data.expr.ty.hasField(name), .attributed => return ty.data.attributed.base.hasField(name), .invalid => return false, else => unreachable, } return false; } pub fn minInt(ty: Type, comp: *const Compilation) i64 { std.debug.assert(ty.isInt()); if (ty.isUnsignedInt(comp)) return 0; return switch (ty.sizeof(comp).?) { 1 => std.math.minInt(i8), 2 => std.math.minInt(i16), 4 => std.math.minInt(i32), 8 => std.math.minInt(i64), else => unreachable, }; } pub fn maxInt(ty: Type, comp: *const Compilation) u64 { std.debug.assert(ty.isInt()); return switch (ty.sizeof(comp).?) { 1 => if (ty.isUnsignedInt(comp)) @as(u64, std.math.maxInt(u8)) else std.math.maxInt(i8), 2 => if (ty.isUnsignedInt(comp)) @as(u64, std.math.maxInt(u16)) else std.math.maxInt(i16), 4 => if (ty.isUnsignedInt(comp)) @as(u64, std.math.maxInt(u32)) else std.math.maxInt(i32), 8 => if (ty.isUnsignedInt(comp)) @as(u64, std.math.maxInt(u64)) else std.math.maxInt(i64), else => unreachable, }; } const TypeSizeOrder = enum { lt, gt, eq, indeterminate, }; pub fn sizeCompare(a: Type, b: Type, comp: *Compilation) TypeSizeOrder { const a_size = a.sizeof(comp) orelse return .indeterminate; const b_size = b.sizeof(comp) orelse return .indeterminate; return switch (std.math.order(a_size, b_size)) { .lt => .lt, .gt => .gt, .eq => .eq, }; } /// Size of type as reported by sizeof pub fn sizeof(ty: Type, comp: *const Compilation) ?u64 { return switch (ty.specifier) { .auto_type => unreachable, .variable_len_array, .unspecified_variable_len_array => return null, .incomplete_array => return if (comp.langopts.emulate == .msvc) @as(?u64, 0) else null, .func, .var_args_func, .old_style_func, .void, .bool => 1, .char, .schar, .uchar => 1, .short => comp.target.c_type_byte_size(.short), .ushort => comp.target.c_type_byte_size(.ushort), .int => comp.target.c_type_byte_size(.int), .uint => comp.target.c_type_byte_size(.uint), .long => comp.target.c_type_byte_size(.long), .ulong => comp.target.c_type_byte_size(.ulong), .long_long => comp.target.c_type_byte_size(.longlong), .ulong_long => comp.target.c_type_byte_size(.ulonglong), .long_double => comp.target.c_type_byte_size(.longdouble), .int128, .uint128 => 16, .fp16, .float16 => 2, .float => comp.target.c_type_byte_size(.float), .double => comp.target.c_type_byte_size(.double), .float80 => 16, .float128 => 16, .bit_int => { return std.mem.alignForward(u64, (ty.data.int.bits + 7) / 8, ty.alignof(comp)); }, // zig fmt: off .complex_char, .complex_schar, .complex_uchar, .complex_short, .complex_ushort, .complex_int, .complex_uint, .complex_long, .complex_ulong, .complex_long_long, .complex_ulong_long, .complex_int128, .complex_uint128, .complex_float, .complex_double, .complex_long_double, .complex_float80, .complex_float128, .complex_bit_int, => return 2 * ty.makeReal().sizeof(comp).?, // zig fmt: on .pointer, .decayed_array, .decayed_static_array, .decayed_incomplete_array, .decayed_variable_len_array, .decayed_unspecified_variable_len_array, .decayed_typeof_type, .decayed_typeof_expr, .static_array, .nullptr_t, => comp.target.ptrBitWidth() / 8, .array, .vector => { const size = ty.data.array.elem.sizeof(comp) orelse return null; const arr_size = size * ty.data.array.len; if (comp.langopts.emulate == .msvc) { // msvc ignores array type alignment. // Since the size might not be a multiple of the field // alignment, the address of the second element might not be properly aligned // for the field alignment. A flexible array has size 0. See test case 0018. return arr_size; } else { return std.mem.alignForward(u64, arr_size, ty.alignof(comp)); } }, .@"struct", .@"union" => if (ty.data.record.isIncomplete()) null else @as(u64, ty.data.record.type_layout.size_bits / 8), .@"enum" => if (ty.data.@"enum".isIncomplete() and !ty.data.@"enum".fixed) null else ty.data.@"enum".tag_ty.sizeof(comp), .typeof_type => ty.data.sub_type.sizeof(comp), .typeof_expr => ty.data.expr.ty.sizeof(comp), .attributed => ty.data.attributed.base.sizeof(comp), .invalid => return null, }; } pub fn bitSizeof(ty: Type, comp: *const Compilation) ?u64 { return switch (ty.specifier) { .bool => if (comp.langopts.emulate == .msvc) @as(u64, 8) else 1, .typeof_type, .decayed_typeof_type => ty.data.sub_type.bitSizeof(comp), .typeof_expr, .decayed_typeof_expr => ty.data.expr.ty.bitSizeof(comp), .attributed => ty.data.attributed.base.bitSizeof(comp), .bit_int => return ty.data.int.bits, .long_double => comp.target.c_type_bit_size(.longdouble), .float80 => return 80, else => 8 * (ty.sizeof(comp) orelse return null), }; } pub fn alignable(ty: Type) bool { return ty.isArray() or !ty.hasIncompleteSize() or ty.is(.void); } /// Get the alignment of a type pub fn alignof(ty: Type, comp: *const Compilation) u29 { // don't return the attribute for records // layout has already accounted for requested alignment if (ty.requestedAlignment(comp)) |requested| { // gcc does not respect alignment on enums if (ty.get(.@"enum")) |ty_enum| { if (comp.langopts.emulate == .gcc) { return ty_enum.alignof(comp); } } else if (ty.getRecord()) |rec| { if (ty.hasIncompleteSize()) return 0; const computed: u29 = @intCast(@divExact(rec.type_layout.field_alignment_bits, 8)); return @max(requested, computed); } else if (comp.langopts.emulate == .msvc) { const type_align = ty.data.attributed.base.alignof(comp); return @max(requested, type_align); } return requested; } return switch (ty.specifier) { .invalid => unreachable, .auto_type => unreachable, .variable_len_array, .incomplete_array, .unspecified_variable_len_array, .array, .vector, => ty.elemType().alignof(comp), .func, .var_args_func, .old_style_func => target_util.defaultFunctionAlignment(comp.target), .char, .schar, .uchar, .void, .bool => 1, // zig fmt: off .complex_char, .complex_schar, .complex_uchar, .complex_short, .complex_ushort, .complex_int, .complex_uint, .complex_long, .complex_ulong, .complex_long_long, .complex_ulong_long, .complex_int128, .complex_uint128, .complex_float, .complex_double, .complex_long_double, .complex_float80, .complex_float128, .complex_bit_int, => return ty.makeReal().alignof(comp), // zig fmt: on .short => comp.target.c_type_alignment(.short), .ushort => comp.target.c_type_alignment(.ushort), .int => comp.target.c_type_alignment(.int), .uint => comp.target.c_type_alignment(.uint), .long => comp.target.c_type_alignment(.long), .ulong => comp.target.c_type_alignment(.ulong), .long_long => comp.target.c_type_alignment(.longlong), .ulong_long => comp.target.c_type_alignment(.ulonglong), .bit_int => @min( std.math.ceilPowerOfTwoPromote(u16, (ty.data.int.bits + 7) / 8), comp.target.maxIntAlignment(), ), .float => comp.target.c_type_alignment(.float), .double => comp.target.c_type_alignment(.double), .long_double => comp.target.c_type_alignment(.longdouble), .int128, .uint128 => if (comp.target.cpu.arch == .s390x and comp.target.os.tag == .linux and comp.target.isGnu()) 8 else 16, .fp16, .float16 => 2, .float80, .float128 => 16, .pointer, .decayed_array, .decayed_static_array, .decayed_incomplete_array, .decayed_variable_len_array, .decayed_unspecified_variable_len_array, .static_array, .nullptr_t, => switch (comp.target.cpu.arch) { .avr => 1, else => comp.target.ptrBitWidth() / 8, }, .@"struct", .@"union" => if (ty.data.record.isIncomplete()) 0 else @intCast(ty.data.record.type_layout.field_alignment_bits / 8), .@"enum" => if (ty.data.@"enum".isIncomplete() and !ty.data.@"enum".fixed) 0 else ty.data.@"enum".tag_ty.alignof(comp), .typeof_type, .decayed_typeof_type => ty.data.sub_type.alignof(comp), .typeof_expr, .decayed_typeof_expr => ty.data.expr.ty.alignof(comp), .attributed => ty.data.attributed.base.alignof(comp), }; } /// Canonicalize a possibly-typeof() type. If the type is not a typeof() type, simply /// return it. Otherwise, determine the actual qualified type. /// The `qual_handling` parameter can be used to return the full set of qualifiers /// added by typeof() operations, which is useful when determining the elemType of /// arrays and pointers. pub fn canonicalize(ty: Type, qual_handling: enum { standard, preserve_quals }) Type { var cur = ty; if (cur.specifier == .attributed) cur = cur.data.attributed.base; if (!cur.isTypeof()) return cur; var qual = cur.qual; while (true) { switch (cur.specifier) { .typeof_type => cur = cur.data.sub_type.*, .typeof_expr => cur = cur.data.expr.ty, .decayed_typeof_type => { cur = cur.data.sub_type.*; cur.decayArray(); }, .decayed_typeof_expr => { cur = cur.data.expr.ty; cur.decayArray(); }, else => break, } qual = qual.mergeAll(cur.qual); } if ((cur.isArray() or cur.isPtr()) and qual_handling == .standard) { cur.qual = .{}; } else { cur.qual = qual; } return cur; } pub fn get(ty: *const Type, specifier: Specifier) ?*const Type { std.debug.assert(specifier != .typeof_type and specifier != .typeof_expr); return switch (ty.specifier) { .typeof_type => ty.data.sub_type.get(specifier), .typeof_expr => ty.data.expr.ty.get(specifier), .attributed => ty.data.attributed.base.get(specifier), else => if (ty.specifier == specifier) ty else null, }; } pub fn requestedAlignment(ty: Type, comp: *const Compilation) ?u29 { return switch (ty.specifier) { .typeof_type, .decayed_typeof_type => ty.data.sub_type.requestedAlignment(comp), .typeof_expr, .decayed_typeof_expr => ty.data.expr.ty.requestedAlignment(comp), .attributed => annotationAlignment(comp, ty.data.attributed.attributes), else => null, }; } pub fn enumIsPacked(ty: Type, comp: *const Compilation) bool { std.debug.assert(ty.is(.@"enum")); return comp.langopts.short_enums or target_util.packAllEnums(comp.target) or ty.hasAttribute(.@"packed"); } pub fn annotationAlignment(comp: *const Compilation, attrs: ?[]const Attribute) ?u29 { const a = attrs orelse return null; var max_requested: ?u29 = null; for (a) |attribute| { if (attribute.tag != .aligned) continue; const requested = if (attribute.args.aligned.alignment) |alignment| alignment.requested else target_util.defaultAlignment(comp.target); if (max_requested == null or max_requested.? < requested) { max_requested = requested; } } return max_requested; } /// Checks type compatibility for __builtin_types_compatible_p /// Returns true if the unqualified version of `a_param` and `b_param` are the same /// Ignores top-level qualifiers (e.g. `int` and `const int` are compatible) but `int *` and `const int *` are not /// Two types that are typedefed are considered compatible if their underlying types are compatible. /// An enum type is not considered to be compatible with another enum type even if both are compatible with the same integer type; /// `A[]` and `A[N]` for a type `A` and integer `N` are compatible pub fn compatible(a_param: Type, b_param: Type, comp: *const Compilation) bool { var a_unqual = a_param.canonicalize(.standard); a_unqual.qual.@"const" = false; a_unqual.qual.@"volatile" = false; var b_unqual = b_param.canonicalize(.standard); b_unqual.qual.@"const" = false; b_unqual.qual.@"volatile" = false; if (a_unqual.eql(b_unqual, comp, true)) return true; if (!a_unqual.isArray() or !b_unqual.isArray()) return false; if (a_unqual.arrayLen() == null or b_unqual.arrayLen() == null) { // incomplete arrays are compatible with arrays of the same element type // GCC and clang ignore cv-qualifiers on arrays return a_unqual.elemType().compatible(b_unqual.elemType(), comp); } return false; } pub fn eql(a_param: Type, b_param: Type, comp: *const Compilation, check_qualifiers: bool) bool { const a = a_param.canonicalize(.standard); const b = b_param.canonicalize(.standard); if (a.specifier == .invalid or b.specifier == .invalid) return false; if (a.alignof(comp) != b.alignof(comp)) return false; if (a.isPtr()) { if (!b.isPtr()) return false; } else if (a.isFunc()) { if (!b.isFunc()) return false; } else if (a.isArray()) { if (!b.isArray()) return false; } else if (a.specifier != b.specifier) return false; if (a.qual.atomic != b.qual.atomic) return false; if (check_qualifiers) { if (a.qual.@"const" != b.qual.@"const") return false; if (a.qual.@"volatile" != b.qual.@"volatile") return false; } switch (a.specifier) { .pointer, .decayed_array, .decayed_static_array, .decayed_incomplete_array, .decayed_variable_len_array, .decayed_unspecified_variable_len_array, => if (!a_param.elemType().eql(b_param.elemType(), comp, check_qualifiers)) return false, .func, .var_args_func, .old_style_func, => { // TODO validate this if (a.data.func.params.len != b.data.func.params.len) return false; // return type cannot have qualifiers if (!a.returnType().eql(b.returnType(), comp, false)) return false; for (a.data.func.params, b.data.func.params) |param, b_qual| { var a_unqual = param.ty; a_unqual.qual.@"const" = false; a_unqual.qual.@"volatile" = false; var b_unqual = b_qual.ty; b_unqual.qual.@"const" = false; b_unqual.qual.@"volatile" = false; if (!a_unqual.eql(b_unqual, comp, check_qualifiers)) return false; } }, .array, .static_array, .incomplete_array, .vector, => { if (!std.meta.eql(a.arrayLen(), b.arrayLen())) return false; if (!a.elemType().eql(b.elemType(), comp, check_qualifiers)) return false; }, .variable_len_array => if (!a.elemType().eql(b.elemType(), comp, check_qualifiers)) return false, .@"struct", .@"union" => if (a.data.record != b.data.record) return false, .@"enum" => if (a.data.@"enum" != b.data.@"enum") return false, .bit_int, .complex_bit_int => return a.data.int.bits == b.data.int.bits and a.data.int.signedness == b.data.int.signedness, else => {}, } return true; } /// Decays an array to a pointer pub fn decayArray(ty: *Type) void { // the decayed array type is the current specifier +1 ty.specifier = @enumFromInt(@intFromEnum(ty.specifier) + 1); } pub fn originalTypeOfDecayedArray(ty: Type) Type { std.debug.assert(ty.isDecayed()); var copy = ty; copy.specifier = @enumFromInt(@intFromEnum(ty.specifier) - 1); return copy; } /// Rank for floating point conversions, ignoring domain (complex vs real) /// Asserts that ty is a floating point type pub fn floatRank(ty: Type) usize { const real = ty.makeReal(); return switch (real.specifier) { // TODO: bfloat16 => 0 .float16 => 1, .fp16 => 2, .float => 3, .double => 4, .long_double => 5, .float128 => 6, // TODO: ibm128 => 7 else => unreachable, }; } /// Rank for integer conversions, ignoring domain (complex vs real) /// Asserts that ty is an integer type pub fn integerRank(ty: Type, comp: *const Compilation) usize { const real = ty.makeReal(); return @intCast(switch (real.specifier) { .bit_int => @as(u64, real.data.int.bits) << 3, .bool => 1 + (ty.bitSizeof(comp).? << 3), .char, .schar, .uchar => 2 + (ty.bitSizeof(comp).? << 3), .short, .ushort => 3 + (ty.bitSizeof(comp).? << 3), .int, .uint => 4 + (ty.bitSizeof(comp).? << 3), .long, .ulong => 5 + (ty.bitSizeof(comp).? << 3), .long_long, .ulong_long => 6 + (ty.bitSizeof(comp).? << 3), .int128, .uint128 => 7 + (ty.bitSizeof(comp).? << 3), else => unreachable, }); } /// Returns true if `a` and `b` are integer types that differ only in sign pub fn sameRankDifferentSign(a: Type, b: Type, comp: *const Compilation) bool { if (!a.isInt() or !b.isInt()) return false; if (a.integerRank(comp) != b.integerRank(comp)) return false; return a.isUnsignedInt(comp) != b.isUnsignedInt(comp); } pub fn makeReal(ty: Type) Type { // TODO discards attributed/typeof var base = ty.canonicalize(.standard); switch (base.specifier) { .complex_float, .complex_double, .complex_long_double, .complex_float80, .complex_float128 => { base.specifier = @enumFromInt(@intFromEnum(base.specifier) - 5); return base; }, .complex_char, .complex_schar, .complex_uchar, .complex_short, .complex_ushort, .complex_int, .complex_uint, .complex_long, .complex_ulong, .complex_long_long, .complex_ulong_long, .complex_int128, .complex_uint128 => { base.specifier = @enumFromInt(@intFromEnum(base.specifier) - 13); return base; }, .complex_bit_int => { base.specifier = .bit_int; return base; }, else => return ty, } } pub fn makeComplex(ty: Type) Type { // TODO discards attributed/typeof var base = ty.canonicalize(.standard); switch (base.specifier) { .float, .double, .long_double, .float80, .float128 => { base.specifier = @enumFromInt(@intFromEnum(base.specifier) + 5); return base; }, .char, .schar, .uchar, .short, .ushort, .int, .uint, .long, .ulong, .long_long, .ulong_long, .int128, .uint128 => { base.specifier = @enumFromInt(@intFromEnum(base.specifier) + 13); return base; }, .bit_int => { base.specifier = .complex_bit_int; return base; }, else => return ty, } } /// Combines types recursively in the order they were parsed, uses `.void` specifier as a sentinel value. pub fn combine(inner: *Type, outer: Type) Parser.Error!void { switch (inner.specifier) { .pointer => return inner.data.sub_type.combine(outer), .unspecified_variable_len_array => { try inner.data.sub_type.combine(outer); }, .variable_len_array => { try inner.data.expr.ty.combine(outer); }, .array, .static_array, .incomplete_array => { try inner.data.array.elem.combine(outer); }, .func, .var_args_func, .old_style_func => { try inner.data.func.return_type.combine(outer); }, .decayed_array, .decayed_static_array, .decayed_incomplete_array, .decayed_variable_len_array, .decayed_unspecified_variable_len_array, .decayed_typeof_type, .decayed_typeof_expr, => unreachable, // type should not be able to decay before being combined .void => inner.* = outer, else => unreachable, } } pub fn validateCombinedType(ty: Type, p: *Parser, source_tok: TokenIndex) Parser.Error!void { switch (ty.specifier) { .pointer => return ty.data.sub_type.validateCombinedType(p, source_tok), .unspecified_variable_len_array, .variable_len_array, .array, .static_array, .incomplete_array, => { const elem_ty = ty.elemType(); if (elem_ty.hasIncompleteSize()) { try p.errStr(.array_incomplete_elem, source_tok, try p.typeStr(elem_ty)); return error.ParsingFailed; } if (elem_ty.isFunc()) { try p.errTok(.array_func_elem, source_tok); return error.ParsingFailed; } if (elem_ty.specifier == .static_array and elem_ty.isArray()) { try p.errTok(.static_non_outermost_array, source_tok); } if (elem_ty.anyQual() and elem_ty.isArray()) { try p.errTok(.qualifier_non_outermost_array, source_tok); } }, .func, .var_args_func, .old_style_func => { const ret_ty = &ty.data.func.return_type; if (ret_ty.isArray()) try p.errTok(.func_cannot_return_array, source_tok); if (ret_ty.isFunc()) try p.errTok(.func_cannot_return_func, source_tok); if (ret_ty.qual.@"const") { try p.errStr(.qual_on_ret_type, source_tok, "const"); ret_ty.qual.@"const" = false; } if (ret_ty.qual.@"volatile") { try p.errStr(.qual_on_ret_type, source_tok, "volatile"); ret_ty.qual.@"volatile" = false; } if (ret_ty.qual.atomic) { try p.errStr(.qual_on_ret_type, source_tok, "atomic"); ret_ty.qual.atomic = false; } if (ret_ty.is(.fp16) and !p.comp.hasHalfPrecisionFloatABI()) { try p.errStr(.suggest_pointer_for_invalid_fp16, source_tok, "function return value"); } }, .typeof_type, .decayed_typeof_type => return ty.data.sub_type.validateCombinedType(p, source_tok), .typeof_expr, .decayed_typeof_expr => return ty.data.expr.ty.validateCombinedType(p, source_tok), .attributed => return ty.data.attributed.base.validateCombinedType(p, source_tok), else => {}, } } /// An unfinished Type pub const Builder = struct { complex_tok: ?TokenIndex = null, bit_int_tok: ?TokenIndex = null, auto_type_tok: ?TokenIndex = null, typedef: ?struct { tok: TokenIndex, ty: Type, } = null, specifier: Builder.Specifier = .none, qual: Qualifiers.Builder = .{}, typeof: ?Type = null, /// When true an error is returned instead of adding a diagnostic message. /// Used for trying to combine typedef types. error_on_invalid: bool = false, pub const Specifier = union(enum) { none, void, /// GNU __auto_type extension auto_type, nullptr_t, bool, char, schar, uchar, complex_char, complex_schar, complex_uchar, unsigned, signed, short, sshort, ushort, short_int, sshort_int, ushort_int, int, sint, uint, long, slong, ulong, long_int, slong_int, ulong_int, long_long, slong_long, ulong_long, long_long_int, slong_long_int, ulong_long_int, int128, sint128, uint128, complex_unsigned, complex_signed, complex_short, complex_sshort, complex_ushort, complex_short_int, complex_sshort_int, complex_ushort_int, complex_int, complex_sint, complex_uint, complex_long, complex_slong, complex_ulong, complex_long_int, complex_slong_int, complex_ulong_int, complex_long_long, complex_slong_long, complex_ulong_long, complex_long_long_int, complex_slong_long_int, complex_ulong_long_int, complex_int128, complex_sint128, complex_uint128, bit_int: i16, sbit_int: i16, ubit_int: i16, complex_bit_int: i16, complex_sbit_int: i16, complex_ubit_int: i16, fp16, float16, float, double, long_double, float80, float128, complex, complex_float, complex_double, complex_long_double, complex_float80, complex_float128, pointer: *Type, unspecified_variable_len_array: *Type, decayed_unspecified_variable_len_array: *Type, func: *Func, var_args_func: *Func, old_style_func: *Func, array: *Array, decayed_array: *Array, static_array: *Array, decayed_static_array: *Array, incomplete_array: *Array, decayed_incomplete_array: *Array, vector: *Array, variable_len_array: *Expr, decayed_variable_len_array: *Expr, @"struct": *Record, @"union": *Record, @"enum": *Enum, typeof_type: *Type, decayed_typeof_type: *Type, typeof_expr: *Expr, decayed_typeof_expr: *Expr, attributed: *Attributed, pub fn str(spec: Builder.Specifier, langopts: LangOpts) ?[]const u8 { return switch (spec) { .none => unreachable, .void => "void", .auto_type => "__auto_type", .nullptr_t => "nullptr_t", .bool => if (langopts.standard.atLeast(.c2x)) "bool" else "_Bool", .char => "char", .schar => "signed char", .uchar => "unsigned char", .unsigned => "unsigned", .signed => "signed", .short => "short", .ushort => "unsigned short", .sshort => "signed short", .short_int => "short int", .sshort_int => "signed short int", .ushort_int => "unsigned short int", .int => "int", .sint => "signed int", .uint => "unsigned int", .long => "long", .slong => "signed long", .ulong => "unsigned long", .long_int => "long int", .slong_int => "signed long int", .ulong_int => "unsigned long int", .long_long => "long long", .slong_long => "signed long long", .ulong_long => "unsigned long long", .long_long_int => "long long int", .slong_long_int => "signed long long int", .ulong_long_int => "unsigned long long int", .int128 => "__int128", .sint128 => "signed __int128", .uint128 => "unsigned __int128", .bit_int => "_BitInt", .sbit_int => "signed _BitInt", .ubit_int => "unsigned _BitInt", .complex_char => "_Complex char", .complex_schar => "_Complex signed char", .complex_uchar => "_Complex unsigned char", .complex_unsigned => "_Complex unsigned", .complex_signed => "_Complex signed", .complex_short => "_Complex short", .complex_ushort => "_Complex unsigned short", .complex_sshort => "_Complex signed short", .complex_short_int => "_Complex short int", .complex_sshort_int => "_Complex signed short int", .complex_ushort_int => "_Complex unsigned short int", .complex_int => "_Complex int", .complex_sint => "_Complex signed int", .complex_uint => "_Complex unsigned int", .complex_long => "_Complex long", .complex_slong => "_Complex signed long", .complex_ulong => "_Complex unsigned long", .complex_long_int => "_Complex long int", .complex_slong_int => "_Complex signed long int", .complex_ulong_int => "_Complex unsigned long int", .complex_long_long => "_Complex long long", .complex_slong_long => "_Complex signed long long", .complex_ulong_long => "_Complex unsigned long long", .complex_long_long_int => "_Complex long long int", .complex_slong_long_int => "_Complex signed long long int", .complex_ulong_long_int => "_Complex unsigned long long int", .complex_int128 => "_Complex __int128", .complex_sint128 => "_Complex signed __int128", .complex_uint128 => "_Complex unsigned __int128", .complex_bit_int => "_Complex _BitInt", .complex_sbit_int => "_Complex signed _BitInt", .complex_ubit_int => "_Complex unsigned _BitInt", .fp16 => "__fp16", .float16 => "_Float16", .float => "float", .double => "double", .long_double => "long double", .float80 => "__float80", .float128 => "__float128", .complex => "_Complex", .complex_float => "_Complex float", .complex_double => "_Complex double", .complex_long_double => "_Complex long double", .complex_float80 => "_Complex __float80", .complex_float128 => "_Complex __float128", .attributed => |attributed| Builder.fromType(attributed.base).str(langopts), else => null, }; } }; pub fn finish(b: Builder, p: *Parser) Parser.Error!Type { var ty: Type = .{ .specifier = undefined }; if (b.typedef) |typedef| { ty = typedef.ty; if (ty.isArray()) { var elem = ty.elemType(); try b.qual.finish(p, &elem); // TODO this really should be easier switch (ty.specifier) { .array, .static_array, .incomplete_array => { var old = ty.data.array; ty.data.array = try p.arena.create(Array); ty.data.array.* = .{ .len = old.len, .elem = elem, }; }, .variable_len_array, .unspecified_variable_len_array => { var old = ty.data.expr; ty.data.expr = try p.arena.create(Expr); ty.data.expr.* = .{ .node = old.node, .ty = elem, }; }, .typeof_type => {}, // TODO handle .typeof_expr => {}, // TODO handle .attributed => {}, // TODO handle else => unreachable, } return ty; } try b.qual.finish(p, &ty); return ty; } switch (b.specifier) { .none => { if (b.typeof) |typeof| { ty = typeof; } else { ty.specifier = .int; try p.err(.missing_type_specifier); } }, .void => ty.specifier = .void, .auto_type => ty.specifier = .auto_type, .nullptr_t => unreachable, // nullptr_t can only be accessed via typeof(nullptr) .bool => ty.specifier = .bool, .char => ty.specifier = .char, .schar => ty.specifier = .schar, .uchar => ty.specifier = .uchar, .complex_char => ty.specifier = .complex_char, .complex_schar => ty.specifier = .complex_schar, .complex_uchar => ty.specifier = .complex_uchar, .unsigned => ty.specifier = .uint, .signed => ty.specifier = .int, .short_int, .sshort_int, .short, .sshort => ty.specifier = .short, .ushort, .ushort_int => ty.specifier = .ushort, .int, .sint => ty.specifier = .int, .uint => ty.specifier = .uint, .long, .slong, .long_int, .slong_int => ty.specifier = .long, .ulong, .ulong_int => ty.specifier = .ulong, .long_long, .slong_long, .long_long_int, .slong_long_int => ty.specifier = .long_long, .ulong_long, .ulong_long_int => ty.specifier = .ulong_long, .int128, .sint128 => ty.specifier = .int128, .uint128 => ty.specifier = .uint128, .complex_unsigned => ty.specifier = .complex_uint, .complex_signed => ty.specifier = .complex_int, .complex_short_int, .complex_sshort_int, .complex_short, .complex_sshort => ty.specifier = .complex_short, .complex_ushort, .complex_ushort_int => ty.specifier = .complex_ushort, .complex_int, .complex_sint => ty.specifier = .complex_int, .complex_uint => ty.specifier = .complex_uint, .complex_long, .complex_slong, .complex_long_int, .complex_slong_int => ty.specifier = .complex_long, .complex_ulong, .complex_ulong_int => ty.specifier = .complex_ulong, .complex_long_long, .complex_slong_long, .complex_long_long_int, .complex_slong_long_int => ty.specifier = .complex_long_long, .complex_ulong_long, .complex_ulong_long_int => ty.specifier = .complex_ulong_long, .complex_int128, .complex_sint128 => ty.specifier = .complex_int128, .complex_uint128 => ty.specifier = .complex_uint128, .bit_int, .sbit_int, .ubit_int, .complex_bit_int, .complex_ubit_int, .complex_sbit_int => |bits| { const unsigned = b.specifier == .ubit_int or b.specifier == .complex_ubit_int; if (unsigned) { if (bits < 1) { try p.errStr(.unsigned_bit_int_too_small, b.bit_int_tok.?, b.specifier.str(p.comp.langopts).?); return error.ParsingFailed; } } else { if (bits < 2) { try p.errStr(.signed_bit_int_too_small, b.bit_int_tok.?, b.specifier.str(p.comp.langopts).?); return error.ParsingFailed; } } if (bits > Compilation.bit_int_max_bits) { try p.errStr(.bit_int_too_big, b.bit_int_tok.?, b.specifier.str(p.comp.langopts).?); return error.ParsingFailed; } ty.specifier = if (b.complex_tok != null) .complex_bit_int else .bit_int; ty.data = .{ .int = .{ .signedness = if (unsigned) .unsigned else .signed, .bits = @intCast(bits), } }; }, .fp16 => ty.specifier = .fp16, .float16 => ty.specifier = .float16, .float => ty.specifier = .float, .double => ty.specifier = .double, .long_double => ty.specifier = .long_double, .float80 => ty.specifier = .float80, .float128 => ty.specifier = .float128, .complex_float => ty.specifier = .complex_float, .complex_double => ty.specifier = .complex_double, .complex_long_double => ty.specifier = .complex_long_double, .complex_float80 => ty.specifier = .complex_float80, .complex_float128 => ty.specifier = .complex_float128, .complex => { try p.errTok(.plain_complex, p.tok_i - 1); ty.specifier = .complex_double; }, .pointer => |data| { ty.specifier = .pointer; ty.data = .{ .sub_type = data }; }, .unspecified_variable_len_array => |data| { ty.specifier = .unspecified_variable_len_array; ty.data = .{ .sub_type = data }; }, .decayed_unspecified_variable_len_array => |data| { ty.specifier = .decayed_unspecified_variable_len_array; ty.data = .{ .sub_type = data }; }, .func => |data| { ty.specifier = .func; ty.data = .{ .func = data }; }, .var_args_func => |data| { ty.specifier = .var_args_func; ty.data = .{ .func = data }; }, .old_style_func => |data| { ty.specifier = .old_style_func; ty.data = .{ .func = data }; }, .array => |data| { ty.specifier = .array; ty.data = .{ .array = data }; }, .decayed_array => |data| { ty.specifier = .decayed_array; ty.data = .{ .array = data }; }, .static_array => |data| { ty.specifier = .static_array; ty.data = .{ .array = data }; }, .decayed_static_array => |data| { ty.specifier = .decayed_static_array; ty.data = .{ .array = data }; }, .incomplete_array => |data| { ty.specifier = .incomplete_array; ty.data = .{ .array = data }; }, .decayed_incomplete_array => |data| { ty.specifier = .decayed_incomplete_array; ty.data = .{ .array = data }; }, .vector => |data| { ty.specifier = .vector; ty.data = .{ .array = data }; }, .variable_len_array => |data| { ty.specifier = .variable_len_array; ty.data = .{ .expr = data }; }, .decayed_variable_len_array => |data| { ty.specifier = .decayed_variable_len_array; ty.data = .{ .expr = data }; }, .@"struct" => |data| { ty.specifier = .@"struct"; ty.data = .{ .record = data }; }, .@"union" => |data| { ty.specifier = .@"union"; ty.data = .{ .record = data }; }, .@"enum" => |data| { ty.specifier = .@"enum"; ty.data = .{ .@"enum" = data }; }, .typeof_type => |data| { ty.specifier = .typeof_type; ty.data = .{ .sub_type = data }; }, .decayed_typeof_type => |data| { ty.specifier = .decayed_typeof_type; ty.data = .{ .sub_type = data }; }, .typeof_expr => |data| { ty.specifier = .typeof_expr; ty.data = .{ .expr = data }; }, .decayed_typeof_expr => |data| { ty.specifier = .decayed_typeof_expr; ty.data = .{ .expr = data }; }, .attributed => |data| { ty.specifier = .attributed; ty.data = .{ .attributed = data }; }, } if (!ty.isReal() and ty.isInt()) { if (b.complex_tok) |tok| try p.errTok(.complex_int, tok); } try b.qual.finish(p, &ty); return ty; } fn cannotCombine(b: Builder, p: *Parser, source_tok: TokenIndex) !void { if (b.error_on_invalid) return error.CannotCombine; const ty_str = b.specifier.str(p.comp.langopts) orelse try p.typeStr(try b.finish(p)); try p.errExtra(.cannot_combine_spec, source_tok, .{ .str = ty_str }); if (b.typedef) |some| try p.errStr(.spec_from_typedef, some.tok, try p.typeStr(some.ty)); } fn duplicateSpec(b: *Builder, p: *Parser, source_tok: TokenIndex, spec: []const u8) !void { if (b.error_on_invalid) return error.CannotCombine; if (p.comp.langopts.emulate != .clang) return b.cannotCombine(p, source_tok); try p.errStr(.duplicate_decl_spec, p.tok_i, spec); } pub fn combineFromTypeof(b: *Builder, p: *Parser, new: Type, source_tok: TokenIndex) Compilation.Error!void { if (b.typeof != null) return p.errStr(.cannot_combine_spec, source_tok, "typeof"); if (b.specifier != .none) return p.errStr(.invalid_typeof, source_tok, @tagName(b.specifier)); const inner = switch (new.specifier) { .typeof_type => new.data.sub_type.*, .typeof_expr => new.data.expr.ty, .nullptr_t => new, // typeof(nullptr) is special-cased to be an unwrapped typeof-expr else => unreachable, }; b.typeof = switch (inner.specifier) { .attributed => inner.data.attributed.base, else => new, }; } /// Try to combine type from typedef, returns true if successful. pub fn combineTypedef(b: *Builder, p: *Parser, typedef_ty: Type, name_tok: TokenIndex) bool { b.error_on_invalid = true; defer b.error_on_invalid = false; const new_spec = fromType(typedef_ty); b.combineExtra(p, new_spec, 0) catch |err| switch (err) { error.FatalError => unreachable, // we do not add any diagnostics error.OutOfMemory => unreachable, // we do not add any diagnostics error.ParsingFailed => unreachable, // we do not add any diagnostics error.CannotCombine => return false, }; b.typedef = .{ .tok = name_tok, .ty = typedef_ty }; return true; } pub fn combine(b: *Builder, p: *Parser, new: Builder.Specifier, source_tok: TokenIndex) !void { b.combineExtra(p, new, source_tok) catch |err| switch (err) { error.CannotCombine => unreachable, else => |e| return e, }; } fn combineExtra(b: *Builder, p: *Parser, new: Builder.Specifier, source_tok: TokenIndex) !void { if (b.typeof != null) { if (b.error_on_invalid) return error.CannotCombine; try p.errStr(.invalid_typeof, source_tok, @tagName(new)); } switch (new) { .complex => b.complex_tok = source_tok, .bit_int => b.bit_int_tok = source_tok, .auto_type => b.auto_type_tok = source_tok, else => {}, } if (new == .int128 and !target_util.hasInt128(p.comp.target)) { try p.errStr(.type_not_supported_on_target, source_tok, "__int128"); } switch (new) { else => switch (b.specifier) { .none => b.specifier = new, else => return b.cannotCombine(p, source_tok), }, .signed => b.specifier = switch (b.specifier) { .none => .signed, .char => .schar, .short => .sshort, .short_int => .sshort_int, .int => .sint, .long => .slong, .long_int => .slong_int, .long_long => .slong_long, .long_long_int => .slong_long_int, .int128 => .sint128, .bit_int => |bits| .{ .sbit_int = bits }, .complex => .complex_signed, .complex_char => .complex_schar, .complex_short => .complex_sshort, .complex_short_int => .complex_sshort_int, .complex_int => .complex_sint, .complex_long => .complex_slong, .complex_long_int => .complex_slong_int, .complex_long_long => .complex_slong_long, .complex_long_long_int => .complex_slong_long_int, .complex_int128 => .complex_sint128, .complex_bit_int => |bits| .{ .complex_sbit_int = bits }, .signed, .sshort, .sshort_int, .sint, .slong, .slong_int, .slong_long, .slong_long_int, .sint128, .sbit_int, .complex_schar, .complex_signed, .complex_sshort, .complex_sshort_int, .complex_sint, .complex_slong, .complex_slong_int, .complex_slong_long, .complex_slong_long_int, .complex_sint128, .complex_sbit_int, => return b.duplicateSpec(p, source_tok, "signed"), else => return b.cannotCombine(p, source_tok), }, .unsigned => b.specifier = switch (b.specifier) { .none => .unsigned, .char => .uchar, .short => .ushort, .short_int => .ushort_int, .int => .uint, .long => .ulong, .long_int => .ulong_int, .long_long => .ulong_long, .long_long_int => .ulong_long_int, .int128 => .uint128, .bit_int => |bits| .{ .ubit_int = bits }, .complex => .complex_unsigned, .complex_char => .complex_uchar, .complex_short => .complex_ushort, .complex_short_int => .complex_ushort_int, .complex_int => .complex_uint, .complex_long => .complex_ulong, .complex_long_int => .complex_ulong_int, .complex_long_long => .complex_ulong_long, .complex_long_long_int => .complex_ulong_long_int, .complex_int128 => .complex_uint128, .complex_bit_int => |bits| .{ .complex_ubit_int = bits }, .unsigned, .ushort, .ushort_int, .uint, .ulong, .ulong_int, .ulong_long, .ulong_long_int, .uint128, .ubit_int, .complex_uchar, .complex_unsigned, .complex_ushort, .complex_ushort_int, .complex_uint, .complex_ulong, .complex_ulong_int, .complex_ulong_long, .complex_ulong_long_int, .complex_uint128, .complex_ubit_int, => return b.duplicateSpec(p, source_tok, "unsigned"), else => return b.cannotCombine(p, source_tok), }, .char => b.specifier = switch (b.specifier) { .none => .char, .unsigned => .uchar, .signed => .schar, .complex => .complex_char, .complex_signed => .complex_schar, .complex_unsigned => .complex_uchar, else => return b.cannotCombine(p, source_tok), }, .short => b.specifier = switch (b.specifier) { .none => .short, .unsigned => .ushort, .signed => .sshort, .int => .short_int, .sint => .sshort_int, .uint => .ushort_int, .complex => .complex_short, .complex_signed => .complex_sshort, .complex_unsigned => .complex_ushort, else => return b.cannotCombine(p, source_tok), }, .int => b.specifier = switch (b.specifier) { .none => .int, .signed => .sint, .unsigned => .uint, .short => .short_int, .sshort => .sshort_int, .ushort => .ushort_int, .long => .long_int, .slong => .slong_int, .ulong => .ulong_int, .long_long => .long_long_int, .slong_long => .slong_long_int, .ulong_long => .ulong_long_int, .complex => .complex_int, .complex_signed => .complex_sint, .complex_unsigned => .complex_uint, .complex_short => .complex_short_int, .complex_sshort => .complex_sshort_int, .complex_ushort => .complex_ushort_int, .complex_long => .complex_long_int, .complex_slong => .complex_slong_int, .complex_ulong => .complex_ulong_int, .complex_long_long => .complex_long_long_int, .complex_slong_long => .complex_slong_long_int, .complex_ulong_long => .complex_ulong_long_int, else => return b.cannotCombine(p, source_tok), }, .long => b.specifier = switch (b.specifier) { .none => .long, .long => .long_long, .unsigned => .ulong, .signed => .long, .int => .long_int, .sint => .slong_int, .ulong => .ulong_long, .complex => .complex_long, .complex_signed => .complex_slong, .complex_unsigned => .complex_ulong, .complex_long => .complex_long_long, .complex_slong => .complex_slong_long, .complex_ulong => .complex_ulong_long, else => return b.cannotCombine(p, source_tok), }, .int128 => b.specifier = switch (b.specifier) { .none => .int128, .unsigned => .uint128, .signed => .sint128, .complex => .complex_int128, .complex_signed => .complex_sint128, .complex_unsigned => .complex_uint128, else => return b.cannotCombine(p, source_tok), }, .bit_int => b.specifier = switch (b.specifier) { .none => .{ .bit_int = new.bit_int }, .unsigned => .{ .ubit_int = new.bit_int }, .signed => .{ .sbit_int = new.bit_int }, .complex => .{ .complex_bit_int = new.bit_int }, .complex_signed => .{ .complex_sbit_int = new.bit_int }, .complex_unsigned => .{ .complex_ubit_int = new.bit_int }, else => return b.cannotCombine(p, source_tok), }, .auto_type => b.specifier = switch (b.specifier) { .none => .auto_type, else => return b.cannotCombine(p, source_tok), }, .fp16 => b.specifier = switch (b.specifier) { .none => .fp16, else => return b.cannotCombine(p, source_tok), }, .float16 => b.specifier = switch (b.specifier) { .none => .float16, else => return b.cannotCombine(p, source_tok), }, .float => b.specifier = switch (b.specifier) { .none => .float, .complex => .complex_float, else => return b.cannotCombine(p, source_tok), }, .double => b.specifier = switch (b.specifier) { .none => .double, .long => .long_double, .complex_long => .complex_long_double, .complex => .complex_double, else => return b.cannotCombine(p, source_tok), }, .float80 => b.specifier = switch (b.specifier) { .none => .float80, .complex => .complex_float80, else => return b.cannotCombine(p, source_tok), }, .float128 => b.specifier = switch (b.specifier) { .none => .float128, .complex => .complex_float128, else => return b.cannotCombine(p, source_tok), }, .complex => b.specifier = switch (b.specifier) { .none => .complex, .float => .complex_float, .double => .complex_double, .long_double => .complex_long_double, .float80 => .complex_float80, .float128 => .complex_float128, .char => .complex_char, .schar => .complex_schar, .uchar => .complex_uchar, .unsigned => .complex_unsigned, .signed => .complex_signed, .short => .complex_short, .sshort => .complex_sshort, .ushort => .complex_ushort, .short_int => .complex_short_int, .sshort_int => .complex_sshort_int, .ushort_int => .complex_ushort_int, .int => .complex_int, .sint => .complex_sint, .uint => .complex_uint, .long => .complex_long, .slong => .complex_slong, .ulong => .complex_ulong, .long_int => .complex_long_int, .slong_int => .complex_slong_int, .ulong_int => .complex_ulong_int, .long_long => .complex_long_long, .slong_long => .complex_slong_long, .ulong_long => .complex_ulong_long, .long_long_int => .complex_long_long_int, .slong_long_int => .complex_slong_long_int, .ulong_long_int => .complex_ulong_long_int, .int128 => .complex_int128, .sint128 => .complex_sint128, .uint128 => .complex_uint128, .bit_int => |bits| .{ .complex_bit_int = bits }, .sbit_int => |bits| .{ .complex_sbit_int = bits }, .ubit_int => |bits| .{ .complex_ubit_int = bits }, .complex, .complex_float, .complex_double, .complex_long_double, .complex_float80, .complex_float128, .complex_char, .complex_schar, .complex_uchar, .complex_unsigned, .complex_signed, .complex_short, .complex_sshort, .complex_ushort, .complex_short_int, .complex_sshort_int, .complex_ushort_int, .complex_int, .complex_sint, .complex_uint, .complex_long, .complex_slong, .complex_ulong, .complex_long_int, .complex_slong_int, .complex_ulong_int, .complex_long_long, .complex_slong_long, .complex_ulong_long, .complex_long_long_int, .complex_slong_long_int, .complex_ulong_long_int, .complex_int128, .complex_sint128, .complex_uint128, .complex_bit_int, .complex_sbit_int, .complex_ubit_int, => return b.duplicateSpec(p, source_tok, "_Complex"), else => return b.cannotCombine(p, source_tok), }, } } pub fn fromType(ty: Type) Builder.Specifier { return switch (ty.specifier) { .void => .void, .auto_type => .auto_type, .nullptr_t => .nullptr_t, .bool => .bool, .char => .char, .schar => .schar, .uchar => .uchar, .short => .short, .ushort => .ushort, .int => .int, .uint => .uint, .long => .long, .ulong => .ulong, .long_long => .long_long, .ulong_long => .ulong_long, .int128 => .int128, .uint128 => .uint128, .bit_int => if (ty.data.int.signedness == .unsigned) { return .{ .ubit_int = ty.data.int.bits }; } else { return .{ .bit_int = ty.data.int.bits }; }, .complex_char => .complex_char, .complex_schar => .complex_schar, .complex_uchar => .complex_uchar, .complex_short => .complex_short, .complex_ushort => .complex_ushort, .complex_int => .complex_int, .complex_uint => .complex_uint, .complex_long => .complex_long, .complex_ulong => .complex_ulong, .complex_long_long => .complex_long_long, .complex_ulong_long => .complex_ulong_long, .complex_int128 => .complex_int128, .complex_uint128 => .complex_uint128, .complex_bit_int => if (ty.data.int.signedness == .unsigned) { return .{ .complex_ubit_int = ty.data.int.bits }; } else { return .{ .complex_bit_int = ty.data.int.bits }; }, .fp16 => .fp16, .float16 => .float16, .float => .float, .double => .double, .float80 => .float80, .float128 => .float128, .long_double => .long_double, .complex_float => .complex_float, .complex_double => .complex_double, .complex_long_double => .complex_long_double, .complex_float80 => .complex_float80, .complex_float128 => .complex_float128, .pointer => .{ .pointer = ty.data.sub_type }, .unspecified_variable_len_array => .{ .unspecified_variable_len_array = ty.data.sub_type }, .decayed_unspecified_variable_len_array => .{ .decayed_unspecified_variable_len_array = ty.data.sub_type }, .func => .{ .func = ty.data.func }, .var_args_func => .{ .var_args_func = ty.data.func }, .old_style_func => .{ .old_style_func = ty.data.func }, .array => .{ .array = ty.data.array }, .decayed_array => .{ .decayed_array = ty.data.array }, .static_array => .{ .static_array = ty.data.array }, .decayed_static_array => .{ .decayed_static_array = ty.data.array }, .incomplete_array => .{ .incomplete_array = ty.data.array }, .decayed_incomplete_array => .{ .decayed_incomplete_array = ty.data.array }, .vector => .{ .vector = ty.data.array }, .variable_len_array => .{ .variable_len_array = ty.data.expr }, .decayed_variable_len_array => .{ .decayed_variable_len_array = ty.data.expr }, .@"struct" => .{ .@"struct" = ty.data.record }, .@"union" => .{ .@"union" = ty.data.record }, .@"enum" => .{ .@"enum" = ty.data.@"enum" }, .typeof_type => .{ .typeof_type = ty.data.sub_type }, .decayed_typeof_type => .{ .decayed_typeof_type = ty.data.sub_type }, .typeof_expr => .{ .typeof_expr = ty.data.expr }, .decayed_typeof_expr => .{ .decayed_typeof_expr = ty.data.expr }, .attributed => .{ .attributed = ty.data.attributed }, else => unreachable, }; } }; pub fn getAttribute(ty: Type, comptime tag: Attribute.Tag) ?Attribute.ArgumentsForTag(tag) { switch (ty.specifier) { .typeof_type => return ty.data.sub_type.getAttribute(tag), .typeof_expr => return ty.data.expr.ty.getAttribute(tag), .attributed => { for (ty.data.attributed.attributes) |attribute| { if (attribute.tag == tag) return @field(attribute.args, @tagName(tag)); } return null; }, else => return null, } } pub fn hasAttribute(ty: Type, tag: Attribute.Tag) bool { for (ty.getAttributes()) |attr| { if (attr.tag == tag) return true; } return false; } /// printf format modifier pub fn formatModifier(ty: Type) []const u8 { return switch (ty.specifier) { .schar, .uchar => "hh", .short, .ushort => "h", .int, .uint => "", .long, .ulong => "l", .long_long, .ulong_long => "ll", else => unreachable, }; } /// Suffix for integer values of this type pub fn intValueSuffix(ty: Type, comp: *const Compilation) []const u8 { return switch (ty.specifier) { .schar, .short, .int => "", .long => "L", .long_long => "LL", .uchar, .char => { if (ty.specifier == .char and comp.getCharSignedness() == .signed) return ""; // Only 8-bit char supported currently; // TODO: handle platforms with 16-bit int + 16-bit char std.debug.assert(ty.sizeof(comp).? == 1); return ""; }, .ushort => { if (ty.sizeof(comp).? < int.sizeof(comp).?) { return ""; } return "U"; }, .uint => "U", .ulong => "UL", .ulong_long => "ULL", else => unreachable, // not integer }; } /// Print type in C style pub fn print(ty: Type, mapper: StringInterner.TypeMapper, langopts: LangOpts, w: anytype) @TypeOf(w).Error!void { _ = try ty.printPrologue(mapper, langopts, w); try ty.printEpilogue(mapper, langopts, w); } pub fn printNamed(ty: Type, name: []const u8, mapper: StringInterner.TypeMapper, langopts: LangOpts, w: anytype) @TypeOf(w).Error!void { const simple = try ty.printPrologue(mapper, langopts, w); if (simple) try w.writeByte(' '); try w.writeAll(name); try ty.printEpilogue(mapper, langopts, w); } const StringGetter = fn (TokenIndex) []const u8; /// return true if `ty` is simple fn printPrologue(ty: Type, mapper: StringInterner.TypeMapper, langopts: LangOpts, w: anytype) @TypeOf(w).Error!bool { if (ty.qual.atomic) { var non_atomic_ty = ty; non_atomic_ty.qual.atomic = false; try w.writeAll("_Atomic("); try non_atomic_ty.print(mapper, langopts, w); try w.writeAll(")"); return true; } switch (ty.specifier) { .pointer, .decayed_array, .decayed_static_array, .decayed_incomplete_array, .decayed_variable_len_array, .decayed_unspecified_variable_len_array, .decayed_typeof_type, .decayed_typeof_expr, => { const elem_ty = ty.elemType(); const simple = try elem_ty.printPrologue(mapper, langopts, w); if (simple) try w.writeByte(' '); if (elem_ty.isFunc() or elem_ty.isArray()) try w.writeByte('('); try w.writeByte('*'); try ty.qual.dump(w); return false; }, .func, .var_args_func, .old_style_func => { const ret_ty = ty.data.func.return_type; const simple = try ret_ty.printPrologue(mapper, langopts, w); if (simple) try w.writeByte(' '); return false; }, .array, .static_array, .incomplete_array, .unspecified_variable_len_array, .variable_len_array => { const elem_ty = ty.elemType(); const simple = try elem_ty.printPrologue(mapper, langopts, w); if (simple) try w.writeByte(' '); return false; }, .typeof_type, .typeof_expr => { const actual = ty.canonicalize(.standard); return actual.printPrologue(mapper, langopts, w); }, .attributed => { const actual = ty.canonicalize(.standard); return actual.printPrologue(mapper, langopts, w); }, else => {}, } try ty.qual.dump(w); switch (ty.specifier) { .@"enum" => if (ty.data.@"enum".fixed) { try w.print("enum {s}: ", .{mapper.lookup(ty.data.@"enum".name)}); try ty.data.@"enum".tag_ty.dump(mapper, langopts, w); } else { try w.print("enum {s}", .{mapper.lookup(ty.data.@"enum".name)}); }, .@"struct" => try w.print("struct {s}", .{mapper.lookup(ty.data.record.name)}), .@"union" => try w.print("union {s}", .{mapper.lookup(ty.data.record.name)}), .vector => { const len = ty.data.array.len; const elem_ty = ty.data.array.elem; try w.print("__attribute__((__vector_size__({d} * sizeof(", .{len}); _ = try elem_ty.printPrologue(mapper, langopts, w); try w.writeAll(")))) "); _ = try elem_ty.printPrologue(mapper, langopts, w); try w.print(" (vector of {d} '", .{len}); _ = try elem_ty.printPrologue(mapper, langopts, w); try w.writeAll("' values)"); }, else => try w.writeAll(Builder.fromType(ty).str(langopts).?), } return true; } fn printEpilogue(ty: Type, mapper: StringInterner.TypeMapper, langopts: LangOpts, w: anytype) @TypeOf(w).Error!void { if (ty.qual.atomic) return; switch (ty.specifier) { .pointer, .decayed_array, .decayed_static_array, .decayed_incomplete_array, .decayed_variable_len_array, .decayed_unspecified_variable_len_array, .decayed_typeof_type, .decayed_typeof_expr, => { const elem_ty = ty.elemType(); if (elem_ty.isFunc() or elem_ty.isArray()) try w.writeByte(')'); try elem_ty.printEpilogue(mapper, langopts, w); }, .func, .var_args_func, .old_style_func => { try w.writeByte('('); for (ty.data.func.params, 0..) |param, i| { if (i != 0) try w.writeAll(", "); _ = try param.ty.printPrologue(mapper, langopts, w); try param.ty.printEpilogue(mapper, langopts, w); } if (ty.specifier != .func) { if (ty.data.func.params.len != 0) try w.writeAll(", "); try w.writeAll("..."); } else if (ty.data.func.params.len == 0) { try w.writeAll("void"); } try w.writeByte(')'); try ty.data.func.return_type.printEpilogue(mapper, langopts, w); }, .array, .static_array => { try w.writeByte('['); if (ty.specifier == .static_array) try w.writeAll("static "); try ty.qual.dump(w); try w.print("{d}]", .{ty.data.array.len}); try ty.data.array.elem.printEpilogue(mapper, langopts, w); }, .incomplete_array => { try w.writeByte('['); try ty.qual.dump(w); try w.writeByte(']'); try ty.data.array.elem.printEpilogue(mapper, langopts, w); }, .unspecified_variable_len_array => { try w.writeByte('['); try ty.qual.dump(w); try w.writeAll("*]"); try ty.data.sub_type.printEpilogue(mapper, langopts, w); }, .variable_len_array => { try w.writeByte('['); try ty.qual.dump(w); try w.writeAll("]"); try ty.data.expr.ty.printEpilogue(mapper, langopts, w); }, .typeof_type, .typeof_expr => { const actual = ty.canonicalize(.standard); try actual.printEpilogue(mapper, langopts, w); }, .attributed => { const actual = ty.canonicalize(.standard); try actual.printEpilogue(mapper, langopts, w); }, else => {}, } } /// Useful for debugging, too noisy to be enabled by default. const dump_detailed_containers = false; // Print as Zig types since those are actually readable pub fn dump(ty: Type, mapper: StringInterner.TypeMapper, langopts: LangOpts, w: anytype) @TypeOf(w).Error!void { try ty.qual.dump(w); switch (ty.specifier) { .invalid => try w.writeAll("invalid"), .pointer => { try w.writeAll("*"); try ty.data.sub_type.dump(mapper, langopts, w); }, .func, .var_args_func, .old_style_func => { try w.writeAll("fn ("); for (ty.data.func.params, 0..) |param, i| { if (i != 0) try w.writeAll(", "); if (param.name != .empty) try w.print("{s}: ", .{mapper.lookup(param.name)}); try param.ty.dump(mapper, langopts, w); } if (ty.specifier != .func) { if (ty.data.func.params.len != 0) try w.writeAll(", "); try w.writeAll("..."); } try w.writeAll(") "); try ty.data.func.return_type.dump(mapper, langopts, w); }, .array, .static_array, .decayed_array, .decayed_static_array => { if (ty.specifier == .decayed_array or ty.specifier == .decayed_static_array) try w.writeByte('d'); try w.writeByte('['); if (ty.specifier == .static_array or ty.specifier == .decayed_static_array) try w.writeAll("static "); try w.print("{d}]", .{ty.data.array.len}); try ty.data.array.elem.dump(mapper, langopts, w); }, .vector => { try w.print("vector({d}, ", .{ty.data.array.len}); try ty.data.array.elem.dump(mapper, langopts, w); try w.writeAll(")"); }, .incomplete_array, .decayed_incomplete_array => { if (ty.specifier == .decayed_incomplete_array) try w.writeByte('d'); try w.writeAll("[]"); try ty.data.array.elem.dump(mapper, langopts, w); }, .@"enum" => { const enum_ty = ty.data.@"enum"; if (enum_ty.isIncomplete() and !enum_ty.fixed) { try w.print("enum {s}", .{mapper.lookup(enum_ty.name)}); } else { try w.print("enum {s}: ", .{mapper.lookup(enum_ty.name)}); try enum_ty.tag_ty.dump(mapper, langopts, w); } if (dump_detailed_containers) try dumpEnum(enum_ty, mapper, w); }, .@"struct" => { try w.print("struct {s}", .{mapper.lookup(ty.data.record.name)}); if (dump_detailed_containers) try dumpRecord(ty.data.record, mapper, langopts, w); }, .@"union" => { try w.print("union {s}", .{mapper.lookup(ty.data.record.name)}); if (dump_detailed_containers) try dumpRecord(ty.data.record, mapper, langopts, w); }, .unspecified_variable_len_array, .decayed_unspecified_variable_len_array => { if (ty.specifier == .decayed_unspecified_variable_len_array) try w.writeByte('d'); try w.writeAll("[*]"); try ty.data.sub_type.dump(mapper, langopts, w); }, .variable_len_array, .decayed_variable_len_array => { if (ty.specifier == .decayed_variable_len_array) try w.writeByte('d'); try w.writeAll("[]"); try ty.data.expr.ty.dump(mapper, langopts, w); }, .typeof_type, .decayed_typeof_type => { try w.writeAll("typeof("); try ty.data.sub_type.dump(mapper, langopts, w); try w.writeAll(")"); }, .typeof_expr, .decayed_typeof_expr => { try w.writeAll("typeof(: "); try ty.data.expr.ty.dump(mapper, langopts, w); try w.writeAll(")"); }, .attributed => { try w.writeAll("attributed("); try ty.data.attributed.base.dump(mapper, langopts, w); try w.writeAll(")"); }, else => { try w.writeAll(Builder.fromType(ty).str(langopts).?); if (ty.specifier == .bit_int or ty.specifier == .complex_bit_int) { try w.print("({d})", .{ty.data.int.bits}); } }, } } fn dumpEnum(@"enum": *Enum, mapper: StringInterner.TypeMapper, w: anytype) @TypeOf(w).Error!void { try w.writeAll(" {"); for (@"enum".fields) |field| { try w.print(" {s} = {d},", .{ mapper.lookup(field.name), field.value }); } try w.writeAll(" }"); } fn dumpRecord(record: *Record, mapper: StringInterner.TypeMapper, langopts: LangOpts, w: anytype) @TypeOf(w).Error!void { try w.writeAll(" {"); for (record.fields) |field| { try w.writeByte(' '); try field.ty.dump(mapper, langopts, w); try w.print(" {s}: {d};", .{ mapper.lookup(field.name), field.bit_width }); } try w.writeAll(" }"); }