Skip to main content

odoo_lsp/
model.rs

1//! Stores the state of Odoo [models][ModelEntry].
2
3use 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	/// NOTE: For testing only, this function deliberatetely leaks memory to make it
43	/// easier to pattern-match.
44	#[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	/// x2many, many2one model
84	Relational(Spur),
85	Related(ImStr),
86}
87
88#[derive(Debug, PartialEq, Clone, Copy)]
89pub enum PropertyKind {
90	Field,
91	Method,
92}
93
94/// Twin of [PropertyKind] where field contains field type info
95pub 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		// find an exact match first
191		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	/// Dedents the string according to python's textwrap logic, and parses the method parameters.
240	pub fn postprocess_docstring(raw: &str) -> String {
241		use std::fmt::Write;
242
243		// Dedent lines
244		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		// Manual parser for :param <name>: <desc>
275		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(); // skip the colon
286				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	/// Recursively traverses this model's definitions and populates all of its properties, including fields and methods.
430	///
431	/// `locations_filter` can be set to an empty slice to search all definitions, or a list of specific paths to search.
432	///
433	/// Deadlocks if an entry in [`ModelIndex`] is being held with the key `model`.
434	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								// TODO: fields.Reference
501								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			// fields and methods might have been deleted
612			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 to prevent deadlock
641		drop(entry);
642
643		// recursively get or populate ancestors' properties
644		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			// updating done, let's delete dead locations
711			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	/// Splits a mapped access expression, e.g. `foo.bar.baz`, and traverses until the expression is exhausted.
733	///
734	/// For completing a `Model.write({'foo.bar.baz': ..})`:
735	/// - `model` is the key of `Model`
736	/// - `needle` is a left-wise substring of `foo.bar.baz`
737	/// - `range` spans the entire range of `foo.bar.baz`
738	///
739	/// Returns an error if resolution fails before `needle` is exhausted.
740	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			// lhs: foo
753			// rhs: ba
754			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			// old range: foo.bar.baz
782			// range:         bar.baz
783			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	/// Turns related fields ([`FieldKind::Related`]) into concrete fields, and return the field's type itself if successful.
791	///
792	/// Deadlocks if an entry in [`ModelIndex`] is held with the key `model`.
793	#[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		// Why populate?
796		// If we came from a long chain of relations, we might encounter a field on a model
797		// that hasn't been populated yet. This is because we only populate fields when they're
798		// accessed. So we need to populate the fields of the model we're currently on.
799		// It's a no-op if the fields are already populated.
800		// If a stack overflow occurs, check populate_properties.
801		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				// resolved_mapped took us to the final field, now we need to resolve it to a model
816				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					// Field is already used elsewhere, don't modify it.
823					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
886/// `node` must be `[(string) (concatenated_string)]`
887fn 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}