Closed
Description
TypeScript Version: 3.8.3
Search Terms: computed object property name destructuring private ECMAScript
Code
class PropertyAccessor {
readonly #propertyName: string;
constructor(propertyName: string) {
this.#propertyName = propertyName;
}
getPropertyValue(obj: Record<string, string | undefined>): string | undefined {
const { [this.#propertyName]: value } = obj;
return value;
}
}
const accessor = new PropertyAccessor('name');
console.log(
accessor.getPropertyValue({ name: 'Adam' })
);
Expected behavior:
'Adam' is logged at runtime.
Actual behavior:
Runtime error occurs:
Private field '#propertyName' must be declared in an enclosing class
Additional information:
I just started using TypeScript and I ran into this problem while converting one of my javascript projects, the behavior was confusing to me. It seems like the code is transpiled incorrectly, as it works when not using destructuring syntax:
const { [this.#propertyName]: value } = obj;
// compiles to
const { [(_propertyName = new WeakMap(), this.#propertyName)]: value } = obj;
const value = obj[this.#propertyName];
// compiles to
const value = obj[__classPrivateFieldGet(this, _propertyName)];
The error does not occur when using TypeScript's private
field instead of ECMAScript's.
The error does not occur when targeting ESNext.
Related Issues: Maybe #26572 #26355? Doesn't seem like the same problem but similar keywords.
Output
"use strict";
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, privateMap, value) {
if (!privateMap.has(receiver)) {
throw new TypeError("attempted to set private field on non-instance");
}
privateMap.set(receiver, value);
return value;
};
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, privateMap) {
if (!privateMap.has(receiver)) {
throw new TypeError("attempted to get private field on non-instance");
}
return privateMap.get(receiver);
};
var _propertyName;
class PropertyAccessor {
constructor(propertyName) {
_propertyName.set(this, void 0);
__classPrivateFieldSet(this, _propertyName, propertyName);
}
getPropertyValue(obj) {
const { [(_propertyName = new WeakMap(), this.#propertyName)]: value } = obj;
return value;
}
}
const accessor = new PropertyAccessor('name');
console.log(accessor.getPropertyValue({ name: 'Adam' }));
Compiler Options
{
"compilerOptions": {
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictPropertyInitialization": true,
"strictBindCallApply": true,
"noImplicitThis": true,
"noImplicitReturns": true,
"alwaysStrict": true,
"esModuleInterop": true,
"declaration": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"moduleResolution": 2,
"target": "ES2017",
"jsx": "React",
"module": "ESNext"
}
}
Playground Link: Provided