Skip to content

Commit 004d33c

Browse files
committed
feat: multi-inheritance
1 parent f6213a0 commit 004d33c

File tree

9 files changed

+129
-113
lines changed

9 files changed

+129
-113
lines changed

examples/two/test.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,7 @@ class Quux(models.Model):
1313
_name = 'quux'
1414
_inherit = 'bar'
1515

16-
what = fields.Many2one(comodel_name='bar')
16+
what = fields.Many2one(comodel_name='bar')
17+
18+
class Moo(models.Model):
19+
_inherit = ['bar', 'quux']

src/index.rs

Lines changed: 45 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use tree_sitter::{Query, QueryCursor};
1313
use xmlparser::{Token, Tokenizer};
1414

1515
use crate::format_loc;
16-
use crate::model::{Model, ModelIndex};
16+
use crate::model::{Model, ModelId, ModelIndex};
1717
use crate::record::Record;
1818
use crate::utils::{offset_range_to_lsp_range, ByteOffset, CharOffset, ImStr, RangeExt};
1919

@@ -185,7 +185,7 @@ impl ModuleIndex {
185185
}
186186

187187
async fn add_root_xml(path: PathBuf, module_name: ImStr) -> miette::Result<Output> {
188-
let path_uri = path.to_string_lossy();
188+
let uri: Url = format!("file://{}", path.to_string_lossy()).parse().into_diagnostic()?;
189189
let file = tokio::fs::read(&path)
190190
.await
191191
.into_diagnostic()
@@ -201,7 +201,7 @@ async fn add_root_xml(path: PathBuf, module_name: ImStr) -> miette::Result<Outpu
201201
let record = Record::from_reader(
202202
CharOffset(span.start()),
203203
module_name.clone(),
204-
&path_uri,
204+
uri.clone(),
205205
&mut reader,
206206
rope.clone(),
207207
)?;
@@ -210,7 +210,7 @@ async fn add_root_xml(path: PathBuf, module_name: ImStr) -> miette::Result<Outpu
210210
let template = Record::template(
211211
CharOffset(span.start()),
212212
module_name.clone(),
213-
&path_uri,
213+
uri.clone(),
214214
&mut reader,
215215
rope.clone(),
216216
)?;
@@ -238,72 +238,62 @@ fn model_query() -> &'static Query {
238238
}
239239

