/*
 * 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 "passes.h"

#include "astutil.h"
#include "build.h"
#include "buildDefaultFunctions.h"
#include "config.h"
#include "driver.h"
#include "expr.h"
#include "IfExpr.h"
#include "ModuleSymbol.h"
#include "stlUtil.h"
#include "stmt.h"
#include "stringutil.h"
#include "symbol.h"
#include "TryStmt.h"
#include "wellknown.h"

FnSymbol* chplUserMain = NULL;
static bool mainReturnsSomething;

static void build_chpl_entry_points();
static void build_accessors(AggregateType* ct, Symbol* field);

static void buildDefaultOfFunction(AggregateType* ct);

static void build_union_assignment_function(AggregateType* ct);

static void build_enum_cast_function(EnumType* et);
static void build_enum_first_function(EnumType* et);
static void build_enum_enumerate_function(EnumType* et);
static void build_enum_size_function(EnumType* et);
static void build_enum_order_functions(EnumType* et);

static void build_extern_assignment_function(Type* type);

static void build_record_assignment_function(AggregateType* ct);
static void check_not_pod(AggregateType* ct);
static void build_record_hash_function(AggregateType* ct);
static void build_record_equality_function(AggregateType* ct);
static void build_record_inequality_function(AggregateType* ct);

static void buildDefaultReadWriteFunctions(AggregateType* type);

static void buildStringCastFunction(EnumType* type);

static void buildFieldAccessorFunctions(AggregateType* at);


void buildDefaultFunctions() {
  build_chpl_entry_points();

  SET_LINENO(rootModule); // todo - remove reset_ast_loc() calls below?

  std::vector<BaseAST*> asts;

  collect_asts(rootModule, asts);

  for_vector(BaseAST, ast, asts) {
    if (TypeSymbol* type = toTypeSymbol(ast)) {
      // Here we build default functions that are always generated (even when
      // the type symbol has FLAG_NO_DEFAULT_FUNCTIONS attached).
      if (AggregateType* ct = toAggregateType(type->type)) {
        buildFieldAccessorFunctions(ct);

        if (ct->wantsDefaultInitializer()) {
          ct->buildDefaultInitializer();
        }

        ct->buildCopyInitializer();

        if (!ct->symbol->hasFlag(FLAG_REF)) {
          buildDefaultDestructor(ct);
        }

        // Classes should use the nil:<type> _defaultOf method unless they
        // do not inherit from object.  For those types and records, call
        // we need a more complicated _defaultOf method generated by the
        // compiler
        if (!ct->isClass() || ct->symbol->hasFlag(FLAG_NO_OBJECT)) {
          buildDefaultOfFunction(ct);
        }
      }

      if (type->hasFlag(FLAG_NO_DEFAULT_FUNCTIONS)) {

      // Here we build default functions that respect the "no default
      // functions" pragma.
      } else if (AggregateType* ct = toAggregateType(type->type)) {
        buildDefaultReadWriteFunctions(ct);

        if (isRecord(ct)) {
          if (!isRecordWrappedType(ct)) {
            build_record_equality_function(ct);
            build_record_inequality_function(ct);
          }

          build_record_assignment_function(ct);
          build_record_hash_function(ct);

          check_not_pod(ct);
        }

        if (isUnion(ct)) {
          build_union_assignment_function(ct);
        }

      } else if (EnumType* et = toEnumType(type->type)) {
        buildEnumFunctions(et);

      } else {
        // The type is a simple type.

        // Other simple types are handled explicitly in the module code.
        // But to avoid putting a catch-all case there to implement assignment
        // for extern types that are simple (as far as we can tell), we build
        // definitions for those assignments here.
        if (type->hasFlag(FLAG_EXTERN)) {
          //build_extern_init_function(type->type);
          build_extern_assignment_function(type->type);
        }
      }
    }
  }
}


static void buildFieldAccessorFunctions(AggregateType* at) {
  for_fields(field, at) {
    if (!field->hasFlag(FLAG_IMPLICIT_ALIAS_FIELD)) {
      if (isVarSymbol(field)) {
        build_accessors(at, field);
      } else if (isEnumType(field->type)) {
        build_accessors(at, field);
      }
    }
  }
}


// function_exists returns true iff
//  function's name matches name
//  function's number of formals matches numFormals
//  function's first formal's type matches formalType1 if not NULL
//  function's second formal's type matches formalType2 if not NULL
//  function's third formal's type matches formalType3 if not NULL

static bool type_match(Type* type, Symbol* sym) {
  if (type == dtAny)
    return true;
  if (sym->type == type)
    return true;
  SymExpr* se = toSymExpr(sym->defPoint->exprType);
  if (se && se->symbol()->type == type)
    return true;
  return false;
}

typedef enum {
  FIND_EITHER = 0,
  FIND_REF,
  FIND_NOT_REF
} function_exists_kind_t;

static FnSymbol* function_exists(const char* name,
                                 int numFormals,
                                 Type* formalType1,
                                 Type* formalType2,
                                 Type* formalType3,
                                 Type* formalType4,
                                 function_exists_kind_t kind)
{
  switch(numFormals)
  {
   default:
    INT_FATAL("function_exists checks at most 4 argument types.  Add more if needed.");
    break;
   case 4:  if (!formalType4)   INT_FATAL("Missing argument formalType4");  break;
   case 3:  if (!formalType3)   INT_FATAL("Missing argument formalType3");  break;
   case 2:  if (!formalType2)   INT_FATAL("Missing argument formalType2");  break;
   case 1:  if (!formalType1)   INT_FATAL("Missing argument formalType1");  break;
   case 0:  break;
  }

  forv_Vec(FnSymbol, fn, gFnSymbols)
  {
    if (strcmp(name, fn->name))
      continue;

    // numFormals must match exactly.
    if (numFormals != fn->numFormals())
        continue;

    if (formalType1)
      if (!type_match(formalType1, fn->getFormal(1)))
        continue;

    if (formalType2)
      if (!type_match(formalType2, fn->getFormal(2)))
        continue;

    if (formalType3)
      if (!type_match(formalType3, fn->getFormal(3)))
        continue;

    if (formalType4)
      if (!type_match(formalType4, fn->getFormal(4)))
        continue;

    if (kind == FIND_REF && fn->retTag != RET_REF)
      continue;

    if (kind == FIND_NOT_REF && fn->retTag == RET_REF)
      continue;

    return fn;
  }

  // No matching function found.
  return NULL;
}

static FnSymbol* function_exists(const char* name,
                                 Type* formalType1,
                                 function_exists_kind_t kind=FIND_EITHER)
{
  return function_exists(name, 1, formalType1, NULL, NULL, NULL, kind);
}



static FnSymbol* function_exists(const char* name,
                                 Type* formalType1,
                                 Type* formalType2,
                                 function_exists_kind_t kind=FIND_EITHER)
{
  return function_exists(name, 2, formalType1, formalType2, NULL, NULL, kind);
}

static FnSymbol* function_exists(const char* name,
                                 Type* formalType1,
                                 Type* formalType2,
                                 Type* formalType3,
                                 function_exists_kind_t kind=FIND_EITHER)
{
  return function_exists(name, 3,
                         formalType1, formalType2, formalType3, NULL, kind);
}

static void fixup_accessor(AggregateType* ct, Symbol *field,
                           bool fieldIsConst, bool recordLike,
                           FnSymbol* fn)
{
  std::vector<BaseAST*> asts;
  collect_asts(fn, asts);
  for_vector(BaseAST, ast, asts) {
    if (CallExpr* call = toCallExpr(ast)) {
      if (call->isNamedAstr(field->name) && call->numActuals() == 2) {
        if (call->get(1)->typeInfo() == dtMethodToken &&
            call->get(2)->typeInfo() == ct) {
          Expr* arg2 = call->get(2);
          call->replace(new CallExpr(PRIM_GET_MEMBER,
                                     arg2->remove(),
                                     new_CStringSymbol(field->name)));
        }
      }
    }
  }
  fn->addFlag(FLAG_FIELD_ACCESSOR);
  if (fieldIsConst)
    fn->addFlag(FLAG_REF_TO_CONST);
  else if (recordLike)
    fn->addFlag(FLAG_REF_TO_CONST_WHEN_CONST_THIS);
}

// This function builds the getter or the setter, depending on the
// 'setter' argument.
FnSymbol* build_accessor(AggregateType* ct, Symbol* field,
                           bool setter, bool typeMethod) {
  const bool fieldIsConst = field->hasFlag(FLAG_CONST);
  const bool recordLike   = ct->isRecord() || ct->isUnion();
  FnSymbol*  fn           = new FnSymbol(field->name);

  fn->addFlag(FLAG_NO_IMPLICIT_COPY);
  fn->addFlag(FLAG_INLINE);

  if (ct->symbol->hasFlag(FLAG_ATOMIC_TYPE))
    fn->addFlag(FLAG_ATOMIC_TYPE);

  fn->addFlag(FLAG_FIELD_ACCESSOR);

  if (!typeMethod) {
    if (fieldIsConst)
      fn->addFlag(FLAG_REF_TO_CONST);
    else if (recordLike)
      fn->addFlag(FLAG_REF_TO_CONST_WHEN_CONST_THIS);
  }

  fn->insertFormalAtTail(new ArgSymbol(INTENT_BLANK, "_mt", dtMethodToken));

  fn->setMethod(true);

  Type* thisType = ct;
  if (isClassLike(ct) && isClass(ct) && typeMethod)
    thisType = ct->getDecoratedClass(CLASS_TYPE_GENERIC);
  ArgSymbol* _this = new ArgSymbol(INTENT_BLANK, "this", thisType);

  if (typeMethod) {
    _this->addFlag(FLAG_TYPE_VARIABLE);
  }

  _this->addFlag(FLAG_ARG_THIS);
  fn->insertFormalAtTail(_this);

  if (field->isParameter()) {
    fn->retTag = RET_PARAM;

  } else if (field->hasFlag(FLAG_TYPE_VARIABLE)) {
    fn->retTag = RET_TYPE;

  } else {
    // Can't access the field if we don't return param/type.
    // This would be different if we added something like C++ static fields
    INT_ASSERT(!typeMethod);

    if (field->hasFlag(FLAG_SUPER_CLASS)) {
      fn->retTag = RET_VALUE;
    } else {
      if (fieldIsConst || !setter)
        fn->retTag = RET_CONST_REF;
      else {
        fn->retTag = RET_REF;
        if (recordLike) {
          _this->intent = INTENT_REF;
        }
      }
    }
  }

  if (isUnion(ct)) {
    if (setter) {
      // Set the union ID in the setter.
      fn->insertAtTail(
          new CallExpr(PRIM_SET_UNION_ID,
                       _this,
                       new CallExpr(PRIM_FIELD_NAME_TO_NUM,
                                    ct->symbol,
                                    new_CStringSymbol(field->name))));
    } else {
      // Check the union ID in the getter.
      fn->insertAtTail(new CondStmt(
          new CallExpr("!=",
            new CallExpr(PRIM_GET_UNION_ID, _this),
            new CallExpr(PRIM_FIELD_NAME_TO_NUM,
                         ct->symbol,
                         new_CStringSymbol(field->name))),
          new CallExpr("halt", new_StringSymbol("illegal union access"))));
    }
  }

  Expr* toReturn = NULL;
  if (isTypeSymbol(field) && isEnumType(field->type)) {
    fn->insertAtTail(new CallExpr(PRIM_RETURN, field));
    // better flatten enumerated types now
    ct->symbol->defPoint->insertBefore(field->defPoint->remove());
  } else if (field->hasFlag(FLAG_TYPE_VARIABLE) || field->hasFlag(FLAG_SUPER_CLASS)) {
    toReturn = new CallExpr(PRIM_GET_MEMBER_VALUE,
                            new SymExpr(_this),
                            new SymExpr(new_CStringSymbol(field->name)));
  } else {
    toReturn = new CallExpr(PRIM_GET_MEMBER,
                            new SymExpr(_this),
                            new SymExpr(new_CStringSymbol(field->name)));
  }

  if (toReturn != NULL) {
    if (field->hasEitherFlag(FLAG_TYPE_VARIABLE, FLAG_PARAM)) {
      Symbol* alternate = NULL;
      if (field->hasFlag(FLAG_PARAM))
        alternate = gUninstantiated;
      else
        alternate = dtUninstantiated->symbol;

      fn->insertAtTail(new CondStmt(new CallExpr(PRIM_IS_BOUND, _this, new_CStringSymbol(field->name)),
                                    new CallExpr(PRIM_RETURN, toReturn),
                                    new CallExpr(PRIM_RETURN, alternate)));
    } else {
      fn->insertAtTail(new CallExpr(PRIM_RETURN, toReturn));
    }
  }

  DefExpr* def = new DefExpr(fn);

  ct->symbol->defPoint->insertBefore(def);

  reset_ast_loc(fn, field);

  normalize(fn);

  ct->methods.add(fn);

  fn->setMethod(true);

  fn->addFlag(FLAG_METHOD_PRIMARY);

  fn->cname = astr("chpl_get_", ct->symbol->cname, "_", fn->cname);

  fn->addFlag(FLAG_NO_PARENS);

  fn->_this = _this;

  return fn;
}

// Getter and setter functions are provided by the compiler if not supplied by
// the user.
// These functions have the same binding strength as if they were user-defined.
// This function calls build_accessor multiple times to create appropriate
// accessors.
static void build_accessors(AggregateType* ct, Symbol *field) {
  const bool fieldIsConst = field->hasFlag(FLAG_CONST);
  const bool recordLike = ct->isRecord() || ct->isUnion();
  const bool fieldTypeOrParam = field->isParameter() ||
                                field->hasFlag(FLAG_TYPE_VARIABLE);

  FnSymbol *setter = function_exists(field->name,
                                     dtMethodToken, ct, FIND_REF);
  FnSymbol *getter = function_exists(field->name,
                                     dtMethodToken, ct, FIND_NOT_REF);
  if (setter)
    fixup_accessor(ct, field, fieldIsConst, recordLike, setter);
  if (getter)
    fixup_accessor(ct, field, fieldIsConst, recordLike, getter);
  if (getter || setter)
    return;

  // Otherwise, build compiler-default getter and setter.

  if (isUnion(ct)) {
    // Unions need a special getter and setter.
    build_accessor(ct, field, /* setter? */ false, /* type method? */ false);
    build_accessor(ct, field, /* setter? */ true,  /* type method? */ false);
  } else {
    // Otherwise, only build one version for records and classes.
    // This is normally the 'ref' version.
    build_accessor(ct, field,
                   /* setter? */ !fieldIsConst, /* type method? */ false);
  }

  // If the field is type/param, add a type-method accessor.
  if (fieldTypeOrParam) {
    build_accessor(ct, field, /* getter? */ false, /* type method? */ true);
  }
}

