-
Notifications
You must be signed in to change notification settings - Fork 52
[CG-8871] fix: removing dead code removes code that should stay #355
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 11 commits
a72b816
1d7c4f8
4b88f58
b7670f8
4976c09
b357b8c
8b8b7ad
f2a30b8
9402e81
8f96ee6
9fffb72
d99d6b8
e2f310c
98e439c
376041a
bd89175
d04c670
21ffb79
99516f6
47d7fcf
87e6925
c00323b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,18 @@ | ||
from __future__ import annotations | ||
|
||
import os | ||
from typing import TYPE_CHECKING | ||
from typing import TYPE_CHECKING, Self, override | ||
|
||
from codegen.sdk.core.autocommit import reader | ||
from codegen.sdk.core.expressions import Name | ||
from codegen.sdk.core.import_resolution import ExternalImportResolver, Import, ImportResolution | ||
from codegen.sdk.enums import ImportType, NodeType | ||
from codegen.sdk.extensions.resolution import ResolutionStack | ||
from codegen.shared.decorators.docs import noapidoc, py_apidoc | ||
|
||
if TYPE_CHECKING: | ||
from collections.abc import Generator | ||
|
||
from tree_sitter import Node as TSNode | ||
|
||
from codegen.sdk.codebase.codebase_context import CodebaseContext | ||
|
@@ -28,6 +31,10 @@ | |
class PyImport(Import["PyFile"]): | ||
"""Extends Import for Python codebases.""" | ||
|
||
def __init__(self, ts_node, file_node_id, G, parent, module_node, name_node, alias_node, import_type=ImportType.UNKNOWN): | ||
super().__init__(ts_node, file_node_id, G, parent, module_node, name_node, alias_node, import_type) | ||
self.requesting_names = set() | ||
|
||
@reader | ||
def is_module_import(self) -> bool: | ||
"""Determines if the import is a module-level or wildcard import. | ||
|
@@ -117,13 +124,13 @@ def resolve_import(self, base_path: str | None = None, *, add_module_name: str | | |
filepath = module_source.replace(".", "/") + ".py" | ||
filepath = os.path.join(base_path, filepath) | ||
if file := self.ctx.get_file(filepath): | ||
symbol = file.get_node_by_name(symbol_name) | ||
symbol = file.get_node_wildcard_resolves_for(symbol_name) | ||
return ImportResolution(from_file=file, symbol=symbol) | ||
|
||
# =====[ Check if `module/__init__.py` file exists in the graph ]===== | ||
filepath = filepath.replace(".py", "/__init__.py") | ||
if from_file := self.ctx.get_file(filepath): | ||
symbol = from_file.get_node_by_name(symbol_name) | ||
symbol = from_file.get_node_wildcard_resolves_for(symbol_name) | ||
return ImportResolution(from_file=from_file, symbol=symbol) | ||
|
||
# =====[ Case: Can't resolve the import ]===== | ||
|
@@ -133,6 +140,11 @@ def resolve_import(self, base_path: str | None = None, *, add_module_name: str | | |
if base_path == "src": | ||
# Try "test" next | ||
return self.resolve_import(base_path="test", add_module_name=add_module_name) | ||
if base_path == "test" and module_source: | ||
# Try to resolve assuming package nested in repo | ||
possible_package_base_path = module_source.split(".")[0] | ||
if possible_package_base_path not in ("test", "src"): | ||
return self.resolve_import(base_path=possible_package_base_path, add_module_name=add_module_name) | ||
|
||
# if not G_override: | ||
# for resolver in ctx.import_resolvers: | ||
|
@@ -232,6 +244,26 @@ def from_future_import_statement(cls, import_statement: TSNode, file_node_id: No | |
imports.append(imp) | ||
return imports | ||
|
||
@reader | ||
@noapidoc | ||
@override | ||
def _resolved_types(self) -> Generator[ResolutionStack[Self], None, None]: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think this is the right approach. It will create duplicated edges. For example def foo():
...
def bar():
... from filea import *
foo() # will be a usage of foo and bar For some context, we regular imports are resolved as follows.
Since it's impossible to know which symbol you're importing in a wildcard, wildcards follow the following pattern
For example, in the above case, we'd call _resolved_types on the wildcard for foo. But it'll create edges from the import to foo and from the call site to foo There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seemed to work as expected, I ve added additional UTs, have I missed anything? |
||
"""Resolve the types used by this import.""" | ||
ix_seen = set() | ||
|
||
aliased = self.is_aliased_import() | ||
if imported := self._imported_symbol(resolve_exports=True): | ||
if getattr(imported, "is_wildcard_import", False): | ||
imported.set_requesting_names(self) | ||
yield from self.with_resolution_frame(imported, direct=False, aliased=aliased) | ||
else: | ||
yield ResolutionStack(self, aliased=aliased) | ||
|
||
if self.is_wildcard_import(): | ||
for name, wildcard_import in self.names: | ||
if name in self.requesting_names: | ||
yield from [frame.parent_frame for frame in wildcard_import.resolved_type_frames] | ||
|
||
@property | ||
@reader | ||
def import_specifier(self) -> Editable: | ||
|
@@ -254,6 +286,13 @@ def import_specifier(self) -> Editable: | |
if is_match: | ||
return Name(import_specifier, self.file_node_id, self.ctx, self) | ||
|
||
@noapidoc | ||
def set_requesting_names(self, requester: PyImport): | ||
if requester.is_wildcard_import(): | ||
self.requesting_names.update(requester.requesting_names) | ||
else: | ||
self.requesting_names.add(requester.name) | ||
|
||
@reader | ||
def get_import_string( | ||
self, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
from typing import TYPE_CHECKING | ||
|
||
from codegen.sdk.codebase.factory.get_session import get_codebase_session | ||
from codegen.sdk.core.dataclasses.usage import UsageKind | ||
|
||
if TYPE_CHECKING: | ||
from codegen.sdk.core.symbol_groups.tuple import Tuple | ||
|
||
|
||
def test_unpacking_tuple(tmpdir) -> None: | ||
file = "test.py" | ||
# language=python | ||
content = """ | ||
_,symbol,_ = (a, b, c) | ||
""" | ||
with get_codebase_session(tmpdir=tmpdir, files={"test.py": content}) as codebase: | ||
file = codebase.get_file(file) | ||
|
||
symbol = file.get_symbol("symbol") | ||
|
||
len(symbol.usages) == 0 | ||
|
||
|
||
def test_unpacking_tuple_with_use(tmpdir) -> None: | ||
"""Tests that unused variables that are part of unpacking are in use""" | ||
file = "test.py" | ||
# language=python | ||
content = """ | ||
foo,symbol,bar = (a, b, c) | ||
|
||
test=symbol | ||
""" | ||
with get_codebase_session(tmpdir=tmpdir, files={"test.py": content}) as codebase: | ||
file = codebase.get_file(file) | ||
|
||
symbol = file.get_symbol("symbol") | ||
foo = file.get_symbol("foo") | ||
bar = file.get_symbol("bar") | ||
|
||
symbol_list: Tuple = symbol.value | ||
|
||
len(symbol.usages) == 1 | ||
symbol.usages[0].kind == UsageKind.BODY | ||
|
||
len(foo.usages) == 1 | ||
foo.usages[0].kind == UsageKind.ASSIGNMENT_SIBLING | ||
|
||
len(bar.usages) == 1 | ||
bar.usages[0].kind == UsageKind.ASSIGNMENT_SIBLING |
Uh oh!
There was an error while loading. Please reload this page.