Skip to main content

rucc_sema/
check.rs

1//! The pass: what walks the untyped tree and builds the typed one.
2//!
3//! Design: `spec/07-types-and-semantics.md`.
4//!
5//! The shape is the parser's, because the job is the same shape: a context of the things that do
6//! not change, a walk that holds the things that do, and one structure handed back at the end.
7//! What is different is that this walk has two trees, one it reads and one it writes, and the
8//! reason a node is copied across rather than annotated in place is that the two are not the
9//! same tree. A `p->x` is one node in the source and two here, an `int` meeting a `long` is
10//! three, and an array used as a pointer is a node that the source does not contain at all.
11//!
12//! # What is here so far
13//!
14//! Expressions, which is where the constraints of 6.5 live. The ones that name a type are in
15//! `check/expr/typeop.rs` and the rest are in `check/expr.rs`, which is a split by what the two
16//! do rather than by size: an operator that names a type asks the type builder a question first
17//! and most of them answer with a constant. The two that build an object rather than producing a
18//! value, which are the compound literal and GNU's cast to a union type, are in `check/init.rs`
19//! with the rest of initialization.
20//!
21//! Declarations, in `check/decl.rs`, which is what decides the linkage, the storage duration and
22//! the definition state of each name and what reconciles the declarations that share one. The
23//! type a declaration declares comes from `check/ty.rs`, through [`Checker::declared_type`] and
24//! [`Checker::type_name`], which fold a declarator onto the type a specifier list named.
25//!
26//! Statements, in `check/stmt.rs`, which is the one walk here that carries state: what encloses
27//! a statement is what decides whether it is allowed. The function definition is there too, since
28//! a body is the only thing a statement list is ever part of, and so is [`Checker::check_unit`],
29//! which walks a whole translation unit.
30//!
31//! Initialization, in `check/init.rs`, which turns the tree an initializer was parsed into
32//! into a flat list of what goes where. It is its own module because it is its own algorithm:
33//! a cursor over the object being initialized rather than a walk over the source, which is what
34//! makes brace elision, designation and a string literal filling an array all the same thing
35//! seen from different places. The compound literal is there too, since an unnamed object with
36//! an initializer is what it is.
37//!
38//! Folding is reachable from here through [`Checker::eval_constant`] and
39//! [`Checker::eval_integer`], and the checking asks for it in seven places: a narrowing
40//! conversion that changes the value, an `alignas`, a `static_assert`, the initializer of a
41//! `constexpr` object, a case label, the index of a designation, and each element of an
42//! initializer for an object that exists before the program runs.
43//!
44//! # Poisoning
45//!
46//! The rule is the parser's, in `spec/06-lexer-and-parser.md` section 6.8, and it is the same
47//! rule for the same reason. An expression that has been diagnosed becomes
48//! [`ExprKind::Error`](crate::ExprKind::Error), and an operator whose operand is poisoned is
49//! poisoned in turn without a word said about it. That is what keeps one undeclared name from
50//! producing an error for every operator it appears under, and it is why nothing below asks
51//! whether an error has already been reported: it asks whether the node in its hand is one.
52
53use rucc_ast::Ast;
54use rucc_base::{Interner, Symbol};
55use rucc_diag::{DEFAULT_ERROR_LIMIT, Diagnostic, Errors, Severity, Span};
56use rucc_session::Std;
57use rucc_target::TargetInfo;
58use rucc_types::{ArrayLen, IntKind, TypeId, TypeKind, Types, int_width};
59
60use crate::convert::Conv;
61use crate::decl::{
62    Decl, DeclId, DeclKind, DeclList, Definition, Emission, Linkage, StorageDuration,
63};
64use crate::eval::{Eval, NotConstant};
65use crate::expr::{Category, Expr, ExprId, ExprKind};
66use crate::scope::Scopes;
67use crate::tast::{Const, Tast};
68
69mod attr;
70mod builtin;
71mod decl;
72mod expr;
73mod init;
74mod stmt;
75mod ty;
76
77pub use crate::check::builtin::{library_name, unimplemented_builtin};
78
79/// What the checking needs and does not change.
80#[derive(Debug, Clone, Copy)]
81pub struct Context<'a> {
82    /// The spellings, for the diagnostics that name an identifier.
83    pub names: &'a Interner,
84    /// What the target's types are, which every layout and every promotion is decided by.
85    pub target: &'a TargetInfo,
86    /// The dialect.
87    pub std: Std,
88    /// Whether the GNU extensions are on.
89    pub gnu: bool,
90    /// Whether `-pedantic` was given.
91    pub pedantic: bool,
92    /// Whether `-fpermissive` was given, which is read through [`Context::promoted`].
93    pub permissive: bool,
94    /// Whether the whole unit is under GNU's reading of `inline`, which is `-fgnu89-inline`.
95    pub gnu89_inline: bool,
96    /// How many errors to report before stopping, with zero meaning no limit.
97    pub error_limit: usize,
98    /// Whether a C library function written under its own plain name may be taken to mean that
99    /// function, which is `-fno-builtin` and `-ffreestanding` turned around.
100    pub builtins: bool,
101    /// The names `-fno-builtin-<name>` took away one at a time, without the prefix.
102    pub no_builtin: &'a [String],
103    /// Whether an enumeration nothing wrote an underlying type for is represented in the smallest
104    /// integer type that holds it, which is `-fshort-enums`.
105    pub short_enums: bool,
106}
107
108impl<'a> Context<'a> {
109    /// A context with the defaults, for a caller that has an interner and a target to hand.
110    #[must_use]
111    pub fn new(names: &'a Interner, target: &'a TargetInfo, std: Std) -> Context<'a> {
112        Context {
113            names,
114            target,
115            std,
116            gnu: true,
117            pedantic: false,
118            permissive: false,
119            gnu89_inline: false,
120            error_limit: DEFAULT_ERROR_LIMIT,
121            builtins: true,
122            no_builtin: &[],
123            short_enums: false,
124        }
125    }
126
127    /// Whether a name nothing said `gnu_inline` about is read GNU's way all the same.
128    ///
129    /// Two things ask for that and they ask for it for the whole unit rather than for one name.
130    /// C89 is where the older reading came from and has never had any other, and `-fgnu89-inline`
131    /// is how a program written against it says so under a later dialect. The dialect wins where
132    /// the two meet, so `-std=c89 -fno-gnu89-inline` leaves the reading alone. gcc refuses that
133    /// command line instead, and there is nothing else it could have meant.
134    #[must_use]
135    pub fn gnu_inline_by_default(&self) -> bool {
136        self.gnu89_inline || self.std == Std::C89
137    }
138
139    /// Whether a call to `name`, written as the program wrote it, may be taken to mean the C
140    /// library function of that name.
141    ///
142    /// The plain names are the ones the flags are about. A `__builtin_` spelling is the program
143    /// saying which function it means, so `-fno-builtin` leaves it alone and so does
144    /// `-ffreestanding`, which is what lets a freestanding build reach one deliberately.
145    #[must_use]
146    pub fn means_the_library(&self, name: &str) -> bool {
147        match name.strip_prefix("__builtin_") {
148            Some(_) => true,
149            None => self.builtins && !self.no_builtin.iter().any(|off| off == name),
150        }
151    }
152}
153
154/// One of the rules gcc 14 turned from a warning into an error, whose severity here is therefore
155/// a question rather than a constant.
156///
157/// Each is a thing a compiler took quietly for thirty years and stopped taking in 2024, and each
158/// one is in the suites and in real trees for that reason. Three of them are rules C89 did not
159/// have, so under that dialect the program is doing nothing wrong and there is nothing to say.
160/// The rest were constraint violations then as well, and gcc warned about them long before it
161/// started refusing them. Both groups go back to a warning under `-fpermissive`, which is what a
162/// build of code that cannot be changed reaches for.
163///
164/// Measured against gcc 16.2.0 one file per rule, which is where the answers below come from
165/// rather than from the release notes.
166#[derive(Debug, Clone, Copy, PartialEq, Eq)]
167pub enum Promoted {
168    /// A declaration with no type in it, which C89 read as an `int`.
169    ImplicitInt,
170    /// A call to a function nothing declared, which C89 declared as it went.
171    ImplicitCall,
172    /// A parameter named in an old style definition and never declared, which C89 read as an
173    /// `int` in the same way.
174    ImplicitParam,
175    /// A pointer made from an integer, or an integer from a pointer, with no cast.
176    BadConversion,
177    /// A pointer meeting a pointer to something the target is not.
178    IncompatiblePointer,
179    /// `return;` in a function that promised a value, which C89 allowed and only made undefined
180    /// if the caller went on to read the value.
181    ReturnWithNoValue,
182    /// `return expr;` in a function returning `void`, which C89 did not allow either.
183    ReturnWithValue,
184}
185
186impl Context<'_> {
187    /// How to report one of the promoted rules, or `None` when this dialect has nothing to say
188    /// about it.
189    #[must_use]
190    pub fn promoted(&self, rule: Promoted) -> Option<Severity> {
191        if self.std >= Std::C99 {
192            return Some(if self.permissive { Severity::Warning } else { Severity::Error });
193        }
194        match rule {
195            Promoted::ImplicitInt
196            | Promoted::ImplicitCall
197            | Promoted::ImplicitParam
198            | Promoted::ReturnWithNoValue => None,
199            Promoted::BadConversion | Promoted::IncompatiblePointer | Promoted::ReturnWithValue => {
200                Some(Severity::Warning)
201            }
202        }
203    }
204}
205
206/// What one run of the checking produced.
207#[derive(Debug)]
208pub struct Checked {
209    /// The typed tree, which holds poisoned nodes where the source did not check.
210    pub tast: Tast,
211    /// The types, which the tree points into and which outlive it.
212    pub types: Types,
213    /// What went wrong, in the order it was found.
214    pub diagnostics: Vec<Diagnostic>,
215}
216
217impl Checked {
218    /// Whether anything was reported at an error severity.
219    #[must_use]
220    pub fn failed(&self) -> bool {
221        self.diagnostics.iter().any(|d| d.severity.is_fatal())
222    }
223}
224
225/// The checking pass.
226#[derive(Debug)]
227pub struct Checker<'a> {
228    pub(crate) ast: &'a Ast,
229    pub(crate) tast: Tast,
230    pub(crate) types: Types,
231    pub(crate) scopes: Scopes,
232    pub(crate) errors: Errors,
233    pub(crate) cx: Context<'a>,
234    /// What the type builder has already worked out, which is in `check/ty.rs` with the code
235    /// that fills it in.
236    pub(crate) built: ty::Built,
237    /// The function body being checked, absent everywhere else. What is in it is in
238    /// `check/stmt.rs`, which is the only code that reads it.
239    pub(in crate::check) body: Option<stmt::Body>,
240    /// The declarations whose initializers are being checked and whose types or values are not
241    /// known until that finishes, which is what C23 calls underspecified. A name is in scope
242    /// inside its own initializer, so this is what tells a reference to one from a use of the
243    /// object it will become. Nested, because a statement expression may declare another.
244    pub(in crate::check) underspecified: Vec<DeclId>,
245    /// The builtins this compiler declared for a program that called one without declaring it,
246    /// which is what `check/builtin.rs` does the first time it sees one.
247    ///
248    /// It is here to tell that declaration from one the program wrote, which the families that
249    /// are answered rather than called have to be able to do. `__builtin_nan("1")` is a constant
250    /// and `__builtin_nan(p)` is a call, so a file with both leaves a declaration behind, and
251    /// without this the answer to the second one written would depend on the first.
252    pub(in crate::check) declared_builtins: Vec<Symbol>,
253    /// The name being checked as the callee of a call, and nothing anywhere else.
254    ///
255    /// C89 said a call to a name nothing declared declares that name, and only a call does: the
256    /// same name written as a value is undeclared and stays that way. The identifier is checked
257    /// before anything knows what it is under, so this is what tells the one case from the other,
258    /// and it is set for exactly as long as the callee is being checked.
259    pub(in crate::check) calling: Option<Symbol>,
260}
261
262impl<'a> Checker<'a> {
263    /// A checker over one untyped tree.
264    #[must_use]
265    pub fn new(ast: &'a Ast, cx: Context<'a>) -> Checker<'a> {
266        Checker {
267            ast,
268            tast: Tast::new(),
269            types: Types::new(),
270            scopes: Scopes::new(),
271            errors: Errors::new(cx.error_limit),
272            cx,
273            built: ty::Built::default(),
274            body: None,
275            underspecified: Vec::new(),
276            declared_builtins: Vec::new(),
277            calling: None,
278        }
279    }
280
281    /// Checks a whole translation unit, which is what a compilation does.
282    ///
283    /// The declarations are checked in the order they were written, since that is the order the
284    /// scopes are built in and the order the diagnostics belong in.
285    pub fn check_unit(&mut self) {
286        // Copied out because it is a shared reference with the checker's own lifetime, so holding
287        // it does not borrow the checker that each declaration is checked through.
288        let ast = self.ast;
289        for &decl in ast.top_level() {
290            self.check_decl(decl);
291        }
292    }
293
294    /// Checks one expression and gives back the node it became.
295    ///
296    /// Always gives back a node. An expression that does not check is poisoned rather than
297    /// absent, so that the operators around it are still checked and the diagnostics they would
298    /// produce are still held back.
299    pub fn check_expr(&mut self, id: rucc_ast::ExprId) -> ExprId {
300        self.expr(id)
301    }
302
303    /// Folds a checked expression, reporting whatever the folding itself found wrong.
304    ///
305    /// # Errors
306    ///
307    /// [`NotConstant`] when the expression is not one. It is handed back rather than reported
308    /// because the message names the context: `case label does not reduce to an integer
309    /// constant` and `enumerator value for 'x' is not an integer constant` are two sentences
310    /// about the same failure, and only the caller knows which one to write.
311    pub fn eval_constant(&mut self, expr: ExprId) -> Result<Const, NotConstant> {
312        let mut eval = self.eval();
313        let value = eval.constant(expr);
314        self.absorb(eval.finish());
315        value
316    }
317
318    /// The same, for a context that needs an integer constant expression.
319    ///
320    /// # Errors
321    ///
322    /// [`NotConstant`] when the expression is not one, or is a constant of some other type.
323    pub fn eval_integer(&mut self, expr: ExprId) -> Result<i128, NotConstant> {
324        let mut eval = self.eval();
325        let value = eval.integer(expr);
326        self.absorb(eval.finish());
327        value
328    }
329
330    /// The tree, the types and the diagnostics.
331    #[must_use]
332    pub fn finish(self) -> Checked {
333        Checked { tast: self.tast, types: self.types, diagnostics: self.errors.finish() }
334    }
335
336    /// Declares an object in the current scope without a declaration to read it from.
337    ///
338    /// [`Checker::check_decl`] is what a translation unit goes through. This is for the caller
339    /// that wants to check one expression against names it has decided on itself, which is what
340    /// [`Checker::check_expr`] is for and what the tests here are built on.
341    pub fn declare_object(&mut self, name: Symbol, ty: TypeId, span: Span) -> DeclId {
342        let decl = self.object_decl(Some(name), ty, span);
343        self.scopes.declare(name, crate::scope::Binding::Decl(decl));
344        decl
345    }
346
347    /// An object with automatic storage that nothing can name.
348    ///
349    /// A parameter a definition left unnamed is the one of these there is, C23 6.7.7.4p1. The
350    /// object is there and the call passes it, and what it has no way of is being mentioned in
351    /// the body, so there is nothing to put in a scope and a declaration is all it is.
352    pub(crate) fn unnamed_object(&mut self, ty: TypeId, span: Span) -> DeclId {
353        self.object_decl(None, ty, span)
354    }
355
356    /// The declaration both of those are, which differ only in whether anything can say the name.
357    fn object_decl(&mut self, name: Option<Symbol>, ty: TypeId, span: Span) -> DeclId {
358        let kind = if rucc_types::is_function(&self.types, ty) {
359            DeclKind::Function
360        } else {
361            DeclKind::Object
362        };
363        self.tast.decl(
364            Decl {
365                name,
366                ty,
367                kind,
368                linkage: Linkage::None,
369                duration: StorageDuration::Automatic,
370                state: Definition::Defined,
371                alignment: None,
372                constant: false,
373                retained: false,
374                asm_label: None,
375                alias: None,
376                inline: Emission::Silent,
377                gnu_inline: false,
378                noreturn: false,
379                visibility: None,
380                init: None,
381                params: DeclList::EMPTY,
382                body: None,
383            },
384            span,
385        )
386    }
387
388    /// The conversions, over this tree and these types.
389    pub(crate) fn conv(&mut self) -> Conv<'_> {
390        // The target is copied out first because it is a shared reference living as long as the
391        // context, so taking it does not borrow the checker the two mutable ones are taken from.
392        let target = self.cx.target;
393        Conv { tast: &mut self.tast, types: &mut self.types, target }
394    }
395
396    /// The constant folding, over this tree and these types.
397    pub(crate) fn eval(&self) -> Eval<'_> {
398        Eval::new(&self.tast, &self.types, self.cx.target, self.cx.names)
399    }
400
401    /// Reports a diagnostic.
402    pub(crate) fn report(&mut self, diagnostic: Diagnostic) {
403        self.errors.push(diagnostic);
404    }
405
406    /// Reports everything the folding found, which it collects rather than pushing itself
407    /// because it holds the tree while it runs and the error list is beside the tree.
408    pub(crate) fn absorb(&mut self, diagnostics: Vec<Diagnostic>) {
409        for diagnostic in diagnostics {
410            self.errors.push(diagnostic);
411        }
412    }
413
414    /// Whether a checked expression is one that was already the subject of a diagnostic.
415    pub(crate) fn is_poisoned(&self, id: ExprId) -> bool {
416        matches!(self.tast[id].kind, ExprKind::Error)
417    }
418
419    /// A poisoned expression, for the operand that did not check.
420    ///
421    /// Its type is `int` because every node has a type and there is no type meaning "no idea".
422    /// Nothing reads it, since every operator that meets a poisoned operand poisons itself
423    /// before it looks at what type the operand had.
424    pub(crate) fn poison(&mut self, span: Span) -> ExprId {
425        let int = self.types.int(IntKind::Int);
426        self.tast.expr(Expr::new(ExprKind::Error, int, Category::Rvalue), span)
427    }
428
429    /// How a type is written, for a diagnostic that names one.
430    pub(crate) fn spell(&self, ty: TypeId) -> String {
431        rucc_types::spell(&self.types, self.cx.names, ty)
432    }
433
434    /// What a name is spelled, for a diagnostic that quotes one.
435    pub(crate) fn text(&self, name: Symbol) -> &str {
436        self.cx.names.resolve(name)
437    }
438
439    /// `int`, which is the type of every comparison and of `!`.
440    pub(crate) fn int(&self) -> TypeId {
441        self.types.int(IntKind::Int)
442    }
443
444    /// The type `sizeof` and `alignof` answer in, and the one an offset is measured in.
445    ///
446    /// Derived the same way [`Checker::ptrdiff`] is and for the same reason, since `size_t` is
447    /// the unsigned type as wide as a pointer on every target this compiles for and asking the
448    /// widths keeps the two from disagreeing about which one that is.
449    pub(crate) fn size_type(&self) -> TypeId {
450        let width = self.cx.target.pointer_width;
451        for kind in [IntKind::UInt, IntKind::ULong, IntKind::ULongLong] {
452            if int_width(kind, self.cx.target) >= width {
453                return self.types.int(kind);
454            }
455        }
456        self.types.int(IntKind::ULongLong)
457    }
458
459    /// Whether a type's size is worked out where it is reached rather than here.
460    ///
461    /// True for an array whose length is an expression, however deep it is: `int a[n][3]` is one
462    /// and so is `int a[3][n]`. Shared between the operator that measures a type and the
463    /// declaration that has to decide whether the object can live anywhere but the stack.
464    pub(crate) fn is_variable_length(&self, ty: TypeId) -> bool {
465        match self.types.kind(self.types.canonical(ty)) {
466            TypeKind::Array { elem, len } => {
467                matches!(len, ArrayLen::Variable(_)) || self.is_variable_length(elem)
468            }
469            _ => false,
470        }
471    }
472
473    /// Whether a type is variably modified, which is a variable length array or anything built
474    /// out of one.
475    ///
476    /// `int a[n]` is one and so is `int (*p)[n]`, which is where this differs from
477    /// [`Checker::is_variable_length`]: the pointer has the size every pointer has, and the
478    /// thing it points at has a size the program worked out where the declaration was. That is
479    /// why C says a jump may not enter the scope of either of them.
480    pub(crate) fn is_variably_modified(&self, ty: TypeId) -> bool {
481        match self.types.kind(self.types.canonical(ty)) {
482            TypeKind::Array { elem, len } => {
483                matches!(len, ArrayLen::Variable(_)) || self.is_variably_modified(elem)
484            }
485            TypeKind::Pointer(pointee) => self.is_variably_modified(pointee),
486            _ => false,
487        }
488    }
489
490    /// The type of the difference between two pointers.
491    ///
492    /// Derived rather than stored, because `ptrdiff_t` is whatever signed type is as wide as a
493    /// pointer and that is `long` on every LP64 target and `long long` on Windows, which is the
494    /// same fact `long_width` already records. Asking the widths keeps the two from disagreeing.
495    pub(crate) fn ptrdiff(&self) -> TypeId {
496        let width = self.cx.target.pointer_width;
497        for kind in [IntKind::Int, IntKind::Long, IntKind::LongLong] {
498            if int_width(kind, self.cx.target) >= width {
499                return self.types.int(kind);
500            }
501        }
502        self.types.int(IntKind::LongLong)
503    }
504}