/*
 * Copyright 2004-2019 Cray Inc.
 * Other additional copyright holders may be indicated within.
 *
 * The entirety of this work is licensed under the Apache License,
 * Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License.
 *
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "InitNormalize.h"

#include "AstVisitor.h"
#include "AstVisitorTraverse.h"
#include "ForallStmt.h"
#include "IfExpr.h"
#include "initializerRules.h"
#include "LoopExpr.h"
#include "stmt.h"
#include "astutil.h"

static bool isAssignment(CallExpr* callExpr);
static bool isSimpleAssignment(CallExpr* callExpr);
static bool isCompoundAssignment(CallExpr* callExpr);

/************************************* | **************************************
*                                                                             *
*                                                                             *
*                                                                             *
************************************** | *************************************/

InitNormalize::InitNormalize(FnSymbol* fn) {
  mFn            = fn;
  mCurrField     = firstField(fn);
  mPhase         = startPhase(fn);
  mBlockType     = cBlockNormal;
  mPrevBlockType = cBlockNormal;
  mThisAsParent  = NULL;
}

InitNormalize::InitNormalize(BlockStmt* block, const InitNormalize& curr) {
  mFn            = curr.mFn;
  mCurrField     = curr.mCurrField;
  mPhase         = curr.mPhase;
  mThisAsParent  = curr.mThisAsParent;

  if (CallExpr* blockInfo = block->blockInfoGet()) {
    if        (blockInfo->isPrimitive(PRIM_BLOCK_BEGIN)       == true ||
               blockInfo->isPrimitive(PRIM_BLOCK_BEGIN_ON)    == true) {
      mBlockType = cBlockBegin;

    } else if (blockInfo->isPrimitive(PRIM_BLOCK_COBEGIN)     == true) {
      // Lydia NOTE 2017/07/26: If PRIM_BLOCK_COBEGIN_ONs are ever made, we
      // should match against them here
      mBlockType = cBlockCobegin;

    } else if (blockInfo->isPrimitive(PRIM_BLOCK_COFORALL)    == true ||
               blockInfo->isPrimitive(PRIM_BLOCK_COFORALL_ON) == true) {
      mBlockType = cBlockCoforall;

    } else if (blockInfo->isPrimitive(PRIM_BLOCK_ON) == true) {
      mBlockType = cBlockOn;

    } else {
      INT_ASSERT(false);
    }

  } else {
    mBlockType = curr.mBlockType;
  }

  if (mBlockType != curr.mBlockType) {
    mPrevBlockType = curr.mBlockType;
  } else {
    mPrevBlockType = curr.mPrevBlockType;
  }
}

InitNormalize::InitNormalize(CondStmt* cond, const InitNormalize& curr) {
  mFn            = curr.mFn;
  mCurrField     = curr.mCurrField;
  mPhase         = curr.mPhase;
  mPrevBlockType = curr.mPrevBlockType;
  mBlockType     = curr.mBlockType;
  mThisAsParent  = curr.mThisAsParent;
}

InitNormalize::InitNormalize(LoopStmt* loop, const InitNormalize& curr) {
  mFn            = curr.mFn;
  mCurrField     = curr.mCurrField;
  mPhase         = curr.mPhase;
  mBlockType     = cBlockLoop;
  mThisAsParent  = curr.mThisAsParent;

  if (mBlockType != curr.mBlockType) {
    mPrevBlockType = curr.mBlockType;

  } else {
    mPrevBlockType = curr.mPrevBlockType;
  }
}

InitNormalize::InitNormalize(ForallStmt* loop, const InitNormalize& curr) {
  mFn            = curr.mFn;
  mCurrField     = curr.mCurrField;
  mPhase         = curr.mPhase;
  mBlockType     = cBlockForall;
  mThisAsParent  = curr.mThisAsParent;

  if (mBlockType != curr.mBlockType) {
    mPrevBlockType = curr.mBlockType;

  } else {
    mPrevBlockType = curr.mPrevBlockType;
  }
}

void InitNormalize::merge(const InitNormalize& fork) {
  mCurrField = fork.mCurrField;
  mPhase     = fork.mPhase;

  mImplicitFields.insert(fork.mImplicitFields.begin(),
                         fork.mImplicitFields.end());

  if (this->mThisAsParent == NULL) {
    this->mThisAsParent = fork.mThisAsParent;
  }
}

AggregateType* InitNormalize::type() const {
  return mFn != NULL ? toAggregateType(mFn->_this->type) : NULL;
}

FnSymbol* InitNormalize::theFn() const {
  return mFn;
}

InitNormalize::InitPhase InitNormalize::currPhase() const {
  return mPhase;
}

bool InitNormalize::isPhase0() const {
  return mPhase == cPhase0;
}

