Add support for interfaces, part 2: list-of-interface (#54)

## Summary:
In this commit I remove one of the limitations of our support for
interfaces, from #52, by adding support for list-of-interface fields.
This was surprisingly complex!  The issue is that, as before, it's the
containing type that has to do all the glue work -- and it's that glue
work that is complicated by list-of-interface fields.

All in all, it's not that much new code, and by far the hard part is
just 20 lines in the UnmarshalJSON template (which come with almost
twice as many lines of comments to explain them).  It may be easiest to
start by reading some of the generated code, and then read the template.

I also added support for such fields with `pointer: true` specified,
such that the type is `[][]...[]*MyInterface`, although I don't know why
you would want that.  This does *not* allow e.g. `*[]*[][]*MyInterface`;
that would require a way to specify it (see #16) but also add some extra
complexity (as we'd have to actually walk the type-unwrap chain
properly, instead of just counting the number of slices and whether
there's a pointer).

Issue: https://github.com/Khan/genqlient/issues/8

## Test plan:
make check


Author: benjaminjkraft

Reviewers: dnerdy, benjaminjkraft, aberkan, csilvers, MiguelCastillo

Required Reviewers: 

Approved by: dnerdy

Checks:  Test (1.17),  Test (1.16),  Test (1.15),  Test (1.14),  Test (1.13),  Lint,  Lint,  Test (1.17),  Test (1.16),  Test (1.15),  Test (1.14),  Test (1.13)

Pull request URL: https://github.com/Khan/genqlient/pull/54
This commit is contained in:
Ben Kraft
2021-08-25 11:57:24 -07:00
committed by GitHub
parent 4c38cb7759
commit 1e87553788
22 changed files with 1310 additions and 77 deletions
+40 -5
View File
@@ -22,6 +22,18 @@ type goType interface {
// Reference returns the Go name of this type, e.g. []*MyStruct, and may be
// used to refer to it in Go code.
Reference() string
// Remove slice/pointer wrappers, and return the underlying (named (or
// builtin)) type. For example, given []*MyStruct, return MyStruct.
Unwrap() goType
// Count the number of times Unwrap() will unwrap a slice type. For
// example, given [][][]*MyStruct (or []**[][]*MyStruct, but we never
// currently generate that), return 3.
SliceDepth() int
// True if Unwrap() will unwrap a pointer at least once.
IsPointer() bool
}
var (
@@ -106,6 +118,11 @@ type goStructField struct {
Description string
}
func isAbstract(typ goType) bool {
_, ok := typ.Unwrap().(*goInterfaceType)
return ok
}
func (typ *goStructType) WriteDefinition(w io.Writer, g *generator) error {
description := typ.Description
if typ.Incomplete {
@@ -117,7 +134,7 @@ func (typ *goStructType) WriteDefinition(w io.Writer, g *generator) error {
for _, field := range typ.Fields {
writeDescription(w, field.Description)
jsonName := field.JSONName
if _, ok := field.GoType.(*goInterfaceType); ok {
if isAbstract(field.GoType) {
// abstract types are handled in our UnmarshalJSON
jsonName = "-"
}
@@ -155,10 +172,7 @@ func (typ *goStructType) Reference() string { return typ.GoName }
func (typ *goStructType) AbstractFields() []*goStructField {
var ret []*goStructField
for _, field := range typ.Fields {
// TODO(benkraft): To handle list-of-interface fields, we should really
// be "unwrapping" any goSliceType/goPointerType wrappers to find the
// goInterfaceType.
if _, ok := field.GoType.(*goInterfaceType); ok {
if isAbstract(field.GoType) {
ret = append(ret, field)
}
}
@@ -211,6 +225,27 @@ func (typ *goInterfaceType) WriteDefinition(w io.Writer, g *generator) error {
func (typ *goInterfaceType) Reference() string { return typ.GoName }
func (typ *goOpaqueType) Unwrap() goType { return typ }
func (typ *goSliceType) Unwrap() goType { return typ.Elem.Unwrap() }
func (typ *goPointerType) Unwrap() goType { return typ.Elem.Unwrap() }
func (typ *goEnumType) Unwrap() goType { return typ }
func (typ *goStructType) Unwrap() goType { return typ }
func (typ *goInterfaceType) Unwrap() goType { return typ }
func (typ *goOpaqueType) SliceDepth() int { return 0 }
func (typ *goSliceType) SliceDepth() int { return typ.Elem.SliceDepth() + 1 }
func (typ *goPointerType) SliceDepth() int { return 0 }
func (typ *goEnumType) SliceDepth() int { return 0 }
func (typ *goStructType) SliceDepth() int { return 0 }
func (typ *goInterfaceType) SliceDepth() int { return 0 }
func (typ *goOpaqueType) IsPointer() bool { return false }
func (typ *goSliceType) IsPointer() bool { return typ.Elem.IsPointer() }
func (typ *goPointerType) IsPointer() bool { return true }
func (typ *goEnumType) IsPointer() bool { return false }
func (typ *goStructType) IsPointer() bool { return false }
func (typ *goInterfaceType) IsPointer() bool { return false }
func incompleteTypeDescription(goName, graphQLName, description string) string {
// For types where we only have some fields, note that, along with
// the GraphQL documentation (if any). We don't want to just use