diff --git a/src/name_resolver.rs b/src/name_resolver.rs index 22dee30092..018efb3839 100644 --- a/src/name_resolver.rs +++ b/src/name_resolver.rs @@ -1,14 +1,18 @@ use plc_ast::{ - ast::ReferenceAccess, + ast::{ + flatten_expression_list, Assignment, AstNode, AstStatement, DataType, ReferenceAccess, TypeNature, + }, literals::StringValue, visitor::{AstVisitor, Walker}, }; +use plc_source::source_location::SourceLocation; +use plc_util::convention::internal_type_name; use crate::{ index::{Index, VariableIndexEntry}, resolver::{register_string_type, AnnotationMap, AnnotationMapImpl, StatementAnnotation, StringLiterals}, typesystem::{ - get_bigger_type, DataType, DataTypeInformation, BOOL_TYPE, DATE_AND_TIME_TYPE, DATE_TYPE, DINT_TYPE, + self, get_bigger_type, DataTypeInformation, BOOL_TYPE, DATE_AND_TIME_TYPE, DATE_TYPE, DINT_TYPE, LINT_TYPE, REAL_TYPE, TIME_OF_DAY_TYPE, TIME_TYPE, VOID_TYPE, }, }; @@ -20,7 +24,7 @@ pub enum Scope { GlobalVariable, LocalVariable(String), Composite(Vec), - StaticallyCallable, + Callable(Option), } fn to_variable_annotation( @@ -63,9 +67,7 @@ impl Scope { index.find_type(identifier).map(|dt| dt.get_type_information()).map(StatementAnnotation::from) } - Scope::Program => { - index.find_pou(identifier).filter(|p| p.is_program()).map(StatementAnnotation::from) - } + Scope::Program => index.find_pou(identifier).map(StatementAnnotation::from), // lookup a global variable Scope::GlobalVariable => { @@ -83,10 +85,10 @@ impl Scope { } // functions, programs, actions, methods - Scope::StaticallyCallable => { - index.find_pou_implementation(identifier).map(|i| match i.implementation_type { - crate::index::ImplementationType::Program => { - StatementAnnotation::Program { qualified_name: i.get_call_name().to_string() } + Scope::Callable(None) => { + index.find_pou_implementation(identifier).and_then(|i| match i.implementation_type { + crate::index::ImplementationType::Program | crate::index::ImplementationType::Action => { + Some(StatementAnnotation::Program { qualified_name: i.get_call_name().to_string() }) } crate::index::ImplementationType::Function => { let return_type = index @@ -94,16 +96,33 @@ impl Scope { .map(|dt| dt.get_name()) .unwrap_or_else(|| VOID_TYPE) .to_string(); - StatementAnnotation::Function { + Some(StatementAnnotation::Function { return_type, qualified_name: i.call_name.to_string(), call_name: None, - } + }) + } + crate::index::ImplementationType::FunctionBlock + | crate::index::ImplementationType::Class => { + Some(StatementAnnotation::data_type(i.get_type_name())) + } + // crate::index::ImplementationType::Method => todo!(), + _ => None, + }) + } + + // functions, programs, actions, methods + Scope::Callable(Some(qualifier)) => { + //TODO improve! + let qualified_name = format!("{qualifier}.{identifier}"); + index.find_pou_implementation(qualified_name.as_str()).and_then(|i| { + match i.implementation_type { + crate::index::ImplementationType::Action => Some(StatementAnnotation::Program { + qualified_name: i.get_call_name().to_string(), + }), + crate::index::ImplementationType::Method => todo!(), + _ => None, } - crate::index::ImplementationType::FunctionBlock => todo!(), - crate::index::ImplementationType::Action => todo!(), - crate::index::ImplementationType::Class => todo!(), - crate::index::ImplementationType::Method => todo!(), }) } } @@ -131,7 +150,7 @@ impl ScopeStack { ScopeStack { stack: vec![ScopingStrategy::Strict(Scope::Composite(vec![ Scope::GlobalVariable, - Scope::StaticallyCallable, + Scope::Callable(None), ]))], } } @@ -207,7 +226,10 @@ impl AstVisitor for NameResolver<'_> { self.in_a_body = true; self.walk_with_scope( implementation, - ScopingStrategy::Hierarchical(Scope::LocalVariable(implementation.type_name.clone())), + ScopingStrategy::Hierarchical(Scope::Composite(vec![ + Scope::LocalVariable(implementation.type_name.clone()), + Scope::Callable(Some(implementation.type_name.clone())), + ])), ); self.in_a_body = false; } @@ -230,7 +252,10 @@ impl AstVisitor for NameResolver<'_> { (ReferenceAccess::Member(member), base) => { if let Some(base) = base { // resolve member und the base's context - self.scope.push(ScopingStrategy::Strict(Scope::LocalVariable(base.to_string()))); + self.scope.push(ScopingStrategy::Strict(Scope::Composite(vec![ + Scope::LocalVariable(base.to_string()), + Scope::Callable(Some(base.to_string())), + ]))); } member.walk(self); @@ -240,10 +265,20 @@ impl AstVisitor for NameResolver<'_> { self.scope.pop(); } } - (ReferenceAccess::Index(idx), _) => { + (ReferenceAccess::Index(idx), Some(base)) => { // make sure we resolve from the root-scope self.walk_with_scope(idx, ScopingStrategy::Strict(self.root_scope.clone())); - self.annotations.copy_annotation(idx, node); + + // the array-access turns this expression into the array's inner type + if let Some(inner_type_name) = self + .index + .find_effective_type_info(base.as_str()) + .and_then(|t| t.get_inner_array_type_name()) + .and_then(|it| self.index.find_effective_type_by_name(it).map(|it| it.get_name())) + //TODO why effective again? + { + self.annotations.annotate(node, StatementAnnotation::value(inner_type_name)) + } } (ReferenceAccess::Cast(target), Some(base)) => { if let Some(true) = @@ -255,7 +290,6 @@ impl AstVisitor for NameResolver<'_> { ); } else { target.walk(self); - self.annotations.annotate(target, StatementAnnotation::data_type(base)); } if self.annotations.has_type_annotation(target) { self.annotations.annotate(node, StatementAnnotation::data_type(base)); @@ -263,13 +297,18 @@ impl AstVisitor for NameResolver<'_> { } (ReferenceAccess::Deref, Some(base)) => { if let Some(DataTypeInformation::Pointer { inner_type_name, auto_deref: false, .. }) = - self.index.find_type(base).map(DataType::get_type_information) + self.index.find_type(base).map(typesystem::DataType::get_type_information) { self.annotations.annotate(node, StatementAnnotation::data_type(&inner_type_name)); } } (ReferenceAccess::Address, Some(_base)) => { - todo!("Address of operator not implemented yet") + if let Some(inner_type) = base + .map(|base| self.annotations.get_type_or_void(base, self.index).get_name().to_string()) + { + let ptr_type = add_pointer_type(&mut self.annotations.new_index, inner_type); + self.annotations.annotate(node, StatementAnnotation::value(ptr_type)) + } } _ => {} } @@ -366,12 +405,150 @@ impl AstVisitor for NameResolver<'_> { } fn visit_call_statement(&mut self, stmt: &plc_ast::ast::CallStatement, node: &plc_ast::ast::AstNode) { - stmt.walk(self); - + stmt.operator.walk(self); // annotate the whole statement with the resulting type - if let Some(StatementAnnotation::Function { return_type, .. }) = self.annotations.get(&stmt.operator) + let call_target = self.annotations.get(&stmt.operator); + if let Some(StatementAnnotation::Function { return_type, .. }) = call_target { + self.annotations.annotate(node, StatementAnnotation::value(return_type)); + } + + //annotate the parameters with the right hints + // TODO: I need something that gets me the type_name of the resulting POU + if let Some(target_type_name) = self.annotations.get_call_name(&stmt.operator).map(str::to_string) { + let declared_parameters = self.index.get_declared_parameters(target_type_name.as_str()); + + for (idx, arg) in stmt + .parameters + .as_ref() + .map(|it| flatten_expression_list(it)) + .unwrap_or_default() + .iter() + .enumerate() + { + if let AstStatement::Assignment(Assignment { left, right, .. }) = arg.get_stmt() { + // left needs to be resolved in the context of the call operator + self.walk_with_scope( + left, + ScopingStrategy::Strict(Scope::LocalVariable(target_type_name.to_string())), + ); + // right needs to be resolved with normal scope + right.walk(self); + } else { + arg.walk(self); + + // hint it with the argument ast pos n + // TODO: move to the hinter??? + if let Some(declared_parameter) = declared_parameters.get(idx) { + self.annotations.annotate_type_hint( + arg, + StatementAnnotation::value(declared_parameter.get_type_name()), + ); + } + } + } + } + } + + fn visit_paren_expression(&mut self, inner: &plc_ast::ast::AstNode, node: &plc_ast::ast::AstNode) { + inner.walk(self); + self.annotations.copy_annotation(inner, node) + } + + fn visit_user_type_declaration(&mut self, user_type: &plc_ast::ast::UserTypeDeclaration) { + self.visit_data_type(&user_type.data_type); + + if let Some(type_name) = user_type.data_type.get_name() { + let mut initializer_annotator = InitializerAnnotator::new( + &self.index.get_intrinsic_type_by_name(type_name).get_type_information(), + &self.index, + &mut self.annotations, + ); + user_type.initializer.as_ref().inspect(|it| it.walk(&mut initializer_annotator)); + } + } + + fn visit_data_type(&mut self, data_type: &plc_ast::ast::DataType) { + data_type.walk(self); + + // hint the range limits with the original type + // INT(0..100) --> 0 and 100 should be hinted with INT + if let DataType::SubRangeType { + bounds: Some(AstNode { stmt: AstStatement::RangeStatement(range), .. }), + name: Some(name), + .. + } = data_type { - self.annotations.annotate(node, StatementAnnotation::value(return_type.to_string())); + let type_name = self.index.get_intrinsic_type_by_name(name).get_name(); + self.annotations.annotate_type_hint(&range.start, StatementAnnotation::value(type_name)); + self.annotations.annotate_type_hint(&range.end, StatementAnnotation::value(type_name)); + } + } +} + +//TODO find better place + +/// adds a pointer to the given inner_type to the given index and return's its name +fn add_pointer_type(index: &mut Index, inner_type_name: String) -> String { + let new_type_name = internal_type_name("POINTER_TO_", inner_type_name.as_str()); + + if index.find_effective_type_by_name(new_type_name.as_str()).is_none() { + index.register_type(crate::typesystem::DataType { + name: new_type_name.clone(), + initial_value: None, + nature: TypeNature::Any, + information: crate::typesystem::DataTypeInformation::Pointer { + auto_deref: false, + inner_type_name, + name: new_type_name.clone(), + }, + location: SourceLocation::internal(), + }); + } + new_type_name +} + +/// this anotator is used to create the type-annotations on initializers +/// Note that it assumes that it only ever visits initializers! +struct InitializerAnnotator<'i> { + expected_type: &'i DataTypeInformation, + index: &'i Index, + annotations: &'i mut AnnotationMapImpl, +} + +impl<'i> InitializerAnnotator<'i> { + pub fn new( + expected_type: &'i DataTypeInformation, + index: &'i Index, + annotations: &'i mut AnnotationMapImpl, + ) -> Self { + Self { expected_type, index, annotations } + } +} + +impl AstVisitor for InitializerAnnotator<'_> { + + fn visit_literal(&mut self, stmt: &plc_ast::literals::AstLiteral, _node: &AstNode) { + match stmt { + plc_ast::literals::AstLiteral::Null => todo!(), + plc_ast::literals::AstLiteral::Integer(_) => todo!(), + plc_ast::literals::AstLiteral::Date(_) => todo!(), + plc_ast::literals::AstLiteral::DateAndTime(_) => todo!(), + plc_ast::literals::AstLiteral::TimeOfDay(_) => todo!(), + plc_ast::literals::AstLiteral::Time(_) => todo!(), + plc_ast::literals::AstLiteral::Real(_) => todo!(), + plc_ast::literals::AstLiteral::Bool(_) => todo!(), + plc_ast::literals::AstLiteral::String(_) => todo!(), + plc_ast::literals::AstLiteral::Array(members) => { + if let (DataTypeInformation::Array { inner_type_name, .. }, Some(inner_type)) = + (self.expected_type, self.index.find_effective_type(inner_type_name)) + { + + // for member in members.iter() { + // let mut annotator = InitializerAnnotator::new(inner_type, self.index, self.annotations); + // member.walk(&mut annotator); + // } + } + }, } } } diff --git a/src/resolver.rs b/src/resolver.rs index ceb56cfe9c..0e0a92e3fe 100644 --- a/src/resolver.rs +++ b/src/resolver.rs @@ -7,7 +7,7 @@ use rustc_hash::{FxHashMap, FxHashSet}; use type_hint_annotator::TypeHintAnnotator; -use std::hash::Hash; +use std::{hash::Hash}; use plc_ast::{ ast::{ @@ -539,7 +539,10 @@ pub trait AnnotationMap { .get_hint(statement) .or_else(|| self.get(statement)) .and_then(|it| self.get_type_name_for_annotation(it)), - StatementAnnotation::Program { qualified_name } => Some(qualified_name.as_str()), + StatementAnnotation::Program { qualified_name } =>{ + // only return the first segment (without a potential action part) + Some(&qualified_name[0..qualified_name.find(".").unwrap_or(qualified_name.len())]) + } , StatementAnnotation::Type { type_name } => Some(type_name), StatementAnnotation::Function { .. } | StatementAnnotation::Label { .. } => None, } @@ -667,6 +670,9 @@ impl AnnotationMapImpl { if let Some(f) = self.get(from) { self.annotate(to, f.clone()); } + if let Some(f) = self.get_hint(from) { + self.annotate_type_hint(to, f.clone()) + } } /// annotates the given statement (using it's `get_id()`) with the given type-name @@ -684,6 +690,10 @@ impl AnnotationMapImpl { } } + pub fn clear_type_hint(&mut self, s: &AstNode) { + self.type_hint_map.shift_remove(&s.get_id()); + } + /// annotates the given statement s with the call-statement f so codegen can generate /// a hidden call f instead of generating s pub fn annotate_hidden_function_call(&mut self, s: &AstNode, f: AstNode) { diff --git a/src/resolver/tests/resolve_expressions_tests.rs b/src/resolver/tests/resolve_expressions_tests.rs index 44d1537f19..7c552ff435 100644 --- a/src/resolver/tests/resolve_expressions_tests.rs +++ b/src/resolver/tests/resolve_expressions_tests.rs @@ -550,7 +550,7 @@ fn parenthesized_expression_assignment() { let AstStatement::Assignment(Assignment { right, .. }) = &one.stmt else { panic!() }; assert!(&right.is_paren()); assert_eq!(annotations.get_type(right, &index).unwrap().name, "DINT"); - assert_eq!(annotations.get_type_hint(right, &index).unwrap().name, "DINT"); + assert_eq!(annotations.get_type_hint(right, &index), None); // no hint, because it would not differ from type let two = &unit.implementations[0].statements[1]; let AstStatement::Assignment(Assignment { right, .. }) = &two.stmt else { panic!() }; @@ -2274,9 +2274,9 @@ fn global_lint_enums_type_resolving() { assert_eq!( vec![ (Some("DINT"), Some("MyEnum")), - (Some("MyEnum"), Some("MyEnum")), + (Some("MyEnum"), None), (Some("DINT"), Some("MyEnum")), - (Some("MyEnum"), Some("MyEnum")), + (Some("MyEnum"), None), ], initalizer_types ); diff --git a/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__builtin_add_replacement_ast.snap.new b/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__builtin_add_replacement_ast.snap.new new file mode 100644 index 0000000000..1ea20b1805 --- /dev/null +++ b/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__builtin_add_replacement_ast.snap.new @@ -0,0 +1,10 @@ +--- +source: src/resolver/tests/resolve_expressions_tests.rs +assertion_line: 5429 +expression: annotations.get(stmt) +--- +Some( + Value { + resulting_type: "__ADD__T", + }, +) diff --git a/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__builtin_eq_replacement_ast.snap.new b/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__builtin_eq_replacement_ast.snap.new new file mode 100644 index 0000000000..f272a47a1a --- /dev/null +++ b/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__builtin_eq_replacement_ast.snap.new @@ -0,0 +1,8 @@ +--- +source: src/resolver/tests/resolve_expressions_tests.rs +assertion_line: 5365 +expression: "generate_comparison_test(\"EQ\")" +--- +Value { + resulting_type: "BOOL", +} diff --git a/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__builtin_ge_replacement_ast.snap.new b/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__builtin_ge_replacement_ast.snap.new new file mode 100644 index 0000000000..bb05fe03dd --- /dev/null +++ b/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__builtin_ge_replacement_ast.snap.new @@ -0,0 +1,8 @@ +--- +source: src/resolver/tests/resolve_expressions_tests.rs +assertion_line: 5360 +expression: "generate_comparison_test(\"GE\")" +--- +Value { + resulting_type: "BOOL", +} diff --git a/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__builtin_gt_replacement_ast.snap.new b/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__builtin_gt_replacement_ast.snap.new new file mode 100644 index 0000000000..60c64d267c --- /dev/null +++ b/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__builtin_gt_replacement_ast.snap.new @@ -0,0 +1,8 @@ +--- +source: src/resolver/tests/resolve_expressions_tests.rs +assertion_line: 5356 +expression: "generate_comparison_test(\"GT\")" +--- +Value { + resulting_type: "BOOL", +} diff --git a/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__builtin_le_replacement_ast.snap.new b/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__builtin_le_replacement_ast.snap.new new file mode 100644 index 0000000000..ff7cfe9d2e --- /dev/null +++ b/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__builtin_le_replacement_ast.snap.new @@ -0,0 +1,8 @@ +--- +source: src/resolver/tests/resolve_expressions_tests.rs +assertion_line: 5375 +expression: "generate_comparison_test(\"LE\")" +--- +Value { + resulting_type: "BOOL", +} diff --git a/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__builtin_lt_replacement_ast.snap.new b/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__builtin_lt_replacement_ast.snap.new new file mode 100644 index 0000000000..c40e141195 --- /dev/null +++ b/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__builtin_lt_replacement_ast.snap.new @@ -0,0 +1,8 @@ +--- +source: src/resolver/tests/resolve_expressions_tests.rs +assertion_line: 5370 +expression: "generate_comparison_test(\"LT\")" +--- +Value { + resulting_type: "BOOL", +} diff --git a/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__builtin_ne_replacement_ast.snap.new b/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__builtin_ne_replacement_ast.snap.new new file mode 100644 index 0000000000..e6e326b14d --- /dev/null +++ b/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__builtin_ne_replacement_ast.snap.new @@ -0,0 +1,8 @@ +--- +source: src/resolver/tests/resolve_expressions_tests.rs +assertion_line: 5380 +expression: "generate_comparison_test(\"NE\")" +--- +Value { + resulting_type: "BOOL", +} diff --git a/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__comparison_resolves_to_function_call.snap.new b/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__comparison_resolves_to_function_call.snap.new new file mode 100644 index 0000000000..138975ab3a --- /dev/null +++ b/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__comparison_resolves_to_function_call.snap.new @@ -0,0 +1,10 @@ +--- +source: src/resolver/tests/resolve_expressions_tests.rs +assertion_line: 1391 +expression: "format!(\"{annotation:#?}\")" +--- +Some( + Value { + resulting_type: "BOOL", + }, +) diff --git a/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__string_compare_should_resolve_to_bool.snap.new b/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__string_compare_should_resolve_to_bool.snap.new new file mode 100644 index 0000000000..4e85a3cfed --- /dev/null +++ b/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__string_compare_should_resolve_to_bool.snap.new @@ -0,0 +1,8 @@ +--- +source: src/resolver/tests/resolve_expressions_tests.rs +assertion_line: 3258 +expression: annotations.get(a_eq_b).unwrap() +--- +Value { + resulting_type: "BOOL", +} diff --git a/src/resolver/type_hint_annotator.rs b/src/resolver/type_hint_annotator.rs index 6da15bf646..66fd90a93d 100644 --- a/src/resolver/type_hint_annotator.rs +++ b/src/resolver/type_hint_annotator.rs @@ -1,4 +1,4 @@ -use plc_ast::visitor::{AstVisitor, Walker}; +use plc_ast::{ast::{AstStatement, ReferenceAccess, ReferenceExpr}, visitor::{AstVisitor, Walker}}; use crate::index::Index; @@ -46,4 +46,17 @@ impl AstVisitor for TypeHintAnnotator<'_> { .annotate_type_hint(&stmt.right, StatementAnnotation::value(l_type.get_name().to_string())); } } + + fn visit_reference_expr(&mut self, stmt: &plc_ast::ast::ReferenceExpr, node: &plc_ast::ast::AstNode) { + stmt.walk(self); + + // a cast-statement on a literal should directly annotate the literal correctly (avoid casts) + if let ReferenceAccess::Cast(target) = &stmt.access { + if matches!(target.get_stmt(), AstStatement::Literal{..}) { + self.annotations.copy_annotation(node, &target); + self.annotations.clear_type_hint(&target); + } + } + } + }