bool InitNormalize::isPhase1() const {
  return mPhase == cPhase1;
}

bool InitNormalize::isPhase2() const {
  return mPhase == cPhase2;
}

DefExpr* InitNormalize::currField() const {
  return mCurrField;
}

bool InitNormalize::isFieldReinitialized(DefExpr* field) const {
  AggregateType* at     = toAggregateType(mFn->_this->type);
  Expr*          ptr    = at->fields.head;
  bool           retval = false;

  while (ptr != NULL && ptr != mCurrField && retval == false) {
    if (field == ptr) {
      retval = true;
    } else {
      ptr    = ptr->next;
    }
  }

  INT_ASSERT(ptr != NULL);

  return retval;
}

bool InitNormalize::inLoopBody() const {
  return mBlockType == cBlockLoop;
}

bool InitNormalize::inParallelStmt() const {
  return mBlockType == cBlockBegin   ||
         mBlockType == cBlockCobegin  ;
}

bool InitNormalize::inCoforall() const {
  return mBlockType == cBlockCoforall;
}

bool InitNormalize::inForall() const {
  return mBlockType == cBlockForall;
}

bool InitNormalize::inOn() const {
  return mBlockType == cBlockOn;
}

bool InitNormalize::inOnInLoopBody() const {
  return inOn() && mPrevBlockType == cBlockLoop;
}

bool InitNormalize::inOnInParallelStmt() const {
  return inOn() && (mPrevBlockType == cBlockBegin ||
                    mPrevBlockType == cBlockCobegin);
}

bool InitNormalize::inOnInCoforall() const {
  return inOn() && mPrevBlockType == cBlockCoforall;
}

bool InitNormalize::inOnInForall() const {
  return inOn() && mPrevBlockType == cBlockForall;
}

/************************************* | **************************************
*                                                                             *
*                                                                             *
*                                                                             *
************************************** | *************************************/

void InitNormalize::completePhase1(CallExpr* initStmt) {
  if        (isThisInit(initStmt)  == true) {
    mCurrField = NULL;

  } else if (isSuperInit(initStmt) == true) {
    initializeFieldsBefore(initStmt);

  } else if (isInitDone(initStmt)  == true) {
    initializeFieldsBefore(initStmt);

  } else {
    INT_ASSERT(false);
  }

  // Allow users to treat 'this' as the initialized type
  if (type()->isClass()) {
    initStmt->insertAfter(new CallExpr(PRIM_SETCID, new SymExpr(mFn->_this)));
  }

  mPhase = cPhase2;
}

void InitNormalize::completePhase0(CallExpr* initStmt) {
  if (isSuperInit(initStmt) == true) {
    mPhase = cPhase1;
  } else {
    INT_FATAL("completePhase0 expected to be called with super.init");
  }
}

void InitNormalize::initializeFieldsAtTail(BlockStmt* block, DefExpr* endField) {
  if (mCurrField != NULL && mCurrField != endField) {
    Expr* noop = new CallExpr(PRIM_NOOP);

    block->insertAtTail(noop);

    initializeFieldsBefore(noop, endField);

    noop->remove();
  }
}

void InitNormalize::initializeFieldsBefore(Expr* insertBefore,
                                           DefExpr* endField) {
  while (mCurrField != NULL && mCurrField != endField) {
    DefExpr* field = mCurrField;

    initializeField(insertBefore, field);

    mCurrField = toDefExpr(mCurrField->next);
  }
}

void InitNormalize::initializeField(Expr* insertBefore,
                                    DefExpr* field,
                                    Expr* userInit) const {
  Expr* initExpr = userInit;
  Expr* typeExpr = field->exprType;
  Expr* ret      = NULL;

  if (initExpr == NULL && field->init != NULL) {
    initExpr = field->init->copy();
  }

  if (typeExpr == NULL && initExpr == NULL) {
    USR_FATAL_CONT(insertBefore,
                   "can't omit initialization of field \"%s\", "
                   "no type or default value provided",
                   field->sym->name);
  } else if (field->sym->hasEitherFlag(FLAG_PARAM, FLAG_TYPE_VARIABLE)) {
    if (typeExpr != NULL && initExpr == NULL) {
      ret = genericFieldInitTypeWoutInit(insertBefore, field);

    } else if (typeExpr != NULL && initExpr != NULL) {
      ret = genericFieldInitTypeWithInit(insertBefore, field, initExpr);

    } else if (typeExpr == NULL && initExpr != NULL) {
      ret = genericFieldInitTypeInference(insertBefore, field, initExpr);

    } else {
      INT_ASSERT(false);
    }
  } else if (typeExpr == NULL && field->init == NULL) {
    // Special case: initializing 'var x;' field from 'userInit'
    INT_ASSERT(userInit != NULL);
    ret = genericFieldInitTypeInference(insertBefore, field, initExpr);

  } else if (typeExpr != NULL && initExpr == NULL) {
    ret = fieldInitTypeWoutInit(insertBefore, field);

  } else if (typeExpr != NULL && initExpr != NULL) {
    ret = fieldInitTypeWithInit(insertBefore, field, initExpr);

  } else if (typeExpr == NULL && initExpr != NULL) {
    //INT_ASSERT(field->sym->type == dtUnknown);
    ret = fieldInitTypeInference(insertBefore, field, initExpr);

  } else {
    INT_ASSERT(false);
  }

  if (ret != NULL) {
    processThisUses(ret);
  }
}

