// What type an expression has.
//
// This is not a type checker -- Wax's checker runs afterwards and is the
// authority. It is the smaller thing the lowering needs: enough to pick a
// signage for an operator, to widen a packed field on the way out, and to know
// which record a method call is on. Where it does not know, it says so, and the
// lowering falls back to what Wax would have inferred anyway.

///|
/// The type of an expression, as far as declarations make it knowable.
fn Lowering::type_of(self : Lowering, e : @wap.Node) -> @wap.Type? {
  match e.it {
    Int(_) => None
    Float(_) => Some(F64)
    BoolLit(_) => Some(Bool)
    CharLit(_) => Some(Char)
    StrLit(_) => Some(ArrayOf(U8))
    Null => None
    TupleLit(items) => {
      let ts = []
      for i in items {
        match self.type_of(i) {
          Some(t) => ts.push(t)
          None => return None
        }
      }
      Some(Tuple(ts))
    }
    SetLit(_) => None
    Var(n) =>
      match self.local_type(n) {
        Some(t) => Some(t)
        None =>
          match self.globals.get(self.qualify(n)) {
            Some(t) => Some(t)
            None =>
              match self.member_owner.get(self.qualify(n)) {
                Some(owner) => Some(Named(owner))
                None =>
                  match self.funcs.get(self.qualify(n)) {
                    Some(sig) =>
                      Some(Func(params=sig.params, results=sig.results))
                    None => None
                  }
              }
          }
      }
    Field(recv, name) => {
      // `hashing.limit` is a name in another module, not a field of a local.
      match self.module_ref(recv, name) {
        Some(q) => return self.type_of({ it: Var(q), span: e.span, })
        None => ()
      }
      match self.type_of(recv) {
        Some(t) =>
          match self.record_name(t) {
            Some(r) => self.field_type(r, name)
            None => None
          }
        None => None
      }
    }
    Index(a, _) =>
      match self.type_of(a) {
        Some(t) => self.elem_type(t)
        None => None
      }
    Call(callee, _) =>
      match callee.it {
        Var(n) =>
          match self.funcs.get(self.qualify(n)) {
            Some(sig) => one_result(sig.results)
            None => None
          }
        _ =>
          match self.type_of(callee) {
            Some(Func(results~, ..)) => one_result(results)
            _ => None
          }
      }
    MethodCall(recv, name, _) => {
      if name == "len" {
        return Some(I32)
      }
      match self.type_of(recv) {
        Some(t) =>
          match self.record_name(t) {
            Some(r) =>
              match self.lookup_method(r, name) {
                Some(sig) => one_result(sig.results)
                None => None
              }
            None => None
          }
        None => None
      }
    }
    Bin(op, a, b) =>
      match op {
        Eq | Ne | Lt | Gt | Le | Ge => Some(Bool)
        _ =>
          match self.type_of(a) {
            Some(t) => Some(t)
            None => self.type_of(b)
          }
      }
    Un(Neg, a) => self.type_of(a)
    Un(Not, _) => Some(Bool)
    AndAlso(_, _) | OrElse(_, _) | InSet(_, _) | Test(_, _) => Some(Bool)
    Cast(_, t) => Some(t)
    NonNull(inner) =>
      match self.type_of(inner) {
        Some(Nullable(t)) => Some(t)
        other => other
      }
    RecordLit(typ~, ..) | RecordDefault(typ~) =>
      match typ {
        Some(n) => Some(Named(n))
        None => None
      }
    ArrayLit(typ~, ..) | ArrayRepeat(typ~, ..) =>
      match typ {
        Some(n) => Some(Named(n))
        None => None
      }
    Block(body) => self.type_of_body(body)
    If(arms~, typ~) =>
      match typ {
        Some(t) => Some(t)
        None =>
          if arms.length() == 0 {
            None
          } else {
            self.type_of_body(arms[0].1)
          }
      }
    Match(arms~, typ~, ..) =>
      match typ {
        Some(t) => Some(t)
        None =>
          if arms.length() == 0 {
            None
          } else {
            self.type_of_body(arms[0].body)
          }
      }
    _ => None
  }
}

///|
fn one_result(results : Array[@wap.Type]) -> @wap.Type? {
  if results.length() == 1 {
    Some(results[0])
  } else {
    None
  }
}

///|
fn Lowering::type_of_body(
  self : Lowering,
  body : Array[@wap.Node],
) -> @wap.Type? {
  if body.length() == 0 {
    None
  } else {
    self.type_of(body[body.length() - 1])
  }
}

///|
/// A method on a record, searching its ancestors.
fn Lowering::lookup_method(
  self : Lowering,
  record : String,
  name : String,
) -> FnSig? {
  match self.methods.get(self.qualify(record) + "." + name) {
    Some(s) => Some(s)
    None =>
      match self.resolve(record) {
        Some(Record(parent=Some(p), ..)) => self.lookup_method(p, name)
        _ => None
      }
  }
}