Skip to content

Commit

Permalink
feat: complete mixins' fields
Browse files Browse the repository at this point in the history
  • Loading branch information
Desdaemon committed Nov 28, 2023
1 parent 82f25c1 commit 49f721e
Show file tree
Hide file tree
Showing 9 changed files with 143 additions and 95 deletions.
12 changes: 4 additions & 8 deletions src/analyze.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 {} '{}'",
Expand Down Expand Up @@ -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,
Expand Down
37 changes: 13 additions & 24 deletions src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -378,15 +377,10 @@ impl Backend {
}
pub async fn jump_def_field_name(&self, field: &str, model: &str) -> miette::Result<Option<Location>> {
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<Option<Location>> {
let name = some!(interner().get(name));
Expand All @@ -401,19 +395,14 @@ impl Backend {
range: Option<Range>,
) -> miette::Result<Option<Hover>> {
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),
}),
}))
}
Expand Down
5 changes: 4 additions & 1 deletion src/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -437,7 +437,10 @@ pub fn index_models(contents: &[u8]) -> miette::Result<Vec<Model>> {
})
} 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,
})
Expand Down
2 changes: 1 addition & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"));
}
Expand Down
19 changes: 14 additions & 5 deletions src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ pub struct Model {

#[derive(Clone, Debug)]
pub enum ModelType {
Base(ImStr),
Base { name: ImStr, ancestors: Vec<ImStr> },
Inherit(Vec<ImStr>),
}

Expand All @@ -41,6 +41,7 @@ pub type ModelName = Symbol<ModelEntry>;
pub struct ModelEntry {
pub base: Option<ModelLocation>,
pub descendants: Vec<ModelLocation>,
pub ancestors: Vec<ModelName>,
pub fields: Option<SymbolMap<FieldName, Field>>,
pub docstring: Option<Text>,
}
Expand Down Expand Up @@ -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();
Expand All @@ -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={}",
Expand All @@ -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))));
}
}
}
Expand Down
128 changes: 79 additions & 49 deletions src/python.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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
Expand All @@ -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;
}
}
}
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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<FieldName, Field>> {
pub fn populate_field_names<'model>(
&'model self,
model: ModelName,
locations_filter: &'model [Spur],
) -> Option<FutureOr<RefMut<'model, ModelName, ModelEntry>>> {
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::<Vec<_>>();

let query = ModelFields::query();
let mut tasks = tokio::task::JoinSet::<miette::Result<Vec<_>>>::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;
}
Expand Down Expand Up @@ -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::<Vec<_>>();

// 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<Option<Hover>> {
Expand Down
2 changes: 2 additions & 0 deletions src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ pub use visitor::PreTravel;

use crate::index::interner;

pub mod future;

#[macro_export]
macro_rules! some {
($opt:expr) => {
Expand Down
Loading

0 comments on commit 49f721e

Please sign in to comment.