/************************************* | **************************************
*                                                                             *
*                                                                             *
*                                                                             *
************************************** | *************************************/

Expr* InitNormalize::genericFieldInitTypeWoutInit(Expr*    insertBefore,
                                                  DefExpr* field) const {
  INT_ASSERT(field->sym->hasFlag(FLAG_PARAM));

  SET_LINENO(insertBefore);

  Type* type = field->sym->type;

  VarSymbol* tmp      = newTemp("tmp", type);
  DefExpr*   tmpDefn  = new DefExpr(tmp);
  CallExpr*  tmpInit  = new CallExpr(PRIM_DEFAULT_INIT_VAR,
                                     tmp, field->exprType->copy());

  tmp->addFlag(FLAG_PARAM);

  Symbol*    _this    = mFn->_this;
  Symbol*    name     = new_CStringSymbol(field->sym->name);
  CallExpr*  fieldSet = new CallExpr(PRIM_INIT_FIELD, _this, name, tmp);

  insertBefore->insertBefore(tmpDefn);
  insertBefore->insertBefore(tmpInit);
  insertBefore->insertBefore(fieldSet);

  return tmpInit;
}

/************************************* | **************************************
*                                                                             *
*                                                                             *
*                                                                             *
************************************** | *************************************/

Expr* InitNormalize::genericFieldInitTypeWithInit(Expr*    insertBefore,
                                                  DefExpr* field,
                                                  Expr*    initExpr) const {
  INT_ASSERT(field->sym->hasFlag(FLAG_PARAM));

  SET_LINENO(insertBefore);

  CallExpr* cast     = createCast(initExpr, field->exprType->copy());

  Symbol*   name     = new_CStringSymbol(field->sym->name);
  Symbol*   _this    = mFn->_this;

  // TODO - coerce, don't cast
  CallExpr* fieldSet = new CallExpr(PRIM_INIT_FIELD, _this, name, cast);

  insertBefore->insertBefore(fieldSet);

  return cast;
}

/************************************* | **************************************
*                                                                             *
*                                                                             *
*                                                                             *
************************************** | *************************************/

Expr* InitNormalize::genericFieldInitTypeInference(Expr*    insertBefore,
                                                   DefExpr* field,
                                                   Expr*    initExpr) const {
  bool isParam   = field->sym->hasFlag(FLAG_PARAM);
  bool isTypeVar = field->sym->hasFlag(FLAG_TYPE_VARIABLE);

  SET_LINENO(insertBefore);

  // e.g.
  //   var x = <immediate>;
  //   var y = <identifier>;
  if (isSymExpr(initExpr)) {
    if (mFn->isDefaultInit()) {
      Symbol*    name     = new_CStringSymbol(field->sym->name);
      Symbol*    _this    = mFn->_this;
      CallExpr*  fieldSet = new CallExpr(PRIM_INIT_FIELD, _this, name, initExpr);

      insertBefore->insertBefore(fieldSet);

    } else {
      VarSymbol* tmp      = newTemp("tmp");
      DefExpr*   tmpDefn  = new DefExpr(tmp);
      PrimitiveTag tag    = isTypeVar ? PRIM_MOVE : PRIM_INIT_VAR;
      CallExpr*  tmpInit  = new CallExpr(tag, tmp, initExpr);

      Symbol*    _this    = mFn->_this;
      Symbol*    name     = new_CStringSymbol(field->sym->name);
      CallExpr*  fieldSet = new CallExpr(PRIM_INIT_FIELD, _this, name, tmp);

      if (isParam) {
        tmp->addFlag(FLAG_PARAM);
      } else if (isTypeVar) {
        tmp->addFlag(FLAG_TYPE_VARIABLE);
      }

      insertBefore->insertBefore(tmpDefn);
      insertBefore->insertBefore(tmpInit);
      insertBefore->insertBefore(fieldSet);
    }

  // e.g.
  //   var x = f(...);
  //   var y = new MyRecord(...);
  } else if (isUnresolvedSymExpr(initExpr)) {
    // Don't worry too much about it, resolution will handle this.
    // TODO: add comment with example of this case
    Symbol*   _this    = mFn->_this;
    Symbol*   name     = new_CStringSymbol(field->sym->name);
    CallExpr* fieldSet = new CallExpr(PRIM_INIT_FIELD, _this, name, initExpr);

    insertBefore->insertBefore(fieldSet);

  } else {
    if (CallExpr* initCall = toCallExpr(initExpr)) {
      if ((isParam || isTypeVar) && initCall->isPrimitive(PRIM_NEW) == true) {
        const char* kind = isTypeVar ? "type" : "param";
        USR_FATAL(initExpr,
                  "Cannot initialize %s field '%s' with 'new' expression",
                  kind, field->sym->name);
      }
    }

    VarSymbol* tmp      = newTemp("tmp");
    DefExpr*   tmpDefn  = new DefExpr(tmp);
    PrimitiveTag tag    = isTypeVar ? PRIM_MOVE : PRIM_INIT_VAR;
    CallExpr*  tmpInit  = new CallExpr(tag, tmp, initExpr);

    Symbol*    _this    = mFn->_this;
    Symbol*    name     = new_CStringSymbol(field->sym->name);
    CallExpr*  fieldSet = new CallExpr(PRIM_INIT_FIELD, _this, name, tmp);

    if (isParam) {
      tmp->addFlag(FLAG_PARAM);
    } else if (isTypeVar) {
      tmp->addFlag(FLAG_TYPE_VARIABLE);
    }

    insertBefore->insertBefore(tmpDefn);
    insertBefore->insertBefore(tmpInit);
    insertBefore->insertBefore(fieldSet);

  }

  return initExpr;
}

