Package wyc.builder

Source Code of wyc.builder.FlowTypeChecker

package wyc.builder;

import static wyc.lang.WhileyFile.internalFailure;
import static wyc.lang.WhileyFile.syntaxError;
import static wycc.lang.SyntaxError.internalFailure;
import static wycc.lang.SyntaxError.syntaxError;
import static wyil.util.ErrorMessages.*;

import java.io.IOException;
import java.math.BigDecimal;
import java.util.*;

import wyautl_old.lang.Automata;
import wyautl_old.lang.Automaton;
import wybs.lang.*;
import wybs.util.*;
import wyc.lang.*;
import wyc.lang.WhileyFile.Context;
import wycc.lang.NameID;
import wycc.lang.SyntacticElement;
import wycc.lang.SyntaxError;
import wycc.util.Pair;
import wycc.util.ResolveError;
import wyfs.lang.Path;
import wyfs.util.Trie;
import wyil.lang.Constant;
import wyil.lang.Modifier;
import wyil.lang.Type;
import wyil.lang.WyilFile;

/**
* Propagates type information in a <i>flow-sensitive</i> fashion from declared
* parameter and return types through variable declarations and assigned
* expressions, to determine types for all intermediate expressions and
* variables. During this propagation, type checking is performed to ensure
* types are used soundly. For example:
*
* <pre>
* function sum([int] data) => int:
*     int r = 0      // declared int type for r
*     for v in data: // infers int type for v, based on type of data
*         r = r + v  // infers int type for r + v, based on type of operands
*     return r       // infers int type for return expression
* </pre>
*
* <p>
* The flow typing algorithm distinguishes between the <i>declared type</i> of a
* variable and its <i>known type</i>. That is, the known type at any given
* point is permitted to be more precise than the declared type (but not vice
* versa). For example:
* </p>
*
* <pre>
* function id(int x) => int:
*    return x
*
* function f(int y) => int:
*    int|null x = y
*    f(x)
* </pre>
*
* <p>
* The above example is considered type safe because the known type of
* <code>x</code> at the function call is <code>int</code>, which differs from
* its declared type (i.e. <code>int|null</code>).
* </p>
*
* <p>
* Loops present an interesting challenge for type propagation. Consider this
* example:
* </p>
*
* <pre>
* function loopy(int max) => real:
*     var i = 0
*     while i < max:
*         i = i + 0.5
*     return i
* </pre>
*
* <p>
* On the first pass through the loop, variable <code>i</code> is inferred to
* have type <code>int</code> (based on the type of the constant <code>0</code>
* ). However, the add expression is inferred to have type <code>real</code>
* (based on the type of the rhs) and, hence, the resulting type inferred for
* <code>i</code> is <code>real</code>. At this point, the loop must be
* reconsidered taking into account this updated type for <code>i</code>.
* </p>
*
* <p>
* The operation of the flow type checker splits into two stages:
* </p>
* <ul>
* <li><b>Global Propagation.</b> During this stage, all named types are checked
* and expanded.</li>
* <li><b>Local Propagation.</b> During this stage, types are propagated through
* statements and expressions (as above).</li>
* </ul>
*
* <h3>References</h3>
* <ul>
* <li>
* <p>
* David J. Pearce and James Noble. Structural and Flow-Sensitive Types for
* Whiley. Technical Report, Victoria University of Wellington, 2010.
* </p>
* </li>
* </ul>
*
* @author David J. Pearce
*
*/
public class FlowTypeChecker {

  private WhileyBuilder builder;
  private String filename;
  private WhileyFile.FunctionOrMethod current;

  /**
   * The constant cache contains a cache of expanded constant values. This is
   * simply to prevent recomputing them every time.
   */
  private final HashMap<NameID, Constant> constantCache = new HashMap<NameID, Constant>();

  public FlowTypeChecker(WhileyBuilder builder) {
    this.builder = builder;
  }

  // =========================================================================
  // WhileyFile(s)
  // =========================================================================

  public void propagate(List<WhileyFile> files) {
    for (WhileyFile wf : files) {
      propagate(wf);
    }
  }

  public void propagate(WhileyFile wf) {
    this.filename = wf.filename;

    for (WhileyFile.Declaration decl : wf.declarations) {
      try {
        if (decl instanceof WhileyFile.FunctionOrMethod) {
          propagate((WhileyFile.FunctionOrMethod) decl);
        } else if (decl instanceof WhileyFile.Type) {
          propagate((WhileyFile.Type) decl);
        } else if (decl instanceof WhileyFile.Constant) {
          propagate((WhileyFile.Constant) decl);
        }
      } catch (ResolveError e) {
        syntaxError(errorMessage(RESOLUTION_ERROR, e.getMessage()),
            filename, decl, e);
      } catch (SyntaxError e) {
        throw e;
      } catch (Throwable t) {
        internalFailure(t.getMessage(), filename, decl, t);
      }
    }
  }

  // =========================================================================
  // Declarations
  // =========================================================================

  /**
   * Resolve types for a given type declaration. If an invariant expression is
   * given, then we have to propagate and resolve types throughout the
   * expression.
   *
   * @param td
   *            Type declaration to check.
   * @throws IOException
   */
  public void propagate(WhileyFile.Type td) throws IOException {

    // First, resolve the declared syntactic type into the corresponding
    // nominal type.
    td.resolvedType = resolveAsType(td.pattern.toSyntacticType(), td);

    if(Type.isSubtype(Type.T_VOID, td.resolvedType.raw())) {
      // A contractive type is one which cannot accept a finite values.
      // For example, the following is a contractive type:
      //
      // type Contractive is { Contractive x }
      //
      syntaxError("contractive type defined",filename,td);
    } else if (td.invariant != null) {
      // Second, an invariant expression is given, so propagate through
      // that.

      // Construct the appropriate typing environment
      Environment environment = new Environment();
      environment = addDeclaredVariables(td.pattern, environment, td);
      // Propagate type information through the constraint
      td.invariant = propagate(td.invariant, environment, td);
    }
  }

  /**
   * Propagate and check types for a given constant declaration.
   *
   * @param cd
   *            Constant declaration to check.
   * @throws IOException
   */
  public void propagate(WhileyFile.Constant cd) throws IOException, ResolveError {
    NameID nid = new NameID(cd.file().module, cd.name());
    cd.resolvedValue = resolveAsConstant(nid);
  }

  /**
   * Propagate and check types for a given function or method declaration.
   *
   * @param fd
   *            Function or method declaration to check.
   * @throws IOException
   */
  public void propagate(WhileyFile.FunctionOrMethod d) throws IOException {
    this.current = d; // ugly
    Environment environment = new Environment();

    // Resolve the types of all parameters and construct an appropriate
    // environment for use in the flow-sensitive type propagation.
    for (WhileyFile.Parameter p : d.parameters) {
      environment = environment.declare(p.name, resolveAsType(p.type, d), resolveAsType(p.type, d));
    }

    // Resolve types for any preconditions (i.e. requires clauses) provided.
    final List<Expr> d_requires = d.requires;
    for (int i = 0; i != d_requires.size(); ++i) {
      Expr condition = d_requires.get(i);
      condition = propagate(condition, environment.clone(), d);
      d_requires.set(i, condition);
    }

    // Resolve types for any postconditions (i.e. ensures clauses) provided.
    final List<Expr> d_ensures = d.ensures;
    if (d_ensures.size() > 0) {
      // At least one ensures clause is provided; so, first, construct an
      // appropriate environment from the initial one create.
      Environment ensuresEnvironment = addDeclaredVariables(d.ret,
          environment.clone(), d);

      // Now, type check each ensures clause
      for (int i = 0; i != d_ensures.size(); ++i) {
        Expr condition = d_ensures.get(i);
        condition = propagate(condition, ensuresEnvironment, d);
        d_ensures.set(i, condition);
      }
    }

    // Resolve the overall type for the function or method.
    if (d instanceof WhileyFile.Function) {
      WhileyFile.Function f = (WhileyFile.Function) d;
      f.resolvedType = resolveAsType(f.unresolvedType(), d);
    } else {
      WhileyFile.Method m = (WhileyFile.Method) d;
      m.resolvedType = resolveAsType(m.unresolvedType(), d);
    }

    // Finally, propagate type information throughout all statements in the
    // function / method body.
    Environment last = propagate(d.statements, environment);
    if (!d.hasModifier(Modifier.NATIVE) && last != BOTTOM
        && !(current.resolvedType().ret().raw() instanceof Type.Void)) {
      // In this case, code reaches the end of the function or method and,
      // furthermore, that this requires a return value. To get here means
      // that there was no explicit return statement given on at least one
      // execution path.
      syntaxError("missing return statement",filename,d);
    }
  }

  // =========================================================================
  // Blocks & Statements
  // =========================================================================

  /**
   * Propagate type information in a flow-sensitive fashion through a block of
   * statements, whilst type checking each statement and expression.
   *
   * @param block
   *            Block of statements to flow sensitively type check
   * @param environment
   *            Determines the type of all variables immediately going into
   *            this block
   * @return
   */
  private Environment propagate(ArrayList<Stmt> block, Environment environment) {

    for (int i = 0; i != block.size(); ++i) {
      Stmt stmt = block.get(i);
      if (stmt instanceof Expr) {
        block.set(i,
            (Stmt) propagate((Expr) stmt, environment, current));
      } else {
        environment = propagate(stmt, environment);
      }
    }

    return environment;
  }

  /**
   * Propagate type information in a flow-sensitive fashion through a given
   * statement, whilst type checking it at the same time. For statements which
   * contain other statements (e.g. if, while, etc), then this will
   * recursively propagate type information through them as well.
   *
   *
   * @param block
   *            Block of statements to flow-sensitively type check
   * @param environment
   *            Determines the type of all variables immediately going into
   *            this block
   * @return
   */
  private Environment propagate(Stmt stmt, Environment environment) {

    try {
      if (stmt instanceof Stmt.VariableDeclaration) {
        return propagate((Stmt.VariableDeclaration) stmt, environment);
      } else if (stmt instanceof Stmt.Assign) {
        return propagate((Stmt.Assign) stmt, environment);
      } else if (stmt instanceof Stmt.Return) {
        return propagate((Stmt.Return) stmt, environment);
      } else if (stmt instanceof Stmt.IfElse) {
        return propagate((Stmt.IfElse) stmt, environment);
      } else if (stmt instanceof Stmt.While) {
        return propagate((Stmt.While) stmt, environment);
      } else if (stmt instanceof Stmt.ForAll) {
        return propagate((Stmt.ForAll) stmt, environment);
      } else if (stmt instanceof Stmt.Switch) {
        return propagate((Stmt.Switch) stmt, environment);
      } else if (stmt instanceof Stmt.DoWhile) {
        return propagate((Stmt.DoWhile) stmt, environment);
      } else if (stmt instanceof Stmt.Break) {
        return propagate((Stmt.Break) stmt, environment);
      } else if (stmt instanceof Stmt.Throw) {
        return propagate((Stmt.Throw) stmt, environment);
      } else if (stmt instanceof Stmt.TryCatch) {
        return propagate((Stmt.TryCatch) stmt, environment);
      } else if (stmt instanceof Stmt.Assert) {
        return propagate((Stmt.Assert) stmt, environment);
      } else if (stmt instanceof Stmt.Assume) {
        return propagate((Stmt.Assume) stmt, environment);
      } else if (stmt instanceof Stmt.Debug) {
        return propagate((Stmt.Debug) stmt, environment);
      } else if (stmt instanceof Stmt.Skip) {
        return propagate((Stmt.Skip) stmt, environment);
      } else {
        internalFailure("unknown statement: "
            + stmt.getClass().getName(), filename, stmt);
        return null; // deadcode
      }
    } catch (ResolveError e) {
      syntaxError(errorMessage(RESOLUTION_ERROR, e.getMessage()),
          filename, stmt, e);
      return null; // dead code
    } catch (SyntaxError e) {
      throw e;
    } catch (Throwable e) {
      internalFailure(e.getMessage(), filename, stmt, e);
      return null; // dead code
    }
  }

  /**
   * Type check an assertion statement. This requires checking that the
   * expression being asserted is well-formed and has boolean type.
   *
   * @param stmt
   *            Statement to type check
   * @param environment
   *            Determines the type of all variables immediately going into
   *            this block
   * @return
   */
  private Environment propagate(Stmt.Assert stmt, Environment environment) {
    stmt.expr = propagate(stmt.expr, environment, current);
    checkIsSubtype(Type.T_BOOL, stmt.expr);
    return environment;
  }

  /**
   * Type check an assume statement. This requires checking that the
   * expression being asserted is well-formed and has boolean type.
   *
   * @param stmt
   *            Statement to type check
   * @param environment
   *            Determines the type of all variables immediately going into
   *            this block
   * @return
   */
  private Environment propagate(Stmt.Assume stmt, Environment environment) {
    stmt.expr = propagate(stmt.expr, environment, current);
    checkIsSubtype(Type.T_BOOL, stmt.expr);
    return environment;
  }

  /**
   * Type check a variable declaration statement. This must associate the
   * given variable with either its declared and actual type in the
   * environment. If no initialiser is given, then the actual type is the void
   * (since the variable is not yet defined). Otherwise, the actual type is
   * the type of the initialiser expression. Additionally, when an initialiser
   * is given we must check it is well-formed and that it is a subtype of the
   * declared type.
   *
   * @param stmt
   *            Statement to type check
   * @param environment
   *            Determines the type of all variables immediately going into
   *            this block
   * @return
   */
  private Environment propagate(Stmt.VariableDeclaration stmt,
      Environment environment) throws IOException, ResolveError {
    // First, resolve declared type
    stmt.type = resolveAsType(stmt.pattern.toSyntacticType(), current);

    // Second, resolve type of initialiser. This must be performed before we
    // update the environment, since this expression is not allowed to refer
    // to the newly declared variable.
    if (stmt.expr != null) {
      stmt.expr = propagate(stmt.expr, environment, current);
      checkIsSubtype(stmt.type, stmt.expr);
    }

    // Third, update environment accordingly. Observe that we can safely
    // assume any variable(s) are not already declared in the enclosing
    // scope because the parser checks this for us.
    environment = addDeclaredVariables(stmt.pattern, environment, current);

    // Fourth, set the current type of the assigned variable if an
    // initialiser is used. This is because the current type may differ
    // from the declared type.
    if (stmt.expr != null) {
      environment = setCurrentType(stmt.pattern, stmt.expr.result(),
          environment);
    }

    // Done.
    return environment;
  }

  /**
   * Type check an assignment statement.
   *
   * @param stmt
   *            Statement to type check
   * @param environment
   *            Determines the type of all variables immediately going into
   *            this block
   * @return
   */
  private Environment propagate(Stmt.Assign stmt, Environment environment)
      throws IOException, ResolveError {

    Expr.LVal lhs = propagate(stmt.lhs, environment);
    Expr rhs = propagate(stmt.rhs, environment, current);

    if (lhs instanceof Expr.RationalLVal) {
      // represents a destructuring assignment
      Expr.RationalLVal tv = (Expr.RationalLVal) lhs;
      Pair<Expr.AssignedVariable, Expr.AssignedVariable> avs = inferAfterType(
          tv, rhs);
      String numVar = avs.first().var;
      String denVar = avs.second().var;
      checkIsSubtype(environment.getDeclaredType(numVar), avs.first().afterType, avs.first());
      checkIsSubtype(environment.getDeclaredType(denVar), avs.second().afterType, avs.second());
      environment = environment.update(numVar, avs.first().afterType);
      environment = environment.update(denVar, avs.second().afterType);
    } else if (lhs instanceof Expr.Tuple) {
      // represents a destructuring assignment
      Expr.Tuple tv = (Expr.Tuple) lhs;
      List<Expr.AssignedVariable> as = inferAfterType(tv, rhs);
      for (Expr.AssignedVariable av : as) {
        checkIsSubtype(environment.getDeclaredType(av.var),av.afterType,av);
        environment = environment.update(av.var, av.afterType);
      }
    } else {
      // represents element or field update
      Expr.AssignedVariable av = inferAfterType(lhs, rhs.result());
      checkIsSubtype(environment.getDeclaredType(av.var),av.afterType,av);
      environment = environment.update(av.var, av.afterType);
    }

    stmt.lhs = (Expr.LVal) lhs;
    stmt.rhs = rhs;

    return environment;
  }

  private Pair<Expr.AssignedVariable, Expr.AssignedVariable> inferAfterType(
      Expr.RationalLVal tv, Expr rhs) throws IOException {
    Nominal afterType = rhs.result();

    if (!Type.isSubtype(Type.T_REAL, afterType.raw())) {
      syntaxError("real value expected, got " + afterType, filename, rhs);
    }

    if (tv.numerator instanceof Expr.AssignedVariable
        && tv.denominator instanceof Expr.AssignedVariable) {
      Expr.AssignedVariable lv = (Expr.AssignedVariable) tv.numerator;
      Expr.AssignedVariable rv = (Expr.AssignedVariable) tv.denominator;
      lv.type = Nominal.T_VOID;
      rv.type = Nominal.T_VOID;
      lv.afterType = Nominal.T_INT;
      rv.afterType = Nominal.T_INT;
      return new Pair<Expr.AssignedVariable, Expr.AssignedVariable>(lv,
          rv);
    } else {
      syntaxError(errorMessage(INVALID_TUPLE_LVAL), filename, tv);
      return null; // dead code
    }
  }

  private List<Expr.AssignedVariable> inferAfterType(Expr.Tuple lv, Expr rhs)
      throws IOException, ResolveError {
    Nominal afterType = rhs.result();
    // Expand after type as an effective tuple
    Nominal.EffectiveTuple rhsType = expandAsEffectiveTuple(afterType);
    // Construct list of assigned variables
    ArrayList<Expr.AssignedVariable> rs = new ArrayList<Expr.AssignedVariable>();
    for (int i = 0; i != rhsType.elements().size(); ++i) {
      Expr element = lv.fields.get(i);
      if (element instanceof Expr.LVal) {
        rs.add(inferAfterType((Expr.LVal) element, rhsType.element(i)));
      } else {
        syntaxError(errorMessage(INVALID_TUPLE_LVAL), filename, element);
      }
    }
    // done
    return rs;
  }

