v23proxy: Support for cycles in mojom type -> vdl type conversion

Change-Id: I907f7aa4364def1796d40e96033f0a28678411e7
diff --git a/go/src/v.io/x/mojo/transcoder/type.go b/go/src/v.io/x/mojo/transcoder/type.go
index c54612c..ab5bec0 100644
--- a/go/src/v.io/x/mojo/transcoder/type.go
+++ b/go/src/v.io/x/mojo/transcoder/type.go
@@ -13,28 +13,37 @@
 	"v.io/v23/vdl"
 )
 
-/*// Given a descriptor mapping, produce 2 maps.
-// The former maps from mojom identifiers to VDL Type.
-// The latter maps from VDL Type string (hash cons) to the mojom identifier.
-// These maps are used to interconvert more easily.
-func AnalyzeMojomDescriptors(mp map[string]mojom_types.UserDefinedType) map[string]*vdl.Type {
-	m2V := make(map[string]*vdl.Type)
-	for s, udt := range mp {
-		m2V[s] = mojomToVDLTypeUDT(udt, mp)
-	}
-	return m2V
-}*/
+func MojomStructToVDLType(ms mojom_types.MojomStruct, mp map[string]mojom_types.UserDefinedType) (*vdl.Type, error) {
+	builder := &vdl.TypeBuilder{}
+	// Note: The type key is "" below because if there is a cycle, it will have a separate reference under a separate
+	// type key and if there isn't the key is irrelevant.
+	pending := mojomStructToVDLType("", ms, mp, builder, map[string]vdl.TypeOrPending{})
+	builder.Build()
+	return pending.Built()
+}
 
-// Convert the known type reference to a vdl type.
-// Panics if the type reference was not known.
-/*func TypeReferenceToVDLType(tr mojom_types.TypeReference, mp map[string]mojom_types.UserDefinedType) *vdl.Type {
-	if udt, ok := mp[tr.TypeKey]; ok {
-		return mojomToVDLTypeUDT(udt, mp)
+func MojomToVDLType(mt mojom_types.Type, mp map[string]mojom_types.UserDefinedType) (*vdl.Type, error) {
+	builder := &vdl.TypeBuilder{}
+	t := mojomToVDLType(mt, mp, builder, map[string]vdl.TypeOrPending{})
+	builder.Build()
+	if vt, ok := t.(*vdl.Type); ok {
+		return vt, nil
 	}
-	panic("Type Key %s was not present in the mapping", tr.typeKey)
-}*/
+	return t.(vdl.PendingType).Built()
 