static FnSymbol* chpl_gen_main_exists() {
  bool          errorP   = false;
  ModuleSymbol* module   = ModuleSymbol::mainModule();
  FnSymbol*     matchFn  = NULL;
  ModuleSymbol* matchMod = NULL;

  forv_Vec(FnSymbol, fn, gFnSymbols) {
    if (strcmp("main", fn->name) == 0) {
      if (fn->numFormals() == 0 ||
          (fn->numFormals() == 1 &&
           fn->getFormal(1)->typeInfo() == dtArray) ) {
        mainHasArgs = (fn->numFormals() > 0);

        CallExpr* ret = toCallExpr(fn->body->body.last());

        if (ret == NULL || ret->isPrimitive(PRIM_RETURN) == false) {
          INT_FATAL(fn, "function is not normalized");
        }

        SymExpr* sym = toSymExpr(ret->get(1));

        if (sym == NULL) {
          INT_FATAL(fn, "function is not normalized");
        }

        mainReturnsSomething = (sym->symbol() != gVoid);

        ModuleSymbol* fnMod = fn->getModule();

        if (fnMod == module) {
          if (matchFn == NULL) {
            matchFn  = fn;
            matchMod = fnMod;

          } else {
            if (errorP == false) {
              const char* info = "";

              errorP = true;

              if (fnMod == matchMod) {
                info = " (use --main-module to disambiguate)";
              }

              USR_FATAL_CONT("Ambiguous main() function%s:", info);
              USR_PRINT(matchFn, "in module %s", matchMod->name);
            }

            USR_PRINT(fn, "in module %s", fnMod->name);
          } // else, this is not a candidate for the main module
        }

      } else {
        USR_FATAL_CONT("main() function with invalid signature");
        USR_PRINT(fn, "in module %s", fn->getModule()->name);
      }
    }
  }

  if (errorP == false) {
    USR_STOP();
  }

  return matchFn;
}


