Skip to content

breaking: avoid mutations to underlying proxied object with $state #12847

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

Closed
wants to merge 15 commits into from

Conversation

trueadm
Copy link
Contributor

@trueadm trueadm commented Aug 14, 2024

This PR does two things:

  • removes $state.is
  • ensures mutations to $state proxies are no longer applied to the object provided to state

Copy link

changeset-bot bot commented Aug 14, 2024

🦋 Changeset detected

Latest commit: e52d2f4

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
svelte Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@Rich-Harris
Copy link
Member

Added some unit tests, so that we can have some proper guarantees about the thornier edge cases. One of the tests is for Object.defineProperty(proxy, ...), and it currently fails — I won't be able to work on it any more tonight.

I suspect we might have to store descriptors for every property on the ProxyMetadata object, because falling back to the original descriptor is slightly at odds with the goal of the PR. Might be costly though, and if someone defines a property on the original object after it's been proxied then they probably deserve whatever they get, so an alternative could be to only store descriptors when someone invokes the defineProperty trap

@trueadm
Copy link
Contributor Author

trueadm commented Aug 15, 2024

Added some unit tests, so that we can have some proper guarantees about the thornier edge cases. One of the tests is for Object.defineProperty(proxy, ...), and it currently fails — I won't be able to work on it any more tonight.

I suspect we might have to store descriptors for every property on the ProxyMetadata object, because falling back to the original descriptor is slightly at odds with the goal of the PR. Might be costly though, and if someone defines a property on the original object after it's been proxied then they probably deserve whatever they get, so an alternative could be to only store descriptors when someone invokes the defineProperty trap

I was going to say that we could also prevent setting a descriptor that isn't enumerable or an accessor etc then we throw and error and say it's not permitted?

Alternatively, we put descriptors on the metadata sources. Then when retrieving value and setting value we use that descriptor.

Update: I went for the first option, which we can always go back on but the performance of doing two was quite substantially worse than I originally expected. Every time we change the value, we have to create a new descriptor object for the signal, or we have two Maps of sources for the value and descriptor, but in either case I'm seeing 30-50% slower performance when using a large array and pushing in lots of values and then reading them.

@trueadm trueadm marked this pull request as ready for review August 15, 2024 12:30
Copy link
Member

@Rich-Harris Rich-Harris left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

made one small suggestion but LGTM

@Rich-Harris
Copy link
Member

One final thought before we merge this: whither $state.is? Is this too weird?

<script>
  let object = { count: 0 };
  let proxy = $state(object);

  proxy.count += 1;
</script>

<p>{object.count}</p>
<p>{proxy.count}</p>
<p>{$state.is(proxy, object)}</p>

I think it might be. In a world with $state.raw do we still need $state.is? Should we get rid of it?

@trueadm
Copy link
Contributor Author

trueadm commented Aug 15, 2024

One final thought before we merge this: whither $state.is? Is this too weird?

<script>
  let object = { count: 0 };
  let proxy = $state(object);

  proxy.count += 1;
</script>

<p>{object.count}</p>
<p>{proxy.count}</p>
<p>{$state.is(proxy, object)}</p>

I think it might be. In a world with $state.raw do we still need $state.is? Should we get rid of it?

Yep let’s get rid of it

@Rich-Harris
Copy link
Member

We should probably still keep the dev time checks for === etc so that we can guide people towards a solution

@trueadm
Copy link
Contributor Author

trueadm commented Aug 15, 2024

We should probably still keep the dev time checks for === etc so that we can guide people towards a solution

Should we do this separately from this PR?

@Rich-Harris
Copy link
Member

I think we probably need to couple the two things, because this PR arguably breaks $state.is

@trueadm
Copy link
Contributor Author

trueadm commented Aug 15, 2024

I think we probably need to couple the two things, because this PR arguably breaks $state.is

Removed $state.is in the latest commit.

@Rich-Harris
Copy link
Member

We should have a compiler error that says that $state.is was removed, rather than it just erroring as a generic invalid rune.

A couple of other points that just popped up: given this change, we should probably get rid of the 're-use proxy if it exists' logic, and a) stop adding STATE_SYMBOL directly to the original object, and b) remove the is_frozen check (which depends on a), because you can't add the symbol to a frozen object)

@Rich-Harris
Copy link
Member

Opened #12867 and #12868 against this branch to try and implement some of the above

Copy link
Member

@Rich-Harris Rich-Harris left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rescinding approval so we don't accidentally merge until we've resolved the outstanding questions

@Rich-Harris
Copy link
Member

since #12912 moves a bunch of stuff around, I've ported these changes over to #12916

@trueadm trueadm closed this Aug 20, 2024
@trueadm trueadm deleted the remove-proxy-mutations branch November 6, 2024 15:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy