use std::collections::{HashMap, HashSet};
use rucc_ast::{
self as ast, ArraySize, Complexity, Derived, ParamKind, Scalar, TypeSpec, TypeofArg,
};
use rucc_base::Symbol;
use rucc_diag::{Diagnostic, Span};
use rucc_session::Std;
use rucc_types::{
ArrayLen, FloatKind, FunctionType, IntKind, Qualifiers, RecordKind, TypeId, adjust_parameter,
is_complete, is_function, is_integer, is_pointer, is_void, layout,
};
use crate::check::Checker;
use crate::scope::{Binding, Tag, TagKind};
mod tag;
const MAX_BIT_INT_WIDTH: u32 = 128;
const MAX_OBJECT_SIZE: u64 = i64::MAX as u64;
#[derive(Debug, Default)]
pub(crate) struct Built {
specified: HashMap<ast::DeclSpecsId, TypeId>,
defined: HashSet<TypeId>,
}
#[derive(Debug, Clone, Copy)]
struct Subject {
name: Option<Symbol>,
span: Span,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum TagUse {
Known(TypeId),
New,
Anonymous,
Wrong,
}
#[derive(Debug, Clone, Copy, Default)]
struct Place {
parameter: bool,
prototype: bool,
}
impl Checker<'_> {
pub fn type_name(&mut self, id: ast::TypeNameId) -> TypeId {
let name = self.ast[id];
self.declared_type(name.specs, name.declarator)
}
pub fn declared_type(
&mut self,
specs: ast::DeclSpecsId,
declarator: ast::DeclaratorId,
) -> TypeId {
self.build_type(specs, declarator, Place::default())
}
pub fn declare_typedef(&mut self, name: Symbol, ty: TypeId) {
self.scopes.declare(name, Binding::Typedef(ty));
}
fn build_type(
&mut self,
specs: ast::DeclSpecsId,
declarator: ast::DeclaratorId,
place: Place,
) -> TypeId {
let node = self.ast[declarator];
let subject = Subject {
name: node.name,
span: if node.name.is_some() { node.name_span } else { node.span },
};
let base = self.specified_type(specs, subject);
self.derive(base, declarator, subject, place)
}
fn specified_type(&mut self, id: ast::DeclSpecsId, subject: Subject) -> TypeId {
if let Some(&ty) = self.built.specified.get(&id) {
return ty;
}
let specs = self.ast[id];
let base = self.type_spec(specs.ty, specs.span, subject);
let ty = self.qualify(base, specs.quals, specs.span);
self.built.specified.insert(id, ty);
ty
}
fn type_spec(&mut self, spec: TypeSpec, span: Span, subject: Subject) -> TypeId {
match spec {
TypeSpec::None => {
let what = match subject.name {
Some(name) => format!("in declaration of '{}'", self.text(name)),
None => String::new(),
};
let message = format!("type defaults to 'int' {what}");
self.report(
Diagnostic::warning(message.trim_end().to_string(), subject.span)
.with_code("E0526"),
);
self.int()
}
TypeSpec::Builtin(builtin) => match builtin.resolve() {
Some(basic) => self.basic_type(basic.scalar, basic.complexity, span),
None => {
self.report(
Diagnostic::error(
"two or more data types in declaration specifiers".to_string(),
span,
)
.with_code("E0525"),
);
self.int()
}
},
TypeSpec::Record { kind, tag, fields, .. } => self.record_spec(kind, tag, fields, span),
TypeSpec::Enum { tag, enumerators, underlying, .. } => {
self.enum_spec(tag, enumerators, underlying, span)
}
TypeSpec::Typedef(name) => match self.scopes.lookup(name) {
Some(Binding::Typedef(ty)) => ty,
_ => {
let name = self.text(name).to_owned();
self.report(
Diagnostic::error(format!("unknown type name '{name}'"), span)
.with_code("E0546"),
);
self.int()
}
},
TypeSpec::Typeof { unqual, operand } => self.typeof_type(unqual, operand),
TypeSpec::BitInt(width) => self.bit_int_type(width),
TypeSpec::Atomic(inner) => {
let inner = self.type_name(inner);
self.atomic_type(inner, span)
}
TypeSpec::Auto => {
self.unsupported_type("`auto` as a type specifier", span);
self.int()
}
}
}
fn basic_type(&mut self, scalar: Scalar, complexity: Complexity, span: Span) -> TypeId {
let kind = int_kind(scalar);
let float = float_kind(scalar);
match complexity {
Complexity::Real => match (scalar, kind, float) {
(Scalar::Void, _, _) => self.types.void(),
(Scalar::Bool, _, _) => self.types.boolean(),
(_, Some(kind), _) => self.types.int(kind),
(_, _, Some(kind)) => self.types.float(kind),
_ => {
self.unsupported_type(&format!("the type `{}`", spell_scalar(scalar)), span);
self.types.float(FloatKind::Double)
}
},
Complexity::Complex => match float {
Some(kind) => self.types.complex(kind),
None => {
let what = format!("`_Complex` on the type `{}`", spell_scalar(scalar));
self.unsupported_type(&what, span);
self.types.complex(FloatKind::Double)
}
},
Complexity::Imaginary => {
self.unsupported_type("`_Imaginary`", span);
self.types.complex(float.unwrap_or(FloatKind::Double))
}
}
}
fn typeof_type(&mut self, unqual: bool, operand: TypeofArg) -> TypeId {
let ty = match operand {
TypeofArg::Expr(expr) => {
let node = self.expr(expr);
self.tast[node].ty
}
TypeofArg::Type(name) => self.type_name(name),
};
if !unqual {
return ty;
}
let bare = match self.types.kind(self.types.canonical(ty)) {
rucc_types::TypeKind::Atomic(inner) => inner,
_ => ty,
};
self.types.unqualified(bare)
}
fn bit_int_type(&mut self, width: ast::ExprId) -> TypeId {
let value = self.expr(width);
let span = self.tast.expr_span(value);
let Ok(bits) = self.eval_integer(value) else {
if !self.is_poisoned(value) {
self.report(
Diagnostic::error(
"'_BitInt' width is not an integer constant expression".to_string(),
span,
)
.with_code("E0529"),
);
}
return self.int();
};
let least = 2;
if bits < i128::from(least) {
let message = format!("signed _BitInt must have a bit size of at least {least}");
self.report(Diagnostic::error(message, span).with_code("E0529"));
return self.int();
}
if bits > i128::from(MAX_BIT_INT_WIDTH) {
let message = format!(
"signed _BitInt of bit sizes greater than {MAX_BIT_INT_WIDTH} not supported"
);
self.report(Diagnostic::error(message, span).with_code("E0529"));
return self.int();
}
let bits = u32::try_from(bits).unwrap_or(MAX_BIT_INT_WIDTH);
self.types.bit_int(true, bits)
}
fn atomic_type(&mut self, inner: TypeId, span: Span) -> TypeId {
let canonical = self.types.canonical(inner);
let what = if rucc_types::is_array(&self.types, canonical) {
"'_Atomic'-qualified array type"
} else if is_function(&self.types, canonical) {
"'_Atomic'-qualified function type"
} else if !self.types.quals(inner).is_none() {
"'_Atomic' applied to a qualified type"
} else {
return self.types.atomic(inner);
};
self.report(Diagnostic::error(what.to_string(), span).with_code("E0527"));
inner
}
fn record_spec(
&mut self,
kind: ast::RecordKind,
tag: Option<Symbol>,
fields: Option<ast::MemberList>,
span: Span,
) -> TypeId {
let (kind, tag_kind) = match kind {
ast::RecordKind::Struct => (RecordKind::Struct, TagKind::Struct),
ast::RecordKind::Union => (RecordKind::Union, TagKind::Union),
};
let Some(members) = fields else {
return match self.tag_use(tag, tag_kind, span) {
TagUse::Known(ty) => ty,
found => {
let id = self.types.declare_record(kind, tag);
let ty = self.types.record(id);
self.bind_tag(found, tag, tag_kind, ty);
ty
}
};
};
let (id, ty) = self.record_defined(kind, tag, tag_kind, span);
self.built.defined.insert(ty);
self.record_body(id, kind, members, span);
ty
}
fn enum_spec(
&mut self,
tag: Option<Symbol>,
enumerators: Option<ast::EnumeratorList>,
underlying: Option<ast::TypeNameId>,
span: Span,
) -> TypeId {
let underlying = underlying.map(|name| {
let ty = self.type_name(name);
if is_integer(&self.types, self.types.canonical(ty)) {
return ty;
}
self.report(
Diagnostic::error("invalid 'enum' underlying type".to_string(), span)
.with_code("E0530"),
);
self.int()
});
let Some(list) = enumerators else {
return match self.tag_use(tag, TagKind::Enum, span) {
TagUse::Known(ty) => ty,
found => {
let id = self.types.declare_enum(tag);
if let Some(underlying) = underlying {
self.types.complete_enum(id, underlying, true);
}
let ty = self.types.enumeration(id);
self.bind_tag(found, tag, TagKind::Enum, ty);
ty
}
};
};
let (id, ty) = self.enum_defined(tag, span);
self.built.defined.insert(ty);
self.enum_body(id, list, underlying, span);
ty
}
fn tag_use(&mut self, tag: Option<Symbol>, kind: TagKind, span: Span) -> TagUse {
let Some(name) = tag else { return TagUse::Anonymous };
match self.scopes.tag(name) {
Some(found) if found.kind == kind => TagUse::Known(found.ty),
Some(_) => {
let spelled = self.text(name).to_owned();
self.report(
Diagnostic::error(format!("'{spelled}' defined as wrong kind of tag"), span)
.with_code("E0531"),
);
TagUse::Wrong
}
None => TagUse::New,
}
}
fn bind_tag(&mut self, found: TagUse, tag: Option<Symbol>, kind: TagKind, ty: TypeId) {
if !matches!(found, TagUse::New) {
return;
}
if let Some(name) = tag {
self.scopes.declare_tag(name, Tag { kind, ty });
}
}
fn qualify(&mut self, ty: TypeId, quals: ast::Quals, span: Span) -> TypeId {
let ty = if quals.has(ast::Quals::ATOMIC) { self.atomic_type(ty, span) } else { ty };
let mut result = Qualifiers::NONE;
if quals.has(ast::Quals::CONST) {
result = result.with(Qualifiers::CONST);
}
if quals.has(ast::Quals::VOLATILE) {
result = result.with(Qualifiers::VOLATILE);
}
if quals.has(ast::Quals::RESTRICT) {
if is_pointer(&self.types, self.types.canonical(ty)) {
result = result.with(Qualifiers::RESTRICT);
} else {
self.report(
Diagnostic::error("invalid use of 'restrict'".to_string(), span)
.with_code("E0528"),
);
}
}
self.types.qualified(ty, result)
}
fn derive(
&mut self,
base: TypeId,
declarator: ast::DeclaratorId,
subject: Subject,
place: Place,
) -> TypeId {
let ast = self.ast;
let steps = &ast[ast[declarator].derived];
let mut ty = base;
for (index, step) in steps.iter().enumerate().rev() {
let nearest = index == 0;
ty = match *step {
Derived::Pointer { quals, .. } => {
let pointer = self.types.pointer(ty);
self.qualify(pointer, quals, subject.span)
}
Derived::Array { size, quals, has_static } => {
if (!quals.is_none() || has_static) && !(place.parameter && nearest) {
self.report(
Diagnostic::error(
"static or type qualifiers in non-parameter array declarator"
.to_string(),
subject.span,
)
.with_code("E0540"),
);
}
self.array_of(ty, size, subject, place)
}
Derived::Function { params, variadic, kind } => {
self.function_of(ty, params, variadic, kind, subject)
}
};
}
ty
}
fn array_of(
&mut self,
elem: TypeId,
size: ArraySize,
subject: Subject,
place: Place,
) -> TypeId {
let canonical = self.types.canonical(elem);
let bad = if is_void(&self.types, canonical) {
Some(("as array of voids", "E0532"))
} else if is_function(&self.types, canonical) {
Some(("as array of functions", "E0533"))
} else {
None
};
if let Some((what, code)) = bad {
let who = self.declaration_of(subject);
self.report(Diagnostic::error(format!("{who} {what}"), subject.span).with_code(code));
return elem;
}
if !is_complete(&self.types, canonical) {
let spelled = self.spell(elem);
self.report(
Diagnostic::error(
format!("array type has incomplete element type '{spelled}'"),
subject.span,
)
.with_code("E0534"),
);
return elem;
}
let len = self.array_len(elem, size, subject, place);
self.types.array(elem, len)
}
fn array_len(
&mut self,
elem: TypeId,
size: ArraySize,
subject: Subject,
place: Place,
) -> ArrayLen {
let expr = match size {
ArraySize::Unspecified => return ArrayLen::Unknown,
ArraySize::Star if place.prototype => return ArrayLen::Star,
ArraySize::Star => {
self.report(
Diagnostic::error(
"'[*]' not allowed in other than function prototype scope".to_string(),
subject.span,
)
.with_code("E0539"),
);
return ArrayLen::Unknown;
}
ArraySize::Expr(expr) => expr,
};
let value = self.expr(expr);
if self.is_poisoned(value) {
return ArrayLen::Unknown;
}
let span = self.tast.expr_span(value);
if !is_integer(&self.types, self.types.canonical(self.tast[value].ty)) {
self.report(
Diagnostic::error("size of array has non-integer type".to_string(), span)
.with_code("E0535"),
);
return ArrayLen::Unknown;
}
match self.eval_integer(value) {
Ok(count) if count < 0 => {
let who = self.array_named(subject);
self.report(
Diagnostic::error(format!("size of {who} is negative"), span)
.with_code("E0536"),
);
ArrayLen::Unknown
}
Ok(count) => {
let count = u64::try_from(count).unwrap_or(u64::MAX);
if self.too_large(elem, count) {
let who = self.array_named(subject);
let message =
format!("size of {who} exceeds maximum object size '{MAX_OBJECT_SIZE}'");
self.report(Diagnostic::error(message, span).with_code("E0537"));
return ArrayLen::Unknown;
}
ArrayLen::Fixed(count)
}
Err(failure) => {
if failure.poisoned {
return ArrayLen::Unknown;
}
if self.scopes.at_file_scope() {
let who = match subject.name {
Some(name) => format!("'{}'", self.text(name)),
None => "type name".to_string(),
};
self.report(
Diagnostic::error(
format!("variably modified {who} at file scope"),
subject.span,
)
.with_code("E0538"),
);
return ArrayLen::Unknown;
}
ArrayLen::Variable(self.tast.add_vla(value))
}
}
}
fn too_large(&self, elem: TypeId, count: u64) -> bool {
let Ok(elem) = layout(&self.types, elem, self.cx.target) else {
return false;
};
elem.size != 0 && count > MAX_OBJECT_SIZE / elem.size
}
fn function_of(
&mut self,
ret: TypeId,
params: ast::ParamList,
variadic: bool,
kind: ParamKind,
subject: Subject,
) -> TypeId {
let canonical = self.types.canonical(ret);
let bad = if rucc_types::is_array(&self.types, canonical) {
Some(("an array", "E0542"))
} else if is_function(&self.types, canonical) {
Some(("a function", "E0541"))
} else {
None
};
let ret = match bad {
Some((what, code)) => {
let who = self.declared_as(subject);
self.report(
Diagnostic::error(format!("{who} as function returning {what}"), subject.span)
.with_code(code),
);
self.int()
}
None => ret,
};
let (params, prototyped) = match kind {
ParamKind::Void => (Vec::new(), true),
ParamKind::Empty => (Vec::new(), self.cx.std == Std::C23),
ParamKind::Identifiers => (Vec::new(), false),
ParamKind::Prototype => (self.prototype(params), true),
};
self.types.function(FunctionType { ret, params, variadic, prototyped })
}
fn prototype(&mut self, params: ast::ParamList) -> Vec<TypeId> {
let ast = self.ast;
let list = &ast[params];
self.scopes.push();
let mut types = Vec::with_capacity(list.len());
for (index, param) in list.iter().enumerate() {
let ty = match param.specs {
Some(specs) => self.build_type(
specs,
param.declarator,
Place { parameter: true, prototype: true },
),
None => self.int(),
};
let declarator = ast[param.declarator];
let span = if declarator.name.is_some() { declarator.name_span } else { param.span };
self.check_void_parameter(ty, declarator.name, index, span);
let adjusted = adjust_parameter(&mut self.types, ty);
let adjusted = match ast[declarator.derived].first() {
Some(&Derived::Array { quals, .. }) => self.qualify(adjusted, quals, span),
_ => adjusted,
};
types.push(adjusted);
if let Some(name) = declarator.name {
if self.scopes.lookup_here(name).is_some() {
let spelled = self.text(name).to_owned();
self.report(
Diagnostic::error(format!("redefinition of parameter '{spelled}'"), span)
.with_code("E0545"),
);
} else {
self.declare_object(name, ty, span);
}
}
}
self.scopes.pop();
types
}
fn check_void_parameter(&mut self, ty: TypeId, name: Option<Symbol>, index: usize, span: Span) {
if !is_void(&self.types, self.types.canonical(ty)) {
return;
}
let position = index + 1;
match name {
Some(name) => {
let spelled = self.text(name).to_owned();
self.report(
Diagnostic::warning(
format!("parameter {position} ('{spelled}') has void type"),
span,
)
.with_code("E0544"),
);
}
None => {
self.report(
Diagnostic::error("'void' must be the only parameter".to_string(), span)
.with_code("E0543"),
);
}
}
}
fn declaration_of(&self, subject: Subject) -> String {
match subject.name {
Some(name) => format!("declaration of '{}'", self.text(name)),
None => "declaration of type name".to_string(),
}
}
fn declared_as(&self, subject: Subject) -> String {
match subject.name {
Some(name) => format!("'{}' declared", self.text(name)),
None => "type name declared".to_string(),
}
}
fn array_named(&self, subject: Subject) -> String {
match subject.name {
Some(name) => format!("array '{}'", self.text(name)),
None => "unnamed array".to_string(),
}
}
fn unsupported_type(&mut self, what: &str, span: Span) {
self.report(
Diagnostic::error(format!("{what} is not supported yet"), span).with_code("E0519"),
);
}
}
fn int_kind(scalar: Scalar) -> Option<IntKind> {
let kind = match scalar {
Scalar::Char => IntKind::Char,
Scalar::SignedChar => IntKind::SChar,
Scalar::UnsignedChar => IntKind::UChar,
Scalar::Short => IntKind::Short,
Scalar::UnsignedShort => IntKind::UShort,
Scalar::Int => IntKind::Int,
Scalar::UnsignedInt => IntKind::UInt,
Scalar::Long => IntKind::Long,
Scalar::UnsignedLong => IntKind::ULong,
Scalar::LongLong => IntKind::LongLong,
Scalar::UnsignedLongLong => IntKind::ULongLong,
Scalar::Int128 => IntKind::Int128,
Scalar::UnsignedInt128 => IntKind::UInt128,
_ => return None,
};
Some(kind)
}
fn float_kind(scalar: Scalar) -> Option<FloatKind> {
match scalar {
Scalar::Float => Some(FloatKind::Float),
Scalar::Double => Some(FloatKind::Double),
Scalar::LongDouble => Some(FloatKind::LongDouble),
_ => None,
}
}
fn spell_scalar(scalar: Scalar) -> &'static str {
match scalar {
Scalar::Void => "void",
Scalar::Bool => "bool",
Scalar::Char => "char",
Scalar::SignedChar => "signed char",
Scalar::UnsignedChar => "unsigned char",
Scalar::Short => "short",
Scalar::UnsignedShort => "unsigned short",
Scalar::Int => "int",
Scalar::UnsignedInt => "unsigned int",
Scalar::Long => "long",
Scalar::UnsignedLong => "unsigned long",
Scalar::LongLong => "long long",
Scalar::UnsignedLongLong => "unsigned long long",
Scalar::Int128 => "__int128",
Scalar::UnsignedInt128 => "unsigned __int128",
Scalar::Float => "float",
Scalar::Double => "double",
Scalar::LongDouble => "long double",
Scalar::Float16 => "_Float16",
Scalar::Float32 => "_Float32",
Scalar::Float64 => "_Float64",
Scalar::Float128 => "_Float128",
Scalar::Float32x => "_Float32x",
Scalar::Float64x => "_Float64x",
Scalar::Float128x => "_Float128x",
Scalar::Float80 => "__float80",
Scalar::Decimal32 => "_Decimal32",
Scalar::Decimal64 => "_Decimal64",
Scalar::Decimal128 => "_Decimal128",
}
}
#[cfg(test)]
mod tests {
use rucc_ast::{Builtin, BuiltinSet, DeclSpecs, DeclSpecsId, Declarator, DeclaratorId, Quals};
use rucc_base::Interner;
use rucc_lex::{IntConstant, IntConstantType, Remarks};
use rucc_target::{TargetInfo, Triple};
use rucc_types::{TypeKind, spell};
use super::*;
use crate::check::Context;
pub(super) struct Fixture {
pub(super) ast: rucc_ast::Ast,
names: Interner,
target: TargetInfo,
}
impl Fixture {
pub(super) fn new() -> Fixture {
let target =
TargetInfo::new("x86_64-unknown-linux-gnu".parse::<Triple>().expect("a triple"));
Fixture { ast: rucc_ast::Ast::new(), names: Interner::new(), target }
}
pub(super) fn name(&mut self, text: &str) -> Symbol {
self.names.intern(text)
}
pub(super) fn keywords(&mut self, written: &[BuiltinSet]) -> DeclSpecsId {
let mut builtin = Builtin::NONE;
for &keyword in written {
builtin = builtin.add(keyword).expect("a keyword written once");
}
self.specs(TypeSpec::Builtin(builtin), Quals::NONE)
}
pub(super) fn int_specs(&mut self) -> DeclSpecsId {
self.keywords(&[BuiltinSet::INT])
}
pub(super) fn specs(&mut self, ty: TypeSpec, quals: Quals) -> DeclSpecsId {
let mut specs = DeclSpecs::empty(Span::DUMMY);
specs.ty = ty;
specs.quals = quals;
self.ast.add_specs(specs)
}
pub(super) fn declarator(
&mut self,
name: Option<&str>,
derived: &[Derived],
) -> DeclaratorId {
let name = name.map(|text| self.name(text));
let derived = self.ast.add_derived_list(derived);
self.ast.add_declarator(Declarator {
name,
name_span: Span::DUMMY,
derived,
span: Span::DUMMY,
})
}
pub(super) fn type_name(
&mut self,
specs: DeclSpecsId,
derived: &[Derived],
) -> ast::TypeNameId {
let declarator = self.declarator(None, derived);
self.ast.add_type_name(ast::TypeName { specs, declarator, span: Span::DUMMY })
}
pub(super) fn int(&mut self, value: u128) -> ast::ExprId {
let ty = IntConstantType::Standard(IntKind::Int);
let id = self.ast.add_int(IntConstant { value, ty, remarks: Remarks::default() });
self.ast.expr(ast::Expr::Int(id), Span::DUMMY)
}
fn use_name(&mut self, text: &str) -> ast::ExprId {
let name = self.name(text);
self.ast.expr(ast::Expr::Name(name), Span::DUMMY)
}
pub(super) fn checker(&self) -> Checker<'_> {
Checker::new(&self.ast, Context::new(&self.names, &self.target, Std::C23))
}
}
fn fixed(fixture: &mut Fixture, count: u128) -> Derived {
let size = fixture.int(count);
Derived::Array { size: ArraySize::Expr(size), quals: Quals::NONE, has_static: false }
}
fn pointer() -> Derived {
Derived::Pointer { quals: Quals::NONE, attrs: rucc_ast::AttrList::EMPTY }
}
pub(super) fn spelled(checker: &Checker<'_>, ty: TypeId) -> String {
spell(&checker.types, checker.cx.names, ty)
}
fn built(checker: &mut Checker<'_>, specs: DeclSpecsId, declarator: DeclaratorId) -> String {
let ty = checker.declared_type(specs, declarator);
spelled(checker, ty)
}
pub(super) fn messages(checker: &Checker<'_>) -> Vec<String> {
checker.errors.diagnostics().iter().map(|d| d.message.clone()).collect()
}
pub(super) fn message(checker: &Checker<'_>) -> String {
let mut reported = messages(checker);
assert_eq!(reported.len(), 1, "expected exactly one diagnostic, got {reported:?}");
reported.pop().expect("one message")
}
#[test]
fn the_keywords_of_a_specifier_list_name_one_type_between_them() {
let mut fixture = Fixture::new();
let long = fixture.keywords(&[BuiltinSet::UNSIGNED, BuiltinSet::LONG, BuiltinSet::INT]);
let double = fixture.keywords(&[BuiltinSet::LONG, BuiltinSet::DOUBLE]);
let void = fixture.keywords(&[BuiltinSet::VOID]);
let plain = fixture.declarator(Some("x"), &[]);
let mut checker = fixture.checker();
assert_eq!(built(&mut checker, long, plain), "unsigned long");
assert_eq!(built(&mut checker, double, plain), "long double");
assert_eq!(built(&mut checker, void, plain), "void");
assert!(messages(&checker).is_empty());
}
#[test]
fn keywords_that_name_no_type_between_them_are_one_message_and_not_one_per_keyword() {
let mut fixture = Fixture::new();
let specs = fixture.keywords(&[BuiltinSet::SHORT, BuiltinSet::DOUBLE]);
let plain = fixture.declarator(Some("x"), &[]);
let mut checker = fixture.checker();
let ty = checker.declared_type(specs, plain);
assert_eq!(spelled(&checker, ty), "int");
assert_eq!(message(&checker), "two or more data types in declaration specifiers");
}
#[test]
fn a_declaration_with_no_type_at_all_is_an_int_and_a_warning_that_says_whose() {
let mut fixture = Fixture::new();
let specs = fixture.specs(TypeSpec::None, Quals::CONST);
let again = fixture.specs(TypeSpec::None, Quals::NONE);
let named = fixture.declarator(Some("x"), &[]);
let abstracted = fixture.declarator(None, &[]);
let mut checker = fixture.checker();
let ty = checker.declared_type(specs, named);
assert_eq!(spelled(&checker, ty), "const int");
checker.declared_type(again, abstracted);
assert_eq!(
messages(&checker),
["type defaults to 'int' in declaration of 'x'", "type defaults to 'int'"]
);
}
#[test]
fn a_declarator_is_folded_from_the_far_end_so_the_step_nearest_the_name_wins() {
let mut fixture = Fixture::new();
let specs = fixture.int_specs();
let char_specs = fixture.keywords(&[BuiltinSet::CHAR]);
let parameter = fixture.declarator(None, &[]);
let params = fixture.ast.add_param_list(&[ast::Param {
specs: Some(char_specs),
declarator: parameter,
attrs: rucc_ast::AttrList::EMPTY,
span: Span::DUMMY,
}]);
let three = fixed(&mut fixture, 3);
let call = Derived::Function { params, variadic: false, kind: ParamKind::Prototype };
let f = fixture.declarator(Some("f"), &[three, pointer(), call]);
let mut checker = fixture.checker();
let ty = checker.declared_type(specs, f);
assert_eq!(spelled(&checker, ty), "int (*[3])(char)");
assert!(messages(&checker).is_empty());
}
#[test]
fn the_qualifiers_of_a_pointer_are_the_pointers_and_not_the_pointees() {
let mut fixture = Fixture::new();
let konst = fixture.specs(
TypeSpec::Builtin(Builtin::NONE.add(BuiltinSet::INT).expect("int")),
Quals::CONST,
);
let plain = fixture.int_specs();
let to_const = fixture.declarator(Some("p"), &[pointer()]);
let const_pointer = fixture.declarator(
Some("p"),
&[Derived::Pointer { quals: Quals::CONST, attrs: rucc_ast::AttrList::EMPTY }],
);
let mut checker = fixture.checker();
assert_eq!(built(&mut checker, konst, to_const), "const int *");
assert_eq!(built(&mut checker, plain, const_pointer), "int *const");
assert!(messages(&checker).is_empty());
}
#[test]
fn restrict_is_only_for_a_pointer_and_says_so_where_it_is_not() {
let mut fixture = Fixture::new();
let specs = fixture.specs(TypeSpec::None, Quals::RESTRICT);
let plain = fixture.declarator(Some("x"), &[]);
let restricted =
Derived::Pointer { quals: Quals::RESTRICT, attrs: rucc_ast::AttrList::EMPTY };
let int_specs = fixture.int_specs();
let p = fixture.declarator(Some("p"), &[restricted]);
let mut checker = fixture.checker();
assert_eq!(built(&mut checker, int_specs, p), "int *restrict");
checker.declared_type(specs, plain);
assert!(
messages(&checker).contains(&"invalid use of 'restrict'".to_string()),
"got {:?}",
messages(&checker)
);
}
#[test]
fn an_array_of_something_there_can_be_no_array_of_says_which_it_was() {
let mut fixture = Fixture::new();
let void = fixture.keywords(&[BuiltinSet::VOID]);
let int = fixture.int_specs();
let three = fixed(&mut fixture, 3);
let params = fixture.ast.add_param_list(&[]);
let call = Derived::Function { params, variadic: false, kind: ParamKind::Void };
let voids = fixture.declarator(Some("a"), &[three]);
let functions = fixture.declarator(Some("a"), &[three, call]);
let anonymous = fixture.declarator(None, &[three]);
let mut checker = fixture.checker();
checker.declared_type(void, voids);
checker.declared_type(int, functions);
checker.declared_type(void, anonymous);
assert_eq!(
messages(&checker),
[
"declaration of 'a' as array of voids",
"declaration of 'a' as array of functions",
"declaration of type name as array of voids",
]
);
}
#[test]
fn an_array_of_a_tag_that_has_no_definition_yet_names_the_type_it_cannot_size() {
let mut fixture = Fixture::new();
let tag = fixture.name("S");
let specs = fixture.specs(
TypeSpec::Record {
kind: ast::RecordKind::Struct,
tag: Some(tag),
fields: None,
attrs: rucc_ast::AttrList::EMPTY,
},
Quals::NONE,
);
let three = fixed(&mut fixture, 3);
let array = fixture.declarator(Some("a"), &[three]);
let star = fixture.declarator(Some("p"), &[pointer()]);
let mut checker = fixture.checker();
let pointer_ty = checker.declared_type(specs, star);
assert_eq!(spelled(&checker, pointer_ty), "struct S *");
checker.declared_type(specs, array);
assert_eq!(message(&checker), "array type has incomplete element type 'struct S'");
}
#[test]
fn an_array_bound_is_folded_and_a_negative_one_is_refused() {
let mut fixture = Fixture::new();
let specs = fixture.int_specs();
let zero = fixed(&mut fixture, 0);
let four = fixed(&mut fixture, 4);
let negative = {
let one = fixture.int(1);
let size = fixture
.ast
.expr(ast::Expr::Unary { op: rucc_ast::UnaryOp::Minus, operand: one }, Span::DUMMY);
Derived::Array { size: ArraySize::Expr(size), quals: Quals::NONE, has_static: false }
};
let sized = fixture.declarator(Some("a"), &[four]);
let empty = fixture.declarator(Some("a"), &[zero]);
let unspecified = fixture.declarator(
Some("a"),
&[Derived::Array {
size: ArraySize::Unspecified,
quals: Quals::NONE,
has_static: false,
}],
);
let backwards = fixture.declarator(Some("a"), &[negative]);
let mut checker = fixture.checker();
assert_eq!(built(&mut checker, specs, sized), "int [4]");
assert_eq!(built(&mut checker, specs, empty), "int [0]");
assert_eq!(built(&mut checker, specs, unspecified), "int []");
assert!(messages(&checker).is_empty());
checker.declared_type(specs, backwards);
assert_eq!(message(&checker), "size of array 'a' is negative");
}
#[test]
fn an_array_too_large_to_be_an_object_is_measured_in_its_elements() {
let mut fixture = Fixture::new();
let specs = fixture.int_specs();
let count = u128::from(MAX_OBJECT_SIZE / 4 + 1);
let huge = fixed(&mut fixture, count);
let a = fixture.declarator(Some("a"), &[huge]);
let mut checker = fixture.checker();
checker.declared_type(specs, a);
assert_eq!(
message(&checker),
"size of array 'a' exceeds maximum object size '9223372036854775807'"
);
}
#[test]
fn a_bound_that_is_not_a_constant_is_a_variable_length_array_where_there_is_a_run_time() {
let mut fixture = Fixture::new();
let specs = fixture.int_specs();
let n = fixture.use_name("n");
let variable =
Derived::Array { size: ArraySize::Expr(n), quals: Quals::NONE, has_static: false };
let a = fixture.declarator(Some("a"), &[variable]);
let name = fixture.name("n");
let mut checker = fixture.checker();
let int = checker.int();
checker.declare_object(name, int, Span::DUMMY);
checker.declared_type(specs, a);
assert_eq!(message(&checker), "variably modified 'a' at file scope");
checker.scopes.push();
let ty = checker.declared_type(specs, a);
assert_eq!(spelled(&checker, ty), "int [*]");
let again = checker.declared_type(specs, a);
assert_ne!(ty, again);
assert_eq!(
checker.tast.vla_size(vla_id(&checker, ty)),
checker.tast.vla_size(vla_id(&checker, ty))
);
}
fn vla_id(checker: &Checker<'_>, ty: TypeId) -> rucc_types::VlaId {
match checker.types.kind(checker.types.canonical(ty)) {
TypeKind::Array { len: ArrayLen::Variable(id), .. } => id,
other => panic!("expected a variable length array, got {other:?}"),
}
}
#[test]
fn a_star_bound_is_only_a_type_inside_a_prototype() {
let mut fixture = Fixture::new();
let specs = fixture.int_specs();
let star = Derived::Array { size: ArraySize::Star, quals: Quals::NONE, has_static: false };
let parameter = fixture.declarator(Some("a"), &[star]);
let params = fixture.ast.add_param_list(&[ast::Param {
specs: Some(specs),
declarator: parameter,
attrs: rucc_ast::AttrList::EMPTY,
span: Span::DUMMY,
}]);
let call = Derived::Function { params, variadic: false, kind: ParamKind::Prototype };
let f = fixture.declarator(Some("f"), &[call]);
let mut checker = fixture.checker();
let ty = checker.declared_type(specs, f);
assert_eq!(spelled(&checker, ty), "int (int *)");
assert!(messages(&checker).is_empty());
checker.declared_type(specs, parameter);
assert_eq!(message(&checker), "'[*]' not allowed in other than function prototype scope");
}
#[test]
fn the_qualifiers_inside_a_parameters_brackets_end_up_on_the_pointer_it_becomes() {
let mut fixture = Fixture::new();
let specs = fixture.int_specs();
let three = fixture.int(3);
let qualified =
Derived::Array { size: ArraySize::Expr(three), quals: Quals::CONST, has_static: true };
let parameter = fixture.declarator(Some("a"), &[qualified]);
let params = fixture.ast.add_param_list(&[ast::Param {
specs: Some(specs),
declarator: parameter,
attrs: rucc_ast::AttrList::EMPTY,
span: Span::DUMMY,
}]);
let call = Derived::Function { params, variadic: false, kind: ParamKind::Prototype };
let f = fixture.declarator(Some("f"), &[call]);
let mut checker = fixture.checker();
let ty = checker.declared_type(specs, f);
assert_eq!(spelled(&checker, ty), "int (int *const)");
assert!(messages(&checker).is_empty());
checker.declared_type(specs, parameter);
assert_eq!(
message(&checker),
"static or type qualifiers in non-parameter array declarator"
);
}
#[test]
fn a_function_cannot_return_a_function_or_an_array_and_the_message_names_which() {
let mut fixture = Fixture::new();
let specs = fixture.int_specs();
let params = fixture.ast.add_param_list(&[]);
let call = Derived::Function { params, variadic: false, kind: ParamKind::Void };
let three = fixed(&mut fixture, 3);
let returns_function = fixture.declarator(Some("f"), &[call, call]);
let returns_array = fixture.declarator(Some("f"), &[call, three]);
let anonymous = fixture.declarator(None, &[call, three]);
let mut checker = fixture.checker();
checker.declared_type(specs, returns_function);
checker.declared_type(specs, returns_array);
checker.declared_type(specs, anonymous);
assert_eq!(
messages(&checker),
[
"'f' declared as function returning a function",
"'f' declared as function returning an array",
"type name declared as function returning an array",
]
);
}
#[test]
fn an_empty_parameter_list_says_nothing_before_c23_and_says_none_from_it() {
let mut fixture = Fixture::new();
let specs = fixture.int_specs();
let params = fixture.ast.add_param_list(&[]);
let empty = Derived::Function { params, variadic: false, kind: ParamKind::Empty };
let f = fixture.declarator(Some("f"), &[empty]);
let mut checker = fixture.checker();
assert_eq!(built(&mut checker, specs, f), "int (void)");
let mut old = fixture.checker();
old.cx.std = Std::C17;
assert_eq!(built(&mut old, specs, f), "int ()");
assert!(messages(&old).is_empty());
}
#[test]
fn a_parameter_of_type_void_is_only_a_parameter_list_when_it_is_the_whole_of_one() {
let mut fixture = Fixture::new();
let int = fixture.int_specs();
let void = fixture.keywords(&[BuiltinSet::VOID]);
let named = fixture.declarator(Some("v"), &[]);
let unnamed = fixture.declarator(None, &[]);
let param = |declarator| ast::Param {
specs: Some(void),
declarator,
attrs: rucc_ast::AttrList::EMPTY,
span: Span::DUMMY,
};
let params = fixture.ast.add_param_list(&[param(named), param(unnamed)]);
let call = Derived::Function { params, variadic: false, kind: ParamKind::Prototype };
let f = fixture.declarator(Some("f"), &[call]);
let mut checker = fixture.checker();
checker.declared_type(int, f);
assert_eq!(
messages(&checker),
["parameter 1 ('v') has void type", "'void' must be the only parameter"]
);
}
#[test]
fn a_parameter_is_in_scope_for_the_parameters_after_it_and_gone_after_the_prototype() {
let mut fixture = Fixture::new();
let specs = fixture.int_specs();
let n = fixture.declarator(Some("n"), &[]);
let bound = fixture.use_name("n");
let a = fixture.declarator(
Some("a"),
&[Derived::Array {
size: ArraySize::Expr(bound),
quals: Quals::NONE,
has_static: false,
}],
);
let param = |declarator| ast::Param {
specs: Some(specs),
declarator,
attrs: rucc_ast::AttrList::EMPTY,
span: Span::DUMMY,
};
let params = fixture.ast.add_param_list(&[param(n), param(a)]);
let call = Derived::Function { params, variadic: false, kind: ParamKind::Prototype };
let f = fixture.declarator(Some("f"), &[call]);
let name = fixture.name("n");
let mut checker = fixture.checker();
let ty = checker.declared_type(specs, f);
assert_eq!(spelled(&checker, ty), "int (int, int *)");
assert!(messages(&checker).is_empty());
assert!(checker.scopes.lookup(name).is_none());
}
#[test]
fn a_parameter_declared_twice_in_one_prototype_is_reported_once() {
let mut fixture = Fixture::new();
let specs = fixture.int_specs();
let a = fixture.declarator(Some("a"), &[]);
let param = ast::Param {
specs: Some(specs),
declarator: a,
attrs: rucc_ast::AttrList::EMPTY,
span: Span::DUMMY,
};
let params = fixture.ast.add_param_list(&[param, param]);
let call = Derived::Function { params, variadic: false, kind: ParamKind::Prototype };
let f = fixture.declarator(Some("f"), &[call]);
let mut checker = fixture.checker();
checker.declared_type(specs, f);
assert_eq!(message(&checker), "redefinition of parameter 'a'");
}
#[test]
fn a_tag_names_the_same_type_every_time_and_one_kind_of_thing_only() {
let mut fixture = Fixture::new();
let tag = fixture.name("S");
let record = |kind| TypeSpec::Record {
kind,
tag: Some(tag),
fields: None,
attrs: rucc_ast::AttrList::EMPTY,
};
let structure = fixture.specs(record(ast::RecordKind::Struct), Quals::NONE);
let onion = fixture.specs(record(ast::RecordKind::Union), Quals::NONE);
let plain = fixture.declarator(None, &[]);
let mut checker = fixture.checker();
let first = checker.declared_type(structure, plain);
let second = checker.declared_type(structure, plain);
assert_eq!(first, second);
assert!(messages(&checker).is_empty());
let wrong = checker.declared_type(onion, plain);
assert_eq!(message(&checker), "'S' defined as wrong kind of tag");
assert_ne!(wrong, first);
assert_eq!(checker.declared_type(structure, plain), first);
}
#[test]
fn an_anonymous_tag_is_a_new_type_every_time_it_is_written() {
let mut fixture = Fixture::new();
let anonymous = |fixture: &mut Fixture| {
fixture.specs(
TypeSpec::Record {
kind: ast::RecordKind::Struct,
tag: None,
fields: None,
attrs: rucc_ast::AttrList::EMPTY,
},
Quals::NONE,
)
};
let specs = anonymous(&mut fixture);
let written_again = anonymous(&mut fixture);
let plain = fixture.declarator(None, &[]);
let mut checker = fixture.checker();
let first = checker.declared_type(specs, plain);
let second = checker.declared_type(written_again, plain);
assert_ne!(first, second);
assert_eq!(checker.declared_type(specs, plain), first);
}
#[test]
fn an_enumeration_with_the_underlying_type_written_is_complete_from_there() {
let mut fixture = Fixture::new();
let long = fixture.keywords(&[BuiltinSet::LONG]);
let long_name = fixture.type_name(long, &[]);
let tag = fixture.name("E");
let fixed_enum = fixture.specs(
TypeSpec::Enum {
tag: Some(tag),
enumerators: None,
underlying: Some(long_name),
attrs: rucc_ast::AttrList::EMPTY,
},
Quals::NONE,
);
let plain = fixture.declarator(None, &[]);
let mut checker = fixture.checker();
let ty = checker.declared_type(fixed_enum, plain);
assert_eq!(spelled(&checker, ty), "enum E");
assert!(is_complete(&checker.types, ty));
assert!(messages(&checker).is_empty());
}
#[test]
fn an_enumeration_cannot_be_kept_in_something_that_is_not_an_integer_type() {
let mut fixture = Fixture::new();
let double = fixture.keywords(&[BuiltinSet::DOUBLE]);
let double_name = fixture.type_name(double, &[]);
let specs = fixture.specs(
TypeSpec::Enum {
tag: None,
enumerators: None,
underlying: Some(double_name),
attrs: rucc_ast::AttrList::EMPTY,
},
Quals::NONE,
);
let plain = fixture.declarator(None, &[]);
let mut checker = fixture.checker();
checker.declared_type(specs, plain);
assert_eq!(message(&checker), "invalid 'enum' underlying type");
}
#[test]
fn atomic_is_a_type_and_not_a_qualifier_and_two_things_cannot_be_one() {
let mut fixture = Fixture::new();
let int = fixture.int_specs();
let konst = fixture.specs(
TypeSpec::Builtin(Builtin::NONE.add(BuiltinSet::INT).expect("int")),
Quals::CONST,
);
let plain_name = fixture.type_name(int, &[]);
let three = fixed(&mut fixture, 3);
let array_name = fixture.type_name(int, &[three]);
let params = fixture.ast.add_param_list(&[]);
let call = Derived::Function { params, variadic: false, kind: ParamKind::Void };
let function_name = fixture.type_name(int, &[call]);
let const_name = fixture.type_name(konst, &[]);
let atomic = |fixture: &mut Fixture, name| {
let specs = fixture.specs(TypeSpec::Atomic(name), Quals::NONE);
let declarator = fixture.declarator(None, &[]);
(specs, declarator)
};
let (plain, hole) = atomic(&mut fixture, plain_name);
let (array, _) = atomic(&mut fixture, array_name);
let (function, _) = atomic(&mut fixture, function_name);
let (qualified, _) = atomic(&mut fixture, const_name);
let mut checker = fixture.checker();
assert_eq!(built(&mut checker, plain, hole), "_Atomic(int)");
assert!(messages(&checker).is_empty());
checker.declared_type(array, hole);
checker.declared_type(function, hole);
checker.declared_type(qualified, hole);
assert_eq!(
messages(&checker),
[
"'_Atomic'-qualified array type",
"'_Atomic'-qualified function type",
"'_Atomic' applied to a qualified type",
]
);
}
#[test]
fn a_bit_int_is_as_wide_as_it_says_within_the_range_there_is() {
let mut fixture = Fixture::new();
let widths = [37, 1, 200];
let specs: Vec<_> = widths
.iter()
.map(|&width| {
let expr = fixture.int(width);
fixture.specs(TypeSpec::BitInt(expr), Quals::NONE)
})
.collect();
let plain = fixture.declarator(None, &[]);
let mut checker = fixture.checker();
assert_eq!(built(&mut checker, specs[0], plain), "_BitInt(37)");
assert!(messages(&checker).is_empty());
checker.declared_type(specs[1], plain);
checker.declared_type(specs[2], plain);
assert_eq!(
messages(&checker),
[
"signed _BitInt must have a bit size of at least 2",
"signed _BitInt of bit sizes greater than 128 not supported",
]
);
}
#[test]
fn a_typedef_name_is_the_type_it_was_declared_for_and_keeps_its_own_spelling() {
let mut fixture = Fixture::new();
let word = fixture.name("word");
let specs = fixture.specs(TypeSpec::Typedef(word), Quals::CONST);
let p = fixture.declarator(Some("p"), &[pointer()]);
let mut checker = fixture.checker();
let long = checker.types.int(IntKind::Long);
let alias = checker.types.typedef(word, long);
checker.declare_typedef(word, alias);
let ty = checker.declared_type(specs, p);
assert_eq!(spelled(&checker, ty), "const word *");
assert!(messages(&checker).is_empty());
}
#[test]
fn typeof_takes_the_type_of_an_expression_it_does_not_evaluate() {
let mut fixture = Fixture::new();
let x = fixture.use_name("x");
let plain = fixture
.specs(TypeSpec::Typeof { unqual: false, operand: TypeofArg::Expr(x) }, Quals::NONE);
let bare = fixture
.specs(TypeSpec::Typeof { unqual: true, operand: TypeofArg::Expr(x) }, Quals::NONE);
let hole = fixture.declarator(None, &[]);
let name = fixture.name("x");
let mut checker = fixture.checker();
let int = checker.int();
let konst = checker.types.qualified(int, Qualifiers::CONST);
checker.declare_object(name, konst, Span::DUMMY);
assert_eq!(built(&mut checker, plain, hole), "const int");
assert_eq!(built(&mut checker, bare, hole), "int");
assert!(messages(&checker).is_empty());
}
}