code: private String toTypeString(ClassTemplateSpec spec) {
      // If we're supporting projections, all fields, even required ones, may be absent.
      // To support this, we box all primitive field types.
      if (spec.getSchema() == null) { // custom type
        return escapedFullname(spec);
      }
      Type schemaType = spec.getSchema().getType();
      if (schemaType == Type.INT) {
        return "Int"; // TODO: just use Int32 here? (On a 32-bit platform, Int is the same size as Int32.)
      } else if (schemaType == Type.LONG) {
        return "Int"; // TODO: just use Int32 here? (On a 64-bit platform, Int is the same size as Int64.)
      } else if (schemaType == Type.FLOAT) {
        return "Float";
      } else if (schemaType == Type.DOUBLE) {
        return "Double";
      } else if (schemaType == Type.STRING) {
        return "String";
      } else if (schemaType == Type.BOOLEAN) {
        return "Bool";
      } else if (schemaType == Type.BYTES) {
        return "String"; // TODO(jbetz): provide an adapter for converting pegasus byte strings to swift byte[]
      } else if (schemaType == Type.FIXED) {
        return "String"; // TODO(jbetz): provide an adapter for converting pegasus byte strings to swift byte[]
      } else if (schemaType == Type.ENUM) {
        return escapedFullname(spec);
      } else if (schemaType == Type.RECORD) {
        return escapedFullname(spec);
      } else if (schemaType == Type.UNION) {
        return escapedFullname(spec);
      } else if (schemaType == Type.MAP) {
        return "[String: " + toTypeString(((CourierMapTemplateSpec) spec).getValueClass()) + "]";
      } else if (schemaType == Type.ARRAY) {
        return "[" + toTypeString(((ArrayTemplateSpec) spec).getItemClass()) + "]";
      } else {
        throw new IllegalArgumentException("unrecognized type: " + schemaType);
      }
    }
docs: emit string representing type