Merge "js/core: Retry the caveat page"
diff --git a/src/vdl/canonicalize.js b/src/vdl/canonicalize.js
index 31392c4..ae051f3 100644
--- a/src/vdl/canonicalize.js
+++ b/src/vdl/canonicalize.js
@@ -41,6 +41,7 @@
   value: canonicalizeExternal,
   type: canonicalizeTypeExternal,
   zero: canonicalizeZero,
+  construct: canonicalizeConstruct,
   fill: canonicalizeFill,
   reduce: canonicalizeReduce
 };
@@ -59,7 +60,21 @@
  * @return {*} The canonical wire format of inValue.
  */
 function canonicalizeFill(inValue, t) {
-  return canonicalizeExternal(inValue, t, true);
+  return canonicalizeExternal(inValue, t, true, false);
+}
+
+/**
+ * Creates a new copy of inValue that is a constructor's in-memory format of
+ * inValue. Unlike reduce, it does not convert the copy to its native form.
+ * @function
+ * @name construct
+ * @memberof module:vanadium.vdl.canonicalize
+ * @param {*} inValue The value to convert to the flattened wire format.
+ * @param {module:vanadium.vdl.Type} t The type of inValue.
+ * @return {*} The canonical in-format of inValue.
+ */
+function canonicalizeConstruct(inValue, t) {
+  return canonicalizeExternal(inValue, t, false, false);
 }
 
 /**
@@ -73,15 +88,15 @@
  * @return {*} The canonical in-format of inValue.
  */
 function canonicalizeReduce(inValue, t) {
-  return canonicalizeExternal(inValue, t, false);
+  return canonicalizeExternal(inValue, t, false, true);
 }
 
 /**
  * Alias for canonicalizeExternal with inValue = undefined.
  * @private
  */
-function canonicalizeZero(t, deepWrap) {
-  return canonicalizeExternal(undefined, t, deepWrap);
+function canonicalizeZero(t, deepWrap, toNative) {
+  return canonicalizeExternal(undefined, t, deepWrap, toNative);
 }
 
 /**
@@ -93,17 +108,22 @@
  * @private
  * @param {*} inValue The value to be canonicalized.
  * @param {module:vanadium.vdl.Type} t The target type.
- * @param {boolean=} deepWrap Whether or not to deeply wrap. Defaults to true.
+ * @param {boolean=} deepWrap Whether or not to deeply wrap the contents.
+ * @param {boolean=} toNative Whether or not the final result needs to be
+ * converted to a native value.
  * @return {*} The canonicalized value. May potentially refer to v.
  */
-function canonicalizeExternal(inValue, t, deepWrap) {
+function canonicalizeExternal(inValue, t, deepWrap, toNative) {
   if (deepWrap === undefined) {
     deepWrap = true;
   }
+  if (toNative === undefined) {
+    toNative = !deepWrap;
+  }
 
   // Canonicalize the given value as a top-level value.
   var inType = TypeUtil.isTyped(inValue) ? inValue._type : undefined;
-  return canonicalize(inValue, inType, t, deepWrap, new Map(), true);
+  return canonicalize(inValue, inType, t, deepWrap, new Map(), true, toNative);
 }
 
 /**
@@ -119,16 +139,18 @@
  * @param {boolean} deepWrap Whether or not to deeply wrap the contents.
  * @param {object} seen A cache from old to new
  * references that based on type.
- * @param {boolean} isTopLevelValue If true, then the return value is wrapped.
+ * @param {boolean} topLevel If true, then the return value is wrapped.
+ * @param {boolean} toNative Whether or not the final result needs to be
+ * converted to a native value.
  * @return {*} The canonicalized value. May potentially refer to v.
  */
-function canonicalize(inValue, inType, t, deepWrap, seen, isTopLevelValue) {
+function canonicalize(inValue, inType, t, deepWrap, seen, topLevel, toNative) {
   if (!(t instanceof Type)) {
     t = new Type(t);
   }
 
   // This value needs a wrapper if either flag is set.
-  var needsWrap = deepWrap || isTopLevelValue;
+  var needsWrap = deepWrap || topLevel;
 
   // Special case JSValue. Convert the inValue to JSValue form.
   var isJSValue = types.JSVALUE.equals(t);
@@ -155,7 +177,7 @@
     types.ANY.equals(inValue._type)) {
 
     return canonicalize(TypeUtil.unwrap(inValue), inValue._type, t, deepWrap,
-      seen, isTopLevelValue);
+      seen, topLevel, toNative);
   }
 
   // Special case TypeObject. See canonicalizeType.
@@ -217,11 +239,11 @@
     } else {
       // The value inside an ANY needs to be canonicalized as a top-level value.
       canonValue = canonicalize(v, guessedType, guessedType, deepWrap, seen,
-        true);
+        true, toNative);
     }
   } else {
     v = TypeUtil.unwrap(inValue);
-    canonValue = canonicalizeInternal(deepWrap, v, inType, t, seen, outValue);
+    canonValue = canonicalizeInternal(v, inType, t, deepWrap, seen, outValue);
   }
 
   // We need to copy the msg field of WireError to the message property of
