Repository: goadesign/goa (go)

Issue:
CLI help text seems to merge index action for two different services
Using latest goa v3 I define two services, Foo and Bar, each with its own resource type. Each service has an "index" method which has distinct parameters and output type:
`$GOPATH/src/foo/design.go`
```
package design

import (
	. "goa.design/goa/v3/dsl"
)

var JWTAuth = JWTSecurity("jwt", func() {
	Scope("foo:read")
})

var _ = Service("Foo", func() {
	Method("index", func() {
		Security(JWTAuth, func() { Scope("foo:read") })
		Payload(func() {
			TokenField(1, "token", String)
			Field(2, "view", String, func() {
				Enum("default", "extended")
			})
		})
		Result(CollectionOf(Foo))

		GRPC(func() {
			Response(CodeOK)
		})
	})
})

var _ = Service("Bar", func() {
	Method("index", func() {
		Security(JWTAuth, func() { Scope("foo:read") })
		Payload(func() {
			TokenField(1, "token", String)
			Field(2, "foo_id", String)
			Required("foo_id")
		})
		Result(CollectionOf(Bar))

		GRPC(func() {
			Response(CodeOK)
		})
	})
})

var Foo = ResultType("application/vnd.foo.foo", func() {
	TypeName("Foo")
	Field(1, "id", String)
	Field(2, "foo", String)
	Field(3, "bar", String)
	Required("id", "foo", "bar")

	View("default", func() {
		Attribute("id")
		Attribute("foo")
	})
	View("extended", func() {
		Attribute("id")
		Attribute("foo")
		Attribute("bar")
	})
})

var Bar = ResultType("application/vnd.foo.bar", func() {
	TypeName("Bar")
	Field(1, "id", String)
	Field(2, "foo_id", String)
	Field(3, "baz", String)
	Required("id", "foo_id", "baz")
})
```
When I code generate:
`$ goa gen foo/design`
the resulting `$GOPATH/src/foo/gen/grpc/cli/foo/cli.go` appears to merge the help text for both "index" methods such that it is identical:
```
func fooIndexUsage() {
	fmt.Fprintf(os.Stderr, `%s [flags] foo index -message JSON -token STRING

Index implements index.
    -message JSON: 
    -token STRING: 

Example:
    `+os.Args[0]+` foo index --message '{
      "view": "default"
   }' --token "Voluptatem aut maxime est unde."
`, os.Args[0])
}
...
func barIndexUsage() {
	fmt.Fprintf(os.Stderr, `%s [flags] bar index -message JSON -token STRING

Index implements index.
    -message JSON: 
    -token STRING: 

Example:
    `+os.Args[0]+` bar index --message '{
      "view": "default"  <-- should not have a "view" but instead require "foo_id"
   }' --token "Odio optio perferendis reiciendis."
`, os.Args[0])
}
```

Pull request:
Properly handle multiple user name with same name
This can happen for user types that are generated internally by Goa
for example when generating transport specific payload or result types.
The example generation algorithms and other recursive algorithms rely on
the ID() function of UserTypeExpr to return a unique value for different
instances of the type. Before this fix this function returned the name of
the user type which wasn't guaranteed to be unique so for example the same
example would get reused for different generated body types because the
corresponding method names (and thus type names) were identical.

Fix #2263 

