-
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
Allow creating aliases for builtin types, using typename
.
#133
Conversation
@csilvers did you try using Lines 79 to 83 in 59b6df6
genqlient/docs/genqlient_directive.graphql Lines 152 to 171 in 59b6df6
|
The difference is that |
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.
Got it. Okay, this looks reasonable for that case. Could you add an entry to the change log?
Line 25 in 59b6df6
### New features: |
generate/convert.go
Outdated
@@ -479,6 +479,17 @@ func (g *generator) convertDefinition( | |||
return g.addType(goType, goType.GoName, pos) | |||
|
|||
case ast.Scalar: | |||
if builtinTypes[def.Name] != "" { | |||
// In this case, the user asked for a custom Go type-name | |||
// for a built-in type, e.g. `typename MyString string`. |
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.
// for a built-in type, e.g. `typename MyString string`. | |
// for a built-in type, e.g. `type MyString string`. |
generate/convert.go
Outdated
// In this case, the user asked for a custom Go type-name | ||
// for a built-in type, e.g. `typename MyString string`. | ||
goType := &goTypenameForBuiltinType{ | ||
GoTypename: name, |
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: I think GoTypeName
would be more consistent with the rest of the code.
// goTypenameForBuiltinType represents a builtin type that was | ||
// given a different name due to a `typename` directive. We | ||
// create a type like `type MyString string` for it. | ||
goTypenameForBuiltinType struct { |
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 might also name this goTypeForBuiltinType.
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 would have called it goNamedType
, but any of these work!)
I'm still not sure this is a good idea. It seems less than ideal to have two ways to override basic types, one using |
This lets you write code like: ``` query x { # @genqlient(typename: "MyString") someStringField } ``` and genqlient will do ``` typename MyString string type x struct { someStringField MyString } ``` This was not difficult to implement, though it required introducing a new identifier type. The main difficulty I had was weird test failures, that it turns out was due to the tests putting a bunch of fields on the same line, so that the genqlient directive on the previous line applied to all of them, accidentally. This became a problem when `typename` suddenly started being respected for builtin types! I fixed it by just spreading out the queries a bit. Fixes #130
66448b5
to
c2a8f15
Compare
Documentation added -- if it looks ok can you approve? |
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.
LGTM now!
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.
Neat idea! I worry a little bit this will make people want to add methods to the type, although it's not really genqlient that's stopping that (but rather the -- probably wise -- Khan convention that generated code is in its own package).
@@ -479,6 +479,17 @@ func (g *generator) convertDefinition( | |||
return g.addType(goType, goType.GoName, pos) | |||
|
|||
case ast.Scalar: | |||
if builtinTypes[def.Name] != "" { |
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.
In principle we should probably support this for types via global or local bind
as well, if you have an int and you want to bind it to int32
and also use a named type? The best way to do that is probably to do it in the caller convertType
, similar to how we do pointer
. (Or you could make it a TODO until anyone actually cares.)
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 went to write this TODO and realized I don't understand exactly the use case you're talking about. Can you give a concrete example? bind
works with types you define, so you can already do
type MyInt int32
....
# @genqlient(bind: "path/to.MyInt")
myfield
can't you?
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, I see, bind
wins over typename
, I was worried typename
would win over bind
which would be more confusing. So it's still the case that if you have in genqlient.yaml
:
bindings:
MyType:
type: int32
and now you do
# @genqlient(typename: "MyOtherType")
myField
the typename
is ignored. Really we should combine the two, i.e.:
type MyOtherType int32
Of course it's not really clear why you would want that, so we could also explicitly error. But it's honestly probably just as easy to make it do the obvious thing. (Initially I was worried we'd do type MyOtherType int
, which would be wrong.)
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.
Did you mean we should emit type MyOtherType int32
? I guess I'm not seeing what "the obvious thing" is.
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 yes that's what I meant! Edited.
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.
OK, I'll take a stab when I have a moment, or else add a TODO (also when I have a moment :-) )
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.
And, to be clear, that's the obvious thing: define a new alias (from typename
) to the bound type (from bind
).
// goTypenameForBuiltinType represents a builtin type that was | ||
// given a different name due to a `typename` directive. We | ||
// create a type like `type MyString string` for it. | ||
goTypenameForBuiltinType struct { |
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 would have called it goNamedType
, but any of these work!)
In #133, Craig added support for a new use of typename, where it applies to a scalar and means that genqlient should generate a named type, e.g. `# @genqlient(typename: "MyString")` on a node of type string will generate and use `type MyString string`. But this gets a bit confusing if you mix it with `bind`; should `typename: "MyString", bind: "int32"` generate `type MyString int32`, or should one override the other, or what? Of course in practice you're not likely to write that all in one place, but you could via a global binding, or a `for` directive, and in that case probably it was a mistake. In #138, we looked at making them work together correctly, but it added complexity and got even more confusing. So instead, here, we just ban it; we can always add it back if it proves useful. (Or, you can make the `typename` win over a global binding by locally unbinding it via `bind: "-"`.) This required changes in surprisingly many places; I already knew the directive-validation code was due for a refactor but that will happen some other day. The tests show that it works, in any case. Interestingly, this problem actually could have arisen for a struct binding already, before #133. But all the same reasons it's confusing seem to apply, so I just banned it there too. This is technically a breaking change although I doubt anyone will hit it. Test plan: make check
In #133, Craig added support for a new use of typename, where it applies to a scalar and means that genqlient should generate a named type, e.g. `# @genqlient(typename: "MyString")` on a node of type string will generate and use `type MyString string`. But this gets a bit confusing if you mix it with `bind`; should `typename: "MyString", bind: "int32"` generate `type MyString int32`, or should one override the other, or what? Of course in practice you're not likely to write that all in one place, but you could via a global binding, or a `for` directive, and in that case probably it was a mistake. In #138, we looked at making them work together correctly, but it added complexity and got even more confusing. So instead, here, we just ban it; we can always add it back if it proves useful. (Or, you can make the `typename` win over a global binding by locally unbinding it via `bind: "-"`.) This required changes in surprisingly many places; I already knew the directive-validation code was due for a refactor but that will happen some other day. The tests show that it works, in any case. Interestingly, this problem actually could have arisen for a struct binding already, before #133. But all the same reasons it's confusing seem to apply, so I just banned it there too. This is technically a breaking change although I doubt anyone will hit it. Test plan: make check
Summary:
This lets you write code like:
and genqlient will do
This was not difficult to implement, though it required introducing a
new identifier type. The main difficulty I had was weird test
failures, that it turns out was due to the tests putting a bunch of
fields on the same line, so that the genqlient directive on the
previous line applied to all of them, accidentally. This became a
problem when
typename
suddenly started being respected for builtintypes! I fixed it by just spreading out the queries a bit.
Fixes #130
Test plan:
make check