static void build_chpl_entry_points() {
  //
  // chpl_user_main is the (user) programmatic portion of the app
  //
  ModuleSymbol* mainModule   = ModuleSymbol::mainModule();
  chplUserMain = chpl_gen_main_exists();

  if (fLibraryCompile == true && chplUserMain != NULL) {
    USR_WARN(chplUserMain,
             "'main()' has no special meaning when compiling "
             "in --library mode");
  }

  if (chplUserMain == NULL) {
    SET_LINENO(mainModule);

    chplUserMain          = new FnSymbol("main");
    chplUserMain->retType = dtVoid;

    mainModule->block->insertAtTail(new DefExpr(chplUserMain));

    normalize(chplUserMain);

  } else {
    if (! isModuleSymbol(chplUserMain->defPoint->parentSymbol))
      USR_FATAL_CONT(chplUserMain,
                "main function must be defined at module scope");

    if (chplUserMain->retTag == RET_TYPE || chplUserMain->retTag == RET_PARAM)
      USR_FATAL_CONT(chplUserMain,
                "main function cannot return a type or a param");
  }

  SET_LINENO(chplUserMain);

  chplUserMain->cname = "chpl_user_main";

  //
  // chpl_gen_main is the entry point for the compiler-generated code.
  // It invokes the user's code.
  //

  ArgSymbol* arg = new ArgSymbol(INTENT_BLANK, "_arg", dtMainArgument);

  chpl_gen_main          = new FnSymbol("chpl_gen_main");
  chpl_gen_main->retType = dtInt[INT_SIZE_64];
  chpl_gen_main->cname   = "chpl_gen_main";

  chpl_gen_main->insertFormalAtTail(arg);

  chpl_gen_main->addFlag(FLAG_EXPORT);  // chpl_gen_main is always exported.
  chpl_gen_main->addFlag(FLAG_LOCAL_ARGS);
  chpl_gen_main->addFlag(FLAG_COMPILER_GENERATED);
  chpl_gen_main->addFlag(FLAG_GEN_MAIN_FUNC);

  mainModule->block->insertAtTail(new DefExpr(chpl_gen_main));

  VarSymbol* main_ret = newTemp("_main_ret", dtInt[INT_SIZE_64]);
  VarSymbol* endCount = newTemp("_endCount");

  chpl_gen_main->insertAtTail(new DefExpr(main_ret));
  chpl_gen_main->insertAtTail(new DefExpr(endCount));

  //
  // In --minimal-modules compilation mode, we won't have any
  // parallelism, so no need for end counts (or atomic/sync types to
  // support them).
  //
  if (fMinimalModules == false) {
    chpl_gen_main->insertAtTail(new CallExpr(PRIM_MOVE,
                                             endCount,
                                             new CallExpr("_endCountAlloc",
                                                          gFalse)));

    chpl_gen_main->insertAtTail(new CallExpr(PRIM_SET_DYNAMIC_END_COUNT, endCount));
  }

  chpl_gen_main->insertAtTail(new CallExpr("chpl_rt_preUserCodeHook"));

  // We have to initialize the main module explicitly.
  // It will initialize all the modules it uses, recursively.
  if (!fMultiLocaleInterop) {
    chpl_gen_main->insertAtTail(new CallExpr(mainModule->initFn));
  } else {
    // Create an extern definition for the multilocale library server's main
    // function.  chpl_gen_main needs to call it in the course of its run, so
    // that we correctly set up the runtime.
    FnSymbol* chpl_mli_smain = new FnSymbol("chpl_mli_smain");
    chpl_mli_smain->addFlag(FLAG_EXTERN);
    chpl_mli_smain->addFlag(FLAG_LOCAL_ARGS);
    // Takes the connection information
    ArgSymbol* setup_conn = new ArgSymbol(INTENT_BLANK, "setup_conn",
                                          dtStringC);
    chpl_mli_smain->insertFormalAtTail(setup_conn);

    mainModule->block->insertAtTail(new DefExpr(chpl_mli_smain));

    VarSymbol* connection = newTemp("setup_conn");
    chpl_gen_main->insertAtTail(new DefExpr(connection));
    chpl_gen_main->insertAtTail(new CallExpr(PRIM_MOVE, connection,
                                             new CallExpr("chpl_get_mli_connection",
                                                          arg)));
    chpl_gen_main->insertAtTail(new CallExpr("chpl_mli_smain", connection));
    normalize(chpl_mli_smain);
  }

  bool main_ret_set = false;

  if (fLibraryCompile == false) {
    SET_LINENO(chpl_gen_main);

    if (mainHasArgs == true) {
      VarSymbol* converted_args = newTemp("_main_args");

      converted_args->addFlag(FLAG_INSERT_AUTO_DESTROY);

      chpl_gen_main->insertAtTail(new DefExpr(converted_args));
      chpl_gen_main->insertAtTail(new CallExpr(PRIM_MOVE,
                                               converted_args,
                                               new CallExpr("chpl_convert_args", arg)));

      if (mainReturnsSomething) {
        chpl_gen_main->insertAtTail(new CallExpr(PRIM_MOVE,
                                                 main_ret,
                                                 new CallExpr("main", converted_args)));
        main_ret_set = true;

      } else {
        chpl_gen_main->insertAtTail(new CallExpr("main", converted_args));
      }

    } else {
      if (mainReturnsSomething) {
        chpl_gen_main->insertAtTail(new CallExpr(PRIM_MOVE,
                                                 main_ret,
                                                 new CallExpr("main")));
        main_ret_set = true;

      } else {
        chpl_gen_main->insertAtTail(new CallExpr("main"));
      }
    }
  }

  if (!main_ret_set) {
    chpl_gen_main->insertAtTail(new CallExpr(PRIM_MOVE,
                                             main_ret,
                                             new_IntSymbol(0, INT_SIZE_64)));
  }

  chpl_gen_main->insertAtTail(new CallExpr("chpl_rt_postUserCodeHook"));

  //
  // In --minimal-modules compilation mode, we won't be waiting on an
  // endcount (see comment above)
  //
  if (fMinimalModules == false) {
    chpl_gen_main->insertAtTail(new CallExpr("_waitEndCount", endCount));
    chpl_gen_main->insertAtTail(new CallExpr("chpl_deinitModules"));
  }

  chpl_gen_main->insertAtTail(new CallExpr(PRIM_RETURN, main_ret));

  normalize(chpl_gen_main);
}

