Files
zig/deps/aro/Type.zig
Veikka Tuominen 5792570197 add Aro sources as a dependency
ref: 5688dbccfb58216468267a0f46b96bed7013715a
2023-10-01 23:51:54 +03:00

2693 lines
102 KiB
Zig
Vendored

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("<expr>]");
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("[<expr>]");
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(<expr>: ");
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(" }");
}