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

Fix type-naming in the presence of interfaces, and refactor it a lot #71

Merged
merged 3 commits into from
Aug 30, 2021

Conversation

benjaminjkraft
Copy link
Collaborator

Summary:

When adding support for interfaces, I did not do the type-names as I
intended: they came out to be MyFieldMyType, not
MyInterfaceMyFieldMyType, which is inconsistent, but not strictly
wrong. But once supporting fragments, this is also now incorrect.
(Exactly why is described in the comments inline.) In this commit, in
any case, I fix it.

To do that, I finally did the last of the refactors I've been hoping to
do but unable to successfully implement, which is to make the type-name
and type-name-prefix management clearer. In the past it was kind of
spread out, and each caller would have to pass the right name into
convertDefinition, which go quite unwieldy. Now, the case that really
wanted that -- the operation toplevel -- just does it own thing; and the
main name-generation code is factored out into a separate file with
tests, and with a long comment that goes into all the details of the
algorithm that the design-doc didn't cover. (I even had some fun using
a linked list to implement the prefix-stack!)

This allowed me to fix the above bug fairly easily -- actually the fix
was pretty much automatic once I understood how to organize things.
There is one change which is that if your query name is unexported, we
no longer do the same with the input-type names; it's unclear to me if
anyone will actually care about this behavior (Khan always makes the
queries exported) but if they did it was very inconsistent (only at the
query toplevel, and only for input-objects, not enums), so we can
reimplement it properly if that comes up. As a bonus fix, we now better
handle the case where your type-names are lowercase, which is legal if
nonstandard GraphQL.

Issue: #8

Test plan:

make tesc

@benjaminjkraft
Copy link
Collaborator Author

This is a fixed version of #66.

Base automatically changed from benkraft.fragments-1 to main August 28, 2021 01:06
@benjaminjkraft
Copy link
Collaborator Author

(Plus, now, a rebase!)

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.

This is indeed much easier to understand.

Comment on lines +3 to +6
// This file generates the names for genqlient's generated types. This is
// somewhat tricky because the names need to be unique, stable, and, to the
// extent possible, human-readable and -writable. See DESIGN.md for an
// overview of the considerations; in short, we need long names.
Copy link
Contributor

Choose a reason for hiding this comment

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

I said it in #66, but I'll say it again. Amazing documentation! Thanks.

// easy.
type prefixList struct {
last string // the list goes back-to-front, so this is the *last* prefix
rest *prefixList
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: perhaps previous instead of rest? Or maybe name last either prefix or name or element and say this is a forward linked list but with the elements in the opposite order?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Ah, good ideas.

}
l := len(reversed)
for i := 0; i < l/2; i++ {
reversed[i], reversed[l-1-i] = reversed[l-1-i], reversed[i]
Copy link
Contributor

Choose a reason for hiding this comment

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

Heh, nice.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I wish I did not have to know that this is the standard way to reverse a list in Go!

// thing, because we want to do a few things differently, and because we
// know we have an object type, so we can include only that case.
fields, err := g.convertSelectionSet(
&prefixList{last: operation.Name}, operation.SelectionSet, baseType, queryOptions)
Copy link
Contributor

Choose a reason for hiding this comment

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

It seems like a prefixList constructor could be useful so the caller doesn't need to know anything about the internal structure of the list.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

ah, sure.

When adding support for interfaces, I did not do the type-names as I
intended: they came out to be `MyFieldMyType`, not
`MyInterfaceMyFieldMyType`, which is inconsistent, but not strictly
wrong.  But once supporting fragments, this is also now incorrect.
(Exactly why is described in the comments inline.)  In this commit, in
any case, I fix it.

To do that, I finally did the last of the refactors I've been hoping to
do but unable to successfully implement, which is to make the type-name
and type-name-prefix management clearer.  In the past it was kind of
spread out, and each caller would have to pass the right name into
`convertDefinition`, which go quite unwieldy.  Now, the case that really
wanted that -- the operation toplevel -- just does it own thing; and the
main name-generation code  is factored out into a separate file with
tests, and with a long comment that goes into all the details of the
algorithm that the design-doc didn't cover.  (I even had some fun using
a linked list to implement the prefix-stack!)