/************************************* | **************************************
*                                                                             *
*                                                                             *
*                                                                             *
************************************** | *************************************/

Expr* InitNormalize::fieldInitTypeWoutInit(Expr*    insertBefore,
                                           DefExpr* field) const {

  SET_LINENO(insertBefore);

  Type* type = field->sym->type;

  VarSymbol* tmp      = newTemp("tmp", type);
  DefExpr*   tmpDefn  = new DefExpr(tmp);
  CallExpr*  tmpInit  = new CallExpr(PRIM_DEFAULT_INIT_VAR,
                                     tmp, field->exprType->copy());

  Symbol*    _this    = mFn->_this;
  Symbol*    name     = new_CStringSymbol(field->sym->name);
  CallExpr*  fieldSet = new CallExpr(PRIM_SET_MEMBER, _this, name, tmp);

  insertBefore->insertBefore(tmpDefn);
  insertBefore->insertBefore(tmpInit);
  insertBefore->insertBefore(fieldSet);

  return tmpInit;
}

/************************************* | **************************************
*                                                                             *
*                                                                             *
*                                                                             *
************************************** | *************************************/

Expr* InitNormalize::fieldInitTypeWithInit(Expr*    insertBefore,
                                           DefExpr* field,
                                           Expr*    initExpr) const {
  SET_LINENO(insertBefore);

  Type* type = field->sym->type;

  if (mFn->isDefaultInit()) {
    // For default-initializers, copy happens at the callsite
    Symbol* _this = mFn->_this;
    Symbol* name = new_CStringSymbol(field->sym->name);
    CallExpr* fieldSet = new CallExpr(PRIM_INIT_FIELD, _this, name, initExpr);

    insertBefore->insertBefore(fieldSet);

    return initExpr;

  } else {
    // Do not set type of 'tmp' so that resolution will infer it later
    VarSymbol* tmp       = newTemp("tmp");
    DefExpr*   tmpDefn   = new DefExpr(tmp);
    Expr*      checkType = NULL;

    if (field->exprType == NULL) {
      checkType = new SymExpr(type->symbol);
    } else {
      checkType = field->exprType->copy();
    }

    // Set the value for TMP
    CallExpr*  tmpInit = new CallExpr(PRIM_INIT_VAR,
                                      tmp,  initExpr, checkType);

    Symbol*    _this     = mFn->_this;
    Symbol*    name      = new_CStringSymbol(field->sym->name);
    // Calling PRIM_INIT_FIELD here instead of PRIM_SET_MEMBER
    // helps with classes/ferguson/generic-field - check that
    // test if it is revisited.
    CallExpr*  fieldSet  = new CallExpr(PRIM_INIT_FIELD, _this, name, tmp);

    insertBefore->insertBefore(tmpDefn);
    insertBefore->insertBefore(tmpInit);
    insertBefore->insertBefore(fieldSet);

    return tmpInit;
  }
}

/************************************* | **************************************
*                                                                             *
*                                                                             *
*                                                                             *
************************************** | *************************************/

