-
Notifications
You must be signed in to change notification settings - Fork 31
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
Add a robustier instanceof
alternative to allow cross-package compatibility.
#122
Conversation
The new check also fails to resolve stellar/stellar-cli#1285 |
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 small nit, but I don't think it should prevent merging. Always fun digging into the guts of JS.
16aa9da
to
64024ab
Compare
@@ -26,8 +26,13 @@ export class Enum extends XdrPrimitiveType { | |||
* @inheritDoc | |||
*/ | |||
static write(value, writer) { | |||
if (!(value instanceof this)) | |||
throw new XdrWriterError(`unknown ${value} is not a ${this.enumName}`); | |||
if (!this.isValid(value)) { |
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'm assuming that this
is the Class, but personally I find it less confusing if you use the Class directly to remove any ambiguities. Plus the caller could have used bind
to change this method's this
. Not that we need to worry, but it's possible.
if (!this.isValid(value)) { | |
if (!Enum.isValid(value)) { |
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.
Unfortunately, we have to use this
here for an oddly-specific reason: we need to make sure it deals with child classes and any modifications that are made after creation, e.g. like here. Just passing the class name doesn't cover this case.
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.
But shouldn't the classed be sealed? Why do we need to consider the case of subclasses?
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.
You can see in that linked line that a Union
itself is never actually created, it's always a locally-generated subclass of ChildUnion
that has a bunch of properties attached to it (most importantly unionName
) that gets returned. So we'd need to be able to [the specific ChildUnion class].isValid
if we wanted an accurate evaluation of "this"ness.
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've always had issues with this
in JS and in this
case being this
as the Class or (constructor?) is a bit odd to me. But if this is the standard JS way, pay my reservations to heed.
This looks solid to me. I'll echo @willemneal 's thought about using the Class rather than |
I've tried testing this with downstream systems and landed upon the wonderfully helpful "[object Object]'s struct name is SorobanAuthorizationEntry not SorobanAuthorizationEntry: " so clearly the extra check is still failing in key moments. I'm still in the process of debugging it and will merge this once I can pull that off. |
Yooooo I ran this through a project that uses |
Note: Lots of people tagged for review mostly as an FYI as there may or may not be far-reaching implications to this change.
What
Introduce a better check for cross-compatible
XdrCompositeType
subtypes (Union
,Struct
,Enum
, andArray
) based on a Discord discussion from @earrietadev which will check the prototype chain for the constructors and properties we need:(This isn't a proper TypeScript definition because js-xdr (a) doesn't use TS and (b) doesn't use interfaces, but imagine
XdrCompositeType
as being an interface and it makes more sense.)We also make the error messages way better.
Details
Because the classes are generated, their prototype constructor name doesn't match the actual class name more often than not. However, they do still inject the "true" name into their prototype. The three types we're trying to match on all do this differently:
struct
s will usestructName
, see struct.jsunion
s will useunionName
, see union.jsenum
s will useenumName
, see enum.jsIt's not perfect, but it's better than the
instanceof
check we have now.So we will check these fields as well as the existence of a
read()
andwrite()
method to make a best-effort guess that the given value matches the kind we expect.Why
Major versions of a package should probably be cross-compatible in the same dependency tree. For example,
js-xdr@3.1.1
should function interchangeably alongsidejs-xdr@3.0.0
if the additive features are not relevant.That is not the case today due to
instanceof
checks that become package-specific, whileisSerializableIsh
should rectify this.Known Limitations
There are only performance implications for people who are introducing this mixing behavior (besides the new overhead of a function call which I would consider trivial), so there are no performance regression risks.