-
Notifications
You must be signed in to change notification settings - Fork 106
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
static accessor doesn't work for subclasses #468
Comments
Could that cause confusion if A and B are using the same backing data? The expectation might be that it match usage of having a public
Maybe an error is better than going down a different path here? I believe similar errors also already exist in the language. Maybe not the best example but one off the top of my head:
The If |
The |
It would be strange that a modifying a subclass property changes the parent class property too. With regular (non-static) properties, people don't write accessors that modify properties on the class prototype object, they write accessors that modify class Foo {
get foo() {
return Foo.prototype._foo
}
set foo(v) {
Foo.prototype._foo = v
}
} and that is strange: people don't expect modifying one instance modifies all instances. It is weird. Throwing an error is also weird and strange, but at least prevents an issue. It is so weird. If private fields had simple (actual) |
Kind've, but there's already the divergence that for instances Given these existing differences, I don't think |
Not all static members are own properties. My previous example is demonstrating that B inherits its get x() from A. A.hasOwnProperty('x') // true
B.hasOwnProperty('x') // false @trusktr's example is accurate for how In fact the public member version using a public A.x = 1
// B.x = 2
console.log(A.x) // 1
console.log(B.x) // 1 The difference with private members is that it's an error if the private member doesn't already exist as an own member, which it wouldn't for B because there's no super constructor-like step to install A's private members on B as there is for class instances. |
I was meaning that within a single definition of a class: class A {
#x; // A instance
get x(); // A.prototype
static #x; // A
static get x(); // A
} i.e. For So having different behaviour for
Yes, this is how private fields already work for Just as an example use case for static accessors, consider something like: const DEFAULT_WORKER_POOL_SIZE = navigator.hardwareConcurrency;
class WorkerPool {
#defaultWorkerPoolSize = DEFAULT_WORKER_POOL_SIZE;
static get defaultPoolSize(): number {
return WorkerPool.#defaultWorkerPoolSize;
}
static set defaultPoolSize(newSize: number): number {
if (newSize <= 0) {
throw new RangeError(`poolSize must be at least one`);
}
WorkerPool.#defaultPoolSize = newSize;
}
// instance stuff
readonly #pool = new Set<Worker>();
constructor(script: string | URL, {
poolSize = WorkerPool.#defaultPoolSize,
}: { poolSize?: number }={}) {
for (let i = 0; i < WorkerPool.#defaultPoolSize) {
this.#pool.add(new Worker(script));
}
}
runTask(taskName: string): Promise<void> {
// pick available worker somehow, etc and send it the taskName to perform
}
}
// Configure all worker pools to use only half of cpus by default
WorkerPool.defaultPoolSize = Math.ceil(navigator.hardwareConcurrency / 2); In general the idea here is that yes the property would indeed apply to subclasses as well. If subclasses really wanted different behaviour, well that's what overriding The addition of class WorkerPool {
@ensurePositiveNumber // Wrapper for if set value <= 0
accessor defaultPoolSize;
} |
Although I suppose there is the case of wanting to be able to i.e.: class WorkerPool {
static #defaultPoolSize = 12;
static get defaultPoolSize() {
return this.#defaultPoolSize;
}
static set defaultPoolSize(newSize: number) {
this.#defaultPoolSize = newSize;
}
}
class SubWorkerPool extends WorkerPool {}
// Still should return 12 as we've done nothing it
SubWorkerPool.defaultPoolSize;
// Would need to install new field on SubWorkerPool to avoid inheriting
SubWorkerPool.defaultPoolSize = 99; This wouldn't work with simple weakmap semantics because if |
This may be true if someone knows about JS specifics, but perhaps not what people from arbitrary languages with less background (also considering that What if the semantics were like the following? const defaultPoolSize = new WeakMap
class WorkerPool {
static get defaultPoolSize() {
return defaultPoolSize.get(this);
}
static set defaultPoolSize(newSize: number) {
defaultPoolSize.set(this, newSize);
}
}
defaultPoolSize.set(WorkerPool, 12)
class SubWorkerPool extends WorkerPool {}
defaultPoolSize.set(SubWorkerPool, 12) // initialized during extension, but only accessible in WorkerPool scope, similar to a super() call for instances.
// Still should return 12 as we've done nothing it
SubWorkerPool.defaultPoolSize; // returns 12
// field is already installed, is set to new value
SubWorkerPool.defaultPoolSize = 99; I guess the engine would carry around a But the private fields ship sailed already. Can a rescue mission be sent out for it? |
Could we consider some way to allow subclasses "inherit" special static initializers? Is that the desired semantic in static accessor use cases? |
I guess this may work as expect: class A {
static accessor x = 42
}
class B extends A {} -> function _Private() {
const store = new WeakMap()
return {
init(o, v) {
if (store.has(o)) throw new TypeError()
store.set(o, v)
},
get(o) {
if (!store.has(o)) throw new TypeError()
return store.get(o)
},
set(o, v) {
if (!store.has(o)) throw new TypeError()
store.set(o, v)
},
}
}
const _static = _Private()
const _x = _Private()
class A {
static #init() { _x.init(this, 42) }
static get x() { return _x.get(this) }
static set x(v) { _x.set(this, v) }
static {
_static.init(this, this.#init)
this.#init()
}
}
class B extends A {
static #init() { _static.get(A).call(this) }
static {
_static.init(this, this.#init)
this.#init()
}
} |
Yes, although it it a fairly new powerful capability that doesn't currently exist, specifically it would allow interception of subclasses during their creation i.e.: class A {
accessor x = function() {
if (this !== A) {
// "this" is a subclass and we can manipulate it
}
}.call(this);
}
// Currently at most A.[[Get]]("prototype") is observable from this statement
// by A
class B extends A {} At present, the best you can do is guess that a subclass is being created because It seems like a very powerful capability to attach to something like class A {
// some way to declare a field should be installed on subclasses as well
static inherited #field;
// technically "static constructor" is already valid syntax, so some other
// syntax would need to be chosen
static constructor() {
console.log(this); // whatever subclass is being created
this.#field = "someValue";
}
} |
Yeah, I think we eventually need static constructor, and it seems the expected static accessor semantic rely on it. About the capability, base class already could collect subclasses via |
How would the code look like? |
I feel like I lean toward the original proposed solution here: class A {
static #x = 42
static get x() { return A.#x }
static set x(v) { A.#x = v }
} It would definitely be confusing that static accessors could not be inherited at all, and I think that folks who use static private fields would probably use this pattern pretty often when they realized inheritance doesn't work. |
Is this a planned change? If so it's probably worth bringing it up in the March agenda as implementations are starting to happen. |
It is, thank you for bringing it up again. I'm going to be bringing this and a number of other normative changes to the upcoming plenary for consensus. |
In further examination of the spec and thinking this through, I actually believe this is already the way the spec text works. When we create the
This becomes the Going to close this issue for now as fixed, if anyone disagrees and the spec is incorrect, we do have consensus that this should be the behavior, so we will update it later. |
It's not a new problem, actually it's the unfortunate consequence of class static private fields design, but maybe
static accessor
modifiers could at least mitigate the issue by converting static accessor to something like:The text was updated successfully, but these errors were encountered: