From 49f721e61d36d66252af7cb7dd04243cb9e2b6c6 Mon Sep 17 00:00:00 2001 From: Viet Dinh <54ckb0y789@gmail.com> Date: Tue, 28 Nov 2023 17:10:51 -0500 Subject: [PATCH] feat: complete mixins' fields --- src/analyze.rs | 12 ++--- src/backend.rs | 37 +++++-------- src/index.rs | 5 +- src/main.rs | 2 +- src/model.rs | 19 +++++-- src/python.rs | 128 +++++++++++++++++++++++++++----------------- src/utils.rs | 2 + src/utils/future.rs | 23 ++++++++ src/xml.rs | 10 ++-- 9 files changed, 143 insertions(+), 95 deletions(-) create mode 100644 src/utils/future.rs diff --git a/src/analyze.rs b/src/analyze.rs index 87c0fc7..b7709b8 100644 --- a/src/analyze.rs +++ b/src/analyze.rs @@ -5,10 +5,9 @@ use log::debug; use tree_sitter::{Node, QueryCursor}; use odoo_lsp::{ - format_loc, index::interner, model::{FieldKind, ModelName}, - utils::{ByteRange, Erase, PreTravel, RangeExt, TryResultExt}, + utils::{ByteRange, Erase, PreTravel, RangeExt}, ImStr, }; use ts_macros::query; @@ -272,9 +271,6 @@ impl Backend { // What transforms value types? // 1. foo.bar; // foo: Model('t) => bar: Model('t).field('bar') - // 2. foo.mapped('field') - // foo: Model('t) => foo.mapped('field'): Model('t).mapped('field') - // debug!("type_of {} range={:?}", node.kind(), node.byte_range()); if node.byte_range().len() <= 64 { debug!( "type_of {} '{}'", @@ -327,9 +323,9 @@ impl Backend { }; let ident = String::from_utf8_lossy(ident); let ident = interner.get_or_intern(ident.as_ref()); - let mut entry = self.index.models.try_get_mut(&model).expect(format_loc!("deadlock"))?; - let fields = block_on(self.populate_field_names(&mut entry, interner.resolve(&model), &[])); - let field = fields.ok()?.get(&ident.into())?; + let entry = self.populate_field_names(model, &[])?; + let entry = block_on(entry); + let field = entry.fields.as_ref()?.get(&ident.into())?; match field.kind { FieldKind::Relational(model) => Some(Type::Model(interner.resolve(&model).into())), FieldKind::Value => None, diff --git a/src/backend.rs b/src/backend.rs index 66b1e7e..1bba6fc 100644 --- a/src/backend.rs +++ b/src/backend.rs @@ -232,16 +232,15 @@ impl Backend { return Ok(()); } let model_key = interner().get_or_intern(&model); - let Some(mut entry) = self - .index - .models - .try_get_mut(&model_key.into()) - .expect(format_loc!("deadlock")) - else { + let range = char_range_to_lsp_range(range, rope).ok_or_else(|| diagnostic!("range"))?; + let Some(entry) = self.populate_field_names(model_key.into(), &[]) else { return Ok(()); }; - let range = char_range_to_lsp_range(range, rope).ok_or_else(|| diagnostic!("range"))?; - let fields = self.populate_field_names(&mut entry, &model, &[]).await?; + let entry = entry.await; + let fields = entry + .fields + .as_ref() + .expect(format_loc!("(complete_field_name) no fields for {model}")); let completions = fields.iter().flat_map(|(key, _)| { let field_name = interner().resolve(&Spur::try_from_usize(*key as usize).unwrap()); field_name.contains(needle).then(|| CompletionItem { @@ -378,15 +377,10 @@ impl Backend { } pub async fn jump_def_field_name(&self, field: &str, model: &str) -> miette::Result> { let model_key = interner().get_or_intern(model); - let mut entry = some!(self - .index - .models - .try_get_mut(&model_key.into()) - .expect(format_loc!("deadlock"))); let field = some!(interner().get(field)); - let fields = self.populate_field_names(&mut entry, model, &[]).await?; - let field = some!(fields.get(&field.into())); - Ok(Some(field.location.clone().into())) + let entry = some!(self.populate_field_names(model_key.into(), &[])).await; + let field = some!(entry.fields.as_ref()).get(&field.into()); + Ok(Some(some!(field).location.clone().into())) } pub fn jump_def_template_name(&self, name: &str) -> miette::Result> { let name = some!(interner().get(name)); @@ -401,19 +395,14 @@ impl Backend { range: Option, ) -> miette::Result> { let model_key = interner().get_or_intern(model); - let mut entry = some!(self - .index - .models - .try_get_mut(&model_key.into()) - .expect(format_loc!("deadlock"))); let field = some!(interner().get(name)); - let fields = self.populate_field_names(&mut entry, model, &[]).await?; - let field = some!(fields.get(&field.into())); + let fields = some!(self.populate_field_names(model_key.into(), &[])).await; + let field = some!(fields.fields.as_ref()).get(&field.into()); Ok(Some(Hover { range, contents: HoverContents::Markup(MarkupContent { kind: MarkupKind::Markdown, - value: self.field_docstring(name, field, true), + value: self.field_docstring(name, some!(field), true), }), })) } diff --git a/src/index.rs b/src/index.rs index 13e2666..4705b82 100644 --- a/src/index.rs +++ b/src/index.rs @@ -437,7 +437,10 @@ pub fn index_models(contents: &[u8]) -> miette::Result> { }) } else { Some(Model { - type_: ModelType::Base(ImStr::from(String::from_utf8_lossy(base).as_ref())), + type_: ModelType::Base { + name: ImStr::from(String::from_utf8_lossy(base).as_ref()), + ancestors: inherits, + }, range, byte_range, }) diff --git a/src/main.rs b/src/main.rs index 1abe9b8..973cd17 100644 --- a/src/main.rs +++ b/src/main.rs @@ -322,7 +322,7 @@ impl LanguageServer for Backend { if uri.path().ends_with(".py") { let rope = self.document_map.get(uri.path()).unwrap(); let text = Cow::from(rope.slice(..)); - self.update_models(&Text::Full(text.into_owned()), &uri, rope.value().clone()) + self.update_models(Text::Full(text.into_owned()), &uri, rope.value().clone()) .await .report(|| format_loc!("update_models")); } diff --git a/src/model.rs b/src/model.rs index 5c2dd96..1775f4e 100644 --- a/src/model.rs +++ b/src/model.rs @@ -25,7 +25,7 @@ pub struct Model { #[derive(Clone, Debug)] pub enum ModelType { - Base(ImStr), + Base { name: ImStr, ancestors: Vec }, Inherit(Vec), } @@ -41,6 +41,7 @@ pub type ModelName = Symbol; pub struct ModelEntry { pub base: Option, pub descendants: Vec, + pub ancestors: Vec, pub fields: Option>, pub docstring: Option, } @@ -102,7 +103,7 @@ impl ModelIndex { let mut by_prefix = self.by_prefix.write().await; for item in items { match &item.type_ { - ModelType::Base(base) => { + ModelType::Base { name: base, ancestors } => { let name = interner.get_or_intern(base).into(); by_prefix.insert_str(base, name); let mut entry = self.entry(name).or_default(); @@ -114,6 +115,10 @@ impl ModelIndex { }, item.byte_range.clone(), )); + entry.ancestors = ancestors + .into_iter() + .map(|sym| interner.get_or_intern(&sym).into()) + .collect(); } else { debug!( "Conflicting bases:\nfirst={}\n new={}", @@ -137,15 +142,19 @@ impl ModelIndex { } } } - for inherit in inherits { - let inherit = interner.get_or_intern(inherit).into(); - self.entry(inherit).or_default().descendants.push(ModelLocation( + if let Some((primary, ancestors)) = inherits.split_first() { + let inherit = interner.get_or_intern(primary).into(); + let mut entry = self.entry(inherit).or_default(); + entry.descendants.push(ModelLocation( MinLoc { path, range: item.range, }, item.byte_range.clone(), )); + entry + .ancestors + .extend(ancestors.iter().map(|sym| ModelName::from(interner.get_or_intern(sym)))); } } } diff --git a/src/python.rs b/src/python.rs index 30d34af..2729a8b 100644 --- a/src/python.rs +++ b/src/python.rs @@ -1,17 +1,20 @@ use crate::{Backend, Text}; use std::borrow::Cow; +use std::future::ready; use std::path::Path; +use dashmap::mapref::one::RefMut; use lasso::{Key, Spur}; use log::{debug, error, warn}; use miette::{diagnostic, IntoDiagnostic}; use odoo_lsp::index::{index_models, interner, Module, Symbol, SymbolMap}; +use odoo_lsp::utils::future::FutureOr; use ropey::Rope; use tower_lsp::lsp_types::*; use tree_sitter::{Node, Parser, QueryCursor, Tree}; -use odoo_lsp::model::{Field, FieldKind, FieldName, ModelEntry, ModelLocation, ModelType}; +use odoo_lsp::model::{Field, FieldKind, ModelEntry, ModelLocation, ModelName, ModelType}; use odoo_lsp::utils::*; use odoo_lsp::{format_loc, some}; use ts_macros::query; @@ -152,11 +155,7 @@ impl Backend { self.update_ast(text, uri, rope.clone(), old_rope, parser)?; Ok(()) } - pub async fn update_models(&self, text: &Text, uri: &Url, rope: Rope) -> miette::Result<()> { - let mut parser = Parser::new(); - parser - .set_language(tree_sitter_python::language()) - .expect("bug: failed to init python parser"); + pub async fn update_models(&self, text: Text, uri: &Url, rope: Rope) -> miette::Result<()> { let text = match text { Text::Full(text) => Cow::from(text), // TODO: Limit range of possible updates based on delta @@ -167,21 +166,24 @@ impl Backend { self.index.models.append(path, interner(), true, &models).await; for model in models { match model.type_ { - ModelType::Base(model) => { - let model_key = interner().get(&model).unwrap(); + ModelType::Base { name, ancestors } => { + let model_key = interner().get(&name).unwrap(); let mut entry = self.index.models.try_get_mut(&model_key.into()).unwrap(); - self.populate_field_names(&mut entry, &model, &[path]).await?; + entry.ancestors = ancestors + .into_iter() + .map(|sym| interner().get_or_intern(&sym).into()) + .collect(); + drop(entry); + if let Some(fut) = self.populate_field_names(model_key.into(), &[path]) { + fut.await; + } } ModelType::Inherit(inherits) => { let Some(model) = inherits.first() else { continue }; let model_key = interner().get(model).unwrap(); - let mut entry = self - .index - .models - .try_get_mut(&model_key.into()) - .expect(format_loc!("deadlock")) - .unwrap(); - self.populate_field_names(&mut entry, model, &[path]).await?; + if let Some(fut) = self.populate_field_names(model_key.into(), &[path]) { + fut.await; + } } } } @@ -379,11 +381,10 @@ impl Backend { // lhs: foo // rhs: ba needle = rhs; - let model_name = interner().resolve(&model.key()); - let fields = self.populate_field_names(&mut model, model_name, &[]).await?; + let fields = some!(self.populate_field_names(model.key().clone().into(), &[])).await; let field = some!(interner().get(&lhs)); - let field = some!(fields.get(&field.into())); - let FieldKind::Relational(rel) = field.kind else { + let field = some!(fields.fields.as_ref()).get(&field.into()); + let FieldKind::Relational(rel) = some!(field).kind else { return Ok(None); }; drop(model); @@ -528,21 +529,23 @@ impl Backend { } Ok(None) } - pub async fn populate_field_names<'model>( - &self, - entry: &'model mut ModelEntry, - model_name: &str, - locations_filter: &[Spur], - ) -> miette::Result<&'model mut SymbolMap> { + pub fn populate_field_names<'model>( + &'model self, + model: ModelName, + locations_filter: &'model [Spur], + ) -> Option>> { + let model_name = interner().resolve(&model); + let entry = self.index.models.try_get_mut(&model).expect(format_loc!("deadlock"))?; if entry.fields.is_some() && locations_filter.is_empty() { - return Ok(entry.fields.as_mut().unwrap()); + return Some(FutureOr::Ready(ready(entry))); } let t0 = std::time::Instant::now(); let mut out = SymbolMap::default(); - let locations = entry.base.iter().chain(&entry.descendants); + let locations = entry.base.iter().chain(&entry.descendants).cloned().collect::>(); + let query = ModelFields::query(); let mut tasks = tokio::task::JoinSet::>>::new(); - for ModelLocation(location, byte_range) in locations.cloned() { + for ModelLocation(location, byte_range) in locations { if !locations_filter.is_empty() && !locations_filter.contains(&location.path) { continue; } @@ -641,29 +644,56 @@ impl Backend { Ok(fields) }); } - while let Some(fields) = tasks.join_next().await { - let fields = match fields { - Ok(Ok(ret)) => ret, - Ok(Err(err)) => { - warn!("{err}"); - continue; + Some(FutureOr::Pending(Box::pin(async move { + let ancestors = entry.ancestors.iter().cloned().collect::>(); + + // drop to prevent deadlock + drop(entry); + + // recursively get or populate ancestors' fields + for ancestor in ancestors { + if let Some(entry) = self.populate_field_names(ancestor, locations_filter) { + let entry = entry.await; + if let Some(fields) = entry.fields.as_ref() { + // TODO: Implement copy-on-write to increase reuse + out.extend(fields.iter().map(|(key, val)| (key.clone(), val.clone()))); + } } - Err(err) => { - warn!("join error {err}"); - continue; + } + + while let Some(fields) = tasks.join_next().await { + let fields = match fields { + Ok(Ok(ret)) => ret, + Ok(Err(err)) => { + warn!("{err}"); + continue; + } + Err(err) => { + warn!("join error {err}"); + continue; + } + }; + for (key, type_) in fields { + out.insert_checked(key.into_usize() as u64, type_); } - }; - for (key, type_) in fields { - out.insert_checked(key.into_usize() as u64, type_); } - } - log::info!( - "[{}] Populated {} fields in {}ms", - model_name, - out.len(), - t0.elapsed().as_millis() - ); - Ok(entry.fields.insert(out)) + + log::info!( + "[{}] Populated {} fields in {}ms", + model_name, + out.len(), + t0.elapsed().as_millis() + ); + + let mut entry = self + .index + .models + .try_get_mut(&model) + .expect(format_loc!("deadlock")) + .expect(format_loc!("no entry")); + entry.fields = Some(out); + entry + }))) } pub async fn python_hover(&self, params: HoverParams, rope: Rope) -> miette::Result> { diff --git a/src/utils.rs b/src/utils.rs index a335d7b..6ef1e07 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -13,6 +13,8 @@ pub use visitor::PreTravel; use crate::index::interner; +pub mod future; + #[macro_export] macro_rules! some { ($opt:expr) => { diff --git a/src/utils/future.rs b/src/utils/future.rs new file mode 100644 index 0000000..769f333 --- /dev/null +++ b/src/utils/future.rs @@ -0,0 +1,23 @@ +use std::{ + future::Future, + future::Ready, + pin::Pin, + task::{Context, Poll}, +}; + +use futures::{future::BoxFuture, FutureExt}; + +pub enum FutureOr<'a, T> { + Ready(Ready), + Pending(BoxFuture<'a, T>), +} + +impl Future for FutureOr<'_, T> { + type Output = T; + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match self.get_mut() { + Self::Ready(ready) => ready.poll_unpin(cx), + Self::Pending(fut) => fut.as_mut().poll(cx), + } + } +} diff --git a/src/xml.rs b/src/xml.rs index a78a2d5..f113a6d 100644 --- a/src/xml.rs +++ b/src/xml.rs @@ -15,7 +15,7 @@ use tower_lsp::lsp_types::*; use xmlparser::{StrSpan, Token, Tokenizer}; use odoo_lsp::record::Record; -use odoo_lsp::{format_loc, some, utils::*}; +use odoo_lsp::{some, utils::*}; enum RefKind { Ref(Spur), @@ -190,12 +190,8 @@ impl Backend { RefKind::Ref(relation) => { let model_key = some!(model_filter); let model = some!(interner().get(&model_key)); - let mut entry = some!(self - .index - .models - .try_get_mut(&model.into()) - .expect(format_loc!("deadlock"))); - let fields = self.populate_field_names(&mut entry, &model_key, &[]).await?; + let fields = some!(self.populate_field_names(model.into(), &[])).await; + let fields = some!(fields.fields.as_ref()); let Some(Field { kind: FieldKind::Relational(relation), ..