Skip to content

Commit

Permalink
fix(40617): handle uninitialized class member with computed key
Browse files Browse the repository at this point in the history
  • Loading branch information
a-tarasyuk committed Sep 28, 2021
1 parent d518bdb commit e3d3857
Show file tree
Hide file tree
Showing 37 changed files with 845 additions and 168 deletions.
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) || isIdentifier(expr.argumentExpression)) && isNarrowableReference(expr.expression)
|| isAssignmentExpression(expr) && isNarrowableReference(expr.left);
}

Expand Down
34 changes: 26 additions & 8 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22396,11 +22396,27 @@ namespace ts {
}

function getAccessedPropertyName(access: AccessExpression | BindingElement): __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) :
undefined;
if (isPropertyAccessExpression(access)) {
return access.name.escapedText;
}
if (isElementAccessExpression(access)) {
if (isStringOrNumericLiteralLike(access.argumentExpression)) {
return escapeLeadingUnderscores(access.argumentExpression.text);
}
if (isIdentifier(access.argumentExpression)) {
const symbol = getResolvedSymbol(access.argumentExpression);
const type = getSymbolLinks(symbol).type;
if (type === undefined) return undefined;
return type.flags & TypeFlags.UniqueESSymbol ? (type as UniqueESSymbolType).escapedName :
type.flags & TypeFlags.StringOrNumberLiteral ? escapeLeadingUnderscores("" + (type as StringLiteralType | NumberLiteralType).value) : undefined;
}
return undefined;
}
if (isBindingElement(access)) {
const name = getDestructuringPropertyName(access);
return name ? escapeLeadingUnderscores(name) : undefined;
}
return undefined;
}

function containsMatchingReference(source: Node, target: Node) {
Expand Down Expand Up @@ -38598,7 +38614,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 @@ -38634,8 +38650,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 @@ -2686,7 +2686,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
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,10 @@ tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(501,13): error T
tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(515,13): error TS2532: Object is possibly 'undefined'.
tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(518,13): error TS2532: Object is possibly 'undefined'.
tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(567,21): error TS2532: Object is possibly 'undefined'.
tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(586,9): error TS2367: This condition will always return 'false' since the types '"left"' and '"right"' have no overlap.


==== tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts (61 errors) ====
==== tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts (62 errors) ====
// assignments in shortcutting chain
declare const o: undefined | {
[key: string]: any;
Expand Down Expand Up @@ -770,6 +771,8 @@ tests/cases/conformance/controlFlow/controlFlowOptionalChain.ts(567,21): error T
while (arr[i]?.tag === "left") {
i += 1;
if (arr[i]?.tag === "right") {
~~~~~~~~~~~~~~~~~~~~~~~
!!! error TS2367: This condition will always return 'false' since the types '"left"' and '"right"' have no overlap.
console.log("I should ALSO be reachable");
}
}
Expand Down
4 changes: 2 additions & 2 deletions tests/baselines/reference/controlFlowOptionalChain.types
Original file line number Diff line number Diff line change
Expand Up @@ -2057,11 +2057,11 @@ while (arr[i]?.tag === "left") {

if (arr[i]?.tag === "right") {
>arr[i]?.tag === "right" : boolean
>arr[i]?.tag : "left" | "right"
>arr[i]?.tag : "left"
>arr[i] : { tag: "left" | "right"; }
>arr : { tag: "left" | "right"; }[]
>i : number
>tag : "left" | "right"
>tag : "left"
>"right" : "right"

console.log("I should ALSO be reachable");
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
6 changes: 2 additions & 4 deletions tests/baselines/reference/noUncheckedIndexedAccess.errors.txt
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,7 @@ tests/cases/conformance/pedantic/noUncheckedIndexedAccess.ts(90,7): error TS2322
Type 'undefined' is not assignable to type 'string'.
tests/cases/conformance/pedantic/noUncheckedIndexedAccess.ts(98,5): error TS2322: Type 'undefined' is not assignable to type '{ [key: string]: string; a: string; b: string; }[Key]'.
Type 'undefined' is not assignable to type 'string'.
tests/cases/conformance/pedantic/noUncheckedIndexedAccess.ts(99,11): error TS2322: Type 'string | undefined' is not assignable to type 'string'.
Type 'undefined' is not assignable to type 'string'.
tests/cases/conformance/pedantic/noUncheckedIndexedAccess.ts(99,11): error TS2322: Type 'undefined' is not assignable to type 'string'.


==== tests/cases/conformance/pedantic/noUncheckedIndexedAccess.ts (31 errors) ====
Expand Down Expand Up @@ -201,8 +200,7 @@ tests/cases/conformance/pedantic/noUncheckedIndexedAccess.ts(99,11): error TS232
!!! error TS2322: Type 'undefined' is not assignable to type 'string'.
const v: string = myRecord2[key]; // Should error
~
!!! error TS2322: Type 'string | undefined' is not assignable to type 'string'.
!!! error TS2322: Type 'undefined' is not assignable to type 'string'.
!!! error TS2322: Type 'undefined' is not assignable to type 'string'.
};


2 changes: 1 addition & 1 deletion tests/baselines/reference/noUncheckedIndexedAccess.types
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,7 @@ const fn3 = <Key extends keyof typeof myRecord2>(key: Key) => {

const v: string = myRecord2[key]; // Should error
>v : string
>myRecord2[key] : string | undefined
>myRecord2[key] : undefined
>myRecord2 : { [key: string]: string; a: string; b: string; }
>key : Key

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

0 comments on commit e3d3857

Please sign in to comment.