-
Notifications
You must be signed in to change notification settings - Fork 424
Conversation
I've tried to scratch this itch a number of times already, but always backed off because the magnitude of the change seemed risky and the benefits are ultimately not entirely clear. The diff is pretty hard to review, so I would ask that it be purely "mechanical" with no changes of functionality or bug fixes. |
@@ -948,7 +955,8 @@ export class GlobalEnvironmentRecord extends EnvironmentRecord { | |||
|
|||
// 5. If existingProp is undefined, return ? IsExtensible(globalObject). | |||
if (!existingProp) return IsExtensible(realm, globalObject); | |||
Properties.ThrowIfMightHaveBeenDeleted(existingProp.value); | |||
Properties.ThrowIfMightHaveBeenDeleted(existingProp); | |||
existingProp = existingProp.throwIfNotConcrete(globalObject.$Realm); |
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 suggests that the result returned from throwIfNotConcrete can be different from the old value of existingProp. That makes the code harder to read in my opinion.
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 already a pattern we have for Value. I think we should stick to one pattern or switch both at once.
It is plausible that we could make Flow refine these for us similarly to how I did it with IsDataDescriptor. But for now I stuck with the pattern already in place for Values.
"These are all typed as arbitrary an inexact object where all the fields are optional. That means that essentially any object with any typo can be assigned to a descriptor." As a first step, the existing type can just be made exact, no? |
The inexact thing doesn't give us much. We've already fixed a few of those. The problem is that we have hundreds of places that just doesn't deal with joined descriptors and always does the wrong thing instead of fataling. Which means that I can't rely on that mechanism to work. That's preventing me from doing more with this like in #2461. Most things in here are mostly mechanical. I've split out things that are not just mechanical in separate commits and I'll go through and highlight them. In the interpreter I'm trying to just throw fatals as much as possible instead of trying to fix the callsites and then fix them in follow ups. I've tried to support them as much as possible in the serializer to avoid having to error. The relevant serializer commit: 39817cb cc @NTillmann |
ae85558
to
6cd5dbb
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.
sebmarkbage has imported this pull request. If you are a Facebook employee, you can view this diff on Phabricator.
Ok this is now ready to review. I've split the commits up by whether they're adding a primary mechanism that is worth reviewing or if they're just mechanical updates to the code. Primary MechanismThese are worth reviewing: Add normal type for descriptors 064e271 Mechanical WorkThese are most just mechanical and are not worth reviewing in detail. These changes a bunch of callsites to use a slightly different API. Pass Descriptor to ThrowIfMightHaveBeenDeleted instead of Value 15076d0 These add fatal errors where we don't yet handle joined descriptors, or invariants where we already know there won't be any. Throw if not concrete 9cd65f3 RegressionI had to disable one test because we don't actually handle it properly. It just happened to work in this particular edge case accidentally. Expect to throw introspection error on conditional descriptor 6cd5dbb |
src/descriptors.js
Outdated
} | ||
|
||
// Only used if the result of a join of two descriptors is not a data descriptor with identical attribute values. | ||
// When present, any update to the property must produce effects that are the join of updating both desriptors, |
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.
sp: desriptors
src/descriptors.js
Outdated
// using joinCondition as the condition of the join. | ||
export class AbstractJoinedDescriptor extends Descriptor { | ||
joinCondition: AbstractValue; | ||
descriptor1: void | Descriptor; |
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.
Some more comments on the meaning of descriptor1
or descriptor2
being absent would be enlightening. (Not asking for actual code changes here, just comments describing intention.)
Looking around at where AbstractJoinedDescriptor
is being instantiated it seems that (eventually) at least one of the two descriptors must be defined, and the absent descriptor is somewhat equivalent to PropertyDescriptor
where value
is EmptyValue
.
src/descriptors.js
Outdated
if (this.descriptor2 && this.descriptor2.mightHaveBeenDeleted()) { | ||
return true; | ||
} | ||
return false; |
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 a way, the absence of either descriptor indicates that the thing doesn't exists that thus mightHaveBeenDeleted
.
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. Nice refactoring!
They now have to be wrapped in a nominal type.
When these functions are used, they're assumed to be working with concrete property descriptors. We must throw if they're not concrete. To let Flow take advantage of the type assertions, we also do an instanceof check using Predicate Functions.
For a joined descriptor we need to check to see that neither condition might be empty.
The React code isn't always executing to throwing fatals doesn't seem prudent. It effectively assumes that there are no joined descriptors so I'm instead just adding invariants and then we'll have to work those off.
Will need to be fixed in a follow up.
Might consider moving these to instance methods.
There seems to be a slight perf regression which causes one of the RegExp tests to take a bit longer.
852f580
to
b4a46a1
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.
sebmarkbage is landing this pull request. If you are a Facebook employee, you can view this diff on Phabricator.
Release notes: Refactor of joined descriptors. Expect a slight performance regression. Will fatal in more cases that we would previously silently possibly generate the wrong code.
Currently we have three types of property descriptors. 1) Descriptors used by internal slots. 2) Normal concrete descriptors. 3) Abstract joined descriptors.
These are all typed as arbitrary an inexact object where all the fields are optional. That means that essentially any object with any typo can be assigned to a descriptor.
We are also not forced to deal with the joined descriptor case. Almost everywhere we assume that joined descriptors are really just a generic descriptor object which doesn't have any attributes on it.
There are so many small bugs related to this so I figured it's time to start dealing with it.
This PR turns this inexact object into nominally typed PropertyDescriptor, AbstractJoinedDescriptor and InternalSlotDescriptor. Essentially the same model as values.
Thanks to this Flow forces me to ensure that I've covered all of these cases. I've dealt with it in the cases I figured I could figure it out and where it was necessary such as the serializer/join/widen. In all other cases where we don't know, I ensure that we throw a fatal error instead of assuming that a joined descriptor is an empty descriptor.