use std::collections::{HashMap, HashSet};
use std::mem;
use rucc_ast::{self as ast, AsmQuals, ForInit, StorageClass};
use rucc_base::Symbol;
use rucc_diag::{Diagnostic, Span};
use rucc_lex::{Encoding, Remarks, StringLiteral};
use rucc_session::Std;
use rucc_types::{IntegerInfo, Qualifiers, TypeId, is_integer, is_pointer, is_record, is_void};
use crate::asm::{Asm, AsmOperand, AsmOperandList, LabelList};
use crate::check::Checker;
use crate::check::expr::Target;
use crate::decl::{DeclId, DeclList};
use crate::eval;
use crate::expr::{Category, Expr, ExprId, ExprKind};
use crate::stmt::{Case, Stmt, StmtId};
use crate::tast::{Const, Label, LabelId, StrId};
pub(in crate::check) const FUNCTION_NAMES: [&str; 3] =
["__func__", "__FUNCTION__", "__PRETTY_FUNCTION__"];
#[derive(Debug)]
pub(in crate::check) struct Body {
ret: TypeId,
at: Span,
variadic: bool,
last_param: Option<DeclId>,
params: DeclList,
name: Option<Symbol>,
func_name: [Option<StrId>; FUNCTION_NAMES.len()],
labels: HashMap<Symbol, Labelled>,
shadowed: Vec<(Symbol, Option<Labelled>)>,
blocks: Vec<usize>,
switches: Vec<Switch>,
loops: usize,
undeclared: HashSet<Symbol>,
modified: Vec<Modified>,
inside: Option<usize>,
landings: HashMap<LabelId, Landing>,
jumps: Vec<Jump>,
}
#[derive(Debug, Clone, Copy)]
struct Modified {
name: Option<Symbol>,
at: Span,
outer: Option<usize>,
}
#[derive(Debug, Clone, Copy)]
struct Landing {
at: Span,
inside: Option<usize>,
}
#[derive(Debug, Clone, Copy)]
struct Jump {
to: LabelId,
at: Span,
inside: Option<usize>,
}
#[derive(Debug, Clone, Copy)]
pub(in crate::check) struct Enclosing {
pub ret: TypeId,
pub at: Span,
pub variadic: bool,
pub last_param: Option<DeclId>,
pub params: DeclList,
pub name: Option<Symbol>,
}
impl Enclosing {
pub(in crate::check) fn returning(ret: TypeId) -> Enclosing {
Enclosing {
ret,
at: Span::DUMMY,
variadic: false,
last_param: None,
params: DeclList::EMPTY,
name: None,
}
}
}
#[derive(Debug, Clone, Copy)]
struct Labelled {
id: LabelId,
defined: Option<Span>,
at: Span,
}
#[derive(Debug)]
struct Switch {
ty: TypeId,
range: Option<IntegerInfo>,
cases: Vec<Case>,
spans: Vec<Span>,
labels: Vec<StmtId>,
default: Option<(StmtId, Span)>,
}
impl Checker<'_> {
pub fn check_stmt(&mut self, ret: TypeId, id: ast::StmtId) -> StmtId {
let previous = self.open_body(Enclosing::returning(ret));
let stmt = self.stmt(id);
self.close_body(previous);
stmt
}
pub(in crate::check) fn stmt(&mut self, id: ast::StmtId) -> StmtId {
let span = self.ast.stmt_span(id);
let node = match self.ast[id] {
ast::Stmt::Error => Stmt::Error,
ast::Stmt::Empty => Stmt::Empty,
ast::Stmt::Expr(value) => {
let value = self.expr(value);
Stmt::Expr(self.value(value))
}
ast::Stmt::Decl(decl) => {
let decls = self.check_decl(decl);
self.variably_modified(decls);
Stmt::Decls(decls)
}
ast::Stmt::Compound(body) => Stmt::Block(self.block(body)),
ast::Stmt::If { cond, then, otherwise } => {
let cond = self.controlling(cond);
let then = self.stmt(then);
Stmt::If { cond, then, otherwise: otherwise.map(|id| self.stmt(id)) }
}
ast::Stmt::Switch { scrutinee, body } => self.switch(scrutinee, body),
ast::Stmt::While { cond, body } => {
let cond = self.controlling(cond);
Stmt::While { cond, body: self.loop_body(body) }
}
ast::Stmt::DoWhile { body, cond } => {
let body = self.loop_body(body);
Stmt::DoWhile { body, cond: self.controlling(cond) }
}
ast::Stmt::For { init, cond, step, body } => self.for_loop(init, cond, step, body),
ast::Stmt::Goto(name) => Stmt::Goto(self.jump(name, span)),
ast::Stmt::GotoExpr(target) => self.computed_goto(target),
ast::Stmt::Continue => self.continue_stmt(span),
ast::Stmt::Break => self.break_stmt(span),
ast::Stmt::Return(value) => self.return_stmt(value, span),
ast::Stmt::Label { name, body, .. } => self.labelled(name, body, span),
ast::Stmt::Case { lo, hi, body } => self.case(lo, hi, body, span),
ast::Stmt::Default { body } => self.default(body, span),
ast::Stmt::LocalLabels(names) => {
self.local_labels(names, span);
Stmt::Empty
}
ast::Stmt::Asm(asm) => self.asm(asm, span),
};
let stmt = self.tast.stmt(node, span);
if matches!(node, Stmt::Case { .. }) {
if let Some(switch) = self.switches() {
switch.labels.push(stmt);
}
}
stmt
}
pub(in crate::check) fn stmt_expr(&mut self, id: ast::StmtId, span: Span) -> ExprId {
let stmt = self.stmt(id);
let ty = match self.tast[stmt] {
Stmt::Block(body) => match self.tast[body].last() {
Some(&last) => match self.tast[last] {
Stmt::Expr(value) => self.tast[value].ty,
_ => self.types.void(),
},
None => self.types.void(),
},
_ => self.types.void(),
};
self.tast.expr(Expr::new(ExprKind::StmtExpr(stmt), ty, Category::Rvalue), span)
}
pub(in crate::check) fn label_addr(&mut self, name: Symbol, span: Span) -> ExprId {
let label = self.label(name, span);
let ty = self.types.pointer(self.types.void());
self.tast.expr(Expr::new(ExprKind::LabelAddr(label), ty, Category::Rvalue), span)
}
pub(in crate::check) fn open_body(&mut self, func: Enclosing) -> Option<Body> {
let body = Body {
ret: func.ret,
at: func.at,
variadic: func.variadic,
last_param: func.last_param,
params: func.params,
name: func.name,
func_name: [None; FUNCTION_NAMES.len()],
labels: HashMap::new(),
shadowed: Vec::new(),
blocks: Vec::new(),
switches: Vec::new(),
loops: 0,
undeclared: HashSet::new(),
modified: Vec::new(),
inside: None,
landings: HashMap::new(),
jumps: Vec::new(),
};
self.body.replace(body)
}
pub(in crate::check) fn in_variadic_function(&self) -> bool {
self.body.as_ref().is_some_and(|body| body.variadic)
}
pub(in crate::check) fn last_named_parameter(&self) -> Option<DeclId> {
self.body.as_ref().and_then(|body| body.last_param)
}
pub(in crate::check) fn function_name_string(&mut self, which: usize) -> Option<StrId> {
let name = self.body.as_ref()?.name?;
if let Some(id) = self.body.as_ref().and_then(|body| body.func_name[which]) {
return Some(id);
}
let elements = self.text(name).chars().map(|c| c as u32).collect();
let literal =
StringLiteral { elements, encoding: Encoding::Plain, remarks: Remarks::default() };
let id = self.tast.add_string(literal);
if let Some(body) = &mut self.body {
body.func_name[which] = Some(id);
}
Some(id)
}
pub(in crate::check) fn is_parameter(&self, decl: DeclId) -> bool {
self.body.as_ref().is_some_and(|body| self.tast[body.params].contains(&decl))
}
pub(in crate::check) fn first_undeclared_use(&mut self, name: Symbol) -> bool {
match &mut self.body {
Some(body) => body.undeclared.insert(name),
None => true,
}
}
pub(in crate::check) fn close_body(&mut self, previous: Option<Body>) {
let Some(body) = mem::replace(&mut self.body, previous) else {
return;
};
let mut undefined: Vec<Labelled> =
body.labels.into_values().filter(|label| label.defined.is_none()).collect();
undefined.sort_by_key(|label| label.at.lo);
for label in undefined {
self.undefined_label(label);
}
for jump in &body.jumps {
let Some(landing) = body.landings.get(&jump.to) else { continue };
let Some(entered) = landing.inside else { continue };
if open_at(&body.modified, jump.inside, entered) {
continue;
}
self.jumped_into_scope(*jump, *landing, body.modified[entered]);
}
}
fn jumped_into_scope(&mut self, jump: Jump, landing: Landing, entered: Modified) {
let label = self.text(self.tast[jump.to].name).to_owned();
let mut diag =
Diagnostic::error("jump into scope of identifier with variably modified type", jump.at)
.with_code("E0684")
.note(format!("label '{label}' defined here"), landing.at);
if let Some(name) = entered.name {
let spelled = self.text(name).to_owned();
diag = diag.note(format!("'{spelled}' declared here"), entered.at);
}
self.report(diag);
}
pub(in crate::check) fn body_block(&mut self, body: ast::StmtId) -> StmtId {
let span = self.ast.stmt_span(body);
let ast::Stmt::Compound(list) = self.ast[body] else {
return self.stmt(body);
};
let list = self.statements(list);
self.tast.stmt(Stmt::Block(list), span)
}
fn block(&mut self, body: ast::StmtList) -> crate::stmt::StmtList {
self.scopes.push();
let outer = self.open_scope();
let list = self.statements(body);
self.close_scope(outer);
self.scopes.pop();
list
}
fn open_scope(&self) -> Option<usize> {
self.body.as_ref().and_then(|state| state.inside)
}
fn close_scope(&mut self, outer: Option<usize>) {
if let Some(state) = self.body.as_mut() {
state.inside = outer;
}
}
fn variably_modified(&mut self, decls: DeclList) {
let ids = self.tast[decls].to_vec();
for decl in ids {
if !self.is_variably_modified(self.tast[decl].ty) {
continue;
}
let name = self.tast[decl].name;
let at = self.tast.decl_span(decl);
if let Some(state) = self.body.as_mut() {
let outer = state.inside;
state.modified.push(Modified { name, at, outer });
state.inside = Some(state.modified.len() - 1);
}
}
}
fn statements(&mut self, body: ast::StmtList) -> crate::stmt::StmtList {
if let Some(state) = self.body.as_mut() {
let mark = state.shadowed.len();
state.blocks.push(mark);
}
let ids = self.ast[body].to_vec();
let mut stmts = Vec::with_capacity(ids.len());
for id in ids {
stmts.push(self.stmt(id));
}
self.end_block();
self.tast.add_stmt_refs(&stmts)
}
fn end_block(&mut self) {
let Some(body) = self.body.as_mut() else {
return;
};
let Some(mark) = body.blocks.pop() else {
return;
};
let mut gone = Vec::new();
while body.shadowed.len() > mark {
let (name, previous) = body.shadowed.pop().expect("a saved binding");
let local = match previous {
Some(previous) => body.labels.insert(name, previous),
None => body.labels.remove(&name),
};
if let Some(local) = local {
if local.defined.is_none() {
gone.push(local);
}
}
}
gone.sort_by_key(|label| label.at.lo);
for label in gone {
self.undefined_label(label);
}
}
fn loop_body(&mut self, body: ast::StmtId) -> StmtId {
if let Some(state) = self.body.as_mut() {
state.loops += 1;
}
let body = self.stmt(body);
if let Some(state) = self.body.as_mut() {
state.loops -= 1;
}
body
}
fn for_loop(
&mut self,
init: ForInit,
cond: Option<ast::ExprId>,
step: Option<ast::ExprId>,
body: ast::StmtId,
) -> Stmt {
self.scopes.push();
let outer = self.open_scope();
let init = match init {
ForInit::None => None,
ForInit::Expr(value) => {
let span = self.ast.expr_span(value);
let value = self.expr(value);
let value = self.value(value);
Some(self.tast.stmt(Stmt::Expr(value), span))
}
ForInit::Decl(decl) => {
let span = self.ast.decl_span(decl);
let decls = self.check_decl(decl);
self.variably_modified(decls);
self.check_loop_declaration(decl);
Some(self.tast.stmt(Stmt::Decls(decls), span))
}
};
let cond = cond.map(|cond| self.controlling(cond));
let step = step.map(|step| {
let step = self.expr(step);
self.value(step)
});
let body = self.loop_body(body);
self.close_scope(outer);
self.scopes.pop();
Stmt::For { init, cond, step, body }
}
fn check_loop_declaration(&mut self, decl: ast::DeclId) {
if !self.cx.pedantic {
return;
}
let ast::Decl::Var { specs, declarators } = self.ast[decl] else {
return;
};
let specs = self.ast[specs];
let word = match specs.storage {
_ if specs.is_typedef() => "non-variable",
Some(StorageClass::Static) => "static variable",
Some(StorageClass::Extern) => "'extern' variable",
_ => return,
};
let ast = self.ast;
for &item in &ast[declarators] {
let node = ast[item.declarator];
let Some(name) = node.name else { continue };
let spelled = self.text(name).to_owned();
self.report(
Diagnostic::warning(
format!("declaration of {word} '{spelled}' in 'for' loop initial declaration"),
node.name_span,
)
.with_code("E0619"),
);
}
}
fn switch(&mut self, scrutinee: ast::ExprId, body: ast::StmtId) -> Stmt {
let at = self.ast.expr_span(scrutinee);
let cond = self.expr(scrutinee);
let cond = self.value(cond);
let range = eval::int_shape(&self.types, self.tast[cond].ty, self.cx.target);
let cond = self.conv().promote(cond);
let ty = self.tast[cond].ty;
let cond = if self.is_poisoned(cond) || is_integer(&self.types, ty) {
cond
} else {
self.report(Diagnostic::error("switch quantity not an integer", at).with_code("E0620"));
self.poison(at)
};
let ty = if is_integer(&self.types, ty) { ty } else { self.int() };
if let Some(state) = self.body.as_mut() {
state.switches.push(Switch {
ty,
range,
cases: Vec::new(),
spans: Vec::new(),
labels: Vec::new(),
default: None,
});
}
let body = self.stmt(body);
let Some(switch) = self.body.as_mut().and_then(|state| state.switches.pop()) else {
return Stmt::Error;
};
let cases = self.tast.add_cases(&switch.cases);
for &labelled in &switch.labels {
let Stmt::Case { case: entry, body } = self.tast[labelled] else {
continue;
};
let case = cases.iter().nth(entry.index()).expect("a case for every label");
self.tast.set_stmt(labelled, Stmt::Case { case, body });
}
Stmt::Switch { cond, body, cases, default: switch.default.map(|(stmt, _)| stmt) }
}
fn case(
&mut self,
lo: ast::ExprId,
hi: Option<ast::ExprId>,
body: Option<ast::StmtId>,
span: Span,
) -> Stmt {
let entry = self.enter_case(lo, hi, span);
let body = self.labelled_body(body, span);
let Some(entry) = entry else {
return Stmt::Error;
};
self.switches().expect("a switch").cases[entry].body = body;
Stmt::Case { case: rucc_base::Idx::from_usize(entry), body }
}
fn enter_case(
&mut self,
lo: ast::ExprId,
hi: Option<ast::ExprId>,
span: Span,
) -> Option<usize> {
if self.body.as_ref().is_none_or(|state| state.switches.is_empty()) {
self.report(
Diagnostic::error("case label not within a switch statement", span)
.with_code("E0621"),
);
return None;
}
let low = self.case_value(lo, span)?;
let high = match hi {
Some(hi) => self.case_value(hi, span)?,
None => low,
};
if high < low {
self.report(Diagnostic::warning("empty range specified", span).with_code("E0622"));
return None;
}
if let Some(at) = self.overlapping_case(low, high) {
self.report(
Diagnostic::error("duplicate case value", span)
.with_code("E0623")
.note("previously used here".to_owned(), at),
);
return None;
}
let switch = self.switches().expect("a switch");
let entry = switch.cases.len();
switch.cases.push(Case { low, high, body: rucc_base::Idx::from_usize(0) });
switch.spans.push(span);
Some(entry)
}
fn case_value(&mut self, value: ast::ExprId, span: Span) -> Option<i128> {
let at = self.ast.expr_span(value);
let value = self.expr(value);
let value = self.value(value);
let folded = match self.eval_integer(value) {
Ok(folded) => folded,
Err(failed) => {
if !failed.poisoned {
self.report(
Diagnostic::error("case label does not reduce to an integer constant", at)
.with_code("E0624"),
);
}
return None;
}
};
let switch = self.switches()?;
let (ty, range) = (switch.ty, switch.range);
if let Some(range) = range {
if eval::overflows(Const::Int(folded), range) {
self.report(
Diagnostic::warning("case label value exceeds maximum value for type", span)
.with_code("E0625"),
);
}
}
let info = eval::int_shape(&self.types, ty, self.cx.target)?;
Some(eval::narrowed(Const::Int(folded), info))
}
fn overlapping_case(&mut self, low: i128, high: i128) -> Option<Span> {
let switch = self.switches()?;
switch
.cases
.iter()
.position(|case| case.low <= high && low <= case.high)
.map(|index| switch.spans[index])
}
fn default(&mut self, body: Option<ast::StmtId>, span: Span) -> Stmt {
let body = self.labelled_body(body, span);
if self.body.as_ref().is_none_or(|state| state.switches.is_empty()) {
self.report(
Diagnostic::error("'default' label not within a switch statement", span)
.with_code("E0626"),
);
return Stmt::Error;
}
if let Some((_, at)) = self.switches().expect("a switch").default {
self.report(
Diagnostic::error("multiple default labels in one switch", span)
.with_code("E0627")
.note("this is the first default label".to_owned(), at),
);
return Stmt::Error;
}
self.switches().expect("a switch").default = Some((body, span));
Stmt::Default { body }
}
fn labelled(&mut self, name: Symbol, body: Option<ast::StmtId>, span: Span) -> Stmt {
let inside = self.open_scope();
let body = self.labelled_body(body, span);
let label = self.label(name, span);
let defined = self.body.as_ref().and_then(|state| state.labels[&name].defined);
if let Some(at) = defined {
let spelled = self.text(name).to_owned();
self.report(
Diagnostic::error(format!("duplicate label '{spelled}'"), span)
.with_code("E0628")
.note(format!("previous definition of '{spelled}' with type 'void'"), at),
);
return Stmt::Error;
}
if let Some(state) = self.body.as_mut() {
state.labels.entry(name).and_modify(|known| known.defined = Some(span));
state.landings.insert(label, Landing { at: span, inside });
}
self.tast.define_label(label, body);
Stmt::Label { label, body }
}
fn labelled_body(&mut self, body: Option<ast::StmtId>, span: Span) -> StmtId {
match body {
Some(body) => self.stmt(body),
None => self.tast.stmt(Stmt::Empty, span),
}
}
fn local_labels(&mut self, names: ast::SymbolList, span: Span) {
let ast = self.ast;
for &name in &ast[names] {
let id = self.tast.add_label(Label { name, stmt: None });
let local = Labelled { id, defined: None, at: span };
if let Some(state) = self.body.as_mut() {
let previous = state.labels.insert(name, local);
state.shadowed.push((name, previous));
}
}
}
fn jump(&mut self, name: Symbol, span: Span) -> LabelId {
let to = self.label(name, span);
if let Some(state) = self.body.as_mut() {
state.jumps.push(Jump { to, at: span, inside: state.inside });
}
to
}
fn label(&mut self, name: Symbol, span: Span) -> LabelId {
if let Some(known) = self.body.as_ref().and_then(|state| state.labels.get(&name)) {
return known.id;
}
let id = self.tast.add_label(Label { name, stmt: None });
if let Some(state) = self.body.as_mut() {
state.labels.insert(name, Labelled { id, defined: None, at: span });
}
id
}
fn undefined_label(&mut self, label: Labelled) {
let name = self.tast[label.id].name;
let spelled = self.text(name).to_owned();
self.report(
Diagnostic::error(format!("label '{spelled}' used but not defined"), label.at)
.with_code("E0629"),
);
}
fn computed_goto(&mut self, target: ast::ExprId) -> Stmt {
let at = self.ast.expr_span(target);
let target = self.expr(target);
let target = self.value(target);
if self.is_poisoned(target) {
return Stmt::Error;
}
let ty = self.tast[target].ty;
if !is_pointer(&self.types, ty) && !is_integer(&self.types, ty) {
self.report(
Diagnostic::error("computed goto must be pointer type", at).with_code("E0630"),
);
return Stmt::Error;
}
let void = self.types.pointer(self.types.void());
let target = self.conv().to_type(target, void);
Stmt::IndirectGoto(target)
}
fn asm(&mut self, id: ast::AsmId, span: Span) -> Stmt {
let node = self.ast[id];
let outputs = self.asm_operands(node.outputs, 0, true);
let first_input = self.ast[node.outputs].len();
let inputs = self.asm_operands(node.inputs, first_input, false);
let mut clobbers = Vec::with_capacity(self.ast[node.clobbers].len());
for index in 0..self.ast[node.clobbers].len() {
let clobber = self.ast[node.clobbers][index];
clobbers.push(self.asm_string(clobber, span));
}
let clobbers = self.tast.add_str_refs(&clobbers);
let mut labels = Vec::with_capacity(self.ast[node.labels].len());
for index in 0..self.ast[node.labels].len() {
let name = self.ast[node.labels][index];
labels.push(self.label(name, span));
}
let labels = self.tast.add_label_refs(&labels);
let template = self.asm_template(node.template, outputs, inputs, labels, span);
let mut quals = node.quals;
if self.ast[node.outputs].is_empty() || quals.has(AsmQuals::GOTO) {
quals = quals.with(AsmQuals::VOLATILE);
}
Stmt::Asm(self.tast.add_asm(Asm { template, outputs, inputs, clobbers, labels, quals }))
}
fn asm_operands(
&mut self,
list: ast::AsmOperandList,
first: usize,
output: bool,
) -> AsmOperandList {
let mut operands = Vec::with_capacity(self.ast[list].len());
for index in 0..self.ast[list].len() {
let operand = self.ast[list][index];
let operand = self.asm_operand(operand, first + index, output);
operands.push(operand);
}
self.tast.add_asm_operands(&operands)
}
fn asm_operand(&mut self, operand: ast::AsmOperand, number: usize, output: bool) -> AsmOperand {
let span = operand.span;
let constraint = self.asm_string(operand.constraint, span);
let text = spelling(&self.tast[constraint]);
let value = self.expr(operand.value);
let ty = self.tast[value].ty;
let lvalue = matches!(self.tast[value].category, Category::Lvalue | Category::Bitfield);
let record = is_record(&self.types, ty);
let memory = memory_only(&text) || record;
if record && !memory_only(&text) {
self.statement_unsupported("a structure or a union in a register constraint", span);
}
if output {
if !text.starts_with(['=', '+']) {
self.report(
Diagnostic::error("output operand constraint lacks '='", span)
.with_code("E0653"),
);
}
if !lvalue {
self.report(
Diagnostic::error("lvalue required in 'asm' statement", span)
.with_code("E0654"),
);
} else if self.types.quals(ty).has(Qualifiers::CONST) {
let what = self.read_only(value);
self.report(
Diagnostic::error(format!("read-only {what} used as 'asm' output"), span)
.with_code("E0655"),
);
}
} else {
if let Some(sign) = text.chars().find(|&ch| ch == '=' || ch == '+') {
self.report(
Diagnostic::error(format!("input operand constraint contains '{sign}'"), span)
.with_code("E0656"),
);
}
if memory && !lvalue {
self.report(
Diagnostic::error(
format!("memory input {number} is not directly addressable"),
span,
)
.with_code("E0657"),
);
}
}
let value = if output || memory { value } else { self.value(value) };
AsmOperand { name: operand.name, constraint, value, memory }
}
fn asm_string(&mut self, id: ast::StrId, span: Span) -> StrId {
let literal = self.ast[id].clone();
self.asm_narrow(&literal, span);
self.tast.add_string(literal)
}
fn asm_narrow(&mut self, literal: &StringLiteral, span: Span) {
if !matches!(literal.encoding, Encoding::Plain) {
self.report(Diagnostic::error("wide string literal in 'asm'", span).with_code("E0658"));
}
}
fn asm_template(
&mut self,
id: ast::StrId,
outputs: AsmOperandList,
inputs: AsmOperandList,
labels: LabelList,
span: Span,
) -> StrId {
let mut names: Vec<(String, usize)> = Vec::new();
let mut number = 0;
for list in [outputs, inputs] {
for index in 0..self.tast[list].len() {
if let Some(name) = self.tast[list][index].name {
names.push((self.text(name).to_owned(), number));
}
number += 1;
}
}
for index in 0..self.tast[labels].len() {
let label = self.tast[labels][index];
let name = self.tast[label].name;
names.push((self.text(name).to_owned(), number));
number += 1;
}
for at in 1..names.len() {
if names[..at].iter().any(|(earlier, _)| *earlier == names[at].0) {
let name = names[at].0.clone();
self.report(
Diagnostic::error(format!("duplicate asm operand name '{name}'"), span)
.with_code("E0659"),
);
}
}
let literal = self.ast[id].clone();
self.asm_narrow(&literal, span);
let text = self.asm_numbers(spelling(&literal), &names, span);
let elements = text.chars().map(|ch| ch as u32).collect();
self.tast.add_string(StringLiteral { elements, ..literal })
}
fn asm_numbers(&mut self, text: String, names: &[(String, usize)], span: Span) -> String {
let chars: Vec<char> = text.chars().collect();
let mut out = String::with_capacity(text.len());
let mut index = 0;
while index < chars.len() {
let ch = chars[index];
out.push(ch);
index += 1;
if ch != '%' {
continue;
}
let letter = chars.get(index).copied();
let open = match letter {
Some('[') => index,
Some(modifier)
if modifier.is_ascii_alphabetic() && chars.get(index + 1) == Some(&'[') =>
{
out.push(modifier);
index += 1;
index
}
Some('%') => {
out.push('%');
index += 1;
continue;
}
_ => continue,
};
let Some(close) = chars[open..].iter().position(|&ch| ch == ']').map(|at| open + at)
else {
continue;
};
let name: String = chars[open + 1..close].iter().collect();
index = close + 1;
match names.iter().find(|(known, _)| *known == name) {
Some(&(_, number)) => out.push_str(&number.to_string()),
None => {
self.report(
Diagnostic::error(format!("undefined named operand '{name}'"), span)
.with_code("E0660"),
);
out.push_str(&chars[open..=close].iter().collect::<String>());
}
}
}
out
}
fn break_stmt(&mut self, span: Span) -> Stmt {
let inside =
self.body.as_ref().is_some_and(|state| state.loops > 0 || !state.switches.is_empty());
if inside {
return Stmt::Break;
}
self.report(
Diagnostic::error("break statement not within loop or switch", span).with_code("E0631"),
);
Stmt::Error
}
fn continue_stmt(&mut self, span: Span) -> Stmt {
if self.body.as_ref().is_some_and(|state| state.loops > 0) {
return Stmt::Continue;
}
self.report(
Diagnostic::error("continue statement not within a loop", span).with_code("E0632"),
);
Stmt::Error
}
fn return_stmt(&mut self, value: Option<ast::ExprId>, span: Span) -> Stmt {
let Some((ret, at)) = self.body.as_ref().map(|state| (state.ret, state.at)) else {
return Stmt::Return(None);
};
let void = is_void(&self.types, ret);
let old = self.cx.std < Std::C99;
let Some(value) = value else {
if !void && !old {
self.report(
Diagnostic::error(
"'return' with no value, in function returning non-void",
span,
)
.with_code("E0633")
.note("declared here".to_owned(), at),
);
}
return Stmt::Return(None);
};
let where_from = self.ast.expr_span(value);
let value = self.expr(value);
let value = self.value(value);
if !void {
return Stmt::Return(Some(self.assign_to(ret, value, where_from, Target::Return)));
}
if !is_void(&self.types, self.tast[value].ty) && !self.is_poisoned(value) {
let said = "'return' with a value, in function returning void";
let diagnostic = if old {
Diagnostic::warning(said, where_from)
} else {
Diagnostic::error(said, where_from)
};
self.report(diagnostic.with_code("E0634").note("declared here".to_owned(), at));
}
let value = self.conv().to_void(value);
Stmt::Return(Some(value))
}
fn controlling(&mut self, cond: ast::ExprId) -> ExprId {
let span = self.ast.expr_span(cond);
let cond = self.expr(cond);
self.condition(cond, span)
}
fn switches(&mut self) -> Option<&mut Switch> {
self.body.as_mut()?.switches.last_mut()
}
fn statement_unsupported(&mut self, what: &str, span: Span) {
self.report(
Diagnostic::error(format!("{what} is not supported yet"), span).with_code("E0519"),
);
}
}
fn open_at(modified: &[Modified], at: Option<usize>, entered: usize) -> bool {
let mut at = at;
while let Some(index) = at {
if index == entered {
return true;
}
at = modified[index].outer;
}
false
}
fn spelling(literal: &StringLiteral) -> String {
literal.elements.iter().filter_map(|&element| char::from_u32(element)).collect()
}
fn memory_only(constraint: &str) -> bool {
let letters: Vec<char> =
constraint.chars().filter(|ch| !"=+&%#*!?, \t".contains(*ch)).collect();
!letters.is_empty() && letters.iter().all(|ch| "moV<>".contains(*ch))
}
#[cfg(test)]
mod tests {
use rucc_ast::{
ArraySize, AttrList, Builtin, BuiltinSet, DeclSpecs, DeclSpecsId, Declarator, DeclaratorId,
Derived, Quals, TypeSpec,
};
use rucc_base::Interner;
use rucc_lex::{IntConstant, IntConstantType, Remarks};
use rucc_session::Std;
use rucc_target::{TargetInfo, Triple};
use rucc_types::IntKind;
use super::*;
use crate::check::Context;
use crate::print::Printer;
struct Fixture {
ast: rucc_ast::Ast,
names: Interner,
target: TargetInfo,
}
impl Fixture {
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 }
}
fn name(&mut self, text: &str) -> Symbol {
self.names.intern(text)
}
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)
}
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");
}
let mut specs = DeclSpecs::empty(Span::DUMMY);
specs.ty = TypeSpec::Builtin(builtin);
self.ast.add_specs(specs)
}
fn int_specs(&mut self) -> DeclSpecsId {
self.keywords(&[BuiltinSet::INT])
}
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,
})
}
fn local(&mut self, specs: DeclSpecsId, name: &str) -> ast::DeclId {
let declarator = self.declarator(Some(name), &[]);
let item = ast::InitDeclarator {
declarator,
init: None,
asm_label: None,
attrs: AttrList::EMPTY,
span: Span::DUMMY,
};
let declarators = self.ast.add_init_declarator_list(&[item]);
self.ast.decl(ast::Decl::Var { specs, declarators }, Span::DUMMY)
}
fn array(&mut self, specs: DeclSpecsId, name: &str, size: ast::ExprId) -> ast::DeclId {
let derived = [Derived::Array {
size: ArraySize::Expr(size),
quals: Quals::NONE,
has_static: false,
}];
let declarator = self.declarator(Some(name), &derived);
let item = ast::InitDeclarator {
declarator,
init: None,
asm_label: None,
attrs: AttrList::EMPTY,
span: Span::DUMMY,
};
let declarators = self.ast.add_init_declarator_list(&[item]);
self.ast.decl(ast::Decl::Var { specs, declarators }, Span::DUMMY)
}
fn cast(&mut self, specs: DeclSpecsId, value: ast::ExprId) -> ast::ExprId {
let declarator = self.declarator(None, &[]);
let ty = self.ast.add_type_name(ast::TypeName { specs, declarator, span: Span::DUMMY });
self.ast.expr(ast::Expr::Cast { ty, operand: value }, Span::DUMMY)
}
fn stmt(&mut self, stmt: ast::Stmt) -> ast::StmtId {
self.ast.stmt(stmt, Span::DUMMY)
}
fn block(&mut self, body: &[ast::StmtId]) -> ast::StmtId {
let body = self.ast.add_stmt_list(body);
self.stmt(ast::Stmt::Compound(body))
}
fn expr_stmt(&mut self, value: ast::ExprId) -> ast::StmtId {
self.stmt(ast::Stmt::Expr(value))
}
fn labelled(&mut self, text: &str, body: Option<ast::StmtId>) -> ast::StmtId {
let name = self.name(text);
self.stmt(ast::Stmt::Label { name, body, attrs: AttrList::EMPTY })
}
fn goto(&mut self, text: &str) -> ast::StmtId {
let name = self.name(text);
self.stmt(ast::Stmt::Goto(name))
}
fn local_labels(&mut self, names: &[&str]) -> ast::StmtId {
let names: Vec<Symbol> = names.iter().map(|text| self.name(text)).collect();
let names = self.ast.add_symbol_list(&names);
self.stmt(ast::Stmt::LocalLabels(names))
}
fn case(&mut self, lo: u128, hi: Option<u128>, body: Option<ast::StmtId>) -> ast::StmtId {
let lo = self.int(lo);
let hi = hi.map(|hi| self.int(hi));
self.stmt(ast::Stmt::Case { lo, hi, body })
}
fn switch(&mut self, scrutinee: ast::ExprId, body: &[ast::StmtId]) -> ast::StmtId {
let body = self.block(body);
self.stmt(ast::Stmt::Switch { scrutinee, body })
}
fn checker(&self) -> Checker<'_> {
Checker::new(&self.ast, Context::new(&self.names, &self.target, Std::C23))
}
}
fn dump(checker: &Checker<'_>, id: StmtId) -> String {
let mut printer = Printer::new(&checker.tast, &checker.types, checker.cx.names);
printer.stmt(id);
printer.finish()
}
fn messages(checker: &Checker<'_>) -> Vec<String> {
checker
.errors
.diagnostics()
.iter()
.flat_map(|d| {
std::iter::once(d.message.clone())
.chain(d.children.iter().map(|n| n.message.clone()))
})
.collect()
}
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")
}
fn reported(checker: &Checker<'_>) -> Vec<String> {
checker
.errors
.diagnostics()
.iter()
.map(|d| format!("{}: {}", d.severity.as_str(), d.message))
.collect()
}
#[test]
fn a_block_is_a_scope_and_a_name_declared_in_one_is_gone_after_it() {
let mut f = Fixture::new();
let specs = f.int_specs();
let declared = f.local(specs, "x");
let declared = f.stmt(ast::Stmt::Decl(declared));
let inner = f.block(&[declared]);
let use_x = f.use_name("x");
let after = f.expr_stmt(use_x);
let outer = f.block(&[inner, after]);
let mut c = f.checker();
let void = c.types.void();
c.check_stmt(void, outer);
assert_eq!(message(&c), "'x' undeclared (first use in this function)");
}
#[test]
fn a_name_nobody_declared_is_reported_once_per_function_and_not_once_per_use() {
let mut f = Fixture::new();
let first = f.use_name("nope");
let first = f.expr_stmt(first);
let second = f.use_name("nope");
let second = f.expr_stmt(second);
let body = f.block(&[first, second]);
let mut c = f.checker();
let void = c.types.void();
let previous = c.open_body(Enclosing::returning(void));
c.check_stmt(void, body);
c.close_body(previous);
assert_eq!(message(&c), "'nope' undeclared (first use in this function)");
}
#[test]
fn an_expression_statement_holds_the_value_and_not_a_conversion_of_it_to_void() {
let mut f = Fixture::new();
let one = f.int(1);
let stmt = f.expr_stmt(one);
let mut c = f.checker();
let void = c.types.void();
let id = c.check_stmt(void, stmt);
assert_eq!(dump(&c, id), "expr\n const 1 : int\n");
assert!(c.errors.is_empty());
}
#[test]
fn a_statement_expression_has_the_type_of_its_last_statement() {
let mut f = Fixture::new();
let one = f.int(1);
let inner = f.expr_stmt(one);
let body = f.block(&[inner]);
let value = f.ast.expr(ast::Expr::StmtExpr(body), Span::DUMMY);
let stmt = f.expr_stmt(value);
let mut c = f.checker();
let void = c.types.void();
let id = c.check_stmt(void, stmt);
assert_eq!(
dump(&c, id),
"expr\n stmt-expr : int\n block\n expr\n const 1 : int\n"
);
assert!(c.errors.is_empty());
}
#[test]
fn a_statement_expression_that_ends_in_something_else_is_void() {
let mut f = Fixture::new();
let body = f.block(&[]);
let value = f.ast.expr(ast::Expr::StmtExpr(body), Span::DUMMY);
let stmt = f.expr_stmt(value);
let mut c = f.checker();
let void = c.types.void();
let id = c.check_stmt(void, stmt);
assert_eq!(dump(&c, id), "expr\n stmt-expr : void\n block\n");
assert!(c.errors.is_empty());
}
#[test]
fn the_declaration_in_a_for_clause_scopes_to_the_loop_and_not_to_what_follows() {
let mut f = Fixture::new();
let specs = f.int_specs();
let declared = f.local(specs, "i");
let empty = f.stmt(ast::Stmt::Empty);
let loop_stmt = f.stmt(ast::Stmt::For {
init: ForInit::Decl(declared),
cond: None,
step: None,
body: empty,
});
let use_i = f.use_name("i");
let after = f.expr_stmt(use_i);
let outer = f.block(&[loop_stmt, after]);
let mut c = f.checker();
let void = c.types.void();
c.check_stmt(void, outer);
assert_eq!(message(&c), "'i' undeclared (first use in this function)");
}
#[test]
fn a_static_in_a_for_clause_is_accepted_and_only_pedantic_says_anything_about_it() {
let mut f = Fixture::new();
let mut specs = DeclSpecs::empty(Span::DUMMY);
let builtin = Builtin::NONE.add(BuiltinSet::INT).expect("a keyword written once");
specs.ty = TypeSpec::Builtin(builtin);
specs.storage = Some(StorageClass::Static);
let specs = f.ast.add_specs(specs);
let declared = f.local(specs, "i");
let empty = f.stmt(ast::Stmt::Empty);
let loop_stmt = f.stmt(ast::Stmt::For {
init: ForInit::Decl(declared),
cond: None,
step: None,
body: empty,
});
let mut c = f.checker();
let void = c.types.void();
c.check_stmt(void, loop_stmt);
assert!(c.errors.is_empty(), "got {:?}", messages(&c));
let mut c = f.checker();
c.cx.pedantic = true;
let void = c.types.void();
c.check_stmt(void, loop_stmt);
assert_eq!(
reported(&c),
["warning: declaration of static variable 'i' in 'for' loop initial declaration"]
);
}
#[test]
fn continue_needs_a_loop_and_is_not_satisfied_by_a_switch() {
let mut f = Fixture::new();
let one = f.int(1);
let go_on = f.stmt(ast::Stmt::Continue);
let case = f.stmt(ast::Stmt::Case { lo: one, hi: None, body: Some(go_on) });
let scrutinee = f.int(0);
let switch = f.switch(scrutinee, &[case]);
let mut c = f.checker();
let void = c.types.void();
c.check_stmt(void, switch);
assert_eq!(message(&c), "continue statement not within a loop");
}
#[test]
fn break_is_satisfied_by_a_switch_and_reported_where_there_is_neither() {
let mut f = Fixture::new();
let stop = f.stmt(ast::Stmt::Break);
let scrutinee = f.int(0);
let switch = f.switch(scrutinee, &[stop]);
let loose = f.stmt(ast::Stmt::Break);
let mut c = f.checker();
let void = c.types.void();
c.check_stmt(void, switch);
assert!(c.errors.is_empty(), "got {:?}", messages(&c));
let mut c = f.checker();
let void = c.types.void();
c.check_stmt(void, loose);
assert_eq!(message(&c), "break statement not within loop or switch");
}
#[test]
fn a_goto_resolves_to_a_label_the_function_defines_further_down() {
let mut f = Fixture::new();
let jump = f.goto("done");
let empty = f.stmt(ast::Stmt::Empty);
let target = f.labelled("done", Some(empty));
let body = f.block(&[jump, target]);
let mut c = f.checker();
let void = c.types.void();
let id = c.check_stmt(void, body);
assert_eq!(dump(&c, id), "block\n goto #0 done\n label #0 done\n empty\n");
assert!(c.errors.is_empty());
}
#[test]
fn a_label_that_is_jumped_to_and_never_defined_is_reported_at_the_jump() {
let mut f = Fixture::new();
let jump = f.goto("away");
let body = f.block(&[jump]);
let mut c = f.checker();
let void = c.types.void();
c.check_stmt(void, body);
assert_eq!(message(&c), "label 'away' used but not defined");
}
#[test]
fn the_address_of_a_label_is_a_use_of_it_and_not_a_definition() {
let mut f = Fixture::new();
let away = f.name("away");
let value = f.ast.expr(ast::Expr::LabelAddr(away), Span::DUMMY);
let stmt = f.expr_stmt(value);
let mut c = f.checker();
let void = c.types.void();
let id = c.check_stmt(void, stmt);
assert_eq!(dump(&c, id), "expr\n label-addr #0 away : void *\n");
assert_eq!(message(&c), "label 'away' used but not defined");
}
#[test]
fn a_goto_into_the_scope_of_a_variable_length_array_is_reported() {
let mut f = Fixture::new();
let specs = f.int_specs();
let length = f.local(specs, "n");
let length = f.stmt(ast::Stmt::Decl(length));
let jump = f.goto("done");
let size = f.use_name("n");
let array = f.array(specs, "a", size);
let array = f.stmt(ast::Stmt::Decl(array));
let empty = f.stmt(ast::Stmt::Empty);
let target = f.labelled("done", Some(empty));
let inner = f.block(&[array, target]);
let body = f.block(&[length, jump, inner]);
let mut c = f.checker();
let void = c.types.void();
c.check_stmt(void, body);
assert_eq!(
messages(&c),
[
"jump into scope of identifier with variably modified type",
"label 'done' defined here",
"'a' declared here",
]
);
}
#[test]
fn a_goto_out_of_the_scope_of_a_variable_length_array_is_allowed() {
let mut f = Fixture::new();
let specs = f.int_specs();
let length = f.local(specs, "n");
let length = f.stmt(ast::Stmt::Decl(length));
let size = f.use_name("n");
let array = f.array(specs, "a", size);
let array = f.stmt(ast::Stmt::Decl(array));
let jump = f.goto("done");
let inner = f.block(&[array, jump]);
let empty = f.stmt(ast::Stmt::Empty);
let target = f.labelled("done", Some(empty));
let body = f.block(&[length, inner, target]);
let mut c = f.checker();
let void = c.types.void();
c.check_stmt(void, body);
assert!(c.errors.is_empty(), "got {:?}", messages(&c));
}
#[test]
fn a_goto_into_the_scope_of_an_array_whose_length_is_a_constant_is_allowed() {
let mut f = Fixture::new();
let specs = f.int_specs();
let jump = f.goto("done");
let size = f.int(4);
let array = f.array(specs, "a", size);
let array = f.stmt(ast::Stmt::Decl(array));
let empty = f.stmt(ast::Stmt::Empty);
let target = f.labelled("done", Some(empty));
let inner = f.block(&[array, target]);
let body = f.block(&[jump, inner]);
let mut c = f.checker();
let void = c.types.void();
c.check_stmt(void, body);
assert!(c.errors.is_empty(), "got {:?}", messages(&c));
}
#[test]
fn one_label_defined_twice_is_an_error_that_points_at_the_first() {
let mut f = Fixture::new();
let first = f.labelled("here", None);
let second = f.labelled("here", None);
let body = f.block(&[first, second]);
let mut c = f.checker();
let void = c.types.void();
c.check_stmt(void, body);
assert_eq!(
messages(&c),
["duplicate label 'here'", "previous definition of 'here' with type 'void'",]
);
}
#[test]
fn a_local_label_is_undone_when_its_block_ends_so_two_blocks_may_declare_one_name() {
let mut f = Fixture::new();
let sibling = |f: &mut Fixture| {
let declared = f.local_labels(&["done"]);
let jump = f.goto("done");
let target = f.labelled("done", None);
f.block(&[declared, jump, target])
};
let first = sibling(&mut f);
let second = sibling(&mut f);
let body = f.block(&[first, second]);
let mut c = f.checker();
let void = c.types.void();
let id = c.check_stmt(void, body);
assert!(c.errors.is_empty(), "got {:?}", messages(&c));
assert_eq!(
dump(&c, id),
"block\n block\n empty\n goto #0 done\n label #0 done\n empty\n \
block\n empty\n goto #1 done\n label #1 done\n empty\n"
);
}
#[test]
fn a_local_label_that_nothing_defines_is_reported_when_its_block_ends() {
let mut f = Fixture::new();
let declared = f.local_labels(&["done"]);
let jump = f.goto("done");
let inner = f.block(&[declared, jump]);
let target = f.labelled("done", None);
let body = f.block(&[inner, target]);
let mut c = f.checker();
let void = c.types.void();
c.check_stmt(void, body);
assert_eq!(message(&c), "label 'done' used but not defined");
}
#[test]
fn a_computed_goto_wants_something_that_could_be_an_address() {
let mut f = Fixture::new();
let specs = f.keywords(&[BuiltinSet::DOUBLE]);
let zero = f.int(0);
let target = f.cast(specs, zero);
let stmt = f.stmt(ast::Stmt::GotoExpr(target));
let mut c = f.checker();
let void = c.types.void();
c.check_stmt(void, stmt);
assert_eq!(message(&c), "computed goto must be pointer type");
}
#[test]
fn a_switch_on_something_that_is_not_an_integer_is_an_error() {
let mut f = Fixture::new();
let specs = f.keywords(&[BuiltinSet::DOUBLE]);
let zero = f.int(0);
let scrutinee = f.cast(specs, zero);
let switch = f.switch(scrutinee, &[]);
let mut c = f.checker();
let void = c.types.void();
c.check_stmt(void, switch);
assert_eq!(message(&c), "switch quantity not an integer");
}
#[test]
fn the_cases_of_a_switch_are_one_table_in_the_order_they_were_written() {
let mut f = Fixture::new();
let first = f.case(1, None, None);
let second = f.case(4, Some(6), None);
let default = f.stmt(ast::Stmt::Default { body: None });
let scrutinee = f.int(0);
let switch = f.switch(scrutinee, &[first, second, default]);
let mut c = f.checker();
let void = c.types.void();
let id = c.check_stmt(void, switch);
assert!(c.errors.is_empty(), "got {:?}", messages(&c));
assert_eq!(
dump(&c, id),
"switch\n cond\n const 0 : int\n cases\n case #0 1\n case #1 4 ... 6\n \
default\n body\n block\n case #0\n empty\n case #1\n \
empty\n default\n empty\n"
);
}
#[test]
fn two_labels_on_one_statement_are_in_the_table_the_way_round_they_were_written() {
let mut f = Fixture::new();
let inner = f.case(2, None, None);
let outer = f.case(1, None, Some(inner));
let scrutinee = f.int(0);
let switch = f.switch(scrutinee, &[outer]);
let mut c = f.checker();
let void = c.types.void();
let id = c.check_stmt(void, switch);
assert!(c.errors.is_empty(), "got {:?}", messages(&c));
assert_eq!(
dump(&c, id),
"switch\n cond\n const 0 : int\n cases\n case #0 1\n case #1 2\n body\n \
block\n case #0\n case #1\n empty\n"
);
}
#[test]
fn a_case_that_covers_a_value_an_earlier_one_covers_is_a_duplicate() {
let mut f = Fixture::new();
let first = f.case(1, Some(3), None);
let second = f.case(2, None, None);
let scrutinee = f.int(0);
let switch = f.switch(scrutinee, &[first, second]);
let mut c = f.checker();
let void = c.types.void();
c.check_stmt(void, switch);
assert_eq!(messages(&c), ["duplicate case value", "previously used here"]);
}
#[test]
fn a_case_outside_a_switch_is_an_error_and_so_is_a_default() {
let mut f = Fixture::new();
let case = f.case(1, None, None);
let default = f.stmt(ast::Stmt::Default { body: None });
let body = f.block(&[case, default]);
let mut c = f.checker();
let void = c.types.void();
c.check_stmt(void, body);
assert_eq!(
messages(&c),
[
"case label not within a switch statement",
"'default' label not within a switch statement",
]
);
}
#[test]
fn a_case_label_that_is_not_a_constant_is_an_error() {
let mut f = Fixture::new();
let specs = f.int_specs();
let declared = f.local(specs, "n");
let declared = f.stmt(ast::Stmt::Decl(declared));
let use_n = f.use_name("n");
let case = f.stmt(ast::Stmt::Case { lo: use_n, hi: None, body: None });
let scrutinee = f.int(0);
let switch = f.switch(scrutinee, &[case]);
let body = f.block(&[declared, switch]);
let mut c = f.checker();
let void = c.types.void();
c.check_stmt(void, body);
assert_eq!(message(&c), "case label does not reduce to an integer constant");
}
#[test]
fn a_case_range_that_runs_backwards_is_empty() {
let mut f = Fixture::new();
let case = f.case(6, Some(4), None);
let scrutinee = f.int(0);
let switch = f.switch(scrutinee, &[case]);
let mut c = f.checker();
let void = c.types.void();
c.check_stmt(void, switch);
assert_eq!(reported(&c), ["warning: empty range specified"]);
}
#[test]
fn a_case_is_measured_against_the_type_that_was_written_and_not_the_promoted_one() {
let mut f = Fixture::new();
let specs = f.keywords(&[BuiltinSet::CHAR]);
let zero = f.int(0);
let scrutinee = f.cast(specs, zero);
let case = f.case(300, None, None);
let switch = f.switch(scrutinee, &[case]);
let mut c = f.checker();
let void = c.types.void();
c.check_stmt(void, switch);
assert_eq!(reported(&c), ["warning: case label value exceeds maximum value for type"]);
}
#[test]
fn two_defaults_in_one_switch_are_an_error_that_points_at_the_first() {
let mut f = Fixture::new();
let first = f.stmt(ast::Stmt::Default { body: None });
let second = f.stmt(ast::Stmt::Default { body: None });
let scrutinee = f.int(0);
let switch = f.switch(scrutinee, &[first, second]);
let mut c = f.checker();
let void = c.types.void();
c.check_stmt(void, switch);
assert_eq!(
messages(&c),
["multiple default labels in one switch", "this is the first default label"]
);
}
#[test]
fn a_nested_switch_keeps_its_cases_to_itself() {
let mut f = Fixture::new();
let inner_case = f.case(1, None, None);
let inner_scrutinee = f.int(0);
let inner = f.switch(inner_scrutinee, &[inner_case]);
let outer_case = f.case(1, None, Some(inner));
let outer_scrutinee = f.int(0);
let outer = f.switch(outer_scrutinee, &[outer_case]);
let mut c = f.checker();
let void = c.types.void();
let id = c.check_stmt(void, outer);
assert!(c.errors.is_empty(), "got {:?}", messages(&c));
assert_eq!(
dump(&c, id),
"switch\n cond\n const 0 : int\n cases\n case #1 1\n body\n block\n \
case #1\n switch\n cond\n const 0 : int\n \
cases\n case #0 1\n body\n block\n case \
#0\n empty\n"
);
}
#[test]
fn a_bare_return_from_a_function_that_promised_a_value_is_an_error() {
let mut f = Fixture::new();
let stmt = f.stmt(ast::Stmt::Return(None));
let mut c = f.checker();
let int = c.int();
c.check_stmt(int, stmt);
assert_eq!(reported(&c), ["error: 'return' with no value, in function returning non-void"]);
assert_eq!(messages(&c).len(), 2, "the note is attached to it");
}
#[test]
fn a_value_returned_from_a_function_returning_void_is_an_error() {
let mut f = Fixture::new();
let one = f.int(1);
let stmt = f.stmt(ast::Stmt::Return(Some(one)));
let mut c = f.checker();
let void = c.types.void();
c.check_stmt(void, stmt);
assert_eq!(reported(&c), ["error: 'return' with a value, in function returning void"]);
}
#[test]
fn a_void_value_returned_from_a_function_returning_void_is_what_a_wrapper_writes() {
let mut f = Fixture::new();
let specs = f.keywords(&[BuiltinSet::VOID]);
let one = f.int(1);
let value = f.cast(specs, one);
let stmt = f.stmt(ast::Stmt::Return(Some(value)));
let mut c = f.checker();
let void = c.types.void();
c.check_stmt(void, stmt);
assert!(c.errors.is_empty(), "got {:?}", messages(&c));
}
#[test]
fn a_returned_value_is_converted_to_the_return_type() {
let mut f = Fixture::new();
let one = f.int(1);
let stmt = f.stmt(ast::Stmt::Return(Some(one)));
let mut c = f.checker();
let long = c.types.int(IntKind::Long);
let id = c.check_stmt(long, stmt);
assert_eq!(dump(&c, id), "return\n convert arithmetic : long\n const 1 : int\n");
assert!(c.errors.is_empty());
}
}