7
High
// The Bug
How microsoft/TypeScript#25083 fixed enum keys in computed properties — why computed property names with non-identifier enum values were rejected by the type checker.
// Root Cause
In `src/compiler/checker.ts`, the `isLateBindableAST` function determines whether a declaration name can be "late bound" (resolved during type checking rather than during binding). The original code [2]:
```ts
function isLateBindableAST(node: DeclarationName) {
if (!isComputedPropertyName(node) && !isElementAccessExpression(node)) {
return false;
}
const expr = isComputedPropertyName(node) ? node.expression : node.argumentExpression;
return isEntityNameExpression(expr);
}
```
`isEntityNameExpression` only returns `true` for identifiers (`foo`) and dotted names (`A.B`). String literal expressions like `Keys['my-key']` are `ElementAccessExpression` (not entity name expressions), so they were rejected by the guard. But `Keys['my-key']` is a perfectly valid computed property name — the string literal `'my-key'` should be accepted as the property key [2].
---
```ts
function isLateBindableAST(node: DeclarationName) {
if (!isComputedPropertyName(node) && !isElementAccessExpression(node)) {
return false;
}
const expr = isComputedPropertyName(node) ? node.expression : node.argumentExpression;
return isEntityNameExpression(expr);
}
```
`isEntityNameExpression` only returns `true` for identifiers (`foo`) and dotted names (`A.B`). String literal expressions like `Keys['my-key']` are `ElementAccessExpression` (not entity name expressions), so they were rejected by the guard. But `Keys['my-key']` is a perfectly valid computed property name — the string literal `'my-key'` should be accepted as the property key [2].
---
// The Fix
Diff showing the exact changes made to fix the bug.
@@ -13785,13 +13785,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
&& isTypeUsableAsIndexSignature(isComputedPropertyName(node) ? checkComputedPropertyName(node) : checkExpressionCached((node as ElementAccessExpression).argumentExpression));
}
- function isLateBindableAST(node: DeclarationName) {
- if (!isComputedPropertyName(node) && !isElementAccessExpression(node)) {
- return false;
- }
- const expr = isComputedPropertyName(node) ? node.expression : node.argumentExpression;
- return isEntityNameExpression(expr);
- }
+ function isLateBindableAST(node: DeclarationName) {
+ if (!isComputedPropertyName(node) && !isElementAccessExpression(node)) {
+ return false;
+ }
+ const expr = isComputedPropertyName(node) ? node.expression : node.argumentExpression;
+ return isEntityNameExpression(expr) ||
+ isElementAccessExpression(expr) && isStringLiteral(expr.argumentExpression) && isEntityNameExpression(expr.expression);
+ }