Skip to content

Allow extending from any #14935

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Apr 6, 2017
Merged
26 changes: 19 additions & 7 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4595,7 +4595,8 @@ namespace ts {
* The base constructor of a class can resolve to
* * undefinedType if the class has no extends clause,
* * unknownType if an error occurred during resolution of the extends expression,
* * nullType if the extends expression is the null value, or
* * nullType if the extends expression is the null value,
* * anyType if the extends expression has type any, or
* * an object type with at least one construct signature.
*/
function getBaseConstructorTypeOfClass(type: InterfaceType): Type {
Expand All @@ -4617,7 +4618,7 @@ namespace ts {
error(type.symbol.valueDeclaration, Diagnostics._0_is_referenced_directly_or_indirectly_in_its_own_base_expression, symbolToString(type.symbol));
return type.resolvedBaseConstructorType = unknownType;
}
Copy link
Member

@DanielRosenwasser DanielRosenwasser Apr 5, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hard for me to look up right now whether unknownType includes it, could you check for TypeFlags.Any?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just check unknownType and anyType both have TypeFlags.Any

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Includes anyType, autoType, unknownType. Seems like it should work.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

if (baseConstructorType !== unknownType && baseConstructorType !== nullWideningType && !isConstructorType(baseConstructorType)) {
if (!(baseConstructorType.flags & TypeFlags.Any) && baseConstructorType !== nullWideningType && !isConstructorType(baseConstructorType)) {
error(baseTypeNode.expression, Diagnostics.Type_0_is_not_a_constructor_function_type, typeToString(baseConstructorType));
return type.resolvedBaseConstructorType = unknownType;
}
Expand Down Expand Up @@ -4649,7 +4650,7 @@ namespace ts {
function resolveBaseTypesOfClass(type: InterfaceType): void {
type.resolvedBaseTypes = type.resolvedBaseTypes || emptyArray;
const baseConstructorType = getApparentType(getBaseConstructorTypeOfClass(type));
if (!(baseConstructorType.flags & (TypeFlags.Object | TypeFlags.Intersection))) {
if (!(baseConstructorType.flags & (TypeFlags.Object | TypeFlags.Intersection | TypeFlags.Any))) {
return;
}
const baseTypeNode = getBaseTypeNodeOfClass(type);
Expand All @@ -4662,6 +4663,9 @@ namespace ts {
// type arguments in the same manner as a type reference to get the same error reporting experience.
baseType = getTypeFromClassOrInterfaceReference(baseTypeNode, baseConstructorType.symbol);
}
else if (baseConstructorType.flags & TypeFlags.Any) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you leave a comment saying why it is necessary to set it to anyType instead of baseConstructorType?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no good reason; in fact it reduces test churn to return baseConstructorType for tests that extend undefined variables, which I think get unknownType instead of anyType, meaning that you get slightly more errors.

I'll change it to baseConstructorType.

baseType = baseConstructorType;
}
else {
// The class derives from a "class-like" constructor function, check that we have at least one construct signature
// with a matching number of type parameters and use the return type of the first instantiated signature. Elsewhere
Expand Down Expand Up @@ -4715,10 +4719,10 @@ namespace ts {
return true;
}

// A valid base type is any non-generic object type or intersection of non-generic
// A valid base type is `any`, any non-generic object type or intersection of non-generic
// object types.
function isValidBaseType(type: Type): boolean {
return type.flags & (TypeFlags.Object | TypeFlags.NonPrimitive) && !isGenericMappedType(type) ||
return type.flags & (TypeFlags.Object | TypeFlags.NonPrimitive | TypeFlags.Any) && !isGenericMappedType(type) ||
Copy link
Contributor

@yuit yuit Apr 5, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this just be type.flags & (TypeFlags.Object | TypeFlags.NonPrimitive | TypeFlags.Any)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it should work, although it's kind of a weird pun in that we rely on !isGenericMappedType(anyType) being true.

type.flags & TypeFlags.Intersection && !forEach((<IntersectionType>type).types, t => !isValidBaseType(t));
}

Expand Down Expand Up @@ -5130,7 +5134,11 @@ namespace ts {
addInheritedMembers(members, getPropertiesOfType(instantiatedBaseType));
callSignatures = concatenate(callSignatures, getSignaturesOfType(instantiatedBaseType, SignatureKind.Call));
constructSignatures = concatenate(constructSignatures, getSignaturesOfType(instantiatedBaseType, SignatureKind.Construct));
stringIndexInfo = stringIndexInfo || getIndexInfoOfType(instantiatedBaseType, IndexKind.String);
if (!stringIndexInfo) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: would it be easier to read this way:

if (!stringIndexInfo) {
stringIndexInfo = instantiatedBaseType === anyType ? createIndexInfo(anyType, /*isReadonly*/ false) : getIndexInfoOfType(instantiatedBaseType, IndexKind.String);
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess so. Done.

stringIndexInfo = instantiatedBaseType === anyType ?
createIndexInfo(anyType, /*isReadonly*/ false) :
getIndexInfoOfType(instantiatedBaseType, IndexKind.String);
}
numberIndexInfo = numberIndexInfo || getIndexInfoOfType(instantiatedBaseType, IndexKind.Number);
}
}
Expand Down Expand Up @@ -5371,6 +5379,7 @@ namespace ts {
// Combinations of function, class, enum and module
let members = emptySymbols;
let constructSignatures: Signature[] = emptyArray;
let stringIndexInfo: IndexInfo = undefined;
if (symbol.exports) {
members = getExportsOfSymbol(symbol);
}
Expand All @@ -5385,9 +5394,12 @@ namespace ts {
members = createSymbolTable(getNamedMembers(members));
addInheritedMembers(members, getPropertiesOfType(baseConstructorType));
}
else if (baseConstructorType === anyType) {
stringIndexInfo = createIndexInfo(anyType, /*isReadonly*/ false);
}
}
const numberIndexInfo = symbol.flags & SymbolFlags.Enum ? enumNumberIndexInfo : undefined;
setStructuredTypeMembers(type, members, emptyArray, constructSignatures, undefined, numberIndexInfo);
setStructuredTypeMembers(type, members, emptyArray, constructSignatures, stringIndexInfo, numberIndexInfo);
// We resolve the members before computing the signatures because a signature may use
// typeof with a qualified name expression that circularly references the type we are
// in the process of resolving (see issue #6072). The temporarily empty signature list
Expand Down
21 changes: 21 additions & 0 deletions tests/baselines/reference/extendFromAny.errors.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
tests/cases/compiler/extendFromAny.ts(8,9): error TS2339: Property 'length' does not exist on type 'number'.
tests/cases/compiler/extendFromAny.ts(9,10): error TS2339: Property 'length' does not exist on type 'number'.


==== tests/cases/compiler/extendFromAny.ts (2 errors) ====
declare var Base: any;
class C extends Base {
known = 1;
static sknown = 2;
}

let c = new C();
c.known.length; // error, 'known' has no 'length' property
~~~~~~
!!! error TS2339: Property 'length' does not exist on type 'number'.
C.sknown.length; // error, 'sknown' has no 'length' property
~~~~~~
!!! error TS2339: Property 'length' does not exist on type 'number'.
c.unknown.length; // ok, unknown: any
C.sunknown.length; // ok: sunknown: any

40 changes: 40 additions & 0 deletions tests/baselines/reference/extendFromAny.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//// [extendFromAny.ts]
declare var Base: any;
class C extends Base {
known = 1;
static sknown = 2;
}

let c = new C();
c.known.length; // error, 'known' has no 'length' property
C.sknown.length; // error, 'sknown' has no 'length' property
c.unknown.length; // ok, unknown: any
C.sunknown.length; // ok: sunknown: any


//// [extendFromAny.js]
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 C = (function (_super) {
__extends(C, _super);
function C() {
var _this = _super !== null && _super.apply(this, arguments) || this;
_this.known = 1;
return _this;
}
return C;
}(Base));
C.sknown = 2;
var c = new C();
c.known.length; // error, 'known' has no 'length' property
C.sknown.length; // error, 'sknown' has no 'length' property
c.unknown.length; // ok, unknown: any
C.sunknown.length; // ok: sunknown: any
14 changes: 0 additions & 14 deletions tests/baselines/reference/extendsUntypedModule.errors.txt

This file was deleted.

8 changes: 8 additions & 0 deletions tests/baselines/reference/extendsUntypedModule.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
=== /a.ts ===
import Foo from "foo";
>Foo : Symbol(Foo, Decl(a.ts, 0, 6))

class A extends Foo { }
>A : Symbol(A, Decl(a.ts, 0, 22))
>Foo : Symbol(Foo, Decl(a.ts, 0, 6))

8 changes: 8 additions & 0 deletions tests/baselines/reference/extendsUntypedModule.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
=== /a.ts ===
import Foo from "foo";
>Foo : any

class A extends Foo { }
>A : A
>Foo : any

5 changes: 1 addition & 4 deletions tests/baselines/reference/generatorTypeCheck40.errors.txt
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
tests/cases/conformance/es6/yieldExpressions/generatorTypeCheck40.ts(2,21): error TS2507: Type 'any' is not a constructor function type.
tests/cases/conformance/es6/yieldExpressions/generatorTypeCheck40.ts(2,22): error TS1163: A 'yield' expression is only allowed in a generator body.


==== tests/cases/conformance/es6/yieldExpressions/generatorTypeCheck40.ts (2 errors) ====
==== tests/cases/conformance/es6/yieldExpressions/generatorTypeCheck40.ts (1 errors) ====
function* g() {
class C extends (yield 0) { }
~~~~~~~~~
!!! error TS2507: Type 'any' is not a constructor function type.
~~~~~
!!! error TS1163: A 'yield' expression is only allowed in a generator body.
}
5 changes: 1 addition & 4 deletions tests/baselines/reference/generatorTypeCheck55.errors.txt
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
tests/cases/conformance/es6/yieldExpressions/generatorTypeCheck55.ts(2,29): error TS2507: Type 'any' is not a constructor function type.
tests/cases/conformance/es6/yieldExpressions/generatorTypeCheck55.ts(2,30): error TS1163: A 'yield' expression is only allowed in a generator body.


==== tests/cases/conformance/es6/yieldExpressions/generatorTypeCheck55.ts (2 errors) ====
==== tests/cases/conformance/es6/yieldExpressions/generatorTypeCheck55.ts (1 errors) ====
function* g() {
var x = class C extends (yield) {};
~~~~~~~
!!! error TS2507: Type 'any' is not a constructor function type.
~~~~~
!!! error TS1163: A 'yield' expression is only allowed in a generator body.
}
5 changes: 1 addition & 4 deletions tests/baselines/reference/generatorTypeCheck60.errors.txt
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
tests/cases/conformance/es6/yieldExpressions/generatorTypeCheck60.ts(2,21): error TS2507: Type 'any' is not a constructor function type.
tests/cases/conformance/es6/yieldExpressions/generatorTypeCheck60.ts(2,22): error TS1163: A 'yield' expression is only allowed in a generator body.


==== tests/cases/conformance/es6/yieldExpressions/generatorTypeCheck60.ts (2 errors) ====
==== tests/cases/conformance/es6/yieldExpressions/generatorTypeCheck60.ts (1 errors) ====
function* g() {
class C extends (yield) {};
~~~~~~~
!!! error TS2507: Type 'any' is not a constructor function type.
~~~~~
!!! error TS1163: A 'yield' expression is only allowed in a generator body.
}
5 changes: 1 addition & 4 deletions tests/baselines/reference/thisInInvalidContexts.errors.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@ tests/cases/conformance/expressions/thisKeyword/thisInInvalidContexts.ts(14,15):
tests/cases/conformance/expressions/thisKeyword/thisInInvalidContexts.ts(22,15): error TS17009: 'super' must be called before accessing 'this' in the constructor of a derived class.
tests/cases/conformance/expressions/thisKeyword/thisInInvalidContexts.ts(28,13): error TS2331: 'this' cannot be referenced in a module or namespace body.
tests/cases/conformance/expressions/thisKeyword/thisInInvalidContexts.ts(36,13): error TS2526: A 'this' type is available only in a non-static member of a class or interface.
tests/cases/conformance/expressions/thisKeyword/thisInInvalidContexts.ts(38,25): error TS2507: Type 'any' is not a constructor function type.
tests/cases/conformance/expressions/thisKeyword/thisInInvalidContexts.ts(44,9): error TS2332: 'this' cannot be referenced in current location.
tests/cases/conformance/expressions/thisKeyword/thisInInvalidContexts.ts(45,9): error TS2332: 'this' cannot be referenced in current location.


==== tests/cases/conformance/expressions/thisKeyword/thisInInvalidContexts.ts (8 errors) ====
==== tests/cases/conformance/expressions/thisKeyword/thisInInvalidContexts.ts (7 errors) ====
//'this' in static member initializer
class ErrClass1 {
static t = this; // Error
Expand Down Expand Up @@ -57,8 +56,6 @@ tests/cases/conformance/expressions/thisKeyword/thisInInvalidContexts.ts(45,9):
!!! error TS2526: A 'this' type is available only in a non-static member of a class or interface.

class ErrClass3 extends this {
~~~~
!!! error TS2507: Type 'any' is not a constructor function type.

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@ tests/cases/conformance/expressions/thisKeyword/thisInInvalidContextsExternalMod
tests/cases/conformance/expressions/thisKeyword/thisInInvalidContextsExternalModule.ts(22,15): error TS17009: 'super' must be called before accessing 'this' in the constructor of a derived class.
tests/cases/conformance/expressions/thisKeyword/thisInInvalidContextsExternalModule.ts(28,13): error TS2331: 'this' cannot be referenced in a module or namespace body.
tests/cases/conformance/expressions/thisKeyword/thisInInvalidContextsExternalModule.ts(36,13): error TS2526: A 'this' type is available only in a non-static member of a class or interface.
tests/cases/conformance/expressions/thisKeyword/thisInInvalidContextsExternalModule.ts(38,25): error TS2507: Type 'any' is not a constructor function type.
tests/cases/conformance/expressions/thisKeyword/thisInInvalidContextsExternalModule.ts(44,9): error TS2332: 'this' cannot be referenced in current location.
tests/cases/conformance/expressions/thisKeyword/thisInInvalidContextsExternalModule.ts(45,9): error TS2332: 'this' cannot be referenced in current location.


==== tests/cases/conformance/expressions/thisKeyword/thisInInvalidContextsExternalModule.ts (8 errors) ====
==== tests/cases/conformance/expressions/thisKeyword/thisInInvalidContextsExternalModule.ts (7 errors) ====
//'this' in static member initializer
class ErrClass1 {
static t = this; // Error
Expand Down Expand Up @@ -57,8 +56,6 @@ tests/cases/conformance/expressions/thisKeyword/thisInInvalidContextsExternalMod
!!! error TS2526: A 'this' type is available only in a non-static member of a class or interface.

class ErrClass3 extends this {
~~~~
!!! error TS2507: Type 'any' is not a constructor function type.

}

Expand Down
11 changes: 11 additions & 0 deletions tests/cases/compiler/extendFromAny.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
declare var Base: any;
class C extends Base {
known = 1;
static sknown = 2;
}

let c = new C();
c.known.length; // error, 'known' has no 'length' property
C.sknown.length; // error, 'sknown' has no 'length' property
c.unknown.length; // ok, unknown: any
C.sunknown.length; // ok: sunknown: any