Expr* InitNormalize::fieldInitTypeInference(Expr*    insertBefore,
                                            DefExpr* field,
                                            Expr*    initExpr) const {
  SET_LINENO(insertBefore);

  if (mFn->isDefaultInit()) {
    Symbol* _this = mFn->_this;
    Symbol* name = new_CStringSymbol(field->sym->name);
    CallExpr* fieldSet = new CallExpr(PRIM_SET_MEMBER, _this, name, initExpr);

    insertBefore->insertBefore(fieldSet);

  } else {
    VarSymbol* tmp      = newTemp("tmp");
    DefExpr*   tmpDefn  = new DefExpr(tmp);
    CallExpr*  tmpInit  = new CallExpr(PRIM_INIT_VAR, tmp, initExpr);

    Symbol*    _this    = mFn->_this;
    Symbol*    name     = new_CStringSymbol(field->sym->name);
    CallExpr*  fieldSet = new CallExpr(PRIM_SET_MEMBER, _this, name, tmp);

    insertBefore->insertBefore(tmpDefn);
    insertBefore->insertBefore(tmpInit);
    insertBefore->insertBefore(fieldSet);
  }

  return initExpr;
}

/************************************* | **************************************
*                                                                             *
*                                                                             *
*                                                                             *
************************************** | *************************************/

DefExpr* InitNormalize::toLocalField(SymExpr* expr) const {
  return type()->toLocalField(expr);
}

DefExpr* InitNormalize::toLocalField(CallExpr* expr) const {
  return type()->toLocalField(expr);
}

/************************************* | **************************************
*                                                                             *
*                                                                             *
*                                                                             *
************************************** | *************************************/

DefExpr* InitNormalize::toSuperField(SymExpr* expr) const {
  return type()->toSuperField(expr);
}

DefExpr* InitNormalize::toSuperField(AggregateType* at,
                                     const char*    name) const {
  forv_Vec(AggregateType, pt, at->dispatchParents) {
    if (DefExpr* field = pt->toLocalField(name)) {
      return field;
    }
  }

  return NULL;
}

/************************************* | **************************************
*                                                                             *
*                                                                             *
*                                                                             *
************************************** | *************************************/

bool InitNormalize::isFieldInitialized(const DefExpr* field) const {
  const DefExpr* ptr    = mCurrField;
  bool           retval = true;

  while (ptr != NULL && retval == true) {
    if (ptr == field) {
      retval = false;
    } else {
      ptr = toConstDefExpr(ptr->next);
    }
  }

  return retval;
}

bool InitNormalize::isFieldImplicitlyInitialized(DefExpr* field) const {
  return mImplicitFields.find(field) != mImplicitFields.end();
}

/************************************* | **************************************
*                                                                             *
*                                                                             *
*                                                                             *
************************************** | *************************************/

InitNormalize::InitPhase InitNormalize::startPhase(FnSymbol*  fn)    const {
  return startPhase(fn->body);
}

InitNormalize::InitPhase InitNormalize::startPhase(BlockStmt* block) const {
  Expr*     stmt   = block->body.head;
  const InitPhase defaultPhase = cPhase1;
  InitPhase retval = defaultPhase;

  while (stmt != NULL && retval == defaultPhase) {
    if (isDefExpr(stmt) == true) {
      stmt = stmt->next;

    } else if (CallExpr* callExpr = toCallExpr(stmt)) {
      if        (isThisInit(callExpr)  == true) {
        retval = cPhase0;
        break;

      } else if (isSuperInit(callExpr) == true) {
        retval = cPhase0;
        break;

      } else if (isInitDone(callExpr)  == true) {
        retval = cPhase1;
        break;

      } else {
        stmt   = stmt->next;
      }

    } else if (CondStmt* cond = toCondStmt(stmt)) {
      if (cond->elseStmt == NULL) {
        InitPhase thenPhase = startPhase(cond->thenStmt);

        if (thenPhase != defaultPhase) {
          retval = thenPhase;
        } else {
          stmt   = stmt->next;
        }

      } else {
        InitPhase thenPhase = startPhase(cond->thenStmt);
        InitPhase elsePhase = startPhase(cond->elseStmt);

        if        (thenPhase == cPhase0 || elsePhase == cPhase0) {
          retval = cPhase0;

        } else if (thenPhase == cPhase1 || elsePhase == cPhase1) {
          retval = cPhase1;
          // We want to keep looking in case there's a super.init further
          // down.
          stmt = stmt->next;

        } else {
          stmt   = stmt->next;
        }
      }

    } else if (BlockStmt* block = toBlockStmt(stmt)) {
      InitPhase phase = startPhase(block);

      if (phase != defaultPhase) {
        retval = phase;
      } else {
        stmt   = stmt->next;
      }

    } else if (ForallStmt* forall = toForallStmt(stmt)) {
      // Nothing to normalize in fRecIter*
      INT_ASSERT(forall->fRecIterIRdef == NULL);

      InitPhase phase = startPhase(forall->loopBody());

      if (phase != defaultPhase) {
        retval = phase;
      } else {
        stmt   = stmt->next;
      }

    } else {
      stmt = stmt->next;
    }
  }

  return retval;
}

