//-----------------------------------------------------------------------------
//
// Copyright (C) Microsoft Corporation.  All Rights Reserved.
//
//-----------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.IO;
using System.Diagnostics.Contracts;
using Bpl = Microsoft.Boogie;
using System.Collections.ObjectModel;
using System.Text;


namespace Microsoft.Dafny {
  public abstract class Compiler {
    public Compiler(ErrorReporter reporter) {
      Reporter = reporter;
    }

    public abstract string TargetLanguage { get; }

    Stack<TargetWriter> copyInstrWriters = new Stack<TargetWriter>(); // a buffer that stores copy instructions generated by letExpr that uses out param.
    protected Method enclosingMethod;  // non-null when a method body is being translated

    FreshIdGenerator idGenerator = new FreshIdGenerator();

    static FreshIdGenerator compileNameIdGenerator = new FreshIdGenerator();
    public static string FreshId() {
      return compileNameIdGenerator.FreshNumericId();
    }
    public static string FreshId(string prefix) {
      return compileNameIdGenerator.FreshId(prefix);
    }

    Dictionary<Expression, int> uniqueAstNumbers = new Dictionary<Expression, int>();
    int GetUniqueAstNumber(Expression expr) {
      Contract.Requires(expr != null);
      int n;
      if (!uniqueAstNumbers.TryGetValue(expr, out n)) {
        n = uniqueAstNumbers.Count;
        uniqueAstNumbers.Add(expr, n);
      }
      return n;
    }

    public ErrorReporter Reporter;

    protected void Error(Bpl.IToken tok, string msg, TextWriter/*?*/ wr, params object[] args) {
      Contract.Requires(msg != null);
      Contract.Requires(args != null);

      Reporter.Error(MessageSource.Compiler, tok, msg, args);
      if (wr != null) {
        wr.WriteLine("/* {0} */", string.Format("Compilation error: " + msg, args));
      }
    }

    protected virtual void EmitHeader(Program program, TargetWriter wr) { }
    protected virtual void EmitBuiltInDecls(BuiltIns builtIns, TargetWriter wr) { }
    /// <summary>
    /// Emits a call to "mainMethod" as the program's entry point, if such an explicit call is
    /// required in the target language.
    /// </summary>
    public virtual void EmitCallToMain(Method mainMethod, TextWriter wr) { }
    /// <summary>
    /// Creates a static Main method. The caller will fill the body of this static Main with a
    /// call to the instance Main method in the enclosing class.
    /// </summary>
    protected abstract BlockTargetWriter CreateStaticMain(IClassWriter wr);
    protected abstract TargetWriter CreateModule(string moduleName, bool isDefault, bool isExtern, string/*?*/ libraryName, TargetWriter wr);
    protected abstract string GetHelperModuleName();
    protected interface IClassWriter {
      BlockTargetWriter/*?*/ CreateMethod(Method m, bool createBody);
      BlockTargetWriter/*?*/ CreateFunction(string name, List<TypeParameter>/*?*/ typeArgs, List<Formal> formals, Type resultType, Bpl.IToken tok, bool isStatic, bool createBody, MemberDecl member);
      BlockTargetWriter/*?*/ CreateGetter(string name, Type resultType, Bpl.IToken tok, bool isStatic, bool createBody, MemberDecl/*?*/ member);  // returns null iff !createBody
      BlockTargetWriter/*?*/ CreateGetterSetter(string name, Type resultType, Bpl.IToken tok, bool isStatic, bool createBody, MemberDecl/*?*/ member, out TargetWriter setterWriter);  // if createBody, then result and setterWriter are non-null, else both are null
      void DeclareField(string name, bool isStatic, bool isConst, Type type, Bpl.IToken tok, string rhs);
      TextWriter/*?*/ ErrorWriter();
      void Finish();
    }
    protected IClassWriter CreateClass(string name, List<TypeParameter>/*?*/ typeParameters, TargetWriter wr) {
      return CreateClass(name, false, null, typeParameters, null, null, wr);
    }
    /// <summary>
    /// "tok" can be "null" if "superClasses" is.
    /// </summary>
    protected abstract IClassWriter CreateClass(string name, bool isExtern, string/*?*/ fullPrintName, List<TypeParameter>/*?*/ typeParameters, List<Type>/*?*/ superClasses, Bpl.IToken tok, TargetWriter wr);
    /// <summary>
    /// "tok" can be "null" if "superClasses" is.
    /// </summary>
    protected abstract IClassWriter CreateTrait(string name, bool isExtern, List<Type>/*?*/ superClasses, Bpl.IToken tok, TargetWriter wr);
    /// If this returns false, it is assumed that the implementation handles inherited fields on its own.
    protected virtual bool NeedsWrappersForInheritedFields { get => true; }
    protected virtual bool SupportsProperties { get => true; }
    protected abstract BlockTargetWriter CreateIterator(IteratorDecl iter, TargetWriter wr);
    protected abstract void DeclareDatatype(DatatypeDecl dt, TargetWriter wr);
    protected abstract void DeclareNewtype(NewtypeDecl nt, TargetWriter wr);
    protected abstract void DeclareSubsetType(SubsetTypeDecl sst, TargetWriter wr);
    protected string GetNativeTypeName(NativeType nt) {
      Contract.Requires(nt != null);
      string nativeName = null, literalSuffix = null;
      bool needsCastAfterArithmetic = false;
      GetNativeInfo(nt.Sel, out nativeName, out literalSuffix, out needsCastAfterArithmetic);
      return nativeName;
    }
    protected virtual void GetNativeInfo(NativeType.Selection sel, out string name, out string literalSuffix, out bool needsCastAfterArithmetic) {
      switch (sel) {
        case NativeType.Selection.Byte:
          name = "byte"; literalSuffix = ""; needsCastAfterArithmetic = true;
          break;
        case NativeType.Selection.SByte:
          name = "sbyte"; literalSuffix = ""; needsCastAfterArithmetic = true;
          break;
        case NativeType.Selection.UShort:
          name = "ushort"; literalSuffix = ""; needsCastAfterArithmetic = true;
          break;
        case NativeType.Selection.Short:
          name = "short"; literalSuffix = ""; needsCastAfterArithmetic = true;
          break;
        case NativeType.Selection.UInt:
          name = "uint"; literalSuffix = "U"; needsCastAfterArithmetic = false;
          break;
        case NativeType.Selection.Int:
          name = "int"; literalSuffix = ""; needsCastAfterArithmetic = false;
          break;
        case NativeType.Selection.Number:
          name = "number"; literalSuffix = ""; needsCastAfterArithmetic = false;
          break;
        case NativeType.Selection.ULong:
          name = "ulong"; literalSuffix = "UL"; needsCastAfterArithmetic = false;
          break;
        case NativeType.Selection.Long:
          name = "long"; literalSuffix = "L"; needsCastAfterArithmetic = false;
          break;
        default:
          Contract.Assert(false);  // unexpected native type
          throw new cce.UnreachableException();  // to please the compiler
      }
    }

    protected virtual int EmitRuntimeTypeDescriptorsActuals(List<Type> typeArgs, List<TypeParameter> formals, Bpl.IToken tok, bool useAllTypeArgs, TargetWriter wr) {
      Contract.Requires(typeArgs != null);
      Contract.Requires(formals != null);
      Contract.Requires(typeArgs.Count == formals.Count);
      Contract.Requires(tok != null);
      Contract.Requires(wr != null);
      return 0;
    }
    protected abstract void EmitJumpToTailCallStart(TargetWriter wr);
    protected abstract string TypeName(Type type, TextWriter wr, Bpl.IToken tok, MemberDecl/*?*/ member = null);
    public abstract string TypeInitializationValue(Type type, TextWriter/*?*/ wr, Bpl.IToken/*?*/ tok, bool inAutoInitContext);
    protected abstract string TypeName_UDT(string fullCompileName, List<Type> typeArgs, TextWriter wr, Bpl.IToken tok);
    protected abstract string/*?*/ TypeName_Companion(Type type, TextWriter wr, Bpl.IToken tok, MemberDecl/*?*/ member);
    protected string TypeName_Companion(TopLevelDecl cls, TextWriter wr, Bpl.IToken tok) {
      return TypeName_Companion(UserDefinedType.FromTopLevelDecl(tok, cls), wr, tok, null);
    }
    /// Return the "native form" of a type, to which EmitCoercionToNativeForm coerces it.
    protected virtual Type NativeForm(Type type) {
      return type;
    }

    protected abstract bool DeclareFormal(string prefix, string name, Type type, Bpl.IToken tok, bool isInParam, TextWriter wr);
    /// <summary>
    /// If "leaveRoomForRhs" is false and "rhs" is null, then generates:
    ///     type name;
    /// If "leaveRoomForRhs" is false and "rhs" is non-null, then generates:
    ///     type name = rhs;
    /// If "leaveRoomForRhs" is true, in which case "rhs" must be null, then generates:
    ///     type name
    /// which is intended to be followed up by a call to EmitAssignmentRhs.
    /// In the above, if "type" is null, then it is replaced by "var" or "let".
    /// "tok" is allowed to be null if "type" is.
    /// </summary>
    protected abstract void DeclareLocalVar(string name, Type/*?*/ type, Bpl.IToken/*?*/ tok, bool leaveRoomForRhs, string/*?*/ rhs, TargetWriter wr);
    /// <summary>
    /// Generates:
    ///     type name = rhs;
    /// In the above, if "type" is null, then it is replaced by "var" or "let".
    /// "tok" is allowed to be null if "type" is.
    /// </summary>
    protected void DeclareLocalVar(string name, Type/*?*/ type, Bpl.IToken/*?*/ tok, Expression rhs, bool inLetExprBody, TargetWriter wr) {
      var w = DeclareLocalVar(name, type, tok, wr);
      TrExpr(rhs, w, inLetExprBody);
    }
    /// <summary>
    /// Generates
    ///     type name = <<writer returned>>;
    /// In the above, if "type" is null, then it is replaced by "var" or "let".
    /// "tok" is allowed to be null if "type" is.
    /// </summary>
    protected abstract TargetWriter DeclareLocalVar(string name, Type/*?*/ type, Bpl.IToken/*?*/ tok, TargetWriter wr);
    protected virtual void DeclareOutCollector(string collectorVarName, TargetWriter wr) { }  // called only for return-style calls
    protected virtual bool UseReturnStyleOuts(Method m, int nonGhostOutCount) => false;
    protected virtual bool SupportsMultipleReturns { get => false; }
    protected virtual bool NeedsCastFromTypeParameter { get => false; }
    /// The punctuation that comes at the end of a statement.  Note that
    /// statements are followed by newlines regardless.
    protected virtual string StmtTerminator { get => ";"; }
    protected void EndStmt(TargetWriter wr) { wr.WriteLine(StmtTerminator); }
    protected abstract void DeclareLocalOutVar(string name, Type type, Bpl.IToken tok, string rhs, TargetWriter wr);
    protected virtual void EmitActualOutArg(string actualOutParamName, TextWriter wr) { }  // actualOutParamName is always the name of a local variable; called only for non-return-style outs
    protected virtual void EmitOutParameterSplits(string outCollector, List<string> actualOutParamNames, TargetWriter wr) { }  // called only for return-style calls

    protected abstract void EmitActualTypeArgs(List<Type> typeArgs, Bpl.IToken tok, TextWriter wr);
    protected abstract string GenerateLhsDecl(string target, Type/*?*/ type, TextWriter wr, Bpl.IToken tok);
    protected virtual void EmitAssignment(out TargetWriter wLhs, Type/*?*/ lhsType, out TargetWriter wRhs, Type/*?*/ rhsType, TargetWriter wr) {
      wr.Indent();
      wLhs = wr.Fork();
      wr.Write(" = ");
      TargetWriter w;
      if (rhsType != null) {
        w = EmitCoercionIfNecessary(from:rhsType, to:lhsType, tok:Bpl.Token.NoToken, wr:wr);
      } else {
        w = wr;
      }
      wRhs = w.Fork();
      EndStmt(wr);
    }
    protected void EmitAssignment(string lhs, Type/*?*/ lhsType, string rhs, Type/*?*/ rhsType, TargetWriter wr) {
      EmitAssignment(out var wLhs, lhsType, out var wRhs, rhsType, wr);
      wLhs.Write(lhs);
      wRhs.Write(rhs);
    }
    protected void EmitAssignmentRhs(string rhs, TargetWriter wr) {
      var w = EmitAssignmentRhs(wr);
      w.Write(rhs);
    }
    protected void EmitAssignmentRhs(Expression rhs, bool inLetExprBody, TargetWriter wr) {
      var w = EmitAssignmentRhs(wr);
      TrExpr(rhs, w, inLetExprBody);
    }
    protected virtual TargetWriter EmitAssignmentRhs(TargetWriter wr) {
      var w = new TargetWriter(wr.IndentLevel);

      wr.Write(" = ");
      wr.Append(w);
      EndStmt(wr);

      return w;
    }

    protected virtual void EmitMultiAssignment(out List<TargetWriter> wLhss, List<Type> lhsTypes, out List<TargetWriter> wRhss, List<Type> rhsTypes, TargetWriter wr) {
      Contract.Assert(lhsTypes.Count == rhsTypes.Count);
      wLhss = new List<TargetWriter>();
      wRhss = new List<TargetWriter>();
      var rhsVars = new List<string>();
      foreach (var lhsType in lhsTypes) {
        string target = idGenerator.FreshId("_rhs");
        rhsVars.Add(target);
        wr.Indent();
        wr.Write(GenerateLhsDecl(target, lhsType, wr, null));
        wr.Write(" = ");
        wRhss.Add(wr.Fork());
        EndStmt(wr);
      }

      Contract.Assert(rhsVars.Count == lhsTypes.Count);
      for (int i = 0; i < rhsVars.Count; i++) {
        TargetWriter wLhs, wRhsVar;
        EmitAssignment(out wLhs, lhsTypes[i], out wRhsVar, rhsTypes[i], wr);
        wLhss.Add(wLhs);
        wRhsVar.Write(rhsVars[i]);
      }
    }
    
    protected virtual void EmitSetterParameter(TargetWriter wr) {
      wr.Write("value");
    }
    protected abstract void EmitPrintStmt(TargetWriter wr, Expression arg);
    protected abstract void EmitReturn(List<Formal> outParams, TargetWriter wr);
    protected virtual void EmitReturnExpr(Expression expr, bool inLetExprBody, TargetWriter wr) {  // emits "return <expr>;" for function bodies
      var w = EmitReturnExpr(wr);
      TrExpr(expr, w, inLetExprBody);
    }
    protected virtual void EmitReturnExpr(string returnExpr, TargetWriter wr) {  // emits "return <returnExpr>;" for function bodies
      var w = EmitReturnExpr(wr);
      w.Write(returnExpr);
    }
    protected virtual TargetWriter EmitReturnExpr(TargetWriter wr) {
      // emits "return <returnExpr>;" for function bodies
      var w = new TargetWriter(wr.IndentLevel);
      wr.Indent();
      wr.Write("return ");
      wr.Append(w);
      EndStmt(wr);
      return w;
    }
    /// <summary>
    /// Labels the code written to the TargetWriter returned, in such that way that any
    /// emitted break to the label inside that code will abruptly end the execution of the code.
    /// </summary>
    protected abstract TargetWriter CreateLabeledCode(string label, TargetWriter wr);
    protected abstract void EmitBreak(string/*?*/ label, TargetWriter wr);
    protected abstract void EmitYield(TargetWriter wr);
    protected abstract void EmitAbsurd(string/*?*/ message, TargetWriter wr);
    protected TargetWriter EmitIf(string guard, bool hasElse, TargetWriter wr) {
      TargetWriter guardWriter;
      var thn = EmitIf(out guardWriter, hasElse, wr);
      guardWriter.Write(guard);
      return thn;
    }
    protected virtual TargetWriter EmitIf(out TargetWriter guardWriter, bool hasElse, TargetWriter wr) {
      wr.Indent();
      wr.Write("if (");
      guardWriter = new TargetWriter(wr.IndentLevel);
      wr.Append(guardWriter);
      var thn = wr.NewBlock(")");
      if (hasElse) {
        thn.Footer = " else";
        thn.SetBraceStyle(BlockTargetWriter.BraceStyle.Space, BlockTargetWriter.BraceStyle.Space);
        wr.SuppressIndent();
      }
      return thn;
    }
    protected virtual TargetWriter EmitWhile(List<Statement> body, TargetWriter wr) {  // returns the guard writer
      TargetWriter guardWriter;
      var wBody = CreateWhileLoop(out guardWriter, wr);
      TrStmtList(body, wBody);
      return guardWriter;
    }

    protected virtual BlockTargetWriter CreateWhileLoop(out TargetWriter guardWriter, TargetWriter wr) {
      wr.Indent();
      wr.Write("while (");
      guardWriter = new TargetWriter(wr.IndentLevel);
      wr.Append(guardWriter);
      var wBody = wr.NewBlock(")");
      return wBody;
    }
    protected abstract BlockTargetWriter CreateForLoop(string indexVar, string bound, TargetWriter wr);
    protected abstract BlockTargetWriter CreateDoublingForLoop(string indexVar, int start, TargetWriter wr);
    protected abstract void EmitIncrementVar(string varName, TargetWriter wr);  // increments a BigInteger by 1
    protected abstract void EmitDecrementVar(string varName, TargetWriter wr);  // decrements a BigInteger by 1

    protected abstract string GetQuantifierName(string bvType);

    /// <summary>
    /// "tok" can be null if "altVarType" is null, which in turn is allowed if "altBoundVarName" is null
    /// </summary>
    protected abstract BlockTargetWriter CreateForeachLoop(string boundVar, Type/*?*/ boundVarType, out TargetWriter collectionWriter, TargetWriter wr, string/*?*/ altBoundVarName = null, Type/*?*/ altVarType = null, Bpl.IToken/*?*/ tok = null);
    /// <summary>
    /// If "initCall" is non-null, then "initCall.Method is Constructor".
    /// </summary>
    protected abstract void EmitNew(Type type, Bpl.IToken tok, CallStmt/*?*/ initCall, TargetWriter wr);
    protected abstract void EmitNewArray(Type elmtType, Bpl.IToken tok, List<Expression> dimensions, bool mustInitialize, TargetWriter wr);

    protected abstract void EmitLiteralExpr(TextWriter wr, LiteralExpr e);
    protected abstract void EmitStringLiteral(string str, bool isVerbatim, TextWriter wr);
    protected abstract TargetWriter EmitBitvectorTruncation(BitvectorType bvType, bool surroundByUnchecked, TargetWriter wr);
    protected delegate void FCE_Arg_Translator(Expression e, TargetWriter wr, bool inLetExpr=false);