static void build_record_equality_function(AggregateType* ct) {
  if (function_exists("==", ct, ct))
    return;

  FnSymbol* fn = new FnSymbol("==");
  fn->addFlag(FLAG_COMPILER_GENERATED);
  fn->addFlag(FLAG_LAST_RESORT);
  ArgSymbol* arg1 = new ArgSymbol(INTENT_BLANK, "_arg1", ct);
  arg1->addFlag(FLAG_MARKED_GENERIC);
  ArgSymbol* arg2 = new ArgSymbol(INTENT_BLANK, "_arg2", ct);
  arg2->addFlag(FLAG_MARKED_GENERIC);
  fn->insertFormalAtTail(arg1);
  fn->insertFormalAtTail(arg2);
  fn->retType = dtBool;
  fn->where = new BlockStmt(new CallExpr("==",
                                         new CallExpr(PRIM_TYPEOF, arg1),
                                         new CallExpr(PRIM_TYPEOF, arg2)));

  for_fields(tmp, ct) {
    if (!tmp->hasFlag(FLAG_IMPLICIT_ALIAS_FIELD)) {
      Expr* left = new CallExpr(tmp->name, gMethodToken, arg1);
      Expr* right = new CallExpr(tmp->name, gMethodToken, arg2);
      fn->insertAtTail(new CondStmt(new CallExpr("!=", left, right), new CallExpr(PRIM_RETURN, gFalse)));
    }
  }
  fn->insertAtTail(new CallExpr(PRIM_RETURN, gTrue));
  DefExpr* def = new DefExpr(fn);
  ct->symbol->defPoint->insertBefore(def);
  reset_ast_loc(def, ct->symbol);
  normalize(fn);
}


static void build_record_inequality_function(AggregateType* ct) {
  if (function_exists("!=", ct, ct))
    return;

  FnSymbol* fn = new FnSymbol("!=");
  fn->addFlag(FLAG_COMPILER_GENERATED);
  fn->addFlag(FLAG_LAST_RESORT);
  ArgSymbol* arg1 = new ArgSymbol(INTENT_BLANK, "_arg1", ct);
  arg1->addFlag(FLAG_MARKED_GENERIC);
  ArgSymbol* arg2 = new ArgSymbol(INTENT_BLANK, "_arg2", ct);
  arg2->addFlag(FLAG_MARKED_GENERIC);
  fn->insertFormalAtTail(arg1);
  fn->insertFormalAtTail(arg2);
  fn->retType = dtBool;
  fn->where = new BlockStmt(new CallExpr("==",
                                         new CallExpr(PRIM_TYPEOF, arg1),
                                         new CallExpr(PRIM_TYPEOF, arg2)));

  for_fields(tmp, ct) {
    if (!tmp->hasFlag(FLAG_IMPLICIT_ALIAS_FIELD)) {
      Expr* left = new CallExpr(tmp->name, gMethodToken, arg1);
      Expr* right = new CallExpr(tmp->name, gMethodToken, arg2);
      fn->insertAtTail(new CondStmt(new CallExpr("!=", left, right),
                                    new CallExpr(PRIM_RETURN, gTrue)));
    }
  }
  fn->insertAtTail(new CallExpr(PRIM_RETURN, gFalse));
  DefExpr* def = new DefExpr(fn);
  ct->symbol->defPoint->insertBefore(def);
  reset_ast_loc(def, ct->symbol);
  normalize(fn);
}

// Builds default enum functions that are defined outside of the scope in which
// the enum type is defined
// It is necessary to have this separated out, because such functions are not
// automatically created when the EnumType is copied.
void buildEnumFunctions(EnumType* et) {
  buildStringCastFunction(et);

  build_enum_cast_function(et);
  build_enum_enumerate_function(et);
  build_enum_first_function(et);
  build_enum_size_function(et);
  build_enum_order_functions(et);
}


static void build_enum_size_function(EnumType* et) {
  if (function_exists("size", et))
    return;
  // Build a function that returns the length of the enum specified
  FnSymbol* fn = new FnSymbol("size");

  fn->addFlag(FLAG_COMPILER_GENERATED);
  fn->addFlag(FLAG_LAST_RESORT);
  fn->addFlag(FLAG_NO_PARENS);

  fn->setMethod(true);

  fn->insertFormalAtTail(new ArgSymbol(INTENT_BLANK, "_mt", dtMethodToken));

  fn->_this = new ArgSymbol(INTENT_BLANK, "this", et);
  fn->_this->addFlag(FLAG_ARG_THIS);
  fn->_this->addFlag(FLAG_TYPE_VARIABLE);

  fn->insertFormalAtTail(fn->_this);

  fn->retTag = RET_PARAM;

  VarSymbol*  varS = new_IntSymbol(et->constants.length);
  fn->insertAtTail(new CallExpr(PRIM_RETURN, varS));

  DefExpr* fnDef = new DefExpr(fn);
  // needs to go in the base module because when called from _defaultOf(et),
  // they are automatically inserted
  baseModule->block->insertAtTail(fnDef);
  reset_ast_loc(fnDef, et->symbol);

  normalize(fn);
  fn->tagIfGeneric();
}



static void build_enum_first_function(EnumType* et) {
  if (function_exists("chpl_enum_first", et))
    return;
  // Build a function that returns the first option for the enum
  // specified, also known as the default.
  FnSymbol* fn = new FnSymbol("chpl_enum_first");
  fn->addFlag(FLAG_COMPILER_GENERATED);
  fn->addFlag(FLAG_LAST_RESORT);
  // Making this compiler generated allows users to define what the
  // default is for a particular enum.  They can also redefine the
  // _defaultOf function for the enum to obtain this functionality (and
  // that is the encouraged path to take).
  fn->addFlag(FLAG_INLINE);
  ArgSymbol* arg = new ArgSymbol(INTENT_BLANK, "t", et);
  arg->addFlag(FLAG_TYPE_VARIABLE);
  fn->insertFormalAtTail(arg);
  fn->retTag = RET_PARAM;

  DefExpr* defExpr = toDefExpr(et->constants.head);
  if (defExpr)
    fn->insertAtTail(new CallExpr(PRIM_RETURN, defExpr->sym));
  else
    fn->insertAtTail(new CallExpr(PRIM_RETURN, gVoid));
  // If there are one or more enumerators for this type, return the first one
  // listed.  Otherwise return nothing.

  DefExpr* fnDef = new DefExpr(fn);
  // needs to go in the base module because when called from _defaultOf(et),
  // they are automatically inserted
  baseModule->block->insertAtTail(fnDef);
  reset_ast_loc(fnDef, et->symbol);

  normalize(fn);
  fn->tagIfGeneric();
}

