Skip to content

Commit 8705844

Browse files
authored
Merge pull request #31802 from microsoft/wideningInAccessExpressions
Consistent widening in access expressions
2 parents 737cb45 + 4856768 commit 8705844

File tree

6 files changed

+273
-4
lines changed

6 files changed

+273
-4
lines changed

src/compiler/checker.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20219,19 +20219,25 @@ namespace ts {
2021920219
return checkPropertyAccessExpressionOrQualifiedName(node, node.left, node.right);
2022020220
}
2022120221

20222+
function isMethodAccessForCall(node: Node) {
20223+
while (node.parent.kind === SyntaxKind.ParenthesizedExpression) {
20224+
node = node.parent;
20225+
}
20226+
return isCallOrNewExpression(node.parent) && node.parent.expression === node;
20227+
}
20228+
2022220229
function checkPropertyAccessExpressionOrQualifiedName(node: PropertyAccessExpression | QualifiedName, left: Expression | QualifiedName, right: Identifier) {
2022320230
let propType: Type;
2022420231
const leftType = checkNonNullExpression(left);
2022520232
const parentSymbol = getNodeLinks(left).resolvedSymbol;
20226-
// We widen array literals to get type any[] instead of undefined[] in non-strict mode
20227-
const apparentType = getApparentType(isEmptyArrayLiteralType(leftType) ? getWidenedType(leftType) : leftType);
20233+
const assignmentKind = getAssignmentTargetKind(node);
20234+
const apparentType = getApparentType(assignmentKind !== AssignmentKind.None || isMethodAccessForCall(node) ? getWidenedType(leftType) : leftType);
2022820235
if (isTypeAny(apparentType) || apparentType === silentNeverType) {
2022920236
if (isIdentifier(left) && parentSymbol) {
2023020237
markAliasReferenced(parentSymbol, node);
2023120238
}
2023220239
return apparentType;
2023320240
}
20234-
const assignmentKind = getAssignmentTargetKind(node);
2023520241
const prop = getPropertyOfType(apparentType, right.escapedText);
2023620242
if (isIdentifier(left) && parentSymbol && !(prop && isConstEnumOrConstEnumOnlyModule(prop))) {
2023720243
markAliasReferenced(parentSymbol, node);
@@ -20625,7 +20631,8 @@ namespace ts {
2062520631
}
2062620632

2062720633
function checkIndexedAccess(node: ElementAccessExpression): Type {
20628-
const objectType = checkNonNullExpression(node.expression);
20634+
const exprType = checkNonNullExpression(node.expression);
20635+
const objectType = getAssignmentTargetKind(node) !== AssignmentKind.None || isMethodAccessForCall(node) ? getWidenedType(exprType) : exprType;
2062920636

2063020637
const indexExpression = node.argumentExpression;
2063120638
if (!indexExpression) {
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
tests/cases/conformance/expressions/propertyAccess/propertyAccessWidening.ts(18,21): error TS2339: Property 'a' does not exist on type '{ a: string; b: number; } | {}'.
2+
Property 'a' does not exist on type '{}'.
3+
tests/cases/conformance/expressions/propertyAccess/propertyAccessWidening.ts(19,5): error TS7053: Element implicitly has an 'any' type because expression of type '"a"' can't be used to index type '{}'.
4+
Property 'a' does not exist on type '{}'.
5+
6+
7+
==== tests/cases/conformance/expressions/propertyAccess/propertyAccessWidening.ts (2 errors) ====
8+
// Repro from #31762
9+
10+
function g1(headerNames: any) {
11+
let t = [{ hasLineBreak: false, cells: [] }];
12+
const table = [{cells: headerNames }].concat(t);
13+
}
14+
15+
function g2(headerNames: any) {
16+
let t = [{ hasLineBreak: false, cells: [] }];
17+
const table = [{cells: headerNames }]["concat"](t);
18+
}
19+
20+
// Object in property or element access is widened when target of assignment
21+
22+
function foo(options?: { a: string, b: number }) {
23+
let x1 = (options || {}).a; // Object type not widened
24+
let x2 = (options || {})["a"]; // Object type not widened
25+
(options || {}).a = 1; // Object type widened, error
26+
~
27+
!!! error TS2339: Property 'a' does not exist on type '{ a: string; b: number; } | {}'.
28+
!!! error TS2339: Property 'a' does not exist on type '{}'.
29+
(options || {})["a"] = 1; // Object type widened, error
30+
~~~~~~~~~~~~~~~~~~~~
31+
!!! error TS7053: Element implicitly has an 'any' type because expression of type '"a"' can't be used to index type '{}'.
32+
!!! error TS7053: Property 'a' does not exist on type '{}'.
33+
}
34+
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
//// [propertyAccessWidening.ts]
2+
// Repro from #31762
3+
4+
function g1(headerNames: any) {
5+
let t = [{ hasLineBreak: false, cells: [] }];
6+
const table = [{cells: headerNames }].concat(t);
7+
}
8+
9+
function g2(headerNames: any) {
10+
let t = [{ hasLineBreak: false, cells: [] }];
11+
const table = [{cells: headerNames }]["concat"](t);
12+
}
13+
14+
// Object in property or element access is widened when target of assignment
15+
16+
function foo(options?: { a: string, b: number }) {
17+
let x1 = (options || {}).a; // Object type not widened
18+
let x2 = (options || {})["a"]; // Object type not widened
19+
(options || {}).a = 1; // Object type widened, error
20+
(options || {})["a"] = 1; // Object type widened, error
21+
}
22+
23+
24+
//// [propertyAccessWidening.js]
25+
"use strict";
26+
// Repro from #31762
27+
function g1(headerNames) {
28+
var t = [{ hasLineBreak: false, cells: [] }];
29+
var table = [{ cells: headerNames }].concat(t);
30+
}
31+
function g2(headerNames) {
32+
var t = [{ hasLineBreak: false, cells: [] }];
33+
var table = [{ cells: headerNames }]["concat"](t);
34+
}
35+
// Object in property or element access is widened when target of assignment
36+
function foo(options) {
37+
var x1 = (options || {}).a; // Object type not widened
38+
var x2 = (options || {})["a"]; // Object type not widened
39+
(options || {}).a = 1; // Object type widened, error
40+
(options || {})["a"] = 1; // Object type widened, error
41+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
=== tests/cases/conformance/expressions/propertyAccess/propertyAccessWidening.ts ===
2+
// Repro from #31762
3+
4+
function g1(headerNames: any) {
5+
>g1 : Symbol(g1, Decl(propertyAccessWidening.ts, 0, 0))
6+
>headerNames : Symbol(headerNames, Decl(propertyAccessWidening.ts, 2, 12))
7+
8+
let t = [{ hasLineBreak: false, cells: [] }];
9+
>t : Symbol(t, Decl(propertyAccessWidening.ts, 3, 7))
10+
>hasLineBreak : Symbol(hasLineBreak, Decl(propertyAccessWidening.ts, 3, 14))
11+
>cells : Symbol(cells, Decl(propertyAccessWidening.ts, 3, 35))
12+
13+
const table = [{cells: headerNames }].concat(t);
14+
>table : Symbol(table, Decl(propertyAccessWidening.ts, 4, 9))
15+
>[{cells: headerNames }].concat : Symbol(Array.concat, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
16+
>cells : Symbol(cells, Decl(propertyAccessWidening.ts, 4, 20))
17+
>headerNames : Symbol(headerNames, Decl(propertyAccessWidening.ts, 2, 12))
18+
>concat : Symbol(Array.concat, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
19+
>t : Symbol(t, Decl(propertyAccessWidening.ts, 3, 7))
20+
}
21+
22+
function g2(headerNames: any) {
23+
>g2 : Symbol(g2, Decl(propertyAccessWidening.ts, 5, 1))
24+
>headerNames : Symbol(headerNames, Decl(propertyAccessWidening.ts, 7, 12))
25+
26+
let t = [{ hasLineBreak: false, cells: [] }];
27+
>t : Symbol(t, Decl(propertyAccessWidening.ts, 8, 7))
28+
>hasLineBreak : Symbol(hasLineBreak, Decl(propertyAccessWidening.ts, 8, 14))
29+
>cells : Symbol(cells, Decl(propertyAccessWidening.ts, 8, 35))
30+
31+
const table = [{cells: headerNames }]["concat"](t);
32+
>table : Symbol(table, Decl(propertyAccessWidening.ts, 9, 9))
33+
>cells : Symbol(cells, Decl(propertyAccessWidening.ts, 9, 20))
34+
>headerNames : Symbol(headerNames, Decl(propertyAccessWidening.ts, 7, 12))
35+
>"concat" : Symbol(Array.concat, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
36+
>t : Symbol(t, Decl(propertyAccessWidening.ts, 8, 7))
37+
}
38+
39+
// Object in property or element access is widened when target of assignment
40+
41+
function foo(options?: { a: string, b: number }) {
42+
>foo : Symbol(foo, Decl(propertyAccessWidening.ts, 10, 1))
43+
>options : Symbol(options, Decl(propertyAccessWidening.ts, 14, 13))
44+
>a : Symbol(a, Decl(propertyAccessWidening.ts, 14, 24))
45+
>b : Symbol(b, Decl(propertyAccessWidening.ts, 14, 35))
46+
47+
let x1 = (options || {}).a; // Object type not widened
48+
>x1 : Symbol(x1, Decl(propertyAccessWidening.ts, 15, 7))
49+
>(options || {}).a : Symbol(a, Decl(propertyAccessWidening.ts, 14, 24))
50+
>options : Symbol(options, Decl(propertyAccessWidening.ts, 14, 13))
51+
>a : Symbol(a, Decl(propertyAccessWidening.ts, 14, 24))
52+
53+
let x2 = (options || {})["a"]; // Object type not widened
54+
>x2 : Symbol(x2, Decl(propertyAccessWidening.ts, 16, 7))
55+
>options : Symbol(options, Decl(propertyAccessWidening.ts, 14, 13))
56+
>"a" : Symbol(a, Decl(propertyAccessWidening.ts, 14, 24))
57+
58+
(options || {}).a = 1; // Object type widened, error
59+
>options : Symbol(options, Decl(propertyAccessWidening.ts, 14, 13))
60+
61+
(options || {})["a"] = 1; // Object type widened, error
62+
>options : Symbol(options, Decl(propertyAccessWidening.ts, 14, 13))
63+
>"a" : Symbol(a, Decl(propertyAccessWidening.ts, 14, 24))
64+
}
65+
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
=== tests/cases/conformance/expressions/propertyAccess/propertyAccessWidening.ts ===
2+
// Repro from #31762
3+
4+
function g1(headerNames: any) {
5+
>g1 : (headerNames: any) => void
6+
>headerNames : any
7+
8+
let t = [{ hasLineBreak: false, cells: [] }];
9+
>t : { hasLineBreak: boolean; cells: never[]; }[]
10+
>[{ hasLineBreak: false, cells: [] }] : { hasLineBreak: boolean; cells: never[]; }[]
11+
>{ hasLineBreak: false, cells: [] } : { hasLineBreak: boolean; cells: never[]; }
12+
>hasLineBreak : boolean
13+
>false : false
14+
>cells : never[]
15+
>[] : never[]
16+
17+
const table = [{cells: headerNames }].concat(t);
18+
>table : { cells: any; }[]
19+
>[{cells: headerNames }].concat(t) : { cells: any; }[]
20+
>[{cells: headerNames }].concat : { (...items: ConcatArray<{ cells: any; }>[]): { cells: any; }[]; (...items: ({ cells: any; } | ConcatArray<{ cells: any; }>)[]): { cells: any; }[]; }
21+
>[{cells: headerNames }] : { cells: any; }[]
22+
>{cells: headerNames } : { cells: any; }
23+
>cells : any
24+
>headerNames : any
25+
>concat : { (...items: ConcatArray<{ cells: any; }>[]): { cells: any; }[]; (...items: ({ cells: any; } | ConcatArray<{ cells: any; }>)[]): { cells: any; }[]; }
26+
>t : { hasLineBreak: boolean; cells: never[]; }[]
27+
}
28+
29+
function g2(headerNames: any) {
30+
>g2 : (headerNames: any) => void
31+
>headerNames : any
32+
33+
let t = [{ hasLineBreak: false, cells: [] }];
34+
>t : { hasLineBreak: boolean; cells: never[]; }[]
35+
>[{ hasLineBreak: false, cells: [] }] : { hasLineBreak: boolean; cells: never[]; }[]
36+
>{ hasLineBreak: false, cells: [] } : { hasLineBreak: boolean; cells: never[]; }
37+
>hasLineBreak : boolean
38+
>false : false
39+
>cells : never[]
40+
>[] : never[]
41+
42+
const table = [{cells: headerNames }]["concat"](t);
43+
>table : { cells: any; }[]
44+
>[{cells: headerNames }]["concat"](t) : { cells: any; }[]
45+
>[{cells: headerNames }]["concat"] : { (...items: ConcatArray<{ cells: any; }>[]): { cells: any; }[]; (...items: ({ cells: any; } | ConcatArray<{ cells: any; }>)[]): { cells: any; }[]; }
46+
>[{cells: headerNames }] : { cells: any; }[]
47+
>{cells: headerNames } : { cells: any; }
48+
>cells : any
49+
>headerNames : any
50+
>"concat" : "concat"
51+
>t : { hasLineBreak: boolean; cells: never[]; }[]
52+
}
53+
54+
// Object in property or element access is widened when target of assignment
55+
56+
function foo(options?: { a: string, b: number }) {
57+
>foo : (options?: { a: string; b: number; } | undefined) => void
58+
>options : { a: string; b: number; } | undefined
59+
>a : string
60+
>b : number
61+
62+
let x1 = (options || {}).a; // Object type not widened
63+
>x1 : string | undefined
64+
>(options || {}).a : string | undefined
65+
>(options || {}) : { a: string; b: number; } | {}
66+
>options || {} : { a: string; b: number; } | {}
67+
>options : { a: string; b: number; } | undefined
68+
>{} : {}
69+
>a : string | undefined
70+
71+
let x2 = (options || {})["a"]; // Object type not widened
72+
>x2 : string | undefined
73+
>(options || {})["a"] : string | undefined
74+
>(options || {}) : { a: string; b: number; } | {}
75+
>options || {} : { a: string; b: number; } | {}
76+
>options : { a: string; b: number; } | undefined
77+
>{} : {}
78+
>"a" : "a"
79+
80+
(options || {}).a = 1; // Object type widened, error
81+
>(options || {}).a = 1 : 1
82+
>(options || {}).a : any
83+
>(options || {}) : { a: string; b: number; } | {}
84+
>options || {} : { a: string; b: number; } | {}
85+
>options : { a: string; b: number; } | undefined
86+
>{} : {}
87+
>a : any
88+
>1 : 1
89+
90+
(options || {})["a"] = 1; // Object type widened, error
91+
>(options || {})["a"] = 1 : 1
92+
>(options || {})["a"] : any
93+
>(options || {}) : { a: string; b: number; } | {}
94+
>options || {} : { a: string; b: number; } | {}
95+
>options : { a: string; b: number; } | undefined
96+
>{} : {}
97+
>"a" : "a"
98+
>1 : 1
99+
}
100+
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// @strict: true
2+
3+
// Repro from #31762
4+
5+
function g1(headerNames: any) {
6+
let t = [{ hasLineBreak: false, cells: [] }];
7+
const table = [{cells: headerNames }].concat(t);
8+
}
9+
10+
function g2(headerNames: any) {
11+
let t = [{ hasLineBreak: false, cells: [] }];
12+
const table = [{cells: headerNames }]["concat"](t);
13+
}
14+
15+
// Object in property or element access is widened when target of assignment
16+
17+
function foo(options?: { a: string, b: number }) {
18+
let x1 = (options || {}).a; // Object type not widened
19+
let x2 = (options || {})["a"]; // Object type not widened
20+
(options || {}).a = 1; // Object type widened, error
21+
(options || {})["a"] = 1; // Object type widened, error
22+
}

0 commit comments

Comments
 (0)