    protected abstract void EmitRotate(Expression e0, Expression e1, bool isRotateLeft, TargetWriter wr, bool inLetExprBody, FCE_Arg_Translator tr);
    protected abstract void EmitEmptyTupleList(string tupleTypeArgs, TargetWriter wr);
    protected abstract TargetWriter EmitAddTupleToList(string ingredients, string tupleTypeArgs, TargetWriter wr);
    protected abstract void EmitTupleSelect(string prefix, int i, TargetWriter wr);
    /// <summary>
    /// If "from" and "to" are both given, and if a "from" needs an explicit coercion in order to become a "to", emit that coercion.  Needed in languages where either (a) we need to represent upcasts as explicit operations (like Go) or (b) there's static typing but no parametric polymorphism (like Go) so that lots of things need to be boxed and unboxed.
    /// </summary>
    protected virtual TargetWriter EmitCoercionIfNecessary(Type/*?*/ from, Type/*?*/ to, Bpl.IToken tok, TargetWriter wr) {
      return wr;
    }
    protected virtual TargetWriter EmitCoercionToNativeForm(Type/*?*/ from, Bpl.IToken tok, TargetWriter wr) {
      return wr;
    }
    protected virtual TargetWriter EmitCoercionFromNativeForm(Type/*?*/ to, Bpl.IToken tok, TargetWriter wr) {
      return wr;
    }
    protected virtual TargetWriter EmitCoercionToNativeInt(TargetWriter wr) {
      return wr;
    }
    /// <summary>
    /// Emit a coercion of a value to any tuple, returning the writer for the value to coerce.  Needed in translating ForallStmt because some of the tuple components are native ints for which we have no Type object, but Go needs to coerce the value that comes out of the iterator.  Safe to leave this alone in subclasses that don't have the same problem.
    /// </summary>
    protected virtual TargetWriter EmitCoercionToArbitraryTuple(TargetWriter wr) {
      return wr;
    }
    protected virtual string IdName(TopLevelDecl d) {
      Contract.Requires(d != null);
      return IdProtect(d.CompileName);
    }
    protected virtual string IdName(MemberDecl member) {
      Contract.Requires(member != null);
      return IdProtect(member.CompileName);
    }
    protected virtual string IdName(TypeParameter tp) {
      Contract.Requires(tp != null);
      return IdProtect(tp.CompileName);
    }
    protected virtual string IdName(IVariable v) {
      Contract.Requires(v != null);
      return IdProtect(v.CompileName);
    }
    protected virtual string IdMemberName(MemberSelectExpr mse) {
      Contract.Requires(mse != null);
      return IdProtect(mse.MemberName);
    }
    protected virtual string IdProtect(string name) {
      Contract.Requires(name != null);
      return name;
    }
    protected abstract string FullTypeName(UserDefinedType udt, MemberDecl/*?*/ member = null);
    protected abstract void EmitThis(TargetWriter wr);
    protected virtual void EmitNull(Type type, TargetWriter wr) {
      wr.Write("null");
    }
    protected virtual void EmitITE(Expression guard, Expression thn, Expression els, bool inLetExprBody, TargetWriter wr) {
      Contract.Requires(guard != null);
      Contract.Requires(thn != null);
      Contract.Requires(thn.Type != null);
      Contract.Requires(els != null);
      Contract.Requires(wr != null);

      wr.Write("(");
      TrExpr(guard, wr, inLetExprBody);
      wr.Write(") ? (");
      TrExpr(thn, wr, inLetExprBody);
      wr.Write(") : (");
      TrExpr(els, wr, inLetExprBody);
      wr.Write(")");
    }
    protected abstract void EmitDatatypeValue(DatatypeValue dtv, string arguments, TargetWriter wr);
    protected abstract void GetSpecialFieldInfo(SpecialField.ID id, object idParam, out string compiledName, out string preString, out string postString);
    protected abstract TargetWriter EmitMemberSelect(MemberDecl member, bool isLValue, Type expectedType, TargetWriter wr);
    protected void EmitArraySelect(string index, Type elmtType, TargetWriter wr) {
      EmitArraySelect(new List<string>() { index }, elmtType, wr);
    }
    protected abstract TargetWriter EmitArraySelect(List<string> indices, Type elmtType, TargetWriter wr);
    protected abstract TargetWriter EmitArraySelect(List<Expression> indices, Type elmtType, bool inLetExprBody, TargetWriter wr);
    protected virtual void EmitArraySelectAsLvalue(string array, List<string> indices, Type elmtType, TargetWriter wr) {
      wr.Write(array);
      EmitArraySelect(indices, elmtType, wr);
    }
    protected virtual TargetWriter EmitArrayUpdate(List<string> indices, string rhs, Type elmtType, TargetWriter wr) {
      var w = EmitArraySelect(indices, elmtType, wr);
      wr.Write(" = {0}", rhs);
      return w;
    }
    protected TargetWriter EmitArrayUpdate(List<string> indices, Expression rhs, TargetWriter wr) {
      var w = new TargetWriter();
      TrExpr(rhs, w, false);
      return EmitArrayUpdate(indices, w.ToString(), rhs.Type, wr);
    }
    protected virtual string ArrayIndexToInt(string arrayIndex) {
      return arrayIndex;
    }
    protected abstract void EmitExprAsInt(Expression expr, bool inLetExprBody, TargetWriter wr);
    protected abstract void EmitIndexCollectionSelect(Expression source, Expression index, bool inLetExprBody, TargetWriter wr);
    protected abstract void EmitIndexCollectionUpdate(Expression source, Expression index, Expression value, bool inLetExprBody, TargetWriter wr, bool nativeIndex = false);
    protected virtual void EmitIndexCollectionUpdate(out TargetWriter wSource, out TargetWriter wIndex, out TargetWriter wValue, TargetWriter wr, bool nativeIndex = false) {
      wSource = wr.Fork();
      wr.Write('[');
      wIndex = wr.Fork();
      wr.Write("] = ");
      wValue = wr.Fork();
    }
    /// <summary>
    /// If "fromArray" is false, then "source" is a sequence.
    /// If "fromArray" is true, then "source" is an array.
    /// </summary>
    protected abstract void EmitSeqSelectRange(Expression source, Expression/*?*/ lo, Expression/*?*/ hi, bool fromArray, bool inLetExprBody, TargetWriter wr);
    protected abstract void EmitMultiSetFormingExpr(MultiSetFormingExpr expr, bool inLetExprBody, TargetWriter wr);
    protected abstract void EmitApplyExpr(Type functionType, Bpl.IToken tok, Expression function, List<Expression> arguments, bool inLetExprBody, TargetWriter wr);
    protected abstract TargetWriter EmitBetaRedex(List<string> boundVars, List<Expression> arguments, string typeArgs, List<Type> boundTypes, Type resultType, Bpl.IToken resultTok, bool inLetExprBody, TargetWriter wr);
    protected virtual void EmitConstructorCheck(string source, DatatypeCtor ctor, TargetWriter wr) {
      wr.Write("{0}.is_{1}", source, ctor.CompileName);
    }
    /// <summary>
    /// EmitDestructor is somewhat similar to following "source" with a call to EmitMemberSelect.
    /// However, EmitDestructor may also need to perform a cast on "source".
    /// Furthermore, EmitDestructor also needs to work for anonymous destructors.
    /// </summary>
    protected abstract void EmitDestructor(string source, Formal dtor, int formalNonGhostIndex, DatatypeCtor ctor, List<Type> typeArgs, Type bvType, TargetWriter wr);
    protected abstract BlockTargetWriter CreateLambda(List<Type> inTypes, Bpl.IToken tok, List<string> inNames, Type resultType, TargetWriter wr, bool untyped = false);
    protected abstract TargetWriter CreateIIFE_ExprBody(Expression source, bool inLetExprBody, Type sourceType, Bpl.IToken sourceTok, Type resultType, Bpl.IToken resultTok, string bvName, TargetWriter wr);  // Immediately Invoked Function Expression
    protected abstract TargetWriter CreateIIFE_ExprBody(string source, Type sourceType, Bpl.IToken sourceTok, Type resultType, Bpl.IToken resultTok, string bvName, TargetWriter wr);  // Immediately Invoked Function Expression
    protected abstract BlockTargetWriter CreateIIFE0(Type resultType, Bpl.IToken resultTok, TargetWriter wr);  // Immediately Invoked Function Expression
    protected abstract BlockTargetWriter CreateIIFE1(int source, Type resultType, Bpl.IToken resultTok, string bvName, TargetWriter wr);  // Immediately Invoked Function Expression
    public enum ResolvedUnaryOp { BoolNot, BitwiseNot, Cardinality }
    protected abstract void EmitUnaryExpr(ResolvedUnaryOp op, Expression expr, bool inLetExprBody, TargetWriter wr);
    protected abstract void CompileBinOp(BinaryExpr.ResolvedOpcode op,
      Expression e0, Expression e1, Bpl.IToken tok, Type resultType,
      out string opString,
      out string preOpString,
      out string postOpString,
      out string callString,
      out string staticCallString,
      out bool reverseArguments,
      out bool truncateResult,
      out bool convertE1_to_int,
      TextWriter errorWr);
    protected abstract void EmitIsZero(string varName, TargetWriter wr);
    protected abstract void EmitConversionExpr(ConversionExpr e, bool inLetExprBody, TargetWriter wr);
    protected abstract void EmitCollectionDisplay(CollectionType ct, Bpl.IToken tok, List<Expression> elements, bool inLetExprBody, TargetWriter wr);  // used for sets, multisets, and sequences
    protected abstract void EmitMapDisplay(MapType mt, Bpl.IToken tok, List<ExpressionPair> elements, bool inLetExprBody, TargetWriter wr);
    protected abstract void EmitCollectionBuilder_New(CollectionType ct, Bpl.IToken tok, TargetWriter wr);
    protected abstract void EmitCollectionBuilder_Add(CollectionType ct, string collName, Expression elmt, bool inLetExprBody, TargetWriter wr);
    protected abstract TargetWriter EmitMapBuilder_Add(MapType mt, Bpl.IToken tok, string collName, Expression term, bool inLetExprBody, TargetWriter wr);
    protected abstract string GetCollectionBuilder_Build(CollectionType ct, Bpl.IToken tok, string collName, TargetWriter wr);
    protected virtual void EmitIntegerRange(Type type, out TargetWriter wLo, out TargetWriter wHi, TargetWriter wr) {
      if (AsNativeType(type) != null) {
        wr.Write("{0}.IntegerRange(", IdProtect(type.AsNewtype.FullCompileName));
      } else {
        wr.Write("{0}.IntegerRange(", GetHelperModuleName());
      }
      wLo = wr.Fork();
      wr.Write(", ");
      wHi = wr.Fork();
      wr.Write(')');
    }
    protected abstract void EmitSingleValueGenerator(Expression e, bool inLetExprBody, string type, TargetWriter wr);
    protected virtual void FinishModule() { }

    public void Compile(Program program, TargetWriter wrx) {
      Contract.Requires(program != null);

      EmitHeader(program, wrx);
      EmitBuiltInDecls(program.BuiltIns, wrx);

      foreach (ModuleDefinition m in program.CompileModules) {
        if (m.IsAbstract) {
          // the purpose of an abstract module is to skip compilation
          continue;
        }
        var moduleIsExtern = false;
        string libraryName = null;
        if (!DafnyOptions.O.DisallowExterns) {
          var args = Attributes.FindExpressions(m.Attributes, "extern");
          if (args != null) {
            if (args.Count == 2) {
              libraryName = (string)(args[1] as StringLiteralExpr)?.Value;
            }
            moduleIsExtern = true;
          }
        }
        var wr = CreateModule(m.CompileName, m.IsDefaultModule, moduleIsExtern, libraryName, wrx);
        foreach (TopLevelDecl d in m.TopLevelDecls) {
          bool compileIt = true;
          if (Attributes.ContainsBool(d.Attributes, "compile", ref compileIt) && !compileIt) {
            continue;
          }
          wr.WriteLine();
          if (d is OpaqueTypeDecl) {
            var at = (OpaqueTypeDecl)d;
            Error(d.tok, "Opaque type ('{0}') cannot be compiled", wr, at.FullName);
          } else if (d is TypeSynonymDecl) {
            var sst = d as SubsetTypeDecl;
            if (sst != null) {
              DeclareSubsetType(sst, wr);
            }
          } else if (d is NewtypeDecl) {
            var nt = (NewtypeDecl)d;
            DeclareNewtype(nt, wr);
          } else if (d is DatatypeDecl) {
            var dt = (DatatypeDecl)d;
            CheckForCapitalizationConflicts(dt.Ctors);
            foreach (var ctor in dt.Ctors) {
              CheckForCapitalizationConflicts(ctor.Destructors);
            }
            DeclareDatatype(dt, wr);
          } else if (d is IteratorDecl) {
            var iter = (IteratorDecl)d;
            if (DafnyOptions.O.ForbidNondeterminism && iter.Outs.Count > 0) {
              Error(iter.tok, "since yield parameters are initialized arbitrarily, iterators are forbidden by /definiteAssignment:3 option", wr);
            }

            var wIter = CreateIterator(iter, wr);
            if (iter.Body == null) {
              Error(iter.tok, "Iterator {0} has no body", wIter, iter.FullName);
            } else {
              TrStmtList(iter.Body.Body, wIter);
            }

          } else if (d is TraitDecl) {
            // writing the trait
            var trait = (TraitDecl)d;
            var w = CreateTrait(trait.CompileName, trait.IsExtern(out _, out _), null, null, wr);
            CompileClassMembers(trait, w);
          } else if (d is ClassDecl) {
            var cl = (ClassDecl)d;
            var include = true;
            if (cl.IsDefaultClass) {
              Predicate<MemberDecl> compilationMaterial = x =>
                !x.IsGhost && (DafnyOptions.O.DisallowExterns || !Attributes.Contains(x.Attributes, "extern"));
              include = cl.Members.Exists(compilationMaterial) || cl.InheritedMembers.Exists(compilationMaterial);
            }
            if (include) {
              var classIsExtern = !DafnyOptions.O.DisallowExterns && Attributes.Contains(cl.Attributes, "extern");
              var cw = CreateClass(IdName(cl), classIsExtern, cl.FullName, cl.TypeArgs, cl.TraitsTyp, cl.tok, wr);
              CompileClassMembers(cl, cw);
              cw.Finish();
            } else {
              // still check that given members satisfy compilation rules
              var abyss = new NullClassWriter();
              CompileClassMembers(cl, abyss);
            }
          } else if (d is ValuetypeDecl) {
            // nop
          } else if (d is ModuleDecl) {
            // nop
          } else { Contract.Assert(false); }
        }
        
        FinishModule();
      }
    }

    protected class NullClassWriter : IClassWriter {
      private readonly TargetWriter abyss = new TargetWriter();
      private readonly BlockTargetWriter block;

      public NullClassWriter() {
        block = abyss.NewBlock("");
      }

      public BlockTargetWriter/*?*/ CreateMethod(Method m, bool createBody) {
        return createBody ? block : null;
      }
      public BlockTargetWriter/*?*/ CreateFunction(string name, List<TypeParameter>/*?*/ typeArgs, List<Formal> formals, Type resultType, Bpl.IToken tok, bool isStatic, bool createBody, MemberDecl member) {
        return createBody ? block : null;
      }
      public BlockTargetWriter/*?*/ CreateGetter(string name, Type resultType, Bpl.IToken tok, bool isStatic, bool createBody, MemberDecl/*?*/ member) {
        return createBody ? block : null;
      }
      public BlockTargetWriter/*?*/ CreateGetterSetter(string name, Type resultType, Bpl.IToken tok, bool isStatic, bool createBody, MemberDecl/*?*/ member, out TargetWriter setterWriter) {
        if (createBody) {
          setterWriter = block;
          return block;
        } else {
          setterWriter = null;
          return null;
        }
      }
      public void DeclareField(string name, bool isStatic, bool isConst, Type type, Bpl.IToken tok, string rhs) { }

      public TextWriter/*?*/ ErrorWriter() {
        return null; // match the old behavior of Compile() where this is used
      }

      public void Finish() { }
    }

    protected void ReadRuntimeSystem(string filename, TextWriter wr) {
      Contract.Requires(filename != null);
      Contract.Requires(wr != null);

      if (DafnyOptions.O.UseRuntimeLib) {
        return;
      }
      var assemblyLocation = System.Reflection.Assembly.GetExecutingAssembly().Location;
      Contract.Assert(assemblyLocation != null);
      var codebase = System.IO.Path.GetDirectoryName(assemblyLocation);
      Contract.Assert(codebase != null);
      string path = System.IO.Path.Combine(codebase, filename);
      WriteFromFile(path, wr);
    }

    protected void WriteFromFile(string inputFilename, TextWriter outputWriter) {
      using (var rd = new StreamReader(new FileStream(inputFilename, System.IO.FileMode.Open, System.IO.FileAccess.Read))) {
        while (true) {
          string s = rd.ReadLine();
          if (s == null) {
            return;
          }
          outputWriter.WriteLine(s);
        }
      }
    }

    // create a varName that is not a duplicate of formals' name
    protected string GenVarName(string root, List<Formal> formals) {
      bool finished = false;
      while (!finished) {
        finished = true;
        int i = 0;
        foreach (var arg in formals) {
          if (!arg.IsGhost) {
            // FormalName returns a protected name, so we compare a protected version of "root" to it
            if (IdProtect(root).Equals(FormalName(arg, i))) {
              root += root;
              finished = false;
            }
            i++;
          }
        }
      }
      return root;
    }

    protected int WriteFormals(string sep, List<Formal> formals, TextWriter wr) {
      Contract.Requires(sep != null);
      int i = 0;
      foreach (Formal arg in formals) {
        if (!arg.IsGhost) {
          string name = FormalName(arg, i);
          if (DeclareFormal(sep, name, arg.Type, arg.tok, arg.InParam, wr)) {
            sep = ", ";
          }
          i++;
        }
      }
      return i;  // the number of formals written
    }

    protected string FormalName(Formal formal, int i) {
      Contract.Requires(formal != null);
      Contract.Ensures(Contract.Result<string>() != null);

      return IdProtect(formal.HasName ? formal.CompileName : "_a" + i);
    }

    public bool HasMain(Program program, out Method mainMethod) {
      mainMethod = null;
      bool hasMain = false;
      foreach (var module in program.CompileModules) {
        if (module.IsAbstract) {
          // the purpose of an abstract module is to skip compilation
          continue;
        }
        foreach (var decl in module.TopLevelDecls) {
          var c = decl as ClassDecl;
          if (c != null) {
            foreach (var member in c.Members) {
              var m = member as Method;
              if (m != null && IsMain(m)) {
                if (mainMethod == null) {
                  mainMethod = m;
                  hasMain = true;
                } else {
                  // more than one main in the program
                  Error(m.tok, "More than one method is declared as \"main\". First declaration appeared at {0}.", null, ErrorReporter.TokenToString(mainMethod.tok));
                  hasMain = false;
                }
              }
            }
          }
        }
      }
      if (!hasMain) {
        // make sure "mainMethod" returns as null
        mainMethod = null;
      }
      return hasMain;
    }

    public static bool IsMain(Method m) {
      Contract.Requires(m.EnclosingClass is ClassDecl);
      // In order to be a legal Main() method, the following must be true:
      //    The method is not a ghost method 
      //    The method takes no non-ghost parameters and no type parameters
      //    The enclosing class does not take any type parameters
      //    If the method is an instance (that is, non-static) method in a class, then the enclosing class must not declare any constructor
      // In addition, either:
      //    The method is called "Main"
      //    The method has no requires clause 
      //    The method has no modifies clause 
      // or:
      //    The method is annotated with {:main}
      // Note, in the case where the method is annotated with {:main}, the method is allowed to have preconditions and modifies clauses.
      // This lets the programmer add some explicit assumptions about the outside world, modeled, for example, via ghost parameters.
      var cl = (ClassDecl)m.EnclosingClass;
      if (!m.IsGhost && m.TypeArgs.Count == 0 && cl.TypeArgs.Count == 0) {
        if (m.Ins.TrueForAll(f => f.IsGhost) && m.Outs.TrueForAll(f => f.IsGhost)) {
          if (m.IsStatic || (!(cl is TraitDecl) && !cl.HasConstructor)) {
            if (m.Name == "Main" && m.Req.Count == 0 && m.Mod.Expressions.Count == 0) {
              return true;
            } else if (Attributes.Contains(m.Attributes, "main")) {
              return true;
            }
          }
        }
      }
      return false;
    }