Patch:
diff --git a/codegen/service/convert.go b/codegen/service/convert.go
index f6803621..02e9a6ac 100644
--- a/codegen/service/convert.go
+++ b/codegen/service/convert.go
@@ -459,6 +459,7 @@ func buildDesignType(dt *expr.DataType, t reflect.Type, ref expr.DataType, recs
 		ut := &expr.UserTypeExpr{
 			AttributeExpr: &expr.AttributeExpr{Type: &obj},
 			TypeName:      t.Name(),
+			UID:           t.PkgPath() + "#" + t.Name(),
 		}
 		*dt = ut
 		rec.seen[t.Name()] = ut
diff --git a/codegen/service/service_data.go b/codegen/service/service_data.go
index 1e894749..4f644c28 100644
--- a/codegen/service/service_data.go
+++ b/codegen/service/service_data.go
@@ -483,11 +483,12 @@ func (d ServicesData) analyze(service *expr.ServiceExpr) *Data {
 		seenViewed = make(map[string]*ViewedResultTypeData)
 
 		// A function to convert raw object type to user type.
-		makeUserType := func(att *expr.AttributeExpr, name string) {
+		makeUserType := func(att *expr.AttributeExpr, name, id string) {
 			if _, ok := att.Type.(*expr.Object); ok {
 				att.Type = &expr.UserTypeExpr{
 					AttributeExpr: expr.DupAtt(att),
 					TypeName:      name,
+					UID:           id,
 				}
 			}
 			if ut, ok := att.Type.(expr.UserType); ok {
@@ -498,11 +499,11 @@ func (d ServicesData) analyze(service *expr.ServiceExpr) *Data {
 		for _, e := range service.Methods {
 			name := codegen.Goify(e.Name, true)
 			// Create user type for raw object payloads
-			makeUserType(e.Payload, name+"Payload")
+			makeUserType(e.Payload, name+"Payload", service.Name+"#"+name+"Payload")
 			// Create user type for raw object streaming payloads
-			makeUserType(e.StreamingPayload, name+"StreamingPayload")
+			makeUserType(e.StreamingPayload, name+"StreamingPayload", service.Name+"#"+name+"StreamingPayload")
 			// Create user type for raw object results
-			makeUserType(e.Result, name+"Result")
+			makeUserType(e.Result, name+"Result", service.Name+"#"+name+"Result")
 		}
 		recordError := func(er *expr.ErrorExpr) {
 			errTypes = append(errTypes, collectTypes(er.AttributeExpr, scope, seen)...)
diff --git a/expr/http_body_types.go b/expr/http_body_types.go
index 7c07c39f..537a02d0 100644
--- a/expr/http_body_types.go
+++ b/expr/http_body_types.go
@@ -85,6 +85,7 @@ func httpRequestBody(a *HTTPEndpointExpr) *AttributeExpr {
 	ut := &UserTypeExpr{
 		AttributeExpr: att,
 		TypeName:      name,
+		UID:           a.Service.Name() + "#" + a.Name(),
 	}
 	appendSuffix(ut.Attribute().Type, suffix)
 
@@ -109,6 +110,7 @@ func httpStreamingBody(e *HTTPEndpointExpr) *AttributeExpr {
 	ut := &UserTypeExpr{
 		AttributeExpr: DupAtt(att),
 		TypeName:      concat(e.Name(), "Streaming", "Body"),
+		UID:           concat(e.Service.Name(), e.Name(), "Streaming", "Body"),
 	}
 	appendSuffix(ut.Attribute().Type, suffix)
 
@@ -130,7 +132,7 @@ func httpResponseBody(a *HTTPEndpointExpr, resp *HTTPResponseExpr) *AttributeExp
 		suffix = http.StatusText(resp.StatusCode)
 	}
 	name = a.Name() + suffix
-	return buildHTTPResponseBody(name, a.MethodExpr.Result, resp)
+	return buildHTTPResponseBody(name, a.MethodExpr.Result, resp, a.Service)
 }
 
 // httpErrorResponseBody returns an attribute describing the response body of a
@@ -140,10 +142,10 @@ func httpResponseBody(a *HTTPEndpointExpr, resp *HTTPResponseExpr) *AttributeExp
 // parameters.
 func httpErrorResponseBody(a *HTTPEndpointExpr, v *HTTPErrorExpr) *AttributeExpr {
 	name := a.Name() + "_" + v.ErrorExpr.Name
-	return buildHTTPResponseBody(name, v.ErrorExpr.AttributeExpr, v.Response)
+	return buildHTTPResponseBody(name, v.ErrorExpr.AttributeExpr, v.Response, a.Service)
 }
 
-func buildHTTPResponseBody(name string, attr *AttributeExpr, resp *HTTPResponseExpr) *AttributeExpr {
+func buildHTTPResponseBody(name string, attr *AttributeExpr, resp *HTTPResponseExpr, svc *HTTPServiceExpr) *AttributeExpr {
 	const suffix = "ResponseBody"
 	name = concat(name, "Response", "Body")
 	if attr == nil || attr.Type == Empty {
@@ -192,6 +194,7 @@ func buildHTTPResponseBody(name string, attr *AttributeExpr, resp *HTTPResponseE
 	userType := &UserTypeExpr{
 		AttributeExpr: body.Attribute(),
 		TypeName:      name,
+		UID:           concat(svc.Name(), "#", name),
 	}
 	appendSuffix(userType.Attribute().Type, suffix)
 	rt, isrt := attr.Type.(*ResultTypeExpr)
diff --git a/expr/result_type.go b/expr/result_type.go
index 2aaf10a9..679c3c7d 100644
--- a/expr/result_type.go
+++ b/expr/result_type.go
@@ -113,6 +113,7 @@ func NewResultTypeExpr(name, identifier string, fn func()) *ResultTypeExpr {
 		UserTypeExpr: &UserTypeExpr{
 			AttributeExpr: &AttributeExpr{Type: &Object{}, DSLFunc: fn},
 			TypeName:      name,
+			UID:           identifier,
 		},
 		Identifier: identifier,
 	}
@@ -260,6 +261,7 @@ func projectSingle(m *ResultTypeExpr, view string, seen ...map[string]*Attribute
 	} else {
 		seen = append(seen, make(map[string]*AttributeExpr))
 	}
+	id := m.projectIdentifier(view)
 	if ut == nil {
 		ut = &UserTypeExpr{
 			AttributeExpr: &AttributeExpr{
@@ -269,9 +271,10 @@ func projectSingle(m *ResultTypeExpr, view string, seen ...map[string]*Attribute
 		}
 	}
 	ut.TypeName = typeName
+	ut.UID = id
 	ut.AttributeExpr.Type = Dup(v.Type)
 	projected := &ResultTypeExpr{
-		Identifier:   m.projectIdentifier(view),
+		Identifier:   id,
 		UserTypeExpr: ut,
 	}
 	projected.Views = []*ViewExpr{{
@@ -303,8 +306,9 @@ func projectCollection(m *ResultTypeExpr, view string, seen ...map[string]*Attri
 	}
 
 	// Build the projected collection with the results
+	id := m.projectIdentifier(view)
 	proj := &ResultTypeExpr{
-		Identifier: m.projectIdentifier(view),
+		Identifier: id,
 		UserTypeExpr: &UserTypeExpr{
 			AttributeExpr: &AttributeExpr{
 				Description:  m.TypeName + " is the result type for an array of " + e.TypeName + " (" + view + " view)",
@@ -312,6 +316,7 @@ func projectCollection(m *ResultTypeExpr, view string, seen ...map[string]*Attri
 				UserExamples: m.UserExamples,
 			},
 			TypeName: pe.TypeName + "Collection",
+			UID:      id,
 		},
 		Views: []*ViewExpr{{
 			AttributeExpr: DupAtt(pe.View("default").AttributeExpr),
diff --git a/expr/user_type.go b/expr/user_type.go
index 02ad088f..88054f10 100644
--- a/expr/user_type.go
+++ b/expr/user_type.go
@@ -1,25 +1,27 @@
 package expr
 
 type (
-	// UserTypeExpr describes user defined types.
+	// UserTypeExpr describes user defined types. While a given design must
+	// ensure that the names are unique the code used to generate code can
+	// create multiple user types that share the same name (for example because
+	// generated in different packages). UID is always unique and makes it
+	// possible to avoid infinite recursions when traversing the data structures
+	// described by the attribute expression e.g. when computing example values.
 	UserTypeExpr struct {
 		// The embedded attribute expression.
 		*AttributeExpr
 		// Name of type
 		TypeName string
+		// UID of type
+		UID string
 	}
 )
 
-// NewUserTypeExpr creates a user type expression but does not execute the DSL.
-func NewUserTypeExpr(name string, fn func()) *UserTypeExpr {
-	return &UserTypeExpr{
-		TypeName:      name,
-		AttributeExpr: &AttributeExpr{DSLFunc: fn},
-	}
-}
-
-// ID returns the identifier (type name) for the user type.
+// ID returns the unique identifier for the user type.
 func (u *UserTypeExpr) ID() string {
+	if u.UID != "" {
+		return u.UID
+	}
 	return u.Name()
 }
 
@@ -64,6 +66,7 @@ func (u *UserTypeExpr) Dup(att *AttributeExpr) UserType {
 	return &UserTypeExpr{
 		AttributeExpr: att,
 		TypeName:      u.TypeName,
+		UID:           u.UID,
 	}
 }
 
diff --git a/grpc/codegen/protobuf.go b/grpc/codegen/protobuf.go
index 93d989ac..b6371c8d 100644
--- a/grpc/codegen/protobuf.go
+++ b/grpc/codegen/protobuf.go
@@ -57,11 +57,11 @@ func protoBufTypeContext(pkg string, scope *codegen.NameScope) *codegen.Attribut
 // given attribute type is a primitive, array, or a map, it wraps the given
 // attribute under an attribute with name "field" and RPC tag number 1. For,
 // nested arrays/maps, the inner array/map is wrapped into a user type.
-func makeProtoBufMessage(att *expr.AttributeExpr, tname string, scope *codegen.NameScope) *expr.AttributeExpr {
+func makeProtoBufMessage(att *expr.AttributeExpr, tname string, sd *ServiceData) *expr.AttributeExpr {
 	att = expr.DupAtt(att)
 	switch dt := att.Type.(type) {
 	case expr.Primitive:
-		wrapAttr(att, tname)
+		wrapAttr(att, tname, sd)
 		return att
 	case expr.UserType:
 		if dt == expr.Empty {
@@ -69,37 +69,39 @@ func makeProtoBufMessage(att *expr.AttributeExpr, tname string, scope *codegen.N
 			att.Type = &expr.UserTypeExpr{
 				TypeName:      tname,
 				AttributeExpr: &expr.AttributeExpr{Type: &expr.Object{}},
+				UID:           sd.Name + "#" + tname,
 			}
 			return att
 		} else if rt, ok := dt.(*expr.ResultTypeExpr); ok && expr.IsArray(rt) {
 			// result type collection
-			wrapAttr(att, tname)
+			wrapAttr(att, tname, sd)
 		}
 	case *expr.Array, *expr.Map:
-		wrapAttr(att, tname)
+		wrapAttr(att, tname, sd)
 	case *expr.Object:
 		att.Type = &expr.UserTypeExpr{
 			TypeName:      tname,
 			AttributeExpr: expr.DupAtt(att),
+			UID:           sd.Name + "#" + tname,
 		}
 	}
 	// wrap nested arrays/maps
 	n := ""
-	makeProtoBufMessageR(att, &n, scope)
+	makeProtoBufMessageR(att, &n, sd)
 	return att
 }
 
 // makeProtoBufMessageR is the recursive implementation of makeProtoBufMessage.
-func makeProtoBufMessageR(att *expr.AttributeExpr, tname *string, scope *codegen.NameScope, seen ...map[string]struct{}) {
+func makeProtoBufMessageR(att *expr.AttributeExpr, tname *string, sd *ServiceData, seen ...map[string]struct{}) {
 	wrap := func(att *expr.AttributeExpr, tname string) {
 		switch dt := att.Type.(type) {
 		case *expr.Array:
 			wrapAttr(att, "ArrayOf"+tname+
-				protoBufify(protoBufMessageDef(dt.ElemType, scope), true))
+				protoBufify(protoBufMessageDef(dt.ElemType, sd), true), sd)
 		case *expr.Map:
 			wrapAttr(att, tname+"MapOf"+
-				protoBufify(protoBufMessageDef(dt.KeyType, scope), true)+
-				protoBufify(protoBufMessageDef(dt.ElemType, scope), true))
+				protoBufify(protoBufMessageDef(dt.KeyType, sd), true)+
+				protoBufify(protoBufMessageDef(dt.ElemType, sd), true), sd)
 		}
 	}
 	switch dt := att.Type.(type) {
@@ -116,27 +118,27 @@ func makeProtoBufMessageR(att *expr.AttributeExpr, tname *string, scope *codegen
 		}
 		s[dt.ID()] = struct{}{}
 		if rt, ok := dt.(*expr.ResultTypeExpr); ok && expr.IsArray(rt) {
-			wrapAttr(rt.Attribute(), rt.Name())
+			wrapAttr(rt.Attribute(), rt.Name(), sd)
 		}
-		makeProtoBufMessageR(dt.Attribute(), tname, scope, seen...)
+		makeProtoBufMessageR(dt.Attribute(), tname, sd, seen...)
 	case *expr.Array:
-		makeProtoBufMessageR(dt.ElemType, tname, scope, seen...)
+		makeProtoBufMessageR(dt.ElemType, tname, sd, seen...)
 		wrap(dt.ElemType, *tname)
 	case *expr.Map:
 		// need not worry about map keys because protocol buffer supports
 		// only primitives as map keys.
-		makeProtoBufMessageR(dt.ElemType, tname, scope, seen...)
+		makeProtoBufMessageR(dt.ElemType, tname, sd, seen...)
 		wrap(dt.ElemType, *tname)
 	case *expr.Object:
 		for _, nat := range *dt {
-			makeProtoBufMessageR(nat.Attribute, tname, scope, seen...)
+			makeProtoBufMessageR(nat.Attribute, tname, sd, seen...)
 		}
 	}
 }
 
 // wrapAttr makes the attribute type a user type by wrapping the given
 // attribute into an attribute named "field".
-func wrapAttr(att *expr.AttributeExpr, tname string) {
+func wrapAttr(att *expr.AttributeExpr, tname string, sd *ServiceData) {
 	wrap := func(att *expr.AttributeExpr) *expr.AttributeExpr {
 		return &expr.AttributeExpr{
 			Type: &expr.Object{
@@ -160,6 +162,7 @@ func wrapAttr(att *expr.AttributeExpr, tname string) {
 		att.Type = &expr.UserTypeExpr{
 			TypeName:      tname,
 			AttributeExpr: wrap(att),
+			UID:           sd.Name + "#" + tname,
 		}
 	}
 }
@@ -227,16 +230,16 @@ func protoBufGoFullTypeName(att *expr.AttributeExpr, pkg string, s *codegen.Name
 // protoBufMessageDef returns the protocol buffer code that defines a message
 // which matches the data structure definition (the part that comes after
 // `message foo`). The message is defined using the proto3 syntax.
-func protoBufMessageDef(att *expr.AttributeExpr, s *codegen.NameScope) string {
+func protoBufMessageDef(att *expr.AttributeExpr, sd *ServiceData) string {
 	switch actual := att.Type.(type) {
 	case expr.Primitive:
 		return protoBufNativeMessageTypeName(att.Type)
 	case *expr.Array:
-		return "repeated " + protoBufMessageDef(actual.ElemType, s)
+		return "repeated " + protoBufMessageDef(actual.ElemType, sd)
 	case *expr.Map:
-		return fmt.Sprintf("map<%s, %s>", protoBufMessageDef(actual.KeyType, s), protoBufMessageDef(actual.ElemType, s))
+		return fmt.Sprintf("map<%s, %s>", protoBufMessageDef(actual.KeyType, sd), protoBufMessageDef(actual.ElemType, sd))
 	case expr.UserType:
-		return protoBufMessageName(att, s)
+		return protoBufMessageName(att, sd.Scope)
 	case *expr.Object:
 		var ss []string
 		ss = append(ss, " {")
@@ -250,7 +253,7 @@ func protoBufMessageDef(att *expr.AttributeExpr, s *codegen.NameScope) string {
 			{
 				fn = codegen.SnakeCase(protoBufify(nat.Name, false))
 				fnum = rpcTag(nat.Attribute)
-				typ = protoBufMessageDef(nat.Attribute, s)
+				typ = protoBufMessageDef(nat.Attribute, sd)
 				if nat.Attribute.Description != "" {
 					desc = codegen.Comment(nat.Attribute.Description) + "\n\t"
 				}
diff --git a/grpc/codegen/service_data.go b/grpc/codegen/service_data.go
index 2ed7132a..d11ce8e7 100644
--- a/grpc/codegen/service_data.go
+++ b/grpc/codegen/service_data.go
@@ -440,16 +440,16 @@ func (d ServicesData) analyze(gs *expr.GRPCServiceExpr) *ServiceData {
 	}
 	for _, e := range gs.GRPCEndpoints {
 		// convert request and response types to protocol buffer message types
-		e.Request = makeProtoBufMessage(e.Request, protoBufify(e.Name()+"_request", true), sd.Scope)
+		e.Request = makeProtoBufMessage(e.Request, protoBufify(e.Name()+"_request", true), sd)
 		if e.MethodExpr.StreamingPayload.Type != expr.Empty {
-			e.StreamingRequest = makeProtoBufMessage(e.StreamingRequest, protoBufify(e.Name()+"_streaming_request", true), sd.Scope)
+			e.StreamingRequest = makeProtoBufMessage(e.StreamingRequest, protoBufify(e.Name()+"_streaming_request", true), sd)
 		}
-		e.Response.Message = makeProtoBufMessage(e.Response.Message, protoBufify(e.Name()+"_response", true), sd.Scope)
+		e.Response.Message = makeProtoBufMessage(e.Response.Message, protoBufify(e.Name()+"_response", true), sd)
 		for _, er := range e.GRPCErrors {
 			if er.ErrorExpr.Type == expr.ErrorResult || !expr.IsObject(er.ErrorExpr.Type) {
 				continue
 			}
-			er.Response.Message = makeProtoBufMessage(er.Response.Message, protoBufify(e.Name()+"_"+er.Name+"_error", true), sd.Scope)
+			er.Response.Message = makeProtoBufMessage(er.Response.Message, protoBufify(e.Name()+"_"+er.Name+"_error", true), sd)
 		}
 
 		// collect all the nested messages and return the top-level message
@@ -637,7 +637,7 @@ func collectMessages(at *expr.AttributeExpr, sd *ServiceData, seen map[string]st
 			Name:        dt.Name(),
 			VarName:     protoBufMessageName(at, sd.Scope),
 			Description: dt.Attribute().Description,
-			Def:         protoBufMessageDef(att, sd.Scope),
+			Def:         protoBufMessageDef(att, sd),
 			Ref:         protoBufGoFullTypeRef(at, sd.PkgName, sd.Scope),
 			Type:        dt,
 		})


Test patch:
diff --git a/grpc/codegen/protobuf_transform_test.go b/grpc/codegen/protobuf_transform_test.go
index 2a5da585..6280830e 100644
--- a/grpc/codegen/protobuf_transform_test.go
+++ b/grpc/codegen/protobuf_transform_test.go
@@ -11,7 +11,7 @@ import (
 func TestProtoBufTransform(t *testing.T) {
 	root := codegen.RunDSL(t, ctestdata.TestTypesDSL)
 	var (
-		scope = codegen.NewNameScope()
+		sd = &ServiceData{Name: "Service", Scope: codegen.NewNameScope()}
 
 		// types to test
 		primitive = expr.Int
@@ -41,9 +41,9 @@ func TestProtoBufTransform(t *testing.T) {
 		rtCol      = root.UserType("ResultTypeCollection")
 
 		// attribute contexts used in test cases
-		svcCtx = serviceTypeContext("", scope)
-		ptrCtx = pointerContext("", scope)
-		pbCtx  = protoBufTypeContext("", scope)
+		svcCtx = serviceTypeContext("", sd.Scope)
+		ptrCtx = pointerContext("", sd.Scope)
+		pbCtx  = protoBufTypeContext("", sd.Scope)
 	)
 
 	tc := map[string][]struct {
@@ -127,10 +127,10 @@ func TestProtoBufTransform(t *testing.T) {
 					srcCtx := c.Ctx
 					tgtCtx := c.Ctx
 					if c.ToProto {
-						target = makeProtoBufMessage(expr.DupAtt(target), target.Type.Name(), scope)
+						target = makeProtoBufMessage(expr.DupAtt(target), target.Type.Name(), sd)
 						tgtCtx = pbCtx
 					} else {
-						source = makeProtoBufMessage(expr.DupAtt(source), source.Type.Name(), scope)
+						source = makeProtoBufMessage(expr.DupAtt(source), source.Type.Name(), sd)
 						srcCtx = pbCtx
 					}
 					code, _, err := protoBufTransform(source, target, "source", "target", srcCtx, tgtCtx, c.ToProto)