From 3fd17827bfb1e92366205fc8e7d3afb25fb5438f Mon Sep 17 00:00:00 2001 From: Brummel Date: Sun, 10 May 2026 04:31:08 +0200 Subject: [PATCH] iter 22-tidy.5.2: parse_const rejects duplicate doc/type/body clauses --- crates/ailang-surface/src/parse.rs | 100 ++++++++++++++++++++++++++++- 1 file changed, 97 insertions(+), 3 deletions(-) diff --git a/crates/ailang-surface/src/parse.rs b/crates/ailang-surface/src/parse.rs index 7a52956..86fff5d 100644 --- a/crates/ailang-surface/src/parse.rs +++ b/crates/ailang-surface/src/parse.rs @@ -686,9 +686,45 @@ impl<'a> Parser<'a> { let mut value: Option = None; loop { match self.peek_head_ident() { - Some("doc") => doc = Some(self.parse_doc()?), - Some("type") => ty = Some(self.parse_type_attr()?), - Some("body") => value = Some(self.parse_body_attr()?), + Some("doc") => { + if doc.is_some() { + let pos = self.peek().map(|t| t.span.start).unwrap_or(0); + return Err(ParseError::Production { + production: "const-def", + message: format!( + "const `{name}` has duplicate `(doc ...)` clause" + ), + pos, + }); + } + doc = Some(self.parse_doc()?); + } + Some("type") => { + if ty.is_some() { + let pos = self.peek().map(|t| t.span.start).unwrap_or(0); + return Err(ParseError::Production { + production: "const-def", + message: format!( + "const `{name}` has duplicate `(type ...)` clause" + ), + pos, + }); + } + ty = Some(self.parse_type_attr()?); + } + Some("body") => { + if value.is_some() { + let pos = self.peek().map(|t| t.span.start).unwrap_or(0); + return Err(ParseError::Production { + production: "const-def", + message: format!( + "const `{name}` has duplicate `(body ...)` clause" + ), + pos, + }); + } + value = Some(self.parse_body_attr()?); + } Some(other) => { let pos = self.peek().map(|t| t.span.start).unwrap_or(0); return Err(ParseError::Production { @@ -2393,4 +2429,62 @@ mod tests { "expected duplicate-body diagnostic, got: {msg}" ); } + + #[test] + fn parse_const_rejects_duplicate_doc_clause() { + let err = parse( + r#" + (module m + (const c + (doc "first") + (doc "second") + (type (con Int)) + (body 42))) + "#, + ) + .unwrap_err(); + let msg = format!("{err:?}"); + assert!( + msg.contains("const `c` has duplicate `(doc ...)` clause"), + "expected duplicate-doc diagnostic, got: {msg}" + ); + } + + #[test] + fn parse_const_rejects_duplicate_type_clause() { + let err = parse( + r#" + (module m + (const c + (type (con Int)) + (type (con Int)) + (body 42))) + "#, + ) + .unwrap_err(); + let msg = format!("{err:?}"); + assert!( + msg.contains("const `c` has duplicate `(type ...)` clause"), + "expected duplicate-type diagnostic, got: {msg}" + ); + } + + #[test] + fn parse_const_rejects_duplicate_body_clause() { + let err = parse( + r#" + (module m + (const c + (type (con Int)) + (body 1) + (body 2))) + "#, + ) + .unwrap_err(); + let msg = format!("{err:?}"); + assert!( + msg.contains("const `c` has duplicate `(body ...)` clause"), + "expected duplicate-body diagnostic, got: {msg}" + ); + } }