1use 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 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 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 ¶ms {
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 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 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 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 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 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 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 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
620pub(super) fn reported(span: Span) -> Expr {
622 Expr {
623 kind: ExprKind::Error,
624 ty: Type::Error,
625 span,
626 }
627}