    void CompileClassMembers(ClassDecl c, IClassWriter classWriter) {
      Contract.Requires(c != null);
      Contract.Requires(classWriter != null);

      var errorWr = classWriter.ErrorWriter();

      CheckHandleWellformed(c, errorWr);
      CheckForCapitalizationConflicts(c.Members, c.InheritedMembers);
      foreach (var member in c.InheritedMembers) {
        Contract.Assert(!member.IsStatic);  // only instance members should ever be added to .InheritedMembers
        if (member.IsGhost) {
          // skip
        } else if (member is ConstantField && SupportsProperties) {
          if (NeedsWrappersForInheritedFields) {
            var cf = (ConstantField)member;
          
            if (cf.Rhs == null) {
              Contract.Assert(!cf.IsStatic);  // as checked above, only instance members can be inherited
              classWriter.DeclareField("_" + cf.CompileName, false, false, cf.Type, cf.tok, DefaultValue(cf.Type, errorWr, cf.tok, true));
            }
            var w = classWriter.CreateGetter(IdName(cf), cf.Type, cf.tok, false, true, member);
            Contract.Assert(w != null);  // since the previous line asked for a body
            if (cf.Rhs == null) {
              var sw = EmitReturnExpr(w);
              // get { return this._{0}; }
              EmitThis(sw);
              sw.Write("._{0}", cf.CompileName);
            } else {
              CompileReturnBody(cf.Rhs, w);
            }
          }
        } else if (member is Field) {
          if (NeedsWrappersForInheritedFields) {
            var f = (Field)member;
            // every field is inherited
            classWriter.DeclareField("_" + f.CompileName, false, false, f.Type, f.tok, DefaultValue(f.Type, errorWr, f.tok, true));
            TargetWriter wSet;
            var wGet = classWriter.CreateGetterSetter(IdName(f), f.Type, f.tok, false, true, member, out wSet);
            {
              var sw = EmitReturnExpr(wGet);
              // get { return this._{0}; }
              EmitThis(sw);
              sw.Write("._{0}", f.CompileName);
            }
            {
              // set { this._{0} = value; }
              wSet.Indent();
              EmitThis(wSet);
              wSet.Write("._{0}", f.CompileName);
              var sw = EmitAssignmentRhs(wSet);
              EmitSetterParameter(sw);
            }
          }
        } else if (member is Function) {
          var f = (Function)member;
          Contract.Assert(f.Body != null);
          CompileFunction(f, classWriter);
        } else if (member is Method) {
          var method = (Method)member;
          Contract.Assert(method.Body != null);
          CompileMethod(method, classWriter);
        } else {
          Contract.Assert(false);  // unexpected member
        }
      }
      foreach (MemberDecl member in c.Members) {
        if (member is Field) {
          var f = (Field)member;
          if (f.IsGhost) {
            // emit nothing, but check for assumes
            if (f is ConstantField cf && cf.Rhs != null) {
              var v = new CheckHasNoAssumes_Visitor(this, errorWr);
              v.Visit(cf.Rhs);
            }
          } else if (f is ConstantField) {
            var cf = (ConstantField)f;
            if (SupportsProperties) {
              BlockTargetWriter wBody;
              if (cf.IsStatic) {
                wBody = classWriter.CreateGetter(IdName(cf), cf.Type, cf.tok, true, true, cf);
                Contract.Assert(wBody != null);  // since the previous line asked for a body
              } else if (c is TraitDecl) {
                wBody = classWriter.CreateGetter(IdName(cf), cf.Type, cf.tok, false, false, cf);
                Contract.Assert(wBody == null);  // since the previous line said not to create a body
              } else if (cf.Rhs == null) {
                // create a backing field, since this constant field may be assigned in constructors
                classWriter.DeclareField("_" + f.CompileName, false, false, f.Type, f.tok, DefaultValue(f.Type, errorWr, f.tok, true));
                wBody = classWriter.CreateGetter(IdName(cf), cf.Type, cf.tok, false, true, cf);
                Contract.Assert(wBody != null);  // since the previous line asked for a body
              } else {
                wBody = classWriter.CreateGetter(IdName(cf), cf.Type, cf.tok, false, true, cf);
                Contract.Assert(wBody != null);  // since the previous line asked for a body
              }
              if (wBody != null) {
                if (cf.Rhs != null) {
                  CompileReturnBody(cf.Rhs, wBody);
                } else if (!cf.IsStatic) {
                  var sw = EmitReturnExpr(wBody);
                  var wThis = EmitMemberSelect(cf, true, f.Type, sw);
                  EmitThis(wThis);
                } else {
                  EmitReturnExpr(DefaultValue(cf.Type, wBody, cf.tok, true), wBody);
                }
              }
            } else {
              // If properties aren't supported, just use a field and hope
              // everyone plays nicely ...
              string rhs;
              if (cf.Rhs != null) {
                var w = new TargetWriter();
                TrExpr(cf.Rhs, w, false);
                rhs = w.ToString();
              } else {
                rhs = null;
              }
              classWriter.DeclareField(IdName(f), f.IsStatic, true, f.Type, f.tok, rhs);
            }
          } else if (c is TraitDecl && NeedsWrappersForInheritedFields) {
            TargetWriter wSet;
            var wGet = classWriter.CreateGetterSetter(IdName(f), f.Type, f.tok, f.IsStatic, false, member, out wSet);
            Contract.Assert(wSet == null && wGet == null);  // since the previous line specified no body
          } else {
            classWriter.DeclareField(IdName(f), f.IsStatic, false, f.Type, f.tok, DefaultValue(f.Type, errorWr, f.tok, true));
          }
        } else if (member is Function) {
          var f = (Function)member;
          if (f.Body == null && !(c is TraitDecl && !f.IsStatic) && !(!DafnyOptions.O.DisallowExterns && Attributes.Contains(f.Attributes, "dllimport"))) {
            // A (ghost or non-ghost) function must always have a body, except if it's an instance function in a trait.
            if (Attributes.Contains(f.Attributes, "axiom") || (!DafnyOptions.O.DisallowExterns && Attributes.Contains(f.Attributes, "extern"))) {
              // suppress error message
            } else {
              Error(f.tok, "Function {0} has no body", errorWr, f.FullName);
            }
          } else if (f.IsGhost) {
            // nothing to compile, but we do check for assumes
            if (f.Body == null) {
              Contract.Assert(c is TraitDecl && !f.IsStatic);
            } else {
              var v = new CheckHasNoAssumes_Visitor(this, errorWr);
              v.Visit(f.Body);
            }
          } else if (c is TraitDecl && !f.IsStatic) {
            var w = classWriter.CreateFunction(IdName(f), f.TypeArgs, f.Formals, f.ResultType, f.tok, false, false, f);
            Contract.Assert(w == null);  // since we requested no body
          } else {
            CompileFunction(f, classWriter);
          }
        } else if (member is Method) {
          var m = (Method)member;
          if (m.Body == null && !(c is TraitDecl && !m.IsStatic) && !(!DafnyOptions.O.DisallowExterns && Attributes.Contains(m.Attributes, "dllimport"))) {
            // A (ghost or non-ghost) method must always have a body, except if it's an instance method in a trait.
            if (Attributes.Contains(m.Attributes, "axiom") || (!DafnyOptions.O.DisallowExterns && Attributes.Contains(m.Attributes, "extern"))) {
              // suppress error message
            } else {
              Error(m.tok, "Method {0} has no body", errorWr, m.FullName);
            }
          } else if (m.IsGhost) {
            // nothing to compile, but we do check for assumes
            if (m.Body == null) {
              Contract.Assert(c is TraitDecl && !m.IsStatic);
            } else {
              var v = new CheckHasNoAssumes_Visitor(this, errorWr);
              v.Visit(m.Body);
            }
          } else if (c is TraitDecl && !m.IsStatic) {
            var w = classWriter.CreateMethod(m, false);
            Contract.Assert(w == null);  // since we requested no body
          } else {
            CompileMethod(m, classWriter);
          }
        } else {
          Contract.Assert(false); throw new cce.UnreachableException();  // unexpected member
        }
      }
    }

    void CheckHandleWellformed(ClassDecl cl, TextWriter/*?*/ errorWr) {
      Contract.Requires(cl != null);
      var isHandle = true;
      if (Attributes.ContainsBool(cl.Attributes, "handle", ref isHandle) && isHandle) {
        foreach (var trait in cl.TraitsObj) {
          isHandle = true;
          if (Attributes.ContainsBool(trait.Attributes, "handle", ref isHandle) && isHandle) {
            // all is good
          } else {
            Error(cl.tok, "{0} '{1}' is marked as :handle, so all the traits it extends must be be marked as :handle as well: {2}", errorWr, cl.WhatKind, cl.Name, trait.Name);
          }
        }
        foreach (var member in cl.InheritedMembers.Concat(cl.Members)) {
          if (!member.IsGhost && !member.IsStatic) {
            Error(member.tok, "{0} '{1}' is marked as :handle, so all its non-static members must be ghost: {2}", errorWr, cl.WhatKind, cl.Name, member.Name);
          }
        }
      }
    }

    /// <summary>
    /// Check whether two declarations have the same name if capitalized.
    /// </summary>
    /// <param name="canChange">The declarations to check.</param>
    /// <param name="cantChange">Additional declarations which may conflict, but which can't be given different names.  For example, these may be the inherited members of a class.</param>
    /// <remarks>
    /// If two elements of <paramref name="canChange"/> have the same
    /// capitalization, the lowercase one will get a
    /// <c>{:_capitalizationConflict}</c> attribute.  If
    /// <paramref name="cantChange"/> is given and one of its elements conflicts
    /// with one from <paramref name="canChange"/>, the element from
    /// <paramref name="canChange"/> gets the attribute whether it is lowercase
    /// or not.
    /// </remarks>
    /// <seealso cref="HasCapitalizationConflict"/>
    private void CheckForCapitalizationConflicts<T>(IEnumerable<T> canChange, IEnumerable<T> cantChange = null) where T : Declaration {
      if (cantChange == null) {
        cantChange = Enumerable.Empty<T>();
      }
      IDictionary<string, T> declsByCapName = new Dictionary<string, T>();
      ISet<string> fixedNames = new HashSet<string>(from decl in cantChange select Capitalize(decl.CompileName));

      foreach (var decl in canChange) {
        var name = decl.CompileName;
        var capName = Capitalize(name);
        if (name == capName) {
          if (fixedNames.Contains(name)) {
            // Normally we mark the lowercase one, but in this case we can't change that one
            MarkCapitalizationConflict(decl);
          } else {
            T other;
            if (declsByCapName.TryGetValue(name, out other)) {
              // Presume that the other is the lowercase one
              MarkCapitalizationConflict(other);
            } else {
              declsByCapName.Add(name, decl);
            }
          }
        } else {
          if (declsByCapName.ContainsKey(capName)) {
            MarkCapitalizationConflict(decl);
          } else {
            declsByCapName.Add(capName, decl);
          }
        }
      }
    }

    protected string Capitalize(string str) {
      if (!str.Any(c => c != '_')) {
        return PrefixForForcedCapitalization + str;
      }
      var origStr = str;
      while (str.StartsWith("_")) {
        str = str.Substring(1) + "_";
      }
      if (!char.IsLetter(str[0])) {
        return PrefixForForcedCapitalization + origStr;
      } else {
        return char.ToUpper(str[0]) + str.Substring(1);
      }
    }

    protected virtual string PrefixForForcedCapitalization { get => "Cap_"; }

    private static void MarkCapitalizationConflict(Declaration decl) {
      decl.Attributes = new Attributes(CapitalizationConflictAttribute, new List<Expression>(), decl.Attributes);
    }

    protected static bool HasCapitalizationConflict(Declaration decl) {
      return Attributes.Contains(decl.Attributes, CapitalizationConflictAttribute);
    }

    private static string CapitalizationConflictAttribute = "_capitalizationConflict";

    private void CompileFunction(Function f, IClassWriter cw) {
      Contract.Requires(f != null);
      Contract.Requires(cw != null);

      var w = cw.CreateFunction(IdName(f), f.TypeArgs, f.Formals, f.ResultType, f.tok, f.IsStatic, true, f);
      if (w != null) {
        CompileReturnBody(f.Body, w);
      }
    }

    private void CompileMethod(Method m, IClassWriter cw) {
      Contract.Requires(cw != null);
      Contract.Requires(m != null);

      var w = cw.CreateMethod(m, true);
      if (w != null) {
        foreach (Formal p in m.Outs) {
          if (!p.IsGhost) {
            DeclareLocalOutVar(IdName(p), p.Type, p.tok, DefaultValue(p.Type, w, p.tok, true), w);
          }
        }
        if (m.Body == null) {
          Error(m.tok, "Method {0} has no body", w, m.FullName);
        } else {
          Contract.Assert(enclosingMethod == null);
          enclosingMethod = m;
          TrStmtList(m.Body.Body, w);
          Contract.Assert(enclosingMethod == m);
          enclosingMethod = null;
        }
      }

      // allow the Main method to be an instance method
      if (IsMain(m) && (!m.IsStatic || m.CompileName != "Main")) {
        w = CreateStaticMain(cw);
        if (!m.IsStatic) {
          var c = m.EnclosingClass;
          var typeArgs = c.TypeArgs.ConvertAll(tp => (Type)Type.Bool);
          var ty = new UserDefinedType(m.tok, c.Name, c, typeArgs);
          var wRhs = DeclareLocalVar("b", ty, m.tok, w);
          EmitNew(ty, m.tok, null, wRhs);
          w.Indent(); w.WriteLine("b.{0}();", IdName(m));
        } else {
          w.Indent(); w.WriteLine("{0}();", IdName(m));
        }
      }
    }

    void TrCasePatternOpt<VT>(CasePattern<VT> pat, Expression rhs, TargetWriter wr, bool inLetExprBody) where VT: IVariable {
      TrCasePatternOpt(pat, rhs, null, rhs.Type, rhs.tok, wr, inLetExprBody);
    }

    void TrCasePatternOpt<VT>(CasePattern<VT> pat, Expression rhs, string rhs_string, Type rhsType, Bpl.IToken rhsTok, TargetWriter wr, bool inLetExprBody) where VT: IVariable {
      Contract.Requires(pat != null);
      Contract.Requires(pat.Var != null || rhs != null || rhs_string != null);
      Contract.Requires(rhs != null || rhs_string != null);
      Contract.Requires(rhsType != null && rhsTok != null);

      if (pat.Var != null) {
        // The trivial Dafny "pattern" expression
        //    var x := G
        // is translated into C# as:
        // var x := G;
        var bv = pat.Var;
        if (!bv.IsGhost) {
          var w = DeclareLocalVar(IdProtect(bv.CompileName), bv.Type, rhsTok, wr);
          if (rhs != null) {
            w = EmitCoercionIfNecessary(from: rhs.Type, to: bv.Type, tok:rhsTok, wr:w);
            TrExpr(rhs, w, inLetExprBody);
          } else {
            w.Write(rhs_string);
          }
        }
      } else if (pat.Arguments != null) {
        // The Dafny "pattern" expression
        //    var Pattern(x,y) := G
        // is translated into C# as:
        // var tmp := G;
        // var x := dtorX(tmp);
        // var y := dtorY(tmp);
        var ctor = pat.Ctor;
        Contract.Assert(ctor != null);  // follows from successful resolution
        Contract.Assert(pat.Arguments.Count == ctor.Formals.Count);  // follows from successful resolution

        // Create the temporary variable to hold G
        var tmp_name = idGenerator.FreshId("_let_tmp_rhs");
        if (rhs != null) {
          DeclareLocalVar(tmp_name, rhs.Type, rhs.tok, rhs, inLetExprBody, wr);
        } else {
          DeclareLocalVar(tmp_name, rhsType, rhsTok, false, rhs_string, wr);
        }

        var k = 0;  // number of non-ghost formals processed
        for (int i = 0; i < pat.Arguments.Count; i++) {
          var arg = pat.Arguments[i];
          var formal = ctor.Formals[i];
          if (formal.IsGhost) {
            // nothing to compile, but do a sanity check
            Contract.Assert(Contract.ForAll(arg.Vars, bv => bv.IsGhost));
          } else {
            var sw = new TargetWriter();
            EmitDestructor(tmp_name, formal, k, ctor, ((DatatypeValue)pat.Expr).InferredTypeArgs, arg.Expr.Type, sw);
            TrCasePatternOpt(arg, null, sw.ToString(), pat.Expr.Type, pat.Expr.tok, wr, inLetExprBody);
            k++;
          }
        }
      }
    }

    void TrExprOpt(Expression expr, TargetWriter wr, bool inLetExprBody) {
      Contract.Requires(expr != null);
      if (expr is LetExpr) {
        var e = (LetExpr)expr;
        if (e.Exact) {
          for (int i = 0; i < e.LHSs.Count; i++) {
            var lhs = e.LHSs[i];
            if (Contract.Exists(lhs.Vars, bv => !bv.IsGhost)) {
              TrCasePatternOpt(lhs, e.RHSs[i], wr, inLetExprBody);
            }
          }
          TrExprOpt(e.Body, wr, inLetExprBody);
        } else {
          // We haven't optimized the other cases, so fallback to normal compilation
          EmitReturnExpr(e, inLetExprBody, wr);
        }
      } else if (expr is ITEExpr) {
        var e = (ITEExpr)expr;
        TargetWriter guardWriter;
        var thn = EmitIf(out guardWriter, true, wr);
        TrExpr(e.Test, guardWriter, inLetExprBody);
        TrExprOpt(e.Thn, thn, inLetExprBody);
        var els = wr.NewBlock("");
        TrExprOpt(e.Els, els, inLetExprBody);
      } else if (expr is MatchExpr) {
        var e = (MatchExpr)expr;
        //   var _source = E;
        //   if (source.is_Ctor0) {
        //     FormalType f0 = ((Dt_Ctor0)source._D).a0;
        //     ...
        //     return Body0;
        //   } else if (...) {
        //     ...
        //   } else if (true) {
        //     ...
        //   }
        string source = idGenerator.FreshId("_source");
        DeclareLocalVar(source, e.Source.Type, e.Source.tok, e.Source, inLetExprBody, wr);

        if (e.Cases.Count == 0) {
          // the verifier would have proved we never get here; still, we need some code that will compile
          EmitAbsurd(null, wr);
        } else {
          int i = 0;
          var sourceType = (UserDefinedType)e.Source.Type.NormalizeExpand();
          foreach (MatchCaseExpr mc in e.Cases) {
            var w = MatchCasePrelude(source, sourceType, mc.Ctor, mc.Arguments, i, e.Cases.Count, wr);
            TrExprOpt(mc.Body, w, inLetExprBody);
            i++;
          }
        }
      } else if (expr is StmtExpr) {
        var e = (StmtExpr)expr;
        TrExprOpt(e.E, wr, inLetExprBody);
      } else {
        // We haven't optimized any other cases, so fallback to normal compilation
        EmitReturnExpr(expr, inLetExprBody, wr);
      }
    }

    void CompileReturnBody(Expression body, TargetWriter wr) {
      TrExprOpt(body.Resolved, wr, false);
    }

    // ----- Type ---------------------------------------------------------------------------------

    protected readonly string DafnySetClass = "Dafny.Set";
    protected readonly string DafnyMultiSetClass = "Dafny.MultiSet";
    protected readonly string DafnySeqClass = "Dafny.Sequence";
    protected readonly string DafnyMapClass = "Dafny.Map";

    protected NativeType AsNativeType(Type typ) {
      Contract.Requires(typ != null);
      if (typ.AsNewtype != null) {
        return typ.AsNewtype.NativeType;
      } else if (typ.IsBitVectorType) {
        return typ.AsBitVectorType.NativeType;
      }
      return null;
    }

    /// <summary>
    /// Note, C# reverses the order of brackets in array type names.
    /// </summary>
    protected void TypeName_SplitArrayName(Type type, TextWriter wr, Bpl.IToken tok, out string typeNameSansBrackets, out string brackets) {
      Contract.Requires(type != null);

      var xType = type.NormalizeExpand();
      if (xType.IsArrayType) {
        ArrayClassDecl at = xType.AsArrayType;
        Contract.Assert(at != null);  // follows from type.IsArrayType
        Type elType = UserDefinedType.ArrayElementType(xType);
        TypeName_SplitArrayName(elType, wr, tok, out typeNameSansBrackets, out brackets);
        brackets = TypeNameArrayBrackets(at.Dims) + brackets;
      } else {
        typeNameSansBrackets = TypeName(type, wr, tok);
        brackets = "";
      }
    }

    protected string TypeNameArrayBrackets(int dims) {
      Contract.Requires(0 <= dims);
      var name = "[";
      for (int i = 1; i < dims; i++) {
        name += ",";
      }
      return name + "]";
    }

    protected bool ComplicatedTypeParameterForCompilation(Type t) {
      Contract.Requires(t != null);
      return t.IsTraitType;
    }

    protected string/*!*/ TypeNames(List<Type/*!*/>/*!*/ types, TextWriter wr, Bpl.IToken tok) {
      Contract.Requires(cce.NonNullElements(types));
      Contract.Ensures(Contract.Result<string>() != null);
      string res = "";
      string c = "";
      foreach (var t in types) {
        res += c + TypeName(t, wr, tok);
        c = ",";
      }
      return res;
    }

    // TODO: move this method into CsharpCompiler
    string/*!*/ TypeParameters(List<TypeParameter/*!*/>/*!*/ targs) {
      Contract.Requires(cce.NonNullElements(targs));
      Contract.Ensures(Contract.Result<string>() != null);

      return Util.Comma(targs, IdName);
    }

    /// <summary>
    /// Returns "true" if a value of type "type" can be initialized with the all-zero bit pattern.
    /// </summary>
    public static bool HasSimpleZeroInitializer(Type type) {
      Contract.Requires(type != null);

      bool hs, hz, ik;
      string dv;
      TypeInitialization(type, null, null, null, out hs, out hz, out ik, out dv);
      return hs;
    }

