Skip to content

Commit

Permalink
fix(deducer): resolve deducer malfunction with imports in function body
Browse files Browse the repository at this point in the history
The deducer fails to operate correctly when encountering import statements inside function bodies, attempting to retrieve module symbols from these local scope imports. Since global scope is required for symbol resolution and these imports don't need extraction, the solution is to bypass import statements during the extraction process.
  • Loading branch information
jianzs committed Aug 7, 2024
1 parent 5a96b0e commit 6d20f35
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 0 deletions.
7 changes: 7 additions & 0 deletions .changeset/slimy-pots-wait.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@plutolang/pyright-deducer": patch
---

fix(deducer): resolve deducer malfunction with imports in function body

The deducer fails to operate correctly when encountering import statements inside function bodies, attempting to retrieve module symbols from these local scope imports. Since global scope is required for symbol resolution and these imports don't need extraction, the solution is to bypass import statements during the extraction process.
12 changes: 12 additions & 0 deletions components/deducers/python-pyright/src/code-extractor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import {
FunctionNode,
ImportAsNode,
ImportFromAsNode,
ImportFromNode,
ImportNode,
IndexNode,
LambdaNode,
ListComprehensionForIfNode,
Expand Down Expand Up @@ -965,6 +967,16 @@ class OutsideSymbolFinder extends ParseTreeWalker {
return Array.from(this._envVarNames);
}

public override visitImport(node: ImportNode): boolean {

Check failure on line 970 in components/deducers/python-pyright/src/code-extractor.ts

View workflow job for this annotation

GitHub Actions / Build and test

'node' is defined but never used
// Ignore the import statement. We don't need to extract the imported modules.
return false;
}

public override visitImportFrom(node: ImportFromNode): boolean {

Check failure on line 975 in components/deducers/python-pyright/src/code-extractor.ts

View workflow job for this annotation

GitHub Actions / Build and test

'node' is defined but never used
// Ignore the import-from statement. We don't need to extract the imported modules.
return false;
}

public override visitName(node: NameNode): boolean {
if (node !== this.rootNode && !this.shouldIgnore(node)) {
const symbol = this.typeEvaluator.lookUpSymbolRecursive(node, node.value, false);
Expand Down
61 changes: 61 additions & 0 deletions components/deducers/python-pyright/src/test/code-extractor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import * as TypeConsts from "../type-consts";
import { createValueEvaluator } from "../value-evaluator";
import { CodeSegment, CodeExtractor } from "../code-extractor";
import { ResourceObjectTracker } from "../resource-object-tracker";
import { getTextOfNode } from "../text-utils";

Check failure on line 20 in components/deducers/python-pyright/src/test/code-extractor.test.ts

View workflow job for this annotation

GitHub Actions / Build and test

'getTextOfNode' is defined but never used

const SAMPLES_ROOT = path.join(__dirname, "samples");

Expand Down Expand Up @@ -325,6 +326,66 @@ foo()
clean();
});

describe("Code segment extraction with import statements", () => {
const code = `
def foo1():
import pluto_client
def foo2():
import pluto_client as pc
def foo3():
from pluto_client import Router
def foo4():
from pluto_client import Router as R
foo1()
foo2()
foo3()
foo4()
`;
let program: Program;
let sourceFile: SourceFile;
let clean: () => void;
let extractor: CodeExtractor;
let fetchedNodes: ParseNode[] = [];

beforeAll(() => {
({ program, sourceFile, clean } = TestUtils.parseCode(code));
({ extractor } = createTools(program, sourceFile));
});

afterAll(() => clean());

beforeEach(() => {
const walker = new NodeFetcher((node) => {
return (
node.nodeType === ParseNodeType.Call &&
node.leftExpression.nodeType === ParseNodeType.Name &&
node.leftExpression.value.startsWith("foo")
);
});
walker.walk(sourceFile.getParseResults()!.parseTree!);
fetchedNodes = walker.nodes;
});

test("should fetch 4 nodes", () => {
expect(fetchedNodes).toHaveLength(4);
});

for (let i = 0; i < 4; i++) {
const caseIdx = i;

test(`should not throw for foo${caseIdx + 1}`, () => {
const callNode = fetchedNodes[caseIdx] as ExpressionNode;
expect(() => {
extractor.extractExpressionRecursively(callNode, sourceFile);
}).not.toThrow();
});
}
});

test("should correctly extract the code segment for the closure created within local scope", () => {
const code = `
plain_string = "Hello, world"
Expand Down

0 comments on commit 6d20f35

Please sign in to comment.