Skip to content

Commit

Permalink
Add implicit imports
Browse files Browse the repository at this point in the history
  • Loading branch information
Glyphack committed Jun 2, 2024
1 parent c10395d commit 1cd159f
Show file tree
Hide file tree
Showing 12 changed files with 6,249 additions and 52 deletions.
6,065 changes: 6,065 additions & 0 deletions test.log

Large diffs are not rendered by default.

15 changes: 15 additions & 0 deletions typechecker/src/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ impl BuildManager {
let module = EnderpyFile::new(path, true);
self.modules.insert(module.path(), module);
}
for (_, implicit_import) in imported_module.1.implicit_imports.iter() {
let module = EnderpyFile::new(&implicit_import.path, true);
self.modules.insert(module.path(), module);
}
}
log::debug!("Imports resolved");
for mut module in self.modules.iter_mut() {
Expand Down Expand Up @@ -198,6 +202,17 @@ impl BuildManager {
let e = EnderpyFile::new(resolved_path, true);
files_to_resolve.push(e);
}

for (_, implicit_import) in resolved.implicit_imports.iter() {
let source = match std::fs::read_to_string(&implicit_import.path) {
Ok(source) => source,
Err(e) => {
panic!("cannot read implicit import");
}
};
let e = EnderpyFile::new(&implicit_import.path, true);
files_to_resolve.push(e);
}
}
}
}
Expand Down
14 changes: 14 additions & 0 deletions typechecker/src/checker.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
use std::path::PathBuf;

use ast::{Expression, Statement};
use enderpy_python_parser as parser;
use enderpy_python_parser::ast::{self, *};

use super::{type_evaluator::TypeEvaluator, types::PythonType};
use crate::types::ModuleRef;
use crate::{
ast_visitor::TraversalVisitor, diagnostic::CharacterSpan, file::EnderpyFile,
symbol_table::SymbolTable,
Expand Down Expand Up @@ -164,6 +167,17 @@ impl TraversalVisitor for TypeChecker {
for alias in _i.names.iter() {
self.infer_name_type(&alias.name, alias.node.start, alias.node.end)
}

// Just to show type module when modules are hovered in imports.
let start = _i.node.start + 5;
let stop = start + _i.module.len() as u32 + 1;
self.types.insert(Interval {
start,
stop,
val: PythonType::Module(ModuleRef {
module_path: PathBuf::new(),
}),
});
}

fn visit_if(&mut self, i: &parser::ast::If) {
Expand Down
4 changes: 3 additions & 1 deletion typechecker/src/semantic_analyzer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,8 @@ impl TraversalVisitor for SemanticAnalyzer {
declaration_path,
import_from_node: None,
import_node: Some(i.clone()),
symbol_name: Some(alias.name()),
symbol_name: None,
module_name: Some(alias.name()),
import_result,
});

Expand Down Expand Up @@ -377,6 +378,7 @@ impl TraversalVisitor for SemanticAnalyzer {
import_from_node: Some(_i.clone()),
import_node: None,
symbol_name: Some(alias.name()),
module_name: None,
import_result: module_import_result.clone(),
});

Expand Down
3 changes: 3 additions & 0 deletions typechecker/src/symbol_table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,9 @@ pub struct Alias {
/// Name of the imported symbol in case of ImportFrom
/// e.g. From bar import baz -> baz is the symbol name
pub symbol_name: Option<String>,
/// Name of imported module in case of Import
/// e.g. import os.path -> os.path is the module name
pub module_name: Option<String>,
/// The result of the import
pub import_result: ImportResult,
}
Expand Down
167 changes: 119 additions & 48 deletions typechecker/src/type_evaluator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use super::{
types::{CallableType, LiteralValue, PythonType},
};
use crate::{
ruff_python_import_resolver::import_result::ImportResult,
semantic_analyzer::get_member_access_info,
symbol_table::{self, Class, Declaration, LookupSymbolRequest, SymbolTable, SymbolTableNode},
types::{ClassType, TypeVar},
Expand Down Expand Up @@ -306,6 +307,15 @@ impl TypeEvaluator {
}
}
}
PythonType::Module(module) => {
todo!();
let module_sym_table =
self.get_symbol_table_of(&module.module_path).unwrap();
let typ = self
.infer_type_from_symbol_table(&a.attr, None, module_sym_table, Some(0))
.unwrap();
Ok(typ)
}
_ => Ok(PythonType::Unknown),
}
}
Expand Down Expand Up @@ -444,35 +454,12 @@ impl TypeEvaluator {
name,
symbol_table.file_path,
);
let result = match symbol_table.lookup_in_scope(lookup_request.clone()) {
Some(symbol) => self.get_symbol_node_type(symbol),
None => {
// Check if there's any import * and try to find the symbol in those files
let mut found = None;
for star_import in symbol_table.star_imports.iter() {
for resolved in star_import.resolved_paths.iter() {
let star_import_sym_table = self.get_symbol_table_of(resolved);
let Some(sym_table) = star_import_sym_table else {
continue;
};
let res = sym_table.lookup_in_scope(LookupSymbolRequest {
name,
// Look in global scope of the other file
scope: Some(0),
});
if res.is_some() {
found = res;
break;
}
}
}
match found {
Some(s) => self.get_symbol_node_type(s),
None => bail!("name {name} is not defined"),
}
}
};
result
let result =
match self.lookup_in_table_and_star_imports(symbol_table, lookup_request.clone()) {
Some(s) => s,
None => bail!("name {name} is not defined"),
};
self.get_symbol_node_type(result)
}

