diff --git a/src/resolver.ts b/src/resolver.ts index b7a5183..6dcf27f 100644 --- a/src/resolver.ts +++ b/src/resolver.ts @@ -7,6 +7,8 @@ import { ResolverErrors } from "./errors"; const levenshtein = require('fast-levenshtein'); +const RedefineableTokenSentinel = new Token(TokenType.AT, "", 0, 0, 0); + class Environment { source: string; // The parent of this environment @@ -72,7 +74,7 @@ class Environment { } declareName(identifier: Token) { const lookup = this.lookupNameCurrentEnv(identifier); - if (lookup !== undefined) { + if (lookup !== undefined && lookup !== RedefineableTokenSentinel) { throw new ResolverErrors.NameReassignmentError(identifier.line, identifier.col, this.source, identifier.indexInSource, @@ -82,6 +84,19 @@ class Environment { } this.names.set(identifier.lexeme, identifier); } + // Same as declareName but allowed to re-declare later. + declarePlaceholderName(identifier: Token) { + const lookup = this.lookupNameCurrentEnv(identifier); + if (lookup !== undefined) { + throw new ResolverErrors.NameReassignmentError(identifier.line, identifier.col, + this.source, + identifier.indexInSource, + identifier.indexInSource + identifier.lexeme.length, + lookup); + + } + this.names.set(identifier.lexeme, RedefineableTokenSentinel); + } suggestNameCurrentEnv(identifier: Token): string | null { const name = identifier.lexeme; let minDistance = Infinity; @@ -203,6 +218,13 @@ export class Resolver implements StmtNS.Visitor, ExprNS.Visitor { return; } if (stmt instanceof Array) { + // Resolve all top-level functions first. Python allows functions declared after + // another function to be used in that function. + for (const st of stmt) { + if (st instanceof StmtNS.FunctionDef) { + this.environment?.declarePlaceholderName(st.name); + } + } for (const st of stmt) { st.accept(this); } diff --git a/src/tests/regression.test.ts b/src/tests/regression.test.ts index fdf5294..8107c05 100644 --- a/src/tests/regression.test.ts +++ b/src/tests/regression.test.ts @@ -1,4 +1,4 @@ -import {toEstreeAST} from "./utils"; +import { toEstreeAST, toEstreeAstAndResolve } from "./utils"; describe('Regression tests for py-slang', () => { test('Issue #2', () => { @@ -38,4 +38,15 @@ add_one = lambda : False `; toEstreeAST(text); }) + + test('Issue #35', () => { + const text = ` +def f(): + return g() + +def g(): + return 3 +`; + toEstreeAstAndResolve(text); + }) }) \ No newline at end of file diff --git a/src/tests/utils.ts b/src/tests/utils.ts index bd6e528..8850ddf 100644 --- a/src/tests/utils.ts +++ b/src/tests/utils.ts @@ -33,5 +33,6 @@ export function toEstreeAST(text: string): Expression | Statement { export function toEstreeAstAndResolve(text: string): Expression | Statement { const ast = toPythonAst(text); + new Resolver(text, ast).resolve(ast); return new Translator(text).resolve(ast); } \ No newline at end of file