  private Expr.AssignedVariable inferAfterType(Expr.LVal lv, Nominal afterType) {
    if (lv instanceof Expr.AssignedVariable) {
      Expr.AssignedVariable v = (Expr.AssignedVariable) lv;
      v.afterType = afterType;
      return v;
    } else if (lv instanceof Expr.Dereference) {
      Expr.Dereference pa = (Expr.Dereference) lv;
      // The before and after types are the same since an assignment
      // through a reference does not change its type.
      checkIsSubtype(pa.srcType, Nominal.Reference(afterType), lv);
      return inferAfterType((Expr.LVal) pa.src, pa.srcType);
    } else if (lv instanceof Expr.IndexOf) {
      Expr.IndexOf la = (Expr.IndexOf) lv;
      Nominal.EffectiveIndexible srcType = la.srcType;
      afterType = (Nominal) srcType.update(la.index.result(), afterType);
      return inferAfterType((Expr.LVal) la.src, afterType);
    } else if (lv instanceof Expr.FieldAccess) {
      Expr.FieldAccess la = (Expr.FieldAccess) lv;
      Nominal.EffectiveRecord srcType = la.srcType;
      // I know I can modify this hash map, since it's created fresh
      // in Nominal.Record.fields().
      afterType = (Nominal) srcType.update(la.name, afterType);
      return inferAfterType((Expr.LVal) la.src, afterType);
    } else {
      internalFailure("unknown lval: " + lv.getClass().getName(),
          filename, lv);
      return null; // deadcode
    }
  }

  /**
   * Type check a break statement. This requires propagating the current
   * environment to the block destination, to ensure that the actual types of
   * all variables at that point are precise.
   *
   * @param stmt
   *            Statement to type check
   * @param environment
   *            Determines the type of all variables immediately going into
   *            this block
   * @return
   */
  private Environment propagate(Stmt.Break stmt, Environment environment) {
    // FIXME: need to propagate environment to the break destination
    return BOTTOM;
  }

  /**
   * Type check an assume statement. This requires checking that the
   * expression being printed is well-formed and has string type.
   *
   * @param stmt
   *            Statement to type check
   * @param environment
   *            Determines the type of all variables immediately going into
   *            this block
   * @return
   */
  private Environment propagate(Stmt.Debug stmt, Environment environment) {
    stmt.expr = propagate(stmt.expr, environment, current);
    checkIsSubtype(Type.T_STRING, stmt.expr);
    return environment;
  }

  /**
   * Type check a do-while statement.
   *
   * @param stmt
   *            Statement to type check
   * @param environment
   *            Determines the type of all variables immediately going into
   *            this block
   * @return
   */
  private Environment propagate(Stmt.DoWhile stmt, Environment environment) {

    // Iterate to a fixed point
    environment = computeFixedPoint(environment,stmt.body,stmt.condition,true,stmt);

    // Type invariants
    List<Expr> stmt_invariants = stmt.invariants;
    for (int i = 0; i != stmt_invariants.size(); ++i) {
      Expr invariant = stmt_invariants.get(i);
      invariant = propagate(invariant, environment, current);
      stmt_invariants.set(i, invariant);
      checkIsSubtype(Type.T_BOOL, invariant);
    }

    // Type condition assuming its false to represent the terminated loop.
    // This is important if the condition contains a type test, as we'll
    // know that doesn't hold here.
    Pair<Expr, Environment> p = propagateCondition(stmt.condition, false,
        environment, current);
    stmt.condition = p.first();
    environment = p.second();

    return environment;
  }

  /**
   * Type check a <code>for</code> statement.
   *
   * @param stmt
   *            Statement to type check
   * @param environment
   *            Determines the type of all variables immediately going into
   *            this block
   * @return
   */
  private Environment propagate(Stmt.ForAll stmt, Environment environment)
      throws IOException, ResolveError {

    stmt.source = propagate(stmt.source, environment, current);
    Nominal.EffectiveCollection srcType = expandAsEffectiveCollection(stmt.source
        .result());
    stmt.srcType = srcType;

    if (srcType == null) {
      syntaxError(errorMessage(INVALID_SET_OR_LIST_EXPRESSION), filename,
          stmt);
    }

    // At this point, the major task is to determine what the types for the
    // iteration variables declared in the for loop. More than one variable
    // is permitted in some cases.

    Nominal[] elementTypes = new Nominal[stmt.variables.size()];
    if (elementTypes.length == 2 && srcType instanceof Nominal.EffectiveMap) {
      Nominal.EffectiveMap dt = (Nominal.EffectiveMap) srcType;
      elementTypes[0] = dt.key();
      elementTypes[1] = dt.value();
    } else {
      if (elementTypes.length == 1) {
        elementTypes[0] = srcType.element();
      } else {
        syntaxError(errorMessage(VARIABLE_POSSIBLY_UNITIALISED),
            filename, stmt);
      }
    }

    // Update the environment to include those declared variables
    ArrayList<String> stmtVariables = stmt.variables;
    for (int i = 0; i != elementTypes.length; ++i) {
      String var = stmtVariables.get(i);
      if (environment.containsKey(var)) {
        syntaxError(errorMessage(VARIABLE_ALREADY_DEFINED, var),
            filename, stmt);
      }
      environment = environment.declare(var, elementTypes[i], elementTypes[i]);
    }

    // Iterate to a fixed point
    environment = computeFixedPoint(environment,stmt.body,null,false,stmt);

    // Remove the loop variables from the environment, since they are only
    // scoped for the duration of the body but not beyond.
    for (int i = 0; i != elementTypes.length; ++i) {
      String var = stmtVariables.get(i);
      environment = environment.remove(var);
    }

    // Finally, type the invariant
    if (stmt.invariant != null) {
      stmt.invariant = propagate(stmt.invariant, environment, current);
      checkIsSubtype(Type.T_BOOL, stmt.invariant);
    }

    return environment;
  }

  /**
   * Type check an if-statement. To do this, we propagate the environment
   * through both sides of condition expression. Each can produce a different
   * environment in the case that runtime type tests are used. These
   * potentially updated environments are then passed through the true and
   * false blocks which, in turn, produce updated environments. Finally, these
   * two environments are joined back together. The following illustrates:
   *
   * <pre>
   *                    //  Environment
   * function f(int|null x) => int:
   *                    // {x : int|null}
   *    if x is null:
   *                    // {x : null}
   *        x = 0
   *                    // {x : int}
   *    else:
   *                    // {x : int}
   *        x = x + 1
   *                    // {x : int}
   *    // --------------------------------------------------
   *                    // {x : int} o {x : int} => {x : int}
   *    return x
   * </pre>
   *
   * Here, we see that the type of <code>x</code> is initially
   * <code>int|null</code> before the first statement of the function body. On
   * the true branch of the type test this is updated to <code>null</code>,
   * whilst on the false branch it is updated to <code>int</code>. Finally,
   * the type of <code>x</code> at the end of each block is <code>int</code>
   * and, hence, its type after the if-statement is <code>int</code>.
   *
   * @param stmt
   *            Statement to type check
   * @param environment
   *            Determines the type of all variables immediately going into
   *            this block
   * @return
   */

  private Environment propagate(Stmt.IfElse stmt, Environment environment) {

    // First, check condition and apply variable retypings.
    Pair<Expr, Environment> p1, p2;

    p1 = propagateCondition(stmt.condition, true, environment.clone(),
        current);
    p2 = propagateCondition(stmt.condition, false, environment.clone(),
        current);
    stmt.condition = p1.first();

    Environment trueEnvironment = p1.second();
    Environment falseEnvironment = p2.second();

    // Second, update environments for true and false branches
    if (stmt.trueBranch != null && stmt.falseBranch != null) {
      trueEnvironment = propagate(stmt.trueBranch, trueEnvironment);
      falseEnvironment = propagate(stmt.falseBranch, falseEnvironment);
    } else if (stmt.trueBranch != null) {
      trueEnvironment = propagate(stmt.trueBranch, trueEnvironment);
    } else if (stmt.falseBranch != null) {
      trueEnvironment = environment;
      falseEnvironment = propagate(stmt.falseBranch, falseEnvironment);
    }

    // Finally, join results back together
    return trueEnvironment.merge(environment.keySet(), falseEnvironment);
  }

  /**
   * Type check a <code>return</code> statement. If a return expression is
   * given, then we must check that this is well-formed and is a subtype of
   * the enclosing function or method's declared return type. The environment
   * after a return statement is "bottom" because that represents an
   * unreachable program point.
   *
   * @param stmt
   *            Statement to type check
   * @param environment
   *            Determines the type of all variables immediately going into
   *            this block
   * @return
   */
  private Environment propagate(Stmt.Return stmt, Environment environment)
      throws IOException {

    if (stmt.expr != null) {
      stmt.expr = propagate(stmt.expr, environment, current);
      Nominal rhs = stmt.expr.result();
      checkIsSubtype(current.resolvedType().ret(), rhs, stmt.expr);
    } else if(!(current.resolvedType().ret().raw() instanceof Type.Void)) {
      // In this case, we have an unusual situation. A return statement
      // was provided without a return value, but the enclosing method or
      // function requires a return value.
      syntaxError("missing return value",filename,stmt);
    }

    environment.free();
    return BOTTOM;
  }

  /**
   * Type check a <code>skip</code> statement, which has no effect on the
   * environment.
   *
   * @param stmt
   *            Statement to type check
   * @param environment
   *            Determines the type of all variables immediately going into
   *            this block
   * @return
   */
  private Environment propagate(Stmt.Skip stmt, Environment environment) {
    return environment;
  }

  /**
   * Type check a <code>switch</code> statement. This is similar, in some
   * ways, to the handling of if-statements except that we have n code blocks
   * instead of just two. Therefore, we propagate type information through
   * each block, which produces n potentially different environments and these
   * are all joined together to produce the environment which holds after this
   * statement. For example:
   *
   * <pre>
   *                    //  Environment
   * function f(int x) => int|null:
   *    int|null y
   *                    // {x : int, y : void}
   *    switch x:
   *       case 0:
   *                    // {x : int, y : void}
   *           return 0
   *                    // { }
   *       case 1,2,3:
   *                    // {x : int, y : void}
   *           y = x
   *                    // {x : int, y : int}
   *       default:
   *                    // {x : int, y : void}
   *           y = null
   *                    // {x : int, y : null}
   *    // --------------------------------------------------
   *                    // {} o
   *                    // {x : int, y : int} o
   *                    // {x : int, y : null}
   *                    // => {x : int, y : int|null}
   *    return y
   * </pre>
   *
   * Here, the environment after the declaration of <code>y</code> has its
   * actual type as <code>void</code> since no value has been assigned yet.
   * For each of the case blocks, this initial environment is (separately)
   * updated to produce three different environments. Finally, each of these
   * is joined back together to produce the environment going into the
   * <code>return</code> statement.
   *
   * @param stmt
   *            Statement to type check
   * @param environment
   *            Determines the type of all variables immediately going into
   *            this block
   * @return
   */
  private Environment propagate(Stmt.Switch stmt, Environment environment)
      throws IOException {

    stmt.expr = propagate(stmt.expr, environment, current);

    Environment finalEnv = null;
    boolean hasDefault = false;

    for (Stmt.Case c : stmt.cases) {

      // first, resolve the constants

      ArrayList<Constant> values = new ArrayList<Constant>();
      for (Expr e : c.expr) {
        values.add(resolveAsConstant(e, current));
      }
      c.constants = values;

      // second, propagate through the statements

      Environment localEnv = environment.clone();
      localEnv = propagate(c.stmts, localEnv);

      if (finalEnv == null) {
        finalEnv = localEnv;
      } else {
        finalEnv = finalEnv.merge(environment.keySet(), localEnv);
      }

      // third, keep track of whether a default
      hasDefault |= c.expr.isEmpty();
    }

    if (!hasDefault) {

      // in this case, there is no default case in the switch. We must
      // therefore assume that there are values which will fall right
      // through the switch statement without hitting a case. Therefore,
      // we must include the original environment to accound for this.

      finalEnv = finalEnv.merge(environment.keySet(), environment);
    } else {
      environment.free();
    }

    return finalEnv;
  }

  /**
   * Type check a <code>throw</code> statement. We must check that the throw
   * expression is well-formed. The environment after a throw statement is
   * "bottom" because that represents an unreachable program point.
   *
   * @param stmt
   *            Statement to type check
   * @param environment
   *            Determines the type of all variables immediately going into
   *            this block
   * @return
   */
  private Environment propagate(Stmt.Throw stmt, Environment environment) {
    stmt.expr = propagate(stmt.expr, environment, current);
    return BOTTOM;
  }

  /**
   * Type check a try-catch statement.
   *
   * @param stmt
   *            Statement to type check
   * @param environment
   *            Determines the type of all variables immediately going into
   *            this block
   * @return
   */
  private Environment propagate(Stmt.TryCatch stmt, Environment environment)
      throws IOException {

    for (Stmt.Catch handler : stmt.catches) {

      // FIXME: need to deal with handler environments properly!
      try {
        Nominal type = resolveAsType(handler.unresolvedType, current);
        handler.type = type;
        Environment local = environment.clone();
        local = local.declare(handler.variable, type, type);
        propagate(handler.stmts, local);
        local.free();
      } catch (SyntaxError e) {
        throw e;
      } catch (Throwable t) {
        internalFailure(t.getMessage(), filename, handler, t);
      }
    }

    environment = propagate(stmt.body, environment);

    // need to do handlers here

    return environment;
  }

  /**
   * Type check a <code>whiley</code> statement.
   *
   * @param stmt
   *            Statement to type check
   * @param environment
   *            Determines the type of all variables immediately going into
   *            this block
   * @return
   */
  private Environment propagate(Stmt.While stmt, Environment environment) {

    // Determine typing at beginning of loop
    environment = computeFixedPoint(environment,stmt.body,stmt.condition,false,stmt);

    // Type loop invariant(s)
    List<Expr> stmt_invariants = stmt.invariants;
    for (int i = 0; i != stmt_invariants.size(); ++i) {
      Expr invariant = stmt_invariants.get(i);
      invariant = propagate(invariant, environment, current);
      stmt_invariants.set(i, invariant);
      checkIsSubtype(Type.T_BOOL, invariant);
    }

    // Type condition assuming its false to represent the terminated loop.
    // This is important if the condition contains a type test, as we'll
    // know that doesn't hold here.
    Pair<Expr, Environment> p = propagateCondition(stmt.condition, false,
        environment, current);
    stmt.condition = p.first();
    environment = p.second();

    return environment;
  }

  // =========================================================================
  // LVals
  // =========================================================================

  private Expr.LVal propagate(Expr.LVal lval, Environment environment) {
    try {
      if (lval instanceof Expr.AbstractVariable) {
        Expr.AbstractVariable av = (Expr.AbstractVariable) lval;
        Nominal p = environment.getCurrentType(av.var);
        if (p == null) {
          syntaxError(errorMessage(UNKNOWN_VARIABLE), filename, lval);
        }
        Expr.AssignedVariable lv = new Expr.AssignedVariable(av.var,
            av.attributes());
        lv.type = p;
        return lv;
      } else if (lval instanceof Expr.RationalLVal) {
        Expr.RationalLVal av = (Expr.RationalLVal) lval;
        av.numerator = propagate(av.numerator, environment);
        av.denominator = propagate(av.denominator, environment);
        return av;
      } else if (lval instanceof Expr.Dereference) {
        Expr.Dereference pa = (Expr.Dereference) lval;
        Expr.LVal src = propagate((Expr.LVal) pa.src, environment);
        pa.src = src;
        pa.srcType = expandAsReference(src.result());
        return pa;
      } else if (lval instanceof Expr.IndexOf) {
        // this indicates either a list, string or dictionary update
        Expr.IndexOf ai = (Expr.IndexOf) lval;
        Expr.LVal src = propagate((Expr.LVal) ai.src, environment);
        Expr index = propagate(ai.index, environment, current);
        ai.src = src;
        ai.index = index;
        Nominal.EffectiveIndexible srcType = expandAsEffectiveMap(src
            .result());
        if (srcType == null) {
          syntaxError(errorMessage(INVALID_LVAL_EXPRESSION),
              filename, lval);
        }
        ai.srcType = srcType;
        return ai;
      } else if (lval instanceof Expr.FieldAccess) {
        // this indicates a record update
        Expr.FieldAccess ad = (Expr.FieldAccess) lval;
        Expr.LVal src = propagate((Expr.LVal) ad.src, environment);
        Expr.FieldAccess ra = new Expr.FieldAccess(src, ad.name,
            ad.attributes());
        Nominal.EffectiveRecord srcType = expandAsEffectiveRecord(src
            .result());
        if (srcType == null) {
          syntaxError(errorMessage(INVALID_LVAL_EXPRESSION),
              filename, lval);
        } else if (srcType.field(ra.name) == null) {
          syntaxError(errorMessage(RECORD_MISSING_FIELD, ra.name),
              filename, lval);
        }
        ra.srcType = srcType;
        return ra;
      } else if (lval instanceof Expr.Tuple) {
        // this indicates a tuple update
        Expr.Tuple tup = (Expr.Tuple) lval;
        ArrayList<Nominal> elements = new ArrayList<Nominal>();
        for (int i = 0; i != tup.fields.size(); ++i) {
          Expr element = tup.fields.get(i);
          if (element instanceof Expr.LVal) {
            element = propagate((Expr.LVal) element, environment);
            tup.fields.set(i, element);
            elements.add(element.result());
          } else {
            syntaxError(errorMessage(INVALID_LVAL_EXPRESSION),
                filename, lval);
          }
        }

        tup.type = Nominal.Tuple(elements);
        return tup;
      }
    } catch (SyntaxError e) {
      throw e;
    } catch (Throwable e) {
      internalFailure(e.getMessage(), filename, lval, e);
      return null; // dead code
    }
    internalFailure("unknown lval: " + lval.getClass().getName(), filename,
        lval);
    return null; // dead code
  }