static void build_enum_enumerate_function(EnumType* et) {
  // Build a function that returns a tuple of the enum's values
  // Each enum type has its own chpl_enum_enumerate function.
  FnSymbol* fn = new FnSymbol("chpl_enum_enumerate");
  fn->addFlag(FLAG_COMPILER_GENERATED);
  fn->addFlag(FLAG_LAST_RESORT);
  ArgSymbol* arg = new ArgSymbol(INTENT_BLANK, "t", et);
  arg->addFlag(FLAG_TYPE_VARIABLE);
  fn->insertFormalAtTail(arg);

  baseModule->block->insertAtTail(new DefExpr(fn));

  // Generate the tuple of enum values for the given enum type
  CallExpr* call = new CallExpr("_build_tuple");
  for_enums(constant, et) {
    call->insertAtTail(constant->sym);
  }

  fn->insertAtTail(new CallExpr(PRIM_RETURN, call));

  normalize(fn);
  fn->tagIfGeneric();
}

static void build_enum_cast_function(EnumType* et) {
  bool initsExist = !et->isAbstract();

  FnSymbol* fn;
  ArgSymbol* arg1, *arg2;
  DefExpr* def;
  // only build the int<->enum cast functions if some enums were given values
  if (initsExist) {
    // build the integral value to enumerated type cast function
    fn = new FnSymbol(astr_cast);
    fn->addFlag(FLAG_COMPILER_GENERATED);
    fn->addFlag(FLAG_LAST_RESORT);
    arg1 = new ArgSymbol(INTENT_BLANK, "t", et);
    arg1->addFlag(FLAG_TYPE_VARIABLE);
    arg2 = new ArgSymbol(INTENT_BLANK, "_arg2", dtIntegral);
    fn->insertFormalAtTail(arg1);
    fn->insertFormalAtTail(arg2);

    // Generate a select statement with when clauses for each of the
    // enumeration constants, and an otherwise clause that calls halt.
    int64_t count = 0;
    BlockStmt* whenstmts = buildChapelStmt();
    Expr* lastInit = NULL;
    for_enums(constant, et) {
      if (constant->init) {
        lastInit = constant->init;
        count = 0;
      } else {
        if (lastInit) {
          count++;
        }
      }
      if (lastInit != NULL) {
        CondStmt* when =
          new CondStmt(new CallExpr(PRIM_WHEN,
                                    new CallExpr("+", lastInit->copy(),
                                                 new SymExpr(new_IntSymbol(count)))),
                       new CallExpr(PRIM_RETURN,
                                    new CallExpr(PRIM_CAST,
                                                  et->symbol, arg2)));
        whenstmts->insertAtTail(when);
      }
    }
    const char * errorString = "enumerated type out of bounds";
    CondStmt* otherwise =
      new CondStmt(new CallExpr(PRIM_WHEN),
                   new BlockStmt(new CallExpr("halt",
                                 new_StringSymbol(errorString))));
    whenstmts->insertAtTail(otherwise);
    fn->insertAtTail(buildSelectStmt(new SymExpr(arg2), whenstmts));

    def = new DefExpr(fn);
    baseModule->block->insertAtTail(def);
    reset_ast_loc(def, et->symbol);
    normalize(fn);
    fn->tagIfGeneric();

    // build the enumerated to integer cast
    fn = new FnSymbol(astr_cast);
    fn->addFlag(FLAG_COMPILER_GENERATED);
    fn->addFlag(FLAG_LAST_RESORT);
    arg1 = new ArgSymbol(INTENT_BLANK, "t", dtIntegral);
    arg1->addFlag(FLAG_TYPE_VARIABLE);
    arg2 = new ArgSymbol(INTENT_BLANK, "_arg2", et);
    fn->insertFormalAtTail(arg1);
    fn->insertFormalAtTail(arg2);

    if (et->isConcrete()) {
      // If this enum is concrete, rely on the C cast and inline
      fn->insertAtTail(new CallExpr(PRIM_RETURN,
                                    new CallExpr(PRIM_CAST, arg1, arg2)));
      fn->addFlag(FLAG_INLINE);
    } else {
      // Otherwise, it's semi-concrete, so we need errors for some cases.
      // Generate a select statement with when clauses for each of the
      // enumeration constants, and an otherwise clause that calls halt.

      count = 0;
      whenstmts = buildChapelStmt();
      lastInit = NULL;
      for_enums(constant, et) {
        if (constant->init) {
          lastInit = constant->init;
          count = 0;
        } else {
          if (lastInit != NULL) {
            count++;
          }
        }
        CallExpr* result;
        if (lastInit) {
          result = new CallExpr(PRIM_RETURN,
                                new CallExpr(PRIM_CAST, arg1,
                                             new CallExpr("+", lastInit->copy(),
                                                          new SymExpr(new_IntSymbol(count)))));
        } else {
          const char *errorString = astr("illegal cast: ", et->symbol->name,
                                         ".", constant->sym->name,
                                         " has no integer value");
          result = new CallExpr("halt", new_StringSymbol(errorString));
        }
        CondStmt* when =
          new CondStmt(new CallExpr(PRIM_WHEN, new SymExpr(constant->sym)),
                       result);

        whenstmts->insertAtTail(when);
      }

      otherwise =
        new CondStmt(new CallExpr(PRIM_WHEN),
                     new BlockStmt(new CallExpr("halt",
                                                new_StringSymbol(errorString))));
      whenstmts->insertAtTail(otherwise);
      fn->insertAtTail(buildSelectStmt(new SymExpr(arg2), whenstmts));
    }

    def = new DefExpr(fn);
    //
    // Like other enum functions, these cast functions have
    // traditionally needed to go in the base module in order to be
    // available in the event of local enums.
    //
    baseModule->block->insertAtTail(def);
    reset_ast_loc(def, et->symbol);
    normalize(fn);
    fn->tagIfGeneric();
  }

  // string to enumerated type cast function
  fn = new FnSymbol(astr_cast);
  fn->addFlag(FLAG_COMPILER_GENERATED);
  fn->addFlag(FLAG_LAST_RESORT);
  arg1 = new ArgSymbol(INTENT_BLANK, "t", et);
  arg1->addFlag(FLAG_TYPE_VARIABLE);
  arg2 = new ArgSymbol(INTENT_BLANK, "_arg2", dtString);
  fn->insertFormalAtTail(arg1);
  fn->insertFormalAtTail(arg2);

  CondStmt* cond = NULL;
  for_enums(constant, et) {
    cond = new CondStmt(
             new CallExpr("==", arg2, new_StringSymbol(constant->sym->name)),
             new CallExpr(PRIM_RETURN, constant->sym),
             cond);
    cond = new CondStmt(
             new CallExpr("==", arg2,
                          new_StringSymbol(
                            astr(et->symbol->name, ".", constant->sym->name))),
             new CallExpr(PRIM_RETURN, constant->sym),
             cond);
  }

  fn->insertAtTail(cond);

  fn->throwsErrorInit();
  fn->insertAtTail(new TryStmt(
                     false,
                     new BlockStmt(
                       new CallExpr("chpl_enum_cast_error",
                                    arg2,
                                    new_StringSymbol(et->symbol->name))),
                     NULL));
  fn->addFlag(FLAG_INSERT_LINE_FILE_INFO);
  fn->addFlag(FLAG_ALWAYS_PROPAGATE_LINE_FILE_INFO);

  fn->insertAtTail(new CallExpr(PRIM_RETURN,
                                toDefExpr(et->constants.first())->sym));

  def = new DefExpr(fn);
  //
  // these cast functions need to go in the base module because they
  // are automatically inserted to handle implicit coercions
  //
  baseModule->block->insertAtTail(def);
  reset_ast_loc(def, et->symbol);
  normalize(fn);
  fn->tagIfGeneric();
}