-func mojomToVDLTypeUDT(udt mojom_types.UserDefinedType, mp map[string]mojom_types.UserDefinedType) (vt *vdl.Type) {
+}
+
+func mojomStructToVDLType(typeKey string, ms mojom_types.MojomStruct, mp map[string]mojom_types.UserDefinedType, builder *vdl.TypeBuilder, pendingUdts map[string]vdl.TypeOrPending) (vt vdl.PendingType) {
+	strct := builder.Struct()
+	vt = builder.Named(mojomToVdlPath(*ms.DeclData.FullIdentifier)).AssignBase(strct)
+	pendingUdts[typeKey] = vt
+	for _, mfield := range ms.Fields {
+		strct.AppendField(*mfield.DeclData.ShortName, mojomToVDLType(mfield.Type, mp, builder, pendingUdts))
+	}
+	return
+}
+
+func mojomToVDLTypeUDT(typeKey string, udt mojom_types.UserDefinedType, mp map[string]mojom_types.UserDefinedType, builder *vdl.TypeBuilder, pendingUdts map[string]vdl.TypeOrPending) (vt vdl.TypeOrPending) {
 	u := interface{}(udt)
 	switch u := u.(type) { // To do the type switch, udt has to be converted to interface{}.
 	case *mojom_types.UserDefinedTypeEnumType: // enum
@@ -49,21 +58,18 @@
 		}
 
 		vt = vdl.NamedType(mojomToVdlPath(*me.DeclData.FullIdentifier), vdl.EnumType(labels...))
+		pendingUdts[typeKey] = vt
 	case *mojom_types.UserDefinedTypeStructType: // struct
-		ms := u.Value
-
-		vt = MojomStructToVDLType(ms, mp)
+		vt = mojomStructToVDLType(typeKey, u.Value, mp, builder, pendingUdts)
 	case *mojom_types.UserDefinedTypeUnionType: // union
 		mu := u.Value
 
-		vfields := make([]vdl.Field, len(mu.Fields))
-		for ix, mfield := range mu.Fields {
-			vfields[ix] = vdl.Field{
-				Name: *mfield.DeclData.ShortName,
-				Type: MojomToVDLType(mfield.Type, mp),
-			}
+		union := builder.Union()
+		vt = builder.Named(mojomToVdlPath(*mu.DeclData.FullIdentifier)).AssignBase(union)
+		pendingUdts[typeKey] = vt
+		for _, mfield := range mu.Fields {
+			union = union.AppendField(*mfield.DeclData.ShortName, mojomToVDLType(mfield.Type, mp, builder, pendingUdts))
 		}
-		vt = vdl.NamedType(mojomToVdlPath(*mu.DeclData.FullIdentifier), vdl.UnionType(vfields...))
 	case *mojom_types.UserDefinedTypeInterfaceType: // interface
 		panic("interfaces don't exist in vdl")
 	default: // unknown
@@ -72,21 +78,8 @@
 	return vt
 }
 
-func MojomStructToVDLType(ms mojom_types.MojomStruct, mp map[string]mojom_types.UserDefinedType) (vt *vdl.Type) {
-	vfields := make([]vdl.Field, len(ms.Fields))
-	for ix, mfield := range ms.Fields {
-		vfields[ix] = vdl.Field{
-			Name: *mfield.DeclData.ShortName,
-			Type: MojomToVDLType(mfield.Type, mp),
-		}
-	}
-	vt = vdl.NamedType(mojomToVdlPath(*ms.DeclData.FullIdentifier), vdl.StructType(vfields...))
-	return vt
-}
-
 // Given a mojom Type and the descriptor mapping, produce the corresponding vdltype.
-func MojomToVDLType(mojomtype mojom_types.Type, mp map[string]mojom_types.UserDefinedType) (vt *vdl.Type) {
-	// TODO(alexfandrianto): Cyclic types?
+func mojomToVDLType(mojomtype mojom_types.Type, mp map[string]mojom_types.UserDefinedType, builder *vdl.TypeBuilder, pendingUdts map[string]vdl.TypeOrPending) (vt vdl.TypeOrPending) {
 	mt := interface{}(mojomtype)
 	switch mt := interface{}(mt).(type) { // To do the type switch, mt has to be converted to interface{}.
 	case *mojom_types.TypeSimpleType: // TypeSimpleType
@@ -126,9 +119,12 @@
 			panic("nullable arrays don't exist in vdl")
 		}
 		if at.FixedLength > 0 {
-			vt = vdl.ArrayType(int(at.FixedLength), MojomToVDLType(at.ElementType, mp))
+			vt = builder.Array().
+				AssignLen(int(at.FixedLength)).
+				AssignElem(mojomToVDLType(at.ElementType, mp, builder, pendingUdts))
 		} else {
-			vt = vdl.ListType(MojomToVDLType(at.ElementType, mp))
+			vt = builder.List().
+				AssignElem(mojomToVDLType(at.ElementType, mp, builder, pendingUdts))
 		}
 	case *mojom_types.TypeMapType: // TypeMapType
 		// Note that mojom doesn't have sets.
@@ -136,7 +132,9 @@
 		if m.Nullable {
 			panic("nullable maps don't exist in vdl")
 		}
-		vt = vdl.MapType(MojomToVDLType(m.KeyType, mp), MojomToVDLType(m.ValueType, mp))
+		vt = builder.Map().
+			AssignKey(mojomToVDLType(m.KeyType, mp, builder, pendingUdts)).
+			AssignElem(mojomToVDLType(m.ValueType, mp, builder, pendingUdts))
 	case *mojom_types.TypeHandleType: // TypeHandleType
 		panic("handles don't exist in vdl")
 	case *mojom_types.TypeTypeReference: // TypeTypeReference
@@ -145,10 +143,17 @@
 			panic("interface requests don't exist in vdl")
 		}
 		udt := mp[*tr.TypeKey]
-		if udt.Tag() != 1 && tr.Nullable {
-			panic("nullable non-struct type reference cannot be represented in vdl")
+		var ok bool
+		vt, ok = pendingUdts[*tr.TypeKey]
+		if !ok {
+			vt = mojomToVDLTypeUDT(*tr.TypeKey, udt, mp, builder, pendingUdts)
 		}
-		vt = mojomToVDLTypeUDT(udt, mp)
+		if tr.Nullable {
+			if udt.Tag() != 1 {
+				panic("nullable non-struct type reference cannot be represented in vdl")
+			}
+			vt = builder.Optional().AssignElem(vt)
+		}
 	default:
 		panic(fmt.Errorf("%#v has unknown tag %d", mojomtype, mojomtype.Tag()))
 	}
diff --git a/go/src/v.io/x/mojo/transcoder/type_test.go b/go/src/v.io/x/mojo/transcoder/type_test.go
index e968af1..fd46fd5 100644
--- a/go/src/v.io/x/mojo/transcoder/type_test.go
+++ b/go/src/v.io/x/mojo/transcoder/type_test.go
@@ -10,8 +10,6 @@
 	"reflect"
 	"testing"
 
-	"github.com/davecgh/go-spew/spew"
-
 	"v.io/v23/vdl"
 	"v.io/x/mojo/transcoder"
 )
@@ -19,20 +17,19 @@
 func TestVdlAndMojoTypeConversion(t *testing.T) {
 	// Create types.
 	enumType := vdl.NamedType("v23proxy/tests/transcoder_testcases.TestEnum", vdl.EnumType("A", "B", "C"))
-
 	basicStructType := vdl.NamedType("v23proxy/tests/transcoder_testcases.TestBasicStruct", vdl.StructType(vdl.Field{"TestEnum", enumType}, vdl.Field{"A", vdl.Int32Type}))
-	/* disabled until recursive test is enabled
+
 	builder := vdl.TypeBuilder{}
 	strct := builder.Struct()
 	strct.AppendField("TestEnum", enumType)
-	namedStruct := builder.Named("v23proxy/tests/transcoder_testcases.TestStruct").AssignBase(strct)
-	strct.AppendField("TestStruct", builder.Optional().AssignElem(namedStruct))
+	namedStruct := builder.Named("v23proxy/tests/transcoder_testcases.TestCyclicStruct").AssignBase(strct)
+	strct.AppendField("TestCyclicStruct", builder.Optional().AssignElem(namedStruct))
 	strct.AppendField("A", vdl.Int32Type)
 	builder.Build()
 	cyclicStructType, err := namedStruct.Built()
 	if err != nil {
 		t.Fatalf("error building struct: %v", err)
-	}*/
+	}
 
 	tests := []struct {
 		vdl   *vdl.Type
@@ -133,7 +130,6 @@
 				"transcoder_testcases_TestEnum__":        transcoder_testcases.GetAllMojomTypeDefinitions()["transcoder_testcases_TestEnum__"],
 			},
 		},
