From bbbb0eefaff30425ef90009b49cd935b90e2bb4b Mon Sep 17 00:00:00 2001 From: Volkan Sagcan Date: Tue, 26 Nov 2024 15:43:20 +0100 Subject: [PATCH] feat: Introduce interfaces --- compiler/plc_ast/src/ast.rs | 45 ++- compiler/plc_driver/src/pipelines.rs | 1 + ...ests__jump_and_label_converted_to_ast.snap | 2 + ...ser__control__tests__negated_jump_ast.snap | 2 + ...ted_jump_generated_as_empty_statement.snap | 2 + ...ser__control__tests__unnamed_controls.snap | 2 + src/codegen/tests/online_change_tests.rs | 4 + ...tests__hardware_access_assign_codegen.snap | 18 - ...ession_tests__hardware_access_codegen.snap | 24 -- src/index.rs | 102 ++++- src/index/tests.rs | 1 + src/index/tests/interface_tests.rs | 247 ++++++++++++ src/index/visitor.rs | 15 +- src/lexer.rs | 2 + src/lexer/tokens.rs | 17 +- src/lowering/initializers.rs | 2 + src/parser.rs | 80 +++- src/parser/tests.rs | 1 + src/parser/tests/class_parser_tests.rs | 18 +- src/parser/tests/expressions_parser_tests.rs | 2 + src/parser/tests/function_parser_tests.rs | 2 + src/parser/tests/interface_parser_tests.rs | 373 ++++++++++++++++++ ...s__direct_access_as_expression_parsed.snap | 2 + ...s_parser_tests__exp_mul_priority_test.snap | 2 + ...exponent_literals_parsed_as_variables.snap | 1 + ...rameter_assignments_in_call_statement.snap | 3 + ..._parser_tests__multi_type_declaration.snap | 1 + ...num_with_initial_values_can_be_parsed.snap | 1 + ...parser_tests__global_var_with_address.snap | 2 +- ...le_parser_tests__pou_var_with_address.snap | 2 +- ...ble_parser_tests__struct_with_address.snap | 2 +- src/parser/tests/variable_parser_tests.rs | 5 + src/resolver.rs | 5 + ...ts__hardware_access_types_annotated-2.snap | 13 - ...ts__hardware_access_types_annotated-3.snap | 13 - ...ts__hardware_access_types_annotated-4.snap | 13 - ...ts__hardware_access_types_annotated-5.snap | 13 - ...ests__hardware_access_types_annotated.snap | 13 - src/tests/adr/initializer_functions_adr.rs | 2 + src/validation/global.rs | 12 + src/validation/pou.rs | 185 ++++++++- src/validation/tests.rs | 1 + .../tests/duplicates_validation_test.rs | 67 ++-- .../tests/interface_validation_tests.rs | 330 ++++++++++++++++ 44 files changed, 1446 insertions(+), 204 deletions(-) delete mode 100644 src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__hardware_access_assign_codegen.snap delete mode 100644 src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__hardware_access_codegen.snap create mode 100644 src/index/tests/interface_tests.rs create mode 100644 src/parser/tests/interface_parser_tests.rs delete mode 100644 src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__hardware_access_types_annotated-2.snap delete mode 100644 src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__hardware_access_types_annotated-3.snap delete mode 100644 src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__hardware_access_types_annotated-4.snap delete mode 100644 src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__hardware_access_types_annotated-5.snap delete mode 100644 src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__hardware_access_types_annotated.snap create mode 100644 src/validation/tests/interface_validation_tests.rs diff --git a/compiler/plc_ast/src/ast.rs b/compiler/plc_ast/src/ast.rs index 2ea725a07f..3d8f2934e3 100644 --- a/compiler/plc_ast/src/ast.rs +++ b/compiler/plc_ast/src/ast.rs @@ -22,7 +22,7 @@ use plc_source::source_location::*; pub type AstId = usize; -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct GenericBinding { pub name: String, pub nature: TypeNature, @@ -31,20 +31,39 @@ pub struct GenericBinding { #[derive(PartialEq)] pub struct Pou { pub name: String, + pub pou_type: PouType, // TODO(volsa): Rename to kind pub variable_blocks: Vec, - pub pou_type: PouType, pub return_type: Option, - /// the SourceLocation of the whole POU + /// The SourceLocation of the whole POU pub location: SourceLocation, - /// the SourceLocation of the POU's name + /// The SourceLocation of the POUs name pub name_location: SourceLocation, pub poly_mode: Option, pub generics: Vec, pub linkage: LinkageType, pub super_class: Option, + /// A list of interfaces this POU implements + pub interfaces: Vec, pub is_const: bool, } +// XXX: Nice to have, at some point in the future: generics and default implementations +#[derive(Debug, PartialEq)] +pub struct Interface { + pub name: String, + pub methods: Vec, + pub location: SourceLocation, + pub location_name: SourceLocation, +} + +/// Helper struct for [`Pou`] to get the location of the interface without relying on [`Interface`] which +/// only exists if the interface is actually defined. Mostly needed for user-friendly validation messages. +#[derive(Debug, PartialEq)] +pub struct InterfaceDeclaration { + pub name: String, + pub location: SourceLocation, +} + #[derive(Debug, PartialEq, Eq)] pub enum PolymorphismMode { None, @@ -203,7 +222,8 @@ impl Debug for Pou { str.field("name", &self.name) .field("variable_blocks", &self.variable_blocks) .field("pou_type", &self.pou_type) - .field("return_type", &self.return_type); + .field("return_type", &self.return_type) + .field("interfaces", &self.interfaces); if !self.generics.is_empty() { str.field("generics", &self.generics); } @@ -257,7 +277,10 @@ pub enum PouType { FunctionBlock, Action, Class, - Method { owner_class: String }, + Method { + /// The parent of this method, i.e. either a function block / class or an interface + parent: String, + }, Init, ProjectInit, } @@ -280,8 +303,8 @@ impl Display for PouType { impl PouType { /// returns Some(owner_class) if this is a `Method` or otherwhise `None` pub fn get_optional_owner_class(&self) -> Option { - if let PouType::Method { owner_class } = self { - Some(owner_class.clone()) + if let PouType::Method { parent } = self { + Some(parent.clone()) } else { None } @@ -315,8 +338,11 @@ impl ConfigVariable { pub struct CompilationUnit { pub global_vars: Vec, pub var_config: Vec, + /// List of POU definitions (signature and some additional metadata) pub units: Vec, + /// List of statements within a POU body pub implementations: Vec, + pub interfaces: Vec, pub user_types: Vec, pub file_name: String, } @@ -328,6 +354,7 @@ impl CompilationUnit { var_config: Vec::new(), units: Vec::new(), implementations: Vec::new(), + interfaces: Vec::new(), user_types: Vec::new(), file_name: file_name.to_string(), } @@ -1207,7 +1234,7 @@ mod tests { assert_eq!(PouType::FunctionBlock.to_string(), "FunctionBlock"); assert_eq!(PouType::Action.to_string(), "Action"); assert_eq!(PouType::Class.to_string(), "Class"); - assert_eq!(PouType::Method { owner_class: "...".to_string() }.to_string(), "Method"); + assert_eq!(PouType::Method { parent: "...".to_string() }.to_string(), "Method"); } #[test] diff --git a/compiler/plc_driver/src/pipelines.rs b/compiler/plc_driver/src/pipelines.rs index 945713d3ea..b204514496 100644 --- a/compiler/plc_driver/src/pipelines.rs +++ b/compiler/plc_driver/src/pipelines.rs @@ -107,6 +107,7 @@ impl ParsedProject { source_code::SourceType::Xml => cfc::xml_parser::parse_file, source_code::SourceType::Unknown => unreachable!(), }; + parse_func(source, LinkageType::Internal, ctxt.provider(), diagnostician) }) .collect::>(); diff --git a/compiler/plc_xml/src/xml_parser/snapshots/plc_xml__xml_parser__control__tests__jump_and_label_converted_to_ast.snap b/compiler/plc_xml/src/xml_parser/snapshots/plc_xml__xml_parser__control__tests__jump_and_label_converted_to_ast.snap index 4eab49f4e0..fd34107c31 100644 --- a/compiler/plc_xml/src/xml_parser/snapshots/plc_xml__xml_parser__control__tests__jump_and_label_converted_to_ast.snap +++ b/compiler/plc_xml/src/xml_parser/snapshots/plc_xml__xml_parser__control__tests__jump_and_label_converted_to_ast.snap @@ -28,6 +28,7 @@ CompilationUnit { ], pou_type: Program, return_type: None, + interfaces: [], }, ], implementations: [ @@ -83,6 +84,7 @@ CompilationUnit { access: None, }, ], + interfaces: [], user_types: [], file_name: "", } diff --git a/compiler/plc_xml/src/xml_parser/snapshots/plc_xml__xml_parser__control__tests__negated_jump_ast.snap b/compiler/plc_xml/src/xml_parser/snapshots/plc_xml__xml_parser__control__tests__negated_jump_ast.snap index 01c6c85359..02fcb27b11 100644 --- a/compiler/plc_xml/src/xml_parser/snapshots/plc_xml__xml_parser__control__tests__negated_jump_ast.snap +++ b/compiler/plc_xml/src/xml_parser/snapshots/plc_xml__xml_parser__control__tests__negated_jump_ast.snap @@ -28,6 +28,7 @@ CompilationUnit { ], pou_type: Program, return_type: None, + interfaces: [], }, ], implementations: [ @@ -86,6 +87,7 @@ CompilationUnit { access: None, }, ], + interfaces: [], user_types: [], file_name: "", } diff --git a/compiler/plc_xml/src/xml_parser/snapshots/plc_xml__xml_parser__control__tests__unconnected_jump_generated_as_empty_statement.snap b/compiler/plc_xml/src/xml_parser/snapshots/plc_xml__xml_parser__control__tests__unconnected_jump_generated_as_empty_statement.snap index d23fa2df03..62cc288c15 100644 --- a/compiler/plc_xml/src/xml_parser/snapshots/plc_xml__xml_parser__control__tests__unconnected_jump_generated_as_empty_statement.snap +++ b/compiler/plc_xml/src/xml_parser/snapshots/plc_xml__xml_parser__control__tests__unconnected_jump_generated_as_empty_statement.snap @@ -28,6 +28,7 @@ CompilationUnit { ], pou_type: Program, return_type: None, + interfaces: [], }, ], implementations: [ @@ -66,6 +67,7 @@ CompilationUnit { access: None, }, ], + interfaces: [], user_types: [], file_name: "", } diff --git a/compiler/plc_xml/src/xml_parser/snapshots/plc_xml__xml_parser__control__tests__unnamed_controls.snap b/compiler/plc_xml/src/xml_parser/snapshots/plc_xml__xml_parser__control__tests__unnamed_controls.snap index 3ba50d05dd..b770bc6069 100644 --- a/compiler/plc_xml/src/xml_parser/snapshots/plc_xml__xml_parser__control__tests__unnamed_controls.snap +++ b/compiler/plc_xml/src/xml_parser/snapshots/plc_xml__xml_parser__control__tests__unnamed_controls.snap @@ -11,6 +11,7 @@ CompilationUnit { variable_blocks: [], pou_type: Program, return_type: None, + interfaces: [], }, ], implementations: [ @@ -47,6 +48,7 @@ CompilationUnit { access: None, }, ], + interfaces: [], user_types: [], file_name: "", } diff --git a/src/codegen/tests/online_change_tests.rs b/src/codegen/tests/online_change_tests.rs index 687d5ae4e7..ffc63bb82b 100644 --- a/src/codegen/tests/online_change_tests.rs +++ b/src/codegen/tests/online_change_tests.rs @@ -3,6 +3,7 @@ use insta::assert_snapshot; use crate::test_utils::tests::codegen_with_online_change as codegen; #[test] +#[cfg_attr(target_os = "macos", ignore = "FIXME: leading comma in sections name")] fn generate_function_with_online_change() { let src = codegen( " @@ -42,6 +43,7 @@ fn generate_function_with_online_change() { } #[test] +#[cfg_attr(target_os = "macos", ignore = "FIXME: leading comma in sections name")] fn generate_program_with_online_change() { let src = codegen( " @@ -105,6 +107,7 @@ fn generate_program_with_online_change() { } #[test] +#[cfg_attr(target_os = "macos", ignore = "FIXME: leading comma in sections name")] fn generate_program_and_var_with_online_change() { let src = codegen( " @@ -176,6 +179,7 @@ fn generate_program_and_var_with_online_change() { } #[test] +#[cfg_attr(target_os = "macos", ignore = "FIXME: leading comma in sections name")] fn generate_function_and_var_with_online_change() { let src = codegen( " diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__hardware_access_assign_codegen.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__hardware_access_assign_codegen.snap deleted file mode 100644 index 97091e50ab..0000000000 --- a/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__hardware_access_assign_codegen.snap +++ /dev/null @@ -1,18 +0,0 @@ ---- -source: src/codegen/tests/expression_tests.rs -expression: result ---- -; ModuleID = 'main' -source_filename = "main" - -%prg = type { i8, i8, i8 } - -@prg_instance = global %prg zeroinitializer, section "var-$RUSTY$prg_instance:r3u8u8u8" - -define void @prg(%prg* %0) { -entry: - %x = getelementptr inbounds %prg, %prg* %0, i32 0, i32 0 - %y = getelementptr inbounds %prg, %prg* %0, i32 0, i32 1 - %z = getelementptr inbounds %prg, %prg* %0, i32 0, i32 2 - ret void -} diff --git a/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__hardware_access_codegen.snap b/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__hardware_access_codegen.snap deleted file mode 100644 index b4989a14e7..0000000000 --- a/src/codegen/tests/snapshots/rusty__codegen__tests__expression_tests__hardware_access_codegen.snap +++ /dev/null @@ -1,24 +0,0 @@ ---- -source: src/codegen/tests/expression_tests.rs -expression: result ---- -; ModuleID = 'main' -source_filename = "main" - -%prg = type { i8, i8, i8 } - -@prg_instance = global %prg zeroinitializer, section "var-$RUSTY$prg_instance:r3u8u8u8" - -define void @prg(%prg* %0) { -entry: - %x = getelementptr inbounds %prg, %prg* %0, i32 0, i32 0 - %y = getelementptr inbounds %prg, %prg* %0, i32 0, i32 1 - %z = getelementptr inbounds %prg, %prg* %0, i32 0, i32 2 - store i8 0, i8* %x, align 1 - store i8 0, i8* %y, align 1 - store i8 0, i8* %z, align 1 - store i8 0, i8* %x, align 1 - store i8 0, i8* %y, align 1 - store i8 0, i8* %z, align 1 - ret void -} diff --git a/src/index.rs b/src/index.rs index 85614ec6cb..8cacefd4b5 100644 --- a/src/index.rs +++ b/src/index.rs @@ -7,7 +7,7 @@ use rustc_hash::{FxHashSet, FxHasher}; use plc_ast::ast::{ AstId, AstNode, AstStatement, ConfigVariable, DirectAccessType, GenericBinding, HardwareAccessType, - LinkageType, PouType, TypeNature, + Interface, LinkageType, PouType, TypeNature, }; use plc_diagnostics::diagnostics::Diagnostic; use plc_source::source_location::SourceLocation; @@ -321,7 +321,7 @@ impl VariableIndexEntry { } } -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum ArgumentType { ByVal(VariableType), ByRef(VariableType), @@ -335,23 +335,16 @@ impl ArgumentType { } } - pub fn get_inner_ref(&self) -> &VariableType { - match self { - ArgumentType::ByRef(val) => val, - ArgumentType::ByVal(val) => val, - } - } - pub fn is_by_ref(&self) -> bool { matches!(self, ArgumentType::ByRef(..)) } pub fn is_private(&self) -> bool { - matches!(self.get_inner_ref(), VariableType::Temp | VariableType::Local) + matches!(self.get_inner(), VariableType::Temp | VariableType::Local) } pub fn is_input(&self) -> bool { - matches!(self.get_inner_ref(), VariableType::Input) + matches!(self.get_inner(), VariableType::Input) } } @@ -469,7 +462,53 @@ impl ImplementationType { } } -#[derive(Debug, PartialEq, Eq)] +#[derive(PartialEq, Eq)] +pub struct InterfaceIndexEntry { + /// The interface name + pub name: String, + + /// The location of the interface as a whole + pub location: SourceLocation, + + /// The location of the interface name + pub location_name: SourceLocation, + + /// A list of qualified names of the methods in this interface; the actual methods are located in + /// [`Index::pous`] + pub methods: Vec, +} + +impl InterfaceIndexEntry { + /// Returns a list of methods defined in this interface + pub fn get_methods<'idx>(&self, index: &'idx Index) -> Vec<&'idx PouIndexEntry> { + self.methods + .iter() + .map(|name| index.find_pou(name).expect("must exist because of present InterfaceIndexEntry")) + .collect() + } +} + +impl std::fmt::Debug for InterfaceIndexEntry { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("InterfaceIndexEntry") + .field("name", &self.name) + .field("methods", &self.methods) + .finish() + } +} + +impl From<&Interface> for InterfaceIndexEntry { + fn from(interface: &Interface) -> Self { + InterfaceIndexEntry { + name: interface.name.clone(), + location: interface.location.clone(), + location_name: interface.location_name.clone(), + methods: interface.methods.iter().map(|method| method.name.clone()).collect(), + } + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Hash)] pub enum PouIndexEntry { Program { name: String, @@ -880,24 +919,27 @@ impl TypeIndex { /// The index contains information about all referencable elements. #[derive(Debug, Default)] pub struct Index { - /// all global variables + /// All global variables global_variables: SymbolMap, - /// all struct initializers + /// All struct initializers global_initializers: SymbolMap, - /// all enum-members with their names + /// All enum-members with their names enum_global_variables: SymbolMap, - // all pous, + /// All pous, pous: SymbolMap, - /// all implementations - // we keep an IndexMap for implementations since duplication issues regarding implementations - // is handled by the `pous` SymbolMap + /// All interface definitions + interfaces: SymbolMap, + + /// All implementations + /// We keep an IndexMap for implementations since duplication issues regarding implementations + /// is handled by the `pous` SymbolMap implementations: FxIndexMap, - /// an index with all type-information + /// An index with all type-information type_index: TypeIndex, constant_expressions: ConstExpressions, @@ -1007,6 +1049,9 @@ impl Index { //implementations self.implementations.extend(other.implementations); + // interfaces + self.interfaces.extend(other.interfaces); + //pous for (name, elements) in other.pous.drain(..) { for ele in elements { @@ -1179,6 +1224,11 @@ impl Index { } } + /// Returns an interface with the given name or None if it does not exist + pub fn find_interface(&self, name: &str) -> Option<&InterfaceIndexEntry> { + self.interfaces.get(name) + } + /// return the `VariableIndexEntry` associated with the given fully qualified name using `.` as /// a delimiter. (e.g. "PLC_PRG.x", or "MyClass.MyMethod.x") pub fn find_fully_qualified_variable(&self, fully_qualified_name: &str) -> Option<&VariableIndexEntry> { @@ -1404,6 +1454,12 @@ impl Index { variable.and_then(|it| self.get_type(it.get_type_name()).ok()) } + pub fn get_return_type_or_void(&self, pou_name: &str) -> &DataType { + self.find_return_variable(pou_name) + .and_then(|variable| self.find_type(variable.get_type_name())) + .unwrap_or(self.get_void_type()) + } + pub fn get_type_information_or_void(&self, type_name: &str) -> &DataTypeInformation { self.find_effective_type_by_name(type_name) .map(|it| it.get_type_information()) @@ -1440,6 +1496,11 @@ impl Index { &self.pous } + /// Returns a reference of the [`Index::interfaces`] field + pub fn get_interfaces(&self) -> &SymbolMap { + &self.interfaces + } + pub fn get_global_initializers(&self) -> &SymbolMap { &self.global_initializers } @@ -1527,6 +1588,7 @@ impl Index { let qualified_name = qualified_name(container_name, variable_name); + // TODO: This doesn't register anything? It just creates a new VariableIndexEntry thus rename fn name? VariableIndexEntry::new( variable_name, &qualified_name, diff --git a/src/index/tests.rs b/src/index/tests.rs index d1001bd62b..f0bad025b5 100644 --- a/src/index/tests.rs +++ b/src/index/tests.rs @@ -3,3 +3,4 @@ mod builtin_tests; mod generic_tests; mod index_tests; mod instance_resolver_tests; +mod interface_tests; diff --git a/src/index/tests/interface_tests.rs b/src/index/tests/interface_tests.rs new file mode 100644 index 0000000000..455613994c --- /dev/null +++ b/src/index/tests/interface_tests.rs @@ -0,0 +1,247 @@ +use crate::test_utils::tests::index; + +#[test] +fn empty_interface() { + let source = r" + INTERFACE myInterface + END_INTERFACE + "; + + let (_, index) = index(source); + + insta::assert_debug_snapshot!(index.find_interface("myInterface").unwrap(), @r###" + InterfaceIndexEntry { + name: "myInterface", + methods: [], + } + "###); +} + +#[test] +fn interface_with_single_method() { + let source = r" + INTERFACE myInterface + METHOD foo : INT + VAR_INPUT + a : INT; + b : INT; + END_VAR + END_METHOD + END_INTERFACE + "; + + let (_, index) = index(source); + + insta::assert_debug_snapshot!(index.find_interface("myInterface").unwrap(), @r###" + InterfaceIndexEntry { + name: "myInterface", + methods: [ + "myInterface.foo", + ], + } + "###); + + insta::assert_debug_snapshot!(index.find_pou("myInterface.foo").unwrap(), @r###" + Method { + name: "myInterface.foo", + parent_pou_name: "myInterface", + return_type: "INT", + instance_struct_name: "myInterface.foo", + linkage: Internal, + location: SourceLocation { + span: Range( + TextLocation { + line: 2, + column: 15, + offset: 42, + }..TextLocation { + line: 2, + column: 18, + offset: 45, + }, + ), + }, + } + "###); + + insta::assert_debug_snapshot!(index.get_pou_members("myInterface.foo"), @r###" + [ + VariableIndexEntry { + name: "a", + qualified_name: "myInterface.foo.a", + initial_value: None, + argument_type: ByVal( + Input, + ), + is_constant: false, + is_var_external: false, + data_type_name: "INT", + location_in_parent: 0, + linkage: Internal, + binding: None, + source_location: SourceLocation { + span: Range( + TextLocation { + line: 4, + column: 16, + offset: 90, + }..TextLocation { + line: 4, + column: 17, + offset: 91, + }, + ), + }, + varargs: None, + }, + VariableIndexEntry { + name: "b", + qualified_name: "myInterface.foo.b", + initial_value: None, + argument_type: ByVal( + Input, + ), + is_constant: false, + is_var_external: false, + data_type_name: "INT", + location_in_parent: 1, + linkage: Internal, + binding: None, + source_location: SourceLocation { + span: Range( + TextLocation { + line: 5, + column: 16, + offset: 115, + }..TextLocation { + line: 5, + column: 17, + offset: 116, + }, + ), + }, + varargs: None, + }, + VariableIndexEntry { + name: "foo", + qualified_name: "myInterface.foo.foo", + initial_value: None, + argument_type: ByVal( + Return, + ), + is_constant: false, + is_var_external: false, + data_type_name: "INT", + location_in_parent: 2, + linkage: Internal, + binding: None, + source_location: SourceLocation { + span: Range( + TextLocation { + line: 2, + column: 15, + offset: 42, + }..TextLocation { + line: 2, + column: 18, + offset: 45, + }, + ), + }, + varargs: None, + }, + ] + "###); +} + +#[test] +fn get_interface_methods() { + let source = r" + INTERFACE myInterface + METHOD foo : SINT + VAR_INPUT + a : SINT; + END_VAR + END_METHOD + + METHOD bar : INT + VAR_INPUT + b : INT; + END_VAR + END_METHOD + + METHOD baz : DINT + VAR_INPUT + c : DINT; + END_VAR + END_METHOD + "; + + let (_, index) = index(source); + let entry = index.find_interface("myInterface").unwrap(); + + insta::assert_debug_snapshot!(entry.get_methods(&index), @r###" + [ + Method { + name: "myInterface.foo", + parent_pou_name: "myInterface", + return_type: "SINT", + instance_struct_name: "myInterface.foo", + linkage: Internal, + location: SourceLocation { + span: Range( + TextLocation { + line: 2, + column: 15, + offset: 42, + }..TextLocation { + line: 2, + column: 18, + offset: 45, + }, + ), + }, + }, + Method { + name: "myInterface.bar", + parent_pou_name: "myInterface", + return_type: "INT", + instance_struct_name: "myInterface.bar", + linkage: Internal, + location: SourceLocation { + span: Range( + TextLocation { + line: 8, + column: 15, + offset: 156, + }..TextLocation { + line: 8, + column: 18, + offset: 159, + }, + ), + }, + }, + Method { + name: "myInterface.baz", + parent_pou_name: "myInterface", + return_type: "DINT", + instance_struct_name: "myInterface.baz", + linkage: Internal, + location: SourceLocation { + span: Range( + TextLocation { + line: 14, + column: 15, + offset: 268, + }..TextLocation { + line: 14, + column: 18, + offset: 271, + }, + ), + }, + }, + ] + "###); +} diff --git a/src/index/visitor.rs b/src/index/visitor.rs index c481772dba..815f03a49f 100644 --- a/src/index/visitor.rs +++ b/src/index/visitor.rs @@ -1,6 +1,6 @@ // Copyright (c) 2020 Ghaith Hachem and Mathias Rieder use super::{HardwareBinding, PouIndexEntry, VariableIndexEntry, VariableType}; -use crate::index::{ArgumentType, Index, MemberInfo}; +use crate::index::{ArgumentType, Index, InterfaceIndexEntry, MemberInfo}; use crate::typesystem::{self, *}; use plc_ast::ast::{ self, ArgumentProperty, Assignment, AstFactory, AstNode, AstStatement, AutoDerefType, CompilationUnit, @@ -12,6 +12,7 @@ use plc_diagnostics::diagnostics::Diagnostic; use plc_source::source_location::SourceLocation; use plc_util::convention::internal_type_name; +// TODO: Can this not take ownership of the CompilationUnit? Clones would then be unnecessary. pub fn visit(unit: &CompilationUnit) -> Index { let mut index = Index::default(); //Create user defined datatypes @@ -37,6 +38,14 @@ pub fn visit(unit: &CompilationUnit) -> Index { index.config_variables.push(config_variable.clone()); } + for interface in &unit.interfaces { + for method in &interface.methods { + visit_pou(&mut index, method); + } + + index.interfaces.insert(interface.name.clone(), InterfaceIndexEntry::from(interface)); + } + index } @@ -196,11 +205,11 @@ pub fn visit_pou(index: &mut Index, pou: &Pou) { )); index.register_pou_type(datatype); } - PouType::Method { owner_class } => { + PouType::Method { parent } => { index.register_pou(PouIndexEntry::create_method_entry( &pou.name, return_type_name, - owner_class, + parent, pou.linkage, pou.name_location.clone(), )); diff --git a/src/lexer.rs b/src/lexer.rs index 2db1242a50..a48584f731 100644 --- a/src/lexer.rs +++ b/src/lexer.rs @@ -70,6 +70,7 @@ impl<'a> ParseSession<'a> { self.id_provider.next_id() } + // TODO(volsa): Remove reference from `token` parameter, since `Token` is `Copy` /// Tries to consume the given token, returning false if it failed. pub fn try_consume(&mut self, token: &Token) -> bool { if self.token == *token { @@ -80,6 +81,7 @@ impl<'a> ParseSession<'a> { false } + // TODO(volsa): Rename to `try_consume_or_report` pub fn consume_or_report(&mut self, token: Token) { if !self.try_consume(&token) { self.accept_diagnostic(Diagnostic::missing_token(format!("{token:?}").as_str(), self.location())); diff --git a/src/lexer/tokens.rs b/src/lexer/tokens.rs index 10f1e0bc95..2caa3bdb97 100644 --- a/src/lexer/tokens.rs +++ b/src/lexer/tokens.rs @@ -2,7 +2,7 @@ use logos::Logos; use plc_ast::ast::{DirectAccessType, HardwareAccessType}; -#[derive(Debug, PartialEq, Eq, Logos, Clone)] +#[derive(Debug, PartialEq, Eq, Logos, Clone, Copy)] pub enum Token { #[error] #[regex(r"\(\*", |lex| super::parse_comments(lex))] @@ -39,6 +39,21 @@ pub enum Token { #[token("EXTENDS", ignore(case))] KeywordExtends, + #[token("IMPLEMENTS", ignore(case))] + KeywordImplements, + + #[token("INTERFACE", ignore(case))] + KeywordInterface, + + #[token("END_INTERFACE", ignore(case))] + KeywordEndInterface, + + #[token("PROPERTY", ignore(case))] + KeywordProperty, + + #[token("END_PROPERTY", ignore(case))] + KeywordEndProperty, + #[token("VAR_INPUT", ignore(case))] #[token("VARINPUT", ignore(case))] KeywordVarInput, diff --git a/src/lowering/initializers.rs b/src/lowering/initializers.rs index 7791cac3cb..c4dde15761 100644 --- a/src/lowering/initializers.rs +++ b/src/lowering/initializers.rs @@ -321,6 +321,7 @@ fn new_pou( generics: vec![], linkage: LinkageType::Internal, super_class: None, + interfaces: vec![], // TODO: Should this be empty? is_const: false, } } @@ -351,6 +352,7 @@ fn new_unit(pou: Pou, implementation: Implementation, file_name: &str) -> Compil var_config: Default::default(), units: vec![pou], implementations: vec![implementation], + interfaces: vec![], // TODO: probably shouldn't be empty user_types: vec![], file_name: file_name.into(), } diff --git a/src/parser.rs b/src/parser.rs index c49ed3e522..bc31141f2f 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -6,8 +6,9 @@ use plc_ast::{ ast::{ AccessModifier, ArgumentProperty, AstFactory, AstNode, AstStatement, AutoDerefType, CompilationUnit, ConfigVariable, DataType, DataTypeDeclaration, DirectAccessType, GenericBinding, HardwareAccessType, - Implementation, LinkageType, PolymorphismMode, Pou, PouType, ReferenceAccess, ReferenceExpr, - TypeNature, UserTypeDeclaration, Variable, VariableBlock, VariableBlockType, + Implementation, Interface, InterfaceDeclaration, LinkageType, PolymorphismMode, Pou, PouType, + ReferenceAccess, ReferenceExpr, TypeNature, UserTypeDeclaration, Variable, VariableBlock, + VariableBlockType, }, provider::IdProvider, }; @@ -83,6 +84,7 @@ pub fn parse(mut lexer: ParseSession, lnk: LinkageType, file_name: &str) -> Pars lexer.advance(); continue; } + KeywordInterface => unit.interfaces.push(parse_interface(&mut lexer)), KeywordVarGlobal => unit.global_vars.push(parse_variable_block(&mut lexer, linkage)), KeywordVarConfig => unit.var_config.extend(parse_config_variables(&mut lexer)), @@ -166,6 +168,43 @@ fn parse_actions( }) } +fn parse_interface(lexer: &mut ParseSession) -> Interface { + let location_start = lexer.range().start; + lexer.consume_or_report(KeywordInterface); + + // TODO: Error handle empty interface name + let (name, location_name) = parse_identifier(lexer).unwrap(); + + let mut methods = Vec::new(); + loop { + match lexer.token { + KeywordMethod => { + let (method, implem) = parse_method(lexer, &name, LinkageType::Internal, false).unwrap(); + debug_assert!( + implem.statements.is_empty(), + "the method body should be empty, do we need an error here?" + ); + + methods.push(method); + } + + KeywordProperty => unimplemented!(), + + _ => break, + } + } + + lexer.try_consume(&KeywordEndInterface); + let location_end = lexer.range().start; + + Interface { + name, + methods, + location: lexer.source_range_factory.create_range(location_start..location_end), + location_name, + } +} + /// /// parse a pou /// # Arguments @@ -191,7 +230,7 @@ fn parse_pou( let start = lexer.range().start; lexer.advance(); //Consume ProgramKeyword let closing_tokens = vec![ - expected_end_token.clone(), + expected_end_token, KeywordEndAction, KeywordEndProgram, KeywordEndFunction, @@ -207,11 +246,11 @@ fn parse_pou( parse_identifier(lexer).unwrap_or_else(|| ("".to_string(), SourceLocation::undefined())); // parse POU name let generics = parse_generics(lexer); + let interfaces = parse_implements(lexer); with_scope(lexer, name.clone(), |lexer| { // TODO: Parse USING directives let super_class = parse_super_class(lexer); - // TODO: Parse IMPLEMENTS specifier // parse an optional return type // classes do not have a return type (check in validator) @@ -281,6 +320,7 @@ fn parse_pou( generics, linkage, super_class, + interfaces, is_const: constant, }]; pous.append(&mut impl_pous); @@ -328,6 +368,23 @@ fn parse_generics(lexer: &mut ParseSession) -> Vec { } } +fn parse_implements(lexer: &mut ParseSession) -> Vec { + let mut implements = vec![]; + if lexer.try_consume(&KeywordImplements) { + loop { + let (name, location) = parse_identifier(lexer).unwrap(); + implements.push(InterfaceDeclaration { name, location }); + + // TODO: This will panic if the there's a trailing comma with no following identifier + if !lexer.try_consume(&Token::KeywordComma) { + break; + } + } + } + + implements +} + fn parse_type_nature(lexer: &mut ParseSession, nature: &str) -> TypeNature { match nature { "ANY" => TypeNature::Any, @@ -442,7 +499,7 @@ fn parse_return_type(lexer: &mut ParseSession, pou_type: &PouType) -> Option Option<(Pou, Implementation)> { @@ -463,7 +520,7 @@ fn parse_method( lexer.advance(); // eat METHOD keyword let access = Some(parse_access_modifier(lexer)); - let pou_type = PouType::Method { owner_class: class_name.into() }; + let pou_type = PouType::Method { parent: parent.into() }; let poly_mode = parse_polymorphism_mode(lexer, &pou_type); let overriding = lexer.try_consume(&KeywordOverride); let (name, name_location) = parse_identifier(lexer)?; @@ -480,11 +537,11 @@ fn parse_method( variable_blocks.push(parse_variable_block(lexer, LinkageType::Internal)); } - let call_name = qualified_name(class_name, &name); + let call_name = qualified_name(parent, &name); let implementation = parse_implementation( lexer, linkage, - PouType::Method { owner_class: class_name.into() }, + PouType::Method { parent: parent.into() }, &call_name, &call_name, !generics.is_empty(), @@ -508,6 +565,7 @@ fn parse_method( generics, linkage, super_class: None, + interfaces: Vec::new(), is_const: constant, }, implementation, @@ -652,7 +710,7 @@ fn parse_full_data_type_definition( name: Option, ) -> Option { let end_keyword = if lexer.token == KeywordStruct { KeywordEndStruct } else { KeywordSemicolon }; - let parsed_datatype = parse_any_in_region(lexer, vec![end_keyword.clone()], |lexer| { + let parsed_datatype = parse_any_in_region(lexer, vec![end_keyword], |lexer| { let sized = lexer.try_consume(&PropertySized); if lexer.try_consume(&KeywordDotDotDot) { Some(( @@ -840,7 +898,7 @@ fn parse_type_reference_type_definition( } fn parse_string_size_expression(lexer: &mut ParseSession) -> Option { - let opening_token = lexer.token.clone(); + let opening_token = lexer.token; if lexer.try_consume(&KeywordSquareParensOpen) || lexer.try_consume(&KeywordParensOpen) { let opening_location = lexer.range().start; let closing_tokens = vec![KeywordSquareParensClose, KeywordParensClose]; @@ -1063,7 +1121,7 @@ fn parse_control(lexer: &mut ParseSession) -> AstNode { } fn parse_variable_block_type(lexer: &mut ParseSession) -> VariableBlockType { - let block_type = lexer.token.clone(); + let block_type = lexer.token; //Consume the type token lexer.advance(); let argument_property = if lexer.try_consume(&PropertyByRef) { diff --git a/src/parser/tests.rs b/src/parser/tests.rs index 503be7cdb3..8e7efa2e0c 100644 --- a/src/parser/tests.rs +++ b/src/parser/tests.rs @@ -12,6 +12,7 @@ mod control_parser_tests; mod expressions_parser_tests; mod function_parser_tests; mod initializer_parser_tests; +mod interface_parser_tests; mod misc_parser_tests; mod parse_errors; mod parse_generics; diff --git a/src/parser/tests/class_parser_tests.rs b/src/parser/tests/class_parser_tests.rs index 433c06666a..469508b37f 100644 --- a/src/parser/tests/class_parser_tests.rs +++ b/src/parser/tests/class_parser_tests.rs @@ -73,7 +73,7 @@ fn method_with_defaults_can_be_parsed() { assert_eq!(unit.implementations.len(), 2); let method_pou = &unit.units[1]; - assert_eq!(method_pou.pou_type, PouType::Method { owner_class: "MyClass".into() }); + assert_eq!(method_pou.pou_type, PouType::Method { parent: "MyClass".into() }); let method = &unit.implementations[0]; assert_eq!(method_pou.name, "MyClass.testMethod"); @@ -95,7 +95,7 @@ fn method_can_be_parsed() { assert_eq!(unit.implementations.len(), 2); let method_pou = &unit.units[1]; - assert_eq!(method_pou.pou_type, PouType::Method { owner_class: "MyClass".into() }); + assert_eq!(method_pou.pou_type, PouType::Method { parent: "MyClass".into() }); let method = &unit.implementations[0]; assert_eq!(method_pou.name, "MyClass.testMethod2"); @@ -134,7 +134,7 @@ fn method_with_return_type_can_be_parsed() { assert_eq!(class.pou_type, PouType::Class); let method_pou = &unit.units[1]; - assert_eq!(method_pou.pou_type, PouType::Method { owner_class: "MyClass".into() }); + assert_eq!(method_pou.pou_type, PouType::Method { parent: "MyClass".into() }); let method = &unit.implementations[0]; // classes have implementation because they are treated as other POUs @@ -280,7 +280,7 @@ fn fb_method_can_be_parsed() { assert_eq!(unit.implementations.len(), 2); let method_pou = &unit.units[1]; - assert_eq!(method_pou.pou_type, PouType::Method { owner_class: "MyFb".into() }); + assert_eq!(method_pou.pou_type, PouType::Method { parent: "MyFb".into() }); let method = &unit.implementations[0]; assert_eq!(method_pou.name, "MyFb.testMethod2"); @@ -328,7 +328,7 @@ fn fb_method_with_return_type_can_be_parsed() { assert_eq!(class.pou_type, PouType::FunctionBlock); let method_pou = &unit.units[1]; - assert_eq!(method_pou.pou_type, PouType::Method { owner_class: "MyShinyFb".into() }); + assert_eq!(method_pou.pou_type, PouType::Method { parent: "MyShinyFb".into() }); let method = &unit.implementations[0]; // classes have implementation because they are treated as other POUs @@ -357,7 +357,7 @@ fn program_methods_can_be_parsed() { assert_eq!(unit.implementations.len(), 2); let method_pou = &unit.units[1]; - assert_eq!(method_pou.pou_type, PouType::Method { owner_class: "prog".into() }); + assert_eq!(method_pou.pou_type, PouType::Method { parent: "prog".into() }); let method = &unit.implementations[0]; assert_eq!(method_pou.name, "prog.testMethod2"); @@ -405,7 +405,7 @@ fn program_method_with_return_type_can_be_parsed() { assert_eq!(class.pou_type, PouType::Program); let method_pou = &unit.units[1]; - assert_eq!(method_pou.pou_type, PouType::Method { owner_class: "prog".into() }); + assert_eq!(method_pou.pou_type, PouType::Method { parent: "prog".into() }); let method = &unit.implementations[0]; // classes have implementation because they are treated as other POUs @@ -475,11 +475,11 @@ fn method_variable_blocks_can_be_parsed() { let (unit, _) = parse(src); let fb_mthd = &unit.units[1]; assert_eq!(fb_mthd.name, "fb.mthd".to_string()); - assert_eq!(fb_mthd.pou_type, PouType::Method { owner_class: "fb".into() }); + assert_eq!(fb_mthd.pou_type, PouType::Method { parent: "fb".into() }); let prg_mthd = &unit.units[3]; assert_eq!(prg_mthd.name, "prg.mthd".to_string()); - assert_eq!(prg_mthd.pou_type, PouType::Method { owner_class: "prg".into() }); + assert_eq!(prg_mthd.pou_type, PouType::Method { parent: "prg".into() }); // we expect one of each of these `VariableBlockType` to be parsed let expected_var_blocks = vec![ diff --git a/src/parser/tests/expressions_parser_tests.rs b/src/parser/tests/expressions_parser_tests.rs index 8b7f070109..4d6eec4cf9 100644 --- a/src/parser/tests/expressions_parser_tests.rs +++ b/src/parser/tests/expressions_parser_tests.rs @@ -1597,6 +1597,7 @@ fn sized_string_as_function_return() { generics: vec![], linkage: LinkageType::Internal, super_class: None, + interfaces: vec![], is_const: false, }; @@ -1640,6 +1641,7 @@ fn array_type_as_function_return() { generics: vec![], linkage: LinkageType::Internal, super_class: None, + interfaces: vec![], is_const: false, }; diff --git a/src/parser/tests/function_parser_tests.rs b/src/parser/tests/function_parser_tests.rs index 45bacba8ad..270ff69a0d 100644 --- a/src/parser/tests/function_parser_tests.rs +++ b/src/parser/tests/function_parser_tests.rs @@ -240,6 +240,7 @@ fn varargs_parameters_can_be_parsed() { generics: vec![], linkage: LinkageType::Internal, super_class: None, + interfaces: vec![], is_const: false, }; assert_eq!(format!("{expected:#?}"), format!("{x:#?}").as_str()); @@ -311,6 +312,7 @@ fn sized_varargs_parameters_can_be_parsed() { generics: vec![], linkage: LinkageType::Internal, super_class: None, + interfaces: vec![], is_const: false, }; assert_eq!(format!("{expected:#?}"), format!("{x:#?}").as_str()); diff --git a/src/parser/tests/interface_parser_tests.rs b/src/parser/tests/interface_parser_tests.rs new file mode 100644 index 0000000000..bea04b4341 --- /dev/null +++ b/src/parser/tests/interface_parser_tests.rs @@ -0,0 +1,373 @@ +use crate::test_utils::tests::parse; + +#[test] +fn empty_interface() { + let source = r" + INTERFACE myInterface + END_INTERFACE + "; + + let (unit, diagnostics) = parse(source); + + assert_eq!(diagnostics.len(), 0, "Expected no diagnostics but got {:#?}", diagnostics); + insta::assert_debug_snapshot!(unit.interfaces, @r###" + [ + Interface { + name: "myInterface", + methods: [], + location: SourceLocation { + span: Range( + TextLocation { + line: 1, + column: 4, + offset: 5, + }..TextLocation { + line: 3, + column: 4, + offset: 49, + }, + ), + }, + location_name: SourceLocation { + span: Range( + TextLocation { + line: 1, + column: 14, + offset: 15, + }..TextLocation { + line: 1, + column: 25, + offset: 26, + }, + ), + }, + }, + ] + "###); +} + +#[test] +fn interface_with_single_method() { + let source = r" + INTERFACE myInterface + METHOD foo : INT + VAR_INPUT + a : INT; + b : INT; + END_VAR + END_METHOD + END_INTERFACE + "; + + let (unit, diagnostics) = parse(source); + + assert_eq!(diagnostics.len(), 0, "Expected no diagnostics but got {:#?}", diagnostics); + insta::assert_debug_snapshot!(unit.interfaces, @r###" + [ + Interface { + name: "myInterface", + methods: [ + POU { + name: "myInterface.foo", + variable_blocks: [ + VariableBlock { + variables: [ + Variable { + name: "a", + data_type: DataTypeReference { + referenced_type: "INT", + }, + }, + Variable { + name: "b", + data_type: DataTypeReference { + referenced_type: "INT", + }, + }, + ], + variable_block_type: Input( + ByVal, + ), + }, + ], + pou_type: Method { + parent: "myInterface", + }, + return_type: Some( + DataTypeReference { + referenced_type: "INT", + }, + ), + interfaces: [], + }, + ], + location: SourceLocation { + span: Range( + TextLocation { + line: 1, + column: 4, + offset: 5, + }..TextLocation { + line: 9, + column: 4, + offset: 185, + }, + ), + }, + location_name: SourceLocation { + span: Range( + TextLocation { + line: 1, + column: 14, + offset: 15, + }..TextLocation { + line: 1, + column: 25, + offset: 26, + }, + ), + }, + }, + ] + "###); +} + +#[test] +fn interface_with_multiple_methods() { + let source = r" + INTERFACE myInterface + METHOD foo : INT + VAR_INPUT + a : INT; + b : INT; + END_VAR + END_METHOD + + METHOD bar : INT + VAR_INPUT + c : INT; + END_VAR + + VAR_IN_OUT + d : INT; + END_VAR + END_METHOD + END_INTERFACE + "; + + let (unit, diagnostics) = parse(source); + + assert_eq!(diagnostics.len(), 0, "Expected no diagnostics but got {:#?}", diagnostics); + insta::assert_debug_snapshot!(unit.interfaces, @r###" + [ + Interface { + name: "myInterface", + methods: [ + POU { + name: "myInterface.foo", + variable_blocks: [ + VariableBlock { + variables: [ + Variable { + name: "a", + data_type: DataTypeReference { + referenced_type: "INT", + }, + }, + Variable { + name: "b", + data_type: DataTypeReference { + referenced_type: "INT", + }, + }, + ], + variable_block_type: Input( + ByVal, + ), + }, + ], + pou_type: Method { + parent: "myInterface", + }, + return_type: Some( + DataTypeReference { + referenced_type: "INT", + }, + ), + interfaces: [], + }, + POU { + name: "myInterface.bar", + variable_blocks: [ + VariableBlock { + variables: [ + Variable { + name: "c", + data_type: DataTypeReference { + referenced_type: "INT", + }, + }, + ], + variable_block_type: Input( + ByVal, + ), + }, + VariableBlock { + variables: [ + Variable { + name: "d", + data_type: DataTypeReference { + referenced_type: "INT", + }, + }, + ], + variable_block_type: InOut, + }, + ], + pou_type: Method { + parent: "myInterface", + }, + return_type: Some( + DataTypeReference { + referenced_type: "INT", + }, + ), + interfaces: [], + }, + ], + location: SourceLocation { + span: Range( + TextLocation { + line: 1, + column: 4, + offset: 5, + }..TextLocation { + line: 19, + column: 4, + offset: 366, + }, + ), + }, + location_name: SourceLocation { + span: Range( + TextLocation { + line: 1, + column: 14, + offset: 15, + }..TextLocation { + line: 1, + column: 25, + offset: 26, + }, + ), + }, + }, + ] + "###); +} + +#[test] +fn pou_implementing_single_interface() { + let source = r#" + FUNCTION_BLOCK foo IMPLEMENTS myInterface END_FUNCTION_BLOCK + "#; + + let (unit, diagnostics) = parse(source); + + assert_eq!(diagnostics.len(), 0, "Expected no diagnostics but got {:#?}", diagnostics); + insta::assert_debug_snapshot!(unit.units[0], @r###" + POU { + name: "foo", + variable_blocks: [], + pou_type: FunctionBlock, + return_type: None, + interfaces: [ + InterfaceDeclaration { + name: "myInterface", + location: SourceLocation { + span: Range( + TextLocation { + line: 1, + column: 34, + offset: 35, + }..TextLocation { + line: 1, + column: 45, + offset: 46, + }, + ), + }, + }, + ], + } + "###); +} + +#[test] +fn pou_implementing_multiple_interfaces() { + let source = r#" + FUNCTION_BLOCK foo IMPLEMENTS InterfaceA, InterfaceB, InterfaceC END_FUNCTION_BLOCK + "#; + + let (unit, diagnostics) = parse(source); + + assert_eq!(diagnostics.len(), 0, "Expected no diagnostics but got {:#?}", diagnostics); + insta::assert_debug_snapshot!(unit.units[0], @r###" + POU { + name: "foo", + variable_blocks: [], + pou_type: FunctionBlock, + return_type: None, + interfaces: [ + InterfaceDeclaration { + name: "InterfaceA", + location: SourceLocation { + span: Range( + TextLocation { + line: 1, + column: 34, + offset: 35, + }..TextLocation { + line: 1, + column: 44, + offset: 45, + }, + ), + }, + }, + InterfaceDeclaration { + name: "InterfaceB", + location: SourceLocation { + span: Range( + TextLocation { + line: 1, + column: 46, + offset: 47, + }..TextLocation { + line: 1, + column: 56, + offset: 57, + }, + ), + }, + }, + InterfaceDeclaration { + name: "InterfaceC", + location: SourceLocation { + span: Range( + TextLocation { + line: 1, + column: 58, + offset: 59, + }..TextLocation { + line: 1, + column: 68, + offset: 69, + }, + ), + }, + }, + ], + } + "###); +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__direct_access_as_expression_parsed.snap b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__direct_access_as_expression_parsed.snap index a0860697b6..f3b5ac4afa 100644 --- a/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__direct_access_as_expression_parsed.snap +++ b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__direct_access_as_expression_parsed.snap @@ -11,6 +11,7 @@ CompilationUnit { variable_blocks: [], pou_type: Program, return_type: None, + interfaces: [], }, ], implementations: [ @@ -163,6 +164,7 @@ CompilationUnit { access: None, }, ], + interfaces: [], user_types: [], file_name: "test.st", } diff --git a/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__exp_mul_priority_test.snap b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__exp_mul_priority_test.snap index dfb562eb31..d96cc0e672 100644 --- a/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__exp_mul_priority_test.snap +++ b/src/parser/tests/snapshots/rusty__parser__tests__expressions_parser_tests__exp_mul_priority_test.snap @@ -15,6 +15,7 @@ CompilationUnit { referenced_type: "INT", }, ), + interfaces: [], }, ], implementations: [ @@ -99,6 +100,7 @@ CompilationUnit { access: None, }, ], + interfaces: [], user_types: [], file_name: "test.st", } diff --git a/src/parser/tests/snapshots/rusty__parser__tests__misc_parser_tests__exponent_literals_parsed_as_variables.snap b/src/parser/tests/snapshots/rusty__parser__tests__misc_parser_tests__exponent_literals_parsed_as_variables.snap index 3191ac0db6..387326ad23 100644 --- a/src/parser/tests/snapshots/rusty__parser__tests__misc_parser_tests__exponent_literals_parsed_as_variables.snap +++ b/src/parser/tests/snapshots/rusty__parser__tests__misc_parser_tests__exponent_literals_parsed_as_variables.snap @@ -25,4 +25,5 @@ POU { referenced_type: "E2", }, ), + interfaces: [], } diff --git a/src/parser/tests/snapshots/rusty__parser__tests__statement_parser_tests__empty_parameter_assignments_in_call_statement.snap b/src/parser/tests/snapshots/rusty__parser__tests__statement_parser_tests__empty_parameter_assignments_in_call_statement.snap index 55190f22bc..d76aaade6f 100644 --- a/src/parser/tests/snapshots/rusty__parser__tests__statement_parser_tests__empty_parameter_assignments_in_call_statement.snap +++ b/src/parser/tests/snapshots/rusty__parser__tests__statement_parser_tests__empty_parameter_assignments_in_call_statement.snap @@ -51,6 +51,7 @@ CompilationUnit { referenced_type: "INT", }, ), + interfaces: [], }, POU { name: "main", @@ -81,6 +82,7 @@ CompilationUnit { ], pou_type: Program, return_type: None, + interfaces: [], }, ], implementations: [ @@ -207,6 +209,7 @@ CompilationUnit { access: None, }, ], + interfaces: [], user_types: [], file_name: "test.st", } diff --git a/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__multi_type_declaration.snap b/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__multi_type_declaration.snap index 7fdc8b5440..7ce496b768 100644 --- a/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__multi_type_declaration.snap +++ b/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__multi_type_declaration.snap @@ -7,6 +7,7 @@ CompilationUnit { var_config: [], units: [], implementations: [], + interfaces: [], user_types: [ UserTypeDeclaration { data_type: StructType { diff --git a/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__typed_inline_enum_with_initial_values_can_be_parsed.snap b/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__typed_inline_enum_with_initial_values_can_be_parsed.snap index 81b1f76169..dba631f28a 100644 --- a/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__typed_inline_enum_with_initial_values_can_be_parsed.snap +++ b/src/parser/tests/snapshots/rusty__parser__tests__type_parser_tests__typed_inline_enum_with_initial_values_can_be_parsed.snap @@ -65,4 +65,5 @@ POU { ], pou_type: Program, return_type: None, + interfaces: [], } diff --git a/src/parser/tests/snapshots/rusty__parser__tests__variable_parser_tests__global_var_with_address.snap b/src/parser/tests/snapshots/rusty__parser__tests__variable_parser_tests__global_var_with_address.snap index c4e0da2b76..d0a0300234 100644 --- a/src/parser/tests/snapshots/rusty__parser__tests__variable_parser_tests__global_var_with_address.snap +++ b/src/parser/tests/snapshots/rusty__parser__tests__variable_parser_tests__global_var_with_address.snap @@ -2,4 +2,4 @@ source: src/parser/tests/variable_parser_tests.rs expression: "format!(\"{result:?}\")" --- -CompilationUnit { global_vars: [VariableBlock { variables: [Variable { name: "a", data_type: DataTypeDefinition { data_type: PointerType { name: None, referenced_type: DataTypeReference { referenced_type: "INT" }, auto_deref: Some(Alias) } }, address: Some(HardwareAccess { direction: Input, access: Template, address: [], location: SourceLocation { span: Range(TextLocation { line: 1, column: 14, offset: 25 }..TextLocation { line: 1, column: 20, offset: 31 }) } }) }, Variable { name: "b", data_type: DataTypeDefinition { data_type: PointerType { name: None, referenced_type: DataTypeReference { referenced_type: "INT" }, auto_deref: Some(Alias) } }, address: Some(HardwareAccess { direction: Output, access: Template, address: [], location: SourceLocation { span: Range(TextLocation { line: 2, column: 14, offset: 53 }..TextLocation { line: 2, column: 20, offset: 59 }) } }) }, Variable { name: "c", data_type: DataTypeDefinition { data_type: PointerType { name: None, referenced_type: DataTypeReference { referenced_type: "INT" }, auto_deref: Some(Alias) } }, address: Some(HardwareAccess { direction: Memory, access: Template, address: [], location: SourceLocation { span: Range(TextLocation { line: 3, column: 14, offset: 81 }..TextLocation { line: 3, column: 20, offset: 87 }) } }) }, Variable { name: "aa", data_type: DataTypeDefinition { data_type: PointerType { name: None, referenced_type: DataTypeReference { referenced_type: "INT" }, auto_deref: Some(Alias) } }, address: Some(HardwareAccess { direction: Input, access: Bit, address: [LiteralInteger { value: 7 }], location: SourceLocation { span: Range(TextLocation { line: 4, column: 15, offset: 110 }..TextLocation { line: 4, column: 22, offset: 117 }) } }) }, Variable { name: "bb", data_type: DataTypeDefinition { data_type: PointerType { name: None, referenced_type: DataTypeReference { referenced_type: "INT" }, auto_deref: Some(Alias) } }, address: Some(HardwareAccess { direction: Output, access: Byte, address: [LiteralInteger { value: 5 }, LiteralInteger { value: 5 }], location: SourceLocation { span: Range(TextLocation { line: 5, column: 15, offset: 140 }..TextLocation { line: 5, column: 24, offset: 149 }) } }) }, Variable { name: "cc", data_type: DataTypeDefinition { data_type: PointerType { name: None, referenced_type: DataTypeReference { referenced_type: "INT" }, auto_deref: Some(Alias) } }, address: Some(HardwareAccess { direction: Memory, access: DWord, address: [LiteralInteger { value: 3 }, LiteralInteger { value: 3 }, LiteralInteger { value: 3 }], location: SourceLocation { span: Range(TextLocation { line: 6, column: 15, offset: 172 }..TextLocation { line: 6, column: 26, offset: 183 }) } }) }, Variable { name: "dd", data_type: DataTypeDefinition { data_type: PointerType { name: None, referenced_type: DataTypeReference { referenced_type: "INT" }, auto_deref: Some(Alias) } }, address: Some(HardwareAccess { direction: Global, access: DWord, address: [LiteralInteger { value: 4 }, LiteralInteger { value: 3 }, LiteralInteger { value: 3 }], location: SourceLocation { span: Range(TextLocation { line: 7, column: 15, offset: 206 }..TextLocation { line: 7, column: 26, offset: 217 }) } }) }], variable_block_type: Global }], var_config: [], units: [], implementations: [], user_types: [], file_name: "test.st" } +CompilationUnit { global_vars: [VariableBlock { variables: [Variable { name: "a", data_type: DataTypeDefinition { data_type: PointerType { name: None, referenced_type: DataTypeReference { referenced_type: "INT" }, auto_deref: Some(Alias) } }, address: Some(HardwareAccess { direction: Input, access: Template, address: [], location: SourceLocation { span: Range(TextLocation { line: 1, column: 14, offset: 25 }..TextLocation { line: 1, column: 20, offset: 31 }) } }) }, Variable { name: "b", data_type: DataTypeDefinition { data_type: PointerType { name: None, referenced_type: DataTypeReference { referenced_type: "INT" }, auto_deref: Some(Alias) } }, address: Some(HardwareAccess { direction: Output, access: Template, address: [], location: SourceLocation { span: Range(TextLocation { line: 2, column: 14, offset: 53 }..TextLocation { line: 2, column: 20, offset: 59 }) } }) }, Variable { name: "c", data_type: DataTypeDefinition { data_type: PointerType { name: None, referenced_type: DataTypeReference { referenced_type: "INT" }, auto_deref: Some(Alias) } }, address: Some(HardwareAccess { direction: Memory, access: Template, address: [], location: SourceLocation { span: Range(TextLocation { line: 3, column: 14, offset: 81 }..TextLocation { line: 3, column: 20, offset: 87 }) } }) }, Variable { name: "aa", data_type: DataTypeDefinition { data_type: PointerType { name: None, referenced_type: DataTypeReference { referenced_type: "INT" }, auto_deref: Some(Alias) } }, address: Some(HardwareAccess { direction: Input, access: Bit, address: [LiteralInteger { value: 7 }], location: SourceLocation { span: Range(TextLocation { line: 4, column: 15, offset: 110 }..TextLocation { line: 4, column: 22, offset: 117 }) } }) }, Variable { name: "bb", data_type: DataTypeDefinition { data_type: PointerType { name: None, referenced_type: DataTypeReference { referenced_type: "INT" }, auto_deref: Some(Alias) } }, address: Some(HardwareAccess { direction: Output, access: Byte, address: [LiteralInteger { value: 5 }, LiteralInteger { value: 5 }], location: SourceLocation { span: Range(TextLocation { line: 5, column: 15, offset: 140 }..TextLocation { line: 5, column: 24, offset: 149 }) } }) }, Variable { name: "cc", data_type: DataTypeDefinition { data_type: PointerType { name: None, referenced_type: DataTypeReference { referenced_type: "INT" }, auto_deref: Some(Alias) } }, address: Some(HardwareAccess { direction: Memory, access: DWord, address: [LiteralInteger { value: 3 }, LiteralInteger { value: 3 }, LiteralInteger { value: 3 }], location: SourceLocation { span: Range(TextLocation { line: 6, column: 15, offset: 172 }..TextLocation { line: 6, column: 26, offset: 183 }) } }) }, Variable { name: "dd", data_type: DataTypeDefinition { data_type: PointerType { name: None, referenced_type: DataTypeReference { referenced_type: "INT" }, auto_deref: Some(Alias) } }, address: Some(HardwareAccess { direction: Global, access: DWord, address: [LiteralInteger { value: 4 }, LiteralInteger { value: 3 }, LiteralInteger { value: 3 }], location: SourceLocation { span: Range(TextLocation { line: 7, column: 15, offset: 206 }..TextLocation { line: 7, column: 26, offset: 217 }) } }) }], variable_block_type: Global }], var_config: [], units: [], implementations: [], interfaces: [], user_types: [], file_name: "test.st" } diff --git a/src/parser/tests/snapshots/rusty__parser__tests__variable_parser_tests__pou_var_with_address.snap b/src/parser/tests/snapshots/rusty__parser__tests__variable_parser_tests__pou_var_with_address.snap index e8a26e89a3..1cb4e74ff3 100644 --- a/src/parser/tests/snapshots/rusty__parser__tests__variable_parser_tests__pou_var_with_address.snap +++ b/src/parser/tests/snapshots/rusty__parser__tests__variable_parser_tests__pou_var_with_address.snap @@ -2,4 +2,4 @@ source: src/parser/tests/variable_parser_tests.rs expression: "format!(\"{result:?}\")" --- -CompilationUnit { global_vars: [], var_config: [], units: [POU { name: "main", variable_blocks: [VariableBlock { variables: [Variable { name: "a", data_type: DataTypeDefinition { data_type: PointerType { name: None, referenced_type: DataTypeReference { referenced_type: "INT" }, auto_deref: Some(Alias) } }, address: Some(HardwareAccess { direction: Input, access: Template, address: [], location: SourceLocation { span: Range(TextLocation { line: 2, column: 14, offset: 35 }..TextLocation { line: 2, column: 20, offset: 41 }) } }) }, Variable { name: "b", data_type: DataTypeDefinition { data_type: PointerType { name: None, referenced_type: DataTypeReference { referenced_type: "INT" }, auto_deref: Some(Alias) } }, address: Some(HardwareAccess { direction: Output, access: Template, address: [], location: SourceLocation { span: Range(TextLocation { line: 3, column: 14, offset: 63 }..TextLocation { line: 3, column: 20, offset: 69 }) } }) }, Variable { name: "c", data_type: DataTypeDefinition { data_type: PointerType { name: None, referenced_type: DataTypeReference { referenced_type: "INT" }, auto_deref: Some(Alias) } }, address: Some(HardwareAccess { direction: Memory, access: Template, address: [], location: SourceLocation { span: Range(TextLocation { line: 4, column: 16, offset: 93 }..TextLocation { line: 4, column: 22, offset: 99 }) } }) }, Variable { name: "d", data_type: DataTypeDefinition { data_type: PointerType { name: None, referenced_type: DataTypeReference { referenced_type: "INT" }, auto_deref: Some(Alias) } }, address: Some(HardwareAccess { direction: Memory, access: Template, address: [], location: SourceLocation { span: Range(TextLocation { line: 4, column: 16, offset: 93 }..TextLocation { line: 4, column: 22, offset: 99 }) } }) }, Variable { name: "aa", data_type: DataTypeDefinition { data_type: PointerType { name: None, referenced_type: DataTypeReference { referenced_type: "INT" }, auto_deref: Some(Alias) } }, address: Some(HardwareAccess { direction: Input, access: Bit, address: [LiteralInteger { value: 7 }], location: SourceLocation { span: Range(TextLocation { line: 5, column: 15, offset: 122 }..TextLocation { line: 5, column: 22, offset: 129 }) } }) }, Variable { name: "bb", data_type: DataTypeDefinition { data_type: PointerType { name: None, referenced_type: DataTypeReference { referenced_type: "INT" }, auto_deref: Some(Alias) } }, address: Some(HardwareAccess { direction: Output, access: Byte, address: [LiteralInteger { value: 5 }, LiteralInteger { value: 5 }], location: SourceLocation { span: Range(TextLocation { line: 6, column: 15, offset: 152 }..TextLocation { line: 6, column: 24, offset: 161 }) } }) }, Variable { name: "cc", data_type: DataTypeDefinition { data_type: PointerType { name: None, referenced_type: DataTypeReference { referenced_type: "INT" }, auto_deref: Some(Alias) } }, address: Some(HardwareAccess { direction: Memory, access: DWord, address: [LiteralInteger { value: 3 }, LiteralInteger { value: 3 }, LiteralInteger { value: 3 }], location: SourceLocation { span: Range(TextLocation { line: 7, column: 15, offset: 184 }..TextLocation { line: 7, column: 26, offset: 195 }) } }) }, Variable { name: "dd", data_type: DataTypeDefinition { data_type: PointerType { name: None, referenced_type: DataTypeReference { referenced_type: "INT" }, auto_deref: Some(Alias) } }, address: Some(HardwareAccess { direction: Global, access: DWord, address: [LiteralInteger { value: 4 }, LiteralInteger { value: 3 }, LiteralInteger { value: 3 }], location: SourceLocation { span: Range(TextLocation { line: 8, column: 15, offset: 218 }..TextLocation { line: 8, column: 26, offset: 229 }) } }) }], variable_block_type: Local }], pou_type: Program, return_type: None }], implementations: [Implementation { name: "main", type_name: "main", linkage: Internal, pou_type: Program, statements: [], location: SourceLocation { span: Range(TextLocation { line: 10, column: 4, offset: 253 }..TextLocation { line: 10, column: 15, offset: 264 }) }, name_location: SourceLocation { span: Range(TextLocation { line: 0, column: 8, offset: 8 }..TextLocation { line: 0, column: 12, offset: 12 }) }, overriding: false, generic: false, access: None }], user_types: [], file_name: "test.st" } +CompilationUnit { global_vars: [], var_config: [], units: [POU { name: "main", variable_blocks: [VariableBlock { variables: [Variable { name: "a", data_type: DataTypeDefinition { data_type: PointerType { name: None, referenced_type: DataTypeReference { referenced_type: "INT" }, auto_deref: Some(Alias) } }, address: Some(HardwareAccess { direction: Input, access: Template, address: [], location: SourceLocation { span: Range(TextLocation { line: 2, column: 14, offset: 35 }..TextLocation { line: 2, column: 20, offset: 41 }) } }) }, Variable { name: "b", data_type: DataTypeDefinition { data_type: PointerType { name: None, referenced_type: DataTypeReference { referenced_type: "INT" }, auto_deref: Some(Alias) } }, address: Some(HardwareAccess { direction: Output, access: Template, address: [], location: SourceLocation { span: Range(TextLocation { line: 3, column: 14, offset: 63 }..TextLocation { line: 3, column: 20, offset: 69 }) } }) }, Variable { name: "c", data_type: DataTypeDefinition { data_type: PointerType { name: None, referenced_type: DataTypeReference { referenced_type: "INT" }, auto_deref: Some(Alias) } }, address: Some(HardwareAccess { direction: Memory, access: Template, address: [], location: SourceLocation { span: Range(TextLocation { line: 4, column: 16, offset: 93 }..TextLocation { line: 4, column: 22, offset: 99 }) } }) }, Variable { name: "d", data_type: DataTypeDefinition { data_type: PointerType { name: None, referenced_type: DataTypeReference { referenced_type: "INT" }, auto_deref: Some(Alias) } }, address: Some(HardwareAccess { direction: Memory, access: Template, address: [], location: SourceLocation { span: Range(TextLocation { line: 4, column: 16, offset: 93 }..TextLocation { line: 4, column: 22, offset: 99 }) } }) }, Variable { name: "aa", data_type: DataTypeDefinition { data_type: PointerType { name: None, referenced_type: DataTypeReference { referenced_type: "INT" }, auto_deref: Some(Alias) } }, address: Some(HardwareAccess { direction: Input, access: Bit, address: [LiteralInteger { value: 7 }], location: SourceLocation { span: Range(TextLocation { line: 5, column: 15, offset: 122 }..TextLocation { line: 5, column: 22, offset: 129 }) } }) }, Variable { name: "bb", data_type: DataTypeDefinition { data_type: PointerType { name: None, referenced_type: DataTypeReference { referenced_type: "INT" }, auto_deref: Some(Alias) } }, address: Some(HardwareAccess { direction: Output, access: Byte, address: [LiteralInteger { value: 5 }, LiteralInteger { value: 5 }], location: SourceLocation { span: Range(TextLocation { line: 6, column: 15, offset: 152 }..TextLocation { line: 6, column: 24, offset: 161 }) } }) }, Variable { name: "cc", data_type: DataTypeDefinition { data_type: PointerType { name: None, referenced_type: DataTypeReference { referenced_type: "INT" }, auto_deref: Some(Alias) } }, address: Some(HardwareAccess { direction: Memory, access: DWord, address: [LiteralInteger { value: 3 }, LiteralInteger { value: 3 }, LiteralInteger { value: 3 }], location: SourceLocation { span: Range(TextLocation { line: 7, column: 15, offset: 184 }..TextLocation { line: 7, column: 26, offset: 195 }) } }) }, Variable { name: "dd", data_type: DataTypeDefinition { data_type: PointerType { name: None, referenced_type: DataTypeReference { referenced_type: "INT" }, auto_deref: Some(Alias) } }, address: Some(HardwareAccess { direction: Global, access: DWord, address: [LiteralInteger { value: 4 }, LiteralInteger { value: 3 }, LiteralInteger { value: 3 }], location: SourceLocation { span: Range(TextLocation { line: 8, column: 15, offset: 218 }..TextLocation { line: 8, column: 26, offset: 229 }) } }) }], variable_block_type: Local }], pou_type: Program, return_type: None, interfaces: [] }], implementations: [Implementation { name: "main", type_name: "main", linkage: Internal, pou_type: Program, statements: [], location: SourceLocation { span: Range(TextLocation { line: 10, column: 4, offset: 253 }..TextLocation { line: 10, column: 15, offset: 264 }) }, name_location: SourceLocation { span: Range(TextLocation { line: 0, column: 8, offset: 8 }..TextLocation { line: 0, column: 12, offset: 12 }) }, overriding: false, generic: false, access: None }], interfaces: [], user_types: [], file_name: "test.st" } diff --git a/src/parser/tests/snapshots/rusty__parser__tests__variable_parser_tests__struct_with_address.snap b/src/parser/tests/snapshots/rusty__parser__tests__variable_parser_tests__struct_with_address.snap index 59cec00259..6298877203 100644 --- a/src/parser/tests/snapshots/rusty__parser__tests__variable_parser_tests__struct_with_address.snap +++ b/src/parser/tests/snapshots/rusty__parser__tests__variable_parser_tests__struct_with_address.snap @@ -2,4 +2,4 @@ source: src/parser/tests/variable_parser_tests.rs expression: "format!(\"{result:?}\")" --- -CompilationUnit { global_vars: [], var_config: [], units: [], implementations: [], user_types: [UserTypeDeclaration { data_type: StructType { name: Some("t"), variables: [Variable { name: "a", data_type: DataTypeDefinition { data_type: PointerType { name: None, referenced_type: DataTypeReference { referenced_type: "INT" }, auto_deref: Some(Alias) } }, address: Some(HardwareAccess { direction: Input, access: Template, address: [], location: SourceLocation { span: Range(TextLocation { line: 1, column: 14, offset: 30 }..TextLocation { line: 1, column: 20, offset: 36 }) } }) }, Variable { name: "b", data_type: DataTypeDefinition { data_type: PointerType { name: None, referenced_type: DataTypeReference { referenced_type: "INT" }, auto_deref: Some(Alias) } }, address: Some(HardwareAccess { direction: Output, access: Template, address: [], location: SourceLocation { span: Range(TextLocation { line: 2, column: 14, offset: 58 }..TextLocation { line: 2, column: 20, offset: 64 }) } }) }, Variable { name: "c", data_type: DataTypeDefinition { data_type: PointerType { name: None, referenced_type: DataTypeReference { referenced_type: "INT" }, auto_deref: Some(Alias) } }, address: Some(HardwareAccess { direction: Memory, access: Template, address: [], location: SourceLocation { span: Range(TextLocation { line: 3, column: 14, offset: 86 }..TextLocation { line: 3, column: 20, offset: 92 }) } }) }, Variable { name: "aa", data_type: DataTypeDefinition { data_type: PointerType { name: None, referenced_type: DataTypeReference { referenced_type: "INT" }, auto_deref: Some(Alias) } }, address: Some(HardwareAccess { direction: Input, access: Bit, address: [LiteralInteger { value: 7 }], location: SourceLocation { span: Range(TextLocation { line: 4, column: 15, offset: 115 }..TextLocation { line: 4, column: 22, offset: 122 }) } }) }, Variable { name: "bb", data_type: DataTypeDefinition { data_type: PointerType { name: None, referenced_type: DataTypeReference { referenced_type: "INT" }, auto_deref: Some(Alias) } }, address: Some(HardwareAccess { direction: Output, access: Byte, address: [LiteralInteger { value: 5 }, LiteralInteger { value: 5 }], location: SourceLocation { span: Range(TextLocation { line: 5, column: 15, offset: 145 }..TextLocation { line: 5, column: 24, offset: 154 }) } }) }, Variable { name: "cc", data_type: DataTypeDefinition { data_type: PointerType { name: None, referenced_type: DataTypeReference { referenced_type: "INT" }, auto_deref: Some(Alias) } }, address: Some(HardwareAccess { direction: Memory, access: DWord, address: [LiteralInteger { value: 3 }, LiteralInteger { value: 3 }, LiteralInteger { value: 3 }], location: SourceLocation { span: Range(TextLocation { line: 6, column: 15, offset: 177 }..TextLocation { line: 6, column: 26, offset: 188 }) } }) }, Variable { name: "dd", data_type: DataTypeDefinition { data_type: PointerType { name: None, referenced_type: DataTypeReference { referenced_type: "INT" }, auto_deref: Some(Alias) } }, address: Some(HardwareAccess { direction: Global, access: DWord, address: [LiteralInteger { value: 4 }, LiteralInteger { value: 3 }, LiteralInteger { value: 3 }], location: SourceLocation { span: Range(TextLocation { line: 7, column: 15, offset: 211 }..TextLocation { line: 7, column: 26, offset: 222 }) } }) }] }, initializer: None, scope: None }], file_name: "test.st" } +CompilationUnit { global_vars: [], var_config: [], units: [], implementations: [], interfaces: [], user_types: [UserTypeDeclaration { data_type: StructType { name: Some("t"), variables: [Variable { name: "a", data_type: DataTypeDefinition { data_type: PointerType { name: None, referenced_type: DataTypeReference { referenced_type: "INT" }, auto_deref: Some(Alias) } }, address: Some(HardwareAccess { direction: Input, access: Template, address: [], location: SourceLocation { span: Range(TextLocation { line: 1, column: 14, offset: 30 }..TextLocation { line: 1, column: 20, offset: 36 }) } }) }, Variable { name: "b", data_type: DataTypeDefinition { data_type: PointerType { name: None, referenced_type: DataTypeReference { referenced_type: "INT" }, auto_deref: Some(Alias) } }, address: Some(HardwareAccess { direction: Output, access: Template, address: [], location: SourceLocation { span: Range(TextLocation { line: 2, column: 14, offset: 58 }..TextLocation { line: 2, column: 20, offset: 64 }) } }) }, Variable { name: "c", data_type: DataTypeDefinition { data_type: PointerType { name: None, referenced_type: DataTypeReference { referenced_type: "INT" }, auto_deref: Some(Alias) } }, address: Some(HardwareAccess { direction: Memory, access: Template, address: [], location: SourceLocation { span: Range(TextLocation { line: 3, column: 14, offset: 86 }..TextLocation { line: 3, column: 20, offset: 92 }) } }) }, Variable { name: "aa", data_type: DataTypeDefinition { data_type: PointerType { name: None, referenced_type: DataTypeReference { referenced_type: "INT" }, auto_deref: Some(Alias) } }, address: Some(HardwareAccess { direction: Input, access: Bit, address: [LiteralInteger { value: 7 }], location: SourceLocation { span: Range(TextLocation { line: 4, column: 15, offset: 115 }..TextLocation { line: 4, column: 22, offset: 122 }) } }) }, Variable { name: "bb", data_type: DataTypeDefinition { data_type: PointerType { name: None, referenced_type: DataTypeReference { referenced_type: "INT" }, auto_deref: Some(Alias) } }, address: Some(HardwareAccess { direction: Output, access: Byte, address: [LiteralInteger { value: 5 }, LiteralInteger { value: 5 }], location: SourceLocation { span: Range(TextLocation { line: 5, column: 15, offset: 145 }..TextLocation { line: 5, column: 24, offset: 154 }) } }) }, Variable { name: "cc", data_type: DataTypeDefinition { data_type: PointerType { name: None, referenced_type: DataTypeReference { referenced_type: "INT" }, auto_deref: Some(Alias) } }, address: Some(HardwareAccess { direction: Memory, access: DWord, address: [LiteralInteger { value: 3 }, LiteralInteger { value: 3 }, LiteralInteger { value: 3 }], location: SourceLocation { span: Range(TextLocation { line: 6, column: 15, offset: 177 }..TextLocation { line: 6, column: 26, offset: 188 }) } }) }, Variable { name: "dd", data_type: DataTypeDefinition { data_type: PointerType { name: None, referenced_type: DataTypeReference { referenced_type: "INT" }, auto_deref: Some(Alias) } }, address: Some(HardwareAccess { direction: Global, access: DWord, address: [LiteralInteger { value: 4 }, LiteralInteger { value: 3 }, LiteralInteger { value: 3 }], location: SourceLocation { span: Range(TextLocation { line: 7, column: 15, offset: 211 }..TextLocation { line: 7, column: 26, offset: 222 }) } }) }] }, initializer: None, scope: None }], file_name: "test.st" } diff --git a/src/parser/tests/variable_parser_tests.rs b/src/parser/tests/variable_parser_tests.rs index d6d885c220..af1fa523c3 100644 --- a/src/parser/tests/variable_parser_tests.rs +++ b/src/parser/tests/variable_parser_tests.rs @@ -404,6 +404,7 @@ fn var_config_test() { ], units: [], implementations: [], + interfaces: [], user_types: [], file_name: "test.st", } @@ -501,6 +502,7 @@ fn var_external() { ], pou_type: Function, return_type: None, + interfaces: [], }, ], implementations: [ @@ -541,6 +543,7 @@ fn var_external() { access: None, }, ], + interfaces: [], user_types: [], file_name: "test.st", } @@ -625,6 +628,7 @@ fn var_external_constant() { ], pou_type: Function, return_type: None, + interfaces: [], }, ], implementations: [ @@ -665,6 +669,7 @@ fn var_external_constant() { access: None, }, ], + interfaces: [], user_types: [], file_name: "test.st", } diff --git a/src/resolver.rs b/src/resolver.rs index 0050a30512..6664bf1006 100644 --- a/src/resolver.rs +++ b/src/resolver.rs @@ -804,6 +804,11 @@ impl<'i> TypeAnnotator<'i> { visitor.visit_pou(ctx, pou); } + for _ in &unit.interfaces { + // Do nothing, mostly because POUs defined in interfaces are empty therefore there shouldn't be + // anything to annotate + } + for t in &unit.user_types { visitor.visit_user_type_declaration(t, ctx); } diff --git a/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__hardware_access_types_annotated-2.snap b/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__hardware_access_types_annotated-2.snap deleted file mode 100644 index e32e91a6b1..0000000000 --- a/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__hardware_access_types_annotated-2.snap +++ /dev/null @@ -1,13 +0,0 @@ ---- -source: src/resolver/tests/resolve_expressions_tests.rs -expression: annotations.get(right).unwrap() ---- -Variable { - resulting_type: "INT", - qualified_name: "__PI_1_2", - constant: false, - argument_type: ByVal( - Global, - ), - auto_deref: None, -} diff --git a/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__hardware_access_types_annotated-3.snap b/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__hardware_access_types_annotated-3.snap deleted file mode 100644 index 3a3e74029d..0000000000 --- a/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__hardware_access_types_annotated-3.snap +++ /dev/null @@ -1,13 +0,0 @@ ---- -source: src/resolver/tests/resolve_expressions_tests.rs -expression: annotations.get(right).unwrap() ---- -Variable { - resulting_type: "DINT", - qualified_name: "__M_1_3", - constant: false, - argument_type: ByVal( - Global, - ), - auto_deref: None, -} diff --git a/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__hardware_access_types_annotated-4.snap b/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__hardware_access_types_annotated-4.snap deleted file mode 100644 index c7d6f3cade..0000000000 --- a/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__hardware_access_types_annotated-4.snap +++ /dev/null @@ -1,13 +0,0 @@ ---- -source: src/resolver/tests/resolve_expressions_tests.rs -expression: annotations.get(right).unwrap() ---- -Variable { - resulting_type: "BOOL", - qualified_name: "__G_1_4", - constant: false, - argument_type: ByVal( - Global, - ), - auto_deref: None, -} diff --git a/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__hardware_access_types_annotated-5.snap b/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__hardware_access_types_annotated-5.snap deleted file mode 100644 index 47af5c7c42..0000000000 --- a/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__hardware_access_types_annotated-5.snap +++ /dev/null @@ -1,13 +0,0 @@ ---- -source: src/resolver/tests/resolve_expressions_tests.rs -expression: annotations.get(right).unwrap() ---- -Variable { - resulting_type: "LINT", - qualified_name: "__PI_2_1", - constant: false, - argument_type: ByVal( - Global, - ), - auto_deref: None, -} diff --git a/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__hardware_access_types_annotated.snap b/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__hardware_access_types_annotated.snap deleted file mode 100644 index fb8245746e..0000000000 --- a/src/resolver/tests/snapshots/rusty__resolver__tests__resolve_expressions_tests__hardware_access_types_annotated.snap +++ /dev/null @@ -1,13 +0,0 @@ ---- -source: src/resolver/tests/resolve_expressions_tests.rs -expression: annotations.get(right).unwrap() ---- -Variable { - resulting_type: "BYTE", - qualified_name: "__PI_1_1", - constant: false, - argument_type: ByVal( - Global, - ), - auto_deref: None, -} diff --git a/src/tests/adr/initializer_functions_adr.rs b/src/tests/adr/initializer_functions_adr.rs index ce1321ee0d..b5126eddf6 100644 --- a/src/tests/adr/initializer_functions_adr.rs +++ b/src/tests/adr/initializer_functions_adr.rs @@ -73,6 +73,7 @@ fn ref_call_in_initializer_is_lowered_to_init_function() { ], pou_type: Init, return_type: None, + interfaces: [], } "###); } @@ -342,6 +343,7 @@ fn global_initializers_are_wrapped_in_single_init_function() { variable_blocks: [], pou_type: ProjectInit, return_type: None, + interfaces: [], } "###); diff --git a/src/validation/global.rs b/src/validation/global.rs index 25f3420a24..1018abe331 100644 --- a/src/validation/global.rs +++ b/src/validation/global.rs @@ -76,6 +76,9 @@ impl GlobalValidator { // all POUs self.validate_unique_pous(index); + + // all interfaces + self.validate_unique_interfaces(index); } /// validates following uniqueness-clusters: @@ -220,6 +223,15 @@ impl GlobalValidator { } } + fn validate_unique_interfaces(&mut self, index: &Index) { + let interfaces = index + .get_interfaces() + .values() + .map(|interface| (interface.name.as_str(), &interface.location_name)); + + self.check_uniqueness_of_cluster(interfaces, Some("Ambiguous interface")); + } + fn check_uniqueness_of_cluster<'a, T>(&mut self, cluster: T, additional_text: Option<&str>) where T: Iterator, diff --git a/src/validation/pou.rs b/src/validation/pou.rs index 6680ac2fd3..cd48265155 100644 --- a/src/validation/pou.rs +++ b/src/validation/pou.rs @@ -4,11 +4,12 @@ use plc_diagnostics::diagnostics::Diagnostic; use super::{ statement::visit_statement, variable::visit_variable_block, ValidationContext, Validator, Validators, }; -use crate::resolver::AnnotationMap; +use crate::{index::PouIndexEntry, resolver::AnnotationMap}; pub fn visit_pou(validator: &mut Validator, pou: &Pou, context: &ValidationContext<'_, T>) { if pou.linkage != LinkageType::External { validate_pou(validator, pou, context); + validate_interface_impl(validator, context, pou); for block in &pou.variable_blocks { visit_variable_block(validator, Some(pou), block, context); @@ -16,6 +17,188 @@ pub fn visit_pou(validator: &mut Validator, pou: &Pou, context } } +// TODO: a method or property can be defined by multiple implemented interfaces, as long as the signature is the same +// -> an error is shown when a method or property is declared in multiple interfaces of a function block, when the signature does not match +// TODO: Check that the interface is only implemented once +// TODO: Go over all new diagnostics and make sure they have correct (new?) error codes with a nice to read markdown file +fn validate_interface_impl(validator: &mut Validator, ctxt: &ValidationContext<'_, T>, pou: &Pou) +where + T: AnnotationMap, +{ + if pou.interfaces.is_empty() { + return; + } + + // Check if the interfaces are implemented on the correct POU types + if !matches!(pou.pou_type, PouType::FunctionBlock | PouType::Class) { + let location = { + let location_first = pou.interfaces.first().unwrap(); + let location_last = pou.interfaces.last().unwrap(); + + location_first.location.span(&location_last.location) + }; + + validator.push_diagnostic( + Diagnostic::new("Interfaces can only be implemented by either classes or function blocks") + .with_error_code("E001") + .with_location(location), + ); + } + + // Check if the declared interfaces exist, i.e. the comma seperated interfaces after `[...] IMPLEMENTS` + let mut interfaces = Vec::new(); + for declaration in &pou.interfaces { + match ctxt.index.find_interface(&declaration.name) { + Some(interface) => interfaces.push(interface), + + None => { + validator.push_diagnostic( + Diagnostic::new(format!("Interface `{}` does not exist", declaration.name)) + .with_error_code("E001") + .with_location(&declaration.location), + ); + } + } + } + + // Check if the POUs are implementing interfaces methods + let methods_interface = interfaces.iter().flat_map(|it| it.get_methods(ctxt.index)).collect::>(); + + for method_interface in &methods_interface { + let (_, method_name) = method_interface.get_name().split_once('.').unwrap(); // TODO: Find better approach + + match ctxt.index.find_method(&pou.name, method_name) { + Some(method_pou) => { + validate_method_signature(validator, ctxt, method_pou, method_interface); + } + None => { + validator.push_diagnostic( + Diagnostic::new(format!( + "Method implementation of `{}` missing in POU `{}`", + method_name, pou.name + )) + .with_error_code("E002") + .with_location(&pou.name_location) + .with_secondary_location(method_interface.get_location()), + ); + } + } + } +} + +pub fn validate_method_signature( + validator: &mut Validator, + ctxt: &ValidationContext<'_, T>, + method_pou: &PouIndexEntry, + method_interface: &PouIndexEntry, +) where + T: AnnotationMap, +{ + // Check if the return type matches + let return_type_pou = ctxt.index.get_return_type_or_void(method_pou.get_name()); + let return_type_interface = ctxt.index.get_return_type_or_void(method_interface.get_name()); + + if return_type_pou != return_type_interface { + validator.push_diagnostic( + Diagnostic::new(format!( + "Return type of method `{}` does not match the return type of the interface method, expected `{}` but got `{}` instead", + method_pou.get_name(), return_type_interface.get_name(), return_type_pou.get_name() + )) + .with_error_code("E001") + .with_location(method_pou.get_location()) + .with_secondary_location(method_interface.get_location()), + ); + } + + // Check if the parameters match; note that the order of the parameters is important due to implicit calls + let parameters_pou = ctxt.index.get_declared_parameters(method_pou.get_name()); + let parameters_interface = ctxt.index.get_declared_parameters(method_interface.get_name()); + + dbg!(¶meters_pou, ¶meters_interface); + + for (idx, parameter_interface) in parameters_interface.iter().enumerate() { + match parameters_pou.get(idx) { + Some(parameter_pou) => { + // Name + if parameter_pou.get_name() != parameter_interface.get_name() { + validator.push_diagnostic( + // TODO: be more explicit in error message as to why the order is important (implicit calls) + Diagnostic::new(format!( + "Expected parameter `{}` but got `{}`", + parameter_interface.get_name(), + parameter_pou.get_name() + )) + .with_error_code("E001") + .with_location(¶meter_interface.source_location) + .with_secondary_location(¶meter_pou.source_location), + ); + } + + // Type + if parameter_pou.get_type_name() != parameter_interface.get_type_name() { + validator.push_diagnostic( + Diagnostic::new(format!( + "Expected parameter `{}` to have type `{}` but got `{}` instead", + parameter_pou.get_name(), + parameter_pou.get_type_name(), + parameter_interface.get_type_name(), + )) + .with_error_code("E001") + .with_location(method_pou.get_location()) + .with_secondary_location(¶meter_interface.source_location), + ); + } + + // Declaration Type (VAR_INPUT, VAR_OUTPUT, VAR_IN_OUT) + if parameter_pou.get_declaration_type() != parameter_interface.get_declaration_type() { + validator.push_diagnostic( + Diagnostic::new(format!( + "Expected parameter `{}` to have declaration type `{}` but got `{}` instead", + parameter_pou.get_name(), + parameter_interface.get_declaration_type().get_inner(), + parameter_pou.get_declaration_type().get_inner(), + )) + .with_error_code("E001") + .with_location(method_pou.get_location()) + .with_secondary_location(¶meter_interface.source_location), + ); + } + } + + // Method did not implement the parameter + None => { + validator.push_diagnostic( + Diagnostic::new(format!( + "Parameter `{}` missing in method `{}`", + parameter_interface.get_name(), + method_pou.get_name() + )) + .with_error_code("E001") + .with_location(method_pou.get_location()) + .with_secondary_location(¶meter_interface.source_location), + ); + } + } + } + + // Exceeding parameters in the POU, which we did not catch in the for loop above because we were only + // iterating over the interface parameters; anyhow any exceeding parameter is considered an error because + // the function signature no longer holds + if parameters_pou.len() > parameters_interface.len() { + for parameter in parameters_pou.into_iter().skip(parameters_interface.len()) { + validator.push_diagnostic( + Diagnostic::new(format!( + "Parameter `{}` is not defined in the interface method", + parameter.get_name() + )) + .with_error_code("E001") + .with_location(¶meter.source_location) + .with_secondary_location(method_interface.get_location()), + ); + } + } +} + pub fn visit_implementation( validator: &mut Validator, implementation: &Implementation, diff --git a/src/validation/tests.rs b/src/validation/tests.rs index eb1f77197f..ce41badddf 100644 --- a/src/validation/tests.rs +++ b/src/validation/tests.rs @@ -6,6 +6,7 @@ mod builtin_validation_tests; mod duplicates_validation_test; mod enum_validation_test; mod generic_validation_tests; +mod interface_validation_tests; mod literals_validation_tests; mod naming_validation_test; mod pou_validation_tests; diff --git a/src/validation/tests/duplicates_validation_test.rs b/src/validation/tests/duplicates_validation_test.rs index 0ce02d34af..832d1ae696 100644 --- a/src/validation/tests/duplicates_validation_test.rs +++ b/src/validation/tests/duplicates_validation_test.rs @@ -761,43 +761,30 @@ fn duplicate_method_names_should_return_an_error() { "###); } -// #[test] -// fn duplicate_with_generic_ir() { -// // GIVEN several files with calls to a generic function -// let file1: SourceCode = r" -// {external} -// FUNCTION foo : DATE -// VAR_INPUT -// a : T; -// b : T; -// c : T; -// END_VAR -// END_FUNCTION -// " -// .into(); - -// let file2: SourceCode = r" -// PROGRAM prg1 -// foo(INT#1, SINT#2, SINT#3); -// foo(DINT#1, SINT#2, SINT#3); -// foo(INT#1, SINT#2, SINT#3); -// foo(INT#1, SINT#2, SINT#3); -// END_PROGRAM -// " -// .into(); -// let file3: SourceCode = r" -// PROGRAM prg2 -// foo(INT#1, SINT#2, SINT#3); -// foo(DINT#1, SINT#2, SINT#3); -// foo(INT#1, SINT#2, SINT#3); -// foo(INT#1, SINT#2, SINT#3); -// END_PROGRAM -// " -// .into(); -// // WHEN we compile -// let ir = compile_to_string(vec![file1, file2, file3], vec![], None, DebugLevel::None).unwrap(); - -// // THEN we expect only 1 declaration per type-specific implementation of the generic function -// // although file2 & file3 both discovered them independently -// assert_snapshot!(ir); -// } +#[test] +fn duplicate_interfaces() { + let source = r" + INTERFACE foo /* ... */ END_INTERFACE + INTERFACE foo /* ... */ END_INTERFACE + "; + + let diagnostics = parse_and_validate_buffered(source); + assert_snapshot!(diagnostics, @r###" + error[E004]: foo: Ambiguous interface + ┌─ :2:15 + │ + 2 │ INTERFACE foo /* ... */ END_INTERFACE + │ ^^^ foo: Ambiguous interface + 3 │ INTERFACE foo /* ... */ END_INTERFACE + │ --- see also + + error[E004]: foo: Ambiguous interface + ┌─ :3:15 + │ + 2 │ INTERFACE foo /* ... */ END_INTERFACE + │ --- see also + 3 │ INTERFACE foo /* ... */ END_INTERFACE + │ ^^^ foo: Ambiguous interface + + "###); +} diff --git a/src/validation/tests/interface_validation_tests.rs b/src/validation/tests/interface_validation_tests.rs new file mode 100644 index 0000000000..f4676befe9 --- /dev/null +++ b/src/validation/tests/interface_validation_tests.rs @@ -0,0 +1,330 @@ +use crate::test_utils::tests::parse_and_validate_buffered; + +#[test] +fn pou_implementing_non_existing_interfaces() { + let source = r" + FUNCTION_BLOCK foo IMPLEMENTS delulu /* ... */ END_FUNCTION_BLOCK + FUNCTION_BLOCK bar IMPLEMENTS delulu, delululu /* ... */ END_FUNCTION_BLOCK + "; + + let diagnostics = parse_and_validate_buffered(source); + insta::assert_snapshot!(diagnostics, @r" + error[E001]: Interface `delulu` does not exist + ┌─ :2:35 + │ + 2 │ FUNCTION_BLOCK foo IMPLEMENTS delulu /* ... */ END_FUNCTION_BLOCK + │ ^^^^^^ Interface `delulu` does not exist + + error[E001]: Interface `delulu` does not exist + ┌─ :3:35 + │ + 3 │ FUNCTION_BLOCK bar IMPLEMENTS delulu, delululu /* ... */ END_FUNCTION_BLOCK + │ ^^^^^^ Interface `delulu` does not exist + + error[E001]: Interface `delululu` does not exist + ┌─ :3:43 + │ + 3 │ FUNCTION_BLOCK bar IMPLEMENTS delulu, delululu /* ... */ END_FUNCTION_BLOCK + │ ^^^^^^^^ Interface `delululu` does not exist + "); +} + +#[test] +fn pou_implementing_same_interface_multiple_times() { + let source = r" + INTERFACE interfaceA /* ... */ END_INTERFACE + FUNCTION_BLOCK foo IMPLEMENTS interfaceA, interfaceA /* ... */ END_FUNCTION_BLOCK + "; + + let diagnostics = parse_and_validate_buffered(source); + insta::assert_snapshot!(diagnostics, @r""); +} + +#[test] +fn not_supported_pou_type_implements_interface() { + let source = r" + INTERFACE interfaceA /* ... */ END_INTERFACE + INTERFACE interfaceB /* ... */ END_INTERFACE + + // Valid + CLASS foo IMPLEMENTS interfaceA /* ... */ END_CLASS + FUNCTION_BLOCK bar IMPLEMENTS interfaceA, interfaceB /* ... */ END_FUNCTION_BLOCK + + // Invalid + PROGRAM baz IMPLEMENTS interfaceA /* ... */ END_PROGRAM + FUNCTION qux IMPLEMENTS interfaceA, interfaceB /* ... */ END_FUNCTION + "; + + let diagnostics = parse_and_validate_buffered(source); + insta::assert_snapshot!(diagnostics, @r" + error[E001]: Interfaces can only be implemented by either classes or function blocks + ┌─ :10:32 + │ + 10 │ PROGRAM baz IMPLEMENTS interfaceA /* ... */ END_PROGRAM + │ ^^^^^^^^^^ Interfaces can only be implemented by either classes or function blocks + + error[E001]: Interfaces can only be implemented by either classes or function blocks + ┌─ :11:32 + │ + 11 │ FUNCTION qux IMPLEMENTS interfaceA, interfaceB /* ... */ END_FUNCTION + │ ^^^^^^^^^^^^^^^^^^^^^^ Interfaces can only be implemented by either classes or function blocks + "); +} + +#[test] +fn pou_implements_method_with_wrong_return_type() { + let source = r" + INTERFACE interfaceA + METHOD methodA : DINT /* ... */ END_METHOD + END_INTERFACE + + FUNCTION_BLOCK fb IMPLEMENTS interfaceA + METHOD methodA : BOOL /* ... */ END_METHOD + END_FUNCTION_BLOCK + "; + + let diagnostics = parse_and_validate_buffered(source); + insta::assert_snapshot!(diagnostics, @r" + error[E001]: Return type of method `fb.methodA` does not match the return type of the interface method, expected `DINT` but got `BOOL` instead + ┌─ :7:16 + │ + 3 │ METHOD methodA : DINT /* ... */ END_METHOD + │ ------- see also + · + 7 │ METHOD methodA : BOOL /* ... */ END_METHOD + │ ^^^^^^^ Return type of method `fb.methodA` does not match the return type of the interface method, expected `DINT` but got `BOOL` instead + "); +} + +#[test] +fn pou_does_not_implement_interface_methods() { + let source = r" + INTERFACE interfaceA + METHOD methodA /* ... */ END_METHOD + END_INTERFACE + + FUNCTION_BLOCK fb IMPLEMENTS interfaceA + // Missing `methodA` implementation + END_FUNCTION_BLOCK + "; + + let diagnostics = parse_and_validate_buffered(source); + insta::assert_snapshot!(diagnostics, @r" + error[E002]: Method implementation of `methodA` missing in POU `fb` + ┌─ :6:20 + │ + 3 │ METHOD methodA /* ... */ END_METHOD + │ ------- see also + · + 6 │ FUNCTION_BLOCK fb IMPLEMENTS interfaceA + │ ^^ Method implementation of `methodA` missing in POU `fb` + "); +} + +#[test] +fn pou_with_missing_parameter_in_interface_implementation() { + let source = r" + INTERFACE interfaceA + METHOD methodA + VAR_INPUT + a : DINT; + b : DINT; + c : DINT; + END_VAR + END_METHOD + END_INTERFACE + + FUNCTION_BLOCK fb IMPLEMENTS interfaceA + METHOD methodA + VAR_INPUT + a : DINT; + b : DINT; + END_VAR + END_METHOD + END_FUNCTION_BLOCK + "; + + let diagnostics = parse_and_validate_buffered(source); + insta::assert_snapshot!(diagnostics, @r" + error[E001]: Parameter `c` missing in method `fb.methodA` + ┌─ :13:16 + │ + 7 │ c : DINT; + │ - see also + · + 13 │ METHOD methodA + │ ^^^^^^^ Parameter `c` missing in method `fb.methodA` + "); +} + +#[test] +fn pou_with_unordered_parameters_in_interface_implementation() { + let source = r" + INTERFACE interfaceA + METHOD methodA + VAR_INPUT + b : DINT; + a : DINT; + c : DINT; + END_VAR + END_METHOD + END_INTERFACE + + FUNCTION_BLOCK fb IMPLEMENTS interfaceA + METHOD methodA + VAR_INPUT + a : DINT; + b : DINT; + c : DINT; + END_VAR + END_METHOD + END_FUNCTION_BLOCK + "; + + let diagnostics = parse_and_validate_buffered(source); + insta::assert_snapshot!(diagnostics, @r" + error[E001]: Expected parameter `b` but got `a` + ┌─ :5:17 + │ + 5 │ b : DINT; + │ ^ Expected parameter `b` but got `a` + · + 15 │ a : DINT; + │ - see also + + error[E001]: Expected parameter `a` but got `b` + ┌─ :6:17 + │ + 6 │ a : DINT; + │ ^ Expected parameter `a` but got `b` + · + 16 │ b : DINT; + │ - see also + "); +} + +#[test] +fn pou_with_incorrect_parameter_type_in_interface_implementation() { + let source = r" + INTERFACE interfaceA + METHOD methodA + VAR_INPUT + a : DINT; + END_VAR + END_METHOD + END_INTERFACE + + FUNCTION_BLOCK fb IMPLEMENTS interfaceA + METHOD methodA + VAR_INPUT + a : BOOL; + END_VAR + END_METHOD + END_FUNCTION_BLOCK + "; + + let diagnostics = parse_and_validate_buffered(source); + insta::assert_snapshot!(diagnostics, @r" + error[E001]: Expected parameter `a` to have type `BOOL` but got `DINT` instead + ┌─ :11:16 + │ + 5 │ a : DINT; + │ - see also + · + 11 │ METHOD methodA + │ ^^^^^^^ Expected parameter `a` to have type `BOOL` but got `DINT` instead + "); +} + +#[test] +fn pou_with_incorrect_parameter_declaration_type_in_interface_implementation() { + let source = r" + INTERFACE interfaceA + METHOD methodA + VAR_INPUT {ref} + a : DINT; + END_VAR + END_METHOD + END_INTERFACE + + FUNCTION_BLOCK fb IMPLEMENTS interfaceA + METHOD methodA + VAR_IN_OUT + a : DINT; + END_VAR + END_METHOD + END_FUNCTION_BLOCK + "; + + let diagnostics = parse_and_validate_buffered(source); + insta::assert_snapshot!(diagnostics, @r" + error[E001]: Expected parameter `a` to have declaration type `Input` but got `InOut` instead + ┌─ :11:16 + │ + 5 │ a : DINT; + │ - see also + · + 11 │ METHOD methodA + │ ^^^^^^^ Expected parameter `a` to have declaration type `Input` but got `InOut` instead + "); +} + +#[test] +fn pou_with_more_parameters_than_defined_in_interface() { + let source = r" + INTERFACE interfaceA + METHOD methodA + VAR_INPUT + a : DINT; + b : DINT; + c : DINT; + END_VAR + END_METHOD + END_INTERFACE + + FUNCTION_BLOCK fb IMPLEMENTS interfaceA + METHOD methodA + VAR_INPUT + a : DINT; + b : DINT; + c : DINT; + + // Invalid parameters + d : DINT; + e : DINT; + f : DINT; + END_VAR + END_METHOD + END_FUNCTION_BLOCK + "; + + let diagnostics = parse_and_validate_buffered(source); + insta::assert_snapshot!(diagnostics, @r" + error[E001]: Parameter `d` is not defined in the interface method + ┌─ :20:17 + │ + 3 │ METHOD methodA + │ ------- see also + · + 20 │ d : DINT; + │ ^ Parameter `d` is not defined in the interface method + + error[E001]: Parameter `e` is not defined in the interface method + ┌─ :21:17 + │ + 3 │ METHOD methodA + │ ------- see also + · + 21 │ e : DINT; + │ ^ Parameter `e` is not defined in the interface method + + error[E001]: Parameter `f` is not defined in the interface method + ┌─ :22:17 + │ + 3 │ METHOD methodA + │ ------- see also + · + 22 │ f : DINT; + │ ^ Parameter `f` is not defined in the interface method + "); +}