    /// <summary>
    /// Returns "true" if a value of type "type" can be initialized with the all-zero bit pattern or by calling the type's _DafnyDefaultValue method.
    /// </summary>
    public static bool HasZeroInitializer(Type type) {
      Contract.Requires(type != null);

      bool hs, hz, ik;
      string dv;
      TypeInitialization(type, null, null, null, out hs, out hz, out ik, out dv);
      return hz;
    }

    /// <summary>
    /// Returns "true" if "type" denotes a type for which a specific compiled value (non-ghost witness) is known.
    /// </summary>
    public static bool InitializerIsKnown(Type type) {
      Contract.Requires(type != null);

      bool hs, hz, ik;
      string dv;
      TypeInitialization(type, null, null, null, out hs, out hz, out ik, out dv);
      return ik;
    }

    protected string DefaultValue(Type type, TextWriter wr, Bpl.IToken tok, bool inAutoInitContext = false) {
      Contract.Requires(type != null);
      Contract.Requires(wr != null);
      Contract.Requires(tok != null);
      Contract.Ensures(Contract.Result<string>() != null);

      bool hs, hz, ik;
      string dv;
      TypeInitialization(type, this, wr, tok, out hs, out hz, out ik, out dv, inAutoInitContext);
      return dv;
    }

    /// <summary>
    /// This method returns three things about the given type. Since the three things are related,
    /// it makes sense to compute them side by side.
    ///   hasZeroInitializer - "true" if a value of type "type" can be initialized with the all-zero bit pattern or
    ///                        by calling the type's _DafnyDefaultValue method.
    ///   hasSimpleZeroInitializer - "true" if a value of type "type" can be initialized with the all-zero bit pattern.
    ///   initializerIsKnown - "true" if "type" denotes a type for which a specific value (witness) is known.
    ///   defaultValue - If "compiler" is non-null, "defaultValue" is the C# representation of one possible value of the
    ///                  type (not necessarily the same value as the zero initializer, if any, may give).
    ///                  If "compiler" is null, then "defaultValue" can return as anything.
    ///   inAutoInitContext - If "true", the default value produced may have dummy values (outside the Dafny type) for
    ///                       components those type requires user-specified initialization.
    /// </summary>
    static void TypeInitialization(Type type, Compiler/*?*/ compiler, TextWriter/*?*/ wr, Bpl.IToken/*?*/ tok,
        out bool hasSimpleZeroInitializer, out bool hasZeroInitializer, out bool initializerIsKnown, out string defaultValue,
        bool inAutoInitContext = false) {
      Contract.Requires(type != null);
      Contract.Requires(compiler == null || (wr != null && tok != null));
      Contract.Ensures(!Contract.ValueAtReturn(out hasSimpleZeroInitializer) || Contract.ValueAtReturn(out hasZeroInitializer));  // hasSimpleZeroInitializer ==> hasZeroInitializer 
      Contract.Ensures(!Contract.ValueAtReturn(out hasZeroInitializer) || Contract.ValueAtReturn(out initializerIsKnown));  // hasZeroInitializer ==> initializerIsKnown
      Contract.Ensures(compiler == null || Contract.ValueAtReturn(out defaultValue) != null);

      var xType = type.NormalizeExpandKeepConstraints();
      if (xType is TypeProxy) {
        // unresolved proxy; just treat as bool, since no particular type information is apparently needed for this type
        xType = new BoolType();
      }

      defaultValue = compiler?.TypeInitializationValue(xType, wr, tok, inAutoInitContext);
      if (xType is BoolType) {
        hasSimpleZeroInitializer = true;
        hasZeroInitializer = true;
        initializerIsKnown = true;
        return;
      } else if (xType is CharType) {
        hasSimpleZeroInitializer = true;
        hasZeroInitializer = true;
        initializerIsKnown = true;
        return;
      } else if (xType is IntType || xType is BigOrdinalType) {
        hasSimpleZeroInitializer = true;
        hasZeroInitializer = true;
        initializerIsKnown = true;
        return;
      } else if (xType is RealType) {
        hasSimpleZeroInitializer = true;
        hasZeroInitializer = true;
        initializerIsKnown = true;
        return;
      } else if (xType is BitvectorType) {
        var t = (BitvectorType)xType;
        hasSimpleZeroInitializer = true;
        hasZeroInitializer = true;
        initializerIsKnown = true;
        return;
      } else if (xType is CollectionType) {
        hasSimpleZeroInitializer = false;
        hasZeroInitializer = true;
        initializerIsKnown = true;
        return;
      }

      var udt = (UserDefinedType)xType;
      if (udt.ResolvedParam != null) {
        hasSimpleZeroInitializer = false;
        hasZeroInitializer = udt.ResolvedParam.Characteristics.MustSupportZeroInitialization;
        initializerIsKnown = hasZeroInitializer;
        // If the module is complete, we expect "udt.ResolvedClass == null" at this time. However, it could be that
        // the compiler has already generated an error about this type not being compilable, in which case
        // "udt.ResolvedClass" might be non-null here.
        return;
      }
      var cl = udt.ResolvedClass;
      Contract.Assert(cl != null);
      if (cl is OpaqueTypeDecl) {
        hasSimpleZeroInitializer = false;
        hasZeroInitializer = ((OpaqueTypeDecl)cl).TheType.Characteristics.MustSupportZeroInitialization;
        initializerIsKnown = hasZeroInitializer;
        // The compiler should never need to know a "defaultValue" for an opaque type, but this routine may
        // be asked from outside the compiler about one of the other output booleans.
        Contract.Assume(compiler == null);
        return;
      } else if (cl is NewtypeDecl) {
        var td = (NewtypeDecl)cl;
        if (td.Witness != null) {
          hasSimpleZeroInitializer = false;
          hasZeroInitializer = false;
          initializerIsKnown = td.WitnessKind != SubsetTypeDecl.WKind.Ghost;
          return;
        } else if (td.NativeType != null) {
          bool ik;
          string dv;
          TypeInitialization(td.BaseType, null, null, null, out hasSimpleZeroInitializer, out hasZeroInitializer, out ik, out dv);
          initializerIsKnown = true;
          return;
        } else {
          Contract.Assert(td.WitnessKind != SubsetTypeDecl.WKind.Special);  // this value is never used with NewtypeDecl
          string dv;
          TypeInitialization(td.BaseType, compiler, wr, udt.tok, out hasSimpleZeroInitializer, out hasZeroInitializer, out initializerIsKnown, out dv);
          Contract.Assert(compiler == null || string.Equals(dv, defaultValue));
          return;
        }
      } else if (cl is SubsetTypeDecl) {
        var td = (SubsetTypeDecl)cl;
        if (td.Witness != null) {
          hasSimpleZeroInitializer = false;
          hasZeroInitializer = false;
          initializerIsKnown = td.WitnessKind != SubsetTypeDecl.WKind.Ghost;
          return;
        } else if (td.WitnessKind == SubsetTypeDecl.WKind.Special) {
          // WKind.Special is only used with -->, ->, and non-null types:
          Contract.Assert(ArrowType.IsPartialArrowTypeName(td.Name) || ArrowType.IsTotalArrowTypeName(td.Name) || td is NonNullTypeDecl);
          if (ArrowType.IsPartialArrowTypeName(td.Name)) {
            // partial arrow
            hasSimpleZeroInitializer = true;
            hasZeroInitializer = true;
            initializerIsKnown = true;
            return;
          } else if (ArrowType.IsTotalArrowTypeName(td.Name)) {
            // total arrow
            Contract.Assert(udt.TypeArgs.Count == td.TypeArgs.Count);
            Contract.Assert(1 <= udt.TypeArgs.Count);  // the return type is one of the type arguments
            hasSimpleZeroInitializer = false;
            hasZeroInitializer = false;
            bool hs, hz;
            string dv;
            TypeInitialization(udt.TypeArgs.Last(), compiler, wr, udt.tok, out hs, out hz, out initializerIsKnown, out dv);
            return;
          } else if (((NonNullTypeDecl)td).Class is ArrayClassDecl) {
            // non-null array type; we know how to initialize them
            hasSimpleZeroInitializer = false;
            hasZeroInitializer = false;
            initializerIsKnown = true;
            Contract.Assert(udt.TypeArgs.Count == 1);
          } else {
            // non-null (non-array) type
            hasSimpleZeroInitializer = false;
            hasZeroInitializer = false;
            initializerIsKnown = false;  // (this could be improved in some cases)
            return;
          }
        } else {
          string dv;
          TypeInitialization(td.RhsWithArgument(udt.TypeArgs), compiler, wr, udt.tok, out hasSimpleZeroInitializer, out hasZeroInitializer, out initializerIsKnown, out dv);
          Contract.Assert(compiler == null || string.Equals(dv, defaultValue));
          return;
        }
      } else if (cl is TypeSynonymDeclBase) {
        hasSimpleZeroInitializer = false;
        hasZeroInitializer = ((TypeSynonymDeclBase)cl).Characteristics.MustSupportZeroInitialization;
        initializerIsKnown = hasZeroInitializer;
        // The compiler should never need to know a "defaultValue" for a(n internal) type synonym, but this routine may
        // be asked from outside the compiler about one of the other output booleans.
        Contract.Assume(compiler == null);
        return;
      } else if (cl is ClassDecl) {
        hasSimpleZeroInitializer = true;
        hasZeroInitializer = true;
        initializerIsKnown = true;
        return;
      } else if (cl is DatatypeDecl) {
        // --- hasZeroInitializer ---
        hasSimpleZeroInitializer = false;  // TODO: improve this one in special cases where one of the datatype values can be represented by "null"
        // --- initializerIsKnown ---
        if (cl is CoDatatypeDecl) {
          hasZeroInitializer = false;  // TODO: improve this one by laying down a _DafnyDefaultValue method when it's possible
          // The constructors of a codatatype may use type arguments that are not "smaller" than "type",
          // in which case recursing on the types of the constructor's formals may lead to an infinite loop
          // here.  If this were important, the code here could be changed to detect such loop, which would
          // let "initializerIsKnown" be computed more precisely.  For now, set it to "true" if the type
          // has a zero initializer.
          initializerIsKnown = hasZeroInitializer;
        } else {
          var defaultCtor = ((IndDatatypeDecl)cl).DefaultCtor;
          var subst = Resolver.TypeSubstitutionMap(cl.TypeArgs, udt.TypeArgs);
          hasZeroInitializer = defaultCtor.Formals.TrueForAll(formal => formal.IsGhost || HasZeroInitializer(Resolver.SubstType(formal.Type, subst)));
          initializerIsKnown = defaultCtor.Formals.TrueForAll(formal => formal.IsGhost || InitializerIsKnown(Resolver.SubstType(formal.Type, subst)));
        }
        return;
      } else {
        Contract.Assert(false); throw new cce.UnreachableException();  // unexpected type
      }
    }

    // ----- Stmt ---------------------------------------------------------------------------------

    public class CheckHasNoAssumes_Visitor : BottomUpVisitor
    {
      readonly Compiler compiler;
      TextWriter wr;
      public CheckHasNoAssumes_Visitor(Compiler c, TextWriter wr) {
        Contract.Requires(c != null);
        compiler = c;
        this.wr = wr;
      }
      protected override void VisitOneStmt(Statement stmt) {
        if (stmt is AssumeStmt) {
          compiler.Error(stmt.Tok, "an assume statement cannot be compiled", wr);
        } else if (stmt is AssignSuchThatStmt) {
          var s = (AssignSuchThatStmt)stmt;
          if (s.AssumeToken != null) {
            compiler.Error(stmt.Tok, "an assume statement cannot be compiled", wr);
          }
        } else if (stmt is ForallStmt) {
          var s = (ForallStmt)stmt;
          if (s.Body == null) {
            compiler.Error(stmt.Tok, "a forall statement without a body cannot be compiled", wr);
          }
        } else if (stmt is WhileStmt) {
          var s = (WhileStmt)stmt;
          if (s.Body == null) {
            compiler.Error(stmt.Tok, "a while statement without a body cannot be compiled", wr);
          }
        }
      }
    }