//
// Catch case a case like:
//
// proc init(cond:bool) {
//   if cond {
//     this.initDone();
//   } else {
//     this.init();
//   }
// }
//
// The initializer begins in phase zero, which is the state we inherit upon
// entering the 'then' branch of the conditional statement. This is the
// incorrect phase though, so we need to re-check the start phase of the
// 'then' block and use that instead.
//
// Note though that we don't want to update the state unless there is an
// initDone. Otherwise simple conditionals like this would advance the outer
// phase to phase 1:
//
// proc init(cond:bool) {
//   if cond {
//     writeln("then");
//   } else {
//     writeln("else");
//   }
//   super.init();
// }
//
// INIT TODO: Can we restrict this operation to just conditionals?
//
void InitNormalize::checkPhase(BlockStmt* block) {
  if (mPhase == cPhase0) {
    InitPhase newPhase = startPhase(block);

    if (newPhase == cPhase1) {
      if (hasInitDone(block)) {
        mPhase = newPhase;
      }
    }
  }
}

DefExpr* InitNormalize::firstField(FnSymbol* fn) const {
  AggregateType* at     = toAggregateType(fn->_this->type);
  DefExpr*       retval = toDefExpr(at->fields.head);

  // Skip the pseudo-field "super"
  if (at->isClass() == true && at->symbol->hasFlag(FLAG_NO_OBJECT) == false) {

    if (at->isGeneric() == true) {
      AggregateType* pt = toAggregateType(retval->sym->type);

      INT_ASSERT(pt);

      if (pt->isGeneric() == true) {
        // If the super type is generic, label it so that we can
        // handle that appropriately during initializer resolution
        retval->sym->addFlag(FLAG_DELAY_GENERIC_EXPANSION);
      }
    }

    retval = toDefExpr(retval->next);
  }

  return retval;
}

//
// Initialize the 'mThisAsParent' field
//
// mThisAsParent points to the same data as 'this', but has the parent type
// of 'this'. This method also inserts a call to set the class ID of
// mThisAsParent (and 'this') so that we only dynamically dispatch methods to
// the parent.
//
void InitNormalize::makeThisAsParent(CallExpr* call) {
  if (type()->symbol->hasFlag(FLAG_NO_OBJECT)) {
    return;
  }

  // type parentType = this.super.type;
  // var thisAsParent : parentType = (_cast parentType this);

  INT_ASSERT(this->mThisAsParent == NULL);

  VarSymbol* parentType = newTemp();
  DefExpr*   parentDef  = new DefExpr(parentType);
  parentType->addFlag(FLAG_TYPE_VARIABLE);

  // Let resolution figure out the type for us
  // (move parentType (typeof (.v this super)))
  CallExpr* getSuper = new CallExpr(PRIM_GET_MEMBER_VALUE, mFn->_this, new_CStringSymbol("super"));
  CallExpr* typeInit = new CallExpr(PRIM_MOVE, parentType,
                                    new CallExpr(PRIM_TYPEOF, getSuper));

  // Important to use PRIM_CAST here, otherwise resolution will complain about
  // 'this' having an unknown type if the class is generic.
  VarSymbol* thisAsParent = newTemp("chpl__thisAsParent");
  SymExpr*   tapType      = new SymExpr(parentType);
  CallExpr*  tapInit      = new CallExpr(PRIM_CAST, parentType, new SymExpr(mFn->_this));
  DefExpr*   tapDef       = new DefExpr(thisAsParent, tapInit, tapType);

  CallExpr* setCID = new CallExpr(PRIM_SETCID, thisAsParent);

  call->insertAfter(setCID);
  call->insertAfter(tapDef);
  call->insertAfter(typeInit);
  call->insertAfter(parentDef);

  this->mThisAsParent = thisAsParent;
}

VarSymbol* InitNormalize::getThisAsParent() const {
  return mThisAsParent;
}

static bool isThisDot(CallExpr* call) {
  bool retval = false;

  if (call->isNamedAstr(astrSdot) == true) {
    SymExpr* base = toSymExpr(call->get(1));
    if (base != NULL &&
        base->symbol()->hasFlag(FLAG_ARG_THIS)) {
      retval = true;
    }
  }
  
  return retval;
}

static bool isMethodCall(CallExpr* call) {
  bool retval = false;

  if (CallExpr* parent = toCallExpr(call->parentExpr)) {
    if (parent->baseExpr == call) {
      retval = true;
    }
  }

  return retval;
}