/// Get the type of a symbol node based on declarations
Expand Down Expand Up @@ -561,10 +548,35 @@ impl TypeEvaluator {
}
}
Declaration::Alias(a) => {
let resolved = self.resolve_alias(a, symbol_table);
match resolved {
Some(node) => self.get_symbol_node_type(node),
None => Ok(PythonType::Unknown),
log::debug!("evaluating alias {:?}", a);
// when symbol is an alias that is named to that symbol return Module type
// e.g. from . import path as _path
// then type of _path is Module(path)
match &a.symbol_name {
Some(name) => {
let resolved = self.resolve_alias(name, &a.import_result, symbol_table);
match resolved {
Some(node) => self.get_symbol_node_type(node),
None => Ok(PythonType::Unknown),
}
}
None => {
let mut found_module: Option<PythonType> = None;
for resolved_path in a.import_result.resolved_paths.iter() {
let Some(sym_table_alias_pointing_to) =
self.get_symbol_table_of(resolved_path)
else {
break;
};
found_module = Some(PythonType::Module(crate::types::ModuleRef {
module_path: sym_table_alias_pointing_to.file_path.clone(),
}));
}
match found_module {
Some(s) => Ok(s),
None => Ok(PythonType::Unknown),
}
}
}
}
Declaration::TypeParameter(_) => Ok(PythonType::Unknown),
Expand Down Expand Up @@ -990,25 +1002,18 @@ impl TypeEvaluator {
false
}

// Follows Alias declaration and resolves it to a class declaration
// Follows Alias declaration and resolves it to another symbol node
// It searches through imported symbol tables for the module alias imports
// and resolves the alias to the class declaration
// TODO: refactor all aliases and not only classes
fn resolve_alias(
&self,
a: &symbol_table::Alias,
// a: &symbol_table::Alias,
imported_symbol_name: &str,
import_result: &ImportResult,
alias_symbol_table: &SymbolTable,
) -> Option<&symbol_table::SymbolTableNode> {
log::debug!("resolving alias: {:?}", a);
let class_name = match a.symbol_name {
Some(ref name) => name.clone(),
None => panic!("Alias {:?} has no symbol name", a.import_node),
};

for resolved_path in a.import_result.resolved_paths.iter() {
for resolved_path in import_result.resolved_paths.iter() {
log::debug!("checking path {:?}", resolved_path);
// TODO: This is a hack to resolve Iterator alias in sys/__init__.pyi
let symbol_table_with_alias_def = if class_name == "Iterator" {
let symbol_table_with_alias_def = if imported_symbol_name == "Iterator" {
self.imported_symbol_tables
.iter()
.find(|symbol_table| symbol_table.file_path.ends_with("stdlib/typing.pyi"))
Expand All @@ -1025,12 +1030,12 @@ impl TypeEvaluator {
// then it's cyclic and do not resolve
if alias_symbol_table.file_path.as_path() == resolved_path {
log::debug!("alias resolution skipped");
return None;
continue;
}

let resolved_symbol =
symbol_table_with_alias_def?.lookup_in_scope(LookupSymbolRequest {
name: &class_name,
name: imported_symbol_name,
scope: None,
});

Expand All @@ -1041,6 +1046,38 @@ impl TypeEvaluator {
return resolved_symbol;
}
}

for (_, implicit_import) in import_result.implicit_imports.iter() {
let resolved_path = &implicit_import.path;
log::debug!("checking path {:?}", resolved_path);
// TODO: This is a hack to resolve Iterator alias in sys/__init__.pyi
let symbol_table_with_alias_def = if imported_symbol_name == "Iterator" {
self.imported_symbol_tables
.iter()
.find(|symbol_table| symbol_table.file_path.ends_with("stdlib/typing.pyi"))
} else {
self.get_symbol_table_of(resolved_path)
};

if symbol_table_with_alias_def.is_none() {
panic!("Symbol table not found for alias: {:?}", resolved_path);
}

let resolved_symbol = self.lookup_in_table_and_star_imports(
symbol_table_with_alias_def?,
LookupSymbolRequest {
name: imported_symbol_name,
scope: None,
},
);

log::debug!("symbols {:?}", symbol_table_with_alias_def?.global_scope());

if resolved_symbol.is_some() {
log::debug!("alias resolved to {:?}", resolved_symbol);
return resolved_symbol;
}
}
None
}

Expand Down Expand Up @@ -1080,4 +1117,38 @@ impl TypeEvaluator {
) -> PythonType {
f_type.return_type.clone()
}

fn lookup_in_table_and_star_imports<'a>(
&'a self,
symbol_table: &'a SymbolTable,
lookup_request: LookupSymbolRequest,
) -> Option<&SymbolTableNode> {
let find_in_current_symbol_table = symbol_table.lookup_in_scope(lookup_request.clone());
if find_in_current_symbol_table.is_some() {
return find_in_current_symbol_table;
}

log::debug!(
"did not find symbol {} in symbol table, checking star imports",
lookup_request.name
);
// Check if there's any import * and try to find the symbol in those files
let mut found = None;
for star_import in symbol_table.star_imports.iter() {
log::debug!("checking star imports {:?}", star_import);
for resolved in star_import.resolved_paths.iter() {
log::debug!("checking path {:?}", resolved);
let star_import_sym_table = self.get_symbol_table_of(resolved);
let Some(sym_table) = star_import_sym_table else {
panic!("symbol table of star import not found at {:?}", resolved);
};
let res = sym_table.lookup_in_scope(lookup_request.clone());
if res.is_some() {
found = res;
break;
}
}
}
found
}
}
8 changes: 8 additions & 0 deletions typechecker/src/types.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use is_macro::Is;
use std::fmt::Display;
use std::path::PathBuf;

use enderpy_python_parser::ast;

Expand All @@ -22,6 +23,7 @@ pub enum PythonType {
/// In type inference the values are not assumed to be literals unless they
/// are explicitly declared as such.
KnownValue(KnownValue),
Module(ModuleRef),
/// Union type
MultiValue(Vec<PythonType>),
Callable(Box<CallableType>),
Expand Down Expand Up @@ -190,11 +192,17 @@ impl Display for LiteralValue {
}
}

#[derive(Debug, Clone, Eq, PartialEq)]
pub struct ModuleRef {
pub module_path: PathBuf,
}

impl Display for PythonType {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let type_str = match self {
PythonType::None => "None",
PythonType::Any => "Any",
PythonType::Module(_) => "Module",
PythonType::Unknown => "Unknown",
PythonType::Callable(callable_type) => {
let fmt = format!("(function) {}", callable_type.name.as_str());
Expand Down
1 change: 1 addition & 0 deletions typechecker/test_data/inputs/import_star_test/a.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from .b import *
import os

print(in_b)
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
---
source: typechecker/src/build.rs
description: "from .b import *\n\nprint(in_b)\n"
description: "from .b import *\nimport os\n\nprint(in_b)\n\nos.path.dirname(\"\")\n"
expression: result
---
[ImportResult { is_relative: true, is_import_found: true, is_partly_resolved: false, is_namespace_package: false, is_init_file_present: false, is_stub_package: false, import_type: Local, resolved_paths: ["test_data/inputs/import_star_test/b.py"], search_path: Some("test_data/inputs/import_star_test"), is_stub_file: false, is_native_lib: false, is_stdlib_typeshed_file: false, is_third_party_typeshed_file: false, is_local_typings_file: false, implicit_imports: ImplicitImports({}), filtered_implicit_imports: ImplicitImports({}), non_stub_import_result: None, py_typed_info: None, package_directory: None }]
Symbols in global
os - declaration: Alias - properties: SymbolFlags(0x0)
- Declarations:
--: Alias

Scopes:

Expand Down
Loading

0 comments on commit 1cd159f

Please sign in to comment.