    void TrStmt(Statement stmt, TargetWriter wr) {
      Contract.Requires(stmt != null);
      Contract.Requires(wr != null);

      if (stmt.IsGhost) {
        var v = new CheckHasNoAssumes_Visitor(this, wr);
        v.Visit(stmt);
        wr.Indent(); wr.WriteLine("{ }");
        return;
      }
      if (stmt is PrintStmt) {
        var s = (PrintStmt)stmt;
        foreach (var arg in s.Args) {
          EmitPrintStmt(wr, arg);
        }
      } else if (stmt is BreakStmt) {
        var s = (BreakStmt)stmt;
        EmitBreak(s.TargetStmt.Labels.Data.AssignUniqueId(idGenerator), wr);
      } else if (stmt is ProduceStmt) {
        var s = (ProduceStmt)stmt;
        if (s.hiddenUpdate != null) {
          TrStmt(s.hiddenUpdate, wr);
        }
        if (s is YieldStmt) {
          EmitYield(wr);
        } else {
          EmitReturn(this.enclosingMethod.Outs, wr);
        }
      } else if (stmt is UpdateStmt) {
        var s = (UpdateStmt)stmt;
        var resolved = s.ResolvedStatements;
        if (resolved.Count == 1) {
          TrStmt(resolved[0], wr);
        } else {
          // multi-assignment
          Contract.Assert(s.Lhss.Count == resolved.Count);
          Contract.Assert(s.Rhss.Count == resolved.Count);
          var lhsTypes = new List<Type>();
          var rhsTypes = new List<Type>();
          var lhss = new List<Expression>();
          var rhss = new List<AssignmentRhs>();
          for (int i = 0; i < resolved.Count; i++) {
            if (!resolved[i].IsGhost) {
              var lhs = s.Lhss[i];
              var rhs = s.Rhss[i];
              if (rhs is HavocRhs) {
                if (DafnyOptions.O.ForbidNondeterminism) {
                  Error(rhs.Tok, "nondeterministic assignment forbidden by /definiteAssignment:3 option", wr);
                }
              } else {
                lhss.Add(lhs);
                lhsTypes.Add(lhs.Type);
                rhss.Add(rhs);
                rhsTypes.Add(TypeOfRhs(rhs));
              }
            }
          }

          var wStmts = wr.Fork();
          List<TargetWriter> wLhss, wRhss;
          EmitMultiAssignment(out wLhss, lhsTypes, out wRhss, rhsTypes, wr);
          for (int i = 0; i < wLhss.Count; i++) {
            wLhss[i].Write(CreateLvalue(lhss[i], wStmts));
            TrRhs(rhss[i], wRhss[i], wStmts);
          }
        }
      } else if (stmt is AssignStmt) {
        var s = (AssignStmt)stmt;
        Contract.Assert(!(s.Lhs is SeqSelectExpr) || ((SeqSelectExpr)s.Lhs).SelectOne);  // multi-element array assignments are not allowed
        if (s.Rhs is HavocRhs) {
          if (DafnyOptions.O.ForbidNondeterminism) {
            Error(s.Rhs.Tok, "nondeterministic assignment forbidden by /definiteAssignment:3 option", wr);
          }
        } else {
          var lvalue = CreateLvalue(s.Lhs, wr);
          var wStmts = wr.Fork();
          TargetWriter wLhs, wRhs;
          EmitAssignment(out wLhs, s.Lhs.Type, out wRhs, TypeOfRhs(s.Rhs), wr);
          wLhs.Write(lvalue);
          TrRhs(s.Rhs, wRhs, wStmts);
        }

      } else if (stmt is AssignSuchThatStmt) {
        var s = (AssignSuchThatStmt)stmt;
        if (DafnyOptions.O.ForbidNondeterminism) {
          Error(s.Tok, "assign-such-that statement forbidden by /definiteAssignment:3 option", wr);
        }
        if (s.AssumeToken != null) {
          // Note, a non-ghost AssignSuchThatStmt may contain an assume
          Error(s.AssumeToken, "an assume statement cannot be compiled", wr);
        } else {
          var lhss = s.Lhss.ConvertAll(lhs => ((IdentifierExpr)lhs.Resolved).Var);  // the resolver allows only IdentifierExpr left-hand sides
          var missingBounds = ComprehensionExpr.BoundedPool.MissingBounds(lhss, s.Bounds, ComprehensionExpr.BoundedPool.PoolVirtues.Enumerable);
          if (missingBounds.Count != 0) {
            foreach (var bv in missingBounds) {
              Error(s.Tok, "this assign-such-that statement is too advanced for the current compiler; Dafny's heuristics cannot find any bound for variable '{0}'", wr, bv.Name);
            }
          } else {
            Contract.Assert(s.Bounds != null);
            TrAssignSuchThat(lhss, s.Expr, s.Bounds, s.Tok.line, wr, false);
          }
        }

      } else if (stmt is CallStmt) {
        CallStmt s = (CallStmt)stmt;
        wr.Write(TrCallStmt(s, null, wr.IndentLevel).ToString());

      } else if (stmt is BlockStmt) {
        wr.Indent();
        var w = wr.NewBlock("");
        w.SetBraceStyle(BlockTargetWriter.BraceStyle.Nothing, BlockTargetWriter.BraceStyle.Newline);
        TrStmtList(((BlockStmt)stmt).Body, w);

      } else if (stmt is IfStmt) {
        IfStmt s = (IfStmt)stmt;
        if (s.Guard == null) {
          if (DafnyOptions.O.ForbidNondeterminism) {
            Error(s.Tok, "nondeterministic if statement forbidden by /definiteAssignment:3 option", wr);
          }
          // we can compile the branch of our choice
          if (s.Els == null) {
            // let's compile the "else" branch, since that involves no work
            // (still, let's leave a marker in the source code to indicate that this is what we did)
            wr.Indent();
            wr.WriteLine("if (!false) { }");
          } else {
            // let's compile the "then" branch
            wr.Indent();
            wr.Write("if (true) ");
            TrStmt(s.Thn, wr);
          }
        } else {
          if (s.IsBindingGuard && DafnyOptions.O.ForbidNondeterminism) {
            Error(s.Tok, "binding if statement forbidden by /definiteAssignment:3 option", wr);
          }
          TargetWriter guardWriter;
          var thenWriter = EmitIf(out guardWriter, s.Els != null, wr);
          TrExpr(s.IsBindingGuard ? Translator.AlphaRename((ExistsExpr)s.Guard, "eg_d") : s.Guard, guardWriter, false);

          // We'd like to do "TrStmt(s.Thn, indent)", except we want the scope of any existential variables to come inside the block
          if (s.IsBindingGuard) {
            IntroduceAndAssignBoundVars((ExistsExpr)s.Guard, thenWriter);
          }
          TrStmtList(s.Thn.Body, thenWriter);

          if (s.Els != null) {
            TrStmt(s.Els, wr);
          }
        }

      } else if (stmt is AlternativeStmt) {
        var s = (AlternativeStmt)stmt;
        if (DafnyOptions.O.ForbidNondeterminism && 2 <= s.Alternatives.Count) {
          Error(s.Tok, "case-based if statement forbidden by /definiteAssignment:3 option", wr);
        }
        foreach (var alternative in s.Alternatives) {
          TargetWriter guardWriter;
          var thn = EmitIf(out guardWriter, true, wr);
          TrExpr(alternative.IsBindingGuard ? Translator.AlphaRename((ExistsExpr)alternative.Guard, "eg_d") : alternative.Guard, guardWriter, false);
          if (alternative.IsBindingGuard) {
            IntroduceAndAssignBoundVars((ExistsExpr)alternative.Guard, thn);
          }
          TrStmtList(alternative.Body, thn);
        }
        using (var wElse = wr.NewBlock("")) {
          EmitAbsurd("unreachable alternative", wElse);
        }

      } else if (stmt is WhileStmt) {
        WhileStmt s = (WhileStmt)stmt;
        if (s.Body == null) {
          return;
        }
        if (s.Guard == null) {
          if (DafnyOptions.O.ForbidNondeterminism) {
            Error(s.Tok, "nondeterministic loop forbidden by /definiteAssignment:3 option", wr);
          }
          // this loop is allowed to stop iterating at any time; we choose to never iterate; but we still emit a loop structure
          var guardWriter = EmitWhile(new List<Statement>(), wr);
          guardWriter.Write("false");
        } else {
          var guardWriter = EmitWhile(s.Body.Body, wr);
          TrExpr(s.Guard, guardWriter, false);
        }

      } else if (stmt is AlternativeLoopStmt) {
        var s = (AlternativeLoopStmt)stmt;
        if (DafnyOptions.O.ForbidNondeterminism) {
          Error(s.Tok, "case-based loop forbidden by /definiteAssignment:3 option", wr);
        }
        if (s.Alternatives.Count != 0) {
          wr.Indent();
          TargetWriter whileGuardWriter;
          var w = CreateWhileLoop(out whileGuardWriter, wr);
          whileGuardWriter.Write("true");
          foreach (var alternative in s.Alternatives) {
            TargetWriter guardWriter;
            var thn = EmitIf(out guardWriter, true, w);
            TrExpr(alternative.Guard, guardWriter, false);
            TrStmtList(alternative.Body, thn);
          }
          using (var wElse = w.NewBlock("")) {
            EmitBreak(null, wElse);
          }
        }

      } else if (stmt is ForallStmt) {
        var s = (ForallStmt)stmt;
        if (s.Kind != ForallStmt.BodyKind.Assign) {
          // Call and Proof have no side effects, so they can simply be optimized away.
          return;
        } else if (s.BoundVars.Count == 0) {
          // the bound variables just spell out a single point, so the forall statement is equivalent to one execution of the body
          TrStmt(s.Body, wr);
          return;
        }
        var s0 = (AssignStmt)s.S0;
        if (s0.Rhs is HavocRhs) {
          if (DafnyOptions.O.ForbidNondeterminism) {
            Error(s0.Rhs.Tok, "nondeterministic assignment forbidden by /definiteAssignment:3 option", wr);
          }
          // The forall statement says to havoc a bunch of things.  This can be efficiently compiled
          // into doing nothing.
          return;
        }
        var rhs = ((ExprRhs)s0.Rhs).Expr;

        // Compile:
        //   forall (w,x,y,z | Range(w,x,y,z)) {
        //     LHS(w,x,y,z) := RHS(w,x,y,z);
        //   }
        // where w,x,y,z have types seq<W>,set<X>,int,bool and LHS has L-1 top-level subexpressions
        // (that is, L denotes the number of top-level subexpressions of LHS plus 1),
        // into:
        //   var ingredients = new List< L-Tuple >();
        //   foreach (W w in sq.UniqueElements) {
        //     foreach (X x in st.Elements) {
        //       for (BigInteger y = Lo; j < Hi; j++) {
        //         for (bool z in Helper.AllBooleans) {
        //           if (Range(w,x,y,z)) {
        //             ingredients.Add(new L-Tuple( LHS0(w,x,y,z), LHS1(w,x,y,z), ..., RHS(w,x,y,z) ));
        //           }
        //         }
        //       }
        //     }
        //   }
        //   foreach (L-Tuple l in ingredients) {
        //     LHS[ l0, l1, l2, ..., l(L-2) ] = l(L-1);
        //   }
        //
        // Note, because the .NET Tuple class only supports up to 8 components, the compiler implementation
        // here supports arrays only up to 6 dimensions.  This does not seem like a serious practical limitation.
        // However, it may be more noticeable if the forall statement supported forall assignments in its
        // body.  To support cases where tuples would need more than 8 components, .NET Tuple's would have to
        // be nested.

        // Temporary names
        var c = idGenerator.FreshNumericId("_ingredients+_tup");
        string ingredients = "_ingredients" + c;
        string tup = "_tup" + c;

        // Compute L
        int L;
        string tupleTypeArgs;
        List<Type> tupleTypeArgsList;
        if (s0.Lhs is MemberSelectExpr) {
          var lhs = (MemberSelectExpr)s0.Lhs;
          L = 2;
          tupleTypeArgs = TypeName(lhs.Obj.Type, wr, lhs.tok);
          tupleTypeArgsList = new List<Type> { lhs.Obj.Type };
        } else if (s0.Lhs is SeqSelectExpr) {
          var lhs = (SeqSelectExpr)s0.Lhs;
          L = 3;
          // note, we might as well do the BigInteger-to-int cast for array indices here, before putting things into the Tuple rather than when they are extracted from the Tuple
          tupleTypeArgs = TypeName(lhs.Seq.Type, wr, lhs.tok) + ",int";
          tupleTypeArgsList = new List<Type> { lhs.Seq.Type, null };
        } else {
          var lhs = (MultiSelectExpr)s0.Lhs;
          L = 2 + lhs.Indices.Count;
          if (8 < L) {
            Error(lhs.tok, "compiler currently does not support assignments to more-than-6-dimensional arrays in forall statements", wr);
            return;
          }
          tupleTypeArgs = TypeName(lhs.Array.Type, wr, lhs.tok);
          tupleTypeArgsList = new List<Type> { lhs.Array.Type };
          for (int i = 0; i < lhs.Indices.Count; i++) {
            // note, we might as well do the BigInteger-to-int cast for array indices here, before putting things into the Tuple rather than when they are extracted from the Tuple
            tupleTypeArgs += ",int";
            tupleTypeArgsList.Add(null);
          }
          
        }
        tupleTypeArgs += "," + TypeName(rhs.Type, wr, rhs.tok);
        tupleTypeArgsList.Add(rhs.Type);

        // declare and construct "ingredients"
        using (var wrVarInit = DeclareLocalVar(ingredients, null, null, wr)) {
          EmitEmptyTupleList(tupleTypeArgs, wrVarInit);
        }

        var wrOuter = wr;
        var n = s.BoundVars.Count;
        Contract.Assert(s.Bounds.Count == n);
        for (int i = 0; i < n; i++) {
          var bound = s.Bounds[i];
          var bv = s.BoundVars[i];
          TargetWriter collectionWriter;
          wr = CreateForeachLoop(IdName(bv), bv.Type, out collectionWriter, wr);
          CompileCollection(bound, bv, false, false, collectionWriter, s.Bounds, s.BoundVars, i);
        }

        // if (range) {
        //   ingredients.Add(new L-Tuple( LHS0(w,x,y,z), LHS1(w,x,y,z), ..., RHS(w,x,y,z) ));
        // }
        TargetWriter guardWriter;
        wr = EmitIf(out guardWriter, false, wr);
        foreach (var bv in s.BoundVars) {
          var bvConstraints = Resolver.GetImpliedTypeConstraint(bv, bv.Type);
          TrParenExpr(bvConstraints, guardWriter, false);
          guardWriter.Write(" && ");
        }
        TrParenExpr(s.Range, guardWriter, false);

        using (var wrTuple = EmitAddTupleToList(ingredients, tupleTypeArgs, wr)) {
          if (s0.Lhs is MemberSelectExpr) {
            var lhs = (MemberSelectExpr)s0.Lhs;
            TrExpr(lhs.Obj, wrTuple, false);
          } else if (s0.Lhs is SeqSelectExpr) {
            var lhs = (SeqSelectExpr)s0.Lhs;
            TrExpr(lhs.Seq, wrTuple, false);
            wrTuple.Write(", ");
            EmitExprAsInt(lhs.E0, false, wrTuple);
          } else {
            var lhs = (MultiSelectExpr)s0.Lhs;
            TrExpr(lhs.Array, wrTuple, false);
            for (int i = 0; i < lhs.Indices.Count; i++) {
              wrTuple.Write(", ");
              EmitExprAsInt(lhs.Indices[i], false, wrTuple);
            }
          }
          wrTuple.Write(", ");
          TrExpr(rhs, wrTuple, false);
        }

        //   foreach (L-Tuple l in ingredients) {
        //     LHS[ l0, l1, l2, ..., l(L-2) ] = l(L-1);
        //   }
        TargetWriter collWriter;
        wr = CreateForeachLoop(tup, null, out collWriter, wrOuter);
        collWriter.Write(ingredients);
        {
          var wTup = new TargetWriter();
          var wCoerceTup = EmitCoercionToArbitraryTuple(wTup);
          wCoerceTup.Write(tup);
          tup = wTup.ToString();
        }
        wr.Indent();
        if (s0.Lhs is MemberSelectExpr) {
          var lhs = (MemberSelectExpr)s0.Lhs;
          var wCoerced = EmitCoercionIfNecessary(from:null, to:tupleTypeArgsList[0], tok:s0.Tok, wr:wr);
          EmitTupleSelect(tup, 0, wCoerced);
          wr.Write(".{0} = ", IdMemberName(lhs));
          wCoerced = EmitCoercionIfNecessary(from:null, to:tupleTypeArgsList[1], tok:s0.Tok, wr:wr);
          EmitTupleSelect(tup, 1, wCoerced);
          EndStmt(wr);
        } else if (s0.Lhs is SeqSelectExpr) {
          var lhs = (SeqSelectExpr)s0.Lhs;
          TargetWriter wColl, wIndex, wValue;
          EmitIndexCollectionUpdate(out wColl, out wIndex, out wValue, wr, nativeIndex: true);

          var wCoerce = EmitCoercionIfNecessary(from:null, to:lhs.Seq.Type, tok:s0.Tok, wr:wColl);
          EmitTupleSelect(tup, 0, wCoerce);
          
          var wCast = EmitCoercionToNativeInt(wIndex);
          EmitTupleSelect(tup, 1, wCast);

          EmitTupleSelect(tup, 2, wValue);
          EndStmt(wr);
        } else {
          var lhs = (MultiSelectExpr)s0.Lhs;
          var wArray = new TargetWriter();
          var wCoerced = EmitCoercionIfNecessary(from:null, to:tupleTypeArgsList[0], tok:s0.Tok, wr:wArray);
          EmitTupleSelect(tup, 0, wCoerced);
          var array = wArray.ToString();
          var indices = new List<string>();          
          for (int i = 0; i < lhs.Indices.Count; i++) {
            var wIndex = new TargetWriter();
            EmitTupleSelect(tup, i+1, wIndex);
            indices.Add(wIndex.ToString());
          }
          EmitArraySelectAsLvalue(array, indices, tupleTypeArgsList[L-1], wr);
          wr.Write(" = ");
          EmitTupleSelect(tup, L-1, wr);
          EndStmt(wr);
        }

      } else if (stmt is MatchStmt) {
        MatchStmt s = (MatchStmt)stmt;
        // Type source = e;
        // if (source.is_Ctor0) {
        //   FormalType f0 = ((Dt_Ctor0)source._D).a0;
        //   ...
        //   Body0;
        // } else if (...) {
        //   ...
        // } else if (true) {
        //   ...
        // }
        if (s.Cases.Count != 0) {
          string source = idGenerator.FreshId("_source");
          DeclareLocalVar(source, s.Source.Type, s.Source.tok, s.Source, false, wr);

          int i = 0;
          var sourceType = (UserDefinedType)s.Source.Type.NormalizeExpand();
          foreach (MatchCaseStmt mc in s.Cases) {
            var w = MatchCasePrelude(source, sourceType, cce.NonNull(mc.Ctor), mc.Arguments, i, s.Cases.Count, wr);
            TrStmtList(mc.Body, w);
            i++;
          }
        }

      } else if (stmt is VarDeclStmt) {
        var s = (VarDeclStmt)stmt;
        var i = 0;
        foreach (var local in s.Locals) {
          bool hasRhs = s.Update is AssignSuchThatStmt;
          if (!hasRhs && s.Update is UpdateStmt u) {
            if (i < u.Rhss.Count && u.Rhss[i] is HavocRhs) {
              // there's no specific initial value
            } else {
              hasRhs = true;
            }
          }
          TrLocalVar(local, !hasRhs, wr);
          i++;
        }
        if (s.Update != null) {
          TrStmt(s.Update, wr);
        }

      } else if (stmt is LetStmt) {
        var s = (LetStmt)stmt;
        if (Contract.Exists(s.LHS.Vars, bv => !bv.IsGhost)) {
          TrCasePatternOpt(s.LHS, s.RHS, wr, false);
        }
      } else if (stmt is ModifyStmt) {
        var s = (ModifyStmt)stmt;
        if (s.Body != null) {
          TrStmt(s.Body, wr);
        } else if (DafnyOptions.O.ForbidNondeterminism) {
          Error(s.Tok, "modify statement without a body forbidden by /definiteAssignment:3 option", wr);
        }

      } else {
        Contract.Assert(false); throw new cce.UnreachableException();  // unexpected statement
      }
    }

    void CompileCollection(ComprehensionExpr.BoundedPool bound, IVariable bv, bool inLetExprBody, bool includeDuplicates,
        TargetWriter collectionWriter,
        List<ComprehensionExpr.BoundedPool>/*?*/ bounds = null, List<BoundVar>/*?*/ boundVars = null, int boundIndex = 0) {
      Contract.Requires(bound != null);
      Contract.Requires(bounds == null || (boundVars != null && bounds.Count == boundVars.Count && 0 <= boundIndex && boundIndex < bounds.Count));
      Contract.Requires(collectionWriter != null);
      var propertySuffix = SupportsProperties ? "" : "()";

      if (bound is ComprehensionExpr.BoolBoundedPool) {
        collectionWriter.Write("{0}.AllBooleans()", GetHelperModuleName());
      } else if (bound is ComprehensionExpr.CharBoundedPool) {
        collectionWriter.Write("{0}.AllChars()", GetHelperModuleName());
      } else if (bound is ComprehensionExpr.IntBoundedPool) {
        var b = (ComprehensionExpr.IntBoundedPool)bound;
        TargetWriter wLo, wHi;
        EmitIntegerRange(bv.Type, out wLo, out wHi, collectionWriter);
        if (b.LowerBound == null) {
          EmitNull(bv.Type, wLo);
        } else if (bounds != null) {
          var low = SubstituteBound(b, bounds, boundVars, boundIndex, true);
          TrExpr(low, wLo, inLetExprBody);
        } else {
          TrExpr(b.LowerBound, wLo, inLetExprBody);
        }
        if (b.UpperBound == null) {
          EmitNull(bv.Type, wHi);
        } else if (bounds != null) {
          var high = SubstituteBound(b, bounds, boundVars, boundIndex, false);
          TrExpr(high, wHi, inLetExprBody);
        } else {
          TrExpr(b.UpperBound, wHi, inLetExprBody);
        }
      } else if (bound is AssignSuchThatStmt.WiggleWaggleBound) {
        collectionWriter.Write("{0}.AllIntegers()", GetHelperModuleName());
      } else if (bound is ComprehensionExpr.ExactBoundedPool) {
        var b = (ComprehensionExpr.ExactBoundedPool)bound;
        EmitSingleValueGenerator(b.E, inLetExprBody, TypeName(b.E.Type, collectionWriter, b.E.tok), collectionWriter);
      } else if (bound is ComprehensionExpr.SetBoundedPool) {
        var b = (ComprehensionExpr.SetBoundedPool)bound;
        TrParenExpr(b.Set, collectionWriter, inLetExprBody);
        collectionWriter.Write(".Elements" + propertySuffix);
      } else if (bound is ComprehensionExpr.MultiSetBoundedPool) {
        var b = (ComprehensionExpr.MultiSetBoundedPool)bound;
        TrParenExpr(b.MultiSet, collectionWriter, inLetExprBody);
        collectionWriter.Write((includeDuplicates ? ".Elements" : ".UniqueElements") + propertySuffix);
      } else if (bound is ComprehensionExpr.SubSetBoundedPool) {
        var b = (ComprehensionExpr.SubSetBoundedPool)bound;
        TrParenExpr(b.UpperBound, collectionWriter, inLetExprBody);
        collectionWriter.Write(".AllSubsets" + propertySuffix);
      } else if (bound is ComprehensionExpr.MapBoundedPool) {
        var b = (ComprehensionExpr.MapBoundedPool)bound;
        TrParenExpr(b.Map, collectionWriter, inLetExprBody);
        collectionWriter.Write(".Keys{0}.Elements{0}", propertySuffix);
      } else if (bound is ComprehensionExpr.SeqBoundedPool) {
        var b = (ComprehensionExpr.SeqBoundedPool)bound;
        TrParenExpr(b.Seq, collectionWriter, inLetExprBody);
        collectionWriter.Write((includeDuplicates ? ".Elements" : ".UniqueElements") + propertySuffix);
      } else if (bound is ComprehensionExpr.DatatypeBoundedPool) {
        var b = (ComprehensionExpr.DatatypeBoundedPool)bound;
        collectionWriter.Write("{0}.AllSingletonConstructors{1}", TypeName_Companion(bv.Type, collectionWriter, bv.Tok, null), propertySuffix);
      } else {
        Contract.Assert(false); throw new cce.UnreachableException();  // unexpected BoundedPool type
      }
    }

    private Expression SubstituteBound(ComprehensionExpr.IntBoundedPool b, List<ComprehensionExpr.BoundedPool> bounds, List<BoundVar> boundVars, int index, bool lowBound) {
      Contract.Requires(b != null);
      Contract.Requires((lowBound ? b.LowerBound : b.UpperBound) != null);
      Contract.Requires(bounds != null);
      Contract.Requires(boundVars != null);
      Contract.Requires(bounds.Count == boundVars.Count);
      Contract.Requires(0 <= index && index < boundVars.Count);
      // if the outer bound is dependent on the inner boundvar, we need to
      // substitute the inner boundvar with its bound.
      var bnd = lowBound ? b.LowerBound : b.UpperBound;
      var sm = new Dictionary<IVariable, Expression>();
      for (int i = index+1; i < boundVars.Count; i++) {
        var bound = bounds[i];
        if (bound is ComprehensionExpr.IntBoundedPool) {
          var ib = (ComprehensionExpr.IntBoundedPool)bound;
          var bv = boundVars[i];
          sm[bv] = lowBound ? ib.LowerBound : ib.UpperBound;
        }
      }
      var su = new Translator.Substituter(null, sm, new Dictionary<TypeParameter, Type>());
      return su.Substitute(bnd);
    }

    private void IntroduceAndAssignBoundVars(ExistsExpr exists, TargetWriter wr) {
      Contract.Requires(exists != null);
      Contract.Assume(exists.Bounds != null);  // follows from successful resolution
      Contract.Assert(exists.Range == null);  // follows from invariant of class IfStmt
      foreach (var bv in exists.BoundVars) {
        TrLocalVar(bv, false, wr);
      }
      var ivars = exists.BoundVars.ConvertAll(bv => (IVariable)bv);
      TrAssignSuchThat(ivars, exists.Term, exists.Bounds, exists.tok.line, wr, false);
    }

    private void TrAssignSuchThat(List<IVariable> lhss, Expression constraint, List<ComprehensionExpr.BoundedPool> bounds, int debuginfoLine, TargetWriter wr, bool inLetExprBody) {
      Contract.Requires(lhss != null);
      Contract.Requires(constraint != null);
      Contract.Requires(bounds != null);
      // For "i,j,k,l :| R(i,j,k,l);", emit something like:
      //
      // for (BigInteger iterLimit = 5; ; iterLimit *= 2) {
      //   var il$0 = iterLimit;
      //   foreach (L l' in sq.Elements) { l = l';
      //     if (il$0 == 0) { break; }  il$0--;
      //     var il$1 = iterLimit;
      //     foreach (K k' in st.Elements) { k = k';
      //       if (il$1 == 0) { break; }  il$1--;
      //       var il$2 = iterLimit;
      //       j = Lo;
      //       for (;; j++) {
      //         if (il$2 == 0) { break; }  il$2--;
      //         foreach (bool i' in Helper.AllBooleans) { i = i';
      //           if (R(i,j,k,l)) {
      //             goto ASSIGN_SUCH_THAT_<id>;
      //           }
      //         }
      //       }
      //     }
      //   }
      // }
      // throw new Exception("assign-such-that search produced no value"); // a verified program never gets here; however, we need this "throw" to please the C# compiler
      // ASSIGN_SUCH_THAT_<id>: ;
      //
      // where the iterLimit loop can be omitted if lhss.Count == 1 or if all bounds are finite.  Further optimizations could be done, but
      // are omitted for now.
      //
      var n = lhss.Count;
      Contract.Assert(bounds.Count == n);
      var c = idGenerator.FreshNumericId("_ASSIGN_SUCH_THAT_+_iterLimit_");
      var doneLabel = "_ASSIGN_SUCH_THAT_" + c;
      var iterLimit = "_iterLimit_" + c;

      bool needIterLimit = lhss.Count != 1 && bounds.Exists(bnd => (bnd.Virtues & ComprehensionExpr.BoundedPool.PoolVirtues.Finite) == 0);
      wr = CreateLabeledCode(doneLabel, wr);
      var wrOuter = wr;
      if (needIterLimit) {
        wr = CreateDoublingForLoop(iterLimit, 5, wr);
      }

      for (int i = 0; i < n; i++) {
        var bound = bounds[i];
        Contract.Assert((bound.Virtues & ComprehensionExpr.BoundedPool.PoolVirtues.Enumerable) != 0);  // if we have got this far, it must be an enumerable bound
        var bv = lhss[i];
        if (needIterLimit) {
          DeclareLocalVar(string.Format("{0}_{1}", iterLimit, i), null, null, false, iterLimit, wr);
        }
        var tmpVar = idGenerator.FreshId("_assign_such_that_");
        TargetWriter collectionWriter;
        wr = CreateForeachLoop(tmpVar, bv.Type, out collectionWriter, wr, IdName(bv));
        CompileCollection(bound, bv, inLetExprBody, true, collectionWriter);
        if (needIterLimit) {
          var varName = string.Format("{0}_{1}", iterLimit, i);
          TargetWriter isZeroWriter;
          var thn = EmitIf(out isZeroWriter, false, wr);
          EmitIsZero(varName, isZeroWriter);
          EmitBreak(null, thn);
          EmitDecrementVar(varName, wr);
        }
      }
      TargetWriter guardWriter;
      var wBody = EmitIf(out guardWriter, false, wr);
      TrExpr(constraint, guardWriter, inLetExprBody);
      EmitBreak(doneLabel, wBody);

      EmitAbsurd(string.Format("assign-such-that search produced no value (line {0})", debuginfoLine), wrOuter);
    }

