text: /*
   * Copyright 2004-2018 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 "config.h"
  #include "driver.h"
  #include "expr.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 buildClassBorrowMethod(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_init_function(Type* type);
  static void build_extern_assignment_function(Type* type);
  
  static void build_record_assignment_function(AggregateType* ct);
  static void build_record_cast_function(AggregateType* ct);
  static void build_record_copy_function(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_cast_function(ct);
            build_record_copy_function(ct);
            build_record_hash_function(ct);
          }
  
          if (isUnion(ct)) {
            build_union_assignment_function(ct);
          }
  
          if (isClass(ct)) {
            buildClassBorrowMethod(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);
  
    ArgSymbol* _this = new ArgSymbol(INTENT_BLANK, "this", ct);
  
    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"))));
      }
    }
  
    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)) {
      fn->insertAtTail(new CallExpr(PRIM_RETURN,
                                    new CallExpr(PRIM_GET_MEMBER_VALUE,
                                                 new SymExpr(_this),
                                                 new SymExpr(new_CStringSymbol(field->name)))));
    } else {
      fn->insertAtTail(new CallExpr(PRIM_RETURN,
                                    new CallExpr(PRIM_GET_MEMBER,
                                                 new SymExpr(_this),
                                                 new SymExpr(new_CStringSymbol(field->name)))));
    }
  
    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.
    chpl_gen_main->insertAtTail(new CallExpr(mainModule->initFn));
  
    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, dtVoid));
    // If there are one or more enumerators for this type, return the first one
    // listed.  Otherwise return void.
  
    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_init_function(Type* type)
  {
    if (function_exists("_defaultOf", type))
      return;
  
    // In the world where initialization lived entirely within the compiler,
    // externs did not initialize themselves except in very rare cases (see
    // c_strings).  This is the same as using noinit with the type instance.
    // In the world of module default initializers, externs can be specified to
    // use noinit here, while specific externs that differ can define their own
    // default initialization in the modules.  If we had a way to determine that
    // a type was extern from just the provided type, this code could also move
    // to the modules.
    FnSymbol* fn = new FnSymbol("_defaultOf");
    fn->addFlag(FLAG_INLINE);
    fn->addFlag(FLAG_COMPILER_GENERATED);
    fn->addFlag(FLAG_LAST_RESORT);
    ArgSymbol* arg = new ArgSymbol(INTENT_BLANK, "t", type);
    arg->addFlag(FLAG_TYPE_VARIABLE);
    fn->insertFormalAtTail(arg);
    VarSymbol* ret = new VarSymbol("ret", type);
    fn->insertAtTail(new CallExpr(PRIM_MOVE, ret,
                                  new CallExpr(PRIM_NO_INIT, new SymExpr(type->symbol))));
    fn->insertAtTail(new CallExpr(PRIM_RETURN, ret));
    fn->insertAtHead(new DefExpr(ret));
    fn->retType = type;
    DefExpr* def = new DefExpr(fn);
    type->symbol->defPoint->insertBefore(def);
    reset_ast_loc(def, type->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);
  }
  
  
  // _cast is automatically called to perform coercions.
  // If the coercion is permissible, then the operand can be statically cast to
  // the target type.
  static void build_record_cast_function(AggregateType* ct) {
  
    // Don't do this for tuples.
    // Tuple cast function is generated during resolution.
    if (ct->symbol->hasFlag(FLAG_TUPLE))
      return;
  
    // Don't do this for Owned/etc.
    if (ct->symbol->hasFlag(FLAG_MANAGED_POINTER))
      return;
  
    FnSymbol* fn = new FnSymbol(astr_cast);
    fn->addFlag(FLAG_COMPILER_GENERATED);
    fn->addFlag(FLAG_LAST_RESORT);
    fn->addFlag(FLAG_INLINE);
    ArgSymbol* t = new ArgSymbol(INTENT_BLANK, "t", ct);
    t->addFlag(FLAG_TYPE_VARIABLE);
    ArgSymbol* arg = new ArgSymbol(INTENT_BLANK, "arg", dtAny);
    fn->insertFormalAtTail(t);
    fn->insertFormalAtTail(arg);
    VarSymbol* ret = newTemp();
    fn->insertAtTail(new DefExpr(ret));
    fn->insertAtTail(new CallExpr(PRIM_MOVE, ret, new CallExpr(PRIM_INIT, t)));
    fn->insertAtTail(new CallExpr("=", ret, arg));
    fn->insertAtTail(new CallExpr(PRIM_RETURN, ret));
    DefExpr* def = new DefExpr(fn);
    ct->symbol->defPoint->insertBefore(def);
    reset_ast_loc(def, ct->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 buildClassBorrowMethod(AggregateType* ct) {
    if (function_exists("borrow", dtMethodToken, ct))
      return;
  
    FnSymbol* fn = new FnSymbol("borrow");
    fn->addFlag(FLAG_COMPILER_GENERATED);
  
    if (ct->isClass() && ct != dtObject)
      fn->addFlag(FLAG_OVERRIDE);
    else
      fn->addFlag(FLAG_INLINE);
  
    fn->cname = astr("borrow");
    fn->_this = new ArgSymbol(INTENT_BLANK, "this", ct);
    fn->_this->addFlag(FLAG_ARG_THIS);
    fn->setMethod(true);
    fn->insertFormalAtTail(new ArgSymbol(INTENT_BLANK, "_mt", dtMethodToken));
    fn->insertFormalAtTail(fn->_this);
  
    fn->retType = ct;
  
    fn->insertAtTail(new CallExpr(PRIM_RETURN, fn->_this));
  
    DefExpr* def = new DefExpr(fn);
    ct->symbol->defPoint->insertBefore(def);
    reset_ast_loc(def, ct->symbol);
    normalize(fn);
  }
  
  /************************************* | **************************************
  *                                                                             *
  *                                                                             *
  *                                                                             *
  ************************************** | *************************************/
  
  static void build_record_copy_function(AggregateType* at) {
    if (function_exists("chpl__initCopy", at) == NULL) {
      if (isRecordWithInitializers(at) == true) {
  
        // 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);
        }
  
      } else {
        FnSymbol*  fn  = new FnSymbol("chpl__initCopy");
        DefExpr*   def = new DefExpr(fn);
        ArgSymbol* arg = new ArgSymbol(INTENT_CONST, "x", at);
  
        arg->addFlag(FLAG_MARKED_GENERIC);
  
        fn->addFlag(FLAG_INIT_COPY_FN);
        fn->addFlag(FLAG_COMPILER_GENERATED);
        fn->addFlag(FLAG_LAST_RESORT);
  
        fn->insertFormalAtTail(arg);
  
        if (at->symbol->hasFlag(FLAG_EXTERN) == true) {
          fn->insertAtTail(new CallExpr(PRIM_RETURN, new SymExpr(arg)));
  
        } else {
          CallExpr* call = new CallExpr(at->defaultInitializer);
  
          for_fields(tmp, at) {
            if (tmp->hasFlag(FLAG_IMPLICIT_ALIAS_FIELD) == false) {
              Symbol*   sym  = new_CStringSymbol(tmp->name);
              CallExpr* init = new CallExpr(".", arg, sym);
  
              call->insertAtTail(new NamedExpr(tmp->name, init));
            }
          }
  
          fn->insertAtTail(new CallExpr(PRIM_RETURN, call));
        }
  
        at->symbol->defPoint->insertBefore(def);
  
        reset_ast_loc(def, at->symbol);
  
        normalize(fn);
      }
    }
  }
  
  /************************************* | **************************************
  *                                                                             *
  *                                                                             *
  *                                                                             *
  ************************************** | *************************************/
  
  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 buildInitializerCall(AggregateType* ct,
                                   FnSymbol*      fn,
                                   ArgSymbol*     arg);
  
  static void buildRecordDefaultOf(AggregateType* ct,
                                   FnSymbol*      fn,
                                   ArgSymbol*     arg);
  
  static void buildRecordQuery(FnSymbol*      fn,
                               ArgSymbol*     arg,
                               CallExpr*      call,
                               Symbol*        formal,
                               Flag           flag,
                               PrimitiveTag   tag,
                               bool           named);
  
  static void buildRecordQueryVarField(FnSymbol*  fn,
                                       ArgSymbol* arg,
                                       CallExpr*  call,
                                       Symbol*    formal,
                                       bool       named);
  
  static void buildDefaultOfFunction(AggregateType* ct) {
    if        (isNonGenericClassWithInitializers(ct)  == true) {
  
  
    } else if (isNonGenericRecordWithInitializers(ct) == true) {
  
  
    } else if (function_exists("_defaultOf", ct)        == NULL  &&
               ct->symbol->hasFlag(FLAG_ITERATOR_CLASS) == false &&
               ct->defaultValue                         != gNil) {
  
      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 if (ct->hasUserDefinedInit == true ||
                 ct->wantsDefaultInitializer()) {
        buildInitializerCall(ct, fn, arg);
      } else {
        buildRecordDefaultOf(ct, fn, arg);
      }
  
      ct->symbol->defPoint->insertBefore(def);
  
      reset_ast_loc(def, ct->symbol);
  
      // Do not normalize until the definition has been inserted
      normalize(fn);
    }
  }
  
  static void buildInitializerCall(AggregateType* ct,
                                   FnSymbol*      fn,
                                   ArgSymbol*     arg) {
    VarSymbol* _this = newTemp("_this", ct);
    CallExpr*  call  = new CallExpr("init");
  
    _this->addFlag(FLAG_DELAY_GENERIC_EXPANSION);
  
    fn->insertAtHead(new DefExpr(_this));
  
    call->insertAtTail(new SymExpr(gMethodToken));
    call->insertAtTail(new NamedExpr("this", new SymExpr(_this)));
  
    bool named = ct->wantsDefaultInitializer();
  
    for_fields(field, ct) {
      bool isGenericField = false;
      if (field->type->symbol->hasFlag(FLAG_GENERIC)) {
        isGenericField = true;
      } else if (AggregateType* at = toAggregateType(field->type)) {
        isGenericField = at->isGeneric();
      }
  
      if (field->isParameter() == true) {
        Flag         flag = FLAG_PARAM;
        PrimitiveTag tag  = PRIM_QUERY_PARAM_FIELD;
  
        buildRecordQuery(fn, arg, call, field, flag, tag, named);
  
      } else if (field->hasFlag(FLAG_TYPE_VARIABLE) == true) {
        Flag         flag = FLAG_TYPE_VARIABLE;
        PrimitiveTag tag  = PRIM_QUERY_TYPE_FIELD;
  
        buildRecordQuery(fn, arg, call, field, flag, tag, named);
  
      } else if ((field->defPoint->exprType == NULL &&
                 field->defPoint->init     == NULL) ||
                 isGenericField) {
  
        // Can only use a NamedExpr if using a default initializer. Otherwise the
        // user can use an arbitrary identifier.
        //
        // BHARSH INIT TODO: Try to write a test that fails because we cannot
        // correctly generate a _defaultOf for something with an explicit init.
        buildRecordQueryVarField(fn, arg, call, field, named);
  
      }
    }
  
    fn->insertAtTail(call);
  
    fn->insertAtTail(new CallExpr(PRIM_RETURN, new SymExpr(_this)));
  }
  
  static void buildRecordDefaultOf(AggregateType* ct,
                                   FnSymbol*      fn,
                                   ArgSymbol*     arg) {
    CallExpr* call = new CallExpr(ct->defaultInitializer->name);
  
    // Insert all required arguments into this call
    for_formals(formal, ct->defaultInitializer) {
      if (formal->type == dtMethodToken) {
        call->insertAtTail(gMethodToken);
  
      } else if (formal->isParameter() == true) {
        Flag         flag = FLAG_PARAM;
        PrimitiveTag tag  = PRIM_QUERY_PARAM_FIELD;
  
        buildRecordQuery(fn, arg, call, formal, flag, tag, true);
  
      } else if (formal->hasFlag(FLAG_TYPE_VARIABLE) == true) {
        Flag         flag = FLAG_TYPE_VARIABLE;
        PrimitiveTag tag  = PRIM_QUERY_TYPE_FIELD;
  
        buildRecordQuery(fn, arg, call, formal, flag, tag, true);
  
      } else if (formal->defaultExpr == NULL) {
        buildRecordQueryVarField(fn, arg, call, formal, true);
      }
    }
  
    fn->insertAtTail(new CallExpr(PRIM_RETURN, call));
  }
  
  static void buildRecordQuery(FnSymbol*      fn,
                               ArgSymbol*     arg,
                               CallExpr*      call,
                               Symbol*        formal,
                               Flag           flag,
                               PrimitiveTag   tag,
                               bool           named) {
    VarSymbol* tmp   = newTemp(formal->name);
    VarSymbol* name  = new_CStringSymbol(formal->name);
    CallExpr*  query = new CallExpr(tag, arg, name);
  
    tmp->addFlag(flag);
  
    fn->insertAtTail(new CallExpr(PRIM_MOVE, tmp, query));
    fn->insertAtHead(new DefExpr(tmp));
  
    if (named) {
      call->insertAtTail(new NamedExpr(formal->name, new SymExpr(tmp)));
    } else {
      call->insertAtTail(new SymExpr(tmp));
    }
  }
  
  static void buildRecordQueryVarField(FnSymbol*  fn,
                                       ArgSymbol* arg,
                                       CallExpr*  call,
                                       Symbol*    formal,
                                       bool       named) {
    VarSymbol* tmp  = newTemp(formal->name);
    CallExpr*  init = NULL;
  
    fn->insertAtHead(new DefExpr(tmp));
  
    VarSymbol* typeTmp   = newTemp("type_tmp");
    VarSymbol* callTmp   = newTemp("call_tmp");
    VarSymbol* name      = new_CStringSymbol(formal->name);
  
    CallExpr*  getMember = new CallExpr(PRIM_GET_MEMBER_VALUE, arg, name);
    CallExpr*  typeOf    = new CallExpr(PRIM_TYPEOF, callTmp);
  
    typeTmp->addFlag(FLAG_TYPE_VARIABLE);
  
    fn->insertAtHead(new DefExpr(callTmp));
    fn->insertAtHead(new DefExpr(typeTmp));
  
    fn->insertAtTail(new CallExpr(PRIM_MOVE, callTmp, getMember));
    fn->insertAtTail(new CallExpr(PRIM_MOVE, typeTmp, typeOf));
  
    init = new CallExpr(PRIM_INIT, typeTmp);
  
    fn->insertAtTail(new CallExpr(PRIM_MOVE, tmp, init));
  
    if (named) {
      call->insertAtTail(new NamedExpr(formal->name, new SymExpr(tmp)));
    } else {
      call->insertAtTail(new SymExpr(tmp));
    }
  }
  
  /************************************* | **************************************
  *                                                                             *
  *                                                                             *
  *                                                                             *
  ************************************** | *************************************/
  
  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;
  }
  
  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) {
      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->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("writeThisDefaultImpl",
                                      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);
    }
  
    // 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);
    }
  }
  