Skip to main content

nifty_check/checker/
expr.rs

1//! Expressions and places.
2
3use std::rc::Rc;
4
5use nifty_syntax::Span;
6use nifty_syntax::ast::{self, Arg, ExprKind as Ast, StringPart, UnaryOp};
7
8use super::args::Args;
9use super::env::LocalKind;
10use super::{Checker, Global};
11use crate::functions::OPEN_WINDOW;
12use crate::hir::{BinaryOp, Expr, ExprKind, Place, PlaceRoot, PlaceStep, Segment, StructId, Type};
13use crate::methods::{self, MethodKind};
14use crate::{elements, functions};
15
16impl Checker {
17    /// Checks `expr`; `expected` guides literals that can't stand alone, such as `[]`.
18    pub(super) fn expr(&mut self, expr: &ast::Expr, expected: Option<&Type>) -> Expr {
19        let span = expr.span;
20        let (kind, ty) = match &expr.kind {
21            Ast::Int(value) => (ExprKind::Int(*value), Type::Int),
22            Ast::Float(value) => (ExprKind::Float(*value), Type::Float),
23            Ast::Bool(value) => (ExprKind::Bool(*value), Type::Bool),
24            Ast::String(parts) => (self.string(parts), Type::String),
25            Ast::Name(name) => return self.name(name, span),
26            Ast::List(items) => return self.list(items, expected, span),
27            Ast::Unary { op, operand } => return self.unary(*op, operand, span),
28            Ast::Binary { op, lhs, rhs } => return self.binary(*op, lhs, rhs, span),
29            Ast::Call {
30                callee,
31                args,
32                trailing_block,
33            } => {
34                if let Some(block) = trailing_block {
35                    self.error("only elements take a `{ … }` block", block.span);
36                }
37                return self.call(callee, args, span);
38            }
39            Ast::Field { base, name } => return self.field(base, name, span),
40            Ast::Index { base, index } => return self.index(base, index, span),
41            Ast::ImplicitMember(name) => {
42                self.error(
43                    format!(
44                        "`.{}` only works where a style or alignment is expected",
45                        name.name
46                    ),
47                    span,
48                );
49                (ExprKind::Error, Type::Error)
50            }
51            Ast::Error => (ExprKind::Error, Type::Error),
52        };
53        Expr { kind, ty, span }
54    }
55
56    /// Checks `expr` and reports it unless it has type `expected`.
57    pub(super) fn expect(&mut self, expr: &ast::Expr, expected: &Type) -> Expr {
58        let checked = self.expr(expr, Some(expected));
59        self.require(&checked.ty, expected, checked.span);
60        checked
61    }
62
63    fn string(&mut self, parts: &[StringPart]) -> ExprKind {
64        if let [StringPart::Text(text)] = parts {
65            return ExprKind::String(Rc::from(text.as_str()));
66        }
67        let segments = parts
68            .iter()
69            .map(|part| match part {
70                StringPart::Text(text) => Segment::Text(Rc::from(text.as_str())),
71                StringPart::Expr(value) => {
72                    let value = self.expr(value, None);
73                    if !matches!(
74                        value.ty,
75                        Type::Int | Type::Float | Type::Bool | Type::String | Type::Error
76                    ) {
77                        self.error(
78                            format!("can't put a `{}` in a string", self.type_name(&value.ty)),
79                            value.span,
80                        );
81                    }
82                    Segment::Value(value)
83                }
84            })
85            .collect();
86        ExprKind::Interpolate(segments)
87    }
88
89    fn name(&mut self, name: &str, span: Span) -> Expr {
90        if let Some(local) = self.body.lookup(name) {
91            let ty = self.body.local(local).ty.clone();
92            return Expr {
93                kind: ExprKind::Local(local),
94                ty,
95                span,
96            };
97        }
98        match self.global(name) {
99            Some(Global::State(state)) => {
100                self.effects.reads.insert(state);
101                Expr {
102                    kind: ExprKind::State(state),
103                    ty: self.program.states[state.0].ty.clone(),
104                    span,
105                }
106            }
107            Some(Global::Struct(_)) => self.error_expr(
108                format!("`{name}` is a struct; create one with `{name}(field: value)`"),
109                span,
110            ),
111            Some(Global::Fn(_)) => self.error_expr(
112                format!("`{name}` is a function; call it with `{name}(…)`"),
113                span,
114            ),
115            Some(Global::View(_)) => self.error_expr(
116                format!("`{name}` is a view; use it inside a window or view body"),
117                span,
118            ),
119            Some(Global::Window(_)) => self.window_misused(name, span),
120            None if elements::element(name).is_some() => self.error_expr(
121                format!("`{name}` is an element; use it inside a window or view body"),
122                span,
123            ),
124            None if functions::function(name).is_some() => self.error_expr(
125                format!("`{name}` is a built-in function; call it with `{name}(…)`"),
126                span,
127            ),
128            None => self.error_expr(format!("unknown name `{name}`"), span),
129        }
130    }
131
132    fn list(&mut self, items: &[ast::Expr], expected: Option<&Type>, span: Span) -> Expr {
133        let expected_element = match expected {
134            Some(Type::List(element)) => Some(element.as_ref().clone()),
135            _ => None,
136        };
137        let Some((first, rest)) = items.split_first() else {
138            return match expected_element {
139                Some(element) => Expr {
140                    kind: ExprKind::List(Vec::new()),
141                    ty: Type::List(Rc::new(element)),
142                    span,
143                },
144                None => self.error_expr(
145                    "can't tell what this empty list holds; add a type such as `: [int]`",
146                    span,
147                ),
148            };
149        };
150        let first = self.expr(first, expected_element.as_ref());
151        let element = expected_element.unwrap_or_else(|| first.ty.clone());
152        self.require(&first.ty, &element, first.span);
153        let mut checked = vec![first];
154        checked.extend(rest.iter().map(|item| self.expect(item, &element)));
155        Expr {
156            kind: ExprKind::List(checked),
157            ty: Type::List(Rc::new(element)),
158            span,
159        }
160    }
161
162    fn unary(&mut self, op: UnaryOp, operand: &ast::Expr, span: Span) -> Expr {
163        match op {
164            UnaryOp::Not => {
165                let operand = self.expect(operand, &Type::Bool);
166                Expr {
167                    kind: ExprKind::Not(Box::new(operand)),
168                    ty: Type::Bool,
169                    span,
170                }
171            }
172            UnaryOp::Neg => {
173                let operand = self.expr(operand, None);
174                if !matches!(operand.ty, Type::Int | Type::Float | Type::Error) {
175                    self.error(
176                        format!("can't negate a `{}`", self.type_name(&operand.ty)),
177                        operand.span,
178                    );
179                }
180                Expr {
181                    ty: operand.ty.clone(),
182                    kind: ExprKind::Negate(Box::new(operand)),
183                    span,
184                }
185            }
186            UnaryOp::Bind => self.error_expr(
187                "`$` makes a two-way binding, which only `text_field`, `checkbox` and `download` accept",
188                span,
189            ),
190        }
191    }
192
193    fn binary(&mut self, op: BinaryOp, lhs: &ast::Expr, rhs: &ast::Expr, span: Span) -> Expr {
194        let lhs = self.expr(lhs, None);
195        let rhs = self.expr(rhs, Some(&lhs.ty));
196        let ty = self.binary_type(op, &lhs.ty, &rhs.ty, span);
197        Expr {
198            kind: ExprKind::Binary(op, Box::new(lhs), Box::new(rhs)),
199            ty,
200            span,
201        }
202    }
203
204    pub(super) fn binary_type(&mut self, op: BinaryOp, lhs: &Type, rhs: &Type, span: Span) -> Type {
205        if lhs.is_error() || rhs.is_error() {
206            return Type::Error;
207        }
208        let result = match op {
209            BinaryOp::And | BinaryOp::Or => {
210                (*lhs == Type::Bool && *rhs == Type::Bool).then_some(Type::Bool)
211            }
212            BinaryOp::Eq | BinaryOp::NotEq => {
213                (lhs == rhs && *lhs != Type::Void).then_some(Type::Bool)
214            }
215            BinaryOp::Lt | BinaryOp::LtEq | BinaryOp::Gt | BinaryOp::GtEq => (lhs == rhs
216                && matches!(lhs, Type::Int | Type::Float | Type::String))
217            .then_some(Type::Bool),
218            BinaryOp::Add => (lhs == rhs && matches!(lhs, Type::Int | Type::Float | Type::String))
219                .then(|| lhs.clone()),
220            BinaryOp::Sub | BinaryOp::Mul | BinaryOp::Div | BinaryOp::Rem => {
221                (lhs == rhs && matches!(lhs, Type::Int | Type::Float)).then(|| lhs.clone())
222            }
223        };
224        result.unwrap_or_else(|| {
225            let hint = if op == BinaryOp::Add && (*lhs == Type::String || *rhs == Type::String) {
226                "; to join text and a value, use interpolation: \"{value}\""
227            } else {
228                ""
229            };
230            self.error(
231                format!(
232                    "can't use {} with `{}` and `{}`{hint}",
233                    op.token().describe(),
234                    self.type_name(lhs),
235                    self.type_name(rhs)
236                ),
237                span,
238            );
239            Type::Error
240        })
241    }
242
243    fn call(&mut self, callee: &ast::Expr, args: &[Arg], span: Span) -> Expr {
244        match &callee.kind {
245            Ast::Field { base, name } => self.method(base, name, args, span),
246            Ast::Name(name) if self.body.lookup(name).is_none() => match self.global(name) {
247                Some(Global::Fn(function)) => self.call_function(function, name, args, span),
248                Some(Global::Struct(id)) => self.construct(id, args, span),
249                Some(Global::View(_)) => self.error_expr(
250                    format!("`{name}` is a view; use it inside a window or view body"),
251                    span,
252                ),
253                Some(Global::Window(_)) => self.window_misused(name, span),
254                _ if elements::element(name).is_some() => self.error_expr(
255                    format!("`{name}` is an element; use it inside a window or view body"),
256                    span,
257                ),
258                _ => match functions::function(name) {
259                    Some(function) => self.built_in_call(function, args, span),
260                    None => self.error_expr(format!("unknown function `{name}`"), callee.span),
261                },
262            },
263            _ => self.error_expr("this can't be called", callee.span),
264        }
265    }
266
267    fn call_function(
268        &mut self,
269        function: crate::hir::FnId,
270        name: &str,
271        args: &[Arg],
272        span: Span,
273    ) -> Expr {
274        let signature = &self.fn_signatures[function.0];
275        let params = signature.params.clone();
276        let return_type = signature.return_type.clone();
277        let mut args = Args::new(self, format!("`{name}`"), args);
278        let mut checked = Vec::new();
279        for (param, ty) in &params {
280            let arg = args.param(param);
281            checked.push(self.required_arg(arg, ty, span, || {
282                format!("`{name}` needs an argument for `{param}`")
283            }));
284        }
285        args.finish(self);
286        self.effects.calls.insert(function);
287        Expr {
288            kind: ExprKind::Call(function, checked),
289            ty: return_type,
290            span,
291        }
292    }
293
294    fn construct(&mut self, id: StructId, args: &[Arg], span: Span) -> Expr {
295        let name = self.program.structs[id.0].name.clone();
296        let fields: Vec<(String, Type)> = self.program.structs[id.0]
297            .fields
298            .iter()
299            .map(|field| (field.name.clone(), field.ty.clone()))
300            .collect();
301        let mut args = Args::new(self, format!("`{name}`"), args);
302        let mut checked = Vec::new();
303        for (field, ty) in &fields {
304            let arg = args.named(field);
305            checked.push(self.required_arg(arg, ty, span, || {
306                format!("`{name}` needs a value for `{field}`")
307            }));
308        }
309        args.finish(self);
310        Expr {
311            kind: ExprKind::Construct(id, checked),
312            ty: Type::Struct(id),
313            span,
314        }
315    }
316
317    fn method(&mut self, base: &ast::Expr, name: &ast::Ident, args: &[Arg], span: Span) -> Expr {
318        match methods::method(&name.name).map(|method| method.kind) {
319            Some(kind @ (MethodKind::Len | MethodKind::IsEmpty)) => {
320                let receiver = self.expr(base, None);
321                self.no_args(&name.name, args);
322                if !matches!(receiver.ty, Type::List(_) | Type::String | Type::Error) {
323                    return self.no_method(&receiver.ty, name);
324                }
325                let receiver = Box::new(receiver);
326                let (kind, ty) = if kind == MethodKind::Len {
327                    (ExprKind::Len(receiver), Type::Int)
328                } else {
329                    (ExprKind::IsEmpty(receiver), Type::Bool)
330                };
331                Expr { kind, ty, span }
332            }
333            Some(kind @ (MethodKind::Push | MethodKind::Remove)) => {
334                let Some(place) = self.place(base) else {
335                    return self
336                        .error_expr("only a list in state or a variable can change", base.span);
337                };
338                let Type::List(element) = place.ty.clone() else {
339                    if place.ty.is_error() {
340                        return reported(span);
341                    }
342                    return self.no_method(&place.ty, name);
343                };
344                self.note_write(&place);
345                let [arg] = args else {
346                    return self.error_expr(format!("`{}` takes one argument", name.name), span);
347                };
348                if kind == MethodKind::Push {
349                    let value = self.expect(&arg.value, &element);
350                    Expr {
351                        kind: ExprKind::Push(place, Box::new(value)),
352                        ty: Type::Void,
353                        span,
354                    }
355                } else {
356                    let index = self.expect(&arg.value, &Type::Int);
357                    Expr {
358                        kind: ExprKind::Remove(place, Box::new(index)),
359                        ty: Type::Void,
360                        span,
361                    }
362                }
363            }
364            None => {
365                let receiver = self.expr(base, None);
366                if receiver.ty.is_error() {
367                    return reported(span);
368                }
369                self.no_method(&receiver.ty, name)
370            }
371        }
372    }
373
374    fn no_args(&mut self, method: &str, args: &[Arg]) {
375        if let Some(first) = args.first() {
376            self.error(format!("`{method}` takes no arguments"), first.value.span);
377        }
378    }
379
380    fn no_method(&mut self, ty: &Type, name: &ast::Ident) -> Expr {
381        let available = match ty {
382            Type::List(_) => "; lists have `len`, `is_empty`, `push` and `remove`",
383            Type::String => "; strings have `len` and `is_empty`",
384            _ => "",
385        };
386        self.error_expr(
387            format!(
388                "`{}` has no method `{}`{available}",
389                self.type_name(ty),
390                name.name
391            ),
392            name.span,
393        )
394    }
395
396    fn field(&mut self, base: &ast::Expr, name: &ast::Ident, span: Span) -> Expr {
397        let base = self.expr(base, None);
398        match self.field_index(&base.ty, name) {
399            Some((index, ty)) => Expr {
400                kind: ExprKind::Field(Box::new(base), index),
401                ty,
402                span,
403            },
404            None => reported(span),
405        }
406    }
407
408    /// The field's position and type, or `None` after reporting why there is no such field.
409    fn field_index(&mut self, ty: &Type, name: &ast::Ident) -> Option<(usize, Type)> {
410        match ty {
411            Type::Struct(id) => {
412                let found = self.program.structs[id.0]
413                    .fields
414                    .iter()
415                    .enumerate()
416                    .find(|(_, field)| field.name == name.name)
417                    .map(|(index, field)| (index, field.ty.clone()));
418                if found.is_none() {
419                    self.error(
420                        format!("`{}` has no field `{}`", self.type_name(ty), name.name),
421                        name.span,
422                    );
423                }
424                found
425            }
426            Type::Error => None,
427            _ => {
428                self.error(
429                    format!(
430                        "`{}` has no fields; `{}` isn't one",
431                        self.type_name(ty),
432                        name.name
433                    ),
434                    name.span,
435                );
436                None
437            }
438        }
439    }
440
441    fn index(&mut self, base: &ast::Expr, index: &ast::Expr, span: Span) -> Expr {
442        let base = self.expr(base, None);
443        let index = self.expect(index, &Type::Int);
444        let ty = match &base.ty {
445            Type::List(element) => element.as_ref().clone(),
446            Type::Error => Type::Error,
447            other => {
448                self.error(
449                    format!("can't index into a `{}`", self.type_name(other)),
450                    base.span,
451                );
452                Type::Error
453            }
454        };
455        Expr {
456            kind: ExprKind::Index(Box::new(base), Box::new(index)),
457            ty,
458            span,
459        }
460    }
461
462    /// Converts an assignable expression into a place, or reports why it can't be changed.
463    pub(super) fn place(&mut self, expr: &ast::Expr) -> Option<Place> {
464        let span = expr.span;
465        match &expr.kind {
466            Ast::Name(name) => {
467                if let Some(local) = self.body.lookup(name) {
468                    let ty = self.body.local(local).ty.clone();
469                    return Some(Place {
470                        root: PlaceRoot::Local(local),
471                        path: Vec::new(),
472                        ty,
473                        span,
474                    });
475                }
476                if let Some(Global::State(state)) = self.global(name) {
477                    return Some(Place {
478                        root: PlaceRoot::State(state),
479                        path: Vec::new(),
480                        ty: self.program.states[state.0].ty.clone(),
481                        span,
482                    });
483                }
484                self.name(name, span);
485                None
486            }
487            Ast::Field { base, name } => {
488                let mut place = self.place(base)?;
489                let (index, ty) = self.field_index(&place.ty, name)?;
490                place.path.push(PlaceStep::Field(index));
491                place.ty = ty;
492                place.span = span;
493                Some(place)
494            }
495            Ast::Index { base, index } => {
496                let mut place = self.place(base)?;
497                let index = self.expect(index, &Type::Int);
498                place.ty = match &place.ty {
499                    Type::List(element) => element.as_ref().clone(),
500                    other => {
501                        self.error(
502                            format!("can't index into a `{}`", self.type_name(other)),
503                            place.span,
504                        );
505                        return None;
506                    }
507                };
508                place.path.push(PlaceStep::Index(index));
509                place.span = span;
510                Some(place)
511            }
512            _ => {
513                self.error(
514                    "this can't be changed; only state, variables, fields and list items can",
515                    span,
516                );
517                None
518            }
519        }
520    }
521
522    /// Whether `place` changes state: directly, or through a `for` variable aliasing it.
523    pub(super) fn writes_state(&self, place: &Place) -> bool {
524        match place.root {
525            PlaceRoot::State(_) => true,
526            PlaceRoot::Local(local) => self.body.local(local).kind == LocalKind::Alias,
527        }
528    }
529
530    /// The place a `$place` argument binds, or `None` after reporting why it can't be bound.
531    /// `owner` names what takes the binding in error messages, such as "`checkbox`".
532    /// The place `arg` binds, for `owner`'s parameter `label`, or its positional one.
533    pub(super) fn bound_place(
534        &mut self,
535        owner: &str,
536        label: Option<&str>,
537        arg: Option<&ast::Expr>,
538        ty: &Type,
539        span: Span,
540    ) -> Option<Place> {
541        let Some(ast::Expr {
542            kind:
543                Ast::Unary {
544                    op: UnaryOp::Bind,
545                    operand,
546                },
547            ..
548        }) = arg
549        else {
550            let example =
551                label.map_or_else(|| "$name".to_owned(), |label| format!("{label}: $name"));
552            self.error(
553                format!(
554                    "`{owner}` needs a two-way binding to a `{}`, such as `{example}`",
555                    self.type_name(ty)
556                ),
557                arg.map_or(span, |arg| arg.span),
558            );
559            return None;
560        };
561        let place = self.place(operand)?;
562        if !self.writes_state(&place) {
563            self.error(
564                "only state, or a `for` variable looping over state, can be bound",
565                place.span,
566            );
567            return None;
568        }
569        self.require(&place.ty, ty, place.span);
570        Some(place)
571    }
572
573    /// Records a write to `place`, or reports that a loop's counter can't be written.
574    pub(super) fn note_write(&mut self, place: &Place) {
575        if let PlaceRoot::Local(local) = place.root
576            && self.body.local(local).kind == LocalKind::Counter
577        {
578            self.error(
579                "the loop sets this each time round, so it can't be changed; copy it into a `let` first",
580                place.span,
581            );
582        }
583        if self.writes_state(place) {
584            self.effects.writes = true;
585        }
586        if let PlaceRoot::State(state) = place.root {
587            self.effects.reads.insert(state);
588        }
589    }
590
591    /// A window's name used as a value or called, which only opening and closing it take.
592    fn window_misused(&mut self, name: &str, span: Span) -> Expr {
593        self.error_expr(
594            format!("`{name}` is a window; open it with `{OPEN_WINDOW}({name})`"),
595            span,
596        )
597    }
598
599    pub(super) fn error_expr(&mut self, message: impl Into<String>, span: Span) -> Expr {
600        self.error(message, span);
601        reported(span)
602    }
603
604    /// Checks a required argument, or reports it missing and stands in an error expression.
605    pub(super) fn required_arg(
606        &mut self,
607        arg: Option<&ast::Expr>,
608        ty: &Type,
609        span: Span,
610        missing: impl FnOnce() -> String,
611    ) -> Expr {
612        if let Some(arg) = arg {
613            return self.expect(arg, ty);
614        }
615        self.error(missing(), span);
616        reported(span)
617    }
618}
619
620/// An error expression for a problem that was already reported.
621pub(super) fn reported(span: Span) -> Expr {
622    Expr {
623        kind: ExprKind::Error,
624        ty: Type::Error,
625        span,
626    }
627}