    string CreateLvalue(Expression lhs, TargetWriter wr) {
      Contract.Requires(lhs != null);
      Contract.Requires(wr != null);

      lhs = lhs.Resolved;
      if (lhs is IdentifierExpr) {
        var ll = (IdentifierExpr)lhs;
        return IdName(ll.Var);
      } else if (lhs is MemberSelectExpr) {
        var ll = (MemberSelectExpr)lhs;
        Contract.Assert(!ll.Member.IsInstanceIndependentConstant);  // instance-independent const's don't have assignment statements
        var obj = StabilizeExpr(ll.Obj, "_obj", wr);
        var sw = new TargetWriter();
        var w = EmitMemberSelect(ll.Member, true, lhs.Type, sw);
        w.Write(obj);
        return sw.ToString();
      } else if (lhs is SeqSelectExpr) {
        var ll = (SeqSelectExpr)lhs;
        var arr = StabilizeExpr(ll.Seq, "_arr", wr);
        var index = StabilizeExpr(ll.E0, "_index", wr);
        var sw = new TargetWriter();
        EmitArraySelectAsLvalue(arr, new List<string>() { index }, ll.Type, sw);
        return sw.ToString();
      } else {
        var ll = (MultiSelectExpr)lhs;
        string arr = StabilizeExpr(ll.Array, "_arr", wr);
        var indices = new List<string>();
        int i = 0;
        foreach (var idx in ll.Indices) {
          indices.Add(StabilizeExpr(idx, "_index" + i + "_", wr));
          i++;
        }
        var sw = new TargetWriter();
        EmitArraySelectAsLvalue(arr, indices, ll.Type, sw);
        return sw.ToString();
      }
    }

    /// <summary>
    /// If the given expression's value is stable, translate it and return the
    /// string form.  Otherwise, output code to evaluate the expression, then
    /// return a fresh variable bound to its value.
    /// </summary>
    /// <param name="e">An expression to evaluate</param>
    /// <param name="prefix">The prefix to give the fresh variable, if
    /// needed.</param>
    /// <param name="wr">A writer in a position to write statements
    /// evaluating the expression</param>
    /// <returns>A string giving the translated value as a stable
    /// expression</returns>
    /// <seealso cref="IsStableExpr"/>
    private string StabilizeExpr(Expression e, string prefix, TargetWriter wr) {
      if (IsStableExpr(e)) {
        var sw = new TargetWriter();
        TrParenExpr(e, sw, false);
        return sw.ToString();
      } else {
        var v = idGenerator.FreshId(prefix);
        DeclareLocalVar(v, null, null, e, false, wr);
        return v;
      }
    }

    /// <summary>
    /// Returns whether the given expression is <em>stable</em>, that is,
    /// whether its value is fixed over the course of the evaluation of an
    /// expression.  Note that anything that could be altered by a function call
    /// (say, the value of a non-constant field) is unstable.
    /// </summary>
    private bool IsStableExpr(Expression e) {
      if (e is IdentifierExpr || e is ThisExpr || e is LiteralExpr) {
        return true;
      } else if (e is MemberSelectExpr mse) {
        if (!IsStableExpr(mse.Obj)) {
          return false;
        }
        var member = mse.Member;
        if (member is ConstantField) {
          return true;
        } else if (member is SpecialField sf) {
          switch (sf.SpecialId) {
            case SpecialField.ID.ArrayLength:
            case SpecialField.ID.ArrayLengthInt:
            case SpecialField.ID.Floor:
            case SpecialField.ID.IsLimit:
            case SpecialField.ID.IsSucc:
            case SpecialField.ID.Offset:
            case SpecialField.ID.IsNat:
            case SpecialField.ID.Keys:
            case SpecialField.ID.Values:
            case SpecialField.ID.Items:
              return true;
            default:
              return false;
          }
        } else {
          return false;
        }
      } else if (e is ConcreteSyntaxExpression cse) {
        return IsStableExpr(cse.ResolvedExpression);
      } else {
        return false;
      }
    }

    /// <summary>
    /// Translate the right-hand side of an assignment.
    /// </summary>
    /// <param name="rhs">The RHS to translate</param>
    /// <param name="wr">The writer at the position for the translated RHS</param>
    /// <param name="wStmts">A writer at an earlier position where extra statements may be written</param>
    void TrRhs(AssignmentRhs rhs, TargetWriter wr, TargetWriter wStmts) {
      Contract.Requires(!(rhs is HavocRhs));
      Contract.Requires(wr != null);

      var tRhs = rhs as TypeRhs;

      if (tRhs == null) {
        var eRhs = (ExprRhs)rhs;  // it's not HavocRhs (by the precondition) or TypeRhs (by the "if" test), so it's gotta be ExprRhs
        TrExpr(eRhs.Expr, wr, false);
      } else {
        var nw = idGenerator.FreshId("_nw");
        var wRhs = DeclareLocalVar(nw, null, null, wStmts);
        TrTypeRhs(tRhs, wRhs);

        // Proceed with initialization
        if (tRhs.InitCall != null) {
          string q, n;
          if (tRhs.InitCall.Method is Constructor && tRhs.InitCall.Method.IsExtern(out q, out n)) {
            // initialization was done at the time of allocation
          } else {
            wStmts.Write(TrCallStmt(tRhs.InitCall, nw, wStmts.IndentLevel).ToString());
          }
        } else if (tRhs.ElementInit != null) {
          // Compute the array-initializing function once and for all (as required by the language definition)
          string f = idGenerator.FreshId("_arrayinit");
          DeclareLocalVar(f, null, null, tRhs.ElementInit, false, wStmts);
          // Build a loop nest that will call the initializer for all indicies
          var indices = Translator.Map(Enumerable.Range(0, tRhs.ArrayDimensions.Count), ii => idGenerator.FreshId("_arrayinit_" + ii));
          var w = wStmts;
          for (var d = 0; d < tRhs.ArrayDimensions.Count; d++) {
            string len, p0, p1;
            GetSpecialFieldInfo(SpecialField.ID.ArrayLengthInt, tRhs.ArrayDimensions.Count == 1 ? null : (object)d, out len, out p0, out p1);
            var bound = string.Format("{0}.{1}", nw, len);
            w = CreateForLoop(indices[d], bound, w);
          }
          w.Indent();
          var eltRhs = string.Format("{0}({1})", f, Util.Comma(indices, ArrayIndexToInt));
          var wArray = EmitArrayUpdate(indices, eltRhs, tRhs.ElementInit.Type, w);
          wArray.Write(nw);
          EndStmt(w);
        } else if (tRhs.InitDisplay != null) {
          var ii = 0;
          foreach (var v in tRhs.InitDisplay) {
            wStmts.Indent();
            var wArray = EmitArrayUpdate(new List<string> { ii.ToString() }, v, wStmts);
            wArray.Write(nw);
            EndStmt(wStmts);
            ii++;
          }
        }

        // Assign to the final LHS
        wr.Write(nw);
      }
    }

    // to do: Make Type an abstract property of AssignmentRhs.  Unfortunately, this would first require convincing Microsoft that it makes sense for a base class to have a property that's only settable in some subclasses.  Until then, let it be known that Java's "properties" (i.e. getter/setter pairs) are more powerful >:-)
    private static Type TypeOfRhs(AssignmentRhs rhs) {
      if (rhs is TypeRhs tRhs) {
        return tRhs.Type;
      } else if (rhs is ExprRhs eRhs) {
        return eRhs.Expr.Type;
      } else {
        return null;
      }
    }

    TextWriter TrCallStmt(CallStmt s, string receiverReplacement, int indent) {
      Contract.Requires(s != null);
      Contract.Assert(s.Method != null);  // follows from the fact that stmt has been successfully resolved

      var wr = new TargetWriter(indent);
      if (s.Method == enclosingMethod && enclosingMethod.IsTailRecursive) {
        // compile call as tail-recursive

        // assign the actual in-parameters to temporary variables
        var inTmps = new List<string>();
        if (receiverReplacement != null) {
          // TODO:  What to do here?  When does this happen, what does it mean?
        } else if (!s.Method.IsStatic) {

          string inTmp = idGenerator.FreshId("_in");
          inTmps.Add(inTmp);
          DeclareLocalVar(inTmp, null, null, s.Receiver, false, wr);
        }
        for (int i = 0; i < s.Method.Ins.Count; i++) {
          Formal p = s.Method.Ins[i];
          if (!p.IsGhost) {
            string inTmp = idGenerator.FreshId("_in");
            inTmps.Add(inTmp);
            DeclareLocalVar(inTmp, null, null, s.Args[i], false, wr);
          }
        }
        // Now, assign to the formals
        int n = 0;
        if (!s.Method.IsStatic) {
          wr.Indent();
          wr.Write("_this = {0}", inTmps[n]);
          EndStmt(wr);
          n++;
        }
        foreach (var p in s.Method.Ins) {
          if (!p.IsGhost) {
            wr.Indent();
            wr.WriteLine("{0} = {1}", p.CompileName, inTmps[n]);
            EndStmt(wr);
            n++;
          }
        }
        Contract.Assert(n == inTmps.Count);
        // finally, the jump back to the head of the method
        EmitJumpToTailCallStart(wr);

      } else {
        // compile call as a regular call

        var lvalues = new List<string/*?*/>();  // contains an entry for each non-ghost formal out-parameter, but the entry is null if the actual out-parameter is ghost
        Contract.Assert(s.Lhs.Count == s.Method.Outs.Count);
        for (int i = 0; i < s.Method.Outs.Count; i++) {
          Formal p = s.Method.Outs[i];
          if (!p.IsGhost) {
            var lhs = s.Lhs[i].Resolved;
            if (lhs is IdentifierExpr lhsIE && lhsIE.Var.IsGhost) {
              lvalues.Add(null);
            } else if (lhs is MemberSelectExpr lhsMSE && lhsMSE.Member.IsGhost) {
              lvalues.Add(null);
            } else {
              lvalues.Add(CreateLvalue(s.Lhs[i], wr));
            }
          }
        }
        var outTmps = new List<string>();  // contains a name for each non-ghost formal out-parameter
        for (int i = 0; i < s.Method.Outs.Count; i++) {
          Formal p = s.Method.Outs[i];
          if (!p.IsGhost) {
            string target = idGenerator.FreshId("_out");
            outTmps.Add(target);
            Type type;
            if (!NeedsCastFromTypeParameter) {
              type = s.Lhs[i].Type;
            } else {
              //
              // The type of the parameter will differ from the LHS type in a
              // situation like this:
              //
              //   method Gimmie<R(0)>() returns (r: R) { }
              //
              //   var a : int := Gimmie();
              //
              // The method Gimme will be compiled down to Go (or JavaScript)
              // as a function which returns any value (some details omitted):
              //   
              //   func Gimmie(ty _dafny.Type) interface{} {
              //     return ty.Default()
              //   }
              // 
              // If we naively translate, we get a type error:
              //
              //   var lhs int = Gimmie(_dafny.IntType) // error!
              //
              // Fortunately, we have the information at hand to do better.  The
              // LHS does have the actual final type (int in this case), and the
              // out parameter on the method tells us the compiled type
              // returned.  Therefore what we want to do is this:
              //
              //   var lhs dafny.Int
              //   var _out interface{}
              //
              //   _out = Gimmie(dafny.IntType)
              //
              //   lhs = _out.(int) // type cast
              //
              // All we have to do now is declare the out variable with the type
              // from the parameter; later we'll do the type cast.
              //
              // None of this is necessary if the language supports parametric
              // functions to begin with (C#) or has dynamic typing so none of
              // this comes up (JavaScript), so we only do this if
              // NeedsCastFromTypeParameter is on.
              type = p.Type;
            }
            if (s.Method.IsExtern(out _, out _)) {
              type = NativeForm(type);
            }
            DeclareLocalVar(target, type, s.Lhs[i].tok, false, null, wr);
          }
        }
        Contract.Assert(lvalues.Count == outTmps.Count);

        bool returnStyleOuts = UseReturnStyleOuts(s.Method, outTmps.Count);
        var returnStyleOutCollector = outTmps.Count > 0 && returnStyleOuts && !SupportsMultipleReturns ? idGenerator.FreshId("_outcollector") : null;
        if (returnStyleOutCollector != null) {
          wr.Indent();
          DeclareOutCollector(returnStyleOutCollector, wr);
        } else if (outTmps.Count > 0 && returnStyleOuts && SupportsMultipleReturns) {
          wr.Indent();
          wr.Write("{0} = ", Util.Comma(outTmps));
        } else {
          wr.Indent();
        }
        if (receiverReplacement != null) {
          wr.Write(IdProtect(receiverReplacement));
          wr.Write(".{0}", IdName(s.Method));
        } else if (!s.Method.IsStatic) {
          TrParenExpr(s.Receiver, wr, false);
          wr.Write(".{0}", IdName(s.Method));
        } else {
          string qual, compileName;
          if (s.Method.IsExtern(out qual, out compileName) && qual != null) {
            wr.Write("{0}.{1}", qual, compileName);
          } else {
            wr.Write(TypeName_Companion(s.Receiver.Type, wr, s.Tok, s.Method));
            wr.Write(".{0}", IdName(s.Method));
          }
        }
        List<Type> typeArgs;
        if (s.Method.TypeArgs.Count != 0) {
          var typeSubst = s.MethodSelect.TypeArgumentSubstitutions();
          typeArgs = s.Method.TypeArgs.ConvertAll(ta => typeSubst[ta]);
        } else {
          typeArgs = new List<Type>();
        }
        EmitActualTypeArgs(typeArgs, s.Tok, wr);
        wr.Write("(");
        var nRTDs = EmitRuntimeTypeDescriptorsActuals(typeArgs, s.Method.TypeArgs, s.Tok, false, wr);
        string sep = nRTDs == 0 ? "" : ", ";
        for (int i = 0; i < s.Method.Ins.Count; i++) {
          Formal p = s.Method.Ins[i];
          if (!p.IsGhost) {
            wr.Write(sep);
            // Order of coercions is important here: EmitCoercionToNativeForm may coerce into a type we're unaware of, so it *has* to be second
            var w = EmitCoercionIfNecessary(s.Args[i].Type, s.Method.Ins[i].Type, s.Tok, wr);
            if (s.Method.IsExtern(out _, out _)) {
              w = EmitCoercionToNativeForm(s.Method.Ins[i].Type, s.Tok, w);
            }
            TrExpr(s.Args[i], w, false);
            sep = ", ";
          }
        }

        if (returnStyleOutCollector == null && !SupportsMultipleReturns) {
          foreach (var outTmp in outTmps) {
            wr.Write(sep);
            EmitActualOutArg(outTmp, wr);
            sep = ", ";
          }
        }
        wr.Write(')');
        EndStmt(wr);
        if (returnStyleOutCollector != null) {
          EmitOutParameterSplits(returnStyleOutCollector, outTmps, wr);
        }

        // assign to the actual LHSs
        for (int j = 0, l = 0; j < s.Method.Outs.Count; j++) {
          var p = s.Method.Outs[j];
          if (!p.IsGhost) {
            var lvalue = lvalues[l];
            if (lvalue != null) {
              // The type information here takes care both of implicit upcasts and
              // implicit downcasts from type parameters (see above).
              TargetWriter wLhs, wRhs;
              EmitAssignment(out wLhs, s.Lhs[j].Type, out wRhs, p.Type, wr);
              wLhs.Write(lvalue);
              if (s.Method.IsExtern(out _, out _)) {
                wRhs = EmitCoercionFromNativeForm(p.Type, s.Tok, wRhs);
              }
              wRhs.Write(outTmps[l]);
              // Coercion from the out type to the LHS type is the responsibility
              // of the EmitAssignment above
            }
            l++;
          }
        }
      }
      return wr;
    }

    /// <summary>
    /// Before calling TrAssignmentRhs(rhs), the caller must have spilled the let variables declared in "tp".
    /// </summary>
    void TrTypeRhs(TypeRhs tp, TargetWriter wr) {
      Contract.Requires(tp != null);
      Contract.Requires(wr != null);

      if (tp.ArrayDimensions == null) {
        var initCall = tp.InitCall != null && tp.InitCall.Method is Constructor ? tp.InitCall : null;
        EmitNew(tp.EType, tp.Tok, initCall, wr);
      } else if (tp.ElementInit != null || tp.InitDisplay != null) {
        EmitNewArray(tp.EType, tp.Tok, tp.ArrayDimensions, false, wr);
      } else {
        // If an initializer is not known, the only way the verifier would have allowed this allocation
        // is if the requested size is 0.
        EmitNewArray(tp.EType, tp.Tok, tp.ArrayDimensions, InitializerIsKnown(tp.EType), wr);
      }
    }

    void TrStmtList(List<Statement> stmts, TargetWriter writer) {
      Contract.Requires(cce.NonNullElements(stmts));
      Contract.Requires(writer != null);
      foreach (Statement ss in stmts) {
        // label:        // if any
        //   <prelude>   // filled via copyInstrWriters -- copies out-parameters used in letexpr to local variables
        //   ss          // translation of ss has side effect of filling the top copyInstrWriters
        var w = writer;
        if (ss.Labels != null) {
          w = CreateLabeledCode(ss.Labels.Data.AssignUniqueId(idGenerator), w);
        }
        var prelude = new TargetWriter(w.IndentLevel);
        w.Append(prelude);
        copyInstrWriters.Push(prelude);
        TrStmt(ss, w);
        copyInstrWriters.Pop();
      }
    }

    void TrLocalVar(IVariable v, bool alwaysInitialize, TargetWriter wr) {
      Contract.Requires(v != null);
      if (v.IsGhost) {
        // only emit non-ghosts (we get here only for local variables introduced implicitly by call statements)
        return;
      }
      DeclareLocalVar(IdName(v), v.Type, v.Tok, false, alwaysInitialize ? DefaultValue(v.Type, wr, v.Tok) : null, wr);
    }

    TargetWriter MatchCasePrelude(string source, UserDefinedType sourceType, DatatypeCtor ctor, List<BoundVar> arguments, int caseIndex, int caseCount, TargetWriter wr) {
      Contract.Requires(source != null);
      Contract.Requires(sourceType != null);
      Contract.Requires(ctor != null);
      Contract.Requires(cce.NonNullElements(arguments));
      Contract.Requires(0 <= caseIndex && caseIndex < caseCount);
      // if (source.is_Ctor0) {
      //   FormalType f0 = ((Dt_Ctor0)source._D).a0;
      //   ...
      var lastCase = caseIndex == caseCount - 1;
      TargetWriter w;
      if (lastCase) {
        // Need to avoid if (true) because some languages (Go, someday Java)
        // pretend that an if (true) isn't a certainty, leading to a complaint
        // about a missing return statement
        w = wr.NewBlock("");
      } else {
        TargetWriter guardWriter;
        w = EmitIf(out guardWriter, !lastCase, wr);
        EmitConstructorCheck(source, ctor, guardWriter);
      }

      int k = 0;  // number of processed non-ghost arguments
      for (int m = 0; m < ctor.Formals.Count; m++) {
        Formal arg = ctor.Formals[m];
        if (!arg.IsGhost) {
          BoundVar bv = arguments[m];
          // FormalType f0 = ((Dt_Ctor0)source._D).a0;
          var sw = DeclareLocalVar(IdName(bv), bv.Type, bv.Tok, w);
          EmitDestructor(source, arg, k, ctor, sourceType.TypeArgs, bv.Type, sw);
          k++;
        }
      }
      return w;
    }

