Skip to main content

odoo_lsp/
analyze.rs

1//! Methods related to type analysis. The two most important methods are
2//! [`Index::model_of_range`] and [`Index::type_of`].
3
4use core::borrow::Borrow;
5use std::{
6	fmt::{Debug, Write},
7	ops::ControlFlow,
8	sync::{Arc, OnceLock, atomic::Ordering},
9};
10
11use dashmap::DashMap;
12use fomat_macros::fomat;
13use lasso::Spur;
14use ropey::Rope;
15use tracing::instrument;
16use tree_sitter::{Node, Parser, QueryCursor, StreamingIterator};
17
18use crate::{
19	ImStr, dig, format_loc,
20	index::{_G, _I, _R, Index, Symbol},
21	model::{Method, ModelName, PropertyInfo},
22	test_utils,
23	utils::{ByteOffset, ByteRange, Defer, PreTravel, RangeExt, TryResultExt, python_next_named_sibling, rope_conv},
24};
25use ts_macros::query;
26
27mod scope;
28pub use scope::Scope;
29
30pub fn type_cache() -> &'static TypeCache {
31	static CACHE: OnceLock<TypeCache> = OnceLock::new();
32	CACHE.get_or_init(TypeCache::default)
33}
34
35macro_rules! _T {
36	(@ $builtin:expr) => {
37		$crate::analyze::type_cache().get_or_intern(Type::PyBuiltin($builtin.into()))
38	};
39	($model:literal) => {
40		$crate::analyze::type_cache().get_or_intern(Type::Model($model.into()))
41	};
42	($expr:expr) => {
43		$crate::analyze::type_cache().get_or_intern($expr)
44	};
45}
46
47macro_rules! _TR {
48	($expr:expr) => {
49		$crate::analyze::type_cache().resolve($expr)
50	};
51}
52
53pub static MODEL_METHODS: phf::Set<&str> = phf::phf_set!(
54	"create",
55	"copy",
56	"name_create",
57	"browse",
58	"filtered",
59	"filtered_domain",
60	"sorted",
61	"search",
62	"search_fetch",
63	"name_search",
64	"ensure_one",
65	"with_context",
66	"with_user",
67	"with_company",
68	"with_env",
69	"sudo",
70	"exists",
71	"concat",
72	// TODO: Limit to Forms only
73	"new",
74	"edit",
75	"save",
76);
77
78/// The subset of types that may resolve to a model.
79#[derive(Clone, Debug, PartialEq, Eq, Hash)]
80pub enum Type {
81	Env,
82	/// \*.env.ref()
83	RefFn,
84	/// Functions that return another model, regardless of input.
85	ModelFn(ImStr),
86	Model(ImStr),
87	/// Unresolved model.
88	Record(ImStr),
89	Super,
90	Method(ModelName, ImStr),
91	/// To hardcode some methods, such as dict.items()
92	PythonMethod(TypeId, ImStr),
93	/// `odoo.http.request`
94	HttpRequest,
95	Dict(TypeId, TypeId),
96	/// A bag of enumerated properties and their types
97	DictBag(Vec<(DictKey, TypeId)>),
98	/// Equivalent to Value, but may have a better semantic name
99	PyBuiltin(ImStr),
100	List(ListElement),
101	Tuple(Vec<TypeId>),
102	Iterable(Option<TypeId>),
103	/// Can never be resolved, useful for non-model bindings.
104	Value,
105}
106
107impl Type {
108	#[inline]
109	fn is_dictlike(&self) -> bool {
110		matches!(self, Type::Dict(..) | Type::DictBag(..))
111	}
112}
113
114#[derive(Clone, PartialEq, Eq, Hash)]
115pub enum ListElement {
116	Vacant,
117	Occupied(TypeId),
118}
119
120impl Debug for ListElement {
121	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
122		match self {
123			Self::Vacant => f.write_str("..."),
124			Self::Occupied(inner) => inner.fmt(f),
125		}
126	}
127}
128
129impl From<ListElement> for Option<TypeId> {
130	#[inline]
131	fn from(value: ListElement) -> Self {
132		match value {
133			ListElement::Vacant => None,
134			ListElement::Occupied(inner) => Some(inner),
135		}
136	}
137}
138
139#[derive(Clone, PartialEq, Eq, Hash)]
140pub enum DictKey {
141	String(ImStr),
142	Type(TypeId),
143}
144
145impl Debug for DictKey {
146	#[inline]
147	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
148		match self {
149			Self::String(key) => key.fmt(f),
150			Self::Type(key) => key.fmt(f),
151		}
152	}
153}
154
155#[derive(Clone, Debug)]
156pub enum FunctionParam {
157	Param(ImStr),
158	/// `(positional_separator)`
159	PosEnd,
160	/// `(keyword_separator)` or `(list_splat_pattern)`
161	EitherEnd(Option<ImStr>),
162	/// `(default_parameter)`
163	Named(ImStr),
164	Kwargs(ImStr),
165}
166
167impl core::fmt::Display for FunctionParam {
168	fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> std::fmt::Result {
169		match self {
170			FunctionParam::Param(param) => f.write_str(param),
171			FunctionParam::PosEnd => f.write_char('/'),
172			FunctionParam::EitherEnd(None) => f.write_char('*'),
173			FunctionParam::EitherEnd(Some(param)) => write!(f, "*{param}"),
174			FunctionParam::Named(param) => write!(f, "{param}=..."),
175			FunctionParam::Kwargs(param) => write!(f, "**{param}"),
176		}
177	}
178}
179
180#[derive(Default)]
181pub struct TypeCache {
182	types: boxcar::Vec<Type>,
183	ids: DashMap<Type, TypeId>,
184}
185
186impl TypeCache {
187	#[inline]
188	pub fn get_or_intern(&self, type_: Type) -> TypeId {
189		if let Some(id) = self.ids.get(&type_) {
190			return *id;
191		}
192		self.intern(type_)
193	}
194	fn intern(&self, type_: Type) -> TypeId {
195		let id = TypeId(self.types.push(type_.clone()).try_into().unwrap());
196		self.ids.insert(type_, id);
197		id
198	}
199	#[inline]
200	pub fn resolve<T: Borrow<TypeId>>(&self, id: T) -> &Type {
201		unsafe { self.types.get_unchecked(id.borrow().0 as usize) }
202	}
203}
204
205#[repr(transparent)]
206#[derive(Clone, Copy, PartialEq, Eq, Hash)]
207pub struct TypeId(u32);
208
209impl TypeId {
210	#[inline]
211	pub fn is_dictlike(&self) -> bool {
212		type_cache().resolve(self).is_dictlike()
213	}
214	#[inline]
215	pub fn is_dict(&self) -> bool {
216		matches!(type_cache().resolve(self), Type::Dict(..))
217	}
218	#[inline]
219	pub fn is_dictbag(&self) -> bool {
220		matches!(type_cache().resolve(self), Type::DictBag(..))
221	}
222}
223
224impl Debug for TypeId {
225	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
226		_TR!(*self).fmt(f)
227	}
228}
229
230pub fn normalize<'r, 'n>(node: &'r mut Node<'n>) -> &'r mut Node<'n> {
231	let mut cursor = node.walk();
232	while matches!(
233		node.kind(),
234		"expression_statement" | "parenthesized_expression" | "module"
235	) {
236		let Some(child) = node.named_children(&mut cursor).find(|child| child.kind() != "comment") else {
237			break;
238		};
239		*node = child;
240	}
241	node
242}
243
244#[rustfmt::skip]
245query! {
246	#[derive(Debug)]
247	FieldCompletion(Name, SelfParam, Scope);
248((class_definition
249  (block
250    (expression_statement [
251      (assignment (identifier) @_name (string) @NAME)
252	  (assignment (identifier) @_inherit (list . (string) @NAME)) ])?
253    [
254      (decorated_definition
255        (function_definition
256          (parameters . (identifier) @SELF_PARAM)) @SCOPE)
257      (function_definition (parameters . (identifier) @SELF_PARAM)) @SCOPE])) @class
258  (#eq? @_inherit "_inherit")
259  (#match? @_name "^_(name|inherit)$"))
260}
261
262#[rustfmt::skip]
263query! {
264	MappedCall(Callee, Iter);
265((call
266  (attribute (_) @CALLEE (identifier) @_mapped)
267  (argument_list [
268    (lambda (lambda_parameters . (identifier) @ITER))
269    (keyword_argument
270      (identifier) @_func
271      (lambda (lambda_parameters . (identifier) @ITER)))]))
272  (#match? @_func "^(func|key)$")
273  (#match? @_mapped "^(mapp|filter|sort|group)ed$"))
274}
275
276#[rustfmt::skip]
277query! {
278	PythonBuiltinCall(Append, AppendList, AppendMap, AppendMapKey, AppendValue, UpdateMap, UpdateArgs);
279// value.append(...) OR value['foobar'].append(...)
280(call
281  (attribute
282    (subscript (identifier) @APPEND_MAP (string (string_content) @APPEND_MAP_KEY))
283    (identifier) @_append)
284  (argument_list . (_) @APPEND_VALUE)
285  (#eq? @_append "append"))
286
287(call
288  (attribute
289    (identifier) @APPEND_LIST
290    (identifier) @APPEND)
291  (argument_list . (_) @APPEND_VALUE)
292  (#eq? @_append "append"))
293
294// value.update(...)
295(call
296  (attribute
297  	(identifier) @UPDATE_MAP
298  	(identifier) @_update)
299  (argument_list) @UPDATE_ARGS
300  (#eq? @_update "update"))
301}
302
303pub type ScopeControlFlow = ControlFlow<Option<Scope>, bool>;
304impl Index {
305	#[inline]
306	pub fn model_of_range(&self, node: Node<'_>, range: ByteRange, contents: &str) -> Option<ModelName> {
307		let (type_at_cursor, scope) = self.type_of_range(node, range, contents)?;
308		self.try_resolve_model(_TR!(type_at_cursor), &scope)
309	}
310	pub fn type_of_range(&self, root: Node<'_>, range: ByteRange, contents: &str) -> Option<(TypeId, Scope)> {
311		// Phase 1: Determine the scope.
312		let (self_type, fn_scope, self_param) = determine_scope(root, contents, range.start.0)?;
313
314		// Phase 2: Build the scope up to offset
315		// What contributes to a method scope's variables?
316		// 1. Top-level statements; completely opaque to us.
317		// 2. Class definitions; technically useless to us.
318		//    Self-type analysis only uses a small part of the class definition.
319		// 3. Parameters, e.g. self which always has a fixed type
320		// 4. Assignments (including walrus-assignment)
321		let mut scope = Scope::default();
322		let self_type = match self_type {
323			Some(type_) => &contents[type_.byte_range().shrink(1)],
324			None => "",
325		};
326		scope.insert(self_param.to_string(), Type::Model(self_type.into()));
327		scope.super_ = Some(self_param.into());
328
329		// special case: is this a property?
330		// TODO: fields
331		let node_at_cursor = fn_scope.descendant_for_byte_range(range.start.0, range.end.0)?;
332		if node_at_cursor.kind() == "identifier" && fn_scope.child_by_field_name("name") == Some(node_at_cursor) {
333			return Some((
334				_T!(Type::Method(
335					_I(self_type).into(),
336					contents[node_at_cursor.byte_range()].into()
337				)),
338				scope,
339			));
340		}
341
342		// Phase 3: With the proper context available, determine the type of the node.
343		self.type_of_node(Some(scope), fn_scope, range, contents)
344	}
345	pub fn type_of_node(
346		&self,
347		scope: Option<Scope>,
348		node: Node<'_>,
349		range: ByteRange,
350		contents: &str,
351	) -> Option<(TypeId, Scope)> {
352		let scope = scope.unwrap_or_default();
353		let (orig_scope, scope) = Self::walk_scope(node, Some(scope), |scope, node| {
354			self.build_scope(scope, node, range.end.0, contents)
355		});
356		let scope = scope.unwrap_or(orig_scope);
357		let node_at_cursor = node.descendant_for_byte_range(range.start.0, range.end.0)?;
358		let type_at_cursor = self.type_of(node_at_cursor, &scope, contents)?;
359		Some((type_at_cursor, scope))
360	}
361	/// Builds the scope up to `offset`, in bytes.
362	///
363	/// #### About [ScopeControlFlow]
364	/// This is one of the rare occasions where [ControlFlow] is used. It is similar to
365	/// [Result] in that the try-operator (?) can be used to end iteration on a
366	/// [ControlFlow::Break]. Otherwise, [ControlFlow::Continue] has a continuation value
367	/// that must be passed up the chain, since it indicates whether [Scope::enter] was called.
368	pub fn build_scope(&self, scope: &mut Scope, node: Node, offset: usize, contents: &str) -> ScopeControlFlow {
369		if node.start_byte() > offset {
370			return ControlFlow::Break(Some(core::mem::take(scope)));
371		}
372		match node.kind() {
373			"assignment" | "named_expression" => {
374				// (_ left right)
375				let lhs = node.named_child(0).unwrap();
376				if lhs.kind() == "identifier"
377					&& let rhs = python_next_named_sibling(lhs).expect(format_loc!("rhs"))
378					&& let Some(id) = self.type_of(rhs, scope, contents)
379				{
380					let lhs = &contents[lhs.byte_range()];
381					scope.insert(lhs.to_string(), _TR!(id).clone());
382				} else if lhs.kind() == "subscript"
383					&& let Some(map) = dig!(lhs, identifier)
384					&& let Some(key) = dig!(lhs, string(1).string_content(1))
385					&& let Some(rhs) = python_next_named_sibling(lhs)
386					&& let type_ = self.type_of(rhs, scope, contents)
387					&& let Some(Type::DictBag(properties)) = scope.get_mut(&contents[map.byte_range()])
388				{
389					let type_ = type_.unwrap_or_else(|| _T!(Type::Value));
390					let key = &contents[key.byte_range()];
391					if let Some(idx) = properties.iter().position(|(prop, _)| match prop {
392						DictKey::String(prop) => prop.as_str() == key,
393						DictKey::Type(_) => false,
394					}) {
395						properties[idx].1 = type_;
396					} else {
397						properties.push((DictKey::String(ImStr::from(key)), type_));
398					}
399				} else if lhs.kind() == "pattern_list"
400					&& let Some(rhs) = python_next_named_sibling(lhs)
401					&& let Some(type_) = self.type_of(rhs, scope, contents)
402				{
403					self.destructure_into_patternlist_like(lhs, type_, scope, contents);
404				}
405			}
406			"for_statement" => {
407				// (for_statement left right body)
408				scope.enter(true);
409				let lhs = node.named_child(0).unwrap();
410
411				if let Some(rhs) = python_next_named_sibling(lhs)
412					&& let Some(type_) = self.type_of(rhs, scope, contents)
413					&& let Some(inner) = self.type_of_iterable(type_)
414				{
415					self.destructure_into_patternlist_like(lhs, inner, scope, contents);
416				}
417				return ControlFlow::Continue(true);
418			}
419			"function_definition" => {
420				let mut inherit_super = false;
421				let mut node = node;
422				while let Some(parent) = node.parent() {
423					match parent.kind() {
424						"decorated_definition" => {
425							node = parent;
426							continue;
427						}
428						"block" => inherit_super = parent.parent().is_some_and(|gp| gp.kind() == "class_definition"),
429						_ => {}
430					}
431					break;
432				}
433				scope.enter(inherit_super);
434				return ControlFlow::Continue(true);
435			}
436			"list_comprehension" | "set_comprehension" | "dictionary_comprehension" | "generator_expression"
437				if node.byte_range().contains(&offset) =>
438			{
439				// (_ body: _ (for_in_clause left: _ right: _))
440				let for_in = node.named_child(1).unwrap();
441				if let Some(lhs) = for_in.child_by_field_name("left")
442					&& let Some(rhs) = for_in.child_by_field_name("right")
443					&& let Some(tid) = self.type_of(rhs, scope, contents)
444					&& let Some(inner) = self.type_of_iterable(tid)
445				{
446					self.destructure_into_patternlist_like(lhs, inner, scope, contents);
447				}
448			}
449			"call" if node.byte_range().contains_end(offset) => {
450				// model.{mapped,filtered,sorted,*}(lambda rec: ..)
451				let query = MappedCall::query();
452				let mut cursor = QueryCursor::new();
453				let mut matches = cursor.matches(query, node, contents.as_bytes());
454				if let Some(mapped_call) = matches.next()
455					&& let callee = mapped_call
456						.nodes_for_capture_index(MappedCall::Callee as _)
457						.next()
458						.unwrap() && let Some(tid) = self.type_of(callee, scope, contents)
459				{
460					let iter = mapped_call
461						.nodes_for_capture_index(MappedCall::Iter as _)
462						.next()
463						.unwrap();
464					let iter = &contents[iter.byte_range()];
465					scope.insert(iter.to_string(), _TR!(tid).clone());
466				}
467			}
468			"call" => {
469				let query = PythonBuiltinCall::query();
470				let mut cursor = QueryCursor::new();
471				let mut matches = cursor.matches(query, node, contents.as_bytes());
472				let Some(call) = matches.next() else {
473					return ControlFlow::Continue(false);
474				};
475				if let Some(value) = call.nodes_for_capture_index(PythonBuiltinCall::AppendValue as _).next()
476					&& let Some(tid) = self.type_of(value, scope, contents)
477				{
478					if let Some(list) = call.nodes_for_capture_index(PythonBuiltinCall::AppendList as _).next() {
479						if let Some(Type::List(slot @ ListElement::Vacant)) =
480							scope.get_mut(&contents[list.byte_range()])
481						{
482							*slot = ListElement::Occupied(tid);
483						}
484					} else if let Some(map) = call.nodes_for_capture_index(PythonBuiltinCall::AppendMap as _).next() {
485						let Some(key) = call
486							.nodes_for_capture_index(PythonBuiltinCall::AppendMapKey as _)
487							.next()
488						else {
489							return ControlFlow::Continue(false);
490						};
491						let key = &contents[key.byte_range()];
492
493						if let Some(Type::DictBag(properties)) = scope.get_mut(&contents[map.byte_range()])
494							&& let Some((_, slot)) = properties.iter_mut().find(|(prop, id)| match prop {
495								DictKey::String(prop) => {
496									prop.as_str() == key && _T!(Type::List(ListElement::Vacant)) == *id
497								}
498								DictKey::Type(_) => false,
499							}) {
500							*slot = _T!(Type::List(ListElement::Occupied(tid)));
501						}
502					}
503				} else if let Some(map) = call.nodes_for_capture_index(PythonBuiltinCall::UpdateMap as _).next() {
504					let Some(Type::DictBag(properties)) = scope.get_mut(&contents[map.byte_range()]) else {
505						return ControlFlow::Continue(false);
506					};
507					let Some(args) = call.nodes_for_capture_index(PythonBuiltinCall::UpdateArgs as _).next() else {
508						return ControlFlow::Continue(false);
509					};
510
511					let mut properties = core::mem::take(properties);
512					let mut cursor = args.walk();
513					let mut children = args.named_children(&mut cursor);
514					if let Some(first) = children.by_ref().next()
515						&& let Some(tid) = self.type_of(first, scope, contents)
516						&& let Type::DictBag(update_props) = _TR!(tid)
517					{
518						properties.extend(update_props.clone());
519					}
520
521					for named_arg in children {
522						if named_arg.kind() == "keyword_argument"
523							&& let Some(name) = named_arg.child_by_field_name("name")
524							&& let Some(value) = named_arg.child_by_field_name("value")
525						{
526							let key = &contents[name.byte_range()];
527							let type_ = self.type_of(value, scope, contents).unwrap_or_else(|| _T!(Type::Value));
528							if let Some(idx) = properties.iter().position(|(prop, _)| match prop {
529								DictKey::String(prop) => prop.as_str() == key,
530								DictKey::Type(_) => false,
531							}) {
532								properties[idx].1 = type_;
533							} else {
534								properties.push((DictKey::String(ImStr::from(key)), type_));
535							}
536						} else if named_arg.kind() == "dictionary_splat"
537							&& let Some(value) = named_arg.named_child(0)
538							&& let Some(tid) = self.type_of(value, scope, contents)
539							&& let Type::DictBag(update_props) = _TR!(tid)
540						{
541							properties.extend(update_props.clone());
542						}
543					}
544
545					scope.insert(contents[map.byte_range()].to_string(), Type::DictBag(properties));
546				}
547			}
548			"with_statement" => {
549				// with Form(self.env['..']) as alias:
550				// TODO: Support more structures as needed
551				// (with_statement
552				// 	 (with_clause
553				//     (with_item
554				//       (as_pattern
555				//         (call (identifier) ..)
556				//         (as_pattern_target (identifier))))))
557				if let Some(value) = dig!(node, with_clause.with_item.as_pattern.call)
558					&& let Some(target) = python_next_named_sibling(value)
559					&& target.kind() == "as_pattern_target"
560					&& let Some(alias) = dig!(target, identifier)
561					&& let Some(callee) = value.named_child(0)
562				{
563					// TODO: Remove this hardcoded case
564					if callee.kind() == "identifier"
565						&& "Form" == &contents[callee.byte_range()]
566						&& let Some(first_arg) = value.named_child(1).expect("call args").named_child(0)
567						&& let Some(type_) = self.type_of(first_arg, scope, contents)
568					{
569						let alias = &contents[alias.byte_range()];
570						scope.insert(alias.to_string(), _TR!(type_).clone());
571					} else if let Some(type_) = self.type_of(value, scope, contents) {
572						let alias = &contents[alias.byte_range()];
573						scope.insert(alias.to_string(), _TR!(type_).clone());
574					}
575				}
576			}
577			_ => {}
578		}
579
580		ControlFlow::Continue(false)
581	}
582	/// [Type::Value] is not returned by this method.
583	pub fn type_of(&self, mut node: Node, scope: &Scope, contents: &str) -> Option<TypeId> {
584		// What contributes to value types?
585		// 1. *.env['foo'] => Model('foo')
586		// 2. *.env.ref(<record-id>) => Model(<model of record-id>)
587
588		// What preserves value types?
589		// 1. for foo in bar;
590		//    bar: 't => foo: 't
591		// 2. foo = bar;
592		//    bar: 't => foo: 't (and various other operators)
593		// 3. foo.sudo();
594		//    foo: 't => foo.sudo(): 't
595		//    sudo, with_user, with_env, with_context, ..
596		// 4. [foo for foo in bar];
597		//    bar: 't => foo: 't
598		// 5: foo[..]
599		//    foo: 't => foo[..]: 't
600
601		// What transforms value types?
602		// 1. foo.bar;
603		//    foo: Model('t) => bar: Model('t).field('bar')
604		// 2. foo.mapped('..')
605		//    foo: Model('t) => _: Model('t).mapped('..')
606		// 3. foo.mapped(lambda rec: 't): 't
607		#[cfg(debug_assertions)]
608		if node.byte_range().len() <= 64 {
609			tracing::trace!("type_of {} '{}'", node.kind(), &contents[node.byte_range()]);
610		} else {
611			tracing::trace!("type_of {} range={:?}", node.kind(), node.byte_range());
612		}
613		match normalize(&mut node).kind() {
614			"subscript" => {
615				let lhs = node.child_by_field_name("value")?;
616				let rhs = node.child_by_field_name("subscript")?;
617				let obj_ty = self.type_of(lhs, scope, contents)?;
618				match _TR!(obj_ty) {
619					Type::Env if rhs.kind() == "string" => {
620						Some(_T!(Type::Model(contents[rhs.byte_range().shrink(1)].into())))
621					}
622					Type::Env => Some(_T!["unknown"]),
623					Type::Model(_) | Type::Record(_) => Some(obj_ty),
624					Type::Dict(key, value) => {
625						let rhs = self.type_of(rhs, scope, contents);
626						// FIXME: We trust that the user makes the correct judgment here and returns the type requested.
627						rhs.is_none_or(|rhs| rhs == *key).then_some(*value)
628					}
629					Type::DictBag(properties) => {
630						// compare by key
631						if let Some(rhs) = dig!(rhs, string_content(1)) {
632							let rhs = &contents[rhs.byte_range()];
633							for (key, value) in properties {
634								match key {
635									DictKey::String(key) if key.as_str() == rhs => {
636										return Some(*value);
637									}
638									DictKey::String(_) | DictKey::Type(_) => {}
639								}
640							}
641							return None;
642						}
643
644						// compare by type
645						let rhs = self.type_of(rhs, scope, contents)?;
646						for (key, value) in properties {
647							match key {
648								DictKey::Type(key) if *key == rhs => return Some(*value),
649								DictKey::Type(_) | DictKey::String(_) => {}
650							}
651						}
652
653						None
654					}
655					// FIXME: Again, just trust that the user is doing the right thing.
656					Type::List(ListElement::Occupied(slot)) => Some(*slot),
657					_ => None,
658				}
659			}
660			"attribute" => self.type_of_attribute_node(node, scope, contents),
661			"identifier" => {
662				if let Some(parent) = node.parent()
663					&& parent.kind() == "attribute"
664					&& parent.named_child(0).unwrap() != node
665				{
666					return self.type_of_attribute_node(parent, scope, contents);
667				}
668
669				let key = &contents[node.byte_range()];
670				if key == "super" {
671					return Some(_T!(Type::Super));
672				}
673				if let Some(type_) = scope.get(key) {
674					return Some(_T!(type_.clone()));
675				}
676				if key == "request" {
677					return Some(_T!(Type::HttpRequest));
678				}
679				None
680			}
681			"assignment" => {
682				let rhs = node.named_child(1)?;
683				self.type_of(rhs, scope, contents)
684			}
685			"call" => self.type_of_call_node(node, scope, contents),
686			"binary_operator" | "boolean_operator" => {
687				// (_ left right)
688				if let Some(left) = node.child_by_field_name("left")
689					&& let Some(typ) = self.type_of(left, scope, contents)
690				{
691					Some(typ)
692				} else {
693					self.type_of(node.child_by_field_name("right")?, scope, contents)
694				}
695			}
696			"conditional_expression" => {
697				// a if b else c
698				let ty = node
699					.named_child(0)
700					.and_then(|child| self.type_of(child, scope, contents));
701				ty.or_else(|| {
702					node.named_child(2)
703						.and_then(|child| self.type_of(child, scope, contents))
704				})
705			}
706			"dictionary_comprehension" => {
707				let pair = dig!(node, pair)?;
708				let mut comprehension_scope;
709				let mut pair_scope = scope;
710				if let Some(for_in_clause) = dig!(node, for_in_clause(1))
711					&& let Some(scrutinee) = for_in_clause.child_by_field_name("left")
712					&& let Some(iteratee) = for_in_clause.child_by_field_name("right")
713					&& let Some(iter_ty) = self.type_of(iteratee, scope, contents)
714					&& let Some(iter_ty) = self.type_of_iterable(iter_ty)
715				{
716					// FIXME: How to prevent this clone?
717					comprehension_scope = Scope::new(Some(scope.clone()));
718					self.destructure_into_patternlist_like(scrutinee, iter_ty, &mut comprehension_scope, contents);
719					pair_scope = &comprehension_scope;
720				}
721				let lhs = pair
722					.named_child(0)
723					.and_then(|lhs| self.type_of(lhs, pair_scope, contents));
724				let rhs = pair
725					.named_child(1)
726					.and_then(|lhs| self.type_of(lhs, pair_scope, contents));
727				if lhs.is_some() || rhs.is_some() {
728					let value_id = _T!(Type::Value);
729					Some(_T!(Type::Dict(lhs.unwrap_or(value_id), rhs.unwrap_or(value_id))))
730				} else {
731					None
732				}
733			}
734			"dictionary" => {
735				let mut properties = vec![];
736				for child in node.named_children(&mut node.walk()) {
737					if child.kind() == "pair"
738						&& let Some(lhs) = child.child_by_field_name("key")
739						&& let Some(rhs) = child.child_by_field_name("value")
740					{
741						let key;
742						if let Some(lhs) = dig!(lhs, string_content(1)) {
743							key = DictKey::String(ImStr::from(&contents[lhs.byte_range()]));
744						} else if matches!(lhs.kind(), "true" | "false" | "string" | "none" | "float" | "integer") {
745							key = DictKey::Type(_T!( @contents[lhs.byte_range()]));
746						} else if let Some(lhs) = self.type_of(lhs, scope, contents) {
747							key = DictKey::Type(lhs);
748						} else {
749							continue;
750						}
751
752						let value = self.type_of(rhs, scope, contents).unwrap_or_else(|| _T!(Type::Value));
753						properties.push((key, value));
754					}
755				}
756				Some(_T!(Type::DictBag(properties)))
757			}
758			"list" => {
759				let mut slot = ListElement::Vacant;
760				for child in node.named_children(&mut node.walk()) {
761					if let Some(child) = self.type_of(child, scope, contents) {
762						slot = ListElement::Occupied(child);
763						break;
764					}
765				}
766				Some(_T!(Type::List(slot)))
767			}
768			"expression_list" | "tuple" => {
769				let mut cursor = node.walk();
770				let value_id = _T!(Type::Value);
771				let tuple = node.named_children(&mut cursor).filter_map(|child| {
772					if child.kind() == "comment" {
773						return None;
774					}
775					Some(self.type_of(child, scope, contents).unwrap_or(value_id))
776				});
777				Some(_T!(Type::Tuple(tuple.collect())))
778			}
779			"string" => Some(_T!( @ "str")),
780			"integer" => Some(_T!( @ "int")),
781			"float" => Some(_T!( @ "float")),
782			"true" | "false" | "comparison_operator" => Some(_T!( @ "bool")),
783			_ => None,
784		}
785	}
786	pub(crate) fn type_of_iterable(&self, tid: TypeId) -> Option<TypeId> {
787		match _TR!(tid) {
788			Type::Model(_) => Some(tid),
789			Type::List(inner) => inner.clone().into(),
790			Type::Iterable(inner) => *inner,
791			// TODO: tuple -> union
792			_ => None,
793		}
794	}
795	fn wrap_in_container<F: FnOnce(Type) -> Type>(type_: Type, producer: F) -> Type {
796		match type_ {
797			Type::Model(..) => type_,
798			_ => producer(type_),
799		}
800	}
801	fn type_of_call_node(&self, call: Node<'_>, scope: &Scope, contents: &str) -> Option<TypeId> {
802		let func = call.named_child(0)?;
803		if func.kind() == "identifier" {
804			match &contents[func.byte_range()] {
805				"zip" => {
806					let args = call.named_child(1)?;
807					let mut cursor = args.walk();
808					let value_id = _T!(Type::Value);
809					let children = args.named_children(&mut cursor).map(|child| {
810						let tid = self.type_of(child, scope, contents).unwrap_or(value_id);
811						self.type_of_iterable(tid).unwrap_or(value_id)
812					});
813					let tuple = _T!(Type::Tuple(children.collect()));
814					return Some(_T!(Type::Iterable(Some(tuple))));
815				}
816				"enumerate" => {
817					let arg = call.named_child(1)?.named_child(0);
818					let arg = arg
819						.and_then(|arg| self.type_of(arg, scope, contents))
820						.unwrap_or_else(|| _T!(Type::Value));
821					let intid = _T!(Type::PyBuiltin("int".into()));
822					let tuple = _T!(Type::Tuple(vec![intid, arg]));
823					return Some(_T!(Type::Iterable(Some(tuple))));
824				}
825				"tuple" => {
826					let args = call.named_child(1)?;
827					if args.kind() == "argument_list" {
828						let mut cursor = args.walk();
829						let value_id = _T!(Type::Value);
830						let children = args
831							.named_children(&mut cursor)
832							.map(|child| self.type_of(child, scope, contents).unwrap_or(value_id));
833						return Some(_T!(Type::Tuple(children.collect())));
834					}
835				}
836				"dict" => {
837					let arg = call.named_child(1)?.named_child(0)?;
838					let arg = self.type_of(arg, scope, contents)?;
839					let arg = self.type_of_iterable(arg)?;
840					if let Type::Tuple(tuple) = _TR!(arg)
841						&& let [lhs, rhs] = &tuple[..]
842					{
843						return Some(_T!(Type::Dict(*lhs, *rhs)));
844					}
845					return Some(_T!(Type::Dict(_T!(Type::Value), _T!(Type::Value))));
846				}
847				"defaultdict" => {
848					let arg = call.named_child(1)?.named_child(0)?;
849					if matches!(&contents[arg.byte_range()], "list" | "dict" | "float" | "int") {
850						// TODO: consider more general functions and type ctors
851						return Some(_T!(Type::Dict(
852							_T!(Type::Value),
853							_T!(Type::PyBuiltin(contents[arg.byte_range()].into()))
854						)));
855					}
856					if arg.kind() != "lambda" {
857						return Some(_T!(Type::Dict(_T!(Type::Value), _T!(Type::Value))));
858					}
859					// (lambda body: (_))
860					let body = arg.child_by_field_name("body")?;
861					let body_ty = self.type_of(body, scope, contents).unwrap_or_else(|| _T!(Type::Value));
862					return Some(_T!(Type::Dict(_T!(Type::Value), body_ty)));
863				}
864				"super" => {}
865				_ => return None,
866			};
867		}
868
869		let func = self.type_of(func, scope, contents)?;
870		match _TR!(func) {
871			Type::RefFn => {
872				// (call (_) @func (argument_list . (string) @xml_id))
873				let xml_id = call.named_child(1)?.named_child(0)?;
874				if xml_id.kind() == "string" {
875					Some(_T!(Type::Record(contents[xml_id.byte_range().shrink(1)].into())))
876				} else {
877					None
878				}
879			}
880			Type::ModelFn(model) => Some(_T!(Type::Model(model.clone()))),
881			Type::Super => Some(_T!(scope.get(scope.super_.as_deref()?).cloned()?)),
882			Type::Method(model, mapped) if mapped.as_str() == "mapped" => {
883				// (call (_) @func (argument_list . [(string) (lambda)] @mapped))
884				let mapped = call.named_child(1)?.named_child(0)?;
885				match mapped.kind() {
886					"string" => {
887						let mut model: Spur = (*model).into();
888						let mut mapped = &contents[mapped.byte_range().shrink(1)];
889						self.models.resolve_mapped(&mut model, &mut mapped, None).ok()?;
890						let type_ = self.type_of_attribute(&Type::Model(_R(model).into()), mapped, scope)?;
891						let type_ = Index::wrap_in_container(type_, |it| Type::List(ListElement::Occupied(_T!(it))));
892						Some(_T!(type_))
893					}
894					"lambda" => {
895						// (lambda (lambda_parameters)? body: (_))
896						let mut scope = Scope::new(Some(scope.clone()));
897						if let Some(params) = mapped.child_by_field_name(b"parameters") {
898							let first_arg = params.named_child(0)?;
899							if first_arg.kind() == "identifier" {
900								let first_arg = &contents[first_arg.byte_range()];
901								scope.insert(first_arg.to_string(), Type::Model(_R(model).into()));
902							}
903						}
904						let body = mapped.child_by_field_name(b"body")?;
905						let type_ = self.type_of(body, &scope, contents).unwrap_or_else(|| _T!(Type::Value));
906						let type_ = Index::wrap_in_container(_TR!(type_).clone(), |it| {
907							Type::List(ListElement::Occupied(_T!(it)))
908						});
909						Some(_T!(type_))
910					}
911					_ => None,
912				}
913			}
914			Type::Method(model, grouped) if grouped.as_str() == "grouped" => {
915				// (call (_) @func (argument_list . [(string) (lambda)] @mapped))
916				let grouped = call.named_child(1)?.named_child(0)?;
917				match grouped.kind() {
918					"string" => {
919						let mut model: Spur = (*model).into();
920						let mut grouped = &contents[grouped.byte_range().shrink(1)];
921						self.models.resolve_mapped(&mut model, &mut grouped, None).ok()?;
922						let model = Type::Model(_R(model).into());
923						let groupby = self.type_of_attribute(&model, grouped, scope)?;
924						Some(_T!(Type::Dict(_T!(groupby), _T!(model))))
925					}
926					"lambda" => {
927						let mut scope = Scope::new(Some(scope.clone()));
928						if let Some(params) = grouped.child_by_field_name(b"parameters") {
929							let first_arg = params.named_child(0)?;
930							if first_arg.kind() == "identifier" {
931								let first_arg = &contents[first_arg.byte_range()];
932								scope.insert(first_arg.to_string(), Type::Model(_R(model).into()));
933							}
934						}
935						let body = grouped.child_by_field_name(b"body")?;
936						let groupby = self.type_of(body, &scope, contents).unwrap_or_else(|| _T!(Type::Value));
937						let model = Type::Model(_R(model).into());
938						Some(_T!(Type::Dict(groupby, _T!(model))))
939					}
940					_ => None,
941				}
942			}
943			Type::Method(model, read_group) if read_group.as_str() == "_read_group" => {
944				let mut groupby = vec![];
945				let mut aggs = vec![];
946				let args = call.named_child(1)?;
947
948				#[derive(PartialEq, Eq)]
949				enum Aggregation<'a> {
950					Recordset,
951					Raw(&'a str),
952				}
953
954				fn gather_attributes<'out>(
955					contents: &'out str,
956					arg: Node,
957					out: &mut Vec<(&'out str, Option<Aggregation<'out>>)>,
958				) {
959					let mut cursor = arg.walk();
960					for field in arg.named_children(&mut cursor) {
961						if let Some(field) = dig!(field, string_content(1)) {
962							let mut field = &contents[field.byte_range()];
963							let mut agg = None;
964							if let Some((inner, raw_agg)) = field.split_once(':') {
965								field = inner;
966								match raw_agg {
967									"recordset" => agg = Some(Aggregation::Recordset),
968									_ => agg = Some(Aggregation::Raw(raw_agg)),
969								}
970							}
971							out.push((field, agg));
972						}
973					}
974				}
975
976				for (idx, arg) in args.named_children(&mut args.walk()).enumerate().take(3) {
977					if arg.kind() == "keyword_argument" {
978						let out = match &contents[arg.child_by_field_name("key")?.byte_range()] {
979							"groupby" => &mut groupby,
980							"aggregates" => &mut aggs,
981							_ => continue,
982						};
983						let Some(arg) = arg.child_by_field_name("value") else {
984							continue;
985						};
986						if arg.kind() != "list" {
987							continue;
988						}
989						gather_attributes(contents, arg, out);
990						continue;
991					}
992
993					if arg.kind() != "list" || idx > 2 || idx == 0 {
994						continue;
995					}
996
997					let out = if idx == 1 { &mut groupby } else { &mut aggs };
998					gather_attributes(contents, arg, out);
999				}
1000
1001				groupby.extend(aggs);
1002				groupby.dedup();
1003				let model = Type::Model(_R(*model).into());
1004				let model_tid = _T!(model.clone());
1005				let value_id = _T!(Type::Value);
1006				// FIXME: This is not quite correct as only recordset and numeric aggregations make sense.
1007				let aggs = groupby.into_iter().map(|(attr, agg)| {
1008					if attr == "id"
1009						&& let Some(Aggregation::Recordset) = agg
1010					{
1011						return model_tid;
1012					}
1013					match self.type_of_attribute(&model, attr, scope) {
1014						Some(type_) => _T!(type_),
1015						None => value_id,
1016					}
1017				});
1018				let tuple = _T!(Type::Tuple(aggs.collect()));
1019				Some(_T!(Type::List(ListElement::Occupied(tuple))))
1020			}
1021			Type::Method(model, read) if read.as_str() == "read" => {
1022				let model = Type::Model(_R(model).into());
1023				let args = call.named_child(1)?;
1024				let fields = dig!(args, list)?;
1025				let mut dict: Vec<(DictKey, TypeId)> = vec![];
1026				for field in fields.named_children(&mut fields.walk()) {
1027					let Some(field) = dig!(field, string_content(1)) else {
1028						continue;
1029					};
1030					let key = &contents[field.byte_range()];
1031					if let Some(field_ty) = self.type_of_attribute(&model, key, scope) {
1032						dict.push((DictKey::String(key.into()), _T!(field_ty)));
1033					}
1034				}
1035				Some(_T!(Type::List(ListElement::Occupied(_T!(Type::DictBag(dict))))))
1036			}
1037			Type::Method(model, method) => {
1038				let method = _G(method)?;
1039				let args = self.prepare_call_scope(*model, method.into(), call, scope, contents);
1040				Some(self.eval_method_rtype(method.into(), **model, args)?)
1041			}
1042			Type::PythonMethod(dict, method) if dict.is_dict() => {
1043				let Type::Dict(lhs, rhs) = _TR!(dict) else {
1044					unreachable!()
1045				};
1046				match method.as_str() {
1047					"items" => {
1048						let tuple = _T!(Type::Tuple(vec![*lhs, *rhs]));
1049						Some(_T!(Type::Iterable(Some(tuple))))
1050					}
1051					"get" => Some(*rhs),
1052					_ => None,
1053				}
1054			}
1055			Type::PythonMethod(dictbag, method) if dictbag.is_dictbag() => {
1056				let Type::DictBag(items) = _TR!(dictbag) else {
1057					unreachable!()
1058				};
1059				match method.as_str() {
1060					"get" => {
1061						let args = call.named_child(1)?;
1062						let arg_as_string = dig!(args, string.string_content(1));
1063						let argtype = args
1064							.named_child(0)
1065							.and_then(|node| self.type_of(node, scope, contents))
1066							.unwrap_or_else(|| _T!(Type::Value));
1067						items.iter().find_map(|(key, val)| match key {
1068							DictKey::String(key) => match arg_as_string {
1069								None => None,
1070								Some(arg) => (key.as_str() == &contents[arg.byte_range()]).then_some(*val),
1071							},
1072							DictKey::Type(key) => (*key == argtype).then_some(*val),
1073						})
1074					}
1075					_ => None,
1076				}
1077			}
1078			Type::Env
1079			| Type::Record(..)
1080			| Type::Model(..)
1081			| Type::HttpRequest
1082			| Type::Value
1083			| Type::PyBuiltin(..)
1084			| Type::Dict(..)
1085			| Type::DictBag(..)
1086			| Type::List(..)
1087			| Type::Iterable(..)
1088			| Type::Tuple(..)
1089			| Type::PythonMethod(..) => None,
1090		}
1091	}
1092
1093	#[instrument(skip_all, fields(model, method))]
1094	pub fn prepare_call_scope(
1095		&self,
1096		model: ModelName,
1097		method: Symbol<Method>,
1098		call: Node,
1099		scope: &Scope,
1100		contents: &str,
1101	) -> Option<(Vec<ImStr>, Scope)> {
1102		// (call
1103		//   (arguments_list
1104		//     (_)
1105		//     (keyword_argument (identifier) (_))))
1106		let arguments_list = dig!(call, argument_list(1))?;
1107
1108		let model = self.models.populate_properties(model, &[])?;
1109		let method = model.methods.as_ref()?.get(&method)?;
1110		let arguments = method.arguments.clone().unwrap_or_default();
1111		if arguments.is_empty() {
1112			return None;
1113		}
1114
1115		drop(model);
1116		let mut argtypes = Scope::new(None);
1117		let mut args = vec![];
1118		for (idx, arg) in arguments_list.named_children(&mut arguments_list.walk()).enumerate() {
1119			if arg.kind() == "keyword_argument"
1120				&& let Some(key) = arg.child_by_field_name("key")
1121				&& let Some(value) = arg.child_by_field_name("value")
1122			{
1123				let key = &contents[key.byte_range()];
1124				if !arguments.iter().any(|arg| match arg {
1125					FunctionParam::Named(arg) => arg.as_str() == key,
1126					_ => false,
1127				}) {
1128					continue;
1129				}
1130				let Some(tid) = self.type_of(value, scope, contents) else {
1131					continue;
1132				};
1133				args.push(key.into());
1134				argtypes.insert(key.to_string(), _TR!(tid).clone());
1135			} else if let Some(FunctionParam::Param(argname)) = arguments.get(idx)
1136				&& let Some(tid) = self.type_of(arg, scope, contents)
1137			{
1138				args.push(argname.clone());
1139				argtypes.insert(argname.to_string(), _TR!(tid).clone());
1140			} else {
1141				continue;
1142			}
1143		}
1144
1145		Some((args, argtypes))
1146	}
1147	#[instrument(skip_all, ret)]
1148	fn type_of_attribute_node(&self, attribute: Node<'_>, scope: &Scope, contents: &str) -> Option<TypeId> {
1149		let lhs = attribute.named_child(0)?;
1150		let lhsid = self.type_of(lhs, scope, contents)?;
1151		let lhs = _TR!(lhsid);
1152		let rhs = attribute.named_child(1)?;
1153		let attrname = &contents[rhs.byte_range()];
1154		match &contents[rhs.byte_range()] {
1155			"env" if matches!(lhs, Type::Model(..) | Type::Record(..) | Type::HttpRequest) => Some(Type::Env),
1156			"website" if matches!(lhs, Type::HttpRequest) => Some(Type::Model("website".into())),
1157			"ref" if matches!(lhs, Type::Env) => Some(Type::RefFn),
1158			"user" if matches!(lhs, Type::Env) => Some(Type::Model("res.users".into())),
1159			"company" | "companies" if matches!(lhs, Type::Env) => Some(Type::Model("res.company".into())),
1160			"mapped" | "grouped" | "_read_group" | "read" => {
1161				let model = self.try_resolve_model(lhs, scope)?;
1162				Some(Type::Method(model, attrname.into()))
1163			}
1164			dict_method @ ("items" | "get") if lhs.is_dictlike() => Some(Type::PythonMethod(lhsid, dict_method.into())),
1165			func if MODEL_METHODS.contains(func) => match lhs {
1166				Type::Model(model) => Some(Type::ModelFn(model.clone())),
1167				Type::Record(xml_id) => {
1168					let xml_id = _G(xml_id)?;
1169					let record = self.records.get(&xml_id)?;
1170					Some(Type::ModelFn(_R(*record.model.as_deref()?).into()))
1171				}
1172				_ => None,
1173			},
1174			ident if rhs.kind() == "identifier" => self.type_of_attribute(lhs, ident, scope),
1175			_ => None,
1176		}
1177		.map(|it| _T!(it))
1178	}
1179	#[instrument(skip_all, fields(attr=attr), ret)]
1180	pub fn type_of_attribute(&self, type_: &Type, attr: &str, scope: &Scope) -> Option<Type> {
1181		let model = self.try_resolve_model(type_, scope)?;
1182		let model_entry = self.models.populate_properties(model, &[])?;
1183		if let Some(attr_key) = _G(attr)
1184			&& let Some(attr_kind) = model_entry.prop_kind(attr_key)
1185		{
1186			match attr_kind {
1187				PropertyInfo::Field(type_) => {
1188					drop(model_entry);
1189					if let Some(relation) = self.models.resolve_related_field(attr_key.into(), model.into()) {
1190						return Some(Type::Model(_R(relation).into()));
1191					}
1192
1193					match _R(type_) {
1194						"Selection" | "Char" | "Text" | "Html" => Some(Type::PyBuiltin("str".into())),
1195						"Integer" => Some(Type::PyBuiltin("int".into())),
1196						"Float" | "Monetary" => Some(Type::PyBuiltin("float".into())),
1197						"Date" => Some(Type::PyBuiltin("date".into())),
1198						"Datetime" => Some(Type::PyBuiltin("datetime".into())),
1199						_ => None,
1200					}
1201				}
1202				PropertyInfo::Method => Some(Type::Method(model, attr.into())),
1203			}
1204		} else {
1205			match attr {
1206				"id" if matches!(type_, Type::Model(..) | Type::Record(..)) => Some(Type::PyBuiltin("Id".into())),
1207				"ids" if matches!(type_, Type::Model(..) | Type::Record(..)) => {
1208					Some(Type::List(ListElement::Occupied(_T!(Type::PyBuiltin("Id".into())))))
1209				}
1210				"display_name" if matches!(type_, Type::Model(..) | Type::Record(..)) => {
1211					Some(Type::PyBuiltin("str".into()))
1212				}
1213				"create_date" | "write_date" if matches!(type_, Type::Model(..) | Type::Record(..)) => {
1214					Some(Type::PyBuiltin("datetime".into()))
1215				}
1216				"create_uid" | "write_uid" if matches!(type_, Type::Model(..) | Type::Record(..)) => {
1217					Some(Type::Model("res.users".into()))
1218				}
1219				"_fields" if matches!(type_, Type::Model(..) | Type::Record(..)) => {
1220					Some(Type::Dict(_T!(Type::PyBuiltin("str".into())), _T!["ir.model.fields"]))
1221				}
1222				"env" if matches!(type_, Type::Model(..) | Type::Record(..) | Type::HttpRequest) => Some(Type::Env),
1223				_ => None,
1224			}
1225		}
1226	}
1227	pub fn has_attribute(&self, type_: &Type, attr: &str, scope: &Scope) -> bool {
1228		(|| -> Option<()> {
1229			let model = self.try_resolve_model(type_, scope)?;
1230			let entry = self.models.populate_properties(model, &[])?;
1231			let attr = _G(attr)?;
1232			entry.prop_kind(attr).map(|_| ())
1233		})()
1234		.is_some()
1235	}
1236	/// Call this method if it's unclear whether `type_` is a [`Type::Model`] and you just want the model's name.
1237	pub fn try_resolve_model(&self, type_: &Type, scope: &Scope) -> Option<ModelName> {
1238		match type_ {
1239			Type::Model(model) => Some(_G(model)?.into()),
1240			Type::Record(xml_id) => {
1241				// TODO: Refactor into method
1242				let xml_id = _G(xml_id)?;
1243				let record = self.records.get(&xml_id)?;
1244				record.model
1245			}
1246			Type::Super => self.try_resolve_model(scope.get(scope.super_.as_deref()?)?, scope),
1247			_ => None,
1248		}
1249	}
1250	#[inline]
1251	pub fn type_display(&self, type_: TypeId) -> Option<String> {
1252		self.type_display_indent(type_, 0)
1253	}
1254	fn type_display_indent(&self, type_: TypeId, indent: usize) -> Option<String> {
1255		match _TR!(type_) {
1256			Type::Dict(lhs, rhs) => {
1257				let lhs = self.type_display_indent(*lhs, indent);
1258				let lhs = lhs.as_deref().unwrap_or("...");
1259				let rhs = self.type_display_indent(*rhs, indent);
1260				let rhs = rhs.as_deref().unwrap_or("...");
1261				Some(fomat! { "dict[" (lhs) ", " (rhs) "]" })
1262			}
1263			Type::DictBag(properties) => {
1264				let preindent = " ".repeat(indent + 2);
1265				let empty_properties = properties.is_empty();
1266				let properties_fragment = fomat! {
1267					for (key, value) in properties {
1268						(preindent)
1269						match key {
1270							DictKey::String(key) => { "\"" (key) "\"" }
1271							DictKey::Type(key) if key.is_dictlike() => { "{...}" }
1272							DictKey::Type(key) => { (self.type_display_indent(*key, indent + 2).as_deref().unwrap_or("...")) }
1273						} ": " (self.type_display_indent(*value, indent + 2).as_deref().unwrap_or("..."))
1274					} sep { ",\n" }
1275				};
1276				let unindent = " ".repeat(indent);
1277				Some(fomat! {
1278					if !empty_properties {
1279						"{\n" (properties_fragment) "\n" (unindent) "}"
1280					} else {
1281						"{}"
1282					}
1283				})
1284			}
1285			Type::PyBuiltin(builtin) => Some(builtin.as_str().into()),
1286			Type::List(slot) => {
1287				let slot = match slot {
1288					ListElement::Vacant => None,
1289					ListElement::Occupied(slot) => self.type_display_indent(*slot, indent),
1290				};
1291				Some(match slot {
1292					Some(slot) => format!("list[{slot}]"),
1293					None => "list".into(),
1294				})
1295			}
1296			Type::Env => Some("Environment".into()),
1297			Type::Model(model) => Some(format!(r#"Model["{model}"]"#)),
1298			Type::Record(xml_id) => {
1299				let xml_id = _G(xml_id)?;
1300				let record = self.records.get(&xml_id)?;
1301				Some(_R(record.model?).into())
1302			}
1303			Type::Tuple(items) => Some(fomat! {
1304				"tuple["
1305				for item in items {
1306					(self.type_display_indent(*item, indent).as_deref().unwrap_or("..."))
1307				} sep { ", " }
1308				"]"
1309			}),
1310			Type::Iterable(output) => {
1311				let output = output.and_then(|inner| self.type_display_indent(inner, indent));
1312				let output = output.as_deref().unwrap_or("...");
1313				Some(format!("Iterable[{output}]"))
1314			}
1315			Type::Method(..) => unreachable!("Bug: this function should not handle methods"),
1316			Type::RefFn | Type::ModelFn(_) | Type::Super | Type::HttpRequest | Type::Value | Type::PythonMethod(..) => {
1317				if cfg!(debug_assertions) {
1318					Some(format!("{type_:?}"))
1319				} else {
1320					None
1321				}
1322			}
1323		}
1324	}
1325	/// Iterates depth-first over `node` using [`PreTravel`]. Automatically calls [`Scope::exit`] at suitable points.
1326	///
1327	/// [`ControlFlow::Continue`] accepts a boolean to indicate whether [`Scope::enter`] was called.
1328	///
1329	/// To accumulate bindings into a scope, use [`Index::build_scope`].
1330	pub fn walk_scope<T>(
1331		node: Node,
1332		scope: Option<Scope>,
1333		mut step: impl FnMut(&mut Scope, Node) -> ControlFlow<Option<T>, bool>,
1334	) -> (Scope, Option<T>) {
1335		let mut scope = scope.unwrap_or_default();
1336		let mut scope_ends = vec![];
1337		for node in PreTravel::new(node) {
1338			if !node.is_named() {
1339				continue;
1340			}
1341			if let Some(&end) = scope_ends.last()
1342				&& node.start_byte() > end
1343			{
1344				scope.exit();
1345				scope_ends.pop();
1346			}
1347			match step(&mut scope, node) {
1348				ControlFlow::Break(value) => return (scope, value),
1349				ControlFlow::Continue(entered) => {
1350					if entered {
1351						scope_ends.push(node.end_byte());
1352					}
1353				}
1354			}
1355		}
1356		(scope, None)
1357	}
1358	/// Resolves the return type of a method as well as populating its arguments and docstring.
1359	///
1360	/// `parameters` can be provided using [`Index::prepare_call_scope`].  
1361	#[instrument(level = "trace", ret, skip(self, model), fields(model = _R(model)))]
1362	pub fn eval_method_rtype(
1363		&self,
1364		method: Symbol<Method>,
1365		model: Spur,
1366		parameters: Option<(Vec<ImStr>, Scope)>,
1367	) -> Option<TypeId> {
1368		_ = self.models.populate_properties(model.into(), &[]);
1369		let mut model_entry = self.models.try_get_mut(&model).expect(format_loc!("deadlock"))?;
1370		let method_obj = model_entry.methods.as_mut()?.get_mut(&method)?;
1371
1372		if method_obj
1373			.pending_eval
1374			.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
1375			.is_err()
1376		{
1377			return None;
1378		}
1379
1380		let _guard = Defer(Some(|| {
1381			if let Some(model_entry) = self.models.get_mut(&model)
1382				&& let Some(methods) = model_entry.methods.as_ref()
1383				&& let Some(method) = methods.get(&method)
1384			{
1385				method.pending_eval.store(false, Ordering::Relaxed);
1386			}
1387		}));
1388
1389		let (argnames, mut scope) = parameters.unwrap_or_default();
1390		let cache_key = argnames
1391			.into_iter()
1392			.map(|arg| _T!(scope.get(&*arg).cloned().unwrap_or(Type::Value)))
1393			.collect::<Vec<_>>();
1394		if let Some(tid) = method_obj.eval_cache.get(&cache_key) {
1395			drop(model_entry);
1396			return Some(tid);
1397		}
1398
1399		let location = method_obj.locations.first().cloned()?;
1400		drop(model_entry);
1401
1402		let ast;
1403		let contents;
1404		let end_offset: ByteOffset;
1405		let path = location.path.to_path();
1406		if let Some(cached) = self.ast_cache.get(&path) {
1407			end_offset = rope_conv(location.range.end, cached.rope.slice(..));
1408			ast = cached.tree.clone();
1409			contents = String::from(cached.rope.clone());
1410		} else {
1411			contents = test_utils::fs::read_to_string(location.path.to_path()).unwrap();
1412			let rope = Rope::from_str(&contents);
1413			end_offset = rope_conv(location.range.end, rope.slice(..));
1414			let mut parser = Parser::new();
1415			parser.set_language(&tree_sitter_python::LANGUAGE.into()).unwrap();
1416			ast = parser.parse(contents.as_bytes(), None)?;
1417			self.ast_cache.insert(
1418				path,
1419				Arc::new(crate::index::AstCacheItem {
1420					tree: ast.clone(),
1421					rope,
1422				}),
1423			);
1424		}
1425
1426		// TODO: Improve this heuristic
1427		fn is_toplevel_return(mut node: Node) -> bool {
1428			while node.kind() != "function_definition" {
1429				tracing::trace!("{}", node.kind());
1430				match node.parent() {
1431					Some(parent) => node = parent,
1432					None => return false,
1433				}
1434			}
1435
1436			fn is_block_of_class(node: Node) -> bool {
1437				node.kind() == "block" && node.parent().is_some_and(|parent| parent.kind() == "class_definition")
1438			}
1439
1440			if let Some(decoration) = node.parent()
1441				&& decoration.kind() == "decorated_definition"
1442			{
1443				return decoration.parent().is_some_and(is_block_of_class);
1444			}
1445
1446			node.parent().is_some_and(is_block_of_class)
1447		}
1448
1449		let (self_type, fn_scope, self_param) = determine_scope(ast.root_node(), &contents, end_offset.0)?;
1450		let self_type = match self_type {
1451			Some(type_) => &contents[type_.byte_range().shrink(1)],
1452			None => "",
1453		};
1454		scope.super_ = Some(self_param.into());
1455		scope.insert(self_param.to_string(), Type::Model(self_type.into()));
1456		let offset = fn_scope.end_byte();
1457		let (_, type_) = Self::walk_scope(fn_scope, Some(scope), |scope, node| {
1458			let entered = self.build_scope(scope, node, offset, &contents).map_break(|_| None)?;
1459			// TODO: When implementing freestanding functions, make the toplevel check optional
1460			if node.kind() == "return_statement" && is_toplevel_return(node) {
1461				let Some(child) = node.named_child(0) else {
1462					return ControlFlow::Continue(entered);
1463				};
1464				let Some(type_) = self.type_of(child, scope, &contents) else {
1465					return ControlFlow::Continue(entered);
1466				};
1467
1468				let type_ = _TR!(type_);
1469				return match self.try_resolve_model(type_, scope) {
1470					Some(resolved) => ControlFlow::Break(Some(Type::Model(ImStr::from(_R(resolved))))),
1471					None => ControlFlow::Break(Some(type_.clone())),
1472				};
1473			}
1474
1475			ControlFlow::Continue(entered)
1476		});
1477
1478		let mut model = self.models.try_get_mut(&model).expect(format_loc!("deadlock"))?;
1479		let method = Arc::make_mut(model.methods.as_mut()?.get_mut(&method)?);
1480
1481		let docstring = Self::parse_method_docstring(fn_scope, &contents)
1482			.map(|doc| ImStr::from(Method::postprocess_docstring(doc)));
1483		method.docstring = docstring;
1484
1485		if let Some(params) = fn_scope.child_by_field_name("parameters") {
1486			// python parameters can be delineated by three separators:
1487			// - parameters before `/` are positional only (`positional_separator`)
1488			// - parameters between `/` and `*` can be either positional or named
1489			// - a catch-all positional `*args` (`keyword_separator` or `list_splat_pattern`)
1490			// - parameters after `*` and before `**` are named (`default_parameter`)
1491			// - a catch-all named `**kwargs` (`dictionary_splat_pattern` only)
1492			let mut cursor = params.walk();
1493			let args = params.named_children(&mut cursor).skip(1).filter_map(|param| {
1494				Some(match param.kind() {
1495					"identifier" => FunctionParam::Param(ImStr::from(&contents[param.byte_range()])),
1496					"positional_separator" => FunctionParam::PosEnd,
1497					"keyword_separator" => FunctionParam::EitherEnd(None),
1498					"list_splat_pattern" => {
1499						FunctionParam::EitherEnd(Some(ImStr::from(&contents[param.named_child(0)?.byte_range()])))
1500					}
1501					"dictionary_splat_pattern" => FunctionParam::Kwargs("kwargs".into()),
1502					"default_parameter" => {
1503						let name = param.named_child(0)?;
1504						let name = &contents[name.byte_range()];
1505						FunctionParam::Named(ImStr::from(name))
1506					}
1507					_ => return None,
1508				})
1509			});
1510			method.arguments = Some(args.collect());
1511		}
1512
1513		method.pending_eval.store(false, Ordering::Release);
1514		if let Some(type_) = type_ {
1515			let tid = _T!(type_);
1516			method.eval_cache.insert(cache_key, tid);
1517			Some(tid)
1518		} else {
1519			None
1520		}
1521	}
1522	/// `pattern` is `(identifier | pattern_list | tuple_pattern)`, the `a, b` in `for a, b in ...`.
1523	fn destructure_into_patternlist_like(&self, pattern: Node, tid: TypeId, scope: &mut Scope, contents: &str) {
1524		if pattern.kind() == "identifier" {
1525			let name = &contents[pattern.byte_range()];
1526			scope.insert(name.to_string(), _TR!(tid).clone());
1527		} else if matches!(pattern.kind(), "pattern_list" | "tuple_pattern") {
1528			if let Type::Tuple(inner) = _TR!(tid) {
1529				let mut inner = inner.iter();
1530				for child in pattern.named_children(&mut pattern.walk()) {
1531					if matches!(child.kind(), "identifier" | "tuple_pattern")
1532						&& let Some(type_) = inner.next()
1533					{
1534						self.destructure_into_patternlist_like(child, *type_, scope, contents);
1535					}
1536				}
1537			} else if let Some(inner) = self.type_of_iterable(tid) {
1538				// spread this type to all params
1539				for child in pattern.named_children(&mut pattern.walk()) {
1540					if matches!(child.kind(), "identifier" | "tuple_pattern") {
1541						self.destructure_into_patternlist_like(child, inner, scope, contents);
1542					}
1543				}
1544			}
1545		}
1546	}
1547	fn parse_method_docstring<'out>(fn_scope: Node, contents: &'out str) -> Option<&'out str> {
1548		let block = fn_scope.child_by_field_name("body")?;
1549		dig!(block, expression_statement.string.string_content(1)).map(|node| &contents[node.byte_range()])
1550	}
1551}
1552
1553/// Returns `(self_type, fn_scope, self_param)`.
1554///
1555/// `fn_scope` is customarily a `function_definition` node.
1556#[instrument(level = "trace", skip_all, ret)]
1557pub fn determine_scope<'out, 'node>(
1558	node: Node<'node>,
1559	contents: &'out str,
1560	offset: usize,
1561) -> Option<(Option<Node<'node>>, Node<'node>, &'out str)> {
1562	let query = FieldCompletion::query();
1563	let mut self_type = None;
1564	let mut self_param = None;
1565	let mut fn_scope = None;
1566	let mut cursor = QueryCursor::new();
1567	let mut matches = cursor.matches(query, node, contents.as_bytes());
1568	'scoping: while let Some(match_) = matches.next() {
1569		// @class
1570		let class = match_.captures.first()?;
1571		if !class.node.byte_range().contains_end(offset) {
1572			continue;
1573		}
1574		for capture in match_.captures {
1575			match FieldCompletion::from(capture.index) {
1576				Some(FieldCompletion::Name) => {
1577					if self_type.is_none() {
1578						self_type = Some(capture.node);
1579					}
1580				}
1581				Some(FieldCompletion::SelfParam) => {
1582					self_param = Some(capture.node);
1583				}
1584				Some(FieldCompletion::Scope) => {
1585					if !capture.node.byte_range().contains_end(offset) {
1586						continue 'scoping;
1587					}
1588					fn_scope = Some(capture.node);
1589				}
1590				None => {}
1591			}
1592		}
1593		if fn_scope.is_some() {
1594			break;
1595		}
1596	}
1597	let fn_scope = fn_scope?;
1598	let self_param = &contents[self_param?.byte_range()];
1599	Some((self_type, fn_scope, self_param))
1600}
1601
1602#[cfg(test)]
1603mod tests {
1604	use pretty_assertions::assert_eq;
1605	use ropey::Rope;
1606	use tower_lsp_server::ls_types::Position;
1607	use tree_sitter::{Parser, QueryCursor, StreamingIterator, StreamingIteratorMut};
1608
1609	use crate::analyze::{FieldCompletion, Type, type_cache};
1610	use crate::index::_I;
1611	use crate::utils::{ByteOffset, acc_vec, rope_conv};
1612	use crate::{index::Index, test_utils::cases::foo::prepare_foo_index};
1613
1614	#[test]
1615	fn test_field_completion() {
1616		let mut parser = Parser::new();
1617		parser.set_language(&tree_sitter_python::LANGUAGE.into()).unwrap();
1618		let contents = br#"
1619class Foo(models.AbstractModel):
1620	_name = 'foo'
1621	_description = 'What?'
1622	_inherit = 'inherit_foo'
1623	foo = fields.Char(related='related')
1624	@api.depends('mapped')
1625	def foo(self):
1626		pass
1627"#;
1628		let ast = parser.parse(&contents[..], None).unwrap();
1629		let query = FieldCompletion::query();
1630		let mut cursor = QueryCursor::new();
1631		let actual = cursor
1632			.matches(query, ast.root_node(), &contents[..])
1633			.map(|match_| {
1634				match_
1635					.captures
1636					.iter()
1637					.map(|capture| FieldCompletion::from(capture.index))
1638					.collect::<Vec<_>>()
1639			})
1640			.fold_mut(vec![], acc_vec);
1641		// Allow nested patterns
1642		let actual = actual.iter().map(Vec::as_slice).collect::<Vec<_>>();
1643		use FieldCompletion as T;
1644		assert!(
1645			matches!(
1646				&actual[..],
1647				[
1648					[None, None, Some(T::Name), Some(T::Scope), Some(T::SelfParam)],
1649					[None, None, Some(T::Name), Some(T::Scope), Some(T::SelfParam)]
1650				]
1651			),
1652			"{actual:?}"
1653		)
1654	}
1655
1656	#[test]
1657	fn test_determine_scope() {
1658		let mut parser = Parser::new();
1659		parser.set_language(&tree_sitter_python::LANGUAGE.into()).unwrap();
1660		let contents = r#"
1661class Foo(models.Model):
1662	_name = 'foo'
1663	def scope(self):
1664		pass
1665"#;
1666		let ast = parser.parse(contents, None).unwrap();
1667		let rope = Rope::from(contents);
1668		let fn_start: ByteOffset = rope_conv(Position { line: 3, character: 1 }, rope.slice(..));
1669		let fn_scope = ast
1670			.root_node()
1671			.named_descendant_for_byte_range(fn_start.0, fn_start.0)
1672			.unwrap();
1673		super::determine_scope(ast.root_node(), contents, fn_start.0)
1674			.unwrap_or_else(|| panic!("{}", fn_scope.to_sexp()));
1675	}
1676
1677	#[test]
1678	fn test_resolve_method_returntype() {
1679		let index = Index {
1680			models: prepare_foo_index(),
1681			..Default::default()
1682		};
1683
1684		assert_eq!(
1685			index.eval_method_rtype(_I("test").into(), _I("bar"), None),
1686			Some(type_cache().get_or_intern(Type::Model("foo".into())))
1687		)
1688	}
1689
1690	#[test]
1691	fn test_super_analysis() {
1692		let index = Index {
1693			models: prepare_foo_index(),
1694			..Default::default()
1695		};
1696
1697		assert_eq!(
1698			index.eval_method_rtype(_I("test").into(), _I("quux"), None),
1699			Some(type_cache().get_or_intern(Type::Model("foo".into())))
1700		)
1701	}
1702}