Add resolving and binding
parent
875373df43
commit
3ec297c9e1
@ -0,0 +1,261 @@
|
|||||||
|
package resolver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golox/ast"
|
||||||
|
"golox/errors"
|
||||||
|
"golox/interpreter"
|
||||||
|
"golox/token"
|
||||||
|
)
|
||||||
|
|
||||||
|
// FuncType represents the type of a function.
|
||||||
|
type FuncType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// NoneFuncType represents a function with no type.
|
||||||
|
NoneFuncType FuncType = iota
|
||||||
|
// FunctionFuncType represents a function.
|
||||||
|
FunctionFuncType
|
||||||
|
// MethodFuncType represents a method.
|
||||||
|
MethodFuncType
|
||||||
|
// InitializerFuncType represents an initializer.
|
||||||
|
InitializerFuncType
|
||||||
|
)
|
||||||
|
|
||||||
|
// Resolver implements variable resolution for the AST.
|
||||||
|
type Resolver struct {
|
||||||
|
errLogger errors.Logger
|
||||||
|
interpreter *interpreter.Interpreter
|
||||||
|
scopes []map[string]bool
|
||||||
|
currFunc FuncType
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a new Resolver.
|
||||||
|
func New(i *interpreter.Interpreter, el errors.Logger) *Resolver {
|
||||||
|
return &Resolver{el, i, []map[string]bool{}, NoneFuncType}
|
||||||
|
}
|
||||||
|
|
||||||
|
// VisitBlockStmt visits a block statement.
|
||||||
|
func (r *Resolver) VisitBlockStmt(bs *ast.BlockStmt) any {
|
||||||
|
r.beginScope()
|
||||||
|
r.Resolve(bs.Statements)
|
||||||
|
r.endScope()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve resolves the variables for each statements in stmts.
|
||||||
|
func (r *Resolver) Resolve(stmts []ast.Stmt) {
|
||||||
|
for _, stmt := range stmts {
|
||||||
|
r.resolveStmt(stmt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolveStmt resolves a statement.
|
||||||
|
func (r *Resolver) resolveStmt(stmt ast.Stmt) {
|
||||||
|
stmt.Accept(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolveExpr resolves an expression.
|
||||||
|
func (r *Resolver) resolveExpr(expr ast.Expr) {
|
||||||
|
expr.Accept(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// beginScope begins a new scope.
|
||||||
|
func (r *Resolver) beginScope() {
|
||||||
|
r.scopes = append(r.scopes, map[string]bool{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// endScope ends the current scope.
|
||||||
|
func (r *Resolver) endScope() {
|
||||||
|
r.scopes = r.scopes[:len(r.scopes)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// visitVarStmt visits a variable statement.
|
||||||
|
func (r *Resolver) VisitVarStmt(vs *ast.VarStmt) any {
|
||||||
|
r.declare(&vs.Name)
|
||||||
|
if vs.Initializer != nil {
|
||||||
|
r.resolveExpr(vs.Initializer)
|
||||||
|
}
|
||||||
|
r.define(&vs.Name)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// declare declares a variable in the current scope.
|
||||||
|
func (r *Resolver) declare(name *token.Token) {
|
||||||
|
if len(r.scopes) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
scope := r.scopes[len(r.scopes)-1]
|
||||||
|
if _, ok := scope[name.Lexeme]; ok {
|
||||||
|
r.errLogger.ErrorAtToken(*name, "Variable with this name already declared in this scope.")
|
||||||
|
}
|
||||||
|
scope[name.Lexeme] = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// define defines a variable in the current scope.
|
||||||
|
func (r *Resolver) define(name *token.Token) {
|
||||||
|
if len(r.scopes) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
r.scopes[len(r.scopes)-1][name.Lexeme] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// VisitVariableExpr visits a variable expression.
|
||||||
|
func (r *Resolver) VisitVariableExpr(ve *ast.VariableExpr) any {
|
||||||
|
if len(r.scopes) > 0 {
|
||||||
|
if _, ok := r.scopes[len(r.scopes)-1][ve.Name.Lexeme]; ok && !r.scopes[len(r.scopes)-1][ve.Name.Lexeme] {
|
||||||
|
r.errLogger.ErrorAtToken(ve.Name, "Cannot read local variable in its own initializer.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
r.resolveLocal(ve, &ve.Name)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolveLocal resolves a local variable.
|
||||||
|
func (r *Resolver) resolveLocal(expr ast.Expr, name *token.Token) {
|
||||||
|
for i := len(r.scopes) - 1; i >= 0; i-- {
|
||||||
|
if _, ok := r.scopes[i][name.Lexeme]; ok {
|
||||||
|
r.interpreter.Resolve(expr, len(r.scopes)-1-i)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// VisitAssignExpr visits an assignment expression.
|
||||||
|
func (r *Resolver) VisitAssignExpr(ae *ast.AssignExpr) any {
|
||||||
|
r.resolveExpr(ae.Value)
|
||||||
|
r.resolveLocal(ae, &ae.Name)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// VisitFunctionStmt visits a function statement.
|
||||||
|
func (r *Resolver) VisitFunctionStmt(fs *ast.FunctionStmt) any {
|
||||||
|
r.declare(&fs.Name)
|
||||||
|
r.define(&fs.Name)
|
||||||
|
|
||||||
|
r.resolveFunction(fs, FunctionFuncType)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolveFunction resolves a function.
|
||||||
|
func (r *Resolver) resolveFunction(fs *ast.FunctionStmt, t FuncType) {
|
||||||
|
enclosingFunc := r.currFunc
|
||||||
|
r.currFunc = t
|
||||||
|
|
||||||
|
r.beginScope()
|
||||||
|
for _, param := range fs.Params {
|
||||||
|
r.declare(¶m)
|
||||||
|
r.define(¶m)
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Resolve(fs.Body)
|
||||||
|
r.endScope()
|
||||||
|
|
||||||
|
r.currFunc = enclosingFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
// VisitExpressionStmt visits an expression statement.
|
||||||
|
func (r *Resolver) VisitExpressionStmt(es *ast.ExpressionStmt) any {
|
||||||
|
r.resolveExpr(es.Expression)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// VisitIfStmt visits an if statement.
|
||||||
|
func (r *Resolver) VisitIfStmt(is *ast.IfStmt) any {
|
||||||
|
r.resolveExpr(is.Condition)
|
||||||
|
r.resolveStmt(is.ThenBranch)
|
||||||
|
if is.ElseBranch != nil {
|
||||||
|
r.resolveStmt(is.ElseBranch)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// VisitPrintStmt visits a print statement.
|
||||||
|
func (r *Resolver) VisitPrintStmt(ps *ast.PrintStmt) any {
|
||||||
|
r.resolveExpr(ps.Expression)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// VisitReturnStmt visits a return statement.
|
||||||
|
func (r *Resolver) VisitReturnStmt(rs *ast.ReturnStmt) any {
|
||||||
|
if r.currFunc == NoneFuncType {
|
||||||
|
r.errLogger.ErrorAtToken(rs.Keyword, "Cannot return from top-level code.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if rs.Value != nil {
|
||||||
|
r.resolveExpr(rs.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// VisitWhileStmt visits a while statement.
|
||||||
|
func (r *Resolver) VisitWhileStmt(ws *ast.WhileStmt) any {
|
||||||
|
r.resolveExpr(ws.Condition)
|
||||||
|
r.resolveStmt(ws.Body)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// VisitErrorStmt visits an error statement.
|
||||||
|
func (r *Resolver) VisitErrorStmt(es *ast.ErrorStmt) any {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// VisitBinaryExpr visits a binary expression.
|
||||||
|
func (r *Resolver) VisitBinaryExpr(be *ast.BinaryExpr) any {
|
||||||
|
r.resolveExpr(be.Left)
|
||||||
|
r.resolveExpr(be.Right)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// VisitCallExpr visits a call expression.
|
||||||
|
func (r *Resolver) VisitCallExpr(ce *ast.CallExpr) any {
|
||||||
|
r.resolveExpr(ce.Callee)
|
||||||
|
for _, arg := range ce.Arguments {
|
||||||
|
r.resolveExpr(arg)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// VisitGroupingExpr visits a grouping expression.
|
||||||
|
func (r *Resolver) VisitGroupingExpr(ge *ast.GroupingExpr) any {
|
||||||
|
r.resolveExpr(ge.Expression)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// VisitLiteralExpr visits a literal expression.
|
||||||
|
func (r *Resolver) VisitLiteralExpr(le *ast.LiteralExpr) any {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// VisitLogicalExpr visits a logical expression.
|
||||||
|
func (r *Resolver) VisitLogicalExpr(le *ast.LogicalExpr) any {
|
||||||
|
r.resolveExpr(le.Left)
|
||||||
|
r.resolveExpr(le.Right)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// VisitUnaryExpr visits a unary expression.
|
||||||
|
func (r *Resolver) VisitUnaryExpr(ue *ast.UnaryExpr) any {
|
||||||
|
r.resolveExpr(ue.Right)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// VisitErrorExpr visits an error statement.
|
||||||
|
func (r *Resolver) VisitErrorExpr(ee *ast.ErrorExpr) any {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@ -0,0 +1,276 @@
|
|||||||
|
package resolver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"golox/ast"
|
||||||
|
"golox/errors"
|
||||||
|
"golox/interpreter"
|
||||||
|
"golox/token"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestResolver_VisitBlockStmt(t *testing.T) {
|
||||||
|
logger := errors.NewMockErrorLogger()
|
||||||
|
interp := &interpreter.Interpreter{}
|
||||||
|
resolver := New(interp, logger)
|
||||||
|
|
||||||
|
blockStmt := &ast.BlockStmt{
|
||||||
|
Statements: []ast.Stmt{},
|
||||||
|
}
|
||||||
|
|
||||||
|
resolver.VisitBlockStmt(blockStmt)
|
||||||
|
|
||||||
|
if len(resolver.scopes) != 0 {
|
||||||
|
t.Errorf("expected scopes to be empty, got %v", resolver.scopes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResolver_VisitVarStmt(t *testing.T) {
|
||||||
|
logger := errors.NewMockErrorLogger()
|
||||||
|
interp := &interpreter.Interpreter{}
|
||||||
|
resolver := New(interp, logger)
|
||||||
|
|
||||||
|
varStmt := &ast.VarStmt{
|
||||||
|
Name: token.Token{Lexeme: "a"},
|
||||||
|
Initializer: nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
resolver.VisitVarStmt(varStmt)
|
||||||
|
|
||||||
|
if len(logger.Errors) != 0 {
|
||||||
|
t.Errorf("expected no errors, got %v", logger.Errors)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResolver_VisitVariableExpr(t *testing.T) {
|
||||||
|
logger := errors.NewMockErrorLogger()
|
||||||
|
interp := &interpreter.Interpreter{}
|
||||||
|
resolver := New(interp, logger)
|
||||||
|
|
||||||
|
varExpr := &ast.VariableExpr{
|
||||||
|
Name: token.Token{Lexeme: "a"},
|
||||||
|
}
|
||||||
|
|
||||||
|
resolver.VisitVariableExpr(varExpr)
|
||||||
|
|
||||||
|
if len(logger.Errors) != 0 {
|
||||||
|
t.Errorf("expected no errors, got %v", logger.Errors)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResolver_VisitAssignExpr(t *testing.T) {
|
||||||
|
logger := errors.NewMockErrorLogger()
|
||||||
|
interp := &interpreter.Interpreter{}
|
||||||
|
resolver := New(interp, logger)
|
||||||
|
|
||||||
|
assignExpr := &ast.AssignExpr{
|
||||||
|
Name: token.Token{Lexeme: "a"},
|
||||||
|
Value: &ast.LiteralExpr{Value: 42},
|
||||||
|
}
|
||||||
|
|
||||||
|
resolver.VisitAssignExpr(assignExpr)
|
||||||
|
|
||||||
|
if len(logger.Errors) != 0 {
|
||||||
|
t.Errorf("expected no errors, got %v", logger.Errors)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResolver_VisitFunctionStmt(t *testing.T) {
|
||||||
|
logger := errors.NewMockErrorLogger()
|
||||||
|
interp := &interpreter.Interpreter{}
|
||||||
|
resolver := New(interp, logger)
|
||||||
|
|
||||||
|
funcStmt := &ast.FunctionStmt{
|
||||||
|
Name: token.Token{Lexeme: "foo"},
|
||||||
|
Params: []token.Token{},
|
||||||
|
Body: []ast.Stmt{},
|
||||||
|
}
|
||||||
|
|
||||||
|
resolver.VisitFunctionStmt(funcStmt)
|
||||||
|
|
||||||
|
if len(logger.Errors) != 0 {
|
||||||
|
t.Errorf("expected no errors, got %v", logger.Errors)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResolver_VisitReturnStmt(t *testing.T) {
|
||||||
|
logger := errors.NewMockErrorLogger()
|
||||||
|
interp := &interpreter.Interpreter{}
|
||||||
|
resolver := New(interp, logger)
|
||||||
|
|
||||||
|
returnStmt := &ast.ReturnStmt{
|
||||||
|
Keyword: token.Token{Lexeme: "return"},
|
||||||
|
Value: &ast.LiteralExpr{Value: 42},
|
||||||
|
}
|
||||||
|
|
||||||
|
resolver.currFunc = FunctionFuncType
|
||||||
|
resolver.VisitReturnStmt(returnStmt)
|
||||||
|
|
||||||
|
if len(logger.Errors) != 0 {
|
||||||
|
t.Errorf("expected no errors, got %v", logger.Errors)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResolver_VisitIfStmt(t *testing.T) {
|
||||||
|
logger := errors.NewMockErrorLogger()
|
||||||
|
interp := &interpreter.Interpreter{}
|
||||||
|
resolver := New(interp, logger)
|
||||||
|
|
||||||
|
ifStmt := &ast.IfStmt{
|
||||||
|
Condition: &ast.LiteralExpr{Value: true},
|
||||||
|
ThenBranch: &ast.BlockStmt{
|
||||||
|
Statements: []ast.Stmt{},
|
||||||
|
},
|
||||||
|
ElseBranch: nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
resolver.VisitIfStmt(ifStmt)
|
||||||
|
|
||||||
|
if len(logger.Errors) != 0 {
|
||||||
|
t.Errorf("expected no errors, got %v", logger.Errors)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResolver_VisitWhileStmt(t *testing.T) {
|
||||||
|
logger := errors.NewMockErrorLogger()
|
||||||
|
interp := &interpreter.Interpreter{}
|
||||||
|
resolver := New(interp, logger)
|
||||||
|
|
||||||
|
whileStmt := &ast.WhileStmt{
|
||||||
|
Condition: &ast.LiteralExpr{Value: true},
|
||||||
|
Body: &ast.BlockStmt{
|
||||||
|
Statements: []ast.Stmt{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
resolver.VisitWhileStmt(whileStmt)
|
||||||
|
|
||||||
|
if len(logger.Errors) != 0 {
|
||||||
|
t.Errorf("expected no errors, got %v", logger.Errors)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResolver_VisitExpressionStmt(t *testing.T) {
|
||||||
|
logger := errors.NewMockErrorLogger()
|
||||||
|
interp := &interpreter.Interpreter{}
|
||||||
|
resolver := New(interp, logger)
|
||||||
|
|
||||||
|
exprStmt := &ast.ExpressionStmt{
|
||||||
|
Expression: &ast.LiteralExpr{Value: 42},
|
||||||
|
}
|
||||||
|
|
||||||
|
resolver.VisitExpressionStmt(exprStmt)
|
||||||
|
|
||||||
|
if len(logger.Errors) != 0 {
|
||||||
|
t.Errorf("expected no errors, got %v", logger.Errors)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResolver_Resolve(t *testing.T) {
|
||||||
|
logger := errors.NewMockErrorLogger()
|
||||||
|
interp := &interpreter.Interpreter{}
|
||||||
|
resolver := New(interp, logger)
|
||||||
|
|
||||||
|
stmts := []ast.Stmt{
|
||||||
|
&ast.BlockStmt{
|
||||||
|
Statements: []ast.Stmt{
|
||||||
|
&ast.VarStmt{
|
||||||
|
Name: token.Token{Lexeme: "a"},
|
||||||
|
Initializer: nil,
|
||||||
|
},
|
||||||
|
&ast.VarStmt{
|
||||||
|
Name: token.Token{Lexeme: "a"},
|
||||||
|
Initializer: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
resolver.Resolve(stmts)
|
||||||
|
|
||||||
|
if len(logger.Errors) != 1 {
|
||||||
|
t.Errorf("expected 1 error, got %v", logger.Errors)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResolver_ResolveStmt(t *testing.T) {
|
||||||
|
logger := errors.NewMockErrorLogger()
|
||||||
|
interp := &interpreter.Interpreter{}
|
||||||
|
resolver := New(interp, logger)
|
||||||
|
|
||||||
|
stmt := &ast.VarStmt{
|
||||||
|
Name: token.Token{Lexeme: "a"},
|
||||||
|
Initializer: nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
resolver.resolveStmt(stmt)
|
||||||
|
|
||||||
|
if len(logger.Errors) != 0 {
|
||||||
|
t.Errorf("expected no errors, got %v", logger.Errors)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResolver_ResolveExpr(t *testing.T) {
|
||||||
|
logger := errors.NewMockErrorLogger()
|
||||||
|
interp := &interpreter.Interpreter{}
|
||||||
|
resolver := New(interp, logger)
|
||||||
|
|
||||||
|
expr := &ast.LiteralExpr{Value: 42}
|
||||||
|
|
||||||
|
resolver.resolveExpr(expr)
|
||||||
|
|
||||||
|
if len(logger.Errors) != 0 {
|
||||||
|
t.Errorf("expected no errors, got %v", logger.Errors)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResolver_Declare(t *testing.T) {
|
||||||
|
logger := errors.NewMockErrorLogger()
|
||||||
|
interp := &interpreter.Interpreter{}
|
||||||
|
resolver := New(interp, logger)
|
||||||
|
|
||||||
|
resolver.beginScope()
|
||||||
|
resolver.declare(&token.Token{Lexeme: "a"})
|
||||||
|
|
||||||
|
if len(resolver.scopes) != 1 {
|
||||||
|
t.Errorf("expected 1 scope, got %v", resolver.scopes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResolver_Define(t *testing.T) {
|
||||||
|
logger := errors.NewMockErrorLogger()
|
||||||
|
interp := &interpreter.Interpreter{}
|
||||||
|
resolver := New(interp, logger)
|
||||||
|
|
||||||
|
resolver.beginScope()
|
||||||
|
resolver.define(&token.Token{Lexeme: "a"})
|
||||||
|
|
||||||
|
if len(resolver.scopes) != 1 {
|
||||||
|
t.Errorf("expected 1 scope, got %v", resolver.scopes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResolver_BeginScope(t *testing.T) {
|
||||||
|
logger := errors.NewMockErrorLogger()
|
||||||
|
interp := &interpreter.Interpreter{}
|
||||||
|
resolver := New(interp, logger)
|
||||||
|
|
||||||
|
resolver.beginScope()
|
||||||
|
|
||||||
|
if len(resolver.scopes) != 1 {
|
||||||
|
t.Errorf("expected 1 scope, got %v", resolver.scopes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResolver_EndScope(t *testing.T) {
|
||||||
|
logger := errors.NewMockErrorLogger()
|
||||||
|
interp := &interpreter.Interpreter{}
|
||||||
|
resolver := New(interp, logger)
|
||||||
|
|
||||||
|
resolver.beginScope()
|
||||||
|
resolver.endScope()
|
||||||
|
|
||||||
|
if len(resolver.scopes) != 0 {
|
||||||
|
t.Errorf("expected 0 scopes, got %v", resolver.scopes)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
var a = "global";
|
||||||
|
{
|
||||||
|
fun showA() {
|
||||||
|
print a;
|
||||||
|
}
|
||||||
|
|
||||||
|
showA();
|
||||||
|
var a = "block";
|
||||||
|
showA();
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue