Skip to content
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

Allow extending from any #14935

Merged
merged 12 commits into from
Apr 6, 2017
44 changes: 25 additions & 19 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,8 +203,6 @@ namespace ts {
const evolvingArrayTypes: EvolvingArrayType[] = [];

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

const anyType = createIntrinsicType(TypeFlags.Any, "any");
Expand Down Expand Up @@ -1253,7 +1251,7 @@ namespace ts {

if (moduleSymbol) {
let exportDefaultSymbol: Symbol;
if (isUntypedOrShorthandAmbientModuleSymbol(moduleSymbol)) {
if (isShorthandAmbientModuleSymbol(moduleSymbol)) {
exportDefaultSymbol = moduleSymbol;
}
else {
Expand Down Expand Up @@ -1333,7 +1331,7 @@ namespace ts {
if (targetSymbol) {
const name = specifier.propertyName || specifier.name;
if (name.text) {
if (isUntypedOrShorthandAmbientModuleSymbol(moduleSymbol)) {
if (isShorthandAmbientModuleSymbol(moduleSymbol)) {
Copy link

@ghost ghost Apr 5, 2017

Choose a reason for hiding this comment

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

I like the old name better, even if it's a bit long.

return moduleSymbol;
}

Expand Down Expand Up @@ -1586,19 +1584,15 @@ namespace ts {
if (isForAugmentation) {
const diag = Diagnostics.Invalid_module_name_in_augmentation_Module_0_resolves_to_an_untyped_module_at_1_which_cannot_be_augmented;
error(errorNode, diag, moduleReference, resolvedModule.resolvedFileName);
return undefined;
}
else if (noImplicitAny && moduleNotFoundError) {
error(errorNode,
Diagnostics.Could_not_find_a_declaration_file_for_module_0_1_implicitly_has_an_any_type,
moduleReference,
resolvedModule.resolvedFileName);
return undefined;
}
// Unlike a failed import, an untyped module produces a dummy symbol.
// This is checked for by `isUntypedOrShorthandAmbientModuleSymbol`.
// This must be different than `unknownSymbol` because `getBaseConstructorTypeOfClass` won't fail for `unknownSymbol`.
return untypedModuleSymbol;
// Failed imports and untyped modules are both treated in an untyped manner; only difference is whether we give a diagnostic first.
return undefined;
}

if (moduleNotFoundError) {
Expand Down Expand Up @@ -4368,7 +4362,7 @@ namespace ts {
function getTypeOfFuncClassEnumModule(symbol: Symbol): Type {
const links = getSymbolLinks(symbol);
if (!links.type) {
if (symbol.flags & SymbolFlags.Module && isUntypedOrShorthandAmbientModuleSymbol(symbol)) {
if (symbol.flags & SymbolFlags.Module && isShorthandAmbientModuleSymbol(symbol)) {
links.type = anyType;
}
else {
Expand Down Expand Up @@ -4595,7 +4589,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 +4612,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.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 +4644,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 +4657,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 = 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 +4713,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) ||
type.flags & TypeFlags.Intersection && !forEach((<IntersectionType>type).types, t => !isValidBaseType(t));
}

Expand Down Expand Up @@ -5130,7 +5128,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) {
stringIndexInfo = instantiatedBaseType === anyType ?
createIndexInfo(anyType, /*isReadonly*/ false) :
getIndexInfoOfType(instantiatedBaseType, IndexKind.String);
}
numberIndexInfo = numberIndexInfo || getIndexInfoOfType(instantiatedBaseType, IndexKind.Number);
}
}
Expand Down Expand Up @@ -5371,6 +5373,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 +5388,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 Expand Up @@ -22052,7 +22058,7 @@ namespace ts {

function moduleExportsSomeValue(moduleReferenceExpression: Expression): boolean {
let moduleSymbol = resolveExternalModuleName(moduleReferenceExpression.parent, moduleReferenceExpression);
if (!moduleSymbol || isUntypedOrShorthandAmbientModuleSymbol(moduleSymbol)) {
if (!moduleSymbol || isShorthandAmbientModuleSymbol(moduleSymbol)) {
// If the module is not found or is shorthand, assume that it may export a value.
return true;
}
Expand Down
6 changes: 3 additions & 3 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -438,9 +438,9 @@ namespace ts {
((<ModuleDeclaration>node).name.kind === SyntaxKind.StringLiteral || isGlobalScopeAugmentation(<ModuleDeclaration>node));
}

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

function isShorthandAmbientModule(node: Node): boolean {
Expand Down
2 changes: 1 addition & 1 deletion src/services/findAllReferences.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ namespace ts.FindAllReferences {
return { symbol };
}

if (ts.isUntypedOrShorthandAmbientModuleSymbol(aliasedSymbol)) {
if (ts.isShorthandAmbientModuleSymbol(aliasedSymbol)) {
return { symbol, shorthandModuleSymbol: aliasedSymbol };
}

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
12 changes: 8 additions & 4 deletions tests/baselines/reference/extendsUntypedModule.errors.txt
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
/a.ts(2,17): error TS2507: Type 'any' is not a constructor function type.
/a.ts(2,8): error TS6133: 'Bar' is declared but never used.


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

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

This file is not read.

==== /node_modules/bar/index.js (0 errors) ====
Nor is this one.

7 changes: 6 additions & 1 deletion tests/baselines/reference/extendsUntypedModule.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,13 @@

This file is not read.

//// [index.js]
Nor is this one.

//// [a.ts]
import Foo from "foo";
class A extends Foo { }
import Bar from "bar"; // error: unused
export class A extends Foo { }


//// [a.js]
Expand All @@ -31,3 +35,4 @@ var A = (function (_super) {
}
return A;
}(foo_1["default"]));
exports.A = A;
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
Loading