  /**
   * The purpose of this method is to add variable names declared within a
   * type pattern to the given environment. For example, as follows:
   *
   * <pre>
   * define tup as {int x, int y} where x < y
   * </pre>
   *
   * In this case, <code>x</code> and <code>y</code> are variable names
   * declared as part of the pattern.
   *
   * <p>
   * Note, variables are both declared and initialised with the given type. In
   * some cases (e.g. parameters), this makes sense. In other cases (e.g.
   * local variable declarations), it does not. In the latter, the variable
   * should then be updated with an appropriate type.
   * </p>
   *
   * @param src
   * @param t
   * @param environment
   */
  private Environment addDeclaredVariables(TypePattern pattern,
      Environment environment, WhileyFile.Context context) {
    if (pattern instanceof TypePattern.Union) {
      // FIXME: in principle, we can do better here. However, I leave this
      // unusual case for the future.
    } else if (pattern instanceof TypePattern.Intersection) {
      // FIXME: in principle, we can do better here. However, I leave this
      // unusual case for the future.
    } else if (pattern instanceof TypePattern.Rational) {
      TypePattern.Rational tp = (TypePattern.Rational) pattern;
      environment = addDeclaredVariables(tp.numerator, environment,
          context);
      environment = addDeclaredVariables(tp.denominator, environment,
          context);
    } else if (pattern instanceof TypePattern.Record) {
      TypePattern.Record tp = (TypePattern.Record) pattern;
      for (TypePattern element : tp.elements) {
        environment = addDeclaredVariables(element, environment,
            context);
      }
    } else if (pattern instanceof TypePattern.Tuple) {
      TypePattern.Tuple tp = (TypePattern.Tuple) pattern;
      for (TypePattern element : tp.elements) {
        environment = addDeclaredVariables(element, environment,
            context);
      }
    } else {
      TypePattern.Leaf lp = (TypePattern.Leaf) pattern;

      if (lp.var != null) {
        Nominal type = resolveAsType(pattern.toSyntacticType(), context);
        environment = environment.declare(lp.var.var, type, type);
      }
    }

    return environment;
  }

  /**
   * Set the current type of one or more variables in a given type pattern.
   * The current type may differ from the declared type at a given program
   * point, depending upon what is known at that point.
   *
   * @param pattern The type pattern containing those variables to update
   * @param type The type that the pattern (as a whole) should be updated to
   * @param environment The environment which should be updated
   * @return
   */
  private Environment setCurrentType(TypePattern pattern, Nominal type,
      Environment environment) throws ResolveError, IOException {
    if (pattern instanceof TypePattern.Union) {
      // FIXME: in principle, we can do better here. However, I leave this
      // unusual case for the future.
    } else if (pattern instanceof TypePattern.Intersection) {
      // FIXME: in principle, we can do better here. However, I leave this
      // unusual case for the future.
    } else if (pattern instanceof TypePattern.Rational) {
      TypePattern.Rational tp = (TypePattern.Rational) pattern;
      environment = setCurrentType(tp.numerator, Nominal.T_INT, environment);
      environment = setCurrentType(tp.denominator, Nominal.T_INT, environment);
    } else if (pattern instanceof TypePattern.Record) {
      TypePattern.Record tp = (TypePattern.Record) pattern;
      Nominal.EffectiveRecord tt = expandAsEffectiveRecord(type);
      for (TypePattern.Leaf element : tp.elements) {
        Nominal elementType = tt.field(element.var.var);
        environment = setCurrentType(element, elementType, environment);
      }
    } else if (pattern instanceof TypePattern.Tuple) {
      TypePattern.Tuple tp = (TypePattern.Tuple) pattern;
      Nominal.EffectiveTuple tt = expandAsEffectiveTuple(type);
      for(int i=0;i!=tp.elements.size();++i) {
        TypePattern element = tp.elements.get(i);
        Nominal elementType = tt.element(i);
        environment = setCurrentType(element, elementType, environment);
      }
    } else {
      TypePattern.Leaf lp = (TypePattern.Leaf) pattern;

      if (lp.var != null) {
        environment = environment.update(lp.var.var, type);
      }
    }

    return environment;
  }

  // =========================================================================
  // Condition
  // =========================================================================

  /**
   * <p>
   * Propagate type information through an expression being used as a
   * condition, whilst checking it is well-typed at the same time. When used
   * as a condition (e.g. of an if-statement) an expression may update the
   * environment in accordance with any type tests used within. This is
   * important to ensure that variables are retyped in e.g. if-statements. For
   * example:
   * </p>
   *
   * <pre>
   * if x is int && x >= 0
   *    // x is int
   * else:
   *    //
   * </pre>
   * <p>
   * Here, the if-condition must update the type of x in the true branch, but
   * *cannot* update the type of x in the false branch.
   * </p>
   * <p>
   * To handle conditions on the false branch, this function uses a sign flag
   * rather than expanding them using DeMorgan's laws (for efficiency). When
   * determining type for the false branch, the sign flag is initially false.
   * This prevents falsely concluding that e.g. "x is int" holds in the false
   * branch.
   * </p>
   *
   * @param expr
   *            Condition expression to type check and propagate through
   * @param sign
   *            Indicates how expression should be treated. If true, then
   *            expression is treated "as is"; if false, then expression
   *            should be treated as negated
   * @param environment
   *            Determines the type of all variables immediately going into
   *            this expression
   * @param context
   *            Enclosing context of this expression (e.g. type declaration,
   *            function declaration, etc)
   * @return
   */
  public Pair<Expr, Environment> propagateCondition(Expr expr, boolean sign,
      Environment environment, Context context) {

    // Split up into the compound and non-compound forms.

    if (expr instanceof Expr.UnOp) {
      return propagateCondition((Expr.UnOp) expr, sign, environment,
          context);
    } else if (expr instanceof Expr.BinOp) {
      return propagateCondition((Expr.BinOp) expr, sign, environment,
          context);
    } else {
      // For non-compound forms, can just default back to the base rules
      // for general expressions.
      expr = propagate(expr, environment, context);
      checkIsSubtype(Type.T_BOOL, expr, context);
      return new Pair<Expr, Environment>(expr, environment);
    }
  }

  /**
   * <p>
   * Propagate type information through a unary expression being used as a
   * condition and, in fact, only logical not is syntactically valid here.
   * </p>
   *
   * @param expr
   *            Condition expression to type check and propagate through
   * @param sign
   *            Indicates how expression should be treated. If true, then
   *            expression is treated "as is"; if false, then expression
   *            should be treated as negated
   * @param environment
   *            Determines the type of all variables immediately going into
   *            this expression
   * @param context
   *            Enclosing context of this expression (e.g. type declaration,
   *            function declaration, etc)
   * @return
   */
  private Pair<Expr, Environment> propagateCondition(Expr.UnOp expr,
      boolean sign, Environment environment, Context context) {
    Expr.UnOp uop = (Expr.UnOp) expr;

    // Check whether we have logical not

    if (uop.op == Expr.UOp.NOT) {
      Pair<Expr, Environment> p = propagateCondition(uop.mhs, !sign,
          environment, context);
      uop.mhs = p.first();
      checkIsSubtype(Type.T_BOOL, uop.mhs, context);
      uop.type = Nominal.T_BOOL;
      return new Pair(uop, p.second());
    } else {
      // Nothing else other than logical not is valid at this point.
      syntaxError(errorMessage(INVALID_BOOLEAN_EXPRESSION), context, expr);
      return null; // deadcode
    }
  }

  /**
   * <p>
   * Propagate type information through a binary expression being used as a
   * condition. In this case, only logical connectives ("&&", "||", "^") and
   * comparators (e.g. "==", "<=", etc) are permitted here.
   * </p>
   *
   * @param expr
   *            Condition expression to type check and propagate through
   * @param sign
   *            Indicates how expression should be treated. If true, then
   *            expression is treated "as is"; if false, then expression
   *            should be treated as negated
   * @param environment
   *            Determines the type of all variables immediately going into
   *            this expression
   * @param context
   *            Enclosing context of this expression (e.g. type declaration,
   *            function declaration, etc)
   * @return
   */
  private Pair<Expr, Environment> propagateCondition(Expr.BinOp bop,
      boolean sign, Environment environment, Context context) {
    Expr.BOp op = bop.op;

    // Split into the two broard cases: logical connectives and primitives.

    switch (op) {
    case AND:
    case OR:
    case XOR:
      return resolveNonLeafCondition(bop, sign, environment, context);
    case EQ:
    case NEQ:
    case LT:
    case LTEQ:
    case GT:
    case GTEQ:
    case ELEMENTOF:
    case SUBSET:
    case SUBSETEQ:
    case IS:
      return resolveLeafCondition(bop, sign, environment, context);
    default:
      syntaxError(errorMessage(INVALID_BOOLEAN_EXPRESSION), context, bop);
      return null; // dead code
    }
  }

  /**
   * <p>
   * Propagate type information through a binary expression being used as a
   * logical connective ("&&", "||", "^").
   * </p>
   *
   * @param bop
   *            Binary operator for this expression.
   * @param sign
   *            Indicates how expression should be treated. If true, then
   *            expression is treated "as is"; if false, then expression
   *            should be treated as negated
   * @param environment
   *            Determines the type of all variables immediately going into
   *            this expression
   * @param context
   *            Enclosing context of this expression (e.g. type declaration,
   *            function declaration, etc)
   * @return
   */
  private Pair<Expr, Environment> resolveNonLeafCondition(Expr.BinOp bop,
      boolean sign, Environment environment, Context context) {
    Expr.BOp op = bop.op;
    Pair<Expr, Environment> p;
    boolean followOn = (sign && op == Expr.BOp.AND)
        || (!sign && op == Expr.BOp.OR);

    if (followOn) {
      // In this case, the environment feeds directly from the result of
      // propagating through the lhs into the rhs, and then into the
      // result of this expression. This means that updates to the
      // environment by either the lhs or rhs are visible outside of this
      // method.
      p = propagateCondition(bop.lhs, sign, environment.clone(), context);
      bop.lhs = p.first();
      p = propagateCondition(bop.rhs, sign, p.second(), context);
      bop.rhs = p.first();
      environment = p.second();
    } else {
      // We could do better here
      p = propagateCondition(bop.lhs, sign, environment.clone(), context);
      bop.lhs = p.first();
      Environment local = p.second();
      // Recompute the lhs assuming that it is false. This is necessary to
      // generate the right environment going into the rhs, which is only
      // evaluated if the lhs is false. For example:
      //
      // if(e is int && e > 0):
      // //
      // else:
      // // <-
      //
      // In the false branch, we're determing the environment for
      // !(e is int && e > 0). This becomes !(e is int) || (e <= 0) where
      // on the rhs we require (e is int).
      p = propagateCondition(bop.lhs, !sign, environment.clone(), context);
      // Note, the following is intentional since we're specifically
      // considering the case where the lhs was false, and this case is
      // true.
      p = propagateCondition(bop.rhs, sign, p.second(), context);
      bop.rhs = p.first();
      environment = local.merge(local.keySet(), p.second());
    }

    checkIsSubtype(Type.T_BOOL, bop.lhs, context);
    checkIsSubtype(Type.T_BOOL, bop.rhs, context);
    bop.srcType = Nominal.T_BOOL;

    return new Pair<Expr, Environment>(bop, environment);
  }

  /**
   * <p>
   * Propagate type information through a binary expression being used as a
   * comparators (e.g. "==", "<=", etc).
   * </p>
   *
   * @param bop
   *            Binary operator for this expression.
   * @param sign
   *            Indicates how expression should be treated. If true, then
   *            expression is treated "as is"; if false, then expression
   *            should be treated as negated
   * @param environment
   *            Determines the type of all variables immediately going into
   *            this expression
   * @param context
   *            Enclosing context of this expression (e.g. type declaration,
   *            function declaration, etc)
   * @return
   */
  private Pair<Expr, Environment> resolveLeafCondition(Expr.BinOp bop,
      boolean sign, Environment environment, Context context) {
    Expr.BOp op = bop.op;

    Expr lhs = propagate(bop.lhs, environment, context);
    Expr rhs = propagate(bop.rhs, environment, context);
    bop.lhs = lhs;
    bop.rhs = rhs;

    Type lhsRawType = lhs.result().raw();
    Type rhsRawType = rhs.result().raw();

    switch (op) {
    case IS:
      // this one is slightly more difficult. In the special case that
      // we have a type constant on the right-hand side then we want
      // to check that it makes sense. Otherwise, we just check that
      // it has type meta.

      if (rhs instanceof Expr.TypeVal) {
        // yes, right-hand side is a constant
        Expr.TypeVal tv = (Expr.TypeVal) rhs;
        Nominal unconstrainedTestType = resolveAsUnconstrainedType(
            tv.unresolvedType, context);

        /**
         * Determine the types guaranteed to hold on the true and false
         * branches respectively. We have to use the negated
         * unconstrainedTestType for the false branch because only that
         * is guaranteed if the test fails. For example:
         *
         * <pre>
         * define nat as int where $ &gt;= 0
         * define listnat as [int]|nat
         *
         * int f([int]|int x):
         *    if x if listnat:
         *        x : [int]|int
         *        ...
         *    else:
         *        x : int
         * </pre>
         *
         * The unconstrained type of listnat is [int], since nat is a
         * constrained type.
         */
        Nominal glbForFalseBranch = Nominal.intersect(lhs.result(),
            Nominal.Negation(unconstrainedTestType));
        Nominal glbForTrueBranch = Nominal.intersect(lhs.result(),
            tv.type);

        if (glbForFalseBranch.raw() == Type.T_VOID) {
          // DEFINITE TRUE CASE
          syntaxError(errorMessage(BRANCH_ALWAYS_TAKEN), context, bop);
        } else if (glbForTrueBranch.raw() == Type.T_VOID) {
          // DEFINITE FALSE CASE
          syntaxError(
              errorMessage(INCOMPARABLE_OPERANDS, lhsRawType,
                  tv.type.raw()), context, bop);
        }

        // Finally, if the lhs is local variable then update its
        // type in the resulting environment.
        if (lhs instanceof Expr.LocalVariable) {
          Expr.LocalVariable lv = (Expr.LocalVariable) lhs;
          Nominal newType;
          if (sign) {
            newType = glbForTrueBranch;
          } else {
            newType = glbForFalseBranch;
          }
          environment = environment.update(lv.var, newType);
        }
      } else {
        // In this case, we can't update the type of the lhs since
        // we don't know anything about the rhs. It may be possible
        // to support bounds here in order to do that, but frankly
        // that's future work :)
        checkIsSubtype(Type.T_META, rhs, context);
      }

      bop.srcType = lhs.result();
      break;
    case ELEMENTOF:
      Type.EffectiveList listType = rhsRawType instanceof Type.EffectiveList ? (Type.EffectiveList) rhsRawType
          : null;
      Type.EffectiveSet setType = rhsRawType instanceof Type.EffectiveSet ? (Type.EffectiveSet) rhsRawType
          : null;

      if (listType != null
          && !Type.isSubtype(listType.element(),
              lhsRawType)) {
        syntaxError(
            errorMessage(INCOMPARABLE_OPERANDS, lhsRawType,
                listType.element()), context, bop);
      } else if (setType != null
          && !Type.isSubtype(setType.element(),
              lhsRawType)) {
        syntaxError(
            errorMessage(INCOMPARABLE_OPERANDS, lhsRawType,
                setType.element()), context, bop);
      }
      bop.srcType = rhs.result();
      break;
    case SUBSET:
    case SUBSETEQ:
      checkIsSubtype(Type.T_SET_ANY, rhs, context);
      checkIsSubtype(lhs.result(), rhs, context);
      //
      if (!lhsRawType.equals(rhsRawType)) {
        syntaxError(
            errorMessage(INCOMPARABLE_OPERANDS, lhsRawType,
                rhsRawType), filename, bop);
        return null;
      } else {
        bop.srcType = lhs.result();
      }
      break;
    case LT:
    case LTEQ:
    case GTEQ:
    case GT:
      checkSuptypes(lhs, context, Nominal.T_INT, Nominal.T_REAL,
          Nominal.T_CHAR);
      checkSuptypes(rhs, context, Nominal.T_INT, Nominal.T_REAL,
          Nominal.T_CHAR);
      //
      if (!lhsRawType.equals(rhsRawType)) {
        syntaxError(
            errorMessage(INCOMPARABLE_OPERANDS, lhsRawType,
                rhsRawType), filename, bop);
        return null;
      } else {
        bop.srcType = lhs.result();
      }
      break;
    case NEQ:
      // following is a sneaky trick for the special case below
      sign = !sign;
    case EQ:

      // first, check for special case of e.g. x != null. This is then
      // treated the same as !(x is null)

      if (lhs instanceof Expr.LocalVariable
          && rhs instanceof Expr.Constant
          && ((Expr.Constant) rhs).value == Constant.V_NULL) {
        // bingo, special case
        Expr.LocalVariable lv = (Expr.LocalVariable) lhs;
        Nominal newType;
        Nominal glb = Nominal.intersect(lhs.result(), Nominal.T_NULL);
        if (glb.raw() == Type.T_VOID) {
          syntaxError(
              errorMessage(INCOMPARABLE_OPERANDS, lhs.result()
                  .raw(), Type.T_NULL), context, bop);
          return null;
        } else if (sign) {
          newType = glb;
        } else {
          newType = Nominal
              .intersect(lhs.result(), Nominal.T_NOTNULL);
        }
        bop.srcType = lhs.result();
        environment = environment.update(lv.var, newType);
      } else {
        // handle general case
        if (Type.isSubtype(lhsRawType, rhsRawType)) {
          bop.srcType = lhs.result();
        } else if (Type.isSubtype(rhsRawType,
            lhsRawType)) {
          bop.srcType = rhs.result();
        } else {
          syntaxError(
              errorMessage(INCOMPARABLE_OPERANDS, lhsRawType,
                  rhsRawType), context, bop);
          return null; // dead code
        }
      }
    }

    return new Pair<Expr, Environment>(bop, environment);
  }

  // =========================================================================
  // Expressions
  // =========================================================================

