Implement tuple types as type references to synthesized generic types

This commit is contained in:
Anders Hejlsberg 2016-08-21 16:18:45 -07:00
parent 66f6f9ba05
commit 582d8b8fc8
2 changed files with 87 additions and 95 deletions

View File

@ -106,7 +106,7 @@ namespace ts {
isOptionalParameter
};
const tupleTypes = createMap<TupleType>();
const tupleTypes: TupleType[] = [];
const unionTypes = createMap<UnionType>();
const intersectionTypes = createMap<IntersectionType>();
const stringLiteralTypes = createMap<LiteralType>();
@ -2145,9 +2145,6 @@ namespace ts {
// The specified symbol flags need to be reinterpreted as type flags
buildSymbolDisplay(type.symbol, writer, enclosingDeclaration, SymbolFlags.Type, SymbolFormatFlags.None, nextFlags);
}
else if (type.flags & TypeFlags.Tuple) {
writeTupleType(<TupleType>type);
}
else if (!(flags & TypeFormatFlags.InTypeAlias) && type.flags & (TypeFlags.Anonymous | TypeFlags.UnionOrIntersection) && type.aliasSymbol) {
const typeArguments = type.aliasTypeArguments;
writeSymbolTypeReference(type.aliasSymbol, typeArguments, 0, typeArguments ? typeArguments.length : 0, nextFlags);
@ -2214,6 +2211,11 @@ namespace ts {
writePunctuation(writer, SyntaxKind.OpenBracketToken);
writePunctuation(writer, SyntaxKind.CloseBracketToken);
}
else if (type.target.flags & TypeFlags.Tuple) {
writePunctuation(writer, SyntaxKind.OpenBracketToken);
writeTypeList(type.typeArguments.slice(0, type.target.typeParameters.length), SyntaxKind.CommaToken);
writePunctuation(writer, SyntaxKind.CloseBracketToken);
}
else {
// Write the type reference in the format f<A>.g<B>.C<X, Y> where A and B are type arguments
// for outer type parameters, and f and g are the respective declaring containers of those
@ -2242,12 +2244,6 @@ namespace ts {
}
}
function writeTupleType(type: TupleType) {
writePunctuation(writer, SyntaxKind.OpenBracketToken);
writeTypeList(type.elementTypes, SyntaxKind.CommaToken);
writePunctuation(writer, SyntaxKind.CloseBracketToken);
}
function writeUnionOrIntersectionType(type: UnionOrIntersectionType, flags: TypeFormatFlags) {
if (flags & TypeFormatFlags.InElementType) {
writePunctuation(writer, SyntaxKind.OpenParenToken);
@ -2958,7 +2954,7 @@ namespace ts {
: elementType;
if (!type) {
if (isTupleType(parentType)) {
error(declaration, Diagnostics.Tuple_type_0_with_length_1_cannot_be_assigned_to_tuple_with_length_2, typeToString(parentType), (<TupleType>parentType).elementTypes.length, pattern.elements.length);
error(declaration, Diagnostics.Tuple_type_0_with_length_1_cannot_be_assigned_to_tuple_with_length_2, typeToString(parentType), getTypeReferenceArity(<TypeReference>parentType), pattern.elements.length);
}
else {
error(declaration, Diagnostics.Type_0_has_no_property_1, typeToString(parentType), propName);
@ -3150,12 +3146,12 @@ namespace ts {
}
// If the pattern has at least one element, and no rest element, then it should imply a tuple type.
const elementTypes = map(elements, e => e.kind === SyntaxKind.OmittedExpression ? anyType : getTypeFromBindingElement(e, includePatternInType, reportErrors));
let result = createTupleType(elementTypes);
if (includePatternInType) {
const result = createNewTupleType(elementTypes);
result = cloneTypeReference(result);
result.pattern = pattern;
return result;
}
return createTupleType(elementTypes);
return result;
}
// Return the type implied by a binding pattern. This is the type implied purely by the binding pattern itself
@ -3564,18 +3560,19 @@ namespace ts {
}
function getBaseTypes(type: InterfaceType): ObjectType[] {
const isClass = type.symbol.flags & SymbolFlags.Class;
const isInterface = type.symbol.flags & SymbolFlags.Interface;
if (!type.resolvedBaseTypes) {
if (!isClass && !isInterface) {
Debug.fail("type must be class or interface");
if (type.flags & TypeFlags.Tuple) {
type.resolvedBaseTypes = [createArrayType(getUnionType(type.typeParameters))];
}
if (isClass) {
else if (type.symbol.flags & SymbolFlags.Class) {
resolveBaseTypesOfClass(type);
}
if (isInterface) {
else if (type.symbol.flags & SymbolFlags.Interface) {
resolveBaseTypesOfInterface(type);
}
else {
Debug.fail("type must be class or interface");
}
}
return type.resolvedBaseTypes;
}
@ -4001,20 +3998,25 @@ namespace ts {
return createTypeReference((<TypeReference>type).target,
concatenate((<TypeReference>type).typeArguments, [thisArgument || (<TypeReference>type).target.thisType]));
}
if (type.flags & TypeFlags.Tuple) {
return createTupleType((type as TupleType).elementTypes, thisArgument);
}
return type;
}
function resolveObjectTypeMembers(type: ObjectType, source: InterfaceTypeWithDeclaredMembers, typeParameters: TypeParameter[], typeArguments: Type[]) {
let mapper = identityMapper;
let members = source.symbol.members;
let callSignatures = source.declaredCallSignatures;
let constructSignatures = source.declaredConstructSignatures;
let stringIndexInfo = source.declaredStringIndexInfo;
let numberIndexInfo = source.declaredNumberIndexInfo;
if (!rangeEquals(typeParameters, typeArguments, 0, typeParameters.length)) {
let mapper: TypeMapper;
let members: SymbolTable;
let callSignatures: Signature[];
let constructSignatures: Signature[];
let stringIndexInfo: IndexInfo;
let numberIndexInfo: IndexInfo;
if (rangeEquals(typeParameters, typeArguments, 0, typeParameters.length)) {
mapper = identityMapper;
members = source.symbol ? source.symbol.members : createSymbolTable(source.declaredProperties);
callSignatures = source.declaredCallSignatures;
constructSignatures = source.declaredConstructSignatures;
stringIndexInfo = source.declaredStringIndexInfo;
numberIndexInfo = source.declaredNumberIndexInfo;
}
else {
mapper = createTypeMapper(typeParameters, typeArguments);
members = createInstantiatedSymbolTable(source.declaredProperties, mapper, /*mappingThisOnly*/ typeParameters.length === 1);
callSignatures = instantiateList(source.declaredCallSignatures, mapper, instantiateSignature);
@ -4024,7 +4026,7 @@ namespace ts {
}
const baseTypes = getBaseTypes(source);
if (baseTypes.length) {
if (members === source.symbol.members) {
if (source.symbol && members === source.symbol.members) {
members = createSymbolTable(source.declaredProperties);
}
const thisArgument = lastOrUndefined(typeArguments);
@ -4094,26 +4096,6 @@ namespace ts {
return result;
}
function createTupleTypeMemberSymbols(memberTypes: Type[]): SymbolTable {
const members = createMap<Symbol>();
for (let i = 0; i < memberTypes.length; i++) {
const symbol = <TransientSymbol>createSymbol(SymbolFlags.Property | SymbolFlags.Transient, "" + i);
symbol.type = memberTypes[i];
members[i] = symbol;
}
return members;
}
function resolveTupleTypeMembers(type: TupleType) {
const arrayElementType = getUnionType(type.elementTypes);
// Make the tuple type itself the 'this' type by including an extra type argument
// (Unless it's provided in the case that the tuple is a type parameter constraint)
const arrayType = resolveStructuredTypeMembers(createTypeFromGenericGlobalType(globalArrayType, [arrayElementType, type.thisType || type]));
const members = createTupleTypeMemberSymbols(type.elementTypes);
addInheritedMembers(members, arrayType.properties);
setObjectTypeMembers(type, members, arrayType.callSignatures, arrayType.constructSignatures, arrayType.stringIndexInfo, arrayType.numberIndexInfo);
}
function findMatchingSignature(signatureList: Signature[], signature: Signature, partialMatch: boolean, ignoreThisTypes: boolean, ignoreReturnTypes: boolean): Signature {
for (const s of signatureList) {
if (compareSignaturesIdentical(s, signature, partialMatch, ignoreThisTypes, ignoreReturnTypes, compareTypesIdentical)) {
@ -4292,9 +4274,6 @@ namespace ts {
else if (type.flags & TypeFlags.Anonymous) {
resolveAnonymousTypeMembers(<AnonymousType>type);
}
else if (type.flags & TypeFlags.Tuple) {
resolveTupleTypeMembers(<TupleType>type);
}
else if (type.flags & TypeFlags.Union) {
resolveUnionTypeMembers(<UnionType>type);
}
@ -4992,6 +4971,13 @@ namespace ts {
return type;
}
function cloneTypeReference(source: TypeReference): TypeReference {
let type = <TypeReference>createObjectType(source.flags, source.symbol);
type.target = source.target;
type.typeArguments = source.typeArguments;
return type;
}
// Get type from reference to class or interface
function getTypeFromClassOrInterfaceReference(node: TypeReferenceNode | ExpressionWithTypeArguments | JSDocTypeReference, symbol: Symbol): Type {
const type = <InterfaceType>getDeclaredTypeOfSymbol(getMergedSymbol(symbol));
@ -5234,17 +5220,44 @@ namespace ts {
return links.resolvedType;
}
function createTupleType(elementTypes: Type[], thisType?: Type) {
const id = getTypeListId(elementTypes) + "," + (thisType ? thisType.id : 0);
return tupleTypes[id] || (tupleTypes[id] = createNewTupleType(elementTypes, thisType));
function createTupleTypeOfArity(arity: number): TupleType {
const typeParameters: TypeParameter[] = [];
const properties: Symbol[] = [];
for (let i = 0; i < arity; i++) {
const typeParameter = <TypeParameter>createType(TypeFlags.TypeParameter);
typeParameters.push(typeParameter);
const property = <TransientSymbol>createSymbol(SymbolFlags.Property | SymbolFlags.Transient, "" + i);
property.type = typeParameter;
properties.push(property);
}
const type = <TupleType & InterfaceTypeWithDeclaredMembers>createObjectType(TypeFlags.Tuple | TypeFlags.Reference);
type.typeParameters = typeParameters;
type.outerTypeParameters = undefined;
type.localTypeParameters = typeParameters;
type.instantiations = createMap<TypeReference>();
type.instantiations[getTypeListId(type.typeParameters)] = <GenericType>type;
type.target = <GenericType>type;
type.typeArguments = type.typeParameters;
type.thisType = <TypeParameter>createType(TypeFlags.TypeParameter | TypeFlags.ThisType);
type.thisType.constraint = type;
type.declaredProperties = properties;
type.declaredCallSignatures = emptyArray;
type.declaredConstructSignatures = emptyArray;
type.declaredStringIndexInfo = undefined;
type.declaredNumberIndexInfo = undefined;
return type;
}
function createNewTupleType(elementTypes: Type[], thisType?: Type) {
const propagatedFlags = getPropagatingFlagsOfTypes(elementTypes, /*excludeKinds*/ 0);
const type = <TupleType>createObjectType(TypeFlags.Tuple | propagatedFlags);
type.elementTypes = elementTypes;
type.thisType = thisType;
return type;
function getTupleTypeOfArity(arity: number): TupleType {
return tupleTypes[arity] || (tupleTypes[arity] = createTupleTypeOfArity(arity));
}
function getTypeReferenceArity(type: TypeReference): number {
return type.target.typeParameters.length;
}
function createTupleType(elementTypes: Type[]) {
return createTypeReference(getTupleTypeOfArity(elementTypes.length), elementTypes);
}
function getTypeFromTupleTypeNode(node: TupleTypeNode): Type {
@ -5845,9 +5858,6 @@ namespace ts {
if (type.flags & TypeFlags.Reference) {
return createTypeReference((<TypeReference>type).target, instantiateList((<TypeReference>type).typeArguments, mapper, instantiateType));
}
if (type.flags & TypeFlags.Tuple) {
return createTupleType(instantiateList((<TupleType>type).elementTypes, mapper, instantiateType));
}
if (type.flags & TypeFlags.Union && !(type.flags & TypeFlags.Primitive)) {
return getUnionType(instantiateList((<UnionType>type).types, mapper, instantiateType), /*subtypeReduction*/ false, type.aliasSymbol, mapper.targetTypes);
}
@ -7168,8 +7178,8 @@ namespace ts {
* Check if a Type was written as a tuple type literal.
* Prefer using isTupleLikeType() unless the use of `elementTypes` is required.
*/
function isTupleType(type: Type): type is TupleType {
return !!(type.flags & TypeFlags.Tuple);
function isTupleType(type: Type): boolean {
return !!(type.flags & TypeFlags.Reference && (<TypeReference>type).target.flags & TypeFlags.Tuple);
}
function getFalsyFlagsOfTypes(types: Type[]): TypeFlags {
@ -7301,11 +7311,8 @@ namespace ts {
if (type.flags & TypeFlags.Union) {
return getUnionType(map((<UnionType>type).types, getWidenedConstituentType));
}
if (isArrayType(type)) {
return createArrayType(getWidenedType((<TypeReference>type).typeArguments[0]));
}
if (isTupleType(type)) {
return createTupleType(map(type.elementTypes, getWidenedType));
if (isArrayType(type) || isTupleType(type)) {
return createTypeReference((<TypeReference>type).target, map((<TypeReference>type).typeArguments, getWidenedType));
}
}
return type;
@ -7331,11 +7338,8 @@ namespace ts {
}
}
}
if (isArrayType(type)) {
return reportWideningErrorsInType((<TypeReference>type).typeArguments[0]);
}
if (isTupleType(type)) {
for (const t of type.elementTypes) {
if (isArrayType(type) || isTupleType(type)) {
for (const t of (<TypeReference>type).typeArguments) {
if (reportWideningErrorsInType(t)) {
errorReported = true;
}
@ -7445,7 +7449,6 @@ namespace ts {
function couldContainTypeParameters(type: Type): boolean {
return !!(type.flags & TypeFlags.TypeParameter ||
type.flags & TypeFlags.Reference && forEach((<TypeReference>type).typeArguments, couldContainTypeParameters) ||
type.flags & TypeFlags.Tuple && forEach((<TupleType>type).elementTypes, couldContainTypeParameters) ||
type.flags & TypeFlags.Anonymous && type.symbol && type.symbol.flags & (SymbolFlags.Method | SymbolFlags.TypeLiteral | SymbolFlags.Class) ||
type.flags & TypeFlags.UnionOrIntersection && couldUnionOrIntersectionContainTypeParameters(<UnionOrIntersectionType>type));
}
@ -7548,14 +7551,6 @@ namespace ts {
inferFromTypes(sourceTypes[i], targetTypes[i]);
}
}
else if (source.flags & TypeFlags.Tuple && target.flags & TypeFlags.Tuple && (<TupleType>source).elementTypes.length === (<TupleType>target).elementTypes.length) {
// If source and target are tuples of the same size, infer from element types
const sourceTypes = (<TupleType>source).elementTypes;
const targetTypes = (<TupleType>target).elementTypes;
for (let i = 0; i < sourceTypes.length; i++) {
inferFromTypes(sourceTypes[i], targetTypes[i]);
}
}
else if (target.flags & TypeFlags.UnionOrIntersection) {
const targetTypes = (<UnionOrIntersectionType>target).types;
let typeParameterCount = 0;
@ -9913,7 +9908,7 @@ namespace ts {
// If array literal is actually a destructuring pattern, mark it as an implied type. We do this such
// that we get the same behavior for "var [x, y] = []" and "[x, y] = []".
if (inDestructuringPattern && elementTypes.length) {
const type = createNewTupleType(elementTypes);
const type = cloneTypeReference(createTupleType(elementTypes));
type.pattern = node;
return type;
}
@ -9927,7 +9922,7 @@ namespace ts {
for (let i = elementTypes.length; i < patternElements.length; i++) {
const patternElement = patternElements[i];
if (hasDefaultValue(patternElement)) {
elementTypes.push((<TupleType>contextualType).elementTypes[i]);
elementTypes.push((<TypeReference>contextualType).typeArguments[i]);
}
else {
if (patternElement.kind !== SyntaxKind.OmittedExpression) {
@ -13038,7 +13033,7 @@ namespace ts {
// such as NodeCheckFlags.LexicalThis on "this"expression.
checkExpression(element);
if (isTupleType(sourceType)) {
error(element, Diagnostics.Tuple_type_0_with_length_1_cannot_be_assigned_to_tuple_with_length_2, typeToString(sourceType), (<TupleType>sourceType).elementTypes.length, elements.length);
error(element, Diagnostics.Tuple_type_0_with_length_1_cannot_be_assigned_to_tuple_with_length_2, typeToString(sourceType), getTypeReferenceArity(<TypeReference>sourceType), elements.length);
}
else {
error(element, Diagnostics.Type_0_has_no_property_1, typeToString(sourceType), propName);
@ -18515,7 +18510,7 @@ namespace ts {
else if (isTypeOfKind(type, TypeFlags.StringLike)) {
return TypeReferenceSerializationKind.StringLikeType;
}
else if (isTypeOfKind(type, TypeFlags.Tuple)) {
else if (isTupleType(type)) {
return TypeReferenceSerializationKind.ArrayLikeType;
}
else if (isTypeOfKind(type, TypeFlags.ESSymbol)) {

View File

@ -2385,10 +2385,7 @@ namespace ts {
instantiations: Map<TypeReference>; // Generic instantiation cache
}
export interface TupleType extends ObjectType {
elementTypes: Type[]; // Element types
thisType?: Type; // This-type of tuple (only needed for tuples that are constraints of type parameters)
}
export interface TupleType extends GenericType { }
export interface UnionOrIntersectionType extends Type {
types: Type[]; // Constituent types