//
// Create a function that converts an enum symbol to an integer 0, 1, 2, ...:
//
//   'proc chpl_enumToOrder([param] e: enumerated) [param] : int'
//
static void build_enum_to_order_function(EnumType* et, bool paramVersion) {
  FnSymbol* fn = new FnSymbol(astr("chpl__enumToOrder"));
  fn->addFlag(FLAG_COMPILER_GENERATED);
  fn->addFlag(FLAG_LAST_RESORT);
  ArgSymbol* arg1 = new ArgSymbol(paramVersion ? INTENT_PARAM: INTENT_BLANK,
                                  "e", et);
  fn->insertFormalAtTail(arg1);
  if (paramVersion)
    fn->retTag = RET_PARAM;

  // Generate a select statement with when clauses for each of the
  // enumeration constants, and an otherwise clause that calls halt.
  int64_t count = 0;
  BlockStmt* whenstmts = buildChapelStmt();
  for_enums(constant, et) {
    CondStmt* when =
      new CondStmt(new CallExpr(PRIM_WHEN, new SymExpr(constant->sym)),
                   new CallExpr(PRIM_RETURN, new SymExpr(new_IntSymbol(count)))
                   );
    whenstmts->insertAtTail(when);
    count++;
  }
  const char * errorString = "enumerated type out of bounds";
  CondStmt* otherwise =
    new CondStmt(new CallExpr(PRIM_WHEN),
                 new BlockStmt(new CallExpr("halt",
                                            new_StringSymbol(errorString))));
  whenstmts->insertAtTail(otherwise);
  fn->insertAtTail(buildSelectStmt(new SymExpr(arg1), whenstmts));
  DefExpr* def = new DefExpr(fn);
  baseModule->block->insertAtTail(def);
  reset_ast_loc(def, et->symbol);
  normalize(fn);
  fn->tagIfGeneric();
}

//
// Create a function that converts an integer 0, 1, 2, ... into a
// value from the given enumerated type 'et':
//
//   'proc chpl_enumToOrder(i: integral, type et: et): et'
//
static void build_order_to_enum_function(EnumType* et) {
  FnSymbol* fn = new FnSymbol(astr("chpl__orderToEnum"));
  fn->addFlag(FLAG_COMPILER_GENERATED);
  fn->addFlag(FLAG_LAST_RESORT);
  ArgSymbol* arg1 = new ArgSymbol(INTENT_BLANK, "i", dtIntegral);
  ArgSymbol* arg2 = new ArgSymbol(INTENT_BLANK, "et", et);
  arg2->addFlag(FLAG_TYPE_VARIABLE);
  fn->insertFormalAtTail(arg1);
  fn->insertFormalAtTail(arg2);

  // Generate a select statement with when clauses for each of the
  // enumeration constants, and an otherwise clause that calls halt.
  int64_t count = 0;
  BlockStmt* whenstmts = buildChapelStmt();
  for_enums(constant, et) {
    CondStmt* when =
      new CondStmt(new CallExpr(PRIM_WHEN, new SymExpr(new_IntSymbol(count))),
                   new CallExpr(PRIM_RETURN, new SymExpr(constant->sym)));;
    whenstmts->insertAtTail(when);
    count++;
  }
  const char * errorString = "enumerated type out of bounds in chpl__orderToEnum()";
  CondStmt* otherwise =
    new CondStmt(new CallExpr(PRIM_WHEN),
                 new BlockStmt(new CallExpr("halt",
                                            new_StringSymbol(errorString))));
  whenstmts->insertAtTail(otherwise);
  fn->insertAtTail(buildSelectStmt(new SymExpr(arg1), whenstmts));
  DefExpr* def = new DefExpr(fn);
  baseModule->block->insertAtTail(def);
  reset_ast_loc(def, et->symbol);
  normalize(fn);
  fn->tagIfGeneric();
}

//
// Build functions for converting enums to 0-based ordinal values and
// back again
//
static void build_enum_order_functions(EnumType* et) {
  //
  // TODO: optimize case when enums are adjacent by using math rather
  // than select statements
  //
  build_enum_to_order_function(et, true);
  build_enum_to_order_function(et, false);
  build_order_to_enum_function(et);
}


static void build_record_assignment_function(AggregateType* ct) {
  if (function_exists("=", ct, ct))
    return;

  bool externRecord = ct->symbol->hasFlag(FLAG_EXTERN);

  FnSymbol* fn = new FnSymbol("=");
  fn->addFlag(FLAG_ASSIGNOP);
  fn->addFlag(FLAG_COMPILER_GENERATED);
  fn->addFlag(FLAG_LAST_RESORT);

  ArgSymbol* arg1 = new ArgSymbol(INTENT_REF, "_arg1", ct);
  arg1->addFlag(FLAG_MARKED_GENERIC);
  ArgSymbol* arg2 = new ArgSymbol(INTENT_REF_MAYBE_CONST, "_arg2", ct);
  arg2->addFlag(FLAG_MARKED_GENERIC);

  fn->insertFormalAtTail(arg1);
  fn->insertFormalAtTail(arg2);

  fn->retType = dtVoid;
  fn->where = new BlockStmt(new CallExpr("==",
                                         new CallExpr(PRIM_TYPEOF, arg1),
                                         new CallExpr(PRIM_TYPEOF, arg2)));


  if (externRecord) {
    fn->insertAtTail(new CallExpr(PRIM_ASSIGN, arg1, arg2));
    ct->symbol->addFlag(FLAG_POD);
    fn->addFlag(FLAG_INLINE);
  } else {
    for_fields(tmp, ct) {
      if (!tmp->hasFlag(FLAG_IMPLICIT_ALIAS_FIELD)) {
        if (!tmp->hasFlag(FLAG_TYPE_VARIABLE) &&
            !tmp->isParameter())
          fn->insertAtTail(new CallExpr("=",
                                        new CallExpr(".", arg1, new_CStringSymbol(tmp->name)),
                                        new CallExpr(".", arg2, new_CStringSymbol(tmp->name))));
      }
    }
  }

  DefExpr* def = new DefExpr(fn);
  ct->symbol->defPoint->insertBefore(def);
  reset_ast_loc(def, ct->symbol);
  normalize(fn);
}