  /**
   * Propagate types through a given expression, whilst checking that it is
   * well typed. In this case, any use of a runtime type test cannot effect
   * callers of this function.
   *
   * @param expr
   *            Expression to propagate types through.
   * @param environment
   *            Determines the type of all variables immediately going into
   *            this expression
   * @param context
   *            Enclosing context of this expression (e.g. type declaration,
   *            function declaration, etc)
   * @return
   */
  public Expr propagate(Expr expr, Environment environment, Context context) {

    try {
      if (expr instanceof Expr.BinOp) {
        return propagate((Expr.BinOp) expr, environment, context);
      } else if (expr instanceof Expr.UnOp) {
        return propagate((Expr.UnOp) expr, environment, context);
      } else if (expr instanceof Expr.Comprehension) {
        return propagate((Expr.Comprehension) expr, environment,
            context);
      } else if (expr instanceof Expr.Constant) {
        return propagate((Expr.Constant) expr, environment, context);
      } else if (expr instanceof Expr.Cast) {
        return propagate((Expr.Cast) expr, environment, context);
      } else if (expr instanceof Expr.ConstantAccess) {
        return propagate((Expr.ConstantAccess) expr, environment,
            context);
      } else if (expr instanceof Expr.FieldAccess) {
        return propagate((Expr.FieldAccess) expr, environment, context);
      } else if (expr instanceof Expr.Map) {
        return propagate((Expr.Map) expr, environment, context);
      } else if (expr instanceof Expr.AbstractFunctionOrMethod) {
        return propagate((Expr.AbstractFunctionOrMethod) expr,
            environment, context);
      } else if (expr instanceof Expr.AbstractInvoke) {
        return propagate((Expr.AbstractInvoke) expr, environment,
            context);
      } else if (expr instanceof Expr.AbstractIndirectInvoke) {
        return propagate((Expr.AbstractIndirectInvoke) expr,
            environment, context);
      } else if (expr instanceof Expr.IndexOf) {
        return propagate((Expr.IndexOf) expr, environment, context);
      } else if (expr instanceof Expr.Lambda) {
        return propagate((Expr.Lambda) expr, environment, context);
      } else if (expr instanceof Expr.LengthOf) {
        return propagate((Expr.LengthOf) expr, environment, context);
      } else if (expr instanceof Expr.LocalVariable) {
        return propagate((Expr.LocalVariable) expr, environment,
            context);
      } else if (expr instanceof Expr.List) {
        return propagate((Expr.List) expr, environment, context);
      } else if (expr instanceof Expr.Set) {
        return propagate((Expr.Set) expr, environment, context);
      } else if (expr instanceof Expr.SubList) {
        return propagate((Expr.SubList) expr, environment, context);
      } else if (expr instanceof Expr.SubString) {
        return propagate((Expr.SubString) expr, environment, context);
      } else if (expr instanceof Expr.Dereference) {
        return propagate((Expr.Dereference) expr, environment, context);
      } else if (expr instanceof Expr.Record) {
        return propagate((Expr.Record) expr, environment, context);
      } else if (expr instanceof Expr.New) {
        return propagate((Expr.New) expr, environment, context);
      } else if (expr instanceof Expr.Tuple) {
        return propagate((Expr.Tuple) expr, environment, context);
      } else if (expr instanceof Expr.TypeVal) {
        return propagate((Expr.TypeVal) expr, environment, context);
      }
    } catch (ResolveError e) {
      syntaxError(errorMessage(RESOLUTION_ERROR, e.getMessage()),
          context, expr, e);
    } catch (SyntaxError e) {
      throw e;
    } catch (Throwable e) {
      internalFailure(e.getMessage(), context, expr, e);
      return null; // dead code
    }
    internalFailure("unknown expression: " + expr.getClass().getName(),
        context, expr);
    return null; // dead code
  }

  private Expr propagate(Expr.BinOp expr, Environment environment,
      Context context) throws IOException {

    // TODO: split binop into arithmetic and conditional operators. This
    // would avoid the following case analysis since conditional binary
    // operators and arithmetic binary operators actually behave quite
    // differently.

    switch (expr.op) {
    case AND:
    case OR:
    case XOR:
    case EQ:
    case NEQ:
    case LT:
    case LTEQ:
    case GT:
    case GTEQ:
    case ELEMENTOF:
    case SUBSET:
    case SUBSETEQ:
    case IS:
      return propagateCondition(expr, true, environment, context).first();
    }

    Expr lhs = propagate(expr.lhs, environment, context);
    Expr rhs = propagate(expr.rhs, environment, context);
    expr.lhs = lhs;
    expr.rhs = rhs;
    Type lhsRawType = lhs.result().raw();
    Type rhsRawType = rhs.result().raw();

    boolean lhs_set = Type.isSubtype(Type.T_SET_ANY,
        lhsRawType);
    boolean rhs_set = Type.isSubtype(Type.T_SET_ANY,
        rhsRawType);
    boolean lhs_list = Type.isSubtype(Type.T_LIST_ANY,
        lhsRawType);
    boolean rhs_list = Type.isSubtype(Type.T_LIST_ANY,
        rhsRawType);
    boolean lhs_str = Type.isSubtype(Type.T_STRING, lhsRawType);
    boolean rhs_str = Type.isSubtype(Type.T_STRING, rhsRawType);

    Type srcType;

    if (lhs_str || rhs_str) {

      switch (expr.op) {
      case LISTAPPEND:
        expr.op = Expr.BOp.STRINGAPPEND;
      case STRINGAPPEND:
        break;
      default:
        syntaxError("Invalid string operation: " + expr.op, context,
            expr);
      }

      srcType = Type.T_STRING;
    } else if (lhs_list && rhs_list) {
      checkIsSubtype(Type.T_LIST_ANY, lhs, context);
      checkIsSubtype(Type.T_LIST_ANY, rhs, context);
      Type.EffectiveList lel = (Type.EffectiveList) lhsRawType;
      Type.EffectiveList rel = (Type.EffectiveList) rhsRawType;

      switch (expr.op) {
      case LISTAPPEND:
        srcType = Type.List(Type.Union(lel.element(), rel.element()),
            false);
        break;
      default:
        syntaxError("invalid list operation: " + expr.op, context, expr);
        return null; // dead-code
      }
    } else if (lhs_set && rhs_set) {
      checkIsSubtype(Type.T_SET_ANY, lhs, context);
      checkIsSubtype(Type.T_SET_ANY, rhs, context);

      // FIXME: something tells me there should be a function for doing
      // this. Perhaps effectiveSetType?

      if (lhs_list) {
        Type.EffectiveList tmp = (Type.EffectiveList) lhsRawType;
        lhsRawType = Type.Set(tmp.element(), false);
      }

      if (rhs_list) {
        Type.EffectiveList tmp = (Type.EffectiveList) rhsRawType;
        rhsRawType = Type.Set(tmp.element(), false);
      }

      // FIXME: loss of nominal information here
      Type.EffectiveSet ls = (Type.EffectiveSet) lhsRawType;
      Type.EffectiveSet rs = (Type.EffectiveSet) rhsRawType;

      switch (expr.op) {
      case ADD:
        expr.op = Expr.BOp.UNION;
      case UNION:
        // TODO: this forces unnecessary coercions, which would be
        // good to remove.
        srcType = Type.Set(Type.Union(ls.element(), rs.element()),
            false);
        break;
      case BITWISEAND:
        expr.op = Expr.BOp.INTERSECTION;
      case INTERSECTION:
        // FIXME: this is just plain wierd.
        if (Type.isSubtype(lhsRawType, rhsRawType)) {
          srcType = rhsRawType;
        } else {
          srcType = lhsRawType;
        }
        break;
      case SUB:
        expr.op = Expr.BOp.DIFFERENCE;
      case DIFFERENCE:
        srcType = lhsRawType;
        break;
      default:
        syntaxError("invalid set operation: " + expr.op, context, expr);
        return null; // deadcode
      }
    } else {
      switch (expr.op) {
      case IS:
      case AND:
      case OR:
      case XOR:
        return propagateCondition(expr, true, environment, context)
            .first();
      case BITWISEAND:
      case BITWISEOR:
      case BITWISEXOR:
        checkIsSubtype(Type.T_BYTE, lhs, context);
        checkIsSubtype(Type.T_BYTE, rhs, context);
        srcType = Type.T_BYTE;
        break;
      case LEFTSHIFT:
      case RIGHTSHIFT:
        checkIsSubtype(Type.T_BYTE, lhs, context);
        checkIsSubtype(Type.T_INT, rhs, context);
        srcType = Type.T_BYTE;
        break;
      case RANGE:
        checkIsSubtype(Type.T_INT, lhs, context);
        checkIsSubtype(Type.T_INT, rhs, context);
        srcType = Type.List(Type.T_INT, false);
        break;
      case REM:
        checkIsSubtype(Type.T_INT, lhs, context);
        checkIsSubtype(Type.T_INT, rhs, context);
        srcType = Type.T_INT;
        break;
      default:
        // all other operations go through here
        checkSuptypes(lhs, context, Nominal.T_INT, Nominal.T_REAL,
            Nominal.T_CHAR);
        checkSuptypes(rhs, context, Nominal.T_INT, Nominal.T_REAL,
            Nominal.T_CHAR);
        //
        if (!lhsRawType.equals(rhsRawType)) {
          syntaxError(
              errorMessage(INCOMPARABLE_OPERANDS, lhsRawType,
                  rhsRawType), filename, expr);
          return null;
        } else {
          srcType = lhsRawType;
        }
      }
    }

    // FIXME: loss of nominal information
    expr.srcType = Nominal.construct(srcType, srcType);

    return expr;
  }

  private Expr propagate(Expr.UnOp expr, Environment environment,
      Context context) throws IOException {

    if (expr.op == Expr.UOp.NOT) {
      // hand off to special method for conditions
      return propagateCondition(expr, true, environment, context).first();
    }

    Expr src = propagate(expr.mhs, environment, context);
    expr.mhs = src;

    switch (expr.op) {
    case NEG:
      checkSuptypes(src, context, Nominal.T_INT, Nominal.T_REAL);
      break;
    case INVERT:
      checkIsSubtype(Type.T_BYTE, src, context);
      break;

    default:
      internalFailure(
          "unknown operator: " + expr.op.getClass().getName(),
          context, expr);
    }

    expr.type = src.result();

    return expr;
  }

  private Expr propagate(Expr.Comprehension expr, Environment environment,
      Context context) throws IOException, ResolveError {

    ArrayList<Pair<String, Expr>> sources = expr.sources;
    Environment local = environment.clone();
    for (int i = 0; i != sources.size(); ++i) {
      Pair<String, Expr> p = sources.get(i);
      Expr e = propagate(p.second(), local, context);
      p = new Pair<String, Expr>(p.first(), e);
      sources.set(i, p);
      Nominal type = e.result();
      Nominal.EffectiveCollection colType = expandAsEffectiveCollection(type);
      if (colType == null) {
        syntaxError(errorMessage(INVALID_SET_OR_LIST_EXPRESSION),
            context, e);
        return null; // dead code
      }
      // update environment for subsequent source expressions, the
      // condition and the value.
      local = local.declare(p.first(), colType.element(), colType.element());
    }

    if (expr.condition != null) {
      expr.condition = propagate(expr.condition, local, context);
    }

    if (expr.cop == Expr.COp.SETCOMP || expr.cop == Expr.COp.LISTCOMP) {
      expr.value = propagate(expr.value, local, context);
      expr.type = Nominal.Set(expr.value.result(), false);
    } else {
      expr.type = Nominal.T_BOOL;
    }

    local.free();

    return expr;
  }

  private Expr propagate(Expr.Constant expr, Environment environment,
      Context context) {
    return expr;
  }

  private Expr propagate(Expr.Cast c, Environment environment, Context context)
      throws IOException {
    c.expr = propagate(c.expr, environment, context);
    c.type = resolveAsType(c.unresolvedType, context);
    Type from = c.expr.result().raw();
    Type to = c.type.raw();
    if (!Type.isExplicitCoerciveSubtype(to, from)) {
      syntaxError(errorMessage(SUBTYPE_ERROR, to, from), context, c);
    }
    return c;
  }

  private Expr propagate(Expr.AbstractFunctionOrMethod expr,
      Environment environment, Context context) throws IOException, ResolveError {

    if (expr instanceof Expr.FunctionOrMethod) {
      return expr;
    }

    Pair<NameID, Nominal.FunctionOrMethod> p;

    if (expr.paramTypes != null) {
      ArrayList<Nominal> paramTypes = new ArrayList<Nominal>();
      for (SyntacticType t : expr.paramTypes) {
        paramTypes.add(resolveAsType(t, context));
      }
      // FIXME: clearly a bug here in the case of message reference
      p = (Pair<NameID, Nominal.FunctionOrMethod>) resolveAsFunctionOrMethod(
          expr.name, paramTypes, context);
    } else {
      p = resolveAsFunctionOrMethod(expr.name, context);
    }

    expr = new Expr.FunctionOrMethod(p.first(), expr.paramTypes,
        expr.attributes());
    expr.type = p.second();
    return expr;
  }

  private Expr propagate(Expr.Lambda expr, Environment environment,
      Context context) throws IOException {

    ArrayList<Type> rawTypes = new ArrayList<Type>();
    ArrayList<Type> nomTypes = new ArrayList<Type>();

    for (WhileyFile.Parameter p : expr.parameters) {
      Nominal n = resolveAsType(p.type, context);
      rawTypes.add(n.raw());
      nomTypes.add(n.nominal());
      // Now, update the environment to include those declared variables
      String var = p.name();
      if (environment.containsKey(var)) {
        syntaxError(errorMessage(VARIABLE_ALREADY_DEFINED, var),
            context, p);
      }
      environment = environment.declare(var, n, n);
    }

    expr.body = propagate(expr.body, environment, context);

    Type.FunctionOrMethod rawType;
    Type.FunctionOrMethod nomType;

    if (Exprs.isPure(expr.body, context)) {
      rawType = Type.Function(expr.body.result().raw(), Type.T_VOID,
          rawTypes);
      nomType = Type.Function(expr.body.result().nominal(), Type.T_VOID,
          nomTypes);
    } else {
      rawType = Type.Method(expr.body.result().raw(), Type.T_VOID,
          rawTypes);
      nomType = Type.Method(expr.body.result().nominal(), Type.T_VOID,
          nomTypes);
    }

    expr.type = (Nominal.FunctionOrMethod) Nominal.construct(nomType,
        rawType);
    return expr;
  }

  private Expr propagate(Expr.AbstractIndirectInvoke expr,
      Environment environment, Context context) throws IOException, ResolveError {

    expr.src = propagate(expr.src, environment, context);
    Nominal type = expr.src.result();
    if (!(type instanceof Nominal.FunctionOrMethod)) {
      syntaxError("function or method type expected", context, expr.src);
    }

    Nominal.FunctionOrMethod funType = (Nominal.FunctionOrMethod) type;
    List<Nominal> paramTypes = funType.params();
    ArrayList<Expr> exprArgs = expr.arguments;

    if (paramTypes.size() != exprArgs.size()) {
      syntaxError(
          "insufficient arguments for function or method invocation",
          context, expr.src);
    }

    for (int i = 0; i != exprArgs.size(); ++i) {
      Nominal pt = paramTypes.get(i);
      Expr arg = propagate(exprArgs.get(i), environment, context);
      checkIsSubtype(pt, arg, context);
      exprArgs.set(i, arg);

    }

    if (funType instanceof Nominal.Function) {
      Expr.IndirectFunctionCall ifc = new Expr.IndirectFunctionCall(
          expr.src, exprArgs, expr.attributes());
      ifc.functionType = (Nominal.Function) funType;
      return ifc;
    } else {
      Expr.IndirectMethodCall imc = new Expr.IndirectMethodCall(expr.src,
          exprArgs, expr.attributes());
      imc.methodType = (Nominal.Method) funType;
      return imc;
    }

  }

  private Expr propagate(Expr.AbstractInvoke expr, Environment environment,
      Context context) throws IOException, ResolveError {

    // first, resolve through receiver and parameters.

    Path.ID qualification = expr.qualification;
    ArrayList<Expr> exprArgs = expr.arguments;
    ArrayList<Nominal> paramTypes = new ArrayList<Nominal>();
    for (int i = 0; i != exprArgs.size(); ++i) {
      Expr arg = propagate(exprArgs.get(i), environment, context);
      exprArgs.set(i, arg);
      paramTypes.add(arg.result());
    }

    // second, determine the fully qualified name of this function based on
    // the given function name and any supplied qualifications.
    ArrayList<String> qualifications = new ArrayList<String>();
    if (expr.qualification != null) {
      for (String n : expr.qualification) {
        qualifications.add(n);
      }
    }
    qualifications.add(expr.name);
    NameID name = resolveAsName(qualifications, context);

    // third, lookup the appropriate function or method based on the name
    // and given parameter types.
    Nominal.FunctionOrMethod funType = resolveAsFunctionOrMethod(name,
        paramTypes, context);
    if (funType instanceof Nominal.Function) {
      Expr.FunctionCall r = new Expr.FunctionCall(name, qualification,
          exprArgs, expr.attributes());
      r.functionType = (Nominal.Function) funType;
      return r;
    } else {
      Expr.MethodCall r = new Expr.MethodCall(name, qualification,
          exprArgs, expr.attributes());
      r.methodType = (Nominal.Method) funType;
      return r;
    }
  }

  private Expr propagate(Expr.IndexOf expr, Environment environment,
      Context context) throws IOException, ResolveError {
    expr.src = propagate(expr.src, environment, context);
    expr.index = propagate(expr.index, environment, context);
    Nominal.EffectiveIndexible srcType = expandAsEffectiveMap(expr.src
        .result());

    if (srcType == null) {
      syntaxError(errorMessage(INVALID_SET_OR_LIST_EXPRESSION), context,
          expr.src);
    } else {
      expr.srcType = srcType;
    }

    checkIsSubtype(srcType.key(), expr.index, context);

    return expr;
  }

  private Expr propagate(Expr.LengthOf expr, Environment environment,
      Context context) throws IOException, ResolveError {
    expr.src = propagate(expr.src, environment, context);
    Nominal srcType = expr.src.result();
    Type rawSrcType = srcType.raw();

    // First, check whether this is still only an abstract access and, in
    // such case, upgrade it to the appropriate access expression.

    if (rawSrcType instanceof Type.EffectiveCollection) {
      expr.srcType = expandAsEffectiveCollection(srcType);
      return expr;
    } else {
      syntaxError("found " + expr.src.result().nominal()
          + ", expected string, set, list or dictionary.", context,
          expr.src);
    }

    // Second, determine the expanded src type for this access expression
    // and check the key value.

    checkIsSubtype(Type.T_STRING, expr.src, context);

    return expr;
  }

