Require blocks in fewer places

Due to Python's ... interesting ... scoping rules, we want to avoid
forcing block scopes in certain places.  Instead, we will let arbitrary
statements take their place.  Of course, this could be a block, but it
very well could be a multi-statement (essentially a block that doesn't
imply a new lexical scope), or anything, really.
This commit is contained in:
joeduffy 2017-04-10 10:06:27 -07:00
parent 2c6ad1e331
commit 1299e4ade7
10 changed files with 81 additions and 70 deletions

View file

@ -177,19 +177,19 @@ type Function interface {
Definition
GetParameters() *[]*LocalVariable
GetReturnType() *TypeToken
GetBody() *Block
GetBody() Statement
}
type FunctionNode struct {
// note that this node intentionally omits any embedded base, to avoid diamond "inheritance".
Parameters *[]*LocalVariable `json:"parameters,omitempty"`
ReturnType *TypeToken `json:"returnType,omitempty"`
Body *Block `json:"body,omitempty"`
Body Statement `json:"body,omitempty"`
}
func (node *FunctionNode) GetParameters() *[]*LocalVariable { return node.Parameters }
func (node *FunctionNode) GetReturnType() *TypeToken { return node.ReturnType }
func (node *FunctionNode) GetBody() *Block { return node.Body }
func (node *FunctionNode) GetBody() Statement { return node.Body }
// ModuleMethod is just a function with an accessibility modifier.
type ModuleMethod struct {

View file

@ -31,6 +31,7 @@ const ImportKind NodeKind = "Import"
/* Blocks */
// Block is a grouping of statements that enjoy their own lexical scope.
type Block struct {
StatementNode
Statements []Statement `json:"statements"`
@ -57,9 +58,9 @@ const LocalVariableDeclarationKind NodeKind = "LocalVariableDeclaration"
type TryCatchFinally struct {
StatementNode
TryBlock *Block `json:"tryBlock"`
CatchBlocks *[]*TryCatchBlock `json:"catchBlocks,omitempty"`
FinallyBlock *Block `json:"finallyBlock"`
TryClause Statement `json:"tryClause"`
CatchClauses *[]*TryCatchClause `json:"catchClauses,omitempty"`
FinallyClause Statement `json:"finallyClause"`
}
var _ Node = (*TryCatchFinally)(nil)
@ -67,13 +68,13 @@ var _ Statement = (*TryCatchFinally)(nil)
const TryCatchFinallyKind NodeKind = "TryCatchFinally"
type TryCatchBlock struct {
type TryCatchClause struct {
NodeValue
Exception *LocalVariable `json:"exception,omitempty"`
Block *Block `json:"block"`
Body Statement `json:"body"`
}
var _ Node = (*TryCatchBlock)(nil)
var _ Node = (*TryCatchClause)(nil)
/* Branches */
@ -173,7 +174,7 @@ const ThrowStatementKind NodeKind = "ThrowStatement"
type WhileStatement struct {
StatementNode
Condition *Expression `json:"condition,omitempty"` // a `bool` statement indicating whether to cotinue.
Body *Block `json:"body"` // the body to execute provided the condition remains `true`.
Body Statement `json:"body"` // the body to execute provided the condition remains `true`.
}
var _ Node = (*WhileStatement)(nil)
@ -187,7 +188,7 @@ type ForStatement struct {
Init *Statement `json:"init,omitempty"` // an initialization statement.
Condition *Expression `json:"condition,omitempty"` // a `bool` statement indicating whether to continue.
Post *Statement `json:"post,omitempty"` // a statement to run after the body, before the next iteration.
Body *Block `json:"body"` // the body to execute provided the condition remains `true`.
Body Statement `json:"body"` // the body to execute provided the condition remains `true`.
}
var _ Node = (*ForStatement)(nil)

View file

@ -91,18 +91,18 @@ func Walk(v Visitor, node Node) {
case *LocalVariableDeclaration:
Walk(v, n.Local)
case *TryCatchFinally:
Walk(v, n.TryBlock)
if n.CatchBlocks != nil {
for _, catch := range *n.CatchBlocks {
Walk(v, n.TryClause)
if n.CatchClauses != nil {
for _, catch := range *n.CatchClauses {
Walk(v, catch)
}
}
if n.FinallyBlock != nil {
Walk(v, n.FinallyBlock)
if n.FinallyClause != nil {
Walk(v, n.FinallyClause)
}
case *TryCatchBlock:
case *TryCatchClause:
Walk(v, n.Exception)
Walk(v, n.Block)
Walk(v, n.Body)
case *IfStatement:
Walk(v, n.Condition)
Walk(v, n.Consequent)

View file

@ -659,7 +659,7 @@ func (e *evaluator) evalCall(node diag.Diagable, fnc symbols.Function,
case *Intrinsic:
uw = f.Invoke(e, this, args)
default:
uw = e.evalBlock(fnode.GetBody())
uw = e.evalStatement(fnode.GetBody())
}
// Check that the unwind is as expected. In particular:
@ -801,13 +801,13 @@ func (e *evaluator) evalLocalVariableDeclaration(node *ast.LocalVariableDeclarat
}
func (e *evaluator) evalTryCatchFinally(node *ast.TryCatchFinally) *rt.Unwind {
// First, execute the TryBlock.
uw := e.evalBlock(node.TryBlock)
// First, execute the try part.
uw := e.evalStatement(node.TryClause)
if uw != nil && uw.Throw() {
// The try block threw something; see if there is a handler that covers this.
thrown := uw.Exception().Thrown
if node.CatchBlocks != nil {
for _, catch := range *node.CatchBlocks {
if node.CatchClauses != nil {
for _, catch := range *node.CatchClauses {
ex := e.ctx.RequireVariable(catch.Exception).(*symbols.LocalVariable)
exty := ex.Type()
if types.CanConvert(thrown.Type(), exty) {
@ -815,7 +815,7 @@ func (e *evaluator) evalTryCatchFinally(node *ast.TryCatchFinally) *rt.Unwind {
// evaluate the block, and swap the rt.Unwind information ("handling" the in-flight exception).
e.pushScope(nil)
e.locals.SetValue(ex, thrown)
uw = e.evalBlock(catch.Block)
uw = e.evalStatement(catch.Body)
e.popScope(false)
break
}
@ -823,9 +823,9 @@ func (e *evaluator) evalTryCatchFinally(node *ast.TryCatchFinally) *rt.Unwind {
}
}
// No matter the rt.Unwind instructions, be sure to invoke the FinallyBlock.
if node.FinallyBlock != nil {
uwf := e.evalBlock(node.FinallyBlock)
// No matter the rt.Unwind instructions, be sure to invoke the finally part.
if node.FinallyClause != nil {
uwf := e.evalStatement(node.FinallyClause)
// Any rt.Unwind information from the finally block overrides the try rt.Unwind that was in flight.
if uwf != nil {
@ -989,10 +989,6 @@ func (e *evaluator) evalWhileStatement(node *ast.WhileStatement) *rt.Unwind {
}
func (e *evaluator) evalForStatement(node *ast.ForStatement) *rt.Unwind {
// Enter into a new scope so that any new variables are inside of the for block.
e.pushScope(nil)
defer e.popScope(false)
// Now run the initialization code.
if node.Init != nil {
if uw := e.evalStatement(*node.Init); uw != nil {

View file

@ -102,7 +102,7 @@ export type ClassPropertyKind = "ClassProperty";
export interface Function extends Definition {
parameters?: LocalVariable[];
returnType?: TypeToken;
body?: statements.Block;
body?: statements.Statement;
}
// A module method is just a function defined at the module scope.

View file

@ -37,7 +37,7 @@ export type NodeKind =
statements.BlockKind |
statements.LocalVariableDeclarationKind |
statements.TryCatchFinallyKind |
statements.TryCatchBlockKind |
statements.TryCatchClauseKind |
statements.BreakStatementKind |
statements.ContinueStatementKind |
statements.IfStatementKind |

View file

@ -18,6 +18,7 @@ export type ImportKind = "Import";
/** Blocks **/
// Block is a grouping of statements that enjoy their own lexical scope.
export interface Block extends Statement {
kind: BlockKind;
statements: Statement[];
@ -37,21 +38,21 @@ export type LocalVariableDeclarationKind = "LocalVariableDeclaration";
/** Try/Catch/Finally **/
export interface TryCatchFinally extends Statement {
kind: TryCatchFinallyKind;
tryBlock: Block;
catchBlocks?: TryCatchBlock[];
finallyBlock?: Block;
kind: TryCatchFinallyKind;
tryClause: Statement;
catchClauses?: TryCatchClause[];
finallyClause?: Statement;
}
export const tryCatchFinallyKind = "TryCatchFinally";
export type TryCatchFinallyKind = "TryCatchFinally";
export interface TryCatchBlock extends Node {
kind: TryCatchBlockKind;
block: Block;
export interface TryCatchClause extends Node {
kind: TryCatchClauseKind;
body: Statement;
exception?: LocalVariable;
}
export const tryCatchBlockKind = "TryCatchBlock";
export type TryCatchBlockKind = "TryCatchBlock";
export const tryCatchClauseKind = "TryCatchClause";
export type TryCatchClauseKind = "TryCatchClause";
/** Branches **/

View file

@ -1089,7 +1089,7 @@ export class Transformer {
let initializer: ast.ModuleMethod = {
kind: ast.moduleMethodKind,
name: ident(tokens.initializerFunction),
body: {
body: <ast.Block>{
kind: ast.blockKind,
statements: statements,
},
@ -1657,12 +1657,15 @@ export class Transformer {
insertAt = 0; // add the initializers to the empty block.
members[tokens.constructorFunction] = ctor;
}
let bodyBlock: ast.Block;
if (ctor.body) {
bodyBlock = <ast.Block>ctor.body;
if (extend) {
// If there is a superclass, find the insertion point right *after* the explicit call to
// `super()`, to achieve the expected initialization order.
for (let i = 0; i < ctor.body.statements.length; i++) {
if (this.isSuperCall(ctor.body.statements[i], extend.tok)) {
for (let i = 0; i < bodyBlock.statements.length; i++) {
if (this.isSuperCall(bodyBlock.statements[i], extend.tok)) {
insertAt = i+1; // place the initializers right after this call.
break;
}
@ -1674,14 +1677,15 @@ export class Transformer {
}
}
else {
ctor.body = this.withLocation(node, <ast.Block>{
bodyBlock = this.withLocation(node, <ast.Block>{
kind: ast.blockKind,
statements: [],
});
ctor.body = bodyBlock;
if (extend) {
// Generate an automatic call to the base class. Omitting this is only legal if the base class
// constructor has zero arguments, so we just generate a simple `super();` call.
ctor.body.statements.push(
bodyBlock.statements.push(
this.copyLocation(ctor.body, this.createEmptySuperCall(extend.tok)));
insertAt = 1; // insert the initializers immediately after this call.
}
@ -1690,10 +1694,10 @@ export class Transformer {
}
}
ctor.body.statements =
ctor.body.statements.slice(0, insertAt).concat(
bodyBlock.statements =
bodyBlock.statements.slice(0, insertAt).concat(
instancePropertyInitializers).concat(
ctor.body.statements.slice(insertAt, ctor.body.statements.length));
bodyBlock.statements.slice(insertAt, bodyBlock.statements.length));
}
let mods: ts.ModifierFlags = ts.getCombinedModifierFlags(node);
@ -2254,13 +2258,21 @@ export class Transformer {
expression: await this.transformExpression(node.incrementor),
});
}
return this.withLocation(node, <ast.ForStatement>{
let forStmt: ast.ForStatement = this.withLocation(node, <ast.ForStatement>{
kind: ast.forStatementKind,
init: init,
condition: condition,
post: post,
body: await this.transformStatement(node.statement),
});
// Place the for statement into a block so that any new variables introduced inside of the init are lexically
// scoped to the for loop, rather than outside of it.
return <ast.Block>{
kind: ast.blockKind,
statements: [ forStmt ],
};
}
private async transformForInitializer(node: ts.ForInitializer): Promise<ast.Statement> {

View file

@ -216,7 +216,7 @@ class Function(Definition):
assert (parameters is None or
(isinstance(parameters, list) and all(isinstance(node, LocalVariable) for node in parameters)))
assert return_type is None or isinstance(return_type, TypeToken)
assert body is None or isinstance(body, Block)
assert body is None or isinstance(body, Statement)
super(Function, self).__init__(kind, name, loc=loc)
self.parameters = parameters
self.return_type = return_type
@ -229,7 +229,7 @@ class ModuleMethod(Function, ModuleMember):
assert (parameters is None or
(isinstance(parameters, list) and all(isinstance(node, LocalVariable) for node in parameters)))
assert return_type is None or isinstance(return_type, TypeToken)
assert body is None or isinstance(body, Block)
assert body is None or isinstance(body, Statement)
super(ModuleMethod, self).__init__("ModuleMethod", name, parameters, return_type, body, loc)
class ClassMethod(Function, ClassMember):
@ -240,7 +240,7 @@ class ClassMethod(Function, ClassMember):
assert (parameters is None or
(isinstance(parameters, list) and all(isinstance(node, LocalVariable) for node in parameters)))
assert return_type is None or isinstance(return_type, TypeToken)
assert body is None or isinstance(body, Block)
assert body is None or isinstance(body, Statement)
assert access is None or access in tokens.accs
assert static is None or isinstance(bool, static)
assert sealed is None or isinstance(bool, sealed)
@ -289,22 +289,22 @@ class LocalVariableDeclaration(Statement):
# ...Try/Catch/Finally
class TryCatchFinally(Statement):
def __init__(self, try_block, catch_blocks=None, finally_block=None, loc=None):
assert isinstance(try_block, Block)
assert (catch_blocks is None or
(isinstance(catch_blocks, list) and all(isinstance(node, TryCatchBlock) for node in catch_blocks)))
assert finally_block is None or isinstance(finally_block, Block)
def __init__(self, try_clause, catch_clauses=None, finally_clause=None, loc=None):
assert isinstance(try_clause, Statement)
assert (catch_clauses is None or
(isinstance(catch_blocks, list) and all(isinstance(node, TryCatchClause) for node in catch_clauses)))
assert finally_clause is None or isinstance(finally_clause, Statement)
super(TryCatchFinally, self).__init__("TryCatchFinally", loc)
self.try_block = try_block
self.catch_blocks = catch_blocks
self.finally_block = finally_block
self.try_clause = try_clause
self.catch_clauses = catch_clauses
self.finally_clause = finally_clause
class TryCatchBlock(Node):
def __init__(self, block, exception=None, loc=None):
assert isinstance(block, Block)
class TryCatchClause(Node):
def __init__(self, body, exception=None, loc=None):
assert isinstance(body, Statement)
assert exception is None or isinstance(exception, LocalVariable)
super(TryCatchBlock, self).__init__("TryCatchBlock", loc)
self.block = block
super(TryCatchClause, self).__init__("TryCatchClause", loc)
self.body = body
self.exception = exception
# ...Branches

View file

@ -144,13 +144,13 @@ class Transformer:
# If any top-level statements spilled over, add them to the initializer.
if len(initstmts) > 0:
initbody = ast.Block(initstmts)
initbody = ast.MultiStatement(initstmts)
members[tokens.func_init] = ast.ModuleMethod(self.ident(tokens.func_init), body=initbody)
# All Python scripts are executable, so ensure that an entrypoint exists. It consists of an empty block
# because it exists solely to trigger the module initializer routine (if it exists).
members[tokens.func_entrypoint] = ast.ModuleMethod(
self.ident(tokens.func_entrypoint), body=ast.Block(list()))
self.ident(tokens.func_entrypoint), body=ast.MultiStatement(list()))
# For every property "declaration" encountered during the transformation, add a module property.
for propname in self.ctx.globals:
@ -249,7 +249,8 @@ class Transformer:
if file and start:
loc = ast.Location(file, start, end)
return ast.Block(stmts, loc)
# Note that, to emulate Python's more "dynamic" scoping rules, we do not emit a true block.
return ast.MultiStatement(stmts, loc)
def transform_Assert(self, node):
self.not_yet_implemented(node) # test, msg