Skip to content

Commit f73f012

Browse files
mmahroussfda-odoo
authored andcommitted
[FIX] make fields init hook work for user defined Fields
1 parent 3b874c4 commit f73f012

File tree

4 files changed

+107
-60
lines changed

4 files changed

+107
-60
lines changed

server/src/core/evaluation.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -897,14 +897,27 @@ impl Evaluation {
897897
let init = base_sym.borrow().get_member_symbol(session, &S!("__init__"), module.clone(), true, false, false, false);
898898
let mut found_hook = false;
899899
if let Some(init) = init.0.first() {
900+
let init_sym_file = init.borrow().get_file().as_ref().unwrap().upgrade().unwrap().clone();
901+
if init.borrow().evaluations().is_some()
902+
&& init.borrow().evaluations().unwrap().len() == 0
903+
&& !init_sym_file.borrow().is_external()
904+
&& init_sym_file.borrow().build_status(BuildSteps::ARCH_EVAL) == BuildStatus::DONE
905+
&& init.borrow().build_status(BuildSteps::ARCH) != BuildStatus::IN_PROGRESS
906+
&& init.borrow().build_status(BuildSteps::ARCH_EVAL) != BuildStatus::IN_PROGRESS
907+
&& init.borrow().build_status(BuildSteps::VALIDATION) == BuildStatus::PENDING {
908+
let mut v = PythonValidator::new(init.borrow().get_entry().unwrap(), init.clone());
909+
v.validate(session);
910+
}
900911
if let Some(init_eval) = init.borrow().evaluations() {
901912
//init will always return an instance of the class, so we are not searching the method to check its return type, but rather to check if there is
902913
//an hook on it. Hooks, can be used to use parameters for context (see relational fields for example).
903914
if init_eval.len() == 1 && init_eval[0].symbol.get_symbol_hook.is_some() {
915+
context.as_mut().unwrap().insert(S!("constructing_class"), ContextValue::SYMBOL(Rc::downgrade(&base_sym)));
904916
context.as_mut().unwrap().insert(S!("parameters"), ContextValue::ARGUMENTS(expr.arguments.clone()));
905917
found_hook = true;
906918
let init_result = init_eval[0].symbol.get_symbol_as_weak(session, context, &mut diagnostics, Some(parent.borrow().get_file().unwrap().upgrade().unwrap().clone()));
907919
context.as_mut().unwrap().remove(&S!("parameters"));
920+
context.as_mut().unwrap().remove(&S!("constructing_class"));
908921
evals.push(Evaluation{
909922
symbol: EvaluationSymbol {
910923
sym: EvaluationSymbolPtr::WEAK(init_result),

server/src/core/python_arch_eval.rs

Lines changed: 42 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::cell::RefCell;
44
use std::{u32, vec};
55

66
use ruff_text_size::{Ranged, TextRange, TextSize};
7-
use ruff_python_ast::{Alias, AnyRootNodeRef, Expr, ExprNamed, FStringPart, Identifier, Stmt, StmtAnnAssign, StmtAssign, StmtClassDef, StmtExpr, StmtFor, StmtFunctionDef, StmtIf, StmtReturn, StmtTry, StmtWhile, StmtWith};
7+
use ruff_python_ast::{Alias, AnyRootNodeRef, Expr, ExprNamed, FStringPart, Identifier, NodeIndex, Stmt, StmtAnnAssign, StmtAssign, StmtClassDef, StmtExpr, StmtFor, StmtFunctionDef, StmtIf, StmtReturn, StmtTry, StmtWhile, StmtWith};
88
use lsp_types::{Diagnostic, Position, Range};
99
use tracing::{debug, trace, warn};
1010

@@ -102,18 +102,23 @@ impl PythonArchEval {
102102
(file_info_ast_bw.get_stmts().unwrap(), None)
103103
},
104104
false => {
105-
let func_stmt = file_info_ast_bw.indexed_module.as_ref().unwrap().get_by_index(self.sym_stack[0].borrow().node_index().unwrap().load());
106-
match func_stmt {
107-
AnyRootNodeRef::Stmt(Stmt::FunctionDef(func_stmt)) => {
108-
(&func_stmt.body, Some(func_stmt))
109-
},
110-
_ => panic!("Expected function definition")
105+
let fun_index = self.sym_stack[0].borrow().node_index().unwrap().load();
106+
if fun_index == NodeIndex::NONE{ // uninitialized node index
107+
// Function has no body or is dynamically created from a hook
108+
(&vec![], None) // essentially skip evaluation
109+
} else {
110+
let func_stmt = file_info_ast_bw.indexed_module.as_ref().unwrap().get_by_index(fun_index);
111+
match func_stmt {
112+
AnyRootNodeRef::Stmt(Stmt::FunctionDef(func_stmt)) => {
113+
(&func_stmt.body, Some(func_stmt))
114+
},
115+
_ => panic!("Expected function definition")
116+
}
111117
}
112118
}
113119
};
114120
self.visit_sub_stmts(session, &ast);
115-
if !self.file_mode {
116-
let func_stmt = maybe_func_stmt.unwrap();
121+
if !self.file_mode && let Some(func_stmt) = maybe_func_stmt {
117122
self.diagnostics.extend(
118123
PythonArchEvalHooks::handle_func_decorators(session, func_stmt, self.sym_stack[0].clone(), self.file.clone(), self.current_step)
119124
);
@@ -557,7 +562,9 @@ impl PythonArchEval {
557562
};
558563
let model_classes = model.borrow().all_symbols(session, parent_class.find_module(), false);
559564
let fn_name = self.sym_stack[0].borrow().name().clone();
560-
let allowed_fields: HashSet<_> = model_classes.iter().filter_map(|(sym, _)| sym.borrow().as_class_sym()._model.as_ref().unwrap().computes.get(&fn_name).cloned()).flatten().collect();
565+
let allowed_fields: HashSet<_> = model_classes.iter().filter_map(|(sym, _)|
566+
sym.borrow().as_class_sym()._model.as_ref().unwrap().computes.get(&fn_name).cloned()
567+
).flatten().collect();
561568
if allowed_fields.is_empty() {
562569
continue;
563570
}
@@ -655,33 +662,33 @@ impl PythonArchEval {
655662
continue;
656663
}
657664
let symbol = &ref_sym[0].upgrade_weak();
658-
if let Some(symbol) = symbol {
659-
if symbol.borrow().typ() != SymType::COMPILED {
660-
if symbol.borrow().typ() != SymType::CLASS {
661-
if symbol.borrow().typ() != SymType::VARIABLE { //we followed_ref already, so if it's still a variable, it means we can't evaluate it. Skip diagnostic
662-
if let Some(diagnostic) = create_diagnostic(&session, DiagnosticCode::OLS01002, &[&AstUtils::flatten_expr(base)]) {
663-
self.diagnostics.push(Diagnostic {
664-
range: Range::new(Position::new(base.start().to_u32(), 0), Position::new(base.end().to_u32(), 0)),
665-
..diagnostic
666-
});
667-
}
668-
}
669-
} else {
670-
//Even if this is a valid class, we have to be sure that its own bases should have been loaded already
671-
let sym_file = symbol.borrow().get_file().clone();
672-
if let Some(file) = sym_file {
673-
if let Some(file) = file.upgrade() {
674-
if file.borrow().build_status(BuildSteps::ARCH_EVAL) != BuildStatus::DONE {
675-
SyncOdoo::build_now(session, &file, BuildSteps::ARCH_EVAL);
676-
}
677-
if !Rc::ptr_eq(&self.file, &file) {
678-
self.file.borrow_mut().add_dependency(&mut file.borrow_mut(), self.current_step, BuildSteps::ARCH_EVAL);
679-
}
680-
}
681-
}
682-
loc_sym.borrow_mut().as_class_sym_mut().bases.push(Rc::downgrade(&symbol));
665+
let Some(symbol) = symbol else {
666+
continue;
667+
};
668+
if symbol.borrow().typ() == SymType::COMPILED {
669+
continue; //Compiled classes do not have their bases loaded
670+
}
671+
if symbol.borrow().typ() != SymType::CLASS {
672+
if symbol.borrow().typ() != SymType::VARIABLE { //we followed_ref already, so if it's still a variable, it means we can't evaluate it. Skip diagnostic
673+
if let Some(diagnostic) = create_diagnostic(&session, DiagnosticCode::OLS01002, &[&AstUtils::flatten_expr(base)]) {
674+
self.diagnostics.push(Diagnostic {
675+
range: Range::new(Position::new(base.start().to_u32(), 0), Position::new(base.end().to_u32(), 0)),
676+
..diagnostic
677+
});
678+
}
679+
}
680+
} else {
681+
//Even if this is a valid class, we have to be sure that its own bases should have been loaded already
682+
let sym_file = symbol.borrow().get_file().clone();
683+
if let Some(file) = sym_file.and_then(|fw| fw.upgrade()) {
684+
if file.borrow().build_status(BuildSteps::ARCH_EVAL) != BuildStatus::DONE {
685+
SyncOdoo::build_now(session, &file, BuildSteps::ARCH_EVAL);
686+
}
687+
if !Rc::ptr_eq(&self.file, &file) {
688+
self.file.borrow_mut().add_dependency(&mut file.borrow_mut(), self.current_step, BuildSteps::ARCH_EVAL);
683689
}
684690
}
691+
loc_sym.borrow_mut().as_class_sym_mut().bases.push(Rc::downgrade(&symbol));
685692
}
686693
}
687694
}

server/src/core/python_arch_eval_hooks.rs

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use ruff_python_ast::StmtFunctionDef;
1111
use ruff_text_size::Ranged;
1212
use ruff_text_size::TextRange;
1313
use tracing::warn;
14+
use weak_table::traits::WeakElement;
1415
use crate::core::diagnostics::{create_diagnostic, DiagnosticCode};
1516
use crate::core::odoo::SyncOdoo;
1617
use crate::core::evaluation::Context;
@@ -586,6 +587,25 @@ static arch_eval_function_hooks: Lazy<Vec<PythonArchEvalFunctionHook>> = Lazy::n
586587
func: |_odoo: &mut SyncOdoo, _entry_point: &Rc<RefCell<EntryPoint>>, symbol: Rc<RefCell<Symbol>>| {
587588
PythonArchEvalHooks::_validation_env_ref(symbol.clone());
588589
}},
590+
PythonArchEvalFunctionHook {odoo_entry: true,
591+
tree: vec![(Sy!("0.0"), Sy!("18.1"), (vec![Sy!("odoo"), Sy!("fields")], vec![Sy!("Field"), Sy!("__init__")])),
592+
(Sy!("18.1"), Sy!("999.0"), (vec![Sy!("odoo"), Sy!("orm"), Sy!("fields")], vec![Sy!("Field"), Sy!("__init__")]))],
593+
if_exist_only: true,
594+
func: |_odoo: &mut SyncOdoo, _entry_point: &Rc<RefCell<EntryPoint>>, symbol: Rc<RefCell<Symbol>>| {
595+
let Some(fields_class_sym) = symbol.borrow().get_in_parents(&vec![SymType::CLASS], true).and_then(|s| s.upgrade()) else {
596+
return;
597+
};
598+
symbol.borrow_mut().set_evaluations(vec![Evaluation {
599+
symbol: EvaluationSymbol::new_with_symbol(
600+
Rc::downgrade(&fields_class_sym),
601+
Some(true),
602+
HashMap::new(),
603+
Some(PythonArchEvalHooks::eval_init)
604+
),
605+
value: None,
606+
range: None,
607+
}]);
608+
}},
589609
]});
590610

591611

@@ -997,7 +1017,7 @@ impl PythonArchEvalHooks {
9971017
context.get(&S!("range")).unwrap().as_text_range().start().to_u32(),
9981018
false
9991019
);
1000-
let mut context = HashMap::new();
1020+
let mut result_context = HashMap::new();
10011021

10021022
let mut contexts_to_add = HashMap::new();
10031023
if relational {
@@ -1026,28 +1046,32 @@ impl PythonArchEvalHooks {
10261046
for (arg_name, (field_name_expr, arg_range, bool_or_str)) in contexts_to_add {
10271047
match bool_or_str {
10281048
"str" => if let Some(related_string) = Evaluation::expr_to_str(session, field_name_expr, parent.clone(), &parameters.range.start(), false, &mut vec![]).0 {
1029-
context.insert(S!(arg_name), ContextValue::STRING(related_string.to_string()));
1030-
context.insert(format!("{arg_name}_arg_range"), ContextValue::RANGE(arg_range.clone()));
1049+
result_context.insert(S!(arg_name), ContextValue::STRING(related_string.to_string()));
1050+
result_context.insert(format!("{arg_name}_arg_range"), ContextValue::RANGE(arg_range.clone()));
10311051
},
10321052
"bool" => {
10331053
let maybe_boolean = Evaluation::expr_to_bool(session, field_name_expr, parent.clone(), &parameters.range.start(), false, &mut vec![]).0;
10341054
if let Some(boolean) = maybe_boolean {
1035-
context.insert(S!(arg_name), ContextValue::BOOLEAN(boolean));
1055+
result_context.insert(S!(arg_name), ContextValue::BOOLEAN(boolean));
10361056
}
10371057
if arg_name == "default" {
1038-
context.insert(S!("default"), ContextValue::BOOLEAN(true)); //set to True as the value is not really useful for now, but we want the key in context if one default is set
1058+
result_context.insert(S!("default"), ContextValue::BOOLEAN(true)); //set to True as the value is not really useful for now, but we want the key in context if one default is set
10391059
}
10401060
},
10411061
_ => {}
10421062
}
10431063
}
10441064

1045-
context.extend([
1065+
result_context.extend([
10461066
(S!("field_parent"), ContextValue::SYMBOL(Rc::downgrade(&parent))),
10471067
]);
1068+
let weak_eval = match context.get(&S!("constructing_class")) {
1069+
Some(ContextValue::SYMBOL(weak)) if !weak.is_expired() => weak.clone(),
1070+
_ => evaluation_sym.get_weak().weak.clone(),
1071+
};
10481072
return Some(EvaluationSymbolPtr::WEAK(EvaluationSymbolWeak {
1049-
weak: evaluation_sym.get_weak().weak.clone(),
1050-
context,
1073+
weak: weak_eval,
1074+
context: result_context,
10511075
instance: Some(true),
10521076
is_super: false
10531077
}));

server/src/core/python_validator.rs

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use ruff_python_ast::{Alias, AnyRootNodeRef, Expr, Identifier, Stmt, StmtAnnAssign, StmtAssert, StmtAssign, StmtAugAssign, StmtClassDef, StmtMatch, StmtRaise, StmtTry, StmtTypeAlias, StmtWith};
1+
use ruff_python_ast::{Alias, AnyRootNodeRef, Expr, Identifier, NodeIndex, Stmt, StmtAnnAssign, StmtAssert, StmtAssign, StmtAugAssign, StmtClassDef, StmtMatch, StmtRaise, StmtTry, StmtTypeAlias, StmtWith};
22
use ruff_text_size::{Ranged, TextRange, TextSize};
33
use tracing::{trace, warn};
44
use std::rc::Rc;
@@ -154,22 +154,25 @@ impl PythonValidator {
154154
let file_info = file_info_rc.borrow();
155155
if file_info.file_info_ast.borrow().indexed_module.is_some() {
156156
let file_info_ast = file_info.file_info_ast.borrow();
157-
let stmt = file_info_ast.indexed_module.as_ref().unwrap().get_by_index(self.sym_stack[0].borrow().node_index().unwrap().load());
158-
let body = match stmt {
159-
AnyRootNodeRef::Stmt(Stmt::FunctionDef(s)) => {
160-
&s.body
161-
},
162-
_ => {panic!("Wrong statement in validation ast extraction {} ", sym_type)}
163-
};
164-
let old_noqa = session.current_noqa.clone();
165-
session.current_noqa = self.sym_stack[0].borrow().get_noqas();
166-
self.validate_body(session, body);
167-
session.current_noqa = old_noqa;
168-
match stmt {
169-
AnyRootNodeRef::Stmt(Stmt::FunctionDef(_)) => {
170-
self.sym_stack[0].borrow_mut().as_func_mut().diagnostics.insert(BuildSteps::VALIDATION, self.diagnostics.clone());
171-
},
172-
_ => {panic!("Wrong statement in validation ast extraction {} ", sym_type)}
157+
let func_index = self.sym_stack[0].borrow().node_index().unwrap().load();
158+
if func_index != NodeIndex::NONE {
159+
let stmt = file_info_ast.indexed_module.as_ref().unwrap().get_by_index(func_index);
160+
let body = match stmt {
161+
AnyRootNodeRef::Stmt(Stmt::FunctionDef(s)) => {
162+
&s.body
163+
},
164+
_ => {panic!("Wrong statement in validation ast extraction {} ", sym_type)}
165+
};
166+
let old_noqa = session.current_noqa.clone();
167+
session.current_noqa = self.sym_stack[0].borrow().get_noqas();
168+
self.validate_body(session, body);
169+
session.current_noqa = old_noqa;
170+
match stmt {
171+
AnyRootNodeRef::Stmt(Stmt::FunctionDef(_)) => {
172+
self.sym_stack[0].borrow_mut().as_func_mut().diagnostics.insert(BuildSteps::VALIDATION, self.diagnostics.clone());
173+
},
174+
_ => {panic!("Wrong statement in validation ast extraction {} ", sym_type)}
175+
}
173176
}
174177
} else {
175178
warn!("no ast found on file info");

0 commit comments

Comments
 (0)