#![doc(html_root_url = "https://docs.rs/rucc-types/0.2.14")]
mod classify;
mod compat;
mod convert;
mod kind;
mod layout;
mod print;
mod record;
mod types;
pub use crate::classify::{
element, is_aggregate, is_arithmetic, is_array, is_atomic, is_complete, is_complex,
is_floating, is_function, is_integer, is_modifiable, is_object, is_pointer, is_real,
is_real_floating, is_record, is_scalar, is_vector, is_void, pointee,
};
pub use crate::compat::{adjust_parameter, compatible, composite};
pub use crate::convert::{promote, promote_bit_field, usual_arithmetic};
pub use crate::kind::{
ArrayLen, EnumId, FloatKind, FunctionId, FunctionType, IntKind, Qualifiers, RecordId,
RecordKind, Type, TypeKind, VlaId,
};
pub use crate::layout::{
IntegerInfo, Layout, LayoutError, float_format, float_width, int_width, integer_info, layout,
};
pub use crate::print::{declare, spell};
pub use crate::record::{
Field, FieldDecl, RecordError, RecordLayout, RecordOptions, layout_record,
};
pub use crate::types::{EnumInfo, RecordInfo, TypeId, Types};
pub const MILESTONE: &str = "M2";
#[cfg(test)]
mod tests {
use rucc_base::{Interner, Symbol};
use rucc_target::{TargetInfo, Triple};
use super::*;
fn target(triple: &str) -> TargetInfo {
TargetInfo::new(triple.parse::<Triple>().expect("a triple the compiler supports"))
}
fn linux() -> TargetInfo {
target("x86_64-unknown-linux-gnu")
}
fn lay_out(types: &Types, kind: RecordKind, fields: &[FieldDecl]) -> RecordLayout {
layout_record(types, kind, fields, &RecordOptions::default(), &linux())
.expect("a record every member of which has a layout")
}
fn offsets(laid_out: &RecordLayout) -> Vec<u64> {
laid_out.fields.iter().map(|field| field.offset).collect()
}
fn record(types: &mut Types, kind: RecordKind, fields: &[FieldDecl]) -> TypeId {
let id = types.declare_record(kind, None);
let laid_out = lay_out(types, kind, fields);
types.complete_record(id, laid_out);
types.record(id)
}
fn member(ty: TypeId) -> FieldDecl {
FieldDecl::new(None, ty)
}
fn bits(interner: &mut Interner, name: &str, ty: TypeId, width: u32) -> FieldDecl {
FieldDecl::bit_field(Some(interner.intern(name)), ty, width)
}
fn unnamed_bits(ty: TypeId, width: u32) -> FieldDecl {
FieldDecl::bit_field(None, ty, width)
}
#[test]
fn milestone_is_recorded() {
assert!(MILESTONE.starts_with('M'));
}
#[test]
fn an_integer_type_answers_with_the_width_of_its_value_and_not_of_its_object() {
let mut interner = Interner::new();
let mut types = Types::new();
let target = linux();
let boolean = types.boolean();
let bits = types.bit_int(true, 37);
let short = types.int(IntKind::Short);
let alias = types.typedef(interner.intern("word"), short);
let unsigned_char = types.int(IntKind::UChar);
let atomic = types.atomic(unsigned_char);
let shape = |ty| integer_info(&types, ty, &target).expect("an integer type");
assert_eq!(shape(boolean), IntegerInfo::new(false, 1));
assert_eq!(shape(bits), IntegerInfo::new(true, 37));
assert_eq!(shape(types.int(IntKind::Int)), IntegerInfo::new(true, 32));
assert_eq!(shape(types.int(IntKind::ULong)), IntegerInfo::new(false, 64));
assert_eq!(shape(alias), IntegerInfo::new(true, 16));
assert_eq!(shape(atomic), IntegerInfo::new(false, 8));
assert_eq!(integer_info(&types, types.float(FloatKind::Double), &target), None);
}
#[test]
fn an_enumeration_answers_with_the_type_the_enumerators_are_kept_in() {
let mut interner = Interner::new();
let mut types = Types::new();
let target = linux();
let colour = types.declare_enum(Some(interner.intern("colour")));
let ty = types.enumeration(colour);
assert_eq!(integer_info(&types, ty, &target), None);
let underlying = types.int(IntKind::ULong);
types.complete_enum(colour, underlying, true);
assert_eq!(integer_info(&types, ty, &target), Some(IntegerInfo::new(false, 64)));
}
#[test]
fn a_value_stored_in_an_integer_type_keeps_the_bits_the_type_has_room_for() {
let char_type = IntegerInfo::new(true, 8);
assert_eq!(char_type.wrap(300), 44);
assert!(!char_type.holds(300));
assert!(char_type.holds(-128));
assert_eq!(IntegerInfo::new(false, 32).wrap(-1), 4_294_967_295);
assert_eq!(IntegerInfo::new(false, 8).wrap(-1), 255);
assert!(IntegerInfo::new(false, 128).holds(i128::MIN));
assert!(IntegerInfo::new(true, 128).holds(i128::MIN));
assert_eq!(IntegerInfo::new(true, 128).wrap(i128::MAX), i128::MAX);
}
#[test]
fn a_long_double_has_a_format_the_size_does_not_give_away() {
let target = linux();
assert_eq!(float_width(FloatKind::LongDouble, &target), 128);
assert_eq!(
float_format(FloatKind::LongDouble, &target),
rucc_base::float::Format::X87Extended
);
assert_eq!(float_format(FloatKind::Float, &target), rucc_base::float::Format::Single);
}
#[test]
fn an_interchange_type_names_a_format_and_an_extended_one_names_the_target() {
use rucc_base::float::Format;
for target in [&linux(), &target("aarch64-apple-darwin")] {
assert_eq!(float_format(FloatKind::Float16, target), Format::Half);
assert_eq!(float_format(FloatKind::Float32, target), Format::Single);
assert_eq!(float_format(FloatKind::Float64, target), Format::Double);
assert_eq!(float_format(FloatKind::Float128, target), Format::Quad);
assert_eq!(float_width(FloatKind::Float16, target), 16);
assert_eq!(float_width(FloatKind::Float32, target), 32);
assert_eq!(float_width(FloatKind::Float64, target), 64);
assert_eq!(float_width(FloatKind::Float128, target), 128);
assert_eq!(float_format(FloatKind::Float32x, target), Format::Double);
}
let x86 = linux();
assert_eq!(float_format(FloatKind::Float64x, &x86), Format::X87Extended);
assert_eq!(float_format(FloatKind::LongDouble, &x86), Format::X87Extended);
let mac = target("aarch64-apple-darwin");
assert_eq!(float_format(FloatKind::Float64x, &mac), Format::Quad);
assert_eq!(float_format(FloatKind::LongDouble, &mac), Format::Double);
assert_eq!(float_width(FloatKind::Float64x, &x86), 128);
assert_eq!(float_width(FloatKind::Float64x, &mac), 128);
}
#[test]
fn every_floating_type_is_as_wide_as_the_format_it_is_stored_in() {
let types = Types::new();
let sizes = |target: &TargetInfo| -> Vec<(u64, u64)> {
FloatKind::ALL
.iter()
.map(|&kind| {
let found = layout(&types, types.float(kind), target).expect("a complete type");
(found.size, found.align)
})
.collect()
};
assert_eq!(
sizes(&linux()),
[(2, 2), (4, 4), (4, 4), (8, 8), (8, 8), (8, 8), (16, 16), (16, 16), (16, 16)]
);
assert_eq!(
sizes(&target("aarch64-apple-darwin")),
[(2, 2), (4, 4), (4, 4), (8, 8), (8, 8), (8, 8), (8, 8), (16, 16), (16, 16)]
);
}
#[test]
fn every_floating_type_has_a_slot_of_its_own_and_a_name_of_its_own() {
let types = Types::new();
let mut seen = Vec::new();
for kind in FloatKind::ALL {
seen.push(types.float(kind));
}
let mut sorted = seen.clone();
sorted.sort_unstable();
sorted.dedup();
assert_eq!(sorted.len(), seen.len(), "two floating types share an id");
let names: Vec<&str> = FloatKind::ALL.iter().map(|kind| kind.as_str()).collect();
assert_eq!(
names,
[
"_Float16",
"float",
"_Float32",
"double",
"_Float32x",
"_Float64",
"long double",
"_Float64x",
"_Float128",
]
);
}
#[test]
fn the_same_type_asked_for_twice_is_the_same_id() {
let mut types = Types::new();
let a = types.pointer(types.int(IntKind::Int));
let b = types.pointer(types.int(IntKind::Int));
assert_eq!(a, b, "interning is what makes type identity an integer comparison");
let c = types.pointer(types.int(IntKind::Long));
assert_ne!(a, c);
}
#[test]
fn a_qualifier_makes_a_different_type_with_the_same_shape() {
let mut types = Types::new();
let int = types.int(IntKind::Int);
let konst = types.qualified(int, Qualifiers::CONST);
assert_ne!(int, konst);
assert_eq!(types.kind(konst), types.kind(int));
assert!(types.quals(konst).has(Qualifiers::CONST));
assert_eq!(types.unqualified(konst), int);
}
#[test]
fn qualifiers_accumulate_and_do_not_depend_on_the_order_they_were_written() {
let mut types = Types::new();
let int = types.int(IntKind::Int);
let a = types.qualified(int, Qualifiers::CONST);
let a = types.qualified(a, Qualifiers::VOLATILE);
let b = types.qualified(int, Qualifiers::VOLATILE);
let b = types.qualified(b, Qualifiers::CONST);
assert_eq!(a, b, "`const volatile int` and `volatile const int` are one type");
}
#[test]
fn qualifying_an_array_qualifies_its_element() {
let mut types = Types::new();
let int = types.int(IntKind::Int);
let array = types.array(int, ArrayLen::Fixed(4));
let konst = types.qualified(array, Qualifiers::CONST);
assert!(types.quals(konst).is_none(), "the array itself is unqualified");
let TypeKind::Array { elem, len } = types.kind(konst) else {
panic!("still an array");
};
assert_eq!(len, ArrayLen::Fixed(4));
assert!(types.quals(elem).has(Qualifiers::CONST));
}
#[test]
fn a_typedef_is_a_different_type_that_means_the_same_thing() {
let mut interner = Interner::new();
let mut types = Types::new();
let int = types.int(IntKind::Int);
let name = types.typedef(interner.intern("int32_t"), int);
assert_ne!(name, int, "the sugar survives, so a diagnostic can print it");
assert_eq!(types.canonical(name), int, "and no rule ever sees it");
assert!(types.is_sugar(name));
assert!(!types.is_sugar(int));
}
#[test]
fn sugar_below_the_outermost_node_is_resolved_too() {
let mut interner = Interner::new();
let mut types = Types::new();
let int = types.int(IntKind::Int);
let name = types.typedef(interner.intern("int32_t"), int);
let sugar_pointer = types.pointer(name);
let plain_pointer = types.pointer(int);
assert_ne!(sugar_pointer, plain_pointer);
assert_eq!(types.canonical(sugar_pointer), plain_pointer);
let sugar_array = types.array(name, ArrayLen::Fixed(3));
let plain_array = types.array(int, ArrayLen::Fixed(3));
assert_eq!(types.canonical(sugar_array), plain_array);
}
#[test]
fn a_typedef_of_a_typedef_canonicalises_all_the_way_down() {
let mut interner = Interner::new();
let mut types = Types::new();
let int = types.int(IntKind::Int);
let mut current = int;
for i in 0..8 {
current = types.typedef(interner.intern(&format!("t{i}")), current);
}
assert_eq!(types.canonical(current), int);
}
#[test]
fn a_qualified_typedef_keeps_the_name_and_canonicalises_to_the_qualified_type() {
let mut interner = Interner::new();
let mut types = Types::new();
let int = types.int(IntKind::Int);
let name = types.typedef(interner.intern("int32_t"), int);
let konst = types.qualified(name, Qualifiers::CONST);
assert!(matches!(types.kind(konst), TypeKind::Typedef { .. }), "still prints as int32_t");
let want = types.qualified(int, Qualifiers::CONST);
assert_eq!(types.canonical(konst), want);
}
#[test]
fn a_typedef_of_an_array_pushes_a_qualifier_to_the_element_when_it_canonicalises() {
let mut interner = Interner::new();
let mut types = Types::new();
let int = types.int(IntKind::Int);
let array = types.array(int, ArrayLen::Fixed(4));
let name = types.typedef(interner.intern("A"), array);
let konst = types.qualified(name, Qualifiers::CONST);
let konst_int = types.qualified(int, Qualifiers::CONST);
let want = types.array(konst_int, ArrayLen::Fixed(4));
assert_eq!(types.canonical(konst), want);
}
#[test]
fn a_function_type_is_deduplicated_by_its_signature() {
let mut types = Types::new();
let int = types.int(IntKind::Int);
let long = types.int(IntKind::Long);
let make = |types: &mut Types, params: Vec<TypeId>, variadic| {
types.function(FunctionType { ret: int, params, variadic, prototyped: true })
};
let a = make(&mut types, vec![int, long], false);
let b = make(&mut types, vec![int, long], false);
assert_eq!(a, b);
assert_ne!(a, make(&mut types, vec![int, long], true), "`...` is part of the type");
assert_ne!(a, make(&mut types, vec![long, int], false));
}
#[test]
fn a_function_type_written_with_a_typedef_canonicalises_through_its_signature() {
let mut interner = Interner::new();
let mut types = Types::new();
let int = types.int(IntKind::Int);
let name = types.typedef(interner.intern("int32_t"), int);
let sugar = types.function(FunctionType {
ret: name,
params: vec![name],
variadic: false,
prototyped: true,
});
let plain = types.function(FunctionType {
ret: int,
params: vec![int],
variadic: false,
prototyped: true,
});
assert_ne!(sugar, plain);
assert_eq!(types.canonical(sugar), plain);
}
#[test]
fn a_record_is_its_declaration_and_not_its_members() {
let mut interner = Interner::new();
let mut types = Types::new();
let tag = interner.intern("point");
let first = types.declare_record(RecordKind::Struct, Some(tag));
let second = types.declare_record(RecordKind::Struct, Some(tag));
assert_ne!(types.record(first), types.record(second));
assert_eq!(types.record(first), types.record(first));
}
#[test]
fn a_record_has_no_layout_until_it_has_been_completed() {
let mut types = Types::new();
let id = types.declare_record(RecordKind::Struct, None);
let ty = types.record(id);
assert_eq!(layout(&types, ty, &linux()), Err(LayoutError::Incomplete));
let long_long = types.int(IntKind::LongLong);
let laid_out = lay_out(&types, RecordKind::Struct, &[member(long_long); 2]);
types.complete_record(id, laid_out);
assert_eq!(layout(&types, ty, &linux()).unwrap(), Layout::new(16, 8));
}
#[test]
fn an_enum_takes_the_layout_of_its_underlying_type() {
let mut types = Types::new();
let id = types.declare_enum(None);
let ty = types.enumeration(id);
assert_eq!(layout(&types, ty, &linux()), Err(LayoutError::Incomplete));
let int = types.int(IntKind::Int);
types.complete_enum(id, int, false);
assert_eq!(layout(&types, ty, &linux()).unwrap(), Layout::new(4, 4));
}
#[test]
fn the_scalar_widths_come_from_the_target() {
let mut types = Types::new();
let linux = linux();
let windows = target("x86_64-pc-windows-msvc");
let darwin = target("aarch64-apple-darwin");
let long = types.int(IntKind::Long);
assert_eq!(layout(&types, long, &linux).unwrap(), Layout::new(8, 8));
assert_eq!(layout(&types, long, &windows).unwrap(), Layout::new(4, 4), "LLP64");
let ldouble = types.float(FloatKind::LongDouble);
assert_eq!(layout(&types, ldouble, &linux).unwrap(), Layout::new(16, 16));
assert_eq!(layout(&types, ldouble, &darwin).unwrap(), Layout::new(8, 8));
let pointer = types.pointer(types.void());
assert_eq!(layout(&types, pointer, &linux).unwrap(), Layout::new(8, 8));
let boolean = types.boolean();
assert_eq!(layout(&types, boolean, &linux).unwrap(), Layout::new(1, 1));
}
#[test]
fn a_complex_type_is_two_of_its_component_with_the_components_alignment() {
let mut types = Types::new();
let linux = linux();
let cfloat = types.complex(FloatKind::Float);
assert_eq!(layout(&types, cfloat, &linux).unwrap(), Layout::new(8, 4));
let cdouble = types.complex(FloatKind::Double);
assert_eq!(layout(&types, cdouble, &linux).unwrap(), Layout::new(16, 8));
let cldouble = types.complex(FloatKind::LongDouble);
assert_eq!(layout(&types, cldouble, &linux).unwrap(), Layout::new(32, 16));
let darwin = target("aarch64-apple-darwin");
assert_eq!(layout(&types, cldouble, &darwin).unwrap(), Layout::new(16, 8));
}
#[test]
fn an_atomic_type_can_be_more_aligned_than_the_type_it_wraps() {
let mut types = Types::new();
let linux = linux();
let long_long = types.int(IntKind::LongLong);
let plain = record(&mut types, RecordKind::Struct, &[member(long_long); 2]);
let atomic = types.atomic(plain);
assert_eq!(layout(&types, plain, &linux).unwrap(), Layout::new(16, 8));
assert_eq!(layout(&types, atomic, &linux).unwrap(), Layout::new(16, 16));
let odd = record(&mut types, RecordKind::Struct, &[member(long_long); 3]);
let atomic_odd = types.atomic(odd);
assert_eq!(layout(&types, atomic_odd, &linux).unwrap(), Layout::new(24, 8));
let int = types.int(IntKind::Int);
let atomic_int = types.atomic(int);
assert_eq!(layout(&types, atomic_int, &linux).unwrap(), Layout::new(4, 4));
}
#[test]
fn a_bit_int_is_laid_out_like_a_standard_integer_until_it_outgrows_one() {
let mut types = Types::new();
let linux = linux();
let darwin = target("aarch64-apple-darwin");
let cases = [(7, 1, 1), (8, 1, 1), (9, 2, 2), (17, 4, 4), (33, 8, 8), (64, 8, 8)];
for (width, size, align) in cases {
let ty = types.bit_int(true, width);
assert_eq!(layout(&types, ty, &linux).unwrap(), Layout::new(size, align), "{width}");
assert_eq!(layout(&types, ty, &darwin).unwrap(), Layout::new(size, align), "{width}");
}
for width in [65, 96, 128] {
let ty = types.bit_int(false, width);
assert_eq!(layout(&types, ty, &linux).unwrap(), Layout::new(16, 8), "{width}");
assert_eq!(layout(&types, ty, &darwin).unwrap(), Layout::new(16, 16), "{width}");
}
let wide = types.bit_int(true, 129);
assert_eq!(layout(&types, wide, &linux).unwrap(), Layout::new(24, 8));
assert_eq!(layout(&types, wide, &darwin).unwrap(), Layout::new(32, 16));
}
#[test]
fn an_array_is_its_element_repeated_and_keeps_its_elements_alignment() {
let mut types = Types::new();
let linux = linux();
let int = types.int(IntKind::Int);
let ty = types.array(int, ArrayLen::Fixed(10));
assert_eq!(layout(&types, ty, &linux).unwrap(), Layout::new(40, 4));
let nested = types.array(ty, ArrayLen::Fixed(3));
assert_eq!(layout(&types, nested, &linux).unwrap(), Layout::new(120, 4));
}
#[test]
fn an_array_without_a_size_is_incomplete_and_an_impossible_one_says_so() {
let mut types = Types::new();
let linux = linux();
let int = types.int(IntKind::Int);
for len in [ArrayLen::Unknown, ArrayLen::Star, ArrayLen::Variable(VlaId(0))] {
let ty = types.array(int, len);
assert_eq!(layout(&types, ty, &linux), Err(LayoutError::Incomplete));
}
let huge = types.array(int, ArrayLen::Fixed(u64::MAX));
assert_eq!(layout(&types, huge, &linux), Err(LayoutError::TooLarge));
}
#[test]
fn two_variable_length_arrays_of_the_same_element_are_still_different_types() {
let mut types = Types::new();
let int = types.int(IntKind::Int);
let a = types.array(int, ArrayLen::Variable(VlaId(0)));
let b = types.array(int, ArrayLen::Variable(VlaId(1)));
assert_ne!(a, b);
}
#[test]
fn a_vector_is_rounded_up_to_a_power_of_two_and_aligned_to_the_whole_thing() {
let mut types = Types::new();
let linux = linux();
let int = types.int(IntKind::Int);
let four = types.vector(int, 4);
assert_eq!(layout(&types, four, &linux).unwrap(), Layout::new(16, 16));
let three = types.vector(int, 3);
assert_eq!(layout(&types, three, &linux).unwrap(), Layout::new(16, 16));
let three_chars = types.vector(types.int(IntKind::Char), 3);
assert_eq!(layout(&types, three_chars, &linux).unwrap(), Layout::new(4, 4));
}
#[test]
fn the_types_without_a_size_say_which_kind_of_without_they_are() {
let mut types = Types::new();
let linux = linux();
let void = types.void();
assert_eq!(layout(&types, void, &linux), Err(LayoutError::Incomplete));
let int = types.int(IntKind::Int);
let function = types.function(FunctionType {
ret: int,
params: Vec::new(),
variadic: false,
prototyped: true,
});
assert_eq!(layout(&types, function, &linux), Err(LayoutError::Function));
let pointer_to_function = types.pointer(function);
assert_eq!(layout(&types, pointer_to_function, &linux).unwrap(), Layout::new(8, 8));
}
#[test]
fn a_struct_puts_each_member_at_the_next_offset_it_is_allowed_to_start_at() {
let types = Types::new();
let char_ = types.int(IntKind::Char);
let int = types.int(IntKind::Int);
let laid_out = lay_out(&types, RecordKind::Struct, &[member(char_), member(int)]);
assert_eq!(laid_out.layout, Layout::new(8, 4));
assert_eq!(offsets(&laid_out), [0, 32]);
assert_eq!(laid_out.fields[1].byte_offset(), 4);
let long_long = types.int(IntKind::LongLong);
let laid_out = lay_out(&types, RecordKind::Struct, &[member(long_long), member(char_)]);
assert_eq!(laid_out.layout, Layout::new(16, 8));
}
#[test]
fn a_union_starts_every_member_at_zero_and_is_as_large_as_the_largest() {
let mut types = Types::new();
let char_ = types.int(IntKind::Char);
let int = types.int(IntKind::Int);
let laid_out = lay_out(&types, RecordKind::Union, &[member(char_), member(int)]);
assert_eq!(laid_out.layout, Layout::new(4, 4));
assert_eq!(offsets(&laid_out), [0, 0]);
let nine = types.array(char_, ArrayLen::Fixed(9));
let short = types.int(IntKind::Short);
let laid_out = lay_out(&types, RecordKind::Union, &[member(nine), member(short)]);
assert_eq!(laid_out.layout, Layout::new(10, 2));
}
#[test]
fn bit_fields_share_a_unit_until_one_of_them_would_span_two() {
let mut interner = Interner::new();
let types = Types::new();
let char_ = types.int(IntKind::Char);
let int = types.int(IntKind::Int);
let long_long = types.int(IntKind::LongLong);
let fields = [bits(&mut interner, "a", int, 3), bits(&mut interner, "b", int, 5)];
let laid_out = lay_out(&types, RecordKind::Struct, &fields);
assert_eq!(laid_out.layout, Layout::new(4, 4));
assert_eq!(offsets(&laid_out), [0, 3]);
let fields = [member(char_), bits(&mut interner, "b", int, 30)];
let laid_out = lay_out(&types, RecordKind::Struct, &fields);
assert_eq!(laid_out.layout, Layout::new(8, 4));
assert_eq!(offsets(&laid_out), [0, 32]);
let fields = [member(char_), bits(&mut interner, "b", long_long, 33)];
let laid_out = lay_out(&types, RecordKind::Struct, &fields);
assert_eq!(laid_out.layout, Layout::new(8, 8));
assert_eq!(offsets(&laid_out), [0, 8]);
let fields = [bits(&mut interner, "a", int, 3), member(char_)];
let laid_out = lay_out(&types, RecordKind::Struct, &fields);
assert_eq!(offsets(&laid_out), [0, 8]);
}
#[test]
fn a_zero_width_bit_field_moves_the_next_member_on_and_nothing_else() {
let types = Types::new();
let char_ = types.int(IntKind::Char);
let int = types.int(IntKind::Int);
let fields = [member(char_), unnamed_bits(int, 0), member(char_)];
let laid_out = lay_out(&types, RecordKind::Struct, &fields);
assert_eq!(laid_out.layout, Layout::new(5, 1));
assert_eq!(offsets(&laid_out), [0, 32, 32]);
assert_eq!(laid_out.fields.len(), 3, "one field per declaration, so indices line up");
}
#[test]
fn an_unnamed_bit_field_does_not_raise_the_records_alignment_but_a_named_one_does() {
let mut interner = Interner::new();
let types = Types::new();
let char_ = types.int(IntKind::Char);
let int = types.int(IntKind::Int);
let unnamed = [member(char_), unnamed_bits(int, 20)];
let unnamed = lay_out(&types, RecordKind::Struct, &unnamed);
assert_eq!(unnamed.layout, Layout::new(4, 1));
let named = [member(char_), bits(&mut interner, "b", int, 20)];
let named = lay_out(&types, RecordKind::Struct, &named);
assert_eq!(named.layout, Layout::new(4, 4));
assert_eq!(offsets(&named), [0, 8], "the same place either way");
let wider = [member(char_), unnamed_bits(int, 30)];
let wider = lay_out(&types, RecordKind::Struct, &wider);
assert_eq!(wider.layout, Layout::new(8, 1));
assert_eq!(offsets(&wider), [0, 32]);
}
#[test]
fn packed_drops_every_member_to_a_byte_and_bit_fields_to_the_next_free_bit() {
let mut interner = Interner::new();
let types = Types::new();
let char_ = types.int(IntKind::Char);
let int = types.int(IntKind::Int);
let packed = RecordOptions { packed: true, ..RecordOptions::default() };
let fields = [member(char_), member(int)];
let laid_out = layout_record(&types, RecordKind::Struct, &fields, &packed, &linux())
.expect("a packed struct of two complete members");
assert_eq!(laid_out.layout, Layout::new(5, 1));
assert_eq!(offsets(&laid_out), [0, 8]);
let fields = [member(char_), bits(&mut interner, "b", int, 30)];
let laid_out = layout_record(&types, RecordKind::Struct, &fields, &packed, &linux())
.expect("a packed struct with a bit-field");
assert_eq!(laid_out.layout, Layout::new(5, 1));
assert_eq!(offsets(&laid_out), [0, 8], "no boundary left to move to");
let fields = [member(char_), unnamed_bits(int, 0), member(char_)];
let laid_out = layout_record(&types, RecordKind::Struct, &fields, &packed, &linux())
.expect("a packed struct with a zero width bit-field");
assert_eq!(laid_out.layout, Layout::new(5, 1));
assert_eq!(offsets(&laid_out), [0, 32, 32]);
}
#[test]
fn pragma_pack_caps_alignment_and_leaves_a_bit_field_where_it_already_is() {
let mut interner = Interner::new();
let types = Types::new();
let char_ = types.int(IntKind::Char);
let int = types.int(IntKind::Int);
let pack = RecordOptions { pack: Some(2), ..RecordOptions::default() };
let fields = [member(char_), member(int)];
let laid_out = layout_record(&types, RecordKind::Struct, &fields, &pack, &linux())
.expect("a packed struct of two complete members");
assert_eq!(laid_out.layout, Layout::new(6, 2));
assert_eq!(offsets(&laid_out), [0, 16]);
let fields = [member(char_), bits(&mut interner, "b", int, 30)];
let laid_out = layout_record(&types, RecordKind::Struct, &fields, &pack, &linux())
.expect("a packed struct with a bit-field");
assert_eq!(laid_out.layout, Layout::new(6, 2));
assert_eq!(offsets(&laid_out), [0, 8]);
let fields = [member(char_), unnamed_bits(int, 30)];
let laid_out = layout_record(&types, RecordKind::Struct, &fields, &pack, &linux())
.expect("a packed struct with an unnamed bit-field");
assert_eq!(laid_out.layout, Layout::new(5, 1));
}
#[test]
fn an_alignment_the_program_asked_for_raises_the_member_and_the_record() {
let types = Types::new();
let char_ = types.int(IntKind::Char);
let int = types.int(IntKind::Int);
let aligned = FieldDecl { align: Some(16), ..member(int) };
let laid_out = lay_out(&types, RecordKind::Struct, &[member(char_), aligned]);
assert_eq!(laid_out.layout, Layout::new(32, 16));
assert_eq!(offsets(&laid_out), [0, 128]);
let options = RecordOptions { packed: true, align: Some(4), pack: None };
let fields = [member(char_), member(int)];
let laid_out = layout_record(&types, RecordKind::Struct, &fields, &options, &linux())
.expect("a packed struct with an alignment asked for");
assert_eq!(laid_out.layout, Layout::new(8, 4));
assert_eq!(offsets(&laid_out), [0, 8]);
}
#[test]
fn a_flexible_array_member_costs_nothing_but_its_alignment() {
let mut types = Types::new();
let char_ = types.int(IntKind::Char);
let int = types.int(IntKind::Int);
let long_long = types.int(IntKind::LongLong);
let chars = types.array(char_, ArrayLen::Unknown);
let laid_out = lay_out(&types, RecordKind::Struct, &[member(int), member(chars)]);
assert_eq!(laid_out.layout, Layout::new(4, 4));
assert_eq!(offsets(&laid_out), [0, 32]);
let longs = types.array(long_long, ArrayLen::Unknown);
let laid_out = lay_out(&types, RecordKind::Struct, &[member(char_), member(longs)]);
assert_eq!(laid_out.layout, Layout::new(8, 8));
assert_eq!(offsets(&laid_out), [0, 64]);
let fields = [member(chars), member(int)];
let error =
layout_record(&types, RecordKind::Struct, &fields, &RecordOptions::default(), &linux());
assert_eq!(error, Err(RecordError::Member { index: 0, error: LayoutError::Incomplete }));
}
#[test]
fn a_record_with_no_members_is_zero_bytes_aligned_to_one() {
let types = Types::new();
let laid_out = lay_out(&types, RecordKind::Struct, &[]);
assert_eq!(laid_out.layout, Layout::new(0, 1));
}
#[test]
fn a_bit_field_wider_than_the_type_it_is_declared_with_is_refused() {
let types = Types::new();
let int = types.int(IntKind::Int);
let fields = [unnamed_bits(int, 33)];
let error =
layout_record(&types, RecordKind::Struct, &fields, &RecordOptions::default(), &linux());
let want = RecordError::BitFieldTooWide { index: 0, width: 33, capacity: 32 };
assert_eq!(error, Err(want));
}
#[test]
fn a_record_reports_its_members_once_it_has_been_completed() {
let mut interner = Interner::new();
let mut types = Types::new();
let char_ = types.int(IntKind::Char);
let int = types.int(IntKind::Int);
let name = interner.intern("count");
let fields = [member(char_), FieldDecl::new(Some(name), int)];
let id = types.declare_record(RecordKind::Struct, None);
let laid_out = lay_out(&types, RecordKind::Struct, &fields);
types.complete_record(id, laid_out);
let ty = types.record(id);
assert_eq!(layout(&types, ty, &linux()).unwrap(), Layout::new(8, 4));
let field = types.field(id, name).expect("the member that was declared");
assert_eq!(field.byte_offset(), 4);
assert!(!field.is_bit_field());
assert_eq!(types.field(id, interner.intern("missing")), None);
}
#[test]
fn a_nested_record_brings_its_own_alignment_with_it() {
let mut types = Types::new();
let char_ = types.int(IntKind::Char);
let int = types.int(IntKind::Int);
let inner = record(&mut types, RecordKind::Struct, &[member(char_)]);
let laid_out = lay_out(&types, RecordKind::Struct, &[member(inner), member(int)]);
assert_eq!(laid_out.layout, Layout::new(8, 4));
assert_eq!(offsets(&laid_out), [0, 32]);
let anonymous = record(&mut types, RecordKind::Struct, &[member(int), member(char_)]);
let laid_out = lay_out(&types, RecordKind::Struct, &[member(char_), member(anonymous)]);
assert_eq!(laid_out.layout, Layout::new(12, 4));
assert_eq!(offsets(&laid_out), [0, 32]);
}
#[test]
fn everything_narrower_than_an_int_promotes_to_one() {
let mut types = Types::new();
let linux = linux();
let int = types.int(IntKind::Int);
let narrow =
[IntKind::Char, IntKind::SChar, IntKind::UChar, IntKind::Short, IntKind::UShort];
for kind in narrow {
let ty = types.int(kind);
assert_eq!(promote(&mut types, ty, &linux), int, "{}", kind.as_str());
}
let boolean = types.boolean();
assert_eq!(promote(&mut types, boolean, &linux), int, "C23 made bool a real type");
for kind in [IntKind::Int, IntKind::UInt, IntKind::Long, IntKind::ULongLong] {
let ty = types.int(kind);
assert_eq!(promote(&mut types, ty, &linux), ty, "{}", kind.as_str());
}
}
#[test]
fn a_bit_int_is_not_promoted_at_all() {
let mut types = Types::new();
let linux = linux();
let small = types.bit_int(true, 8);
assert_eq!(promote(&mut types, small, &linux), small);
assert_eq!(usual_arithmetic(&mut types, small, small, &linux), Some(small));
}
#[test]
fn a_bit_field_is_promoted_by_its_width_and_not_by_its_type() {
let mut types = Types::new();
let linux = linux();
let int = types.int(IntKind::Int);
let uint = types.int(IntKind::UInt);
let ullong = types.int(IntKind::ULongLong);
assert_eq!(promote_bit_field(&mut types, uint, 3, &linux), int);
assert_eq!(promote_bit_field(&mut types, uint, 32, &linux), uint);
assert_eq!(promote_bit_field(&mut types, int, 20, &linux), int);
assert_eq!(promote_bit_field(&mut types, ullong, 40, &linux), ullong);
}
#[test]
fn an_enumeration_promotes_through_what_it_is_represented_in() {
let mut types = Types::new();
let linux = linux();
let int = types.int(IntKind::Int);
let short = types.int(IntKind::Short);
let uint = types.int(IntKind::UInt);
let fixed = types.declare_enum(None);
types.complete_enum(fixed, short, true);
let fixed = types.enumeration(fixed);
assert_eq!(promote(&mut types, fixed, &linux), int);
let unsigned = types.declare_enum(None);
types.complete_enum(unsigned, uint, false);
let unsigned = types.enumeration(unsigned);
assert_eq!(promote(&mut types, unsigned, &linux), uint);
let undecided = types.declare_enum(None);
let undecided = types.enumeration(undecided);
assert_eq!(promote(&mut types, undecided, &linux), int);
}
#[test]
fn the_qualifiers_and_the_atomic_come_off_before_anything_else() {
let mut types = Types::new();
let linux = linux();
let int = types.int(IntKind::Int);
let konst = types.qualified(int, Qualifiers::CONST);
let atomic = types.atomic(konst);
assert_eq!(promote(&mut types, atomic, &linux), int);
assert_eq!(usual_arithmetic(&mut types, atomic, konst, &linux), Some(int));
}
#[test]
fn the_usual_arithmetic_conversions_between_the_standard_integer_types() {
let mut types = Types::new();
let linux = linux();
let cases = [
(IntKind::Int, IntKind::UInt, IntKind::UInt),
(IntKind::Int, IntKind::Long, IntKind::Long),
(IntKind::UInt, IntKind::Long, IntKind::Long),
(IntKind::UInt, IntKind::ULong, IntKind::ULong),
(IntKind::Int, IntKind::LongLong, IntKind::LongLong),
(IntKind::UInt, IntKind::LongLong, IntKind::LongLong),
(IntKind::ULong, IntKind::LongLong, IntKind::ULongLong),
(IntKind::Char, IntKind::Char, IntKind::Int),
(IntKind::UChar, IntKind::UShort, IntKind::Int),
];
for (left, right, want) in cases {
let left = types.int(left);
let right = types.int(right);
let want = types.int(want);
assert_eq!(usual_arithmetic(&mut types, left, right, &linux), Some(want));
assert_eq!(usual_arithmetic(&mut types, right, left, &linux), Some(want), "either way");
}
}
#[test]
fn int128_is_sixteen_bytes_aligned_to_sixteen_and_outranks_long_long() {
let mut types = Types::new();
let linux = linux();
let signed = types.int(IntKind::Int128);
let unsigned = types.int(IntKind::UInt128);
for id in [signed, unsigned] {
let laid_out = layout(&types, id, &linux).expect("a complete type");
assert_eq!(laid_out.size, 16);
assert_eq!(laid_out.align, 16);
}
let ull = types.int(IntKind::ULongLong);
assert_eq!(usual_arithmetic(&mut types, signed, ull, &linux), Some(signed));
assert_eq!(promote(&mut types, signed, &linux), signed);
}
#[test]
fn a_bit_int_of_a_hundred_and_twenty_eight_bits_is_not_int128() {
let mut types = Types::new();
let linux = linux();
let int128 = types.int(IntKind::Int128);
let bit_int = types.bit_int(true, 128);
assert_ne!(int128, bit_int);
assert!(!compatible(&types, int128, bit_int));
assert_eq!(layout(&types, bit_int, &linux).expect("complete").align, 8);
assert_eq!(layout(&types, int128, &linux).expect("complete").align, 16);
}
#[test]
fn the_last_arm_takes_the_unsigned_type_of_the_wider_one() {
let mut types = Types::new();
let linux = linux();
let ulong = types.int(IntKind::ULong);
let long_long = types.int(IntKind::LongLong);
let want = types.int(IntKind::ULongLong);
assert_eq!(usual_arithmetic(&mut types, ulong, long_long, &linux), Some(want));
let windows = target("x86_64-pc-windows-msvc");
assert_eq!(usual_arithmetic(&mut types, ulong, long_long, &windows), Some(long_long));
}
#[test]
fn a_bit_int_is_ranked_by_its_width_against_the_standard_types() {
let mut types = Types::new();
let linux = linux();
let b40 = types.bit_int(true, 40);
let ub40 = types.bit_int(false, 40);
let b8 = types.bit_int(true, 8);
let b32 = types.bit_int(true, 32);
let int = types.int(IntKind::Int);
let uint = types.int(IntKind::UInt);
let long = types.int(IntKind::Long);
let char_ = types.int(IntKind::Char);
assert_eq!(usual_arithmetic(&mut types, b40, int, &linux), Some(b40));
assert_eq!(usual_arithmetic(&mut types, b40, long, &linux), Some(long));
assert_eq!(usual_arithmetic(&mut types, b32, int, &linux), Some(int));
assert_eq!(usual_arithmetic(&mut types, b32, uint, &linux), Some(uint));
assert_eq!(usual_arithmetic(&mut types, b8, char_, &linux), Some(int));
assert_eq!(usual_arithmetic(&mut types, ub40, int, &linux), Some(ub40));
assert_eq!(usual_arithmetic(&mut types, ub40, long, &linux), Some(long));
assert_eq!(usual_arithmetic(&mut types, b40, ub40, &linux), Some(ub40));
}
#[test]
fn a_floating_operand_decides_the_answer_whatever_the_other_side_is() {
let mut types = Types::new();
let linux = linux();
let float = types.float(FloatKind::Float);
let double = types.float(FloatKind::Double);
let long_double = types.float(FloatKind::LongDouble);
let ullong = types.int(IntKind::ULongLong);
let int = types.int(IntKind::Int);
assert_eq!(usual_arithmetic(&mut types, int, float, &linux), Some(float));
assert_eq!(usual_arithmetic(&mut types, float, double, &linux), Some(double));
assert_eq!(usual_arithmetic(&mut types, double, long_double, &linux), Some(long_double));
assert_eq!(usual_arithmetic(&mut types, ullong, float, &linux), Some(float));
}
fn combines(target: &TargetInfo, a: FloatKind, b: FloatKind, expected: FloatKind) {
let mut types = Types::new();
let left = types.float(a);
let right = types.float(b);
let want = types.float(expected);
assert_eq!(usual_arithmetic(&mut types, left, right, target), Some(want), "{a:?} + {b:?}");
assert_eq!(usual_arithmetic(&mut types, right, left, target), Some(want), "{b:?} + {a:?}");
}
#[test]
fn two_floating_types_of_the_same_format_are_still_two_types_and_one_of_them_wins() {
let x86 = linux();
combines(&x86, FloatKind::Double, FloatKind::Float64, FloatKind::Float64);
combines(&x86, FloatKind::Float, FloatKind::Float32, FloatKind::Float32);
combines(&x86, FloatKind::Double, FloatKind::Float32x, FloatKind::Double);
combines(&x86, FloatKind::LongDouble, FloatKind::Float64x, FloatKind::LongDouble);
combines(&x86, FloatKind::Float128, FloatKind::LongDouble, FloatKind::Float128);
combines(&x86, FloatKind::Float64x, FloatKind::Float128, FloatKind::Float128);
combines(&x86, FloatKind::Double, FloatKind::LongDouble, FloatKind::LongDouble);
combines(&x86, FloatKind::Float32x, FloatKind::Float64, FloatKind::Float64);
combines(&x86, FloatKind::Float64x, FloatKind::Float64, FloatKind::Float64x);
combines(&x86, FloatKind::LongDouble, FloatKind::Float64, FloatKind::LongDouble);
}
#[test]
fn the_widest_floating_type_is_a_question_about_the_target_and_not_about_the_names() {
let mac = target("aarch64-apple-darwin");
combines(&mac, FloatKind::LongDouble, FloatKind::Float64x, FloatKind::Float64x);
combines(&mac, FloatKind::Double, FloatKind::LongDouble, FloatKind::LongDouble);
combines(&mac, FloatKind::Float128, FloatKind::LongDouble, FloatKind::Float128);
combines(&mac, FloatKind::Float64x, FloatKind::Float64, FloatKind::Float64x);
combines(&mac, FloatKind::Float32x, FloatKind::Float32, FloatKind::Float32x);
combines(&mac, FloatKind::Float32x, FloatKind::Float64, FloatKind::Float64);
combines(&mac, FloatKind::Double, FloatKind::Float64, FloatKind::Float64);
combines(&mac, FloatKind::Float16, FloatKind::Float, FloatKind::Float);
combines(&mac, FloatKind::Float16, FloatKind::Double, FloatKind::Double);
combines(&mac, FloatKind::Float16, FloatKind::Float16, FloatKind::Float16);
}
#[test]
fn a_complex_operand_makes_the_answer_complex_after_the_real_types_have_combined() {
let mut types = Types::new();
let linux = linux();
let cfloat = types.complex(FloatKind::Float);
let cdouble = types.complex(FloatKind::Double);
let cldouble = types.complex(FloatKind::LongDouble);
let double = types.float(FloatKind::Double);
let long_double = types.float(FloatKind::LongDouble);
let float = types.float(FloatKind::Float);
let int = types.int(IntKind::Int);
assert_eq!(usual_arithmetic(&mut types, cfloat, double, &linux), Some(cdouble));
assert_eq!(usual_arithmetic(&mut types, cfloat, int, &linux), Some(cfloat));
assert_eq!(usual_arithmetic(&mut types, cdouble, long_double, &linux), Some(cldouble));
assert_eq!(usual_arithmetic(&mut types, cfloat, float, &linux), Some(cfloat));
}
#[test]
fn an_operand_that_is_not_arithmetic_has_no_common_type() {
let mut types = Types::new();
let linux = linux();
let int = types.int(IntKind::Int);
let pointer = types.pointer(int);
assert_eq!(usual_arithmetic(&mut types, pointer, int, &linux), None);
assert_eq!(usual_arithmetic(&mut types, pointer, pointer, &linux), None);
let void = types.void();
assert_eq!(usual_arithmetic(&mut types, void, int, &linux), None);
assert_eq!(promote(&mut types, pointer, &linux), pointer);
}
#[test]
fn the_conversions_read_through_sugar() {
let mut interner = Interner::new();
let mut types = Types::new();
let linux = linux();
let char_ = types.int(IntKind::Char);
let name = types.typedef(interner.intern("byte"), char_);
let int = types.int(IntKind::Int);
assert_eq!(promote(&mut types, name, &linux), int);
}
fn prototype(types: &mut Types, params: Vec<TypeId>, variadic: bool) -> TypeId {
let ret = types.void();
types.function(FunctionType { ret, params, variadic, prototyped: true })
}
fn old_style(types: &mut Types) -> TypeId {
let ret = types.void();
types.function(FunctionType { ret, params: Vec::new(), variadic: false, prototyped: false })
}
fn tagged(types: &mut Types, tag: Symbol, fields: &[FieldDecl]) -> RecordId {
let id = types.declare_record(RecordKind::Struct, Some(tag));
let laid_out = lay_out(types, RecordKind::Struct, fields);
types.complete_record(id, laid_out);
id
}
#[test]
fn a_type_is_compatible_with_itself_however_it_was_written() {
let mut interner = Interner::new();
let mut types = Types::new();
let int = types.int(IntKind::Int);
let name = types.typedef(interner.intern("int32_t"), int);
assert!(compatible(&types, name, int), "the sugar is the same type underneath");
assert_eq!(composite(&mut types, name, int), Some(name), "and it keeps its name");
let konst = types.qualified(int, Qualifiers::CONST);
assert!(!compatible(&types, konst, int));
let konst_pointer = types.pointer(konst);
let pointer = types.pointer(int);
assert!(!compatible(&types, konst_pointer, pointer));
assert_eq!(composite(&mut types, konst_pointer, pointer), None);
let char_ = types.int(IntKind::Char);
let schar = types.int(IntKind::SChar);
assert!(!compatible(&types, char_, schar));
let atomic = types.atomic(int);
assert!(!compatible(&types, atomic, int));
}
#[test]
fn an_enumeration_is_compatible_with_the_type_it_is_represented_in() {
let mut types = Types::new();
let uint = types.int(IntKind::UInt);
let int = types.int(IntKind::Int);
let id = types.declare_enum(None);
types.complete_enum(id, uint, false);
let e = types.enumeration(id);
assert!(compatible(&types, e, uint));
assert!(compatible(&types, uint, e), "and the relation is symmetric");
assert!(!compatible(&types, e, int));
let other = types.declare_enum(None);
types.complete_enum(other, uint, false);
let other = types.enumeration(other);
assert!(!compatible(&types, e, other));
let undecided = types.declare_enum(None);
let undecided = types.enumeration(undecided);
assert!(!compatible(&types, undecided, uint));
assert!(compatible(&types, undecided, undecided));
}
#[test]
fn an_array_without_a_size_is_compatible_with_one_that_has_it() {
let mut types = Types::new();
let int = types.int(IntKind::Int);
let unknown = types.array(int, ArrayLen::Unknown);
let four = types.array(int, ArrayLen::Fixed(4));
let five = types.array(int, ArrayLen::Fixed(5));
assert!(compatible(&types, unknown, four));
assert!(!compatible(&types, four, five));
assert_eq!(composite(&mut types, unknown, four), Some(four));
assert_eq!(composite(&mut types, four, unknown), Some(four), "either way round");
assert_eq!(composite(&mut types, four, five), None);
let vla = types.array(int, ArrayLen::Variable(VlaId(0)));
assert!(compatible(&types, vla, four));
assert_eq!(composite(&mut types, vla, four), Some(four));
let long = types.int(IntKind::Long);
let longs = types.array(long, ArrayLen::Fixed(4));
assert!(!compatible(&types, four, longs));
}
#[test]
fn a_parameter_declared_as_an_array_is_a_pointer() {
let mut types = Types::new();
let int = types.int(IntKind::Int);
let three = types.array(int, ArrayLen::Fixed(3));
let pointer = types.pointer(int);
assert_eq!(adjust_parameter(&mut types, three), pointer);
let function = prototype(&mut types, vec![int], false);
let function_pointer = types.pointer(function);
assert_eq!(adjust_parameter(&mut types, function), function_pointer);
let konst = types.qualified(int, Qualifiers::CONST);
assert_eq!(adjust_parameter(&mut types, konst), int);
let to_konst = types.pointer(konst);
assert_eq!(adjust_parameter(&mut types, to_konst), to_konst);
}
#[test]
fn an_old_style_declaration_is_compatible_with_the_prototypes_a_call_could_not_tell_from_it() {
let mut types = Types::new();
let old = old_style(&mut types);
let int = types.int(IntKind::Int);
let long = types.int(IntKind::Long);
let char_ = types.int(IntKind::Char);
let float = types.float(FloatKind::Float);
let double = types.float(FloatKind::Double);
let takes_int = prototype(&mut types, vec![int], false);
assert!(compatible(&types, old, takes_int));
assert!(compatible(&types, takes_int, old), "and the relation is symmetric");
assert_eq!(composite(&mut types, old, takes_int), Some(takes_int));
let pointer = types.pointer(int);
for params in [vec![long], vec![double], vec![pointer], vec![int, long]] {
let ty = prototype(&mut types, params, false);
assert!(compatible(&types, old, ty), "nothing here is touched by a promotion");
}
for params in [vec![char_], vec![float], vec![int, char_]] {
let ty = prototype(&mut types, params, false);
assert!(!compatible(&types, old, ty));
assert_eq!(composite(&mut types, old, ty), None);
}
let variadic = prototype(&mut types, vec![int], true);
assert!(!compatible(&types, old, variadic));
let uint = types.int(IntKind::UInt);
let id = types.declare_enum(None);
types.complete_enum(id, uint, false);
let e = types.enumeration(id);
let takes_enum = prototype(&mut types, vec![e], false);
assert!(compatible(&types, old, takes_enum));
assert!(compatible(&types, old, old));
let returns_int = types.function(FunctionType {
ret: int,
params: Vec::new(),
variadic: false,
prototyped: false,
});
assert!(!compatible(&types, returns_int, takes_int));
}
#[test]
fn from_c23_an_empty_parameter_list_is_a_prototype_and_conflicts_where_it_used_to_merge() {
let mut types = Types::new();
let int = types.int(IntKind::Int);
let takes_int = prototype(&mut types, vec![int], false);
let takes_nothing = prototype(&mut types, Vec::new(), false);
let old = old_style(&mut types);
assert!(!compatible(&types, takes_nothing, takes_int));
assert!(compatible(&types, old, takes_int), "the C17 reading of the same source");
}
#[test]
fn two_prototypes_have_to_agree_about_everything() {
let mut types = Types::new();
let int = types.int(IntKind::Int);
let long = types.int(IntKind::Long);
let base = prototype(&mut types, vec![int, int], false);
for other in [vec![int], vec![int, long], vec![int, int, int], Vec::new()] {
let other = prototype(&mut types, other, false);
assert!(!compatible(&types, base, other));
}
let variadic = prototype(&mut types, vec![int, int], true);
assert!(!compatible(&types, base, variadic), "`...` is part of the type");
let four = types.array(int, ArrayLen::Fixed(4));
let unknown = types.array(int, ArrayLen::Unknown);
let to_four = types.pointer(four);
let to_unknown = types.pointer(unknown);
let a = prototype(&mut types, vec![to_four], false);
let b = prototype(&mut types, vec![to_unknown], false);
assert!(compatible(&types, a, b));
assert_eq!(composite(&mut types, a, b), Some(a));
}
#[test]
fn a_pointer_composite_reaches_through_to_what_is_pointed_at() {
let mut types = Types::new();
let int = types.int(IntKind::Int);
let four = types.array(int, ArrayLen::Fixed(4));
let unknown = types.array(int, ArrayLen::Unknown);
let to_four = types.pointer(four);
let to_unknown = types.pointer(unknown);
assert_eq!(composite(&mut types, to_unknown, to_four), Some(to_four));
let konst_to_unknown = types.qualified(to_unknown, Qualifiers::CONST);
let konst_to_four = types.qualified(to_four, Qualifiers::CONST);
assert_eq!(composite(&mut types, konst_to_unknown, konst_to_four), Some(konst_to_four));
}
#[test]
fn two_record_declarations_with_the_same_tag_and_the_same_members_are_compatible() {
let mut interner = Interner::new();
let mut types = Types::new();
let tag = interner.intern("point");
let x = interner.intern("x");
let y = interner.intern("y");
let int = types.int(IntKind::Int);
let members = [FieldDecl::new(Some(x), int), FieldDecl::new(Some(y), int)];
let first = tagged(&mut types, tag, &members);
let second = tagged(&mut types, tag, &members);
let first = types.record(first);
let second = types.record(second);
assert_ne!(first, second, "still two declarations and two types");
assert!(compatible(&types, first, second));
let z = interner.intern("z");
let long = types.int(IntKind::Long);
let renamed = [FieldDecl::new(Some(x), int), FieldDecl::new(Some(z), int)];
let retyped = [FieldDecl::new(Some(x), int), FieldDecl::new(Some(y), long)];
for other in [&renamed[..], &retyped[..], &members[..1]] {
let other = tagged(&mut types, tag, other);
let other = types.record(other);
assert!(!compatible(&types, first, other));
}
let elsewhere = tagged(&mut types, interner.intern("pair"), &members);
let elsewhere = types.record(elsewhere);
assert!(!compatible(&types, first, elsewhere));
let anonymous = record(&mut types, RecordKind::Struct, &members);
let also_anonymous = record(&mut types, RecordKind::Struct, &members);
assert!(!compatible(&types, anonymous, also_anonymous));
let incomplete = types.declare_record(RecordKind::Struct, Some(tag));
let incomplete = types.record(incomplete);
assert!(!compatible(&types, first, incomplete));
assert!(compatible(&types, incomplete, incomplete));
}
#[test]
fn a_self_referential_record_is_compared_without_going_round_forever() {
let mut interner = Interner::new();
let mut types = Types::new();
let tag = interner.intern("node");
let value = interner.intern("value");
let next = interner.intern("next");
let int = types.int(IntKind::Int);
let node = |types: &mut Types| {
let id = types.declare_record(RecordKind::Struct, Some(tag));
let ty = types.record(id);
let pointer = types.pointer(ty);
let members = [FieldDecl::new(Some(value), int), FieldDecl::new(Some(next), pointer)];
let laid_out = lay_out(types, RecordKind::Struct, &members);
types.complete_record(id, laid_out);
ty
};
let first = node(&mut types);
let second = node(&mut types);
assert_ne!(first, second);
assert!(compatible(&types, first, second));
let id = types.declare_record(RecordKind::Struct, Some(tag));
let ty = types.record(id);
let pointer = types.pointer(ty);
let members = [FieldDecl::new(Some(next), pointer), FieldDecl::new(Some(value), int)];
let laid_out = lay_out(&types, RecordKind::Struct, &members);
types.complete_record(id, laid_out);
assert!(!compatible(&types, first, ty));
}
#[test]
fn layout_reads_through_sugar() {
let mut interner = Interner::new();
let mut types = Types::new();
let long = types.int(IntKind::Long);
let name = types.typedef(interner.intern("word"), long);
let array = types.array(name, ArrayLen::Fixed(4));
assert_eq!(layout(&types, array, &linux()).unwrap(), Layout::new(32, 8));
}
}