diff --git a/compiler/plc_ast/src/ast.rs b/compiler/plc_ast/src/ast.rs index 43a5744da3..0c451ad639 100644 --- a/compiler/plc_ast/src/ast.rs +++ b/compiler/plc_ast/src/ast.rs @@ -628,6 +628,8 @@ pub enum AstStatement { ReturnStatement(ReturnStatement), JumpStatement(JumpStatement), LabelStatement(LabelStatement), + InlineVariable(InlineVariable), + DataTypeDeclaration(Box), } impl Debug for AstNode { @@ -747,6 +749,12 @@ impl Debug for AstNode { AstStatement::LabelStatement(LabelStatement { name, .. }) => { f.debug_struct("LabelStatement").field("name", name).finish() } + AstStatement::InlineVariable(InlineVariable { name, datatype }) => { + f.debug_struct("InlineVariable").field("name", name).field("datatype", datatype).finish() + } + AstStatement::DataTypeDeclaration(decl) => { + f.debug_tuple("DataTypeDeclaration").field(decl).finish() + } } } } @@ -1519,6 +1527,27 @@ impl AstFactory { pub fn create_label_statement(name: String, location: SourceLocation, id: AstId) -> AstNode { AstNode { stmt: AstStatement::LabelStatement(LabelStatement { name }), location, id } } + + /// Creates a new inline declaration by boxing the name and datatype + pub fn create_inline_declaration( + name: AstNode, + datatype: Option, + id: AstId, + location: SourceLocation, + ) -> AstNode { + let name = Box::new(name); + let datatype = datatype.map(Box::new); + AstNode { stmt: AstStatement::InlineVariable(InlineVariable { name, datatype }), id, location } + } + + pub fn create_type_declaration( + datatype: DataTypeDeclaration, + id: AstId, + location: SourceLocation, + ) -> AstNode { + let datatype = Box::new(datatype); + AstNode { stmt: AstStatement::DataTypeDeclaration(datatype), id, location } + } } #[derive(Debug, Clone, PartialEq)] pub struct EmptyStatement {} @@ -1601,3 +1630,10 @@ pub struct JumpStatement { pub struct LabelStatement { pub name: String, } + +/// Represents a new vaiable declaration in the body +#[derive(Clone, Debug, PartialEq)] +pub struct InlineVariable { + pub name: Box, + pub datatype: Option>, +} diff --git a/src/index.rs b/src/index.rs index 5eb395d974..bc18d21c8c 100644 --- a/src/index.rs +++ b/src/index.rs @@ -22,6 +22,7 @@ use self::{ pub mod const_expressions; mod instance_iterator; +pub mod scoped_index; pub mod symbol; #[cfg(test)] mod tests; @@ -734,7 +735,7 @@ impl PouIndexEntry { /// the TypeIndex carries all types. /// it is extracted into its seaprate struct so it can be /// internally borrowed individually from the other maps -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct TypeIndex { /// all types (structs, enums, type, POUs, etc.) types: SymbolMap, diff --git a/src/index/scoped_index.rs b/src/index/scoped_index.rs new file mode 100644 index 0000000000..ef85b070a2 --- /dev/null +++ b/src/index/scoped_index.rs @@ -0,0 +1,64 @@ +use std::{collections::HashMap, rc::Rc}; + +use plc_ast::provider::IdProvider; +use plc_source::source_location::SourceLocation; + +use crate::typesystem::DataType; + +use super::VariableIndexEntry; + +/// A minimal index implementation that can be used for local scopes +#[derive(Debug, Clone)] +pub struct ScopedIndex { + ///The scope of the current index, this is usually a POU + scope: String, + + /// A unique identifier that new variables in this scope will inherit + suffix_provider: IdProvider, + + /// The location that caused this scope to be created + start_location: SourceLocation, + + /// New variables defined by this index + variables: HashMap, + + /// Datatypes defined by this index + type_index: HashMap, + + parent: Option>, +} + +impl ScopedIndex { + pub fn merge_into(self, target: &mut Self) { + target.variables.extend(self.variables); + target.type_index.extend(self.type_index); + } + + pub fn add_variable(&mut self, _name: &str) {} + + pub fn add_type(&mut self, _name: &str) {} + + pub fn find_variable(&self, _name: &str) -> Option<&VariableIndexEntry> { + todo!() + } + + pub fn find_type(&self, _name: &str) -> Option<&DataType> { + todo!() + } + + pub fn new( + parent: Option>, + location: SourceLocation, + scope: &str, + suffix_provider: IdProvider, + ) -> ScopedIndex { + ScopedIndex { + scope: scope.to_string(), + suffix_provider, + start_location: location, + parent, + type_index: Default::default(), + variables: Default::default(), + } + } +} diff --git a/src/index/symbol.rs b/src/index/symbol.rs index e4558d5052..b39bae5090 100644 --- a/src/index/symbol.rs +++ b/src/index/symbol.rs @@ -18,6 +18,16 @@ impl Default for SymbolMap { } } +impl Clone for SymbolMap +where + K: Clone, + V: Clone, +{ + fn clone(&self) -> Self { + Self { inner_map: self.inner_map.clone() } + } +} + impl SymbolMap where K: Hash + Eq, diff --git a/src/lexer/tests/lexer_tests.rs b/src/lexer/tests/lexer_tests.rs index 1befe8edc7..1d8056a4ef 100644 --- a/src/lexer/tests/lexer_tests.rs +++ b/src/lexer/tests/lexer_tests.rs @@ -58,7 +58,7 @@ fn undefined_pragmas_are_ignored_by_the_lexer() { #[test] fn registered_pragmas_parsed() { let mut lexer = lex(r" - {external}{ref}{sized}{not_registerd} + {external}{ref}{sized}{def}{not_registerd} "); assert_eq!(lexer.token, PropertyExternal, "Token : {}", lexer.slice()); lexer.advance(); @@ -66,6 +66,8 @@ fn registered_pragmas_parsed() { lexer.advance(); assert_eq!(lexer.token, PropertySized, "Token : {}", lexer.slice()); lexer.advance(); + assert_eq!(lexer.token, PropertyDef, "Token : {}", lexer.slice()); + lexer.advance(); assert_eq!(lexer.token, End); } diff --git a/src/lexer/tokens.rs b/src/lexer/tokens.rs index 02baa2a7e1..d8892f00d0 100644 --- a/src/lexer/tokens.rs +++ b/src/lexer/tokens.rs @@ -22,6 +22,9 @@ pub enum Token { #[token("{sized}")] PropertySized, + #[token("{def}")] + PropertyDef, + #[token("PROGRAM", ignore(case))] KeywordProgram, diff --git a/src/parser.rs b/src/parser.rs index 9ca95a6282..1d1adc3f52 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -342,7 +342,7 @@ fn parse_super_class(lexer: &mut ParseSession) -> Option { fn parse_return_type(lexer: &mut ParseSession, pou_type: &PouType) -> Option { let start_return_type = lexer.range().start; if lexer.try_consume(&KeywordColon) { - if let Some((declaration, initializer)) = parse_data_type_definition(lexer, None) { + if let Some((declaration, initializer)) = parse_datatype_with_initializer(lexer, None) { if let Some(init) = initializer { lexer.accept_diagnostic( Diagnostic::warning( @@ -612,7 +612,7 @@ fn parse_full_data_type_definition( None, )) } else { - parse_data_type_definition(lexer, name).map(|(type_def, initializer)| { + parse_datatype_with_initializer(lexer, name).map(|(type_def, initializer)| { if lexer.try_consume(&KeywordDotDotDot) { ( DataTypeDeclaration::DataTypeDefinition { @@ -630,23 +630,29 @@ fn parse_full_data_type_definition( }) } -// TYPE xxx : 'STRUCT' | '(' | IDENTIFIER -fn parse_data_type_definition( +fn parse_datatype_with_initializer( lexer: &mut ParseSession, name: Option, ) -> Option { + parse_data_type_definition(lexer, name).map(|type_def| { + let initializer = + if lexer.try_consume(&KeywordAssignment) { Some(parse_expression(lexer)) } else { None }; + + (type_def, initializer) + }) +} + +// TYPE xxx : 'STRUCT' | '(' | IDENTIFIER +fn parse_data_type_definition(lexer: &mut ParseSession, name: Option) -> Option { let start = lexer.location(); if lexer.try_consume(&KeywordStruct) { // Parse struct let variables = parse_variable_list(lexer); - Some(( - DataTypeDeclaration::DataTypeDefinition { - data_type: DataType::StructType { name, variables }, - location: start.span(&lexer.location()), - scope: lexer.scope.clone(), - }, - None, - )) + Some(DataTypeDeclaration::DataTypeDefinition { + data_type: DataType::StructType { name, variables }, + location: start.span(&lexer.location()), + scope: lexer.scope.clone(), + }) } else if lexer.try_consume(&KeywordArray) { parse_array_type_definition(lexer, name) } else if lexer.try_consume(&KeywordPointer) { @@ -687,23 +693,18 @@ fn parse_pointer_definition( lexer: &mut ParseSession, name: Option, start_pos: usize, -) -> Option<(DataTypeDeclaration, Option)> { - parse_data_type_definition(lexer, None).map(|(decl, initializer)| { - ( - DataTypeDeclaration::DataTypeDefinition { - data_type: DataType::PointerType { name, referenced_type: Box::new(decl) }, - location: lexer.source_range_factory.create_range(start_pos..lexer.last_range.end), - scope: lexer.scope.clone(), - }, - initializer, - ) +) -> Option { + parse_data_type_definition(lexer, None).map(|decl| DataTypeDeclaration::DataTypeDefinition { + data_type: DataType::PointerType { name, referenced_type: Box::new(decl) }, + location: lexer.source_range_factory.create_range(start_pos..lexer.last_range.end), + scope: lexer.scope.clone(), }) } fn parse_type_reference_type_definition( lexer: &mut ParseSession, name: Option, -) -> Option<(DataTypeDeclaration, Option)> { +) -> Option { let start = lexer.range().start; //Subrange let referenced_type = lexer.slice_and_advance(); @@ -718,9 +719,6 @@ fn parse_type_reference_type_definition( None }; - let initial_value = - if lexer.try_consume(&KeywordAssignment) { Some(parse_expression(lexer)) } else { None }; - let end = lexer.last_range.end; if name.is_some() || bounds.is_some() { let data_type = match bounds { @@ -758,15 +756,12 @@ fn parse_type_reference_type_definition( scope: lexer.scope.clone(), }, }; - Some((data_type, initial_value)) + Some(data_type) } else { - Some(( - DataTypeDeclaration::DataTypeReference { - referenced_type, - location: lexer.source_range_factory.create_range(start..end), - }, - initial_value, - )) + Some(DataTypeDeclaration::DataTypeReference { + referenced_type, + location: lexer.source_range_factory.create_range(start..end), + }) } } @@ -805,7 +800,7 @@ fn parse_string_size_expression(lexer: &mut ParseSession) -> Option { fn parse_string_type_definition( lexer: &mut ParseSession, name: Option, -) -> Option<(DataTypeDeclaration, Option)> { +) -> Option { let text = lexer.slice().to_string(); let start = lexer.range().start; let is_wide = lexer.token == KeywordWideString; @@ -832,34 +827,26 @@ fn parse_string_type_definition( }), _ => Some(DataTypeDeclaration::DataTypeReference { referenced_type: text, location }), } - .zip(Some(lexer.try_consume(&KeywordAssignment).then(|| parse_expression(lexer)))) } -fn parse_enum_type_definition( - lexer: &mut ParseSession, - name: Option, -) -> Option<(DataTypeDeclaration, Option)> { +fn parse_enum_type_definition(lexer: &mut ParseSession, name: Option) -> Option { let start = lexer.last_location(); let elements = parse_any_in_region(lexer, vec![KeywordParensClose], |lexer| { // Parse Enum - we expect at least one element let elements = parse_expression_list(lexer); Some(elements) })?; - let initializer = lexer.try_consume(&KeywordAssignment).then(|| parse_expression(lexer)); - Some(( - DataTypeDeclaration::DataTypeDefinition { - data_type: DataType::EnumType { name, elements, numeric_type: DINT_TYPE.to_string() }, - location: start.span(&lexer.last_location()), - scope: lexer.scope.clone(), - }, - initializer, - )) + Some(DataTypeDeclaration::DataTypeDefinition { + data_type: DataType::EnumType { name, elements, numeric_type: DINT_TYPE.to_string() }, + location: start.span(&lexer.last_location()), + scope: lexer.scope.clone(), + }) } fn parse_array_type_definition( lexer: &mut ParseSession, name: Option, -) -> Option<(DataTypeDeclaration, Option)> { +) -> Option { let start = lexer.last_range.start; let range = parse_any_in_region(lexer, vec![KeywordOf], |lexer| { // Parse Array range @@ -876,7 +863,7 @@ fn parse_array_type_definition( })?; let inner_type_defintion = parse_data_type_definition(lexer, None); - inner_type_defintion.map(|(reference, initializer)| { + inner_type_defintion.map(|reference| { let reference_end = reference.get_location().to_range().map(|it| it.end).unwrap_or(0); let location = lexer.source_range_factory.create_range(start..reference_end); @@ -907,19 +894,16 @@ fn parse_array_type_definition( } }; - ( - DataTypeDeclaration::DataTypeDefinition { - data_type: DataType::ArrayType { - name, - bounds: range, - referenced_type: Box::new(reference), - is_variable_length, - }, - location, - scope: lexer.scope.clone(), + DataTypeDeclaration::DataTypeDefinition { + data_type: DataType::ArrayType { + name, + bounds: range, + referenced_type: Box::new(reference), + is_variable_length, }, - initializer, - ) + location, + scope: lexer.scope.clone(), + } }) } diff --git a/src/parser/expressions_parser.rs b/src/parser/expressions_parser.rs index 2131dfa37d..58ce5b7f11 100644 --- a/src/parser/expressions_parser.rs +++ b/src/parser/expressions_parser.rs @@ -15,7 +15,7 @@ use plc_source::source_location::SourceLocation; use regex::{Captures, Regex}; use std::{ops::Range, str::FromStr}; -use super::parse_hardware_access; +use super::{parse_data_type_definition, parse_hardware_access}; macro_rules! parse_left_associative_expression { ($lexer: expr, $action : expr, @@ -281,6 +281,12 @@ fn parse_atomic_leaf_expression(lexer: &mut ParseSession<'_>) -> Result parse_null_literal(lexer), KeywordSquareParensOpen => parse_array_literal(lexer), DirectAccess(access) => parse_direct_access(lexer, access), + PropertyDef => { + //Just consume the {def} and go further, if it's a variable we parse it next + lexer.advance(); + parse_atomic_leaf_expression(lexer) + } + KeywordVar => parse_inline_declaration(lexer), _ => { if lexer.closing_keywords.contains(&vec![KeywordParensClose]) && matches!(lexer.last_token, KeywordOutputAssignment | KeywordAssignment) @@ -296,6 +302,27 @@ fn parse_atomic_leaf_expression(lexer: &mut ParseSession<'_>) -> Result) -> Result { + //Consume the direct access + let location = lexer.location(); + //Inline variable declaration + lexer.advance(); + //Parse the name + let name = parse_identifier(lexer); + let datatype = if lexer.try_consume(&KeywordColon) { + //Parse datatype + let type_location = lexer.location(); + parse_data_type_definition(lexer, None).map(|it| { + AstFactory::create_type_declaration(it, lexer.next_id(), type_location.span(&lexer.location())) + }) + } else { + None + }; + let location = location.span(&lexer.last_location()); + + Ok(AstFactory::create_inline_declaration(name, datatype, lexer.next_id(), location)) +} + fn parse_identifier(lexer: &mut ParseSession<'_>) -> AstNode { AstFactory::create_identifier(&lexer.slice_and_advance(), &lexer.last_location(), lexer.next_id()) } diff --git a/src/parser/tests.rs b/src/parser/tests.rs index 8ee0e295b6..08a03df0ab 100644 --- a/src/parser/tests.rs +++ b/src/parser/tests.rs @@ -11,6 +11,7 @@ mod control_parser_tests; mod expressions_parser_tests; mod function_parser_tests; mod initializer_parser_tests; +mod inline_variable_tests; mod misc_parser_tests; mod parse_errors; mod parse_generics; diff --git a/src/parser/tests/inline_variable_tests.rs b/src/parser/tests/inline_variable_tests.rs new file mode 100644 index 0000000000..a0e104a17e --- /dev/null +++ b/src/parser/tests/inline_variable_tests.rs @@ -0,0 +1,73 @@ +use crate::test_utils::tests::parse; + +#[test] +fn inline_variable_declaration_on_new_line_with_def_pragma() { + let (result, ..) = parse( + r#" + PROGRAM main + {def} VAR x : DINT; + VAR y := 1; + END_PROGRAM + "#, + ); + insta::assert_debug_snapshot!(result); +} + +#[test] +fn variable_block_still_parsed_without_pragma() { + let (result, ..) = parse( + r#" + PROGRAM main + VAR x : DINT; END_VAR; + {def} VAR y := 1; + END_PROGRAM + "#, + ); + insta::assert_debug_snapshot!(result); +} + +#[test] +fn when_already_in_body_pragma_optional() { + let (result, ..) = parse( + r#" + PROGRAM main + VAR x : DINT; END_VAR + x := 10; + VAR y := 1; + y := 10; + END_PROGRAM + "#, + ); + insta::assert_debug_snapshot!(result); +} + +#[test] +fn variable_declared_in_for_loop() { + let (result, ..) = parse( + r#" + PROGRAM main + FOR VAR x := 0 TO 10 DO + END_FOR + END_PROGRAM + "#, + ); + insta::assert_debug_snapshot!(result); +} + +#[test] +fn variable_declared_in_inner_scope() { + let (result, ..) = parse( + r#" + PROGRAM main + FOR VAR x := 0 TO 10 DO + VAR y := 5; + END_FOR + + IF true THEN + VAR y := 10; + END_IF + END_PROGRAM + "#, + ); + insta::assert_debug_snapshot!(result); +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__inline_variable_tests__inline_variable_declaration_on_new_line_with_def_pragma.snap b/src/parser/tests/snapshots/rusty__parser__tests__inline_variable_tests__inline_variable_declaration_on_new_line_with_def_pragma.snap new file mode 100644 index 0000000000..08a804bf53 --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__inline_variable_tests__inline_variable_declaration_on_new_line_with_def_pragma.snap @@ -0,0 +1,79 @@ +--- +source: src/parser/tests/inline_variable_tests.rs +expression: result +--- +CompilationUnit { + global_vars: [], + units: [ + POU { + name: "main", + variable_blocks: [], + pou_type: Program, + return_type: None, + }, + ], + implementations: [ + Implementation { + name: "main", + type_name: "main", + linkage: Internal, + pou_type: Program, + statements: [ + InlineVariable { + name: Identifier { + name: "x", + }, + datatype: Some( + DataTypeDeclaration( + DataTypeReference { + referenced_type: "DINT", + }, + ), + ), + }, + Assignment { + left: InlineVariable { + name: Identifier { + name: "y", + }, + datatype: None, + }, + right: LiteralInteger { + value: 1, + }, + }, + ], + location: SourceLocation { + span: Range( + TextLocation { + line: 2, + column: 12, + offset: 39, + }..TextLocation { + line: 4, + column: 23, + offset: 106, + }, + ), + }, + name_location: SourceLocation { + span: Range( + TextLocation { + line: 1, + column: 20, + offset: 21, + }..TextLocation { + line: 1, + column: 24, + offset: 25, + }, + ), + }, + overriding: false, + generic: false, + access: None, + }, + ], + user_types: [], + file_name: "test.st", +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__inline_variable_tests__variable_block_still_parsed_without_pragma.snap b/src/parser/tests/snapshots/rusty__parser__tests__inline_variable_tests__variable_block_still_parsed_without_pragma.snap new file mode 100644 index 0000000000..4052dd2741 --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__inline_variable_tests__variable_block_still_parsed_without_pragma.snap @@ -0,0 +1,80 @@ +--- +source: src/parser/tests/inline_variable_tests.rs +expression: result +--- +CompilationUnit { + global_vars: [], + units: [ + POU { + name: "main", + variable_blocks: [ + VariableBlock { + variables: [ + Variable { + name: "x", + data_type: DataTypeReference { + referenced_type: "DINT", + }, + }, + ], + variable_block_type: Local, + }, + ], + pou_type: Program, + return_type: None, + }, + ], + implementations: [ + Implementation { + name: "main", + type_name: "main", + linkage: Internal, + pou_type: Program, + statements: [ + EmptyStatement, + Assignment { + left: InlineVariable { + name: Identifier { + name: "y", + }, + datatype: None, + }, + right: LiteralInteger { + value: 1, + }, + }, + ], + location: SourceLocation { + span: Range( + TextLocation { + line: 2, + column: 33, + offset: 60, + }..TextLocation { + line: 4, + column: 23, + offset: 115, + }, + ), + }, + name_location: SourceLocation { + span: Range( + TextLocation { + line: 1, + column: 20, + offset: 21, + }..TextLocation { + line: 1, + column: 24, + offset: 25, + }, + ), + }, + overriding: false, + generic: false, + access: None, + }, + ], + user_types: [], + file_name: "test.st", +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__inline_variable_tests__variable_declared_in_for_loop.snap b/src/parser/tests/snapshots/rusty__parser__tests__inline_variable_tests__variable_declared_in_for_loop.snap new file mode 100644 index 0000000000..1e813b7b86 --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__inline_variable_tests__variable_declared_in_for_loop.snap @@ -0,0 +1,72 @@ +--- +source: src/parser/tests/inline_variable_tests.rs +expression: result +--- +CompilationUnit { + global_vars: [], + units: [ + POU { + name: "main", + variable_blocks: [], + pou_type: Program, + return_type: None, + }, + ], + implementations: [ + Implementation { + name: "main", + type_name: "main", + linkage: Internal, + pou_type: Program, + statements: [ + ForLoopStatement { + counter: InlineVariable { + name: Identifier { + name: "x", + }, + datatype: None, + }, + start: LiteralInteger { + value: 0, + }, + end: LiteralInteger { + value: 10, + }, + by_step: None, + body: [], + }, + ], + location: SourceLocation { + span: Range( + TextLocation { + line: 2, + column: 12, + offset: 39, + }..TextLocation { + line: 4, + column: 23, + offset: 106, + }, + ), + }, + name_location: SourceLocation { + span: Range( + TextLocation { + line: 1, + column: 20, + offset: 21, + }..TextLocation { + line: 1, + column: 24, + offset: 25, + }, + ), + }, + overriding: false, + generic: false, + access: None, + }, + ], + user_types: [], + file_name: "test.st", +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__inline_variable_tests__variable_declared_in_inner_scope.snap b/src/parser/tests/snapshots/rusty__parser__tests__inline_variable_tests__variable_declared_in_inner_scope.snap new file mode 100644 index 0000000000..017d0ad171 --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__inline_variable_tests__variable_declared_in_inner_scope.snap @@ -0,0 +1,107 @@ +--- +source: src/parser/tests/inline_variable_tests.rs +expression: result +--- +CompilationUnit { + global_vars: [], + units: [ + POU { + name: "main", + variable_blocks: [], + pou_type: Program, + return_type: None, + }, + ], + implementations: [ + Implementation { + name: "main", + type_name: "main", + linkage: Internal, + pou_type: Program, + statements: [ + ForLoopStatement { + counter: InlineVariable { + name: Identifier { + name: "x", + }, + datatype: None, + }, + start: LiteralInteger { + value: 0, + }, + end: LiteralInteger { + value: 10, + }, + by_step: None, + body: [ + Assignment { + left: InlineVariable { + name: Identifier { + name: "y", + }, + datatype: None, + }, + right: LiteralInteger { + value: 5, + }, + }, + ], + }, + IfStatement { + blocks: [ + ConditionalBlock { + condition: LiteralBool { + value: true, + }, + body: [ + Assignment { + left: InlineVariable { + name: Identifier { + name: "y", + }, + datatype: None, + }, + right: LiteralInteger { + value: 10, + }, + }, + ], + }, + ], + else_block: [], + }, + ], + location: SourceLocation { + span: Range( + TextLocation { + line: 2, + column: 12, + offset: 39, + }..TextLocation { + line: 9, + column: 23, + offset: 208, + }, + ), + }, + name_location: SourceLocation { + span: Range( + TextLocation { + line: 1, + column: 20, + offset: 21, + }..TextLocation { + line: 1, + column: 24, + offset: 25, + }, + ), + }, + overriding: false, + generic: false, + access: None, + }, + ], + user_types: [], + file_name: "test.st", +} diff --git a/src/parser/tests/snapshots/rusty__parser__tests__inline_variable_tests__when_already_in_body_pragma_optional.snap b/src/parser/tests/snapshots/rusty__parser__tests__inline_variable_tests__when_already_in_body_pragma_optional.snap new file mode 100644 index 0000000000..be29738c2e --- /dev/null +++ b/src/parser/tests/snapshots/rusty__parser__tests__inline_variable_tests__when_already_in_body_pragma_optional.snap @@ -0,0 +1,105 @@ +--- +source: src/parser/tests/inline_variable_tests.rs +expression: result +--- +CompilationUnit { + global_vars: [], + units: [ + POU { + name: "main", + variable_blocks: [ + VariableBlock { + variables: [ + Variable { + name: "x", + data_type: DataTypeReference { + referenced_type: "DINT", + }, + }, + ], + variable_block_type: Local, + }, + ], + pou_type: Program, + return_type: None, + }, + ], + implementations: [ + Implementation { + name: "main", + type_name: "main", + linkage: Internal, + pou_type: Program, + statements: [ + Assignment { + left: ReferenceExpr { + kind: Member( + Identifier { + name: "x", + }, + ), + base: None, + }, + right: LiteralInteger { + value: 10, + }, + }, + Assignment { + left: InlineVariable { + name: Identifier { + name: "y", + }, + datatype: None, + }, + right: LiteralInteger { + value: 1, + }, + }, + Assignment { + left: ReferenceExpr { + kind: Member( + Identifier { + name: "y", + }, + ), + base: None, + }, + right: LiteralInteger { + value: 10, + }, + }, + ], + location: SourceLocation { + span: Range( + TextLocation { + line: 3, + column: 12, + offset: 73, + }..TextLocation { + line: 6, + column: 23, + offset: 150, + }, + ), + }, + name_location: SourceLocation { + span: Range( + TextLocation { + line: 1, + column: 20, + offset: 21, + }..TextLocation { + line: 1, + column: 24, + offset: 25, + }, + ), + }, + overriding: false, + generic: false, + access: None, + }, + ], + user_types: [], + file_name: "test.st", +} diff --git a/src/resolver.rs b/src/resolver.rs index 5e6e153d2b..1f61c298ca 100644 --- a/src/resolver.rs +++ b/src/resolver.rs @@ -8,6 +8,7 @@ use std::{ collections::{HashMap, HashSet}, hash::Hash, + rc::Rc, }; use indexmap::{IndexMap, IndexSet}; @@ -30,7 +31,9 @@ pub mod generics; use crate::{ builtins::{self, BuiltIn}, - index::{ArgumentType, Index, PouIndexEntry, VariableIndexEntry, VariableType}, + index::{ + scoped_index::ScopedIndex, ArgumentType, Index, PouIndexEntry, VariableIndexEntry, VariableType, + }, typesystem::{ self, get_bigger_type, DataTypeInformation, InternalType, StringEncoding, StructSource, BOOL_TYPE, BYTE_TYPE, DATE_AND_TIME_TYPE, DATE_TYPE, DINT_TYPE, DWORD_TYPE, LINT_TYPE, LREAL_TYPE, LWORD_TYPE, @@ -45,12 +48,14 @@ mod tests; /// use like `visit_all_statements!(self, ctx, stmt1, stmt2, stmt3, ...)` macro_rules! visit_all_statements { ($self:expr, $ctx:expr, $last:expr ) => { - $self.visit_statement($ctx, $last); + $self.visit_statement($ctx, $last) }; ($self:expr, $ctx:expr, $head:expr, $($tail:expr), +) => { - $self.visit_statement($ctx, $head); - visit_all_statements!($self, $ctx, $($tail),+) + { + let ctx = $self.visit_statement($ctx, $head); + visit_all_statements!($self, ctx, $($tail),+) + } }; } @@ -85,6 +90,9 @@ pub struct VisitorContext<'s> { // what's the current strategy for resolving resolve_strategy: Vec, + + //Scoped index identifies local variables, should be merged when out of scope + scoped_index: Option>, } impl<'s> VisitorContext<'s> { @@ -93,6 +101,8 @@ impl<'s> VisitorContext<'s> { let mut ctx = self.clone(); ctx.qualifier = Some(qualifier); ctx.constant = false; + //Do not share the scope, the qualifier has a different scope here + ctx.scoped_index = None; ctx } @@ -109,6 +119,8 @@ impl<'s> VisitorContext<'s> { let mut ctx = self.clone(); ctx.lhs = Some(lhs_pou); ctx.constant = false; + //On a left hand side, there is no need for new scopes + ctx.scoped_index = None; ctx } @@ -116,6 +128,8 @@ impl<'s> VisitorContext<'s> { fn with_const(&self, const_state: bool) -> VisitorContext<'s> { let mut ctx = self.clone(); ctx.constant = const_state; + //Constants cannot be temp variables, no need for new scopes + ctx.scoped_index = None, ctx } @@ -123,6 +137,8 @@ impl<'s> VisitorContext<'s> { fn enter_body(&self) -> Self { let mut ctx = self.clone(); ctx.in_body = true; + //No new scopes needed at this stage, this is just a marker to indicate that we are in an impl + ctx.scoped_index = None; ctx } @@ -143,6 +159,24 @@ impl<'s> VisitorContext<'s> { fn is_in_a_body(&self) -> bool { self.in_body } + + fn open_scope(&self, location: SourceLocation, suffix_provider: IdProvider) -> VisitorContext<'s> { + VisitorContext { + pou: self.pou, + qualifier: self.qualifier.clone(), + lhs: self.lhs, + constant: self.constant, + in_body: true, + id_provider: self.id_provider.clone(), + resolve_strategy: self.resolve_strategy.clone(), + scoped_index: Some(Rc::new(ScopedIndex::new( + self.scoped_index.clone(), + location, + self.pou.unwrap_or_default(), + suffix_provider, + ))), + } + } } pub struct TypeAnnotator<'i> { @@ -153,6 +187,7 @@ pub struct TypeAnnotator<'i> { /// A map containing every jump encountered in a file, and the label of where this jump should /// point. This is later used to annotate all jumps after the initial visit is done. jumps_to_annotate: HashMap>>, + suffix_provider: IdProvider, } impl TypeAnnotator<'_> { @@ -219,7 +254,7 @@ impl TypeAnnotator<'_> { ), _ => self.create_typed_compare_call_statement(&mut ctx, operator, left, right, statement), }; - self.visit_statement(&ctx, &call_statement); + self.visit_statement(ctx, &call_statement); self.update_expected_types(self.index.get_type_or_panic(typesystem::BOOL_TYPE), &call_statement); self.annotate(statement, StatementAnnotation::ReplacementAst { statement: call_statement }); self.update_expected_types(self.index.get_type_or_panic(typesystem::BOOL_TYPE), statement); @@ -720,6 +755,7 @@ impl<'i> TypeAnnotator<'i> { dependencies: IndexSet::new(), string_literals: StringLiterals { utf08: HashSet::new(), utf16: HashSet::new() }, jumps_to_annotate: HashMap::new(), + suffix_provider: Default::default(), } } @@ -740,6 +776,7 @@ impl<'i> TypeAnnotator<'i> { id_provider, resolve_strategy: ResolvingScope::default_scopes(), in_control: false, + scoped_index: None, }; for global_variable in unit.global_vars.iter().flat_map(|it| it.variables.iter()) { @@ -758,9 +795,9 @@ impl<'i> TypeAnnotator<'i> { let body_ctx = ctx.enter_body(); for i in &unit.implementations { visitor.dependencies.extend(visitor.get_datatype_dependencies(&i.name, IndexSet::new())); - i.statements.iter().for_each(|s| visitor.visit_statement(&body_ctx.with_pou(i.name.as_str()), s)); + visitor.visit_body(&i.statements, &body_ctx.with_pou(i.name.as_str())); + //TODO: Merge CTX } - // enum initializers may have been introduced by the visitor (indexer) // so we should try to resolve and type-annotate them here as well for enum_element in @@ -776,9 +813,9 @@ impl<'i> TypeAnnotator<'i> { } if let Some(scope) = scope { - visitor.visit_statement(&ctx.with_pou(scope), statement); + visitor.visit_statement(ctx.with_pou(scope), statement); } else { - visitor.visit_statement(ctx, statement); + visitor.visit_statement(ctx.clone(), statement); } } } @@ -807,7 +844,7 @@ impl<'i> TypeAnnotator<'i> { if let Some((initializer, name)) = user_data_type.initializer.as_ref().zip(user_data_type.data_type.get_name()) { - self.visit_statement(ctx, initializer); + self.visit_statement(ctx.clone(), initializer); //update the type-hint for the initializer if let Some(right_type) = self.index.find_effective_type_by_name(name) { @@ -833,9 +870,9 @@ impl<'i> TypeAnnotator<'i> { /// updates the expected types of statement on the right side of an assignment /// e.g. x : ARRAY [0..1] OF BYTE := [2,3]; /// note that the left side needs to be annotated before this call - fn update_right_hand_side_expected_type( - &mut self, - ctx: &VisitorContext, + fn update_right_hand_side_expected_type<'s>( + &'s mut self, + ctx: &VisitorContext<'s>, annotated_left_side: &AstNode, right_side: &AstNode, ) { @@ -1006,7 +1043,7 @@ impl<'i> TypeAnnotator<'i> { // it will be replaced by the appropriate literal later self.annotate(initializer, StatementAnnotation::value(expected_type.get_name())); } else { - self.visit_statement(&ctx, initializer); + self.visit_statement(ctx.clone(), initializer); } self.type_hint_for_variable_initializer(initializer, expected_type, &ctx); @@ -1071,7 +1108,7 @@ impl<'i> TypeAnnotator<'i> { for expression in expressions { self.annotation_map.annotate_type_hint(expression, hint.clone()); - self.visit_statement(&ctx, expression); + self.visit_statement(ctx.clone(), expression); self.type_hint_for_array_of_structs(expected_type, expression, &ctx); } @@ -1183,13 +1220,13 @@ impl<'i> TypeAnnotator<'i> { } DataType::SubRangeType { referenced_type, bounds: Some(bounds), .. } => { if let Some(expected_type) = self.index.find_effective_type_by_name(referenced_type) { - self.visit_statement(ctx, bounds); + self.visit_statement(ctx.clone(), bounds); self.update_expected_types(expected_type, bounds); } } DataType::EnumType { elements, name, .. } => { let ctx = name.as_ref().map(|n| ctx.with_lhs(n)).unwrap_or(ctx.clone()); - self.visit_statement(&ctx, elements); + self.visit_statement(ctx.clone(), elements); } DataType::PointerType { referenced_type, .. } => { self.visit_data_type_declaration(ctx, referenced_type.as_ref()) @@ -1198,8 +1235,12 @@ impl<'i> TypeAnnotator<'i> { } } - pub fn visit_statement(&mut self, ctx: &VisitorContext, statement: &AstNode) { - self.visit_statement_control(ctx, statement); + pub fn visit_statement<'ctx>( + &mut self, + ctx: VisitorContext<'ctx>, + statement: &AstNode, + ) -> VisitorContext<'ctx> { + self.visit_statement_control(ctx, statement) } /// This function is only really useful for [`AstStatement::ParenExpression`] where we would @@ -1216,12 +1257,55 @@ impl<'i> TypeAnnotator<'i> { } /// annotate a control statement - fn visit_statement_control(&mut self, ctx: &VisitorContext, statement: &AstNode) { + fn visit_statement_control<'ctx>( + &mut self, + ctx: VisitorContext<'ctx>, + statement: &AstNode, + ) -> VisitorContext<'ctx> { match statement.get_stmt() { AstStatement::ParenExpression(expr) => { self.visit_statement(ctx, expr); self.inherit_annotations(statement, expr); } + //TODO: check where to open scopes + /* + AstStatement::ControlStatement(AstControlStatement::If(stmt), ..) => { + stmt.blocks.iter().for_each(|b| { + let ctx = self.visit_statement( + ctx.open_scope(b.condition.get_location(), self.suffix_provider.clone()), + b.condition.as_ref(), + ); + self.visit_body(&b.body, &ctx); + }); + self.visit_body(&stmt.else_block, &ctx); + ctx + } + AstStatement::ControlStatement(AstControlStatement::ForLoop(stmt)) => { + let ctx = ctx.open_scope(statement.get_location(), self.suffix_provider.clone()); + let ctx = self.visit_statement(ctx, &stmt.start); + let ctx = self.visit_statement(ctx, &stmt.end); + let ctx = + if let Some(by_step) = &stmt.by_step { self.visit_statement(ctx, by_step) } else { ctx }; + let counter_scope = self.visit_statement(ctx, &stmt.counter); + //TODO: counter could be a new variable, annotate it with the start type + //Hint annotate start, end and step with the counter's real type + if let Some(type_name) = self + .annotation_map + .get_type(&stmt.counter, self.index) + .map(typesystem::DataType::get_name) + { + let annotation = StatementAnnotation::value(type_name); + self.annotation_map.annotate_type_hint(&stmt.start, annotation.clone()); + self.annotation_map.annotate_type_hint(&stmt.end, annotation.clone()); + if let Some(by_step) = &stmt.by_step { + self.annotation_map.annotate_type_hint(by_step, annotation); + } + } + //TODO: fix for context + self.visit_body(&stmt.body, &counter_scope); + counter_scope //.drop_statement + + */ AstStatement::ControlStatement(control) => { match control { AstControlStatement::If(stmt) => { @@ -1276,20 +1360,26 @@ impl<'i> TypeAnnotator<'i> { } /// annotate an expression statement - fn visit_statement_expression(&mut self, ctx: &VisitorContext, statement: &AstNode) { + fn visit_statement_expression<'ctx>( + &mut self, + ctx: VisitorContext<'ctx>, + statement: &AstNode, + ) -> VisitorContext<'ctx> { match statement.get_stmt() { AstStatement::DirectAccess(data, ..) => { let ctx = VisitorContext { qualifier: None, ..ctx.clone() }; - visit_all_statements!(self, &ctx, &data.index); + let ctx = visit_all_statements!(self, ctx, &data.index); let access_type = get_direct_access_type(&data.access); self.annotate(statement, StatementAnnotation::value(access_type)); + ctx } AstStatement::HardwareAccess(data, ..) => { let access_type = get_direct_access_type(&data.access); self.annotate(statement, StatementAnnotation::value(access_type)); + ctx } AstStatement::BinaryExpression(data, ..) => { - visit_all_statements!(self, ctx, &data.left, &data.right); + let ctx = visit_all_statements!(self, ctx, &data.left, &data.right); let statement_type = { let left_type = self .annotation_map @@ -1371,7 +1461,7 @@ impl<'i> TypeAnnotator<'i> { Some(target_type.to_string()) } else if data.operator.is_comparison_operator() { //Annotate as the function call to XXX_EQUALS/LESS/GREATER.. - self.visit_compare_statement(ctx, statement); + self.visit_compare_statement(&ctx, statement); None } else { None @@ -1388,10 +1478,11 @@ impl<'i> TypeAnnotator<'i> { self.annotation_map .annotate_type_hint(statement, StatementAnnotation::value(statement_type)) } - } + }; + ctx } AstStatement::UnaryExpression(data, ..) => { - self.visit_statement(ctx, &data.value); + let ctx = self.visit_statement(ctx, &data.value); let statement_type = if data.operator == Operator::Minus { let inner_type = @@ -1412,115 +1503,133 @@ impl<'i> TypeAnnotator<'i> { if let Some(statement_type) = statement_type { self.annotate(statement, StatementAnnotation::value(statement_type)); - } + }; + ctx } AstStatement::ExpressionList(expressions, ..) => { - expressions.iter().for_each(|e| self.visit_statement(ctx, e)) + expressions.iter().fold(ctx, |ctx, e| self.visit_statement(ctx, e)) } AstStatement::RangeStatement(data, ..) => { - visit_all_statements!(self, ctx, &data.start, &data.end); + visit_all_statements!(self, ctx, &data.start, &data.end) } AstStatement::Assignment(data, ..) => { - self.visit_statement(&ctx.enter_control(), &data.right); - if let Some(lhs) = ctx.lhs { + //Right statement is visited without the left statement's context + let ctx = self.visit_statement(&ctx.enter_control(), &data.right); + let ctx = if let Some(lhs) = &ctx.lhs { //special context for left hand side - self.visit_statement(&ctx.with_pou(lhs).with_lhs(lhs), &data.left); + self.visit_statement(ctx.with_pou(lhs).with_lhs(lhs), &data.left); + //Do not use the new context further, it is only relevant for left hand side + ctx } else { - self.visit_statement(ctx, &data.left); - } + //Continue using the new context as it could contain new variables + self.visit_statement(ctx, &data.left) + }; + //TODO: Left might be a new variable, infer its type from right // give a type hint that we want the right side to be stored in the left's type - self.update_right_hand_side_expected_type(ctx, &data.left, &data.right); + self.update_right_hand_side_expected_type(&ctx, &data.left, &data.right); + ctx } AstStatement::OutputAssignment(data, ..) => { - visit_all_statements!(self, ctx, &data.left, &data.right); - if let Some(lhs) = ctx.lhs { + let ctx = visit_all_statements!(self, ctx, &data.right); + let ctx = if let Some(lhs) = &ctx.lhs { //special context for left hand side - self.visit_statement(&ctx.with_pou(lhs), &data.left); + self.visit_statement(ctx.with_pou(lhs), &data.left); + //Do not use the new context further, it is only relevant for left hand side + ctx } else { - self.visit_statement(ctx, &data.left); - } - self.update_right_hand_side_expected_type(ctx, &data.left, &data.right); + //Continue using the new context as it could contain new variables + self.visit_statement(ctx, &data.left) + }; + self.update_right_hand_side_expected_type(&ctx, &data.left, &data.right); + ctx } AstStatement::CallStatement(..) => { - self.visit_call_statement(statement, ctx); + self.visit_call_statement(statement, &ctx); + ctx } AstStatement::CastStatement(CastStatement { target, type_name }, ..) => { //see if this type really exists let data_type = self.index.find_effective_type_info(type_name); - let statement_to_annotation = if let Some(DataTypeInformation::Enum { name, .. }) = data_type - { - //enum cast - self.visit_statement(&ctx.with_qualifier(name.to_string()), target); - //use the type of the target - let type_name = self.annotation_map.get_type_or_void(target, self.index).get_name(); - vec![(statement, type_name.to_string())] - } else if let Some(t) = data_type { - // special handling for unlucky casted-strings where caste-type does not match the literal encoding - // ´STRING#"abc"´ or ´WSTRING#'abc'´ - match (t, target.as_ref().get_stmt()) { - ( - DataTypeInformation::String { encoding: StringEncoding::Utf8, .. }, - AstStatement::Literal(AstLiteral::String(StringValue { - value, - is_wide: is_wide @ true, - })), - ) - | ( - DataTypeInformation::String { encoding: StringEncoding::Utf16, .. }, - AstStatement::Literal(AstLiteral::String(StringValue { - value, - is_wide: is_wide @ false, - })), - ) => { - // visit the target-statement as if the programmer used the correct quotes to prevent - // a utf16 literal-global-variable that needs to be casted back to utf8 or vice versa - self.visit_statement( - ctx, - &AstNode::new_literal( - AstLiteral::new_string(value.clone(), !is_wide), - target.get_id(), - target.get_location(), - ), - ); - } - _ => {} - } - vec![(statement, t.get_name().to_string()), (target, t.get_name().to_string())] - } else { - //unknown type? what should we do here? - self.visit_statement(ctx, target); - vec![] - }; + let (ctx, statement_to_annotation) = + if let Some(DataTypeInformation::Enum { name, .. }) = data_type { + //enum cast + let ctx = self.visit_statement(ctx.with_qualifier(name.to_string()), target); + //use the type of the target + let type_name = self.annotation_map.get_type_or_void(target, self.index).get_name(); + (ctx, vec![(statement, type_name.to_string())]) + } else if let Some(t) = data_type { + // special handling for unlucky casted-strings where caste-type does not match the literal encoding + // ´STRING#"abc"´ or ´WSTRING#'abc'´ + let ctx = match (t, target.as_ref().get_stmt()) { + ( + DataTypeInformation::String { encoding: StringEncoding::Utf8, .. }, + AstStatement::Literal(AstLiteral::String(StringValue { + value, + is_wide: is_wide @ true, + })), + ) + | ( + DataTypeInformation::String { encoding: StringEncoding::Utf16, .. }, + AstStatement::Literal(AstLiteral::String(StringValue { + value, + is_wide: is_wide @ false, + })), + ) => { + // visit the target-statement as if the programmer used the correct quotes to prevent + // a utf16 literal-global-variable that needs to be casted back to utf8 or vice versa + self.visit_statement( + ctx, + &AstNode::new_literal( + AstLiteral::new_string(value.clone(), !is_wide), + target.get_id(), + target.get_location(), + ), + ) + } + _ => ctx, + }; + (ctx, vec![(statement, t.get_name().to_string()), (target, t.get_name().to_string())]) + } else { + //unknown type? what should we do here? + (self.visit_statement(ctx, target), vec![]) + }; for (stmt, annotation) in statement_to_annotation { self.annotate(stmt, StatementAnnotation::value(annotation)); } + ctx } AstStatement::ReferenceExpr(data, ..) => { - self.visit_reference_expr(&data.access, data.base.as_deref(), statement, ctx); + self.visit_reference_expr(&data.access, data.base.as_deref(), statement, &ctx); + ctx } AstStatement::ReturnStatement(ReturnStatement { condition }) => { if let Some(condition) = condition { self.visit_statement(ctx, condition) + } else { + ctx } } AstStatement::LabelStatement(..) => { if let Some(pou) = ctx.pou { self.annotation_map.new_index.add_label(pou, statement.into()); } + ctx } AstStatement::JumpStatement(JumpStatement { condition, target }) => { - self.visit_statement(ctx, condition); + let ctx = self.visit_statement(ctx, condition); if let Some((name, pou)) = target.get_flat_reference_name().zip(ctx.pou) { let pou = self.jumps_to_annotate.entry(pou.to_string()).or_default(); let jumps = pou.entry(name.to_string()).or_default(); jumps.push(statement.get_id()); } + ctx } _ => { - self.visit_statement_literals(ctx, statement); + self.visit_statement_literals(&ctx, statement); + ctx } } } @@ -1534,7 +1643,7 @@ impl<'i> TypeAnnotator<'i> { ) { // first resolve base if let Some(base) = base { - self.visit_statement(ctx, base); + self.visit_statement(ctx.clone(), base); }; match ( @@ -1586,7 +1695,7 @@ impl<'i> TypeAnnotator<'i> { } } (ReferenceAccess::Index(index), Some(base)) => { - self.visit_statement(ctx, index); + self.visit_statement(ctx.clone(), index); if let Some(inner_type) = self .index .find_effective_type_info(base.as_str()) @@ -1664,7 +1773,7 @@ impl<'i> TypeAnnotator<'i> { AstStatement::DirectAccess(data, ..) if qualifier.is_some() => { // x.%X1 - bit access - self.visit_statement(ctx, data.index.as_ref()); + self.visit_statement(ctx.clone(), data.index.as_ref()); Some(StatementAnnotation::value(get_direct_access_type(&data.access))) } _ => None, @@ -1731,13 +1840,13 @@ impl<'i> TypeAnnotator<'i> { unreachable!("Always a call statement"); }; // #604 needed for recursive function calls - self.visit_statement(&ctx.with_resolving_strategy(ResolvingScope::call_operator_scopes()), operator); + self.visit_statement(ctx.with_resolving_strategy(ResolvingScope::call_operator_scopes()), operator); let operator_qualifier = self.get_call_name(operator); //Use the context without the is_call =true //TODO why do we start a lhs context here??? let ctx = ctx.with_lhs(operator_qualifier.as_str()); if let Some(parameters) = parameters_stmt { - self.visit_statement(&ctx, parameters); + self.visit_statement(ctx.clone(), parameters); }; if let Some(annotation) = builtins::get_builtin(&operator_qualifier).and_then(BuiltIn::get_annotation) @@ -1851,14 +1960,14 @@ impl<'i> TypeAnnotator<'i> { self.annotate(statement, StatementAnnotation::value(get_real_type_name_for(value))); } AstLiteral::Array(Array { elements: Some(elements), .. }) => { - self.visit_statement(ctx, elements.as_ref()); + self.visit_statement(ctx.clone(), elements.as_ref()); //TODO as of yet we have no way to derive a name that reflects a fixed size array } _ => {} // ignore literalNull, arrays (they are covered earlier) } } AstStatement::MultipliedStatement(data, ..) => { - self.visit_statement(ctx, &data.element) + self.visit_statement(ctx.clone(), &data.element); //TODO as of yet we have no way to derive a name that reflects a fixed size array } _ => {} @@ -1878,6 +1987,17 @@ impl<'i> TypeAnnotator<'i> { self.update_expected_types(&bigger_type, statement); } } + + fn visit_body(&mut self, body: &[AstNode], ctx: &VisitorContext<'_>) { + //Open body scope + if let Some(location) = body.first().map(AstNode::get_location) { + let _scope = + body.iter().fold(ctx.open_scope(location, self.suffix_provider.clone()), |ctx, s| { + self.visit_statement(ctx, s) + }); + //close body scope + } + } } fn get_direct_access_type(access: &DirectAccessType) -> &'static str { diff --git a/src/resolver/generics.rs b/src/resolver/generics.rs index 2a90620605..2027f3e996 100644 --- a/src/resolver/generics.rs +++ b/src/resolver/generics.rs @@ -114,7 +114,7 @@ impl<'i> TypeAnnotator<'i> { } // Adjust annotations on the inner statement if let Some(s) = parameters.as_ref() { - self.visit_statement(&ctx, s); + self.visit_statement(ctx, s); self.update_generic_function_parameters(s, implementation_name, generic_map); } }