Adding support for tuple types (e.g. [number, string])

This commit is contained in:
Anders Hejlsberg 2014-08-11 12:21:26 -07:00
parent c71e596ba8
commit 5b255243c9
4 changed files with 189 additions and 30 deletions

View File

@ -56,6 +56,7 @@ module ts {
var globalBooleanType: ObjectType;
var globalRegExpType: ObjectType;
var tupleTypes: Map<TupleType> = {};
var stringLiteralTypes: Map<StringLiteralType> = {};
var fullTypeCheck = false;
@ -619,15 +620,13 @@ module ts {
}
function isOptionalProperty(propertySymbol: Symbol): boolean {
if (propertySymbol.flags & SymbolFlags.Prototype) {
return false;
}
// class C {
// constructor(public x?) { }
// }
//
// x is an optional parameter, but it is a required property.
return (propertySymbol.valueDeclaration.flags & NodeFlags.QuestionMark) && propertySymbol.valueDeclaration.kind !== SyntaxKind.Parameter;
return propertySymbol.valueDeclaration && propertySymbol.valueDeclaration.flags & NodeFlags.QuestionMark &&
propertySymbol.valueDeclaration.kind !== SyntaxKind.Parameter;
}
function forEachSymbolTableInScope<T>(enclosingDeclaration: Node, callback: (symbolTable: SymbolTable) => T): T {
@ -843,6 +842,9 @@ module ts {
else if (type.flags & (TypeFlags.Class | TypeFlags.Interface | TypeFlags.Enum | TypeFlags.TypeParameter)) {
writer.writeSymbol(type.symbol, enclosingDeclaration, SymbolFlags.Type);
}
else if (type.flags & TypeFlags.Tuple) {
writeTupleType(<TupleType>type);
}
else if (type.flags & TypeFlags.Anonymous) {
writeAnonymousType(<ObjectType>type, allowFunctionOrConstructorTypeLiteral);
}
@ -855,6 +857,15 @@ module ts {
}
}
function writeTypeList(types: Type[]) {
for (var i = 0; i < types.length; i++) {
if (i > 0) {
writer.write(", ");
}
writeType(types[i], /*allowFunctionOrConstructorTypeLiteral*/ true);
}
}
function writeTypeReference(type: TypeReference) {
if (type.target === globalArrayType && !(flags & TypeFormatFlags.WriteArrayAsGenericType)) {
// If we are writing array element type the arrow style signatures are not allowed as
@ -865,16 +876,17 @@ module ts {
else {
writer.writeSymbol(type.target.symbol, enclosingDeclaration, SymbolFlags.Type);
writer.write("<");
for (var i = 0; i < type.typeArguments.length; i++) {
if (i > 0) {
writer.write(", ");
}
writeType(type.typeArguments[i], /*allowFunctionOrConstructorTypeLiteral*/ true);
}
writeTypeList(type.typeArguments);
writer.write(">");
}
}
function writeTupleType(type: TupleType) {
writer.write("[");
writeTypeList(type.elementTypes);
writer.write("]");
}
function writeAnonymousType(type: ObjectType, allowFunctionOrConstructorTypeLiteral: boolean) {
// Always use 'typeof T' for type of class, enum, and module objects
if (type.symbol && type.symbol.flags & (SymbolFlags.Class | SymbolFlags.Enum | SymbolFlags.ValueModule)) {
@ -1649,6 +1661,23 @@ module ts {
return [createSignature(undefined, classType.typeParameters, emptyArray, classType, 0, false, false)];
}
function createTupleTypeMemberSymbols(memberTypes: Type[]): SymbolTable {
var members: SymbolTable = {};
for (var i = 0; i < memberTypes.length; i++) {
var symbol = <TransientSymbol>createSymbol(SymbolFlags.Property | SymbolFlags.Transient, "" + i);
symbol.type = memberTypes[i];
members[i] = symbol;
}
return members;
}
function resolveTupleTypeMembers(type: TupleType) {
var arrayType = resolveObjectTypeMembers(createArrayType(getBestCommonType(type.elementTypes)));
var members = createTupleTypeMemberSymbols(type.elementTypes);
addInheritedMembers(members, arrayType.properties);
setObjectTypeMembers(type, members, arrayType.callSignatures, arrayType.constructSignatures, arrayType.stringIndexType, arrayType.numberIndexType);
}
function resolveAnonymousTypeMembers(type: ObjectType) {
var symbol = type.symbol;
var members = emptySymbols;
@ -1682,6 +1711,9 @@ module ts {
else if (type.flags & TypeFlags.Anonymous) {
resolveAnonymousTypeMembers(<ObjectType>type);
}
else if (type.flags & TypeFlags.Tuple) {
resolveTupleTypeMembers(<TupleType>type);
}
else {
resolveTypeReferenceMembers(<TypeReference>type);
}
@ -2123,6 +2155,24 @@ module ts {
return links.resolvedType;
}
function createTupleType(elementTypes: Type[]) {
var id = getTypeListId(elementTypes);
var type = tupleTypes[id];
if (!type) {
type = tupleTypes[id] = <TupleType>createObjectType(TypeFlags.Tuple);
type.elementTypes = elementTypes;
}
return type;
}
function getTypeFromTupleTypeNode(node: TupleTypeNode): Type {
var links = getNodeLinks(node);
if (!links.resolvedType) {
links.resolvedType = createTupleType(map(node.elementTypes, t => getTypeFromTypeNode(t)));
}
return links.resolvedType;
}
function getTypeFromTypeLiteralNode(node: TypeLiteralNode): Type {
var links = getNodeLinks(node);
if (!links.resolvedType) {
@ -2172,6 +2222,8 @@ module ts {
return getTypeFromTypeQueryNode(<TypeQueryNode>node);
case SyntaxKind.ArrayType:
return getTypeFromArrayTypeNode(<ArrayTypeNode>node);
case SyntaxKind.TupleType:
return getTypeFromTupleTypeNode(<TupleTypeNode>node);
case SyntaxKind.TypeLiteral:
return getTypeFromTypeLiteralNode(<TypeLiteralNode>node);
default:
@ -2327,6 +2379,9 @@ module 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));
}
}
return type;
}
@ -3015,20 +3070,16 @@ module ts {
while (isArrayType(type)) {
type = (<GenericType>type).typeArguments[0];
}
return type;
}
function getWidenedTypeOfArrayLiteral(type: Type): Type {
var elementType = (<TypeReference>type).typeArguments[0];
var widenedType = getWidenedType(elementType);
type = elementType !== widenedType ? createArrayType(widenedType) : type;
return type;
}
/* If we are widening on a literal, then we may need to the 'node' parameter for reporting purposes */
function getWidenedType(type: Type): Type {
if (type.flags & (TypeFlags.Undefined | TypeFlags.Null)) {
return anyType;
@ -3125,9 +3176,9 @@ module ts {
inferFromTypes(sourceTypes[i], targetTypes[i]);
}
}
else if (source.flags & TypeFlags.ObjectType && (target.flags & TypeFlags.Reference || (target.flags & TypeFlags.Anonymous) &&
target.symbol && target.symbol.flags & (SymbolFlags.Method | SymbolFlags.TypeLiteral))) {
// If source is an object type, and target is a type reference, the type of a method, or a type literal, infer from members
else if (source.flags & TypeFlags.ObjectType && (target.flags & (TypeFlags.Reference | TypeFlags.Tuple) ||
(target.flags & TypeFlags.Anonymous) && target.symbol && target.symbol.flags & (SymbolFlags.Method | SymbolFlags.TypeLiteral))) {
// If source is an object type, and target is a type reference, a tuple type, the type of a method, or a type literal, infer from members
if (!isInProcess(source, target) && isWithinDepthLimit(source, sourceStack) && isWithinDepthLimit(target, targetStack)) {
if (depth === 0) {
sourceStack = [];
@ -3574,7 +3625,19 @@ module ts {
function getContextualTypeForElementExpression(node: Expression): Type {
var arrayLiteral = <ArrayLiteral>node.parent;
var type = getContextualType(arrayLiteral);
return type ? getIndexTypeOfType(type, IndexKind.Number) : undefined;
if (type) {
if (type.flags & TypeFlags.Tuple) {
var index = indexOf(arrayLiteral.elements, node);
if (index >= 0) {
var prop = getPropertyOfType(type, "" + index);
if (prop) {
return getTypeOfSymbol(prop);
}
}
}
return getIndexTypeOfType(type, IndexKind.Number);
}
return undefined;
}
function getContextualTypeForConditionalOperand(node: Expression): Type {
@ -3633,17 +3696,23 @@ module ts {
}
function checkArrayLiteral(node: ArrayLiteral, contextualMapper?: TypeMapper): Type {
var contextualType = getContextualType(node);
var isTupleLiteral = contextualType && (contextualType.flags & TypeFlags.Tuple) !== 0;
var elementTypes: Type[] = [];
forEach(node.elements, element => {
if (element.kind !== SyntaxKind.OmittedExpression) {
var type = checkExpression(element, contextualMapper);
if (!contains(elementTypes, type)) elementTypes.push(type);
var type = element.kind !== SyntaxKind.OmittedExpression ? checkExpression(element, contextualMapper) : undefinedType;
if (isTupleLiteral || !contains(elementTypes, type)) {
elementTypes.push(type);
}
});
var contextualType = isInferentialContext(contextualMapper) ? undefined : getContextualType(node);
var contextualElementType = contextualType && getIndexTypeOfType(contextualType, IndexKind.Number);
if (isTupleLiteral) {
return createTupleType(elementTypes);
}
var contextualElementType = contextualType && !isInferentialContext(contextualMapper) ? getIndexTypeOfType(contextualType, IndexKind.Number) : undefined;
var elementType = getBestCommonType(elementTypes, contextualElementType, true);
if (!elementType) elementType = elementTypes.length ? emptyObjectType : undefinedType;
if (!elementType) {
elementType = elementTypes.length ? emptyObjectType : undefinedType;
}
return createArrayType(elementType);
}
@ -3711,11 +3780,11 @@ module ts {
}
function getDeclarationKindFromSymbol(s: Symbol) {
return s.flags & SymbolFlags.Prototype ? SyntaxKind.Property : s.valueDeclaration.kind;
return s.valueDeclaration ? s.valueDeclaration.kind : SyntaxKind.Property;
}
function getDeclarationFlagsFromSymbol(s: Symbol) {
return s.flags & SymbolFlags.Prototype ? NodeFlags.Public | NodeFlags.Static : s.valueDeclaration.flags;
return s.valueDeclaration ? s.valueDeclaration.flags : s.flags & SymbolFlags.Prototype ? NodeFlags.Public | NodeFlags.Static : 0;
}
function checkPropertyAccess(node: PropertyAccess) {
@ -4991,7 +5060,11 @@ module ts {
}
function checkArrayType(node: ArrayTypeNode) {
getTypeFromArrayTypeNode(node);
checkSourceElement(node.elementType);
}
function checkTupleType(node: TupleTypeNode) {
forEach(node.elementTypes, checkSourceElement);
}
function isPrivateWithinAmbient(node: Node): boolean {
@ -6197,6 +6270,8 @@ module ts {
return checkTypeLiteral(<TypeLiteralNode>node);
case SyntaxKind.ArrayType:
return checkArrayType(<ArrayTypeNode>node);
case SyntaxKind.TupleType:
return checkTupleType(<TupleTypeNode>node);
case SyntaxKind.FunctionDeclaration:
return checkFunctionDeclaration(<FunctionDeclaration>node);
case SyntaxKind.Block:

View File

@ -201,6 +201,8 @@ module ts {
return children((<TypeLiteralNode>node).members);
case SyntaxKind.ArrayType:
return child((<ArrayTypeNode>node).elementType);
case SyntaxKind.TupleType:
return children((<TupleTypeNode>node).elementTypes);
case SyntaxKind.ArrayLiteral:
return children((<ArrayLiteral>node).elements);
case SyntaxKind.ObjectLiteral:
@ -352,6 +354,7 @@ module ts {
Parameters, // Parameters in parameter list
TypeParameters, // Type parameters in type parameter list
TypeArguments, // Type arguments in type argument list
TupleElementTypes, // Element types in tuple element type list
Count // Number of parsing contexts
}
@ -379,6 +382,7 @@ module ts {
case ParsingContext.Parameters: return Diagnostics.Parameter_declaration_expected;
case ParsingContext.TypeParameters: return Diagnostics.Type_parameter_declaration_expected;
case ParsingContext.TypeArguments: return Diagnostics.Type_argument_expected;
case ParsingContext.TupleElementTypes: return Diagnostics.Type_expected;
}
};
@ -837,6 +841,7 @@ module ts {
case ParsingContext.Parameters:
return isParameter();
case ParsingContext.TypeArguments:
case ParsingContext.TupleElementTypes:
return isType();
}
@ -872,6 +877,7 @@ module ts {
// Tokens other than ')' are here for better error recovery
return token === SyntaxKind.CloseParenToken || token === SyntaxKind.SemicolonToken;
case ParsingContext.ArrayLiteralMembers:
case ParsingContext.TupleElementTypes:
return token === SyntaxKind.CloseBracketToken;
case ParsingContext.Parameters:
// Tokens other than ')' and ']' (the latter for index signatures) are here for better error recovery
@ -1390,6 +1396,17 @@ module ts {
return finishNode(node);
}
function parseTupleType(): TupleTypeNode {
var node = <TupleTypeNode>createNode(SyntaxKind.TupleType);
var startTokenPos = scanner.getTokenPos();
var startErrorCount = file.syntacticErrors.length;
node.elementTypes = parseBracketedList(ParsingContext.TupleElementTypes, parseType, SyntaxKind.OpenBracketToken, SyntaxKind.CloseBracketToken);
if (!node.elementTypes.length && file.syntacticErrors.length === startErrorCount) {
grammarErrorAtPos(startTokenPos, scanner.getStartPos() - startTokenPos, Diagnostics.Type_argument_list_cannot_be_empty);
}
return finishNode(node);
}
function parseFunctionType(signatureKind: SyntaxKind): TypeLiteralNode {
var node = <TypeLiteralNode>createNode(SyntaxKind.TypeLiteral);
var member = <SignatureDeclaration>createNode(signatureKind);
@ -1420,6 +1437,8 @@ module ts {
return parseTypeQuery();
case SyntaxKind.OpenBraceToken:
return parseTypeLiteral();
case SyntaxKind.OpenBracketToken:
return parseTupleType();
case SyntaxKind.OpenParenToken:
case SyntaxKind.LessThanToken:
return parseFunctionType(SyntaxKind.CallSignature);
@ -1443,6 +1462,7 @@ module ts {
case SyntaxKind.VoidKeyword:
case SyntaxKind.TypeOfKeyword:
case SyntaxKind.OpenBraceToken:
case SyntaxKind.OpenBracketToken:
case SyntaxKind.LessThanToken:
case SyntaxKind.NewKeyword:
return true;

View File

@ -149,6 +149,7 @@ module ts {
TypeQuery,
TypeLiteral,
ArrayType,
TupleType,
// Expression
ArrayLiteral,
ObjectLiteral,
@ -316,6 +317,10 @@ module ts {
elementType: TypeNode;
}
export interface TupleTypeNode extends TypeNode {
elementTypes: NodeArray<TypeNode>;
}
export interface StringLiteralTypeNode extends TypeNode {
text: string;
}
@ -791,13 +796,14 @@ module ts {
Class = 0x00000400, // Class
Interface = 0x00000800, // Interface
Reference = 0x00001000, // Generic type reference
Anonymous = 0x00002000, // Anonymous
FromSignature = 0x00004000, // Created for signature assignment check
Tuple = 0x00002000, // Tuple
Anonymous = 0x00004000, // Anonymous
FromSignature = 0x00008000, // Created for signature assignment check
Intrinsic = Any | String | Number | Boolean | Void | Undefined | Null,
StringLike = String | StringLiteral,
NumberLike = Number | Enum,
ObjectType = Class | Interface | Reference | Anonymous
ObjectType = Class | Interface | Reference | Tuple | Anonymous
}
// Properties common to all types
@ -850,6 +856,11 @@ module ts {
openReferenceChecks: Map<boolean>; // Open type reference check cache
}
export interface TupleType extends ObjectType {
elementTypes: Type[]; // Element types
baseArrayType: TypeReference; // Array<T> where T is best common type of element types
}
// Resolved object type
export interface ResolvedObjectType extends ObjectType {
members: SymbolTable; // Properties by name

View File

@ -0,0 +1,53 @@
var v1: []; // Error
var v2: [number];
var v3: [number, string];
var v4: [number, [string, string]];
var t: [number, string];
var t0 = t[0]; // number
var t0: number;
var t1 = t[1]; // string
var t1: string;
var t2 = t[2]; // {}
var t2: {};
t = []; // Error
t = [1]; // Error
t = [1, "hello"]; // Ok
t = ["hello", 1]; // Error
t = [1, "hello", 2]; // Ok
var tf: [string, (x: string) => number] = ["hello", x => x.length];
declare function ff<T, U>(a: T, b: [T, (x: T) => U]): U;
var ff1 = ff("hello", ["foo", x => x.length]);
var ff1: number;
function tuple2<T0, T1>(item0: T0, item1: T1): [T0, T1]{
return [item0, item1];
}
var tt = tuple2(1, "string");
var tt0 = tt[0];
var tt0: number;
var tt1 = tt[1];
var tt1: string;
var tt2 = tt[2];
var tt2: {};
tt = tuple2(1, undefined);
tt = [1, undefined];
tt = [undefined, undefined];
tt = []; // Error
var a: number[];
var a1: [number, string];
var a2: [number, number];
var a3: [number, {}];
a = a1; // Error
a = a2;
a = a3; // Error
a1 = a2; // Error
a1 = a3; // Error
a3 = a1;
a3 = a2;