use rucc_ast as ast;
use rucc_base::Symbol;
use rucc_diag::{Diagnostic, Span};
use rucc_types::{FloatKind, TypeId, is_real_floating};
use crate::check::Checker;
use crate::expr::{Category, Expr, ExprId, ExprKind, Sign};
#[derive(Debug, Clone, Copy)]
struct Row {
name: &'static str,
op: Sign,
at: FloatKind,
}
const FAMILY: &[Row] = &[
Row { name: "__builtin_fabs", op: Sign::Clear, at: FloatKind::Double },
Row { name: "__builtin_fabsf", op: Sign::Clear, at: FloatKind::Float },
Row { name: "__builtin_fabsl", op: Sign::Clear, at: FloatKind::LongDouble },
Row { name: "__builtin_copysign", op: Sign::Of, at: FloatKind::Double },
Row { name: "__builtin_copysignf", op: Sign::Of, at: FloatKind::Float },
Row { name: "__builtin_copysignl", op: Sign::Of, at: FloatKind::LongDouble },
];
#[cfg(test)]
pub(super) fn is_family(name: &str) -> bool {
FAMILY.iter().any(|row| row.name == name)
}
impl Checker<'_> {
pub(super) fn sign_builtin_call(
&mut self,
name: Symbol,
args: ast::ExprList,
span: Span,
) -> Option<ExprId> {
let spelled = self.text(name);
let row = *FAMILY.iter().find(|row| row.name == spelled)?;
Some(self.sign_call(row, args, span))
}
fn sign_call(&mut self, row: Row, args: ast::ExprList, span: Span) -> ExprId {
let spelled = row.name;
let written: Vec<ast::ExprId> = self.ast[args].to_vec();
let args: Vec<ExprId> = written
.into_iter()
.map(|arg| {
let arg = self.expr(arg);
self.value(arg)
})
.collect();
let wanted = if row.op.is_pair() { 2 } else { 1 };
if args.len() != wanted {
let how = if args.len() < wanted { "few" } else { "many" };
self.report(
Diagnostic::error(format!("too {how} arguments to function '{spelled}'"), span)
.with_code("E0511"),
);
return self.poison(span);
}
if args.iter().any(|&arg| self.is_poisoned(arg)) {
return self.poison(span);
}
let ty = self.types.float(row.at);
let converted: Vec<ExprId> =
args.into_iter().map(|arg| self.convert_operand(arg, ty)).collect();
if !converted.iter().all(|&arg| is_real_floating(&self.types, self.tast[arg].ty)) {
let s = if wanted == 2 { "s" } else { "" };
self.report(
Diagnostic::error(
format!("non-floating-point argument{s} in call to function '{spelled}'"),
span,
)
.with_code("E0685"),
);
return self.poison(span);
}
let rhs = converted.get(1).copied();
self.tast.expr(
Expr::new(ExprKind::Sign { op: row.op, lhs: converted[0], rhs }, ty, Category::Rvalue),
span,
)
}
fn convert_operand(&mut self, arg: ExprId, ty: TypeId) -> ExprId {
if rucc_types::is_arithmetic(&self.types, self.tast[arg].ty) {
return self.conv().to_type(arg, ty);
}
arg
}
}
#[cfg(test)]
mod tests {
use rucc_gnu::{Kind, Status};
use super::*;
#[test]
fn every_name_in_the_family_is_a_row_of_the_table_and_is_answered_not_called() {
for row in FAMILY {
let Some(feature) = rucc_gnu::lookup(Kind::Builtin, row.name) else {
panic!("{} is answered here and is not in features.toml", row.name);
};
assert_eq!(feature.status, Status::Implemented, "{}", row.name);
assert!(feature.signature.is_empty(), "{} is answered and not called", row.name);
}
}
#[test]
fn no_name_is_in_the_table_twice() {
let mut names: Vec<&str> = FAMILY.iter().map(|row| row.name).collect();
names.sort_unstable();
let all = names.len();
names.dedup();
assert_eq!(names.len(), all, "a name is in the table twice");
}
}