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