//
// Returns true if 'type' or its parents have a method named 'methodName'
//
// Note: Assumes only one entry in dispatchParents.
//
static bool typeHasMethod(AggregateType* type, const char* methodName) {
  bool retval = false;

  INT_ASSERT(type != NULL);

  if (type != dtObject) {
    forv_Vec(FnSymbol, method, type->methods) {
      if (method && strcmp(method->name, methodName) == 0) {
        retval = true;
        break;
      }
    }

    if (retval == false && isClass(type)) {
      AggregateType* parent = type->dispatchParents.v[0];
      retval = typeHasMethod(parent, methodName);
    }
  }

  return retval;
}

class ProcessThisUses : public AstVisitorTraverse
{
  public:
    ProcessThisUses(const InitNormalize* state) {
      this->state = state;
    }
    virtual ~ProcessThisUses() { }

    virtual void visitSymExpr(SymExpr* node);
    virtual bool enterCallExpr(CallExpr* node);
    virtual bool enterFnSym(FnSymbol* node);

  private:
    const InitNormalize* state;
};

void ProcessThisUses::visitSymExpr(SymExpr* node) {
  DefExpr* field = NULL;

  if (node->symbol()->hasFlag(FLAG_ARG_THIS)) {
    if (DefExpr* parentDef = toDefExpr(node->parentExpr)) {
      if (parentDef->sym->hasFlag(FLAG_REF_VAR)) {
        USR_FATAL_CONT(node, "cannot take a reference to \"this\" before this.complete()");
      } else {
        USR_FATAL_CONT(node, "cannot initialize a variable from \"this\" before this.complete()");
      }
    } else {
      CallExpr* call = NULL;
      Expr* cur = node;
      while (call == NULL && cur->parentExpr != NULL) {
        if (CallExpr* parent = toCallExpr(cur->parentExpr)) {
          call = parent;
        } else {
          cur = cur->parentExpr;
        }
      }

      if (call && call->isPrimitive() == false) {
        if (state->isPhase0()) {
          USR_FATAL_CONT(node, "cannot pass \"this\" to a function before calling super.init() or this.init()");
        } else if (state->type()->isRecord()) {
          USR_FATAL_CONT(node, "cannot pass a record to a function before this.complete()");
        }
      }

      if (isClass(state->type())) {
        node->setSymbol(state->getThisAsParent());
      }
    }
  } else if (DefExpr* local = state->type()->toLocalField(node)) {
    field = local;
    if (state->isFieldInitialized(field) == false) {
      USR_FATAL_CONT(node,
                     "field \"%s\" used before it is initialized",
                     field->sym->name);
    }
  } else if (DefExpr* super = state->type()->toSuperField(node)) {
    field = super;
    if (state->isPhase0()) {
      USR_FATAL_CONT(node,
                     "cannot access parent field \"%s\" before super.init() or this.init()",
                     field->sym->name);
    }
  }

  if (field != NULL) {
    PrimitiveTag tag = PRIM_GET_MEMBER;
    if (field->sym->hasEitherFlag(FLAG_PARAM, FLAG_TYPE_VARIABLE)) {
      tag = PRIM_GET_MEMBER_VALUE;
    }

    node->replace(new CallExpr(tag,
                               state->theFn()->_this,
                               new_CStringSymbol(field->sym->name)));

  }
}

bool ProcessThisUses::enterCallExpr(CallExpr* node) {
  AggregateType* type = state->type();
  if (DefExpr* field = type->toLocalField(node)) {
    if (state->isFieldInitialized(field) == false) {
      USR_FATAL_CONT(node,
                     "field \"%s\" used before it is initialized",
                     field->sym->name);
    } else if (isCallExpr(node->parentExpr)) {
      // this.myField.something
      if (typeHasMethod(type, field->sym->name)) {
        USR_FATAL_CONT(node, "cannot call field-accessor method \"%s\" before this.complete()", field->sym->name);
      }
      node->baseExpr->remove();
      node->baseExpr = NULL;
      node->primitive = primitives[PRIM_GET_MEMBER];

    }
    return false;
  } else if (DefExpr* field = type->toSuperField(node)) {
    if (state->isPhase0()) {
      USR_FATAL_CONT(node,
                     "cannot access parent field \"%s\" before super.init() or this.init()",
                     field->sym->name);
    } else if (state->isPhase1()) {
      node->baseExpr->remove();
      node->baseExpr = NULL;
      node->primitive = primitives[PRIM_GET_MEMBER];
    }
    return false;
  } else if (isAssignment(node)) {
    if (CallExpr* LHS = toCallExpr(node->get(1))) {
      if (isThisDot(LHS)) {
        if (LHS->square == false) {
          node->get(1)->accept(this);
          // Regular 'this.x = ' , just look at the RHS
          node->get(2)->accept(this);
          return false;
        }
      }
    }
  } else if (isThisDot(node) && isMethodCall(node)) {
    if (state->isPhase0()) {
      USR_FATAL_CONT(node, "cannot call a method before super.init() or this.init()");
      return false;
    } else if (type->isRecord()) {
      USR_FATAL_CONT(node, "cannot call a method on a record before this.complete()");
      return false;
    } else {
      Immediate*     imm        = getSymbolImmediate(toSymExpr(node->get(2))->symbol());
      const char*    methodName = imm->string_value();
      AggregateType* parentType = type->dispatchParents.v[0];

      if (typeHasMethod(parentType, methodName) == false) {
        USR_FATAL_CONT(node, "cannot call method \"%s\" on type \"%s\" before this.complete()", methodName, type->symbol->name);
        USR_PRINT(node, "before this.complete() \"this\" is treated as parent type \"%s\" for method calls", parentType->symbol->name);
        return false;
      }
    }
  } else if (node->isPrimitive(PRIM_CAST) || node->isNamedAstr(astr_cast)) {
    if (SymExpr* se = toSymExpr(node->get(2))) {
      if (se->symbol()->hasFlag(FLAG_ARG_THIS)) {
        USR_FATAL_CONT(node, "cannot cast \"this\" before this.complete()");
        return false;
      }
    }
  }

  return true;
}

