//
// 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
//

**
** Inherit processes each TypeDef to resolve the inherited slots.
** This step is used to check invalid inheritances due to conflicting
** slots and invalid overrides.
**
class Inherit : CompilerStep
{

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

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

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

  override Void run()
  {
    log.debug("Inherit")

    // at this point OrderByInheritance should have everything
    // ordered correctly to just do a simple walk
    walk(compiler, VisitDepth.typeDef)
  }

  override Void visitTypeDef(TypeDef t)
  {
    // inherit all parent types
    inheritType(t, t.base)
    t.mixins.each |CType m| { inheritType(t, m) }

    // check overrides all overrode something
    t.slotDefs.each |SlotDef slot|
    {
      if (slot.isOverride && !slot.overridden && !slot.isAccessor)
        err("Override of unknown virtual slot '$slot.name'", slot.loc)
    }
  }

//////////////////////////////////////////////////////////////////////////
// Inherit
//////////////////////////////////////////////////////////////////////////

  private Void inheritType(TypeDef t, CType? parent)
  {
    if (parent == null)
    {
      if (t.qname == "sys::Obj") return
      else throw err("Illegal state", t.loc)
    }

    closure := |CSlot s|
    {
      if (parent.isMixin && s.parent.isObj) return
      try
      {
        inheritSlot(t, s)
      }
      catch (CompilerErr e)
      {
      }
    }

    // inherit each slot from parent type (if test then
    // sort the slots to test errors in consistent order)
    if (compiler.input.isTest)
      parent.slots.vals.sort(|CSlot a, CSlot b->Int| {return a.name <=> b.name}).each(closure)
    else
      parent.slots.each(closure)
  }

  private Void inheritSlot(TypeDef t, CSlot newSlot)
  {
    // TODO: I think we need a lot more checking here, especially if
    // private/internal is public in the Java VM, because right now
    // we just ignore all ctor, privates, and internals - but they might
    // cause us real conflicts at JVM/IL emit time if we didn't detect
    // here.  Plus right now overrides of private/internal show up
    // as unknown virtuals, when in reality we could check them here
    // as scope errors.  So this method needs some refactoring to fully
    // handle all the cases (along with a comprehensive test)

    // we never inherit constructors, private slots,
    // or internal slots outside of the pod
    if (newSlot.isCtor || newSlot.isPrivate ||
        (newSlot.isInternal && newSlot.parent.pod != t.pod))
      return

    // check if there is already a slot mapped by that name
    name := newSlot.name
    oldSlot := t.slot(name)

    // if a new slot, add it to the type and we are done
    if (oldSlot == null)
    {
      t.addSlot(newSlot)
      return
    }

    // if we've inherited the exact same slot from two different
    // class hiearchies, then no need to continue
    if (newSlot === oldSlot) return

    // if this is one of the type's slot definitions, then check
    // that we have a valid inheritance override, in which case
    // we leave the old slot as the definition for this slot
    // name - otherwise we will log and throw an error; in all
    // cases we mark this slot overridden so that we don't report
    // spurious "Override of unknown virtual slot" errors
    if (oldSlot.parent === t && !newSlot.isCtor)
    {
      slotDef := (SlotDef)oldSlot
      slotDef.overridden = true
      checkOverride(t, newSlot, slotDef)
      return
    }

    // if the two slots don't have matching signatures
    // then this is an inheritance conflict
    if (!matchingSignatures(oldSlot, newSlot))
      throw err("Inherited slots have conflicting signatures '$oldSlot.qname' and '$newSlot.qname'", t.loc)

    // check if there is a clear keeper between old and new slots
    keep := keep(oldSlot, newSlot)
    if (keep != null)
    {
      if (keep === newSlot) t.replaceSlot(oldSlot, newSlot)
      return
    }

    // if both are virtual, then subclass must remove ambiguous
    if (oldSlot.isVirtual && newSlot.isVirtual)
      throw err("Must override ambiguous inheritance '$oldSlot.qname' and '$newSlot.qname'", t.loc)

    // anything else is an unfixable inheritance conflict
    throw err("Inheritance conflict '$oldSlot.qname' and '$newSlot.qname'", t.loc)
  }

  **
  ** Return if two slots have matching signatures
  **
  private Bool matchingSignatures(CSlot a, CSlot b)
  {
    fa := a as CField
    fb := b as CField
    ma := a as CMethod
    mb := b as CMethod

    if (fa != null && fb != null)
      return fa.fieldType == fb.fieldType

    if (ma != null && mb != null)
      return ma.returnType == mb.returnType &&
             ma.inheritedReturnType == mb.inheritedReturnType &&
             ma.hasSameParams(mb)

    if (fa != null && mb != null)
      return fa.fieldType == mb.returnType &&
             fa.fieldType == mb.inheritedReturnType &&
             mb.params.size == 0

    if (ma != null && fb != null)
      return ma.returnType == fb.fieldType &&
             ma.inheritedReturnType == fb.fieldType &&
             ma.params.size == 0

    return false
  }

  **
  ** Return if there is a clear keeper between a and b - if so
  ** return the one to keep otherwise return null.
  **
  private CSlot? keep(CSlot a, CSlot b)
  {
    // if one is abstract and one concrete we keep the concrete one
    if (a.isAbstract && !b.isAbstract) return b
    if (!a.isAbstract && b.isAbstract) return a

    // keep one if it is a clear override from the other
    if (a.parent.fits(b.parent)) return a
    if (b.parent.fits(a.parent)) return b

    return null
  }