This allowed me to fix the above bug fairly easily -- actually the fix
was pretty much automatic once I understood how to organize things.
There is one change which is that if your query name is unexported, we
no longer do the same with the input-type names; it's unclear to me if
anyone will actually care about this behavior (Khan always makes the
queries exported) but if they did it was very inconsistent (only at the
query toplevel, and only for input-objects, not enums), so we can
reimplement it properly if that comes up.  As a bonus fix, we now better
handle the case where your type-names are lowercase, which is legal if
nonstandard GraphQL.

Issue: #8

Test plan: make tesc

Reviewers: marksandstrom, adam, miguel
@benjaminjkraft benjaminjkraft merged commit 6fdb170 into main Aug 30, 2021
@benjaminjkraft benjaminjkraft deleted the benkraft.fragments-2 branch August 30, 2021 17:50
benjaminjkraft added a commit that referenced this pull request Sep 22, 2021
We typically name our types `OperationFieldTypeFieldType`, but if a
type's name matches the preceding field-name, we omit the type-name.
In #71 I changed the behavior such that we no longer do that in the case
where the type's name matches some suffix of the name-so-far that's
longer than just the leaf field-name.

This was semi-intentional; I assumed it didn't matter and would be more
predictable this way.  But it turns out that was a feature, both in the
sense that almost any change to the type-name-generator is breaking, and
in the sense that it made the names uglier.  Plus, now that we have
better conflict-detection (#94), the possibility that some tricksy
type-names could cause problems is no longer as much of an issue, so we
can be a little less careful here.  (Although I think this is no less
safe than before; the field-names are the important part.)  So in this
commit I revert the change.

Specifically, this comes up a lot at Khan where we do
```
mutation ForcePhantom {
    forcePhantom {     # type: ForcePhantom
        error { ... }  # type: ForcePhantomError
    }
}
```
Before #71, and again after this change, we'll generate
`ForcePhantomForcePhantomError` for `error`; before we'd generate
`ForcePhantomForcePhantomErrorForcePhantomError`.

Issue: #109

Test plan:
make tesc

Reviewers: csilvers, marksandstrom, steve, mahtab, adam, miguel, jvoll
benjaminjkraft added a commit that referenced this pull request Sep 23, 2021
## Summary:
We typically name our types `OperationFieldTypeFieldType`, but if a
type's name matches the preceding field-name, we omit the type-name.
In #71 I changed the behavior such that we no longer do that in the case
where the type's name matches some suffix of the name-so-far that's
longer than just the leaf field-name.

This was semi-intentional; I assumed it didn't matter and would be more
predictable this way.  But it turns out that was a feature, both in the
sense that almost any change to the type-name-generator is breaking, and
in the sense that it made the names uglier.  Plus, now that we have
better conflict-detection (#94), the possibility that some tricksy
type-names could cause problems is no longer as much of an issue, so we
can be a little less careful here.  (Although I think this is no less
safe than before; the field-names are the important part.)  So in this
commit I revert the change.

Specifically, this comes up a lot at Khan where we do
```
mutation ForcePhantom {
    forcePhantom {     # type: ForcePhantom
        error { ... }  # type: ForcePhantomError
    }
}
```
Before #71, and again after this change, we'll generate
`ForcePhantomForcePhantomError` for `error`; before we'd generate
`ForcePhantomForcePhantomErrorForcePhantomError`.

Issue: #109

## Test plan:
make tesc


Author: benjaminjkraft

Reviewers: csilvers, aberkan, dnerdy, jvoll, mahtabsabet, MiguelCastillo, StevenACoffman

Required Reviewers: 

Approved By: csilvers

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

Pull Request URL: #110
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.

2 participants