  private Expr propagate(Expr.LocalVariable expr, Environment environment,
      Context context) throws IOException {
    Nominal type = environment.getCurrentType(expr.var);
    expr.type = type;
    return expr;
  }

  private Expr propagate(Expr.Set expr, Environment environment,
      Context context) {
    Nominal element = Nominal.T_VOID;

    ArrayList<Expr> exprs = expr.arguments;
    for (int i = 0; i != exprs.size(); ++i) {
      Expr e = propagate(exprs.get(i), environment, context);
      Nominal t = e.result();
      exprs.set(i, e);
      element = Nominal.Union(t, element);
    }

    expr.type = Nominal.Set(element, false);

    return expr;
  }

  private Expr propagate(Expr.List expr, Environment environment,
      Context context) {
    Nominal element = Nominal.T_VOID;

    ArrayList<Expr> exprs = expr.arguments;
    for (int i = 0; i != exprs.size(); ++i) {
      Expr e = propagate(exprs.get(i), environment, context);
      Nominal t = e.result();
      exprs.set(i, e);
      element = Nominal.Union(t, element);
    }

    expr.type = Nominal.List(element, false);

    return expr;
  }

  private Expr propagate(Expr.Map expr, Environment environment,
      Context context) {
    Nominal keyType = Nominal.T_VOID;
    Nominal valueType = Nominal.T_VOID;

    ArrayList<Pair<Expr, Expr>> exprs = expr.pairs;
    for (int i = 0; i != exprs.size(); ++i) {
      Pair<Expr, Expr> p = exprs.get(i);
      Expr key = propagate(p.first(), environment, context);
      Expr value = propagate(p.second(), environment, context);
      Nominal kt = key.result();
      Nominal vt = value.result();
      exprs.set(i, new Pair<Expr, Expr>(key, value));

      keyType = Nominal.Union(kt, keyType);
      valueType = Nominal.Union(vt, valueType);
    }

    expr.type = Nominal.Map(keyType, valueType);

    return expr;
  }

  private Expr propagate(Expr.Record expr, Environment environment,
      Context context) {

    HashMap<String, Expr> exprFields = expr.fields;
    HashMap<String, Nominal> fieldTypes = new HashMap<String, Nominal>();

    ArrayList<String> fields = new ArrayList<String>(exprFields.keySet());
    for (String field : fields) {
      Expr e = propagate(exprFields.get(field), environment, context);
      Nominal t = e.result();
      exprFields.put(field, e);
      fieldTypes.put(field, t);
    }

    expr.type = Nominal.Record(false, fieldTypes);

    return expr;
  }

  private Expr propagate(Expr.Tuple expr, Environment environment,
      Context context) {
    ArrayList<Expr> exprFields = expr.fields;
    ArrayList<Nominal> fieldTypes = new ArrayList<Nominal>();

    for (int i = 0; i != exprFields.size(); ++i) {
      Expr e = propagate(exprFields.get(i), environment, context);
      Nominal t = e.result();
      exprFields.set(i, e);
      fieldTypes.add(t);
    }

    expr.type = Nominal.Tuple(fieldTypes);

    return expr;
  }

  private Expr propagate(Expr.SubList expr, Environment environment,
      Context context) throws IOException, ResolveError {

    expr.src = propagate(expr.src, environment, context);
    expr.start = propagate(expr.start, environment, context);
    expr.end = propagate(expr.end, environment, context);

    checkSuptypes(expr.src, context, Nominal.T_LIST_ANY, Nominal.T_STRING);
    checkIsSubtype(Type.T_INT, expr.start, context);
    checkIsSubtype(Type.T_INT, expr.end, context);

    expr.type = expandAsEffectiveList(expr.src.result());
    if (expr.type == null) {
      // must be a substring
      return new Expr.SubString(expr.src, expr.start, expr.end,
          expr.attributes());
    }

    return expr;
  }

  private Expr propagate(Expr.SubString expr, Environment environment,
      Context context) throws IOException {

    expr.src = propagate(expr.src, environment, context);
    expr.start = propagate(expr.start, environment, context);
    expr.end = propagate(expr.end, environment, context);

    checkIsSubtype(Type.T_STRING, expr.src, context);
    checkIsSubtype(Type.T_INT, expr.start, context);
    checkIsSubtype(Type.T_INT, expr.end, context);

    return expr;
  }

  private Expr propagate(Expr.FieldAccess ra, Environment environment,
      Context context) throws IOException, ResolveError {
    ra.src = propagate(ra.src, environment, context);
    Nominal srcType = ra.src.result();
    Nominal.EffectiveRecord recType = expandAsEffectiveRecord(srcType);
    if (recType == null) {
      syntaxError(errorMessage(RECORD_TYPE_REQUIRED, srcType.raw()),
          context, ra);
    }
    Nominal fieldType = recType.field(ra.name);
    if (fieldType == null) {
      syntaxError(errorMessage(RECORD_MISSING_FIELD, ra.name), context,
          ra);
    }
    ra.srcType = recType;
    return ra;
  }

  private Expr propagate(Expr.ConstantAccess expr, Environment environment,
      Context context) throws IOException {
    // First, determine the fully qualified name of this function based on
    // the given function name and any supplied qualifications.
    ArrayList<String> qualifications = new ArrayList<String>();
    if (expr.qualification != null) {
      for (String n : expr.qualification) {
        qualifications.add(n);
      }
    }
    qualifications.add(expr.name);
    try {
      NameID name = resolveAsName(qualifications, context);
      // Second, determine the value of the constant.
      expr.value = resolveAsConstant(name);
      return expr;
    } catch (ResolveError e) {
      syntaxError(errorMessage(UNKNOWN_VARIABLE), context, expr);
      return null;
    }
  }

  private Expr propagate(Expr.Dereference expr, Environment environment,
      Context context) throws IOException, ResolveError {
    Expr src = propagate(expr.src, environment, context);
    expr.src = src;
    Nominal.Reference srcType = expandAsReference(src.result());
    if (srcType == null) {
      syntaxError("invalid reference expression", context, src);
    }
    expr.srcType = srcType;
    return expr;
  }

  private Expr propagate(Expr.New expr, Environment environment,
      Context context) {
    expr.expr = propagate(expr.expr, environment, context);
    expr.type = Nominal.Reference(expr.expr.result());
    return expr;
  }

  private Expr propagate(Expr.TypeVal expr, Environment environment,
      Context context) throws IOException {
    expr.type = resolveAsType(expr.unresolvedType, context);
    return expr;
  }

  // =========================================================================
  // Compute fixed point
  // =========================================================================

  /**
   * Compute the fixed point of an environment across a body of statements.
   * The fixed point is the environment which, starting from the initial
   * environment, doesn't change after being put through body. For example:
   *
   * <pre>
   * x = 1
   * while i < 10:
   *    // x -> int, i -> int
   *    x = null
   *    i = i + 1
   *    // x -> null, i -> int
   * </pre>
   *
   * <p>
   * Here, we see the environment before the loop body, along with that after.
   * The fixed point for this example, then, is {x -> int|null, i -> int}
   * </p>
   *
   * <p>
   * <b>NOTE:</b> The fixed-point computation is technically not guaranteed to
   * terminate (i.e. because the lattice has infinite height). As a simplistic
   * step, for now, the computatino just bails out after 10 iterations. In
   * principle, one can do better and this is discussed in the following
   * paper:
   *
   * <ul>
   * <li>A Calculus for Constraint-Based Flow Typing. David J. Pearce. In
   * Proceedings of the Workshop on Formal Techniques for Java-like Languages
   * (FTFJP), Article 7, 2013.</li>
   * </ul>
   * (Aaaahhh, the irony that I haven't implemented by own paper :)
   * </p>
   *
   * @param environment
   *            The initial environment, which is guaranteed not to be changed
   *            by this method.
   * @param body
   *            The statement body which is to be iterated over.
   * @param condition
   *            An optional condition which is to be included in the
   *            computation. Maybe null.
   * @param doWhile
   *            Indicates whether this is a do-while loop or not. A do-while
   *            loop is different because the condition does not hold on the
   *            first iteration.
   * @return
   */
  private Environment computeFixedPoint(Environment environment,
      ArrayList<Stmt> body, Expr condition, boolean doWhile,
      SyntacticElement element) {
    // The count is used simply to guarantee termination.
    int count = 0;
    // The original environment is an exact copy of the initial environment.
    // This is needed to feed into the iteration.
    Environment original = environment.clone();
    // We clone the original environment again to force the refcount > 1
    original = original.clone();
    // Precompute the set of variables to be merged
    Set<String> variables = original.keySet();
    // The old environment is used to compare the environment after one
    // iteration with previous "old" environment to see whether anything has
    // changed.
    Environment old;
    // The temporary environment is used simply to hold the environment in
    // between the condition and the statement body.
    Environment tmp;
    do {
      // First, take a copy of environment so we can later tell whether anything changed.
      old = environment.clone();
      // Second, propagate through condition (if applicable). This may
      // update the environment if one or more type tests are used.
      if(condition != null && !doWhile) {
        tmp = propagateCondition(condition, true, old.clone(), current)
            .second();
      } else {
        tmp = old;
        doWhile = false;
      }
      // Merge updated environment with original environment to produce
      // potentially updated environment.
      environment = original.merge(variables, propagate(body, tmp));
      old.free(); // hacky, but safe
      // Finally, check loop count to force termination
      if(count++ == 10) {
        internalFailure("Unable to type loop",filename,element);
      }
    } while (!environment.equals(old));

    return environment;
  }

  // =========================================================================
  // Resolve as Function or Method
  // =========================================================================

  /**
   * Responsible for determining the true type of a method or function being
   * invoked. To do this, it must find the function/method with the most
   * precise type that matches the argument types.
   *
   * @param nid
   * @param parameters
   * @return
   * @throws IOException
   */
  public Nominal.FunctionOrMethod resolveAsFunctionOrMethod(NameID nid,
      List<Nominal> parameters, Context context) throws IOException,
      ResolveError {

    // Thet set of candidate names and types for this function or method.
    HashSet<Pair<NameID, Nominal.FunctionOrMethod>> candidates = new HashSet<Pair<NameID, Nominal.FunctionOrMethod>>();

    // First, add all valid candidates to the list without considering which
    // is the most precise.
    addCandidateFunctionsAndMethods(nid, parameters, candidates, context);

    // Second, add to narrow down the list of candidates to a single choice.
    // If this is impossible, then we have an ambiguity error.
    return selectCandidateFunctionOrMethod(nid.name(), parameters,
        candidates, context).second();
  }

  /**
   * Responsible for determining the true type of a method or function being
   * invoked. In this case, no argument types are given. This means that any
   * match is returned. However, if there are multiple matches, then an
   * ambiguity error is reported.
   *
   * @param name
   *            --- function or method name whose type to determine.
   * @param context
   *            --- context in which to resolve this name.
   * @return
   * @throws IOException
   */
  public Pair<NameID, Nominal.FunctionOrMethod> resolveAsFunctionOrMethod(
      String name, Context context) throws IOException, ResolveError {
    return resolveAsFunctionOrMethod(name, null, context);
  }

  /**
   * Responsible for determining the true type of a method or function being
   * invoked. To do this, it must find the function/method with the most
   * precise type that matches the argument types.
   *
   * @param name
   *            --- name of function or method whose type to determine.
   * @param parameters
   *            --- required parameter types for the function or method.
   * @param context
   *            --- context in which to resolve this name.
   * @return
   * @throws IOException
   */
  public Pair<NameID, Nominal.FunctionOrMethod> resolveAsFunctionOrMethod(
      String name, List<Nominal> parameters, Context context)
      throws IOException,ResolveError {

    HashSet<Pair<NameID, Nominal.FunctionOrMethod>> candidates = new HashSet<Pair<NameID, Nominal.FunctionOrMethod>>();
    // first, try to find the matching message
    for (WhileyFile.Import imp : context.imports()) {
      String impName = imp.name;
      if (impName == null || impName.equals(name) || impName.equals("*")) {
        Trie filter = imp.filter;
        if (impName == null) {
          // import name is null, but it's possible that a module of
          // the given name exists, in which case any matching names
          // are automatically imported.
          filter = filter.parent().append(name);
        }
        for (Path.ID mid : builder.imports(filter)) {
          NameID nid = new NameID(mid, name);
          addCandidateFunctionsAndMethods(nid, parameters,
              candidates, context);
        }
      }
    }

    return selectCandidateFunctionOrMethod(name, parameters, candidates,
        context);
  }

  private boolean paramSubtypes(Type.FunctionOrMethod f1,
      Type.FunctionOrMethod f2) {
    List<Type> f1_params = f1.params();
    List<Type> f2_params = f2.params();
    if (f1_params.size() == f2_params.size()) {
      for (int i = 0; i != f1_params.size(); ++i) {
        Type f1_param = f1_params.get(i);
        Type f2_param = f2_params.get(i);
        if (!Type.isSubtype(f1_param, f2_param)) {
          return false;
        }
      }

      return true;
    }
    return false;
  }

  private boolean paramStrictSubtypes(Type.FunctionOrMethod f1,
      Type.FunctionOrMethod f2) {
    List<Type> f1_params = f1.params();
    List<Type> f2_params = f2.params();
    if (f1_params.size() == f2_params.size()) {
      boolean allEqual = true;
      for (int i = 0; i != f1_params.size(); ++i) {
        Type f1_param = f1_params.get(i);
        Type f2_param = f2_params.get(i);
        if (!Type.isSubtype(f1_param, f2_param)) {
          return false;
        }
        allEqual &= f1_param.equals(f2_param);
      }

      // This function returns true if the parameters are a strict
      // subtype. Therefore, if they are all equal it must return false.

      return !allEqual;
    }
    return false;
  }

  private String parameterString(List<Nominal> paramTypes) {
    String paramStr = "(";
    boolean firstTime = true;
    if (paramTypes == null) {
      paramStr += "...";
    } else {
      for (Nominal t : paramTypes) {
        if (!firstTime) {
          paramStr += ",";
        }
        firstTime = false;
        paramStr += t.nominal();
      }
    }
    return paramStr + ")";
  }

  private Pair<NameID, Nominal.FunctionOrMethod> selectCandidateFunctionOrMethod(
      String name, List<Nominal> parameters,
      Collection<Pair<NameID, Nominal.FunctionOrMethod>> candidates,
      Context context) throws IOException,ResolveError {

    List<Type> rawParameters;
    Type.Function target;

    if (parameters != null) {
      rawParameters = stripNominal(parameters);
      target = (Type.Function) Type.Function(Type.T_ANY, Type.T_ANY,
          rawParameters);
    } else {
      rawParameters = null;
      target = null;
    }

    NameID candidateID = null;
    Nominal.FunctionOrMethod candidateType = null;
    for (Pair<NameID, Nominal.FunctionOrMethod> p : candidates) {
      Nominal.FunctionOrMethod nft = p.second();
      Type.FunctionOrMethod ft = nft.raw();
      if (parameters == null || paramSubtypes(ft, target)) {
        // this is now a genuine candidate
        if (candidateType == null
            || paramStrictSubtypes(candidateType.raw(), ft)) {
          candidateType = nft;
          candidateID = p.first();
        } else if (!paramStrictSubtypes(ft, candidateType.raw())) {
          // this is an ambiguous error
          String msg = name + parameterString(parameters)
              + " is ambiguous";
          // FIXME: should report all ambiguous matches here
          msg += "\n\tfound: " + candidateID + " : "
              + candidateType.nominal();
          msg += "\n\tfound: " + p.first() + " : "
              + p.second().nominal();
          throw new ResolveError(msg);
        }
      }
    }

    if (candidateType == null) {
      // second, didn't find matching message so generate error message
      String msg = "no match for " + name + parameterString(parameters);

      for (Pair<NameID, Nominal.FunctionOrMethod> p : candidates) {
        msg += "\n\tfound: " + p.first() + " : " + p.second().nominal();
      }

      throw new ResolveError(msg);
    } else {
      // now check protection modifier
      WhileyFile wf = builder.getSourceFile(candidateID.module());
      if (wf != null) {
        if (wf != context.file()) {
          for (WhileyFile.FunctionOrMethod d : wf.declarations(
              WhileyFile.FunctionOrMethod.class,
              candidateID.name())) {
            if (d.parameters.equals(candidateType.params())) {
              if (!d.hasModifier(Modifier.PUBLIC)
                  && !d.hasModifier(Modifier.PROTECTED)) {
                String msg = candidateID.module() + "." + name
                    + parameterString(parameters)
                    + " is not visible";
                throw new ResolveError(msg);
              }
            }
          }
        }
      } else {
        WyilFile m = builder.getModule(candidateID.module());
        WyilFile.FunctionOrMethodDeclaration d = m.functionOrMethod(
            candidateID.name(), candidateType.raw());
        if (!d.hasModifier(Modifier.PUBLIC)
            && !d.hasModifier(Modifier.PROTECTED)) {
          String msg = candidateID.module() + "." + name
              + parameterString(parameters) + " is not visible";
          throw new ResolveError(msg);
        }
      }
    }

    return new Pair<NameID, Nominal.FunctionOrMethod>(candidateID,
        candidateType);
  }