    // ----- Expression ---------------------------------------------------------------------------

    /// <summary>
    /// Before calling TrParenExpr(expr), the caller must have spilled the let variables declared in "expr".
    /// </summary>
    protected void TrParenExpr(string prefix, Expression expr, TargetWriter wr, bool inLetExprBody) {
      Contract.Requires(prefix != null);
      Contract.Requires(expr != null);
      Contract.Requires(wr != null);
      wr.Write(prefix);
      TrParenExpr(expr, wr, inLetExprBody);
    }

    /// <summary>
    /// Before calling TrParenExpr(expr), the caller must have spilled the let variables declared in "expr".
    /// </summary>
    protected void TrParenExpr(Expression expr, TargetWriter wr, bool inLetExprBody) {
      Contract.Requires(expr != null);
      Contract.Requires(wr != null);
      wr.Write("(");
      TrExpr(expr, wr, inLetExprBody);
      wr.Write(")");
    }

    /// <summary>
    /// Before calling TrExprList(exprs), the caller must have spilled the let variables declared in expressions in "exprs".
    /// </summary>
    protected void TrExprList(List<Expression> exprs, TargetWriter wr, bool inLetExprBody, Type/*?*/ type = null) {
      Contract.Requires(cce.NonNullElements(exprs));
      wr.Write("(");
      string sep = "";
      foreach (Expression e in exprs) {
        wr.Write(sep);
        TargetWriter w;
        if (type != null) {
          w = wr.Fork();
          w = EmitCoercionIfNecessary(e.Type, type, e.tok, w);
        } else {
          w = wr;
        }
        TrExpr(e, w, inLetExprBody);
        sep = ", ";
      }
      wr.Write(")");
    }

    /// <summary>
    /// Before calling TrExpr(expr), the caller must have spilled the let variables declared in "expr".
    /// </summary>
    protected void TrExpr(Expression expr, TargetWriter wr, bool inLetExprBody) {
      Contract.Requires(expr != null);
      Contract.Requires(wr != null);

      if (expr is LiteralExpr) {
        LiteralExpr e = (LiteralExpr)expr;
        EmitLiteralExpr(wr, e);

      } else if (expr is ThisExpr) {
        EmitThis(wr);

      } else if (expr is IdentifierExpr) {
        var e = (IdentifierExpr)expr;
        if (e.Var is Formal && inLetExprBody && !((Formal)e.Var).InParam) {
          // out param in letExpr body, need to copy it to a temp since
          // letExpr body is translated to an anonymous function that doesn't
          // allow out parameters
          var name = string.Format("_pat_let_tv{0}", GetUniqueAstNumber(e));
          wr.Write(name);
          DeclareLocalVar(name, null, null, false, IdName(e.Var), copyInstrWriters.Peek());
        } else {
          wr.Write(IdName(e.Var));
        }
      } else if (expr is SetDisplayExpr) {
        var e = (SetDisplayExpr)expr;
        EmitCollectionDisplay(e.Type.AsSetType, e.tok, e.Elements, inLetExprBody, wr);

      } else if (expr is MultiSetDisplayExpr) {
        var e = (MultiSetDisplayExpr)expr;
        EmitCollectionDisplay(e.Type.AsMultiSetType, e.tok, e.Elements, inLetExprBody, wr);

      } else if (expr is SeqDisplayExpr) {
        var e = (SeqDisplayExpr)expr;
        EmitCollectionDisplay(e.Type.AsSeqType, e.tok, e.Elements, inLetExprBody, wr);

      } else if (expr is MapDisplayExpr) {
        var e = (MapDisplayExpr)expr;
        EmitMapDisplay(e.Type.AsMapType, e.tok, e.Elements, inLetExprBody, wr);

      } else if (expr is MemberSelectExpr) {
        MemberSelectExpr e = (MemberSelectExpr)expr;
        SpecialField sf = e.Member as SpecialField;
        if (sf != null) {
          string compiledName, preStr, postStr;
          GetSpecialFieldInfo(sf.SpecialId, sf.IdParam, out compiledName, out preStr, out postStr);
          wr.Write(preStr);
          var w = EmitMemberSelect(sf, false, expr.Type, wr);
          if (sf.IsStatic) {
            w.Write(TypeName_Companion(e.Obj.Type, wr, e.tok, sf));
          } else {
            TrParenExpr(e.Obj, w, inLetExprBody);
          }
          wr.Write(postStr);
        } else {
          var w = EmitMemberSelect(e.Member, false, expr.Type, wr);
          TrExpr(e.Obj, w, inLetExprBody);
        }

      } else if (expr is SeqSelectExpr) {
        SeqSelectExpr e = (SeqSelectExpr)expr;
        Contract.Assert(e.Seq.Type != null);
        if (e.Seq.Type.IsArrayType) {
          if (e.SelectOne) {
            Contract.Assert(e.E0 != null && e.E1 == null);
            var w = EmitArraySelect(new List<Expression>() { e.E0 }, e.Type, inLetExprBody, wr);
            TrParenExpr(e.Seq, w, inLetExprBody);
          } else {
            EmitSeqSelectRange(e.Seq, e.E0, e.E1, true, inLetExprBody, wr);
          }
        } else if (e.SelectOne) {
          Contract.Assert(e.E0 != null && e.E1 == null);
          EmitIndexCollectionSelect(e.Seq, e.E0, inLetExprBody, wr);
        } else {
          EmitSeqSelectRange(e.Seq, e.E0, e.E1, false, inLetExprBody, wr);
        }
      } else if (expr is MultiSetFormingExpr) {
        var e = (MultiSetFormingExpr)expr;
        EmitMultiSetFormingExpr(e, inLetExprBody, wr);
      } else if (expr is MultiSelectExpr) {
        MultiSelectExpr e = (MultiSelectExpr)expr;
        var w = EmitArraySelect(e.Indices, e.Type, inLetExprBody, wr);
        TrParenExpr(e.Array, w, inLetExprBody);
        
      } else if (expr is SeqUpdateExpr) {
        SeqUpdateExpr e = (SeqUpdateExpr)expr;
        if (e.ResolvedUpdateExpr != null) {
          TrExpr(e.ResolvedUpdateExpr, wr, inLetExprBody);
        } else {
          EmitIndexCollectionUpdate(e.Seq, e.Index, e.Value, inLetExprBody, wr);
        }

      } else if (expr is FunctionCallExpr) {
        FunctionCallExpr e = (FunctionCallExpr)expr;
        if (e.Function is SpecialFunction) {
          CompileSpecialFunctionCallExpr(e, wr, inLetExprBody, TrExpr);
        } else {
          CompileFunctionCallExpr(e, wr, inLetExprBody, TrExpr);
        }

      } else if (expr is ApplyExpr) {
        var e = expr as ApplyExpr;
        EmitApplyExpr(e.Function.Type, e.tok, e.Function, e.Args, inLetExprBody, wr);

      } else if (expr is DatatypeValue) {
        var dtv = (DatatypeValue)expr;
        Contract.Assert(dtv.Ctor != null);  // since dtv has been successfully resolved

        var wrArgumentList = new TargetWriter();
        string sep = "";
        for (int i = 0; i < dtv.Arguments.Count; i++) {
          var formal = dtv.Ctor.Formals[i];
          if (!formal.IsGhost) {
            wrArgumentList.Write(sep);
            var w = EmitCoercionIfNecessary(from:dtv.Arguments[i].Type, to:dtv.Ctor.Formals[i].Type, tok:dtv.tok, wr:wrArgumentList);
            TrExpr(dtv.Arguments[i], w, inLetExprBody);
            sep = ", ";
          }
        }
        EmitDatatypeValue(dtv, wrArgumentList.ToString(), wr);

      } else if (expr is OldExpr) {
        Contract.Assert(false); throw new cce.UnreachableException();  // 'old' is always a ghost

      } else if (expr is UnaryOpExpr) {
        var e = (UnaryOpExpr)expr;
        switch (e.Op) {
          case UnaryOpExpr.Opcode.Not:
            if (e.Type.IsBitVectorType) {
              var bvType = e.Type.AsBitVectorType;
              var wrTruncOperand = EmitBitvectorTruncation(bvType, false, wr);
              EmitUnaryExpr(ResolvedUnaryOp.BitwiseNot, e.E, inLetExprBody, wrTruncOperand);
            } else {
              EmitUnaryExpr(ResolvedUnaryOp.BoolNot, e.E, inLetExprBody, wr);
            }
            break;
          case UnaryOpExpr.Opcode.Cardinality:
            EmitUnaryExpr(ResolvedUnaryOp.Cardinality, e.E, inLetExprBody, wr);
            break;
          default:
            Contract.Assert(false); throw new cce.UnreachableException();  // unexpected unary expression
        }

      } else if (expr is ConversionExpr) {
        var e = (ConversionExpr)expr;
        EmitConversionExpr(e, inLetExprBody, wr);

      } else if (expr is BinaryExpr) {
        var e = (BinaryExpr)expr;
        string opString, preOpString, postOpString, callString, staticCallString;
        bool reverseArguments, truncateResult, convertE1_to_int;
        CompileBinOp(e.ResolvedOp, e.E0, e.E1, e.tok, expr.Type,
          out opString,
          out preOpString,
          out postOpString,
          out callString,
          out staticCallString,
          out reverseArguments,
          out truncateResult,
          out convertE1_to_int,
          wr);

        if (truncateResult && e.Type.IsBitVectorType) {
          wr = EmitBitvectorTruncation(e.Type.AsBitVectorType, true, wr);
        }
        var e0 = reverseArguments ? e.E1 : e.E0;
        var e1 = reverseArguments ? e.E0 : e.E1;
        if (opString != null) {
          var nativeType = AsNativeType(e.Type);
          string nativeName = null, literalSuffix = null;
          bool needsCast = false;
          if (nativeType != null) {
            GetNativeInfo(nativeType.Sel, out nativeName, out literalSuffix, out needsCast);
          }
          if (needsCast) {
            wr.Write("(" + nativeName + ")(");
          }
          wr.Write(preOpString);
          TrParenExpr(e0, wr, inLetExprBody);
          wr.Write(" {0} ", opString);
          if (convertE1_to_int) {
            EmitExprAsInt(e1, inLetExprBody, wr);
          } else {
            TrParenExpr(e1, wr, inLetExprBody);
          }
          if (needsCast) {
            wr.Write(")");
          }
          wr.Write(postOpString);
        } else if (callString != null) {
          wr.Write(preOpString);
          TrParenExpr(e0, wr, inLetExprBody);
          wr.Write(".{0}(", callString);
          TrExpr(e1, wr, inLetExprBody);
          wr.Write(")");
          wr.Write(postOpString);
        } else if (staticCallString != null) {
          wr.Write(preOpString);
          wr.Write("{0}(", staticCallString);
          TrExpr(e0, wr, inLetExprBody);
          wr.Write(", ");
          TrExpr(e1, wr, inLetExprBody);
          wr.Write(")");
          wr.Write(postOpString);
        }

      } else if (expr is TernaryExpr) {
        Contract.Assume(false);  // currently, none of the ternary expressions is compilable

      } else if (expr is LetExpr) {
        var e = (LetExpr)expr;
        if (e.Exact) {
          // The Dafny "let" expression
          //    var Pattern(x,y) := G; E
          // is translated into C# as:
          //    LamLet(G, tmp =>
          //      LamLet(dtorX(tmp), x =>
          //      LamLet(dtorY(tmp), y => E)))
          Contract.Assert(e.LHSs.Count == e.RHSs.Count);  // checked by resolution
          var w = wr;
          for (int i = 0; i < e.LHSs.Count; i++) {
            var lhs = e.LHSs[i];
            if (Contract.Exists(lhs.Vars, bv => !bv.IsGhost)) {
              var rhsName = string.Format("_pat_let{0}_{1}", GetUniqueAstNumber(e), i);
              w = CreateIIFE_ExprBody(e.RHSs[i], inLetExprBody, e.RHSs[i].Type, e.RHSs[i].tok, e.Body.Type, e.Body.tok, rhsName, w);
              w = TrCasePattern(lhs, rhsName, e.Body.Type, w);
            }
          }
          TrExpr(e.Body, w, true);
        } else if (e.BoundVars.All(bv => bv.IsGhost)) {
          // The Dafny "let" expression
          //    ghost var x,y :| Constraint; E
          // is compiled just like E is, because the resolver has already checked that x,y (or other ghost variables, for that matter) don't
          // occur in E (moreover, the verifier has checked that values for x,y satisfying Constraint exist).
          TrExpr(e.Body, wr, inLetExprBody);
        } else {
          // The Dafny "let" expression
          //    var x,y :| Constraint; E
          // is translated into C# as:
          //    LamLet(0, dummy => {  // the only purpose of this construction here is to allow us to add some code inside an expression in C#
          //        var x,y;
          //        // Embark on computation that fills in x,y according to Constraint; the computation stops when the first
          //        // such value is found, but since the verifier checks that x,y follows uniquely from Constraint, this is
          //        // not a source of nondeterminancy.
          //        return E;
          //      })
          Contract.Assert(e.RHSs.Count == 1);  // checked by resolution
          var missingBounds = ComprehensionExpr.BoolBoundedPool.MissingBounds(e.BoundVars.ToList<BoundVar>(), e.Constraint_Bounds, ComprehensionExpr.BoundedPool.PoolVirtues.Enumerable);
          if (missingBounds.Count != 0) {
            foreach (var bv in missingBounds) {
              Error(e.tok, "this let-such-that expression is too advanced for the current compiler; Dafny's heuristics cannot find any bound for variable '{0}'", wr, bv.Name);
            }
          } else {
            var w = CreateIIFE1(0, e.Body.Type, e.Body.tok, "_let_dummy_" + GetUniqueAstNumber(e), wr);
            foreach (var bv in e.BoundVars) {
              DeclareLocalVar(IdName(bv), bv.Type, bv.tok, false, DefaultValue(bv.Type, wr, bv.tok), w);
            }
            TrAssignSuchThat(new List<IVariable>(e.BoundVars).ConvertAll(bv => (IVariable)bv), e.RHSs[0], e.Constraint_Bounds, e.tok.line, w, inLetExprBody);
            EmitReturnExpr(e.Body, true, w);
          }
        }

      } else if (expr is MatchExpr) {
        var e = (MatchExpr)expr;
        // ((System.Func<SourceType, TargetType>)((SourceType _source) => {
        //   if (source.is_Ctor0) {
        //     FormalType f0 = ((Dt_Ctor0)source._D).a0;
        //     ...
        //     return Body0;
        //   } else if (...) {
        //     ...
        //   } else if (true) {
        //     ...
        //   }
        // }))(src)

        string source = idGenerator.FreshId("_source");
        var w = CreateLambda(new List<Type>() { e.Source.Type }, e.tok, new List<string>() { source }, e.Type, wr);

        if (e.Cases.Count == 0) {
          // the verifier would have proved we never get here; still, we need some code that will compile
          EmitAbsurd(null, w);
        } else {
          int i = 0;
          var sourceType = (UserDefinedType)e.Source.Type.NormalizeExpand();
          foreach (MatchCaseExpr mc in e.Cases) {
            var wCase = MatchCasePrelude(source, sourceType, mc.Ctor, mc.Arguments, i, e.Cases.Count, w);
            EmitReturnExpr(mc.Body, inLetExprBody, wCase);
            i++;
          }
        }
        // We end with applying the source expression to the delegate we just built
        TrParenExpr(e.Source, wr, inLetExprBody);

      } else if (expr is QuantifierExpr) {
        var e = (QuantifierExpr)expr;

        // Compilation does not check whether a quantifier was split.

        Contract.Assert(e.Bounds != null);  // for non-ghost quantifiers, the resolver would have insisted on finding bounds
        var n = e.BoundVars.Count;
        Contract.Assert(e.Bounds.Count == n);
        var wBody = wr;
        for (int i = 0; i < n; i++) {
          var bound = e.Bounds[i];
          var bv = e.BoundVars[i];
          // emit:  Dafny.Helpers.Quantifier(rangeOfValues, isForall, bv => body)
          wBody.Write("{0}(", GetQuantifierName(TypeName(bv.Type, wBody, bv.tok)));
          CompileCollection(bound, bv, inLetExprBody, false, wBody, e.Bounds, e.BoundVars, i);
          wBody.Write(", {0}, ", expr is ForallExpr ? "true" : "false");
          var native = AsNativeType(e.BoundVars[i].Type);
          TargetWriter newWBody = CreateLambda(new List<Type>{ bv.Type }, e.tok, new List<string>{ IdName(bv) }, Type.Bool, wBody, untyped: true);
          newWBody = EmitReturnExpr(newWBody);
          wBody.Write(')');
          wBody = newWBody;
        }
        TrExpr(e.LogicalBody(true), wBody, true);

      } else if (expr is SetComprehension) {
        var e = (SetComprehension)expr;
        // For "set i,j,k,l | R(i,j,k,l) :: Term(i,j,k,l)" where the term has type "G", emit something like:
        // ((System.Func<Set<G>>)(() => {
        //   var _coll = new List<G>();
        //   foreach (var tmp_l in sq.Elements) { L l = (L)tmp_l;
        //     foreach (var tmp_k in st.Elements) { K k = (K)tmp_k;
        //       for (BigInteger j = Lo; j < Hi; j++) {
        //         for (bool i in Helper.AllBooleans) {
        //           if (R(i,j,k,l)) {
        //             _coll.Add(Term(i,j,k,l));
        //           }
        //         }
        //       }
        //     }
        //   }
        //   return Dafny.Set<G>.FromCollection(_coll);
        // }))()
        Contract.Assert(e.Bounds != null);  // the resolver would have insisted on finding bounds
        var typeName = TypeName(e.Type.AsSetType.Arg, wr, e.tok);
        var collection_name = idGenerator.FreshId("_coll");
        var bwr = CreateIIFE0(e.Type.AsSetType, e.tok, wr);
        wr = bwr;
        using (var wrVarInit = DeclareLocalVar(collection_name, null, null, wr)) {
          EmitCollectionBuilder_New(e.Type.AsSetType, e.tok, wrVarInit);
        }
        var n = e.BoundVars.Count;
        Contract.Assert(e.Bounds.Count == n);
        for (int i = 0; i < n; i++) {
          var bound = e.Bounds[i];
          var bv = e.BoundVars[i];
          TargetWriter collectionWriter;
          var tmpVar = idGenerator.FreshId("_compr_");
          wr = CreateForeachLoop(tmpVar, bv.Type, out collectionWriter, wr, IdName(bv), bv.Type, bv.tok);
          CompileCollection(bound, bv, inLetExprBody, true, collectionWriter);
        }
        TargetWriter guardWriter;
        var thn = EmitIf(out guardWriter, false, wr);
        TrExpr(e.Range, guardWriter, inLetExprBody);
        EmitCollectionBuilder_Add(e.Type.AsSetType, collection_name, e.Term, inLetExprBody, thn);
        var s = GetCollectionBuilder_Build(e.Type.AsSetType, e.tok, collection_name, wr);
        EmitReturnExpr(s, bwr);

      } else if (expr is MapComprehension) {
        var e = (MapComprehension)expr;
        // For "map i | R(i) :: Term(i)" where the term has type "V" and i has type "U", emit something like:
        // ((System.Func<Map<U, V>>)(() => {
        //   var _coll = new List<Pair<U,V>>();
        //   foreach (L l in sq.Elements) {
        //     foreach (K k in st.Elements) {
        //       for (BigInteger j = Lo; j < Hi; j++) {
        //         for (bool i in Helper.AllBooleans) {
        //           if (R(i,j,k,l)) {
        //             _coll.Add(new Pair(i, Term(i));
        //           }
        //         }
        //       }
        //     }
        //   }
        //   return Dafny.Map<U, V>.FromCollection(_coll);
        // }))()
        Contract.Assert(e.Bounds != null);  // the resolver would have insisted on finding bounds
        var domtypeName = TypeName(e.Type.AsMapType.Domain, wr, e.tok);
        var rantypeName = TypeName(e.Type.AsMapType.Range, wr, e.tok);
        var collection_name = idGenerator.FreshId("_coll");
        wr = CreateIIFE0(e.Type.AsMapType, e.tok, wr);
        using (var wrVarInit = DeclareLocalVar(collection_name, null, null, wr)) {
          EmitCollectionBuilder_New(e.Type.AsMapType, e.tok, wrVarInit);
        }
        var n = e.BoundVars.Count;
        Contract.Assert(e.Bounds.Count == n && n == 1);
        var bound = e.Bounds[0];
        var bv = e.BoundVars[0];
        Contract.Assume(e.BoundVars.Count == 1);  // TODO: implement the case where e.BoundVars.Count > 1
        TargetWriter collectionWriter;
        var w = CreateForeachLoop(IdName(bv), bv.Type, out collectionWriter, wr);
        CompileCollection(bound, bv, inLetExprBody, true, collectionWriter);
        TargetWriter guardWriter;
        var thn = EmitIf(out guardWriter, false, w);
        TrExpr(e.Range, guardWriter, inLetExprBody);
        var termLeftWriter = EmitMapBuilder_Add(e.Type.AsMapType, e.tok, collection_name, e.Term, inLetExprBody, thn);
        if (e.TermLeft == null) {
          termLeftWriter.Write(IdName(bv));
        } else {
          TrExpr(e.TermLeft, termLeftWriter, inLetExprBody);
        }

        var s = GetCollectionBuilder_Build(e.Type.AsMapType, e.tok, collection_name, wr);
        EmitReturnExpr(s, wr);

      } else if (expr is LambdaExpr) {
        LambdaExpr e = (LambdaExpr)expr;

        List<BoundVar> bvars;
        List<Expression> fexprs;
        Translator.Substituter su;
        CreateFreeVarSubstitution(expr, out bvars, out fexprs, out su);

        var typeArgs = TypeName_UDT(ArrowType.Arrow_FullCompileName, Util.Snoc(bvars.ConvertAll(bv => bv.Type), expr.Type), wr, Bpl.Token.NoToken);
        var boundVars = Util.Comma(bvars, IdName);
        wr = EmitBetaRedex(bvars.ConvertAll(IdName), fexprs, typeArgs, bvars.ConvertAll(bv => bv.Type), expr.Type, expr.tok, inLetExprBody, wr);
        wr = CreateLambda(e.BoundVars.ConvertAll(bv => bv.Type), Bpl.Token.NoToken, e.BoundVars.ConvertAll(IdName), e.Body.Type, wr);
        wr = EmitReturnExpr(wr);
        TrExpr(su.Substitute(e.Body), wr, inLetExprBody);

      } else if (expr is StmtExpr) {
        var e = (StmtExpr)expr;
        TrExpr(e.E, wr, inLetExprBody);

      } else if (expr is ITEExpr) {
        var e = (ITEExpr)expr;
        EmitITE(e.Test, e.Thn, e.Els, inLetExprBody, wr);

      } else if (expr is ConcreteSyntaxExpression) {
        var e = (ConcreteSyntaxExpression)expr;
        TrExpr(e.ResolvedExpression, wr, inLetExprBody);

      } else if (expr is NamedExpr) {
        TrExpr(((NamedExpr)expr).Body, wr, inLetExprBody);
      } else {
        Contract.Assert(false); throw new cce.UnreachableException();  // unexpected expression
      }
    }

