use std::cell::Cell;
use std::collections::HashMap;
use std::fmt::Debug;
use crate::common::errors::*;
use crate::common::score::{NodeMaker, NodeStorage, Result};
use crate::common::source::{Span, Spanned, INVALID_SPAN};
use crate::common::{NodeId, Verbosity};
use crate::hir;
use crate::konst::*;
use crate::lazy::LazyNode;
use crate::score::*;
use crate::ty::*;
pub struct TypeckContext<'sbc, 'lazy: 'sbc, 'sb: 'lazy, 'ast: 'sb, 'ctx: 'sb> {
    
    pub ctx: &'sbc ScoreContext<'lazy, 'sb, 'ast, 'ctx>,
    
    failed: Cell<bool>,
}
impl<'sbc, 'lazy, 'sb, 'ast, 'ctx> TypeckContext<'sbc, 'lazy, 'sb, 'ast, 'ctx> {
    
    pub fn new(
        ctx: &'sbc ScoreContext<'lazy, 'sb, 'ast, 'ctx>,
    ) -> TypeckContext<'sbc, 'lazy, 'sb, 'ast, 'ctx> {
        TypeckContext {
            ctx: ctx,
            failed: Cell::new(false),
        }
    }
    
    pub fn finish(self) -> bool {
        !self.failed.get()
    }
    
    pub fn emit(&self, diag: DiagBuilder2) {
        if diag.severity >= Severity::Error {
            self.failed.set(true);
        }
        self.ctx.sess.emit(diag)
    }
    
    
    
    
    pub fn lazy_typeck<I>(&self, id: I)
    where
        I: Into<NodeId>,
    {
        let id = id.into();
        
        if let Some(&node) = self.ctx.sb.typeck_table.borrow().get(&id) {
            if node.is_err() {
                self.failed.set(true);
            }
            return;
        }
        
        
        let task = self.ctx.lazy.typeck.borrow_mut().set(id, LazyNode::Running);
        let result = match task {
            Some(LazyNode::Pending(f)) => f(self),
            Some(LazyNode::Running) => {
                self.ctx.bug(id, format!("recursion on typeck of {:?}", id));
                Err(())
            }
            None => {
                self.ctx
                    .bug(id, format!("no typeck scheduled for {:?}", id));
                Err(())
            }
        };
        if result.is_err() {
            self.failed.set(true);
        }
        self.ctx.sb.typeck_table.borrow_mut().insert(id, result);
    }
    
    
    
    
    
    pub fn lazy_typeval<I>(&self, id: I) -> Result<&'ctx Ty>
    where
        I: Into<NodeId>,
    {
        let id = id.into();
        
        if let Some(&node) = self.ctx.sb.typeval_table.borrow().get(&id) {
            return node;
        }
        
        
        let task = self
            .ctx
            .lazy
            .typeval
            .borrow_mut()
            .set(id, LazyNode::Running);
        let result = match task {
            Some(LazyNode::Pending(f)) => f(self),
            Some(LazyNode::Running) => {
                self.ctx
                    .bug(id, format!("recursion on typeval of {:?}", id));
                Err(())
            }
            None => {
                self.ctx
                    .bug(id, format!("no typeval scheduled for {:?}", id));
                Err(())
            }
        };
        if result.is_err() {
            self.failed.set(true);
        }
        
        
        if self.ctx.sess.opts.verbosity.contains(Verbosity::TYPES) {
            match (result, self.ctx.span(id)) {
                (Ok(ty), Some(span)) => {
                    self.emit(
                        DiagBuilder2::note(format!("type of `{}` is {}", span.extract(), ty))
                            .span(span),
                    );
                }
                _ => (),
            }
        }
        self.ctx.sb.typeval_table.borrow_mut().insert(id, result);
        result
    }
    
    pub fn must_match(&self, exp: &'ctx Ty, act: &'ctx Ty, span: Span) -> bool {
        assert!(span != INVALID_SPAN);
        if self.ctx.sess.opts.verbosity.contains(Verbosity::TYPECK) {
            self.emit(
                DiagBuilder2::note(format!("typeck expected {} and actual {}", exp, act))
                    .span(span),
            );
        }
        if exp == act {
            return true;
        }
        let (exp_flat, act_flat) = match (
            self.ctx.deref_named_type(exp),
            self.ctx.deref_named_type(act),
        ) {
            (Ok(e), Ok(a)) => (e, a),
            _ => return false,
        };
        match (exp_flat, act_flat) {
            (e, a) if e == a => return true,
            
            (&Ty::Int(..), &Ty::UniversalInt) => return true,
            _ => (),
        }
        self.emit(
            DiagBuilder2::error(format!(
                "expected type {}, but `{}` has type {}",
                exp,
                span.extract(),
                act
            ))
            .span(span)
            .add_note(format!("expected type: {}", exp_flat))
            .add_note(format!("  actual type: {}", act_flat)),
        );
        false
    }
    
    pub fn must_cast(&self, into: &'ctx Ty, from: &'ctx Ty, span: Span) -> bool {
        self.must_match(into, from, span)
    }
    
    pub fn typeck_delay_mechanism(&self, _node: &'ctx hir::DelayMechanism) {
        
    }
    
    pub fn typeck_waveform(&self, node: &'ctx hir::Waveform, exp: &'ctx Ty) {
        for elem in node {
            self.typeck_wave_elem(elem, exp);
        }
    }
    
    pub fn typeck_wave_elem(&self, node: &'ctx hir::WaveElem, _exp: &'ctx Ty) {
        if let Some(_value) = node.value {
            
            
            
            
        }
        if let Some(_after) = node.after {
            
            
        }
    }
    
    pub fn typeck_subprog_spec(&self, node: &'ctx hir::SubprogSpec) {
        self.typeck_slice(&node.generics);
        
        self.typeck_slice(&node.params);
        if let Some(ref ty) = node.return_type {
            self.typeck(ty.value);
        }
    }
    
    pub fn typeck_node<I>(&self, id: I, exp: &'ctx Ty)
    where
        I: 'ctx + Copy + Debug + Into<NodeId>,
        ScoreContext<'lazy, 'sb, 'ast, 'ctx>: NodeMaker<I, &'ctx Ty>,
    {
        if let Ok(act) = self.ctx.ty(id) {
            if act != exp {
                
                self.emit(DiagBuilder2::error(format!(
                    "typecheck failed, expected {:?}, got {:?}",
                    exp, act
                )));
            }
        } else {
            self.failed.set(true);
        }
    }
    
    pub fn typeck_slice<T, I>(&self, ids: T)
    where
        T: AsRef<[I]>,
        I: Copy,
        TypeckContext<'sbc, 'lazy, 'sb, 'ast, 'ctx>: Typeck<I>,
    {
        for &id in ids.as_ref() {
            self.typeck(id);
        }
    }
    
    pub fn apply_range_constraint(&self, ty: &Ty, con: Spanned<&hir::Range>) -> Result<&'ctx Ty> {
        
        let (dir, lb, rb) = match *con.value {
            hir::Range::Immediate(dir, lb, rb) => (dir, lb, rb),
        };
        let lb = self.ctx.const_value(lb)?;
        let rb = self.ctx.const_value(rb)?;
        
        let ty = self.ctx.deref_named_type(ty)?;
        match *ty {
            Ty::Int(ref ty) => {
                
                let (lb, rb) = match (lb, rb) {
                    (&Const::Int(ref lb), &Const::Int(ref rb)) => (lb, rb),
                    _ => {
                        self.emit(
                            DiagBuilder2::error(format!(
                                "non-integer range `{} {} {}` cannot constrain an integer type",
                                lb, dir, rb
                            ))
                            .span(con.span),
                        );
                        return Err(());
                    }
                };
                
                if ty.dir != dir || ty.left_bound > lb.value || ty.right_bound < rb.value {
                    self.emit(
                        DiagBuilder2::error(format!(
                            "`{} {} {}` is not a subrange of `{}`",
                            lb, dir, rb, ty
                        ))
                        .span(con.span),
                    );
                    return Err(());
                }
                
                Ok(self
                    .ctx
                    .intern_ty(IntTy::new(ty.dir, lb.value.clone(), rb.value.clone()).maybe_null()))
            }
            
            _ => {
                self.emit(
                    DiagBuilder2::error(format!(
                        "{} cannot be constrained by range",
                        ty.kind_desc()
                    ))
                    .span(con.span),
                );
                return Err(());
            }
        }
    }
    
    pub fn apply_array_constraint(
        &self,
        ty: &'ctx Ty,
        con: Spanned<&hir::ArrayConstraint>,
    ) -> Result<&'ctx Ty> {
        
        let ty = self.ctx.deref_named_type(ty)?;
        match *ty {
            Ty::Array(ref ty) => {
                let indices = if !con.value.index.is_empty() {
                    if con.value.index.len() != ty.indices.len() {
                        self.emit(
                            DiagBuilder2::error(format!(
                                "constrained {} indices, but array has {}",
                                con.value.index.len(),
                                ty.indices.len()
                            ))
                            .span(con.span)
                            .add_note(format!(
                                "`{}` constrained with `{}`",
                                ty,
                                con.span.extract()
                            )),
                        );
                        return Err(());
                    }
                    ty.indices
                        .iter()
                        .zip(con.value.index.iter())
                        .map(|(ty, con)| self.apply_index_constraint(ty, con.as_ref()))
                        .collect::<Result<Vec<_>>>()?
                } else {
                    ty.indices.clone()
                };
                let element = if let Some(ref elem_con) = con.value.elem {
                    match elem_con.value {
                        hir::ElementConstraint::Array(ref ac) => self.apply_array_constraint(
                            &*ty.element,
                            Spanned::new(ac, elem_con.span),
                        )?,
                        hir::ElementConstraint::Record(ref rc) => self.apply_record_constraint(
                            &*ty.element,
                            Spanned::new(rc, elem_con.span),
                        )?,
                    }
                } else {
                    &*ty.element
                };
                Ok(self
                    .ctx
                    .intern_ty(ArrayTy::new(indices, Box::new(element.clone()))))
            }
            _ => {
                self.emit(
                    DiagBuilder2::error(format!(
                        "array constraint `{}` does not apply to {}",
                        con.span.extract(),
                        ty.kind_desc()
                    ))
                    .span(con.span),
                );
                return Err(());
            }
        }
    }
    
    pub fn apply_record_constraint(
        &self,
        ty: &'ctx Ty,
        con: Spanned<&hir::RecordConstraint>,
    ) -> Result<&'ctx Ty> {
        use moore_common::name::Name;
        
        let ty = self.ctx.deref_named_type(ty)?;
        match *ty {
            Ty::Record(ref ty) => {
                let mut fields: Vec<(Name, &Ty)> = ty
                    .fields
                    .iter()
                    .map(|&(name, ref ty)| (name, ty.as_ref()))
                    .collect();
                let mut had_fails = false;
                for &(name, ref con) in &con.value.elems {
                    
                    let idx = match ty.lookup.get(&name.value) {
                        Some(&idx) => idx,
                        None => {
                            self.emit(
                                DiagBuilder2::error(format!(
                                    "record has no element `{}`",
                                    name.value
                                ))
                                .span(name.span)
                                .add_note(format!("{}", ty)),
                            );
                            had_fails = true;
                            continue;
                        }
                    };
                    
                    fields[idx].1 = match con.value {
                        hir::ElementConstraint::Array(ref ac) => {
                            self.apply_array_constraint(&fields[idx].1, Spanned::new(ac, con.span))?
                        }
                        hir::ElementConstraint::Record(ref rc) => self
                            .apply_record_constraint(&fields[idx].1, Spanned::new(rc, con.span))?,
                    };
                }
                if had_fails {
                    return Err(());
                }
                let fields = fields
                    .into_iter()
                    .map(|(name, ty)| (name, Box::new(ty.clone())))
                    .collect();
                Ok(self.ctx.intern_ty(RecordTy::new(fields)))
            }
            _ => {
                self.emit(
                    DiagBuilder2::error(format!(
                        "array constraint `{}` does not apply to {}",
                        con.span.extract(),
                        ty.kind_desc()
                    ))
                    .span(con.span),
                );
                return Err(());
            }
        }
    }
    
    pub fn apply_index_constraint(
        &self,
        index: &'ctx ArrayIndex,
        con: Spanned<&hir::DiscreteRange>,
    ) -> Result<ArrayIndex> {
        
        let con_ty = Spanned::new(
            self.ctx
                .deref_named_type(self.type_from_discrete_range(con)?)?,
            con.span,
        );
        
        let index_ty = match *index {
            ArrayIndex::Unbounded(ref ty) | ArrayIndex::Constrained(ref ty) => {
                self.apply_subtype(&*ty, con_ty)?
            }
        };
        Ok(ArrayIndex::Constrained(Box::new(index_ty.clone())))
    }
    
    pub fn apply_subtype(&self, orig_ty: &'ctx Ty, subty: Spanned<&Ty>) -> Result<&'ctx Ty> {
        let deref = self.ctx.deref_named_type(orig_ty)?;
        let span = subty.span;
        match (deref, self.ctx.deref_named_type(subty.value)?) {
            (&Ty::Int(ref ty), &Ty::Int(ref subty)) => {
                use std::cmp::{max, min};
                if ty.dir != subty.dir {
                    self.emit(
                        DiagBuilder2::error(format!(
                            "directions disagree; `{}` and `{}`",
                            subty, ty
                        ))
                        .span(span),
                    );
                    return Err(());
                }
                let (ty_lo, ty_hi, subty_lo, subty_hi) = match ty.dir {
                    Dir::To => (
                        &ty.left_bound,
                        &ty.right_bound,
                        &subty.left_bound,
                        &subty.right_bound,
                    ),
                    Dir::Downto => (
                        &ty.right_bound,
                        &ty.left_bound,
                        &subty.right_bound,
                        &subty.left_bound,
                    ),
                };
                if ty_lo > subty_lo || ty_hi < subty_hi {
                    self.emit(
                        DiagBuilder2::error(format!("`{}` is not a subrange of `{}`", subty, ty))
                            .span(span)
                            .add_note(
                                "The range of a subtype must be entirely \
                                contained within the range of the target type.",
                            ), 
                    );
                }
                let lo = max(ty_lo, subty_lo);
                let hi = min(ty_hi, subty_hi);
                let (lb, rb) = match ty.dir {
                    Dir::To => (lo, hi),
                    Dir::Downto => (hi, lo),
                };
                let new_ty: Ty = IntTy::new(ty.dir, lb.clone(), rb.clone()).into();
                if &new_ty == deref {
                    Ok(orig_ty)
                } else {
                    Ok(self.ctx.intern_ty(new_ty))
                }
            }
            _ => {
                self.emit(
                    DiagBuilder2::error(format!(
                        "`{}` is not a subtype of `{}`",
                        subty.span.extract(),
                        orig_ty
                    ))
                    .span(span),
                );
                return Err(());
            }
        }
    }
    
    pub fn type_from_discrete_range(
        &self,
        range: Spanned<&hir::DiscreteRange>,
    ) -> Result<&'ctx Ty> {
        match *range.value {
            hir::DiscreteRange::Subtype(id) => self.ctx.ty(id),
            hir::DiscreteRange::Range(ref r) => self.type_from_range(Spanned::new(r, range.span)),
        }
    }
    
    pub fn type_from_range(&self, range: Spanned<&hir::Range>) -> Result<&'ctx Ty> {
        match *range.value {
            hir::Range::Immediate(dir, lb, rb) => {
                let lb = self.ctx.const_value(lb)?;
                let rb = self.ctx.const_value(rb)?;
                match (lb, rb) {
                    (&Const::Int(ref lb), &Const::Int(ref rb)) => Ok(self
                        .ctx
                        .intern_ty(IntTy::new(dir, lb.value.clone(), rb.value.clone()))),
                    _ => {
                        self.emit(
                            DiagBuilder2::error(format!(
                                "`{} {} {}` is not a valid range",
                                lb, dir, rb
                            ))
                            .span(range.span),
                        );
                        return Err(());
                    }
                }
            }
        }
    }
}
use crate::ty2::RangeDir;
impl From<Dir> for RangeDir {
    fn from(d: Dir) -> RangeDir {
        match d {
            Dir::To => RangeDir::To,
            Dir::Downto => RangeDir::Downto,
        }
    }
}
pub trait Typeck<I> {
    fn typeck(&self, id: I);
}
macro_rules! impl_typeck {
    ($slf:tt, $id:ident: $id_ty:ty => $blk:block) => {
        impl<'sbc, 'lazy, 'sb, 'ast, 'ctx> Typeck<$id_ty> for TypeckContext<'sbc, 'lazy, 'sb, 'ast, 'ctx> {
            fn typeck(&$slf, $id: $id_ty) $blk
        }
    }
}
macro_rules! impl_typeck_err {
    ($slf:tt, $id:ident: $id_ty:ty => $blk:block) => {
        impl<'sbc, 'lazy, 'sb, 'ast, 'ctx> Typeck<$id_ty> for TypeckContext<'sbc, 'lazy, 'sb, 'ast, 'ctx> {
            fn typeck(&$slf, $id: $id_ty) {
                use std;
                let res = (move || -> Result<()> { $blk })();
                std::mem::forget(res);
            }
        }
    }
}
impl<'sbc, 'lazy: 'sbc, 'sb: 'lazy, 'ast: 'sb, 'ctx: 'sb, I> Typeck<I>
    for TypeckContext<'sbc, 'lazy, 'sb, 'ast, 'ctx>
