//
// Copyright (c) 2006, Brian Frank and Andy Frank
// Licensed under the Academic Free License version 3.0
//
// History:
//    2 Dec 05  Brian Frank  Creation (originally InitShimSlots)
//   23 Sep 06  Brian Frank  Ported from Java to Fan
//

**
** CheckInheritance is used to check invalid extends or mixins.
**
class CheckInheritance : CompilerStep
{

//////////////////////////////////////////////////////////////////////////
// Constructor
//////////////////////////////////////////////////////////////////////////

  new make(Compiler compiler)
    : super(compiler)
  {
  }

//////////////////////////////////////////////////////////////////////////
// Run
//////////////////////////////////////////////////////////////////////////

  override Void run()
  {
    log.debug("CheckInheritance")
    walk(compiler, VisitDepth.typeDef)
    bombIfErr
  }

  override Void visitTypeDef(TypeDef t)
  {
    // check out of order base vs mixins first
    if (!checkOutOfOrder(t)) return

    // check extends
    checkExtends(t, t.base)

    // check each mixin
    t.mixins.each |CType m| { checkMixin(t, m) }
  }

//////////////////////////////////////////////////////////////////////////
// Checks
//////////////////////////////////////////////////////////////////////////

  private Bool checkOutOfOrder(TypeDef t)
  {
    if (!t.baseSpecified)
    {
      cls := t.mixins.find |CType x->Bool| { !x.isMixin }
      if (cls != null)
      {
        err("Invalid inheritance order, ensure class '$cls' comes first before mixins", t.loc)
        return false
      }
    }
    return true
  }

  private Void checkExtends(TypeDef t, CType? base)
  {
    // base is null only for sys::Obj
    if (base == null && t.qname == "sys::Obj")
      return

    // ensure mixin doesn't extend class
    if (t.isMixin && t.baseSpecified)
      err("Mixin '$t.name' cannot extend class '$base'", t.loc)

    // ensure enum doesn't extend class
    if (t.isEnum && t.baseSpecified)
      err("Enum '$t.name' cannot extend class '$base'", t.loc)

    // ensure facet doesn't extend class
    if (t.isFacet && t.baseSpecified)
      err("Facet '$t.name' cannot extend class '$base'", t.loc)

    // check extends a mixin
    if (base.isMixin)
      err("Class '$t.name' cannot extend mixin '$base'", t.loc)

    // check extends parameterized type
    if (base.isParameterized)
      err("Class '$t.name' cannot extend parameterized type '$base'", t.loc)

    // check extends final
    if (base.isFinal)
      err("Class '$t.name' cannot extend final class '$base'", t.loc)

    // check extends internal scoped outside my pod
    if (base.isInternal && t.pod != base.pod)
      err("Class '$t.name' cannot access internal scoped class '$base'", t.loc)
  }

  private Void checkMixin(TypeDef t, CType m)
  {
    // check mixins a class
    if (!m.isMixin)
    {
      if (t.isMixin)
        err("Mixin '$t.name' cannot extend class '$m'", t.loc)
      else
        err("Class '$t.name' cannot mixin class '$m'", t.loc)
    }

    // check extends internal scoped outside my pod
    if (m.isInternal && t.pod != m.pod)
      err("Type '$t.name' cannot access internal scoped mixin '$m'", t.loc)
  }

}