    void CreateFreeVarSubstitution(Expression expr, out List<BoundVar> bvars, out List<Expression> fexprs, out Translator.Substituter su) {
      Contract.Requires(expr != null);

      var fvs = Translator.ComputeFreeVariables(expr);
      var sm = new Dictionary<IVariable, Expression>();

      bvars = new List<BoundVar>();
      fexprs = new List<Expression>();
      foreach (var fv in fvs) {
        fexprs.Add(new IdentifierExpr(fv.Tok, fv.Name) {
          Var = fv, // resolved here!
          Type = fv.Type
        });
        var bv = new BoundVar(fv.Tok, fv.Name, fv.Type);
        bvars.Add(bv);
        sm[fv] = new IdentifierExpr(bv.Tok, bv.Name) {
          Var = bv, // resolved here!
          Type = bv.Type
        };
      }

      su = new Translator.Substituter(null, sm, new Dictionary<TypeParameter, Type>());
    }

    protected bool IsHandleComparison(Bpl.IToken tok, Expression e0, Expression e1, TextWriter errorWr) {
      Contract.Requires(tok != null);
      Contract.Requires(e0 != null);
      Contract.Requires(e1 != null);
      TopLevelDecl cl;
      var isHandle0 = true;
      cl = (e0.Type.NormalizeExpand() as UserDefinedType)?.ResolvedClass;
      if (cl == null || !Attributes.ContainsBool(cl.Attributes, "handle", ref isHandle0)) {
        isHandle0 = false;
      }
      var isHandle1 = true;
      cl = (e1.Type.NormalizeExpand() as UserDefinedType)?.ResolvedClass;
      if (cl == null || !Attributes.ContainsBool(cl.Attributes, "handle", ref isHandle1)) {
        isHandle1 = false;
      }
      if (isHandle0 && isHandle1) {
        return true;
      } else if (isHandle0 || isHandle1) {
        Error(tok, "Comparison of a handle can only be with another handle", errorWr);
      }
      return false;
    }

    protected void TrStringLiteral(StringLiteralExpr str, TextWriter wr) {
      Contract.Requires(str != null);
      Contract.Requires(wr != null);
      EmitStringLiteral((string)str.Value, str.IsVerbatim, wr);
    }

    /// <summary>
    /// Try to evaluate "expr" into one BigInteger.  On success, return it; otherwise, return "null".
    /// </summary>
    /// <param name="expr"></param>
    /// <returns></returns>
    public static Nullable<BigInteger> PartiallyEvaluate(Expression expr) {
      Contract.Requires(expr != null);
      expr = expr.Resolved;
      if (expr is LiteralExpr) {
        var e = (LiteralExpr)expr;
        if (e.Value is BigInteger) {
          return (BigInteger)e.Value;
        }
      } else if (expr is BinaryExpr) {
        var e = (BinaryExpr)expr;
        switch (e.ResolvedOp) {
          case BinaryExpr.ResolvedOpcode.Add:
          case BinaryExpr.ResolvedOpcode.Sub:
          case BinaryExpr.ResolvedOpcode.Mul:
            // possibly the most important case is Sub, since that's how NegationExpression's end up
            var arg0 = PartiallyEvaluate(e.E0);
            var arg1 = arg0 == null ? null : PartiallyEvaluate(e.E1);
            if (arg1 != null) {
              switch (e.ResolvedOp) {
                case BinaryExpr.ResolvedOpcode.Add:
                  return arg0 + arg1;
                case BinaryExpr.ResolvedOpcode.Sub:
                  return arg0 - arg1;
                case BinaryExpr.ResolvedOpcode.Mul:
                  return arg0 * arg1;
                default:
                  Contract.Assert(false);
                  break;  // please compiler
              }
            }
            break;
          default:
            break;
        }
      }
      return null;
    }

    TargetWriter TrCasePattern(CasePattern<BoundVar> pat, string rhsString, Type bodyType, TargetWriter wr) {
      Contract.Requires(pat != null);
      Contract.Requires(rhsString != null);
      Contract.Requires(bodyType != null);
      Contract.Requires(wr != null);

      if (pat.Var != null) {
        var bv = pat.Var;
        if (!bv.IsGhost) {
          wr = CreateIIFE_ExprBody(rhsString, bv.Type, bv.tok, bodyType, pat.tok, IdProtect(bv.CompileName), wr);
        }
      } else if (pat.Arguments != null) {
        var ctor = pat.Ctor;
        Contract.Assert(ctor != null);  // follows from successful resolution
        Contract.Assert(pat.Arguments.Count == ctor.Formals.Count);  // follows from successful resolution
        var k = 0;  // number of non-ghost formals processed
        for (int i = 0; i < pat.Arguments.Count; i++) {
          var arg = pat.Arguments[i];
          var formal = ctor.Formals[i];
          if (formal.IsGhost) {
            // nothing to compile, but do a sanity check
            Contract.Assert(!Contract.Exists(arg.Vars, bv => !bv.IsGhost));
          } else {
            var sw = new TargetWriter(wr.IndentLevel);
            EmitDestructor(rhsString, formal, k, ctor, ((DatatypeValue)pat.Expr).InferredTypeArgs, arg.Expr.Type, sw);
            wr = TrCasePattern(arg, sw.ToString(), bodyType, wr);
            k++;
          }
        }
      }
      return wr;
    }

    void CompileSpecialFunctionCallExpr(FunctionCallExpr e, TargetWriter wr, bool inLetExprBody, FCE_Arg_Translator tr) {
      string name = e.Function.Name;
      
      if (name == "RotateLeft") {
        EmitRotate(e.Receiver, e.Args[0], true, wr, inLetExprBody, tr);
      } else if (name == "RotateRight") {
        EmitRotate(e.Receiver, e.Args[0], false, wr, inLetExprBody, tr);
      } else {
        CompileFunctionCallExpr(e, wr, inLetExprBody, tr);
      }
    }

    void CompileFunctionCallExpr(FunctionCallExpr e, TargetWriter wr, bool inLetExprBody, FCE_Arg_Translator tr) {
      Contract.Requires(e != null && e.Function != null);
      Contract.Requires(wr != null);
      Contract.Requires(tr != null);
      Function f = e.Function;

      wr = EmitCoercionIfNecessary(f.ResultType, e.Type, e.tok, wr);

      if (f.IsStatic) {
        wr.Write(TypeName_Companion(e.Receiver.Type, wr, e.tok, f));
      } else {
        wr.Write("(");
        tr(e.Receiver, wr, inLetExprBody);
        wr.Write(")");
      }
      wr.Write(".{0}", IdName(f));
      List<Type> typeArgs;
      if (f.TypeArgs.Count != 0) {
        typeArgs = f.TypeArgs.ConvertAll(ta => e.TypeArgumentSubstitutions[ta]);
        EmitActualTypeArgs(typeArgs, f.tok, wr);
      } else {
        typeArgs = new List<Type>();
      }
      wr.Write("(");
      var nRTDs = EmitRuntimeTypeDescriptorsActuals(typeArgs, f.TypeArgs, e.tok, false, wr);
      string sep = nRTDs == 0 ? "" : ", ";
      for (int i = 0; i < e.Args.Count; i++) {
        if (!e.Function.Formals[i].IsGhost) {
          wr.Write(sep);
          var w = EmitCoercionIfNecessary(from:e.Args[i].Type, to:e.Function.Formals[i].Type, tok:e.tok, wr:wr);
          tr(e.Args[i], w, inLetExprBody);
          sep = ", ";
        }
      }
      wr.Write(")");
    }
    
    public virtual bool SupportsInMemoryCompilation { get => true; }
    
    /// <summary>
    /// Compile the target program known as "dafnyProgramName".
    /// "targetProgramText" contains the program text.
    /// If "targetFilename" is non-null, it is the name of the target program text stored as a
    /// file. "targetFileName" must be non-null if "otherFileNames" is nonempty.
    /// "otherFileNames" is a list of other files to include in the compilation.
    /// 
    /// "hasMain" says whether or not the program contains a "Main()" program.
    /// 
    /// Upon successful compilation, "runAfterCompile" says whether or not to execute the program.
    /// 
    /// Output any errors to "outputWriter".
    /// Returns "false" if there were errors. Then, "compilationResult" should not be used.
    /// Returns "true" on success. Then, "compilationResult" is a value that can be passed in to
    /// the instance's "RunTargetProgram" method.
    /// </summary>
    public virtual bool CompileTargetProgram(string dafnyProgramName, string targetProgramText, string/*?*/ callToMain, string/*?*/ targetFilename, ReadOnlyCollection<string> otherFileNames,
      bool hasMain, bool runAfterCompile, TextWriter outputWriter, out object compilationResult) {
      Contract.Requires(dafnyProgramName != null);
      Contract.Requires(targetProgramText != null);
      Contract.Requires(otherFileNames != null);
      Contract.Requires(otherFileNames.Count == 0 || targetFilename != null);
      Contract.Requires(this.SupportsInMemoryCompilation || targetFilename != null);
      Contract.Requires(!runAfterCompile || hasMain);
      Contract.Requires(outputWriter != null);

      compilationResult = null;
      return true;
    }

    /// <summary>
    /// Runs a target program after it has been successfully compiled.
    /// dafnyProgram, targetProgramText, targetFilename, and otherFileNames are the same as the corresponding parameters to "CompileTargetProgram".
    /// "callToMain" is an explicit call to Main, as required by the target compilation language.
    /// "compilationResult" is a value returned by "CompileTargetProgram" for these parameters.
    /// 
    /// Returns "true" on success, "false" on error. Any errors are output to "outputWriter".
    /// </summary>
    public virtual bool RunTargetProgram(string dafnyProgramName, string targetProgramText, string/*?*/ callToMain, string/*?*/ targetFilename, ReadOnlyCollection<string> otherFileNames,
      object compilationResult, TextWriter outputWriter) {
      Contract.Requires(dafnyProgramName != null);
      Contract.Requires(targetProgramText != null);
      Contract.Requires(otherFileNames != null);
      Contract.Requires(otherFileNames.Count == 0 || targetFilename != null);
      Contract.Requires(outputWriter != null);
      return true;
    }
  }

  public class TargetWriter : TextWriter {
    public TargetWriter(int indent = 0) {
      Contract.Requires(0 <= indent);
      IndentLevel = indent;
      IndentString = new string(' ', indent);
    }
    public override Encoding Encoding {
      get { return Encoding.Default; }
    }

    // ----- Indention ------------------------------

    public readonly int IndentLevel;
    protected const int IndentAmount = 2;
    const string IndentAmountString = "  ";  // this should have the length IndentAmount
    public readonly string IndentString;
    public string UnIndentString => new string(' ', Math.Max(IndentLevel - IndentAmount, 0));
    private bool suppressNextIndent = false;
    public void Indent() {
      if (suppressNextIndent) {
        suppressNextIndent = false;
      } else {
        Write(IndentString);
      }
    }
    public void IndentExtra(int times = 1) {
      Contract.Requires(-1 <= times);
      if (suppressNextIndent) {
        suppressNextIndent = false;
      } else if (times == -1) {
        Write(UnIndentString);
      } else {
        Indent();
        for (; 0 <= --times;) {
          Write(IndentAmountString);
        }
      }
    }
    public void SuppressIndent(){
      suppressNextIndent = true;
    }

    // ----- Things ------------------------------

    readonly List<object> things = new List<object>();

    public void Append(TargetWriter wr) {
      Contract.Requires(wr != null);
      things.Add(wr);
    }

    // ----- Writing ------------------------------

    public override void Write(char[] buffer, int index, int count) {
      things.Add(new string(buffer, index, count));
    }
    public override void Write(string value) {
      things.Add(value);
    }
    public override void Write(char value) {
      things.Add(new string(value, 1));
    }

    public void RepeatWrite(int times, string template, string separator) {
      Contract.Requires(1 <= times);
      Contract.Requires(template != null);
      Contract.Requires(separator != null);
      string sep = "";
      for (int i = 0; i < times; i++) {
        Write(sep);
        Write(template, i);
        sep = separator;
      }
    }

    public TargetWriter Fork() {
      var ans = new TargetWriter(IndentLevel);
      things.Add(ans);
      return ans;
    }

    // ----- Nested blocks ------------------------------

    public BlockTargetWriter NewBlock(string header, string/*?*/ footer = null) {
      Contract.Requires(header != null);
      var btw = new BlockTargetWriter(IndentLevel + IndentAmount, header, footer);
      btw.SetBraceStyle(BlockTargetWriter.BraceStyle.Space, BlockTargetWriter.BraceStyle.Newline);
      things.Add(btw);
      return btw;
    }
    public BlockTargetWriter NewNamedBlock(string headerFormat, params object[] headerArgs) {
      Contract.Requires(headerFormat != null);
      var btw = new BlockTargetWriter(IndentLevel + IndentAmount, string.Format(headerFormat, headerArgs), null);
      btw.SetBraceStyle(BlockTargetWriter.BraceStyle.Space, BlockTargetWriter.BraceStyle.Newline);
      things.Add(btw);
      return btw;
    }
    public BlockTargetWriter NewBigBlock(string header, string/*?*/ footer) {
      Contract.Requires(header != null);
      var btw = new BlockTargetWriter(IndentLevel + IndentAmount, header, footer);
      btw.SetBraceStyle(BlockTargetWriter.BraceStyle.Space, BlockTargetWriter.BraceStyle.Newline);
      things.Add(btw);
      return btw;
    }
    public BlockTargetWriter NewBlockWithPrefix(string headerFormat, string prefixFormat, params object[] headerArgs) {
      Contract.Requires(headerFormat != null);
      Contract.Requires(prefixFormat != null);
      var btw = new BlockTargetWriter(IndentLevel + IndentAmount, string.Format(headerFormat, headerArgs), null);
      btw.SetBraceStyle(BlockTargetWriter.BraceStyle.Space, BlockTargetWriter.BraceStyle.Newline);
      btw.BodyPrefix = string.Format(prefixFormat, headerArgs);
      things.Add(btw);
      return btw;
    }

    public TargetWriter NewSection() {
      var w = new TargetWriter(IndentLevel);
      things.Add(w);
      return w;
    }

    public FileTargetWriter NewFile(string filename) {
      var w = new FileTargetWriter(filename);
      things.Add(w);
      return w;
    }

    // ----- Collection ------------------------------

    public override string ToString() {
      var sw = new StringWriter();
      var q = new Queue<FileTargetWriter>();
      Collect(sw, q);
      while (q.Count != 0) {
        var ftw = q.Dequeue();
        sw.WriteLine("#file {0}", ftw.Filename);
        ftw.Collect(sw, q);
      }
      return sw.ToString();
    }
    public virtual void Collect(TextWriter wr, Queue<FileTargetWriter> files) {
      Contract.Requires(wr != null);
      Contract.Requires(files != null);

      if (things != null) {
        CollectThings(wr, files);
      }
    }
    protected void CollectThings(TextWriter wr, Queue<FileTargetWriter> files) {
      Contract.Requires(wr != null);
      Contract.Requires(files != null);

      foreach (var o in things) {
        if (o is string) {
          wr.Write((string)o);
        } else if (o is FileTargetWriter ftw) {
          files.Enqueue(ftw);
        } else if (o is TargetWriter) {
          ((TargetWriter)o).Collect(wr, files);
        } else {
          wr.Write(o.ToString());
        }
      }
    }
  }
  public class BlockTargetWriter : TargetWriter {
    string header;
    public enum BraceStyle { Nothing, Space, Newline }
    BraceStyle openBraceStyle = BraceStyle.Space;
    BraceStyle closeBraceStyle = BraceStyle.Newline;
    public string BodyPrefix;  // just inside the open curly (before the newline)
    public string BodySuffix;  // just before the close curly
    public string Footer;  // just after the close curly
    public BlockTargetWriter(int indentInsideBraces, string header, string/*?*/ footer)
    : base(indentInsideBraces) {
      Contract.Requires(IndentAmount <= indentInsideBraces);
      Contract.Requires(header != null);
      this.header = header;
      this.Footer = footer;
    }
    public void SetBraceStyle(BraceStyle open, BraceStyle close) {
      this.openBraceStyle = open;
      this.closeBraceStyle = close;
    }
    public void AppendHeader(string format, params object[] args) {
      Contract.Requires(format != null);
      header += args.Length == 0 ? format : string.Format(format, args);
    }

    public override void Collect(TextWriter wr, Queue<FileTargetWriter> files) {
      wr.Write(header);
      switch (openBraceStyle) {
        case BraceStyle.Nothing:
        default:
          break;
        case BraceStyle.Space:
          wr.Write(" ");
          break;
        case BraceStyle.Newline:
          wr.WriteLine();
          wr.Write(UnIndentString);
          break;
      }
      wr.WriteLine("{{{0}", BodyPrefix != null ? " " + BodyPrefix : "");
      CollectThings(wr, files);
      if (BodySuffix != null) {
        wr.Write(BodySuffix);
      }
      wr.Write(UnIndentString);
      wr.Write("}");
      if (Footer != null) {
        wr.Write(Footer);
      }
      switch (closeBraceStyle) {
        case BraceStyle.Nothing:
        default:
          break;
        case BraceStyle.Space:
          wr.Write(" ");
          break;
        case BraceStyle.Newline:
          wr.WriteLine();
          break;
      }
    }
  }

  public class FileTargetWriter : TargetWriter {
    public readonly string Filename;

    public FileTargetWriter(string filename) {
      Contract.Requires(filename != null);
      Filename = filename;
    }
  }
}
