From 850e3581edecd738f5f9c56b728619da865419c7 Mon Sep 17 00:00:00 2001 From: Maximilian Bosch Date: Thu, 11 Nov 2021 11:16:39 +0100 Subject: [PATCH] Mark duplicated arguments in argument-patterns as syntax error To remain consistent with `nix-instantiate --parser`, expressions like { a, a }: a should be marked as invalid. Closes #45 --- src/parser.rs | 36 ++++++++++++++++++++++++++-- test_data/parser/patterns/8.expect | 38 ++++++++++++++++++++++++++++++ test_data/parser/patterns/8.nix | 1 + 3 files changed, 73 insertions(+), 2 deletions(-) create mode 100644 test_data/parser/patterns/8.expect create mode 100644 test_data/parser/patterns/8.nix diff --git a/src/parser.rs b/src/parser.rs index d8b41a2..2a1b8ea 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,7 +1,7 @@ //! The parser: turns a series of tokens into an AST use std::{ - collections::{HashSet, VecDeque}, + collections::{HashSet, HashMap, VecDeque}, fmt, }; @@ -34,6 +34,8 @@ pub enum ParseError { UnexpectedEOF, /// UnexpectedEOFWanted is used when specific tokens are expected, but the end of file is reached UnexpectedEOFWanted(Box<[SyntaxKind]>), + /// UnexpectedDuplicatedArgs is used when formal arguments are duplicated, e.g. `{ a, a }` + UnexpectedDuplicatedArgs(TextRange, String), } impl fmt::Display for ParseError { @@ -75,6 +77,15 @@ impl fmt::Display for ParseError { ParseError::UnexpectedEOFWanted(kinds) => { write!(f, "unexpected end of file, wanted any of {:?}", kinds) } + ParseError::UnexpectedDuplicatedArgs(range, ident) => { + write!( + f, + "argument `{}' is duplicated in {}..{}", + ident, + usize::from(range.start()), + usize::from(range.end()) + ) + } } } } @@ -326,6 +337,7 @@ where if self.peek().map(|t| t == TOKEN_CURLY_B_CLOSE).unwrap_or(true) { self.bump(); } else { + let mut args = HashMap::::new(); loop { match self.expect_peek_any(&[TOKEN_CURLY_B_CLOSE, TOKEN_ELLIPSIS, TOKEN_IDENT]) { Some(TOKEN_CURLY_B_CLOSE) => { @@ -339,8 +351,28 @@ where } Some(TOKEN_IDENT) => { self.start_node(NODE_PAT_ENTRY); + let tp = self.get_text_position(); + let mut ident_done = false; + if let Some((_, ident_name)) = self.peek_raw() { + let contains = args.contains_key(ident_name); + let id = ident_name.clone(); + let id_str = ident_name.to_string().clone(); + args.insert(id, tp); + if contains { + ident_done = true; + self.start_error_node(); + self.expect_ident(); + let fin = self.finish_error_node(); + self.errors.push(ParseError::UnexpectedDuplicatedArgs( + TextRange::new(tp, fin), + id_str + )); + } + } - self.expect_ident(); + if !ident_done { + self.expect_ident(); + } if let Some(TOKEN_QUESTION) = self.peek() { self.bump(); diff --git a/test_data/parser/patterns/8.expect b/test_data/parser/patterns/8.expect new file mode 100644 index 0000000..bf9b88f --- /dev/null +++ b/test_data/parser/patterns/8.expect @@ -0,0 +1,38 @@ +error: argument `a' is duplicated in 8..9 +error: error node at 8..9 +NODE_ROOT 0..14 { + NODE_LAMBDA 0..14 { + NODE_PATTERN 0..11 { + TOKEN_CURLY_B_OPEN("{") 0..1 + TOKEN_WHITESPACE(" ") 1..2 + NODE_PAT_ENTRY 2..3 { + NODE_IDENT 2..3 { + TOKEN_IDENT("a") 2..3 + } + } + TOKEN_COMMA(",") 3..4 + TOKEN_WHITESPACE(" ") 4..5 + NODE_PAT_ENTRY 5..6 { + NODE_IDENT 5..6 { + TOKEN_IDENT("b") 5..6 + } + } + TOKEN_COMMA(",") 6..7 + TOKEN_WHITESPACE(" ") 7..8 + NODE_PAT_ENTRY 8..9 { + NODE_ERROR 8..9 { + NODE_IDENT 8..9 { + TOKEN_IDENT("a") 8..9 + } + } + } + TOKEN_WHITESPACE(" ") 9..10 + TOKEN_CURLY_B_CLOSE("}") 10..11 + } + TOKEN_COLON(":") 11..12 + TOKEN_WHITESPACE(" ") 12..13 + NODE_IDENT 13..14 { + TOKEN_IDENT("a") 13..14 + } + } +} diff --git a/test_data/parser/patterns/8.nix b/test_data/parser/patterns/8.nix new file mode 100644 index 0000000..ae885d7 --- /dev/null +++ b/test_data/parser/patterns/8.nix @@ -0,0 +1 @@ +{ a, b, a }: a