From b49e2772ea1e6cb09382ecbfc5dd81e89f470683 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 4 Dec 2014 17:43:15 -0800 Subject: [PATCH] Addressing CR feedback --- src/compiler/checker.ts | 65 ++++++++++++++++++++++++++++------- src/compiler/emitter.ts | 19 ++++++---- src/compiler/parser.ts | 2 +- src/services/navigationBar.ts | 7 +++- 4 files changed, 72 insertions(+), 21 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index e65567cf8a5..43340d39e86 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1660,17 +1660,23 @@ module ts { return classType.typeParameters ? createTypeReference(classType, map(classType.typeParameters, _ => anyType)) : classType; } + // Return the type of the given property in the given type, or undefined if no such property exists function getTypeOfPropertyOfType(type: Type, name: string): Type { var prop = getPropertyOfType(type, name); return prop ? getTypeOfSymbol(prop) : undefined; } + // Return the inferred type for a binding element function getTypeForBindingElement(declaration: BindingElement): Type { var pattern = declaration.parent; var parentType = getTypeForVariableDeclaration(pattern.parent); + // If parent has the unknown (error) type, then so does this binding element if (parentType === unknownType) { return unknownType; } + // If no type was specified or inferred for parent, or if the specified or inferred type is any, + // infer from the initializer of the binding element if one is present. Otherwise, go with the + // undefined or any type of the parent. if (!parentType || parentType === anyType) { if (declaration.initializer) { return checkExpressionCached(declaration.initializer); @@ -1678,7 +1684,10 @@ module ts { return parentType; } if (pattern.kind === SyntaxKind.ObjectBindingPattern) { + // Use explicitly specified property name ({ p: xxx } form), or otherwise the implied name ({ p } form) var name = declaration.propertyName || declaration.name; + // 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. var type = getTypeOfPropertyOfType(parentType, name.text) || isNumericName(name.text) && getIndexTypeOfType(parentType, IndexKind.Number) || getIndexTypeOfType(parentType, IndexKind.String); @@ -1688,12 +1697,14 @@ module ts { } return type; } + // For an array binding element the specified or inferred type of the parent must be assignable to any[] if (!isTypeAssignableTo(parentType, anyArrayType)) { error(pattern, Diagnostics.Type_0_is_not_an_array_type, typeToString(parentType)); return unknownType; } + // Use specific property type when parent is a tuple or numeric index type when parent is an array var propName = "" + indexOf(pattern.elements, declaration); - var type = isTupleType(parentType) ? getTypeOfPropertyOfType(parentType, propName) : getIndexTypeOfType(parentType, IndexKind.Number); + var type = isTupleLikeType(parentType) ? getTypeOfPropertyOfType(parentType, propName) : getIndexTypeOfType(parentType, IndexKind.Number); if (!type) { error(declaration, Diagnostics.Type_0_has_no_property_1, typeToString(parentType), propName); return unknownType; @@ -1701,6 +1712,7 @@ module ts { return type; } + // Return the inferred type for a variable, parameter, or property declaration function getTypeForVariableDeclaration(declaration: VariableDeclaration): Type { // A variable declared in a for..in statement is always of type any if (declaration.parent.kind === SyntaxKind.ForInStatement) { @@ -1732,15 +1744,17 @@ module ts { if (declaration.initializer) { return checkExpressionCached(declaration.initializer); } - // If it is a short-hand property assignment; Use the type of the identifier + // If it is a short-hand property assignment, use the type of the identifier if (declaration.kind === SyntaxKind.ShorthandPropertyAssignment) { - var type = checkIdentifier(declaration.name); - return type + return checkIdentifier(declaration.name); } // No type specified and nothing can be inferred return undefined; } + // 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 { if (element.initializer) { return getWidenedType(checkExpressionCached(element.initializer)); @@ -1751,6 +1765,13 @@ module ts { return anyType; } + // Return the type implied by a binding pattern. This is the type implied purely by the binding pattern itself + // and without regard to its context (i.e. without regard any type annotation or initializer associated with the + // declaration in which the binding pattern is contained). For example, the implied type of [x, y] is [any, any] + // and the implied type of { x, y: z = 1 } is { x: any; y: number; }. The type implied by a binding pattern is + // 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 { if (pattern.kind === SyntaxKind.ArrayBindingPattern) { return createTupleType(map(pattern.elements, e => e.kind === SyntaxKind.OmittedExpression ? anyType : getTypeFromBindingElement(e))); @@ -1766,14 +1787,28 @@ module ts { return createAnonymousType(undefined, members, emptyArray, emptyArray, undefined, undefined); } + // Return the type associated with a variable, parameter, or property declaration. In the simple case this is the type + // specified in a type annotation or inferred from an initializer. However, in the case of a destructuring declaration it + // is a bit more involved. For example: + // + // var [x, s = ""] = [1, "one"]; + // + // Here, the array literal [1, "one"] is contextually typed by the type [any, string], which is the implied type of the + // binding pattern [x, s = ""]. Because the contextual type is a tuple type, the resulting type of [1, "one"] is the + // tuple type [number, string]. Thus, the type inferred for 'x' is number and the type inferred for 's' is string. function getWidenedTypeForVariableDeclaration(declaration: VariableDeclaration, reportErrors?: boolean): Type { var type = getTypeForVariableDeclaration(declaration); if (type) { if (reportErrors) { reportErrorsFromWidening(declaration, type); } + // During a normal type check we'll never get to here with a property assignment (the check of the containing + // object literal uses a different path). We exclude widening only so that language services and type verification + // tools see the actual type. return declaration.kind !== SyntaxKind.PropertyAssignment ? getWidenedType(type) : type; } + // If no type was specified and nothing could be inferred, and if the declaration specifies a binding pattern, use + // the type implied by the binding pattern if (isBindingPattern(declaration.name)) { return getTypeFromBindingPattern(declaration.name); } @@ -4052,7 +4087,7 @@ module ts { return type.flags & TypeFlags.Reference && (type).target === globalArrayType; } - function isTupleType(type: Type): boolean { + function isTupleLikeType(type: Type): boolean { return !!getPropertyOfType(type, "0"); } @@ -4890,9 +4925,10 @@ module ts { } // In a variable, parameter or property declaration with a type annotation, the contextual type of an initializer - // expression is the type of the variable, parameter or property. In a parameter declaration of a contextually - // typed function expression, the contextual type of an initializer expression is the contextual type of the - // parameter. + // expression is the type of the variable, parameter or property. Otherwise, in a parameter declaration of a + // contextually typed function expression, the contextual type of an initializer expression is the contextual type + // of the parameter. Otherwise, in a variable or parameter declaration with a binding pattern name, the contextual + // type of an initializer expression is the type implied by the binding pattern. function getContextualTypeForInitializerExpression(node: Expression): Type { var declaration = node.parent; if (node === declaration.initializer) { @@ -5001,8 +5037,8 @@ module ts { } // Return true if the given contextual type is a tuple-like type - function contextualTypeIsTupleType(type: Type): boolean { - return !!(type.flags & TypeFlags.Union ? forEach((type).types, t => isTupleType(t)) : isTupleType(type)); + function contextualTypeIsTupleLikeType(type: Type): boolean { + return !!(type.flags & TypeFlags.Union ? forEach((type).types, t => isTupleLikeType(t)) : isTupleLikeType(type)); } // Return true if the given contextual type provides an index signature of the given kind @@ -5160,6 +5196,9 @@ module ts { return mapper && mapper !== identityMapper; } + // A node is an assignment target if it is on the left hand side of an '=' token, if it is parented by a property + // assignment in an object literal that is an assignment target, or if it is parented by an array literal that is + // an assignment target. Examples include 'a = xxx', '{ p: a } = xxx', '[{ p: a}] = xxx'. function isAssignmentTarget(node: Node): boolean { var parent = node.parent; if (parent.kind === SyntaxKind.BinaryExpression && (parent).operator === SyntaxKind.EqualsToken && (parent).left === node) { @@ -5181,7 +5220,7 @@ module ts { } var elementTypes = map(elements, e => checkExpression(e, contextualMapper)); var contextualType = getContextualType(node); - if ((contextualType && contextualTypeIsTupleType(contextualType)) || isAssignmentTarget(node)) { + if ((contextualType && contextualTypeIsTupleLikeType(contextualType)) || isAssignmentTarget(node)) { return createTupleType(elementTypes); } return createArrayType(getUnionType(elementTypes)); @@ -6492,7 +6531,7 @@ module ts { var p = properties[i]; if (p.kind === SyntaxKind.PropertyAssignment || p.kind === SyntaxKind.ShorthandPropertyAssignment) { // TODO(andersh): Computed property support - var name = ((p).name); + var name = (p).name; var type = sourceType.flags & TypeFlags.Any ? sourceType : getTypeOfPropertyOfType(sourceType, name.text) || isNumericName(name.text) && getIndexTypeOfType(sourceType, IndexKind.Number) || @@ -6523,7 +6562,7 @@ module ts { if (e.kind !== SyntaxKind.OmittedExpression) { var propName = "" + i; var type = sourceType.flags & TypeFlags.Any ? sourceType : - isTupleType(sourceType) ? getTypeOfPropertyOfType(sourceType, propName) : + isTupleLikeType(sourceType) ? getTypeOfPropertyOfType(sourceType, propName) : getIndexTypeOfType(sourceType, IndexKind.Number); if (type) { checkDestructuringAssignment(e, type, contextualMapper); diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index f58e3c3a050..2856e806de1 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -1881,13 +1881,18 @@ module ts { writeFile(compilerHost, diagnostics, jsFilePath, emitOutput, writeByteOrderMark); } - function createTempVariable(location: Node): Identifier { - do { - // First _a..._z, then _0, _1, ... - var name = "_" + (tempCount < 26 ? String.fromCharCode(tempCount + 0x61) : tempCount - 26); + // Create a temporary variable with a unique unused name. The forLoopVariable parameter signals that the + // name should be one that is appropriate for a for loop variable. + function createTempVariable(location: Node, forLoopVariable?: boolean): Identifier { + var name = forLoopVariable ? "_i" : undefined; + while (true) { + if (name && resolver.isUnknownIdentifier(location, name)) { + break; + } + // _a .. _h, _j ... _z, _0, _1, ... + name = "_" + (tempCount < 25 ? String.fromCharCode(tempCount + (tempCount < 8 ? 0: 1) + CharacterCodes.a) : tempCount - 25); tempCount++; } - while (!resolver.isUnknownIdentifier(location, name)); var result = createNode(SyntaxKind.Identifier); result.text = name; return result; @@ -2758,6 +2763,8 @@ module ts { function emitDestructuring(root: BinaryExpression | BindingElement, value?: Expression) { var emitCount = 0; + // An exported declaration is actually emitted as an assignment (to a property on the module object), so + // temporary variables in an exported declaration need to have real declarations elsewhere. var isDeclaration = (root.kind === SyntaxKind.VariableDeclaration && !(root.flags & NodeFlags.Export)) || root.kind === SyntaxKind.Parameter; if (root.kind === SyntaxKind.BinaryExpression) { emitAssignmentExpression(root); @@ -3038,7 +3045,7 @@ module ts { if (hasRestParameters(node)) { var restIndex = node.parameters.length - 1; var restParam = node.parameters[restIndex]; - var tempName = createTempVariable(node).text; + var tempName = createTempVariable(node, /*forLoopVariable*/ true).text; writeLine(); emitLeadingComments(restParam); emitStart(restParam); diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 94772b8b5dc..bfb9636f1c8 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -812,7 +812,7 @@ module ts { return undefined; } - enum ParsingContext { + const enum ParsingContext { SourceElements, // Elements in source file ModuleElements, // Elements in module declaration BlockStatements, // Statements in block diff --git a/src/services/navigationBar.ts b/src/services/navigationBar.ts index 40cd8980608..6a4d7cc3eaa 100644 --- a/src/services/navigationBar.ts +++ b/src/services/navigationBar.ts @@ -46,11 +46,16 @@ module ts.NavigationBar { case SyntaxKind.VariableStatement: forEach((node).declarations, visit); break; + case SyntaxKind.ObjectBindingPattern: + case SyntaxKind.ArrayBindingPattern: + forEach((node).elements, visit); + break; case SyntaxKind.VariableDeclaration: if (isBindingPattern(node)) { - forEach(((node).name).elements, visit); + visit((node).name); break; } + // Fall through case SyntaxKind.ClassDeclaration: case SyntaxKind.EnumDeclaration: case SyntaxKind.InterfaceDeclaration: