Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
282 changes: 206 additions & 76 deletions server/src/core/import_resolver.rs

Large diffs are not rendered by default.

9 changes: 4 additions & 5 deletions server/src/core/python_arch_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ impl PythonArchBuilder {
symbol.set_build_status(BuildSteps::ARCH, BuildStatus::DONE);
}

fn create_local_symbols_from_import_stmt(&mut self, session: &mut SessionInfo, from_stmt: Option<&Identifier>, name_aliases: &[Alias], level: Option<u32>, _range: &TextRange) -> Result<(), Error> {
fn create_local_symbols_from_import_stmt(&mut self, session: &mut SessionInfo, from_stmt: Option<&Identifier>, name_aliases: &[Alias], level: u32, _range: &TextRange) -> Result<(), Error> {
for import_name in name_aliases {
if import_name.name.as_str() == "*" {
if self.sym_stack.len() != 1 { //only at top level for now.
Expand All @@ -170,8 +170,7 @@ impl PythonArchBuilder {
&mut None).remove(0); //we don't need the vector with this call as there will be 1 result.
if !import_result.found {
self.entry_point.borrow_mut().not_found_symbols.insert(self.file.clone());
let file_tree_flattened = [import_result.file_tree.0.clone(), import_result.file_tree.1.clone()].concat();
self.file.borrow_mut().not_found_paths_mut().push((self.current_step, file_tree_flattened));
self.file.borrow_mut().not_found_paths_mut().push((self.current_step, import_result.file_tree.clone()));
continue;
}
let mut all_name_allowed = true;
Expand Down Expand Up @@ -255,10 +254,10 @@ impl PythonArchBuilder {
for stmt in nodes.iter() {
match stmt {
Stmt::Import(import_stmt) => {
self.create_local_symbols_from_import_stmt(session, None, &import_stmt.names, None, &import_stmt.range)?
self.create_local_symbols_from_import_stmt(session, None, &import_stmt.names, 0, &import_stmt.range)?
},
Stmt::ImportFrom(import_from_stmt) => {
self.create_local_symbols_from_import_stmt(session, import_from_stmt.module.as_ref(), &import_from_stmt.names, Some(import_from_stmt.level), &import_from_stmt.range)?
self.create_local_symbols_from_import_stmt(session, import_from_stmt.module.as_ref(), &import_from_stmt.names, import_from_stmt.level, &import_from_stmt.range)?
},
Stmt::AnnAssign(ann_assign_stmt) => {
self._visit_ann_assign(session, ann_assign_stmt);
Expand Down
2 changes: 1 addition & 1 deletion server/src/core/python_arch_builder_hooks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ impl PythonArchBuilderHooks {
main_odoo_symbol = main_ep.borrow().get_symbol();
}
if let Some(main_odoo_symbol) = main_odoo_symbol {
let werkzeug_patch = manual_import(session, &main_odoo_symbol, Some(full_path_monkeypatches), "werkzeug", None, None, &mut None);
let werkzeug_patch = manual_import(session, &main_odoo_symbol, Some(full_path_monkeypatches), "werkzeug", None, 0, &mut None);
for werkzeug_patch in werkzeug_patch {
if werkzeug_patch.found {
info!("monkeypatch manually found");
Expand Down
14 changes: 7 additions & 7 deletions server/src/core/python_arch_eval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,10 +146,10 @@ impl PythonArchEval {
fn visit_stmt(&mut self, session: &mut SessionInfo, stmt: &Stmt) {
match stmt {
Stmt::Import(import_stmt) => {
self.eval_symbols_from_import_stmt(session, None, &import_stmt.names, None, &import_stmt.range)
self.eval_symbols_from_import_stmt(session, None, &import_stmt.names, 0, &import_stmt.range)
},
Stmt::ImportFrom(import_from_stmt) => {
self.eval_symbols_from_import_stmt(session, import_from_stmt.module.as_ref(), &import_from_stmt.names, Some(import_from_stmt.level), &import_from_stmt.range)
self.eval_symbols_from_import_stmt(session, import_from_stmt.module.as_ref(), &import_from_stmt.names, import_from_stmt.level, &import_from_stmt.range)
},
Stmt::ClassDef(class_stmt) => {
self.visit_class_def(session, class_stmt);
Expand Down Expand Up @@ -379,7 +379,7 @@ impl PythonArchEval {
false
}

fn eval_symbols_from_import_stmt(&mut self, session: &mut SessionInfo, from_stmt: Option<&Identifier>, name_aliases: &[Alias], level: Option<u32>, _range: &TextRange) {
fn eval_symbols_from_import_stmt(&mut self, session: &mut SessionInfo, from_stmt: Option<&Identifier>, name_aliases: &[Alias], level: u32, _range: &TextRange) {
if name_aliases.len() == 1 && name_aliases[0].name.to_string() == "*" {
return;
}
Expand All @@ -392,7 +392,7 @@ impl PythonArchEval {
&mut Some(&mut self.diagnostics));

for _import_result in import_results.iter() {
let variable = self.sym_stack.last().unwrap().borrow().get_positioned_symbol(&_import_result.name, &_import_result.range);
let variable = self.sym_stack.last().unwrap().borrow().get_positioned_symbol(&_import_result.var_name, &_import_result.range);
let Some(variable) = variable.clone() else {
continue;
};
Expand All @@ -413,7 +413,7 @@ impl PythonArchEval {
}
}
} else {
let mut file_tree = [_import_result.file_tree.0.clone(), _import_result.file_tree.1.clone()].concat();
let mut file_tree = _import_result.file_tree.clone();
file_tree.extend(_import_result.name.split(".").map(|s| oyarn!("{}", s)));
self.file.borrow_mut().not_found_paths_mut().push((self.current_step, file_tree.clone()));
self.entry_point.borrow_mut().not_found_symbols.insert(self.file.clone());
Expand All @@ -428,9 +428,9 @@ impl PythonArchEval {
}

} else {
let mut file_tree = [_import_result.file_tree.0.clone(), _import_result.file_tree.1.clone()].concat();
let mut file_tree = _import_result.file_tree.clone();
file_tree.extend(_import_result.name.split(".").map(|s| oyarn!("{}", s)));
if BUILT_IN_LIBS.contains(&file_tree[0].as_str()) {
if session.sync_odoo.config.diag_missing_imports != DiagMissingImportsMode::All && BUILT_IN_LIBS.contains(&file_tree[0].as_str()) {
continue;
}
if !self.safe_import.last().unwrap() {
Expand Down
106 changes: 101 additions & 5 deletions server/src/features/ast_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ use std::cell::RefCell;
use crate::constants::{BuildStatus, BuildSteps, SymType};
use crate::core::evaluation::{AnalyzeAstResult, Context, ContextValue, Evaluation, ExprOrIdent};
use crate::core::odoo::SyncOdoo;
use crate::core::import_resolver::{resolve_from_stmt, resolve_import_stmt};
use crate::core::symbols::symbol::Symbol;
use crate::core::file_mgr::FileInfo;
use crate::threads::SessionInfo;
use crate::S;
use ruff_python_ast::name::Name;
use ruff_python_ast::visitor::{Visitor, walk_expr, walk_stmt, walk_alias, walk_except_handler, walk_parameter, walk_keyword, walk_pattern_keyword, walk_type_param, walk_pattern};
use ruff_python_ast::{Alias, ExceptHandler, Expr, ExprCall, Keyword, Parameter, Pattern, PatternKeyword, Stmt, TypeParam};
use ruff_text_size::{Ranged, TextRange, TextSize};
use ruff_python_ast::{Alias, AtomicNodeIndex, ExceptHandler, Expr, ExprCall, Identifier, Keyword, Parameter, Pattern, PatternKeyword, Stmt, TypeParam};
use ruff_text_size::{Ranged, TextRange, TextSize, TextSlice};
use tracing::warn;

pub struct AstUtils {}
Expand All @@ -23,6 +25,10 @@ impl AstUtils {
let file_info_ast = file_info.borrow().file_info_ast.clone();
let file_info_ast = file_info_ast.borrow();
for stmt in file_info_ast.get_stmts().unwrap().iter() {
//we have to handle imports differently as symbols are not visible in file.
if let Some(test_import) = Self::get_symbol_in_import(session, file_symbol, offset, stmt) {
return test_import;
}
(expr, call_expr) = ExprFinderVisitor::find_expr_at(stmt, offset);
if expr.is_some() {
break;
Expand Down Expand Up @@ -75,6 +81,99 @@ impl AstUtils {
}
}

fn get_symbol_in_import(session: &mut SessionInfo, file_symbol: &Rc<RefCell<Symbol>>, offset: u32, stmt: &Stmt) -> Option<(AnalyzeAstResult, Option<TextRange>, Option<ExprCall>)> {
match stmt {
//for all imports, the idea will be to check if we are on the last name of the import (then it has benn imported already and we can fallback on it),
//or then take the full tree to the offset symbol and resolve_import on it as it was in a 'from' clause.
Stmt::Import(stmt) => {
for alias in stmt.names.iter() {
if alias.range().contains(TextSize::new(offset)) {
let mut is_last = false;
let (to_analyze, range) = if alias.name.range().contains(TextSize::new(offset)) {
let next_dot_offset = alias.name.id.as_str()[offset as usize - alias.range().start().to_usize()..].find(".");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we get the range of the name, not the whole alias? They probably have the same start, it is probably okay either way. Maybe for readability?

alias.name.range().start().to_usize()

if let Some(next_dot_offset) = next_dot_offset {
let end = offset as usize + next_dot_offset;
let text = &alias.name.id.as_str()[..end - alias.range().start().to_usize()];
let start_range = text.rfind(".").map(|p| p+1).unwrap_or(0) + alias.name.range().start().to_usize();
(text, TextRange::new(TextSize::new(start_range as u32), TextSize::new(end as u32)))
} else {
is_last = true;
(alias.name.id.as_str(), alias.name.range())
}
} else if alias.asname.is_some() && alias.asname.as_ref().unwrap().range().contains(TextSize::new(offset)) {
is_last = true;
(alias.asname.as_ref().unwrap().id.as_str(), alias.asname.as_ref().unwrap().range())
} else {
return None;
};
if !is_last {
//we import as a from_stmt, to refuse import of variables, as the import stmt is not complete
let to_analyze = Identifier { id: Name::new(to_analyze), range: TextRange::new(TextSize::new(0), TextSize::new(0)), node_index: AtomicNodeIndex::dummy() };
let (from_symbol, _fallback_sym, _file_tree) = resolve_from_stmt(session, file_symbol, Some(&to_analyze), 0);
if let Some(symbol) = from_symbol {
let result = AnalyzeAstResult {
evaluations: vec![Evaluation::eval_from_symbol(&Rc::downgrade(&symbol), None)],
diagnostics: vec![],
};
return Some((result, Some(range), None));
}
} else {
let res = resolve_import_stmt(session, file_symbol, None, &[
Alias { //create a dummy alias with a asname to force full import
name: Identifier { id: Name::new(to_analyze), range: TextRange::new(TextSize::new(0), TextSize::new(0)), node_index: AtomicNodeIndex::dummy() },
asname: Some(Identifier { id: Name::new("fake_name"), range: alias.name.range().clone(), node_index: AtomicNodeIndex::dummy() }),
range: alias.range(),
node_index: AtomicNodeIndex::dummy()
}], 0, &mut None);
let res = res.into_iter().filter(|s| s.found).collect::<Vec<_>>();
if !res.is_empty() {
let result = AnalyzeAstResult {
evaluations: res.iter().map(
|s| Evaluation::eval_from_symbol(&Rc::downgrade(&s.symbol), None)
).collect(),
diagnostics: vec![],
};
return Some((result, Some(range), None));
}
}
return None;
}
}
},
Stmt::ImportFrom(stmt) => {
//only check module as names are already supported by default ast walking and name resolution
if stmt.module.is_some() && stmt.module.as_ref().unwrap().range().contains(TextSize::new(offset)) {
let module = stmt.module.as_ref().unwrap();
let (to_analyze, range) = if module.range().contains(TextSize::new(offset)) {
let next_dot_offset = module.id.as_str()[offset as usize - module.range().start().to_usize()..].find(".");
if let Some(next_dot_offset) = next_dot_offset {
let end = offset as usize + next_dot_offset;
let text = &module.id.as_str()[..end - module.range().start().to_usize()];
let start_range = text.rfind(".").map(|p| p+1).unwrap_or(0) + module.range().start().to_usize();
(text, TextRange::new(TextSize::new(start_range as u32), TextSize::new(end as u32)))
} else {
(module.id.as_str(), module.range())
}
} else {
return None;
};
let to_analyze = Identifier { id: Name::new(to_analyze), range: TextRange::new(TextSize::new(0), TextSize::new(0)), node_index: AtomicNodeIndex::dummy() };
let (from_symbol, _fallback_sym, _file_tree) = resolve_from_stmt(session, file_symbol, Some(&to_analyze), 0);
if let Some(symbol) = from_symbol {
let result = AnalyzeAstResult {
evaluations: vec![Evaluation::eval_from_symbol(&Rc::downgrade(&symbol), None)],
diagnostics: vec![],
};
return Some((result, Some(range), None));
}
}
},
_ => {
return None;
}
}
None
}
}


Expand Down Expand Up @@ -225,7 +324,6 @@ impl<'a> Visitor<'a> for ExprFinderVisitor<'a> {
let idents = match stmt {
Stmt::FunctionDef(stmt) => vec![&stmt.name],
Stmt::ClassDef(stmt) => vec![&stmt.name],
Stmt::ImportFrom(stmt) => if let Some(ref module) = stmt.module {vec![module]} else {vec![]},
Stmt::Global(stmt) => stmt.names.iter().collect(),
Stmt::Nonlocal(stmt) => stmt.names.iter().collect(),
_ => vec![],
Expand All @@ -237,8 +335,6 @@ impl<'a> Visitor<'a> for ExprFinderVisitor<'a> {
break;
}
}
} else {
walk_stmt(self, stmt);
}
}
}
Expand Down
33 changes: 18 additions & 15 deletions server/src/features/completion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -309,12 +309,13 @@ fn complete_assert_stmt(session: &mut SessionInfo<'_>, file: &Rc<RefCell<Symbol>
fn complete_import_stmt(session: &mut SessionInfo, file: &Rc<RefCell<Symbol>>, stmt_import: &StmtImport, offset: usize) -> Option<CompletionResponse> {
let mut items = vec![];
for alias in stmt_import.names.iter() {
if alias.name.range().end().to_usize() == offset {
let names = import_resolver::get_all_valid_names(session, file, None, S!(alias.name.id.as_str()), None);
for name in names {
if alias.name.range().start().to_usize() < offset && alias.name.range.end().to_usize() >= offset {
let to_complete = alias.name.id.to_string().get(0 .. offset - alias.name.range.start().to_usize()).unwrap_or("").to_string();
let names = import_resolver::get_all_valid_names(session, file, None, to_complete, 0, false);
for (name, sym_typ) in names {
items.push(CompletionItem {
label: name.to_string(),
kind: Some(lsp_types::CompletionItemKind::MODULE),
kind: Some(get_completion_item_kind(&sym_typ)),
..Default::default()
});
}
Expand All @@ -329,24 +330,26 @@ fn complete_import_stmt(session: &mut SessionInfo, file: &Rc<RefCell<Symbol>>, s
fn complete_import_from_stmt(session: &mut SessionInfo, file: &Rc<RefCell<Symbol>>, stmt_import: &StmtImportFrom, offset: usize) -> Option<CompletionResponse> {
let mut items = vec![];
if let Some(module) = stmt_import.module.as_ref() {
if module.range.end().to_usize() == offset && !stmt_import.names.is_empty() {
let names = import_resolver::get_all_valid_names(session, file, None, S!(stmt_import.names[0].name.id.as_str()), Some(stmt_import.level));
for name in names {
if module.range.start().to_usize() < offset && module.range.end().to_usize() >= offset {
let to_complete = module.id.to_string().get(0 .. offset - module.range.start().to_usize()).unwrap_or("").to_string();
let names = import_resolver::get_all_valid_names(session, file, Some(to_complete), S!(""), stmt_import.level, true);
for (name, sym_type) in names {
items.push(CompletionItem {
label: name.to_string(),
kind: Some(lsp_types::CompletionItemKind::MODULE),
kind: Some(get_completion_item_kind(&sym_type)),
..Default::default()
});
}
}
}
for alias in stmt_import.names.iter() {
if alias.name.range().end().to_usize() == offset {
let names = import_resolver::get_all_valid_names(session, file, stmt_import.module.as_ref(), S!(alias.name.id.as_str()), Some(stmt_import.level));
for name in names {
if alias.name.range().start().to_usize() < offset && alias.name.range.end().to_usize() >= offset {
let to_complete = alias.name.id.to_string().get(0 .. offset - alias.name.range.start().to_usize()).unwrap_or("").to_string();
let names = import_resolver::get_all_valid_names(session, file, stmt_import.module.as_ref().map(|m| m.id.to_string()), to_complete, stmt_import.level, false);
for (name, sym_type) in names {
items.push(CompletionItem {
label: name.to_string(),
kind: Some(lsp_types::CompletionItemKind::MODULE),
kind: Some(get_completion_item_kind(&sym_type)),
..Default::default()
});
}
Expand Down Expand Up @@ -1091,7 +1094,7 @@ fn build_completion_item_from_symbol(session: &mut SessionInfo, symbols: Vec<Rc<
description: label_details_description,
}),
detail: Some(type_details.iter().map(|detail| detail.to_string()).join(" | ").to_string()),
kind: Some(get_completion_item_kind(&symbols[0])),
kind: Some(get_completion_item_kind(&symbols[0].borrow().typ())),
sort_text: Some(get_sort_text_for_symbol(&symbols[0])),
documentation: Some(
lsp_types::Documentation::MarkupContent(MarkupContent {
Expand Down Expand Up @@ -1136,8 +1139,8 @@ fn get_sort_text_for_symbol(sym: &Rc<RefCell<Symbol>>/*, cl: Option<Rc<RefCell<S
text
}

fn get_completion_item_kind(symbol: &Rc<RefCell<Symbol>>) -> CompletionItemKind {
match symbol.borrow().typ() {
fn get_completion_item_kind(typ: &SymType) -> CompletionItemKind {
match typ {
SymType::ROOT => CompletionItemKind::TEXT,
SymType::DISK_DIR => CompletionItemKind::FOLDER,
SymType::NAMESPACE => CompletionItemKind::FOLDER,
Expand Down
Loading