use yog_ids::script_problem;
use super::{Context, DefKind, Resolver, lifetime_key};
use crate::ast::{
Arg, Element, Expr, ExprKind, IT, LambdaBody, LambdaParam, Name, Pass, StrSeg, Type, TypeKind,
UnOp,
};
use crate::words::r#static;
impl Resolver<'_, '_> {
pub(super) fn opt_expr(&mut self, e: Option<&Expr>) {
if let Some(e) = e {
self.expr(e);
}
}
pub(super) fn expr(&mut self, e: &Expr) {
match &e.kind {
ExprKind::Ident(name) => self.name_use(name, e),
ExprKind::This if !self.cx.member => self.report.error(
script_problem::THIS_OUTSIDE_MEMBER,
e.span,
"`this` is the object in a type's, a content's, a component's or a patch's members",
),
ExprKind::Base if !self.cx.member => self.report.error(
script_problem::THIS_OUTSIDE_MEMBER,
e.span,
"`base` is the object as its base class, in a class's members",
),
ExprKind::TypeOf(ty) => self.ty(ty),
ExprKind::Macro { args, .. } => args.iter().for_each(|a| self.expr(a)),
ExprKind::Declare { ty, name, mutable } => {
self.opt_ty(ty.as_ref());
self.bind(name, DefKind::local(*mutable));
}
ExprKind::NameOf(expr) | ExprKind::Move(expr) => self.expr(expr),
ExprKind::Default(ty) => self.opt_ty(ty.as_ref()),
ExprKind::Str(segs) => {
for s in segs {
if let StrSeg::Expr(e) = s {
self.expr(e);
}
}
}
ExprKind::Tuple(items) => items.iter().for_each(|i| self.expr(i)),
ExprKind::Collection(items) => items.iter().for_each(|i| match i {
Element::Item(e) | Element::Spread(e) => self.expr(e),
Element::Entry(k, v) => {
self.expr(k);
self.expr(v);
}
}),
ExprKind::New { ty, args, init } => {
self.opt_ty(ty.as_ref());
args.iter().flatten().for_each(|a| self.expr(&a.value));
init.iter().for_each(|(_, v)| self.expr(v));
}
ExprKind::Lambda { params, body } => self.lambda(params, body),
ExprKind::Unary { op, expr } => {
if *op == UnOp::Await && !self.cx.awaits {
self.report.error(
script_problem::AWAIT_OUTSIDE_ASYNC,
e.span,
"`await` waits in an `async` method: `async void Arm() { await Task.Delay(5s); }`",
);
}
self.expr(expr);
}
_ => self.compound(e),
}
}
fn arg(&mut self, a: &Arg) {
match a.pass {
Some(Pass::Ref | Pass::Out) => self.assigned(&a.value),
_ => self.expr(&a.value),
}
}
fn name_use(&mut self, name: &str, e: &Expr) {
if self.refer(name, e.span).is_some() || name != IT {
return;
}
if !self.cx.it {
self.report.error(
script_problem::IT_OUTSIDE_LAMBDA,
e.span,
"`it` is a block lambda's argument: `list.Select { it * 2 }`",
);
}
}
fn compound(&mut self, e: &Expr) {
match &e.kind {
ExprKind::Cast { ty, expr }
| ExprKind::As { expr, ty }
| ExprKind::Has { expr, ty } => {
self.ty(ty);
self.expr(expr);
}
ExprKind::Binary {
left: a, right: b, ..
}
| ExprKind::Range { from: a, to: b, .. } => {
self.expr(a);
self.expr(b);
}
ExprKind::Is { expr, pattern } => {
self.expr(expr);
self.bind_pattern(pattern);
}
ExprKind::Conditional {
cond,
then,
otherwise,
} => {
self.expr(cond);
self.expr(then);
self.expr(otherwise);
}
ExprKind::Switch { expr, arms } => {
self.expr(expr);
for a in arms {
self.scoped(|r| {
r.bind_pattern(&a.pattern);
r.opt_expr(a.guard.as_ref());
r.expr(&a.value);
});
}
}
ExprKind::Call { callee, args } => {
self.expr(callee);
args.iter().for_each(|a| self.arg(a));
}
ExprKind::Index { expr, index } => {
self.expr(expr);
self.expr(index);
}
ExprKind::TypeArgs { expr, args } => {
self.expr(expr);
args.iter().for_each(|t| self.ty(t));
}
ExprKind::Member { expr, .. } | ExprKind::NotNull(expr) => self.expr(expr),
_ => {}
}
}
fn lambda(&mut self, params: &[LambdaParam], body: &LambdaBody) {
let cx = Context {
loops: 0,
it: params.is_empty() || self.cx.it,
before: false,
..self.cx
};
self.within(cx, |r| {
r.scoped(|r| {
for p in params {
r.opt_ty(p.ty.as_ref());
r.bind(&p.name, DefKind::Param);
}
match body {
LambdaBody::Expr(e) => r.expr(e),
LambdaBody::Block(stmts) => r.stmts(stmts),
}
});
});
}
pub(super) fn opt_ty(&mut self, t: Option<&Type>) {
if let Some(t) = t {
self.ty(t);
}
}
pub(super) fn ty(&mut self, t: &Type) {
match &t.kind {
TypeKind::Path { path, args } => {
if let Some(first) = path.first() {
self.refer(&first.text, first.span);
}
args.iter().for_each(|a| self.ty(a));
}
TypeKind::Tuple(items) => items.iter().for_each(|(t, _)| self.ty(t)),
TypeKind::Nullable(inner) => self.ty(inner),
TypeKind::Both(all) => all.iter().for_each(|t| self.ty(t)),
TypeKind::Borrow { lifetime, ty, .. } => {
if let Some(l) = lifetime {
self.lifetime(l);
}
self.ty(ty);
}
TypeKind::Lifetime(l) => self.lifetime(l),
}
}
pub(super) fn lifetime(&mut self, l: &Name) {
if l.text == r#static::WORD.text || self.refer(&lifetime_key(&l.text), l.span).is_some() {
return;
}
self.report.error(
script_problem::LIFETIME,
l.span,
format!("no `<'{}>` declares this lifetime", l.text),
);
}
}