Merge pull request #15239 from Microsoft/typeof-can-refer-class-before-declaration

Typeof can refer to block-scoped values before declaration
This commit is contained in:
Nathan Shively-Sanders 2017-04-18 13:12:28 -07:00 committed by GitHub
commit e36d8d5c63
6 changed files with 183 additions and 165 deletions

View file

@ -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)
@ -764,13 +764,15 @@ namespace ts {
// 2. inside a function
// 3. inside an instance property initializer, a reference to a non-instance property
// 4. 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)
if (usage.parent.kind === SyntaxKind.ExportSpecifier) {
// export specifiers do not use the variable, they only make it available for use
return true;
}
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);
@ -800,12 +802,10 @@ namespace ts {
}
function isUsedInFunctionOrInstanceProperty(usage: Node, declaration: Node, container?: Node): boolean {
let current = usage;
while (current) {
return !!findAncestor(usage, current => {
if (current === container) {
return false;
return "quit";
}
if (isFunctionLike(current)) {
return true;
}
@ -827,20 +827,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;
});
}
}
@ -1263,15 +1250,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 === stopAt || isFunctionLike(n) ? "quit" : n === parent);
}
function getAnyImportSyntax(node: Node): AnyImportSyntax {
@ -1280,10 +1259,7 @@ namespace ts {
return <ImportEqualsDeclaration>node;
}
while (node && node.kind !== SyntaxKind.ImportDeclaration) {
node = node.parent;
}
return <ImportDeclaration>node;
return findAncestor(node, n => n.kind === SyntaxKind.ImportDeclaration) as ImportDeclaration;
}
}
@ -2145,11 +2121,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);
}
}
@ -2893,10 +2866,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);
}
@ -3840,8 +3810,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:
@ -3849,13 +3818,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 {
@ -7927,8 +7895,10 @@ 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 => {
if (node.kind === SyntaxKind.ModuleDeclaration || node.kind === SyntaxKind.SourceFile) {
return "quit";
}
switch (node.kind) {
case SyntaxKind.FunctionType:
case SyntaxKind.ConstructorType:
@ -7975,13 +7945,8 @@ namespace ts {
}
}
break;
case SyntaxKind.ModuleDeclaration:
case SyntaxKind.SourceFile:
return false;
}
node = node.parent;
}
return false;
});
}
function isTopLevelTypeAlias(symbol: Symbol) {
@ -10445,19 +10410,9 @@ 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 ? 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
@ -11680,15 +11635,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.
@ -11705,15 +11656,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) {
@ -11885,15 +11828,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, n => n === threshold ? "quit" : isFunctionLike(n));
}
function checkNestedBlockScopedBinding(node: Identifier, symbol: Symbol): void {
@ -11945,8 +11880,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;
}
@ -11967,15 +11902,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 ? "quit" : n === container.statement);
}
function captureLexicalThis(node: Node, container: Node): void {
@ -12157,12 +12084,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 === constructorDecl ? "quit" : n.kind === SyntaxKind.Parameter);
}
function checkSuperExpression(node: Node): Type {
@ -12188,10 +12110,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 === container ? "quit" : n.kind === SyntaxKind.ComputedPropertyName);
if (current && current.kind === SyntaxKind.ComputedPropertyName) {
error(node, Diagnostics.super_cannot_be_referenced_in_a_computed_property_name);
}
@ -12795,13 +12714,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
@ -19039,8 +18953,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) {
@ -19049,15 +18962,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) {
@ -19066,10 +18977,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) {
@ -19253,19 +19163,20 @@ 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 => {
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
}
@ -20063,18 +19974,17 @@ 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 && (<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 (isFunctionLike(current)) {
return "quit";
}
if (current.kind === SyntaxKind.LabeledStatement && (<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;
}
});
}
// ensure that label is unique
@ -22329,11 +22239,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 <ModuleDeclaration | EnumDeclaration>n;
}
}
return findAncestor(node.parent, n => isModuleOrEnumDeclaration(n) && getSymbolOfNode(n) === parentSymbol) as ModuleDeclaration | EnumDeclaration;
}
}
}

View file

@ -224,6 +224,25 @@ 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 the callback returns "quit"
* At that point findAncestor returns undefined.
*/
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<T, U>(arrayA: T[], arrayB: U[], callback: (a: T, b: U, index: number) => void): void {
Debug.assert(arrayA.length === arrayB.length);

View file

@ -0,0 +1,19 @@
//// [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 };

View file

@ -0,0 +1,32 @@
=== tests/cases/compiler/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, 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, 0, 55))
static s = 2;
>s : Symbol(C.s, Decl(typeofUsedBeforeBlockScoped.ts, 1, 9))
}
type W = typeof o.n;
>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, 5, 3))
>o : Symbol(o, Decl(typeofUsedBeforeBlockScoped.ts, 6, 3))
let o = { n: 12 };
>o : Symbol(o, Decl(typeofUsedBeforeBlockScoped.ts, 6, 3))
>n : Symbol(n, Decl(typeofUsedBeforeBlockScoped.ts, 6, 9))

View file

@ -0,0 +1,35 @@
=== tests/cases/compiler/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

View file

@ -0,0 +1,7 @@
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 };