-
Notifications
You must be signed in to change notification settings - Fork 20
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
Support BigInt values #37
Support BigInt values #37
Conversation
Regarding 4) I wish I wouldn't have had to rewrite everything, but as far as I could tell nothing was being tested... Huge thanks to @PatrickTasse for providing the test data. Another thing is that implementing the parsing/normalization revealed some TSP messages to be incorrectly defined? If you look at the changes made to the TypeScript interfaces I had to add an optional marker ( |
Lastly this work is based on #28 so there might be a few common changes. |
f2b5b51
to
1496d5e
Compare
c873d59
to
fad685c
Compare
fad685c
to
dd60fd8
Compare
src/protocol/tsp-client.test.ts
Outdated
const response = await client.fetchExperiment('not-relevant'); | ||
const experiment = response.getModel()!; | ||
|
||
expect(typeof experiment.end).toEqual('bigint'); |
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're only checking that it's a bigint, but we're not checking that the bigint was created without loss of precision. Or would it be possible to check the actual value against the expected value from the fixture?
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.
Can we keep this as a followup?
6549971
to
25f5c00
Compare
I just contributed the last remaining improvements to the typings for the I also rebased on master. |
f8e1389
to
f6f2d26
Compare
JSON doesn't constrain numeric values to be specific sizes. In most languages this isn't an issue thanks to proper support for a wide variety of number precision. Unfortunately JavaScript struggles with integer numbers bigger than `Number.MAX_SAFE_INTEGER`. This is due to JavaScript's implementation of integer values as `double`. For anything bigger than this max safe integer value JavaScript provides a `BigInt` type. The first issue with this behavior is that when parsing JSON, there is no way of telling which "number flavor" to expect, APIs just don't allow us to define so. Another issue is that the default JSON serializer/deserializer doesn't even know how to handle `BigInt`. This commit fixes those issues as followed: 1. Use a custom JSON parser `json-bigint` that can serialize and deserialze `BigInt` values. A quirk is that it will only deserialize a number into a `BigInt` if the serialized value ends up being bigger than JavaScript's max safe integer. This means there is ambiguity when deserialing data. 2. To address the ambiguity issue, I added a TypeScript mapped type `Deserialized<T>` that given a type `T` will replace all fields that are either `bigint` or `number`. Note that this is only static typing to teach TypeScript about this behavior. Then when receiving messages, one has to define `Normalizer` functions which will take this ambiguous type as input and return a "normalized" version that matches the original type `T`. See this as post-processing to make sure the received data is using the proper data types in the right places. 3. To help with the normalization I added a `createNormalizer` helper function that should statically figure out what fields need to be normalized for a given type and ensure that those are properly handled. 4. Rewrite all the tests to validate this logic using test data as coming out of the current Trace Server. Signed-off-by: Paul Marechal <[email protected]> Co-authored-by: Patrick Tasse <[email protected]>
f6f2d26
to
5eb3020
Compare
I rebased and squashed all the commits. I know this is a big change but ultimately it does a lot of good with the test suite. What bothers me is how I had to implement a mini serialization framework to handle the big int values in structs on a per-case basis. The root of the issue is that there's no type reflection in TypeScript: the interfaces are erased from the runtime. So if we want runtime mechanics based on interfaces, we have to go the way I've done: explicitly create functions for each type, as a separate entity. |
cc @bhufmann @PatrickTasse please tell me if anything bothers you with this PR. |
@PatrickTasse could you please continue with your review? |
/** | ||
* `true` if `T` must be normalized, `false` otherwise. | ||
*/ | ||
export type MustBeNormalized<T> = |
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.
My apologize if this is not right place, I was looking through the PR and wonder how does this type work ? I can't seem to understand the concept, especially with the 0
and 1
type being used.
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's a pain.
- I want to check if
Deserialized<T>
is assignable toT
. On paper, this should only fail when something likenumber
gets converted intobigint | number
inDeserialized<T>
. IfT
is "safe" thenDeserialized<T>
will be assignable toT
and everyone is happy. - Let's assume we use
any
in our typeT
: thenDeserialized<T>
will be compatible withT
, but I wanted this to fail because technically anumber
could slide into it and get deserialized asbigint | number
. - Only
any
andunknown
seem to be assignable tounknown
0
is not assignable to1
- To fix (2) I replace occurrences of
any
by0
on one side, and by1
on the other side so that fields typed asany
forcefully conflict with one another.
Doing this ensures that any
fields get marked as "must be normalized".
1/ I see that this PR also use the custom parser for serialization, is that necessary ? I don't see the ambiguity when serializing bigint (Correct me if I'm wrong!). The reason I'm asking is the custom parser might take perf hit compare to current implementation (default parser + regex) 2/ Provide that it's necessary, does it call EDIT: typo |
@haoadoresorange
|
I ran a quick benchmark on EDIT: I ran a couple more for different object size and the result is fairly consistent. There's a ~30% perf loss when using the custom |
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.
Thank you for this awesome contribution!
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.
Thanks a lot. Looks good to me. I tested within the theia-trace-extension and it works well. Thanks also to enhance the unit tests.
JSON doesn't constrain numeric values to be specific sizes. In most
languages this isn't an issue thanks to proper support for a wide
variety of number precision.
Unfortunately JavaScript struggles with integer numbers bigger than
Number.MAX_SAFE_INTEGER
. This is due to JavaScript's implementation ofinteger values as
double
. For anything bigger than this max safeinteger value JavaScript provides a
BigInt
type.The first issue with this behavior is that when parsing JSON, there is
no way of telling which "number flavor" to expect, APIs just don't allow
us to define so. Another issue is that the default JSON
serializer/deserializer doesn't even know how to handle
BigInt
.This commit fixes those issues as followed:
json-bigint
that can serialize anddeserialze
BigInt
values. A quirk is that it will only deserializea number into a
BigInt
if the serialized value ends up being biggerthan JavaScript's max safe integer. This means there is ambiguity
when deserialing data.
Deserialized<T>
that given a typeT
will replace all fieldsthat are either
bigint
ornumber
. Note that this is only statictyping to teach TypeScript about this behavior. Then when receiving
messages, one has to define
Normalizer
functions which will takethis ambiguous type as input and return a "normalized" version that
matches the original type
T
. See this as post-processing to makesure the received data is using the proper data types in the right
places.
createNormalizer
helperfunction that should statically figure out what fields need to be
normalized for a given type and ensure that those are properly
handled.
coming out of the current Trace Server.