Explicitly typed special assignments are context sensitive (#25619)

* Explicitly typed js assignments: context sensitive

Explicitly typed special assignments should be context sensitive if they
have an explicit type tag. Previously no special assignments were
context sensitive because they are declarations, and in the common,
untyped, case we inspect the right side of the assignment to get the
type of the left side, and inspect the right side of the assignment to
get the type of the left side, etc etc.

Note that some special assignments still return `any` from
checkExpression, so still don't get the right type.

Fixes #25571

* Change prototype property handling+update bselines

* Fix indentation in test

* Update baselines
This commit is contained in:
Nathan Shively-Sanders 2018-07-12 15:28:53 -07:00 committed by GitHub
parent f500289a44
commit 32e60a9647
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 579 additions and 22 deletions

View file

@ -15785,22 +15785,22 @@ namespace ts {
}
// In an assignment expression, the right operand is contextually typed by the type of the left operand.
// Don't do this for special property assignments to avoid circularity.
// Don't do this for special property assignments unless there is a type tag on the assignment, to avoid circularity from checking the right operand.
function isContextSensitiveAssignment(binaryExpression: BinaryExpression): boolean {
const kind = getSpecialPropertyAssignmentKind(binaryExpression);
switch (kind) {
case SpecialPropertyAssignmentKind.None:
return true;
case SpecialPropertyAssignmentKind.Property:
case SpecialPropertyAssignmentKind.ExportsProperty:
case SpecialPropertyAssignmentKind.Prototype:
case SpecialPropertyAssignmentKind.PrototypeProperty:
// If `binaryExpression.left` was assigned a symbol, then this is a new declaration; otherwise it is an assignment to an existing declaration.
// See `bindStaticPropertyAssignment` in `binder.ts`.
return !binaryExpression.left.symbol;
case SpecialPropertyAssignmentKind.ExportsProperty:
case SpecialPropertyAssignmentKind.ModuleExports:
case SpecialPropertyAssignmentKind.PrototypeProperty:
return !binaryExpression.left.symbol || binaryExpression.left.symbol.valueDeclaration && !!getJSDocTypeTag(binaryExpression.left.symbol.valueDeclaration);
case SpecialPropertyAssignmentKind.ThisProperty:
case SpecialPropertyAssignmentKind.Prototype:
return false;
case SpecialPropertyAssignmentKind.ModuleExports:
return !binaryExpression.symbol || binaryExpression.symbol.valueDeclaration && !!getJSDocTypeTag(binaryExpression.symbol.valueDeclaration);
default:
return Debug.assertNever(kind);
}

View file

@ -7,11 +7,11 @@ export function abc(a, b, c) { return 5; }
>5 : 5
module.exports = { abc };
>module.exports = { abc } : { [x: string]: any; abc: (a: any, b: any, c: any) => number; }
>module.exports = { abc } : { abc: (a: any, b: any, c: any) => number; }
>module.exports : any
>module : any
>exports : any
>{ abc } : { [x: string]: any; abc: (a: any, b: any, c: any) => number; }
>{ abc } : { abc: (a: any, b: any, c: any) => number; }
>abc : (a: any, b: any, c: any) => number
=== tests/cases/conformance/salsa/use.js ===

View file

@ -0,0 +1,92 @@
tests/cases/conformance/salsa/mod.js(5,7): error TS7006: Parameter 'n' implicitly has an 'any' type.
tests/cases/conformance/salsa/test.js(52,7): error TS7006: Parameter 'n' implicitly has an 'any' type.
tests/cases/conformance/salsa/test.js(70,7): error TS7006: Parameter 'n' implicitly has an 'any' type.
==== tests/cases/conformance/salsa/test.js (2 errors) ====
/** @typedef {{
status: 'done'
m(n: number): void
}} DoneStatus */
// property assignment
var ns = {}
/** @type {DoneStatus} */
ns.x = {
status: 'done',
m(n) { }
}
ns.x = {
status: 'done',
m(n) { }
}
ns.x
// this-property assignment
class Thing {
constructor() {
/** @type {DoneStatus} */
this.s = {
status: 'done',
m(n) { }
}
}
fail() {
this.s = {
status: 'done',
m(n) { }
}
}
}
// exports-property assignment
/** @type {DoneStatus} */
exports.x = {
status: "done",
m(n) { }
}
exports.x
/** @type {DoneStatus} contextual typing is allowed, but module.exports.y: any.
Guess it doesn't check the type tag? */
module.exports.y = {
status: "done",
m(n) { }
~
!!! error TS7006: Parameter 'n' implicitly has an 'any' type.
}
module.exports.y
// prototype-property assignment
/** @type {DoneStatus} */
Thing.prototype.x = {
status: 'done',
m(n) { }
}
Thing.prototype.x
// prototype assignment
function F() {
}
/** @type {DoneStatus} */
F.prototype = {
status: "done",
m(n) { }
~
!!! error TS7006: Parameter 'n' implicitly has an 'any' type.
}
==== tests/cases/conformance/salsa/mod.js (1 errors) ====
// module.exports assignment
/** @type {{ status: 'done' }} */
module.exports = {
status: "done",
m(n) { }
~
!!! error TS7006: Parameter 'n' implicitly has an 'any' type.
}

View file

@ -0,0 +1,173 @@
=== tests/cases/conformance/salsa/test.js ===
/** @typedef {{
status: 'done'
m(n: number): void
}} DoneStatus */
// property assignment
var ns = {}
>ns : Symbol(ns, Decl(test.js, 6, 3))
/** @type {DoneStatus} */
ns.x = {
>ns.x : Symbol(ns.x, Decl(test.js, 6, 11), Decl(test.js, 11, 1))
>ns : Symbol(ns, Decl(test.js, 6, 3))
>x : Symbol(ns.x, Decl(test.js, 6, 11), Decl(test.js, 11, 1))
status: 'done',
>status : Symbol(status, Decl(test.js, 8, 8))
m(n) { }
>m : Symbol(m, Decl(test.js, 9, 19))
>n : Symbol(n, Decl(test.js, 10, 6))
}
ns.x = {
>ns.x : Symbol(ns.x, Decl(test.js, 6, 11), Decl(test.js, 11, 1))
>ns : Symbol(ns, Decl(test.js, 6, 3))
>x : Symbol(ns.x, Decl(test.js, 6, 11), Decl(test.js, 11, 1))
status: 'done',
>status : Symbol(status, Decl(test.js, 13, 8))
m(n) { }
>m : Symbol(m, Decl(test.js, 14, 19))
>n : Symbol(n, Decl(test.js, 15, 6))
}
ns.x
>ns.x : Symbol(ns.x, Decl(test.js, 6, 11), Decl(test.js, 11, 1))
>ns : Symbol(ns, Decl(test.js, 6, 3))
>x : Symbol(ns.x, Decl(test.js, 6, 11), Decl(test.js, 11, 1))
// this-property assignment
class Thing {
>Thing : Symbol(Thing, Decl(test.js, 17, 4))
constructor() {
/** @type {DoneStatus} */
this.s = {
>this.s : Symbol(Thing.s, Decl(test.js, 22, 19), Decl(test.js, 30, 12))
>this : Symbol(Thing, Decl(test.js, 17, 4))
>s : Symbol(Thing.s, Decl(test.js, 22, 19), Decl(test.js, 30, 12))
status: 'done',
>status : Symbol(status, Decl(test.js, 24, 18))
m(n) { }
>m : Symbol(m, Decl(test.js, 25, 27))
>n : Symbol(n, Decl(test.js, 26, 14))
}
}
fail() {
>fail : Symbol(Thing.fail, Decl(test.js, 28, 5))
this.s = {
>this.s : Symbol(Thing.s, Decl(test.js, 22, 19), Decl(test.js, 30, 12))
>this : Symbol(Thing, Decl(test.js, 17, 4))
>s : Symbol(Thing.s, Decl(test.js, 22, 19), Decl(test.js, 30, 12))
status: 'done',
>status : Symbol(status, Decl(test.js, 31, 18))
m(n) { }
>m : Symbol(m, Decl(test.js, 32, 27))
>n : Symbol(n, Decl(test.js, 33, 14))
}
}
}
// exports-property assignment
/** @type {DoneStatus} */
exports.x = {
>exports.x : Symbol(x, Decl(test.js, 36, 1))
>exports : Symbol(x, Decl(test.js, 36, 1))
>x : Symbol(x, Decl(test.js, 36, 1))
status: "done",
>status : Symbol(status, Decl(test.js, 41, 13))
m(n) { }
>m : Symbol(m, Decl(test.js, 42, 19))
>n : Symbol(n, Decl(test.js, 43, 6))
}
exports.x
>exports.x : Symbol(x, Decl(test.js, 36, 1))
>exports : Symbol("tests/cases/conformance/salsa/test", Decl(test.js, 0, 0))
>x : Symbol(x, Decl(test.js, 36, 1))
/** @type {DoneStatus} contextual typing is allowed, but module.exports.y: any.
Guess it doesn't check the type tag? */
module.exports.y = {
>module.exports : Symbol(y, Decl(test.js, 45, 9))
>module : Symbol(module)
>y : Symbol(y, Decl(test.js, 45, 9))
status: "done",
>status : Symbol(status, Decl(test.js, 49, 20))
m(n) { }
>m : Symbol(m, Decl(test.js, 50, 19))
>n : Symbol(n, Decl(test.js, 51, 6))
}
module.exports.y
>module : Symbol(module)
// prototype-property assignment
/** @type {DoneStatus} */
Thing.prototype.x = {
>Thing.prototype.x : Symbol(Thing.x, Decl(test.js, 53, 16))
>Thing.prototype : Symbol(Thing.x, Decl(test.js, 53, 16))
>Thing : Symbol(Thing, Decl(test.js, 17, 4))
>prototype : Symbol(Thing.prototype)
>x : Symbol(Thing.x, Decl(test.js, 53, 16))
status: 'done',
>status : Symbol(status, Decl(test.js, 57, 21))
m(n) { }
>m : Symbol(m, Decl(test.js, 58, 19))
>n : Symbol(n, Decl(test.js, 59, 6))
}
Thing.prototype.x
>Thing.prototype.x : Symbol(Thing.x, Decl(test.js, 53, 16))
>Thing.prototype : Symbol(Thing.prototype)
>Thing : Symbol(Thing, Decl(test.js, 17, 4))
>prototype : Symbol(Thing.prototype)
>x : Symbol(Thing.x, Decl(test.js, 53, 16))
// prototype assignment
function F() {
>F : Symbol(F, Decl(test.js, 61, 17), Decl(test.js, 65, 1))
}
/** @type {DoneStatus} */
F.prototype = {
>F.prototype : Symbol(F.prototype, Decl(test.js, 65, 1))
>F : Symbol(F, Decl(test.js, 61, 17), Decl(test.js, 65, 1))
>prototype : Symbol(F.prototype, Decl(test.js, 65, 1))
status: "done",
>status : Symbol(status, Decl(test.js, 67, 15))
m(n) { }
>m : Symbol(m, Decl(test.js, 68, 19))
>n : Symbol(n, Decl(test.js, 69, 6))
}
=== tests/cases/conformance/salsa/mod.js ===
// module.exports assignment
/** @type {{ status: 'done' }} */
module.exports = {
>module : Symbol(export=, Decl(mod.js, 0, 0))
>exports : Symbol(export=, Decl(mod.js, 0, 0))
status: "done",
>status : Symbol(status, Decl(mod.js, 2, 18))
m(n) { }
>m : Symbol(m, Decl(mod.js, 3, 19))
>n : Symbol(n, Decl(mod.js, 4, 6))
}

View file

@ -0,0 +1,208 @@
=== tests/cases/conformance/salsa/test.js ===
/** @typedef {{
status: 'done'
m(n: number): void
}} DoneStatus */
// property assignment
var ns = {}
>ns : { [x: string]: any; x: { status: "done"; m(n: number): void; }; }
>{} : { [x: string]: any; }
/** @type {DoneStatus} */
ns.x = {
>ns.x = { status: 'done', m(n) { }} : { status: "done"; m(n: number): void; }
>ns.x : { status: "done"; m(n: number): void; }
>ns : { [x: string]: any; x: { status: "done"; m(n: number): void; }; }
>x : { status: "done"; m(n: number): void; }
>{ status: 'done', m(n) { }} : { status: "done"; m(n: number): void; }
status: 'done',
>status : "done"
>'done' : "done"
m(n) { }
>m : (n: number) => void
>n : number
}
ns.x = {
>ns.x = { status: 'done', m(n) { }} : { status: "done"; m(n: number): void; }
>ns.x : { status: "done"; m(n: number): void; }
>ns : { [x: string]: any; x: { status: "done"; m(n: number): void; }; }
>x : { status: "done"; m(n: number): void; }
>{ status: 'done', m(n) { }} : { status: "done"; m(n: number): void; }
status: 'done',
>status : "done"
>'done' : "done"
m(n) { }
>m : (n: number) => void
>n : number
}
ns.x
>ns.x : { status: "done"; m(n: number): void; }
>ns : { [x: string]: any; x: { status: "done"; m(n: number): void; }; }
>x : { status: "done"; m(n: number): void; }
// this-property assignment
class Thing {
>Thing : Thing
constructor() {
/** @type {DoneStatus} */
this.s = {
>this.s = { status: 'done', m(n) { } } : { status: "done"; m(n: number): void; }
>this.s : { status: "done"; m(n: number): void; }
>this : this
>s : { status: "done"; m(n: number): void; }
>{ status: 'done', m(n) { } } : { status: "done"; m(n: number): void; }
status: 'done',
>status : "done"
>'done' : "done"
m(n) { }
>m : (n: number) => void
>n : number
}
}
fail() {
>fail : () => void
this.s = {
>this.s = { status: 'done', m(n) { } } : { status: "done"; m(n: number): void; }
>this.s : { status: "done"; m(n: number): void; }
>this : this
>s : { status: "done"; m(n: number): void; }
>{ status: 'done', m(n) { } } : { status: "done"; m(n: number): void; }
status: 'done',
>status : "done"
>'done' : "done"
m(n) { }
>m : (n: number) => void
>n : number
}
}
}
// exports-property assignment
/** @type {DoneStatus} */
exports.x = {
>exports.x = { status: "done", m(n) { }} : { status: "done"; m(n: number): void; }
>exports.x : { status: "done"; m(n: number): void; }
>exports : typeof import("tests/cases/conformance/salsa/test")
>x : { status: "done"; m(n: number): void; }
>{ status: "done", m(n) { }} : { status: "done"; m(n: number): void; }
status: "done",
>status : "done"
>"done" : "done"
m(n) { }
>m : (n: number) => void
>n : number
}
exports.x
>exports.x : { status: "done"; m(n: number): void; }
>exports : typeof import("tests/cases/conformance/salsa/test")
>x : { status: "done"; m(n: number): void; }
/** @type {DoneStatus} contextual typing is allowed, but module.exports.y: any.
Guess it doesn't check the type tag? */
module.exports.y = {
>module.exports.y = { status: "done", m(n) { }} : { status: string; m(n: any): void; }
>module.exports.y : any
>module.exports : any
>module : any
>exports : any
>y : any
>{ status: "done", m(n) { }} : { status: string; m(n: any): void; }
status: "done",
>status : string
>"done" : "done"
m(n) { }
>m : (n: any) => void
>n : any
}
module.exports.y
>module.exports.y : any
>module.exports : any
>module : any
>exports : any
>y : any
// prototype-property assignment
/** @type {DoneStatus} */
Thing.prototype.x = {
>Thing.prototype.x = { status: 'done', m(n) { }} : { status: "done"; m(n: number): void; }
>Thing.prototype.x : { status: "done"; m(n: number): void; }
>Thing.prototype : Thing
>Thing : typeof Thing
>prototype : Thing
>x : { status: "done"; m(n: number): void; }
>{ status: 'done', m(n) { }} : { status: "done"; m(n: number): void; }
status: 'done',
>status : "done"
>'done' : "done"
m(n) { }
>m : (n: number) => void
>n : number
}
Thing.prototype.x
>Thing.prototype.x : { status: "done"; m(n: number): void; }
>Thing.prototype : Thing
>Thing : typeof Thing
>prototype : Thing
>x : { status: "done"; m(n: number): void; }
// prototype assignment
function F() {
>F : typeof F
}
/** @type {DoneStatus} */
F.prototype = {
>F.prototype = { status: "done", m(n) { }} : { status: string; m(n: any): void; }
>F.prototype : { [x: string]: any; }
>F : typeof F
>prototype : { [x: string]: any; }
>{ status: "done", m(n) { }} : { status: string; m(n: any): void; }
status: "done",
>status : string
>"done" : "done"
m(n) { }
>m : (n: any) => void
>n : any
}
=== tests/cases/conformance/salsa/mod.js ===
// module.exports assignment
/** @type {{ status: 'done' }} */
module.exports = {
>module.exports = { status: "done", m(n) { }} : { status: string; m(n: any): void; }
>module.exports : any
>module : any
>exports : any
>{ status: "done", m(n) { }} : { status: string; m(n: any): void; }
status: "done",
>status : string
>"done" : "done"
m(n) { }
>m : (n: any) => void
>n : any
}

View file

@ -223,19 +223,19 @@ multipleDeclarationAlias5.func9 = function () { };
>function () { } : () => void
var multipleDeclarationAlias6 = exports = module.exports = {};
>multipleDeclarationAlias6 : { [x: string]: any; }
>exports = module.exports = {} : { [x: string]: any; }
>multipleDeclarationAlias6 : {}
>exports = module.exports = {} : {}
>exports : typeof import("tests/cases/conformance/salsa/b")
>module.exports = {} : { [x: string]: any; }
>module.exports = {} : {}
>module.exports : any
>module : any
>exports : any
>{} : { [x: string]: any; }
>{} : {}
multipleDeclarationAlias6.func10 = function () { };
>multipleDeclarationAlias6.func10 = function () { } : () => void
>multipleDeclarationAlias6.func10 : any
>multipleDeclarationAlias6 : { [x: string]: any; }
>multipleDeclarationAlias6 : {}
>func10 : any
>function () { } : () => void
@ -294,13 +294,13 @@ module.exports.func12 = function () { };
>function () { } : () => void
exports = module.exports = {};
>exports = module.exports = {} : { [x: string]: any; }
>exports = module.exports = {} : {}
>exports : typeof import("tests/cases/conformance/salsa/b")
>module.exports = {} : { [x: string]: any; }
>module.exports = {} : {}
>module.exports : any
>module : any
>exports : any
>{} : { [x: string]: any; }
>{} : {}
exports.func13 = function () { };
>exports.func13 = function () { } : () => void
@ -319,13 +319,13 @@ module.exports.func14 = function () { };
>function () { } : () => void
exports = module.exports = {};
>exports = module.exports = {} : { [x: string]: any; }
>exports = module.exports = {} : {}
>exports : typeof import("tests/cases/conformance/salsa/b")
>module.exports = {} : { [x: string]: any; }
>module.exports = {} : {}
>module.exports : any
>module : any
>exports : any
>{} : { [x: string]: any; }
>{} : {}
exports.func15 = function () { };
>exports.func15 = function () { } : () => void
@ -369,11 +369,11 @@ module.exports.func18 = function () { };
>function () { } : () => void
module.exports = {};
>module.exports = {} : { [x: string]: any; }
>module.exports = {} : {}
>module.exports : any
>module : any
>exports : any
>{} : { [x: string]: any; }
>{} : {}
exports.func19 = function () { };
>exports.func19 = function () { } : () => void

View file

@ -0,0 +1,84 @@
// @checkJs: true
// @allowJs: true
// @noEmit: true
// @Filename: test.js
// @strict: true
/** @typedef {{
status: 'done'
m(n: number): void
}} DoneStatus */
// property assignment
var ns = {}
/** @type {DoneStatus} */
ns.x = {
status: 'done',
m(n) { }
}
ns.x = {
status: 'done',
m(n) { }
}
ns.x
// this-property assignment
class Thing {
constructor() {
/** @type {DoneStatus} */
this.s = {
status: 'done',
m(n) { }
}
}
fail() {
this.s = {
status: 'done',
m(n) { }
}
}
}
// exports-property assignment
/** @type {DoneStatus} */
exports.x = {
status: "done",
m(n) { }
}
exports.x
/** @type {DoneStatus} contextual typing is allowed, but module.exports.y: any.
Guess it doesn't check the type tag? */
module.exports.y = {
status: "done",
m(n) { }
}
module.exports.y
// prototype-property assignment
/** @type {DoneStatus} */
Thing.prototype.x = {
status: 'done',
m(n) { }
}
Thing.prototype.x
// prototype assignment
function F() {
}
/** @type {DoneStatus} */
F.prototype = {
status: "done",
m(n) { }
}
// @Filename: mod.js
// module.exports assignment
/** @type {{ status: 'done' }} */
module.exports = {
status: "done",
m(n) { }
}