diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 00498191f287a..b581ca315b4c7 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -5129,7 +5129,8 @@ namespace ts { const excludeModifiers = isUnion ? ModifierFlags.NonPublicAccessibilityModifier : 0; // Flags we want to propagate to the result if they exist in all source symbols let commonFlags = isUnion ? SymbolFlags.None : SymbolFlags.Optional; - let checkFlags = CheckFlags.SyntheticProperty; + let syntheticFlag = CheckFlags.SyntheticMethod; + let checkFlags = 0; for (const current of types) { const type = getApparentType(current); if (type !== unknownType) { @@ -5148,6 +5149,9 @@ namespace ts { (modifiers & ModifierFlags.Protected ? CheckFlags.ContainsProtected : 0) | (modifiers & ModifierFlags.Private ? CheckFlags.ContainsPrivate : 0) | (modifiers & ModifierFlags.Static ? CheckFlags.ContainsStatic : 0); + if (!isMethodLike(prop)) { + syntheticFlag = CheckFlags.SyntheticProperty; + } } else if (isUnion) { checkFlags |= CheckFlags.Partial; @@ -5177,7 +5181,7 @@ namespace ts { propTypes.push(type); } const result = createSymbol(SymbolFlags.Property | commonFlags, name); - result.checkFlags = checkFlags; + result.checkFlags = syntheticFlag | checkFlags; result.containingType = containingType; result.declarations = declarations; result.type = isUnion ? getUnionType(propTypes) : getIntersectionType(propTypes); @@ -8630,7 +8634,7 @@ namespace ts { // Invoke the callback for each underlying property symbol of the given symbol and return the first // value that isn't undefined. function forEachProperty(prop: Symbol, callback: (p: Symbol) => T): T { - if (getCheckFlags(prop) & CheckFlags.SyntheticProperty) { + if (getCheckFlags(prop) & CheckFlags.Synthetic) { for (const t of (prop).containingType.types) { const p = getPropertyOfType(t, prop.name); const result = p && forEachProperty(p, callback); @@ -13112,7 +13116,7 @@ namespace ts { const flags = getCombinedModifierFlags(s.valueDeclaration); return s.parent && s.parent.flags & SymbolFlags.Class ? flags : flags & ~ModifierFlags.AccessibilityModifier; } - if (getCheckFlags(s) & CheckFlags.SyntheticProperty) { + if (getCheckFlags(s) & CheckFlags.Synthetic) { const checkFlags = (s).checkFlags; const accessModifier = checkFlags & CheckFlags.ContainsPrivate ? ModifierFlags.Private : checkFlags & CheckFlags.ContainsPublic ? ModifierFlags.Public : @@ -13130,6 +13134,10 @@ namespace ts { return s.valueDeclaration ? getCombinedNodeFlags(s.valueDeclaration) : 0; } + function isMethodLike(symbol: Symbol) { + return !!(symbol.flags & SymbolFlags.Method || getCheckFlags(symbol) & CheckFlags.SyntheticMethod); + } + /** * Check whether the requested property access is valid. * Returns true if node is a valid property access, and false otherwise. @@ -13159,11 +13167,11 @@ namespace ts { // where this references the constructor function object of a derived class, // a super property access is permitted and must specify a public static member function of the base class. if (languageVersion < ScriptTarget.ES2015) { - const propKind = getDeclarationKindFromSymbol(prop); - if (propKind !== SyntaxKind.MethodDeclaration && propKind !== SyntaxKind.MethodSignature) { - // `prop` refers to a *property* declared in the super class - // rather than a *method*, so it does not satisfy the above criteria. - + const hasNonMethodDeclaration = forEachProperty(prop, p => { + const propKind = getDeclarationKindFromSymbol(p); + return propKind !== SyntaxKind.MethodDeclaration && propKind !== SyntaxKind.MethodSignature; + }); + if (hasNonMethodDeclaration) { error(errorNode, Diagnostics.Only_public_and_protected_methods_of_the_base_class_are_accessible_via_the_super_keyword); return false; } @@ -19697,7 +19705,7 @@ namespace ts { else { // derived overrides base. const derivedDeclarationFlags = getDeclarationModifierFlagsFromSymbol(derived); - if ((baseDeclarationFlags & ModifierFlags.Private) || (derivedDeclarationFlags & ModifierFlags.Private)) { + if (baseDeclarationFlags & ModifierFlags.Private || derivedDeclarationFlags & ModifierFlags.Private) { // either base or derived property is private - not override, skip it continue; } @@ -19707,28 +19715,24 @@ namespace ts { continue; } - if ((base.flags & derived.flags & SymbolFlags.Method) || ((base.flags & SymbolFlags.PropertyOrAccessor) && (derived.flags & SymbolFlags.PropertyOrAccessor))) { + if (isMethodLike(base) && isMethodLike(derived) || base.flags & SymbolFlags.PropertyOrAccessor && derived.flags & SymbolFlags.PropertyOrAccessor) { // method is overridden with method or property/accessor is overridden with property/accessor - correct case continue; } let errorMessage: DiagnosticMessage; - if (base.flags & SymbolFlags.Method) { + if (isMethodLike(base)) { if (derived.flags & SymbolFlags.Accessor) { errorMessage = Diagnostics.Class_0_defines_instance_member_function_1_but_extended_class_2_defines_it_as_instance_member_accessor; } else { - Debug.assert((derived.flags & SymbolFlags.Property) !== 0); errorMessage = Diagnostics.Class_0_defines_instance_member_function_1_but_extended_class_2_defines_it_as_instance_member_property; } } else if (base.flags & SymbolFlags.Property) { - Debug.assert((derived.flags & SymbolFlags.Method) !== 0); errorMessage = Diagnostics.Class_0_defines_instance_member_property_1_but_extended_class_2_defines_it_as_instance_member_function; } else { - Debug.assert((base.flags & SymbolFlags.Accessor) !== 0); - Debug.assert((derived.flags & SymbolFlags.Method) !== 0); errorMessage = Diagnostics.Class_0_defines_instance_member_accessor_1_but_extended_class_2_defines_it_as_instance_member_function; } @@ -21387,7 +21391,7 @@ namespace ts { } function getRootSymbols(symbol: Symbol): Symbol[] { - if (getCheckFlags(symbol) & CheckFlags.SyntheticProperty) { + if (getCheckFlags(symbol) & CheckFlags.Synthetic) { const symbols: Symbol[] = []; const name = symbol.name; forEach(getSymbolLinks(symbol).containingType.types, t => { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 97b12aeb71200..e36731ff363f2 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2811,13 +2811,15 @@ namespace ts { export const enum CheckFlags { Instantiated = 1 << 0, // Instantiated symbol SyntheticProperty = 1 << 1, // Property in union or intersection type - Readonly = 1 << 2, // Readonly transient symbol - Partial = 1 << 3, // Synthetic property present in some but not all constituents - HasNonUniformType = 1 << 4, // Synthetic property with non-uniform type in constituents - ContainsPublic = 1 << 5, // Synthetic property with public constituent(s) - ContainsProtected = 1 << 6, // Synthetic property with protected constituent(s) - ContainsPrivate = 1 << 7, // Synthetic property with private constituent(s) - ContainsStatic = 1 << 8, // Synthetic property with static constituent(s) + SyntheticMethod = 1 << 2, // Method in union or intersection type + Readonly = 1 << 3, // Readonly transient symbol + Partial = 1 << 4, // Synthetic property present in some but not all constituents + HasNonUniformType = 1 << 5, // Synthetic property with non-uniform type in constituents + ContainsPublic = 1 << 6, // Synthetic property with public constituent(s) + ContainsProtected = 1 << 7, // Synthetic property with protected constituent(s) + ContainsPrivate = 1 << 8, // Synthetic property with private constituent(s) + ContainsStatic = 1 << 9, // Synthetic property with static constituent(s) + Synthetic = SyntheticProperty | SyntheticMethod } /* @internal */ diff --git a/src/services/findAllReferences.ts b/src/services/findAllReferences.ts index bc1e50391c854..2c999ba9fa059 100644 --- a/src/services/findAllReferences.ts +++ b/src/services/findAllReferences.ts @@ -281,7 +281,7 @@ namespace ts.FindAllReferences { // if this symbol is visible from its parent container, e.g. exported, then bail out // if symbol correspond to the union property - bail out - if (symbol.parent || (symbol.flags & SymbolFlags.Transient && (symbol).checkFlags & CheckFlags.SyntheticProperty)) { + if (symbol.parent || (symbol.flags & SymbolFlags.Transient && (symbol).checkFlags & CheckFlags.Synthetic)) { return undefined; } diff --git a/src/services/symbolDisplay.ts b/src/services/symbolDisplay.ts index b7b739b424470..b457165b67f70 100644 --- a/src/services/symbolDisplay.ts +++ b/src/services/symbolDisplay.ts @@ -52,7 +52,7 @@ namespace ts.SymbolDisplay { if (flags & SymbolFlags.Constructor) return ScriptElementKind.constructorImplementationElement; if (flags & SymbolFlags.Property) { - if (flags & SymbolFlags.Transient && (symbol).checkFlags & CheckFlags.SyntheticProperty) { + if (flags & SymbolFlags.Transient && (symbol).checkFlags & CheckFlags.Synthetic) { // If union property is result of union of non method (property/accessors/variables), it is labeled as property const unionPropertyKind = forEach(typeChecker.getRootSymbols(symbol), rootSymbol => { const rootSymbolFlags = rootSymbol.getFlags(); diff --git a/tests/baselines/reference/overrideBaseIntersectionMethod.js b/tests/baselines/reference/overrideBaseIntersectionMethod.js new file mode 100644 index 0000000000000..26613f07469ab --- /dev/null +++ b/tests/baselines/reference/overrideBaseIntersectionMethod.js @@ -0,0 +1,83 @@ +//// [overrideBaseIntersectionMethod.ts] + +// Repro from #14615 + +type Constructor = new (...args: any[]) => T; + +const WithLocation = >(Base: T) => class extends Base { + getLocation(): [number, number] { + const [x,y] = super.getLocation(); + return [this.x | x, this.y | y]; + } +} + +class Point { + constructor(public x: number, public y: number) { } + getLocation(): [number, number] { + return [0,0]; + } +} + +class Foo extends WithLocation(Point) { + calculate() { + return this.x + this.y; + } + getLocation() { + return super.getLocation() + } + whereAmI() { + return this.getLocation(); + } +} + + +//// [overrideBaseIntersectionMethod.js] +// Repro from #14615 +"use strict"; +var __extends = (this && this.__extends) || (function () { + var extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +var WithLocation = function (Base) { return (function (_super) { + __extends(class_1, _super); + function class_1() { + return _super !== null && _super.apply(this, arguments) || this; + } + class_1.prototype.getLocation = function () { + var _a = _super.prototype.getLocation.call(this), x = _a[0], y = _a[1]; + return [this.x | x, this.y | y]; + }; + return class_1; +}(Base)); }; +var Point = (function () { + function Point(x, y) { + this.x = x; + this.y = y; + } + Point.prototype.getLocation = function () { + return [0, 0]; + }; + return Point; +}()); +var Foo = (function (_super) { + __extends(Foo, _super); + function Foo() { + return _super !== null && _super.apply(this, arguments) || this; + } + Foo.prototype.calculate = function () { + return this.x + this.y; + }; + Foo.prototype.getLocation = function () { + return _super.prototype.getLocation.call(this); + }; + Foo.prototype.whereAmI = function () { + return this.getLocation(); + }; + return Foo; +}(WithLocation(Point))); diff --git a/tests/baselines/reference/overrideBaseIntersectionMethod.symbols b/tests/baselines/reference/overrideBaseIntersectionMethod.symbols new file mode 100644 index 0000000000000..45a8454340821 --- /dev/null +++ b/tests/baselines/reference/overrideBaseIntersectionMethod.symbols @@ -0,0 +1,88 @@ +=== tests/cases/compiler/overrideBaseIntersectionMethod.ts === + +// Repro from #14615 + +type Constructor = new (...args: any[]) => T; +>Constructor : Symbol(Constructor, Decl(overrideBaseIntersectionMethod.ts, 0, 0)) +>T : Symbol(T, Decl(overrideBaseIntersectionMethod.ts, 3, 17)) +>args : Symbol(args, Decl(overrideBaseIntersectionMethod.ts, 3, 27)) +>T : Symbol(T, Decl(overrideBaseIntersectionMethod.ts, 3, 17)) + +const WithLocation = >(Base: T) => class extends Base { +>WithLocation : Symbol(WithLocation, Decl(overrideBaseIntersectionMethod.ts, 5, 5)) +>T : Symbol(T, Decl(overrideBaseIntersectionMethod.ts, 5, 22)) +>Constructor : Symbol(Constructor, Decl(overrideBaseIntersectionMethod.ts, 0, 0)) +>Point : Symbol(Point, Decl(overrideBaseIntersectionMethod.ts, 10, 1)) +>Base : Symbol(Base, Decl(overrideBaseIntersectionMethod.ts, 5, 52)) +>T : Symbol(T, Decl(overrideBaseIntersectionMethod.ts, 5, 22)) +>Base : Symbol(Base, Decl(overrideBaseIntersectionMethod.ts, 5, 52)) + + getLocation(): [number, number] { +>getLocation : Symbol((Anonymous class).getLocation, Decl(overrideBaseIntersectionMethod.ts, 5, 84)) + + const [x,y] = super.getLocation(); +>x : Symbol(x, Decl(overrideBaseIntersectionMethod.ts, 7, 11)) +>y : Symbol(y, Decl(overrideBaseIntersectionMethod.ts, 7, 13)) +>super.getLocation : Symbol(Point.getLocation, Decl(overrideBaseIntersectionMethod.ts, 13, 53)) +>super : Symbol(Point, Decl(overrideBaseIntersectionMethod.ts, 10, 1)) +>getLocation : Symbol(Point.getLocation, Decl(overrideBaseIntersectionMethod.ts, 13, 53)) + + return [this.x | x, this.y | y]; +>this.x : Symbol(Point.x, Decl(overrideBaseIntersectionMethod.ts, 13, 14)) +>this : Symbol((Anonymous class), Decl(overrideBaseIntersectionMethod.ts, 5, 63)) +>x : Symbol(Point.x, Decl(overrideBaseIntersectionMethod.ts, 13, 14)) +>x : Symbol(x, Decl(overrideBaseIntersectionMethod.ts, 7, 11)) +>this.y : Symbol(Point.y, Decl(overrideBaseIntersectionMethod.ts, 13, 31)) +>this : Symbol((Anonymous class), Decl(overrideBaseIntersectionMethod.ts, 5, 63)) +>y : Symbol(Point.y, Decl(overrideBaseIntersectionMethod.ts, 13, 31)) +>y : Symbol(y, Decl(overrideBaseIntersectionMethod.ts, 7, 13)) + } +} + +class Point { +>Point : Symbol(Point, Decl(overrideBaseIntersectionMethod.ts, 10, 1)) + + constructor(public x: number, public y: number) { } +>x : Symbol(Point.x, Decl(overrideBaseIntersectionMethod.ts, 13, 14)) +>y : Symbol(Point.y, Decl(overrideBaseIntersectionMethod.ts, 13, 31)) + + getLocation(): [number, number] { +>getLocation : Symbol(Point.getLocation, Decl(overrideBaseIntersectionMethod.ts, 13, 53)) + + return [0,0]; + } +} + +class Foo extends WithLocation(Point) { +>Foo : Symbol(Foo, Decl(overrideBaseIntersectionMethod.ts, 17, 1)) +>WithLocation : Symbol(WithLocation, Decl(overrideBaseIntersectionMethod.ts, 5, 5)) +>Point : Symbol(Point, Decl(overrideBaseIntersectionMethod.ts, 10, 1)) + + calculate() { +>calculate : Symbol(Foo.calculate, Decl(overrideBaseIntersectionMethod.ts, 19, 39)) + + return this.x + this.y; +>this.x : Symbol(Point.x, Decl(overrideBaseIntersectionMethod.ts, 13, 14)) +>this : Symbol(Foo, Decl(overrideBaseIntersectionMethod.ts, 17, 1)) +>x : Symbol(Point.x, Decl(overrideBaseIntersectionMethod.ts, 13, 14)) +>this.y : Symbol(Point.y, Decl(overrideBaseIntersectionMethod.ts, 13, 31)) +>this : Symbol(Foo, Decl(overrideBaseIntersectionMethod.ts, 17, 1)) +>y : Symbol(Point.y, Decl(overrideBaseIntersectionMethod.ts, 13, 31)) + } + getLocation() { +>getLocation : Symbol(Foo.getLocation, Decl(overrideBaseIntersectionMethod.ts, 22, 3)) + + return super.getLocation() +>super.getLocation : Symbol(getLocation, Decl(overrideBaseIntersectionMethod.ts, 5, 84), Decl(overrideBaseIntersectionMethod.ts, 13, 53)) +>getLocation : Symbol(getLocation, Decl(overrideBaseIntersectionMethod.ts, 5, 84), Decl(overrideBaseIntersectionMethod.ts, 13, 53)) + } + whereAmI() { +>whereAmI : Symbol(Foo.whereAmI, Decl(overrideBaseIntersectionMethod.ts, 25, 3)) + + return this.getLocation(); +>this.getLocation : Symbol(Foo.getLocation, Decl(overrideBaseIntersectionMethod.ts, 22, 3)) +>this : Symbol(Foo, Decl(overrideBaseIntersectionMethod.ts, 17, 1)) +>getLocation : Symbol(Foo.getLocation, Decl(overrideBaseIntersectionMethod.ts, 22, 3)) + } +} + diff --git a/tests/baselines/reference/overrideBaseIntersectionMethod.types b/tests/baselines/reference/overrideBaseIntersectionMethod.types new file mode 100644 index 0000000000000..3e31211bdbf21 --- /dev/null +++ b/tests/baselines/reference/overrideBaseIntersectionMethod.types @@ -0,0 +1,102 @@ +=== tests/cases/compiler/overrideBaseIntersectionMethod.ts === + +// Repro from #14615 + +type Constructor = new (...args: any[]) => T; +>Constructor : Constructor +>T : T +>args : any[] +>T : T + +const WithLocation = >(Base: T) => class extends Base { +>WithLocation : >(Base: T) => { new (...args: any[]): (Anonymous class); prototype: .(Anonymous class); } & T +>>(Base: T) => class extends Base { getLocation(): [number, number] { const [x,y] = super.getLocation(); return [this.x | x, this.y | y]; }} : >(Base: T) => { new (...args: any[]): (Anonymous class); prototype: .(Anonymous class); } & T +>T : T +>Constructor : Constructor +>Point : Point +>Base : T +>T : T +>class extends Base { getLocation(): [number, number] { const [x,y] = super.getLocation(); return [this.x | x, this.y | y]; }} : { new (...args: any[]): (Anonymous class); prototype: .(Anonymous class); } & T +>Base : Point + + getLocation(): [number, number] { +>getLocation : () => [number, number] + + const [x,y] = super.getLocation(); +>x : number +>y : number +>super.getLocation() : [number, number] +>super.getLocation : () => [number, number] +>super : Point +>getLocation : () => [number, number] + + return [this.x | x, this.y | y]; +>[this.x | x, this.y | y] : [number, number] +>this.x | x : number +>this.x : number +>this : this +>x : number +>x : number +>this.y | y : number +>this.y : number +>this : this +>y : number +>y : number + } +} + +class Point { +>Point : Point + + constructor(public x: number, public y: number) { } +>x : number +>y : number + + getLocation(): [number, number] { +>getLocation : () => [number, number] + + return [0,0]; +>[0,0] : [number, number] +>0 : 0 +>0 : 0 + } +} + +class Foo extends WithLocation(Point) { +>Foo : Foo +>WithLocation(Point) : .(Anonymous class) & Point +>WithLocation : >(Base: T) => { new (...args: any[]): (Anonymous class); prototype: .(Anonymous class); } & T +>Point : typeof Point + + calculate() { +>calculate : () => number + + return this.x + this.y; +>this.x + this.y : number +>this.x : number +>this : this +>x : number +>this.y : number +>this : this +>y : number + } + getLocation() { +>getLocation : () => [number, number] + + return super.getLocation() +>super.getLocation() : [number, number] +>super.getLocation : () => [number, number] +>super : .(Anonymous class) & Point +>getLocation : () => [number, number] + } + whereAmI() { +>whereAmI : () => [number, number] + + return this.getLocation(); +>this.getLocation() : [number, number] +>this.getLocation : () => [number, number] +>this : this +>getLocation : () => [number, number] + } +} + diff --git a/tests/cases/compiler/overrideBaseIntersectionMethod.ts b/tests/cases/compiler/overrideBaseIntersectionMethod.ts new file mode 100644 index 0000000000000..99cd4664bc562 --- /dev/null +++ b/tests/cases/compiler/overrideBaseIntersectionMethod.ts @@ -0,0 +1,31 @@ +// @strict: true + +// Repro from #14615 + +type Constructor = new (...args: any[]) => T; + +const WithLocation = >(Base: T) => class extends Base { + getLocation(): [number, number] { + const [x,y] = super.getLocation(); + return [this.x | x, this.y | y]; + } +} + +class Point { + constructor(public x: number, public y: number) { } + getLocation(): [number, number] { + return [0,0]; + } +} + +class Foo extends WithLocation(Point) { + calculate() { + return this.x + this.y; + } + getLocation() { + return super.getLocation() + } + whereAmI() { + return this.getLocation(); + } +}