Skip to content
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

[Experiment] fix(40617): handle uninitialized class member with computed key #45974

Merged
merged 1 commit into from
Mar 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/compiler/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -889,7 +889,7 @@ namespace ts {
return isDottedName(expr)
|| (isPropertyAccessExpression(expr) || isNonNullExpression(expr) || isParenthesizedExpression(expr)) && isNarrowableReference(expr.expression)
|| isBinaryExpression(expr) && expr.operatorToken.kind === SyntaxKind.CommaToken && isNarrowableReference(expr.right)
|| isElementAccessExpression(expr) && isStringOrNumericLiteralLike(expr.argumentExpression) && isNarrowableReference(expr.expression)
|| isElementAccessExpression(expr) && (isStringOrNumericLiteralLike(expr.argumentExpression) || isEntityNameExpression(expr.argumentExpression)) && isNarrowableReference(expr.expression)
|| isAssignmentExpression(expr) && isNarrowableReference(expr.left);
}

Expand Down
67 changes: 55 additions & 12 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22773,9 +22773,10 @@ namespace ts {
return isMatchingReference((source as NonNullExpression | ParenthesizedExpression).expression, target);
case SyntaxKind.PropertyAccessExpression:
case SyntaxKind.ElementAccessExpression:
return isAccessExpression(target) &&
getAccessedPropertyName(source as AccessExpression) === getAccessedPropertyName(target) &&
isMatchingReference((source as AccessExpression).expression, target.expression);
const sourcePropertyName = getAccessedPropertyName(source as AccessExpression);
const targetPropertyName = isAccessExpression(target) ? getAccessedPropertyName(target) : undefined;
return sourcePropertyName !== undefined && targetPropertyName !== undefined && targetPropertyName === sourcePropertyName &&
isMatchingReference((source as AccessExpression).expression, (target as AccessExpression).expression);
case SyntaxKind.QualifiedName:
return isAccessExpression(target) &&
(source as QualifiedName).right.escapedText === getAccessedPropertyName(target) &&
Expand All @@ -22787,12 +22788,52 @@ namespace ts {
}

function getAccessedPropertyName(access: AccessExpression | BindingElement | ParameterDeclaration): __String | undefined {
let propertyName;
return access.kind === SyntaxKind.PropertyAccessExpression ? access.name.escapedText :
access.kind === SyntaxKind.ElementAccessExpression && isStringOrNumericLiteralLike(access.argumentExpression) ? escapeLeadingUnderscores(access.argumentExpression.text) :
access.kind === SyntaxKind.BindingElement && (propertyName = getDestructuringPropertyName(access)) ? escapeLeadingUnderscores(propertyName) :
access.kind === SyntaxKind.Parameter ? ("" + access.parent.parameters.indexOf(access)) as __String :
undefined;
if (isPropertyAccessExpression(access)) {
return access.name.escapedText;
}
if (isElementAccessExpression(access)) {
return tryGetElementAccessExpressionName(access);
}
if (isBindingElement(access)) {
const name = getDestructuringPropertyName(access);
return name ? escapeLeadingUnderscores(name) : undefined;
}
if (isParameter(access)) {
return ("" + access.parent.parameters.indexOf(access)) as __String;
}
return undefined;
}

function tryGetNameFromType(type: Type) {
return type.flags & TypeFlags.UniqueESSymbol ? (type as UniqueESSymbolType).escapedName :
type.flags & TypeFlags.StringOrNumberLiteral ? escapeLeadingUnderscores("" + (type as StringLiteralType | NumberLiteralType).value) : undefined;
}

function tryGetElementAccessExpressionName(node: ElementAccessExpression) {
if (isStringOrNumericLiteralLike(node.argumentExpression)) {
return escapeLeadingUnderscores(node.argumentExpression.text);
}
if (isEntityNameExpression(node.argumentExpression)) {
const symbol = resolveEntityName(node.argumentExpression, SymbolFlags.Value, /*ignoreErrors*/ true);
if (!symbol || !isConstVariable(symbol)) return undefined;

const declaration = symbol.valueDeclaration;
if (declaration === undefined) return undefined;

const type = tryGetTypeFromEffectiveTypeNode(declaration);
if (type) {
const name = tryGetNameFromType(type);
if (name !== undefined) {
return name;
}
}

if (hasOnlyExpressionInitializer(declaration)) {
const initializer = getEffectiveInitializer(declaration);
return initializer && tryGetNameFromType(getTypeOfExpression(initializer));
}
}
return undefined;
}

function containsMatchingReference(source: Node, target: Node) {
Expand Down Expand Up @@ -39310,7 +39351,7 @@ namespace ts {
}
if (!isStatic(member) && isPropertyWithoutInitializer(member)) {
const propName = (member as PropertyDeclaration).name;
if (isIdentifier(propName) || isPrivateIdentifier(propName)) {
if (isIdentifier(propName) || isPrivateIdentifier(propName) || isComputedPropertyName(propName)) {
const type = getTypeOfSymbol(getSymbolOfNode(member));
if (!(type.flags & TypeFlags.AnyOrUnknown || getFalsyFlags(type) & TypeFlags.Undefined)) {
if (!constructor || !isPropertyInitializedInConstructor(propName, type, constructor)) {
Expand Down Expand Up @@ -39346,8 +39387,10 @@ namespace ts {
return false;
}

function isPropertyInitializedInConstructor(propName: Identifier | PrivateIdentifier, propType: Type, constructor: ConstructorDeclaration) {
const reference = factory.createPropertyAccessExpression(factory.createThis(), propName);
function isPropertyInitializedInConstructor(propName: Identifier | PrivateIdentifier | ComputedPropertyName, propType: Type, constructor: ConstructorDeclaration) {
const reference = isComputedPropertyName(propName)
? factory.createElementAccessExpression(factory.createThis(), propName.expression)
: factory.createPropertyAccessExpression(factory.createThis(), propName);
setParent(reference.expression, reference);
setParent(reference, constructor);
reference.flowNode = constructor.returnFlowNode;
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/commandLineParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2753,7 +2753,7 @@ namespace ts {
function getPropFromRaw<T>(prop: "files" | "include" | "exclude" | "references", validateElement: (value: unknown) => boolean, elementTypeName: string): PropOfRaw<T> {
if (hasProperty(raw, prop) && !isNullOrUndefined(raw[prop])) {
if (isArray(raw[prop])) {
const result = raw[prop];
const result = raw[prop] as T[];
if (!sourceFile && !every(result, validateElement)) {
errors.push(createCompilerDiagnostic(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, prop, elementTypeName));
}
Expand Down
10 changes: 5 additions & 5 deletions tests/baselines/reference/incrementOnNullAssertion.types
Original file line number Diff line number Diff line change
Expand Up @@ -27,21 +27,21 @@ if (foo[x] === undefined) {
}
else {
let nu = foo[x]
>nu : number | undefined
>foo[x] : number | undefined
>nu : number
>foo[x] : number
>foo : Dictionary<number>
>x : "bar"

let n = foo[x]
>n : number | undefined
>foo[x] : number | undefined
>n : number
>foo[x] : number
>foo : Dictionary<number>
>x : "bar"

foo[x]!++
>foo[x]!++ : number
>foo[x]! : number
>foo[x] : number | undefined
>foo[x] : number
>foo : Dictionary<number>
>x : "bar"
}
Expand Down
15 changes: 15 additions & 0 deletions tests/baselines/reference/strictPropertyInitialization.errors.txt
Original file line number Diff line number Diff line change
Expand Up @@ -164,4 +164,19 @@ tests/cases/conformance/classes/propertyMemberDeclarations/strictPropertyInitial
this.#b = someValue();
}
}

const a = 'a';
const b = Symbol();

class C12 {
[a]: number;
[b]: number;
['c']: number;

constructor() {
this[a] = 1;
this[b] = 1;
this['c'] = 1;
}
}

32 changes: 32 additions & 0 deletions tests/baselines/reference/strictPropertyInitialization.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,21 @@ class C11 {
this.#b = someValue();
}
}

const a = 'a';
const b = Symbol();

class C12 {
[a]: number;
[b]: number;
['c']: number;

constructor() {
this[a] = 1;
this[b] = 1;
this['c'] = 1;
}
}


//// [strictPropertyInitialization.js]
Expand Down Expand Up @@ -235,6 +250,15 @@ class C11 {
}
}
_C11_b = new WeakMap();
const a = 'a';
const b = Symbol();
class C12 {
constructor() {
this[a] = 1;
this[b] = 1;
this['c'] = 1;
}
}


//// [strictPropertyInitialization.d.ts]
Expand Down Expand Up @@ -303,3 +327,11 @@ declare class C11 {
a: number;
constructor();
}
declare const a = "a";
declare const b: unique symbol;
declare class C12 {
[a]: number;
[b]: number;
['c']: number;
constructor();
}
37 changes: 37 additions & 0 deletions tests/baselines/reference/strictPropertyInitialization.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -311,3 +311,40 @@ class C11 {
}
}

const a = 'a';
>a : Symbol(a, Decl(strictPropertyInitialization.ts, 134, 5))

const b = Symbol();
>b : Symbol(b, Decl(strictPropertyInitialization.ts, 135, 5))
>Symbol : Symbol(Symbol, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --))

class C12 {
>C12 : Symbol(C12, Decl(strictPropertyInitialization.ts, 135, 19))

[a]: number;
>[a] : Symbol(C12[a], Decl(strictPropertyInitialization.ts, 137, 11))
>a : Symbol(a, Decl(strictPropertyInitialization.ts, 134, 5))

[b]: number;
>[b] : Symbol(C12[b], Decl(strictPropertyInitialization.ts, 138, 16))
>b : Symbol(b, Decl(strictPropertyInitialization.ts, 135, 5))

['c']: number;
>['c'] : Symbol(C12['c'], Decl(strictPropertyInitialization.ts, 139, 16))
>'c' : Symbol(C12['c'], Decl(strictPropertyInitialization.ts, 139, 16))

constructor() {
this[a] = 1;
>this : Symbol(C12, Decl(strictPropertyInitialization.ts, 135, 19))
>a : Symbol(a, Decl(strictPropertyInitialization.ts, 134, 5))

this[b] = 1;
>this : Symbol(C12, Decl(strictPropertyInitialization.ts, 135, 19))
>b : Symbol(b, Decl(strictPropertyInitialization.ts, 135, 5))

this['c'] = 1;
>this : Symbol(C12, Decl(strictPropertyInitialization.ts, 135, 19))
>'c' : Symbol(C12['c'], Decl(strictPropertyInitialization.ts, 139, 16))
}
}

48 changes: 48 additions & 0 deletions tests/baselines/reference/strictPropertyInitialization.types
Original file line number Diff line number Diff line change
Expand Up @@ -347,3 +347,51 @@ class C11 {
}
}

const a = 'a';
>a : "a"
>'a' : "a"

const b = Symbol();
>b : unique symbol
>Symbol() : unique symbol
>Symbol : SymbolConstructor

class C12 {
>C12 : C12

[a]: number;
>[a] : number
>a : "a"

[b]: number;
>[b] : number
>b : unique symbol

['c']: number;
>['c'] : number
>'c' : "c"

constructor() {
this[a] = 1;
>this[a] = 1 : 1
>this[a] : number
>this : this
>a : "a"
>1 : 1

this[b] = 1;
>this[b] = 1 : 1
>this[b] : number
>this : this
>b : unique symbol
>1 : 1

this['c'] = 1;
>this['c'] = 1 : 1
>this['c'] : number
>this : this
>'c' : "c"
>1 : 1
}
}

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
//// [typeGuardNarrowsIndexedAccessOfKnownProperty.ts]
//// [typeGuardNarrowsIndexedAccessOfKnownProperty1.ts]
interface Square {
["dash-ok"]: "square";
["square-size"]: number;
Expand Down Expand Up @@ -80,7 +80,7 @@ export function g(pair: [number, string?]): string {
}


//// [typeGuardNarrowsIndexedAccessOfKnownProperty.js]
//// [typeGuardNarrowsIndexedAccessOfKnownProperty1.js]
"use strict";
exports.__esModule = true;
exports.g = void 0;
Expand Down
Loading