@Override
  public void typeResolve(Environment base) {
    if (typeParams != null) {
      FlatCheckedEnvironment params =
          new FlatCheckedEnvironment(getTypeParamDefinitions(), base, NameScope.NAMES);

      type = type.typeResolve(params, null);
    } else {
      type = type.typeResolve(base, null);
    }

    if (base.isVDMPP()) {
      name.setTypeQualifier(type.parameters);
    }

    if (body instanceof TCSubclassResponsibilityExpression
        || body instanceof TCNotYetSpecifiedExpression) {
      isUndefined = true;
    }

    if (precondition != null) {
      predef.typeResolve(base);
    }

    if (postcondition != null) {
      postdef.typeResolve(base);
    }

    for (TCPatternList pp : paramPatternList) {
      pp.typeResolve(base);
    }
  }
  @Override
  public void typeCheck(Environment base, NameScope scope) {
    TCDefinitionList defs = new TCDefinitionList();

    if (typeParams != null) {
      defs.addAll(getTypeParamDefinitions());
    }

    TypeComparator.checkComposeTypes(type, base, false);

    expectedResult = checkParams(paramPatternList.listIterator(), type);

    paramDefinitionList = getParamDefinitions();

    for (TCDefinitionList pdef : paramDefinitionList) {
      defs.addAll(pdef); // All definitions of all parameter lists
    }

    FlatEnvironment local = new FlatCheckedEnvironment(defs, base, scope);
    FlatCheckedEnvironment checked = (FlatCheckedEnvironment) local;
    checked.setStatic(accessSpecifier);
    checked.setEnclosingDefinition(this);
    checked.setFunctional(true);

    defs.typeCheck(local, scope);

    if (base.isVDMPP() && !accessSpecifier.isStatic) {
      local.add(getSelfDefinition());
    }

    if (predef != null) {
      TCBooleanType expected = new TCBooleanType(location);
      TCType b = predef.body.typeCheck(local, null, NameScope.NAMES, expected);

      if (!b.isType(TCBooleanType.class, location)) {
        report(3018, "Precondition returns unexpected type");
        detail2("Actual", b, "Expected", expected);
      }

      TCDefinitionList qualified = predef.body.getQualifiedDefs(local);

      if (!qualified.isEmpty()) {
        local = new FlatEnvironment(qualified, local); // NB Not checked!
      }
    }

    if (postdef != null) {
      TCPattern rp = new TCIdentifierPattern(name.getResultName(location));
      TCDefinitionList rdefs = rp.getDefinitions(expectedResult, NameScope.NAMES);
      FlatCheckedEnvironment post = new FlatCheckedEnvironment(rdefs, local, NameScope.NAMES);

      TCBooleanType expected = new TCBooleanType(location);
      TCType b = postdef.body.typeCheck(post, null, NameScope.NAMES, expected);

      if (!b.isType(TCBooleanType.class, location)) {
        report(3018, "Postcondition returns unexpected type");
        detail2("Actual", b, "Expected", expected);
      }
    }

    // This check returns the type of the function body in the case where
    // all of the curried parameter sets are provided.

    actualResult = body.typeCheck(local, null, scope, expectedResult);

    if (!TypeComparator.compatible(expectedResult, actualResult)) {
      report(3018, "Function returns unexpected type");
      detail2("Actual", actualResult, "Expected", expectedResult);
    }

    if (type.narrowerThan(accessSpecifier)) {
      report(3019, "Function parameter visibility less than function definition");
    }

    if (base.isVDMPP()
        && accessSpecifier.access == Token.PRIVATE
        && body instanceof TCSubclassResponsibilityExpression) {
      report(3329, "Abstract function/operation must be public or protected");
    }

    if (measure == null && recursive) {
      warning(5012, "Recursive function has no measure");
    } else if (measure != null) {
      if (base.isVDMPP()) measure.setTypeQualifier(getMeasureParams());
      measuredef = base.findName(measure, scope);

      if (measuredef == null) {
        measure.report(3270, "Measure " + measure + " is not in scope");
      } else if (!(measuredef instanceof TCExplicitFunctionDefinition)) {
        measure.report(3271, "Measure " + measure + " is not an explicit function");
      } else if (measuredef == this) {
        measure.report(3304, "Recursive function cannot be its own measure");
      } else {
        TCExplicitFunctionDefinition efd = (TCExplicitFunctionDefinition) measuredef;

        if (this.typeParams == null && efd.typeParams != null) {
          measure.report(3309, "Measure must not be polymorphic");
        } else if (this.typeParams != null && efd.typeParams == null) {
          measure.report(3310, "Measure must also be polymorphic");
        } else if (this.typeParams != null
            && efd.typeParams != null
            && !this.typeParams.equals(efd.typeParams)) {
          measure.report(3318, "Measure's type parameters must match function's");
          detail2("Actual", efd.typeParams, "Expected", typeParams);
        }

        TCFunctionType mtype = (TCFunctionType) measuredef.getType();

        if (typeParams != null) // Polymorphic, so compare "shape" of param signature
        {
          if (!mtype.parameters.toString().equals(getMeasureParams().toString())) {
            measure.report(3303, "Measure parameters different to function");
            detail2(measure.getName(), mtype.parameters, "Expected", getMeasureParams());
          }
        } else if (!TypeComparator.compatible(mtype.parameters, getMeasureParams())) {
          measure.report(3303, "Measure parameters different to function");
          detail2(measure.getName(), mtype.parameters, "Expected", getMeasureParams());
        }

        if (!(mtype.result instanceof TCNaturalType)) {
          if (mtype.result.isProduct(location)) {
            TCProductType pt = mtype.result.getProduct();

            for (TCType t : pt.types) {
              if (!(t instanceof TCNaturalType)) {
                measure.report(3272, "Measure range is not a nat, or a nat tuple");
                measure.detail("Actual", mtype.result);
                break;
              }
            }

            measureLexical = pt.types.size();
          } else {
            measure.report(3272, "Measure range is not a nat, or a nat tuple");
            measure.detail("Actual", mtype.result);
          }
        }
      }
    }

    if (!(body instanceof TCNotYetSpecifiedExpression)
        && !(body instanceof TCSubclassResponsibilityExpression)) {
      local.unusedCheck();
    }
  }