From 6f33db2f560f27be3cb06318972964f46eb639f9 Mon Sep 17 00:00:00 2001 From: Orta Therox Date: Thu, 8 Aug 2019 19:08:44 -0400 Subject: [PATCH] Allow looking for properties inside the base of a mixin'd class --- src/compiler/checker.ts | 12 +- src/compiler/utilities.ts | 4 + src/services/services.ts | 1 - .../reference/api/tsserverlibrary.d.ts | 1 + tests/baselines/reference/api/typescript.d.ts | 1 + .../expressionPropertyLookupIncludesMixins.js | 90 +++++++++++++++ ...essionPropertyLookupIncludesMixins.symbols | 105 ++++++++++++++++++ ...pressionPropertyLookupIncludesMixins.types | 83 ++++++++++++++ .../expressionPropertyLookupIncludesMixins.ts | 34 ++++++ 9 files changed, 329 insertions(+), 2 deletions(-) create mode 100644 tests/baselines/reference/expressionPropertyLookupIncludesMixins.js create mode 100644 tests/baselines/reference/expressionPropertyLookupIncludesMixins.symbols create mode 100644 tests/baselines/reference/expressionPropertyLookupIncludesMixins.types create mode 100644 tests/cases/compiler/expressionPropertyLookupIncludesMixins.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index e3da199310027..a8f2909ee6913 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -20645,10 +20645,20 @@ namespace ts { } return apparentType; } - const prop = getPropertyOfType(apparentType, right.escapedText); + let prop = getPropertyOfType(apparentType, right.escapedText); if (isIdentifier(left) && parentSymbol && !(prop && isConstEnumOrConstEnumOnlyModule(prop))) { markAliasReferenced(parentSymbol, node); } + + // Check for properties added via mixer-style base types also + if (!prop && typeIsInterfaceType(apparentType)) { + for (const base of apparentType.resolvedBaseTypes) { + if (!prop) { + prop = getPropertyOfType(base, right.escapedText); + } + } + } + if (!prop) { const indexInfo = assignmentKind === AssignmentKind.None || !isGenericObjectType(leftType) || isThisTypeParameter(leftType) ? getIndexInfoOfType(apparentType, IndexKind.String) : undefined; if (!(indexInfo && indexInfo.type)) { diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 4ca13e663ab18..5670fdbfe1f19 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -5440,6 +5440,10 @@ namespace ts { ? node.parent.constraint : undefined; } + + export function typeIsInterfaceType(type: Type): type is InterfaceType { + return !!type && hasProperty(type as InterfaceType, "typeParameters") && hasProperty(type as InterfaceType, "resolvedBaseTypes"); + } } // Simple node tests of the form `node.kind === SyntaxKind.Foo`. diff --git a/src/services/services.ts b/src/services/services.ts index 42b3ac3b31dd5..df9997803f957 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -420,7 +420,6 @@ namespace ts { getDefault(): Type | undefined { return this.checker.getDefaultFromTypeParameter(this); } - isUnion(): this is UnionType { return !!(this.flags & TypeFlags.Union); } diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 2f5960f9c215d..d4bbe27076704 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -3381,6 +3381,7 @@ declare namespace ts { */ function getEffectiveTypeParameterDeclarations(node: DeclarationWithTypeParameters): ReadonlyArray; function getEffectiveConstraintOfTypeParameter(node: TypeParameterDeclaration): TypeNode | undefined; + function typeIsInterfaceType(type: Type): type is InterfaceType; } declare namespace ts { function isNumericLiteral(node: Node): node is NumericLiteral; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 0b7ee8d086de3..6ad01ff151988 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -3381,6 +3381,7 @@ declare namespace ts { */ function getEffectiveTypeParameterDeclarations(node: DeclarationWithTypeParameters): ReadonlyArray; function getEffectiveConstraintOfTypeParameter(node: TypeParameterDeclaration): TypeNode | undefined; + function typeIsInterfaceType(type: Type): type is InterfaceType; } declare namespace ts { function isNumericLiteral(node: Node): node is NumericLiteral; diff --git a/tests/baselines/reference/expressionPropertyLookupIncludesMixins.js b/tests/baselines/reference/expressionPropertyLookupIncludesMixins.js new file mode 100644 index 0000000000000..e110ad341f170 --- /dev/null +++ b/tests/baselines/reference/expressionPropertyLookupIncludesMixins.js @@ -0,0 +1,90 @@ +//// [expressionPropertyLookupIncludesMixins.ts] +// https://github.com/microsoft/TypeScript/issues/31426 + +export type AnyFunction = (...input : any[]) => A +export type AnyConstructor = new (...input : any[]) => A +export type Mixin = InstanceType> + +export const Box = >(base : T) => +class Box extends base { + value : any +} +export interface Box extends Mixin {} + +export const Observable = >(base : T) => +class Observable extends base { + observe () : IQuark { + return + } +} +export interface Observable extends Mixin {} + +export const CQuark = >(base : T) => +class Quark extends base { + + observe () : Quark { + // No error here! + this.value + + + return + } +} +export interface IQuark extends Mixin {} + +const test = (a : IQuark) => a.value // <-- Should not error + + +//// [expressionPropertyLookupIncludesMixins.js] +"use strict"; +// https://github.com/microsoft/TypeScript/issues/31426 +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + 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 extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +exports.__esModule = true; +exports.Box = function (base) { + return /** @class */ (function (_super) { + __extends(Box, _super); + function Box() { + return _super !== null && _super.apply(this, arguments) || this; + } + return Box; + }(base)); +}; +exports.Observable = function (base) { + return /** @class */ (function (_super) { + __extends(Observable, _super); + function Observable() { + return _super !== null && _super.apply(this, arguments) || this; + } + Observable.prototype.observe = function () { + return; + }; + return Observable; + }(base)); +}; +exports.CQuark = function (base) { + return /** @class */ (function (_super) { + __extends(Quark, _super); + function Quark() { + return _super !== null && _super.apply(this, arguments) || this; + } + Quark.prototype.observe = function () { + // No error here! + this.value; + return; + }; + return Quark; + }(base)); +}; +var test = function (a) { return a.value; }; // <-- Should not error diff --git a/tests/baselines/reference/expressionPropertyLookupIncludesMixins.symbols b/tests/baselines/reference/expressionPropertyLookupIncludesMixins.symbols new file mode 100644 index 0000000000000..5f07027b7fe29 --- /dev/null +++ b/tests/baselines/reference/expressionPropertyLookupIncludesMixins.symbols @@ -0,0 +1,105 @@ +=== tests/cases/compiler/expressionPropertyLookupIncludesMixins.ts === +// https://github.com/microsoft/TypeScript/issues/31426 + +export type AnyFunction = (...input : any[]) => A +>AnyFunction : Symbol(AnyFunction, Decl(expressionPropertyLookupIncludesMixins.ts, 0, 0)) +>A : Symbol(A, Decl(expressionPropertyLookupIncludesMixins.ts, 2, 24)) +>input : Symbol(input, Decl(expressionPropertyLookupIncludesMixins.ts, 2, 43)) +>A : Symbol(A, Decl(expressionPropertyLookupIncludesMixins.ts, 2, 24)) + +export type AnyConstructor = new (...input : any[]) => A +>AnyConstructor : Symbol(AnyConstructor, Decl(expressionPropertyLookupIncludesMixins.ts, 2, 65)) +>A : Symbol(A, Decl(expressionPropertyLookupIncludesMixins.ts, 3, 27)) +>input : Symbol(input, Decl(expressionPropertyLookupIncludesMixins.ts, 3, 47)) +>A : Symbol(A, Decl(expressionPropertyLookupIncludesMixins.ts, 3, 27)) + +export type Mixin = InstanceType> +>Mixin : Symbol(Mixin, Decl(expressionPropertyLookupIncludesMixins.ts, 3, 69)) +>T : Symbol(T, Decl(expressionPropertyLookupIncludesMixins.ts, 4, 18)) +>AnyFunction : Symbol(AnyFunction, Decl(expressionPropertyLookupIncludesMixins.ts, 0, 0)) +>InstanceType : Symbol(InstanceType, Decl(lib.es5.d.ts, --, --)) +>ReturnType : Symbol(ReturnType, Decl(lib.es5.d.ts, --, --)) +>T : Symbol(T, Decl(expressionPropertyLookupIncludesMixins.ts, 4, 18)) + +export const Box = >(base : T) => +>Box : Symbol(Box, Decl(expressionPropertyLookupIncludesMixins.ts, 6, 12), Decl(expressionPropertyLookupIncludesMixins.ts, 9, 1)) +>T : Symbol(T, Decl(expressionPropertyLookupIncludesMixins.ts, 6, 20)) +>AnyConstructor : Symbol(AnyConstructor, Decl(expressionPropertyLookupIncludesMixins.ts, 2, 65)) +>base : Symbol(base, Decl(expressionPropertyLookupIncludesMixins.ts, 6, 54)) +>T : Symbol(T, Decl(expressionPropertyLookupIncludesMixins.ts, 6, 20)) + +class Box extends base { +>Box : Symbol(Box, Decl(expressionPropertyLookupIncludesMixins.ts, 6, 66)) +>base : Symbol(base, Decl(expressionPropertyLookupIncludesMixins.ts, 6, 54)) + + value : any +>value : Symbol(Box.value, Decl(expressionPropertyLookupIncludesMixins.ts, 7, 24)) +} +export interface Box extends Mixin {} +>Box : Symbol(Box, Decl(expressionPropertyLookupIncludesMixins.ts, 6, 12), Decl(expressionPropertyLookupIncludesMixins.ts, 9, 1)) +>Mixin : Symbol(Mixin, Decl(expressionPropertyLookupIncludesMixins.ts, 3, 69)) +>Box : Symbol(Box, Decl(expressionPropertyLookupIncludesMixins.ts, 6, 12), Decl(expressionPropertyLookupIncludesMixins.ts, 9, 1)) + +export const Observable = >(base : T) => +>Observable : Symbol(Observable, Decl(expressionPropertyLookupIncludesMixins.ts, 12, 12), Decl(expressionPropertyLookupIncludesMixins.ts, 17, 1)) +>T : Symbol(T, Decl(expressionPropertyLookupIncludesMixins.ts, 12, 27)) +>AnyConstructor : Symbol(AnyConstructor, Decl(expressionPropertyLookupIncludesMixins.ts, 2, 65)) +>base : Symbol(base, Decl(expressionPropertyLookupIncludesMixins.ts, 12, 61)) +>T : Symbol(T, Decl(expressionPropertyLookupIncludesMixins.ts, 12, 27)) + +class Observable extends base { +>Observable : Symbol(Observable, Decl(expressionPropertyLookupIncludesMixins.ts, 12, 73)) +>base : Symbol(base, Decl(expressionPropertyLookupIncludesMixins.ts, 12, 61)) + + observe () : IQuark { +>observe : Symbol(Observable.observe, Decl(expressionPropertyLookupIncludesMixins.ts, 13, 31)) +>IQuark : Symbol(IQuark, Decl(expressionPropertyLookupIncludesMixins.ts, 30, 1)) + + return + } +} +export interface Observable extends Mixin {} +>Observable : Symbol(Observable, Decl(expressionPropertyLookupIncludesMixins.ts, 12, 12), Decl(expressionPropertyLookupIncludesMixins.ts, 17, 1)) +>Mixin : Symbol(Mixin, Decl(expressionPropertyLookupIncludesMixins.ts, 3, 69)) +>Observable : Symbol(Observable, Decl(expressionPropertyLookupIncludesMixins.ts, 12, 12), Decl(expressionPropertyLookupIncludesMixins.ts, 17, 1)) + +export const CQuark = >(base : T) => +>CQuark : Symbol(CQuark, Decl(expressionPropertyLookupIncludesMixins.ts, 20, 12)) +>T : Symbol(T, Decl(expressionPropertyLookupIncludesMixins.ts, 20, 23)) +>AnyConstructor : Symbol(AnyConstructor, Decl(expressionPropertyLookupIncludesMixins.ts, 2, 65)) +>Box : Symbol(Box, Decl(expressionPropertyLookupIncludesMixins.ts, 6, 12), Decl(expressionPropertyLookupIncludesMixins.ts, 9, 1)) +>Observable : Symbol(Observable, Decl(expressionPropertyLookupIncludesMixins.ts, 12, 12), Decl(expressionPropertyLookupIncludesMixins.ts, 17, 1)) +>base : Symbol(base, Decl(expressionPropertyLookupIncludesMixins.ts, 20, 67)) +>T : Symbol(T, Decl(expressionPropertyLookupIncludesMixins.ts, 20, 23)) + +class Quark extends base { +>Quark : Symbol(Quark, Decl(expressionPropertyLookupIncludesMixins.ts, 20, 79)) +>base : Symbol(base, Decl(expressionPropertyLookupIncludesMixins.ts, 20, 67)) + + observe () : Quark { +>observe : Symbol(Quark.observe, Decl(expressionPropertyLookupIncludesMixins.ts, 21, 26)) +>Quark : Symbol(Quark, Decl(expressionPropertyLookupIncludesMixins.ts, 20, 79)) + + // No error here! + this.value +>this.value : Symbol(Box.value, Decl(expressionPropertyLookupIncludesMixins.ts, 7, 24)) +>this : Symbol(Quark, Decl(expressionPropertyLookupIncludesMixins.ts, 20, 79)) +>value : Symbol(Box.value, Decl(expressionPropertyLookupIncludesMixins.ts, 7, 24)) + + + return + } +} +export interface IQuark extends Mixin {} +>IQuark : Symbol(IQuark, Decl(expressionPropertyLookupIncludesMixins.ts, 30, 1)) +>Mixin : Symbol(Mixin, Decl(expressionPropertyLookupIncludesMixins.ts, 3, 69)) +>CQuark : Symbol(CQuark, Decl(expressionPropertyLookupIncludesMixins.ts, 20, 12)) + +const test = (a : IQuark) => a.value // <-- Should not error +>test : Symbol(test, Decl(expressionPropertyLookupIncludesMixins.ts, 33, 5)) +>a : Symbol(a, Decl(expressionPropertyLookupIncludesMixins.ts, 33, 14)) +>IQuark : Symbol(IQuark, Decl(expressionPropertyLookupIncludesMixins.ts, 30, 1)) +>a.value : Symbol(Box.value, Decl(expressionPropertyLookupIncludesMixins.ts, 7, 24)) +>a : Symbol(a, Decl(expressionPropertyLookupIncludesMixins.ts, 33, 14)) +>value : Symbol(Box.value, Decl(expressionPropertyLookupIncludesMixins.ts, 7, 24)) + diff --git a/tests/baselines/reference/expressionPropertyLookupIncludesMixins.types b/tests/baselines/reference/expressionPropertyLookupIncludesMixins.types new file mode 100644 index 0000000000000..00b52ec17a6a9 --- /dev/null +++ b/tests/baselines/reference/expressionPropertyLookupIncludesMixins.types @@ -0,0 +1,83 @@ +=== tests/cases/compiler/expressionPropertyLookupIncludesMixins.ts === +// https://github.com/microsoft/TypeScript/issues/31426 + +export type AnyFunction = (...input : any[]) => A +>AnyFunction : AnyFunction +>input : any[] + +export type AnyConstructor = new (...input : any[]) => A +>AnyConstructor : AnyConstructor +>input : any[] + +export type Mixin = InstanceType> +>Mixin : InstanceType> + +export const Box = >(base : T) => +>Box : >(base: T) => { new (...input: any[]): Box; prototype: Box.Box; } & T +>>(base : T) =>class Box extends base { value : any} : >(base: T) => { new (...input: any[]): Box; prototype: Box.Box; } & T +>base : T + +class Box extends base { +>class Box extends base { value : any} : { new (...input: any[]): Box; prototype: Box.Box; } & T +>Box : { new (...input: any[]): Box; prototype: Box.Box; } & T +>base : object + + value : any +>value : any +} +export interface Box extends Mixin {} +>Box : >(base: T) => { new (...input: any[]): Box; prototype: Box.Box; } & T + +export const Observable = >(base : T) => +>Observable : >(base: T) => { new (...input: any[]): Observable; prototype: Observable.Observable; } & T +>>(base : T) =>class Observable extends base { observe () : IQuark { return }} : >(base: T) => { new (...input: any[]): Observable; prototype: Observable.Observable; } & T +>base : T + +class Observable extends base { +>class Observable extends base { observe () : IQuark { return }} : { new (...input: any[]): Observable; prototype: Observable.Observable; } & T +>Observable : { new (...input: any[]): Observable; prototype: Observable.Observable; } & T +>base : object + + observe () : IQuark { +>observe : () => IQuark + + return + } +} +export interface Observable extends Mixin {} +>Observable : >(base: T) => { new (...input: any[]): Observable; prototype: Observable.Observable; } & T + +export const CQuark = >(base : T) => +>CQuark : >(base: T) => { new (...input: any[]): Quark; prototype: CQuark.Quark; } & T +>>(base : T) =>class Quark extends base { observe () : Quark { // No error here! this.value return }} : >(base: T) => { new (...input: any[]): Quark; prototype: CQuark.Quark; } & T +>base : T + +class Quark extends base { +>class Quark extends base { observe () : Quark { // No error here! this.value return }} : { new (...input: any[]): Quark; prototype: CQuark.Quark; } & T +>Quark : { new (...input: any[]): Quark; prototype: CQuark.Quark; } & T +>base : Box & Observable + + observe () : Quark { +>observe : () => Quark + + // No error here! + this.value +>this.value : any +>this : this +>value : any + + + return + } +} +export interface IQuark extends Mixin {} +>CQuark : >(base: T) => { new (...input: any[]): Quark; prototype: CQuark.Quark; } & T + +const test = (a : IQuark) => a.value // <-- Should not error +>test : (a: IQuark) => any +>(a : IQuark) => a.value : (a: IQuark) => any +>a : IQuark +>a.value : any +>a : IQuark +>value : any + diff --git a/tests/cases/compiler/expressionPropertyLookupIncludesMixins.ts b/tests/cases/compiler/expressionPropertyLookupIncludesMixins.ts new file mode 100644 index 0000000000000..8dc24e4d72613 --- /dev/null +++ b/tests/cases/compiler/expressionPropertyLookupIncludesMixins.ts @@ -0,0 +1,34 @@ +// https://github.com/microsoft/TypeScript/issues/31426 + +export type AnyFunction = (...input : any[]) => A +export type AnyConstructor = new (...input : any[]) => A +export type Mixin = InstanceType> + +export const Box = >(base : T) => +class Box extends base { + value : any +} +export interface Box extends Mixin {} + +export const Observable = >(base : T) => +class Observable extends base { + observe () : IQuark { + return + } +} +export interface Observable extends Mixin {} + +export const CQuark = >(base : T) => +class Quark extends base { + + observe () : Quark { + // No error here! + this.value + + + return + } +} +export interface IQuark extends Mixin {} + +const test = (a : IQuark) => a.value // <-- Should not error