  /**
   * Add all "candidate" functions or methods matching a fully qualified name.
   * Candidates are those which the same name and matching number of
   * arguments. They must also be visible to the given context. The context is
   * important because some functions and methods will not be visible from all
   * contexts. For example, a private function is only visible from within the
   * same source file. Furthermore, the context affects what exactly will be
   * seen externally. For example, consider a function "f(T x)=>int" where
   * type "T" is declared as protected within the same file. The external type
   * of "f" will be "(T)=>int"; however, the internal type will be e.g.
   * "(int)=>int" if T is defined as int.
   *
   * @param nid
   *            --- Fully qualified name of function being matched
   * @param parameters
   *            --- The list of parameter types, although this is only used to
   *            determine the number of parameters
   * @param candidates
   *            --- The list into which all identified candidates will be
   *            placed (i.e. this is an output parameter)
   * @param context
   *            --- The context in which we are looking for the given method.
   * @throws IOException
   */
  private void addCandidateFunctionsAndMethods(NameID nid,
      List<?> parameters,
      Collection<Pair<NameID, Nominal.FunctionOrMethod>> candidates,
      Context context) throws IOException {
    Path.ID mid = nid.module();

    int nparams = parameters != null ? parameters.size() : -1;

    WhileyFile wf = builder.getSourceFile(mid);
    if (wf != null) {
      for (WhileyFile.FunctionOrMethod f : wf.declarations(
          WhileyFile.FunctionOrMethod.class, nid.name())) {
        if (nparams == -1 || f.parameters.size() == nparams) {
          Nominal.FunctionOrMethod ft = (Nominal.FunctionOrMethod) resolveAsType(
              f.unresolvedType(), f);
          candidates.add(new Pair<NameID, Nominal.FunctionOrMethod>(
              nid, ft));
        }
      }
    } else {
      WyilFile m = builder.getModule(mid);
      for (WyilFile.FunctionOrMethodDeclaration mm : m.functionOrMethods()) {
        if ((mm.isFunction() || mm.isMethod())
            && mm.name().equals(nid.name())
            && (nparams == -1 || mm.type().params().size() == nparams)) {
          // FIXME: loss of nominal information

          // FIXME: loss of visibility information (e.g if this
          // function is declared in terms of a protected type)
          Type.FunctionOrMethod t = (Type.FunctionOrMethod) mm
              .type();
          Nominal.FunctionOrMethod fom;
          if (t instanceof Type.Function) {
            Type.Function ft = (Type.Function) t;
            fom = new Nominal.Function(ft, ft);
          } else {
            Type.Method mt = (Type.Method) t;
            fom = new Nominal.Method(mt, mt);
          }
          candidates
          .add(new Pair<NameID, Nominal.FunctionOrMethod>(
              nid, fom));
        }
      }
    }
  }

  private static List<Type> stripNominal(List<Nominal> types) {
    ArrayList<Type> r = new ArrayList<Type>();
    for (Nominal t : types) {
      r.add(t.raw());
    }
    return r;
  }

  // =========================================================================
  // ResolveAsName
  // =========================================================================

  /**
   * <p>
   * Responsible for resolve names, types, constants and functions / methods
   * at the global level. Resolution is determined by the context in which a
   * given name/type/constant/function/method appears. That is, what imports
   * are active in the enclosing WhileyFile. For example, consider this:
   * </p>
   *
   * <pre>
   * import whiley.lang.*
   *
   * type nat is Int.uint
   *
   * import whiley.ui.*
   * </pre>
   *
   * <p>
   * In this example, the statement "<code>import whiley.lang.*</code>" is
   * active for the type declaration, whilst the statement "
   * <code>import whiley.ui.*</code>". The context of the type declaration is
   * everything in the enclosing file up to the declaration itself. Therefore,
   * in resolving the name <code>Int.uint</code>, this will examine the
   * package whiley.lang to see whether a compilation unit named "Int" exists.
   * If so, it will then resolve the name <code>Int.uint</code> to
   * <code>whiley.lang.Int.uint</code>.
   * </p>
   *
   * @param name
   *            A module name without package specifier.
   * @param context
   *            --- context in which to resolve.
   * @return The resolved name.
   * @throws IOException
   *             if it couldn't resolve the name
   */
  public NameID resolveAsName(String name, Context context)
      throws IOException, ResolveError {
    for (WhileyFile.Import imp : context.imports()) {
      String impName = imp.name;
      if (impName == null || impName.equals(name) || impName.equals("*")) {
        Trie filter = imp.filter;
        if (impName == null) {
          // import name is null, but it's possible that a module of
          // the given name exists, in which case any matching names
          // are automatically imported.
          filter = filter.parent().append(name);
        }
        for (Path.ID mid : builder.imports(filter)) {
          NameID nid = new NameID(mid, name);
          if (builder.isName(nid)) {
            // ok, we have found the name in question. But, is it
            // visible?
            if (isNameVisible(nid, context)) {
              return nid;
            } else {
              throw new ResolveError(nid + " is not visible");
            }
          }
        }
      }
    }

    throw new ResolveError("name not found: " + name);
  }

  /**
   * This methods attempts to resolve the given list of names into a single
   * named item (e.g. type, method, constant, etc). For example,
   * <code>["whiley","lang","Math","max"]</code> would be resolved, since
   * <code>whiley.lang.Math.max</code> is a valid function name. In contrast,
   * <code>["whiley","lang","Math"]</code> does not resolve since
   * <code>whiley.lang.Math</code> refers to a module.
   *
   * @param names
   *            A list of components making up the name, which may include the
   *            package and enclosing module.
   * @param context
   *            --- context in which to resolve *
   * @return The resolved name.
   * @throws IOException
   *             if it couldn't resolve the name
   */
  public NameID resolveAsName(List<String> names, Context context)
      throws IOException, ResolveError {
    if (names.size() == 1) {
      return resolveAsName(names.get(0), context);
    } else if (names.size() == 2) {
      String name = names.get(1);
      Path.ID mid = resolveAsModule(names.get(0), context);
      NameID nid = new NameID(mid, name);
      if (builder.isName(nid)) {
        if (isNameVisible(nid, context)) {
          return nid;
        } else {
          throw new ResolveError(nid + " is not visible");
        }
      }
    } else {
      String name = names.get(names.size() - 1);
      String module = names.get(names.size() - 2);
      Path.ID pkg = Trie.ROOT;
      for (int i = 0; i != names.size() - 2; ++i) {
        pkg = pkg.append(names.get(i));
      }
      Path.ID mid = pkg.append(module);
      NameID nid = new NameID(mid, name);
      if (builder.isName(nid)) {
        if (isNameVisible(nid, context)) {
          return nid;
        } else {
          throw new ResolveError(nid + " is not visible");
        }
      }
    }

    String name = null;
    for (String n : names) {
      if (name != null) {
        name = name + "." + n;
      } else {
        name = n;
      }
    }
    throw new ResolveError("name not found: " + name);
  }

  /**
   * This method attempts to resolve a name as a module in a given name
   * context.
   *
   * @param name
   *            --- name to be resolved
   * @param context
   *            --- context in which to resolve
   * @return
   * @throws IOException
   */
  public Path.ID resolveAsModule(String name, Context context)
      throws IOException, ResolveError {

    for (WhileyFile.Import imp : context.imports()) {
      Trie filter = imp.filter;
      String last = filter.last();
      if (last.equals("*")) {
        // this is generic import, so narrow the filter.
        filter = filter.parent().append(name);
      } else if (!last.equals(name)) {
        continue; // skip as not relevant
      }

      for (Path.ID mid : builder.imports(filter)) {
        return mid;
      }
    }

    throw new ResolveError("module not found: " + name);
  }

  // =========================================================================
  // ResolveAsType
  // =========================================================================

  public Nominal.Function resolveAsType(SyntacticType.Function t,
      Context context) {
    // We need to sanity check the parameter types we have here, since
    // occasionally we can end up with something other than a function type.
    // This may seem surprising, but it can happen when one of the types
    // involved is contractive (normally by accident).
    for(SyntacticType param : t.paramTypes) {
      Nominal nominal = resolveAsType(param,context);
      if (Type.isSubtype(Type.T_VOID, nominal.raw())) {
        syntaxError("contractive type encountered",filename,param);
      }
    }
    Nominal ret = resolveAsType(t.ret,context);
    if(!(t.ret instanceof SyntacticType.Void) && Type.isSubtype(Type.T_VOID, ret.raw())){
      syntaxError("contractive type encountered",filename,t.ret);
    }
    Nominal thrws = resolveAsType(t.throwType,context);
    if(!(t.throwType instanceof SyntacticType.Void) && Type.isSubtype(Type.T_VOID, thrws.raw())){
      syntaxError("contractive type encountered",filename,t.throwType);
    }
    return (Nominal.Function) resolveAsType((SyntacticType) t, context);
  }

  public Nominal.Method resolveAsType(SyntacticType.Method t, Context context) {
    // We need to sanity check the parameter types we have here, since
    // occasionally we can end up with something other than a function type.
    // This may seem surprising, but it can happen when one of the types
    // involved is contractive (normally by accident).
    for(SyntacticType param : t.paramTypes) {
      Nominal nominal = resolveAsType(param,context);
      if (Type.isSubtype(Type.T_VOID, nominal.raw())) {
        syntaxError("contractive type encountered",filename,param);
      }
    }
    Nominal ret = resolveAsType(t.ret,context);
    if(!(t.ret instanceof SyntacticType.Void) && Type.isSubtype(Type.T_VOID, ret.raw())){
      syntaxError("contractive type encountered",filename,t.ret);
    }
    Nominal thrws = resolveAsType(t.throwType,context);
    if(!(t.throwType instanceof SyntacticType.Void) && Type.isSubtype(Type.T_VOID, thrws.raw())){
      syntaxError("contractive type encountered",filename,t.throwType);
    }
    return (Nominal.Method) resolveAsType((SyntacticType) t, context);
  }

  /**
   * Resolve a type in a given context by identifying all unknown names and
   * replacing them with nominal types. The context is that declaration (e.g.
   * type, constant, function, etc) which encloses the give type. The context
   * determines what import statements are visible to help resolving external
   * names.
   *
   * @param type
   *            --- type to be resolved.
   * @param context
   *            --- context in which to resolve the type.
   * @return
   * @throws IOException
   */
  public Nominal resolveAsType(SyntacticType type, Context context) {
    Type nominalType = resolveAsType(type, context, true, false);
    Type rawType = resolveAsType(type, context, false, false);
    return Nominal.construct(nominalType, rawType);
  }

  /**
   * Resolve a type in a given context by identifying all unknown names and
   * replacing them with nominal types. In this case, any constrained types
   * are treated as void. This is critical for properly dealing with type
   * tests, which may otherwise assume types are unconstrained.
   *
   * @param type
   *            --- type to be resolved.
   * @param context
   *            --- context in which to resolve the type.
   * @return
   * @throws IOException
   */
  public Nominal resolveAsUnconstrainedType(SyntacticType type,
      Context context) {
    Type nominalType = resolveAsType(type, context, true, true);
    Type rawType = resolveAsType(type, context, false, true);
    return Nominal.construct(nominalType, rawType);
  }

  private Type resolveAsType(SyntacticType t, Context context,
      boolean nominal, boolean unconstrained) {

    if (t instanceof SyntacticType.Primitive) {
      if (t instanceof SyntacticType.Any) {
        return Type.T_ANY;
      } else if (t instanceof SyntacticType.Void) {
        return Type.T_VOID;
      } else if (t instanceof SyntacticType.Null) {
        return Type.T_NULL;
      } else if (t instanceof SyntacticType.Bool) {
        return Type.T_BOOL;
      } else if (t instanceof SyntacticType.Byte) {
        return Type.T_BYTE;
      } else if (t instanceof SyntacticType.Char) {
        return Type.T_CHAR;
      } else if (t instanceof SyntacticType.Int) {
        return Type.T_INT;
      } else if (t instanceof SyntacticType.Real) {
        return Type.T_REAL;
      } else if (t instanceof SyntacticType.Strung) {
        return Type.T_STRING;
      } else {
        internalFailure("unrecognised type encountered ("
            + t.getClass().getName() + ")", context, t);
        return null; // deadcode
      }
    } else {
      ArrayList<Automaton.State> states = new ArrayList<Automaton.State>();
      HashMap<NameID, Integer> roots = new HashMap<NameID, Integer>();
      resolveAsType(t, context, states, roots, nominal, unconstrained);
      return Type.construct(new Automaton(states));
    }
  }

  /**
   * The following method resolves a syntactic type in a given context.
   *
   * @param type
   *            --- type to be resolved
   * @param context
   *            --- context in which to resolve the type
   * @param states
   * @param roots
   * @return
   * @throws IOException
   */
  private int resolveAsType(SyntacticType type, Context context,
      ArrayList<Automaton.State> states, HashMap<NameID, Integer> roots,
      boolean nominal, boolean unconstrained) {

    if (type instanceof SyntacticType.Primitive) {
      return resolveAsType((SyntacticType.Primitive) type, context,
          states);
    }

    int myIndex = states.size();
    int myKind;
    int[] myChildren;
    Object myData = null;
    boolean myDeterministic = true;

    states.add(null); // reserve space for me

    if (type instanceof SyntacticType.List) {
      SyntacticType.List lt = (SyntacticType.List) type;
      myKind = Type.K_LIST;
      myChildren = new int[1];
      myChildren[0] = resolveAsType(lt.element, context, states, roots,
          nominal, unconstrained);
      myData = false;
    } else if (type instanceof SyntacticType.Set) {
      SyntacticType.Set st = (SyntacticType.Set) type;
      myKind = Type.K_SET;
      myChildren = new int[1];
      myChildren[0] = resolveAsType(st.element, context, states, roots,
          nominal, unconstrained);
      myData = false;
    } else if (type instanceof SyntacticType.Map) {
      SyntacticType.Map st = (SyntacticType.Map) type;
      myKind = Type.K_MAP;
      myChildren = new int[2];
      myChildren[0] = resolveAsType(st.key, context, states, roots,
          nominal, unconstrained);
      myChildren[1] = resolveAsType(st.value, context, states, roots,
          nominal, unconstrained);
    } else if (type instanceof SyntacticType.Record) {
      SyntacticType.Record tt = (SyntacticType.Record) type;
      HashMap<String, SyntacticType> ttTypes = tt.types;
      Type.Record.State fields = new Type.Record.State(tt.isOpen,
          ttTypes.keySet());
      Collections.sort(fields);
      myKind = Type.K_RECORD;
      myChildren = new int[fields.size()];
      for (int i = 0; i != fields.size(); ++i) {
        String field = fields.get(i);
        myChildren[i] = resolveAsType(ttTypes.get(field), context,
            states, roots, nominal, unconstrained);
      }
      myData = fields;
    } else if (type instanceof SyntacticType.Tuple) {
      SyntacticType.Tuple tt = (SyntacticType.Tuple) type;
      ArrayList<SyntacticType> ttTypes = tt.types;
      myKind = Type.K_TUPLE;
      myChildren = new int[ttTypes.size()];
      for (int i = 0; i != ttTypes.size(); ++i) {
        myChildren[i] = resolveAsType(ttTypes.get(i), context, states,
            roots, nominal, unconstrained);
      }
    } else if (type instanceof SyntacticType.Nominal) {
      // This case corresponds to a user-defined type. This will be
      // defined in some module (possibly ours), and we need to identify
      // what module that is here, and save it for future use.
      // Furthermore, we need to determine whether the name is visible
      // (i.e. non-private) and/or whether the body of the type is visible
      // (i.e. non-protected).
      SyntacticType.Nominal dt = (SyntacticType.Nominal) type;
      NameID nid;
      try {
        // Determine the full qualified name of this nominal type. This
        // will additionally ensure that the name is visible
        nid = resolveAsName(dt.names, context);

        if (nominal || !isTypeVisible(nid, context)) {
          myKind = Type.K_NOMINAL;
          myData = nid;
          myChildren = Automaton.NOCHILDREN;
        } else {
          // At this point, we're going to expand the given nominal
          // type. We're going to use resolveAsType(NameID,...) to do
          // this which will load the expanded type onto states at the
          // current point. Therefore, we need to remove the initial
          // null we loaded on.
          states.remove(myIndex);
          return resolveAsType(nid, states, roots, unconstrained);
        }
      } catch (ResolveError e) {
        syntaxError(e.getMessage(), context, dt, e);
        return 0; // dead-code
      } catch (SyntaxError e) {
        throw e;
      } catch (Throwable e) {
        internalFailure(e.getMessage(), context, dt, e);
        return 0; // dead-code
      }
    } else if (type instanceof SyntacticType.Negation) {
      SyntacticType.Negation ut = (SyntacticType.Negation) type;
      myKind = Type.K_NEGATION;
      myChildren = new int[1];
      myChildren[0] = resolveAsType(ut.element, context, states, roots,
          nominal, unconstrained);
    } else if (type instanceof SyntacticType.Union) {
      SyntacticType.Union ut = (SyntacticType.Union) type;
      ArrayList<SyntacticType.NonUnion> utTypes = ut.bounds;
      myKind = Type.K_UNION;
      myChildren = new int[utTypes.size()];
      for (int i = 0; i != utTypes.size(); ++i) {
        myChildren[i] = resolveAsType(utTypes.get(i), context, states,
            roots, nominal, unconstrained);
      }
      myDeterministic = false;
    } else if (type instanceof SyntacticType.Intersection) {
      internalFailure("intersection types not supported yet", context,
          type);
      return 0; // dead-code
    } else if (type instanceof SyntacticType.Reference) {
      SyntacticType.Reference ut = (SyntacticType.Reference) type;
      myKind = Type.K_REFERENCE;
      myChildren = new int[1];
      myChildren[0] = resolveAsType(ut.element, context, states, roots,
          nominal, unconstrained);
    } else {
      SyntacticType.FunctionOrMethod ut = (SyntacticType.FunctionOrMethod) type;
      ArrayList<SyntacticType> utParamTypes = ut.paramTypes;
      int start = 0;

      if (ut instanceof SyntacticType.Method) {
        myKind = Type.K_METHOD;
      } else {
        myKind = Type.K_FUNCTION;
      }

      myChildren = new int[start + 2 + utParamTypes.size()];

      myChildren[start++] = resolveAsType(ut.ret, context, states, roots,
          nominal, unconstrained);
      if (ut.throwType == null) {
        // this case indicates the user did not provide a throws clause.
        myChildren[start++] = resolveAsType(new SyntacticType.Void(),
            context, states, roots, nominal, unconstrained);
      } else {
        myChildren[start++] = resolveAsType(ut.throwType, context,
            states, roots, nominal, unconstrained);
      }
      for (SyntacticType pt : utParamTypes) {
        myChildren[start++] = resolveAsType(pt, context, states, roots,
            nominal, unconstrained);
      }
    }

    states.set(myIndex, new Automaton.State(myKind, myData,
        myDeterministic, myChildren));

    return myIndex;
  }

