Merge pull request #31802 from microsoft/wideningInAccessExpressions

Consistent widening in access expressions
This commit is contained in:
Anders Hejlsberg 2019-06-07 12:46:24 -07:00 committed by GitHub
commit 8705844879
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 273 additions and 4 deletions

View file

@ -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) {

View 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 '{}'.
}

View 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
}

View 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))
}

View 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
}

View file

@ -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
}