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
Continuation of proposal-class-fields/issues/106 #1
Comments
That's a spec issue. My recommendation is for
Who said it should? I only used WeakMap as an example of a highly common use case. Sorry if that didn't come through in my wording. Case 3 above includes but is by no means limited to WeakMap, Map, WeakSet, and Set.
That's what we need to figure out. I don't think it should apply to non-key cases as this implies calling
Like everything else with Proxy, it's a "call me if I exist" function. Since all Maps and Sets would use the Proxy for its identity, we could allow the
The biggest weakness to your proposal is that it requires an object to be specifically constructed to be Proxy safe. The current ES standard leans in the opposite direction, in that you must construct your class in a Proxy-unsafe way for Proxy to have difficulty with it. This is a problem due to all of the existing code. None of that code would be viable using your approach. Further, new developments would take longer trying to take into account potential uses of Proxy by 3rd party developers. The situation becomes even more complex when a developer uses 2 different code sets together. If one is a library using hidden data and the other a monitoring library using Proxy... I'm sure you can see the difficulty. The approach I'm offering allows developers to remain ignorant of their user's use of Proxy. The responsibility of ensuring a Proxy doesn't break thus falls to the Proxy developer/user. For this kind of problem, a 100% solution isn't possible, but I believe I'm closing in on an 85%. |
Sorry, my wording was probably wrong as well. What I meant to convey is that there are a lot of hidden data implementations out there, some predating Map and other containers that use object as keys. While putting the limit at "anything in the language that uses the object as a container key" would probably solve most modern use-cases, I'm a little worried this creates a dichotomy in the language where in some special cases we resolve the proxy's target for identity, and in some other we don't.
I guess I wasn't clear there either.
I don't think I follow.
I believe mine mostly allows that as well. Only if they do something that breaks regular proxies do they need to add an explicit hook. The hook however works for all kind of custom hidden data implementations, or potentially other use-cases we haven't thought of yet. |
That's a reasonable concern, and why I seek to keep the rule as consistent as possible. Since only intrinsic objects can make use of an object as a key, it should be clear and reasonable for this rule to only affect intrinsic objects with that capability (Maps and Sets).
I presume that in your example, it is the Proxy object's identity that would be stored in the WeakMap. The following might be the rule of key evaluation for intrinsic objects under this idea:
The point is, always try for an exact match first. If that fails, then, if the key is a Proxy and resolve doesn't return key, then return based on the resolved key. The fact that the 4th parameter can hold "identity" helps in resolving cases where you may want a Proxy to key differently from its target while still being able to deal with hidden data implementations. |
That's an interesting idea for Should it always use the proxy object's identity, or should it go through the That leaves the question of I just don't see how you can cleanly mutate a container with a proxy object as key. At which point checking if proxy objects are themselves in containers becomes pointless. |
It's possible. Just takes a bit of thought.
Has would follow the same logic as For On the other hand, |
For
Basically, if multiple keys reference the data, delete all keys to delete the data. Otherwise, the remaining keys will still reference the data and the deleted key is free to be used for different data. The same key-aliasing rule can work for Sets as well as long as the |
I'm not really sold on the original idea here...do proxies really need to have the power to arbitrarily change the target to any object? If sticking with the general approach of a I did see @ljharb's comment about multiple targets already being one of the intended use cases for proxies, which is fine for public slots (I'm assuming "public slots" is a valid term—perhaps I should just say public properties), but when internal slots get involved, I suspect there will be a host of potential encapsulation issues and other complexities that would otherwise be avoided. |
I'm not sure I follow the alias idea and how it'd work. I've created a repository to illustrate the following: https://github.com/mhofman/proposal-proxy-resolve-playground Let's assume a class has hidden data through multiple Now let's assume another library that monitors accesses to the above class' instances. It creates a proxy object for each of its users so that it can link each access to the relevant user. Multiple proxy objects may then share the same target. It uses a I've tried to implement your As you can see, I'm not sure how to implement the |
I thought about that a couple of days after my suggested changes in 106. I really don't like the idea of the resolve function being able to return any arbitrary object. At the same time, I have to take into account the intent @ljharb mentioned. What I need to know is whether or not that intent extends to internal slots. If I go by the current functionality, I'd wager that the intent should not be extended that far, and that it's ok to reduce the return value of resolve to a boolean. That would make me a lot happier. If, however, the intent must be extended to internal slots, then we don't have much wiggle room. |
That you mentioned Babel makes this easier. Babel's implementation of private fields is a good example to draw from. If memory serves, all data stored in a private field WeakMap is wrapped in an object akin to a property descriptor object. If we apply the same concept to this idea, all Maps would have a "container" they create and own for each value added to the Map. As such, I would tend to implement Map something like this: //The map needs to know how to unwrap a Proxy, hence `Proxy.unwrap`.
class AliasingMap extends Map {
has(key) {
return super.has(key) || super.has(UProxy.unwrap(key));
}
get(key) {
let retval;
if (super.has(key) {
retval = super.get(key);
}
else {
let pkey = UProxy.unwrap(key);
if (super.has(pkey)) {
retval = super.get(pkey);
super.set(key, retval);
}
}
return retval.value;
}
set(key, value) {
let container = super.get(key) || {};
container.value = value;
super.set(key, value};
}
/* No need to redefine delete. */
} |
BTW, here's the corresponding Proxy implementation: let UProxy = (function() {
let pvt = new WeakMap();
return class UProxy {
constructor(target, handler) {
let rval = new Proxy(target, handler);
pvt.set(rval, target);
return rval;
}
static unwrap(obj) {
console.log("Unwrapping Proxy object...");
return pvt.get(obj);
}
}
})(); |
Babel does indeed save a property descriptor in recent versions. I mentioned more as an example. Regarding your code example, I'm even more confused as I thought you were advocating for a Feel free to create a PR on my playground repo if you can make it work. |
Sorry about that. Been typing here while distracted by work. 😜 The |
This is the Proxy I mean to give you. This is the one that will have resolve support. I'll work on a PR for your repo later tonight. let RProxy = (function() {
const map = new WeakMap;
return class RProxy {
constructor(target, handler) {
let retval = new Proxy(target, new Proxy(handler, {
get(t, key, r) {
return function(...args) {
let [tgt, prop] = args;
let receiver = retval;
if ((prop != "resolve") && ("resolve" in tgt)) {
let isPvt = !(["string", "symbol"].includes(typeof(prop)));
receiver = tgt.resolve(target, receiver, isPvt, key);
assert(receiver && (typeof(receiver) == "object"));
}
if (key == "get") {
args[2] = receiver;
}
else if (key == "set") {
args[3] = receiver;
}
return (handler[key] || Reflect[key])(...args);
}
}
}));
map.set(retval, target);
return retval;
}
}
})(); |
Hello! What's the TLDR with private fields and Proxy? EDIT: is this a better question for the new forums? |
@trusktr that's a good place to ask; but the answer is "it's the same as internal slots; they don't tunnel through proxies" (except for Array.isArray, and |
The proxy issue is also mentioned in the private fields syntax FAQ:
class K { #x; get x() { return this.#x; } }
let k = new Proxy(new K, { });
k.x // TypeError |
Here, instead of simply bantering about what we need, I'm working towards a proposal to submit, but I need more information from the community. The current goal of this repo is to provide a solution to the issues of using Proxy with respect to hidden data implementations, both within the engine and in user code.
The current discussion surrounds the idea offered by @isiahmeadows and modified by me that adds a
resolve(target, receiver, isPrivate, method)
method to the Proxy handler.For more history on this conversation...
@mhofman wrote:
The text was updated successfully, but these errors were encountered: