Check base type for special property declarations (#23671)

If the base type has a property by that name, add it to the list
constructor types to make it as authoritative as special assignments
found in the constructor.
This commit is contained in:
Nathan Shively-Sanders 2018-04-26 08:14:22 -07:00 committed by GitHub
parent aa102435b3
commit 1541599ea0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 270 additions and 2 deletions

View File

@ -4430,7 +4430,7 @@ namespace ts {
const special = getSpecialPropertyAssignmentKind(expression);
if (special === SpecialPropertyAssignmentKind.ThisProperty) {
const thisContainer = getThisContainer(expression, /*includeArrowFunctions*/ false);
// Properties defined in a constructor (or javascript constructor function) don't get undefined added.
// Properties defined in a constructor (or base constructor, or javascript constructor function) don't get undefined added.
// Function expressions that are assigned to the prototype count as methods.
declarationInConstructor = thisContainer.kind === SyntaxKind.Constructor ||
thisContainer.kind === SyntaxKind.FunctionDeclaration ||
@ -4500,7 +4500,14 @@ namespace ts {
}
let type = jsDocType;
if (!type) {
// use only the constructor types unless only null | undefined (including widening variants) were assigned there
// use only the constructor types unless they were only assigned null | undefined (including widening variants)
if (definedInMethod) {
const propType = getTypeOfSpecialPropertyOfBaseType(symbol);
if (propType) {
(constructorTypes || (constructorTypes = [])).push(propType);
definedInConstructor = true;
}
}
const sourceTypes = some(constructorTypes, t => !!(t.flags & ~(TypeFlags.Nullable | TypeFlags.ContainsWideningType))) ? constructorTypes : types;
type = getUnionType(sourceTypes, UnionReduction.Subtype);
}
@ -4514,6 +4521,20 @@ namespace ts {
return widened;
}
/** check for definition in base class if any declaration is in a class */
function getTypeOfSpecialPropertyOfBaseType(specialProperty: Symbol) {
const parentDeclaration = forEach(specialProperty.declarations, d => {
const parent = getThisContainer(d, /*includeArrowFunctions*/ false).parent;
return isClassLike(parent) && parent;
});
if (parentDeclaration) {
const classType = getDeclaredTypeOfSymbol(getSymbolOfNode(parentDeclaration)) as InterfaceType;
const baseClassType = classType && getBaseTypes(classType)[0];
if (baseClassType) {
return getTypeOfPropertyOfType(baseClassType, specialProperty.escapedName);
}
}
}
// Return the type implied by a binding pattern element. This is the type of the initializer of the element if
// one is present. Otherwise, if the element is itself a binding pattern, it is the type implied by the binding

View File

@ -0,0 +1,25 @@
=== tests/cases/conformance/salsa/a.js ===
class Base {
>Base : Symbol(Base, Decl(a.js, 0, 0))
constructor() {
this.p = 1
>this.p : Symbol(Base.p, Decl(a.js, 1, 19))
>this : Symbol(Base, Decl(a.js, 0, 0))
>p : Symbol(Base.p, Decl(a.js, 1, 19))
}
}
class Derived extends Base {
>Derived : Symbol(Derived, Decl(a.js, 4, 1))
>Base : Symbol(Base, Decl(a.js, 0, 0))
m() {
>m : Symbol(Derived.m, Decl(a.js, 5, 28))
this.p = 1
>this.p : Symbol(Derived.p, Decl(a.js, 6, 9))
>this : Symbol(Derived, Decl(a.js, 4, 1))
>p : Symbol(Derived.p, Decl(a.js, 6, 9))
}
}

View File

@ -0,0 +1,29 @@
=== tests/cases/conformance/salsa/a.js ===
class Base {
>Base : Base
constructor() {
this.p = 1
>this.p = 1 : 1
>this.p : number
>this : this
>p : number
>1 : 1
}
}
class Derived extends Base {
>Derived : Derived
>Base : Base
m() {
>m : () => void
this.p = 1
>this.p = 1 : 1
>this.p : number
>this : this
>p : number
>1 : 1
}
}

View File

@ -0,0 +1,28 @@
=== tests/cases/conformance/salsa/a.js ===
class Base {
>Base : Symbol(Base, Decl(a.js, 0, 0))
m() {
>m : Symbol(Base.m, Decl(a.js, 0, 12))
this.p = 1
>this.p : Symbol(Base.p, Decl(a.js, 1, 9))
>this : Symbol(Base, Decl(a.js, 0, 0))
>p : Symbol(Base.p, Decl(a.js, 1, 9))
}
}
class Derived extends Base {
>Derived : Symbol(Derived, Decl(a.js, 4, 1))
>Base : Symbol(Base, Decl(a.js, 0, 0))
m() {
>m : Symbol(Derived.m, Decl(a.js, 5, 28))
// should be OK, and p should have type number | undefined from its base
this.p = 1
>this.p : Symbol(Derived.p, Decl(a.js, 6, 9))
>this : Symbol(Derived, Decl(a.js, 4, 1))
>p : Symbol(Derived.p, Decl(a.js, 6, 9))
}
}

View File

@ -0,0 +1,32 @@
=== tests/cases/conformance/salsa/a.js ===
class Base {
>Base : Base
m() {
>m : () => void
this.p = 1
>this.p = 1 : 1
>this.p : number | undefined
>this : this
>p : number | undefined
>1 : 1
}
}
class Derived extends Base {
>Derived : Derived
>Base : Base
m() {
>m : () => void
// should be OK, and p should have type number | undefined from its base
this.p = 1
>this.p = 1 : 1
>this.p : number | undefined
>this : this
>p : number | undefined
>1 : 1
}
}

View File

@ -0,0 +1,37 @@
=== tests/cases/conformance/salsa/a.js ===
class Base {
>Base : Symbol(Base, Decl(a.js, 0, 0))
m() {
>m : Symbol(Base.m, Decl(a.js, 0, 12))
this.p = 1
>this.p : Symbol(Base.p, Decl(a.js, 1, 9))
>this : Symbol(Base, Decl(a.js, 0, 0))
>p : Symbol(Base.p, Decl(a.js, 1, 9))
}
}
class Derived extends Base {
>Derived : Symbol(Derived, Decl(a.js, 4, 1))
>Base : Symbol(Base, Decl(a.js, 0, 0))
constructor() {
super();
>super : Symbol(Base, Decl(a.js, 0, 0))
// should be OK, and p should have type number from this assignment
this.p = 1
>this.p : Symbol(Derived.p, Decl(a.js, 7, 16))
>this : Symbol(Derived, Decl(a.js, 4, 1))
>p : Symbol(Derived.p, Decl(a.js, 7, 16))
}
test() {
>test : Symbol(Derived.test, Decl(a.js, 10, 5))
return this.p
>this.p : Symbol(Derived.p, Decl(a.js, 7, 16))
>this : Symbol(Derived, Decl(a.js, 4, 1))
>p : Symbol(Derived.p, Decl(a.js, 7, 16))
}
}

View File

@ -0,0 +1,42 @@
=== tests/cases/conformance/salsa/a.js ===
class Base {
>Base : Base
m() {
>m : () => void
this.p = 1
>this.p = 1 : 1
>this.p : number | undefined
>this : this
>p : number | undefined
>1 : 1
}
}
class Derived extends Base {
>Derived : Derived
>Base : Base
constructor() {
super();
>super() : void
>super : typeof Base
// should be OK, and p should have type number from this assignment
this.p = 1
>this.p = 1 : 1
>this.p : number
>this : this
>p : number
>1 : 1
}
test() {
>test : () => number
return this.p
>this.p : number
>this : this
>p : number
}
}

View File

@ -0,0 +1,16 @@
// @noEmit: true
// @allowJs: true
// @checkJs: true
// @noImplicitAny: true
// @strictNullChecks: true
// @Filename: a.js
class Base {
constructor() {
this.p = 1
}
}
class Derived extends Base {
m() {
this.p = 1
}
}

View File

@ -0,0 +1,17 @@
// @noEmit: true
// @allowJs: true
// @checkJs: true
// @noImplicitAny: true
// @strictNullChecks: true
// @Filename: a.js
class Base {
m() {
this.p = 1
}
}
class Derived extends Base {
m() {
// should be OK, and p should have type number | undefined from its base
this.p = 1
}
}

View File

@ -0,0 +1,21 @@
// @noEmit: true
// @allowJs: true
// @checkJs: true
// @noImplicitAny: true
// @strictNullChecks: true
// @Filename: a.js
class Base {
m() {
this.p = 1
}
}
class Derived extends Base {
constructor() {
super();
// should be OK, and p should have type number from this assignment
this.p = 1
}
test() {
return this.p
}
}