-
Notifications
You must be signed in to change notification settings - Fork 12.8k
Adding support for tuple types (e.g. [number, string]) #428
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
Changes from 2 commits
5b25524
3b1dbad
ef52312
92b3677
f0b33b3
0cf503f
c0e802d
63b83e7
be7e0a7
b4cddc3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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,14 @@ 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 +843,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 +858,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 +877,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 +1662,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 +1712,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); | ||
} | ||
|
@@ -2037,7 +2070,7 @@ module ts { | |
if (type.flags & (TypeFlags.Class | TypeFlags.Interface) && type.flags & TypeFlags.Reference) { | ||
var typeParameters = (<InterfaceType>type).typeParameters; | ||
if (node.typeArguments && node.typeArguments.length === typeParameters.length) { | ||
type = createTypeReference(<GenericType>type, map(node.typeArguments, t => getTypeFromTypeNode(t))); | ||
type = createTypeReference(<GenericType>type, map(node.typeArguments, getTypeFromTypeNode)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good ol' η-reduction. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. someone ate my tea There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Someone eta my tea There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
} | ||
else { | ||
error(node, Diagnostics.Generic_type_0_requires_1_type_argument_s, typeToString(type, /*enclosingDeclaration*/ undefined, TypeFormatFlags.WriteArrayAsGenericType), typeParameters.length); | ||
|
@@ -2123,6 +2156,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, getTypeFromTypeNode)); | ||
} | ||
return links.resolvedType; | ||
} | ||
|
||
function getTypeFromTypeLiteralNode(node: TypeLiteralNode): Type { | ||
var links = getNodeLinks(node); | ||
if (!links.resolvedType) { | ||
|
@@ -2172,6 +2223,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 +2380,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 +3071,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 +3177,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) || | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is getting more confusing. can you extract out a method that breaks out the checks, to make it more understandable what it is doing. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There's a comment on the next line that explains what the test is doing. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The comment is really just an English transliteration of the Boolean check. it doesn't really convey what's actually happening. i.e. a comment for "source.flags & TypeFlags.ObjectType && (target.flags & TypeFlags.Reference" saying "if the source is an object and the target is a reference" doesn't really explain things. What I meant was more have English prose explaining why these are the right set of checks. For example, why is is right that 'inferFromTypes' cares about tuples. And why is right that inferFromTypes does not care about the other sorts of types not checked here. In other words, say someone has to come along and try to determine if this code is correct or not. Having the English prose explaining why it is precisely supposed to be this way is enormously helpful. |
||
(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 +3626,19 @@ module ts { | |
function getContextualTypeForElementExpression(node: Expression): Type { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not sure what this is doing in the context of tuples. Can you clarify why this is necessary. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the contextual type is a tuple type we want to return the tuple element type at the same index as the expression node. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry, I meant "clarify in the source code." These comments in the code review are not something people see when working with the code. |
||
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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. seems odd that you have to recompute the index of the expresssion. Is this not known by the caller of this function? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The caller of this function is always |
||
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 +3697,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 +3781,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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the former way seemed better. It was explicit that only the special .Prototype member was considered to be a property, and anything else went to the decl. Now, the new code is assuming anything without a valueDecl is a property. It's not clear why that invariant would be true. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Some symbols have no declarations associated with them and we need a default policy for those. This establishes that policy. Also, note that the function is simply a helper for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "This establishes that policy" When establishing policy we should be documenting it (at least with some sort of comment). For example, in the comment for 'valueDeclaration' on Symbol we should be documenting: "if a symbol does not have a valueDeclaration, it is assumed to be a property. Example of this are the synthesized '.prototype' property as well as synthesized tuple index properties." There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Definitely agree with Cyrus here There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, will add a comment. |
||
} | ||
|
||
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 +5061,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 +6271,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: | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
consider adding a createTransientSymbol helper so you can avoid the 'or' and the redundant cast.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Only used in a few places, don't think it is worth it to create a helper.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently we have three places where we do this (and this will bring us to four). Once we're repeating ourselves that many times, it seems desirable to start using a helper :)
createSymbol itself is only used 9 times (so only 6 times without making something transient). It seems like nearly half the times we create a symbol, it's a transient symbol. This seems worthwhile to me :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree. As well as a comment about when to create a transient symbol vs a nontransient symbol.