diff --git a/crates/solidity-v2/inputs/language/src/BREAKING_CHANGES.md b/crates/solidity-v2/inputs/language/src/BREAKING_CHANGES.md index 956bb90f83..54ddefdc1a 100644 --- a/crates/solidity-v2/inputs/language/src/BREAKING_CHANGES.md +++ b/crates/solidity-v2/inputs/language/src/BREAKING_CHANGES.md @@ -27,3 +27,54 @@ We should consider adding validation for these at a later stage if needed: - They were only enabled after `0.7.0`. - `HexLiteral` and `YulHexLiteral` and `DecimalLiteral` and `YulDecimalLiteral`: - It was illegal for them to be followed by `IdentifierStart`. Now we will produce two separate tokens rather than rejecting it. + +## Grammar + +The following changes modify the language definition to support the new parser and resolve grammar ambiguities. +In some cases we also try to simplify the model. + +### AddressKeyword + +- Made the `address` keyword reserved in all versions, handling the few cases where it can be used as an identifier separately. +- `IdentifierPathElement` handles the cases where `address` can be used as an `Identifier`, either in an `IdentifierPath` or a `MemberAccessExpression`. + +### IdentifierPathElement + +New enum added to allow the reserved `address` keyword in identifier paths and member access expressions +(from Solidity 0.6.0): + +- Variants: `Identifier` | `AddressKeyword` (enabled from 0.6.0) +- Used in `MemberAccessExpression` and in `IdentifierPath` + +### IdentifierPath + +Changed from a `Separated` list of `Identifier`, to a list of `IdentifierPathElement`, to capture the reserved +`AddressKeyword` as part of the path. + +- **Before**: `Separated(name = IdentifierPath, reference = Identifier, separator = Period)` +- **After**: `Separated(name = IdentifierPath, reference = IdentifierPathElement, separator = Period)` + +### TupleDeconstructionStatement + +Major restructuring to resolve ambiguities with tuple expressions and assignment expressions: + +- **Before**: Single struct with optional `var_keyword`, `open_paren`, `elements: TupleDeconstructionElements`, `close_paren`, `equal`, `expression`, `semicolon`. +- **After**: Split into typed and untyped (var) variants: + - `TupleDeconstructionStatement`: Contains `target: TupleDeconstructionTarget`, `equal`, `expression`, `semicolon` + - `TupleDeconstructionTarget`: Enum with `VarTupleDeconstructionTarget` (till 0.5.0) | `TypedTupleDeconstructionTarget` + - `VarTupleDeconstructionTarget`: For `var (a, b) = ...` syntax (till 0.5.0) + - `TypedTupleDeconstructionTarget`: For `(uint a, uint b) = ...` syntax + +This makes certain cases that were allowed before disallowed in V2, in particular having untyped declarations (like `(a, bool b) = ...`) +or having typed together with `var` (like `var (a, bool b) = ...`). +The cases where using empty tuples are still ambiguous, `(,,,) = ...` can still be a `TupleDeconstructionStatement` or a +an `AssignmentExpression` with a `TupleExpression` on the lhs. + +Removed types: `TupleDeconstructionElements`, `TupleDeconstructionElement`, `TupleMember`, `TypedTupleMember`, `UntypedTupleMember` + +Added types: `TupleDeconstructionTarget`, `VarTupleDeconstructionTarget`, `UntypedTupleDeconstructionElements`, `UntypedTupleDeconstructionElement`, `TypedTupleDeconstructionTarget`, `TypedTupleDeconstructionElements`, `TypedTupleDeconstructionElement`, `TypedTupleDeconstructionMember` + +### NamedArgumentsDeclaration + +- Changed `arguments` field from `Optional(NamedArgumentGroup)` to `Required(NamedArgumentGroup)`. +- This avoids ambiguity with empty argument lists `()` which could be either positional or named. diff --git a/crates/solidity-v2/inputs/language/src/definition.rs b/crates/solidity-v2/inputs/language/src/definition.rs index f43b9886e7..60608b6dbd 100644 --- a/crates/solidity-v2/inputs/language/src/definition.rs +++ b/crates/solidity-v2/inputs/language/src/definition.rs @@ -492,10 +492,13 @@ language_v2_macros::compile!(Language( definitions = [KeywordDefinition(value = Atom("abstract"))] ), Keyword( + // `address` is a reserved keyword, but it can still be used as an identifier in some contexts, + // in particular as a member access (e.g., `myPayload.address`) or as an identifier + // path + // See `IdentifierPathElement` for details name = AddressKeyword, identifier = Identifier, - definitions = - [KeywordDefinition(reserved = Never, value = Atom("address"))] + definitions = [KeywordDefinition(value = Atom("address"))] ), Keyword( name = AfterKeyword, @@ -2925,49 +2928,75 @@ language_v2_macros::compile!(Language( items = [ Struct( name = TupleDeconstructionStatement, + error_recovery = FieldsErrorRecovery(terminator = semicolon), + fields = ( + target = Required(TupleDeconstructionTarget), + equal = Required(Equal), + expression = Required(Expression), + semicolon = Required(Semicolon) + ) + ), + Enum( + name = TupleDeconstructionTarget, + variants = [ + EnumVariant( + reference = VarTupleDeconstructionTarget, + enabled = Till("0.5.0") + ), + EnumVariant(reference = TypedTupleDeconstructionTarget) + ] + ), + Struct( + name = VarTupleDeconstructionTarget, + enabled = Till("0.5.0"), error_recovery = FieldsErrorRecovery( - terminator = semicolon, delimiters = FieldDelimiters(open = open_paren, close = close_paren) ), fields = ( - var_keyword = - Optional(reference = VarKeyword, enabled = Till("0.5.0")), + var_keyword = Required(VarKeyword), open_paren = Required(OpenParen), - elements = Required(TupleDeconstructionElements), - close_paren = Required(CloseParen), - equal = Required(Equal), - expression = Required(Expression), - semicolon = Required(Semicolon) + elements = Required(UntypedTupleDeconstructionElements), + close_paren = Required(CloseParen) ) ), Separated( - name = TupleDeconstructionElements, - reference = TupleDeconstructionElement, - separator = Comma + name = UntypedTupleDeconstructionElements, + reference = UntypedTupleDeconstructionElement, + separator = Comma, + enabled = Till("0.5.0") ), Struct( - name = TupleDeconstructionElement, - fields = (member = Optional(reference = TupleMember)) - ), - Enum( - name = TupleMember, - variants = [ - EnumVariant(reference = TypedTupleMember), - EnumVariant(reference = UntypedTupleMember) - ] + name = UntypedTupleDeconstructionElement, + enabled = Till("0.5.0"), + fields = (name = Optional(reference = Identifier)) ), Struct( - name = TypedTupleMember, + name = TypedTupleDeconstructionTarget, + error_recovery = FieldsErrorRecovery( + delimiters = + FieldDelimiters(open = open_paren, close = close_paren) + ), fields = ( - type_name = Required(TypeName), - storage_location = Optional(reference = StorageLocation), - name = Required(Identifier) + open_paren = Required(OpenParen), + elements = Required(TypedTupleDeconstructionElements), + close_paren = Required(CloseParen) ) ), + Separated( + name = TypedTupleDeconstructionElements, + reference = TypedTupleDeconstructionElement, + separator = Comma + ), Struct( - name = UntypedTupleMember, + name = TypedTupleDeconstructionElement, + fields = + (member = Optional(reference = TypedTupleDeconstructionMember)) + ), + Struct( + name = TypedTupleDeconstructionMember, fields = ( + type_name = Required(TypeName), storage_location = Optional(reference = StorageLocation), name = Required(Identifier) ) @@ -3498,7 +3527,7 @@ language_v2_macros::compile!(Language( model = Postfix, fields = ( period = Required(Period), - member = Required(Identifier) + member = Required(IdentifierPathElement) ) )] ), @@ -3590,7 +3619,7 @@ language_v2_macros::compile!(Language( ), fields = ( open_paren = Required(OpenParen), - arguments = Optional(reference = NamedArgumentGroup), + arguments = Required(NamedArgumentGroup), close_paren = Required(CloseParen) ) ), @@ -3985,11 +4014,22 @@ language_v2_macros::compile!(Language( title = "Identifiers", lexical_context = Solidity, items = [ + // Since an identifier path can include the reserved keyword `address` as parth of the path + // we use `IdentifierPathElement` to represent each element, instead of `Identifier`. Separated( name = IdentifierPath, - reference = Identifier, + reference = IdentifierPathElement, separator = Period ), + Enum( + // An element of an identifier path can be either an identifier or the reserved `address` keyword + // Note: This is also used on `MemberAccessExpression` + name = IdentifierPathElement, + variants = [ + EnumVariant(reference = Identifier), + EnumVariant(reference = AddressKeyword, enabled = From("0.6.0")) + ] + ), Token( name = Identifier, definitions = [TokenDefinition(Sequence([ diff --git a/crates/solidity-v2/outputs/cargo/cst/src/structured_cst/nodes.generated.rs b/crates/solidity-v2/outputs/cargo/cst/src/structured_cst/nodes.generated.rs index 5ed63d171b..d3e7e4288c 100644 --- a/crates/solidity-v2/outputs/cargo/cst/src/structured_cst/nodes.generated.rs +++ b/crates/solidity-v2/outputs/cargo/cst/src/structured_cst/nodes.generated.rs @@ -1271,13 +1271,13 @@ pub type MemberAccessExpression = Rc; pub struct MemberAccessExpressionStruct { pub operand: Expression, pub period: Period, - pub member: Identifier, + pub member: IdentifierPathElement, } pub fn new_member_access_expression( operand: Expression, period: Period, - member: Identifier, + member: IdentifierPathElement, ) -> MemberAccessExpression { Rc::new(MemberAccessExpressionStruct { operand, @@ -1388,13 +1388,13 @@ pub type NamedArgumentsDeclaration = Rc; #[derive(Clone, Debug)] pub struct NamedArgumentsDeclarationStruct { pub open_paren: OpenParen, - pub arguments: Option, + pub arguments: NamedArgumentGroup, pub close_paren: CloseParen, } pub fn new_named_arguments_declaration( open_paren: OpenParen, - arguments: Option, + arguments: NamedArgumentGroup, close_paren: CloseParen, ) -> NamedArgumentsDeclaration { Rc::new(NamedArgumentsDeclarationStruct { @@ -1907,44 +1907,24 @@ pub fn new_try_statement( }) } -pub type TupleDeconstructionElement = Rc; - -#[derive(Clone, Debug)] -pub struct TupleDeconstructionElementStruct { - pub member: Option, -} - -pub fn new_tuple_deconstruction_element(member: Option) -> TupleDeconstructionElement { - Rc::new(TupleDeconstructionElementStruct { member }) -} - pub type TupleDeconstructionStatement = Rc; #[derive(Clone, Debug)] pub struct TupleDeconstructionStatementStruct { - pub var_keyword: Option, - pub open_paren: OpenParen, - pub elements: TupleDeconstructionElements, - pub close_paren: CloseParen, + pub target: TupleDeconstructionTarget, pub equal: Equal, pub expression: Expression, pub semicolon: Semicolon, } pub fn new_tuple_deconstruction_statement( - var_keyword: Option, - open_paren: OpenParen, - elements: TupleDeconstructionElements, - close_paren: CloseParen, + target: TupleDeconstructionTarget, equal: Equal, expression: Expression, semicolon: Semicolon, ) -> TupleDeconstructionStatement { Rc::new(TupleDeconstructionStatementStruct { - var_keyword, - open_paren, - elements, - close_paren, + target, equal, expression, semicolon, @@ -2007,27 +1987,61 @@ pub fn new_type_expression( }) } -pub type TypedTupleMember = Rc; +pub type TypedTupleDeconstructionElement = Rc; + +#[derive(Clone, Debug)] +pub struct TypedTupleDeconstructionElementStruct { + pub member: Option, +} + +pub fn new_typed_tuple_deconstruction_element( + member: Option, +) -> TypedTupleDeconstructionElement { + Rc::new(TypedTupleDeconstructionElementStruct { member }) +} + +pub type TypedTupleDeconstructionMember = Rc; #[derive(Clone, Debug)] -pub struct TypedTupleMemberStruct { +pub struct TypedTupleDeconstructionMemberStruct { pub type_name: TypeName, pub storage_location: Option, pub name: Identifier, } -pub fn new_typed_tuple_member( +pub fn new_typed_tuple_deconstruction_member( type_name: TypeName, storage_location: Option, name: Identifier, -) -> TypedTupleMember { - Rc::new(TypedTupleMemberStruct { +) -> TypedTupleDeconstructionMember { + Rc::new(TypedTupleDeconstructionMemberStruct { type_name, storage_location, name, }) } +pub type TypedTupleDeconstructionTarget = Rc; + +#[derive(Clone, Debug)] +pub struct TypedTupleDeconstructionTargetStruct { + pub open_paren: OpenParen, + pub elements: TypedTupleDeconstructionElements, + pub close_paren: CloseParen, +} + +pub fn new_typed_tuple_deconstruction_target( + open_paren: OpenParen, + elements: TypedTupleDeconstructionElements, + close_paren: CloseParen, +) -> TypedTupleDeconstructionTarget { + Rc::new(TypedTupleDeconstructionTargetStruct { + open_paren, + elements, + close_paren, + }) +} + pub type UncheckedBlock = Rc; #[derive(Clone, Debug)] @@ -2067,22 +2081,17 @@ pub fn new_unnamed_function_definition( }) } -pub type UntypedTupleMember = Rc; +pub type UntypedTupleDeconstructionElement = Rc; #[derive(Clone, Debug)] -pub struct UntypedTupleMemberStruct { - pub storage_location: Option, - pub name: Identifier, +pub struct UntypedTupleDeconstructionElementStruct { + pub name: Option, } -pub fn new_untyped_tuple_member( - storage_location: Option, - name: Identifier, -) -> UntypedTupleMember { - Rc::new(UntypedTupleMemberStruct { - storage_location, - name, - }) +pub fn new_untyped_tuple_deconstruction_element( + name: Option, +) -> UntypedTupleDeconstructionElement { + Rc::new(UntypedTupleDeconstructionElementStruct { name }) } pub type UserDefinedValueTypeDefinition = Rc; @@ -2193,6 +2202,30 @@ pub fn new_using_directive( }) } +pub type VarTupleDeconstructionTarget = Rc; + +#[derive(Clone, Debug)] +pub struct VarTupleDeconstructionTargetStruct { + pub var_keyword: VarKeyword, + pub open_paren: OpenParen, + pub elements: UntypedTupleDeconstructionElements, + pub close_paren: CloseParen, +} + +pub fn new_var_tuple_deconstruction_target( + var_keyword: VarKeyword, + open_paren: OpenParen, + elements: UntypedTupleDeconstructionElements, + close_paren: CloseParen, +) -> VarTupleDeconstructionTarget { + Rc::new(VarTupleDeconstructionTargetStruct { + var_keyword, + open_paren, + elements, + close_paren, + }) +} + pub type VariableDeclarationStatement = Rc; #[derive(Clone, Debug)] @@ -3640,6 +3673,22 @@ pub fn new_hex_string_literal_double_quoted_hex_string_literal( HexStringLiteral::DoubleQuotedHexStringLiteral(element) } +#[derive(Clone, Debug)] +pub enum IdentifierPathElement { + Identifier(Identifier), + AddressKeyword(AddressKeyword), +} + +pub fn new_identifier_path_element_identifier(element: Identifier) -> IdentifierPathElement { + IdentifierPathElement::Identifier(element) +} + +pub fn new_identifier_path_element_address_keyword( + element: AddressKeyword, +) -> IdentifierPathElement { + IdentifierPathElement::AddressKeyword(element) +} + #[derive(Clone, Debug)] pub enum ImportClause { PathImport(PathImport), @@ -4092,17 +4141,21 @@ pub fn new_string_literal_double_quoted_string_literal( } #[derive(Clone, Debug)] -pub enum TupleMember { - TypedTupleMember(TypedTupleMember), - UntypedTupleMember(UntypedTupleMember), +pub enum TupleDeconstructionTarget { + VarTupleDeconstructionTarget(VarTupleDeconstructionTarget), + TypedTupleDeconstructionTarget(TypedTupleDeconstructionTarget), } -pub fn new_tuple_member_typed_tuple_member(element: TypedTupleMember) -> TupleMember { - TupleMember::TypedTupleMember(element) +pub fn new_tuple_deconstruction_target_var_tuple_deconstruction_target( + element: VarTupleDeconstructionTarget, +) -> TupleDeconstructionTarget { + TupleDeconstructionTarget::VarTupleDeconstructionTarget(element) } -pub fn new_tuple_member_untyped_tuple_member(element: UntypedTupleMember) -> TupleMember { - TupleMember::UntypedTupleMember(element) +pub fn new_tuple_deconstruction_target_typed_tuple_deconstruction_target( + element: TypedTupleDeconstructionTarget, +) -> TupleDeconstructionTarget { + TupleDeconstructionTarget::TypedTupleDeconstructionTarget(element) } #[derive(Clone, Debug)] @@ -4731,10 +4784,10 @@ pub fn new_hex_string_literals(elements: Vec) -> HexStringLite #[derive(Clone, Debug)] pub struct IdentifierPath { - pub elements: Vec, + pub elements: Vec, } -pub fn new_identifier_path(elements: Vec) -> IdentifierPath { +pub fn new_identifier_path(elements: Vec) -> IdentifierPath { IdentifierPath { elements } } @@ -4889,23 +4942,23 @@ pub fn new_struct_members(elements: Vec) -> StructMembers { } #[derive(Clone, Debug)] -pub struct TupleDeconstructionElements { - pub elements: Vec, +pub struct TupleValues { + pub elements: Vec, } -pub fn new_tuple_deconstruction_elements( - elements: Vec, -) -> TupleDeconstructionElements { - TupleDeconstructionElements { elements } +pub fn new_tuple_values(elements: Vec) -> TupleValues { + TupleValues { elements } } #[derive(Clone, Debug)] -pub struct TupleValues { - pub elements: Vec, +pub struct TypedTupleDeconstructionElements { + pub elements: Vec, } -pub fn new_tuple_values(elements: Vec) -> TupleValues { - TupleValues { elements } +pub fn new_typed_tuple_deconstruction_elements( + elements: Vec, +) -> TypedTupleDeconstructionElements { + TypedTupleDeconstructionElements { elements } } #[derive(Clone, Debug)] @@ -4928,6 +4981,17 @@ pub fn new_unnamed_function_attributes( UnnamedFunctionAttributes { elements } } +#[derive(Clone, Debug)] +pub struct UntypedTupleDeconstructionElements { + pub elements: Vec, +} + +pub fn new_untyped_tuple_deconstruction_elements( + elements: Vec, +) -> UntypedTupleDeconstructionElements { + UntypedTupleDeconstructionElements { elements } +} + #[derive(Clone, Debug)] pub struct UsingDeconstructionSymbols { pub elements: Vec, diff --git a/crates/solidity-v2/outputs/cargo/parser/src/lexer/contexts.generated.rs b/crates/solidity-v2/outputs/cargo/parser/src/lexer/contexts.generated.rs index 6dcee5e5f5..13673cfa71 100644 --- a/crates/solidity-v2/outputs/cargo/parser/src/lexer/contexts.generated.rs +++ b/crates/solidity-v2/outputs/cargo/parser/src/lexer/contexts.generated.rs @@ -118,7 +118,7 @@ pub enum SolidityContext { #[regex(r#"v2"#, |_| { LexemeKind::AbicoderV2Keyword_Unreserved }, priority = 3000003)] #[regex(r#"ABIEncoderV2"#, |_| { LexemeKind::ABIEncoderV2Keyword_Unreserved }, priority = 3000004)] #[regex(r#"abstract"#, |_| { LexemeKind::AbstractKeyword_Reserved }, priority = 3000005)] - #[regex(r#"address"#, |_| { LexemeKind::AddressKeyword_Unreserved }, priority = 3000006)] + #[regex(r#"address"#, |_| { LexemeKind::AddressKeyword_Reserved }, priority = 3000006)] #[regex(r#"after"#, |_| { LexemeKind::AfterKeyword_Reserved }, priority = 3000007)] #[regex(r#"alias"#, |lexer| { if LanguageVersion::V0_5_0 <= lexer.extras.language_version { LexemeKind::AliasKeyword_Reserved } else { LexemeKind::AliasKeyword_Unreserved } }, priority = 3000008)] #[regex(r#"anonymous"#, |_| { LexemeKind::AnonymousKeyword_Reserved }, priority = 3000009)] diff --git a/crates/solidity-v2/outputs/cargo/parser/src/lexer/lexemes.generated.rs b/crates/solidity-v2/outputs/cargo/parser/src/lexer/lexemes.generated.rs index 4f68f27886..aa60f936e2 100644 --- a/crates/solidity-v2/outputs/cargo/parser/src/lexer/lexemes.generated.rs +++ b/crates/solidity-v2/outputs/cargo/parser/src/lexer/lexemes.generated.rs @@ -20,7 +20,7 @@ pub enum LexemeKind { AbicoderV1Keyword_Unreserved, AbicoderV2Keyword_Unreserved, AbstractKeyword_Reserved, - AddressKeyword_Unreserved, + AddressKeyword_Reserved, AfterKeyword_Reserved, AliasKeyword_Reserved, AliasKeyword_Unreserved, diff --git a/crates/solidity-v2/outputs/cargo/parser/src/parser/temp_cst_output/node_checker.generated.rs b/crates/solidity-v2/outputs/cargo/parser/src/parser/temp_cst_output/node_checker.generated.rs index 42d395cdc0..c7f2f3710b 100644 --- a/crates/solidity-v2/outputs/cargo/parser/src/parser/temp_cst_output/node_checker.generated.rs +++ b/crates/solidity-v2/outputs/cargo/parser/src/parser/temp_cst_output/node_checker.generated.rs @@ -6817,7 +6817,10 @@ impl NodeChecker for NamedArgumentsDeclaration { } // arguments - if let Some(arguments) = &self.arguments { + + { + let arguments = &self.arguments; + // Prepare edge label if let Some((child, child_offset)) = @@ -6831,12 +6834,6 @@ impl NodeChecker for NamedArgumentsDeclaration { node_range.clone(), )); } - } else { - // If it's not there on the AST, it shouldn't be in the CST - if let Some((child, _)) = extract_first_with_label(&mut children, EdgeLabel::Arguments) - { - errors.push(NodeCheckerError::new(format!("Expected arguments to not be present in the CST, but it was there: {child:#?}"), node_range.clone())); - } } // close_paren @@ -9190,71 +9187,15 @@ impl NodeChecker for TryStatement { } } -/// Generic `NodeChecker` for sequences -impl NodeChecker for TupleDeconstructionElement { - fn check_node_with_offset(&self, node: &Node, text_offset: TextIndex) -> Vec { - let node_range = text_offset..(text_offset + node.text_len()); - - if node.kind() != NodeKind::Nonterminal(NonterminalKind::TupleDeconstructionElement) { - // Don't even check the rest - return vec![NodeCheckerError::new( - format!( - "Expected node kind to be {}, but it was {}", - NonterminalKind::TupleDeconstructionElement, - node.kind() - ), - node_range, - )]; - } - - let mut children = children_with_offsets(node, text_offset); - - let mut errors = vec![]; - - // member - if let Some(member) = &self.member { - // Prepare edge label - - if let Some((child, child_offset)) = - extract_first_with_label(&mut children, EdgeLabel::Member) - { - let child_errors = member.check_node_with_offset(&child.node, child_offset); - errors.extend(child_errors); - } else { - errors.push(NodeCheckerError::new( - "Expected member to be present in the CST, but it was not".to_string(), - node_range.clone(), - )); - } - } else { - // If it's not there on the AST, it shouldn't be in the CST - if let Some((child, _)) = extract_first_with_label(&mut children, EdgeLabel::Member) { - errors.push(NodeCheckerError::new(format!("Expected member to not be present in the CST, but it was there: {child:#?}"), node_range.clone())); - } - } - - if !children.is_empty() { - errors.push(NodeCheckerError::new( - format!("Expected 0 children left, but there's some left {children:#?}"), - node_range, - )); - } - - errors - } -} - -/// Generic `NodeChecker` for sequences +/// `NodeChecker` for `TupleDeconstructionStatement` - v2 has `target` field that wraps v1's direct fields impl NodeChecker for TupleDeconstructionStatement { fn check_node_with_offset(&self, node: &Node, text_offset: TextIndex) -> Vec { let node_range = text_offset..(text_offset + node.text_len()); if node.kind() != NodeKind::Nonterminal(NonterminalKind::TupleDeconstructionStatement) { - // Don't even check the rest return vec![NodeCheckerError::new( format!( - "Expected node kind to be {}, but it was {}", - NonterminalKind::TupleDeconstructionStatement, + "Expected node kind to be TupleDeconstructionStatement, but it was {}", node.kind() ), node_range, @@ -9262,150 +9203,58 @@ impl NodeChecker for TupleDeconstructionStatement { } let mut children = children_with_offsets(node, text_offset); - let mut errors = vec![]; - // var_keyword - if let Some(var_keyword) = &self.var_keyword { - // Prepare edge label + // v2's `target` field maps to v1's var_keyword?, open_paren, elements, close_paren + // We pass the whole node to the target checker which will extract the relevant fields + errors.extend(self.target.check_node_with_offset(node, text_offset)); - if let Some((child, child_offset)) = - extract_first_with_label(&mut children, EdgeLabel::VarKeyword) - { - let child_errors = var_keyword.check_node_with_offset(&child.node, child_offset); - errors.extend(child_errors); - } else { - errors.push(NodeCheckerError::new( - "Expected var_keyword to be present in the CST, but it was not".to_string(), - node_range.clone(), - )); - } - } else { - // If it's not there on the AST, it shouldn't be in the CST - if let Some((child, _)) = extract_first_with_label(&mut children, EdgeLabel::VarKeyword) - { - errors.push(NodeCheckerError::new(format!("Expected var_keyword to not be present in the CST, but it was there: {child:#?}"), node_range.clone())); - } - } - - // open_paren - - { - let open_paren = &self.open_paren; - - // Prepare edge label - - if let Some((child, child_offset)) = - extract_first_with_label(&mut children, EdgeLabel::OpenParen) - { - let child_errors = open_paren.check_node_with_offset(&child.node, child_offset); - errors.extend(child_errors); - } else { - errors.push(NodeCheckerError::new( - "Expected open_paren to be present in the CST, but it was not".to_string(), - node_range.clone(), - )); - } - } - - // elements - - { - let elements = &self.elements; - - // Prepare edge label - - if let Some((child, child_offset)) = - extract_first_with_label(&mut children, EdgeLabel::Elements) - { - let child_errors = elements.check_node_with_offset(&child.node, child_offset); - errors.extend(child_errors); - } else { - errors.push(NodeCheckerError::new( - "Expected elements to be present in the CST, but it was not".to_string(), - node_range.clone(), - )); - } - } - - // close_paren - - { - let close_paren = &self.close_paren; - - // Prepare edge label - - if let Some((child, child_offset)) = - extract_first_with_label(&mut children, EdgeLabel::CloseParen) - { - let child_errors = close_paren.check_node_with_offset(&child.node, child_offset); - errors.extend(child_errors); - } else { - errors.push(NodeCheckerError::new( - "Expected close_paren to be present in the CST, but it was not".to_string(), - node_range.clone(), - )); - } - } + // Remove the fields that target already checked + extract_first_with_label(&mut children, EdgeLabel::VarKeyword); + extract_first_with_label(&mut children, EdgeLabel::OpenParen); + extract_first_with_label(&mut children, EdgeLabel::Elements); + extract_first_with_label(&mut children, EdgeLabel::CloseParen); // equal - + if let Some((child, child_offset)) = + extract_first_with_label(&mut children, EdgeLabel::Equal) { - let equal = &self.equal; - - // Prepare edge label - - if let Some((child, child_offset)) = - extract_first_with_label(&mut children, EdgeLabel::Equal) - { - let child_errors = equal.check_node_with_offset(&child.node, child_offset); - errors.extend(child_errors); - } else { - errors.push(NodeCheckerError::new( - "Expected equal to be present in the CST, but it was not".to_string(), - node_range.clone(), - )); - } + errors.extend(self.equal.check_node_with_offset(&child.node, child_offset)); + } else { + errors.push(NodeCheckerError::new( + "Expected equal to be present in the CST, but it was not".to_string(), + node_range.clone(), + )); } // expression - + if let Some((child, child_offset)) = + extract_first_with_label(&mut children, EdgeLabel::Expression) { - let expression = &self.expression; - - // Prepare edge label - - if let Some((child, child_offset)) = - extract_first_with_label(&mut children, EdgeLabel::Expression) - { - let child_errors = expression.check_node_with_offset(&child.node, child_offset); - errors.extend(child_errors); - } else { - errors.push(NodeCheckerError::new( - "Expected expression to be present in the CST, but it was not".to_string(), - node_range.clone(), - )); - } + errors.extend( + self.expression + .check_node_with_offset(&child.node, child_offset), + ); + } else { + errors.push(NodeCheckerError::new( + "Expected expression to be present in the CST, but it was not".to_string(), + node_range.clone(), + )); } // semicolon - + if let Some((child, child_offset)) = + extract_first_with_label(&mut children, EdgeLabel::Semicolon) { - let semicolon = &self.semicolon; - - // Prepare edge label - - if let Some((child, child_offset)) = - extract_first_with_label(&mut children, EdgeLabel::Semicolon) - { - let child_errors = semicolon.check_node_with_offset(&child.node, child_offset); - errors.extend(child_errors); - } else { - errors.push(NodeCheckerError::new( - "Expected semicolon to be present in the CST, but it was not".to_string(), - node_range.clone(), - )); - } + errors.extend( + self.semicolon + .check_node_with_offset(&child.node, child_offset), + ); + } else { + errors.push(NodeCheckerError::new( + "Expected semicolon to be present in the CST, but it was not".to_string(), + node_range.clone(), + )); } if !children.is_empty() { @@ -9678,17 +9527,15 @@ impl NodeChecker for TypeExpression { } } -/// Generic `NodeChecker` for sequences -impl NodeChecker for TypedTupleMember { +/// `NodeChecker` for `TypedTupleDeconstructionElement` maps to v1's `TupleDeconstructionElement` +impl NodeChecker for TypedTupleDeconstructionElement { fn check_node_with_offset(&self, node: &Node, text_offset: TextIndex) -> Vec { let node_range = text_offset..(text_offset + node.text_len()); - if node.kind() != NodeKind::Nonterminal(NonterminalKind::TypedTupleMember) { - // Don't even check the rest + if node.kind() != NodeKind::Nonterminal(NonterminalKind::TupleDeconstructionElement) { return vec![NodeCheckerError::new( format!( - "Expected node kind to be {}, but it was {}", - NonterminalKind::TypedTupleMember, + "Expected node kind to be TupleDeconstructionElement, but it was {}", node.kind() ), node_range, @@ -9696,39 +9543,88 @@ impl NodeChecker for TypedTupleMember { } let mut children = children_with_offsets(node, text_offset); - let mut errors = vec![]; - // type_name - - { - let type_name = &self.type_name; - - // Prepare edge label - + if let Some(member) = &self.member { if let Some((child, child_offset)) = - extract_first_with_label(&mut children, EdgeLabel::TypeName) + extract_first_with_label(&mut children, EdgeLabel::Member) { - let child_errors = type_name.check_node_with_offset(&child.node, child_offset); - errors.extend(child_errors); + errors.extend(member.check_node_with_offset(&child.node, child_offset)); } else { errors.push(NodeCheckerError::new( - "Expected type_name to be present in the CST, but it was not".to_string(), + "Expected member to be present in the CST, but it was not".to_string(), node_range.clone(), )); } + } else if let Some((child, _)) = extract_first_with_label(&mut children, EdgeLabel::Member) + { + errors.push(NodeCheckerError::new( + format!( + "Expected member to not be present in the CST, but it was there: {child:#?}" + ), + node_range.clone(), + )); } - // storage_location - if let Some(storage_location) = &self.storage_location { - // Prepare edge label + if !children.is_empty() { + errors.push(NodeCheckerError::new( + format!("Expected 0 children left, but there's some left {children:#?}"), + node_range, + )); + } + errors + } +} + +/// `NodeChecker` for `TypedTupleDeconstructionMember` maps to v1's `TypedTupleMember` +impl NodeChecker for TypedTupleDeconstructionMember { + fn check_node_with_offset(&self, node: &Node, text_offset: TextIndex) -> Vec { + let node_range = text_offset..(text_offset + node.text_len()); + + // v1 has TupleMember -> TypedTupleMember, so we need to check if this is a TupleMember first + // and then check the variant + if node.kind() == NodeKind::Nonterminal(NonterminalKind::TupleMember) { + let children = children_with_offsets(node, text_offset); + if let Some((variant_child, variant_offset)) = children.first() { + return self.check_node_with_offset(&variant_child.node, *variant_offset); + } + } + + if node.kind() != NodeKind::Nonterminal(NonterminalKind::TypedTupleMember) { + return vec![NodeCheckerError::new( + format!( + "Expected node kind to be TypedTupleMember, but it was {}", + node.kind() + ), + node_range, + )]; + } + + let mut children = children_with_offsets(node, text_offset); + let mut errors = vec![]; + + // type_name + if let Some((child, child_offset)) = + extract_first_with_label(&mut children, EdgeLabel::TypeName) + { + errors.extend( + self.type_name + .check_node_with_offset(&child.node, child_offset), + ); + } else { + errors.push(NodeCheckerError::new( + "Expected type_name to be present in the CST, but it was not".to_string(), + node_range.clone(), + )); + } + + // storage_location (optional) + if let Some(storage_location) = &self.storage_location { if let Some((child, child_offset)) = extract_first_with_label(&mut children, EdgeLabel::StorageLocation) { - let child_errors = - storage_location.check_node_with_offset(&child.node, child_offset); - errors.extend(child_errors); + errors.extend(storage_location.check_node_with_offset(&child.node, child_offset)); } else { errors.push(NodeCheckerError::new( "Expected storage_location to be present in the CST, but it was not" @@ -9736,35 +9632,95 @@ impl NodeChecker for TypedTupleMember { node_range.clone(), )); } - } else { - // If it's not there on the AST, it shouldn't be in the CST - if let Some((child, _)) = - extract_first_with_label(&mut children, EdgeLabel::StorageLocation) - { - errors.push(NodeCheckerError::new(format!("Expected storage_location to not be present in the CST, but it was there: {child:#?}"), node_range.clone())); - } + } else if let Some((child, _)) = + extract_first_with_label(&mut children, EdgeLabel::StorageLocation) + { + errors.push(NodeCheckerError::new( + format!("Expected storage_location to not be present in the CST, but it was there: {child:#?}"), + node_range.clone(), + )); } // name + if let Some((child, child_offset)) = + extract_first_with_label(&mut children, EdgeLabel::Name) + { + errors.extend(self.name.check_node_with_offset(&child.node, child_offset)); + } else { + errors.push(NodeCheckerError::new( + "Expected name to be present in the CST, but it was not".to_string(), + node_range.clone(), + )); + } + + if !children.is_empty() { + errors.push(NodeCheckerError::new( + format!("Expected 0 children left, but there's some left {children:#?}"), + node_range, + )); + } + + errors + } +} + +/// `NodeChecker` for `TypedTupleDeconstructionTarget` - no direct v1 equivalent, check children directly +impl NodeChecker for TypedTupleDeconstructionTarget { + fn check_node_with_offset(&self, node: &Node, text_offset: TextIndex) -> Vec { + let node_range = text_offset..(text_offset + node.text_len()); + let mut children = children_with_offsets(node, text_offset); + let mut errors = vec![]; + // open_paren + if let Some((child, child_offset)) = + extract_first_with_label(&mut children, EdgeLabel::OpenParen) { - let name = &self.name; + errors.extend( + self.open_paren + .check_node_with_offset(&child.node, child_offset), + ); + } else { + errors.push(NodeCheckerError::new( + "Expected open_paren to be present in the CST, but it was not".to_string(), + node_range.clone(), + )); + } - // Prepare edge label + // elements + if let Some((child, child_offset)) = + extract_first_with_label(&mut children, EdgeLabel::Elements) + { + errors.extend( + self.elements + .check_node_with_offset(&child.node, child_offset), + ); + } else { + errors.push(NodeCheckerError::new( + "Expected elements to be present in the CST, but it was not".to_string(), + node_range.clone(), + )); + } - if let Some((child, child_offset)) = - extract_first_with_label(&mut children, EdgeLabel::Name) - { - let child_errors = name.check_node_with_offset(&child.node, child_offset); - errors.extend(child_errors); - } else { - errors.push(NodeCheckerError::new( - "Expected name to be present in the CST, but it was not".to_string(), - node_range.clone(), - )); - } + // close_paren + if let Some((child, child_offset)) = + extract_first_with_label(&mut children, EdgeLabel::CloseParen) + { + errors.extend( + self.close_paren + .check_node_with_offset(&child.node, child_offset), + ); + } else { + errors.push(NodeCheckerError::new( + "Expected close_paren to be present in the CST, but it was not".to_string(), + node_range.clone(), + )); } + // Remove fields that belong to TupleDeconstructionStatement (checked by parent) + extract_first_with_label(&mut children, EdgeLabel::Equal); + extract_first_with_label(&mut children, EdgeLabel::Expression); + extract_first_with_label(&mut children, EdgeLabel::Semicolon); + if !children.is_empty() { errors.push(NodeCheckerError::new( format!("Expected 0 children left, but there's some left {children:#?}"), @@ -9964,17 +9920,15 @@ impl NodeChecker for UnnamedFunctionDefinition { } } -/// Generic `NodeChecker` for sequences -impl NodeChecker for UntypedTupleMember { +/// `NodeChecker` for `UntypedTupleDeconstructionElement` maps to v1's `TupleDeconstructionElement` +impl NodeChecker for UntypedTupleDeconstructionElement { fn check_node_with_offset(&self, node: &Node, text_offset: TextIndex) -> Vec { let node_range = text_offset..(text_offset + node.text_len()); - if node.kind() != NodeKind::Nonterminal(NonterminalKind::UntypedTupleMember) { - // Don't even check the rest + if node.kind() != NodeKind::Nonterminal(NonterminalKind::TupleDeconstructionElement) { return vec![NodeCheckerError::new( format!( - "Expected node kind to be {}, but it was {}", - NonterminalKind::UntypedTupleMember, + "Expected node kind to be TupleDeconstructionElement, but it was {}", node.kind() ), node_range, @@ -9982,59 +9936,46 @@ impl NodeChecker for UntypedTupleMember { } let mut children = children_with_offsets(node, text_offset); - let mut errors = vec![]; - // storage_location - if let Some(storage_location) = &self.storage_location { - // Prepare edge label - + // v2's UntypedTupleDeconstructionElement has `name: Identifier?` + // v1's TupleDeconstructionElement has `member: TupleMember?` where TupleMember can be UntypedTupleMember + if let Some(name) = &self.name { if let Some((child, child_offset)) = - extract_first_with_label(&mut children, EdgeLabel::StorageLocation) + extract_first_with_label(&mut children, EdgeLabel::Member) { - let child_errors = - storage_location.check_node_with_offset(&child.node, child_offset); - errors.extend(child_errors); + // The child should be a TupleMember -> UntypedTupleMember -> name + // We need to navigate through TupleMember to get to UntypedTupleMember + let member_children = children_with_offsets(&child.node, child_offset); + if let Some((variant_child, variant_offset)) = member_children.first() { + // This should be the UntypedTupleMember + let untyped_children = + children_with_offsets(&variant_child.node, *variant_offset); + // Find the Name edge in UntypedTupleMember + for (untyped_child, untyped_child_offset) in untyped_children { + if untyped_child.label == EdgeLabel::Name { + errors.extend( + name.check_node_with_offset( + &untyped_child.node, + untyped_child_offset, + ), + ); + } + } + } } else { errors.push(NodeCheckerError::new( - "Expected storage_location to be present in the CST, but it was not" - .to_string(), + "Expected member to be present in the CST, but it was not".to_string(), node_range.clone(), )); } - } else { - // If it's not there on the AST, it shouldn't be in the CST - if let Some((child, _)) = - extract_first_with_label(&mut children, EdgeLabel::StorageLocation) - { - errors.push(NodeCheckerError::new(format!("Expected storage_location to not be present in the CST, but it was there: {child:#?}"), node_range.clone())); - } - } - - // name - + } else if let Some((child, _)) = extract_first_with_label(&mut children, EdgeLabel::Member) { - let name = &self.name; - - // Prepare edge label - - if let Some((child, child_offset)) = - extract_first_with_label(&mut children, EdgeLabel::Name) - { - let child_errors = name.check_node_with_offset(&child.node, child_offset); - errors.extend(child_errors); - } else { - errors.push(NodeCheckerError::new( - "Expected name to be present in the CST, but it was not".to_string(), - node_range.clone(), - )); - } - } - - if !children.is_empty() { errors.push(NodeCheckerError::new( - format!("Expected 0 children left, but there's some left {children:#?}"), - node_range, + format!( + "Expected member to not be present in the CST, but it was there: {child:#?}" + ), + node_range.clone(), )); } @@ -10573,6 +10514,89 @@ impl NodeChecker for UsingDirective { } } +/// `NodeChecker` for `VarTupleDeconstructionTarget` - no direct v1 equivalent, check children directly +impl NodeChecker for VarTupleDeconstructionTarget { + fn check_node_with_offset(&self, node: &Node, text_offset: TextIndex) -> Vec { + let node_range = text_offset..(text_offset + node.text_len()); + let mut children = children_with_offsets(node, text_offset); + let mut errors = vec![]; + + // var_keyword + if let Some((child, child_offset)) = + extract_first_with_label(&mut children, EdgeLabel::VarKeyword) + { + errors.extend( + self.var_keyword + .check_node_with_offset(&child.node, child_offset), + ); + } else { + errors.push(NodeCheckerError::new( + "Expected var_keyword to be present in the CST, but it was not".to_string(), + node_range.clone(), + )); + } + + // open_paren + if let Some((child, child_offset)) = + extract_first_with_label(&mut children, EdgeLabel::OpenParen) + { + errors.extend( + self.open_paren + .check_node_with_offset(&child.node, child_offset), + ); + } else { + errors.push(NodeCheckerError::new( + "Expected open_paren to be present in the CST, but it was not".to_string(), + node_range.clone(), + )); + } + + // elements + if let Some((child, child_offset)) = + extract_first_with_label(&mut children, EdgeLabel::Elements) + { + errors.extend( + self.elements + .check_node_with_offset(&child.node, child_offset), + ); + } else { + errors.push(NodeCheckerError::new( + "Expected elements to be present in the CST, but it was not".to_string(), + node_range.clone(), + )); + } + + // close_paren + if let Some((child, child_offset)) = + extract_first_with_label(&mut children, EdgeLabel::CloseParen) + { + errors.extend( + self.close_paren + .check_node_with_offset(&child.node, child_offset), + ); + } else { + errors.push(NodeCheckerError::new( + "Expected close_paren to be present in the CST, but it was not".to_string(), + node_range.clone(), + )); + } + + // Remove fields that belong to TupleDeconstructionStatement (checked by parent) + extract_first_with_label(&mut children, EdgeLabel::Equal); + extract_first_with_label(&mut children, EdgeLabel::Expression); + extract_first_with_label(&mut children, EdgeLabel::Semicolon); + + if !children.is_empty() { + errors.push(NodeCheckerError::new( + format!("Expected 0 children left, but there's some left {children:#?}"), + node_range, + )); + } + + errors + } +} + /// Generic `NodeChecker` for sequences impl NodeChecker for VariableDeclarationStatement { fn check_node_with_offset(&self, node: &Node, text_offset: TextIndex) -> Vec { @@ -14243,6 +14267,21 @@ impl NodeChecker for HexStringLiteral { } } +/// `NodeChecker` for `IdentifierPathElement` is done by hand since it's not present in V1 +impl NodeChecker for IdentifierPathElement { + fn check_node_with_offset(&self, node: &Node, text_offset: TextIndex) -> Vec { + match self { + Self::Identifier(element) => element.check_node_with_offset(node, text_offset), + Self::AddressKeyword(element) => { + let ident = Identifier { + range: element.range.clone(), + }; + ident.check_node_with_offset(node, text_offset) + } + } + } +} + /// Generic `NodeChecker` for choices impl NodeChecker for ImportClause { fn check_node_with_offset(&self, node: &Node, text_offset: TextIndex) -> Vec { @@ -15135,61 +15174,19 @@ impl NodeChecker for StringLiteral { } } -/// Generic `NodeChecker` for choices -impl NodeChecker for TupleMember { +/// `NodeChecker` for `TupleDeconstructionTarget` - no direct v1 equivalent, delegate to variants +impl NodeChecker for TupleDeconstructionTarget { fn check_node_with_offset(&self, node: &Node, text_offset: TextIndex) -> Vec { - let node_range = text_offset..(text_offset + node.text_len()); - - if node.kind() != NodeKind::Nonterminal(NonterminalKind::TupleMember) { - // Don't even check the rest - return vec![NodeCheckerError::new( - format!( - "Expected node kind to be {}, but it was {}", - NonterminalKind::TupleMember, - node.kind() - ), - node_range, - )]; - } - - let children = children_with_offsets(node, text_offset); - - if children.len() != 1 { - return vec![NodeCheckerError::new( - format!( - "Expected exactly one child for {}, but got: {children:#?}", - NonterminalKind::TupleMember - ), - node_range, - )]; - } - - let (child, child_offset) = &children[0]; - - if child.label != EdgeLabel::Variant { - let child_range = *child_offset..(*child_offset + child.node.text_len()); - return vec![NodeCheckerError::new( - format!( - "Expected child to be of variant type, but it was {}", - child.label - ), - child_range, - )]; - } - - let mut errors = vec![]; - + // TupleDeconstructionTarget doesn't exist in v1, so we skip the kind check + // and directly delegate to the variant's checker match self { - Self::TypedTupleMember(element) => { - errors.extend(element.check_node_with_offset(&child.node, *child_offset)); + Self::VarTupleDeconstructionTarget(element) => { + element.check_node_with_offset(node, text_offset) } - - Self::UntypedTupleMember(element) => { - errors.extend(element.check_node_with_offset(&child.node, *child_offset)); + Self::TypedTupleDeconstructionTarget(element) => { + element.check_node_with_offset(node, text_offset) } } - - errors } } @@ -17546,16 +17543,16 @@ impl NodeChecker for StructMembers { } /// Generic `NodeChecker` for repeated and separated -impl NodeChecker for TupleDeconstructionElements { +impl NodeChecker for TupleValues { fn check_node_with_offset(&self, node: &Node, text_offset: TextIndex) -> Vec { let node_range = text_offset..(text_offset + node.text_len()); - if node.kind() != NodeKind::Nonterminal(NonterminalKind::TupleDeconstructionElements) { + if node.kind() != NodeKind::Nonterminal(NonterminalKind::TupleValues) { // Don't even check the rest return vec![NodeCheckerError::new( format!( "Expected node kind to be {}, but it was {}", - NonterminalKind::TupleDeconstructionElements, + NonterminalKind::TupleValues, node.kind() ), node_range, @@ -17585,17 +17582,15 @@ impl NodeChecker for TupleDeconstructionElements { } } -/// Generic `NodeChecker` for repeated and separated -impl NodeChecker for TupleValues { +/// `NodeChecker` for `TypedTupleDeconstructionElements` maps to v1's `TupleDeconstructionElements` +impl NodeChecker for TypedTupleDeconstructionElements { fn check_node_with_offset(&self, node: &Node, text_offset: TextIndex) -> Vec { let node_range = text_offset..(text_offset + node.text_len()); - if node.kind() != NodeKind::Nonterminal(NonterminalKind::TupleValues) { - // Don't even check the rest + if node.kind() != NodeKind::Nonterminal(NonterminalKind::TupleDeconstructionElements) { return vec![NodeCheckerError::new( format!( - "Expected node kind to be {}, but it was {}", - NonterminalKind::TupleValues, + "Expected node kind to be TupleDeconstructionElements, but it was {}", node.kind() ), node_range, @@ -17705,6 +17700,44 @@ impl NodeChecker for UnnamedFunctionAttributes { } } +/// `NodeChecker` for `UntypedTupleDeconstructionElements` maps to v1's `TupleDeconstructionElements` +impl NodeChecker for UntypedTupleDeconstructionElements { + fn check_node_with_offset(&self, node: &Node, text_offset: TextIndex) -> Vec { + let node_range = text_offset..(text_offset + node.text_len()); + + if node.kind() != NodeKind::Nonterminal(NonterminalKind::TupleDeconstructionElements) { + return vec![NodeCheckerError::new( + format!( + "Expected node kind to be TupleDeconstructionElements, but it was {}", + node.kind() + ), + node_range, + )]; + } + + let children = children_with_offsets(node, text_offset); + + if children.len() != self.elements.len() { + return vec![NodeCheckerError::new( + format!( + "Expected {} elements, but got: {:#?}", + self.elements.len(), + children + ), + node_range, + )]; + } + + let mut errors = vec![]; + + for (i, (child, child_offset)) in children.iter().enumerate() { + let element = &self.elements[i]; + errors.extend(element.check_node_with_offset(&child.node, *child_offset)); + } + errors + } +} + /// Generic `NodeChecker` for repeated and separated impl NodeChecker for UsingDeconstructionSymbols { fn check_node_with_offset(&self, node: &Node, text_offset: TextIndex) -> Vec { diff --git a/crates/solidity-v2/outputs/cargo/parser/src/parser/temp_cst_output/node_checker.rs.jinja2 b/crates/solidity-v2/outputs/cargo/parser/src/parser/temp_cst_output/node_checker.rs.jinja2 index 152ae53b1c..edbd36f17e 100644 --- a/crates/solidity-v2/outputs/cargo/parser/src/parser/temp_cst_output/node_checker.rs.jinja2 +++ b/crates/solidity-v2/outputs/cargo/parser/src/parser/temp_cst_output/node_checker.rs.jinja2 @@ -98,6 +98,359 @@ fn children_with_offsets(node: &Node, text_offset: TextIndex) -> Vec<(Edge, Text {% for parent_type, sequence in target.sequences %} +{% if parent_type == "TupleDeconstructionStatement" %} +/// `NodeChecker` for `TupleDeconstructionStatement` - v2 has `target` field that wraps v1's direct fields +impl NodeChecker for TupleDeconstructionStatement { + fn check_node_with_offset(&self, node: &Node, text_offset: TextIndex) -> Vec { + let node_range = text_offset..(text_offset + node.text_len()); + + if node.kind() != NodeKind::Nonterminal(NonterminalKind::TupleDeconstructionStatement) { + return vec![NodeCheckerError::new(format!( + "Expected node kind to be TupleDeconstructionStatement, but it was {}", + node.kind() + ), node_range)]; + } + + let mut children = children_with_offsets(node, text_offset); + let mut errors = vec![]; + + // v2's `target` field maps to v1's var_keyword?, open_paren, elements, close_paren + // We pass the whole node to the target checker which will extract the relevant fields + errors.extend(self.target.check_node_with_offset(node, text_offset)); + + // Remove the fields that target already checked + extract_first_with_label(&mut children, EdgeLabel::VarKeyword); + extract_first_with_label(&mut children, EdgeLabel::OpenParen); + extract_first_with_label(&mut children, EdgeLabel::Elements); + extract_first_with_label(&mut children, EdgeLabel::CloseParen); + + // equal + if let Some((child, child_offset)) = extract_first_with_label(&mut children, EdgeLabel::Equal) { + errors.extend(self.equal.check_node_with_offset(&child.node, child_offset)); + } else { + errors.push(NodeCheckerError::new( + "Expected equal to be present in the CST, but it was not".to_string(), + node_range.clone(), + )); + } + + // expression + if let Some((child, child_offset)) = extract_first_with_label(&mut children, EdgeLabel::Expression) { + errors.extend(self.expression.check_node_with_offset(&child.node, child_offset)); + } else { + errors.push(NodeCheckerError::new( + "Expected expression to be present in the CST, but it was not".to_string(), + node_range.clone(), + )); + } + + // semicolon + if let Some((child, child_offset)) = extract_first_with_label(&mut children, EdgeLabel::Semicolon) { + errors.extend(self.semicolon.check_node_with_offset(&child.node, child_offset)); + } else { + errors.push(NodeCheckerError::new( + "Expected semicolon to be present in the CST, but it was not".to_string(), + node_range.clone(), + )); + } + + if !children.is_empty() { + errors.push(NodeCheckerError::new( + format!("Expected 0 children left, but there's some left {children:#?}"), + node_range, + )); + } + + errors + } +} + +{% elif parent_type == "TypedTupleDeconstructionElement" %} +/// `NodeChecker` for `TypedTupleDeconstructionElement` maps to v1's `TupleDeconstructionElement` +impl NodeChecker for TypedTupleDeconstructionElement { + fn check_node_with_offset(&self, node: &Node, text_offset: TextIndex) -> Vec { + let node_range = text_offset..(text_offset + node.text_len()); + + if node.kind() != NodeKind::Nonterminal(NonterminalKind::TupleDeconstructionElement) { + return vec![NodeCheckerError::new(format!( + "Expected node kind to be TupleDeconstructionElement, but it was {}", + node.kind() + ), node_range)]; + } + + let mut children = children_with_offsets(node, text_offset); + let mut errors = vec![]; + + if let Some(member) = &self.member { + if let Some((child, child_offset)) = extract_first_with_label(&mut children, EdgeLabel::Member) { + errors.extend(member.check_node_with_offset(&child.node, child_offset)); + } else { + errors.push(NodeCheckerError::new( + "Expected member to be present in the CST, but it was not".to_string(), + node_range.clone(), + )); + } + } else if let Some((child, _)) = extract_first_with_label(&mut children, EdgeLabel::Member) { + errors.push(NodeCheckerError::new( + format!("Expected member to not be present in the CST, but it was there: {child:#?}"), + node_range.clone(), + )); + } + + if !children.is_empty() { + errors.push(NodeCheckerError::new( + format!("Expected 0 children left, but there's some left {children:#?}"), + node_range, + )); + } + + errors + } +} + +{% elif parent_type == "UntypedTupleDeconstructionElement" %} +/// `NodeChecker` for `UntypedTupleDeconstructionElement` maps to v1's `TupleDeconstructionElement` +impl NodeChecker for UntypedTupleDeconstructionElement { + fn check_node_with_offset(&self, node: &Node, text_offset: TextIndex) -> Vec { + let node_range = text_offset..(text_offset + node.text_len()); + + if node.kind() != NodeKind::Nonterminal(NonterminalKind::TupleDeconstructionElement) { + return vec![NodeCheckerError::new(format!( + "Expected node kind to be TupleDeconstructionElement, but it was {}", + node.kind() + ), node_range)]; + } + + let mut children = children_with_offsets(node, text_offset); + let mut errors = vec![]; + + // v2's UntypedTupleDeconstructionElement has `name: Identifier?` + // v1's TupleDeconstructionElement has `member: TupleMember?` where TupleMember can be UntypedTupleMember + if let Some(name) = &self.name { + if let Some((child, child_offset)) = extract_first_with_label(&mut children, EdgeLabel::Member) { + // The child should be a TupleMember -> UntypedTupleMember -> name + // We need to navigate through TupleMember to get to UntypedTupleMember + let member_children = children_with_offsets(&child.node, child_offset); + if let Some((variant_child, variant_offset)) = member_children.first() { + // This should be the UntypedTupleMember + let untyped_children = children_with_offsets(&variant_child.node, *variant_offset); + // Find the Name edge in UntypedTupleMember + for (untyped_child, untyped_child_offset) in untyped_children { + if untyped_child.label == EdgeLabel::Name { + errors.extend(name.check_node_with_offset(&untyped_child.node, untyped_child_offset)); + } + } + } + } else { + errors.push(NodeCheckerError::new( + "Expected member to be present in the CST, but it was not".to_string(), + node_range.clone(), + )); + } + } else if let Some((child, _)) = extract_first_with_label(&mut children, EdgeLabel::Member) { + errors.push(NodeCheckerError::new( + format!("Expected member to not be present in the CST, but it was there: {child:#?}"), + node_range.clone(), + )); + } + + errors + } +} + +{% elif parent_type == "TypedTupleDeconstructionMember" %} +/// `NodeChecker` for `TypedTupleDeconstructionMember` maps to v1's `TypedTupleMember` +impl NodeChecker for TypedTupleDeconstructionMember { + fn check_node_with_offset(&self, node: &Node, text_offset: TextIndex) -> Vec { + let node_range = text_offset..(text_offset + node.text_len()); + + // v1 has TupleMember -> TypedTupleMember, so we need to check if this is a TupleMember first + // and then check the variant + if node.kind() == NodeKind::Nonterminal(NonterminalKind::TupleMember) { + let children = children_with_offsets(node, text_offset); + if let Some((variant_child, variant_offset)) = children.first() { + return self.check_node_with_offset(&variant_child.node, *variant_offset); + } + } + + if node.kind() != NodeKind::Nonterminal(NonterminalKind::TypedTupleMember) { + return vec![NodeCheckerError::new(format!( + "Expected node kind to be TypedTupleMember, but it was {}", + node.kind() + ), node_range)]; + } + + let mut children = children_with_offsets(node, text_offset); + let mut errors = vec![]; + + // type_name + if let Some((child, child_offset)) = extract_first_with_label(&mut children, EdgeLabel::TypeName) { + errors.extend(self.type_name.check_node_with_offset(&child.node, child_offset)); + } else { + errors.push(NodeCheckerError::new( + "Expected type_name to be present in the CST, but it was not".to_string(), + node_range.clone(), + )); + } + + // storage_location (optional) + if let Some(storage_location) = &self.storage_location { + if let Some((child, child_offset)) = extract_first_with_label(&mut children, EdgeLabel::StorageLocation) { + errors.extend(storage_location.check_node_with_offset(&child.node, child_offset)); + } else { + errors.push(NodeCheckerError::new( + "Expected storage_location to be present in the CST, but it was not".to_string(), + node_range.clone(), + )); + } + } else if let Some((child, _)) = extract_first_with_label(&mut children, EdgeLabel::StorageLocation) { + errors.push(NodeCheckerError::new( + format!("Expected storage_location to not be present in the CST, but it was there: {child:#?}"), + node_range.clone(), + )); + } + + // name + if let Some((child, child_offset)) = extract_first_with_label(&mut children, EdgeLabel::Name) { + errors.extend(self.name.check_node_with_offset(&child.node, child_offset)); + } else { + errors.push(NodeCheckerError::new( + "Expected name to be present in the CST, but it was not".to_string(), + node_range.clone(), + )); + } + + if !children.is_empty() { + errors.push(NodeCheckerError::new( + format!("Expected 0 children left, but there's some left {children:#?}"), + node_range, + )); + } + + errors + } +} + +{% elif parent_type == "TypedTupleDeconstructionTarget" %} +/// `NodeChecker` for `TypedTupleDeconstructionTarget` - no direct v1 equivalent, check children directly +impl NodeChecker for TypedTupleDeconstructionTarget { + fn check_node_with_offset(&self, node: &Node, text_offset: TextIndex) -> Vec { + let node_range = text_offset..(text_offset + node.text_len()); + let mut children = children_with_offsets(node, text_offset); + let mut errors = vec![]; + + // open_paren + if let Some((child, child_offset)) = extract_first_with_label(&mut children, EdgeLabel::OpenParen) { + errors.extend(self.open_paren.check_node_with_offset(&child.node, child_offset)); + } else { + errors.push(NodeCheckerError::new( + "Expected open_paren to be present in the CST, but it was not".to_string(), + node_range.clone(), + )); + } + + // elements + if let Some((child, child_offset)) = extract_first_with_label(&mut children, EdgeLabel::Elements) { + errors.extend(self.elements.check_node_with_offset(&child.node, child_offset)); + } else { + errors.push(NodeCheckerError::new( + "Expected elements to be present in the CST, but it was not".to_string(), + node_range.clone(), + )); + } + + // close_paren + if let Some((child, child_offset)) = extract_first_with_label(&mut children, EdgeLabel::CloseParen) { + errors.extend(self.close_paren.check_node_with_offset(&child.node, child_offset)); + } else { + errors.push(NodeCheckerError::new( + "Expected close_paren to be present in the CST, but it was not".to_string(), + node_range.clone(), + )); + } + + // Remove fields that belong to TupleDeconstructionStatement (checked by parent) + extract_first_with_label(&mut children, EdgeLabel::Equal); + extract_first_with_label(&mut children, EdgeLabel::Expression); + extract_first_with_label(&mut children, EdgeLabel::Semicolon); + + if !children.is_empty() { + errors.push(NodeCheckerError::new( + format!("Expected 0 children left, but there's some left {children:#?}"), + node_range, + )); + } + + errors + } +} + +{% elif parent_type == "VarTupleDeconstructionTarget" %} +/// `NodeChecker` for `VarTupleDeconstructionTarget` - no direct v1 equivalent, check children directly +impl NodeChecker for VarTupleDeconstructionTarget { + fn check_node_with_offset(&self, node: &Node, text_offset: TextIndex) -> Vec { + let node_range = text_offset..(text_offset + node.text_len()); + let mut children = children_with_offsets(node, text_offset); + let mut errors = vec![]; + + // var_keyword + if let Some((child, child_offset)) = extract_first_with_label(&mut children, EdgeLabel::VarKeyword) { + errors.extend(self.var_keyword.check_node_with_offset(&child.node, child_offset)); + } else { + errors.push(NodeCheckerError::new( + "Expected var_keyword to be present in the CST, but it was not".to_string(), + node_range.clone(), + )); + } + + // open_paren + if let Some((child, child_offset)) = extract_first_with_label(&mut children, EdgeLabel::OpenParen) { + errors.extend(self.open_paren.check_node_with_offset(&child.node, child_offset)); + } else { + errors.push(NodeCheckerError::new( + "Expected open_paren to be present in the CST, but it was not".to_string(), + node_range.clone(), + )); + } + + // elements + if let Some((child, child_offset)) = extract_first_with_label(&mut children, EdgeLabel::Elements) { + errors.extend(self.elements.check_node_with_offset(&child.node, child_offset)); + } else { + errors.push(NodeCheckerError::new( + "Expected elements to be present in the CST, but it was not".to_string(), + node_range.clone(), + )); + } + + // close_paren + if let Some((child, child_offset)) = extract_first_with_label(&mut children, EdgeLabel::CloseParen) { + errors.extend(self.close_paren.check_node_with_offset(&child.node, child_offset)); + } else { + errors.push(NodeCheckerError::new( + "Expected close_paren to be present in the CST, but it was not".to_string(), + node_range.clone(), + )); + } + + // Remove fields that belong to TupleDeconstructionStatement (checked by parent) + extract_first_with_label(&mut children, EdgeLabel::Equal); + extract_first_with_label(&mut children, EdgeLabel::Expression); + extract_first_with_label(&mut children, EdgeLabel::Semicolon); + + if !children.is_empty() { + errors.push(NodeCheckerError::new( + format!("Expected 0 children left, but there's some left {children:#?}"), + node_range, + )); + } + + errors + } +} + +{% else %} + /// Generic `NodeChecker` for sequences impl NodeChecker for {{ parent_type }} { fn check_node_with_offset(&self, node: &Node, text_offset: TextIndex) -> Vec { @@ -161,6 +514,7 @@ impl NodeChecker for {{ parent_type }} { errors } } +{% endif %} {% endfor %} @@ -189,6 +543,37 @@ impl NodeChecker for {{ parent_type }} { } } } + + +{% elif parent_type == "IdentifierPathElement" %} +/// `NodeChecker` for `IdentifierPathElement` is done by hand since it's not present in V1 +impl NodeChecker for IdentifierPathElement { + fn check_node_with_offset(&self, node: &Node, text_offset: TextIndex) -> Vec { + match self { + Self::Identifier(element) => element.check_node_with_offset(node, text_offset), + Self::AddressKeyword(element) => { + let ident = Identifier { + range: element.range.clone(), + }; + ident.check_node_with_offset(node, text_offset) + }, + } + } +} + +{% elif parent_type == "TupleDeconstructionTarget" %} +/// `NodeChecker` for `TupleDeconstructionTarget` - no direct v1 equivalent, delegate to variants +impl NodeChecker for TupleDeconstructionTarget { + fn check_node_with_offset(&self, node: &Node, text_offset: TextIndex) -> Vec { + // TupleDeconstructionTarget doesn't exist in v1, so we skip the kind check + // and directly delegate to the variant's checker + match self { + Self::VarTupleDeconstructionTarget(element) => element.check_node_with_offset(node, text_offset), + Self::TypedTupleDeconstructionTarget(element) => element.check_node_with_offset(node, text_offset), + } + } +} + {% else %} /// Generic `NodeChecker` for choices impl NodeChecker for {{ parent_type }} { @@ -234,6 +619,73 @@ impl NodeChecker for {{ parent_type }} { // {% for parent_type, collection in target.collections %} +{% if parent_type == "TypedTupleDeconstructionElements" %} +/// `NodeChecker` for `TypedTupleDeconstructionElements` maps to v1's `TupleDeconstructionElements` +impl NodeChecker for TypedTupleDeconstructionElements { + fn check_node_with_offset(&self, node: &Node, text_offset: TextIndex) -> Vec { + let node_range = text_offset..(text_offset + node.text_len()); + + if node.kind() != NodeKind::Nonterminal(NonterminalKind::TupleDeconstructionElements) { + return vec![NodeCheckerError::new(format!( + "Expected node kind to be TupleDeconstructionElements, but it was {}", + node.kind() + ), node_range)]; + } + + let children = children_with_offsets(node, text_offset); + + if children.len() != self.elements.len() { + return vec![NodeCheckerError::new(format!( + "Expected {} elements, but got: {:#?}", + self.elements.len(), + children + ), node_range)]; + } + + let mut errors = vec![]; + + for (i, (child, child_offset)) in children.iter().enumerate() { + let element = &self.elements[i]; + errors.extend(element.check_node_with_offset(&child.node, *child_offset)); + } + errors + } +} + +{% elif parent_type == "UntypedTupleDeconstructionElements" %} +/// `NodeChecker` for `UntypedTupleDeconstructionElements` maps to v1's `TupleDeconstructionElements` +impl NodeChecker for UntypedTupleDeconstructionElements { + fn check_node_with_offset(&self, node: &Node, text_offset: TextIndex) -> Vec { + let node_range = text_offset..(text_offset + node.text_len()); + + if node.kind() != NodeKind::Nonterminal(NonterminalKind::TupleDeconstructionElements) { + return vec![NodeCheckerError::new(format!( + "Expected node kind to be TupleDeconstructionElements, but it was {}", + node.kind() + ), node_range)]; + } + + let children = children_with_offsets(node, text_offset); + + if children.len() != self.elements.len() { + return vec![NodeCheckerError::new(format!( + "Expected {} elements, but got: {:#?}", + self.elements.len(), + children + ), node_range)]; + } + + let mut errors = vec![]; + + for (i, (child, child_offset)) in children.iter().enumerate() { + let element = &self.elements[i]; + errors.extend(element.check_node_with_offset(&child.node, *child_offset)); + } + errors + } +} + +{% else %} /// Generic `NodeChecker` for repeated and separated impl NodeChecker for {{ parent_type }} { fn check_node_with_offset(&self, node: &Node, text_offset: TextIndex) -> Vec { @@ -259,6 +711,7 @@ impl NodeChecker for {{ parent_type }} { errors } } +{% endif %} {% endfor %} diff --git a/crates/solidity/outputs/cargo/tests/src/cst/cst_output/generated/tuple_deconstruction_statement.rs b/crates/solidity/outputs/cargo/tests/src/cst/cst_output/generated/tuple_deconstruction_statement.rs index aab12f50c9..cdbe0a9037 100644 --- a/crates/solidity/outputs/cargo/tests/src/cst/cst_output/generated/tuple_deconstruction_statement.rs +++ b/crates/solidity/outputs/cargo/tests/src/cst/cst_output/generated/tuple_deconstruction_statement.rs @@ -21,6 +21,16 @@ fn empty() -> Result<()> { run(T, "empty") } +#[test] +fn empty_tuple() -> Result<()> { + run(T, "empty_tuple") +} + +#[test] +fn empty_var_tuple() -> Result<()> { + run(T, "empty_var_tuple") +} + #[test] fn ignored_members() -> Result<()> { run(T, "ignored_members") diff --git a/crates/solidity/testing/snapshots/cst_output/TupleDeconstructionStatement/empty_tuple/generated/0.4.11-success.yml b/crates/solidity/testing/snapshots/cst_output/TupleDeconstructionStatement/empty_tuple/generated/0.4.11-success.yml new file mode 100644 index 0000000000..08d0e50b7d --- /dev/null +++ b/crates/solidity/testing/snapshots/cst_output/TupleDeconstructionStatement/empty_tuple/generated/0.4.11-success.yml @@ -0,0 +1,28 @@ +# This file is generated automatically by infrastructure scripts. Please don't edit by hand. + +Source: > + 1 │ (,,,,) = rhs; │ 0..13 + +Errors: [] + +Tree: + - (root꞉ TupleDeconstructionStatement): # "(,,,,) = rhs;\n" (0..14) + - (open_paren꞉ OpenParen): "(" # (0..1) + - (elements꞉ TupleDeconstructionElements): # ",,,," (1..5) + - (item꞉ TupleDeconstructionElement): [] # (1..1) + - (separator꞉ Comma): "," # (1..2) + - (item꞉ TupleDeconstructionElement): [] # (2..2) + - (separator꞉ Comma): "," # (2..3) + - (item꞉ TupleDeconstructionElement): [] # (3..3) + - (separator꞉ Comma): "," # (3..4) + - (item꞉ TupleDeconstructionElement): [] # (4..4) + - (separator꞉ Comma): "," # (4..5) + - (item꞉ TupleDeconstructionElement): [] # (5..5) + - (close_paren꞉ CloseParen): ")" # (5..6) + - (leading_trivia꞉ Whitespace): " " # (6..7) + - (equal꞉ Equal): "=" # (7..8) + - (expression꞉ Expression): # " rhs" (8..12) + - (leading_trivia꞉ Whitespace): " " # (8..9) + - (variant꞉ Identifier): "rhs" # (9..12) + - (semicolon꞉ Semicolon): ";" # (12..13) + - (trailing_trivia꞉ EndOfLine): "\n" # (13..14) diff --git a/crates/solidity/testing/snapshots/cst_output/TupleDeconstructionStatement/empty_tuple/input.sol b/crates/solidity/testing/snapshots/cst_output/TupleDeconstructionStatement/empty_tuple/input.sol new file mode 100644 index 0000000000..7fa557ab62 --- /dev/null +++ b/crates/solidity/testing/snapshots/cst_output/TupleDeconstructionStatement/empty_tuple/input.sol @@ -0,0 +1 @@ +(,,,,) = rhs; diff --git a/crates/solidity/testing/snapshots/cst_output/TupleDeconstructionStatement/empty_var_tuple/generated/0.4.11-success.yml b/crates/solidity/testing/snapshots/cst_output/TupleDeconstructionStatement/empty_var_tuple/generated/0.4.11-success.yml new file mode 100644 index 0000000000..13624b9ced --- /dev/null +++ b/crates/solidity/testing/snapshots/cst_output/TupleDeconstructionStatement/empty_var_tuple/generated/0.4.11-success.yml @@ -0,0 +1,30 @@ +# This file is generated automatically by infrastructure scripts. Please don't edit by hand. + +Source: > + 1 │ var (,,,,) = rhs; │ 0..17 + +Errors: [] + +Tree: + - (root꞉ TupleDeconstructionStatement): # "var (,,,,) = rhs;\n" (0..18) + - (var_keyword꞉ VarKeyword): "var" # (0..3) + - (leading_trivia꞉ Whitespace): " " # (3..4) + - (open_paren꞉ OpenParen): "(" # (4..5) + - (elements꞉ TupleDeconstructionElements): # ",,,," (5..9) + - (item꞉ TupleDeconstructionElement): [] # (5..5) + - (separator꞉ Comma): "," # (5..6) + - (item꞉ TupleDeconstructionElement): [] # (6..6) + - (separator꞉ Comma): "," # (6..7) + - (item꞉ TupleDeconstructionElement): [] # (7..7) + - (separator꞉ Comma): "," # (7..8) + - (item꞉ TupleDeconstructionElement): [] # (8..8) + - (separator꞉ Comma): "," # (8..9) + - (item꞉ TupleDeconstructionElement): [] # (9..9) + - (close_paren꞉ CloseParen): ")" # (9..10) + - (leading_trivia꞉ Whitespace): " " # (10..11) + - (equal꞉ Equal): "=" # (11..12) + - (expression꞉ Expression): # " rhs" (12..16) + - (leading_trivia꞉ Whitespace): " " # (12..13) + - (variant꞉ Identifier): "rhs" # (13..16) + - (semicolon꞉ Semicolon): ";" # (16..17) + - (trailing_trivia꞉ EndOfLine): "\n" # (17..18) diff --git a/crates/solidity/testing/snapshots/cst_output/TupleDeconstructionStatement/empty_var_tuple/generated/0.5.0-failure.yml b/crates/solidity/testing/snapshots/cst_output/TupleDeconstructionStatement/empty_var_tuple/generated/0.5.0-failure.yml new file mode 100644 index 0000000000..154f8dd3f0 --- /dev/null +++ b/crates/solidity/testing/snapshots/cst_output/TupleDeconstructionStatement/empty_var_tuple/generated/0.5.0-failure.yml @@ -0,0 +1,17 @@ +# This file is generated automatically by infrastructure scripts. Please don't edit by hand. + +Source: > + 1 │ var (,,,,) = rhs; │ 0..17 + +Errors: # 1 total + - > + Error: Expected OpenParen. + ╭─[crates/solidity/testing/snapshots/cst_output/TupleDeconstructionStatement/empty_var_tuple/input.sol:1:1] + │ + 1 │ var (,,,,) = rhs; + │ ─────────┬──────── + │ ╰────────── Error occurred here. + ───╯ + +Tree: + - (root꞉ TupleDeconstructionStatement) ► (unrecognized꞉ UNRECOGNIZED): "var (,,,,) = rhs;\n" # (0..18) diff --git a/crates/solidity/testing/snapshots/cst_output/TupleDeconstructionStatement/empty_var_tuple/input.sol b/crates/solidity/testing/snapshots/cst_output/TupleDeconstructionStatement/empty_var_tuple/input.sol new file mode 100644 index 0000000000..1c09b78e5c --- /dev/null +++ b/crates/solidity/testing/snapshots/cst_output/TupleDeconstructionStatement/empty_var_tuple/input.sol @@ -0,0 +1 @@ +var (,,,,) = rhs;