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
22 changes: 16 additions & 6 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4617,7 +4617,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;
}
if (baseConstructorType !== unknownType && baseConstructorType !== nullWideningType && !isConstructorType(baseConstructorType)) {
if (baseConstructorType !== anyType && baseConstructorType !== unknownType && baseConstructorType !== nullWideningType && !isConstructorType(baseConstructorType)) {
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

error(baseTypeNode.expression, Diagnostics.Type_0_is_not_a_constructor_function_type, typeToString(baseConstructorType));
return type.resolvedBaseConstructorType = unknownType;
}
Expand Down Expand Up @@ -4649,7 +4649,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 +4662,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) {
baseType = anyType;
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.

}
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 +4718,11 @@ 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.Any) ||
type.flags & (TypeFlags.Object | TypeFlags.NonPrimitive) && !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,9 @@ 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);
stringIndexInfo = stringIndexInfo || (instantiatedBaseType === anyType ?
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.

createIndexInfo(anyType, /*isReadonly*/ false) :
getIndexInfoOfType(instantiatedBaseType, IndexKind.String));
numberIndexInfo = numberIndexInfo || getIndexInfoOfType(instantiatedBaseType, IndexKind.Number);
}
}
Expand Down Expand Up @@ -5371,6 +5377,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 +5392,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.
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
tests/cases/conformance/parser/ecmascript5/Generics/parserGenericsInTypeContexts1.ts(1,17): error TS2304: Cannot find name 'A'.
tests/cases/conformance/parser/ecmascript5/Generics/parserGenericsInTypeContexts1.ts(1,19): error TS2304: Cannot find name 'T'.
tests/cases/conformance/parser/ecmascript5/Generics/parserGenericsInTypeContexts1.ts(1,33): error TS2304: Cannot find name 'B'.
tests/cases/conformance/parser/ecmascript5/Generics/parserGenericsInTypeContexts1.ts(4,9): error TS2315: Type 'C' is not generic.
tests/cases/conformance/parser/ecmascript5/Generics/parserGenericsInTypeContexts1.ts(5,9): error TS2304: Cannot find name 'D'.
Expand All @@ -9,10 +10,12 @@ tests/cases/conformance/parser/ecmascript5/Generics/parserGenericsInTypeContexts
tests/cases/conformance/parser/ecmascript5/Generics/parserGenericsInTypeContexts1.ts(14,16): error TS2304: Cannot find name 'F'.


