1use std::borrow::Cow;
4use std::collections::HashMap;
5use std::collections::hash_map::Entry;
6use std::fmt::Display;
7use std::ops::Deref;
8use std::sync::Arc;
9use std::sync::RwLock;
10use std::sync::atomic::AtomicBool;
11
12use dashmap::DashMap;
13use dashmap::mapref::one::RefMut;
14use dashmap::try_result::TryResult;
15use derive_more::{Deref, DerefMut};
16use mini_moka::sync::Cache;
17use qp_trie::Trie;
18use rayon::prelude::{IntoParallelIterator, ParallelIterator};
19use smart_default::SmartDefault;
20use ts_macros::query;
21
22use crate::analyze::TypeId;
23use crate::prelude::*;
24
25use crate::analyze::FunctionParam;
26use crate::{ImStr, errloc, format_loc, test_utils};
27
28#[derive(Clone, Debug)]
29pub struct Model {
30 pub type_: ModelType,
31 pub range: Range,
32 pub byte_range: ByteRange,
33}
34
35#[derive(Clone, Debug)]
36pub enum ModelType {
37 Base { name: ImStr, ancestors: Vec<ImStr> },
38 Inherit(Vec<ImStr>),
39}
40
41impl ModelType {
42 #[cfg(test)]
45 pub fn splay(&self) -> (Option<&str>, &[&str]) {
46 match self {
47 Self::Base { name, ancestors } => (
48 Some(name.as_str()),
49 Box::leak(ancestors.iter().map(|a| a.as_str()).collect::<Box<[_]>>()),
50 ),
51 Self::Inherit(ancestors) => (
52 None,
53 Box::leak(ancestors.iter().map(|a| a.as_str()).collect::<Box<[_]>>()),
54 ),
55 }
56 }
57}
58
59#[derive(SmartDefault)]
60pub struct ModelIndex {
61 #[default(_code = "DashMap::with_shard_amount(4)")]
62 inner: DashMap<ModelName, ModelEntry>,
63 pub by_prefix: RwLock<Trie<ImStr, ModelName>>,
64}
65
66pub type ModelName = Symbol<ModelEntry>;
67
68#[derive(Default)]
69pub struct ModelEntry {
70 pub base: Option<ModelLocation>,
71 pub descendants: Vec<ModelLocation>,
72 pub ancestors: Vec<ModelName>,
73 pub fields: Option<HashMap<Symbol<Field>, Arc<Field>>>,
74 pub methods: Option<HashMap<Symbol<Method>, Arc<Method>>>,
75 pub properties_by_prefix: qp_trie::Trie<&'static [u8], PropertyKind>,
76 pub docstring: Option<ImStr>,
77 pub deleted: bool,
78}
79
80#[derive(Clone, Debug)]
81pub enum FieldKind {
82 Value,
83 Relational(Spur),
85 Related(ImStr),
86}
87
88#[derive(Debug, PartialEq, Clone, Copy)]
89pub enum PropertyKind {
90 Field,
91 Method,
92}
93
94pub enum PropertyInfo {
96 Field(Spur),
97 Method,
98}
99
100#[derive(Clone, Debug)]
101pub struct Field {
102 pub kind: FieldKind,
103 pub type_: Spur,
104 pub location: TrackedMinLoc,
105 pub help: Option<ImStr>,
106}
107
108#[derive(Debug, SmartDefault)]
109pub struct Method {
110 pub locations: Vec<TrackedMinLoc>,
111 pub docstring: Option<ImStr>,
112 pub arguments: Option<Box<[FunctionParam]>>,
113 pub pending_eval: AtomicBool,
114 #[default(_code = "Cache::new(8)")]
115 pub eval_cache: Cache<Vec<TypeId>, TypeId>,
116}
117
118impl Clone for Method {
119 fn clone(&self) -> Self {
120 Self {
121 locations: self.locations.clone(),
122 docstring: self.docstring.clone(),
123 arguments: self.arguments.clone(),
124 pending_eval: AtomicBool::new(false),
125 eval_cache: Cache::new(8),
126 }
127 }
128}
129
130#[derive(Deref, DerefMut, Clone, Debug)]
131pub struct TrackedMinLoc {
132 #[deref]
133 #[deref_mut]
134 inner: MinLoc,
135 pub active: bool,
136}
137
138impl From<MinLoc> for TrackedMinLoc {
139 #[inline]
140 fn from(inner: MinLoc) -> Self {
141 Self { inner, active: true }
142 }
143}
144
145impl Field {
146 pub fn merge<'this>(self: &'this mut Arc<Self>, other: &Self) -> &'this mut Self {
147 let self_ = Arc::make_mut(self);
148 let Self {
149 kind,
150 type_,
151 location,
152 help,
153 } = other;
154 debug!("TODO Field inheritance location {location:?}");
155 match &mut self_.kind {
156 FieldKind::Value | FieldKind::Related(_) => self_.kind = kind.clone(),
157 FieldKind::Relational(_) => {}
158 }
159 self_.type_ = *type_;
160 self_.location.active = true;
161 if let Some(help) = help {
162 self_.help = Some(help.clone());
163 }
164 self_
165 }
166}
167
168impl Method {
169 pub fn add_override(self: &mut Arc<Self>, location: MinLoc, top_level_scope: Option<Range>, base: bool) {
170 let self_ = Arc::make_mut(self);
171 let Some((idx, _)) = self_
172 .locations
173 .iter()
174 .enumerate()
175 .rfind(|(_, loc)| loc.path == location.path)
176 else {
177 if base {
178 self_.locations.insert(0, location.into());
179 } else {
180 self_.locations.push(location.into());
181 }
182 return;
183 };
184
185 let Some(top_level_scope) = top_level_scope else {
186 self_.locations.insert(idx + 1, location.into());
187 return;
188 };
189
190 if let Some(loc) = self_.locations.iter_mut().take(idx + 1).find(|loc| {
192 loc.path == location.path
193 && (loc.range.start >= top_level_scope.start && loc.range.end <= top_level_scope.end)
194 }) {
195 loc.range = location.range;
196 loc.active = true;
197 return;
198 }
199
200 self_.locations.insert(idx + 1, location.into());
201 }
202 pub fn merge(self: &mut Arc<Self>, other: &Self) {
203 let self_ = Arc::make_mut(self);
204 if other.docstring.is_some() {
205 self_.docstring = other.docstring.clone();
206 }
207 if self_.locations.is_empty() {
208 self_.locations.clone_from(&other.locations);
209 return;
210 }
211
212 let mut ranges_by_locations = HashMap::<_, Vec<_>>::new();
213 for loc in other.locations.iter() {
214 ranges_by_locations.entry(loc.path).or_default().push(loc.range);
215 }
216 for (path, ranges) in ranges_by_locations {
217 let mut first = None;
218 let mut last = None;
219 for (idx, loc) in self_.locations.iter().enumerate() {
220 if loc.path == path {
221 if first.is_none() {
222 first = Some(idx);
223 }
224 last = Some(idx);
225 } else if first.is_some() {
226 break;
227 }
228 }
229 let (Some(first), Some(last)) = (first, last) else {
230 (self_.locations).extend(ranges.into_iter().map(|range| MinLoc { path, range }.into()));
231 continue;
232 };
233 (self_.locations).splice(
234 first..=last,
235 ranges.into_iter().map(|range| MinLoc { path, range }.into()),
236 );
237 }
238 }
239 pub fn postprocess_docstring(raw: &str) -> String {
241 use std::fmt::Write;
242
243 let lines: Vec<&str> = raw.lines().collect();
245 let nonempty: Vec<&str> = lines.iter().filter(|l| !l.trim().is_empty()).copied().collect();
246 let min_indent = nonempty
247 .into_iter()
248 .skip(1)
249 .filter_map(|l| {
250 let trimmed = l.trim_start();
251 if trimmed.is_empty() {
252 None
253 } else {
254 Some(l.len() - trimmed.len())
255 }
256 })
257 .min()
258 .unwrap_or(0);
259 let dedented: Vec<String> = lines
260 .into_iter()
261 .enumerate()
262 .map(|(i, l)| {
263 if i == 0 {
264 l.trim().to_string()
265 } else if l.len() >= min_indent {
266 l[min_indent..].to_string()
267 } else {
268 l.trim_start().to_string()
269 }
270 })
271 .collect();
272 let dedented_str = dedented.join("\n");
273
274 let mut params = Vec::new();
276 let mut body = Vec::new();
277 for line in dedented_str.lines() {
278 let trimmed = line.trim_start();
279 if let Some(rest) = trimmed.strip_prefix(":param")
280 && let rest = rest.trim_start()
281 && let Some(colon_idx) = rest.find(':')
282 {
283 let (name, desc) = rest.split_at(colon_idx);
284 let name = name.trim();
285 let desc = desc[1..].trim(); if !name.is_empty() {
287 params.push((name.to_string(), desc.to_string()));
288 continue;
289 }
290 }
291 body.push(line);
292 }
293 let mut result = body.join("\n").trim().to_string();
294 if !params.is_empty() {
295 result.push_str("\n\n");
296 for (name, desc) in params {
297 _ = writeln!(&mut result, "- **{name}**: {desc}");
298 }
299 }
300 result
301 }
302}
303
304#[derive(Clone)]
305pub struct ModelLocation(pub MinLoc, pub ByteRange);
306
307impl Display for ModelLocation {
308 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
309 write!(
310 f,
311 "{}:{}:{}",
312 self.0.path,
313 self.0.range.start.line + 1,
314 self.0.range.start.character + 1,
315 )
316 }
317}
318
319impl Deref for ModelIndex {
320 type Target = DashMap<ModelName, ModelEntry>;
321
322 fn deref(&self) -> &Self::Target {
323 &self.inner
324 }
325}
326
327#[rustfmt::skip]
328query! {
329 ModelProperties(Field, Type, Relation, Arg, Value, Method, MethodBody);
330((class_definition
331 (block
332 (expression_statement
333 (assignment
334 (identifier) @FIELD
335 (call [
336 (identifier) @TYPE
337 (attribute (identifier) @_fields (identifier) @TYPE) ]
338 (argument_list . ((comment)* . (string) @RELATION)?
339 ((keyword_argument (identifier) @ARG (_) @VALUE) ","?)*))))))
340 (#eq? @_fields "fields")
341 (#match? @TYPE "^[A-Z]"))
342
343(class_definition
344 (block [
345 (function_definition (identifier) @METHOD) @METHOD_BODY
346 (decorated_definition
347 (function_definition (identifier) @METHOD) @METHOD_BODY) ]))
348}
349
350#[derive(Debug)]
351pub enum ResolveMappedError {
352 NonRelational,
353}
354
355impl Display for ResolveMappedError {
356 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
357 match self {
358 Self::NonRelational => f.write_str("Tried to access a field on a non-relational field"),
359 }
360 }
361}
362
363impl core::error::Error for ResolveMappedError {}
364
365impl ModelIndex {
366 pub fn append(&self, path: PathSymbol, replace: bool, items: &[Model]) {
367 let mut by_prefix = self
368 .by_prefix
369 .write()
370 .expect(format_loc!("unable to acquire write lock now"));
371 for item in items {
372 match &item.type_ {
373 ModelType::Base { name: base, ancestors } => {
374 let name = _I(base).into();
375 by_prefix.insert(base.clone(), name);
376 let mut entry = self.entry(name).or_default();
377 if entry.base.is_none() || replace {
378 entry.base = Some(ModelLocation(
379 MinLoc {
380 path,
381 range: item.range,
382 },
383 item.byte_range.clone(),
384 ));
385 entry
386 .ancestors
387 .extend(ancestors.iter().map(|sym| ModelName::from(_I(sym))));
388 } else if let Some(base) = entry.base.as_ref() {
389 warn!(
390 "Conflicting bases for {}:\nfirst={base}\n new={}",
391 _R(name),
392 ModelLocation(
393 MinLoc {
394 path,
395 range: item.range
396 },
397 item.byte_range.clone()
398 )
399 )
400 }
401 }
402 ModelType::Inherit(inherits) => {
403 if replace {
404 for inherit in inherits {
405 let Some(inherit) = _G(inherit) else { continue };
406 if let Some(mut entry) = self.get_mut(&inherit) {
407 entry.descendants.retain(|loc| loc.0.path != path)
408 }
409 }
410 }
411 if let Some((primary, ancestors)) = inherits.split_first() {
412 let inherit = _I(primary).into();
413 let mut entry = self.entry(inherit).or_default();
414 entry.descendants.push(ModelLocation(
415 MinLoc {
416 path,
417 range: item.range,
418 },
419 item.byte_range.clone(),
420 ));
421 entry
422 .ancestors
423 .extend(ancestors.iter().map(|sym| ModelName::from(_I(sym))));
424 }
425 }
426 }
427 }
428 }
429 pub fn populate_properties<'model>(
435 &'model self,
436 model: ModelName,
437 locations_filter: &[PathSymbol],
438 ) -> Option<RefMut<'model, ModelName, ModelEntry>> {
439 let mut entry = match self.try_get_mut(&model) {
440 TryResult::Present(entry) => entry,
441 TryResult::Absent => {
442 return None;
443 }
444 TryResult::Locked => {
445 cold_path();
446 panic!("{} deadlock on model {}", loc!(), _R(model));
447 }
448 };
449 if likely(entry.fields.is_some() && entry.methods.is_some() && locations_filter.is_empty()) {
450 return Some(entry);
451 }
452 let t0 = std::time::Instant::now();
453 let locations = entry.base.iter().chain(&entry.descendants).cloned().collect::<Vec<_>>();
454
455 let query = ModelProperties::query();
456 let iter = locations
457 .into_par_iter()
458 .filter_map(|ModelLocation(location, byte_range)| {
459 if !locations_filter.is_empty() && !locations_filter.contains(&location.path) {
460 return None;
461 }
462 let mut fields = vec![];
463 let mut methods = vec![];
464 let fpath = location.path.to_path();
465 let contents = test_utils::fs::read_to_string(&fpath)
466 .map_err(|err| error!("Failed to read {}:\n{err}", fpath.display()))
467 .ok()?;
468 let mut parser = Parser::new();
469 parser
470 .set_language(&tree_sitter_python::LANGUAGE.into())
471 .expect(format_loc!("Failed to set language"));
472 let ast = parser.parse(&contents, None)?;
473 let byte_range = byte_range.erase();
474 let mut cursor = QueryCursor::new();
475 cursor.set_byte_range(byte_range);
476
477 let mut matches = cursor.matches(query, ast.root_node(), contents.as_bytes());
478 while let Some(match_) = matches.next() {
479 let mut field = None;
480 let mut type_ = None;
481 let mut is_relational = false;
482 let mut relation = None;
483 let mut kwarg = None::<Kwargs>;
484 let mut help = None;
485 let mut related = None;
486 enum Kwargs {
487 ComodelName,
488 Help,
489 Related,
490 }
491 let mut method_name = None;
492 let mut method_body = None;
493 for capture in match_.captures {
494 match ModelProperties::from(capture.index) {
495 Some(ModelProperties::Field) => {
496 field = Some(capture.node);
497 }
498 Some(ModelProperties::Type) => {
499 type_ = Some(capture.node.byte_range());
500 is_relational = matches!(
502 &contents[capture.node.byte_range()],
503 "One2many" | "Many2one" | "Many2many"
504 );
505 }
506 Some(ModelProperties::Relation) => {
507 if is_relational {
508 relation = Some(capture.node.byte_range().shrink(1));
509 }
510 }
511 Some(ModelProperties::Arg) => match &contents[capture.node.byte_range()] {
512 "comodel_name" if is_relational => kwarg = Some(Kwargs::ComodelName),
513 "help" => kwarg = Some(Kwargs::Help),
514 "related" => kwarg = Some(Kwargs::Related),
515 _ => kwarg = None,
516 },
517 Some(ModelProperties::Value) => match kwarg {
518 Some(Kwargs::ComodelName) => {
519 if capture.node.kind() == "string" {
520 relation = Some(capture.node.byte_range().shrink(1));
521 }
522 }
523 Some(Kwargs::Help) => {
524 if matches!(capture.node.kind(), "string" | "concatenated_string") {
525 help = Some(parse_help(&capture.node, &contents));
526 }
527 }
528 Some(Kwargs::Related) => {
529 if capture.node.kind() == "string" {
530 related = Some(capture.node.byte_range().shrink(1));
531 }
532 }
533 None => {}
534 },
535 Some(ModelProperties::Method) => {
536 method_name = Some(capture.node);
537 }
538 Some(ModelProperties::MethodBody) => {
539 method_body = Some(capture.node);
540 }
541 None => {}
542 }
543 }
544 if let Some(field) = field
545 && let Some(type_) = type_
546 {
547 let range = span_conv(field.range());
548 let field_str = &contents[field.byte_range()];
549 let field = _I(field_str);
550 let type_ = &contents[type_];
551 let location = MinLoc {
552 path: location.path,
553 range,
554 }
555 .into();
556 let help = help.as_deref().map(ImStr::from);
557 let kind = if let Some(relation) = relation {
558 let relation = &contents[relation];
559 let relation = _I(relation);
560 FieldKind::Relational(relation)
561 } else if let Some(related) = related {
562 FieldKind::Related(contents[related].into())
563 } else {
564 if is_relational {
565 debug!("is_relational but no relation found: field={field_str} type={type_}");
566 }
567 FieldKind::Value
568 };
569 let type_ = _I(type_);
570 fields.push((
571 field,
572 Field {
573 kind,
574 type_,
575 location,
576 help,
577 },
578 ))
579 }
580 if let Some(method) = method_name
581 && let Some(body) = method_body
582 {
583 let method_str = &contents[method.byte_range()];
584 let calls_super = contents[body.byte_range()].contains("super(");
585 let method = _I(method_str);
586 let range = span_conv(body.range());
587 let top_level_scope = ast
588 .root_node()
589 .child_with_descendant(body)
590 .map(|scope| span_conv(scope.range()));
591 methods.push((
592 method,
593 top_level_scope,
594 calls_super,
595 MinLoc {
596 path: location.path,
597 range,
598 },
599 ))
600 }
601 }
602 Some((fields, methods))
603 });
604
605 let ancestors = entry.ancestors.to_vec();
606 let mut out_fields = entry.fields.take().unwrap_or_default();
607 let mut out_methods = entry.methods.take().unwrap_or_default();
608 let mut properties_set = core::mem::take(&mut entry.properties_by_prefix);
609
610 if !locations_filter.is_empty() {
611 let locations_filter = locations_filter
613 .iter()
614 .map(|filter| filter.to_path())
615 .collect::<Vec<_>>();
616
617 for (_, field) in out_fields.iter_mut() {
618 if locations_filter
619 .iter()
620 .any(|filter| field.location.path.to_path().starts_with(filter))
621 {
622 Arc::make_mut(field).location.active = false;
623 }
624 }
625
626 for (_, method) in out_methods.iter_mut() {
627 let method = Arc::make_mut(method);
628 for loc in method.locations.iter_mut() {
629 if locations_filter
630 .iter()
631 .any(|filter| loc.path.to_path().starts_with(filter))
632 {
633 loc.active = false;
634 method.arguments = None;
635 }
636 }
637 }
638 }
639
640 drop(entry);
642
643 for ancestor in ancestors {
645 if let Some(entry) = self.populate_properties(ancestor, locations_filter) {
646 if let Some(fields) = entry.fields.as_ref() {
647 for (name, field) in fields {
648 properties_set.insert(_R(*name).as_bytes(), PropertyKind::Field);
649 match out_fields.entry(*name) {
650 Entry::Occupied(mut old_field) => {
651 old_field.get_mut().merge(field);
652 }
653 Entry::Vacant(empty) => {
654 empty.insert(field.clone());
655 }
656 }
657 }
658 }
659 if let Some(methods) = entry.methods.as_ref() {
660 for (name, method) in methods {
661 properties_set.insert(_R(*name).as_bytes(), PropertyKind::Method);
662 match out_methods.entry(*name) {
663 Entry::Occupied(mut old_method) => {
664 old_method.get_mut().merge(method);
665 }
666 Entry::Vacant(empty) => {
667 empty.insert(method.clone());
668 }
669 }
670 }
671 }
672 }
673 }
674
675 let (fields, methods): (Vec<_>, Vec<_>) = iter.collect();
676
677 for (key, field) in fields.into_iter().flatten() {
678 match out_fields.entry(key.into()) {
679 Entry::Occupied(mut old_field) => {
680 old_field.get_mut().merge(&field);
681 }
682 Entry::Vacant(empty) => {
683 empty.insert(field.into());
684 }
685 }
686 properties_set.insert(_R(key).as_bytes(), PropertyKind::Field);
687 }
688
689 for (key, top_level_scope, calls_super, method_location) in methods.into_iter().flatten() {
690 match out_methods.entry(key.into()) {
691 Entry::Occupied(mut old_method) => {
692 old_method
693 .get_mut()
694 .add_override(method_location, top_level_scope, !calls_super);
695 }
696 Entry::Vacant(empty) => {
697 empty.insert(
698 Method {
699 locations: vec![method_location.into()],
700 ..Default::default()
701 }
702 .into(),
703 );
704 }
705 }
706 properties_set.insert(_R(key).as_bytes(), PropertyKind::Method);
707 }
708
709 if !locations_filter.is_empty() {
710 out_fields.retain(|_, field| field.location.active);
712 out_methods.retain(|_, method| {
713 let method = Arc::make_mut(method);
714 method.locations.retain(|loc| loc.active);
715 !method.locations.is_empty()
716 });
717 }
718
719 let model_name = _R(model);
720 info!(
721 "{model_name}: {} fields, {} methods, {}ms",
722 out_fields.len(),
723 out_methods.len(),
724 t0.elapsed().as_millis(),
725 );
726 let mut entry = self.try_get_mut(&model).expect(format_loc!("deadlock")).unwrap();
727 entry.fields = Some(out_fields);
728 entry.methods = Some(out_methods);
729 entry.properties_by_prefix = properties_set;
730 Some(entry)
731 }
732 pub fn resolve_mapped(
741 &self,
742 model: &mut Spur,
743 needle: &mut &str,
744 mut range: Option<&mut ByteRange>,
745 ) -> Result<(), ResolveMappedError> {
746 while let Some((lhs, rhs)) = needle.split_once('.') {
747 trace!("(resolved_mapped) `{needle}` model=`{}`", _R(*model));
748 let mut resolved = _G(lhs).and_then(|key| self.resolve_related_field(key.into(), *model));
749 if let Some(normalized) = resolved {
750 trace!("(resolved_mapped) prenormalized: {}", _R(normalized));
751 }
752 if resolved.is_none() {
755 let Some(model_entry) = self.populate_properties((*model).into(), &[]) else {
756 debug!("tried to resolve before fields are populated for `{}`", _R(*model));
757 return Ok(());
758 };
759 let field = _G(lhs);
760 let field = field.and_then(|field| model_entry.fields.as_ref()?.get(&field));
761 match field.as_ref().map(|f| &f.kind) {
762 Some(FieldKind::Relational(rel)) => resolved = Some(*rel),
763 None | Some(FieldKind::Value) => return Err(ResolveMappedError::NonRelational),
764 Some(FieldKind::Related(..)) => {
765 drop(model_entry);
766 resolved = self.resolve_related_field(_G(lhs).unwrap().into(), *model);
767 }
768 }
769 }
770 let Some(rel) = resolved else {
771 warn!("unresolved field `{}`.`{lhs}`", _R(*model));
772 *needle = lhs;
773 if let Some(range) = range.as_mut() {
774 let end = range.start.0 + lhs.len();
775 **range = range.start..ByteOffset(end);
776 }
777 return Err(ResolveMappedError::NonRelational);
778 };
779 *needle = rhs;
780 *model = rel;
781 if let Some(range) = range.as_mut() {
784 let start = range.start.0 + lhs.len() + 1;
785 **range = ByteOffset(start)..range.end;
786 }
787 }
788 Ok(())
789 }
790 #[must_use = "normalized relation might not have been updated back to the central index"]
794 pub fn resolve_related_field(&self, field: Symbol<Field>, model: Spur) -> Option<Spur> {
795 let entry = self.populate_properties(model.into(), &[])?;
802 let field_entry = entry.fields.as_ref()?.get(&field)?;
803 let mut kind = field_entry.kind.clone();
804 let mut field_model = model;
805 if let FieldKind::Related(related) = &field_entry.kind {
806 trace!(
807 "(normalize_field_relation) related={related} field={} model={}",
808 _R(field),
809 _R(model)
810 );
811 let related = related.clone();
812 let mut related = related.as_str();
813 drop(entry);
814 if self.resolve_mapped(&mut field_model, &mut related, None).is_ok() {
815 let related_key = _G(related)?;
817 let field_model = self.resolve_related_field(related_key.into(), field_model)?;
818
819 kind = FieldKind::Relational(field_model);
820 let mut model_entry = self.try_get_mut(&model).expect(format_loc!("deadlock"))?;
821 let Some(field) = Arc::get_mut(model_entry.fields.as_mut()?.get_mut(&field)?) else {
822 return Some(field_model);
824 };
825 field.kind = kind.clone();
826 } else {
827 warn!("failed to normalize {related}");
828 }
829 }
830
831 match kind {
832 FieldKind::Relational(rel) => Some(rel),
833 FieldKind::Value => None,
834 FieldKind::Related(_) => None,
835 }
836 }
837}
838
839#[rustfmt::skip]
840query! {
841 ModelHelp(Docstring);
842(class_definition
843 (block .
844 (expression_statement
845 (string
846 ((string_start) . (string_content) @DOCSTRING)))))
847}
848
849impl ModelEntry {
850 pub fn resolve_details(&mut self) -> anyhow::Result<()> {
851 let Some(ModelLocation(loc, byte_range)) = &self.base else {
852 return Ok(());
853 };
854 if self.docstring.is_none() {
855 let contents = std::fs::read(loc.path.to_path())?;
856 let mut parser = Parser::new();
857 parser.set_language(&tree_sitter_python::LANGUAGE.into())?;
858 let ast = parser.parse(&contents, None).ok_or_else(|| errloc!("AST not parsed"))?;
859 let query = ModelHelp::query();
860 let mut cursor = QueryCursor::new();
861 cursor.set_byte_range(byte_range.erase());
862 let mut matches = cursor.matches(query, ast.root_node(), &contents[..]);
863 while let Some(match_) = matches.next() {
864 if let Some(docstring) = match_.nodes_for_capture_index(0).next() {
865 let contents = String::from_utf8_lossy(&contents[docstring.byte_range()]);
866 self.docstring = Some(ImStr::from(contents.trim()));
867 return Ok(());
868 }
869 }
870 self.docstring = Some("".into());
871 }
872
873 Ok(())
874 }
875 pub fn prop_kind(&self, prop: Spur) -> Option<PropertyInfo> {
876 if let Some(field) = self.fields.as_ref().and_then(|fields| fields.get(&prop)) {
877 Some(PropertyInfo::Field(field.type_))
878 } else if self.methods.as_ref().is_some_and(|methods| methods.contains_key(&prop)) {
879 Some(PropertyInfo::Method)
880 } else {
881 None
882 }
883 }
884}
885
886fn parse_help<'text>(node: &Node, contents: &'text str) -> Cow<'text, str> {
888 let mut cursor = node.walk();
889 match node.kind() {
890 "string" => {
891 let content = node
892 .children(&mut cursor)
893 .find_map(|child| (child.kind() == "string_content").then(|| &contents[child.byte_range()]));
894 content.unwrap_or("").into()
895 }
896 "concatenated_string" => {
897 let mut content = vec![];
898 for string in node.children(&mut cursor) {
899 if string.kind() == "string" {
900 let mut cursor = string.walk();
901 let children = string.children(&mut cursor).find_map(|child| {
902 (child.kind() == "string_content").then(|| {
903 contents[child.byte_range()]
904 .trim()
905 .replace("\\n", " \n")
906 .replace("\\t", "\t")
907 })
908 });
909 content.extend(children);
910 }
911 }
912 Cow::from(content.join(" "))
913 }
914 _ => unreachable!(),
915 }
916}
917
918#[cfg(test)]
919mod tests {
920 use pretty_assertions::assert_eq;
921 use std::collections::HashSet;
922 use tree_sitter::{Parser, QueryCursor, StreamingIterator, StreamingIteratorMut};
923
924 use crate::{
925 index::{_I, _R, ModelQuery},
926 test_utils::cases::foo::{FOO_PY, prepare_foo_index},
927 utils::acc_vec,
928 };
929
930 fn clamp_str(str: &str) -> &str {
931 if str.len() > 10 { &str[..10] } else { str }
932 }
933
934 #[test]
935 fn test_model_query() {
936 let query = ModelQuery::query();
937 let mut parser = Parser::new();
938 parser.set_language(&tree_sitter_python::LANGUAGE.into()).unwrap();
939 let ast = parser.parse(FOO_PY, None).unwrap();
940 let matches = QueryCursor::new()
941 .matches(query, ast.root_node(), FOO_PY)
942 .map(|match_| {
943 (match_.captures.iter())
944 .map(|cap| {
945 (
946 ModelQuery::from(cap.index),
947 clamp_str(unsafe { core::str::from_utf8_unchecked(&FOO_PY[cap.node.byte_range()]) }),
948 )
949 })
950 .collect::<Vec<_>>()
951 })
952 .fold_mut(vec![], acc_vec);
953 let matches = matches.iter().map(|match_| &match_[..]).collect::<Vec<_>>();
954 use ModelQuery as T;
955 assert_eq!(
956 matches.as_slice(),
957 [
958 [
959 (Some(T::Model), "class Foo("),
960 (None, "Model"),
961 (Some(T::Name), "_name"),
962 ],
963 [
964 (Some(T::Model), "class Bar("),
965 (None, "Model"),
966 (Some(T::Name), "_name"),
967 ],
968 [
969 (Some(T::Model), "class Bar("),
970 (None, "Model"),
971 (Some(T::Name), "_inherit"),
972 ],
973 [
974 (Some(T::Model), "class Quux"),
975 (None, "Model"),
976 (Some(T::Name), "_name"),
977 ],
978 [
979 (Some(T::Model), "class Quux"),
980 (None, "Model"),
981 (Some(T::Name), "_inherit"),
982 ]
983 ]
984 );
985 }
986
987 #[test]
988 fn test_populate_properties() {
989 let index = prepare_foo_index();
990
991 let foo = index.populate_properties(_I("foo").into(), &[]).unwrap();
992
993 assert_eq!(
994 foo.fields.as_ref().unwrap().keys().next().map(|sym| _R(*sym)),
995 Some("bar")
996 );
997 drop(foo);
998
999 let bar = index.populate_properties(_I("bar").into(), &[]).unwrap();
1000
1001 let bar_fields = bar
1002 .fields
1003 .as_ref()
1004 .unwrap()
1005 .keys()
1006 .map(|sym| _R(*sym))
1007 .collect::<HashSet<_>>();
1008 assert_eq!(bar_fields.len(), 2);
1009 assert!(bar_fields.contains("baz"));
1010 assert!(bar_fields.contains("bar"));
1011 }
1012}