Skip to content

Commit 3029b8f

Browse files
authored
Merge pull request #14935 from Microsoft/allow-extending-from-any
Allow extending from any
2 parents dce7fca + f635042 commit 3029b8f

15 files changed

+127
-50
lines changed

src/compiler/checker.ts

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -204,8 +204,6 @@ namespace ts {
204204
const evolvingArrayTypes: EvolvingArrayType[] = [];
205205

206206
const unknownSymbol = createSymbol(SymbolFlags.Property, "unknown");
207-
const untypedModuleSymbol = createSymbol(SymbolFlags.ValueModule, "<untyped>");
208-
untypedModuleSymbol.exports = createMap<Symbol>();
209207
const resolvingSymbol = createSymbol(0, "__resolving__");
210208

211209
const anyType = createIntrinsicType(TypeFlags.Any, "any");
@@ -1267,7 +1265,7 @@ namespace ts {
12671265

12681266
if (moduleSymbol) {
12691267
let exportDefaultSymbol: Symbol;
1270-
if (isUntypedOrShorthandAmbientModuleSymbol(moduleSymbol)) {
1268+
if (isShorthandAmbientModuleSymbol(moduleSymbol)) {
12711269
exportDefaultSymbol = moduleSymbol;
12721270
}
12731271
else {
@@ -1347,7 +1345,7 @@ namespace ts {
13471345
if (targetSymbol) {
13481346
const name = specifier.propertyName || specifier.name;
13491347
if (name.text) {
1350-
if (isUntypedOrShorthandAmbientModuleSymbol(moduleSymbol)) {
1348+
if (isShorthandAmbientModuleSymbol(moduleSymbol)) {
13511349
return moduleSymbol;
13521350
}
13531351

@@ -1623,19 +1621,15 @@ namespace ts {
16231621
if (isForAugmentation) {
16241622
const diag = Diagnostics.Invalid_module_name_in_augmentation_Module_0_resolves_to_an_untyped_module_at_1_which_cannot_be_augmented;
16251623
error(errorNode, diag, moduleReference, resolvedModule.resolvedFileName);
1626-
return undefined;
16271624
}
16281625
else if (noImplicitAny && moduleNotFoundError) {
16291626
error(errorNode,
16301627
Diagnostics.Could_not_find_a_declaration_file_for_module_0_1_implicitly_has_an_any_type,
16311628
moduleReference,
16321629
resolvedModule.resolvedFileName);
1633-
return undefined;
16341630
}
1635-
// Unlike a failed import, an untyped module produces a dummy symbol.
1636-
// This is checked for by `isUntypedOrShorthandAmbientModuleSymbol`.
1637-
// This must be different than `unknownSymbol` because `getBaseConstructorTypeOfClass` won't fail for `unknownSymbol`.
1638-
return untypedModuleSymbol;
1631+
// Failed imports and untyped modules are both treated in an untyped manner; only difference is whether we give a diagnostic first.
1632+
return undefined;
16391633
}
16401634

16411635
if (moduleNotFoundError) {
@@ -4405,7 +4399,7 @@ namespace ts {
44054399
function getTypeOfFuncClassEnumModule(symbol: Symbol): Type {
44064400
const links = getSymbolLinks(symbol);
44074401
if (!links.type) {
4408-
if (symbol.flags & SymbolFlags.Module && isUntypedOrShorthandAmbientModuleSymbol(symbol)) {
4402+
if (symbol.flags & SymbolFlags.Module && isShorthandAmbientModuleSymbol(symbol)) {
44094403
links.type = anyType;
44104404
}
44114405
else {
@@ -4641,7 +4635,8 @@ namespace ts {
46414635
* The base constructor of a class can resolve to
46424636
* * undefinedType if the class has no extends clause,
46434637
* * unknownType if an error occurred during resolution of the extends expression,
4644-
* * nullType if the extends expression is the null value, or
4638+
* * nullType if the extends expression is the null value,
4639+
* * anyType if the extends expression has type any, or
46454640
* * an object type with at least one construct signature.
46464641
*/
46474642
function getBaseConstructorTypeOfClass(type: InterfaceType): Type {
@@ -4663,7 +4658,7 @@ namespace ts {
46634658
error(type.symbol.valueDeclaration, Diagnostics._0_is_referenced_directly_or_indirectly_in_its_own_base_expression, symbolToString(type.symbol));
46644659
return type.resolvedBaseConstructorType = unknownType;
46654660
}
4666-
if (baseConstructorType !== unknownType && baseConstructorType !== nullWideningType && !isConstructorType(baseConstructorType)) {
4661+
if (!(baseConstructorType.flags & TypeFlags.Any) && baseConstructorType !== nullWideningType && !isConstructorType(baseConstructorType)) {
46674662
error(baseTypeNode.expression, Diagnostics.Type_0_is_not_a_constructor_function_type, typeToString(baseConstructorType));
46684663
return type.resolvedBaseConstructorType = unknownType;
46694664
}
@@ -4695,7 +4690,7 @@ namespace ts {
46954690
function resolveBaseTypesOfClass(type: InterfaceType): void {
46964691
type.resolvedBaseTypes = type.resolvedBaseTypes || emptyArray;
46974692
const baseConstructorType = getApparentType(getBaseConstructorTypeOfClass(type));
4698-
if (!(baseConstructorType.flags & (TypeFlags.Object | TypeFlags.Intersection))) {
4693+
if (!(baseConstructorType.flags & (TypeFlags.Object | TypeFlags.Intersection | TypeFlags.Any))) {
46994694
return;
47004695
}
47014696
const baseTypeNode = getBaseTypeNodeOfClass(type);
@@ -4708,6 +4703,9 @@ namespace ts {
47084703
// type arguments in the same manner as a type reference to get the same error reporting experience.
47094704
baseType = getTypeFromClassOrInterfaceReference(baseTypeNode, baseConstructorType.symbol);
47104705
}
4706+
else if (baseConstructorType.flags & TypeFlags.Any) {
4707+
baseType = baseConstructorType;
4708+
}
47114709
else {
47124710
// The class derives from a "class-like" constructor function, check that we have at least one construct signature
47134711
// with a matching number of type parameters and use the return type of the first instantiated signature. Elsewhere
@@ -4761,10 +4759,10 @@ namespace ts {
47614759
return true;
47624760
}
47634761

4764-
// A valid base type is any non-generic object type or intersection of non-generic
4762+
// A valid base type is `any`, any non-generic object type or intersection of non-generic
47654763
// object types.
47664764
function isValidBaseType(type: Type): boolean {
4767-
return type.flags & (TypeFlags.Object | TypeFlags.NonPrimitive) && !isGenericMappedType(type) ||
4765+
return type.flags & (TypeFlags.Object | TypeFlags.NonPrimitive | TypeFlags.Any) && !isGenericMappedType(type) ||
47684766
type.flags & TypeFlags.Intersection && !forEach((<IntersectionType>type).types, t => !isValidBaseType(t));
47694767
}
47704768

@@ -5176,7 +5174,11 @@ namespace ts {
51765174
addInheritedMembers(members, getPropertiesOfType(instantiatedBaseType));
51775175
callSignatures = concatenate(callSignatures, getSignaturesOfType(instantiatedBaseType, SignatureKind.Call));
51785176
constructSignatures = concatenate(constructSignatures, getSignaturesOfType(instantiatedBaseType, SignatureKind.Construct));
5179-
stringIndexInfo = stringIndexInfo || getIndexInfoOfType(instantiatedBaseType, IndexKind.String);
5177+
if (!stringIndexInfo) {
5178+
stringIndexInfo = instantiatedBaseType === anyType ?
5179+
createIndexInfo(anyType, /*isReadonly*/ false) :
5180+
getIndexInfoOfType(instantiatedBaseType, IndexKind.String);
5181+
}
51805182
numberIndexInfo = numberIndexInfo || getIndexInfoOfType(instantiatedBaseType, IndexKind.Number);
51815183
}
51825184
}
@@ -5418,6 +5420,7 @@ namespace ts {
54185420
// Combinations of function, class, enum and module
54195421
let members = emptySymbols;
54205422
let constructSignatures: Signature[] = emptyArray;
5423+
let stringIndexInfo: IndexInfo = undefined;
54215424
if (symbol.exports) {
54225425
members = getExportsOfSymbol(symbol);
54235426
}
@@ -5432,9 +5435,12 @@ namespace ts {
54325435
members = createSymbolTable(getNamedMembers(members));
54335436
addInheritedMembers(members, getPropertiesOfType(baseConstructorType));
54345437
}
5438+
else if (baseConstructorType === anyType) {
5439+
stringIndexInfo = createIndexInfo(anyType, /*isReadonly*/ false);
5440+
}
54355441
}
54365442
const numberIndexInfo = symbol.flags & SymbolFlags.Enum ? enumNumberIndexInfo : undefined;
5437-
setStructuredTypeMembers(type, members, emptyArray, constructSignatures, undefined, numberIndexInfo);
5443+
setStructuredTypeMembers(type, members, emptyArray, constructSignatures, stringIndexInfo, numberIndexInfo);
54385444
// We resolve the members before computing the signatures because a signature may use
54395445
// typeof with a qualified name expression that circularly references the type we are
54405446
// in the process of resolving (see issue #6072). The temporarily empty signature list
@@ -22172,7 +22178,7 @@ namespace ts {
2217222178

2217322179
function moduleExportsSomeValue(moduleReferenceExpression: Expression): boolean {
2217422180
let moduleSymbol = resolveExternalModuleName(moduleReferenceExpression.parent, moduleReferenceExpression);
22175-
if (!moduleSymbol || isUntypedOrShorthandAmbientModuleSymbol(moduleSymbol)) {
22181+
if (!moduleSymbol || isShorthandAmbientModuleSymbol(moduleSymbol)) {
2217622182
// If the module is not found or is shorthand, assume that it may export a value.
2217722183
return true;
2217822184
}

src/compiler/utilities.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -379,9 +379,9 @@ namespace ts {
379379
((<ModuleDeclaration>node).name.kind === SyntaxKind.StringLiteral || isGlobalScopeAugmentation(<ModuleDeclaration>node));
380380
}
381381

382-
/** Given a symbol for a module, checks that it is either an untyped import or a shorthand ambient module. */
383-
export function isUntypedOrShorthandAmbientModuleSymbol(moduleSymbol: Symbol): boolean {
384-
return !moduleSymbol.declarations || isShorthandAmbientModule(moduleSymbol.valueDeclaration);
382+
/** Given a symbol for a module, checks that it is a shorthand ambient module. */
383+
export function isShorthandAmbientModuleSymbol(moduleSymbol: Symbol): boolean {
384+
return isShorthandAmbientModule(moduleSymbol.valueDeclaration);
385385
}
386386

387387
function isShorthandAmbientModule(node: Node): boolean {

src/services/findAllReferences.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ namespace ts.FindAllReferences {
187187
return { symbol };
188188
}
189189

190-
if (ts.isUntypedOrShorthandAmbientModuleSymbol(aliasedSymbol)) {
190+
if (ts.isShorthandAmbientModuleSymbol(aliasedSymbol)) {
191191
return { symbol, shorthandModuleSymbol: aliasedSymbol };
192192
}
193193

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
tests/cases/compiler/extendFromAny.ts(8,9): error TS2339: Property 'length' does not exist on type 'number'.
2+
tests/cases/compiler/extendFromAny.ts(9,10): error TS2339: Property 'length' does not exist on type 'number'.
3+
4+
5+
==== tests/cases/compiler/extendFromAny.ts (2 errors) ====
6+
declare var Base: any;
7+
class C extends Base {
8+
known = 1;
9+
static sknown = 2;
10+
}
11+
12+
let c = new C();
13+
c.known.length; // error, 'known' has no 'length' property
14+
~~~~~~
15+
!!! error TS2339: Property 'length' does not exist on type 'number'.
16+
C.sknown.length; // error, 'sknown' has no 'length' property
17+
~~~~~~
18+
!!! error TS2339: Property 'length' does not exist on type 'number'.
19+
c.unknown.length; // ok, unknown: any
20+
C.sunknown.length; // ok: sunknown: any
21+
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
//// [extendFromAny.ts]
2+
declare var Base: any;
3+
class C extends Base {
4+
known = 1;
5+
static sknown = 2;
6+
}
7+
8+
let c = new C();
9+
c.known.length; // error, 'known' has no 'length' property
10+
C.sknown.length; // error, 'sknown' has no 'length' property
11+
c.unknown.length; // ok, unknown: any
12+
C.sunknown.length; // ok: sunknown: any
13+
14+
15+
//// [extendFromAny.js]
16+
var __extends = (this && this.__extends) || (function () {
17+
var extendStatics = Object.setPrototypeOf ||
18+
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
19+
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
20+
return function (d, b) {
21+
extendStatics(d, b);
22+
function __() { this.constructor = d; }
23+
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
24+
};
25+
})();
26+
var C = (function (_super) {
27+
__extends(C, _super);
28+
function C() {
29+
var _this = _super !== null && _super.apply(this, arguments) || this;
30+
_this.known = 1;
31+
return _this;
32+
}
33+
return C;
34+
}(Base));
35+
C.sknown = 2;
36+
var c = new C();
37+
c.known.length; // error, 'known' has no 'length' property
38+
C.sknown.length; // error, 'sknown' has no 'length' property
39+
c.unknown.length; // ok, unknown: any
40+
C.sunknown.length; // ok: sunknown: any
Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
1-
/a.ts(2,17): error TS2507: Type 'any' is not a constructor function type.
1+
/a.ts(2,8): error TS6133: 'Bar' is declared but never used.
22

33

44
==== /a.ts (1 errors) ====
55
import Foo from "foo";
6-
class A extends Foo { }
7-
~~~
8-
!!! error TS2507: Type 'any' is not a constructor function type.
6+
import Bar from "bar"; // error: unused
7+
~~~
8+
!!! error TS6133: 'Bar' is declared but never used.
9+
export class A extends Foo { }
910

1011
==== /node_modules/foo/index.js (0 errors) ====
1112
// Test that extending an untyped module is an error, unlike extending unknownSymbol.
1213

1314
This file is not read.
15+
16+
==== /node_modules/bar/index.js (0 errors) ====
17+
Nor is this one.
1418

tests/baselines/reference/extendsUntypedModule.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,13 @@
55

66
This file is not read.
77

8+
//// [index.js]
9+
Nor is this one.
10+
811
//// [a.ts]
912
import Foo from "foo";
10-
class A extends Foo { }
13+
import Bar from "bar"; // error: unused
14+
export class A extends Foo { }
1115

1216

1317
//// [a.js]
@@ -31,3 +35,4 @@ var A = (function (_super) {
3135
}
3236
return A;
3337
}(foo_1["default"]));
38+
exports.A = A;
Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
1-
tests/cases/conformance/es6/yieldExpressions/generatorTypeCheck40.ts(2,21): error TS2507: Type 'any' is not a constructor function type.
21
tests/cases/conformance/es6/yieldExpressions/generatorTypeCheck40.ts(2,22): error TS1163: A 'yield' expression is only allowed in a generator body.
32

43

5-
==== tests/cases/conformance/es6/yieldExpressions/generatorTypeCheck40.ts (2 errors) ====
4+
==== tests/cases/conformance/es6/yieldExpressions/generatorTypeCheck40.ts (1 errors) ====
65
function* g() {
76
class C extends (yield 0) { }
8-
~~~~~~~~~
9-
!!! error TS2507: Type 'any' is not a constructor function type.
107
~~~~~
118
!!! error TS1163: A 'yield' expression is only allowed in a generator body.
129
}
Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
1-
tests/cases/conformance/es6/yieldExpressions/generatorTypeCheck55.ts(2,29): error TS2507: Type 'any' is not a constructor function type.
21
tests/cases/conformance/es6/yieldExpressions/generatorTypeCheck55.ts(2,30): error TS1163: A 'yield' expression is only allowed in a generator body.
32

43

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

43

5-
==== tests/cases/conformance/es6/yieldExpressions/generatorTypeCheck60.ts (2 errors) ====
4+
==== tests/cases/conformance/es6/yieldExpressions/generatorTypeCheck60.ts (1 errors) ====
65
function* g() {
76
class C extends (yield) {};
8-
~~~~~~~
9-
!!! error TS2507: Type 'any' is not a constructor function type.
107
~~~~~
118
!!! error TS1163: A 'yield' expression is only allowed in a generator body.
129
}

tests/baselines/reference/thisInInvalidContexts.errors.txt

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,11 @@ tests/cases/conformance/expressions/thisKeyword/thisInInvalidContexts.ts(14,15):
33
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.
44
tests/cases/conformance/expressions/thisKeyword/thisInInvalidContexts.ts(28,13): error TS2331: 'this' cannot be referenced in a module or namespace body.
55
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.
6-
tests/cases/conformance/expressions/thisKeyword/thisInInvalidContexts.ts(38,25): error TS2507: Type 'any' is not a constructor function type.
76
tests/cases/conformance/expressions/thisKeyword/thisInInvalidContexts.ts(44,9): error TS2332: 'this' cannot be referenced in current location.
87
tests/cases/conformance/expressions/thisKeyword/thisInInvalidContexts.ts(45,9): error TS2332: 'this' cannot be referenced in current location.
98

109

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

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

6360
}
6461

tests/baselines/reference/thisInInvalidContextsExternalModule.errors.txt

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,11 @@ tests/cases/conformance/expressions/thisKeyword/thisInInvalidContextsExternalMod
33
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.
44
tests/cases/conformance/expressions/thisKeyword/thisInInvalidContextsExternalModule.ts(28,13): error TS2331: 'this' cannot be referenced in a module or namespace body.
55
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.
6-
tests/cases/conformance/expressions/thisKeyword/thisInInvalidContextsExternalModule.ts(38,25): error TS2507: Type 'any' is not a constructor function type.
76
tests/cases/conformance/expressions/thisKeyword/thisInInvalidContextsExternalModule.ts(44,9): error TS2332: 'this' cannot be referenced in current location.
87
tests/cases/conformance/expressions/thisKeyword/thisInInvalidContextsExternalModule.ts(45,9): error TS2332: 'this' cannot be referenced in current location.
98

109

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

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

6360
}
6461

0 commit comments

Comments
 (0)