diff --git a/.vscode/launch.json b/.vscode/launch.json index b4f69d35..e9abb25b 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -28,6 +28,56 @@ "cwd": "${workspaceFolder}/server", "console": "externalTerminal" }, + { + "name": "Launch Server (gdb)", + "type": "cppdbg", + "request": "launch", + "preLaunchTask": "cargo build", + "program": "${workspaceFolder}/server/target/debug/odoo_ls_server", + "args": ["--use-tcp"], + "cwd": "${workspaceFolder}/server", + "externalConsole": false, + "MIMode": "gdb", + "miDebuggerPath": "rust-gdb", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + }, + { + "description": "Set print depth limit to prevent infinite recursion", + "text": "set print max-depth 3", + "ignoreFailures": false + }, + ] + }, + { + "name": "Debug Test (gdb)", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/server/target/debug/deps/test_setup-22eac7e5dc85cac2", + "args": [], + "cwd": "${workspaceFolder}/server", + "environment": [ + { "name": "COMMUNITY_PATH", "value": "/path/to/odoo" } + ], + "externalConsole": false, + "MIMode": "gdb", + "miDebuggerPath": "rust-gdb", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + }, + { + "description": "Set print depth limit to prevent infinite recursion", + "text": "set print max-depth 3", + "ignoreFailures": false + }, + ] + }, { "name": "Launch Server (cppvsdbg)", "type": "cppvsdbg", diff --git a/changelog.md b/changelog.md index 6ae741a0..bcee1243 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,24 @@ # Changelog +## [1.1.2] - 2025/12/10 - CachedModel fixes + +### Server + +- Various fixes +- Fix and support for CachedModel introduced in 19.1 +- Use a deterministic job queue to avoid random errors caused by different order of symbols + - For that we replace the current HashSet with a FIFO one, so symbols are processed in the queue order + +### Fixes + +- Fix wiki link for configuration on welcome page +- Avoid having empty paths for addons or additional stubs in cli mode +- Avoid adding model dependencies in orm files to avoid rebuilding base files +- Avoid loading Models defined inside functions, e.g. tests. +- Avoid attempting to rebuild `__iter__` on external files, as their file infos are deleted +- Fix fetching symbols in inheritance tree by early stopping when one is found + + ## [1.1.1] - 2025/11/24 - Untitled files and Encoding ### Server diff --git a/server/Cargo.toml b/server/Cargo.toml index bfb82be3..5b9571cf 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "odoo_ls_server" -version = "1.1.1" +version = "1.1.2" edition = "2024" authors = ["Odoo"] readme = "../README.md" diff --git a/server/src/cli_backend.rs b/server/src/cli_backend.rs index 4fa5447b..b65db740 100644 --- a/server/src/cli_backend.rs +++ b/server/src/cli_backend.rs @@ -53,11 +53,11 @@ impl CliBackend { } let mut config = ConfigEntry::new(); - config.addons_paths = addons_paths.into_iter().map(|p| fs::canonicalize(p).unwrap_or_else(|_| PathBuf::from(S!(""))).sanitize()).collect(); + config.addons_paths = addons_paths.into_iter().map(|p| fs::canonicalize(p).unwrap_or_else(|_| PathBuf::from(S!(""))).sanitize()).filter(|x| !x.is_empty()).collect(); config.odoo_path = Some(fs::canonicalize(community_path.unwrap_or(S!(""))).unwrap_or_else(|_| PathBuf::from(S!(""))).sanitize()); config.diag_missing_imports = DiagMissingImportsMode::All; config.no_typeshed_stubs = self.cli.no_typeshed_stubs; - config.additional_stubs = self.cli.stubs.clone().unwrap_or(vec![]).into_iter().map(|p| fs::canonicalize(p).unwrap_or_else(|_| PathBuf::from(S!(""))).sanitize()).collect(); + config.additional_stubs = self.cli.stubs.clone().unwrap_or(vec![]).into_iter().map(|p| fs::canonicalize(p).unwrap_or_else(|_| PathBuf::from(S!(""))).sanitize()).filter(|x| !x.is_empty()).collect(); config.stdlib = self.cli.stdlib.clone().map(|p| fs::canonicalize(p).unwrap_or_else(|_| PathBuf::from(S!(""))).sanitize()).unwrap_or(S!("")); config.python_path = self.cli.python.clone().unwrap_or(get_python_command().unwrap_or(S!(""))); SyncOdoo::init(&mut session, config); diff --git a/server/src/constants.rs b/server/src/constants.rs index 8d2f3d14..325662b6 100644 --- a/server/src/constants.rs +++ b/server/src/constants.rs @@ -3,7 +3,7 @@ use core::fmt; pub const EXTENSION_NAME: &str = "Odoo"; -pub const EXTENSION_VERSION: &str = "1.1.1"; +pub const EXTENSION_VERSION: &str = "1.1.2"; pub const MAX_WATCHED_FILES_UPDATES_BEFORE_RESTART: u32 = 10; @@ -125,7 +125,7 @@ pub const BUILT_IN_LIBS: &[&str] = &["string", "re", "difflib", "textwrap", "un "cgi", "cgitb", "chunk", "crypt", "imghdr", "imp", "mailcap", "msilib", "nis", "nntplib", "optparse", "ossaudiodev", "pipes", "smtpd", "sndhdr", "spwd", "sunau", "telnetlib", "uu", "xdrlib", "struct", "codecs"]; -pub const CONFIG_WIKI_URL: &str = "https://github.com/odoo/odoo-ls/wiki/Configuration-files"; +pub const CONFIG_WIKI_URL: &str = "https://github.com/odoo/odoo-ls/wiki/3.-Configuration-files"; use std::sync::LazyLock; diff --git a/server/src/core/diagnostic_codes_list.rs b/server/src/core/diagnostic_codes_list.rs index 8b45d703..c9cfec03 100644 --- a/server/src/core/diagnostic_codes_list.rs +++ b/server/src/core/diagnostic_codes_list.rs @@ -172,6 +172,10 @@ OLS03022, DiagnosticSetting::Error, "Inverse field is not a Many2one field", * -> current_model is not right */ OLS03023, DiagnosticSetting::Error, "Inverse field {0} is not pointing to the current model {1}, but rather to {2}", +/** + * Models declared in a function are not supported by OdooLS. This info is indicating that no features will be enabled for this model. + */ +OLS03024, DiagnosticSetting::Info, "Models declared in a function are not supported by OdooLS. OdooLS will use it as a normal class for the rest of the function only", /** * Form is no longer available on odoo.tests.common, thus it should not be imported from there. */ diff --git a/server/src/core/odoo.rs b/server/src/core/odoo.rs index 7d870824..bf42bd51 100644 --- a/server/src/core/odoo.rs +++ b/server/src/core/odoo.rs @@ -6,13 +6,13 @@ use crate::core::xml_validation::XmlValidator; use crate::features::document_symbols::DocumentSymbolFeature; use crate::features::references::ReferenceFeature; use crate::features::workspace_symbols::WorkspaceSymbolFeature; +use crate::fifo_ptr_weak_hash_set::FifoPtrWeakHashSet; use crate::threads::SessionInfo; use crate::features::completion::CompletionFeature; use crate::features::definition::DefinitionFeature; use crate::features::hover::HoverFeature; use std::collections::HashMap; use std::cell::RefCell; -use std::ffi::OsStr; use std::rc::{Rc, Weak}; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{Arc, Mutex}; @@ -27,7 +27,6 @@ use serde_json::Value; use tracing::{error, warn, info, trace}; use std::collections::HashSet; -use weak_table::PtrWeakHashSet; use std::process::Command; use std::fs; use std::path::{Path, PathBuf}; @@ -86,9 +85,9 @@ pub struct SyncOdoo { pub current_request_id: Option, pub running_request_ids: Arc>>, //Arc to Server mutex for cancellation support pub watched_file_updates: u32, - rebuild_arch: PtrWeakHashSet>>, - rebuild_arch_eval: PtrWeakHashSet>>, - rebuild_validation: PtrWeakHashSet>>, + rebuild_arch: FifoPtrWeakHashSet>, + rebuild_arch_eval: FifoPtrWeakHashSet>, + rebuild_validation: FifoPtrWeakHashSet>, pub state_init: InitState, pub must_reload_paths: Vec<(Weak>, String)>, pub load_odoo_addons: bool, //indicate if we want to load odoo addons or not @@ -131,9 +130,9 @@ impl SyncOdoo { current_request_id: None, running_request_ids: Arc::new(Mutex::new(vec![])), watched_file_updates: 0, - rebuild_arch: PtrWeakHashSet::new(), - rebuild_arch_eval: PtrWeakHashSet::new(), - rebuild_validation: PtrWeakHashSet::new(), + rebuild_arch: FifoPtrWeakHashSet::new(), + rebuild_arch_eval: FifoPtrWeakHashSet::new(), + rebuild_validation: FifoPtrWeakHashSet::new(), state_init: InitState::NOT_READY, must_reload_paths: vec![], load_odoo_addons: true, @@ -161,9 +160,9 @@ impl SyncOdoo { session.sync_odoo.stdlib_dir = SyncOdoo::default_stdlib(); session.sync_odoo.modules = HashMap::new(); session.sync_odoo.models = HashMap::new(); - session.sync_odoo.rebuild_arch = PtrWeakHashSet::new(); - session.sync_odoo.rebuild_arch_eval = PtrWeakHashSet::new(); - session.sync_odoo.rebuild_validation = PtrWeakHashSet::new(); + session.sync_odoo.rebuild_arch = FifoPtrWeakHashSet::new(); + session.sync_odoo.rebuild_arch_eval = FifoPtrWeakHashSet::new(); + session.sync_odoo.rebuild_validation = FifoPtrWeakHashSet::new(); session.sync_odoo.state_init = InitState::NOT_READY; session.sync_odoo.load_odoo_addons = true; session.sync_odoo.need_rebuild = false; @@ -537,7 +536,7 @@ impl SyncOdoo { let mut selected_sym: Option>> = None; let mut selected_count: u32 = 999999999; let mut current_count: u32; - for sym in set { + for sym in set.iter() { current_count = 0; let file = sym.borrow().get_file().unwrap().upgrade().unwrap(); let file = file.borrow(); diff --git a/server/src/core/python_arch_eval.rs b/server/src/core/python_arch_eval.rs index b1e35339..3c1f3021 100644 --- a/server/src/core/python_arch_eval.rs +++ b/server/src/core/python_arch_eval.rs @@ -711,8 +711,17 @@ impl PythonArchEval { self.visit_sub_stmts(session, &class_stmt.body); self.sym_stack.pop(); if !self.sym_stack[0].borrow().is_external() && self.sym_stack[0].borrow().get_entry().is_some_and(|e| e.borrow().typ == EntryPointType::MAIN) { - let odoo_builder_diags = PythonOdooBuilder::new(class_sym_rc).load(session); - self.diagnostics.extend(odoo_builder_diags); + if class_sym_rc.borrow().get_in_parents(&vec![SymType::FUNCTION], true).is_some() { + if let Some(diagnostic) = create_diagnostic(&session, DiagnosticCode::OLS03024, &[]) { + self.diagnostics.push(Diagnostic { + range: FileMgr::textRange_to_temporary_Range(&class_stmt.name.range), + ..diagnostic + }); + } + } else { + let odoo_builder_diags = PythonOdooBuilder::new(class_sym_rc).load(session); + self.diagnostics.extend(odoo_builder_diags); + } } session.current_noqa = old_noqa; } @@ -824,8 +833,11 @@ impl PythonArchEval { if symbol_type.typ() == SymType::CLASS { let (iter, _) = symbol_type.get_member_symbol(session, &S!("__iter__"), None, true, false, false, false, false); if iter.len() == 1 { - SyncOdoo::build_now(session, &iter[0], BuildSteps::ARCH_EVAL); - SyncOdoo::build_now(session, &iter[0], BuildSteps::VALIDATION); + if !iter[0].borrow().is_external() { //we can't rebuild functions of external files + SyncOdoo::build_now(session, &iter[0], BuildSteps::ARCH); + SyncOdoo::build_now(session, &iter[0], BuildSteps::ARCH_EVAL); + SyncOdoo::build_now(session, &iter[0], BuildSteps::VALIDATION); + } if iter[0].borrow().evaluations().is_some() && iter[0].borrow().evaluations().unwrap().len() == 1 { let iter = iter[0].borrow(); let eval_iter = &iter.evaluations().unwrap()[0]; diff --git a/server/src/core/python_arch_eval_hooks.rs b/server/src/core/python_arch_eval_hooks.rs index 44c69c38..ef5b7b8d 100644 --- a/server/src/core/python_arch_eval_hooks.rs +++ b/server/src/core/python_arch_eval_hooks.rs @@ -754,9 +754,9 @@ impl PythonArchEvalHooks { let Some(ContextValue::STRING(s)) = context.get(&S!("args")) else { return res }; - let maybe_model = session.sync_odoo.models.get(&oyarn!("{}", s)); + let maybe_model = session.sync_odoo.models.get(&oyarn!("{}", s)).cloned(); let has_class_in_parents = scope.as_ref().map(|scope| scope.borrow().get_in_parents(&vec![SymType::CLASS], true).is_some()).unwrap_or(false); - if maybe_model.map(|m| m.borrow_mut().has_symbols()).unwrap_or(false) { + if maybe_model.as_ref().map(|m| m.borrow_mut().has_symbols()).unwrap_or(false) { let Some(model) = maybe_model else {unreachable!()}; let module = context.get(&S!("module")); let from_module = if let Some(ContextValue::MODULE(m)) = module { @@ -764,13 +764,21 @@ impl PythonArchEvalHooks { } else { None }; - if let Some(scope) = scope + if let Some(scope_file) = scope .and_then(|s| s.borrow().get_file()) .and_then(|w| w.upgrade()) { - let env_files = session.sync_odoo.get_symbol(session.sync_odoo.config.odoo_path.as_ref().unwrap(), &(vec![Sy!("odoo"), Sy!("api")], vec![]), u32::MAX); - let env_file = env_files.last().unwrap(); - if !Rc::ptr_eq(env_file, &scope) { - scope.borrow_mut().add_model_dependencies(model); + //exclude orm files + if compare_semver(session.sync_odoo.full_version.as_str(), "18.1") < Ordering::Equal { + let env_files = session.sync_odoo.get_symbol(session.sync_odoo.config.odoo_path.as_ref().unwrap(), &(vec![Sy!("odoo"), Sy!("api")], vec![]), u32::MAX); + let env_file = env_files.last().unwrap(); + if !Rc::ptr_eq(env_file, &scope_file) { + scope_file.borrow_mut().add_model_dependencies(&model); + } + } else { + let tree = scope_file.borrow().get_main_entry_tree(session); + if !tree.0.starts_with(&[Sy!("odoo"), Sy!("orm")]) { + scope_file.borrow_mut().add_model_dependencies(&model); + } } } let model = model.clone(); diff --git a/server/src/core/python_odoo_builder.rs b/server/src/core/python_odoo_builder.rs index b6ff7178..67f039da 100644 --- a/server/src/core/python_odoo_builder.rs +++ b/server/src/core/python_odoo_builder.rs @@ -351,71 +351,80 @@ impl PythonOdooBuilder { } } - /* true if the symbol inherit from odoo.models.BaseModel. symbol must be the data of rc_symbol and must be a Class */ + /* true if the symbol inherits from BaseModel, Model, TransientModel, or CachedModel. symbol must be the data of rc_symbol and must be a Class */ fn test_symbol_is_model(&mut self, session: &mut SessionInfo, diagnostics: &mut Vec) -> bool { let symbol = &self.symbol.clone(); let odoo_symbol_tree = symbol.borrow().get_main_entry_tree(session); let mut sym = symbol.borrow_mut(); - if ( - compare_semver(session.sync_odoo.full_version.as_str(), "18.1") == Ordering::Less && odoo_symbol_tree.0.len() == 2 - && odoo_symbol_tree.1.len() == 1 - && odoo_symbol_tree.0[0] == "odoo" - && odoo_symbol_tree.0[1] == "models" - && (odoo_symbol_tree.1[0] == "BaseModel" || odoo_symbol_tree.1[0] == "Model" || odoo_symbol_tree.1[0] == "TransientModel")) - || (compare_semver(session.sync_odoo.full_version.as_str(), "18.1") >= Ordering::Equal && odoo_symbol_tree.0.len() == 3 - && odoo_symbol_tree.1.len() == 1 - && odoo_symbol_tree.0[0] == "odoo" - && odoo_symbol_tree.0[1] == "orm" - && odoo_symbol_tree.0[2] == "models" - && (odoo_symbol_tree.1[0] == "BaseModel" || odoo_symbol_tree.1[0] == "Model" || odoo_symbol_tree.1[0] == "TransientModel")) - || (compare_semver(session.sync_odoo.full_version.as_str(), "18.3") >= Ordering::Equal && odoo_symbol_tree.0.len() == 3 - && odoo_symbol_tree.1.len() == 1 - && odoo_symbol_tree.0[0] == "odoo" - && odoo_symbol_tree.0[1] == "orm" - && odoo_symbol_tree.0[2] == "models_transient" - && odoo_symbol_tree.1[0] == "TransientModel") { - //we don't want to compare these classes with themselves (> 18.3) + if [&[Sy!("BaseModel")], &[Sy!("Model")], &[Sy!("TransientModel")]].iter().any(|x| x == &odoo_symbol_tree.1.as_slice()) && + // [BaseModel|Model|TransientModel] + (( // < 18.1, and we are on odoo.models. + compare_semver(session.sync_odoo.full_version.as_str(), "18.1") == Ordering::Less + && odoo_symbol_tree.0 == &["odoo", "models"] + ) || ( // >= 18.1, and we are on odoo.orm.models. + compare_semver(session.sync_odoo.full_version.as_str(), "18.1") >= Ordering::Equal + && odoo_symbol_tree.0 == &["odoo", "orm", "models"] + )) + // >= 18.3, and we are on odoo.orm.models_transient.TransientModel + || ( + compare_semver(session.sync_odoo.full_version.as_str(), "18.3") >= Ordering::Equal + && odoo_symbol_tree.1 == &["TransientModel"] + && odoo_symbol_tree.0 == &["odoo", "orm", "models_transient"] + ) + // we are on odoo.orm.models_cached.CachedModel + || ( + compare_semver(session.sync_odoo.full_version.as_str(), "19.1") >= Ordering::Equal + && odoo_symbol_tree.1 == &["CachedModel"] + && odoo_symbol_tree.0 == &["odoo", "orm", "models_cached"] + ) + { + //we don't want to compare these classes with themselves, so we exit early return false; - } else { - if sym.as_class_sym().bases.is_empty() { - return false; - } - let mut base_model_tree = (vec![Sy!("odoo"), Sy!("models")], vec![Sy!("BaseModel")]); - let mut model_tree = (vec![Sy!("odoo"), Sy!("models")], vec![Sy!("Model")]); - let mut transient_tree = (vec![Sy!("odoo"), Sy!("models")], vec![Sy!("TransientModel")]); - if compare_semver(session.sync_odoo.full_version.as_str(), "18.1") >= Ordering::Equal { - base_model_tree = (vec![Sy!("odoo"), Sy!("orm"), Sy!("models")], vec![Sy!("BaseModel")]); - model_tree = (vec![Sy!("odoo"), Sy!("orm"), Sy!("models")], vec![Sy!("Model")]); - transient_tree = (vec![Sy!("odoo"), Sy!("orm"), Sy!("models")], vec![Sy!("TransientModel")]); - } - if compare_semver(session.sync_odoo.full_version.as_str(), "18.3") >= Ordering::Equal { - transient_tree = (vec![Sy!("odoo"), Sy!("orm"), Sy!("models_transient")], vec![Sy!("TransientModel")]); - } - let base_model = session.sync_odoo.get_symbol(session.sync_odoo.config.odoo_path.as_ref().unwrap(), &base_model_tree, u32::MAX); - let model = session.sync_odoo.get_symbol(session.sync_odoo.config.odoo_path.as_ref().unwrap(), &model_tree, u32::MAX); - let transient = session.sync_odoo.get_symbol(session.sync_odoo.config.odoo_path.as_ref().unwrap(), &transient_tree, u32::MAX); - if base_model.is_empty() || model.is_empty() || transient.is_empty() { - //one of them is not already loaded, but that's not really an issue, as now odoo step has been merged - //with arch eval step, some files will be odooed before loading the orm fully. In this case we should - //ignore this error. Moreover if a base is set on the class, it means that the base has been loaded, so - //it is NOT a model. - // session.send_notification(ShowMessage::METHOD, ShowMessageParams{ - // typ: MessageType::ERROR, - // message: "Odoo base models are not found. OdooLS will be unable to generate valid diagnostics".to_string() - // }); - return false; - } - let base_model = base_model[0].clone(); - let model = model[0].clone(); - let transient = transient[0].clone(); - if Rc::ptr_eq(symbol, &base_model) || - Rc::ptr_eq(symbol, &model) || - Rc::ptr_eq(symbol, &transient) { - return false; - } - if !sym.as_class_sym().inherits(&base_model, &mut None) { + } + if sym.as_class_sym().bases.is_empty() { + return false; + } + let mut base_model_tree = (vec![Sy!("odoo"), Sy!("models")], vec![Sy!("BaseModel")]); + let mut model_tree = (vec![Sy!("odoo"), Sy!("models")], vec![Sy!("Model")]); + let mut transient_tree = (vec![Sy!("odoo"), Sy!("models")], vec![Sy!("TransientModel")]); + if compare_semver(session.sync_odoo.full_version.as_str(), "18.1") >= Ordering::Equal { + base_model_tree = (vec![Sy!("odoo"), Sy!("orm"), Sy!("models")], vec![Sy!("BaseModel")]); + model_tree = (vec![Sy!("odoo"), Sy!("orm"), Sy!("models")], vec![Sy!("Model")]); + transient_tree = (vec![Sy!("odoo"), Sy!("orm"), Sy!("models")], vec![Sy!("TransientModel")]); + } + if compare_semver(session.sync_odoo.full_version.as_str(), "18.3") >= Ordering::Equal { + transient_tree = (vec![Sy!("odoo"), Sy!("orm"), Sy!("models_transient")], vec![Sy!("TransientModel")]); + } + let base_model_syms = session.sync_odoo.get_symbol(session.sync_odoo.config.odoo_path.as_ref().unwrap(), &base_model_tree, u32::MAX); + let model_syms = session.sync_odoo.get_symbol(session.sync_odoo.config.odoo_path.as_ref().unwrap(), &model_tree, u32::MAX); + let transient_syms = session.sync_odoo.get_symbol(session.sync_odoo.config.odoo_path.as_ref().unwrap(), &transient_tree, u32::MAX); + if base_model_syms.is_empty() || model_syms.is_empty() || transient_syms.is_empty() { + //one of them is not already loaded, but that's not really an issue, as now odoo step has been merged + //with arch eval step, some files will be odooed before loading the orm fully. In this case we should + //ignore this error. Moreover if a base is set on the class, it means that the base has been loaded, so + //it is NOT a model. + // session.send_notification(ShowMessage::METHOD, ShowMessageParams{ + // typ: MessageType::ERROR, + // message: "Odoo base models are not found. OdooLS will be unable to generate valid diagnostics".to_string() + // }); + return false; + } + if Rc::ptr_eq(symbol, &base_model_syms[0]) || + Rc::ptr_eq(symbol, &model_syms[0]) || + Rc::ptr_eq(symbol, &transient_syms[0]) + { + return false; + } + if compare_semver(session.sync_odoo.full_version.as_str(), "19.1") >= Ordering::Equal{ + let cached_model_tree = (vec![Sy!("odoo"), Sy!("orm"), Sy!("models_cached")], vec![Sy!("CachedModel")]); + let cached_model = session.sync_odoo.get_symbol(session.sync_odoo.config.odoo_path.as_ref().unwrap(), &cached_model_tree, u32::MAX); + if cached_model.is_empty() || Rc::ptr_eq(symbol, &cached_model[0]){ return false; } + + } + if !sym.as_class_sym().inherits(&base_model_syms[0], &mut None) { + return false; } sym.as_class_sym_mut()._model = Some(ModelData::new()); let register = sym.get_symbol(&(vec![], vec![Sy!("_register")]), u32::MAX); diff --git a/server/src/core/symbols/symbol.rs b/server/src/core/symbols/symbol.rs index c6445d2d..7f9dffd3 100644 --- a/server/src/core/symbols/symbol.rs +++ b/server/src/core/symbols/symbol.rs @@ -2812,7 +2812,7 @@ impl Symbol { ) -> (Vec>>, Vec) { let mut result: Vec>> = vec![]; let mut visited_symbols: PtrWeakHashSet>> = PtrWeakHashSet::new(); - let mut extend_result = |syms: Vec>>| { + let mut extend_result = |syms: Vec>>, result: &mut Vec>>, visited_symbols: &mut PtrWeakHashSet>>| { syms.iter().for_each(|sym|{ if !visited_symbols.contains(sym){ visited_symbols.insert(sym.clone()); @@ -2826,7 +2826,7 @@ impl Symbol { if let Some(mod_sym) = mod_sym { if !only_fields { if all { - extend_result(vec![mod_sym]); + extend_result(vec![mod_sym], &mut result, &mut visited_symbols); } else { return (vec![mod_sym], diagnostics); } @@ -2842,7 +2842,7 @@ impl Symbol { } if !content_syms.is_empty() { if all { - extend_result(content_syms); + extend_result(content_syms, &mut result, &mut visited_symbols); } else { return (content_syms, diagnostics); } @@ -2865,7 +2865,7 @@ impl Symbol { let (attributs, att_diagnostic) = model_symbol.borrow()._get_member_symbol_helper(session, name, None, true, only_fields, only_methods, all, false, visited_classes); diagnostics.extend(att_diagnostic); if all { - extend_result(attributs); + extend_result(attributs, &mut result, &mut visited_symbols); } else { if !attributs.is_empty() { return (attributs, diagnostics); @@ -2883,7 +2883,7 @@ impl Symbol { let (attributs, att_diagnostic) = model_symbol.borrow()._get_member_symbol_helper(session, name, None, true, true, only_methods, all, false, visited_classes); diagnostics.extend(att_diagnostic); if all { - extend_result(attributs); + extend_result(attributs, &mut result, &mut visited_symbols); } else { if !attributs.is_empty() { return (attributs, diagnostics); @@ -2894,7 +2894,7 @@ impl Symbol { } } } - if self.typ() == SymType::CLASS { + if self.typ() == SymType::CLASS && result.is_empty() { // if we already have something, do not go up in bases for base in self.as_class_sym().bases.iter() { let base = match base.upgrade(){ Some(b) => b, @@ -2908,7 +2908,7 @@ impl Symbol { diagnostics.extend(s_diagnostic); if !s.is_empty() { if all { - extend_result(s); + extend_result(s, &mut result, &mut visited_symbols); } else { return (s, diagnostics); } diff --git a/server/src/fifo_ptr_weak_hash_set.rs b/server/src/fifo_ptr_weak_hash_set.rs new file mode 100644 index 00000000..3027d71b --- /dev/null +++ b/server/src/fifo_ptr_weak_hash_set.rs @@ -0,0 +1,57 @@ +use std::{collections::VecDeque, hash::RandomState, rc::{Rc, Weak}}; +use weak_table::{PtrWeakHashSet}; + +#[derive(Debug)] +pub struct FifoPtrWeakHashSet { + set: PtrWeakHashSet, RandomState>, + queue: VecDeque>, +} + +impl FifoPtrWeakHashSet +{ + pub fn new() -> Self { + Self { + set: PtrWeakHashSet::new(), + queue: VecDeque::new(), + } + } + + pub fn insert(&mut self, v: Rc) { + if !self.set.insert(v.clone()) { //it returns true if absent (wrong doc) + self.queue.push_back(Rc::downgrade(&v)); + } + } + + pub fn iter(&self) -> impl Iterator> { + self.queue.iter().filter_map(|weak| weak.upgrade()) + } + + pub fn contains(&self, v: &Rc) -> bool { + self.set.contains(v) + } + + pub fn clear(&mut self) { + self.set.clear(); + self.queue.clear(); + } + + pub fn remove(&mut self, v: &Rc) -> bool { + if self.set.remove(v) { + let weak = Rc::downgrade(v); + let pos = self.queue.iter().position(|x| Weak::ptr_eq(x, &weak)); + if let Some(pos) = pos { + self.queue.remove(pos); + } + return true + } + false + } + + pub fn is_empty(&self) -> bool { + self.set.is_empty() + } + + pub fn len(&self) -> usize { + self.set.len() + } +} diff --git a/server/src/lib.rs b/server/src/lib.rs index 959a3044..50dc7876 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -5,6 +5,7 @@ pub mod constants; pub mod core; pub mod threads; pub mod features; +pub mod fifo_ptr_weak_hash_set; pub mod server; pub mod tasks; pub mod utils;