Merge pull request #14935 from Microsoft/allow-extending-from-any

Allow extending from any
This commit is contained in:
Nathan Shively-Sanders 2017-04-06 09:18:01 -07:00 committed by GitHub
commit 3029b8fe38
15 changed files with 127 additions and 50 deletions

View File

@ -204,8 +204,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");
@ -1267,7 +1265,7 @@ namespace ts {
if (moduleSymbol) {
let exportDefaultSymbol: Symbol;
if (isUntypedOrShorthandAmbientModuleSymbol(moduleSymbol)) {
if (isShorthandAmbientModuleSymbol(moduleSymbol)) {
exportDefaultSymbol = moduleSymbol;
}
else {
@ -1347,7 +1345,7 @@ namespace ts {
if (targetSymbol) {
const name = specifier.propertyName || specifier.name;
if (name.text) {
if (isUntypedOrShorthandAmbientModuleSymbol(moduleSymbol)) {
if (isShorthandAmbientModuleSymbol(moduleSymbol)) {
return moduleSymbol;
}
@ -1623,19 +1621,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) {
@ -4405,7 +4399,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 {
@ -4641,7 +4635,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 {
@ -4663,7 +4658,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;
}
@ -4695,7 +4690,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);
@ -4708,6 +4703,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
@ -4761,10 +4759,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));
}
@ -5176,7 +5174,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);
}
}
@ -5418,6 +5420,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);
}
@ -5432,9 +5435,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
@ -22172,7 +22178,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;
}

View File

@ -379,9 +379,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 {

View File

@ -187,7 +187,7 @@ namespace ts.FindAllReferences {
return { symbol };
}
if (ts.isUntypedOrShorthandAmbientModuleSymbol(aliasedSymbol)) {
if (ts.isShorthandAmbientModuleSymbol(aliasedSymbol)) {
return { symbol, shorthandModuleSymbol: aliasedSymbol };
}

View File

@ -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

View File

@ -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

View File

@ -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.

View File

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

View File

@ -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.
}

View File

@ -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.
}

View File

@ -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.
}

View File

@ -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
@ -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.
}

View File

@ -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
@ -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.
}

View File

@ -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

View File

@ -1,9 +1,14 @@
// Test that extending an untyped module is an error, unlike extending unknownSymbol.
// @noImplicitReferences: true
// @noUnusedLocals: true
// @Filename: /node_modules/foo/index.js
This file is not read.
// @Filename: /node_modules/bar/index.js
Nor is this one.
// @Filename: /a.ts
import Foo from "foo";
class A extends Foo { }
import Bar from "bar"; // error: unused
export class A extends Foo { }

View File

@ -12,7 +12,7 @@ verify.numberOfErrorsInCurrentFile(0);
goTo.marker("fooModule");
verify.goToDefinitionIs([]);
verify.quickInfoIs("module <untyped>");
verify.quickInfoIs("");
verify.noReferences();
goTo.marker("foo");