Repository: goadesign/goa (go)

Issue:
Build errors when using oneOf and nested types in StreamingResult
Since v3.8.0 (https://github.com/goadesign/goa/issues/3113), I've been able to use `OneOf` in `StreamingResult` when using result types. Thank you for the fix, @raphael!

However, a similar problem appeared when I tried to nest another result type.

```
# github.com/sevein/oneof/gen/chatter
gen/chatter/service.go:102:17: res.Payload.Item undefined (type interface{payloadVal()} has no field or method Item)
gen/chatter/service.go:126:18: vres.Payload.Item undefined (type interface{payloadVal()} has no field or method Item)
```

See [failed build](https://github.com/sevein/oneof/runs/7885866162?check_suite_focus=true) for more details.

Same repo for this issue: https://github.com/sevein/oneof.
Design change: https://github.com/sevein/oneof/commit/72089390edf4baec76bef413e3889a39aa2a0683.

Thank you in advance!


Pull request description:
Fix union transform
When union type has fields that need initialization

Fixes #3122

Patch:
diff --git a/codegen/go_transform.go b/codegen/go_transform.go
index b6c90b4f..f280fe36 100644
--- a/codegen/go_transform.go
+++ b/codegen/go_transform.go
@@ -52,7 +52,6 @@ func init() {
 // newVar if true initializes a target variable with the generated Go code
 // using `:=` operator. If false, it assigns Go code to the target variable
 // using `=`.
-//
 func GoTransform(source, target *expr.AttributeExpr, sourceVar, targetVar string, sourceCtx, targetCtx *AttributeContext, prefix string, newVar bool) (string, []*TransformFunctionData, error) {
 	ta := &TransformAttrs{
 		SourceCtx: sourceCtx,
@@ -224,6 +223,10 @@ func transformObject(source, target *expr.AttributeExpr, sourceVar, targetVar st
 			case expr.IsUnion(srcc.Type):
 				code, err = transformUnion(srcc, tgtc, srcVar, tgtVar, false, ta)
 			case ok:
+				if ta.TargetCtx.isInterface {
+					ref := ta.TargetCtx.Scope.Ref(target, ta.TargetCtx.Pkg(target))
+					tgtVar = targetVar + ".(" + ref + ")." + GoifyAtt(tgtc, tgtMatt.ElemName(n), true)
+				}
 				if !expr.IsPrimitive(srcc.Type) {
 					code = fmt.Sprintf("%s = %s(%s)\n", tgtVar, transformHelperName(srcc, tgtc, ta), srcVar)
 				}
@@ -375,6 +378,10 @@ func transformUnion(source, target *expr.AttributeExpr, sourceVar, targetVar str
 	for i, tt := range tgtUnion.Values {
 		targetTypeNames[i] = ta.TargetCtx.Scope.Name(tt.Attribute, ta.TargetCtx.Pkg(tt.Attribute), ta.TargetCtx.Pointer, ta.TargetCtx.Pointer)
 	}
+
+	// Need to type assert targetVar before assigning field values.
+	ta.TargetCtx.isInterface = true
+
 	data := map[string]interface{}{
 		"SourceTypeRefs": sourceTypeRefs,
 		"SourceTypes":    srcUnion.Values,
@@ -474,7 +481,6 @@ func transformObjectToUnion(source, target *expr.AttributeExpr, sourceVar, targe
 // ta holds the transform attributes
 //
 // seen keeps track of generated transform functions to avoid infinite recursion.
-//
 func transformAttributeHelpers(source, target *expr.AttributeExpr, ta *TransformAttrs, seen map[string]*TransformFunctionData) (helpers []*TransformFunctionData, err error) {
 	// Do not generate a transform function for the top most user type.
 	var other []*TransformFunctionData
@@ -582,6 +588,11 @@ func generateHelper(source, target *expr.AttributeExpr, req bool, ta *TransformA
 	if _, ok := seen[name]; ok {
 		return nil, nil
 	}
+
+	// Reset need for type assertion for union types because we are
+	// generating the code to transform the concrete type.
+	ta.TargetCtx.isInterface = false
+
 	code, err := transformAttribute(source.Type.(expr.UserType).Attribute(), target, "v", "res", true, ta)
 	if err != nil {
 		return nil, err
diff --git a/codegen/service/views.go b/codegen/service/views.go
index 95f3c1b3..7e4cd15c 100644
--- a/codegen/service/views.go
+++ b/codegen/service/views.go
@@ -52,7 +52,6 @@ func ViewsFile(genpkg string, service *expr.ServiceExpr) *codegen.File {
 		// Union methods
 		for _, m := range svc.viewedUnionMethods {
 			sections = append(sections, &codegen.SectionTemplate{
-				// addTypeDefSection(pathWithDefault(m.Loc, svcPath), "~"+m.TypeRef+"."+m.Name, &codegen.SectionTemplate{
 				Name:   "viewed-union-value-method",
 				Source: unionValueMethodT,
 				Data:   m,
diff --git a/codegen/transformer.go b/codegen/transformer.go
index fd578441..ef6ce76c 100644
--- a/codegen/transformer.go
+++ b/codegen/transformer.go
@@ -42,6 +42,9 @@ type (
 		// defaultPkg is the default package name where the attribute
 		// type is found. it can be overridden via struct:pkg:path meta.
 		defaultPkg string
+		// isInterface is true if the attribute is an interface (union type).
+		// In this case assigning child attributes requires a type assertion.
+		isInterface bool
 	}
 
 	// AttributeScope contains the scope of an attribute. It implements the


Test patch:
diff --git a/codegen/service/service_test.go b/codegen/service/service_test.go
index 902e67d2..75bb2b35 100644
--- a/codegen/service/service_test.go
+++ b/codegen/service/service_test.go
@@ -33,6 +33,7 @@ func TestService(t *testing.T) {
 		{"result-with-other-result", testdata.ResultWithOtherResultMethodDSL, testdata.ResultWithOtherResultMethod},
 		{"result-with-result-collection", testdata.ResultWithResultCollectionMethodDSL, testdata.ResultWithResultCollectionMethod},
 		{"result-with-dashed-mime-type", testdata.ResultWithDashedMimeTypeMethodDSL, testdata.ResultWithDashedMimeTypeMethod},
+		{"result-with-one-of-type", testdata.ResultWithOneOfTypeMethodDSL, testdata.ResultWithOneOfTypeMethod},
 		{"service-level-error", testdata.ServiceErrorDSL, testdata.ServiceError},
 		{"custom-errors", testdata.CustomErrorsDSL, testdata.CustomErrors},
 		{"custom-errors-custom-field", testdata.CustomErrorsCustomFieldDSL, testdata.CustomErrorsCustomField},
diff --git a/codegen/service/testdata/service_code.go b/codegen/service/testdata/service_code.go
index 71311517..e0d9da73 100644
--- a/codegen/service/testdata/service_code.go
+++ b/codegen/service/testdata/service_code.go
@@ -1315,6 +1315,183 @@ func newApplicationDashedTypeView(res *ApplicationDashedType) *resultwithdashedm
 }
 `
 
+const ResultWithOneOfTypeMethod = `
+// Service is the ResultWithOneOfType service interface.
+type Service interface {
+	// A implements A.
+	A(context.Context) (res *ResultOneof, err error)
+}
+
+// ServiceName is the name of the service as defined in the design. This is the
+// same value that is set in the endpoint request contexts under the ServiceKey
+// key.
+const ServiceName = "ResultWithOneOfType"
+
+// MethodNames lists the service method names as defined in the design. These
+// are the same values that are set in the endpoint request contexts under the
+// MethodKey key.
+var MethodNames = [1]string{"A"}
+
+type Item struct {
+	A *string
+}
+
+// ResultOneof is the result type of the ResultWithOneOfType service A method.
+type ResultOneof struct {
+	Result interface {
+		resultVal()
+	}
+}
+
+type T struct {
+	Message *string
+}
+
+type U struct {
+	Item *Item
+}
+
+func (*T) resultVal() {}
+func (*U) resultVal() {}
+
+// NewResultOneof initializes result type ResultOneof from viewed result type
+// ResultOneof.
+func NewResultOneof(vres *resultwithoneoftypeviews.ResultOneof) *ResultOneof {
+	return newResultOneof(vres.Projected)
+}
+
+// NewViewedResultOneof initializes viewed result type ResultOneof from result
+// type ResultOneof using the given view.
+func NewViewedResultOneof(res *ResultOneof, view string) *resultwithoneoftypeviews.ResultOneof {
+	p := newResultOneofView(res)
+	return &resultwithoneoftypeviews.ResultOneof{Projected: p, View: "default"}
+}
+
+// newResultOneof converts projected type ResultOneof to service type
+// ResultOneof.
+func newResultOneof(vres *resultwithoneoftypeviews.ResultOneofView) *ResultOneof {
+	res := &ResultOneof{}
+	if vres.Result != nil {
+		switch actual := vres.Result.(type) {
+		case *resultwithoneoftypeviews.TView:
+			res.Result = &T{
+				Message: actual.Message,
+			}
+
+		case *resultwithoneoftypeviews.UView:
+			res.Result = &U{}
+			if actual.Item != nil {
+				res.Result.(*U).Item = transformResultwithoneoftypeviewsItemViewToItem(actual.Item)
+			}
+
+		}
+	}
+	return res
+}
+
+// newResultOneofView projects result type ResultOneof to projected type
+// ResultOneofView using the "default" view.
+func newResultOneofView(res *ResultOneof) *resultwithoneoftypeviews.ResultOneofView {
+	vres := &resultwithoneoftypeviews.ResultOneofView{}
+	if res.Result != nil {
+		switch actual := res.Result.(type) {
+		case *T:
+			vres.Result = &resultwithoneoftypeviews.TView{
+				Message: actual.Message,
+			}
+
+		case *U:
+			vres.Result = &resultwithoneoftypeviews.UView{}
+			if actual.Item != nil {
+				vres.Result.(*resultwithoneoftypeviews.UView).Item = transformItemToResultwithoneoftypeviewsItemView(actual.Item)
+			}
+
+		}
+	}
+	return vres
+}
+
+// transformResultwithoneoftypeviewsTViewToT builds a value of type *T from a
+// value of type *resultwithoneoftypeviews.TView.
+func transformResultwithoneoftypeviewsTViewToT(v *resultwithoneoftypeviews.TView) *T {
+	if v == nil {
+		return nil
+	}
+	res := &T{
+		Message: v.Message,
+	}
+
+	return res
+}
+
+// transformResultwithoneoftypeviewsUViewToU builds a value of type *U from a
+// value of type *resultwithoneoftypeviews.UView.
+func transformResultwithoneoftypeviewsUViewToU(v *resultwithoneoftypeviews.UView) *U {
+	if v == nil {
+		return nil
+	}
+	res := &U{}
+	if v.Item != nil {
+		res.Item = transformResultwithoneoftypeviewsItemViewToItem(v.Item)
+	}
+
+	return res
+}
+
+// transformResultwithoneoftypeviewsItemViewToItem builds a value of type *Item
+// from a value of type *resultwithoneoftypeviews.ItemView.
+func transformResultwithoneoftypeviewsItemViewToItem(v *resultwithoneoftypeviews.ItemView) *Item {
+	if v == nil {
+		return nil
+	}
+	res := &Item{
+		A: v.A,
+	}
+
+	return res
+}
+
+// transformTToResultwithoneoftypeviewsTView builds a value of type
+// *resultwithoneoftypeviews.TView from a value of type *T.
+func transformTToResultwithoneoftypeviewsTView(v *T) *resultwithoneoftypeviews.TView {
+	if v == nil {
+		return nil
+	}
+	res := &resultwithoneoftypeviews.TView{
+		Message: v.Message,
+	}
+
+	return res
+}
+
+// transformUToResultwithoneoftypeviewsUView builds a value of type
+// *resultwithoneoftypeviews.UView from a value of type *U.
+func transformUToResultwithoneoftypeviewsUView(v *U) *resultwithoneoftypeviews.UView {
+	if v == nil {
+		return nil
+	}
+	res := &resultwithoneoftypeviews.UView{}
+	if v.Item != nil {
+		res.Item = transformItemToResultwithoneoftypeviewsItemView(v.Item)
+	}
+
+	return res
+}
+
+// transformItemToResultwithoneoftypeviewsItemView builds a value of type
+// *resultwithoneoftypeviews.ItemView from a value of type *Item.
+func transformItemToResultwithoneoftypeviewsItemView(v *Item) *resultwithoneoftypeviews.ItemView {
+	if v == nil {
+		return nil
+	}
+	res := &resultwithoneoftypeviews.ItemView{
+		A: v.A,
+	}
+
+	return res
+}
+`
+
 const ForceGenerateType = `
 // Service is the ForceGenerateType service interface.
 type Service interface {
diff --git a/codegen/service/testdata/service_dsls.go b/codegen/service/testdata/service_dsls.go
index 5015eac0..3488ab6c 100644
--- a/codegen/service/testdata/service_dsls.go
+++ b/codegen/service/testdata/service_dsls.go
@@ -435,6 +435,25 @@ var ResultWithDashedMimeTypeMethodDSL = func() {
 	})
 }
 
+var ResultWithOneOfTypeMethodDSL = func() {
+	var Item = Type("Item", func() { Attribute("a") })
+	var T = Type("T", func() { Attribute("message") })
+	var U = Type("U", func() { Attribute("item", Item) })
+	var RT = ResultType("application/vnd.result.oneof", func() {
+		Attributes(func() {
+			OneOf("result", func() {
+				Attribute("t", T)
+				Attribute("u", U)
+			})
+		})
+	})
+	Service("ResultWithOneOfType", func() {
+		Method("A", func() {
+			Result(RT)
+		})
+	})
+}
+
 var ForceGenerateTypeDSL = func() {
 	var _ = Type("ForcedType", func() {
 		Attribute("a", String)