static void build_extern_assignment_function(Type* type)
{
  if (function_exists("=", type, type))
    return;

  FnSymbol* fn = new FnSymbol("=");
  fn->addFlag(FLAG_ASSIGNOP);
  type->symbol->addFlag(FLAG_POD);
  fn->addFlag(FLAG_COMPILER_GENERATED);
  fn->addFlag(FLAG_LAST_RESORT);
  fn->addFlag(FLAG_INLINE);

  ArgSymbol* arg1 = new ArgSymbol(INTENT_REF, "_arg1", type);
  ArgSymbol* arg2 = new ArgSymbol(INTENT_BLANK, "_arg2", type);
  fn->insertFormalAtTail(arg1);
  fn->insertFormalAtTail(arg2);
  fn->insertAtTail(new CallExpr(PRIM_ASSIGN, arg1, arg2));
  DefExpr* def = new DefExpr(fn);
  if (type->symbol->defPoint->parentSymbol == rootModule)
    baseModule->block->body.insertAtTail(def);
  else
    type->symbol->defPoint->insertBefore(def);
  reset_ast_loc(def, type->symbol);

  normalize(fn);
}


// TODO: we should know what field is active after assigning unions
static void build_union_assignment_function(AggregateType* ct) {
  if (function_exists("=", ct, ct))
    return;

  FnSymbol* fn = new FnSymbol("=");
  fn->addFlag(FLAG_ASSIGNOP);
  fn->addFlag(FLAG_COMPILER_GENERATED);
  fn->addFlag(FLAG_LAST_RESORT);
  ArgSymbol* arg1 = new ArgSymbol(INTENT_REF, "_arg1", ct);
  ArgSymbol* arg2 = new ArgSymbol(INTENT_BLANK, "_arg2", ct);
  fn->insertFormalAtTail(arg1);
  fn->insertFormalAtTail(arg2);
  fn->retType = dtUnknown;
  fn->insertAtTail(new CallExpr(PRIM_SET_UNION_ID, arg1, new_IntSymbol(0)));
  for_fields(tmp, ct) {
    if (!tmp->hasFlag(FLAG_IMPLICIT_ALIAS_FIELD)) {
      if (!tmp->hasFlag(FLAG_TYPE_VARIABLE)) {
        fn->insertAtTail(new CondStmt(
            new CallExpr("==",
              new CallExpr(PRIM_GET_UNION_ID, arg2),
              new CallExpr(PRIM_FIELD_NAME_TO_NUM,
                           ct->symbol,
                           new_CStringSymbol(tmp->name))),
            new CallExpr("=",
              new CallExpr(".", arg1, new_StringSymbol(tmp->name)),
              new CallExpr(".", arg2, new_StringSymbol(tmp->name)))));
      }
    }
  }
  DefExpr* def = new DefExpr(fn);
  ct->symbol->defPoint->insertBefore(def);
  reset_ast_loc(def, ct->symbol);
  normalize(fn);
}

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

static void check_not_pod(AggregateType* at) {
  if (function_exists("chpl__initCopy", at) == NULL) {

    if (at->hasUserDefinedInitEquals()) {
      at->symbol->addFlag(FLAG_NOT_POD);
    } else {
      // Compiler-generated copy-initializers should not disable POD
      FnSymbol* fn = function_exists("init", dtMethodToken, at, at);
      if (fn != NULL && fn->hasFlag(FLAG_COMPILER_GENERATED) == false) {
        at->symbol->addFlag(FLAG_NOT_POD);
      }
    }
  }
}

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

static void build_record_hash_function(AggregateType *ct) {
  if (function_exists("chpl__defaultHash", ct))
    return;

  FnSymbol *fn = new FnSymbol("chpl__defaultHash");
  fn->addFlag(FLAG_COMPILER_GENERATED);
  fn->addFlag(FLAG_LAST_RESORT);
  ArgSymbol *arg = new ArgSymbol(INTENT_BLANK, "r", ct);
  arg->addFlag(FLAG_MARKED_GENERIC);
  fn->insertFormalAtTail(arg);

  if (ct->fields.length == 0) {
    fn->insertAtTail(new CallExpr(PRIM_RETURN, new_UIntSymbol(0)));
    fn->addFlag(FLAG_INLINE);
  } else {
    CallExpr *call = NULL;
    bool first = true;
    int i = 1;
    for_fields(field, ct) {
      if (!field->hasFlag(FLAG_IMPLICIT_ALIAS_FIELD)) {
        CallExpr *field_access = new CallExpr(field->name, gMethodToken, arg);
        if (first) {
          call = new CallExpr("chpl__defaultHash", field_access);
          first = false;
        } else {
          call = new CallExpr("chpl__defaultHashCombine",
                              new CallExpr("chpl__defaultHash", field_access),
                              call,
                              new_IntSymbol(i));
        }
      }
      i++;
    }
    fn->insertAtTail(new CallExpr(PRIM_RETURN, call));
  }
  DefExpr *def = new DefExpr(fn);
  ct->symbol->defPoint->insertBefore(def);
  reset_ast_loc(def, ct->symbol);
  normalize(fn);
}

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

static void buildDefaultOfFunction(AggregateType* ct) {
  if (ct->symbol->hasEitherFlag(FLAG_TUPLE, FLAG_ITERATOR_RECORD) &&
      ct->defaultValue != gNil &&
      function_exists("_defaultOf", ct) == NULL) {

    FnSymbol*  fn  = new FnSymbol("_defaultOf");
    ArgSymbol* arg = new ArgSymbol(INTENT_BLANK, "t", ct);
    DefExpr*   def = new DefExpr(fn);

    arg->addFlag(FLAG_MARKED_GENERIC);

    arg->addFlag(FLAG_TYPE_VARIABLE);

    fn->addFlag(FLAG_COMPILER_GENERATED);
    fn->addFlag(FLAG_LAST_RESORT);
    fn->addFlag(FLAG_INLINE);

    fn->insertFormalAtTail(arg);

    // The shell for the function must exist before generics are
    // resolved but the body cannot be completed until resolution.
    if (ct->symbol->hasFlag(FLAG_TUPLE)) {

    } else if (ct->symbol->hasFlag(FLAG_ITERATOR_RECORD)) {
      fn->insertAtTail(new CallExpr(PRIM_RETURN, arg));

    } else {
      INT_FATAL("Unhandled _defaultOf generation case");
    }

    ct->symbol->defPoint->insertBefore(def);

    reset_ast_loc(def, ct->symbol);

    // Do not normalize until the definition has been inserted
    normalize(fn);
  }
}

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

static bool inheritsFromError(Type* t) {
  bool retval = false;

  if (t == dtError) {
    retval = true;

  } else if (AggregateType* at = toAggregateType(t)) {
    forv_Vec(AggregateType, parent, at->dispatchParents) {
      if (inheritsFromError(parent) == true) {
        retval = true;
        break;
      }
    }
  }

  return retval;
}