  private int resolveAsType(NameID key, ArrayList<Automaton.State> states,
      HashMap<NameID, Integer> roots, boolean unconstrained)
      throws IOException, ResolveError {

    // First, check the various caches we have
    Integer root = roots.get(key);
    if (root != null) {
      return root;
    }

    // check whether this type is external or not
    WhileyFile wf = builder.getSourceFile(key.module());
    if (wf == null) {
      // indicates a non-local key which we can resolve immediately

      WyilFile mi = builder.getModule(key.module());
      WyilFile.TypeDeclaration td = mi.type(key.name());
      return append(td.type(), states);
    }

    WhileyFile.Type td = wf.typeDecl(key.name());
    if (td == null) {

      // FIXME: the following allows (in certain cases) constants to be
      // interpreted as types. This should not be allowed and needs to be
      // removed in the future. However, to do this requires some kind of
      // unit/constant/enum type.  See #315

      Type t = resolveAsConstant(key).type();
      if (t instanceof Type.Set) {
        if (unconstrained) {
          // crikey this is ugly
          int myIndex = states.size();
          int kind = Type.leafKind(Type.T_VOID);
          Object data = null;
          states.add(new Automaton.State(kind, data, true,
              Automaton.NOCHILDREN));
          return myIndex;
        }
        Type.Set ts = (Type.Set) t;
        return append(ts.element(), states);
      } else {
        throw new ResolveError("type not found: " + key);
      }
    }

    // following is needed to terminate any recursion
    roots.put(key, states.size());
    SyntacticType type = td.pattern.toSyntacticType();

    // now, expand the given type fully
    if (unconstrained && td.invariant != null) {
      int myIndex = states.size();
      int kind = Type.leafKind(Type.T_VOID);
      Object data = null;
      states.add(new Automaton.State(kind, data, true,
          Automaton.NOCHILDREN));
      return myIndex;
    } else if (type instanceof Type.Leaf) {
      //
      // FIXME: I believe this code is now redundant, and should be
      // removed or updated. The problem is that SyntacticType no longer
      // extends Type.
      //
      int myIndex = states.size();
      int kind = Type.leafKind((Type.Leaf) type);
      Object data = Type.leafData((Type.Leaf) type);
      states.add(new Automaton.State(kind, data, true,
          Automaton.NOCHILDREN));
      return myIndex;
    } else {
      return resolveAsType(type, td, states, roots, false, unconstrained);
    }

    // TODO: performance can be improved here, but actually assigning the
    // constructed type into a cache of previously expanded types cache.
    // This is challenging, in the case that the type may not be complete at
    // this point. In particular, if it contains any back-links above this
    // index there could be an issue.
  }

  private int resolveAsType(SyntacticType.Primitive t, Context context,
      ArrayList<Automaton.State> states) {
    int myIndex = states.size();
    int kind;
    if (t instanceof SyntacticType.Any) {
      kind = Type.K_ANY;
    } else if (t instanceof SyntacticType.Void) {
      kind = Type.K_VOID;
    } else if (t instanceof SyntacticType.Null) {
      kind = Type.K_NULL;
    } else if (t instanceof SyntacticType.Bool) {
      kind = Type.K_BOOL;
    } else if (t instanceof SyntacticType.Byte) {
      kind = Type.K_BYTE;
    } else if (t instanceof SyntacticType.Char) {
      kind = Type.K_CHAR;
    } else if (t instanceof SyntacticType.Int) {
      kind = Type.K_INT;
    } else if (t instanceof SyntacticType.Real) {
      kind = Type.K_RATIONAL;
    } else if (t instanceof SyntacticType.Strung) {
      kind = Type.K_STRING;
    } else {
      internalFailure("unrecognised type encountered ("
          + t.getClass().getName() + ")", context, t);
      return 0; // dead-code
    }
    states.add(new Automaton.State(kind, null, true, Automaton.NOCHILDREN));
    return myIndex;
  }

  private static int append(Type type, ArrayList<Automaton.State> states) {
    int myIndex = states.size();
    Automaton automaton = Type.destruct(type);
    Automaton.State[] tStates = automaton.states;
    int[] rmap = new int[tStates.length];
    for (int i = 0, j = myIndex; i != rmap.length; ++i, ++j) {
      rmap[i] = j;
    }
    for (Automaton.State state : tStates) {
      states.add(Automata.remap(state, rmap));
    }
    return myIndex;
  }

  // =========================================================================
  // ResolveAsConstant
  // =========================================================================

  /**
   * <p>
   * Resolve a given name as a constant value. This is a global problem, since
   * a constant declaration in one source file may refer to constants declared
   * in other compilation units. This function will actually evaluate constant
   * expressions (e.g. "1+2") to produce actual constant vales.
   * </p>
   *
   * <p>
   * Constant declarations form a global graph spanning multiple compilation
   * units. In resolving a given constant, this function must traverse those
   * portions of the graph which make up the constant. Constants are not
   * permitted to be declared recursively (i.e. in terms of themselves) and
   * this function will report an error is such a recursive cycle is detected
   * in the constant graph.
   * </p>
   *
   * @param nid
   *            Fully qualified name identifier of constant to resolve
   * @return Constant value representing named constant
   * @throws IOException
   */
  public Constant resolveAsConstant(NameID nid) throws IOException, ResolveError {
    return resolveAsConstant(nid, new HashSet<NameID>());
  }

  /**
   * <p>
   * Resolve a given <i>constant expression</i> as a constant value. A
   * constant expression is one which refers only to known and visible
   * constant values, rather than e.g. local variables. Constant expressions
   * may still use operators (e.g. "1+2", or "1+c" where c is a declared
   * constant).
   * </p>
   *
   * <p>
   * Constant expressions used in a few places in Whiley. In particular, the
   * cases of a <code>switch</code> statement must be defined using constant
   * expressions.
   * </p>
   *
   * @param e
   * @param context
   * @return
   */
  public Constant resolveAsConstant(Expr e, Context context) {
    e = propagate(e, new Environment(), context);
    return resolveAsConstant(e, context, new HashSet<NameID>());
  }

  /**
   * Responsible for turning a named constant expression into a value. This is
   * done by traversing the constant's expression and recursively expanding
   * any named constants it contains. Simplification of constants is also
   * performed where possible.
   *
   * @param key
   *            --- name of constant we are expanding.
   * @param exprs
   *            --- mapping of all names to their( declared) expressions
   * @param visited
   *            --- set of all constants seen during this traversal (used to
   *            detect cycles).
   * @return
   * @throws IOException
   */
  private Constant resolveAsConstant(NameID key, HashSet<NameID> visited)
      throws IOException, ResolveError {
    Constant result = constantCache.get(key);
    if (result != null) {
      return result;
    } else if (visited.contains(key)) {
      throw new ResolveError("cyclic constant definition encountered ("
          + key + " -> " + key + ")");
    } else {
      visited.add(key);
    }

    WhileyFile wf = builder.getSourceFile(key.module());

    if (wf != null) {
      WhileyFile.Declaration decl = wf.declaration(key.name());
      if (decl instanceof WhileyFile.Constant) {
        WhileyFile.Constant cd = (WhileyFile.Constant) decl;
        if (cd.resolvedValue == null) {
          cd.constant = propagate(cd.constant, new Environment(), cd);
          cd.resolvedValue = resolveAsConstant(cd.constant, cd,
              visited);
        }
        result = cd.resolvedValue;
      } else {
        throw new ResolveError("unable to find constant " + key);
      }
    } else {
      WyilFile module = builder.getModule(key.module());
      WyilFile.ConstantDeclaration cd = module.constant(key.name());
      if (cd != null) {
        result = cd.constant();
      } else {
        throw new ResolveError("unable to find constant " + key);
      }
    }

    constantCache.put(key, result);

    return result;
  }

  /**
   * The following is a helper method for resolveAsConstant. It takes a given
   * expression (rather than the name of a constant) and expands to a value
   * (where possible). If the expression contains, for example, method or
   * function declarations then this will certainly fail (producing a syntax
   * error).
   *
   * @param key
   *            --- name of constant we are expanding.
   * @param context
   *            --- context in which to resolve this constant.
   * @param visited
   *            --- set of all constants seen during this traversal (used to
   *            detect cycles).
   */
  private Constant resolveAsConstant(Expr expr, Context context,
      HashSet<NameID> visited) {
    try {
      if (expr instanceof Expr.Constant) {
        Expr.Constant c = (Expr.Constant) expr;
        return c.value;
      } else if (expr instanceof Expr.ConstantAccess) {
        Expr.ConstantAccess c = (Expr.ConstantAccess) expr;
        ArrayList<String> qualifications = new ArrayList<String>();
        if (c.qualification != null) {
          for (String n : c.qualification) {
            qualifications.add(n);
          }
        }
        qualifications.add(c.name);
        try {
          NameID nid = resolveAsName(qualifications, context);
          return resolveAsConstant(nid, visited);
        } catch (ResolveError e) {
          syntaxError(errorMessage(UNKNOWN_VARIABLE), context, expr);
          return null;
        }
      } else if (expr instanceof Expr.BinOp) {
        Expr.BinOp bop = (Expr.BinOp) expr;
        Constant lhs = resolveAsConstant(bop.lhs, context, visited);
        Constant rhs = resolveAsConstant(bop.rhs, context, visited);
        return evaluate(bop, lhs, rhs, context);
      } else if (expr instanceof Expr.UnOp) {
        Expr.UnOp uop = (Expr.UnOp) expr;
        Constant lhs = resolveAsConstant(uop.mhs, context, visited);
        return evaluate(uop, lhs, context);
      } else if (expr instanceof Expr.Set) {
        Expr.Set nop = (Expr.Set) expr;
        ArrayList<Constant> values = new ArrayList<Constant>();
        for (Expr arg : nop.arguments) {
          values.add(resolveAsConstant(arg, context, visited));
        }
        return Constant.V_SET(values);
      } else if (expr instanceof Expr.List) {
        Expr.List nop = (Expr.List) expr;
        ArrayList<Constant> values = new ArrayList<Constant>();
        for (Expr arg : nop.arguments) {
          values.add(resolveAsConstant(arg, context, visited));
        }
        return Constant.V_LIST(values);
      } else if (expr instanceof Expr.Record) {
        Expr.Record rg = (Expr.Record) expr;
        HashMap<String, Constant> values = new HashMap<String, Constant>();
        for (Map.Entry<String, Expr> e : rg.fields.entrySet()) {
          Constant v = resolveAsConstant(e.getValue(), context,
              visited);
          if (v == null) {
            return null;
          }
          values.put(e.getKey(), v);
        }
        return Constant.V_RECORD(values);
      } else if (expr instanceof Expr.Tuple) {
        Expr.Tuple rg = (Expr.Tuple) expr;
        ArrayList<Constant> values = new ArrayList<Constant>();
        for (Expr e : rg.fields) {
          Constant v = resolveAsConstant(e, context, visited);
          if (v == null) {
            return null;
          }
          values.add(v);
        }
        return Constant.V_TUPLE(values);
      } else if (expr instanceof Expr.Map) {
        Expr.Map rg = (Expr.Map) expr;
        HashSet<Pair<Constant, Constant>> values = new HashSet<Pair<Constant, Constant>>();
        for (Pair<Expr, Expr> e : rg.pairs) {
          Constant key = resolveAsConstant(e.first(), context,
              visited);
          Constant value = resolveAsConstant(e.second(), context,
              visited);
          if (key == null || value == null) {
            return null;
          }
          values.add(new Pair<Constant, Constant>(key, value));
        }
        return Constant.V_MAP(values);
      } else if (expr instanceof Expr.FunctionOrMethod) {
        // TODO: add support for proper lambdas
        Expr.FunctionOrMethod f = (Expr.FunctionOrMethod) expr;
        return Constant.V_LAMBDA(f.nid, f.type.raw());
      }
    } catch (SyntaxError.InternalFailure e) {
      throw e;
    } catch (Throwable e) {
      internalFailure(e.getMessage(), context, expr, e);
    }

    internalFailure("unknown constant expression: "
        + expr.getClass().getName(), context, expr);
    return null; // deadcode
  }

  /**
   * Determine whether a name is visible in a given context. This effectively
   * corresponds to checking whether or not the already name exists in the
   * given context; or, a public or protected named is imported from another
   * file.
   *
   * @param nid
   *            Name to check modifiers of
   * @param context
   *            Context in which we are trying to access named item
   *
   * @return True if given context permitted to access name
   * @throws IOException
   */
  public boolean isNameVisible(NameID nid, Context context) throws IOException {
    // Any element in the same file is automatically visible
    if (nid.module().equals(context.file().module)) {
      return true;
    } else {
      return hasModifier(nid, context, Modifier.PUBLIC)
          || hasModifier(nid, context, Modifier.PROTECTED);
    }
  }

  /**
   * Determine whether a named type is fully visible in a given context. This
   * effectively corresponds to checking whether or not the already type
   * exists in the given context; or, a public type is imported from another
   * file.
   *
   * @param nid
   *            Name to check modifiers of
   * @param context
   *            Context in which we are trying to access named item
   *
   * @return True if given context permitted to access name
   * @throws IOException
   */
  public boolean isTypeVisible(NameID nid, Context context) throws IOException {
    // Any element in the same file is automatically visible
    if (nid.module().equals(context.file().module)) {
      return true;
    } else {
      return hasModifier(nid, context, Modifier.PUBLIC);
    }
  }

  /**
   * Determine whether a named item has a modifier matching one of a given
   * list. This is particularly useful for checking visibility (e.g. public,
   * private, etc) of named items.
   *
   * @param nid
   *            Name to check modifiers of
   * @param context
   *            Context in which we are trying to access named item
   * @param modifiers
   *
   * @return True if given context permitted to access name
   * @throws IOException
   */
  public boolean hasModifier(NameID nid, Context context, Modifier modifier)
      throws IOException {
    Path.ID mid = nid.module();

    // Attempt to access source file first.
    WhileyFile wf = builder.getSourceFile(mid);
    if (wf != null) {
      // Source file location, so check visible of element.
      WhileyFile.NamedDeclaration nd = wf.declaration(nid.name());
      return nd != null && nd.hasModifier(modifier);
    } else {
      // Source file not being compiled, therefore attempt to access wyil
      // file directly.

      // we have to do the following basically because we don't load
      // modifiers properly out of jvm class files (at the moment).
      // return false;
      WyilFile w = builder.getModule(mid);
      List<WyilFile.Block> blocks = w.blocks();
      for (int i = 0; i != blocks.size(); ++i) {
        WyilFile.Block d = blocks.get(i);
        if (d instanceof WyilFile.Declaration) {
          WyilFile.Declaration nd = (WyilFile.Declaration) d;
          return nd != null && nd.hasModifier(modifier);
        }
      }
      return false;
    }
  }

  // =========================================================================
  // Constant Evaluation
  // =========================================================================

  /**
   * Evaluate a given unary operator on a given input value.
   *
   * @param operator
   *            Unary operator to evaluate
   * @param operand
   *            Operand to apply operator on
   * @param context
   *            Context in which to apply operator (useful for error
   *            reporting)
   * @return
   */
  private Constant evaluate(Expr.UnOp operator, Constant operand,
      Context context) {
    switch (operator.op) {
    case NOT:
      if (operand instanceof Constant.Bool) {
        Constant.Bool b = (Constant.Bool) operand;
        return Constant.V_BOOL(!b.value);
      }
      syntaxError(errorMessage(INVALID_BOOLEAN_EXPRESSION), context,
          operator);
      break;
    case NEG:
      if (operand instanceof Constant.Integer) {
        Constant.Integer b = (Constant.Integer) operand;
        return Constant.V_INTEGER(b.value.negate());
      } else if (operand instanceof Constant.Decimal) {
        Constant.Decimal b = (Constant.Decimal) operand;
        return Constant.V_DECIMAL(b.value.negate());
      }
      syntaxError(errorMessage(INVALID_NUMERIC_EXPRESSION), context,
          operator);
      break;
    case INVERT:
      if (operand instanceof Constant.Byte) {
        Constant.Byte b = (Constant.Byte) operand;
        return Constant.V_BYTE((byte) ~b.value);
      }
      break;
    }
    syntaxError(errorMessage(INVALID_UNARY_EXPRESSION), context, operator);
    return null;
  }

  private Constant evaluate(Expr.BinOp bop, Constant v1, Constant v2,
      Context context) {
    Type v1_type = v1.type();
    Type v2_type = v2.type();
    Type lub = Type.Union(v1_type, v2_type);

    // FIXME: there are bugs here related to coercions.

    if (Type.isSubtype(Type.T_BOOL, lub)) {
      return evaluateBoolean(bop, (Constant.Bool) v1, (Constant.Bool) v2,
          context);
    } else if (Type.isSubtype(Type.T_INT, lub)) {
      return evaluate(bop, (Constant.Integer) v1, (Constant.Integer) v2,
          context);
    } else if (Type.isSubtype(Type.T_REAL, v1_type)
        && Type.isSubtype(Type.T_REAL, v1_type)) {
      if (v1 instanceof Constant.Integer) {
        Constant.Integer i1 = (Constant.Integer) v1;
        v1 = Constant.V_DECIMAL(new BigDecimal(i1.value));
      } else if (v2 instanceof Constant.Integer) {
        Constant.Integer i2 = (Constant.Integer) v2;
        v2 = Constant.V_DECIMAL(new BigDecimal(i2.value));
      }
      return evaluate(bop, (Constant.Decimal) v1, (Constant.Decimal) v2,
          context);
    } else if (Type.isSubtype(Type.T_LIST_ANY, lub)) {
      return evaluate(bop, (Constant.List) v1, (Constant.List) v2,
          context);
    } else if (Type.isSubtype(Type.T_SET_ANY, lub)) {
      return evaluate(bop, (Constant.Set) v1, (Constant.Set) v2, context);
    }
    syntaxError(errorMessage(INVALID_BINARY_EXPRESSION), context, bop);
    return null;
  }

  private Constant evaluateBoolean(Expr.BinOp bop, Constant.Bool v1,
      Constant.Bool v2, Context context) {
    switch (bop.op) {
    case AND:
      return Constant.V_BOOL(v1.value & v2.value);
    case OR:
      return Constant.V_BOOL(v1.value | v2.value);
    case XOR:
      return Constant.V_BOOL(v1.value ^ v2.value);
    }
    syntaxError(errorMessage(INVALID_BOOLEAN_EXPRESSION), context, bop);
    return null;
  }

  private Constant evaluate(Expr.BinOp bop, Constant.Integer v1,
      Constant.Integer v2, Context context) {
    switch (bop.op) {
    case ADD:
      return Constant.V_INTEGER(v1.value.add(v2.value));
    case SUB:
      return Constant.V_INTEGER(v1.value.subtract(v2.value));
    case MUL:
      return Constant.V_INTEGER(v1.value.multiply(v2.value));
    case DIV:
      return Constant.V_INTEGER(v1.value.divide(v2.value));
    case REM:
      return Constant.V_INTEGER(v1.value.remainder(v2.value));
    }
    syntaxError(errorMessage(INVALID_NUMERIC_EXPRESSION), context, bop);
    return null;
  }