==== tests/cases/conformance/parser/ecmascript5/Generics/parserGenericsInTypeContexts1.ts (9 errors) ====
==== tests/cases/conformance/parser/ecmascript5/Generics/parserGenericsInTypeContexts1.ts (10 errors) ====
class C extends A<T> implements B<T> {
~
!!! error TS2304: Cannot find name 'A'.
~
!!! error TS2304: Cannot find name 'T'.
~
!!! error TS2304: Cannot find name 'B'.
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
tests/cases/conformance/parser/ecmascript5/Generics/parserGenericsInTypeContexts2.ts(1,17): error TS2304: Cannot find name 'A'.
tests/cases/conformance/parser/ecmascript5/Generics/parserGenericsInTypeContexts2.ts(1,19): error TS2304: Cannot find name 'X'.
tests/cases/conformance/parser/ecmascript5/Generics/parserGenericsInTypeContexts2.ts(1,25): error TS2304: Cannot find name 'Y'.
tests/cases/conformance/parser/ecmascript5/Generics/parserGenericsInTypeContexts2.ts(1,45): error TS2304: Cannot find name 'B'.
tests/cases/conformance/parser/ecmascript5/Generics/parserGenericsInTypeContexts2.ts(4,9): error TS2315: Type 'C' is not generic.
tests/cases/conformance/parser/ecmascript5/Generics/parserGenericsInTypeContexts2.ts(5,9): error TS2304: Cannot find name 'D'.
Expand All @@ -9,10 +11,14 @@ tests/cases/conformance/parser/ecmascript5/Generics/parserGenericsInTypeContexts
tests/cases/conformance/parser/ecmascript5/Generics/parserGenericsInTypeContexts2.ts(14,16): error TS2304: Cannot find name 'F'.


==== tests/cases/conformance/parser/ecmascript5/Generics/parserGenericsInTypeContexts2.ts (9 errors) ====
==== tests/cases/conformance/parser/ecmascript5/Generics/parserGenericsInTypeContexts2.ts (11 errors) ====
class C extends A<X<T>, Y<Z<T>>> implements B<X<T>, Y<Z<T>>> {
~
!!! error TS2304: Cannot find name 'A'.
~
!!! error TS2304: Cannot find name 'X'.
~
!!! error TS2304: Cannot find name 'Y'.
~
!!! error TS2304: Cannot find name 'B'.
}
Expand Down
7 changes: 2 additions & 5 deletions tests/baselines/reference/recursiveBaseCheck3.errors.txt
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
tests/cases/compiler/recursiveBaseCheck3.ts(1,7): error TS2506: 'A' is referenced directly or indirectly in its own base expression.
tests/cases/compiler/recursiveBaseCheck3.ts(1,20): error TS2449: Class 'C' used before its declaration.
tests/cases/compiler/recursiveBaseCheck3.ts(2,7): error TS2506: 'C' is referenced directly or indirectly in its own base expression.
tests/cases/compiler/recursiveBaseCheck3.ts(4,9): error TS2339: Property 'blah' does not exist on type 'C<{}>'.


==== tests/cases/compiler/recursiveBaseCheck3.ts (4 errors) ====
==== tests/cases/compiler/recursiveBaseCheck3.ts (3 errors) ====
class A<T> extends C<T> { }
~
!!! error TS2506: 'A' is referenced directly or indirectly in its own base expression.
Expand All @@ -14,6 +13,4 @@ tests/cases/compiler/recursiveBaseCheck3.ts(4,9): error TS2339: Property 'blah'
~
!!! error TS2506: 'C' is referenced directly or indirectly in its own base expression.

(new C).blah;
~~~~
!!! error TS2339: Property 'blah' does not exist on type 'C<{}>'.
(new C).blah;
7 changes: 2 additions & 5 deletions tests/baselines/reference/recursiveBaseCheck4.errors.txt
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
tests/cases/compiler/recursiveBaseCheck4.ts(1,7): error TS2506: 'M' is referenced directly or indirectly in its own base expression.
tests/cases/compiler/recursiveBaseCheck4.ts(2,9): error TS2339: Property 'blah' does not exist on type 'M<{}>'.


==== tests/cases/compiler/recursiveBaseCheck4.ts (2 errors) ====
==== tests/cases/compiler/recursiveBaseCheck4.ts (1 errors) ====
class M<T> extends M<string> { }
~
!!! error TS2506: 'M' is referenced directly or indirectly in its own base expression.
(new M).blah;
~~~~
!!! error TS2339: Property 'blah' does not exist on type 'M<{}>'.
(new M).blah;
7 changes: 2 additions & 5 deletions tests/baselines/reference/recursiveBaseCheck6.errors.txt
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
tests/cases/compiler/recursiveBaseCheck6.ts(1,7): error TS2506: 'S18' is referenced directly or indirectly in its own base expression.
tests/cases/compiler/recursiveBaseCheck6.ts(2,13): error TS2339: Property 'blah' does not exist on type 'S18<{}>'.


==== tests/cases/compiler/recursiveBaseCheck6.ts (2 errors) ====
==== tests/cases/compiler/recursiveBaseCheck6.ts (1 errors) ====
class S18<A> extends S18<{ S19: A; }>{ }
~~~
!!! error TS2506: 'S18' is referenced directly or indirectly in its own base expression.
(new S18()).blah;
~~~~
!!! error TS2339: Property 'blah' does not exist on type 'S18<{}>'.
(new S18()).blah;
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
Loading