240240
async fn add_root_py(path: PathBuf, _: ImStr) -> miette::Result<Output> {
241-
let file = tokio::fs::read(&path)
241+
let contents = tokio::fs::read(&path)
242242
.await
243243
.into_diagnostic()
244244
.with_context(|| format_loc!("Could not read {}", path.display()))?;
245245

246246
let mut parser = tree_sitter::Parser::new();
247247
parser.set_language(tree_sitter_python::language()).into_diagnostic()?;
248248

249-
let ast = parser.parse(&file, None).ok_or_else(|| diagnostic!("AST not parsed"))?;
249+
let ast = parser
250+
.parse(&contents, None)
251+
.ok_or_else(|| diagnostic!("AST not parsed"))?;
250252
let query = model_query();
251253
let mut cursor = QueryCursor::new();
252254

253255
let mut out = vec![];
254-
let rope = Rope::from_str(&String::from_utf8_lossy(&file));
255-
let eq_slice = |range: std::ops::Range<usize>, slice: &[u8]| &file[range] == slice;
256-
for match_ in cursor.matches(query, ast.root_node(), file.as_slice()) {
257-
let mut captures = match_.captures;
258-
let tail = match captures {
259-
[models, _, tail @ ..] if eq_slice(models.node.byte_range(), b"models") => tail,
260-
[_model, tail @ ..] => {
261-
debug_assert!(matches!(&file[_model.node.byte_range()], b"Model" | b"TransientModel"));
262-
tail
263-
}
264-
unk => Err(diagnostic!(
265-
"Bug: Unknown pattern {:?}",
266-
unk.iter()
267-
.map(|capture| String::from_utf8_lossy(&file[capture.node.byte_range()]))
268-
.collect::<Vec<_>>()
269-
))?,
270-
};
271-
captures = tail;
272-
let mut model = None;
273-
while !captures.is_empty() {
274-
match captures {
275-
[_name, name, tail @ ..] if eq_slice(_name.node.byte_range(), b"_name") => {
276-
captures = tail;
277-
let name = name.node.byte_range().contract(1);
278-
if name.is_empty() {
279-
break;
280-
}
281-
model = Some(Model {
282-
model: String::from_utf8_lossy(&file[name.clone()]).to_string(),
283-
range: offset_range_to_lsp_range(name.map_unit(ByteOffset), rope.clone())
284-
.ok_or_else(|| diagnostic!("name range"))?,
285-
inherit: false,
286-
});
256+
let rope = Rope::from_str(&String::from_utf8_lossy(&contents));
257+
'match_: for match_ in cursor.matches(query, ast.root_node(), contents.as_slice()) {
258+
let mut inherits = vec![];
259+
let mut range = None;
260+
let mut maybe_base = None;
261+
for capture in match_.captures {
262+
if capture.index == 3 && maybe_base.is_none() {
263+
// @name
264+
let name = capture.node.byte_range().contract(1);
265+
if name.is_empty() {
266+
continue 'match_;
287267
}
288-
[_inherit, inherit, tail @ ..] if eq_slice(_inherit.node.byte_range(), b"_inherit") => {
289-
captures = tail;
290-
let inherit = inherit.node.byte_range().contract(1);
291-
if inherit.is_empty() {
292-
continue;
293-
}
294-
if model.is_none() {
295-
model = Some(Model {
296-
model: String::from_utf8_lossy(&file[inherit.clone()]).to_string(),
297-
range: offset_range_to_lsp_range(inherit.map_unit(ByteOffset), rope.clone())
298-
.ok_or_else(|| diagnostic!("inherit range"))?,
299-
inherit: true,
300-
});
301-
}
268+
maybe_base = Some(String::from_utf8_lossy(&contents[name]));
269+
} else if capture.index == 5 {
270+
// @inherit
271+
let inherit = capture.node.byte_range().contract(1);
272+
if !inherit.is_empty() {
273+
inherits.push(ImStr::from(String::from_utf8_lossy(&contents[inherit]).as_ref()));
302274
}
303-
unk => Err(diagnostic!("Bug: Leftover captures: {unk:?}"))?,
275+
} else if capture.index == 6 {
276+
// @model
277+
range = Some(capture.node.byte_range());
304278
}
305279
}
306-
out.extend(model);
280+
let range = offset_range_to_lsp_range(range.unwrap().map_unit(ByteOffset), rope.clone())
281+
.ok_or_else(|| diagnostic!("model range"))?;
282+
match (inherits.as_slice(), maybe_base) {
283+
([single], Some(base)) if base.as_ref() == single => out.push(Model {
284+
model: ModelId::Inherit(inherits.into_boxed_slice()),
285+
range,
286+
}),
287+
(_, None) if !inherits.is_empty() => out.push(Model {
288+
model: ModelId::Inherit(inherits.into_boxed_slice()),
289+
range,
290+
}),
291+
(_, Some(base)) => out.push(Model {
292+
model: ModelId::Base(base.as_ref().into()),
293+
range,
294+
}),
295+
_ => {}
296+
}
307297
}
308298

309299
Ok(Output::Models {

src/main.rs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -495,11 +495,11 @@ impl LanguageServer for Backend {
495495
let models = models_by_prefix.iter_prefix(query.as_bytes()).flat_map(|(_, key)| {
496496
self.module_index.models.get(key).into_iter().flat_map(|entry| {
497497
entry.0.as_ref().map(|loc| SymbolInformation {
498-
name: entry.key().clone(),
498+
name: entry.key().to_string(),
499499
kind: SymbolKind::CONSTANT,
500500
tags: None,
501501
deprecated: None,
502-
location: loc.0.clone(),
502+
location: loc.0.clone().into(),
503503
container_name: None,
504504
})
505505
})
@@ -511,7 +511,7 @@ impl LanguageServer for Backend {
511511
kind: SymbolKind::VARIABLE,
512512
tags: None,
513513
deprecated: None,
514-
location: record.location.clone(),
514+
location: record.location.clone().into(),
515515
container_name: None,
516516
}
517517
}
@@ -579,7 +579,7 @@ impl Backend {
579579
.await?;
580580
}
581581
self.client
582-
.publish_diagnostics(params.uri.clone(), diagnostics, Some(params.version))
582+
.publish_diagnostics(params.uri, diagnostics, Some(params.version))
583583
.await;
584584

585585
Ok(())
@@ -736,7 +736,7 @@ impl Backend {
736736
return Ok(None);
737737
}
738738
}
739-
return Ok((self.module_index.records.get(value.as_ref())).map(|entry| entry.location.clone()));
739+
return Ok((self.module_index.records.get(value.as_ref())).map(|entry| entry.location.clone().into()));
740740
}
741741
fn jump_def_model(&self, cursor_value: &str) -> miette::Result<Option<Location>> {
742742
match self
@@ -745,20 +745,20 @@ impl Backend {
745745
.get(cursor_value)
746746
.and_then(|entry| entry.0.as_ref().cloned())
747747
{
748-
Some(ModelLocation(base)) => Ok(Some(base)),
748+
Some(ModelLocation(base)) => Ok(Some(base.into())),
749749
None => Ok(None),
750750
}
751751
}
752752
fn model_references(&self, model: &str) -> miette::Result<Option<Vec<Location>>> {
753753
let mut locations = match self.module_index.models.get(model) {
754-
Some(entry) => entry.1.iter().map(|loc| loc.0.clone()).collect::<Vec<_>>(),
754+
Some(entry) => entry.1.iter().map(|loc| loc.0.clone().into()).collect::<Vec<_>>(),
755755
None => vec![],
756756
};
757757
locations.extend(
758758
self.module_index
759759
.records
760760
.by_model(model)
761-
.map(|record| record.location.clone()),
761+
.map(|record| record.location.clone().into()),
762762
);
763763
Ok(Some(locations))
764764
}
@@ -779,7 +779,7 @@ impl Backend {
779779
.module_index
780780
.records
781781
.by_inherit_id(&inherit_id)
782-
.map(|record| record.location.clone());
782+
.map(|record| record.location.clone().into());
783783
Ok(Some(locations.collect()))
784784
}
785785
async fn on_change_config(&self, config: Config) {

src/model.rs

Lines changed: 33 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,28 @@
11
use std::{fmt::Display, ops::Deref, sync::Arc};
22

33
use dashmap::DashMap;
4-
use log::warn;
54
use qp_trie::{wrapper::BString, Trie};
65
use tokio::sync::RwLock;
76
use tower_lsp::lsp_types::{Location, Range, Url};
87

8+
use crate::utils::ImStr;
9+
910
#[derive(Clone, Debug)]
1011
pub struct Model {
11-
pub model: String,
12+
pub model: ModelId,
1213
pub range: Range,
13-
pub inherit: bool,
14+
}
15+
16+
#[derive(Clone, Debug)]
17+
pub enum ModelId {
18+
Base(ImStr),
19+
Inherit(Box<[ImStr]>),
1420
}
1521

1622
#[derive(Default, Clone)]
1723
pub struct ModelIndex {
18-
inner: DashMap<String, (Option<ModelLocation>, Vec<ModelLocation>)>,
19-
pub by_prefix: Arc<RwLock<Trie<BString, String>>>,
24+
inner: DashMap<ImStr, (Option<ModelLocation>, Vec<ModelLocation>)>,
25+
pub by_prefix: Arc<RwLock<Trie<BString, ImStr>>>,
2026
}
2127

2228
#[derive(Clone)]
@@ -48,7 +54,7 @@ impl Display for ModelLocation {
4854
}
4955

5056
impl Deref for ModelIndex {
51-
type Target = DashMap<String, (Option<ModelLocation>, Vec<ModelLocation>)>;
57+
type Target = DashMap<ImStr, (Option<ModelLocation>, Vec<ModelLocation>)>;
5258

5359
fn deref(&self) -> &Self::Target {
5460
&self.inner
@@ -62,21 +68,27 @@ impl ModelIndex {
6268
{
6369
let mut by_prefix = self.by_prefix.write().await;
6470
for item in items {
65-
let mut entry = self.entry(item.model.to_string()).or_default();
66-
if item.inherit {
67-
by_prefix.insert_str(&item.model, item.model.to_string());
68-
entry.1.push(ModelLocation(Location {
69-
uri: uri.clone(),
70-
range: item.range,
71-
}));
72-
} else if let Some(base) = &entry.0 {
73-
warn!("{} already defined at {base}", entry.key());
74-
} else {
75-
by_prefix.insert_str(&item.model, item.model.to_string());
76-
entry.0 = Some(ModelLocation(Location {
77-
uri: uri.clone(),
78-
range: item.range,
79-
}))
71+
match item.model {
72+
ModelId::Base(base) => {
73+
by_prefix.insert_str(&base, base.clone());
74+
let mut location = Some(ModelLocation(Location {
75+
uri: uri.clone(),
76+
range: item.range,
77+
}));
78+
core::mem::swap(&mut self.entry(base.clone()).or_default().0, &mut location);
79+
#[cfg(debug_assertions)]
80+
if let Some(leftover) = location {
81+
log::debug!("leftover base class {leftover}");
82+
}
83+
}
84+
ModelId::Inherit(inherits) => {
85+
for inherit in inherits.iter() {
86+
self.entry(inherit.clone()).or_default().1.push(ModelLocation(Location {
87+
uri: uri.clone(),
88+
range: item.range,
89+
}))
90+
}
91+
}
8092
}
8193
}
8294
}

src/queries/model.scm

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@
99
(string) @name)?
1010
(assignment
1111
(identifier) @_inherit
12-
(string) @inherit)?)))
13-
(#eq? @_models "models")
14-
(#match? @_Model "^(Transient)?Model$")
15-
(#eq? @_name "_name")
16-
(#eq? @_inherit "_inherit"))
12+
[(string) @inherit
13+
(list (string) @inherit)])?))) @model
14+
(#eq? @_models "models")
15+
(#match? @_Model "^(Transient)?Model$")
16+
(#eq? @_name "_name")
17+
(#eq? @_inherit "_inherit"))

src/queries/py_completions.scm

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@
1616
(expression_statement
1717
(assignment
1818
(identifier) @_inherit
19-
(string) @model))))
19+
[(string) @model
20+
(list (string) @model)]))))
2021
(#eq? @_inherit "_inherit"))
2122

2223
((call
@@ -25,6 +26,6 @@
2526
[(argument_list . (string) @model)
2627
(argument_list
2728
(keyword_argument (identifier) @_comodel_name (string) @model))])
28-
(#eq? @_fields "fields")
29-
(#eq? @_comodel_name "comodel_name")
30-
(#match? @_Field "^(Many2one|One2many|Many2many)$"))
29+
(#eq? @_fields "fields")
30+
(#eq? @_comodel_name "comodel_name")
31+
(#match? @_Field "^(Many2one|One2many|Many2many)$"))

src/queries/py_references.scm

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,16 @@
1616
(expression_statement
1717
(assignment
1818
(identifier) @_field
19-
(string) @model))))
20-
(#match? @_field "^_(name|inherit)$"))
19+
[(string) @model
20+
(list (string) @model)]))))
21+
(#match? @_field "^_(name|inherit)$"))
2122

2223
((call
2324
[(identifier) @_Field
2425
(attribute (identifier) @_fields (identifier) @_Field)]
2526
[(argument_list . (string) @model)
2627
(argument_list
2728
(keyword_argument (identifier) @_comodel_name (string) @model))])
28-
(#eq? @_fields "fields")
29-
(#eq? @_comodel_name "comodel_name")
30-
(#match? @_Field "^(Many2one|One2many|Many2many)$"))
31-
29+
(#eq? @_fields "fields")
30+
(#eq? @_comodel_name "comodel_name")
31+
(#match? @_Field "^(Many2one|One2many|Many2many)$"))

0 commit comments

Comments
 (0)