1use 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 "new",
74 "edit",
75 "save",
76);
77
78#[derive(Clone, Debug, PartialEq, Eq, Hash)]
80pub enum Type {
81 Env,
82 RefFn,
84 ModelFn(ImStr),
86 Model(ImStr),
87 Record(ImStr),
89 Super,
90 Method(ModelName, ImStr),
91 PythonMethod(TypeId, ImStr),
93 HttpRequest,
95 Dict(TypeId, TypeId),
96 DictBag(Vec<(DictKey, TypeId)>),
98 PyBuiltin(ImStr),
100 List(ListElement),
101 Tuple(Vec<TypeId>),
102 Iterable(Option<TypeId>),
103 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 PosEnd,
160 EitherEnd(Option<ImStr>),
162 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(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(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 let (self_type, fn_scope, self_param) = determine_scope(root, contents, range.start.0)?;
313
314 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 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 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 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 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 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 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 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 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 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 pub fn type_of(&self, mut node: Node, scope: &Scope, contents: &str) -> Option<TypeId> {
584 #[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 rhs.is_none_or(|rhs| rhs == *key).then_some(*value)
628 }
629 Type::DictBag(properties) => {
630 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 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 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 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 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 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 _ => 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 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 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 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 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 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 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 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 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 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 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 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 #[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 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 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 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 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 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#[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 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 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}