Skip to content

executor: adds support for anonymous contained structs - #371

Open
chris-ramon wants to merge 2 commits into
masterfrom
anon-contained-struct
Open

chris-ramon wants to merge 2 commits into
masterfrom
anon-contained-struct

Conversation

@chris-ramon

@chris-ramon chris-ramon commented Jul 28, 2018 •

Copy link
Copy Markdown
Member

Overview

Test plan

  • go test ./...

@imjma

imjma commented Sep 14, 2018

Copy link
Copy Markdown

HI, wondering if there is any chance to merge this?

@mrg0lden

Copy link
Copy Markdown

@chris-ramon @pavelnikolov Same question, would you please merge it?

@niondir

niondir commented Oct 22, 2018

Copy link
Copy Markdown

Same here

@chris-ramon

Copy link
Copy Markdown
Member Author

Thanks for letting us know how important this PR are for you guys.

I'll jump back to get this merge as soon as I get the chance today/tomorrow.

@chris-ramon
chris-ramon force-pushed the anon-contained-struct branch from d376817 to d35d558 Compare October 24, 2018 00:23
@coveralls

coveralls commented Oct 24, 2018 •

Copy link
Copy Markdown

Coverage Status

Coverage increased (+0.009%) to 91.787% when pulling d35d558 on anon-contained-struct into a7e15c0 on master.

Ariel Salomon added 2 commits October 23, 2018 19:25
@raininfall

Copy link
Copy Markdown

I think this will resolve the issue without using user-provided resolve function.

if typeField.Anonymous && typeField.Type.Kind() == reflect.Struct {
  anonymousResolvedValue, err := defaultResolveStruct(valueField, fieldName)
  if err != nil {
    return nil, err
  } else if anonymousResolvedValue != nil {
    return anonymousResolvedValue, nil
  }
}

@abraithwaite

Copy link
Copy Markdown

Just commented on the issue, but just saw this PR. I'm still interested in this!

Comment thread executor.go
return valueField.Interface(), nil
}
if typeField.Anonymous && typeField.Type.Kind() == reflect.Struct {
return defaultResolveStruct(valueField, fieldName)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a bug here. We return too early if an anonymous struct comes before a field we're looking for.

I think what we want to do is run through all the top-level fields first, then dive if the field wasn't found.
Repeat for all anonymous structs until it's found. If not found, then return nil, nil.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's a fix, sans a proper test:

abraithwaite@5decd5b

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, @abraithwaite

@clery

clery commented Apr 1, 2019

Copy link
Copy Markdown

Any news on this ?

@cettoana

Copy link
Copy Markdown

Any updates on this PR?

@niondir

niondir commented Jun 13, 2019

Copy link
Copy Markdown

Still waiting for the merge as qell...

@pavelnikolov

pavelnikolov commented Jun 13, 2019 •

Copy link
Copy Markdown

This PR has conflicts.

@SuNNjek

SuNNjek commented Aug 26, 2020

Copy link
Copy Markdown

Is this still being worked on?

@pavelnikolov

Copy link
Copy Markdown

Not that I am aware of

@bakatest-me

bakatest-me commented Jun 23, 2022 •

Copy link
Copy Markdown

Hi
how to use this feature on graphq-go/graphql v0.8.0 (master branch)?
about Anonymous(embed) struct

@taleeus

taleeus commented Mar 20, 2024

Copy link
Copy Markdown

Bump

Nthalk added a commit to IodeSystems/graphql-go that referenced this pull request Aug 17, 2026
DefaultResolveFn only scanned a struct's directly declared fields, so a
field promoted from an embedded struct resolved to null:

    type Inner struct{ Name string `json:"name"` }
    type Outer struct{ Inner; Other string }
    { o { name other } }  ->  {"name": null, "other": "outer"}

Match Go's own promotion rules rather than a first-match recursion: the
search walks the embedding graph breadth-first, so a shallower field
wins outright and a same-depth tie between two branches is ambiguous
and matches nothing — the answer a plain selector expression would give.
A depth-first walk would instead let a deeply embedded field shadow a
shallower one purely by declaration order.

resolveDefaultStructField now returns an index path ([]int) instead of a
single index, and defaultResolveCache memoizes that path. Walking it goes
through fieldByIndexPath rather than reflect.Value.FieldByIndex, which
panics when the path crosses a nil embedded pointer; a nil embed yields a
null field. Self-referential embeddings (type T struct{ *T }) terminate
via a visited-type set.

Only anonymous fields promote — a named struct field is still not
searched.

No hot-path cost: PlannedAppend_DefaultResolve_Struct50 is 8.98µs before
and after (~, p=0.589, n=6), with B/op and allocs/op unchanged. The cache
turns the wider search into a one-off per (type, field name).

Addresses the problem in graphql-go#371, which is still open
upstream. Not cherry-picked: that PR's defaultResolveStruct returns the
result of the first anonymous field it finds even when that returns no
match, so a later sibling holding the field is never reached, and it
drops this fork's cached index in favour of a per-call scan.
Nthalk added a commit to IodeSystems/graphql-go that referenced this pull request Aug 17, 2026
Triaged all 38 open PRs on graphql-go/graphql. Records which are already
present here (15, several via our own independent fixes), the one acted
on (graphql-go#371), and why the rest are not worth taking — so the next sync
re-checks only PRs opened since.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Issue with embedded Struct response