From 850f3cb60962fd292f799b6d8feed33cb614c26a Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 19 Nov 2014 14:42:06 -0800 Subject: [PATCH] Destructuring parameter declarations --- src/compiler/binder.ts | 30 +++++++--- src/compiler/checker.ts | 119 ++++++++++++++++++++-------------------- src/compiler/parser.ts | 23 ++++---- src/compiler/types.ts | 28 +++++----- 4 files changed, 105 insertions(+), 95 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 8cdba4c9058..67e7dc439c9 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -330,6 +330,10 @@ module ts { bindChildren(node, SymbolFlags.BlockScopedVariable, /*isBlockScopeContainer*/ false); } + function getDestructuringParameterName(node: Declaration) { + return "__" + indexOf((node.parent).parameters, node); + } + function bind(node: Node) { node.parent = parent; switch (node.kind) { @@ -337,7 +341,17 @@ module ts { bindDeclaration(node, SymbolFlags.TypeParameter, SymbolFlags.TypeParameterExcludes, /*isBlockScopeContainer*/ false); break; case SyntaxKind.Parameter: - bindDeclaration(node, SymbolFlags.FunctionScopedVariable, SymbolFlags.ParameterExcludes, /*isBlockScopeContainer*/ false); + if (isBindingPattern((node).name)) { + if (isBindingPattern(parent)) { + bindChildren(node, 0, /*isBlockScopeContainer*/ false); + } + else { + bindAnonymousDeclaration(node, SymbolFlags.FunctionScopedVariable, getDestructuringParameterName(node), /*isBlockScopeContainer*/ false); + } + } + else { + bindDeclaration(node, SymbolFlags.FunctionScopedVariable, SymbolFlags.ParameterExcludes, /*isBlockScopeContainer*/ false); + } break; case SyntaxKind.VariableDeclaration: if (isBindingPattern((node).name)) { @@ -351,6 +365,8 @@ module ts { } break; case SyntaxKind.Property: + bindDeclaration(node, SymbolFlags.Property | (node.flags & NodeFlags.QuestionMark ? SymbolFlags.Optional : 0), SymbolFlags.PropertyExcludes, /*isBlockScopeContainer*/ false); + break; case SyntaxKind.PropertyAssignment: bindDeclaration(node, SymbolFlags.Property, SymbolFlags.PropertyExcludes, /*isBlockScopeContainer*/ false); break; @@ -358,16 +374,12 @@ module ts { bindDeclaration(node, SymbolFlags.EnumMember, SymbolFlags.EnumMemberExcludes, /*isBlockScopeContainer*/ false); break; case SyntaxKind.CallSignature: - bindDeclaration(node, SymbolFlags.CallSignature, 0, /*isBlockScopeContainer*/ false); + case SyntaxKind.ConstructSignature: + case SyntaxKind.IndexSignature: + bindDeclaration(node, SymbolFlags.Signature, 0, /*isBlockScopeContainer*/ false); break; case SyntaxKind.Method: - bindDeclaration(node, SymbolFlags.Method, SymbolFlags.MethodExcludes, /*isBlockScopeContainer*/ true); - break; - case SyntaxKind.ConstructSignature: - bindDeclaration(node, SymbolFlags.ConstructSignature, 0, /*isBlockScopeContainer*/ true); - break; - case SyntaxKind.IndexSignature: - bindDeclaration(node, SymbolFlags.IndexSignature, 0, /*isBlockScopeContainer*/ false); + bindDeclaration(node, SymbolFlags.Method | (node.flags & NodeFlags.QuestionMark ? SymbolFlags.Optional : 0), SymbolFlags.MethodExcludes, /*isBlockScopeContainer*/ true); break; case SyntaxKind.FunctionDeclaration: bindDeclaration(node, SymbolFlags.Function, SymbolFlags.FunctionExcludes, /*isBlockScopeContainer*/ true); diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 220505e9d89..b1a0b4ebd1a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -741,17 +741,6 @@ module ts { members, callSignatures, constructSignatures, stringIndexType, numberIndexType); } - function isOptionalProperty(propertySymbol: Symbol): boolean { - // class C { - // constructor(public x?) { } - // } - // - // x is an optional parameter, but it is a required property. - return propertySymbol.valueDeclaration && - propertySymbol.valueDeclaration.flags & NodeFlags.QuestionMark && - propertySymbol.valueDeclaration.kind !== SyntaxKind.Parameter; - } - function forEachSymbolTableInScope(enclosingDeclaration: Node, callback: (symbolTable: SymbolTable) => T): T { var result: T; for (var location = enclosingDeclaration; location; location = location.parent) { @@ -1366,7 +1355,7 @@ module ts { var signatures = getSignaturesOfType(t, SignatureKind.Call); for (var j = 0; j < signatures.length; j++) { buildSymbolDisplay(p, writer); - if (isOptionalProperty(p)) { + if (p.flags & SymbolFlags.Optional) { writePunctuation(writer, SyntaxKind.QuestionToken); } buildSignatureDisplay(signatures[j], writer, enclosingDeclaration, globalFlagsToPass, typeStack); @@ -1376,7 +1365,7 @@ module ts { } else { buildSymbolDisplay(p, writer); - if (isOptionalProperty(p)) { + if (p.flags & SymbolFlags.Optional) { writePunctuation(writer, SyntaxKind.QuestionToken); } writePunctuation(writer, SyntaxKind.ColonToken); @@ -1699,6 +1688,31 @@ module ts { return undefined; } + function getTypeFromBindingElement(element: BindingElement): Type { + if (element.initializer) { + return getWidenedType(checkExpressionCached(element.initializer)); + } + if (isBindingPattern(element.name)) { + return getTypeFromBindingPattern(element.name); + } + return anyType; + } + + function getTypeFromBindingPattern(pattern: BindingPattern): Type { + if (pattern.kind === SyntaxKind.ArrayBindingPattern) { + return createTupleType(map(pattern.elements, getTypeFromBindingElement)); + } + var members: SymbolTable = {}; + forEach(pattern.elements, e => { + var flags = SymbolFlags.Property | SymbolFlags.Transient | (e.initializer ? SymbolFlags.Optional : 0); + var name = e.propertyName || e.name; + var symbol = createSymbol(flags, name.text); + symbol.type = getTypeFromBindingElement(e); + members[symbol.name] = symbol; + }); + return createAnonymousType(undefined, members, emptyArray, emptyArray, undefined, undefined); + } + function getWidenedTypeForVariableDeclaration(declaration: VariableDeclaration | PropertyDeclaration, reportErrors?: boolean): Type { var type = getTypeForVariableDeclaration(declaration); if (type) { @@ -1707,6 +1721,9 @@ module ts { } return declaration.kind !== SyntaxKind.PropertyAssignment ? getWidenedType(type) : type; } + if (isBindingPattern(declaration.name)) { + return getTypeFromBindingPattern(declaration.name); + } // Rest parameters default to type any[], other parameters default to type any type = declaration.flags & NodeFlags.Rest ? createArrayType(anyType) : anyType; // Report implicit any errors unless this is a private property within an ambient declaration if (reportErrors && compilerOptions.noImplicitAny && !isPrivateWithinAmbient(declaration) && !(declaration.kind === SyntaxKind.Parameter && isPrivateWithinAmbient(declaration.parent))) { @@ -3528,7 +3545,7 @@ module ts { var sourceProp = getPropertyOfType(source, targetProp.name); if (sourceProp !== targetProp) { if (!sourceProp) { - if (relation === subtypeRelation || !isOptionalProperty(targetProp)) { + if (relation === subtypeRelation || !(targetProp.flags & SymbolFlags.Optional)) { if (reportErrors) { reportError(Diagnostics.Property_0_is_missing_in_type_1, symbolToString(targetProp), typeToString(source)); } @@ -3580,7 +3597,7 @@ module ts { return Ternary.False; } result &= related; - if (isOptionalProperty(sourceProp) && !isOptionalProperty(targetProp)) { + if (sourceProp.flags & SymbolFlags.Optional && !(targetProp.flags & SymbolFlags.Optional)) { // TypeScript 1.0 spec (April 2014): 3.8.3 // S is a subtype of a type T, and T is a supertype of S if ... // S' and T are object types and, for each member M in T.. @@ -3821,7 +3838,7 @@ module ts { } } else { - if (isOptionalProperty(sourceProp) !== isOptionalProperty(targetProp)) { + if ((sourceProp.flags & SymbolFlags.Optional) !== (targetProp.flags & SymbolFlags.Optional)) { return Ternary.False; } } @@ -6389,30 +6406,27 @@ module ts { // TODO: Check multiple declarations are identical } - // TODO(andersh): Support destructuring - function checkParameter(parameterDeclaration: ParameterDeclaration) { - if (isBindingPattern(parameterDeclaration.name)) { + function checkParameter(node: ParameterDeclaration) { + checkVariableDeclaration(node); + if (isBindingPattern(node.name)) { return; } - checkVariableDeclaration(parameterDeclaration); - - if (fullTypeCheck) { - checkCollisionWithIndexVariableInGeneratedCode(parameterDeclaration, parameterDeclaration.name); - - if (parameterDeclaration.flags & (NodeFlags.Public | NodeFlags.Private | NodeFlags.Protected) && - !(parameterDeclaration.parent.kind === SyntaxKind.Constructor && (parameterDeclaration.parent).body)) { - error(parameterDeclaration, Diagnostics.A_parameter_property_is_only_allowed_in_a_constructor_implementation); + checkCollisionWithIndexVariableInGeneratedCode(node, node.name); + var func = getContainingFunction(node); + if (node.flags & (NodeFlags.Public | NodeFlags.Private | NodeFlags.Protected) && + !(func.kind === SyntaxKind.Constructor && func.body)) { + error(node, Diagnostics.A_parameter_property_is_only_allowed_in_a_constructor_implementation); + } + if (node.flags & NodeFlags.Rest) { + if (!isArrayType(getTypeOfSymbol(node.symbol))) { + error(node, Diagnostics.A_rest_parameter_must_be_of_an_array_type); } - if (parameterDeclaration.flags & NodeFlags.Rest) { - if (!isArrayType(getTypeOfSymbol(parameterDeclaration.symbol))) { - error(parameterDeclaration, Diagnostics.A_rest_parameter_must_be_of_an_array_type); - } - } - else { - if (parameterDeclaration.initializer && !(parameterDeclaration.parent).body) { - error(parameterDeclaration, Diagnostics.A_parameter_initializer_is_only_allowed_in_a_function_or_constructor_implementation); - } + } + if (node.initializer) { + if (!func.body) { + error(node, Diagnostics.A_parameter_initializer_is_only_allowed_in_a_function_or_constructor_implementation); } + checkReferencesInInitializer(node.initializer); } function checkReferencesInInitializer(n: Node): void { @@ -6420,33 +6434,25 @@ module ts { var referencedSymbol = getNodeLinks(n).resolvedSymbol; // check FunctionLikeDeclaration.locals (stores parameters\function local variable) // if it contains entry with a specified name and if this entry matches the resolved symbol - if (referencedSymbol && referencedSymbol !== unknownSymbol && getSymbol(parameterDeclaration.parent.locals, referencedSymbol.name, SymbolFlags.Value) === referencedSymbol) { + if (referencedSymbol && referencedSymbol !== unknownSymbol && getSymbol(func.locals, referencedSymbol.name, SymbolFlags.Value) === referencedSymbol) { if (referencedSymbol.valueDeclaration.kind === SyntaxKind.Parameter) { - if (referencedSymbol.valueDeclaration === parameterDeclaration) { - error(n, Diagnostics.Parameter_0_cannot_be_referenced_in_its_initializer, declarationNameToString(parameterDeclaration.name)); + if (referencedSymbol.valueDeclaration === node) { + error(n, Diagnostics.Parameter_0_cannot_be_referenced_in_its_initializer, declarationNameToString(node.name)); return; } - var enclosingOrReferencedParameter = - forEach((parameterDeclaration.parent).parameters, p => p === parameterDeclaration || p === referencedSymbol.valueDeclaration ? p : undefined); - - if (enclosingOrReferencedParameter === referencedSymbol.valueDeclaration) { + if (referencedSymbol.valueDeclaration.pos < node.pos) { // legal case - parameter initializer references some parameter strictly on left of current parameter declaration return; } // fall through to error reporting } - - error(n, Diagnostics.Initializer_of_parameter_0_cannot_reference_identifier_1_declared_after_it, declarationNameToString(parameterDeclaration.name), declarationNameToString(n)); + error(n, Diagnostics.Initializer_of_parameter_0_cannot_reference_identifier_1_declared_after_it, declarationNameToString(node.name), declarationNameToString(n)); } } else { forEachChild(n, checkReferencesInInitializer); } } - - if (parameterDeclaration.initializer) { - checkReferencesInInitializer(parameterDeclaration.initializer); - } } function checkSignatureDeclaration(node: SignatureDeclaration) { @@ -7075,7 +7081,8 @@ module ts { // - function has implementation (not a signature) // - function has rest parameters // - context is not ambient (otherwise no codegen impact) - if ((node.parent).body && hasRestParameters(node.parent) && !isInAmbientContext(node)) { + var func = getContainingFunction(node); + if (func.body && hasRestParameters(func) && !isInAmbientContext(node)) { error(node, Diagnostics.Duplicate_identifier_i_Compiler_uses_i_to_initialize_rest_parameter); } return; @@ -7122,7 +7129,7 @@ module ts { // no - go up to the next level var current = node; while (current) { - var definedOnCurrentLevel = forEach(symbol.declarations, d => d.parent === current ? d : undefined); + var definedOnCurrentLevel = forEach(symbol.declarations, d => getContainingFunction(d) === current); if (definedOnCurrentLevel) { return; } @@ -7273,11 +7280,7 @@ module ts { function checkVariableDeclaration(node: VariableDeclaration | PropertyDeclaration) { if (isBindingPattern(node.name)) { - forEach((node.name).elements, e => { - if (e.kind === SyntaxKind.VariableDeclaration) { - checkVariableDeclaration(e); - } - }); + forEach((node.name).elements, checkSourceElement); if (node.initializer) { checkTypeAssignableTo(checkExpressionCached(node.initializer), getWidenedTypeForVariableDeclaration(node), node, /*headMessage*/ undefined); } @@ -7308,7 +7311,7 @@ module ts { } function checkVariableStatement(node: VariableStatement) { - forEach(node.declarations, checkVariableDeclaration); + forEach(node.declarations, checkSourceElement); } function checkExpressionStatement(node: ExpressionStatement) { @@ -8184,7 +8187,7 @@ module ts { case SyntaxKind.TryStatement: return checkTryStatement(node); case SyntaxKind.VariableDeclaration: - return Debug.fail("Checker encountered variable declaration"); + return checkVariableDeclaration(node); case SyntaxKind.ClassDeclaration: return checkClassDeclaration(node); case SyntaxKind.InterfaceDeclaration: diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index d48f6489c8e..d97737b5632 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -411,6 +411,9 @@ module ts { case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: case SyntaxKind.Constructor: + case SyntaxKind.CallSignature: + case SyntaxKind.ConstructSignature: + case SyntaxKind.IndexSignature: return true; } } @@ -1245,7 +1248,7 @@ module ts { return token === SyntaxKind.CloseBracketToken; case ParsingContext.Parameters: // Tokens other than ')' and ']' (the latter for index signatures) are here for better error recovery - return token === SyntaxKind.CloseParenToken || token === SyntaxKind.CloseBracketToken || token === SyntaxKind.OpenBraceToken; + return token === SyntaxKind.CloseParenToken || token === SyntaxKind.CloseBracketToken /*|| token === SyntaxKind.OpenBraceToken*/; case ParsingContext.TypeArguments: // Tokens other than '>' are here for better error recovery return token === SyntaxKind.GreaterThanToken || token === SyntaxKind.OpenParenToken; @@ -1568,7 +1571,7 @@ module ts { } function isStartOfParameter(): boolean { - return token === SyntaxKind.DotDotDotToken || isIdentifier() || isModifier(token); + return token === SyntaxKind.DotDotDotToken || isIdentifierOrPattern() || isModifier(token); } function parseParameter(flags: NodeFlags = 0): ParameterDeclaration { @@ -1576,8 +1579,11 @@ module ts { node.flags |= parseAndCheckModifiers(ModifierContext.Parameters); if (parseOptional(SyntaxKind.DotDotDotToken)) { node.flags |= NodeFlags.Rest; + node.name = parseIdentifier(); + } + else { + node.name = parseIdentifierOrPattern(node.flags); } - node.name = parseIdentifier(); if (node.name.kind === SyntaxKind.Missing && node.flags === 0 && isModifier(token)) { // in cases like // 'use strict' @@ -1645,16 +1651,7 @@ module ts { for (var i = 0; i < parameterCount; i++) { var parameter = parameters[i]; - // It is a SyntaxError if the Identifier "eval" or the Identifier "arguments" occurs as the - // Identifier in a PropertySetParameterList of a PropertyAssignment that is contained in strict code - // or if its FunctionBody is strict code(11.1.5). - // It is a SyntaxError if the identifier eval or arguments appears within a FormalParameterList of a - // strict mode FunctionLikeDeclaration or FunctionExpression(13.1) - if (isInStrictMode && isEvalOrArgumentsIdentifier(parameter.name)) { - reportInvalidUseInStrictMode(parameter.name); - return; - } - else if (parameter.flags & NodeFlags.Rest) { + if (parameter.flags & NodeFlags.Rest) { if (i !== (parameterCount - 1)) { grammarErrorOnNode(parameter.name, Diagnostics.A_rest_parameter_must_be_last_in_a_parameter_list); return; diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 0f75b7f410e..797c4e9bbd6 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -845,22 +845,21 @@ module ts { Constructor = 0x00004000, // Constructor GetAccessor = 0x00008000, // Get accessor SetAccessor = 0x00010000, // Set accessor - CallSignature = 0x00020000, // Call signature - ConstructSignature = 0x00040000, // Construct signature - IndexSignature = 0x00080000, // Index signature - TypeParameter = 0x00100000, // Type parameter - TypeAlias = 0x00200000, // Type alias + Signature = 0x00020000, // Call, construct, or index signature + TypeParameter = 0x00040000, // Type parameter + TypeAlias = 0x00080000, // Type alias // Export markers (see comment in declareModuleMember in binder) - ExportValue = 0x00400000, // Exported value marker - ExportType = 0x00800000, // Exported type marker - ExportNamespace = 0x01000000, // Exported namespace marker - Import = 0x02000000, // Import - Instantiated = 0x04000000, // Instantiated symbol - Merged = 0x08000000, // Merged symbol (created during program binding) - Transient = 0x10000000, // Transient symbol (created during type check) - Prototype = 0x20000000, // Prototype property (no source representation) - UnionProperty = 0x40000000, // Property in union type + ExportValue = 0x00100000, // Exported value marker + ExportType = 0x00200000, // Exported type marker + ExportNamespace = 0x00400000, // Exported namespace marker + Import = 0x00800000, // Import + Instantiated = 0x01000000, // Instantiated symbol + Merged = 0x02000000, // Merged symbol (created during program binding) + Transient = 0x04000000, // Transient symbol (created during type check) + Prototype = 0x08000000, // Prototype property (no source representation) + UnionProperty = 0x10000000, // Property in union type + Optional = 0x20000000, // Optional property Enum = RegularEnum | ConstEnum, Variable = FunctionScopedVariable | BlockScopedVariable, @@ -869,7 +868,6 @@ module ts { Namespace = ValueModule | NamespaceModule, Module = ValueModule | NamespaceModule, Accessor = GetAccessor | SetAccessor, - Signature = CallSignature | ConstructSignature | IndexSignature, // Variables can be redeclared, but can not redeclare a block-scoped declaration with the // same name, or any other value that is not a variable, e.g. ValueModule or Class