@@ -242,12 +264,12 @@
     return outValue;
   }
 
-  // Special case JSValue. If !deepWrap, convert the canonValue to native form.
-  if (isJSValue && !deepWrap) {
+  // Special case JSValue. If toNative, convert the canonValue to native form.
+  if (isJSValue && toNative) {
     return jsValueConvert.toNative(canonValue);
   }
-  // Special case Native Value. If !deepWrap, return to native form.
-  if (isNative && !deepWrap) {
+  // Special case Native Value. If toNative, return to native form.
+  if (isNative && toNative) {
     return nativeTypeRegistry.fromWireValue(t, canonValue);
   }
 
@@ -259,7 +281,8 @@
  * unwrapped value.
  * @private
  */
-function canonicalizeInternal(deepWrap, v, inType, t, seen, outValue) {
+function canonicalizeInternal(v, inType, t, deepWrap, seen, outValue) {
+
   // Any undefined value obtains its zero-value.
   if (v === undefined) {
     var zero = zeroValue(t);
@@ -267,12 +290,14 @@
     // The deepWrap flag affects whether the zero value needs internal wrapping.
     // Without it, the zero value is correct.
     if (!deepWrap) {
-      return TypeUtil.unwrap(canonicalize(zero, inType, t, false, seen, false));
+      return TypeUtil.unwrap(canonicalize(zero, inType, t, false, seen, false,
+        true));
     }
 
     // Otherwise, canonicalize but remove the top-level wrapping.
     // The top-level will be reapplied by this function's caller.
-    return TypeUtil.unwrap(canonicalize(zero, inType, t, true, seen, false));
+    return TypeUtil.unwrap(canonicalize(zero, inType, t, true, seen, false,
+      false));
   } else if (v === null && (t.kind !== kind.ANY && t.kind !== kind.OPTIONAL)) {
     throw makeError(v, t, 'value is null for non-optional type');
   }
@@ -293,7 +318,8 @@
       if (v === null) {
         return null;
       }
-      return canonicalize(v, inElemType, t.elem, deepWrap, seen, false);
+      return canonicalize(v, inElemType, t.elem, deepWrap, seen, false,
+        !deepWrap);
     case kind.BOOL:
       // Verify the value is a boolean.
       if (typeof v !== 'boolean') {
@@ -405,7 +431,7 @@
       outValue = new Array(neededLen);
       for (var arri = 0; arri < neededLen; arri++) {
         outValue[arri] = canonicalize(v[arri], inElemType, t.elem, deepWrap,
-          seen, false);
+          seen, false, !deepWrap);
       }
       return outValue;
     case kind.SET:
@@ -428,7 +454,7 @@
       outValue = new Set();
       v.forEach(function(value) {
         outValue.add(canonicalize(value, inKeyType, t.key, deepWrap, seen,
-          false));
+          false, !deepWrap));
       });
 
       return outValue;
@@ -460,8 +486,9 @@
           inElemType = lookupFieldType(inType, key);
         }
         outValue.set(
-          canonicalize(key, inKeyType, t.key, deepWrap, seen, false),
-          canonicalize(val, inElemType, t.elem, deepWrap, seen, false)
+          canonicalize(key, inKeyType, t.key, deepWrap, seen, false, !deepWrap),
+          canonicalize(val, inElemType, t.elem, deepWrap, seen, false,
+            !deepWrap)
         );
       });
 
