From 03b12e88afda403e8527358d73d46d2ee960cef6 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Mon, 17 Apr 2017 15:19:57 -0700 Subject: [PATCH 1/4] 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++) { From 42ac23a5857ee0c71503a2d62d0b9bc71e23e720 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Mon, 17 Apr 2017 15:20:36 -0700 Subject: [PATCH 2/4] Test: typeof can refer to block-scoped var before declaration --- .../reference/typeofUsedBeforeBlockScoped.js | 20 +++++++++++ .../typeofUsedBeforeBlockScoped.symbols | 33 +++++++++++++++++ .../typeofUsedBeforeBlockScoped.types | 36 +++++++++++++++++++ .../compiler/typeofUsedBeforeBlockScoped.ts | 8 +++++ 4 files changed, 97 insertions(+) create mode 100644 tests/baselines/reference/typeofUsedBeforeBlockScoped.js create mode 100644 tests/baselines/reference/typeofUsedBeforeBlockScoped.symbols create mode 100644 tests/baselines/reference/typeofUsedBeforeBlockScoped.types create mode 100644 tests/cases/compiler/typeofUsedBeforeBlockScoped.ts diff --git a/tests/baselines/reference/typeofUsedBeforeBlockScoped.js b/tests/baselines/reference/typeofUsedBeforeBlockScoped.js new file mode 100644 index 00000000000..d897158f145 --- /dev/null +++ b/tests/baselines/reference/typeofUsedBeforeBlockScoped.js @@ -0,0 +1,20 @@ +//// [typeofUsedBeforeBlockScoped.ts] +// TODO: Change name of file to typeofUsedBeforeBlockScoped.ts +type T = typeof C & typeof C.s & typeof o & typeof o.n; +class C { + static s = 2; +} +type W = typeof o.n; +let o2: typeof o; +let o = { n: 12 }; + + +//// [typeofUsedBeforeBlockScoped.js] +var C = (function () { + function C() { + } + return C; +}()); +C.s = 2; +var o2; +var o = { n: 12 }; diff --git a/tests/baselines/reference/typeofUsedBeforeBlockScoped.symbols b/tests/baselines/reference/typeofUsedBeforeBlockScoped.symbols new file mode 100644 index 00000000000..dc2e26bfee0 --- /dev/null +++ b/tests/baselines/reference/typeofUsedBeforeBlockScoped.symbols @@ -0,0 +1,33 @@ +=== tests/cases/compiler/typeofUsedBeforeBlockScoped.ts === +// TODO: Change name of file to typeofUsedBeforeBlockScoped.ts +type T = typeof C & typeof C.s & typeof o & typeof o.n; +>T : Symbol(T, Decl(typeofUsedBeforeBlockScoped.ts, 0, 0)) +>C : Symbol(C, Decl(typeofUsedBeforeBlockScoped.ts, 1, 55)) +>C.s : Symbol(C.s, Decl(typeofUsedBeforeBlockScoped.ts, 2, 9)) +>C : Symbol(C, Decl(typeofUsedBeforeBlockScoped.ts, 1, 55)) +>s : Symbol(C.s, Decl(typeofUsedBeforeBlockScoped.ts, 2, 9)) +>o : Symbol(o, Decl(typeofUsedBeforeBlockScoped.ts, 7, 3)) +>o.n : Symbol(n, Decl(typeofUsedBeforeBlockScoped.ts, 7, 9)) +>o : Symbol(o, Decl(typeofUsedBeforeBlockScoped.ts, 7, 3)) +>n : Symbol(n, Decl(typeofUsedBeforeBlockScoped.ts, 7, 9)) + +class C { +>C : Symbol(C, Decl(typeofUsedBeforeBlockScoped.ts, 1, 55)) + + static s = 2; +>s : Symbol(C.s, Decl(typeofUsedBeforeBlockScoped.ts, 2, 9)) +} +type W = typeof o.n; +>W : Symbol(W, Decl(typeofUsedBeforeBlockScoped.ts, 4, 1)) +>o.n : Symbol(n, Decl(typeofUsedBeforeBlockScoped.ts, 7, 9)) +>o : Symbol(o, Decl(typeofUsedBeforeBlockScoped.ts, 7, 3)) +>n : Symbol(n, Decl(typeofUsedBeforeBlockScoped.ts, 7, 9)) + +let o2: typeof o; +>o2 : Symbol(o2, Decl(typeofUsedBeforeBlockScoped.ts, 6, 3)) +>o : Symbol(o, Decl(typeofUsedBeforeBlockScoped.ts, 7, 3)) + +let o = { n: 12 }; +>o : Symbol(o, Decl(typeofUsedBeforeBlockScoped.ts, 7, 3)) +>n : Symbol(n, Decl(typeofUsedBeforeBlockScoped.ts, 7, 9)) + diff --git a/tests/baselines/reference/typeofUsedBeforeBlockScoped.types b/tests/baselines/reference/typeofUsedBeforeBlockScoped.types new file mode 100644 index 00000000000..87cff61425c --- /dev/null +++ b/tests/baselines/reference/typeofUsedBeforeBlockScoped.types @@ -0,0 +1,36 @@ +=== tests/cases/compiler/typeofUsedBeforeBlockScoped.ts === +// TODO: Change name of file to typeofUsedBeforeBlockScoped.ts +type T = typeof C & typeof C.s & typeof o & typeof o.n; +>T : T +>C : typeof C +>C.s : number +>C : typeof C +>s : number +>o : { n: number; } +>o.n : number +>o : { n: number; } +>n : number + +class C { +>C : C + + static s = 2; +>s : number +>2 : 2 +} +type W = typeof o.n; +>W : number +>o.n : number +>o : { n: number; } +>n : number + +let o2: typeof o; +>o2 : { n: number; } +>o : { n: number; } + +let o = { n: 12 }; +>o : { n: number; } +>{ n: 12 } : { n: number; } +>n : number +>12 : 12 + diff --git a/tests/cases/compiler/typeofUsedBeforeBlockScoped.ts b/tests/cases/compiler/typeofUsedBeforeBlockScoped.ts new file mode 100644 index 00000000000..6ba887d195a --- /dev/null +++ b/tests/cases/compiler/typeofUsedBeforeBlockScoped.ts @@ -0,0 +1,8 @@ +// TODO: Change name of file to typeofUsedBeforeBlockScoped.ts +type T = typeof C & typeof C.s & typeof o & typeof o.n; +class C { + static s = 2; +} +type W = typeof o.n; +let o2: typeof o; +let o = { n: 12 }; From aaf861af96a6222883853c2942e5ec72549dc155 Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Mon, 17 Apr 2017 15:25:36 -0700 Subject: [PATCH 3/4] Remove TODO from test now that it's done --- .../reference/typeofUsedBeforeBlockScoped.js | 1 - .../typeofUsedBeforeBlockScoped.symbols | 37 +++++++++---------- .../typeofUsedBeforeBlockScoped.types | 1 - .../compiler/typeofUsedBeforeBlockScoped.ts | 1 - 4 files changed, 18 insertions(+), 22 deletions(-) diff --git a/tests/baselines/reference/typeofUsedBeforeBlockScoped.js b/tests/baselines/reference/typeofUsedBeforeBlockScoped.js index d897158f145..eebf4beae67 100644 --- a/tests/baselines/reference/typeofUsedBeforeBlockScoped.js +++ b/tests/baselines/reference/typeofUsedBeforeBlockScoped.js @@ -1,5 +1,4 @@ //// [typeofUsedBeforeBlockScoped.ts] -// TODO: Change name of file to typeofUsedBeforeBlockScoped.ts type T = typeof C & typeof C.s & typeof o & typeof o.n; class C { static s = 2; diff --git a/tests/baselines/reference/typeofUsedBeforeBlockScoped.symbols b/tests/baselines/reference/typeofUsedBeforeBlockScoped.symbols index dc2e26bfee0..5cf09a4d4d5 100644 --- a/tests/baselines/reference/typeofUsedBeforeBlockScoped.symbols +++ b/tests/baselines/reference/typeofUsedBeforeBlockScoped.symbols @@ -1,33 +1,32 @@ === tests/cases/compiler/typeofUsedBeforeBlockScoped.ts === -// TODO: Change name of file to typeofUsedBeforeBlockScoped.ts type T = typeof C & typeof C.s & typeof o & typeof o.n; >T : Symbol(T, Decl(typeofUsedBeforeBlockScoped.ts, 0, 0)) ->C : Symbol(C, Decl(typeofUsedBeforeBlockScoped.ts, 1, 55)) ->C.s : Symbol(C.s, Decl(typeofUsedBeforeBlockScoped.ts, 2, 9)) ->C : Symbol(C, Decl(typeofUsedBeforeBlockScoped.ts, 1, 55)) ->s : Symbol(C.s, Decl(typeofUsedBeforeBlockScoped.ts, 2, 9)) ->o : Symbol(o, Decl(typeofUsedBeforeBlockScoped.ts, 7, 3)) ->o.n : Symbol(n, Decl(typeofUsedBeforeBlockScoped.ts, 7, 9)) ->o : Symbol(o, Decl(typeofUsedBeforeBlockScoped.ts, 7, 3)) ->n : Symbol(n, Decl(typeofUsedBeforeBlockScoped.ts, 7, 9)) +>C : Symbol(C, Decl(typeofUsedBeforeBlockScoped.ts, 0, 55)) +>C.s : Symbol(C.s, Decl(typeofUsedBeforeBlockScoped.ts, 1, 9)) +>C : Symbol(C, Decl(typeofUsedBeforeBlockScoped.ts, 0, 55)) +>s : Symbol(C.s, Decl(typeofUsedBeforeBlockScoped.ts, 1, 9)) +>o : Symbol(o, Decl(typeofUsedBeforeBlockScoped.ts, 6, 3)) +>o.n : Symbol(n, Decl(typeofUsedBeforeBlockScoped.ts, 6, 9)) +>o : Symbol(o, Decl(typeofUsedBeforeBlockScoped.ts, 6, 3)) +>n : Symbol(n, Decl(typeofUsedBeforeBlockScoped.ts, 6, 9)) class C { ->C : Symbol(C, Decl(typeofUsedBeforeBlockScoped.ts, 1, 55)) +>C : Symbol(C, Decl(typeofUsedBeforeBlockScoped.ts, 0, 55)) static s = 2; ->s : Symbol(C.s, Decl(typeofUsedBeforeBlockScoped.ts, 2, 9)) +>s : Symbol(C.s, Decl(typeofUsedBeforeBlockScoped.ts, 1, 9)) } type W = typeof o.n; ->W : Symbol(W, Decl(typeofUsedBeforeBlockScoped.ts, 4, 1)) ->o.n : Symbol(n, Decl(typeofUsedBeforeBlockScoped.ts, 7, 9)) ->o : Symbol(o, Decl(typeofUsedBeforeBlockScoped.ts, 7, 3)) ->n : Symbol(n, Decl(typeofUsedBeforeBlockScoped.ts, 7, 9)) +>W : Symbol(W, Decl(typeofUsedBeforeBlockScoped.ts, 3, 1)) +>o.n : Symbol(n, Decl(typeofUsedBeforeBlockScoped.ts, 6, 9)) +>o : Symbol(o, Decl(typeofUsedBeforeBlockScoped.ts, 6, 3)) +>n : Symbol(n, Decl(typeofUsedBeforeBlockScoped.ts, 6, 9)) let o2: typeof o; ->o2 : Symbol(o2, Decl(typeofUsedBeforeBlockScoped.ts, 6, 3)) ->o : Symbol(o, Decl(typeofUsedBeforeBlockScoped.ts, 7, 3)) +>o2 : Symbol(o2, Decl(typeofUsedBeforeBlockScoped.ts, 5, 3)) +>o : Symbol(o, Decl(typeofUsedBeforeBlockScoped.ts, 6, 3)) let o = { n: 12 }; ->o : Symbol(o, Decl(typeofUsedBeforeBlockScoped.ts, 7, 3)) ->n : Symbol(n, Decl(typeofUsedBeforeBlockScoped.ts, 7, 9)) +>o : Symbol(o, Decl(typeofUsedBeforeBlockScoped.ts, 6, 3)) +>n : Symbol(n, Decl(typeofUsedBeforeBlockScoped.ts, 6, 9)) diff --git a/tests/baselines/reference/typeofUsedBeforeBlockScoped.types b/tests/baselines/reference/typeofUsedBeforeBlockScoped.types index 87cff61425c..ec8e30bb135 100644 --- a/tests/baselines/reference/typeofUsedBeforeBlockScoped.types +++ b/tests/baselines/reference/typeofUsedBeforeBlockScoped.types @@ -1,5 +1,4 @@ === tests/cases/compiler/typeofUsedBeforeBlockScoped.ts === -// TODO: Change name of file to typeofUsedBeforeBlockScoped.ts type T = typeof C & typeof C.s & typeof o & typeof o.n; >T : T >C : typeof C diff --git a/tests/cases/compiler/typeofUsedBeforeBlockScoped.ts b/tests/cases/compiler/typeofUsedBeforeBlockScoped.ts index 6ba887d195a..3ab1534875b 100644 --- a/tests/cases/compiler/typeofUsedBeforeBlockScoped.ts +++ b/tests/cases/compiler/typeofUsedBeforeBlockScoped.ts @@ -1,4 +1,3 @@ -// TODO: Change name of file to typeofUsedBeforeBlockScoped.ts type T = typeof C & typeof C.s & typeof o & typeof o.n; class C { static s = 2; From fabf5d8d533aedc17da6d6247c2857e301f22f8e Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Tue, 18 Apr 2017 11:17:07 -0700 Subject: [PATCH 4/4] findAncestor:single callback returns boolean|"quit" Previously there was a 'found' callback and a 'quit' callback. --- src/compiler/checker.ts | 53 ++++++++++++++++++++++++----------------- src/compiler/core.ts | 23 ++++++++---------- 2 files changed, 41 insertions(+), 35 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 1863a3104d3..d2b13b0835c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -803,6 +803,9 @@ namespace ts { function isUsedInFunctionOrInstanceProperty(usage: Node, declaration: Node, container?: Node): boolean { return !!findAncestor(usage, current => { + if (current === container) { + return "quit"; + } if (isFunctionLike(current)) { return true; } @@ -824,7 +827,7 @@ namespace ts { } } } - }, n => n === container); + }); } } @@ -1246,7 +1249,7 @@ namespace ts { * Return false if 'stopAt' node is reached or isFunctionLike(current) === true. */ function isSameScopeDescendentOf(initial: Node, parent: Node, stopAt: Node): boolean { - return parent && !!findAncestor(initial, n => n === parent, n => n === stopAt || isFunctionLike(n)); + return parent && !!findAncestor(initial, n => n === stopAt || isFunctionLike(n) ? "quit" : n === parent); } function getAnyImportSyntax(node: Node): AnyImportSyntax { @@ -7890,6 +7893,9 @@ namespace ts { // the type parameters introduced by enclosing declarations. We just pick the first // declaration since multiple declarations will all have the same parent anyway. return !!findAncestor(symbol.declarations[0], node => { + if (node.kind === SyntaxKind.ModuleDeclaration || node.kind === SyntaxKind.SourceFile) { + return "quit"; + } switch (node.kind) { case SyntaxKind.FunctionType: case SyntaxKind.ConstructorType: @@ -7937,8 +7943,7 @@ namespace ts { } break; } - }, - node => node.kind === SyntaxKind.ModuleDeclaration || node.kind === SyntaxKind.SourceFile); + }); } function isTopLevelTypeAlias(symbol: Symbol) { @@ -10384,8 +10389,7 @@ namespace ts { // The expression is restricted to a single identifier or a sequence of identifiers separated by periods return !!findAncestor( node, - n => n.kind === SyntaxKind.TypeQuery, - n => n.kind !== SyntaxKind.TypeQuery && n.kind !== SyntaxKind.Identifier && n.kind !== SyntaxKind.QualifiedName); + n => n.kind === SyntaxKind.TypeQuery ? true : n.kind === SyntaxKind.Identifier || n.kind === SyntaxKind.QualifiedName ? false : "quit"); } // Return the flow cache key for a "dotted name" (i.e. a sequence of identifiers @@ -11629,7 +11633,7 @@ namespace ts { } function hasParentWithAssignmentsMarked(node: Node) { - return !!findAncestor(node.parent, node => isFunctionLike(node) && getNodeLinks(node).flags & NodeCheckFlags.AssignmentsMarked); + return !!findAncestor(node.parent, node => isFunctionLike(node) && !!(getNodeLinks(node).flags & NodeCheckFlags.AssignmentsMarked)); } function markParameterAssignments(node: Node) { @@ -11801,7 +11805,7 @@ namespace ts { } function isInsideFunction(node: Node, threshold: Node): boolean { - return !!findAncestor(node, isFunctionLike, n => n === threshold); + return !!findAncestor(node, n => n === threshold ? "quit" : isFunctionLike(n)); } function checkNestedBlockScopedBinding(node: Identifier, symbol: Symbol): void { @@ -11875,7 +11879,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 - return !!findAncestor(current, n => n === container.statement, n => n === container); + return !!findAncestor(current, n => n === container ? "quit" : n === container.statement); } function captureLexicalThis(node: Node, container: Node): void { @@ -12057,7 +12061,7 @@ namespace ts { } function isInConstructorArgumentInitializer(node: Node, constructorDecl: Node): boolean { - return !!findAncestor(node, n => n.kind === SyntaxKind.Parameter, n => n === constructorDecl); + return !!findAncestor(node, n => n === constructorDecl ? "quit" : n.kind === SyntaxKind.Parameter); } function checkSuperExpression(node: Node): Type { @@ -12083,7 +12087,7 @@ namespace ts { // class B { // [super.foo()]() {} // } - const current = findAncestor(node, n => n.kind === SyntaxKind.ComputedPropertyName, n => n === container); + const current = findAncestor(node, n => n === container ? "quit" : n.kind === SyntaxKind.ComputedPropertyName); if (current && current.kind === SyntaxKind.ComputedPropertyName) { error(node, Diagnostics.super_cannot_be_referenced_in_a_computed_property_name); } @@ -12687,7 +12691,7 @@ namespace ts { } function getContextualMapper(node: Node) { - node = findAncestor(node, n => n.contextualMapper); + node = findAncestor(node, n => !!n.contextualMapper); return node ? node.contextualMapper : identityMapper; } @@ -19138,14 +19142,17 @@ namespace ts { // - parameter is wrapped in function-like entity 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)) { + current => { + if (current === node.initializer) { + return "quit"; + } + return 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)); + })) { return; } // fall through to report error @@ -19946,13 +19953,15 @@ namespace ts { if (!checkGrammarStatementInAmbientContext(node)) { findAncestor(node.parent, current => { + if (isFunctionLike(current)) { + return "quit"; + } 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 diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 603c6a5a65e..3c867024485 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -224,29 +224,26 @@ 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 + * If no such value is found, it applies the callback until the parent pointer is undefined or the callback returns "quit" * 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; + export function findAncestor(node: Node, callback: (element: Node) => boolean | "quit"): Node { + while (node) { + const result = callback(node); + if (result === "quit") { + return undefined; } + else if (result) { + 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++) {