where
    ScoreContext<'lazy, 'sb, 'ast, 'ctx>: NodeMaker<I, &'ctx Ty>,
{
    fn typeck(&self, id: I) {
        match ScoreContext::make(self.ctx, id) {
            Ok(_) => (),
            Err(()) => self.failed.set(true),
        }
    }
}
pub trait TypeckNode<'ctx, I> {
    fn typeck_node(&self, id: I, expected: &'ctx Ty) -> Result<()>;
}
impl<'lazy, 'sb, 'ast, 'ctx, I> TypeckNode<'ctx, I> for ScoreContext<'lazy, 'sb, 'ast, 'ctx>
where
    ScoreContext<'lazy, 'sb, 'ast, 'ctx>: NodeMaker<I, &'ctx Ty>,
{
    fn typeck_node(&self, id: I, expected: &'ctx Ty) -> Result<()> {
        let actual = self.make(id)?;
        if actual != expected {
            self.emit(DiagBuilder2::error(format!(
                "typecheck failed, expected {:?}, got {:?}",
                expected, actual
            )));
            Err(())
        } else {
            Ok(())
        }
    }
}
macro_rules! unimp {
    ($slf:tt, $id:expr) => {{
        $slf.emit(DiagBuilder2::bug(format!(
            "typeck of {:?} not implemented",
            $id
        )));
        return;
    }};
}
macro_rules! unimp_err {
    ($slf:tt, $id:expr) => {{
        $slf.emit(DiagBuilder2::bug(format!(
            "typeck of {:?} not implemented",
            $id
        )));
        return Err(());
    }};
}
macro_rules! unimpmsg {
    ($slf:tt, $span:expr, $msg:expr) => {{
        $slf.emit(DiagBuilder2::bug(format!("{} not implemented", $msg)).span($span));
        return Err(());
    }};
}
impl_typeck_err!(self, id: LibRef => {
    let hir = self.ctx.hir(id)?;
    self.typeck_slice(&hir.pkg_decls);
    self.typeck_slice(&hir.pkg_insts);
    self.typeck_slice(&hir.pkg_bodies);
    self.typeck_slice(&hir.ctxs);
    self.typeck_slice(&hir.entities);
    self.typeck_slice(&hir.archs);
    self.typeck_slice(&hir.cfgs);
    Ok(())
});
impl_typeck_err!(self, id: PkgDeclRef => {
    let hir = self.ctx.hir(id)?;
    self.typeck_slice(&hir.generics);
    self.typeck_slice(&hir.decls);
    Ok(())
});
impl_typeck_err!(self, id: PkgBodyRef => {
    let hir = self.ctx.hir(id)?;
    self.typeck_slice(&hir.decls);
    Ok(())
});
impl_typeck_err!(self, id: PkgInstRef => {
    let _hir = self.ctx.hir(id)?;
    
    Ok(())
});
impl_typeck!(self, id: CtxRef => {
    unimp!(self, id)
});
impl_typeck!(self, id: CfgRef => {
    unimp!(self, id)
});
impl_typeck_err!(self, id: EntityRef => {
    let hir = self.ctx.hir(id)?;
    for &generic in &hir.generics {
        self.typeck(generic);
    }
    for &port in &hir.ports {
        self.typeck(port);
    }
    Ok(())
});
impl_typeck_err!(self, id: ArchRef => {
    let hir = self.ctx.hir(id)?;
    self.typeck(hir.entity);
    for &decl in &hir.decls {
        self.typeck(decl);
    }
    for &stmt in &hir.stmts {
        self.typeck(stmt);
    }
    Ok(())
});
impl_typeck!(self, id: GenericRef => {
    match id {
        GenericRef::Type(id)    => self.typeck(id),
        GenericRef::Subprog(id) => self.typeck(id),
        GenericRef::Pkg(id)     => self.typeck(id),
        GenericRef::Const(id)   => self.typeck(id),
    }
});
impl_typeck!(self, id: IntfTypeRef => {
    unimp!(self, id)
    
});
impl_typeck!(self, id: IntfSubprogRef => {
    unimp!(self, id)
    
});
impl_typeck!(self, id: IntfPkgRef => {
    unimp!(self, id)
    
});
impl_make!(self, id: IntfConstRef => &Ty {
    
    unimp_err!(self, id)
});
impl_make!(self, id: IntfVarRef => &Ty {
    
    unimp_err!(self, id)
});
impl_make!(self, id: IntfSignalRef => &Ty {
    let hir = self.hir(id)?;
    self.ty(hir.ty)
});
impl_make!(self, id: IntfFileRef => &Ty {
    
    unimp_err!(self, id)
});
impl_typeck!(self, id: DeclInPkgRef => {
    match id {
        DeclInPkgRef::Subprog(id)     => self.typeck(id),
        DeclInPkgRef::SubprogInst(id) => self.typeck(id),
        DeclInPkgRef::Pkg(id)         => self.typeck(id),
        DeclInPkgRef::PkgInst(id)     => self.typeck(id),
        DeclInPkgRef::Type(id)        => self.typeck(id),
        DeclInPkgRef::Subtype(id)     => self.typeck(id),
        DeclInPkgRef::Const(id)       => self.typeck(id),
        DeclInPkgRef::Signal(id)      => self.typeck(id),
        DeclInPkgRef::Var(id)         => self.typeck(id),
        DeclInPkgRef::File(id)        => self.typeck(id),
        DeclInPkgRef::Alias(id)       => self.typeck(id),
        DeclInPkgRef::Comp(id)        => self.typeck(id),
        DeclInPkgRef::Attr(id)        => self.typeck(id),
        DeclInPkgRef::AttrSpec(id)    => self.typeck(id),
        DeclInPkgRef::Discon(id)      => self.typeck(id),
        DeclInPkgRef::GroupTemp(id)   => self.typeck(id),
        DeclInPkgRef::Group(id)       => self.typeck(id),
    }
});
impl_typeck!(self, id: DeclInPkgBodyRef => {
    match id {
        DeclInPkgBodyRef::Subprog(id)     => self.typeck(id),
        DeclInPkgBodyRef::SubprogBody(id) => self.typeck(id),
        DeclInPkgBodyRef::SubprogInst(id) => self.typeck(id),
        DeclInPkgBodyRef::Pkg(id)         => self.typeck(id),
        DeclInPkgBodyRef::PkgBody(id)     => self.typeck(id),
        DeclInPkgBodyRef::PkgInst(id)     => self.typeck(id),
        DeclInPkgBodyRef::Type(id)        => self.typeck(id),
        DeclInPkgBodyRef::Subtype(id)     => self.typeck(id),
        DeclInPkgBodyRef::Const(id)       => self.typeck(id),
        DeclInPkgBodyRef::Var(id)         => self.typeck(id),
        DeclInPkgBodyRef::File(id)        => self.typeck(id),
        DeclInPkgBodyRef::Alias(id)       => self.typeck(id),
        DeclInPkgBodyRef::Attr(id)        => self.typeck(id),
        DeclInPkgBodyRef::AttrSpec(id)    => self.typeck(id),
        DeclInPkgBodyRef::GroupTemp(id)   => self.typeck(id),
        DeclInPkgBodyRef::Group(id)       => self.typeck(id),
    }
});
impl_typeck!(self, id: DeclInSubprogRef => {
    match id {
        DeclInSubprogRef::Subprog(id)     => self.typeck(id),
        DeclInSubprogRef::SubprogBody(id) => self.typeck(id),
        DeclInSubprogRef::SubprogInst(id) => self.typeck(id),
        DeclInSubprogRef::Pkg(id)         => self.typeck(id),
        DeclInSubprogRef::PkgBody(id)     => self.typeck(id),
        DeclInSubprogRef::PkgInst(id)     => self.typeck(id),
        DeclInSubprogRef::Type(id)        => self.typeck(id),
        DeclInSubprogRef::Subtype(id)     => self.typeck(id),
        DeclInSubprogRef::Const(id)       => self.typeck(id),
        DeclInSubprogRef::Var(id)         => self.typeck(id),
        DeclInSubprogRef::File(id)        => self.typeck(id),
        DeclInSubprogRef::Alias(id)       => self.typeck(id),
        DeclInSubprogRef::Attr(id)        => self.typeck(id),
        DeclInSubprogRef::AttrSpec(id)    => self.typeck(id),
        DeclInSubprogRef::GroupTemp(id)   => self.typeck(id),
        DeclInSubprogRef::Group(id)       => self.typeck(id),
    }
});
impl_typeck!(self, id: DeclInBlockRef => {
    match id {
        DeclInBlockRef::Subprog(id)     => self.typeck(id),
        DeclInBlockRef::SubprogBody(id) => self.typeck(id),
        DeclInBlockRef::SubprogInst(id) => self.typeck(id),
        DeclInBlockRef::Pkg(id)         => self.typeck(id),
        DeclInBlockRef::PkgBody(id)     => self.typeck(id),
        DeclInBlockRef::PkgInst(id)     => self.typeck(id),
        DeclInBlockRef::Type(id)        => self.typeck(id),
        DeclInBlockRef::Subtype(id)     => self.typeck(id),
        DeclInBlockRef::Const(id)       => self.typeck(id),
        DeclInBlockRef::Signal(id)      => self.typeck(id),
        DeclInBlockRef::Var(id)         => self.typeck(id),
        DeclInBlockRef::File(id)        => self.typeck(id),
        DeclInBlockRef::Alias(id)       => self.typeck(id),
        DeclInBlockRef::Comp(id)        => self.typeck(id),
        DeclInBlockRef::Attr(id)        => self.typeck(id),
        DeclInBlockRef::AttrSpec(id)    => self.typeck(id),
        DeclInBlockRef::CfgSpec(id)     => self.typeck(id),
        DeclInBlockRef::Discon(id)      => self.typeck(id),
        DeclInBlockRef::GroupTemp(id)   => self.typeck(id),
        DeclInBlockRef::Group(id)       => self.typeck(id),
    }
});
impl_typeck!(self, id: DeclInProcRef => {
    match id {
        DeclInProcRef::Subprog(id)     => self.typeck(id),
        DeclInProcRef::SubprogBody(id) => self.typeck(id),
        DeclInProcRef::SubprogInst(id) => self.typeck(id),
        DeclInProcRef::Pkg(id)         => self.typeck(id),
        DeclInProcRef::PkgBody(id)     => self.typeck(id),
        DeclInProcRef::PkgInst(id)     => self.typeck(id),
        DeclInProcRef::Type(id)        => self.typeck(id),
        DeclInProcRef::Subtype(id)     => self.typeck(id),
        DeclInProcRef::Const(id)       => self.typeck(id),
        DeclInProcRef::Var(id)         => self.typeck(id),
        DeclInProcRef::File(id)        => self.typeck(id),
        DeclInProcRef::Alias(id)       => self.typeck(id),
        DeclInProcRef::Attr(id)        => self.typeck(id),
        DeclInProcRef::AttrSpec(id)    => self.typeck(id),
        DeclInProcRef::GroupTemp(id)   => self.typeck(id),
        DeclInProcRef::Group(id)       => self.typeck(id),
    }
});
impl_typeck!(self, id: ConcStmtRef => {
    match id {
        ConcStmtRef::Block(id)         => self.typeck(id),
        ConcStmtRef::Process(id)       => self.typeck(id),
        ConcStmtRef::ConcProcCall(id)  => self.typeck(id),
        ConcStmtRef::ConcAssert(id)    => self.typeck(id),
        ConcStmtRef::ConcSigAssign(id) => self.typeck(id),
        ConcStmtRef::CompInst(id)      => self.typeck(id),
        ConcStmtRef::ForGen(id)        => self.typeck(id),
        ConcStmtRef::IfGen(id)         => self.typeck(id),
        ConcStmtRef::CaseGen(id)       => self.typeck(id),
    }
});
impl_typeck!(self, id: SeqStmtRef => {
    match id {
        SeqStmtRef::Wait(id)      => self.lazy_typeck(id),
        SeqStmtRef::Assert(id)    => self.lazy_typeck(id),
        SeqStmtRef::Report(id)    => self.lazy_typeck(id),
        SeqStmtRef::SigAssign(id) => self.lazy_typeck(id),
        SeqStmtRef::VarAssign(id) => self.lazy_typeck(id),
        SeqStmtRef::ProcCall(id)  => self.lazy_typeck(id),
        SeqStmtRef::If(id)        => self.lazy_typeck(id),
        SeqStmtRef::Case(id)      => self.lazy_typeck(id),
        SeqStmtRef::Loop(id)      => self.lazy_typeck(id),
        SeqStmtRef::Nexit(id)     => self.lazy_typeck(id),
        SeqStmtRef::Return(id)    => self.lazy_typeck(id),
        SeqStmtRef::Null(id)      => self.lazy_typeck(id),
    }
});
impl_typeck_err!(self, id: SubprogDeclRef => {
    let hir = self.ctx.hir(id)?;
    self.typeck_subprog_spec(&hir.spec);
    Ok(())
});
impl_typeck_err!(self, id: SubprogBodyRef => {
    let hir = self.ctx.hir(id)?;
    self.typeck_subprog_spec(&hir.spec);
    self.typeck_slice(&hir.decls);
    self.typeck_slice(&hir.stmts);
    Ok(())
});
impl_typeck_err!(self, id: SubprogInstRef => {
    let _hir = self.ctx.hir(id)?;
    
    Ok(())
});
impl_typeck_err!(self, id: ConstDeclRef => {
    self.ctx.lazy_typeval(id)?;
    Ok(())
});
impl_typeck_err!(self, id: SignalDeclRef => {
    self.ctx.lazy_typeval(id)?;
    Ok(())
});
impl_typeck_err!(self, id: VarDeclRef => {
    self.ctx.lazy_typeval(id)?;
    Ok(())
});
impl_typeck_err!(self, id: FileDeclRef => {
    self.ctx.lazy_typeval(id)?;
    Ok(())
});
impl_typeck!(self, id: AliasDeclRef => {
    unimp!(self, id)
});
impl_typeck!(self, id: CompDeclRef => {
    unimp!(self, id)
});
impl_typeck!(self, id: AttrDeclRef => {
    unimp!(self, id)
});
impl_typeck!(self, id: AttrSpecRef => {
    unimp!(self, id)
});
impl_typeck!(self, id: CfgSpecRef => {
    unimp!(self, id)
});
impl_typeck!(self, id: DisconSpecRef => {
    unimp!(self, id)
});
impl_typeck!(self, id: GroupTempRef => {
    unimp!(self, id)
});
impl_typeck!(self, id: GroupDeclRef => {
    unimp!(self, id)
});
impl_typeck!(self, id: BlockStmtRef => {
    unimp!(self, id)
});
impl_typeck_err!(self, id: ProcessStmtRef => {
    let hir = self.ctx.hir(id)?;
    for &decl in &hir.decls {
        self.typeck(decl);
    }
    for &stmt in &hir.stmts {
        self.typeck(stmt);
    }
    Ok(())
});
impl_typeck!(self, id: ConcCallStmtRef => {
    unimp!(self, id)
});
impl_typeck!(self, id: ConcAssertStmtRef => {
    unimp!(self, id)
});
impl_typeck!(self, id: ConcSigAssignStmtRef => {
    unimp!(self, id)
});
impl_typeck!(self, id: CompInstStmtRef => {
    unimp!(self, id)
});
impl_typeck!(self, id: ForGenStmtRef => {
    unimp!(self, id)
});
impl_typeck!(self, id: IfGenStmtRef => {
    unimp!(self, id)
});
impl_typeck!(self, id: CaseGenStmtRef => {
    unimp!(self, id)
});
impl_typeck_err!(self, id: SigAssignStmtRef => {
    let hir = self.ctx.hir(id)?;
    let lhs_ty = match hir.target {
        hir::SigAssignTarget::Name(sig) => self.ctx.ty(sig)?,
        hir::SigAssignTarget::Aggregate => unimpmsg!(self, hir.target_span, "assignment to aggregate signal"),
    };
    
    
    
    
    
    
    match hir.kind {
        hir::SigAssignKind::SimpleWave(ref dm, ref wave) => {
            self.typeck_delay_mechanism(dm);
            self.typeck_waveform(wave, lhs_ty);
        }
        hir::SigAssignKind::SimpleForce(_, _expr) => {
            
        }
        hir::SigAssignKind::SimpleRelease(_) => (),
        hir::SigAssignKind::CondWave(ref dm, ref _cond) => {
            self.typeck_delay_mechanism(dm);
            
        }
        hir::SigAssignKind::CondForce(_, ref _cond) => {
            
        }
        hir::SigAssignKind::SelWave(ref dm, ref _sel) => {
            self.typeck_delay_mechanism(dm);
            
        }
        hir::SigAssignKind::SelForce(_, ref _sel) => {
            
        }
    }
    Ok(())
});
impl<'lazy, 'sb, 'ast, 'ctx> ScoreContext<'lazy, 'sb, 'ast, 'ctx> {
    
