-
Notifications
You must be signed in to change notification settings - Fork 8
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: java type handling #2354
fix: java type handling #2354
Conversation
} else if (type.hasString()) { | ||
return ClassName.get(String.class); | ||
} else if (type.hasOptional()) { | ||
return toJavaTypeName(type.getOptional().getType(), typeAliasMap); | ||
// Always box for optional, as normal primities can't be null | ||
return toJavaTypeName(type.getOptional().getType(), typeAliasMap, true); |
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.
if I'm not mistaken it appears that we are unwrapping the optional type and boxing it. What do you think about making use of java.util.Optional[T]
instead in this instance (in the interest of idiomatic optionality semantic preservation)?
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.
A boxed type maps exactly to the underlying FTL type (true, false, undefined). Optional adds an extra state (true, false, empty, the Optional field is null).
Optional has never really hugely caught on, and it has a lot of drawbacks (Intellij actually warns against using it for parameter or field types). It was mostly used to indicate that a method may return a null value. In practice it solves very little, and you end up with an extra possible state that the Optional itself is null. It also does not play particularly well with both Kotlin and JSON mapping libraries, both of which are very relevant here.
To make optional types work the way you would expect in Kotlin you need to use the @NotNull annotation, rather than Optional, which is why I am going for this approach.
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.
Will this get represented in Kotlin as a native optional?
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.
@@ -536,9 +543,33 @@ private static Class<?> loadClass(org.jboss.jandex.Type param) throws ClassNotFo | |||
default: | |||
throw new RuntimeException("Unknown primitive type " + param.asPrimitiveType().primitive()); | |||
} | |||
} else { | |||
throw new RuntimeException("Unknown type " + param.kind()); | |||
} else if (param.kind() == org.jboss.jandex.Type.Kind.ARRAY) { |
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: FTLCodeGenerator.java
and this file both feature type projection logic. It might be useful to consolidate this.
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.
One maps from a Jandex bytecode representation of a type to a FTL schema, the other maps the FTL schema representation of a type to a Java language string representing the type. They are not dealing with the same types of objects, and also have subtle differences in the type that is supposed to be generated. e.g. a FTL boolean type should generally just be mapped to a boolean, except if you are producing a type parameter because you can't use primitives in type params.
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.
At some point soon I will also add Kotlin to that integration test, so you can see how the calls / schemas map in all 3 languages.
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.
The motivation for that comment has to do with the similarity of the type projection task being accomplished. There is probably some desire for this to work consistently across the these type systems and it will be easier to spot unintended drift if they are co-located. I marked this as a nit though because I suspect there are other implementation concerns that might be negatively impacted by decoupling this type projection logic
} | ||
|
||
@Override | ||
public void onCompleted() { | ||
verbService.getModuleContext(ModuleContextRequest.newBuilder().setModule(moduleName).build(), moduleObserver); | ||
onError(new RuntimeException("connection closed")); |
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: an explanation for why connection closure is the only reason for completion might be comment worthy
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 guess it is really stream closed rather than connection closed.
90004dc
to
b947adb
Compare
@@ -90,6 +90,17 @@ func Chain(actions ...Action) Action { | |||
} | |||
} | |||
|
|||
// SubTests runs a list of individual actions as separate tests | |||
func SubTests(tests ...SubTest) Action { | |||
return func(t testing.TB, ic TestContext) { |
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.
@alecthomas are you ok with this approach for running multiple assertions against a single instance?
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.
Chain()
already does functionally the same thing, though this uses named subtests so is slightly different. Seems reasonable.
e4ee054
to
43bf219
Compare
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.
Overall LGTM but let's use the established approach for multi-language tests that we already have. Will send a PR to support it.
@@ -90,6 +90,17 @@ func Chain(actions ...Action) Action { | |||
} | |||
} | |||
|
|||
// SubTests runs a list of individual actions as separate tests | |||
func SubTests(tests ...SubTest) Action { | |||
return func(t testing.TB, ic TestContext) { |
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.
Chain()
already does functionally the same thing, though this uses named subtests so is slightly different. Seems reasonable.
return PairedTest(prefex+"-"+verb, VerbTest[T](verb, value)) | ||
} | ||
|
||
type TestObject 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.
We already have an approach in place for doing multi-language tests, I'd really prefer we don't recreate that in a bespoke manner for the Java runtime.
To make that work with the existing system we'll need some mechanism to tell the integration testing system which languages to run the same test against. I'll send a PR to support this.
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.
Won't chain fail at the first failure? As I had a large number of tests it was really useful to just see the overall state in the IDE.
I didn't realize there was already this sort of multi language testing, I should have checked first.
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.
Also TestObject
is just an arbitrary object with lots of different field types to test how they serialize, it is not really part of the testing system.
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.
Yeah I understand that TestObject
is common to both languages, this kind of "conformance testing" to ensure that the behaviour across multiple languages is identical is exactly what the integration framework was designed for. We actually used to have 1:1 integration test coverage across kotlin and go, but sadly we had to delete them when we paused support :(
43bf219
to
9fc0b37
Compare
I've extended the integration harness to support multiple languages here. Something like the following filesystem layout:
Then run the single test across both languages with: in.Run(t,
in.WithLanguages("go", "java"),
in.CopyModule("hello"),
...
) |
Improves the tests and fixes problems with the handling of some types, there is still a significant amount of work to go.
9fc0b37
to
adfa922
Compare
c99f200
to
cd1ee3f
Compare
awesome, lgtm! should we add a ticket for handling enums/designing how sum types will be represented in Java as well? |
97102ef
to
c4cf07e
Compare
integration/harness.go
Outdated
func WithJava() Option { | ||
// BuildJava is a Run* option that ensures the Java runtime is built. | ||
// If the test languages contain java this is not necessary, as it is implied | ||
func BuildJava() Option { |
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.
Let's keep all options prefixed with "With" so they're easy to discover.
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 just found WithJava kinda confusing as it did not actually add the java language. I will change it to WithJavaBuild
fcf83c2
to
5221b9c
Compare
Improves the tests and fixes problems with the handling of some types, there is still work to go (as seen by the commented out tests), but this significantly improves the situation.