// common code to create a writeThis() function without filling in the body
FnSymbol* buildWriteThisFnSymbol(AggregateType* ct, ArgSymbol** filearg) {
  FnSymbol* fn = new FnSymbol("writeThis");

  fn->addFlag(FLAG_COMPILER_GENERATED);
  fn->addFlag(FLAG_LAST_RESORT);
  if (ct->isClass() && ct != dtObject)
    fn->addFlag(FLAG_OVERRIDE);
  else
    fn->addFlag(FLAG_INLINE);

  fn->cname = astr("_auto_", ct->symbol->name, "_write");
  fn->_this = new ArgSymbol(INTENT_BLANK, "this", ct);
  fn->_this->addFlag(FLAG_ARG_THIS);

  ArgSymbol* fileArg = new ArgSymbol(INTENT_BLANK, "f", dtAny);
  *filearg = fileArg;

  fileArg->addFlag(FLAG_MARKED_GENERIC);

  fn->setMethod(true);

  fn->insertFormalAtTail(new ArgSymbol(INTENT_BLANK, "_mt", dtMethodToken));
  fn->insertFormalAtTail(fn->_this);
  fn->insertFormalAtTail(fileArg);

  fn->retType = dtVoid;

  DefExpr* def = new DefExpr(fn);

  ct->symbol->defPoint->insertBefore(def);

  fn->setMethod(true);
  fn->addFlag(FLAG_METHOD_PRIMARY);

  reset_ast_loc(def, ct->symbol);

  ct->methods.add(fn);

  return fn;
}

static void buildDefaultReadWriteFunctions(AggregateType* ct) {
  bool hasReadWriteThis         = false;
  bool hasReadThis              = false;
  bool hasWriteThis             = false;
  bool makeReadThisAndWriteThis = true;

  //
  // We have no QIO when compiling with --minimal-modules, so no need
  // to build default R/W functions.
  //
  if (fMinimalModules == true) {
    return;
  }

  // This is a workaround - want Error objects to overload message()
  // to build their own description.
  if (inheritsFromError(ct))
    return;

  // If we have a readWriteThis, we'll call it from readThis/writeThis.
  if (function_exists("readWriteThis", dtMethodToken, ct, dtAny)) {
    hasReadWriteThis = true;
  }

  if (function_exists("writeThis", dtMethodToken, ct, dtAny)) {
    hasWriteThis = true;
  }

  if (function_exists("readThis", dtMethodToken, ct, dtAny)) {
    hasReadThis = true;
  }

  // We'll make a writeThis and a readThis if neither exist.
  // If only one exists, we leave just one (as some types
  // can be written but not read, for example).
  if (hasWriteThis || hasReadThis) {
    makeReadThisAndWriteThis = false;
  }

  // Make writeThis when appropriate
  if (makeReadThisAndWriteThis == true && hasWriteThis == false) {
    ArgSymbol* fileArg = NULL;
    FnSymbol* fn = buildWriteThisFnSymbol(ct, &fileArg);

    if (hasReadWriteThis == true) {
      Expr* dotReadWriteThis = buildDotExpr(fn->_this, "readWriteThis");

      fn->insertAtTail(new CallExpr(dotReadWriteThis, fileArg));

    } else {
      fn->insertAtTail(new CallExpr("writeThisDefaultImpl",
                                    fileArg,
                                    fn->_this));
    }

    normalize(fn);
  }

  // Make readThis when appropriate
  if (makeReadThisAndWriteThis == true && hasReadThis == false) {
    FnSymbol* fn = new FnSymbol("readThis");

    fn->addFlag(FLAG_COMPILER_GENERATED);
    fn->addFlag(FLAG_LAST_RESORT);
    if (ct->isClass() && ct != dtObject)
      fn->addFlag(FLAG_OVERRIDE);
    else
      fn->addFlag(FLAG_INLINE);

    fn->cname = astr("_auto_", ct->symbol->name, "_read");

    fn->_this = new ArgSymbol(INTENT_BLANK, "this", ct);
    fn->_this->addFlag(FLAG_ARG_THIS);

    ArgSymbol* fileArg = new ArgSymbol(INTENT_BLANK, "f", dtAny);

    fileArg->addFlag(FLAG_MARKED_GENERIC);

    fn->setMethod(true);

    fn->insertFormalAtTail(new ArgSymbol(INTENT_BLANK, "_mt", dtMethodToken));
    fn->insertFormalAtTail(fn->_this);
    fn->insertFormalAtTail(fileArg);

    fn->retType = dtVoid;

    if (hasReadWriteThis == true) {
      Expr* dotReadWriteThis = buildDotExpr(fn->_this, "readWriteThis");

      fn->insertAtTail(new CallExpr(dotReadWriteThis, fileArg));

    } else {
      fn->insertAtTail(new CallExpr("readThisDefaultImpl",
                                    fileArg,
                                    fn->_this));
    }

    DefExpr* def = new DefExpr(fn);

    ct->symbol->defPoint->insertBefore(def);

    fn->setMethod(true);
    fn->addFlag(FLAG_METHOD_PRIMARY);

    reset_ast_loc(def, ct->symbol);

    normalize(fn);

    ct->methods.add(fn);
  }
}


static void buildStringCastFunction(EnumType* et) {
  if (function_exists(astr_cast, dtString, et))
    return;

  FnSymbol* fn = new FnSymbol(astr_cast);
  fn->addFlag(FLAG_COMPILER_GENERATED);
  fn->addFlag(FLAG_LAST_RESORT);
  ArgSymbol* t = new ArgSymbol(INTENT_BLANK, "t", dtString);
  t->addFlag(FLAG_TYPE_VARIABLE);
  fn->insertFormalAtTail(t);
  ArgSymbol* arg = new ArgSymbol(INTENT_BLANK, "this", et);
  arg->addFlag(FLAG_ARG_THIS);
  fn->insertFormalAtTail(arg);

  for_enums(constant, et) {
    fn->insertAtTail(
      new CondStmt(
        new CallExpr("==", arg, constant->sym),
        new CallExpr(PRIM_RETURN, new_StringSymbol(constant->sym->name))));
  }
  fn->insertAtTail(new CallExpr(PRIM_RETURN, new_StringSymbol("")));

  DefExpr* def = new DefExpr(fn);
  //
  // these cast functions need to go in the base module because they
  // are automatically inserted to handle implicit coercions
  //
  baseModule->block->insertAtTail(def);
  reset_ast_loc(def, et->symbol);
  normalize(fn);
  fn->tagIfGeneric();
}


void buildDefaultDestructor(AggregateType* ct) {
  if (function_exists("deinit", dtMethodToken, ct) == NULL) {
    SET_LINENO(ct->symbol);

    FnSymbol* fn = new FnSymbol("deinit");

    fn->cname = astr("chpl__auto_destroy_", ct->symbol->name);

    fn->setMethod(true);
    fn->addFlag(FLAG_METHOD_PRIMARY);

    fn->addFlag(FLAG_COMPILER_GENERATED);
    fn->addFlag(FLAG_LAST_RESORT);
    fn->addFlag(FLAG_DESTRUCTOR);
    fn->addFlag(FLAG_INLINE);

    if (ct->symbol->hasFlag(FLAG_TUPLE) == true) {
      gGenericTupleDestroy = fn;
    }

    fn->insertFormalAtTail(new ArgSymbol(INTENT_BLANK, "_mt", dtMethodToken));

    fn->_this = new ArgSymbol(INTENT_BLANK, "this", ct);
    fn->_this->addFlag(FLAG_ARG_THIS);

    fn->insertFormalAtTail(fn->_this);

    fn->retType = dtVoid;

    fn->insertAtTail(new CallExpr(PRIM_RETURN, gVoid));

    ct->symbol->defPoint->insertBefore(new DefExpr(fn));


    ct->methods.add(fn);
  }
}