  private Constant evaluate(Expr.BinOp bop, Constant.Decimal v1,
      Constant.Decimal v2, Context context) {
    switch (bop.op) {
    case ADD:
      return Constant.V_DECIMAL(v1.value.add(v2.value));
    case SUB:
      return Constant.V_DECIMAL(v1.value.subtract(v2.value));
    case MUL:
      return Constant.V_DECIMAL(v1.value.multiply(v2.value));
    case DIV:
      return Constant.V_DECIMAL(v1.value.divide(v2.value));
    }
    syntaxError(errorMessage(INVALID_NUMERIC_EXPRESSION), context, bop);
    return null;
  }

  private Constant evaluate(Expr.BinOp bop, Constant.List v1,
      Constant.List v2, Context context) {
    switch (bop.op) {
    case ADD:
      ArrayList<Constant> vals = new ArrayList<Constant>(v1.values);
      vals.addAll(v2.values);
      return Constant.V_LIST(vals);
    }
    syntaxError(errorMessage(INVALID_LIST_EXPRESSION), context, bop);
    return null;
  }

  private Constant evaluate(Expr.BinOp bop, Constant.Set v1, Constant.Set v2,
      Context context) {
    switch (bop.op) {
    case UNION: {
      HashSet<Constant> vals = new HashSet<Constant>(v1.values);
      vals.addAll(v2.values);
      return Constant.V_SET(vals);
    }
    case INTERSECTION: {
      HashSet<Constant> vals = new HashSet<Constant>();
      for (Constant v : v1.values) {
        if (v2.values.contains(v)) {
          vals.add(v);
        }
      }
      return Constant.V_SET(vals);
    }
    case SUB: {
      HashSet<Constant> vals = new HashSet<Constant>();
      for (Constant v : v1.values) {
        if (!v2.values.contains(v)) {
          vals.add(v);
        }
      }
      return Constant.V_SET(vals);
    }
    }
    syntaxError(errorMessage(INVALID_SET_EXPRESSION), context, bop);
    return null;
  }

  // =========================================================================
  // expandAsType
  // =========================================================================

  public Nominal.EffectiveSet expandAsEffectiveSet(Nominal lhs)
      throws IOException, ResolveError {
    Type raw = lhs.raw();
    if (raw instanceof Type.EffectiveSet) {
      Type nominal = expandOneLevel(lhs.nominal());
      if (!(nominal instanceof Type.EffectiveSet)) {
        nominal = raw; // discard nominal information
      }
      return (Nominal.EffectiveSet) Nominal.construct(nominal, raw);
    } else {
      return null;
    }
  }

  public Nominal.EffectiveList expandAsEffectiveList(Nominal lhs)
      throws IOException, ResolveError {
    Type raw = lhs.raw();
    if (raw instanceof Type.EffectiveList) {
      Type nominal = expandOneLevel(lhs.nominal());
      if (!(nominal instanceof Type.EffectiveList)) {
        nominal = raw; // discard nominal information
      }
      return (Nominal.EffectiveList) Nominal.construct(nominal, raw);
    } else {
      return null;
    }
  }

  public Nominal.EffectiveCollection expandAsEffectiveCollection(Nominal lhs)
      throws IOException, ResolveError {
    Type raw = lhs.raw();
    if (raw instanceof Type.EffectiveCollection) {
      Type nominal = expandOneLevel(lhs.nominal());
      if (!(nominal instanceof Type.EffectiveCollection)) {
        nominal = raw; // discard nominal information
      }
      return (Nominal.EffectiveCollection) Nominal
          .construct(nominal, raw);
    } else {
      return null;
    }
  }

  public Nominal.EffectiveIndexible expandAsEffectiveMap(Nominal lhs)
      throws IOException, ResolveError {
    Type raw = lhs.raw();
    if (raw instanceof Type.EffectiveIndexible) {
      Type nominal = expandOneLevel(lhs.nominal());
      if (!(nominal instanceof Type.EffectiveIndexible)) {
        nominal = raw; // discard nominal information
      }
      return (Nominal.EffectiveIndexible) Nominal.construct(nominal, raw);
    } else {
      return null;
    }
  }

  public Nominal.EffectiveMap expandAsEffectiveDictionary(Nominal lhs)
      throws IOException, ResolveError {
    Type raw = lhs.raw();
    if (raw instanceof Type.EffectiveMap) {
      Type nominal = expandOneLevel(lhs.nominal());
      if (!(nominal instanceof Type.EffectiveMap)) {
        nominal = raw; // discard nominal information
      }
      return (Nominal.EffectiveMap) Nominal.construct(nominal, raw);
    } else {
      return null;
    }
  }

  public Nominal.EffectiveRecord expandAsEffectiveRecord(Nominal lhs)
      throws IOException, ResolveError {
    Type raw = lhs.raw();

    if (raw instanceof Type.Record) {
      Type nominal = expandOneLevel(lhs.nominal());
      if (!(nominal instanceof Type.Record)) {
        nominal = (Type) raw; // discard nominal information
      }
      return (Nominal.Record) Nominal.construct(nominal, raw);
    } else if (raw instanceof Type.UnionOfRecords) {
      Type nominal = expandOneLevel(lhs.nominal());
      if (!(nominal instanceof Type.UnionOfRecords)) {
        nominal = (Type) raw; // discard nominal information
      }
      return (Nominal.UnionOfRecords) Nominal.construct(nominal, raw);
    }
    {
      return null;
    }
  }

  public Nominal.EffectiveTuple expandAsEffectiveTuple(Nominal lhs)
      throws IOException, ResolveError {
    Type raw = lhs.raw();
    if (raw instanceof Type.EffectiveTuple) {
      Type nominal = expandOneLevel(lhs.nominal());
      if (!(nominal instanceof Type.EffectiveTuple)) {
        nominal = raw; // discard nominal information
      }
      return (Nominal.EffectiveTuple) Nominal.construct(nominal, raw);
    } else {
      return null;
    }
  }

  public Nominal.Reference expandAsReference(Nominal lhs) throws IOException, ResolveError {
    Type.Reference raw = Type.effectiveReference(lhs.raw());
    if (raw != null) {
      Type nominal = expandOneLevel(lhs.nominal());
      if (!(nominal instanceof Type.Reference)) {
        nominal = raw; // discard nominal information
      }
      return (Nominal.Reference) Nominal.construct(nominal, raw);
    } else {
      return null;
    }
  }

  public Nominal.FunctionOrMethod expandAsFunctionOrMethod(Nominal lhs)
      throws IOException, ResolveError {
    Type.FunctionOrMethod raw = Type.effectiveFunctionOrMethod(lhs.raw());
    if (raw != null) {
      Type nominal = expandOneLevel(lhs.nominal());
      if (!(nominal instanceof Type.FunctionOrMethod)) {
        nominal = raw; // discard nominal information
      }
      return (Nominal.FunctionOrMethod) Nominal.construct(nominal, raw);
    } else {
      return null;
    }
  }

  private Type expandOneLevel(Type type) throws IOException, ResolveError {
    if (type instanceof Type.Nominal) {
      Type.Nominal nt = (Type.Nominal) type;
      NameID nid = nt.name();
      Path.ID mid = nid.module();

      WhileyFile wf = builder.getSourceFile(mid);
      Type r = null;

      if (wf != null) {
        WhileyFile.Declaration decl = wf.declaration(nid.name());
        if (decl instanceof WhileyFile.Type) {
          WhileyFile.Type td = (WhileyFile.Type) decl;
          r = resolveAsType(td.pattern.toSyntacticType(), td)
              .nominal();
        }
      } else {
        WyilFile m = builder.getModule(mid);
        WyilFile.TypeDeclaration td = m.type(nid.name());
        if (td != null) {
          r = td.type();
        }
      }
      if (r == null) {
        throw new ResolveError("unable to locate " + nid);
      }
      return expandOneLevel(r);
    } else if (type instanceof Type.Leaf || type instanceof Type.Reference
        || type instanceof Type.Tuple || type instanceof Type.Set
        || type instanceof Type.List || type instanceof Type.Map
        || type instanceof Type.Record
        || type instanceof Type.FunctionOrMethod
        || type instanceof Type.Negation) {
      return type;
    } else {
      Type.Union ut = (Type.Union) type;
      ArrayList<Type> bounds = new ArrayList<Type>();
      for (Type b : ut.bounds()) {
        bounds.add(expandOneLevel(b));
      }
      return Type.Union(bounds);
    }
  }

  // =========================================================================
  // Misc
  // =========================================================================

  // Check t1 :> t2
  private void checkIsSubtype(Nominal t1, Nominal t2, SyntacticElement elem) {
    if (!Type.isSubtype(t1.raw(), t2.raw())) {
      syntaxError(
          errorMessage(SUBTYPE_ERROR, t1.nominal(), t2.nominal()),
          filename, elem);
    }
  }

  private void checkIsSubtype(Nominal t1, Expr t2) {
    if (!Type.isSubtype(t1.raw(), t2.result().raw())) {
      // We use the nominal type for error reporting, since this includes
      // more helpful names.
      syntaxError(
          errorMessage(SUBTYPE_ERROR, t1.nominal(), t2.result()
              .nominal()), filename, t2);
    }
  }

  private void checkIsSubtype(Type t1, Expr t2) {
    if (!Type.isSubtype(t1, t2.result().raw())) {
      // We use the nominal type for error reporting, since this includes
      // more helpful names.
      syntaxError(errorMessage(SUBTYPE_ERROR, t1, t2.result().nominal()),
          filename, t2);
    }
  }

  // Check t1 :> t2
  private void checkIsSubtype(Nominal t1, Nominal t2, SyntacticElement elem,
      Context context) {
    if (!Type.isSubtype(t1.raw(), t2.raw())) {
      syntaxError(
          errorMessage(SUBTYPE_ERROR, t1.nominal(), t2.nominal()),
          context, elem);
    }
  }

  private void checkIsSubtype(Nominal t1, Expr t2, Context context) {
    if (!Type.isSubtype(t1.raw(), t2.result().raw())) {
      // We use the nominal type for error reporting, since this includes
      // more helpful names.
      syntaxError(
          errorMessage(SUBTYPE_ERROR, t1.nominal(), t2.result()
              .nominal()), context, t2);
    }
  }

  private void checkIsSubtype(Type t1, Expr t2, Context context) {
    if (!Type.isSubtype(t1, t2.result().raw())) {
      // We use the nominal type for error reporting, since this includes
      // more helpful names.
      syntaxError(errorMessage(SUBTYPE_ERROR, t1, t2.result().nominal()),
          context, t2);
    }
  }

  // Check t1 <: t2 or t1 <: t3 ...
  private void checkSuptypes(Expr e, Context context, Nominal... types) {
    Nominal t1 = e.result();
    for(Nominal t : types) {
      if (Type.isSubtype(t.raw(), t1.raw())) {
        return; // OK
      }
    }
    // Construct the message
    String msg = "expecting ";
    boolean firstTime = true;
    for(Nominal t : types) {
      if(!firstTime) {
        msg += " or ";
      }
      firstTime=false;
      msg = msg + t.nominal();
    }
    msg += ", found " + t1.nominal();
    syntaxError(msg, context, e);
  }

  // ==========================================================================
  // Environment Class
  // ==========================================================================

  /**
   * <p>
   * Responsible for mapping source-level variables to their declared and
   * actual types, at any given program point. Since the flow-type checker
   * uses a flow-sensitive approach to type checking, then the typing
   * environment will change as we move through the statements of a function
   * or method.
   * </p>
   *
   * <p>
   * This class is implemented in a functional style to minimise possible
   * problems related to aliasing (which have been a problem in the past). To
   * improve performance, reference counting is to ensure that cloning the
   * underling map is only performed when actually necessary.
   * </p>
   *
   * @author David J. Pearce
   *
   */
  private static final class Environment {

    /**
     * The mapping of variables to their declared type.
     */
    private final HashMap<String, Nominal> declaredTypes;

    /**
     * The mapping of variables to their current type.
     */
    private final HashMap<String, Nominal> currentTypes;

    /**
     * The reference count, which indicate how many references to this
     * environment there are. When there is only one reference, then the put
     * and putAll operations will perform an "inplace" update (i.e. without
     * cloning the underlying collection).
     */
    private int count; // refCount

    /**
     * Construct an empty environment. Initially the reference count is 1.
     */
    public Environment() {
      count = 1;
      currentTypes = new HashMap<String, Nominal>();
      declaredTypes = new HashMap<String, Nominal>();
    }

    /**
     * Construct a fresh environment as a copy of another map. Initially the
     * reference count is 1.
     */
    private Environment(Environment environment) {
      count = 1;
      this.currentTypes = (HashMap<String, Nominal>) environment.currentTypes.clone();
      this.declaredTypes = (HashMap<String, Nominal>) environment.declaredTypes.clone();
    }

    /**
     * Get the type associated with a given variable at the current program
     * point, or null if that variable is not declared.
     *
     * @param variable
     *            Variable to return type for.
     * @return
     */
    public Nominal getCurrentType(String variable) {
      return currentTypes.get(variable);
    }

    /**
     * Get the declared type of a given variable, or null if that variable
     * is not declared.
     *
     * @param variable
     *            Variable to return type for.
     * @return
     */
    public Nominal getDeclaredType(String variable) {
      return declaredTypes.get(variable);
    }

    /**
     * Check whether a given variable is declared within this environment.
     *
     * @param variable
     * @return
     */
    public boolean containsKey(String variable) {
      return declaredTypes.containsKey(variable);
    }

    /**
     * Return the set of declared variables in this environment (a.k.a the
     * domain).
     *
     * @return
     */
    public Set<String> keySet() {
      return declaredTypes.keySet();
    }

    /**
     * Declare a new variable with a given type.  In the case that this
     * environment has a reference count of 1, then an "in place" update is
     * performed. Otherwise, a fresh copy of this environment is returned
     * with the given variable associated with the given type, whilst this
     * environment is unchanged.
     *
     * @param variable
     *            Name of variable to be declared with given type
     * @param declared
     *            Declared type of the given variable
     * @param initial
     *            Initial type of given variable
     * @return An updated version of the environment which contains the new
     *         association.
     */
    public Environment declare(String variable, Nominal declared, Nominal initial) {
      if (declaredTypes.containsKey(variable)) {
        throw new RuntimeException("Variable already declared - "
            + variable);
      }
      if (count == 1) {
        declaredTypes.put(variable, declared);
        currentTypes.put(variable, initial);
        return this;
      } else {
        Environment nenv = new Environment(this);
        nenv.declaredTypes.put(variable, declared);
        nenv.currentTypes.put(variable, initial);
        count--;
        return nenv;
      }
    }

    /**
     * Update the current type of a given variable. If that variable already
     * had a current type, then this is overwritten. In the case that this
     * environment has a reference count of 1, then an "in place" update is
     * performed. Otherwise, a fresh copy of this environment is returned
     * with the given variable associated with the given type, whilst this
     * environment is unchanged.
     *
     * @param variable
     *            Name of variable to be associated with given type
     * @param type
     *            Type to associated with given variable
     * @return An updated version of the environment which contains the new
     *         association.
     */
    public Environment update(String variable, Nominal type) {
      if (!declaredTypes.containsKey(variable)) {
        throw new RuntimeException("Variable not declared - "
            + variable);
      }
      if (count == 1) {
        currentTypes.put(variable, type);
        return this;
      } else {
        Environment nenv = new Environment(this);
        nenv.currentTypes.put(variable, type);
        count--;
        return nenv;
      }
    }

    /**
     * Remove a variable and any associated type from this environment. In
     * the case that this environment has a reference count of 1, then an
     * "in place" update is performed. Otherwise, a fresh copy of this
     * environment is returned with the given variable and any association
     * removed.
     *
     * @param variable
     *            Name of variable to be removed from the environment
     * @return An updated version of the environment in which the given
     *         variable no longer exists.
     */
    public Environment remove(String key) {
      if (count == 1) {
        declaredTypes.remove(key);
        currentTypes.remove(key);
        return this;
      } else {
        Environment nenv = new Environment(this);
        nenv.currentTypes.remove(key);
        nenv.declaredTypes.remove(key);
        count--;
        return nenv;
      }
    }

    /**
     * Merge a given environment with this environment to produce an
     * environment representing their join. Only variables from a given set
     * are included in the result, and all such variables are required to be
     * declared in both environments. The type of each variable included is
     * the union of its type in this environment and the other environment.
     *
     * @param declared
     *            The set of declared variables which should be included in
     *            the result. The intuition is that these are the variables
     *            which were declared in both environments before whatever
     *            updates were made.
     * @param env
     *            The given environment to be merged with this environment.
     * @return
     */
    public final Environment merge(Set<String> declared, Environment env) {

      // first, need to check for the special bottom value case.

      if (this == BOTTOM) {
        return env;
      } else if (env == BOTTOM) {
        return this;
      }

      // ok, not bottom so compute intersection.

      this.free();
      env.free();

      Environment result = new Environment();
      for (String variable : declared) {
        Nominal lhs_t = this.getCurrentType(variable);
        Nominal rhs_t = env.getCurrentType(variable);
        result.declare(variable, this.getDeclaredType(variable),
            Nominal.Union(lhs_t, rhs_t));
      }

      return result;
    }

    /**
     * Create a fresh copy of this environment. In fact, this operation
     * simply increments the reference count of this environment and returns
     * it.
     */
    public Environment clone() {
      count++;
      return this;
    }

    /**
     * Decrease the reference count of this environment by one.
     */
    public void free() {
      --count;
    }

    public String toString() {
      return currentTypes.toString();
    }

    public int hashCode() {
      return currentTypes.hashCode();
    }

    public boolean equals(Object o) {
      if (o instanceof Environment) {
        Environment r = (Environment) o;
        return currentTypes.equals(r.currentTypes);
      }
      return false;
    }
  }

  private static final Environment BOTTOM = new Environment();

}
TOP

Related Classes of wyc.builder.FlowTypeChecker

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.