From 4cb9e3cee220ed0f4cb04e0b95eba05b3ddc49bd Mon Sep 17 00:00:00 2001 From: dcode Date: Sun, 29 Mar 2020 12:49:20 +0200 Subject: [PATCH 1/3] Add a ScopeAnalyzer --- src/ast.ts | 232 +++++++++++++++++- src/compiler.ts | 2 + src/diagnosticMessages.generated.ts | 2 + src/diagnosticMessages.json | 1 + src/extra/ast.ts | 58 ++++- src/parser.ts | 8 +- tests/parser.js | 2 +- tests/parser/arrow-functions.ts.fixture.ts | 12 +- tests/parser/class.ts.fixture.ts | 4 +- tests/parser/constructor.ts.fixture.ts | 6 +- tests/parser/definite-assignment-assertion.ts | 6 +- ...efinite-assignment-assertion.ts.fixture.ts | 8 +- tests/parser/for.ts | 3 + tests/parser/for.ts.fixture.ts | 3 + .../parser/function-expression.ts.fixture.ts | 6 +- tests/parser/function.ts.fixture.ts | 4 +- tests/parser/parameter-order.ts.fixture.ts | 10 +- tests/parser/reserved-keywords.ts.fixture.ts | 6 +- tests/parser/scope.ts | 36 +++ tests/parser/scope.ts.fixture.ts | 39 +++ tests/parser/string-binding.ts.fixture.ts | 24 +- tests/parser/trailing-commas.ts.fixture.ts | 6 +- 22 files changed, 416 insertions(+), 62 deletions(-) create mode 100644 tests/parser/scope.ts create mode 100644 tests/parser/scope.ts.fixture.ts diff --git a/src/ast.ts b/src/ast.ts index 155b1603fc..91cf3715cb 100644 --- a/src/ast.ts +++ b/src/ast.ts @@ -32,6 +32,12 @@ import { CharCode } from "./util"; +import { + DiagnosticEmitter, + DiagnosticMessage, + DiagnosticCode +} from "./diagnostics"; + /** Indicates the kind of a node. */ export enum NodeKind { @@ -1797,6 +1803,8 @@ export abstract class VariableLikeDeclarationStatement extends DeclarationStatem export class BlockStatement extends Statement { /** Contained statements. */ statements: Statement[]; + /** Scoped names. */ + _scope: Map; } /** Represents a `break` statement. */ @@ -1908,6 +1916,8 @@ export class ForStatement extends Statement { incrementor: Expression | null; /** Statement being looped over. */ statement: Statement; + /** Scoped names. */ + _scope: Map; } /** Represents a `for..of` statement. */ @@ -1918,6 +1928,8 @@ export class ForOfStatement extends Statement { iterable: Expression; /** Statement being looped over. */ statement: Statement; + /** Scoped names. */ + _scope: Map; } /** Indicates the kind of an array function. */ @@ -1940,6 +1952,8 @@ export class FunctionDeclaration extends DeclarationStatement { body: Statement | null; /** Arrow function kind, if applicable. */ arrowKind: ArrowKind; + /** Scoped names. */ + _scope: Map; get isGeneric(): bool { var typeParameters = this.typeParameters; @@ -1948,7 +1962,7 @@ export class FunctionDeclaration extends DeclarationStatement { /** Clones this function declaration. */ clone(): FunctionDeclaration { - return Node.createFunctionDeclaration( + var clonedDeclaration = Node.createFunctionDeclaration( this.name, this.typeParameters, this.signature, @@ -1958,6 +1972,8 @@ export class FunctionDeclaration extends DeclarationStatement { this.arrowKind, this.range ); + clonedDeclaration._scope = this._scope; + return clonedDeclaration; } } @@ -2001,6 +2017,8 @@ export class MethodDeclaration extends FunctionDeclaration { export class NamespaceDeclaration extends DeclarationStatement { /** Array of namespace members. */ members: Statement[]; + /** Scoped names. */ + _scope: Map; } /** Represents a `return` statement. */ @@ -2023,6 +2041,8 @@ export class SwitchStatement extends Statement { condition: Expression; /** Contained cases. */ cases: SwitchCase[]; + /** Scoped names. */ + _scope: Map; } /** Represents a `throw` statement. */ @@ -2041,6 +2061,10 @@ export class TryStatement extends Statement { catchStatements: Statement[] | null; /** Statements being executed afterwards, if a `finally` clause is present. */ finallyStatements: Statement[] | null; + /** Scoped names in catch clause. */ + _catchScope: Map | null; + /** Scoped names in finally clause. */ + _finallyScope: Map | null; } /** Represents a `type` declaration. */ @@ -2102,3 +2126,209 @@ export function isTypeOmitted(type: TypeNode): bool { } return false; } + +/** Analyzes function body scopes. */ +export class ScopeAnalyzer extends DiagnosticEmitter { + + /** Function being evaluated. */ + declaration: FunctionDeclaration; + /** Function scope. */ + functionScope: Map = new Map(); + /** Block scope stack. */ + blockScopes: Map[] = []; + + /** Evaluates the specified function. */ + static analyze(declaration: FunctionDeclaration, diagnostics: DiagnosticMessage[]): void { + var instance = new ScopeAnalyzer(declaration, diagnostics); + var body = declaration.body; + if (body) instance.visit(body); + declaration._scope = instance.functionScope; + } + + constructor(declaration: FunctionDeclaration, diagnostics: DiagnosticMessage[]) { + super(diagnostics); + this.declaration = declaration; + var parameters = declaration.signature.parameters; + var functionScope = this.functionScope; + for (let i = 0, k = parameters.length; i < k; ++i) { + functionScope.set(parameters[i].name.text, null); + } + } + + visit(node: Node): void { + switch (node.kind) { + case NodeKind.BLOCK: { + let blockStatement = node; + let scope = new Map(); + let blockScopes = this.blockScopes; + blockScopes.push(scope); + let statements = blockStatement.statements; + for (let i = 0, k = statements.length; i < k; ++i) { + this.visit(statements[i]); + } + blockScopes.pop(); + blockStatement._scope = scope; + break; + } + case NodeKind.BREAK: break; + case NodeKind.CONTINUE: break; + case NodeKind.DO: { + this.visit((node).statement); + break; + } + case NodeKind.EMPTY: break; + case NodeKind.EXPRESSION: { + let expression = (node).expression; + if (expression.kind == NodeKind.FUNCTION) { + this.visit((expression).declaration); + } + break; + } + case NodeKind.FOR: { + let forStatement = node; + let scope = new Map(); + let blockScopes = this.blockScopes; + blockScopes.push(scope); + let initializer = forStatement.initializer; + if (initializer) this.visit(initializer); + this.visit(forStatement.statement); + blockScopes.pop(); + forStatement._scope = scope; + break; + } + case NodeKind.FOROF: { + let forOfStatement = node; + let scope = new Map(); + let blockScopes = this.blockScopes; + blockScopes.push(scope); + let variable = forOfStatement.variable; + if (variable) this.visit(variable); + this.visit(forOfStatement.statement); + blockScopes.pop(); + forOfStatement._scope = scope; + break; + } + case NodeKind.IF: { + let ifStatement = node; + this.visit(ifStatement.ifTrue); + let ifFalse = ifStatement.ifFalse; + if (ifFalse) this.visit(ifFalse); + break; + } + case NodeKind.RETURN: break; + case NodeKind.SWITCH: { + let switchStatement = node; + let scope = new Map(); + let blockScopes = this.blockScopes; + blockScopes.push(scope); + let cases = switchStatement.cases; + for (let i = 0, k = cases.length; i < k; ++i) { + this.visit(cases[i]); + } + blockScopes.pop(); + switchStatement._scope = scope; + break; + } + case NodeKind.THROW: break; + case NodeKind.TRY: { + let tryStatement = node; + let statements = tryStatement.statements; + for (let i = 0, k = statements.length; i < k; ++i) { + this.visit(statements[i]); + } + let catchStatements = tryStatement.catchStatements; + if (catchStatements) { + let scope = new Map(); + let catchVariable = tryStatement.catchVariable; + if (catchVariable) { + scope.set(catchVariable.text, null); + } + let blockScopes = this.blockScopes; + blockScopes.push(scope); + for (let i = 0, k = catchStatements.length; i < k; ++i) { + this.visit(catchStatements[i]); + } + blockScopes.pop(); + tryStatement._catchScope = scope; + } + let finallyStatements = tryStatement.finallyStatements; + if (finallyStatements) { + let scope = new Map(); + let blockScopes = this.blockScopes; + blockScopes.push(scope); + for (let i = 0, k = finallyStatements.length; i < k; ++i) { + this.visit(finallyStatements[i]); + } + blockScopes.pop(); + tryStatement._finallyScope = scope; + } + break; + } + case NodeKind.VARIABLE: { + let declarations = (node).declarations; + for (let i = 0, k = declarations.length; i < k; ++i) { + this.visit(declarations[i]); + } + break; + } + case NodeKind.VOID: break; + case NodeKind.WHILE: { + this.visit((node).statement); + break; + } + case NodeKind.FUNCTIONDECLARATION: { + let declaration = node; + // We are only interested in the name here, not the contents + let name = declaration.name; + if (name.text.length) { + let functionScope = this.functionScope; + if (functionScope.has(name.text)) { + this.error( + DiagnosticCode.Duplicate_identifier_0, + name.range, name.text + ); + } else { + functionScope.set(name.text, declaration); + } + } + break; + } + case NodeKind.VARIABLEDECLARATION: { + let declaration = node; + let name = declaration.name; + let isVar = !(declaration.flags & (CommonFlags.LET | CommonFlags.CONST)); + if (isVar) { + let functionScope = this.functionScope; + if (functionScope.has(name.text)) { + this.error( + DiagnosticCode.Duplicate_identifier_0, + name.range, name.text + ); + } else { + functionScope.set(name.text, declaration); + } + } else { + let blockScopes = this.blockScopes; + let blockScope = blockScopes[assert(blockScopes.length) - 1]; + if (blockScope.has(name.text)) { + this.error( + DiagnosticCode.Cannot_redeclare_block_scoped_variable_0, + name.range, name.text + ); + } else { + blockScope.set(name.text, declaration); + } + } + break; + } + case NodeKind.SWITCHCASE: { + let statements = (node).statements; + for (let i = 0, k = statements.length; i < k; ++i) { + this.visit(statements[i]); + } + break; + } + default: assert(false); + } + } +} diff --git a/src/compiler.ts b/src/compiler.ts index 495f670822..0688116394 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -166,6 +166,7 @@ import { NamedTypeNode, + ScopeAnalyzer, findDecorator, isTypeOmitted } from "./ast"; @@ -1273,6 +1274,7 @@ export class Compiler extends DiagnosticEmitter { // concrete function if (bodyNode) { + ScopeAnalyzer.analyze(instance.declaration, this.diagnostics); // must not be ambient if (instance.is(CommonFlags.AMBIENT)) { diff --git a/src/diagnosticMessages.generated.ts b/src/diagnosticMessages.generated.ts index 45988f1d65..1ff7b5d0de 100644 --- a/src/diagnosticMessages.generated.ts +++ b/src/diagnosticMessages.generated.ts @@ -130,6 +130,7 @@ export enum DiagnosticCode { Duplicate_function_implementation = 2393, Individual_declarations_in_merged_declaration_0_must_be_all_exported_or_all_local = 2395, A_namespace_declaration_cannot_be_located_prior_to_a_class_or_function_with_which_it_is_merged = 2434, + Cannot_redeclare_block_scoped_variable_0 = 2451, The_type_argument_for_type_parameter_0_cannot_be_inferred_from_the_usage_Consider_specifying_the_type_arguments_explicitly = 2453, Type_0_has_no_property_1 = 2460, The_0_operator_cannot_be_applied_to_type_1 = 2469, @@ -284,6 +285,7 @@ export function diagnosticCodeToString(code: DiagnosticCode): string { case 2393: return "Duplicate function implementation."; case 2395: return "Individual declarations in merged declaration '{0}' must be all exported or all local."; case 2434: return "A namespace declaration cannot be located prior to a class or function with which it is merged."; + case 2451: return "Cannot redeclare block-scoped variable '{0}'."; case 2453: return "The type argument for type parameter '{0}' cannot be inferred from the usage. Consider specifying the type arguments explicitly."; case 2460: return "Type '{0}' has no property '{1}'."; case 2469: return "The '{0}' operator cannot be applied to type '{1}'."; diff --git a/src/diagnosticMessages.json b/src/diagnosticMessages.json index 94ef96a447..dafb992a45 100644 --- a/src/diagnosticMessages.json +++ b/src/diagnosticMessages.json @@ -126,6 +126,7 @@ "Duplicate function implementation.": 2393, "Individual declarations in merged declaration '{0}' must be all exported or all local.": 2395, "A namespace declaration cannot be located prior to a class or function with which it is merged.": 2434, + "Cannot redeclare block-scoped variable '{0}'.": 2451, "The type argument for type parameter '{0}' cannot be inferred from the usage. Consider specifying the type arguments explicitly.": 2453, "Type '{0}' has no property '{1}'.": 2460, "The '{0}' operator cannot be applied to type '{1}'.": 2469, diff --git a/src/extra/ast.ts b/src/extra/ast.ts index ad37ee5269..8509c88286 100644 --- a/src/extra/ast.ts +++ b/src/extra/ast.ts @@ -86,7 +86,8 @@ import { SwitchCase, DeclarationStatement, - isTypeOmitted + isTypeOmitted, + ScopeAnalyzer } from "../ast"; import { @@ -102,12 +103,17 @@ import { CommonFlags } from "../common"; +import { + DiagnosticMessage, + DiagnosticEmitter +} from "../diagnostics"; + /** An AST builder. */ -export class ASTBuilder { +export class ASTBuilder extends DiagnosticEmitter { /** Rebuilds the textual source from the specified AST, as far as possible. */ - static build(node: Node): string { - var builder = new ASTBuilder(); + static build(node: Node, diagnostics: DiagnosticMessage[]): string { + var builder = new ASTBuilder(diagnostics); builder.visitNode(node); return builder.finish(); } @@ -115,6 +121,10 @@ export class ASTBuilder { private sb: string[] = []; private indentLevel: i32 = 0; + constructor(diagnostics: DiagnosticMessage[]) { + super(diagnostics); + } + visitNode(node: Node): void { switch (node.kind) { case NodeKind.SOURCE: { @@ -851,7 +861,9 @@ export class ASTBuilder { var statements = node.statements; var numStatements = statements.length; if (numStatements) { - sb.push("{\n"); + sb.push("{"); + this.serializeScope(node._scope); + sb.push("\n"); let indentLevel = ++this.indentLevel; for (let i = 0; i < numStatements; ++i) { indent(sb, indentLevel); @@ -860,7 +872,9 @@ export class ASTBuilder { indent(sb, --this.indentLevel); sb.push("}"); } else { - sb.push("{}"); + sb.push("{"); + this.serializeScope(node._scope); + sb.push("}"); } } @@ -1130,6 +1144,9 @@ export class ASTBuilder { sb.push(";"); } sb.push(") "); + if (this.serializeScope(node._scope)) { + sb.push(" "); + } this.visitNode(node.statement); } @@ -1140,6 +1157,9 @@ export class ASTBuilder { sb.push(" of "); this.visitNode(node.iterable); sb.push(") "); + if (this.serializeScope(node._scope)) { + sb.push(" "); + } this.visitNode(node.statement); } @@ -1206,6 +1226,7 @@ export class ASTBuilder { } } var body = node.body; + if (body != null) ScopeAnalyzer.analyze(node, this.diagnostics); var returnType = signature.returnType; if (node.arrowKind) { if (body) { @@ -1220,6 +1241,9 @@ export class ASTBuilder { } } sb.push(" => "); + if (this.serializeScope(node._scope)) { + sb.push(" "); + } this.visitNode(body); } else { assert(!isTypeOmitted(returnType)); @@ -1238,6 +1262,9 @@ export class ASTBuilder { } if (body) { sb.push(" "); + if (this.serializeScope(node._scope)) { + sb.push(" "); + } this.visitNode(body); } } @@ -1439,15 +1466,18 @@ export class ASTBuilder { var sb = this.sb; sb.push("switch ("); this.visitNode(node.condition); - sb.push(") {\n"); + sb.push(") "); + if (this.serializeScope(node._scope)) { + sb.push(" "); + } + sb.push("{\n"); var indentLevel = ++this.indentLevel; var cases = node.cases; for (let i = 0, k = cases.length; i < k; ++i) { indent(sb, indentLevel); this.visitSwitchCase(cases[i]); - sb.push("\n"); } - --this.indentLevel; + indent(sb, --this.indentLevel); sb.push("}"); } @@ -1488,7 +1518,7 @@ export class ASTBuilder { this.visitNodeAndTerminate(finallyStatements[i]); } } - indent(sb, indentLevel - 1); + indent(sb, --this.indentLevel); sb.push("}"); } @@ -1649,6 +1679,14 @@ export class ASTBuilder { } } + serializeScope(scope: Map): bool { + if (scope != null && scope.size > 0) { + this.sb.push("/* {" + Map_keys(scope).join(",") + "} */"); + return true; + } + return false; + } + finish(): string { var ret = this.sb.join(""); this.sb = []; diff --git a/src/parser.ts b/src/parser.ts index 0c0ac7d9c0..c67261e4ba 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -1439,7 +1439,7 @@ export class Parser extends DiagnosticEmitter { tn.range(signatureStart, tn.pos) ); - var body: Statement | null = null; + var body: BlockStatement | null = null; if (tn.skip(Token.OPENBRACE)) { if (flags & CommonFlags.AMBIENT) { this.error( @@ -1448,7 +1448,7 @@ export class Parser extends DiagnosticEmitter { ); // recoverable } - body = this.parseBlockStatement(tn, false); + body = this.parseBlockStatement(tn, /* topLevel */ false); if (!body) return null; } else if (!(flags & CommonFlags.AMBIENT)) { this.error( @@ -1554,7 +1554,7 @@ export class Parser extends DiagnosticEmitter { var body: Statement | null = null; if (arrowKind) { if (tn.skip(Token.OPENBRACE)) { - body = this.parseBlockStatement(tn, false); + body = this.parseBlockStatement(tn, /* topLevel */ false); } else { let bodyExpression = this.parseExpression(tn, Precedence.COMMA + 1); if (bodyExpression) body = Node.createExpressionStatement(bodyExpression); @@ -1567,7 +1567,7 @@ export class Parser extends DiagnosticEmitter { ); return null; } - body = this.parseBlockStatement(tn, false); + body = this.parseBlockStatement(tn, /* topLevel */ false); } if (!body) return null; diff --git a/tests/parser.js b/tests/parser.js index c17aa82862..4650b965e6 100644 --- a/tests/parser.js +++ b/tests/parser.js @@ -67,7 +67,7 @@ tests.forEach(filename => { var parser = program.parser; var sourceText = fs.readFileSync(basedir + "/" + filename, { encoding: "utf8" }).replace(/\r?\n/g, "\n"); parser.parseFile(sourceText, filename, true); - var serializedSourceText = ASTBuilder.build(program.sources[0]); + var serializedSourceText = ASTBuilder.build(program.sources[0], program.diagnostics); var actual = serializedSourceText + parser.diagnostics.map(diagnostic => "// " + diagnostic +"\n").join(""); var fixture = filename + ".fixture.ts"; diff --git a/tests/parser/arrow-functions.ts.fixture.ts b/tests/parser/arrow-functions.ts.fixture.ts index 3f049b2a6a..b1c565df03 100644 --- a/tests/parser/arrow-functions.ts.fixture.ts +++ b/tests/parser/arrow-functions.ts.fixture.ts @@ -1,8 +1,8 @@ -(x): i32 => x; -(x: i32) => x; -(x?) => x; -(x?, y?) => x; -(x?: i32) => x; -x => x; +(x): i32 => /* {x} */ x; +(x: i32) => /* {x} */ x; +(x?) => /* {x} */ x; +(x?, y?) => /* {x,y} */ x; +(x?: i32) => /* {x} */ x; +x => /* {x} */ x; (b ? x : y); (b ? f : g)(); diff --git a/tests/parser/class.ts.fixture.ts b/tests/parser/class.ts.fixture.ts index a76ddb7202..dad7bd4007 100644 --- a/tests/parser/class.ts.fixture.ts +++ b/tests/parser/class.ts.fixture.ts @@ -3,7 +3,7 @@ export class Valid { instanceFunction(): void {} static staticFunction(): void {} get instanceGetter(): i32 {} - static set staticSetter(v: i32) {} + static set staticSetter(v: i32) /* {v} */ {} instanceField: i32; static staticField: i32; static void: i32; @@ -12,7 +12,7 @@ export class Valid { export class Invalid { constructor() {} instanceFunction() {} - get instanceGetter(a: i32) {} + get instanceGetter(a: i32) /* {a} */ {} set instanceSetter() {} } // ERROR 1092: "Type parameters cannot appear on a constructor declaration." in class.ts:15:14 diff --git a/tests/parser/constructor.ts.fixture.ts b/tests/parser/constructor.ts.fixture.ts index 8b80d9e70f..6501b974f4 100644 --- a/tests/parser/constructor.ts.fixture.ts +++ b/tests/parser/constructor.ts.fixture.ts @@ -1,8 +1,8 @@ class MyClass { constructor() {} - constructor(a: i32) {} - constructor(a: i32, b: i32) {} + constructor(a: i32) /* {a} */ {} + constructor(a: i32, b: i32) /* {a,b} */ {} } class MyClassImplicit { - constructor(public a: i32, private readonly b?: i32 = 2, c?: i32 = 3) {} + constructor(public a: i32, private readonly b?: i32 = 2, c?: i32 = 3) /* {a,b,c} */ {} } diff --git a/tests/parser/definite-assignment-assertion.ts b/tests/parser/definite-assignment-assertion.ts index 05b3cd4f62..69e1298507 100644 --- a/tests/parser/definite-assignment-assertion.ts +++ b/tests/parser/definite-assignment-assertion.ts @@ -1,9 +1,9 @@ class C { x!: i32; - x!: i32 = 0; // invalid - static x!: i32; // invlaid + y!: i32 = 0; // invalid + static z!: i32; // invlaid } function f(): void { let x!: i32; - let x!: i32 = 0; // invalid + let y!: i32 = 0; // invalid } diff --git a/tests/parser/definite-assignment-assertion.ts.fixture.ts b/tests/parser/definite-assignment-assertion.ts.fixture.ts index 996e11f598..d99d32c52e 100644 --- a/tests/parser/definite-assignment-assertion.ts.fixture.ts +++ b/tests/parser/definite-assignment-assertion.ts.fixture.ts @@ -1,11 +1,11 @@ class C { x!: i32; - x!: i32 = 0; - static x!: i32; + y!: i32 = 0; + static z!: i32; } -function f(): void { +function f(): void {/* {x,y} */ let x!: i32; - let x!: i32 = 0; + let y!: i32 = 0; } // ERROR 1255: "A definite assignment assertion '!' is not permitted in this context." in definite-assignment-assertion.ts:3:3 // ERROR 1255: "A definite assignment assertion '!' is not permitted in this context." in definite-assignment-assertion.ts:4:3 diff --git a/tests/parser/for.ts b/tests/parser/for.ts index 3e10da956f..b1a33b60b1 100644 --- a/tests/parser/for.ts +++ b/tests/parser/for.ts @@ -1,6 +1,9 @@ for (var i: i32 = 0; i < 10; ++i) { ; } +for (let i = 0; i < 10; ++i) { + ; +} for (i = 0; i < 10; ++i) { ; } diff --git a/tests/parser/for.ts.fixture.ts b/tests/parser/for.ts.fixture.ts index 3e10da956f..b1a33b60b1 100644 --- a/tests/parser/for.ts.fixture.ts +++ b/tests/parser/for.ts.fixture.ts @@ -1,6 +1,9 @@ for (var i: i32 = 0; i < 10; ++i) { ; } +for (let i = 0; i < 10; ++i) { + ; +} for (i = 0; i < 10; ++i) { ; } diff --git a/tests/parser/function-expression.ts.fixture.ts b/tests/parser/function-expression.ts.fixture.ts index bbd37e9918..f2555e4712 100644 --- a/tests/parser/function-expression.ts.fixture.ts +++ b/tests/parser/function-expression.ts.fixture.ts @@ -4,13 +4,13 @@ var a = function(): void { var b = function someName(): void { ; }; -var c = function(a: i32, b: i32): i32 { +var c = function(a: i32, b: i32): i32 /* {a,b} */ { ; }; var d = (): void => { ; }; -var e = (a: i32, b: i32): i32 => { +var e = (a: i32, b: i32): i32 => /* {a,b} */ { ; }; -var f = (a: i32): i32 => a; +var f = (a: i32): i32 => /* {a} */ a; diff --git a/tests/parser/function.ts.fixture.ts b/tests/parser/function.ts.fixture.ts index 277b48a93f..fec43eff37 100644 --- a/tests/parser/function.ts.fixture.ts +++ b/tests/parser/function.ts.fixture.ts @@ -1,10 +1,10 @@ function simple(): void {} -function typeparams(a?: V | null = null): void {} +function typeparams(a?: V | null = null): void /* {a} */ {} @decorator() function withdecorator(): void {} function withthis(this: i32): i32 { return this; } -function withthisp(this: i32, a: f32, b: f64): i32 { +function withthisp(this: i32, a: f32, b: f64): i32 /* {a,b} */ { return this; } diff --git a/tests/parser/parameter-order.ts.fixture.ts b/tests/parser/parameter-order.ts.fixture.ts index 587b2069da..b2662aee2e 100644 --- a/tests/parser/parameter-order.ts.fixture.ts +++ b/tests/parser/parameter-order.ts.fixture.ts @@ -1,8 +1,8 @@ -function restValid(a: i32, ...b: Array): void {} -function optionalValid(a: i32, b?: i32): void {} -function restParameterMustBeLast(...a: Array, b: i32): void {} -function optionalCannotPrecedeRequired(a?: i32, b: i32): void {} -function optionalWithInitializerCannotPrecedeRequired(a?: i32 = 1, b: i32): void {} +function restValid(a: i32, ...b: Array): void /* {a,b} */ {} +function optionalValid(a: i32, b?: i32): void /* {a,b} */ {} +function restParameterMustBeLast(...a: Array, b: i32): void /* {a,b} */ {} +function optionalCannotPrecedeRequired(a?: i32, b: i32): void /* {a,b} */ {} +function optionalWithInitializerCannotPrecedeRequired(a?: i32 = 1, b: i32): void /* {a,b} */ {} // ERROR 1014: "A rest parameter must be last in a parameter list." in parameter-order.ts:5:37 // ERROR 1016: "A required parameter cannot follow an optional parameter." in parameter-order.ts:8:49 // ERROR 1016: "A required parameter cannot follow an optional parameter." in parameter-order.ts:11:67 diff --git a/tests/parser/reserved-keywords.ts.fixture.ts b/tests/parser/reserved-keywords.ts.fixture.ts index 0193884db8..4a13742dd4 100644 --- a/tests/parser/reserved-keywords.ts.fixture.ts +++ b/tests/parser/reserved-keywords.ts.fixture.ts @@ -1,7 +1,7 @@ -function alsoIdentifier(readonly: i32): void {} +function alsoIdentifier(readonly: i32): void /* {readonly} */ {} class AClass { - constructor(readonly: i32) {} - constructor(readonly readonly: i32) {} + constructor(readonly: i32) /* {readonly} */ {} + constructor(readonly readonly: i32) /* {readonly} */ {} } type type = i32; var type: i32; diff --git a/tests/parser/scope.ts b/tests/parser/scope.ts new file mode 100644 index 0000000000..ad4f38f721 --- /dev/null +++ b/tests/parser/scope.ts @@ -0,0 +1,36 @@ +function foo(a: i32): void { + var b = 0; + let c = 0; + { + var d = 0; + let e = 0; + } + for (var f = 0; f < 10; ++f) { + var g = 0; + let h = 0; + } + for (let i = 0; i < 10; ++i) { + var j = 0; + let k = 0; + } + for (var l of something) { + var m = 0; + let n = 0; + } + for (let o of something) { + var p = 0; + let q = 0; + } + switch (something) { + case 0: var r = 0; + case 1: let s = 0; + case 2: { + var t = 0; + let u = 0; + } + } + function v(): void { + var w = 0; + let x = 0; + } +} diff --git a/tests/parser/scope.ts.fixture.ts b/tests/parser/scope.ts.fixture.ts new file mode 100644 index 0000000000..dc33d5c04a --- /dev/null +++ b/tests/parser/scope.ts.fixture.ts @@ -0,0 +1,39 @@ +function foo(a: i32): void /* {a,b,d,f,g,j,l,m,p,r,t,v} */ {/* {c} */ + var b = 0; + let c = 0; + {/* {e} */ + var d = 0; + let e = 0; + } + for (var f = 0; f < 10; ++f) {/* {h} */ + var g = 0; + let h = 0; + } + for (let i = 0; i < 10; ++i) /* {i} */ {/* {k} */ + var j = 0; + let k = 0; + } + for (var l of something) {/* {n} */ + var m = 0; + let n = 0; + } + for (let o of something) /* {o} */ {/* {q} */ + var p = 0; + let q = 0; + } + switch (something) /* {s} */ { + case 0: + var r = 0; + case 1: + let s = 0; + case 2: + {/* {u} */ + var t = 0; + let u = 0; + } + } + function v(): void /* {w} */ {/* {x} */ + var w = 0; + let x = 0; + }; +} diff --git a/tests/parser/string-binding.ts.fixture.ts b/tests/parser/string-binding.ts.fixture.ts index 92f9e21a5c..fa0641c345 100644 --- a/tests/parser/string-binding.ts.fixture.ts +++ b/tests/parser/string-binding.ts.fixture.ts @@ -1,48 +1,48 @@ @binding(BindingCall.NEW, [BindingType.STRING], BindingType.OBJECT_HANDLE) export class ExternalString { @binding(BindingCall.FUNCTION, [BindingType.U32, BindingType.U32], BindingType.OBJECT_HANDLE) - static fromCharCode(char: u16, schar?: u16 = -1): String { + static fromCharCode(char: u16, schar?: u16 = -1): String /* {char,schar} */ { return unreachable(); } @binding(BindingCall.FUNCTION, [BindingType.U32], BindingType.OBJECT_HANDLE) - static fromCodePoint(codepoint: u32): String { + static fromCodePoint(codepoint: u32): String /* {codepoint} */ { return unreachable(); } @binding(BindingCall.THIS, [BindingType.U32], BindingType.OBJECT_HANDLE) - charAt(index: u32): String { + charAt(index: u32): String /* {index} */ { return unreachable(); } @binding(BindingCall.THIS, [BindingType.U32], BindingType.PASS_THRU) - charCodeAt(index: u32): u16 { + charCodeAt(index: u32): u16 /* {index} */ { return unreachable(); } @binding(BindingCall.THIS, [BindingType.U32], BindingType.PASS_THRU) - codePointAt(index: u32): u32 { + codePointAt(index: u32): u32 /* {index} */ { return unreachable(); } @binding(BindingCall.THIS, [BindingType.OBJECT_HANDLE], BindingType.OBJECT_HANDLE) @operator("+") - concat(other: String): String { + concat(other: String): String /* {other} */ { return unreachable(); } @binding(BindingCall.THIS, [BindingType.OBJECT_HANDLE], BindingType.PASS_THRU) - endsWith(other: String): bool { + endsWith(other: String): bool /* {other} */ { return unreachable(); } @binding(BindingCall.THIS, [BindingType.OBJECT_HANDLE], BindingType.PASS_THRU) - indexOf(other: String): i32 { + indexOf(other: String): i32 /* {other} */ { return unreachable(); } @binding(BindingCall.THIS, [BindingType.OBJECT_HANDLE], BindingType.PASS_THRU) - startsWith(other: String): bool { + startsWith(other: String): bool /* {other} */ { return unreachable(); } @binding(BindingCall.THIS, [BindingType.U32, BindingType.U32], BindingType.OBJECT_HANDLE) - substr(start: i32, length: i32): String { + substr(start: i32, length: i32): String /* {start,length} */ { return unreachable(); } @binding(BindingCall.THIS, [BindingType.U32, BindingType.U32], BindingType.OBJECT_HANDLE) - substring(start: i32, end: i32): String { + substring(start: i32, end: i32): String /* {start,end} */ { return unreachable(); } @binding(BindingCall.THIS, [], BindingType.OBJECT_HANDLE) @@ -59,7 +59,7 @@ export class ExternalString { } @binding(BindingCall.THIS, [BindingType.OBJECT_HANDLE], BindingType.PASS_THRU) @operator("==") - equals(other: String): bool { + equals(other: String): bool /* {other} */ { return unreachable(); } } diff --git a/tests/parser/trailing-commas.ts.fixture.ts b/tests/parser/trailing-commas.ts.fixture.ts index 3433f6e1c4..11439b08af 100644 --- a/tests/parser/trailing-commas.ts.fixture.ts +++ b/tests/parser/trailing-commas.ts.fixture.ts @@ -6,11 +6,11 @@ enum Foo { A, B } -function add(x: i32, y: i32): i32 { +function add(x: i32, y: i32): i32 /* {x,y} */ { return x + y; } -function parameterized(a: A, b: B): void {} -export function compute(): i32 { +function parameterized(a: A, b: B): void /* {a,b} */ {} +export function compute(): i32 {/* {arr} */ const arr: Array = [1, 2]; parameterized(0, 0); return add(1, 2); From ed6f5da21ed5577425cc6616788d058934ccc95f Mon Sep 17 00:00:00 2001 From: dcode Date: Mon, 30 Mar 2020 00:31:07 +0200 Subject: [PATCH 2/3] edge cases, serialization --- src/ast.ts | 131 +++++++++++++++------------- src/diagnosticMessages.generated.ts | 2 - src/diagnosticMessages.json | 1 - src/extra/ast.ts | 6 +- tests/parser/scope.ts | 22 +++++ tests/parser/scope.ts.fixture.ts | 32 +++++++ 6 files changed, 127 insertions(+), 67 deletions(-) diff --git a/src/ast.ts b/src/ast.ts index 91cf3715cb..38709efebe 100644 --- a/src/ast.ts +++ b/src/ast.ts @@ -1804,7 +1804,7 @@ export class BlockStatement extends Statement { /** Contained statements. */ statements: Statement[]; /** Scoped names. */ - _scope: Map; + _scope: string[]; } /** Represents a `break` statement. */ @@ -1917,7 +1917,7 @@ export class ForStatement extends Statement { /** Statement being looped over. */ statement: Statement; /** Scoped names. */ - _scope: Map; + _scope: string[]; } /** Represents a `for..of` statement. */ @@ -1929,7 +1929,7 @@ export class ForOfStatement extends Statement { /** Statement being looped over. */ statement: Statement; /** Scoped names. */ - _scope: Map; + _scope: string[]; } /** Indicates the kind of an array function. */ @@ -1953,7 +1953,7 @@ export class FunctionDeclaration extends DeclarationStatement { /** Arrow function kind, if applicable. */ arrowKind: ArrowKind; /** Scoped names. */ - _scope: Map; + _scope: string[]; get isGeneric(): bool { var typeParameters = this.typeParameters; @@ -2018,7 +2018,7 @@ export class NamespaceDeclaration extends DeclarationStatement { /** Array of namespace members. */ members: Statement[]; /** Scoped names. */ - _scope: Map; + _scope: string[]; } /** Represents a `return` statement. */ @@ -2042,7 +2042,7 @@ export class SwitchStatement extends Statement { /** Contained cases. */ cases: SwitchCase[]; /** Scoped names. */ - _scope: Map; + _scope: string[]; } /** Represents a `throw` statement. */ @@ -2062,9 +2062,9 @@ export class TryStatement extends Statement { /** Statements being executed afterwards, if a `finally` clause is present. */ finallyStatements: Statement[] | null; /** Scoped names in catch clause. */ - _catchScope: Map | null; + _catchScope: string[]; /** Scoped names in finally clause. */ - _finallyScope: Map | null; + _finallyScope: string[]; } /** Represents a `type` declaration. */ @@ -2133,16 +2133,17 @@ export class ScopeAnalyzer extends DiagnosticEmitter { /** Function being evaluated. */ declaration: FunctionDeclaration; /** Function scope. */ - functionScope: Map = new Map(); + functionScope: Map = new Map(); /** Block scope stack. */ - blockScopes: Map[] = []; + blockScopes: Map[] = []; /** Evaluates the specified function. */ static analyze(declaration: FunctionDeclaration, diagnostics: DiagnosticMessage[]): void { var instance = new ScopeAnalyzer(declaration, diagnostics); var body = declaration.body; if (body) instance.visit(body); - declaration._scope = instance.functionScope; + declaration._scope = Map_keys(instance.functionScope); + assert(instance.blockScopes.length == 0); } constructor(declaration: FunctionDeclaration, diagnostics: DiagnosticMessage[]) { @@ -2151,7 +2152,8 @@ export class ScopeAnalyzer extends DiagnosticEmitter { var parameters = declaration.signature.parameters; var functionScope = this.functionScope; for (let i = 0, k = parameters.length; i < k; ++i) { - functionScope.set(parameters[i].name.text, null); + let identifier = parameters[i].name; + functionScope.set(identifier.text, identifier); } } @@ -2159,7 +2161,7 @@ export class ScopeAnalyzer extends DiagnosticEmitter { switch (node.kind) { case NodeKind.BLOCK: { let blockStatement = node; - let scope = new Map(); + let scope = new Map(); let blockScopes = this.blockScopes; blockScopes.push(scope); let statements = blockStatement.statements; @@ -2167,7 +2169,7 @@ export class ScopeAnalyzer extends DiagnosticEmitter { this.visit(statements[i]); } blockScopes.pop(); - blockStatement._scope = scope; + blockStatement._scope = Map_keys(scope); break; } case NodeKind.BREAK: break; @@ -2186,26 +2188,26 @@ export class ScopeAnalyzer extends DiagnosticEmitter { } case NodeKind.FOR: { let forStatement = node; - let scope = new Map(); + let scope = new Map(); let blockScopes = this.blockScopes; blockScopes.push(scope); let initializer = forStatement.initializer; if (initializer) this.visit(initializer); this.visit(forStatement.statement); blockScopes.pop(); - forStatement._scope = scope; + forStatement._scope = Map_keys(scope); break; } case NodeKind.FOROF: { let forOfStatement = node; - let scope = new Map(); + let scope = new Map(); let blockScopes = this.blockScopes; blockScopes.push(scope); let variable = forOfStatement.variable; if (variable) this.visit(variable); this.visit(forOfStatement.statement); blockScopes.pop(); - forOfStatement._scope = scope; + forOfStatement._scope = Map_keys(scope); break; } case NodeKind.IF: { @@ -2218,7 +2220,7 @@ export class ScopeAnalyzer extends DiagnosticEmitter { case NodeKind.RETURN: break; case NodeKind.SWITCH: { let switchStatement = node; - let scope = new Map(); + let scope = new Map(); let blockScopes = this.blockScopes; blockScopes.push(scope); let cases = switchStatement.cases; @@ -2226,7 +2228,7 @@ export class ScopeAnalyzer extends DiagnosticEmitter { this.visit(cases[i]); } blockScopes.pop(); - switchStatement._scope = scope; + switchStatement._scope = Map_keys(scope); break; } case NodeKind.THROW: break; @@ -2238,10 +2240,10 @@ export class ScopeAnalyzer extends DiagnosticEmitter { } let catchStatements = tryStatement.catchStatements; if (catchStatements) { - let scope = new Map(); + let scope = new Map(); let catchVariable = tryStatement.catchVariable; if (catchVariable) { - scope.set(catchVariable.text, null); + scope.set(catchVariable.text, catchVariable); } let blockScopes = this.blockScopes; blockScopes.push(scope); @@ -2249,18 +2251,18 @@ export class ScopeAnalyzer extends DiagnosticEmitter { this.visit(catchStatements[i]); } blockScopes.pop(); - tryStatement._catchScope = scope; + tryStatement._catchScope = Map_keys(scope); } let finallyStatements = tryStatement.finallyStatements; if (finallyStatements) { - let scope = new Map(); + let scope = new Map(); let blockScopes = this.blockScopes; blockScopes.push(scope); for (let i = 0, k = finallyStatements.length; i < k; ++i) { this.visit(finallyStatements[i]); } blockScopes.pop(); - tryStatement._finallyScope = scope; + tryStatement._finallyScope = Map_keys(scope); } break; } @@ -2279,46 +2281,13 @@ export class ScopeAnalyzer extends DiagnosticEmitter { case NodeKind.FUNCTIONDECLARATION: { let declaration = node; // We are only interested in the name here, not the contents - let name = declaration.name; - if (name.text.length) { - let functionScope = this.functionScope; - if (functionScope.has(name.text)) { - this.error( - DiagnosticCode.Duplicate_identifier_0, - name.range, name.text - ); - } else { - functionScope.set(name.text, declaration); - } - } + let identifier = declaration.name; + if (identifier.text.length) this.check(identifier, true); break; } case NodeKind.VARIABLEDECLARATION: { let declaration = node; - let name = declaration.name; - let isVar = !(declaration.flags & (CommonFlags.LET | CommonFlags.CONST)); - if (isVar) { - let functionScope = this.functionScope; - if (functionScope.has(name.text)) { - this.error( - DiagnosticCode.Duplicate_identifier_0, - name.range, name.text - ); - } else { - functionScope.set(name.text, declaration); - } - } else { - let blockScopes = this.blockScopes; - let blockScope = blockScopes[assert(blockScopes.length) - 1]; - if (blockScope.has(name.text)) { - this.error( - DiagnosticCode.Cannot_redeclare_block_scoped_variable_0, - name.range, name.text - ); - } else { - blockScope.set(name.text, declaration); - } - } + this.check(declaration.name, !(declaration.flags & (CommonFlags.LET | CommonFlags.CONST))); break; } case NodeKind.SWITCHCASE: { @@ -2331,4 +2300,44 @@ export class ScopeAnalyzer extends DiagnosticEmitter { default: assert(false); } } + + check(identifier: IdentifierExpression, isVar: bool): void { + var name = identifier.text; + var blockScopes = this.blockScopes; + var blockDepth = assert(blockScopes.length); + var blockScope = blockScopes[blockDepth - 1]; + if (blockScope.has(name)) { + let existing = assert(blockScope.get(name)); + this.errorRelated( + DiagnosticCode.Duplicate_identifier_0, + identifier.range, existing.range, name + ); + } else if (isVar || blockDepth == 1) { + let functionScope = this.functionScope; + if (functionScope.has(name)) { + let existing = assert(functionScope.get(name)); + this.errorRelated( + DiagnosticCode.Duplicate_identifier_0, + identifier.range, existing.range, name + ); + } else { + if (isVar) { + let firstScope = blockScopes[0]; + if (firstScope.has(name)) { + let existing = assert(firstScope.get(name)); + this.errorRelated( + DiagnosticCode.Duplicate_identifier_0, + identifier.range, existing.range, name + ); + } else { + functionScope.set(identifier.text, identifier); + } + } else { + blockScope.set(identifier.text, identifier); + } + } + } else { + blockScope.set(identifier.text, identifier); + } + } } diff --git a/src/diagnosticMessages.generated.ts b/src/diagnosticMessages.generated.ts index 1ff7b5d0de..45988f1d65 100644 --- a/src/diagnosticMessages.generated.ts +++ b/src/diagnosticMessages.generated.ts @@ -130,7 +130,6 @@ export enum DiagnosticCode { Duplicate_function_implementation = 2393, Individual_declarations_in_merged_declaration_0_must_be_all_exported_or_all_local = 2395, A_namespace_declaration_cannot_be_located_prior_to_a_class_or_function_with_which_it_is_merged = 2434, - Cannot_redeclare_block_scoped_variable_0 = 2451, The_type_argument_for_type_parameter_0_cannot_be_inferred_from_the_usage_Consider_specifying_the_type_arguments_explicitly = 2453, Type_0_has_no_property_1 = 2460, The_0_operator_cannot_be_applied_to_type_1 = 2469, @@ -285,7 +284,6 @@ export function diagnosticCodeToString(code: DiagnosticCode): string { case 2393: return "Duplicate function implementation."; case 2395: return "Individual declarations in merged declaration '{0}' must be all exported or all local."; case 2434: return "A namespace declaration cannot be located prior to a class or function with which it is merged."; - case 2451: return "Cannot redeclare block-scoped variable '{0}'."; case 2453: return "The type argument for type parameter '{0}' cannot be inferred from the usage. Consider specifying the type arguments explicitly."; case 2460: return "Type '{0}' has no property '{1}'."; case 2469: return "The '{0}' operator cannot be applied to type '{1}'."; diff --git a/src/diagnosticMessages.json b/src/diagnosticMessages.json index dafb992a45..94ef96a447 100644 --- a/src/diagnosticMessages.json +++ b/src/diagnosticMessages.json @@ -126,7 +126,6 @@ "Duplicate function implementation.": 2393, "Individual declarations in merged declaration '{0}' must be all exported or all local.": 2395, "A namespace declaration cannot be located prior to a class or function with which it is merged.": 2434, - "Cannot redeclare block-scoped variable '{0}'.": 2451, "The type argument for type parameter '{0}' cannot be inferred from the usage. Consider specifying the type arguments explicitly.": 2453, "Type '{0}' has no property '{1}'.": 2460, "The '{0}' operator cannot be applied to type '{1}'.": 2469, diff --git a/src/extra/ast.ts b/src/extra/ast.ts index 8509c88286..1a9a42cf80 100644 --- a/src/extra/ast.ts +++ b/src/extra/ast.ts @@ -1679,9 +1679,9 @@ export class ASTBuilder extends DiagnosticEmitter { } } - serializeScope(scope: Map): bool { - if (scope != null && scope.size > 0) { - this.sb.push("/* {" + Map_keys(scope).join(",") + "} */"); + serializeScope(scope: string[] | null): bool { + if (scope != null && scope.length > 0) { + this.sb.push("/* {" + scope.join(",") + "} */"); return true; } return false; diff --git a/tests/parser/scope.ts b/tests/parser/scope.ts index ad4f38f721..1ace8eeb87 100644 --- a/tests/parser/scope.ts +++ b/tests/parser/scope.ts @@ -33,4 +33,26 @@ function foo(a: i32): void { var w = 0; let x = 0; } + + // Errors + var a = 1; + let a = 1; + { + var a = 1; + } + var b = 1; + let b = 1; + { + var b = 1; + } + var c = 1; + let c = 1; + { + var c = 1; + } + var v = 1; + let v = 1; + { + var v = 1; + } } diff --git a/tests/parser/scope.ts.fixture.ts b/tests/parser/scope.ts.fixture.ts index dc33d5c04a..8fc72d6a89 100644 --- a/tests/parser/scope.ts.fixture.ts +++ b/tests/parser/scope.ts.fixture.ts @@ -36,4 +36,36 @@ function foo(a: i32): void /* {a,b,d,f,g,j,l,m,p,r,t,v} */ {/* {c} */ var w = 0; let x = 0; }; + var a = 1; + let a = 1; + { + var a = 1; + } + var b = 1; + let b = 1; + { + var b = 1; + } + var c = 1; + let c = 1; + { + var c = 1; + } + var v = 1; + let v = 1; + { + var v = 1; + } } +// ERROR 2300: "Duplicate identifier 'a'." in scope.ts:38:7 +// ERROR 2300: "Duplicate identifier 'a'." in scope.ts:39:7 +// ERROR 2300: "Duplicate identifier 'a'." in scope.ts:41:9 +// ERROR 2300: "Duplicate identifier 'b'." in scope.ts:43:7 +// ERROR 2300: "Duplicate identifier 'b'." in scope.ts:44:7 +// ERROR 2300: "Duplicate identifier 'b'." in scope.ts:46:9 +// ERROR 2300: "Duplicate identifier 'c'." in scope.ts:48:7 +// ERROR 2300: "Duplicate identifier 'c'." in scope.ts:49:7 +// ERROR 2300: "Duplicate identifier 'c'." in scope.ts:51:9 +// ERROR 2300: "Duplicate identifier 'v'." in scope.ts:53:7 +// ERROR 2300: "Duplicate identifier 'v'." in scope.ts:54:7 +// ERROR 2300: "Duplicate identifier 'v'." in scope.ts:56:9 From 6e1e63642afbcf7381010fab8ee6293e17cb86fa Mon Sep 17 00:00:00 2001 From: dcode Date: Mon, 30 Mar 2020 03:20:06 +0200 Subject: [PATCH 3/3] full scopes --- src/ast.ts | 65 ++++++++++--------- tests/parser/class.ts.fixture.ts | 4 +- tests/parser/constructor.ts.fixture.ts | 6 +- .../parser/function-expression.ts.fixture.ts | 4 +- tests/parser/function.ts.fixture.ts | 4 +- tests/parser/parameter-order.ts.fixture.ts | 10 +-- tests/parser/reserved-keywords.ts.fixture.ts | 6 +- tests/parser/scope.ts.fixture.ts | 26 ++++---- tests/parser/string-binding.ts.fixture.ts | 24 +++---- tests/parser/trailing-commas.ts.fixture.ts | 4 +- 10 files changed, 80 insertions(+), 73 deletions(-) diff --git a/src/ast.ts b/src/ast.ts index 38709efebe..1a922b7528 100644 --- a/src/ast.ts +++ b/src/ast.ts @@ -2157,19 +2157,40 @@ export class ScopeAnalyzer extends DiagnosticEmitter { } } + pushScope(scope: Map = new Map()): void { + this.blockScopes.push(scope); + } + + popScope(): string[] { + var names = new Set(); + var functionScope = this.functionScope; + // TODO: for(let key of functionScope.keys()) { + for (let _keys = Map_keys(functionScope), i = 0, k = _keys.length; i < k; ++i) { + names.add(_keys[i]); + } + var blockScopes = this.blockScopes; + var blockDepth = assert(blockScopes.length); + for (let i = 0; i < blockDepth; ++i) { + let blockScope = blockScopes[i]; + // TODO: for (let key of blockScope.keys()) { + for (let _keys = Map_keys(blockScope), j = 0, k = _keys.length; j < k; ++j) { + names.add(_keys[j]); + } + } + blockScopes.pop(); + return Set_values(names); + } + visit(node: Node): void { switch (node.kind) { case NodeKind.BLOCK: { let blockStatement = node; - let scope = new Map(); - let blockScopes = this.blockScopes; - blockScopes.push(scope); + this.pushScope(); let statements = blockStatement.statements; for (let i = 0, k = statements.length; i < k; ++i) { this.visit(statements[i]); } - blockScopes.pop(); - blockStatement._scope = Map_keys(scope); + blockStatement._scope = this.popScope(); break; } case NodeKind.BREAK: break; @@ -2188,26 +2209,20 @@ export class ScopeAnalyzer extends DiagnosticEmitter { } case NodeKind.FOR: { let forStatement = node; - let scope = new Map(); - let blockScopes = this.blockScopes; - blockScopes.push(scope); + this.pushScope(); let initializer = forStatement.initializer; if (initializer) this.visit(initializer); this.visit(forStatement.statement); - blockScopes.pop(); - forStatement._scope = Map_keys(scope); + forStatement._scope = this.popScope(); break; } case NodeKind.FOROF: { let forOfStatement = node; - let scope = new Map(); - let blockScopes = this.blockScopes; - blockScopes.push(scope); + this.pushScope(); let variable = forOfStatement.variable; if (variable) this.visit(variable); this.visit(forOfStatement.statement); - blockScopes.pop(); - forOfStatement._scope = Map_keys(scope); + forOfStatement._scope = this.popScope(); break; } case NodeKind.IF: { @@ -2220,15 +2235,12 @@ export class ScopeAnalyzer extends DiagnosticEmitter { case NodeKind.RETURN: break; case NodeKind.SWITCH: { let switchStatement = node; - let scope = new Map(); - let blockScopes = this.blockScopes; - blockScopes.push(scope); + this.pushScope(); let cases = switchStatement.cases; for (let i = 0, k = cases.length; i < k; ++i) { this.visit(cases[i]); } - blockScopes.pop(); - switchStatement._scope = Map_keys(scope); + switchStatement._scope = this.popScope(); break; } case NodeKind.THROW: break; @@ -2241,28 +2253,23 @@ export class ScopeAnalyzer extends DiagnosticEmitter { let catchStatements = tryStatement.catchStatements; if (catchStatements) { let scope = new Map(); + this.pushScope(scope); let catchVariable = tryStatement.catchVariable; if (catchVariable) { scope.set(catchVariable.text, catchVariable); } - let blockScopes = this.blockScopes; - blockScopes.push(scope); for (let i = 0, k = catchStatements.length; i < k; ++i) { this.visit(catchStatements[i]); } - blockScopes.pop(); - tryStatement._catchScope = Map_keys(scope); + tryStatement._catchScope = this.popScope(); } let finallyStatements = tryStatement.finallyStatements; if (finallyStatements) { - let scope = new Map(); - let blockScopes = this.blockScopes; - blockScopes.push(scope); + this.pushScope(); for (let i = 0, k = finallyStatements.length; i < k; ++i) { this.visit(finallyStatements[i]); } - blockScopes.pop(); - tryStatement._finallyScope = Map_keys(scope); + tryStatement._finallyScope = this.popScope(); } break; } diff --git a/tests/parser/class.ts.fixture.ts b/tests/parser/class.ts.fixture.ts index dad7bd4007..7817a5c9c0 100644 --- a/tests/parser/class.ts.fixture.ts +++ b/tests/parser/class.ts.fixture.ts @@ -3,7 +3,7 @@ export class Valid { instanceFunction(): void {} static staticFunction(): void {} get instanceGetter(): i32 {} - static set staticSetter(v: i32) /* {v} */ {} + static set staticSetter(v: i32) /* {v} */ {/* {v} */} instanceField: i32; static staticField: i32; static void: i32; @@ -12,7 +12,7 @@ export class Valid { export class Invalid { constructor() {} instanceFunction() {} - get instanceGetter(a: i32) /* {a} */ {} + get instanceGetter(a: i32) /* {a} */ {/* {a} */} set instanceSetter() {} } // ERROR 1092: "Type parameters cannot appear on a constructor declaration." in class.ts:15:14 diff --git a/tests/parser/constructor.ts.fixture.ts b/tests/parser/constructor.ts.fixture.ts index 6501b974f4..7b91a23db6 100644 --- a/tests/parser/constructor.ts.fixture.ts +++ b/tests/parser/constructor.ts.fixture.ts @@ -1,8 +1,8 @@ class MyClass { constructor() {} - constructor(a: i32) /* {a} */ {} - constructor(a: i32, b: i32) /* {a,b} */ {} + constructor(a: i32) /* {a} */ {/* {a} */} + constructor(a: i32, b: i32) /* {a,b} */ {/* {a,b} */} } class MyClassImplicit { - constructor(public a: i32, private readonly b?: i32 = 2, c?: i32 = 3) /* {a,b,c} */ {} + constructor(public a: i32, private readonly b?: i32 = 2, c?: i32 = 3) /* {a,b,c} */ {/* {a,b,c} */} } diff --git a/tests/parser/function-expression.ts.fixture.ts b/tests/parser/function-expression.ts.fixture.ts index f2555e4712..6efdbf32d3 100644 --- a/tests/parser/function-expression.ts.fixture.ts +++ b/tests/parser/function-expression.ts.fixture.ts @@ -4,13 +4,13 @@ var a = function(): void { var b = function someName(): void { ; }; -var c = function(a: i32, b: i32): i32 /* {a,b} */ { +var c = function(a: i32, b: i32): i32 /* {a,b} */ {/* {a,b} */ ; }; var d = (): void => { ; }; -var e = (a: i32, b: i32): i32 => /* {a,b} */ { +var e = (a: i32, b: i32): i32 => /* {a,b} */ {/* {a,b} */ ; }; var f = (a: i32): i32 => /* {a} */ a; diff --git a/tests/parser/function.ts.fixture.ts b/tests/parser/function.ts.fixture.ts index fec43eff37..f09bc0b75f 100644 --- a/tests/parser/function.ts.fixture.ts +++ b/tests/parser/function.ts.fixture.ts @@ -1,10 +1,10 @@ function simple(): void {} -function typeparams(a?: V | null = null): void /* {a} */ {} +function typeparams(a?: V | null = null): void /* {a} */ {/* {a} */} @decorator() function withdecorator(): void {} function withthis(this: i32): i32 { return this; } -function withthisp(this: i32, a: f32, b: f64): i32 /* {a,b} */ { +function withthisp(this: i32, a: f32, b: f64): i32 /* {a,b} */ {/* {a,b} */ return this; } diff --git a/tests/parser/parameter-order.ts.fixture.ts b/tests/parser/parameter-order.ts.fixture.ts index b2662aee2e..352441d753 100644 --- a/tests/parser/parameter-order.ts.fixture.ts +++ b/tests/parser/parameter-order.ts.fixture.ts @@ -1,8 +1,8 @@ -function restValid(a: i32, ...b: Array): void /* {a,b} */ {} -function optionalValid(a: i32, b?: i32): void /* {a,b} */ {} -function restParameterMustBeLast(...a: Array, b: i32): void /* {a,b} */ {} -function optionalCannotPrecedeRequired(a?: i32, b: i32): void /* {a,b} */ {} -function optionalWithInitializerCannotPrecedeRequired(a?: i32 = 1, b: i32): void /* {a,b} */ {} +function restValid(a: i32, ...b: Array): void /* {a,b} */ {/* {a,b} */} +function optionalValid(a: i32, b?: i32): void /* {a,b} */ {/* {a,b} */} +function restParameterMustBeLast(...a: Array, b: i32): void /* {a,b} */ {/* {a,b} */} +function optionalCannotPrecedeRequired(a?: i32, b: i32): void /* {a,b} */ {/* {a,b} */} +function optionalWithInitializerCannotPrecedeRequired(a?: i32 = 1, b: i32): void /* {a,b} */ {/* {a,b} */} // ERROR 1014: "A rest parameter must be last in a parameter list." in parameter-order.ts:5:37 // ERROR 1016: "A required parameter cannot follow an optional parameter." in parameter-order.ts:8:49 // ERROR 1016: "A required parameter cannot follow an optional parameter." in parameter-order.ts:11:67 diff --git a/tests/parser/reserved-keywords.ts.fixture.ts b/tests/parser/reserved-keywords.ts.fixture.ts index 4a13742dd4..f77e63874d 100644 --- a/tests/parser/reserved-keywords.ts.fixture.ts +++ b/tests/parser/reserved-keywords.ts.fixture.ts @@ -1,7 +1,7 @@ -function alsoIdentifier(readonly: i32): void /* {readonly} */ {} +function alsoIdentifier(readonly: i32): void /* {readonly} */ {/* {readonly} */} class AClass { - constructor(readonly: i32) /* {readonly} */ {} - constructor(readonly readonly: i32) /* {readonly} */ {} + constructor(readonly: i32) /* {readonly} */ {/* {readonly} */} + constructor(readonly readonly: i32) /* {readonly} */ {/* {readonly} */} } type type = i32; var type: i32; diff --git a/tests/parser/scope.ts.fixture.ts b/tests/parser/scope.ts.fixture.ts index 8fc72d6a89..3b4608abe6 100644 --- a/tests/parser/scope.ts.fixture.ts +++ b/tests/parser/scope.ts.fixture.ts @@ -1,59 +1,59 @@ -function foo(a: i32): void /* {a,b,d,f,g,j,l,m,p,r,t,v} */ {/* {c} */ +function foo(a: i32): void /* {a,b,d,f,g,j,l,m,p,r,t,v} */ {/* {a,b,d,f,g,j,l,m,p,r,t,v,c} */ var b = 0; let c = 0; - {/* {e} */ + {/* {a,b,d,c,e} */ var d = 0; let e = 0; } - for (var f = 0; f < 10; ++f) {/* {h} */ + for (var f = 0; f < 10; ++f) /* {a,b,d,f,g,c} */ {/* {a,b,d,f,g,c,h} */ var g = 0; let h = 0; } - for (let i = 0; i < 10; ++i) /* {i} */ {/* {k} */ + for (let i = 0; i < 10; ++i) /* {a,b,d,f,g,j,c,i} */ {/* {a,b,d,f,g,j,c,i,k} */ var j = 0; let k = 0; } - for (var l of something) {/* {n} */ + for (var l of something) /* {a,b,d,f,g,j,l,m,c} */ {/* {a,b,d,f,g,j,l,m,c,n} */ var m = 0; let n = 0; } - for (let o of something) /* {o} */ {/* {q} */ + for (let o of something) /* {a,b,d,f,g,j,l,m,p,c,o} */ {/* {a,b,d,f,g,j,l,m,p,c,o,q} */ var p = 0; let q = 0; } - switch (something) /* {s} */ { + switch (something) /* {a,b,d,f,g,j,l,m,p,r,t,c,s} */ { case 0: var r = 0; case 1: let s = 0; case 2: - {/* {u} */ + {/* {a,b,d,f,g,j,l,m,p,r,t,c,s,u} */ var t = 0; let u = 0; } } - function v(): void /* {w} */ {/* {x} */ + function v(): void /* {w} */ {/* {w,x} */ var w = 0; let x = 0; }; var a = 1; let a = 1; - { + {/* {a,b,d,f,g,j,l,m,p,r,t,v,c} */ var a = 1; } var b = 1; let b = 1; - { + {/* {a,b,d,f,g,j,l,m,p,r,t,v,c} */ var b = 1; } var c = 1; let c = 1; - { + {/* {a,b,d,f,g,j,l,m,p,r,t,v,c} */ var c = 1; } var v = 1; let v = 1; - { + {/* {a,b,d,f,g,j,l,m,p,r,t,v,c} */ var v = 1; } } diff --git a/tests/parser/string-binding.ts.fixture.ts b/tests/parser/string-binding.ts.fixture.ts index fa0641c345..30994d7b0d 100644 --- a/tests/parser/string-binding.ts.fixture.ts +++ b/tests/parser/string-binding.ts.fixture.ts @@ -1,48 +1,48 @@ @binding(BindingCall.NEW, [BindingType.STRING], BindingType.OBJECT_HANDLE) export class ExternalString { @binding(BindingCall.FUNCTION, [BindingType.U32, BindingType.U32], BindingType.OBJECT_HANDLE) - static fromCharCode(char: u16, schar?: u16 = -1): String /* {char,schar} */ { + static fromCharCode(char: u16, schar?: u16 = -1): String /* {char,schar} */ {/* {char,schar} */ return unreachable(); } @binding(BindingCall.FUNCTION, [BindingType.U32], BindingType.OBJECT_HANDLE) - static fromCodePoint(codepoint: u32): String /* {codepoint} */ { + static fromCodePoint(codepoint: u32): String /* {codepoint} */ {/* {codepoint} */ return unreachable(); } @binding(BindingCall.THIS, [BindingType.U32], BindingType.OBJECT_HANDLE) - charAt(index: u32): String /* {index} */ { + charAt(index: u32): String /* {index} */ {/* {index} */ return unreachable(); } @binding(BindingCall.THIS, [BindingType.U32], BindingType.PASS_THRU) - charCodeAt(index: u32): u16 /* {index} */ { + charCodeAt(index: u32): u16 /* {index} */ {/* {index} */ return unreachable(); } @binding(BindingCall.THIS, [BindingType.U32], BindingType.PASS_THRU) - codePointAt(index: u32): u32 /* {index} */ { + codePointAt(index: u32): u32 /* {index} */ {/* {index} */ return unreachable(); } @binding(BindingCall.THIS, [BindingType.OBJECT_HANDLE], BindingType.OBJECT_HANDLE) @operator("+") - concat(other: String): String /* {other} */ { + concat(other: String): String /* {other} */ {/* {other} */ return unreachable(); } @binding(BindingCall.THIS, [BindingType.OBJECT_HANDLE], BindingType.PASS_THRU) - endsWith(other: String): bool /* {other} */ { + endsWith(other: String): bool /* {other} */ {/* {other} */ return unreachable(); } @binding(BindingCall.THIS, [BindingType.OBJECT_HANDLE], BindingType.PASS_THRU) - indexOf(other: String): i32 /* {other} */ { + indexOf(other: String): i32 /* {other} */ {/* {other} */ return unreachable(); } @binding(BindingCall.THIS, [BindingType.OBJECT_HANDLE], BindingType.PASS_THRU) - startsWith(other: String): bool /* {other} */ { + startsWith(other: String): bool /* {other} */ {/* {other} */ return unreachable(); } @binding(BindingCall.THIS, [BindingType.U32, BindingType.U32], BindingType.OBJECT_HANDLE) - substr(start: i32, length: i32): String /* {start,length} */ { + substr(start: i32, length: i32): String /* {start,length} */ {/* {start,length} */ return unreachable(); } @binding(BindingCall.THIS, [BindingType.U32, BindingType.U32], BindingType.OBJECT_HANDLE) - substring(start: i32, end: i32): String /* {start,end} */ { + substring(start: i32, end: i32): String /* {start,end} */ {/* {start,end} */ return unreachable(); } @binding(BindingCall.THIS, [], BindingType.OBJECT_HANDLE) @@ -59,7 +59,7 @@ export class ExternalString { } @binding(BindingCall.THIS, [BindingType.OBJECT_HANDLE], BindingType.PASS_THRU) @operator("==") - equals(other: String): bool /* {other} */ { + equals(other: String): bool /* {other} */ {/* {other} */ return unreachable(); } } diff --git a/tests/parser/trailing-commas.ts.fixture.ts b/tests/parser/trailing-commas.ts.fixture.ts index 11439b08af..f511e8ff92 100644 --- a/tests/parser/trailing-commas.ts.fixture.ts +++ b/tests/parser/trailing-commas.ts.fixture.ts @@ -6,10 +6,10 @@ enum Foo { A, B } -function add(x: i32, y: i32): i32 /* {x,y} */ { +function add(x: i32, y: i32): i32 /* {x,y} */ {/* {x,y} */ return x + y; } -function parameterized(a: A, b: B): void /* {a,b} */ {} +function parameterized(a: A, b: B): void /* {a,b} */ {/* {a,b} */} export function compute(): i32 {/* {arr} */ const arr: Array = [1, 2]; parameterized(0, 0);