diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 1e99d447e6ffe..93a444e1d2863 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -15782,22 +15782,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: - // 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.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 || 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); } diff --git a/tests/baselines/reference/conflictingCommonJSES2015Exports.types b/tests/baselines/reference/conflictingCommonJSES2015Exports.types index a9dc1ce80bd7c..a6afee35f2c09 100644 --- a/tests/baselines/reference/conflictingCommonJSES2015Exports.types +++ b/tests/baselines/reference/conflictingCommonJSES2015Exports.types @@ -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 === diff --git a/tests/baselines/reference/contextualTypedSpecialAssignment.errors.txt b/tests/baselines/reference/contextualTypedSpecialAssignment.errors.txt new file mode 100644 index 0000000000000..e918ff187aeb6 --- /dev/null +++ b/tests/baselines/reference/contextualTypedSpecialAssignment.errors.txt @@ -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. + } + \ No newline at end of file diff --git a/tests/baselines/reference/contextualTypedSpecialAssignment.symbols b/tests/baselines/reference/contextualTypedSpecialAssignment.symbols new file mode 100644 index 0000000000000..dac01d09bcc5e --- /dev/null +++ b/tests/baselines/reference/contextualTypedSpecialAssignment.symbols @@ -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)) +} + diff --git a/tests/baselines/reference/contextualTypedSpecialAssignment.types b/tests/baselines/reference/contextualTypedSpecialAssignment.types new file mode 100644 index 0000000000000..04f32e7a6feb9 --- /dev/null +++ b/tests/baselines/reference/contextualTypedSpecialAssignment.types @@ -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 +} + diff --git a/tests/baselines/reference/moduleExportAlias.types b/tests/baselines/reference/moduleExportAlias.types index 924f02d7fa866..707dc90a1f5c7 100644 --- a/tests/baselines/reference/moduleExportAlias.types +++ b/tests/baselines/reference/moduleExportAlias.types @@ -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 diff --git a/tests/cases/conformance/salsa/contextualTypedSpecialAssignment.ts b/tests/cases/conformance/salsa/contextualTypedSpecialAssignment.ts new file mode 100644 index 0000000000000..4b67e8b9687fc --- /dev/null +++ b/tests/cases/conformance/salsa/contextualTypedSpecialAssignment.ts @@ -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) { } +}