Repository: goadesign/goa
Pull request #3568 (2024-07-31 19:19:45)

Remove generation of unnecessary view types.


Patch:
diff --git a/codegen/service/service_data.go b/codegen/service/service_data.go
index a00e30ad..54173088 100644
--- a/codegen/service/service_data.go
+++ b/codegen/service/service_data.go
@@ -1189,6 +1189,9 @@ func BuildSchemeData(s *expr.SchemeExpr, m *expr.MethodExpr) *SchemeData {
 // just result types - because user types make contain result types and thus may
 // need to be marshalled in different ways depending on the view being used.
 func collectProjectedTypes(projected, att *expr.AttributeExpr, viewspkg string, scope, viewScope *codegen.NameScope, seen map[string]*ProjectedTypeData) (data []*ProjectedTypeData, umeths []*UnionValueMethodData) {
+	if !hasResultType(att) {
+		return
+	}
 	collect := func(projected, att *expr.AttributeExpr) ([]*ProjectedTypeData, []*UnionValueMethodData) {
 		return collectProjectedTypes(projected, att, viewspkg, scope, viewScope, seen)
 	}
@@ -1253,6 +1256,44 @@ func collectProjectedTypes(projected, att *expr.AttributeExpr, viewspkg string,
 	return
 }
 
+// hasResultType returns true if the given attribute has a result type recursively.
+func hasResultType(att *expr.AttributeExpr, seens ...map[string]struct{}) bool {
+	if _, ok := att.Type.(*expr.ResultTypeExpr); ok {
+		return true
+	}
+	var seen map[string]struct{}
+	if len(seens) > 0 {
+		seen = seens[0]
+	} else {
+		seen = make(map[string]struct{})
+	}
+	switch a := att.Type.(type) {
+	case expr.UserType:
+		if _, ok := seen[a.ID()]; ok {
+			return false
+		}
+		seen[a.ID()] = struct{}{}
+		return hasResultType(a.Attribute(), seen)
+	case *expr.Array:
+		return hasResultType(a.ElemType, seen)
+	case *expr.Map:
+		return hasResultType(a.KeyType, seen) || hasResultType(a.ElemType, seen)
+	case *expr.Object:
+		for _, nat := range *a {
+			if hasResultType(nat.Attribute, seen) {
+				return true
+			}
+		}
+	case *expr.Union:
+		for _, nat := range a.Values {
+			if hasResultType(nat.Attribute, seen) {
+				return true
+			}
+		}
+	}
+	return false
+}
+
 // buildProjectedType builds projected type for the given user type.
 //
 // viewspkg is the name of the views package


Test patch:
diff --git a/codegen/service/testdata/service_code.go b/codegen/service/testdata/service_code.go
index f6a9b5bf..8dc4fd23 100644
--- a/codegen/service/testdata/service_code.go
+++ b/codegen/service/testdata/service_code.go
@@ -1537,15 +1537,15 @@ func newResultOneof(vres *resultwithoneoftypeviews.ResultOneofView) *ResultOneof
 	res := &ResultOneof{}
 	if vres.Result != nil {
 		switch actual := vres.Result.(type) {
-		case *resultwithoneoftypeviews.TView:
+		case *resultwithoneoftypeviews.T:
 			obj := &T{
 				Message: actual.Message,
 			}
 			res.Result = obj
-		case *resultwithoneoftypeviews.UView:
+		case *resultwithoneoftypeviews.U:
 			obj := &U{}
 			if actual.Item != nil {
-				obj.(*U).Item = transformResultwithoneoftypeviewsItemViewToItem(actual.Item)
+				obj.(*U).Item = transformResultwithoneoftypeviewsItemToItem(actual.Item)
 			}
 			res.Result = obj
 		}
@@ -1560,14 +1560,14 @@ func newResultOneofView(res *ResultOneof) *resultwithoneoftypeviews.ResultOneofV
 	if res.Result != nil {
 		switch actual := res.Result.(type) {
 		case *T:
-			obj := &resultwithoneoftypeviews.TView{
+			obj := &resultwithoneoftypeviews.T{
 				Message: actual.Message,
 			}
 			vres.Result = obj
 		case *U:
-			obj := &resultwithoneoftypeviews.UView{}
+			obj := &resultwithoneoftypeviews.U{}
 			if actual.Item != nil {
-				obj.(*resultwithoneoftypeviews.UView).Item = transformItemToResultwithoneoftypeviewsItemView(actual.Item)
+				obj.(*resultwithoneoftypeviews.U).Item = transformItemToResultwithoneoftypeviewsItem(actual.Item)
 			}
 			vres.Result = obj
 		}
@@ -1575,9 +1575,9 @@ func newResultOneofView(res *ResultOneof) *resultwithoneoftypeviews.ResultOneofV
 	return vres
 }
 
-// transformResultwithoneoftypeviewsTViewToT builds a value of type *T from a
-// value of type *resultwithoneoftypeviews.TView.
-func transformResultwithoneoftypeviewsTViewToT(v *resultwithoneoftypeviews.TView) *T {
+// transformResultwithoneoftypeviewsTToT builds a value of type *T from a value
+// of type *resultwithoneoftypeviews.T.
+func transformResultwithoneoftypeviewsTToT(v *resultwithoneoftypeviews.T) *T {
 	if v == nil {
 		return nil
 	}
@@ -1588,23 +1588,23 @@ func transformResultwithoneoftypeviewsTViewToT(v *resultwithoneoftypeviews.TView
 	return res
 }
 
-// transformResultwithoneoftypeviewsUViewToU builds a value of type *U from a
-// value of type *resultwithoneoftypeviews.UView.
-func transformResultwithoneoftypeviewsUViewToU(v *resultwithoneoftypeviews.UView) *U {
+// transformResultwithoneoftypeviewsUToU builds a value of type *U from a value
+// of type *resultwithoneoftypeviews.U.
+func transformResultwithoneoftypeviewsUToU(v *resultwithoneoftypeviews.U) *U {
 	if v == nil {
 		return nil
 	}
 	res := &U{}
 	if v.Item != nil {
-		res.Item = transformResultwithoneoftypeviewsItemViewToItem(v.Item)
+		res.Item = transformResultwithoneoftypeviewsItemToItem(v.Item)
 	}
 
 	return res
 }
 
-// transformResultwithoneoftypeviewsItemViewToItem builds a value of type *Item
-// from a value of type *resultwithoneoftypeviews.ItemView.
-func transformResultwithoneoftypeviewsItemViewToItem(v *resultwithoneoftypeviews.ItemView) *Item {
+// transformResultwithoneoftypeviewsItemToItem builds a value of type *Item
+// from a value of type *resultwithoneoftypeviews.Item.
+func transformResultwithoneoftypeviewsItemToItem(v *resultwithoneoftypeviews.Item) *Item {
 	if v == nil {
 		return nil
 	}
@@ -1615,40 +1615,40 @@ func transformResultwithoneoftypeviewsItemViewToItem(v *resultwithoneoftypeviews
 	return res
 }
 
-// transformTToResultwithoneoftypeviewsTView builds a value of type
-// *resultwithoneoftypeviews.TView from a value of type *T.
-func transformTToResultwithoneoftypeviewsTView(v *T) *resultwithoneoftypeviews.TView {
+// transformTToResultwithoneoftypeviewsT builds a value of type
+// *resultwithoneoftypeviews.T from a value of type *T.
+func transformTToResultwithoneoftypeviewsT(v *T) *resultwithoneoftypeviews.T {
 	if v == nil {
 		return nil
 	}
-	res := &resultwithoneoftypeviews.TView{
+	res := &resultwithoneoftypeviews.T{
 		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 {
+// transformUToResultwithoneoftypeviewsU builds a value of type
+// *resultwithoneoftypeviews.U from a value of type *U.
+func transformUToResultwithoneoftypeviewsU(v *U) *resultwithoneoftypeviews.U {
 	if v == nil {
 		return nil
 	}
-	res := &resultwithoneoftypeviews.UView{}
+	res := &resultwithoneoftypeviews.U{}
 	if v.Item != nil {
-		res.Item = transformItemToResultwithoneoftypeviewsItemView(v.Item)
+		res.Item = transformItemToResultwithoneoftypeviewsItem(v.Item)
 	}
 
 	return res
 }
 
-// transformItemToResultwithoneoftypeviewsItemView builds a value of type
-// *resultwithoneoftypeviews.ItemView from a value of type *Item.
-func transformItemToResultwithoneoftypeviewsItemView(v *Item) *resultwithoneoftypeviews.ItemView {
+// transformItemToResultwithoneoftypeviewsItem builds a value of type
+// *resultwithoneoftypeviews.Item from a value of type *Item.
+func transformItemToResultwithoneoftypeviewsItem(v *Item) *resultwithoneoftypeviews.Item {
 	if v == nil {
 		return nil
 	}
-	res := &resultwithoneoftypeviews.ItemView{
+	res := &resultwithoneoftypeviews.Item{
 		A: v.A,
 	}
 
diff --git a/codegen/service/testdata/views_code.go b/codegen/service/testdata/views_code.go
index 6c2c0ff3..8b0767a0 100644
--- a/codegen/service/testdata/views_code.go
+++ b/codegen/service/testdata/views_code.go
@@ -175,15 +175,10 @@ type ResultType struct {
 
 // ResultTypeView is a type that runs validations on a projected type.
 type ResultTypeView struct {
-	A *UserTypeView
+	A *UserType
 	B *string
 }
 
-// UserTypeView is a type that runs validations on a projected type.
-type UserTypeView struct {
-	A *string
-}
-
 var (
 	// ResultTypeMap is a map indexing the attribute names of ResultType by view
 	// name.
@@ -229,12 +224,6 @@ func ValidateResultTypeViewTiny(result *ResultTypeView) (err error) {
 	}
 	return
 }
-
-// ValidateUserTypeView runs the validations defined on UserTypeView.
-func ValidateUserTypeView(result *UserTypeView) (err error) {
-
-	return
-}
 `
 
 const ResultWithResultTypeCode = `// RT is the viewed result type that is projected based on a view.
@@ -255,19 +244,14 @@ type RTView struct {
 // RT2View is a type that runs validations on a projected type.
 type RT2View struct {
 	C *string
-	D *UserTypeView
+	D *UserType
 	E *string
 }
 
-// UserTypeView is a type that runs validations on a projected type.
-type UserTypeView struct {
-	P *string
-}
-
 // RT3View is a type that runs validations on a projected type.
 type RT3View struct {
 	X []string
-	Y map[int]*UserTypeView
+	Y map[int]*UserType
 	Z *string
 }
 
@@ -391,12 +375,6 @@ func ValidateRT2ViewTiny(result *RT2View) (err error) {
 	return
 }
 
-// ValidateUserTypeView runs the validations defined on UserTypeView.
-func ValidateUserTypeView(result *UserTypeView) (err error) {
-
-	return
-}
-
 // ValidateRT3View runs the validations defined on RT3View using the "default"
 // view.
 func ValidateRT3View(result *RT3View) (err error) {
@@ -763,12 +741,9 @@ type Result struct {
 
 // ResultView is a type that runs validations on a projected type.
 type ResultView struct {
-	T []UserTypeView
+	T []UserType
 }
 
-// UserTypeView is a type that runs validations on a projected type.
-type UserTypeView string
-
 var (
 	// ResultMap is a map indexing the attribute names of Result by view name.
 	ResultMap = map[string][]string{
@@ -799,14 +774,6 @@ func ValidateResultView(result *ResultView) (err error) {
 	}
 	return
 }
-
-// ValidateUserTypeView runs the validations defined on UserTypeView.
-func ValidateUserTypeView(result UserTypeView) (err error) {
-	if !(string(result) == "a" || string(result) == "b") {
-		err = goa.MergeErrors(err, goa.InvalidEnumValueError("result", string(result), []any{"a", "b"}))
-	}
-	return
-}
 `
 
 const ResultWithPkgPathCode = `// RT is the viewed result type that is projected based on a view.
@@ -819,12 +786,7 @@ type RT struct {
 
 // RTView is a type that runs validations on a projected type.
 type RTView struct {
-	A *UserTypeView
-}
-
-// UserTypeView is a type that runs validations on a projected type.
-type UserTypeView struct {
-	A *string
+	A *UserType
 }
 
 var (
@@ -853,10 +815,4 @@ func ValidateRTView(result *RTView) (err error) {
 
 	return
 }
-
-// ValidateUserTypeView runs the validations defined on UserTypeView.
-func ValidateUserTypeView(result *UserTypeView) (err error) {
-
-	return
-}
 `
diff --git a/http/codegen/client_body_types_test.go b/http/codegen/client_body_types_test.go
index 1abad7d8..f5519bff 100644
--- a/http/codegen/client_body_types_test.go
+++ b/http/codegen/client_body_types_test.go
@@ -236,7 +236,7 @@ const ExplicitBodyUserResultMultipleViewsInitCode = `// NewMethodExplicitBodyUse
 // "MethodExplicitBodyUserResultMultipleView" endpoint result from a HTTP "OK"
 // response.
 func NewMethodExplicitBodyUserResultMultipleViewResulttypemultipleviewsOK(body *MethodExplicitBodyUserResultMultipleViewResponseBody, c *string) *serviceexplicitbodyuserresultmultipleviewviews.ResulttypemultipleviewsView {
-	v := &serviceexplicitbodyuserresultmultipleviewviews.UserTypeView{
+	v := &serviceexplicitbodyuserresultmultipleviewviews.UserType{
 		X: body.X,
 		Y: body.Y,
 	}
@@ -256,7 +256,7 @@ const ExplicitBodyObjectInitCode = `// NewMethodExplicitBodyUserResultObjectResu
 func NewMethodExplicitBodyUserResultObjectResulttypeOK(body *MethodExplicitBodyUserResultObjectResponseBody, c *string, b *string) *serviceexplicitbodyuserresultobjectviews.ResulttypeView {
 	v := &serviceexplicitbodyuserresultobjectviews.ResulttypeView{}
 	if body.A != nil {
-		v.A = unmarshalUserTypeResponseBodyToServiceexplicitbodyuserresultobjectviewsUserTypeView(body.A)
+		v.A = unmarshalUserTypeResponseBodyToServiceexplicitbodyuserresultobjectviewsUserType(body.A)
 	}
 	v.C = c
 	v.B = b
@@ -272,7 +272,7 @@ const ExplicitBodyObjectViewsInitCode = `// NewMethodExplicitBodyUserResultObjec
 func NewMethodExplicitBodyUserResultObjectMultipleViewResulttypemultipleviewsOK(body *MethodExplicitBodyUserResultObjectMultipleViewResponseBody, c *string) *serviceexplicitbodyuserresultobjectmultipleviewviews.ResulttypemultipleviewsView {
 	v := &serviceexplicitbodyuserresultobjectmultipleviewviews.ResulttypemultipleviewsView{}
 	if body.A != nil {
-		v.A = unmarshalUserTypeResponseBodyToServiceexplicitbodyuserresultobjectmultipleviewviewsUserTypeView(body.A)
+		v.A = unmarshalUserTypeResponseBodyToServiceexplicitbodyuserresultobjectmultipleviewviewsUserType(body.A)
 	}
 	v.C = c