github.com/opiuman/genqlient@v1.0.0/generate/convert.go (about)

     1  package generate
     2  
     3  // This file implements the core type-generation logic of genqlient, whereby we
     4  // traverse an operation-definition (and the schema against which it will be
     5  // executed), and convert that into Go types.  It returns data structures
     6  // representing the types to be generated; these are defined, and converted
     7  // into code, in types.go.
     8  //
     9  // The entrypoints are convertOperation, which builds the response-type for a
    10  // query, and convertArguments, which builds the argument-types.
    11  
    12  import (
    13  	"fmt"
    14  
    15  	"github.com/vektah/gqlparser/v2/ast"
    16  )
    17  
    18  // getType returns the existing type in g.typeMap with the given name, if any,
    19  // and an error if such type is incompatible with this one.
    20  //
    21  // This is useful as an early-out and a safety-check when generating types; if
    22  // the type has already been generated we can skip generating it again.  (This
    23  // is necessary to handle recursive input types, and an optimization in other
    24  // cases.)
    25  func (g *generator) getType(
    26  	goName, graphQLName string,
    27  	selectionSet ast.SelectionSet,
    28  	pos *ast.Position,
    29  ) (goType, error) {
    30  	typ, ok := g.typeMap[goName]
    31  	if !ok {
    32  		return nil, nil
    33  	}
    34  
    35  	if typ.GraphQLTypeName() != graphQLName {
    36  		return typ, errorf(
    37  			pos, "conflicting definition for %s; this can indicate either "+
    38  				"a genqlient internal error, a conflict between user-specified "+
    39  				"type-names, or some very tricksy GraphQL field/type names: "+
    40  				"expected GraphQL type %s, got %s",
    41  			goName, typ.GraphQLTypeName(), graphQLName)
    42  	}
    43  
    44  	expectedSelectionSet := typ.SelectionSet()
    45  	if err := selectionsMatch(pos, selectionSet, expectedSelectionSet); err != nil {
    46  		return typ, errorf(
    47  			pos, "conflicting definition for %s; this can indicate either "+
    48  				"a genqlient internal error, a conflict between user-specified "+
    49  				"type-names, or some very tricksy GraphQL field/type names: %v",
    50  			goName, err)
    51  	}
    52  
    53  	return typ, nil
    54  }
    55  
    56  // addType inserts the type into g.typeMap, checking for conflicts.
    57  //
    58  // The conflict-checking is as described in getType.  Note we have to do it
    59  // here again, even if the caller has already called getType, because the
    60  // caller in between may have generated new types, which potentially creates
    61  // new conflicts.
    62  //
    63  // Returns an already-existing type if found, and otherwise the given type.
    64  func (g *generator) addType(typ goType, goName string, pos *ast.Position) (goType, error) {
    65  	otherTyp, err := g.getType(goName, typ.GraphQLTypeName(), typ.SelectionSet(), pos)
    66  	if otherTyp != nil || err != nil {
    67  		return otherTyp, err
    68  	}
    69  	g.typeMap[goName] = typ
    70  	return typ, nil
    71  }
    72  
    73  // baseTypeForOperation returns the definition of the GraphQL type to which the
    74  // root of the operation corresponds, e.g. the "Query" or "Mutation" type.
    75  func (g *generator) baseTypeForOperation(operation ast.Operation) (*ast.Definition, error) {
    76  	switch operation {
    77  	case ast.Query:
    78  		return g.schema.Query, nil
    79  	case ast.Mutation:
    80  		return g.schema.Mutation, nil
    81  	case ast.Subscription:
    82  		if !g.Config.AllowBrokenFeatures {
    83  			return nil, errorf(nil, "genqlient does not yet support subscriptions")
    84  		}
    85  		return g.schema.Subscription, nil
    86  	default:
    87  		return nil, errorf(nil, "unexpected operation: %v", operation)
    88  	}
    89  }
    90  
    91  // convertOperation builds the response-type into which the given operation's
    92  // result will be unmarshaled.
    93  func (g *generator) convertOperation(
    94  	operation *ast.OperationDefinition,
    95  	queryOptions *genqlientDirective,
    96  ) (goType, error) {
    97  	name := operation.Name + "Response"
    98  	namePrefix := newPrefixList(operation.Name)
    99  	if queryOptions.TypeName != "" {
   100  		name = queryOptions.TypeName
   101  		namePrefix = newPrefixList(queryOptions.TypeName)
   102  	}
   103  
   104  	baseType, err := g.baseTypeForOperation(operation.Operation)
   105  	if err != nil {
   106  		return nil, errorf(operation.Position, "%v", err)
   107  	}
   108  
   109  	// Instead of calling out to convertType/convertDefinition, we do our own
   110  	// thing, because we want to do a few things differently, and because we
   111  	// know we have an object type, so we can include only that case.
   112  	fields, err := g.convertSelectionSet(
   113  		namePrefix, operation.SelectionSet, baseType, queryOptions)
   114  	if err != nil {
   115  		return nil, err
   116  	}
   117  
   118  	// It's not common to use a fragment-spread for the whole query, but you
   119  	// can if you want two queries to return the same type!
   120  	if queryOptions.GetFlatten() {
   121  		i, err := validateFlattenOption(baseType, operation.SelectionSet, operation.Position)
   122  		if err == nil {
   123  			return fields[i].GoType, nil
   124  		}
   125  	}
   126  
   127  	goType := &goStructType{
   128  		GoName: name,
   129  		descriptionInfo: descriptionInfo{
   130  			CommentOverride: fmt.Sprintf(
   131  				"%v is returned by %v on success.", name, operation.Name),
   132  			GraphQLName: baseType.Name,
   133  			// omit the GraphQL description for baseType; it's uninteresting.
   134  		},
   135  		Fields:    fields,
   136  		Selection: operation.SelectionSet,
   137  		Generator: g,
   138  	}
   139  
   140  	return g.addType(goType, goType.GoName, operation.Position)
   141  }
   142  
   143  var builtinTypes = map[string]string{
   144  	// GraphQL guarantees int32 is enough, but using int seems more idiomatic
   145  	"Int":     "int",
   146  	"Float":   "float64",
   147  	"String":  "string",
   148  	"Boolean": "bool",
   149  	"ID":      "string",
   150  }
   151  
   152  // convertArguments builds the type of the GraphQL arguments to the given
   153  // operation.
   154  //
   155  // This type is not exposed to the user; it's just used internally in the
   156  // unmarshaler; and it's used as a container
   157  func (g *generator) convertArguments(
   158  	operation *ast.OperationDefinition,
   159  	queryOptions *genqlientDirective,
   160  ) (*goStructType, error) {
   161  	if len(operation.VariableDefinitions) == 0 {
   162  		return nil, nil
   163  	}
   164  	name := "__" + operation.Name + "Input"
   165  	fields := make([]*goStructField, len(operation.VariableDefinitions))
   166  	for i, arg := range operation.VariableDefinitions {
   167  		if goKeywords[arg.Variable] {
   168  			return nil, errorf(arg.Position, "variable name must not be a go keyword")
   169  		}
   170  
   171  		_, options, err := g.parsePrecedingComment(arg, nil, arg.Position, queryOptions)
   172  		if err != nil {
   173  			return nil, err
   174  		}
   175  
   176  		goName := upperFirst(arg.Variable)
   177  		// Some of the arguments don't apply here, namely the name-prefix (see
   178  		// names.go) and the selection-set (we use all the input type's fields,
   179  		// and so on recursively).  See also the `case ast.InputObject` in
   180  		// convertDefinition, below.
   181  		goTyp, err := g.convertType(nil, arg.Type, nil, options, queryOptions)
   182  		if err != nil {
   183  			return nil, err
   184  		}
   185  
   186  		fields[i] = &goStructField{
   187  			GoName:      goName,
   188  			GoType:      goTyp,
   189  			JSONName:    arg.Variable,
   190  			GraphQLName: arg.Variable,
   191  			Omitempty:   options.GetOmitempty(),
   192  		}
   193  	}
   194  	goTyp := &goStructType{
   195  		GoName:    name,
   196  		Fields:    fields,
   197  		Selection: nil,
   198  		IsInput:   true,
   199  		descriptionInfo: descriptionInfo{
   200  			CommentOverride: fmt.Sprintf("%s is used internally by genqlient", name),
   201  			// fake name, used by addType
   202  			GraphQLName: name,
   203  		},
   204  		Generator: g,
   205  	}
   206  	goTypAgain, err := g.addType(goTyp, goTyp.GoName, operation.Position)
   207  	if err != nil {
   208  		return nil, err
   209  	}
   210  	goTyp, ok := goTypAgain.(*goStructType)
   211  	if !ok {
   212  		return nil, errorf(
   213  			operation.Position, "internal error: input type was %T", goTypAgain)
   214  	}
   215  	return goTyp, nil
   216  }
   217  
   218  // convertType decides the Go type we will generate corresponding to a
   219  // particular GraphQL type.  In this context, "type" represents the type of a
   220  // field, and may be a list or a reference to a named type, with or without the
   221  // "non-null" annotation.
   222  func (g *generator) convertType(
   223  	namePrefix *prefixList,
   224  	typ *ast.Type,
   225  	selectionSet ast.SelectionSet,
   226  	options, queryOptions *genqlientDirective,
   227  ) (goType, error) {
   228  	// We check for local bindings here, so that you can bind, say, a
   229  	// `[String!]` to a struct instead of a slice.  Global bindings can only
   230  	// bind GraphQL named types, at least for now.
   231  	localBinding := options.Bind
   232  	if localBinding != "" && localBinding != "-" {
   233  		goRef, err := g.ref(localBinding)
   234  		// TODO(benkraft): Add syntax to specify a custom (un)marshaler, if
   235  		// it proves useful.
   236  		return &goOpaqueType{GoRef: goRef, GraphQLName: typ.Name()}, err
   237  	}
   238  
   239  	if typ.Elem != nil {
   240  		// Type is a list.
   241  		elem, err := g.convertType(
   242  			namePrefix, typ.Elem, selectionSet, options, queryOptions)
   243  		return &goSliceType{elem}, err
   244  	}
   245  
   246  	// If this is a builtin type or custom scalar, just refer to it.
   247  	def := g.schema.Types[typ.Name()]
   248  	goTyp, err := g.convertDefinition(
   249  		namePrefix, def, typ.Position, selectionSet, options, queryOptions)
   250  
   251  	if g.getStructReference(def) {
   252  		if options.Pointer == nil || *options.Pointer {
   253  			goTyp = &goPointerType{goTyp}
   254  		}
   255  		if options.Omitempty == nil || *options.Omitempty {
   256  			oe := true
   257  			options.Omitempty = &oe
   258  		}
   259  	} else if options.GetPointer() || (!typ.NonNull && g.Config.Optional == "pointer") {
   260  		// Whatever we get, wrap it in a pointer.  (Because of the way the
   261  		// options work, recursing here isn't as connvenient.)
   262  		// Note this does []*T or [][]*T, not e.g. *[][]T.  See #16.
   263  		goTyp = &goPointerType{goTyp}
   264  	}
   265  	return goTyp, err
   266  }
   267  
   268  // getStructReference decides if a field should be of pointer type and have the omitempty flag set.
   269  func (g *generator) getStructReference(
   270  	def *ast.Definition,
   271  ) bool {
   272  	return g.Config.StructReferences &&
   273  		(def.Kind == ast.Object || def.Kind == ast.InputObject)
   274  }
   275  
   276  // convertDefinition decides the Go type we will generate corresponding to a
   277  // particular GraphQL named type.
   278  //
   279  // In this context, "definition" (and "named type") refer to an
   280  // *ast.Definition, which represents the definition of a type in the GraphQL
   281  // schema, which may be referenced by a field-type (see convertType).
   282  func (g *generator) convertDefinition(
   283  	namePrefix *prefixList,
   284  	def *ast.Definition,
   285  	pos *ast.Position,
   286  	selectionSet ast.SelectionSet,
   287  	options, queryOptions *genqlientDirective,
   288  ) (goType, error) {
   289  	// Check if we should use an existing type.  (This is usually true for
   290  	// GraphQL scalars, but we allow you to bind non-scalar types too, if you
   291  	// want, subject to the caveats described in Config.Bindings.)  Local
   292  	// bindings are checked in the caller (convertType) and never get here,
   293  	// unless the binding is "-" which means "ignore the global binding".
   294  	globalBinding, ok := g.Config.Bindings[def.Name]
   295  	if ok && options.Bind != "-" {
   296  		if options.TypeName != "" {
   297  			// The option position (in the query) is more useful here.
   298  			return nil, errorf(options.pos,
   299  				"typename option conflicts with global binding for %s; "+
   300  					"use `bind: \"-\"` to override it", def.Name)
   301  		}
   302  		if def.Kind == ast.Object || def.Kind == ast.Interface || def.Kind == ast.Union {
   303  			err := g.validateBindingSelection(
   304  				def.Name, globalBinding, pos, selectionSet)
   305  			if err != nil {
   306  				return nil, err
   307  			}
   308  		}
   309  		goRef, err := g.ref(globalBinding.Type)
   310  		return &goOpaqueType{
   311  			GoRef:       goRef,
   312  			GraphQLName: def.Name,
   313  			Marshaler:   globalBinding.Marshaler,
   314  			Unmarshaler: globalBinding.Unmarshaler,
   315  		}, err
   316  	}
   317  	goBuiltinName, ok := builtinTypes[def.Name]
   318  	if ok && options.TypeName == "" {
   319  		return &goOpaqueType{GoRef: goBuiltinName, GraphQLName: def.Name}, nil
   320  	}
   321  
   322  	// Determine the name to use for this type.
   323  	var name string
   324  	if options.TypeName != "" {
   325  		if goKeywords[options.TypeName] {
   326  			return nil, errorf(pos, "typename option must not be a go keyword")
   327  		}
   328  		// If the user specified a name, use it!
   329  		name = options.TypeName
   330  		if namePrefix != nil && namePrefix.head == name && namePrefix.tail == nil {
   331  			// Special case: if this name is also the only component of the
   332  			// name-prefix, append the type-name anyway.  This happens when you
   333  			// assign a type name to an interface type, and we are generating
   334  			// one of its implementations.
   335  			name = makeLongTypeName(namePrefix, def.Name)
   336  		}
   337  		// (But the prefix is shared.)
   338  		namePrefix = newPrefixList(options.TypeName)
   339  	} else if def.Kind == ast.InputObject || def.Kind == ast.Enum {
   340  		// If we're an input-object or enum, there is only one type we will
   341  		// ever possibly generate for this type, so we don't need any of the
   342  		// qualifiers.  This is especially helpful because the caller is very
   343  		// likely to need to reference these types in their code.
   344  		name = upperFirst(def.Name)
   345  		// (namePrefix is ignored in this case.)
   346  	} else {
   347  		// Else, construct a name using the usual algorithm (see names.go).
   348  		name = makeTypeName(namePrefix, def.Name)
   349  	}
   350  
   351  	// If we already generated the type, we can skip it as long as it matches
   352  	// (and must fail if it doesn't).  (This can happen for input/enum types,
   353  	// types of fields of interfaces, when options.TypeName is set, or, of
   354  	// course, on invalid configuration or internal error.)
   355  	existing, err := g.getType(name, def.Name, selectionSet, pos)
   356  	if existing != nil || err != nil {
   357  		return existing, err
   358  	}
   359  
   360  	desc := descriptionInfo{
   361  		// TODO(benkraft): Copy any comment above this selection-set?
   362  		GraphQLDescription: def.Description,
   363  		GraphQLName:        def.Name,
   364  	}
   365  
   366  	// The struct option basically means "treat this as if it were an object".
   367  	// (It only applies if valid; this is important if you said the whole
   368  	// query should have `struct: true`.)
   369  	kind := def.Kind
   370  	if options.GetStruct() && validateStructOption(def, selectionSet, pos) == nil {
   371  		kind = ast.Object
   372  	}
   373  	switch kind {
   374  	case ast.Object:
   375  		fields, err := g.convertSelectionSet(
   376  			namePrefix, selectionSet, def, queryOptions)
   377  		if err != nil {
   378  			return nil, err
   379  		}
   380  		if options.GetFlatten() {
   381  			// As with struct, flatten only applies if valid, important if you
   382  			// applied it to the whole query.
   383  			// TODO(benkraft): This is a slightly fragile way to do this;
   384  			// figure out a good way to do it before/while constructing the
   385  			// fields, rather than after.
   386  			i, err := validateFlattenOption(def, selectionSet, pos)
   387  			if err == nil {
   388  				return fields[i].GoType, nil
   389  			}
   390  		}
   391  
   392  		goType := &goStructType{
   393  			GoName:          name,
   394  			Fields:          fields,
   395  			Selection:       selectionSet,
   396  			descriptionInfo: desc,
   397  			Generator:       g,
   398  		}
   399  		return g.addType(goType, goType.GoName, pos)
   400  
   401  	case ast.InputObject:
   402  		goType := &goStructType{
   403  			GoName:          name,
   404  			Fields:          make([]*goStructField, len(def.Fields)),
   405  			descriptionInfo: desc,
   406  			IsInput:         true,
   407  			Generator:       g,
   408  		}
   409  		// To handle recursive types, we need to add the type to the type-map
   410  		// *before* converting its fields.
   411  		_, err := g.addType(goType, goType.GoName, pos)
   412  		if err != nil {
   413  			return nil, err
   414  		}
   415  
   416  		for i, field := range def.Fields {
   417  			_, fieldOptions, err := g.parsePrecedingComment(
   418  				field, def, field.Position, queryOptions)
   419  			if err != nil {
   420  				return nil, err
   421  			}
   422  
   423  			goName := upperFirst(field.Name)
   424  			// Several of the arguments don't really make sense here:
   425  			// (note field.Type is necessarily a scalar, input, or enum)
   426  			// - namePrefix is ignored for input types and enums (see
   427  			//   names.go) and for scalars (they use client-specified
   428  			//   names)
   429  			// - selectionSet is ignored for input types, because we
   430  			//   just use all fields of the type; and it's nonexistent
   431  			//   for scalars and enums, our only other possible types
   432  			// TODO(benkraft): Can we refactor to avoid passing the values that
   433  			// will be ignored?  We know field.Type is a scalar, enum, or input
   434  			// type.  But plumbing that is a bit tricky in practice.
   435  			fieldGoType, err := g.convertType(
   436  				namePrefix, field.Type, nil, fieldOptions, queryOptions)
   437  			if err != nil {
   438  				return nil, err
   439  			}
   440  
   441  			goType.Fields[i] = &goStructField{
   442  				GoName:      goName,
   443  				GoType:      fieldGoType,
   444  				JSONName:    field.Name,
   445  				GraphQLName: field.Name,
   446  				Description: field.Description,
   447  				Omitempty:   fieldOptions.GetOmitempty(),
   448  			}
   449  		}
   450  		return goType, nil
   451  
   452  	case ast.Interface, ast.Union:
   453  		sharedFields, err := g.convertSelectionSet(
   454  			namePrefix, selectionSet, def, queryOptions)
   455  		if err != nil {
   456  			return nil, err
   457  		}
   458  		// Flatten can only flatten if there is only one field (plus perhaps
   459  		// __typename), and it's shared.
   460  		if options.GetFlatten() {
   461  			i, err := validateFlattenOption(def, selectionSet, pos)
   462  			if err == nil {
   463  				return sharedFields[i].GoType, nil
   464  			}
   465  		}
   466  
   467  		implementationTypes := g.schema.GetPossibleTypes(def)
   468  		goType := &goInterfaceType{
   469  			GoName:          name,
   470  			SharedFields:    sharedFields,
   471  			Implementations: make([]*goStructType, len(implementationTypes)),
   472  			Selection:       selectionSet,
   473  			descriptionInfo: desc,
   474  		}
   475  
   476  		for i, implDef := range implementationTypes {
   477  			// TODO(benkraft): In principle we should skip generating a Go
   478  			// field for __typename each of these impl-defs if you didn't
   479  			// request it (and it was automatically added by
   480  			// preprocessQueryDocument).  But in practice it doesn't really
   481  			// hurt, and would be extra work to avoid, so we just leave it.
   482  			implTyp, err := g.convertDefinition(
   483  				namePrefix, implDef, pos, selectionSet, options, queryOptions)
   484  			if err != nil {
   485  				return nil, err
   486  			}
   487  
   488  			implStructTyp, ok := implTyp.(*goStructType)
   489  			if !ok { // (should never happen on a valid schema)
   490  				return nil, errorf(
   491  					pos, "interface %s had non-object implementation %s",
   492  					def.Name, implDef.Name)
   493  			}
   494  			goType.Implementations[i] = implStructTyp
   495  		}
   496  		return g.addType(goType, goType.GoName, pos)
   497  
   498  	case ast.Enum:
   499  		goType := &goEnumType{
   500  			GoName:      name,
   501  			GraphQLName: def.Name,
   502  			Description: def.Description,
   503  			Values:      make([]goEnumValue, len(def.EnumValues)),
   504  		}
   505  		for i, val := range def.EnumValues {
   506  			goType.Values[i] = goEnumValue{Name: val.Name, Description: val.Description}
   507  		}
   508  		return g.addType(goType, goType.GoName, pos)
   509  
   510  	case ast.Scalar:
   511  		if builtinTypes[def.Name] != "" {
   512  			// In this case, the user asked for a custom Go type-name
   513  			// for a built-in type, e.g. `type MyString string`.
   514  			goType := &goTypenameForBuiltinType{
   515  				GoTypeName:    name,
   516  				GoBuiltinName: builtinTypes[def.Name],
   517  				GraphQLName:   def.Name,
   518  			}
   519  			return g.addType(goType, goType.GoTypeName, pos)
   520  		}
   521  
   522  		// (If you had an entry in bindings, we would have returned it above.)
   523  		return nil, errorf(
   524  			pos, `unknown scalar %v: please add it to "bindings" in genqlient.yaml`, def.Name)
   525  	default:
   526  		return nil, errorf(pos, "unexpected kind: %v", def.Kind)
   527  	}
   528  }
   529  
   530  // convertSelectionSet converts a GraphQL selection-set into a list of
   531  // corresponding Go struct-fields (and their Go types)
   532  //
   533  // A selection-set is a list of fields within braces like `{ myField }`, as
   534  // appears at the toplevel of a query, in a field's sub-selections, or within
   535  // an inline or named fragment.
   536  //
   537  // containingTypedef is the type-def whose fields we are selecting, and may be
   538  // an object type or an interface type.  In the case of interfaces, we'll call
   539  // convertSelectionSet once for the interface, and once for each
   540  // implementation.
   541  func (g *generator) convertSelectionSet(
   542  	namePrefix *prefixList,
   543  	selectionSet ast.SelectionSet,
   544  	containingTypedef *ast.Definition,
   545  	queryOptions *genqlientDirective,
   546  ) ([]*goStructField, error) {
   547  	fields := make([]*goStructField, 0, len(selectionSet))
   548  	for _, selection := range selectionSet {
   549  		_, selectionOptions, err := g.parsePrecedingComment(
   550  			selection, nil, selection.GetPosition(), queryOptions)
   551  		if err != nil {
   552  			return nil, err
   553  		}
   554  
   555  		switch selection := selection.(type) {
   556  		case *ast.Field:
   557  			field, err := g.convertField(
   558  				namePrefix, selection, selectionOptions, queryOptions)
   559  			if err != nil {
   560  				return nil, err
   561  			}
   562  			fields = append(fields, field)
   563  		case *ast.FragmentSpread:
   564  			maybeField, err := g.convertFragmentSpread(selection, containingTypedef)
   565  			if err != nil {
   566  				return nil, err
   567  			} else if maybeField != nil {
   568  				fields = append(fields, maybeField)
   569  			}
   570  		case *ast.InlineFragment:
   571  			// (Note this will return nil, nil if the fragment doesn't apply to
   572  			// this type.)
   573  			fragmentFields, err := g.convertInlineFragment(
   574  				namePrefix, selection, containingTypedef, queryOptions)
   575  			if err != nil {
   576  				return nil, err
   577  			}
   578  			fields = append(fields, fragmentFields...)
   579  		default:
   580  			return nil, errorf(nil, "invalid selection type: %T", selection)
   581  		}
   582  	}
   583  
   584  	// We need to deduplicate, if you asked for
   585  	//	{ id, id, id, ... on SubType { id } }
   586  	// (which, yes, is legal) we'll treat that as just { id }.
   587  	uniqFields := make([]*goStructField, 0, len(selectionSet))
   588  	fragmentNames := make(map[string]bool, len(selectionSet))
   589  	fieldNames := make(map[string]bool, len(selectionSet))
   590  	for _, field := range fields {
   591  		// If you embed a field twice via a named fragment, we keep both, even
   592  		// if there are complicated overlaps, since they are separate types to
   593  		// us.  (See also the special handling for IsEmbedded in
   594  		// unmarshal.go.tmpl.)
   595  		//
   596  		// But if you spread the samenamed fragment twice, e.g.
   597  		//	{ ...MyFragment, ... on SubType { ...MyFragment } }
   598  		// we'll still deduplicate that.
   599  		if field.JSONName == "" {
   600  			name := field.GoType.Reference()
   601  			if fragmentNames[name] {
   602  				continue
   603  			}
   604  			uniqFields = append(uniqFields, field)
   605  			fragmentNames[name] = true
   606  			continue
   607  		}
   608  
   609  		// GraphQL (and, effectively, JSON) requires that all fields with the
   610  		// same alias (JSON-name) must be the same (i.e. refer to the same
   611  		// field), so that's how we deduplicate.
   612  		if fieldNames[field.JSONName] {
   613  			// GraphQL (and, effectively, JSON) forbids you from having two
   614  			// fields with the same alias (JSON-name) that refer to different
   615  			// GraphQL fields.  But it does allow you to have the same field
   616  			// with different selections (subject to some additional rules).
   617  			// We say: that's too complicated! and allow duplicate fields
   618  			// only if they're "leaf" types (enum or scalar).
   619  			switch field.GoType.Unwrap().(type) {
   620  			case *goOpaqueType, *goEnumType:
   621  				// Leaf field; we can just deduplicate.
   622  				// Note GraphQL already guarantees that the conflicting field
   623  				// has scalar/enum type iff this field does:
   624  				// https://spec.graphql.org/draft/#SameResponseShape()
   625  				continue
   626  			case *goStructType, *goInterfaceType:
   627  				// TODO(benkraft): Keep track of the position of each
   628  				// selection, so we can put this error on the right line.
   629  				return nil, errorf(nil,
   630  					"genqlient doesn't allow duplicate fields with different selections "+
   631  						"(see https://github.com/opiuman/genqlient/issues/64); "+
   632  						"duplicate field: %s.%s", containingTypedef.Name, field.JSONName)
   633  			default:
   634  				return nil, errorf(nil, "unexpected field-type: %T", field.GoType.Unwrap())
   635  			}
   636  		}
   637  		uniqFields = append(uniqFields, field)
   638  		fieldNames[field.JSONName] = true
   639  	}
   640  	return uniqFields, nil
   641  }
   642  
   643  // fragmentMatches returns true if the given fragment is "active" when applied
   644  // to the given type.
   645  //
   646  // "Active" here means "the fragment's fields will be returned on all objects
   647  // of the given type", which is true when the given type is or implements
   648  // the fragment's type.  This is distinct from the rules for when a fragment
   649  // spread is legal, which is true when the fragment would be active for *any*
   650  // of the concrete types the spread-context could have (see
   651  // https://spec.graphql.org/draft/#sec-Fragment-Spreads or docs/DESIGN.md).
   652  //
   653  // containingTypedef is as described in convertInlineFragment, below.
   654  // fragmentTypedef is the definition of the fragment's type-condition, i.e. the
   655  // definition of MyType in a fragment `on MyType`.
   656  func fragmentMatches(containingTypedef, fragmentTypedef *ast.Definition) bool {
   657  	if containingTypedef.Name == fragmentTypedef.Name {
   658  		return true
   659  	}
   660  	for _, iface := range containingTypedef.Interfaces {
   661  		// Note we don't need to recurse into the interfaces here, because in
   662  		// GraphQL types must list all the interfaces they implement, including
   663  		// all types those interfaces implement [1].  Actually, at present
   664  		// gqlparser doesn't even support interfaces implementing other
   665  		// interfaces, but our code would handle that too.
   666  		// [1] https://spec.graphql.org/draft/#sec-Interfaces.Interfaces-Implementing-Interfaces
   667  		if iface == fragmentTypedef.Name {
   668  			return true
   669  		}
   670  	}
   671  	return false
   672  }
   673  
   674  // convertInlineFragment converts a single GraphQL inline fragment
   675  // (`... on MyType { myField }`) into Go struct-fields.
   676  //
   677  // containingTypedef is the type-def corresponding to the type into which we
   678  // are spreading; it may be either an interface type (when spreading into one)
   679  // or an object type (when writing the implementations of such an interface, or
   680  // when using an inline fragment in an object type which is rare).  If the
   681  // given fragment does not apply to that type, this function returns nil, nil.
   682  //
   683  // In general, we treat such fragments' fields as if they were fields of the
   684  // parent selection-set (except of course they are only included in types the
   685  // fragment matches); see docs/DESIGN.md for more.
   686  func (g *generator) convertInlineFragment(
   687  	namePrefix *prefixList,
   688  	fragment *ast.InlineFragment,
   689  	containingTypedef *ast.Definition,
   690  	queryOptions *genqlientDirective,
   691  ) ([]*goStructField, error) {
   692  	// You might think fragmentTypedef is just fragment.ObjectDefinition, but
   693  	// actually that's the type into which the fragment is spread.
   694  	fragmentTypedef := g.schema.Types[fragment.TypeCondition]
   695  	if !fragmentMatches(containingTypedef, fragmentTypedef) {
   696  		return nil, nil
   697  	}
   698  	return g.convertSelectionSet(namePrefix, fragment.SelectionSet,
   699  		containingTypedef, queryOptions)
   700  }
   701  
   702  // convertFragmentSpread converts a single GraphQL fragment-spread
   703  // (`...MyFragment`) into a Go struct-field.  If the fragment does not apply to
   704  // this type, returns nil.
   705  //
   706  // containingTypedef is as described in convertInlineFragment, above.
   707  func (g *generator) convertFragmentSpread(
   708  	fragmentSpread *ast.FragmentSpread,
   709  	containingTypedef *ast.Definition,
   710  ) (*goStructField, error) {
   711  	if !fragmentMatches(containingTypedef, fragmentSpread.Definition.Definition) {
   712  		return nil, nil
   713  	}
   714  
   715  	typ, ok := g.typeMap[fragmentSpread.Name]
   716  	if !ok {
   717  		// If we haven't yet, convert the fragment itself.  Note that fragments
   718  		// aren't allowed to have cycles, so this won't recurse forever.
   719  		var err error
   720  		typ, err = g.convertNamedFragment(fragmentSpread.Definition)
   721  		if err != nil {
   722  			return nil, err
   723  		}
   724  	}
   725  
   726  	iface, ok := typ.(*goInterfaceType)
   727  	if ok && containingTypedef.Kind == ast.Object {
   728  		// If the containing type is concrete, and the fragment spread is
   729  		// abstract, refer directly to the appropriate implementation, to save
   730  		// the caller having to do type-assertions that will always succeed.
   731  		//
   732  		// That is, if you do
   733  		//	fragment F on I { ... }
   734  		//  query Q { a { ...F } }
   735  		// for the fragment we generate
   736  		//  type F interface { ... }
   737  		//  type FA struct { ... }
   738  		//  // (other implementations)
   739  		// when you spread F into a context of type A, we embed FA, not F.
   740  		for _, impl := range iface.Implementations {
   741  			if impl.GraphQLName == containingTypedef.Name {
   742  				typ = impl
   743  			}
   744  		}
   745  	}
   746  
   747  	// TODO(benkraft): Set directive here if we ever allow @genqlient
   748  	// directives on fragment-spreads.
   749  	return &goStructField{GoName: "" /* i.e. embedded */, GoType: typ}, nil
   750  }
   751  
   752  // convertNamedFragment converts a single GraphQL named fragment-definition
   753  // (`fragment MyFragment on MyType { ... }`) into a Go struct.
   754  func (g *generator) convertNamedFragment(fragment *ast.FragmentDefinition) (goType, error) {
   755  	typ := g.schema.Types[fragment.TypeCondition]
   756  
   757  	comment, directive, err := g.parsePrecedingComment(fragment, nil, fragment.Position, nil)
   758  	if err != nil {
   759  		return nil, err
   760  	}
   761  
   762  	desc := descriptionInfo{
   763  		CommentOverride:    comment,
   764  		GraphQLName:        typ.Name,
   765  		GraphQLDescription: typ.Description,
   766  		FragmentName:       fragment.Name,
   767  	}
   768  
   769  	// The rest basically follows how we convert a definition, except that
   770  	// things like type-names are a bit different.
   771  
   772  	fields, err := g.convertSelectionSet(
   773  		newPrefixList(fragment.Name), fragment.SelectionSet, typ, directive)
   774  	if err != nil {
   775  		return nil, err
   776  	}
   777  	if directive.GetFlatten() {
   778  		// Flatten on a fragment-definition is a bit weird -- it makes one
   779  		// fragment effectively an alias for another -- but no reason we can't
   780  		// allow it.
   781  		i, err := validateFlattenOption(typ, fragment.SelectionSet, fragment.Position)
   782  		if err == nil {
   783  			return fields[i].GoType, nil
   784  		}
   785  	}
   786  
   787  	switch typ.Kind {
   788  	case ast.Object:
   789  		goType := &goStructType{
   790  			GoName:          fragment.Name,
   791  			Fields:          fields,
   792  			Selection:       fragment.SelectionSet,
   793  			descriptionInfo: desc,
   794  			Generator:       g,
   795  		}
   796  		g.typeMap[fragment.Name] = goType
   797  		return goType, nil
   798  	case ast.Interface, ast.Union:
   799  		implementationTypes := g.schema.GetPossibleTypes(typ)
   800  		goType := &goInterfaceType{
   801  			GoName:          fragment.Name,
   802  			SharedFields:    fields,
   803  			Implementations: make([]*goStructType, len(implementationTypes)),
   804  			Selection:       fragment.SelectionSet,
   805  			descriptionInfo: desc,
   806  		}
   807  		g.typeMap[fragment.Name] = goType
   808  
   809  		for i, implDef := range implementationTypes {
   810  			implFields, err := g.convertSelectionSet(
   811  				newPrefixList(fragment.Name), fragment.SelectionSet, implDef, directive)
   812  			if err != nil {
   813  				return nil, err
   814  			}
   815  
   816  			implDesc := desc
   817  			implDesc.GraphQLName = implDef.Name
   818  
   819  			implTyp := &goStructType{
   820  				GoName:          fragment.Name + upperFirst(implDef.Name),
   821  				Fields:          implFields,
   822  				Selection:       fragment.SelectionSet,
   823  				descriptionInfo: implDesc,
   824  				Generator:       g,
   825  			}
   826  			goType.Implementations[i] = implTyp
   827  			g.typeMap[implTyp.GoName] = implTyp
   828  		}
   829  
   830  		return goType, nil
   831  	default:
   832  		return nil, errorf(fragment.Position, "invalid type for fragment: %v is a %v",
   833  			fragment.TypeCondition, typ.Kind)
   834  	}
   835  }
   836  
   837  // convertField converts a single GraphQL operation-field into a Go
   838  // struct-field (and its type).
   839  //
   840  // Note that input-type fields are handled separately (inline in
   841  // convertDefinition), because they come from the type-definition, not the
   842  // operation.
   843  func (g *generator) convertField(
   844  	namePrefix *prefixList,
   845  	field *ast.Field,
   846  	fieldOptions, queryOptions *genqlientDirective,
   847  ) (*goStructField, error) {
   848  	if field.Definition == nil {
   849  		// Unclear why gqlparser hasn't already rejected this,
   850  		// but empirically it might not.
   851  		return nil, errorf(
   852  			field.Position, "undefined field %v", field.Alias)
   853  	}
   854  
   855  	goName := upperFirst(field.Alias)
   856  	namePrefix = nextPrefix(namePrefix, field)
   857  
   858  	fieldGoType, err := g.convertType(
   859  		namePrefix, field.Definition.Type, field.SelectionSet,
   860  		fieldOptions, queryOptions)
   861  	if err != nil {
   862  		return nil, err
   863  	}
   864  
   865  	return &goStructField{
   866  		GoName:      goName,
   867  		GoType:      fieldGoType,
   868  		JSONName:    field.Alias,
   869  		GraphQLName: field.Name,
   870  		Description: field.Definition.Description,
   871  	}, nil
   872  }