Skip to content

Commit

Permalink
Store binding pattern in contextual type
Browse files Browse the repository at this point in the history
  • Loading branch information
ahejlsberg committed Sep 5, 2015
1 parent 66e3aba commit dc8ad6e
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 44 deletions.
86 changes: 46 additions & 40 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(<BindingPattern>declaration.name);
return getTypeFromBindingPattern(<BindingPattern>declaration.name, /*includePatternInType*/ false);
}

// No type specified and nothing can be inferred
Expand All @@ -2421,13 +2421,13 @@ namespace ts {
return getWidenedType(checkExpressionCached(element.initializer));
}
if (isBindingPattern(element.name)) {
return getTypeFromBindingPattern(<BindingPattern>element.name);
return getTypeFromBindingPattern(<BindingPattern>element.name, /*includePatternInType*/ false);

This comment has been minimized.

Copy link
@JsonFreeman

JsonFreeman Sep 6, 2015

Contributor

Should this flag not be passed down from the parent pattern?

This comment has been minimized.

Copy link
@ahejlsberg

ahejlsberg Sep 6, 2015

Author Member

Yes, it should. Good catch!

}
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);
Expand All @@ -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);

This comment has been minimized.

Copy link
@JsonFreeman

JsonFreeman Sep 6, 2015

Contributor

Why does the tuple get cloned, but the object from the object binding pattern not get cloned?

This comment has been minimized.

Copy link
@JsonFreeman

JsonFreeman Sep 6, 2015

Contributor

Is it because of the tuple cache?

This comment has been minimized.

Copy link
@ahejlsberg

ahejlsberg Sep 6, 2015

Author Member

Exactly.

result.pattern = pattern;
}
return result;
}

// Return the type implied by a binding pattern. This is the type implied purely by the binding pattern itself
Expand All @@ -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
Expand Down Expand Up @@ -6606,18 +6605,12 @@ namespace ts {
}
}
if (isBindingPattern(declaration.name)) {
return createImpliedType(getTypeFromBindingPattern(<BindingPattern>declaration.name));
return getTypeFromBindingPattern(<BindingPattern>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) {
Expand Down Expand Up @@ -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 = (<TupleType>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 = (<BindingPattern>pattern).elements;
for (let i = elementTypes.length; i < bindingElements.length; i++) {
let hasDefaultValue = bindingElements[i].initializer;
elementTypes.push(hasDefaultValue ? (<TupleType>contextualType).elementTypes[i] : undefinedType);

This comment has been minimized.

Copy link
@JsonFreeman

JsonFreeman Sep 6, 2015

Contributor

Why is it useful to copy over the types faithfully? Isn't the purpose just to pad the length, so type any will do?

This comment has been minimized.

Copy link
@JsonFreeman

JsonFreeman Sep 6, 2015

Contributor

Looking at the baselines, I see why the undefined is useful here. It errors on further destructuring. What is the purpose of copying the element type from the contextual type? Couldn't the element's initializer inside the binding pattern just supply the type of the element directly, instead of being copied into the type of the parent initializer?

This comment has been minimized.

Copy link
@ahejlsberg

ahejlsberg Sep 6, 2015

Author Member

Correct on undefined vs. any. We need to copy the element type from the contextual type such that the resulting type of the object literal correctly reflects the combined view of the binding pattern and the literal initializer. For example, it a destructuring parameter with an initializer, the combined type becomes the final type of that parameter:

function foo([x, y = 1] = [0]) { }

Here, the final type [number, number] only emerges when we combine the contextual type and the object literal.

This comment has been minimized.

Copy link
@JsonFreeman

JsonFreeman Sep 6, 2015

Contributor

Got it.

}
}
else if (pattern && pattern.kind === SyntaxKind.ArrayLiteralExpression) {
let assignmentElements = (<ArrayLiteralExpression>pattern).elements;
for (let i = elementTypes.length; i < assignmentElements.length; i++) {
let hasDefaultValue = assignmentElements[i].kind === SyntaxKind.BinaryExpression &&
(<BinaryExpression>assignmentElements[i]).operatorToken.kind === SyntaxKind.EqualsToken;
elementTypes.push(hasDefaultValue ? (<TupleType>contextualType).elementTypes[i] : undefinedType);
}
}
if (elementTypes.length) {
Expand Down Expand Up @@ -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;

This comment has been minimized.

Copy link
@JsonFreeman

JsonFreeman Sep 6, 2015

Contributor

What about ObjectLiteral as an assignment pattern?

This comment has been minimized.

Copy link
@ahejlsberg

ahejlsberg Sep 6, 2015

Author Member

That's handled in a later commit.

let typeFlags: TypeFlags = 0;

for (let memberDecl of node.properties) {
Expand All @@ -7151,7 +7157,7 @@ namespace ts {
let prop = <TransientSymbol>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;
Expand Down Expand Up @@ -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;
Expand Down
10 changes: 6 additions & 4 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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 */
Expand Down

0 comments on commit dc8ad6e

Please sign in to comment.