From 03b12e88afda403e8527358d73d46d2ee960cef6 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Mon, 17 Apr 2017 15:19:57 -0700 Subject: [PATCH] Type query can refer to block-scoped vars before declaration --- src/compiler/checker.ts | 231 +++++++++++----------------------------- src/compiler/core.ts | 22 ++++ 2 files changed, 86 insertions(+), 167 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index f11112811f5..73e9b8a7e5a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -727,7 +727,7 @@ namespace ts { if (declarationFile !== useFile) { if ((modulekind && (declarationFile.externalModuleIndicator || useFile.externalModuleIndicator)) || (!compilerOptions.outFile && !compilerOptions.out)) { - // nodes are in different files and order cannot be determines + // nodes are in different files and order cannot be determined return true; } // declaration is after usage @@ -745,7 +745,7 @@ namespace ts { // still might be illegal if declaration and usage are both binding elements (eg var [a = b, b = b] = [1, 2]) const errorBindingElement = getAncestor(usage, SyntaxKind.BindingElement) as BindingElement; if (errorBindingElement) { - return getAncestorBindingPattern(errorBindingElement) !== getAncestorBindingPattern(declaration) || + return findAncestor(errorBindingElement, isBindingElement) !== findAncestor(declaration, isBindingElement) || declaration.pos < errorBindingElement.pos; } // or it might be illegal if usage happens before parent variable is declared (eg var [a] = a) @@ -763,8 +763,10 @@ namespace ts { // 1. inside a function // 2. inside an instance property initializer, a reference to a non-instance property // 3. inside a static property initializer, a reference to a static method in the same class + // or if usage is in a type context: + // 1. inside a type query (typeof in type position) const container = getEnclosingBlockScopeContainer(declaration); - return isUsedInFunctionOrInstanceProperty(usage, declaration, container); + return isInTypeQuery(usage) || isUsedInFunctionOrInstanceProperty(usage, declaration, container); function isImmediatelyUsedInInitializerOfBlockScopedVariable(declaration: VariableDeclaration, usage: Node): boolean { const container = getEnclosingBlockScopeContainer(declaration); @@ -794,12 +796,7 @@ namespace ts { } function isUsedInFunctionOrInstanceProperty(usage: Node, declaration: Node, container?: Node): boolean { - let current = usage; - while (current) { - if (current === container) { - return false; - } - + return !!findAncestor(usage, current => { if (isFunctionLike(current)) { return true; } @@ -821,20 +818,7 @@ namespace ts { } } } - - current = current.parent; - } - return false; - } - - function getAncestorBindingPattern(node: Node): BindingPattern { - while (node) { - if (isBindingPattern(node)) { - return node; - } - node = node.parent; - } - return undefined; + }, n => n === container); } } @@ -1256,15 +1240,7 @@ namespace ts { * Return false if 'stopAt' node is reached or isFunctionLike(current) === true. */ function isSameScopeDescendentOf(initial: Node, parent: Node, stopAt: Node): boolean { - if (!parent) { - return false; - } - for (let current = initial; current && current !== stopAt && !isFunctionLike(current); current = current.parent) { - if (current === parent) { - return true; - } - } - return false; + return parent && !!findAncestor(initial, n => n === parent, n => n === stopAt || isFunctionLike(n)); } function getAnyImportSyntax(node: Node): AnyImportSyntax { @@ -1273,10 +1249,7 @@ namespace ts { return node; } - while (node && node.kind !== SyntaxKind.ImportDeclaration) { - node = node.parent; - } - return node; + return findAncestor(node, n => n.kind === SyntaxKind.ImportDeclaration) as ImportDeclaration; } } @@ -2137,11 +2110,8 @@ namespace ts { return { accessibility: SymbolAccessibility.Accessible }; function getExternalModuleContainer(declaration: Node) { - for (; declaration; declaration = declaration.parent) { - if (hasExternalModuleSymbol(declaration)) { - return getSymbolOfNode(declaration); - } - } + const node = findAncestor(declaration, hasExternalModuleSymbol); + return node && getSymbolOfNode(node); } } @@ -2885,10 +2855,7 @@ namespace ts { function getTypeAliasForTypeLiteral(type: Type): Symbol { if (type.symbol && type.symbol.flags & SymbolFlags.TypeLiteral) { - let node = type.symbol.declarations[0].parent; - while (node.kind === SyntaxKind.ParenthesizedType) { - node = node.parent; - } + const node = findAncestor(type.symbol.declarations[0].parent, n => n.kind !== SyntaxKind.ParenthesizedType); if (node.kind === SyntaxKind.TypeAliasDeclaration) { return getSymbolOfNode(node); } @@ -3831,8 +3798,7 @@ namespace ts { } function getDeclarationContainer(node: Node): Node { - node = getRootDeclaration(node); - while (node) { + node = findAncestor(getRootDeclaration(node), node => { switch (node.kind) { case SyntaxKind.VariableDeclaration: case SyntaxKind.VariableDeclarationList: @@ -3840,13 +3806,12 @@ namespace ts { case SyntaxKind.NamedImports: case SyntaxKind.NamespaceImport: case SyntaxKind.ImportClause: - node = node.parent; - break; - + return false; default: - return node.parent; + return true; } - } + }); + return node && node.parent; } function getTypeOfPrototypeProperty(prototype: Symbol): Type { @@ -7918,8 +7883,7 @@ namespace ts { // Starting with the parent of the symbol's declaration, check if the mapper maps any of // the type parameters introduced by enclosing declarations. We just pick the first // declaration since multiple declarations will all have the same parent anyway. - let node: Node = symbol.declarations[0]; - while (node) { + return !!findAncestor(symbol.declarations[0], node => { switch (node.kind) { case SyntaxKind.FunctionType: case SyntaxKind.ConstructorType: @@ -7966,13 +7930,9 @@ namespace ts { } } break; - case SyntaxKind.ModuleDeclaration: - case SyntaxKind.SourceFile: - return false; } - node = node.parent; - } - return false; + }, + node => node.kind === SyntaxKind.ModuleDeclaration || node.kind === SyntaxKind.SourceFile); } function isTopLevelTypeAlias(symbol: Symbol) { @@ -10416,19 +10376,10 @@ namespace ts { // TypeScript 1.0 spec (April 2014): 3.6.3 // A type query consists of the keyword typeof followed by an expression. // The expression is restricted to a single identifier or a sequence of identifiers separated by periods - while (node) { - switch (node.kind) { - case SyntaxKind.TypeQuery: - return true; - case SyntaxKind.Identifier: - case SyntaxKind.QualifiedName: - node = node.parent; - continue; - default: - return false; - } - } - Debug.fail("should not get here"); + return !!findAncestor( + node, + n => n.kind === SyntaxKind.TypeQuery, + n => n.kind !== SyntaxKind.TypeQuery && n.kind !== SyntaxKind.Identifier && n.kind !== SyntaxKind.QualifiedName); } // Return the flow cache key for a "dotted name" (i.e. a sequence of identifiers @@ -11651,15 +11602,11 @@ namespace ts { } function getControlFlowContainer(node: Node): Node { - while (true) { - node = node.parent; - if (isFunctionLike(node) && !getImmediatelyInvokedFunctionExpression(node) || - node.kind === SyntaxKind.ModuleBlock || - node.kind === SyntaxKind.SourceFile || - node.kind === SyntaxKind.PropertyDeclaration) { - return node; - } - } + return findAncestor(node.parent, node => + isFunctionLike(node) && !getImmediatelyInvokedFunctionExpression(node) || + node.kind === SyntaxKind.ModuleBlock || + node.kind === SyntaxKind.SourceFile || + node.kind === SyntaxKind.PropertyDeclaration); } // Check if a parameter is assigned anywhere within its declaring function. @@ -11676,15 +11623,7 @@ namespace ts { } function hasParentWithAssignmentsMarked(node: Node) { - while (true) { - node = node.parent; - if (!node) { - return false; - } - if (isFunctionLike(node) && getNodeLinks(node).flags & NodeCheckFlags.AssignmentsMarked) { - return true; - } - } + return !!findAncestor(node.parent, node => isFunctionLike(node) && getNodeLinks(node).flags & NodeCheckFlags.AssignmentsMarked); } function markParameterAssignments(node: Node) { @@ -11856,15 +11795,7 @@ namespace ts { } function isInsideFunction(node: Node, threshold: Node): boolean { - let current = node; - while (current && current !== threshold) { - if (isFunctionLike(current)) { - return true; - } - current = current.parent; - } - - return false; + return !!findAncestor(node, isFunctionLike, n => n === threshold); } function checkNestedBlockScopedBinding(node: Identifier, symbol: Symbol): void { @@ -11916,8 +11847,8 @@ namespace ts { } function isAssignedInBodyOfForStatement(node: Identifier, container: ForStatement): boolean { - let current: Node = node; // skip parenthesized nodes + let current: Node = node; while (current.parent.kind === SyntaxKind.ParenthesizedExpression) { current = current.parent; } @@ -11938,15 +11869,7 @@ namespace ts { // at this point we know that node is the target of assignment // now check that modification happens inside the statement part of the ForStatement - while (current !== container) { - if (current === container.statement) { - return true; - } - else { - current = current.parent; - } - } - return false; + return !!findAncestor(current, n => n === container.statement, n => n === container); } function captureLexicalThis(node: Node, container: Node): void { @@ -12128,12 +12051,7 @@ namespace ts { } function isInConstructorArgumentInitializer(node: Node, constructorDecl: Node): boolean { - for (let n = node; n && n !== constructorDecl; n = n.parent) { - if (n.kind === SyntaxKind.Parameter) { - return true; - } - } - return false; + return !!findAncestor(node, n => n.kind === SyntaxKind.Parameter, n => n === constructorDecl); } function checkSuperExpression(node: Node): Type { @@ -12159,10 +12077,7 @@ namespace ts { // class B { // [super.foo()]() {} // } - let current = node; - while (current && current !== container && current.kind !== SyntaxKind.ComputedPropertyName) { - current = current.parent; - } + const current = findAncestor(node, n => n.kind === SyntaxKind.ComputedPropertyName, n => n === container); if (current && current.kind === SyntaxKind.ComputedPropertyName) { error(node, Diagnostics.super_cannot_be_referenced_in_a_computed_property_name); } @@ -12766,13 +12681,8 @@ namespace ts { } function getContextualMapper(node: Node) { - while (node) { - if (node.contextualMapper) { - return node.contextualMapper; - } - node = node.parent; - } - return identityMapper; + node = findAncestor(node, n => n.contextualMapper); + return node ? node.contextualMapper : identityMapper; } // If the given type is an object or union type, if that type has a single signature, and if @@ -19010,8 +18920,7 @@ namespace ts { // this function will run after checking the source file so 'CaptureThis' is correct for all nodes function checkIfThisIsCapturedInEnclosingScope(node: Node): void { - let current = node; - while (current) { + findAncestor(node, current => { if (getNodeCheckFlags(current) & NodeCheckFlags.CaptureThis) { const isDeclaration = node.kind !== SyntaxKind.Identifier; if (isDeclaration) { @@ -19020,15 +18929,13 @@ namespace ts { else { error(node, Diagnostics.Expression_resolves_to_variable_declaration_this_that_compiler_uses_to_capture_this_reference); } - return; + return true; } - current = current.parent; - } + }); } function checkIfNewTargetIsCapturedInEnclosingScope(node: Node): void { - let current = node; - while (current) { + findAncestor(node, current => { if (getNodeCheckFlags(current) & NodeCheckFlags.CaptureNewTarget) { const isDeclaration = node.kind !== SyntaxKind.Identifier; if (isDeclaration) { @@ -19037,10 +18944,9 @@ namespace ts { else { error(node, Diagnostics.Expression_resolves_to_variable_declaration_newTarget_that_compiler_uses_to_capture_new_target_meta_property_reference); } - return; + return true; } - current = current.parent; - } + }); } function checkCollisionWithCapturedSuperVariable(node: Node, name: Identifier) { @@ -19224,19 +19130,17 @@ namespace ts { return; } // - parameter is wrapped in function-like entity - let current = n; - while (current !== node.initializer) { - if (isFunctionLike(current.parent)) { - return; - } - // computed property names/initializers in instance property declaration of class like entities - // are executed in constructor and thus deferred - if (current.parent.kind === SyntaxKind.PropertyDeclaration && - !(hasModifier(current.parent, ModifierFlags.Static)) && - isClassLike(current.parent.parent)) { - return; - } - current = current.parent; + if (findAncestor( + n, + current => + isFunctionLike(current.parent) || + // computed property names/initializers in instance property declaration of class like entities + // are executed in constructor and thus deferred + (current.parent.kind === SyntaxKind.PropertyDeclaration && + !(hasModifier(current.parent, ModifierFlags.Static)) && + isClassLike(current.parent.parent)), + n => n === node.initializer)) { + return; } // fall through to report error } @@ -20034,18 +19938,15 @@ namespace ts { function checkLabeledStatement(node: LabeledStatement) { // Grammar checking if (!checkGrammarStatementInAmbientContext(node)) { - let current = node.parent; - while (current) { - if (isFunctionLike(current)) { - break; - } - if (current.kind === SyntaxKind.LabeledStatement && (current).label.text === node.label.text) { - const sourceFile = getSourceFileOfNode(node); - grammarErrorOnNode(node.label, Diagnostics.Duplicate_label_0, getTextOfNodeFromSourceText(sourceFile.text, node.label)); - break; - } - current = current.parent; - } + findAncestor(node.parent, + current => { + if (current.kind === SyntaxKind.LabeledStatement && (current).label.text === node.label.text) { + const sourceFile = getSourceFileOfNode(node); + grammarErrorOnNode(node.label, Diagnostics.Duplicate_label_0, getTextOfNodeFromSourceText(sourceFile.text, node.label)); + return true; + } + }, + isFunctionLike); } // ensure that label is unique @@ -22298,11 +22199,7 @@ namespace ts { const symbolIsUmdExport = symbolFile !== referenceFile; return symbolIsUmdExport ? undefined : symbolFile; } - for (let n = node.parent; n; n = n.parent) { - if (isModuleOrEnumDeclaration(n) && getSymbolOfNode(n) === parentSymbol) { - return n; - } - } + return findAncestor(node.parent, n => isModuleOrEnumDeclaration(n) && getSymbolOfNode(n) === parentSymbol) as ModuleDeclaration | EnumDeclaration; } } } diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 5296a25d6cf..603c6a5a65e 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -225,6 +225,28 @@ namespace ts { return undefined; } + /** + * Iterates through the parent chain of a node and performs the callback on each parent until the callback + * returns a truthy value, then returns that value. + * If no such value is found, it applies the callback until the parent pointer is undefined or 'done' returns true + * At that point findAncestor returns undefined. + */ + export function findAncestor(node: Node, callback: (element: Node) => T | undefined, done?: (element: Node) => boolean): Node { + if (node) { + while (node) { + if (done && done(node)) { + break; + } + if (callback(node)) { + return node; + } + node = node.parent; + } + } + return undefined; + } + + export function zipWith(arrayA: T[], arrayB: U[], callback: (a: T, b: U, index: number) => void): void { Debug.assert(arrayA.length === arrayB.length); for (let i = 0; i < arrayA.length; i++) {