mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
Merge pull request #4598 from Microsoft/destructuringInitializers
Improved checking of destructuring with literal initializers
This commit is contained in:
+108
-39
@@ -2318,8 +2318,8 @@ namespace ts {
|
||||
// Use type of the specified property, or otherwise, for a numeric name, the type of the numeric index signature,
|
||||
// or otherwise the type of the string index signature.
|
||||
type = getTypeOfPropertyOfType(parentType, name.text) ||
|
||||
isNumericLiteralName(name.text) && getIndexTypeOfType(parentType, IndexKind.Number) ||
|
||||
getIndexTypeOfType(parentType, IndexKind.String);
|
||||
isNumericLiteralName(name.text) && getIndexTypeOfType(parentType, IndexKind.Number) ||
|
||||
getIndexTypeOfType(parentType, IndexKind.String);
|
||||
if (!type) {
|
||||
error(name, Diagnostics.Type_0_has_no_property_1_and_no_string_index_signature, typeToString(parentType), declarationNameToString(name));
|
||||
return unknownType;
|
||||
@@ -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
|
||||
@@ -2416,48 +2416,47 @@ namespace ts {
|
||||
// Return the type implied by a binding pattern element. This is the type of the initializer of the element if
|
||||
// one is present. Otherwise, if the element is itself a binding pattern, it is the type implied by the binding
|
||||
// pattern. Otherwise, it is the type any.
|
||||
function getTypeFromBindingElement(element: BindingElement): Type {
|
||||
function getTypeFromBindingElement(element: BindingElement, includePatternInType?: boolean): Type {
|
||||
if (element.initializer) {
|
||||
return getWidenedType(checkExpressionCached(element.initializer));
|
||||
}
|
||||
if (isBindingPattern(element.name)) {
|
||||
return getTypeFromBindingPattern(<BindingPattern>element.name);
|
||||
return getTypeFromBindingPattern(<BindingPattern>element.name, includePatternInType);
|
||||
}
|
||||
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);
|
||||
let name = e.propertyName || <Identifier>e.name;
|
||||
let symbol = <TransientSymbol>createSymbol(flags, name.text);
|
||||
symbol.type = getTypeFromBindingElement(e);
|
||||
symbol.type = getTypeFromBindingElement(e, includePatternInType);
|
||||
symbol.bindingElement = 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.
|
||||
let elementTypes = map(elements, e => e.kind === SyntaxKind.OmittedExpression ? anyType : getTypeFromBindingElement(e, includePatternInType));
|
||||
if (includePatternInType) {
|
||||
let result = createNewTupleType(elementTypes);
|
||||
result.pattern = pattern;
|
||||
return result;
|
||||
}
|
||||
return createTupleType(elementTypes);
|
||||
}
|
||||
|
||||
@@ -2468,10 +2467,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
|
||||
@@ -3126,7 +3125,7 @@ namespace ts {
|
||||
}
|
||||
|
||||
function findMatchingSignature(signatureList: Signature[], signature: Signature, partialMatch: boolean, ignoreReturnTypes: boolean): Signature {
|
||||
for (let s of signatureList) {
|
||||
for (let s of signatureList) {
|
||||
if (compareSignatures(s, signature, partialMatch, ignoreReturnTypes, compareTypes)) {
|
||||
return s;
|
||||
}
|
||||
@@ -4047,11 +4046,12 @@ namespace ts {
|
||||
|
||||
function createTupleType(elementTypes: Type[]) {
|
||||
let id = getTypeListId(elementTypes);
|
||||
let type = tupleTypes[id];
|
||||
if (!type) {
|
||||
type = tupleTypes[id] = <TupleType>createObjectType(TypeFlags.Tuple | getPropagatingFlagsOfTypes(elementTypes));
|
||||
type.elementTypes = elementTypes;
|
||||
}
|
||||
return tupleTypes[id] || (tupleTypes[id] = createNewTupleType(elementTypes));
|
||||
}
|
||||
|
||||
function createNewTupleType(elementTypes: Type[]) {
|
||||
let type = <TupleType>createObjectType(TypeFlags.Tuple | getPropagatingFlagsOfTypes(elementTypes));
|
||||
type.elementTypes = elementTypes;
|
||||
return type;
|
||||
}
|
||||
|
||||
@@ -6616,7 +6616,7 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
if (isBindingPattern(declaration.name)) {
|
||||
return getTypeFromBindingPattern(<BindingPattern>declaration.name);
|
||||
return getTypeFromBindingPattern(<BindingPattern>declaration.name, /*includePatternInType*/ true);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
@@ -7012,11 +7012,13 @@ namespace ts {
|
||||
return checkIteratedTypeOrElementType(arrayOrIterableType, node.expression, /*allowStringInput*/ false);
|
||||
}
|
||||
|
||||
function hasDefaultValue(node: BindingElement | Expression): boolean {
|
||||
return (node.kind === SyntaxKind.BindingElement && !!(<BindingElement>node).initializer) ||
|
||||
(node.kind === SyntaxKind.BinaryExpression && (<BinaryExpression>node).operatorToken.kind === SyntaxKind.EqualsToken);
|
||||
}
|
||||
|
||||
function checkArrayLiteral(node: ArrayLiteralExpression, contextualMapper?: TypeMapper): Type {
|
||||
let elements = node.elements;
|
||||
if (!elements.length) {
|
||||
return createArrayType(undefinedType);
|
||||
}
|
||||
let hasSpreadElement = false;
|
||||
let elementTypes: Type[] = [];
|
||||
let inDestructuringPattern = isAssignmentTarget(node);
|
||||
@@ -7048,12 +7050,39 @@ namespace ts {
|
||||
hasSpreadElement = hasSpreadElement || e.kind === SyntaxKind.SpreadElementExpression;
|
||||
}
|
||||
if (!hasSpreadElement) {
|
||||
// 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) {
|
||||
let type = createNewTupleType(elementTypes);
|
||||
type.pattern = node;
|
||||
return type;
|
||||
}
|
||||
let contextualType = getContextualType(node);
|
||||
if (contextualType && contextualTypeIsTupleLikeType(contextualType) || inDestructuringPattern) {
|
||||
return createTupleType(elementTypes);
|
||||
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 with the corresponding binding or assignment element types to make the lengths equal.
|
||||
if (pattern && (pattern.kind === SyntaxKind.ArrayBindingPattern || pattern.kind === SyntaxKind.ArrayLiteralExpression)) {
|
||||
let patternElements = (<BindingPattern | ArrayLiteralExpression>pattern).elements;
|
||||
for (let i = elementTypes.length; i < patternElements.length; i++) {
|
||||
let patternElement = patternElements[i];
|
||||
if (hasDefaultValue(patternElement)) {
|
||||
elementTypes.push((<TupleType>contextualType).elementTypes[i]);
|
||||
}
|
||||
else {
|
||||
if (patternElement.kind !== SyntaxKind.OmittedExpression) {
|
||||
error(patternElement, Diagnostics.Initializer_provides_no_value_for_this_binding_element_and_the_binding_element_has_no_default_value);
|
||||
}
|
||||
elementTypes.push(unknownType);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (elementTypes.length) {
|
||||
return createTupleType(elementTypes);
|
||||
}
|
||||
}
|
||||
}
|
||||
return createArrayType(getUnionType(elementTypes));
|
||||
return createArrayType(elementTypes.length ? getUnionType(elementTypes) : undefinedType)
|
||||
}
|
||||
|
||||
function isNumericName(name: DeclarationName): boolean {
|
||||
@@ -7120,6 +7149,9 @@ namespace ts {
|
||||
let propertiesTable: SymbolTable = {};
|
||||
let propertiesArray: Symbol[] = [];
|
||||
let contextualType = getContextualType(node);
|
||||
let contextualTypeHasPattern = contextualType && contextualType.pattern &&
|
||||
(contextualType.pattern.kind === SyntaxKind.ObjectBindingPattern || contextualType.pattern.kind === SyntaxKind.ObjectLiteralExpression);
|
||||
let inDestructuringPattern = isAssignmentTarget(node);
|
||||
let typeFlags: TypeFlags = 0;
|
||||
|
||||
for (let memberDecl of node.properties) {
|
||||
@@ -7140,6 +7172,25 @@ namespace ts {
|
||||
}
|
||||
typeFlags |= type.flags;
|
||||
let prop = <TransientSymbol>createSymbol(SymbolFlags.Property | SymbolFlags.Transient | member.flags, member.name);
|
||||
if (inDestructuringPattern) {
|
||||
// If object literal is an assignment pattern and if the assignment pattern specifies a default value
|
||||
// for the property, make the property optional.
|
||||
if (memberDecl.kind === SyntaxKind.PropertyAssignment && hasDefaultValue((<PropertyAssignment>memberDecl).initializer)) {
|
||||
prop.flags |= SymbolFlags.Optional;
|
||||
}
|
||||
}
|
||||
else if (contextualTypeHasPattern) {
|
||||
// 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.
|
||||
let impliedProp = getPropertyOfType(contextualType, member.name);
|
||||
if (impliedProp) {
|
||||
prop.flags |= impliedProp.flags & SymbolFlags.Optional;
|
||||
}
|
||||
else if (!compilerOptions.suppressExcessPropertyErrors) {
|
||||
error(memberDecl.name, Diagnostics.Object_literal_may_only_specify_known_properties_and_0_does_not_exist_in_type_1,
|
||||
symbolToString(member), typeToString(contextualType));
|
||||
}
|
||||
}
|
||||
prop.declarations = member.declarations;
|
||||
prop.parent = member.parent;
|
||||
if (member.valueDeclaration) {
|
||||
@@ -7166,11 +7217,29 @@ namespace ts {
|
||||
propertiesArray.push(member);
|
||||
}
|
||||
|
||||
// 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 (contextualTypeHasPattern) {
|
||||
for (let prop of getPropertiesOfType(contextualType)) {
|
||||
if (!hasProperty(propertiesTable, prop.name)) {
|
||||
if (!(prop.flags & SymbolFlags.Optional)) {
|
||||
error(prop.valueDeclaration || (<TransientSymbol>prop).bindingElement,
|
||||
Diagnostics.Initializer_provides_no_value_for_this_binding_element_and_the_binding_element_has_no_default_value);
|
||||
}
|
||||
propertiesTable[prop.name] = prop;
|
||||
propertiesArray.push(prop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let stringIndexType = getIndexType(IndexKind.String);
|
||||
let numberIndexType = getIndexType(IndexKind.Number);
|
||||
let result = createAnonymousType(node.symbol, propertiesTable, emptyArray, emptyArray, stringIndexType, numberIndexType);
|
||||
let freshObjectLiteralFlag = compilerOptions.suppressExcessPropertyErrors ? 0 : TypeFlags.FreshObjectLiteral;
|
||||
result.flags |= TypeFlags.ObjectLiteral | TypeFlags.ContainsObjectLiteral | freshObjectLiteralFlag | (typeFlags & TypeFlags.PropagatingFlags);
|
||||
if (inDestructuringPattern) {
|
||||
result.pattern = node;
|
||||
}
|
||||
return result;
|
||||
|
||||
function getIndexType(kind: IndexKind) {
|
||||
|
||||
@@ -415,6 +415,7 @@ namespace ts {
|
||||
The_arguments_object_cannot_be_referenced_in_an_async_arrow_function_Consider_using_a_standard_async_function_expression: { code: 2522, category: DiagnosticCategory.Error, key: "The 'arguments' object cannot be referenced in an async arrow function. Consider using a standard async function expression." },
|
||||
yield_expressions_cannot_be_used_in_a_parameter_initializer: { code: 2523, category: DiagnosticCategory.Error, key: "'yield' expressions cannot be used in a parameter initializer." },
|
||||
await_expressions_cannot_be_used_in_a_parameter_initializer: { code: 2524, category: DiagnosticCategory.Error, key: "'await' expressions cannot be used in a parameter initializer." },
|
||||
Initializer_provides_no_value_for_this_binding_element_and_the_binding_element_has_no_default_value: { code: 2525, category: DiagnosticCategory.Error, key: "Initializer provides no value for this binding element and the binding element has no default value." },
|
||||
JSX_element_attributes_type_0_must_be_an_object_type: { code: 2600, category: DiagnosticCategory.Error, key: "JSX element attributes type '{0}' must be an object type." },
|
||||
The_return_type_of_a_JSX_element_constructor_must_return_an_object_type: { code: 2601, category: DiagnosticCategory.Error, key: "The return type of a JSX element constructor must return an object type." },
|
||||
JSX_element_implicitly_has_type_any_because_the_global_type_JSX_Element_does_not_exist: { code: 2602, category: DiagnosticCategory.Error, key: "JSX element implicitly has type 'any' because the global type 'JSX.Element' does not exist." },
|
||||
|
||||
@@ -1649,6 +1649,10 @@
|
||||
"category": "Error",
|
||||
"code": 2524
|
||||
},
|
||||
"Initializer provides no value for this binding element and the binding element has no default value.": {
|
||||
"category": "Error",
|
||||
"code": 2525
|
||||
},
|
||||
"JSX element attributes type '{0}' must be an object type.": {
|
||||
"category": "Error",
|
||||
"code": 2600
|
||||
|
||||
@@ -1715,6 +1715,7 @@ namespace ts {
|
||||
resolvedExports?: SymbolTable; // Resolved exports of module
|
||||
exportsChecked?: boolean; // True if exports of external module have been checked
|
||||
isNestedRedeclaration?: boolean; // True if symbol is block scoped redeclaration
|
||||
bindingElement?: BindingElement; // Binding element associated with property symbol
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
@@ -1812,11 +1813,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 */
|
||||
@@ -1865,8 +1869,7 @@ namespace ts {
|
||||
}
|
||||
|
||||
export interface TupleType extends ObjectType {
|
||||
elementTypes: Type[]; // Element types
|
||||
baseArrayType: TypeReference; // Array<T> where T is best common type of element types
|
||||
elementTypes: Type[]; // Element types
|
||||
}
|
||||
|
||||
export interface UnionOrIntersectionType extends Type {
|
||||
|
||||
Reference in New Issue
Block a user