@@ -494,7 +521,7 @@
 
         // Each entry needs to be canonicalized too.
         outValue[fieldNameLower] = canonicalize(fieldVal, inFieldType,
-          fieldType, deepWrap, seen, false);
+          fieldType, deepWrap, seen, false, !deepWrap);
       }
 
       return outValue;
@@ -518,7 +545,7 @@
             // The field indexes may not match, so get the field type by name.
             inFieldType = lookupFieldType(inType, key);
             outValue[lowerKey] = canonicalize(v[lowerKey], inFieldType,
-              t.fields[i].type, deepWrap, seen, false);
+              t.fields[i].type, deepWrap, seen, false, !deepWrap);
             isSet = true;
           }
         }
@@ -676,7 +703,7 @@
 
   // Call canonicalize with this typeOfType. Even though typeOfType is a Struct,
   // behind the scenes, canonType will be a TypeObject.
-  var canonType = canonicalize(type, typeOfType, typeOfType, false, seen,
+  var canonType = canonicalize(type, typeOfType, typeOfType, false, seen, false,
     false);
 
   // Certain types may not be named.
diff --git a/src/vdl/create-constructor.js b/src/vdl/create-constructor.js
index b9b8293..3385a3e 100644
--- a/src/vdl/create-constructor.js
+++ b/src/vdl/create-constructor.js
@@ -78,7 +78,9 @@
       return new StructConstructor(val, deepWrap);
     }
     // Canonicalize the given value and copy the resultant fields.
-    var cpy = canonicalize.value(val, this._type, deepWrap);
+    var cpy = deepWrap ?
+      canonicalize.fill(val, this._type) :
+      canonicalize.construct(val, this._type);
 
     for (var fieldName in cpy) {
       if (!cpy.hasOwnProperty(fieldName)) {
@@ -103,7 +105,9 @@
     if (!(this instanceof WrappedConstructor)) {
       return new WrappedConstructor(val, deepWrap);
     }
-    var ideal = canonicalize.value(val, this._type, deepWrap);
+    var ideal = deepWrap ?
+      canonicalize.fill(val, this._type) :
+      canonicalize.reduce(val, this._type);
     this.val = ideal.val;
   };
   constructor.prototype._wrappedType = true;
diff --git a/test/vom/test-native-type.js b/test/vom/test-native-type.js
index 442a0f0..bd9b844 100644
--- a/test/vom/test-native-type.js
+++ b/test/vom/test-native-type.js
@@ -83,3 +83,30 @@
   });
   t.end();
 });
+
+test('time - direct construction', function(t) {
+  var seconds = 10;
+  var nanos = 345000000;
+  var dateStr = '0001-01-01T00:00:10.345Z';
+  var time = new Time({
+    seconds: seconds,
+    nanos: nanos
+  });
+
+  // Time was constructed properly.
+  t.ok(time.seconds, 'seconds exists');
+  t.equal(time.seconds.toNativeNumberApprox(), seconds, 'seconds match');
+  t.equal(time.nanos, nanos, 'nanos match');
+
+  // Time, like a wire time, converts to date properly.
+  var date = registry.fromWireValue(Time.prototype._type, time);
+  t.equal(date.getTime(), Date.parse(dateStr), 'date matches');
+
+  // Wire time has the same fields, but wrapped.
+  var wireTime = registry.fromNativeValue(Time.prototype._type, date);
+  t.equal(wireTime.seconds.val.toNativeNumberApprox(),
+    time.seconds.toNativeNumberApprox(), 'seconds match');
+  t.equal(wireTime.nanos.val, time.nanos, 'nanos match');
+
+  t.end();
+});
\ No newline at end of file