-		/* mojo -> vdl currently doesn't handle cycles
 		{
 			cyclicStructType,
 			&mojom_types.TypeTypeReference{mojom_types.TypeReference{Nullable: false, TypeKey: stringPtr("transcoder_testcases_TestCyclicStruct__")}},
@@ -142,20 +138,21 @@
 				"transcoder_testcases_TestEnum__":         transcoder_testcases.GetAllMojomTypeDefinitions()["transcoder_testcases_TestEnum__"],
 			},
 		},
-		*/
-		// TODO(bprosnitz) Are there any optional types in common between vdl and mojo?
 	}
 
 	for _, test := range tests {
 		mojomtype, mp := transcoder.VDLToMojomType(test.vdl)
 		if !reflect.DeepEqual(mojomtype, test.mojom) {
-			t.Errorf(spew.Sprintf("vdl type %v, when converted to mojo type was %#v. expected %#v", test.vdl, mojomtype, test.mojom))
+			t.Errorf("vdl type %v, when converted to mojo type was %#v. expected %#v", test.vdl, mojomtype, test.mojom)
 		}
 		if !reflect.DeepEqual(mp, test.mp) {
 			t.Errorf("vdl type %v, when converted to mojo type did not match expected user defined types. got %#v, expected %#v", test.vdl, mojomtype, test.mojom)
 		}
 
-		vt := transcoder.MojomToVDLType(test.mojom, test.mp)
+		vt, err := transcoder.MojomToVDLType(test.mojom, test.mp)
+		if err != nil {
+			t.Errorf("error converting mojo type %#v (with user defined types %v): %v", test.mojom, test.mp, err)
+		}
 		if !reflect.DeepEqual(vt, test.vdl) {
 			t.Errorf("mojom type %#v (with user defined types %v), when converted to vdl type was %v. expected %v", test.mojom, test.mp, vt, test.vdl)
 		}