diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index f946c05ee70..70490252abb 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2424,7 +2424,7 @@ module ts { return check(type); function check(type: InterfaceType): boolean { let target = getTargetType(type); - return target === checkBase || forEach(target.baseTypes, check); + return target === checkBase || forEach(getBaseTypes(target), check); } } @@ -2452,6 +2452,70 @@ module ts { return result; } + function getBaseTypes(type: InterfaceType): ObjectType[]{ + let typeWithBaseTypes = type; + if (!typeWithBaseTypes.baseTypes) { + if (type.symbol.flags & SymbolFlags.Class) { + resolveBaseTypesOfClass(typeWithBaseTypes); + } + else if (type.symbol.flags & SymbolFlags.Interface) { + resolveBaseTypesOfInterface(typeWithBaseTypes); + } + else { + Debug.fail("type must be class or interface"); + } + } + + return typeWithBaseTypes.baseTypes; + } + + function resolveBaseTypesOfClass(type: InterfaceTypeWithBaseTypes): void { + type.baseTypes = []; + let declaration = getDeclarationOfKind(type.symbol, SyntaxKind.ClassDeclaration); + let baseTypeNode = getClassExtendsHeritageClauseElement(declaration); + if (baseTypeNode) { + let baseType = getTypeFromHeritageClauseElement(baseTypeNode); + if (baseType !== unknownType) { + if (getTargetType(baseType).flags & TypeFlags.Class) { + if (type !== baseType && !hasBaseType(baseType, type)) { + type.baseTypes.push(baseType); + } + else { + error(declaration, Diagnostics.Type_0_recursively_references_itself_as_a_base_type, typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.WriteArrayAsGenericType)); + } + } + else { + error(baseTypeNode, Diagnostics.A_class_may_only_extend_another_class); + } + } + } + } + + function resolveBaseTypesOfInterface(type: InterfaceTypeWithBaseTypes): void { + type.baseTypes = []; + for (let declaration of type.symbol.declarations) { + if (declaration.kind === SyntaxKind.InterfaceDeclaration && getInterfaceBaseTypeNodes(declaration)) { + for (let node of getInterfaceBaseTypeNodes(declaration)) { + let baseType = getTypeFromHeritageClauseElement(node); + + if (baseType !== unknownType) { + if (getTargetType(baseType).flags & (TypeFlags.Class | TypeFlags.Interface)) { + if (type !== baseType && !hasBaseType(baseType, type)) { + type.baseTypes.push(baseType); + } + else { + error(declaration, Diagnostics.Type_0_recursively_references_itself_as_a_base_type, typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.WriteArrayAsGenericType)); + } + } + else { + error(node, Diagnostics.An_interface_may_only_extend_a_class_or_another_interface); + } + } + } + } + } + } + function getDeclaredTypeOfClass(symbol: Symbol): InterfaceType { let links = getSymbolLinks(symbol); if (!links.declaredType) { @@ -2465,25 +2529,7 @@ module ts { (type).target = type; (type).typeArguments = type.typeParameters; } - type.baseTypes = []; - let declaration = getDeclarationOfKind(symbol, SyntaxKind.ClassDeclaration); - let baseTypeNode = getClassExtendsHeritageClauseElement(declaration); - if (baseTypeNode) { - let baseType = getTypeFromHeritageClauseElement(baseTypeNode); - if (baseType !== unknownType) { - if (getTargetType(baseType).flags & TypeFlags.Class) { - if (type !== baseType && !hasBaseType(baseType, type)) { - type.baseTypes.push(baseType); - } - else { - error(declaration, Diagnostics.Type_0_recursively_references_itself_as_a_base_type, typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.WriteArrayAsGenericType)); - } - } - else { - error(baseTypeNode, Diagnostics.A_class_may_only_extend_another_class); - } - } - } + type.declaredProperties = getNamedMembers(symbol.members); type.declaredCallSignatures = emptyArray; type.declaredConstructSignatures = emptyArray; @@ -2506,28 +2552,7 @@ module ts { (type).target = type; (type).typeArguments = type.typeParameters; } - type.baseTypes = []; - forEach(symbol.declarations, declaration => { - if (declaration.kind === SyntaxKind.InterfaceDeclaration && getInterfaceBaseTypeNodes(declaration)) { - forEach(getInterfaceBaseTypeNodes(declaration), node => { - let baseType = getTypeFromHeritageClauseElement(node); - if (baseType !== unknownType) { - if (getTargetType(baseType).flags & (TypeFlags.Class | TypeFlags.Interface)) { - if (type !== baseType && !hasBaseType(baseType, type)) { - type.baseTypes.push(baseType); - } - else { - error(declaration, Diagnostics.Type_0_recursively_references_itself_as_a_base_type, typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.WriteArrayAsGenericType)); - } - } - else { - error(node, Diagnostics.An_interface_may_only_extend_a_class_or_another_interface); - } - } - }); - } - }); type.declaredProperties = getNamedMembers(symbol.members); type.declaredCallSignatures = getSignaturesOfSymbol(symbol.members["__call"]); type.declaredConstructSignatures = getSignaturesOfSymbol(symbol.members["__new"]); @@ -2647,15 +2672,16 @@ module ts { let constructSignatures = type.declaredConstructSignatures; let stringIndexType = type.declaredStringIndexType; let numberIndexType = type.declaredNumberIndexType; - if (type.baseTypes.length) { + let baseTypes = getBaseTypes(type); + if (baseTypes.length) { members = createSymbolTable(type.declaredProperties); - forEach(type.baseTypes, baseType => { + for (let baseType of baseTypes) { addInheritedMembers(members, getPropertiesOfObjectType(baseType)); callSignatures = concatenate(callSignatures, getSignaturesOfType(baseType, SignatureKind.Call)); constructSignatures = concatenate(constructSignatures, getSignaturesOfType(baseType, SignatureKind.Construct)); stringIndexType = stringIndexType || getIndexTypeOfType(baseType, IndexKind.String); numberIndexType = numberIndexType || getIndexTypeOfType(baseType, IndexKind.Number); - }); + } } setObjectTypeMembers(type, members, callSignatures, constructSignatures, stringIndexType, numberIndexType); } @@ -2668,7 +2694,7 @@ module ts { let constructSignatures = instantiateList(target.declaredConstructSignatures, mapper, instantiateSignature); let stringIndexType = target.declaredStringIndexType ? instantiateType(target.declaredStringIndexType, mapper) : undefined; let numberIndexType = target.declaredNumberIndexType ? instantiateType(target.declaredNumberIndexType, mapper) : undefined; - forEach(target.baseTypes, baseType => { + forEach(getBaseTypes(target), baseType => { let instantiatedBaseType = instantiateType(baseType, mapper); addInheritedMembers(members, getPropertiesOfObjectType(instantiatedBaseType)); callSignatures = concatenate(callSignatures, getSignaturesOfType(instantiatedBaseType, SignatureKind.Call)); @@ -2697,9 +2723,10 @@ module ts { sig.minArgumentCount, sig.hasRestParameter, sig.hasStringLiterals); } - function getDefaultConstructSignatures(classType: InterfaceType): Signature[] { - if (classType.baseTypes.length) { - let baseType = classType.baseTypes[0]; + function getDefaultConstructSignatures(classType: InterfaceType): Signature[]{ + let baseTypes = getBaseTypes(classType); + if (baseTypes.length) { + let baseType = baseTypes[0]; let baseSignatures = getSignaturesOfType(getTypeOfSymbol(baseType.symbol), SignatureKind.Construct); return map(baseSignatures, baseSignature => { let signature = baseType.flags & TypeFlags.Reference ? @@ -2821,9 +2848,10 @@ module ts { if (!constructSignatures.length) { constructSignatures = getDefaultConstructSignatures(classType); } - if (classType.baseTypes.length) { + let baseTypes = getBaseTypes(classType); + if (baseTypes.length) { members = createSymbolTable(getNamedMembers(members)); - addInheritedMembers(members, getPropertiesOfObjectType(getTypeOfSymbol(classType.baseTypes[0].symbol))); + addInheritedMembers(members, getPropertiesOfObjectType(getTypeOfSymbol(baseTypes[0].symbol))); } } stringIndexType = undefined; @@ -5551,7 +5579,8 @@ module ts { let baseClass: Type; if (enclosingClass && getClassExtendsHeritageClauseElement(enclosingClass)) { let classType = getDeclaredTypeOfSymbol(getSymbolOfNode(enclosingClass)); - baseClass = classType.baseTypes.length && classType.baseTypes[0]; + let baseTypes = getBaseTypes(classType); + baseClass = baseTypes.length && baseTypes[0]; } if (!baseClass) { @@ -9834,7 +9863,7 @@ module ts { errorNode = declaredNumberIndexer || declaredStringIndexer; // condition 'errorNode === undefined' may appear if types does not declare nor string neither number indexer if (!errorNode && (type.flags & TypeFlags.Interface)) { - let someBaseTypeHasBothIndexers = forEach((type).baseTypes, base => getIndexTypeOfType(base, IndexKind.String) && getIndexTypeOfType(base, IndexKind.Number)); + let someBaseTypeHasBothIndexers = forEach(getBaseTypes(type), base => getIndexTypeOfType(base, IndexKind.String) && getIndexTypeOfType(base, IndexKind.Number)); errorNode = someBaseTypeHasBothIndexers ? undefined : type.symbol.declarations[0]; } } @@ -9874,7 +9903,7 @@ module ts { // for interfaces property and indexer might be inherited from different bases // check if any base class already has both property and indexer. // check should be performed only if 'type' is the first type that brings property\indexer together - let someBaseClassHasBothPropertyAndIndexer = forEach((containingType).baseTypes, base => getPropertyOfObjectType(base, prop.name) && getIndexTypeOfType(base, indexKind)); + let someBaseClassHasBothPropertyAndIndexer = forEach(getBaseTypes(containingType), base => getPropertyOfObjectType(base, prop.name) && getIndexTypeOfType(base, indexKind)); errorNode = someBaseClassHasBothPropertyAndIndexer ? undefined : containingType.symbol.declarations[0]; } @@ -9958,9 +9987,10 @@ module ts { emitExtends = emitExtends || !isInAmbientContext(node); checkHeritageClauseElement(baseTypeNode); } - if (type.baseTypes.length) { + let baseTypes = getBaseTypes(type); + if (baseTypes.length) { if (produceDiagnostics) { - let baseType = type.baseTypes[0]; + let baseType = baseTypes[0]; checkTypeAssignableTo(type, baseType, node.name || node, Diagnostics.Class_0_incorrectly_extends_base_class_1); let staticBaseType = getTypeOfSymbol(baseType.symbol); checkTypeAssignableTo(staticType, getTypeWithoutConstructors(staticBaseType), node.name || node, @@ -9974,7 +10004,7 @@ module ts { } } - if (type.baseTypes.length || (baseTypeNode && compilerOptions.separateCompilation)) { + if (baseTypes.length || (baseTypeNode && compilerOptions.separateCompilation)) { // Check that base type can be evaluated as expression checkExpressionOrQualifiedName(baseTypeNode.expression); } @@ -10118,7 +10148,8 @@ module ts { } function checkInheritedPropertiesAreIdentical(type: InterfaceType, typeNode: Node): boolean { - if (!type.baseTypes.length || type.baseTypes.length === 1) { + let baseTypes = getBaseTypes(type); + if (baseTypes.length < 2) { return true; } @@ -10126,7 +10157,7 @@ module ts { forEach(type.declaredProperties, p => { seen[p.name] = { prop: p, containingType: type }; }); let ok = true; - for (let base of type.baseTypes) { + for (let base of baseTypes) { let properties = getPropertiesOfObjectType(base); for (let prop of properties) { if (!hasProperty(seen, prop.name)) { @@ -10174,7 +10205,7 @@ module ts { let type = getDeclaredTypeOfSymbol(symbol); // run subsequent checks only if first set succeeded if (checkInheritedPropertiesAreIdentical(type, node.name)) { - forEach(type.baseTypes, baseType => { + forEach(getBaseTypes(type), baseType => { checkTypeAssignableTo(type, baseType, node.name, Diagnostics.Interface_0_incorrectly_extends_interface_1); }); checkIndexConstraints(type); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 370ece347a3..9fa4c941a94 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1485,7 +1485,6 @@ module ts { // Class and interface types (TypeFlags.Class and TypeFlags.Interface) export interface InterfaceType extends ObjectType { typeParameters: TypeParameter[]; // Type parameters (undefined if non-generic) - baseTypes: ObjectType[]; // Base types declaredProperties: Symbol[]; // Declared members declaredCallSignatures: Signature[]; // Declared call signatures declaredConstructSignatures: Signature[]; // Declared construct signatures @@ -1493,6 +1492,10 @@ module ts { declaredNumberIndexType: Type; // Declared numeric index type } + export interface InterfaceTypeWithBaseTypes extends InterfaceType { + baseTypes: ObjectType[]; + } + // Type references (TypeFlags.Reference) export interface TypeReference extends ObjectType { target: GenericType; // Type reference target diff --git a/tests/baselines/reference/classDoesNotDependOnBaseTypes.js b/tests/baselines/reference/classDoesNotDependOnBaseTypes.js new file mode 100644 index 00000000000..a62d5b1532b --- /dev/null +++ b/tests/baselines/reference/classDoesNotDependOnBaseTypes.js @@ -0,0 +1,38 @@ +//// [classDoesNotDependOnBaseTypes.ts] +var x: StringTree; +if (typeof x !== "string") { + x[0] = ""; + x[0] = new StringTreeCollection; +} + +type StringTree = string | StringTreeCollection; +class StringTreeCollectionBase { + [n: number]: StringTree; +} + +class StringTreeCollection extends StringTreeCollectionBase { } + +//// [classDoesNotDependOnBaseTypes.js] +var __extends = this.__extends || function (d, b) { + for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; + function __() { this.constructor = d; } + __.prototype = b.prototype; + d.prototype = new __(); +}; +var x; +if (typeof x !== "string") { + x[0] = ""; + x[0] = new StringTreeCollection; +} +var StringTreeCollectionBase = (function () { + function StringTreeCollectionBase() { + } + return StringTreeCollectionBase; +})(); +var StringTreeCollection = (function (_super) { + __extends(StringTreeCollection, _super); + function StringTreeCollection() { + _super.apply(this, arguments); + } + return StringTreeCollection; +})(StringTreeCollectionBase); diff --git a/tests/baselines/reference/classDoesNotDependOnBaseTypes.symbols b/tests/baselines/reference/classDoesNotDependOnBaseTypes.symbols new file mode 100644 index 00000000000..e45b00f1e8a --- /dev/null +++ b/tests/baselines/reference/classDoesNotDependOnBaseTypes.symbols @@ -0,0 +1,32 @@ +=== tests/cases/conformance/types/typeAliases/classDoesNotDependOnBaseTypes.ts === +var x: StringTree; +>x : Symbol(x, Decl(classDoesNotDependOnBaseTypes.ts, 0, 3)) +>StringTree : Symbol(StringTree, Decl(classDoesNotDependOnBaseTypes.ts, 4, 1)) + +if (typeof x !== "string") { +>x : Symbol(x, Decl(classDoesNotDependOnBaseTypes.ts, 0, 3)) + + x[0] = ""; +>x : Symbol(x, Decl(classDoesNotDependOnBaseTypes.ts, 0, 3)) + + x[0] = new StringTreeCollection; +>x : Symbol(x, Decl(classDoesNotDependOnBaseTypes.ts, 0, 3)) +>StringTreeCollection : Symbol(StringTreeCollection, Decl(classDoesNotDependOnBaseTypes.ts, 9, 1)) +} + +type StringTree = string | StringTreeCollection; +>StringTree : Symbol(StringTree, Decl(classDoesNotDependOnBaseTypes.ts, 4, 1)) +>StringTreeCollection : Symbol(StringTreeCollection, Decl(classDoesNotDependOnBaseTypes.ts, 9, 1)) + +class StringTreeCollectionBase { +>StringTreeCollectionBase : Symbol(StringTreeCollectionBase, Decl(classDoesNotDependOnBaseTypes.ts, 6, 48)) + + [n: number]: StringTree; +>n : Symbol(n, Decl(classDoesNotDependOnBaseTypes.ts, 8, 5)) +>StringTree : Symbol(StringTree, Decl(classDoesNotDependOnBaseTypes.ts, 4, 1)) +} + +class StringTreeCollection extends StringTreeCollectionBase { } +>StringTreeCollection : Symbol(StringTreeCollection, Decl(classDoesNotDependOnBaseTypes.ts, 9, 1)) +>StringTreeCollectionBase : Symbol(StringTreeCollectionBase, Decl(classDoesNotDependOnBaseTypes.ts, 6, 48)) + diff --git a/tests/baselines/reference/classDoesNotDependOnBaseTypes.types b/tests/baselines/reference/classDoesNotDependOnBaseTypes.types new file mode 100644 index 00000000000..c342f0ea002 --- /dev/null +++ b/tests/baselines/reference/classDoesNotDependOnBaseTypes.types @@ -0,0 +1,43 @@ +=== tests/cases/conformance/types/typeAliases/classDoesNotDependOnBaseTypes.ts === +var x: StringTree; +>x : string | StringTreeCollection +>StringTree : string | StringTreeCollection + +if (typeof x !== "string") { +>typeof x !== "string" : boolean +>typeof x : string +>x : string | StringTreeCollection +>"string" : string + + x[0] = ""; +>x[0] = "" : string +>x[0] : string | StringTreeCollection +>x : StringTreeCollection +>0 : number +>"" : string + + x[0] = new StringTreeCollection; +>x[0] = new StringTreeCollection : StringTreeCollection +>x[0] : string | StringTreeCollection +>x : StringTreeCollection +>0 : number +>new StringTreeCollection : StringTreeCollection +>StringTreeCollection : typeof StringTreeCollection +} + +type StringTree = string | StringTreeCollection; +>StringTree : string | StringTreeCollection +>StringTreeCollection : StringTreeCollection + +class StringTreeCollectionBase { +>StringTreeCollectionBase : StringTreeCollectionBase + + [n: number]: StringTree; +>n : number +>StringTree : string | StringTreeCollection +} + +class StringTreeCollection extends StringTreeCollectionBase { } +>StringTreeCollection : StringTreeCollection +>StringTreeCollectionBase : StringTreeCollectionBase + diff --git a/tests/baselines/reference/interfaceDoesNotDependOnBaseTypes.js b/tests/baselines/reference/interfaceDoesNotDependOnBaseTypes.js new file mode 100644 index 00000000000..4d8a23ddd25 --- /dev/null +++ b/tests/baselines/reference/interfaceDoesNotDependOnBaseTypes.js @@ -0,0 +1,16 @@ +//// [interfaceDoesNotDependOnBaseTypes.ts] +var x: StringTree; +if (typeof x !== "string") { + x.push(""); + x.push([""]); +} + +type StringTree = string | StringTreeArray; +interface StringTreeArray extends Array { } + +//// [interfaceDoesNotDependOnBaseTypes.js] +var x; +if (typeof x !== "string") { + x.push(""); + x.push([""]); +} diff --git a/tests/baselines/reference/interfaceDoesNotDependOnBaseTypes.symbols b/tests/baselines/reference/interfaceDoesNotDependOnBaseTypes.symbols new file mode 100644 index 00000000000..6b45cd1daad --- /dev/null +++ b/tests/baselines/reference/interfaceDoesNotDependOnBaseTypes.symbols @@ -0,0 +1,28 @@ +=== tests/cases/conformance/types/typeAliases/interfaceDoesNotDependOnBaseTypes.ts === +var x: StringTree; +>x : Symbol(x, Decl(interfaceDoesNotDependOnBaseTypes.ts, 0, 3)) +>StringTree : Symbol(StringTree, Decl(interfaceDoesNotDependOnBaseTypes.ts, 4, 1)) + +if (typeof x !== "string") { +>x : Symbol(x, Decl(interfaceDoesNotDependOnBaseTypes.ts, 0, 3)) + + x.push(""); +>x.push : Symbol(Array.push, Decl(lib.d.ts, 1016, 29)) +>x : Symbol(x, Decl(interfaceDoesNotDependOnBaseTypes.ts, 0, 3)) +>push : Symbol(Array.push, Decl(lib.d.ts, 1016, 29)) + + x.push([""]); +>x.push : Symbol(Array.push, Decl(lib.d.ts, 1016, 29)) +>x : Symbol(x, Decl(interfaceDoesNotDependOnBaseTypes.ts, 0, 3)) +>push : Symbol(Array.push, Decl(lib.d.ts, 1016, 29)) +} + +type StringTree = string | StringTreeArray; +>StringTree : Symbol(StringTree, Decl(interfaceDoesNotDependOnBaseTypes.ts, 4, 1)) +>StringTreeArray : Symbol(StringTreeArray, Decl(interfaceDoesNotDependOnBaseTypes.ts, 6, 43)) + +interface StringTreeArray extends Array { } +>StringTreeArray : Symbol(StringTreeArray, Decl(interfaceDoesNotDependOnBaseTypes.ts, 6, 43)) +>Array : Symbol(Array, Decl(lib.d.ts, 1000, 23), Decl(lib.d.ts, 1171, 11)) +>StringTree : Symbol(StringTree, Decl(interfaceDoesNotDependOnBaseTypes.ts, 4, 1)) + diff --git a/tests/baselines/reference/interfaceDoesNotDependOnBaseTypes.types b/tests/baselines/reference/interfaceDoesNotDependOnBaseTypes.types new file mode 100644 index 00000000000..7b5d60a5261 --- /dev/null +++ b/tests/baselines/reference/interfaceDoesNotDependOnBaseTypes.types @@ -0,0 +1,36 @@ +=== tests/cases/conformance/types/typeAliases/interfaceDoesNotDependOnBaseTypes.ts === +var x: StringTree; +>x : string | StringTreeArray +>StringTree : string | StringTreeArray + +if (typeof x !== "string") { +>typeof x !== "string" : boolean +>typeof x : string +>x : string | StringTreeArray +>"string" : string + + x.push(""); +>x.push("") : number +>x.push : (...items: (string | StringTreeArray)[]) => number +>x : StringTreeArray +>push : (...items: (string | StringTreeArray)[]) => number +>"" : string + + x.push([""]); +>x.push([""]) : number +>x.push : (...items: (string | StringTreeArray)[]) => number +>x : StringTreeArray +>push : (...items: (string | StringTreeArray)[]) => number +>[""] : string[] +>"" : string +} + +type StringTree = string | StringTreeArray; +>StringTree : string | StringTreeArray +>StringTreeArray : StringTreeArray + +interface StringTreeArray extends Array { } +>StringTreeArray : StringTreeArray +>Array : T[] +>StringTree : string | StringTreeArray + diff --git a/tests/cases/conformance/types/typeAliases/classDoesNotDependOnBaseTypes.ts b/tests/cases/conformance/types/typeAliases/classDoesNotDependOnBaseTypes.ts new file mode 100644 index 00000000000..98f94718f30 --- /dev/null +++ b/tests/cases/conformance/types/typeAliases/classDoesNotDependOnBaseTypes.ts @@ -0,0 +1,12 @@ +var x: StringTree; +if (typeof x !== "string") { + x[0] = ""; + x[0] = new StringTreeCollection; +} + +type StringTree = string | StringTreeCollection; +class StringTreeCollectionBase { + [n: number]: StringTree; +} + +class StringTreeCollection extends StringTreeCollectionBase { } \ No newline at end of file diff --git a/tests/cases/conformance/types/typeAliases/interfaceDoesNotDependOnBaseTypes.ts b/tests/cases/conformance/types/typeAliases/interfaceDoesNotDependOnBaseTypes.ts new file mode 100644 index 00000000000..4e380e96b37 --- /dev/null +++ b/tests/cases/conformance/types/typeAliases/interfaceDoesNotDependOnBaseTypes.ts @@ -0,0 +1,8 @@ +var x: StringTree; +if (typeof x !== "string") { + x.push(""); + x.push([""]); +} + +type StringTree = string | StringTreeArray; +interface StringTreeArray extends Array { } \ No newline at end of file