Merge pull request #31802 from microsoft/wideningInAccessExpressions
Consistent widening in access expressions
This commit is contained in:
commit
8705844879
|
@ -20219,19 +20219,25 @@ namespace ts {
|
|||
return checkPropertyAccessExpressionOrQualifiedName(node, node.left, node.right);
|
||||
}
|
||||
|
||||
function isMethodAccessForCall(node: Node) {
|
||||
while (node.parent.kind === SyntaxKind.ParenthesizedExpression) {
|
||||
node = node.parent;
|
||||
}
|
||||
return isCallOrNewExpression(node.parent) && node.parent.expression === node;
|
||||
}
|
||||
|
||||
function checkPropertyAccessExpressionOrQualifiedName(node: PropertyAccessExpression | QualifiedName, left: Expression | QualifiedName, right: Identifier) {
|
||||
let propType: Type;
|
||||
const leftType = checkNonNullExpression(left);
|
||||
const parentSymbol = getNodeLinks(left).resolvedSymbol;
|
||||
// We widen array literals to get type any[] instead of undefined[] in non-strict mode
|
||||
const apparentType = getApparentType(isEmptyArrayLiteralType(leftType) ? getWidenedType(leftType) : leftType);
|
||||
const assignmentKind = getAssignmentTargetKind(node);
|
||||
const apparentType = getApparentType(assignmentKind !== AssignmentKind.None || isMethodAccessForCall(node) ? getWidenedType(leftType) : leftType);
|
||||
if (isTypeAny(apparentType) || apparentType === silentNeverType) {
|
||||
if (isIdentifier(left) && parentSymbol) {
|
||||
markAliasReferenced(parentSymbol, node);
|
||||
}
|
||||
return apparentType;
|
||||
}
|
||||
const assignmentKind = getAssignmentTargetKind(node);
|
||||
const prop = getPropertyOfType(apparentType, right.escapedText);
|
||||
if (isIdentifier(left) && parentSymbol && !(prop && isConstEnumOrConstEnumOnlyModule(prop))) {
|
||||
markAliasReferenced(parentSymbol, node);
|
||||
|
@ -20625,7 +20631,8 @@ namespace ts {
|
|||
}
|
||||
|
||||
function checkIndexedAccess(node: ElementAccessExpression): Type {
|
||||
const objectType = checkNonNullExpression(node.expression);
|
||||
const exprType = checkNonNullExpression(node.expression);
|
||||
const objectType = getAssignmentTargetKind(node) !== AssignmentKind.None || isMethodAccessForCall(node) ? getWidenedType(exprType) : exprType;
|
||||
|
||||
const indexExpression = node.argumentExpression;
|
||||
if (!indexExpression) {
|
||||
|
|
34
tests/baselines/reference/propertyAccessWidening.errors.txt
Normal file
34
tests/baselines/reference/propertyAccessWidening.errors.txt
Normal file
|
@ -0,0 +1,34 @@
|
|||
tests/cases/conformance/expressions/propertyAccess/propertyAccessWidening.ts(18,21): error TS2339: Property 'a' does not exist on type '{ a: string; b: number; } | {}'.
|
||||
Property 'a' does not exist on type '{}'.
|
||||
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 '{}'.
|
||||
Property 'a' does not exist on type '{}'.
|
||||
|
||||
|
||||
==== tests/cases/conformance/expressions/propertyAccess/propertyAccessWidening.ts (2 errors) ====
|
||||
// Repro from #31762
|
||||
|
||||
function g1(headerNames: any) {
|
||||
let t = [{ hasLineBreak: false, cells: [] }];
|
||||
const table = [{cells: headerNames }].concat(t);
|
||||
}
|
||||
|
||||
function g2(headerNames: any) {
|
||||
let t = [{ hasLineBreak: false, cells: [] }];
|
||||
const table = [{cells: headerNames }]["concat"](t);
|
||||
}
|
||||
|
||||
// Object in property or element access is widened when target of assignment
|
||||
|
||||
function foo(options?: { a: string, b: number }) {
|
||||
let x1 = (options || {}).a; // Object type not widened
|
||||
let x2 = (options || {})["a"]; // Object type not widened
|
||||
(options || {}).a = 1; // Object type widened, error
|
||||
~
|
||||
!!! error TS2339: Property 'a' does not exist on type '{ a: string; b: number; } | {}'.
|
||||
!!! error TS2339: Property 'a' does not exist on type '{}'.
|
||||
(options || {})["a"] = 1; // Object type widened, error
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
!!! error TS7053: Element implicitly has an 'any' type because expression of type '"a"' can't be used to index type '{}'.
|
||||
!!! error TS7053: Property 'a' does not exist on type '{}'.
|
||||
}
|
||||
|
41
tests/baselines/reference/propertyAccessWidening.js
Normal file
41
tests/baselines/reference/propertyAccessWidening.js
Normal file
|
@ -0,0 +1,41 @@
|
|||
//// [propertyAccessWidening.ts]
|
||||
// Repro from #31762
|
||||
|
||||
function g1(headerNames: any) {
|
||||
let t = [{ hasLineBreak: false, cells: [] }];
|
||||
const table = [{cells: headerNames }].concat(t);
|
||||
}
|
||||
|
||||
function g2(headerNames: any) {
|
||||
let t = [{ hasLineBreak: false, cells: [] }];
|
||||
const table = [{cells: headerNames }]["concat"](t);
|
||||
}
|
||||
|
||||
// Object in property or element access is widened when target of assignment
|
||||
|
||||
function foo(options?: { a: string, b: number }) {
|
||||
let x1 = (options || {}).a; // Object type not widened
|
||||
let x2 = (options || {})["a"]; // Object type not widened
|
||||
(options || {}).a = 1; // Object type widened, error
|
||||
(options || {})["a"] = 1; // Object type widened, error
|
||||
}
|
||||
|
||||
|
||||
//// [propertyAccessWidening.js]
|
||||
"use strict";
|
||||
// Repro from #31762
|
||||
function g1(headerNames) {
|
||||
var t = [{ hasLineBreak: false, cells: [] }];
|
||||
var table = [{ cells: headerNames }].concat(t);
|
||||
}
|
||||
function g2(headerNames) {
|
||||
var t = [{ hasLineBreak: false, cells: [] }];
|
||||
var table = [{ cells: headerNames }]["concat"](t);
|
||||
}
|
||||
// Object in property or element access is widened when target of assignment
|
||||
function foo(options) {
|
||||
var x1 = (options || {}).a; // Object type not widened
|
||||
var x2 = (options || {})["a"]; // Object type not widened
|
||||
(options || {}).a = 1; // Object type widened, error
|
||||
(options || {})["a"] = 1; // Object type widened, error
|
||||
}
|
65
tests/baselines/reference/propertyAccessWidening.symbols
Normal file
65
tests/baselines/reference/propertyAccessWidening.symbols
Normal file
|
@ -0,0 +1,65 @@
|
|||
=== tests/cases/conformance/expressions/propertyAccess/propertyAccessWidening.ts ===
|
||||
// Repro from #31762
|
||||
|
||||
function g1(headerNames: any) {
|
||||
>g1 : Symbol(g1, Decl(propertyAccessWidening.ts, 0, 0))
|
||||
>headerNames : Symbol(headerNames, Decl(propertyAccessWidening.ts, 2, 12))
|
||||
|
||||
let t = [{ hasLineBreak: false, cells: [] }];
|
||||
>t : Symbol(t, Decl(propertyAccessWidening.ts, 3, 7))
|
||||
>hasLineBreak : Symbol(hasLineBreak, Decl(propertyAccessWidening.ts, 3, 14))
|
||||
>cells : Symbol(cells, Decl(propertyAccessWidening.ts, 3, 35))
|
||||
|
||||
const table = [{cells: headerNames }].concat(t);
|
||||
>table : Symbol(table, Decl(propertyAccessWidening.ts, 4, 9))
|
||||
>[{cells: headerNames }].concat : Symbol(Array.concat, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
|
||||
>cells : Symbol(cells, Decl(propertyAccessWidening.ts, 4, 20))
|
||||
>headerNames : Symbol(headerNames, Decl(propertyAccessWidening.ts, 2, 12))
|
||||
>concat : Symbol(Array.concat, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
|
||||
>t : Symbol(t, Decl(propertyAccessWidening.ts, 3, 7))
|
||||
}
|
||||
|
||||
function g2(headerNames: any) {
|
||||
>g2 : Symbol(g2, Decl(propertyAccessWidening.ts, 5, 1))
|
||||
>headerNames : Symbol(headerNames, Decl(propertyAccessWidening.ts, 7, 12))
|
||||
|
||||
let t = [{ hasLineBreak: false, cells: [] }];
|
||||
>t : Symbol(t, Decl(propertyAccessWidening.ts, 8, 7))
|
||||
>hasLineBreak : Symbol(hasLineBreak, Decl(propertyAccessWidening.ts, 8, 14))
|
||||
>cells : Symbol(cells, Decl(propertyAccessWidening.ts, 8, 35))
|
||||
|
||||
const table = [{cells: headerNames }]["concat"](t);
|
||||
>table : Symbol(table, Decl(propertyAccessWidening.ts, 9, 9))
|
||||
>cells : Symbol(cells, Decl(propertyAccessWidening.ts, 9, 20))
|
||||
>headerNames : Symbol(headerNames, Decl(propertyAccessWidening.ts, 7, 12))
|
||||
>"concat" : Symbol(Array.concat, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
|
||||
>t : Symbol(t, Decl(propertyAccessWidening.ts, 8, 7))
|
||||
}
|
||||
|
||||
// Object in property or element access is widened when target of assignment
|
||||
|
||||
function foo(options?: { a: string, b: number }) {
|
||||
>foo : Symbol(foo, Decl(propertyAccessWidening.ts, 10, 1))
|
||||
>options : Symbol(options, Decl(propertyAccessWidening.ts, 14, 13))
|
||||
>a : Symbol(a, Decl(propertyAccessWidening.ts, 14, 24))
|
||||
>b : Symbol(b, Decl(propertyAccessWidening.ts, 14, 35))
|
||||
|
||||
let x1 = (options || {}).a; // Object type not widened
|
||||
>x1 : Symbol(x1, Decl(propertyAccessWidening.ts, 15, 7))
|
||||
>(options || {}).a : Symbol(a, Decl(propertyAccessWidening.ts, 14, 24))
|
||||
>options : Symbol(options, Decl(propertyAccessWidening.ts, 14, 13))
|
||||
>a : Symbol(a, Decl(propertyAccessWidening.ts, 14, 24))
|
||||
|
||||
let x2 = (options || {})["a"]; // Object type not widened
|
||||
>x2 : Symbol(x2, Decl(propertyAccessWidening.ts, 16, 7))
|
||||
>options : Symbol(options, Decl(propertyAccessWidening.ts, 14, 13))
|
||||
>"a" : Symbol(a, Decl(propertyAccessWidening.ts, 14, 24))
|
||||
|
||||
(options || {}).a = 1; // Object type widened, error
|
||||
>options : Symbol(options, Decl(propertyAccessWidening.ts, 14, 13))
|
||||
|
||||
(options || {})["a"] = 1; // Object type widened, error
|
||||
>options : Symbol(options, Decl(propertyAccessWidening.ts, 14, 13))
|
||||
>"a" : Symbol(a, Decl(propertyAccessWidening.ts, 14, 24))
|
||||
}
|
||||
|
100
tests/baselines/reference/propertyAccessWidening.types
Normal file
100
tests/baselines/reference/propertyAccessWidening.types
Normal file
|
@ -0,0 +1,100 @@
|
|||
=== tests/cases/conformance/expressions/propertyAccess/propertyAccessWidening.ts ===
|
||||
// Repro from #31762
|
||||
|
||||
function g1(headerNames: any) {
|
||||
>g1 : (headerNames: any) => void
|
||||
>headerNames : any
|
||||
|
||||
let t = [{ hasLineBreak: false, cells: [] }];
|
||||
>t : { hasLineBreak: boolean; cells: never[]; }[]
|
||||
>[{ hasLineBreak: false, cells: [] }] : { hasLineBreak: boolean; cells: never[]; }[]
|
||||
>{ hasLineBreak: false, cells: [] } : { hasLineBreak: boolean; cells: never[]; }
|
||||
>hasLineBreak : boolean
|
||||
>false : false
|
||||
>cells : never[]
|
||||
>[] : never[]
|
||||
|
||||
const table = [{cells: headerNames }].concat(t);
|
||||
>table : { cells: any; }[]
|
||||
>[{cells: headerNames }].concat(t) : { cells: any; }[]
|
||||
>[{cells: headerNames }].concat : { (...items: ConcatArray<{ cells: any; }>[]): { cells: any; }[]; (...items: ({ cells: any; } | ConcatArray<{ cells: any; }>)[]): { cells: any; }[]; }
|
||||
>[{cells: headerNames }] : { cells: any; }[]
|
||||
>{cells: headerNames } : { cells: any; }
|
||||
>cells : any
|
||||
>headerNames : any
|
||||
>concat : { (...items: ConcatArray<{ cells: any; }>[]): { cells: any; }[]; (...items: ({ cells: any; } | ConcatArray<{ cells: any; }>)[]): { cells: any; }[]; }
|
||||
>t : { hasLineBreak: boolean; cells: never[]; }[]
|
||||
}
|
||||
|
||||
function g2(headerNames: any) {
|
||||
>g2 : (headerNames: any) => void
|
||||
>headerNames : any
|
||||
|
||||
let t = [{ hasLineBreak: false, cells: [] }];
|
||||
>t : { hasLineBreak: boolean; cells: never[]; }[]
|
||||
>[{ hasLineBreak: false, cells: [] }] : { hasLineBreak: boolean; cells: never[]; }[]
|
||||
>{ hasLineBreak: false, cells: [] } : { hasLineBreak: boolean; cells: never[]; }
|
||||
>hasLineBreak : boolean
|
||||
>false : false
|
||||
>cells : never[]
|
||||
>[] : never[]
|
||||
|
||||
const table = [{cells: headerNames }]["concat"](t);
|
||||
>table : { cells: any; }[]
|
||||
>[{cells: headerNames }]["concat"](t) : { cells: any; }[]
|
||||
>[{cells: headerNames }]["concat"] : { (...items: ConcatArray<{ cells: any; }>[]): { cells: any; }[]; (...items: ({ cells: any; } | ConcatArray<{ cells: any; }>)[]): { cells: any; }[]; }
|
||||
>[{cells: headerNames }] : { cells: any; }[]
|
||||
>{cells: headerNames } : { cells: any; }
|
||||
>cells : any
|
||||
>headerNames : any
|
||||
>"concat" : "concat"
|
||||
>t : { hasLineBreak: boolean; cells: never[]; }[]
|
||||
}
|
||||
|
||||
// Object in property or element access is widened when target of assignment
|
||||
|
||||
function foo(options?: { a: string, b: number }) {
|
||||
>foo : (options?: { a: string; b: number; } | undefined) => void
|
||||
>options : { a: string; b: number; } | undefined
|
||||
>a : string
|
||||
>b : number
|
||||
|
||||
let x1 = (options || {}).a; // Object type not widened
|
||||
>x1 : string | undefined
|
||||
>(options || {}).a : string | undefined
|
||||
>(options || {}) : { a: string; b: number; } | {}
|
||||
>options || {} : { a: string; b: number; } | {}
|
||||
>options : { a: string; b: number; } | undefined
|
||||
>{} : {}
|
||||
>a : string | undefined
|
||||
|
||||
let x2 = (options || {})["a"]; // Object type not widened
|
||||
>x2 : string | undefined
|
||||
>(options || {})["a"] : string | undefined
|
||||
>(options || {}) : { a: string; b: number; } | {}
|
||||
>options || {} : { a: string; b: number; } | {}
|
||||
>options : { a: string; b: number; } | undefined
|
||||
>{} : {}
|
||||
>"a" : "a"
|
||||
|
||||
(options || {}).a = 1; // Object type widened, error
|
||||
>(options || {}).a = 1 : 1
|
||||
>(options || {}).a : any
|
||||
>(options || {}) : { a: string; b: number; } | {}
|
||||
>options || {} : { a: string; b: number; } | {}
|
||||
>options : { a: string; b: number; } | undefined
|
||||
>{} : {}
|
||||
>a : any
|
||||
>1 : 1
|
||||
|
||||
(options || {})["a"] = 1; // Object type widened, error
|
||||
>(options || {})["a"] = 1 : 1
|
||||
>(options || {})["a"] : any
|
||||
>(options || {}) : { a: string; b: number; } | {}
|
||||
>options || {} : { a: string; b: number; } | {}
|
||||
>options : { a: string; b: number; } | undefined
|
||||
>{} : {}
|
||||
>"a" : "a"
|
||||
>1 : 1
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
// @strict: true
|
||||
|
||||
// Repro from #31762
|
||||
|
||||
function g1(headerNames: any) {
|
||||
let t = [{ hasLineBreak: false, cells: [] }];
|
||||
const table = [{cells: headerNames }].concat(t);
|
||||
}
|
||||
|
||||
function g2(headerNames: any) {
|
||||
let t = [{ hasLineBreak: false, cells: [] }];
|
||||
const table = [{cells: headerNames }]["concat"](t);
|
||||
}
|
||||
|
||||
// Object in property or element access is widened when target of assignment
|
||||
|
||||
function foo(options?: { a: string, b: number }) {
|
||||
let x1 = (options || {}).a; // Object type not widened
|
||||
let x2 = (options || {})["a"]; // Object type not widened
|
||||
(options || {}).a = 1; // Object type widened, error
|
||||
(options || {})["a"] = 1; // Object type widened, error
|
||||
}
|
Loading…
Reference in a new issue