mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-12 12:57:11 -06:00
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:
parent
aa102435b3
commit
1541599ea0
@ -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
|
||||
|
||||
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user