-
Notifications
You must be signed in to change notification settings - Fork 114
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
Conversation
This is a fixed version of #66. |
857294f
to
28d7420
Compare
47014d5
to
d2c6b1b
Compare
(Plus, now, a rebase!) |
There was a problem hiding this 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.
// 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. |
There was a problem hiding this comment.
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.
generate/names.go
Outdated
// easy. | ||
type prefixList struct { | ||
last string // the list goes back-to-front, so this is the *last* prefix | ||
rest *prefixList |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Heh, nice.
There was a problem hiding this comment.
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!
generate/convert.go
Outdated
// 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) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
d2c6b1b
to
72f8e07
Compare
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
## 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
Summary:
When adding support for interfaces, I did not do the type-names as I
intended: they came out to be
MyFieldMyType
, notMyInterfaceMyFieldMyType
, which is inconsistent, but not strictlywrong. 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 reallywanted 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