mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-03-06 02:00:01 -06:00
Js constructor function fixes (#22721)
* Do not add undefined for this assignments in functions * Test:constructor functions with --strict * First draft -- works, but needs a stricter check added * Update baselines * Make undefined-skip stricter and more efficient Symbol-based now instead of syntactic * Exclude prototype function assignments * Add explanatory comment
This commit is contained in:
parent
ab8233c5d3
commit
1074819be3
@ -4259,7 +4259,14 @@ namespace ts {
|
||||
}
|
||||
|
||||
if (isPropertyAccessExpression(expression.left) && expression.left.expression.kind === SyntaxKind.ThisKeyword) {
|
||||
if (getThisContainer(expression, /*includeArrowFunctions*/ false).kind === SyntaxKind.Constructor) {
|
||||
const thisContainer = getThisContainer(expression, /*includeArrowFunctions*/ false);
|
||||
const isPrototypeProperty = isBinaryExpression(thisContainer.parent) &&
|
||||
getSpecialPropertyAssignmentKind(thisContainer.parent) === SpecialPropertyAssignmentKind.PrototypeProperty;
|
||||
// Properties defined in a constructor (or javascript constructor function) don't get undefined added.
|
||||
// Function expressions that are assigned to the prototype count as methods.
|
||||
if (thisContainer.kind === SyntaxKind.Constructor ||
|
||||
thisContainer.kind === SyntaxKind.FunctionDeclaration ||
|
||||
(thisContainer.kind === SyntaxKind.FunctionExpression && !isPrototypeProperty)) {
|
||||
definedInConstructor = true;
|
||||
}
|
||||
else {
|
||||
@ -18566,7 +18573,7 @@ namespace ts {
|
||||
return true;
|
||||
}
|
||||
|
||||
/** NOTE: Return value of `[]` means a different thing than `undefined`. `[]` means return `void`, `undefined` means return `never`. */
|
||||
/** NOTE: Return value of `[]` means a different thing than `undefined`. `[]` means func returns `void`, `undefined` means it returns `never`. */
|
||||
function checkAndAggregateReturnExpressionTypes(func: FunctionLikeDeclaration, checkMode: CheckMode): Type[] | undefined {
|
||||
const functionFlags = getFunctionFlags(func);
|
||||
const aggregatedTypes: Type[] = [];
|
||||
@ -18595,7 +18602,9 @@ namespace ts {
|
||||
if (aggregatedTypes.length === 0 && !hasReturnWithNoExpression && (hasReturnOfTypeNever || mayReturnNever(func))) {
|
||||
return undefined;
|
||||
}
|
||||
if (strictNullChecks && aggregatedTypes.length && hasReturnWithNoExpression) {
|
||||
if (strictNullChecks && aggregatedTypes.length && hasReturnWithNoExpression &&
|
||||
!(isJavaScriptConstructor(func) && aggregatedTypes.some(t => t.symbol === func.symbol))) {
|
||||
// Javascript "callable constructors", containing eg `if (!(this instanceof A)) return new A()` should not add undefined
|
||||
pushIfUnique(aggregatedTypes, undefinedType);
|
||||
}
|
||||
return aggregatedTypes;
|
||||
|
||||
@ -0,0 +1,28 @@
|
||||
tests/cases/conformance/salsa/a.js(9,1): error TS2322: Type 'undefined' is not assignable to type 'number'.
|
||||
|
||||
|
||||
==== tests/cases/conformance/salsa/a.js (1 errors) ====
|
||||
/** @param {number} x */
|
||||
function C(x) {
|
||||
this.x = x
|
||||
}
|
||||
C.prototype.m = function() {
|
||||
this.y = 12
|
||||
}
|
||||
var c = new C(1)
|
||||
c.x = undefined // should error
|
||||
~~~
|
||||
!!! error TS2322: Type 'undefined' is not assignable to type 'number'.
|
||||
c.y = undefined // ok
|
||||
|
||||
/** @param {number} x */
|
||||
function A(x) {
|
||||
if (!(this instanceof A)) {
|
||||
return new A(x)
|
||||
}
|
||||
this.x = x
|
||||
}
|
||||
var k = A(1)
|
||||
var j = new A(2)
|
||||
k.x === j.x
|
||||
|
||||
69
tests/baselines/reference/constructorFunctionsStrict.symbols
Normal file
69
tests/baselines/reference/constructorFunctionsStrict.symbols
Normal file
@ -0,0 +1,69 @@
|
||||
=== tests/cases/conformance/salsa/a.js ===
|
||||
/** @param {number} x */
|
||||
function C(x) {
|
||||
>C : Symbol(C, Decl(a.js, 0, 0))
|
||||
>x : Symbol(x, Decl(a.js, 1, 11))
|
||||
|
||||
this.x = x
|
||||
>x : Symbol(C.x, Decl(a.js, 1, 15))
|
||||
>x : Symbol(x, Decl(a.js, 1, 11))
|
||||
}
|
||||
C.prototype.m = function() {
|
||||
>C.prototype : Symbol(C.m, Decl(a.js, 3, 1))
|
||||
>C : Symbol(C, Decl(a.js, 0, 0))
|
||||
>prototype : Symbol(Function.prototype, Decl(lib.d.ts, --, --))
|
||||
>m : Symbol(C.m, Decl(a.js, 3, 1))
|
||||
|
||||
this.y = 12
|
||||
>this.y : Symbol(C.y, Decl(a.js, 4, 28))
|
||||
>this : Symbol(C, Decl(a.js, 0, 0))
|
||||
>y : Symbol(C.y, Decl(a.js, 4, 28))
|
||||
}
|
||||
var c = new C(1)
|
||||
>c : Symbol(c, Decl(a.js, 7, 3))
|
||||
>C : Symbol(C, Decl(a.js, 0, 0))
|
||||
|
||||
c.x = undefined // should error
|
||||
>c.x : Symbol(C.x, Decl(a.js, 1, 15))
|
||||
>c : Symbol(c, Decl(a.js, 7, 3))
|
||||
>x : Symbol(C.x, Decl(a.js, 1, 15))
|
||||
>undefined : Symbol(undefined)
|
||||
|
||||
c.y = undefined // ok
|
||||
>c.y : Symbol(C.y, Decl(a.js, 4, 28))
|
||||
>c : Symbol(c, Decl(a.js, 7, 3))
|
||||
>y : Symbol(C.y, Decl(a.js, 4, 28))
|
||||
>undefined : Symbol(undefined)
|
||||
|
||||
/** @param {number} x */
|
||||
function A(x) {
|
||||
>A : Symbol(A, Decl(a.js, 9, 15))
|
||||
>x : Symbol(x, Decl(a.js, 12, 11))
|
||||
|
||||
if (!(this instanceof A)) {
|
||||
>A : Symbol(A, Decl(a.js, 9, 15))
|
||||
|
||||
return new A(x)
|
||||
>A : Symbol(A, Decl(a.js, 9, 15))
|
||||
>x : Symbol(x, Decl(a.js, 12, 11))
|
||||
}
|
||||
this.x = x
|
||||
>x : Symbol(A.x, Decl(a.js, 15, 5))
|
||||
>x : Symbol(x, Decl(a.js, 12, 11))
|
||||
}
|
||||
var k = A(1)
|
||||
>k : Symbol(k, Decl(a.js, 18, 3))
|
||||
>A : Symbol(A, Decl(a.js, 9, 15))
|
||||
|
||||
var j = new A(2)
|
||||
>j : Symbol(j, Decl(a.js, 19, 3))
|
||||
>A : Symbol(A, Decl(a.js, 9, 15))
|
||||
|
||||
k.x === j.x
|
||||
>k.x : Symbol(A.x, Decl(a.js, 15, 5))
|
||||
>k : Symbol(k, Decl(a.js, 18, 3))
|
||||
>x : Symbol(A.x, Decl(a.js, 15, 5))
|
||||
>j.x : Symbol(A.x, Decl(a.js, 15, 5))
|
||||
>j : Symbol(j, Decl(a.js, 19, 3))
|
||||
>x : Symbol(A.x, Decl(a.js, 15, 5))
|
||||
|
||||
94
tests/baselines/reference/constructorFunctionsStrict.types
Normal file
94
tests/baselines/reference/constructorFunctionsStrict.types
Normal file
@ -0,0 +1,94 @@
|
||||
=== tests/cases/conformance/salsa/a.js ===
|
||||
/** @param {number} x */
|
||||
function C(x) {
|
||||
>C : (x: number) => void
|
||||
>x : number
|
||||
|
||||
this.x = x
|
||||
>this.x = x : number
|
||||
>this.x : any
|
||||
>this : any
|
||||
>x : any
|
||||
>x : number
|
||||
}
|
||||
C.prototype.m = function() {
|
||||
>C.prototype.m = function() { this.y = 12} : () => void
|
||||
>C.prototype.m : any
|
||||
>C.prototype : any
|
||||
>C : (x: number) => void
|
||||
>prototype : any
|
||||
>m : any
|
||||
>function() { this.y = 12} : () => void
|
||||
|
||||
this.y = 12
|
||||
>this.y = 12 : 12
|
||||
>this.y : number | undefined
|
||||
>this : { x: number; m: () => void; y: number | undefined; }
|
||||
>y : number | undefined
|
||||
>12 : 12
|
||||
}
|
||||
var c = new C(1)
|
||||
>c : { x: number; m: () => void; y: number | undefined; }
|
||||
>new C(1) : { x: number; m: () => void; y: number | undefined; }
|
||||
>C : (x: number) => void
|
||||
>1 : 1
|
||||
|
||||
c.x = undefined // should error
|
||||
>c.x = undefined : undefined
|
||||
>c.x : number
|
||||
>c : { x: number; m: () => void; y: number | undefined; }
|
||||
>x : number
|
||||
>undefined : undefined
|
||||
|
||||
c.y = undefined // ok
|
||||
>c.y = undefined : undefined
|
||||
>c.y : number | undefined
|
||||
>c : { x: number; m: () => void; y: number | undefined; }
|
||||
>y : number | undefined
|
||||
>undefined : undefined
|
||||
|
||||
/** @param {number} x */
|
||||
function A(x) {
|
||||
>A : (x: number) => typeof A
|
||||
>x : number
|
||||
|
||||
if (!(this instanceof A)) {
|
||||
>!(this instanceof A) : boolean
|
||||
>(this instanceof A) : boolean
|
||||
>this instanceof A : boolean
|
||||
>this : any
|
||||
>A : (x: number) => typeof A
|
||||
|
||||
return new A(x)
|
||||
>new A(x) : { x: number; }
|
||||
>A : (x: number) => typeof A
|
||||
>x : number
|
||||
}
|
||||
this.x = x
|
||||
>this.x = x : number
|
||||
>this.x : any
|
||||
>this : any
|
||||
>x : any
|
||||
>x : number
|
||||
}
|
||||
var k = A(1)
|
||||
>k : { x: number; }
|
||||
>A(1) : { x: number; }
|
||||
>A : (x: number) => typeof A
|
||||
>1 : 1
|
||||
|
||||
var j = new A(2)
|
||||
>j : { x: number; }
|
||||
>new A(2) : { x: number; }
|
||||
>A : (x: number) => typeof A
|
||||
>2 : 2
|
||||
|
||||
k.x === j.x
|
||||
>k.x === j.x : boolean
|
||||
>k.x : number
|
||||
>k : { x: number; }
|
||||
>x : number
|
||||
>j.x : number
|
||||
>j : { x: number; }
|
||||
>x : number
|
||||
|
||||
@ -7,10 +7,10 @@ tests/cases/conformance/jsdoc/b.js(51,17): error TS2352: Type 'SomeDerived' cann
|
||||
Property 'q' is missing in type 'SomeDerived'.
|
||||
tests/cases/conformance/jsdoc/b.js(52,17): error TS2352: Type 'SomeBase' cannot be converted to type 'SomeOther'.
|
||||
Property 'q' is missing in type 'SomeBase'.
|
||||
tests/cases/conformance/jsdoc/b.js(58,1): error TS2322: Type '{ p: string | number | undefined; }' is not assignable to type 'SomeBase'.
|
||||
tests/cases/conformance/jsdoc/b.js(58,1): error TS2322: Type '{ p: string | number; }' is not assignable to type 'SomeBase'.
|
||||
Types of property 'p' are incompatible.
|
||||
Type 'string | number | undefined' is not assignable to type 'number'.
|
||||
Type 'undefined' is not assignable to type 'number'.
|
||||
Type 'string | number' is not assignable to type 'number'.
|
||||
Type 'string' is not assignable to type 'number'.
|
||||
tests/cases/conformance/jsdoc/b.js(66,8): error TS2352: Type 'boolean' cannot be converted to type 'string | number'.
|
||||
tests/cases/conformance/jsdoc/b.js(66,15): error TS2304: Cannot find name 'numOrStr'.
|
||||
tests/cases/conformance/jsdoc/b.js(66,24): error TS1005: '}' expected.
|
||||
@ -97,10 +97,10 @@ tests/cases/conformance/jsdoc/b.js(67,8): error TS2454: Variable 'numOrStr' is u
|
||||
|
||||
someBase = someFakeClass; // Error
|
||||
~~~~~~~~
|
||||
!!! error TS2322: Type '{ p: string | number | undefined; }' is not assignable to type 'SomeBase'.
|
||||
!!! error TS2322: Type '{ p: string | number; }' is not assignable to type 'SomeBase'.
|
||||
!!! error TS2322: Types of property 'p' are incompatible.
|
||||
!!! error TS2322: Type 'string | number | undefined' is not assignable to type 'number'.
|
||||
!!! error TS2322: Type 'undefined' is not assignable to type 'number'.
|
||||
!!! error TS2322: Type 'string | number' is not assignable to type 'number'.
|
||||
!!! error TS2322: Type 'string' is not assignable to type 'number'.
|
||||
someBase = /** @type {SomeBase} */(someFakeClass);
|
||||
|
||||
// Type assertion cannot be a type-predicate type
|
||||
|
||||
@ -108,8 +108,8 @@ var someOther = new SomeOther();
|
||||
>SomeOther : typeof SomeOther
|
||||
|
||||
var someFakeClass = new SomeFakeClass();
|
||||
>someFakeClass : { p: string | number | undefined; }
|
||||
>new SomeFakeClass() : { p: string | number | undefined; }
|
||||
>someFakeClass : { p: string | number; }
|
||||
>new SomeFakeClass() : { p: string | number; }
|
||||
>SomeFakeClass : () => void
|
||||
|
||||
someBase = /** @type {SomeBase} */(someDerived);
|
||||
@ -168,24 +168,24 @@ someOther = /** @type {SomeOther} */(someOther);
|
||||
|
||||
someFakeClass = someBase;
|
||||
>someFakeClass = someBase : SomeBase
|
||||
>someFakeClass : { p: string | number | undefined; }
|
||||
>someFakeClass : { p: string | number; }
|
||||
>someBase : SomeBase
|
||||
|
||||
someFakeClass = someDerived;
|
||||
>someFakeClass = someDerived : SomeDerived
|
||||
>someFakeClass : { p: string | number | undefined; }
|
||||
>someFakeClass : { p: string | number; }
|
||||
>someDerived : SomeDerived
|
||||
|
||||
someBase = someFakeClass; // Error
|
||||
>someBase = someFakeClass : { p: string | number | undefined; }
|
||||
>someBase = someFakeClass : { p: string | number; }
|
||||
>someBase : SomeBase
|
||||
>someFakeClass : { p: string | number | undefined; }
|
||||
>someFakeClass : { p: string | number; }
|
||||
|
||||
someBase = /** @type {SomeBase} */(someFakeClass);
|
||||
>someBase = /** @type {SomeBase} */(someFakeClass) : SomeBase
|
||||
>someBase : SomeBase
|
||||
>(someFakeClass) : SomeBase
|
||||
>someFakeClass : { p: string | number | undefined; }
|
||||
>someFakeClass : { p: string | number; }
|
||||
|
||||
// Type assertion cannot be a type-predicate type
|
||||
/** @type {number | string} */
|
||||
|
||||
29
tests/cases/conformance/salsa/constructorFunctionsStrict.ts
Normal file
29
tests/cases/conformance/salsa/constructorFunctionsStrict.ts
Normal file
@ -0,0 +1,29 @@
|
||||
// @noEmit: true
|
||||
// @allowJs: true
|
||||
// @checkJs: true
|
||||
// @strict: true
|
||||
// @noImplicitThis: false
|
||||
// @Filename: a.js
|
||||
|
||||
|
||||
/** @param {number} x */
|
||||
function C(x) {
|
||||
this.x = x
|
||||
}
|
||||
C.prototype.m = function() {
|
||||
this.y = 12
|
||||
}
|
||||
var c = new C(1)
|
||||
c.x = undefined // should error
|
||||
c.y = undefined // ok
|
||||
|
||||
/** @param {number} x */
|
||||
function A(x) {
|
||||
if (!(this instanceof A)) {
|
||||
return new A(x)
|
||||
}
|
||||
this.x = x
|
||||
}
|
||||
var k = A(1)
|
||||
var j = new A(2)
|
||||
k.x === j.x
|
||||
Loading…
x
Reference in New Issue
Block a user