diff --git a/server/src/core/python_arch_eval.rs b/server/src/core/python_arch_eval.rs index b906a44c..0f210f29 100644 --- a/server/src/core/python_arch_eval.rs +++ b/server/src/core/python_arch_eval.rs @@ -9,7 +9,6 @@ use lsp_types::{Diagnostic, DiagnosticSeverity, NumberOrString, Position, Range} use tracing::{debug, trace, warn}; use crate::constants::*; -use crate::core::import_resolver::resolve_import_stmt; use crate::core::odoo::SyncOdoo; use crate::core::symbols::symbol::Symbol; use crate::core::evaluation::Evaluation; @@ -22,9 +21,9 @@ use super::config::DiagMissingImportsMode; use super::entry_point::EntryPoint; use super::evaluation::{ContextValue, EvaluationSymbolPtr, EvaluationSymbolWeak}; use super::file_mgr::FileMgr; -use super::import_resolver::ImportResult; use super::python_arch_eval_hooks::PythonArchEvalHooks; use super::symbols::function_symbol::FunctionSymbol; +use super::symbols::variable_symbol::{ImportInformation, VariableSymbol}; #[derive(Debug, Clone)] @@ -179,122 +178,25 @@ impl PythonArchEval { } } - fn _match_diag_config(&self, odoo: &mut SyncOdoo, symbol: &Rc>) -> bool { - let import_diag_level = &odoo.config.diag_missing_imports; - if *import_diag_level == DiagMissingImportsMode::None { - return false - } - if *import_diag_level == DiagMissingImportsMode::All { - return true - } - if *import_diag_level == DiagMissingImportsMode::OnlyOdoo { - let tree = symbol.borrow().get_tree(); - if tree.0.len() > 0 && tree.0[0] == "odoo" { - return true; - } - } - false - } - - ///Follow the evaluations of sym_ref, evaluate files if needed, and return true if the end evaluation contains from_sym - fn check_for_loop_evaluation(&mut self, session: &mut SessionInfo, sym_ref: Rc>, from_sym: &Rc>) -> bool { - let sym_ref_cl = sym_ref.clone(); - let syms_followed = Symbol::follow_ref(&EvaluationSymbolPtr::WEAK(EvaluationSymbolWeak::new( - Rc::downgrade(&sym_ref_cl), None, false - )), session, &mut None, false, false, None, &mut self.diagnostics); - for sym in syms_followed.iter() { - let sym = sym.upgrade_weak(); - if let Some(sym) = sym { - if sym.borrow().evaluations().is_some() && sym.borrow().evaluations().unwrap().is_empty() { - let file_sym = sym_ref.borrow().get_file(); - if file_sym.is_some() { - let rc_file_sym = file_sym.as_ref().unwrap().upgrade().unwrap(); - if rc_file_sym.borrow_mut().build_status(BuildSteps::ARCH_EVAL) == BuildStatus::PENDING && session.sync_odoo.is_in_rebuild(&rc_file_sym, BuildSteps::ARCH_EVAL) { - session.sync_odoo.remove_from_rebuild_arch_eval(&rc_file_sym); - let mut builder = PythonArchEval::new(self.entry_point.clone(), rc_file_sym); - builder.eval_arch(session); - if self.check_for_loop_evaluation(session, sym_ref.clone(), from_sym) { - return true; - } - } - } - } - if Rc::ptr_eq(&sym, &from_sym) { - return true; - } - } - } - false - } - fn eval_symbols_from_import_stmt(&mut self, session: &mut SessionInfo, from_stmt: Option<&Identifier>, name_aliases: &[Alias], level: Option, range: &TextRange) { if name_aliases.len() == 1 && name_aliases[0].name.to_string() == "*" { return; } - let import_results: Vec = resolve_import_stmt( - session, - &self.file, - from_stmt, - name_aliases, - level, - &mut Some(&mut self.diagnostics)); - - for _import_result in import_results.iter() { - let variable = self.sym_stack.last().unwrap().borrow_mut().get_positioned_symbol(&_import_result.name, &_import_result.range); + for alias in name_aliases { + let var_name = alias.asname.as_ref().unwrap_or(&alias.name).to_string().clone(); + let variable = self.sym_stack.last().unwrap().borrow_mut().get_positioned_symbol(&var_name, &alias.range); let Some(variable) = variable.clone() else { continue; }; - if _import_result.found { - let import_sym_ref = _import_result.symbol.clone(); - let has_loop = self.check_for_loop_evaluation(session, import_sym_ref, &variable); - if !has_loop { //anti-loop. We want to be sure we are not evaluating to the same sym - variable.borrow_mut().set_evaluations(vec![Evaluation::eval_from_symbol(&Rc::downgrade(&_import_result.symbol), None)]); - let file_of_import_symbol = _import_result.symbol.borrow().get_file(); - if let Some(import_file) = file_of_import_symbol { - let import_file = import_file.upgrade().unwrap(); - if !Rc::ptr_eq(&self.file, &import_file) { - self.file.borrow_mut().add_dependency(&mut import_file.borrow_mut(), self.current_step, BuildSteps::ARCH); - } - } - } else { - let mut file_tree = [_import_result.file_tree.0.clone(), _import_result.file_tree.1.clone()].concat(); - file_tree.extend(_import_result.name.split(".").map(str::to_string)); - 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()); - if self._match_diag_config(session.sync_odoo, &_import_result.symbol) { - self.diagnostics.push(Diagnostic::new( - Range::new(Position::new(_import_result.range.start().to_u32(), 0), Position::new(_import_result.range.end().to_u32(), 0)), - Some(DiagnosticSeverity::WARNING), - Some(NumberOrString::String(S!("OLS20004"))), - Some(EXTENSION_NAME.to_string()), - format!("Failed to evaluate import {}", file_tree.clone().join(".")), - None, - None, - )); - } - } - - } else { - let mut file_tree = [_import_result.file_tree.0.clone(), _import_result.file_tree.1.clone()].concat(); - file_tree.extend(_import_result.name.split(".").map(str::to_string)); - if BUILT_IN_LIBS.contains(&file_tree[0].as_str()) { - continue; - } - if !self.safe_import.last().unwrap() { - 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()); - if self._match_diag_config(session.sync_odoo, &_import_result.symbol) { - self.diagnostics.push(Diagnostic::new( - Range::new(Position::new(_import_result.range.start().to_u32(), 0), Position::new(_import_result.range.end().to_u32(), 0)), - Some(DiagnosticSeverity::WARNING), - Some(NumberOrString::String(S!("OLS20001"))), - Some(EXTENSION_NAME.to_string()), - format!("{} not found", file_tree.clone().join(".")), - None, - None, - )); - } - } + variable.borrow_mut().as_variable_mut().import_information = Some(ImportInformation { + from: from_stmt.cloned(), + level: level, + alias: alias.clone(), + import_step: self.current_step.clone() + }); + //In workspace we evaluate imports as we want to raise diagnostics if not found. If outside of the workspace, let's keep only information to lazy load them + if self.file.borrow().in_workspace() || !self.file.borrow().is_external() { + VariableSymbol::load_from_import_information(session, variable, &self.file, &self.entry_point, &mut self.diagnostics); } } } diff --git a/server/src/core/symbols/symbol.rs b/server/src/core/symbols/symbol.rs index e418d7e4..8f497cf3 100644 --- a/server/src/core/symbols/symbol.rs +++ b/server/src/core/symbols/symbol.rs @@ -1716,11 +1716,10 @@ impl Symbol { pub fn next_refs(session: &mut SessionInfo, symbol_rc: Rc>, context: &mut Option, symbol_context: &Context, stop_on_type: bool, diagnostics: &mut Vec) -> VecDeque { //if current symbol is a descriptor, we have to resolve __get__ method before going further let mut res = VecDeque::new(); - let symbol = &*symbol_rc.borrow(); if let Some(base_attr) = symbol_context.get(&S!("base_attr")) { let base_attr = base_attr.as_symbol().upgrade(); if let Some(base_attr) = base_attr { - let attribute_type_sym = symbol; + let attribute_type_sym = &*symbol_rc.borrow(); //TODO shouldn't we set the from_module in the call to get_member_symbol? let get_method = attribute_type_sym.get_member_symbol(session, &S!("__get__"), None, true, false, true, false).0.first().cloned(); match get_method { @@ -1758,7 +1757,16 @@ impl Symbol { } } } - if let Symbol::Variable(v) = symbol { + let symbol_type = symbol_rc.borrow().typ().clone(); + if symbol_type == SymType::VARIABLE { + if symbol_rc.borrow().as_variable().is_import_variable.clone() { + let file = symbol_rc.borrow().get_file().unwrap().clone(); + let file = file.upgrade().expect("invalid weak value"); + let entry_point = file.borrow().get_entry().unwrap(); + VariableSymbol::load_from_import_information(session, symbol_rc.clone(), &file, &entry_point, diagnostics); + } + } + if let Symbol::Variable(v) = &*symbol_rc.borrow() { for eval in v.evaluations.iter() { let ctx = &mut Some(symbol_context.clone().into_iter().chain(context.clone().unwrap_or(HashMap::new()).into_iter()).collect::>()); let mut sym = eval.symbol.get_symbol(session, ctx, diagnostics, None); @@ -1816,21 +1824,21 @@ impl Symbol { continue; } let sym_rc = sym.unwrap(); - let sym = sym_rc.borrow(); - match *sym { - Symbol::Variable(ref v) => { - if stop_on_type && matches!(next_ref_weak.is_instance(), Some(false)) && !v.is_import_variable { + let sym_type = sym_rc.borrow().typ(); + match sym_type { + SymType::VARIABLE => { + if stop_on_type && matches!(next_ref_weak.is_instance(), Some(false)) && !sym_rc.borrow().as_variable().is_import_variable { continue; } - if stop_on_value && v.evaluations.len() == 1 && v.evaluations[0].value.is_some() { + if stop_on_value && sym_rc.borrow().as_variable().evaluations.len() == 1 && sym_rc.borrow().as_variable().evaluations[0].value.is_some() { continue; } - if max_scope.is_some() && !sym.has_rc_in_parents(max_scope.as_ref().unwrap().clone(), true) { + if max_scope.is_some() && !sym_rc.borrow().has_rc_in_parents(max_scope.as_ref().unwrap().clone(), true) { continue; } - if v.evaluations.is_empty() && can_eval_external { + if sym_rc.borrow().as_variable().evaluations.is_empty() && can_eval_external { //no evaluation? let's check that the file has been evaluated - let file_symbol = sym.get_file(); + let file_symbol = sym_rc.borrow().get_file(); if let Some(file_symbol) = file_symbol { if file_symbol.upgrade().expect("invalid weak value").borrow().build_status(BuildSteps::ARCH) == BuildStatus::PENDING && session.sync_odoo.is_in_rebuild(&file_symbol.upgrade().unwrap(), BuildSteps::ARCH_EVAL) { //TODO check ARCH ? @@ -1849,7 +1857,7 @@ impl Symbol { } } }, - Symbol::Class(_) => { + SymType::CLASS => { //On class, follow descriptor declarations let next_sym_refs = Symbol::next_refs(session, sym_rc.clone(), context, &next_ref_weak.context, stop_on_type, &mut vec![]); if !next_sym_refs.is_empty() { @@ -2121,6 +2129,7 @@ impl Symbol { pub fn is_field(&self, session: &mut SessionInfo) -> bool { match self.typ() { SymType::VARIABLE => { + //TODO resolve lazy loading if let Some(evals) = self.evaluations().as_ref() { for eval in evals.iter() { let symbol = eval.symbol.get_symbol(session, &mut None, &mut vec![], None); @@ -2154,6 +2163,7 @@ impl Symbol { pub fn is_specific_field(&self, session: &mut SessionInfo, field_names: &[&str]) -> bool { match self.typ() { SymType::VARIABLE => { + //TODO resolve lazy loading if let Some(evals) = self.evaluations().as_ref() { for eval in evals.iter() { let symbol = eval.symbol.get_symbol(session, &mut None, &mut vec![], None); diff --git a/server/src/core/symbols/variable_symbol.rs b/server/src/core/symbols/variable_symbol.rs index ff3b1fcb..1a94ce79 100644 --- a/server/src/core/symbols/variable_symbol.rs +++ b/server/src/core/symbols/variable_symbol.rs @@ -1,10 +1,20 @@ +use lsp_types::{Diagnostic, DiagnosticSeverity, NumberOrString, Position, Range}; +use ruff_python_ast::{Alias, Identifier}; use ruff_text_size::TextRange; -use crate::{constants::flatten_tree, core::evaluation::Evaluation, threads::SessionInfo}; +use crate::{constants::{flatten_tree, BuildStatus, BuildSteps, BUILT_IN_LIBS, EXTENSION_NAME}, core::{config::DiagMissingImportsMode, entry_point::EntryPoint, evaluation::{Evaluation, EvaluationSymbolPtr, EvaluationSymbolWeak}, import_resolver::{resolve_import_stmt, ImportResult}, odoo::SyncOdoo, python_arch_eval::PythonArchEval}, threads::SessionInfo, S}; use std::{cell::RefCell, rc::{Rc, Weak}}; use super::symbol::Symbol; +#[derive(Debug)] +pub struct ImportInformation { + pub from: Option, + pub alias: Alias, + pub level: Option, + pub import_step: BuildSteps, +} + #[derive(Debug)] pub struct VariableSymbol { pub name: String, @@ -14,6 +24,7 @@ pub struct VariableSymbol { pub weak_self: Option>>, pub parent: Option>>, pub is_import_variable: bool, + pub import_information: Option, pub is_parameter: bool, pub evaluations: Vec, //Vec, because sometimes a single allocation can be ambiguous, like ''' a = "5" if X else 5 ''' pub range: TextRange, @@ -31,6 +42,7 @@ impl VariableSymbol { parent: None, range, is_import_variable: false, + import_information: None, is_parameter: false, evaluations: vec![], } @@ -62,5 +74,111 @@ impl VariableSymbol { } vec![] } + + ///Follow the evaluations of sym_ref, evaluate files if needed, and return true if the end evaluation contains from_sym + fn check_for_loop_evaluation(session: &mut SessionInfo, sym_ref: Rc>, from_sym: &Rc>, entry_point: &Rc>, diagnostics: &mut Vec) -> bool { + let sym_ref_cl = sym_ref.clone(); + let syms_followed = Symbol::follow_ref(&EvaluationSymbolPtr::WEAK(EvaluationSymbolWeak::new( + Rc::downgrade(&sym_ref_cl), None, false + )), session, &mut None, false, false, None, diagnostics); + for sym in syms_followed.iter() { + let sym = sym.upgrade_weak(); + if let Some(sym) = sym { + if sym.borrow().evaluations().is_some() && sym.borrow().evaluations().unwrap().is_empty() { + let file_sym = sym_ref.borrow().get_file(); + if file_sym.is_some() { + let rc_file_sym = file_sym.as_ref().unwrap().upgrade().unwrap(); + if rc_file_sym.borrow_mut().build_status(BuildSteps::ARCH_EVAL) == BuildStatus::PENDING && session.sync_odoo.is_in_rebuild(&rc_file_sym, BuildSteps::ARCH_EVAL) { + session.sync_odoo.remove_from_rebuild_arch_eval(&rc_file_sym); + let mut builder = PythonArchEval::new(entry_point.clone(), rc_file_sym); + builder.eval_arch(session); + if VariableSymbol::check_for_loop_evaluation(session, sym_ref.clone(), from_sym, entry_point, diagnostics) { + return true; + } + } + } + } + if Rc::ptr_eq(&sym, &from_sym) { + return true; + } + } + } + false + } + + fn _match_diag_config(odoo: &mut SyncOdoo, symbol: &Rc>) -> bool { + let import_diag_level = &odoo.config.diag_missing_imports; + if *import_diag_level == DiagMissingImportsMode::None { + return false + } + if *import_diag_level == DiagMissingImportsMode::All { + return true + } + if *import_diag_level == DiagMissingImportsMode::OnlyOdoo { + let tree = symbol.borrow().get_tree(); + if tree.0.len() > 0 && tree.0[0] == "odoo" { + return true; + } + } + false + } + + pub fn load_from_import_information(session: &mut SessionInfo, variable: Rc>, file: &Rc>, entry_point: &Rc>, diagnostics: &mut Vec) { + let Some(import_info) = variable.borrow_mut().as_variable_mut().import_information.take() else { + return; + }; + let import_results: Vec = resolve_import_stmt( + session, + file, + import_info.from.as_ref(), + &[import_info.alias], + import_info.level, + &mut Some(diagnostics)); + + for _import_result in import_results.iter() { + if _import_result.found { + let import_sym_ref = _import_result.symbol.clone(); + let has_loop = VariableSymbol::check_for_loop_evaluation(session, import_sym_ref, &variable, entry_point, diagnostics); + if !has_loop { //anti-loop. We want to be sure we are not evaluating to the same sym + variable.borrow_mut().set_evaluations(vec![Evaluation::eval_from_symbol(&Rc::downgrade(&_import_result.symbol), None)]); + //let's not set dependencies as import_information are only set for files outside of workspace that doesn't have + //any file watcher and so can't be updated, only reloaded + // let file_of_import_symbol = _import_result.symbol.borrow().get_file(); + // if let Some(import_file) = file_of_import_symbol { + // let import_file = import_file.upgrade().unwrap(); + // if !Rc::ptr_eq(file, &import_file) { + // file.borrow_mut().add_dependency(&mut import_file.borrow_mut(), self.current_step, BuildSteps::ARCH); + // } + // } + } else { + let mut file_tree = [_import_result.file_tree.0.clone(), _import_result.file_tree.1.clone()].concat(); + file_tree.extend(_import_result.name.split(".").map(str::to_string)); + file.borrow_mut().not_found_paths_mut().push((import_info.import_step, file_tree.clone())); + entry_point.borrow_mut().not_found_symbols.insert(file.clone()); + if VariableSymbol::_match_diag_config(session.sync_odoo, &_import_result.symbol) { + diagnostics.push(Diagnostic::new( + Range::new(Position::new(_import_result.range.start().to_u32(), 0), Position::new(_import_result.range.end().to_u32(), 0)), + Some(DiagnosticSeverity::WARNING), + Some(NumberOrString::String(S!("OLS20004"))), + Some(EXTENSION_NAME.to_string()), + format!("Failed to evaluate import {}", file_tree.clone().join(".")), + None, + None, + )); + } + } + + } else { + let mut file_tree = [_import_result.file_tree.0.clone(), _import_result.file_tree.1.clone()].concat(); + file_tree.extend(_import_result.name.split(".").map(str::to_string)); + if BUILT_IN_LIBS.contains(&file_tree[0].as_str()) { + continue; + } + file.borrow_mut().not_found_paths_mut().push((import_info.import_step, file_tree.clone())); + entry_point.borrow_mut().not_found_symbols.insert(file.clone()); + //No need to set not found diagnostic, as we are not in workspace + } + } + } } \ No newline at end of file