    pub fn deref_named_type<'a>(&self, ty: &'a Ty) -> Result<&'a Ty>
    where
        'ctx: 'a,
    {
        match ty {
            &Ty::Named(_, tmr) => {
                let inner = self.ty(tmr)?;
                self.deref_named_type(inner)
            }
            other => Ok(other),
        }
    }
}
impl_make!(self, id: TypeMarkRef => &Ty {
    match id {
        TypeMarkRef::Type(id) => self.make(id),
        TypeMarkRef::Subtype(id) => self.make(id),
    }
});
#[deprecated]
impl_make!(self, id: SubtypeIndRef => &Ty {
    self.lazy_typeval(id)
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
});
impl_make!(self, id: TypeDeclRef => &Ty {
    let hir = self.lazy_hir(id)?;
    let data = match hir.data {
        Some(ref d) => d,
        None => {
            self.emit(
                DiagBuilder2::error(format!("declaration of type `{}` is incomplete", hir.name.value))
                .span(hir.name.span)
            );
            return Err(());
        }
    };
    match data.value {
        hir::TypeData::Range(dir, lb_id, rb_id) => {
            self.make_range_ty(dir, lb_id, rb_id, data.span)
        }
        hir::TypeData::Physical(dir, lb_id, rb_id, ref units, primary_index) => {
            let base = self.make_range_ty(dir, lb_id, rb_id, data.span)?;
            let base = match *base {
                Ty::Int(ref it) => it.clone(),
                _ => unreachable!(),
            };
            let units = units.iter().map(|&(name, ref abs, ref rel)|
                PhysicalUnit::new(name.value, abs.clone(), rel.clone())
            ).collect();
            Ok(self.intern_ty(PhysicalTy::new(id, base, units, primary_index)))
        }
        hir::TypeData::Enum(ref lits) => {
            use crate::ty2::{EnumBasetype, EnumVariant};
            let ty = EnumBasetype::new(lits.iter().map(|l| match *l {
                hir::EnumLit::Ident(sp) => EnumVariant::from(sp.value),
                hir::EnumLit::Char(sp)  => EnumVariant::from(sp.value),
            }));
            debugln!("type from enum `{}` = {}", hir.name, ty);
            Ok(self.intern_ty(EnumTy::new(id)))
        }
        hir::TypeData::Access(subty_id) => {
            let ty = self.ty(subty_id)?.clone();
            Ok(self.intern_ty(Ty::Access(Box::new(ty))))
        }
        hir::TypeData::Array(ref index_ids, elem_ty) => {
            
            
            
            let mut had_fails = false;
            let mut indices = Vec::new();
            for &index_id in index_ids {
                let hir = match self.hir(index_id) {
                    Ok(h) => h,
                    Err(()) => { had_fails = true; continue; }
                };
                indices.push(match hir.value {
                    hir::ArrayTypeIndex::Unbounded(tm) => {
                        ArrayIndex::Unbounded(Box::new(self.ty(tm.value)?.clone()))
                    }
                    hir::ArrayTypeIndex::Subtype(subty) => {
                        ArrayIndex::Constrained(Box::new(self.ty(subty)?.clone()))
                    }
                    hir::ArrayTypeIndex::Range(dir, lb_id, rb_id) => {
                        ArrayIndex::Constrained(Box::new(
                            self.make_range_ty(dir, lb_id, rb_id, hir.span)?.clone()
                        ))
                    }
                });
            }
            if had_fails {
                return Err(());
            }
            let elem_ty = self.ty(elem_ty)?.clone();
            Ok(self.intern_ty(ArrayTy::new(indices, Box::new(elem_ty))))
        }
        hir::TypeData::File(tm) => {
            let inner = self.ty(tm.value)?.clone();
            Ok(self.intern_ty(Ty::File(Box::new(inner))))
        }
        hir::TypeData::Record(ref fields) => {
            let mut had_fails = false;
            let mut mapped_fields = Vec::new();
            let mut used_names = HashMap::new();
            for &(name, subty) in fields {
                if let Some(&span) = used_names.get(&name.value) {
                    self.emit(
                        DiagBuilder2::error(format!("field `{}` already declared", name.value))
                        .span(name.span)
                        .add_note("Previous declaration was here:")
                        .span(span)
                    );
                    had_fails = true;
                } else {
                    used_names.insert(name.value, name.span);
                }
                mapped_fields.push((name.value, Box::new(self.ty(subty)?.clone())))
            }
            if had_fails {
                return Err(());
            }
            Ok(self.intern_ty(RecordTy::new(mapped_fields)))
        }
    }
});
impl<'lazy, 'sb, 'ast, 'ctx> ScoreContext<'lazy, 'sb, 'ast, 'ctx> {
    pub fn make_range_ty(
        &self,
        dir: hir::Dir,
        lb_id: ExprRef,
        rb_id: ExprRef,
        span: Span,
    ) -> Result<&'ctx Ty> {
        let lb = self.const_value(lb_id)?;
        let rb = self.const_value(rb_id)?;
        Ok(match (lb, rb) {
            (&Const::Int(ref lb), &Const::Int(ref rb)) => {
                use crate::ty2::{IntegerBasetype, Range};
                let ty = IntegerBasetype::new(Range::with_left_right(
                    dir,
                    lb.value.clone(),
                    rb.value.clone(),
                ));
                debugln!("type from range `{}` = {}", span.extract(), ty);
                self.intern_ty(IntTy::new(dir, lb.value.clone(), rb.value.clone()).maybe_null())
            }
            (&Const::Float(ref _lb), &Const::Float(ref _rb)) => {
                self.emit(DiagBuilder2::error("Float range bounds not yet supported").span(span));
                return Err(());
            }
            _ => {
                self.emit(
                    DiagBuilder2::error("Bounds of range are not of the same type").span(span),
                );
                return Err(());
            }
        })
    }
}
impl_make!(self, id: SubtypeDeclRef => &Ty {
    let hir = self.hir(id)?;
    self.ty(hir.subty)
});
impl_make!(self, id: TypedNodeRef => &Ty {
    match id {
        TypedNodeRef::SubtypeInd(id) => self.make(id),
        TypedNodeRef::Signal(id)     => self.make(id),
    }
});
impl_make!(self, id: SignalRef => &Ty {
    match id {
        SignalRef::Intf(id) => self.make(id),
        SignalRef::Decl(id) => self.lazy_typeval(id),
    }
});
impl_make!(self, id: IntfObjRef => &Ty {
    match id {
        IntfObjRef::Const(id)  => self.make(id),
        IntfObjRef::Var(id)    => self.make(id),
        IntfObjRef::Signal(id) => self.make(id),
        IntfObjRef::File(id)   => self.make(id),
    }
});
impl_make!(self, id: LatentTypeMarkRef => &Ty {
    self.ty(self.hir(id)?.value)
});