  **
  ** Check that def is a valid override of the base slot.
  **
  private Void checkOverride(TypeDef t, CSlot base, SlotDef def)
  {
    loc := def.loc

    // check base is virtual
    if (!base.isVirtual)
      throw err("Cannot override non-virtual slot '$base.qname'", loc)

    // check override keyword was specified
    if (!def.isOverride)
      throw err("Must specify override keyword to override '$base.qname'", loc)

    // check protection scope
    if (isOverrideProtectionErr(base, def))
      throw err("Override narrows protection scope of '$base.qname'", loc)

    // if overriding a FFI slot give bridge a hook
    if (base.isForeign)
      base.bridge.checkOverride(t, base, def)

    // check if this is a method/method override
    if (base is CMethod && def is MethodDef)
    {
      checkMethodMethodOverride(t, (CMethod)base, (MethodDef)def)
      return
    }

    // check if this is a method/field override
    if (base is CMethod && def is FieldDef)
    {
      checkMethodFieldOverride(t, (CMethod)base, (FieldDef)def)
      return
    }

    // check if this is a field/field override
    if (base is CField && def is FieldDef)
    {
      checkFieldFieldOverride(t, (CField)base, (FieldDef)def)
      return
    }

    // TODO otherwise this is a potential inheritance conflict
    throw err("Invalid slot override of '$base.qname'", def.loc)
  }

  private Bool isOverrideProtectionErr(CSlot base, SlotDef def)
  {
    if (def.isPublic)
      return false

    if (def.isProtected)
      return base.isPublic || base.isInternal

    if (def.isInternal)
      return base.isPublic || base.isProtected

    return true
  }

  private Void checkMethodMethodOverride(TypeDef t, CMethod base, MethodDef def)
  {
    loc := def.loc

    defRet := def.returnType
    baseRet := base.returnType

    // if the base is defined as This, then all overrides must be This
    if (baseRet.isThis)
    {
      if (!defRet.isThis)
        throw err("Return in override of '$base.qname' must be This", loc)
    }
    else
    {
      // check return types
      if (defRet != baseRet)
      {
        // check if new return type is a subtype of original
        // return type (we allow covariant return types)
        if (!defRet.fits(baseRet) || (defRet.isVoid && !baseRet.isVoid) || defRet.isNullable != baseRet.isNullable)
          throw err("Return type mismatch in override of '$base.qname' - '$baseRet.inferredAs' != '$defRet'", loc)

        // can't use covariance with value types
        if (defRet.isVal || baseRet.isVal)
          throw err("Cannot use covariance with value types '$base.qname' - '$baseRet' != '$defRet'", loc)
      }

      // if the definition already has a covariant return type, then
      // it must be exactly the same type as this new override.  We
      // can't have conflicting covariant overrides because in JVM we
      // need everything to compile to the same inherited signature with
      // same return type so it resolves correctly
      if (def.inheritedRet != null && def.inheritedRet != base.inheritedReturnType)
        throw err("Conflicting covariant returns: '$def.inheritedRet' and '$base.inheritedReturnType'", loc)

      // used from Jan-Apr 2023
      //      if (def.inheritedRet != null && def.inheritedRet != base.inheritedReturnType && !base.inheritedReturnType.isObj)
    }

    // save original return type
    def.inheritedRet = base.inheritedReturnType

    // check that we have same parameter count
    if (!base.hasSameParams(def))
      throw err("Parameter mismatch in override of '$base.qname' - '$base.nameAndParamTypesToStr' != '$def.nameAndParamTypesToStr'", loc)

    // check override has matching defaults
    base.params.each |b, i|
    {
      d := def.params[i]
      if (b.hasDefault == d.hasDefault) return
      if (d.hasDefault)
        throw err("Parameter '$d.name' must not have default to match overridden method", loc)
      else
        throw err("Parameter '$d.name' must have default to match overridden method", loc)
    }

    // correct override
    return
  }

  private Void checkMethodFieldOverride(TypeDef t, CMethod base, FieldDef def)
  {
    loc := def.loc

    // check that types match
    ft := def.fieldType
    rt := base.returnType
    if (ft != rt)
    {
      // we allow field to be covariant typed
      if (!ft.fits(rt) || ft.isNullable != rt.isNullable)
        throw err("Type mismatch in override of '$base.qname' - '$rt' != '$ft'", loc)

      // can't use covariance with value types
      if (ft.isVal || rt.isVal)
        throw err("Cannot use covariance with value types '$base.qname' - '$rt' != '$ft'", loc)
    }

    // check that field isn't static
    if (def.isStatic)
      throw err("Cannot override virtual method with static field '$def.name'", loc)

    // check that method has no parameters
    if (!base.params.isEmpty)
      throw err("Field '$def.name' cannot override method with params '$base.qname'", loc)

    // save original return type
    def.inheritedRet = base.inheritedReturnType

    // correct override
    return
  }

  private Void checkFieldFieldOverride(TypeDef t, CField base, FieldDef def)
  {
    loc := def.loc

    // check that types match
    if (base.fieldType != def.fieldType)
      throw err("Type mismatch in override of '$base.qname' - '$base.fieldType' != '$def.fieldType'", loc)

    // if overriding a field which has storage, then don't duplicate storage
    if (!base.isAbstract)
      def.concreteBase = base

    // const field cannot override a field (const fields cannot be set,
    // therefore they can override only methods)
    if (def.isConst)
      throw err("Const field '$def.name' cannot override field '$base.qname'", loc)

    // correct override
    return
  }


}