Skip to content

Commit ab7af0c

Browse files
"Hoist" functions in resolver (#45)
Closes #35
1 parent 8299d75 commit ab7af0c

File tree

3 files changed

+36
-2
lines changed

3 files changed

+36
-2
lines changed

src/resolver.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import { ResolverErrors } from "./errors";
77

88
const levenshtein = require('fast-levenshtein');
99

10+
const RedefineableTokenSentinel = new Token(TokenType.AT, "", 0, 0, 0);
11+
1012
class Environment {
1113
source: string;
1214
// The parent of this environment
@@ -72,7 +74,7 @@ class Environment {
7274
}
7375
declareName(identifier: Token) {
7476
const lookup = this.lookupNameCurrentEnv(identifier);
75-
if (lookup !== undefined) {
77+
if (lookup !== undefined && lookup !== RedefineableTokenSentinel) {
7678
throw new ResolverErrors.NameReassignmentError(identifier.line, identifier.col,
7779
this.source,
7880
identifier.indexInSource,
@@ -82,6 +84,19 @@ class Environment {
8284
}
8385
this.names.set(identifier.lexeme, identifier);
8486
}
87+
// Same as declareName but allowed to re-declare later.
88+
declarePlaceholderName(identifier: Token) {
89+
const lookup = this.lookupNameCurrentEnv(identifier);
90+
if (lookup !== undefined) {
91+
throw new ResolverErrors.NameReassignmentError(identifier.line, identifier.col,
92+
this.source,
93+
identifier.indexInSource,
94+
identifier.indexInSource + identifier.lexeme.length,
95+
lookup);
96+
97+
}
98+
this.names.set(identifier.lexeme, RedefineableTokenSentinel);
99+
}
85100
suggestNameCurrentEnv(identifier: Token): string | null {
86101
const name = identifier.lexeme;
87102
let minDistance = Infinity;
@@ -203,6 +218,13 @@ export class Resolver implements StmtNS.Visitor<void>, ExprNS.Visitor<void> {
203218
return;
204219
}
205220
if (stmt instanceof Array) {
221+
// Resolve all top-level functions first. Python allows functions declared after
222+
// another function to be used in that function.
223+
for (const st of stmt) {
224+
if (st instanceof StmtNS.FunctionDef) {
225+
this.environment?.declarePlaceholderName(st.name);
226+
}
227+
}
206228
for (const st of stmt) {
207229
st.accept(this);
208230
}

src/tests/regression.test.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {toEstreeAST} from "./utils";
1+
import { toEstreeAST, toEstreeAstAndResolve } from "./utils";
22

33
describe('Regression tests for py-slang', () => {
44
test('Issue #2', () => {
@@ -38,4 +38,15 @@ add_one = lambda : False
3838
`;
3939
toEstreeAST(text);
4040
})
41+
42+
test('Issue #35', () => {
43+
const text = `
44+
def f():
45+
return g()
46+
47+
def g():
48+
return 3
49+
`;
50+
toEstreeAstAndResolve(text);
51+
})
4152
})

src/tests/utils.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,6 @@ export function toEstreeAST(text: string): Expression | Statement {
3333

3434
export function toEstreeAstAndResolve(text: string): Expression | Statement {
3535
const ast = toPythonAst(text);
36+
new Resolver(text, ast).resolve(ast);
3637
return new Translator(text).resolve(ast);
3738
}

0 commit comments

Comments
 (0)