From dc8ad6eb5e34980fca18de037d65f2f1924e444e Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sat, 5 Sep 2015 13:17:33 -0700 Subject: [PATCH] Store binding pattern in contextual type --- src/compiler/checker.ts | 86 ++++++++++++++++++++++------------------- src/compiler/types.ts | 10 +++-- 2 files changed, 52 insertions(+), 44 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index d4e68381f7b8c..e08d30ca9b960 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2406,7 +2406,7 @@ namespace ts { // If the declaration specifies a binding pattern, use the type implied by the binding pattern if (isBindingPattern(declaration.name)) { - return getTypeFromBindingPattern(declaration.name); + return getTypeFromBindingPattern(declaration.name, /*includePatternInType*/ false); } // No type specified and nothing can be inferred @@ -2421,13 +2421,13 @@ namespace ts { return getWidenedType(checkExpressionCached(element.initializer)); } if (isBindingPattern(element.name)) { - return getTypeFromBindingPattern(element.name); + return getTypeFromBindingPattern(element.name, /*includePatternInType*/ false); } return anyType; } // Return the type implied by an object binding pattern - function getTypeFromObjectBindingPattern(pattern: BindingPattern): Type { + function getTypeFromObjectBindingPattern(pattern: BindingPattern, includePatternInType: boolean): Type { let members: SymbolTable = {}; forEach(pattern.elements, e => { let flags = SymbolFlags.Property | SymbolFlags.Transient | (e.initializer ? SymbolFlags.Optional : 0); @@ -2436,28 +2436,27 @@ namespace ts { symbol.type = getTypeFromBindingElement(e); members[symbol.name] = symbol; }); - return createAnonymousType(undefined, members, emptyArray, emptyArray, undefined, undefined); + let result = createAnonymousType(undefined, members, emptyArray, emptyArray, undefined, undefined); + if (includePatternInType) { + result.pattern = pattern; + } + return result; } // Return the type implied by an array binding pattern - function getTypeFromArrayBindingPattern(pattern: BindingPattern): Type { - let hasSpreadElement: boolean = false; - let elementTypes: Type[] = []; - forEach(pattern.elements, e => { - elementTypes.push(e.kind === SyntaxKind.OmittedExpression || e.dotDotDotToken ? anyType : getTypeFromBindingElement(e)); - if (e.dotDotDotToken) { - hasSpreadElement = true; - } - }); - if (!elementTypes.length) { + function getTypeFromArrayBindingPattern(pattern: BindingPattern, includePatternInType: boolean): Type { + let elements = pattern.elements; + if (elements.length === 0 || elements[elements.length - 1].dotDotDotToken) { return languageVersion >= ScriptTarget.ES6 ? createIterableType(anyType) : anyArrayType; } - else if (hasSpreadElement) { - let unionOfElements = getUnionType(elementTypes); - return languageVersion >= ScriptTarget.ES6 ? createIterableType(unionOfElements) : createArrayType(unionOfElements); - } // If the pattern has at least one element, and no rest element, then it should imply a tuple type. - return createTupleType(elementTypes); + let elementTypes = map(elements, e => e.kind === SyntaxKind.OmittedExpression ? anyType : getTypeFromBindingElement(e)); + let result = createTupleType(elementTypes); + if (includePatternInType) { + result = clone(result); + result.pattern = pattern; + } + return result; } // Return the type implied by a binding pattern. This is the type implied purely by the binding pattern itself @@ -2467,10 +2466,10 @@ namespace ts { // used as the contextual type of an initializer associated with the binding pattern. Also, for a destructuring // parameter with no type annotation or initializer, the type implied by the binding pattern becomes the type of // the parameter. - function getTypeFromBindingPattern(pattern: BindingPattern): Type { + function getTypeFromBindingPattern(pattern: BindingPattern, includePatternInType?: boolean): Type { return pattern.kind === SyntaxKind.ObjectBindingPattern - ? getTypeFromObjectBindingPattern(pattern) - : getTypeFromArrayBindingPattern(pattern); + ? getTypeFromObjectBindingPattern(pattern, includePatternInType) + : getTypeFromArrayBindingPattern(pattern, includePatternInType); } // Return the type associated with a variable, parameter, or property declaration. In the simple case this is the type @@ -6606,18 +6605,12 @@ namespace ts { } } if (isBindingPattern(declaration.name)) { - return createImpliedType(getTypeFromBindingPattern(declaration.name)); + return getTypeFromBindingPattern(declaration.name, /*includePatternInType*/ true); } } return undefined; } - function createImpliedType(type: Type): Type { - var result = clone(type); - result.flags |= TypeFlags.ImpliedType; - return result; - } - function getContextualTypeForReturnExpression(node: Expression): Type { let func = getContainingFunction(node); if (func && !func.asteriskToken) { @@ -7044,17 +7037,28 @@ 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) { - return createImpliedType(createTupleType(elementTypes)); + let type = clone(createTupleType(elementTypes)); + type.pattern = node; + return type; } let contextualType = getContextualType(node); - let contextualTupleLikeType = contextualType && contextualTypeIsTupleLikeType(contextualType) ? contextualType : undefined; - if (contextualTupleLikeType) { - // If array literal is contextually typed by the implied type of a binding pattern, pad the resulting - // tuple type with elements from the binding tuple type to make the lengths equal. - if (contextualTupleLikeType.flags & TypeFlags.Tuple && contextualTupleLikeType.flags & TypeFlags.ImpliedType) { - let contextualElementTypes = (contextualTupleLikeType).elementTypes; - for (let i = elementTypes.length; i < contextualElementTypes.length; i++) { - elementTypes.push(contextualElementTypes[i]); + if (contextualType && contextualTypeIsTupleLikeType(contextualType)) { + let pattern = contextualType.pattern; + // If array literal is contextually typed by a binding pattern or an assignment pattern, + // pad the resulting tuple type to make the lengths equal. + if (pattern && pattern.kind === SyntaxKind.ArrayBindingPattern) { + let bindingElements = (pattern).elements; + for (let i = elementTypes.length; i < bindingElements.length; i++) { + let hasDefaultValue = bindingElements[i].initializer; + elementTypes.push(hasDefaultValue ? (contextualType).elementTypes[i] : undefinedType); + } + } + else if (pattern && pattern.kind === SyntaxKind.ArrayLiteralExpression) { + let assignmentElements = (pattern).elements; + for (let i = elementTypes.length; i < assignmentElements.length; i++) { + let hasDefaultValue = assignmentElements[i].kind === SyntaxKind.BinaryExpression && + (assignmentElements[i]).operatorToken.kind === SyntaxKind.EqualsToken; + elementTypes.push(hasDefaultValue ? (contextualType).elementTypes[i] : undefinedType); } } if (elementTypes.length) { @@ -7129,6 +7133,8 @@ namespace ts { let propertiesTable: SymbolTable = {}; let propertiesArray: Symbol[] = []; let contextualType = getContextualType(node); + let contextualTypeHasPattern = contextualType && contextualType.pattern && + contextualType.pattern.kind === SyntaxKind.ObjectBindingPattern; let typeFlags: TypeFlags = 0; for (let memberDecl of node.properties) { @@ -7151,7 +7157,7 @@ namespace ts { let prop = createSymbol(SymbolFlags.Property | SymbolFlags.Transient | member.flags, member.name); // If object literal is contextually typed by the implied type of a binding pattern, and if the // binding pattern specifies a default value for the property, make the property optional. - if (contextualType && contextualType.flags & TypeFlags.ImpliedType) { + if (contextualTypeHasPattern) { let impliedProp = getPropertyOfType(contextualType, member.name); if (impliedProp) { prop.flags |= impliedProp.flags & SymbolFlags.Optional; @@ -7185,7 +7191,7 @@ namespace ts { // If object literal is contextually typed by the implied type of a binding pattern, augment the result // type with those properties for which the binding pattern specifies a default value. - if (contextualType && contextualType.flags & TypeFlags.ImpliedType) { + if (contextualTypeHasPattern) { for (let prop of getPropertiesOfType(contextualType)) { if (prop.flags & SymbolFlags.Optional && !hasProperty(propertiesTable, prop.name)) { propertiesTable[prop.name] = prop; diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 1313fb5b0311e..d23a42e753af2 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1795,7 +1795,6 @@ namespace ts { /* @internal */ ContainsAnyFunctionType = 0x00800000, // Type is or contains object literal type ESSymbol = 0x01000000, // Type of symbol primitive introduced in ES6 - ImpliedType = 0x02000000, // Type implied by object binding pattern /* @internal */ Intrinsic = Any | String | Number | Boolean | ESSymbol | Void | Undefined | Null, @@ -1812,11 +1811,14 @@ namespace ts { PropagatingFlags = ContainsUndefinedOrNull | ContainsObjectLiteral | ContainsAnyFunctionType } + export type DestructuringPattern = BindingPattern | ObjectLiteralExpression | ArrayLiteralExpression; + // Properties common to all types export interface Type { - flags: TypeFlags; // Flags - /* @internal */ id: number; // Unique ID - symbol?: Symbol; // Symbol associated with type (if any) + flags: TypeFlags; // Flags + /* @internal */ id: number; // Unique ID + symbol?: Symbol; // Symbol associated with type (if any) + pattern?: DestructuringPattern; // Destructuring pattern represented by type (if any) } /* @internal */