Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor import-loading to simplify the type-generation code #101

Merged
merged 2 commits into from
Sep 17, 2021

Conversation

benjaminjkraft
Copy link
Collaborator

Summary:

genqlient has some code (imports.go) dedicated to tracking which
imports we need and avoiding conflicts, as well as converting a
(restricted) Go expression like map[string]github.com/me/mypkg.MyType
to an import (github.com/me/mypkg) and a type-reference
(map[string]mypkg.MyType) to be used in the context of that import,
and at least making some attempt to track conflicts. (Right now the
conflict-avoidance is not very smart, and not very well tested, but it
comes up rarely anyway.) Sadly, that code was a bit cumbersome to use,
because you had to first register the imports (typically from
convert.go), then use them (often from the template).

In this commit I refactor the order we write things in order to allow a
significant simplification of how we import; in particular we no longer
have to guess in advance what imports which template will need; it can
just do {{ref <expr>}} as before, and it just works. To do this, I:

  • changed the importer to have only one API, which adds the import if
    needed, and returns the reference either way
  • added a check that we don't add imports after they're written
  • reorganized the toplevel templates a bit to make sure that check never
    fires; we now generate all the types and operations, then write the
    imports and glue it all together
    This removes a bunch of silly code, and should simplify the process of
    adding custom (un)marshalers (Allow mapping directly to a type but not using its standard JSON serialization #38).

While I was at it, I put the documentation of what expressions we
support in a more visible place, and added a type-assertion that your
custom context type implements context.Context (if applicable).

Test plan:

make check

genqlient has some code (`imports.go`) dedicated to tracking which
imports we need and avoiding conflicts, as well as converting a
(restricted) Go expression like `map[string]github.com/me/mypkg.MyType`
to an import (`github.com/me/mypkg`) and a type-reference
(`map[string]mypkg.MyType`) to be used in the context of that import,
and at least making some attempt to track conflicts.  (Right now the
conflict-avoidance is not very smart, and not very well tested, but it
comes up rarely anyway.)  Sadly, that code was a bit cumbersome to use,
because you had to first register the imports (typically from
`convert.go`), then use them (often from the template).

In this commit I refactor the order we write things in order to allow a
significant simplification of how we import; in particular we no longer
have to guess in advance what imports which template will need; it can
just do `{{ref <expr>}}` as before, and it just works.  To do this, I:
- changed the importer to have only one API, which adds the import if
  needed, and returns the reference either way
- added a check that we don't add imports after they're written
- reorganized the toplevel templates a bit to make sure that check never
  fires; we now generate all the types and operations, then write the
  imports and glue it all together
This removes a bunch of silly code, and should simplify the process of
adding custom (un)marshalers (#38).

While I was at it, I put the documentation of what expressions we
support in a more visible place, and added a type-assertion that your
custom context type implements context.Context (if applicable).

Test plan: make check

Reviewers: marksandstrom, mahtab, jvoll, adam, miguel, steve
Copy link
Member

@StevenACoffman StevenACoffman left a comment

Choose a reason for hiding this comment

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

Oh, thanks! The $ was kind of bugging me too.

Copy link
Contributor

@dnerdy dnerdy left a comment

Choose a reason for hiding this comment

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

I like it! The buffering is much easier to reason about compared to pre-registering references.

for _, op := range document.Operations {
if err = g.addOperation(op); err != nil {
for _, operation := range g.Operations {
err = g.execute("operation.go.tmpl", &bodyBuf, operation)
Copy link
Contributor

Choose a reason for hiding this comment

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

I know "execute" is based on text/template terminology, but that isn't super clear in the context of the generator, IMO. Perhaps this function could be named renderTemplate or something similar.

if !ok {
if g.importsLocked {
return "", errorf(nil,
`genqlient internal error: imports locked but no alias defined for package "%v"`, pkgPath)
Copy link
Contributor

Choose a reason for hiding this comment

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

perhaps: "imports locked but package "%v" has not been imported"?

@benjaminjkraft benjaminjkraft merged commit f72933f into main Sep 17, 2021
@benjaminjkraft benjaminjkraft deleted the benkraft.imports branch September 17, 2021 01:09
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.

3 participants