bool ProcessThisUses::enterFnSym(FnSymbol* node) {
  // Skip nested functions for now
  return false;
}

void InitNormalize::processThisUses(Expr* expr) const {
  if (isPhase2() != true) {
    ProcessThisUses ptu(this);
    expr->accept(&ptu);
  }
}

Expr* InitNormalize::fieldInitFromInitStmt(DefExpr*  field,
                                           CallExpr* initStmt) {
  Expr* retval = NULL;

  if (field != mCurrField) {
    INT_ASSERT(isFieldReinitialized(field) == false);

    while (field != mCurrField) {
      initializeField(initStmt, mCurrField);
      mImplicitFields.insert(mCurrField);

      mCurrField = toDefExpr(mCurrField->next);
    }
  }

  Expr* initExpr = initStmt->get(2)->remove();
  retval         = initStmt->next;

  initializeField(initStmt, field, initExpr);
  initStmt->remove();

  mCurrField = toDefExpr(mCurrField->next);

  return retval;
}


void InitNormalize::describe(int offset) const {
  char pad[512];

  for (int i = 0; i < offset; i++) {
    pad[i] = ' ';
  }

  pad[offset] = '\0';

  printf("%s#<InitNormalize\n", pad);

  printf("%s  Phase: %s\n", pad, phaseToString(mPhase));

  printf("%s  Block: ",       pad);

  switch (mBlockType) {
    case cBlockNormal:
      printf("normal\n");
      break;

    case cBlockLoop:
      printf("loop\n");
      break;

    case cBlockBegin:
      printf("begin\n");
      break;

    case cBlockCobegin:
      printf("cobegin\n");
      break;

    case cBlockCoforall:
      printf("coforall\n");
      break;

    case cBlockForall:
      printf("forall\n");
      break;

    case cBlockOn:
      printf("on\n");
      break;
  }

  printf("%s>\n", pad);
}

const char*
InitNormalize::phaseToString(InitNormalize::InitPhase phase) const {
  const char* retval = "?";

  switch (phase) {
    case cPhase0:
      retval = "Phase0";
      break;

    case cPhase1:
      retval = "Phase1";
      break;

    case cPhase2:
      retval = "Phase2";
      break;
  }

  return retval;
}

static bool isAssignment(CallExpr* callExpr) {
  bool retval = false;

  if (isSimpleAssignment(callExpr)   == true ||
      isCompoundAssignment(callExpr) == true) {
    retval = true;
  }

  return retval;
}

static bool isSimpleAssignment(CallExpr* callExpr) {
  bool retval = false;

  if (callExpr->isNamedAstr(astrSassign) == true) {
    retval = true;
  }

  return retval;
}

static bool isCompoundAssignment(CallExpr* callExpr) {
  bool retval = false;

  if (callExpr->isNamed("+=")  == true ||
      callExpr->isNamed("-=")  == true ||
      callExpr->isNamed("*=")  == true ||
      callExpr->isNamed("/=")  == true ||
      callExpr->isNamed("**=") == true ||
      callExpr->isNamed("%=")  == true ||
      callExpr->isNamed("&=")  == true ||
      callExpr->isNamed("|=")  == true ||
      callExpr->isNamed("^=")  == true ||
      callExpr->isNamed("&&=") == true ||
      callExpr->isNamed("||=") == true ||
      callExpr->isNamed("<<=") == true ||
      callExpr->isNamed(">>=") == true) {
    retval = true;
  }

  return retval;
}
