From 4524539c2bc5f3a9f53bc57a1902264fc894969b Mon Sep 17 00:00:00 2001 From: Marmare314 <49279081+Marmare314@users.noreply.github.com> Date: Thu, 20 Apr 2023 11:05:11 +0200 Subject: [PATCH] forbid underscore as identifier closes #513 (#837) --- src/eval/func.rs | 5 +++ src/eval/mod.rs | 4 ++ src/syntax/ast.rs | 17 ++++++-- src/syntax/lexer.rs | 6 ++- src/syntax/parser.rs | 72 +++++++++++++++++++++++++++++----- tests/typ/compiler/closure.typ | 4 ++ tests/typ/compiler/let.typ | 20 ++++++++++ 7 files changed, 114 insertions(+), 14 deletions(-) diff --git a/src/eval/func.rs b/src/eval/func.rs index faf2d696c..29b85f7a4 100644 --- a/src/eval/func.rs +++ b/src/eval/func.rs @@ -274,6 +274,8 @@ pub enum Param { Named(Ident, Value), /// An argument sink: `..args`. Sink(Option), + /// A placeholder: `_`. + Placeholder, } impl Closure { @@ -334,6 +336,9 @@ impl Closure { args.named::(ident)?.unwrap_or_else(|| default.clone()); vm.define(ident.clone(), value); } + Param::Placeholder => { + args.eat::()?; + } } } diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 797e1fefb..0a963fafc 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -1160,6 +1160,7 @@ impl Eval for ast::Closure { params.push(Param::Named(named.name(), named.expr().eval(vm)?)); } ast::Param::Sink(name) => params.push(Param::Sink(name)), + ast::Param::Placeholder => params.push(Param::Placeholder), } } @@ -1184,6 +1185,7 @@ impl ast::Pattern { vm.define(ident.clone(), value); Ok(Value::None) } + ast::Pattern::Placeholder => Ok(Value::None), ast::Pattern::Destructuring(destruct) => { match value { Value::Array(value) => { @@ -1215,6 +1217,7 @@ impl ast::Pattern { "cannot destructure named elements from an array" ) } + ast::DestructuringKind::Placeholder => i += 1, } } if i < value.len() { @@ -1243,6 +1246,7 @@ impl ast::Pattern { vm.define(ident.clone(), v.clone()); used.insert(key.clone().take()); } + ast::DestructuringKind::Placeholder => {} } } diff --git a/src/syntax/ast.rs b/src/syntax/ast.rs index caf5319f0..f22508e82 100644 --- a/src/syntax/ast.rs +++ b/src/syntax/ast.rs @@ -1568,6 +1568,8 @@ pub enum Param { Named(Named), /// An argument sink: `..args`. Sink(Option), + /// A placeholder: `_`. + Placeholder, } impl AstNode for Param { @@ -1576,6 +1578,7 @@ impl AstNode for Param { SyntaxKind::Ident => node.cast().map(Self::Pos), SyntaxKind::Named => node.cast().map(Self::Named), SyntaxKind::Spread => Some(Self::Sink(node.cast_first_match())), + SyntaxKind::Underscore => Some(Self::Placeholder), _ => Option::None, } } @@ -1585,6 +1588,7 @@ impl AstNode for Param { Self::Pos(v) => v.as_untyped(), Self::Named(v) => v.as_untyped(), Self::Sink(_) => self.as_untyped(), + Self::Placeholder => self.as_untyped(), } } } @@ -1603,6 +1607,8 @@ pub enum DestructuringKind { Sink(Option), /// Named arguments: `x: 1`. Named(Ident, Ident), + /// A placeholder: `_`. + Placeholder, } impl Destructuring { @@ -1619,6 +1625,7 @@ impl Destructuring { let ident = filtered.next().unwrap_or_default(); Some(DestructuringKind::Named(key, ident)) } + SyntaxKind::Underscore => Some(DestructuringKind::Placeholder), _ => Option::None, }) } @@ -1629,6 +1636,7 @@ impl Destructuring { DestructuringKind::Ident(ident) => Some(ident), DestructuringKind::Sink(ident) => ident, DestructuringKind::Named(_, ident) => Some(ident), + DestructuringKind::Placeholder => Option::None, }) } } @@ -1638,6 +1646,8 @@ impl Destructuring { pub enum Pattern { /// A single identifier: `x`. Ident(Ident), + /// A placeholder: `_`. + Placeholder, /// A destructuring pattern: `(x, _, ..y)`. Destructuring(Destructuring), } @@ -1647,6 +1657,7 @@ impl AstNode for Pattern { match node.kind() { SyntaxKind::Ident => node.cast().map(Self::Ident), SyntaxKind::Destructuring => node.cast().map(Self::Destructuring), + SyntaxKind::Underscore => Some(Self::Placeholder), _ => Option::None, } } @@ -1655,6 +1666,7 @@ impl AstNode for Pattern { match self { Self::Ident(v) => v.as_untyped(), Self::Destructuring(v) => v.as_untyped(), + Self::Placeholder => self.as_untyped(), } } } @@ -1665,6 +1677,7 @@ impl Pattern { match self { Pattern::Ident(ident) => vec![ident.clone()], Pattern::Destructuring(destruct) => destruct.idents().collect(), + Pattern::Placeholder => vec![], } } } @@ -1722,9 +1735,7 @@ impl LetBinding { LetBindingKind::Normal(Pattern::Ident(_)) => { self.0.children().filter_map(SyntaxNode::cast).nth(1) } - LetBindingKind::Normal(Pattern::Destructuring(_)) => { - self.0.cast_first_match() - } + LetBindingKind::Normal(_) => self.0.cast_first_match(), LetBindingKind::Closure(_) => self.0.cast_first_match(), } } diff --git a/src/syntax/lexer.rs b/src/syntax/lexer.rs index dcd2509af..dc75a902a 100644 --- a/src/syntax/lexer.rs +++ b/src/syntax/lexer.rs @@ -527,7 +527,11 @@ impl Lexer<'_> { } } - SyntaxKind::Ident + if ident == "_" { + SyntaxKind::Underscore + } else { + SyntaxKind::Ident + } } fn number(&mut self, mut start: usize, c: char) -> SyntaxKind { diff --git a/src/syntax/parser.rs b/src/syntax/parser.rs index 801fdfc8c..389cf0269 100644 --- a/src/syntax/parser.rs +++ b/src/syntax/parser.rs @@ -636,6 +636,17 @@ fn code_primary(p: &mut Parser, atomic: bool) { p.wrap(m, SyntaxKind::Closure); } } + SyntaxKind::Underscore if !atomic => { + p.eat(); + if p.at(SyntaxKind::Arrow) { + p.wrap(m, SyntaxKind::Params); + p.eat(); + code_expr(p); + p.wrap(m, SyntaxKind::Closure); + } else if let Some(underscore) = p.node_mut(m) { + underscore.convert_to_error("expected expression, found underscore"); + } + } SyntaxKind::LeftBrace => code_block(p), SyntaxKind::LeftBracket => content_block(p), @@ -718,6 +729,7 @@ fn with_paren(p: &mut Parser) { match kind { SyntaxKind::Array => validate_array(p, m), SyntaxKind::Dict => validate_dict(p, m), + SyntaxKind::Parenthesized => validate_parenthesized(p, m), _ => {} } p.wrap(m, kind); @@ -787,13 +799,19 @@ fn item(p: &mut Parser, keyed: bool) -> SyntaxKind { return SyntaxKind::Spread; } - code_expr(p); + if !p.eat_if(SyntaxKind::Underscore) { + code_expr(p); + } else { + return SyntaxKind::Underscore; + } if !p.eat_if(SyntaxKind::Colon) { return SyntaxKind::Int; } - code_expr(p); + if !p.eat_if(SyntaxKind::Underscore) { + code_expr(p); + } let kind = match p.node(m).map(SyntaxNode::kind) { Some(SyntaxKind::Ident) => SyntaxKind::Named, @@ -840,12 +858,12 @@ fn args(p: &mut Parser) { enum PatternKind { Ident, + Placeholder, Destructuring, } fn pattern(p: &mut Parser) -> PatternKind { let m = p.marker(); - if p.at(SyntaxKind::LeftParen) { let kind = collection(p, false); validate_destruct_pattern(p, m); @@ -856,6 +874,8 @@ fn pattern(p: &mut Parser) -> PatternKind { p.wrap(m, SyntaxKind::Destructuring); PatternKind::Destructuring } + } else if p.eat_if(SyntaxKind::Underscore) { + PatternKind::Placeholder } else { p.expect(SyntaxKind::Ident); PatternKind::Ident @@ -879,6 +899,7 @@ fn let_binding(p: &mut Parser) { p.wrap(m3, SyntaxKind::Params); } } + PatternKind::Placeholder => {} PatternKind::Destructuring => destructuring = true, } @@ -961,7 +982,9 @@ fn for_loop(p: &mut Parser) { pattern(p); if p.at(SyntaxKind::Comma) { p.expected("keyword `in`. did you mean to use a destructuring pattern?"); - p.eat_if(SyntaxKind::Ident); + if !p.eat_if(SyntaxKind::Ident) { + p.eat_if(SyntaxKind::Underscore); + } p.eat_if(SyntaxKind::In); } else { p.expect(SyntaxKind::In); @@ -1023,10 +1046,25 @@ fn return_stmt(p: &mut Parser) { p.wrap(m, SyntaxKind::FuncReturn); } +fn validate_parenthesized(p: &mut Parser, m: Marker) { + for child in p.post_process(m) { + let kind = child.kind(); + if kind == SyntaxKind::Underscore { + child.convert_to_error(eco_format!( + "expected expression, found {}", + kind.name() + )); + } + } +} + fn validate_array(p: &mut Parser, m: Marker) { for child in p.post_process(m) { let kind = child.kind(); - if kind == SyntaxKind::Named || kind == SyntaxKind::Keyed { + if kind == SyntaxKind::Named + || kind == SyntaxKind::Keyed + || kind == SyntaxKind::Underscore + { child.convert_to_error(eco_format!( "expected expression, found {}", kind.name() @@ -1114,7 +1152,10 @@ fn validate_params(p: &mut Parser, m: Marker) { child.make_erroneous(); } } - SyntaxKind::LeftParen | SyntaxKind::RightParen | SyntaxKind::Comma => {} + SyntaxKind::LeftParen + | SyntaxKind::RightParen + | SyntaxKind::Comma + | SyntaxKind::Underscore => {} kind => { child.convert_to_error(eco_format!( "expected identifier, named pair or argument sink, found {}", @@ -1137,6 +1178,8 @@ fn validate_args(p: &mut Parser, m: Marker) { )); child.make_erroneous(); } + } else if child.kind() == SyntaxKind::Underscore { + child.convert_to_error("unexpected underscore"); } } } @@ -1147,7 +1190,7 @@ fn validate_destruct_pattern(p: &mut Parser, m: Marker) { for child in p.post_process(m) { match child.kind() { SyntaxKind::Ident => { - if child.text() != "_" && !used.insert(child.text().clone()) { + if !used.insert(child.text().clone()) { child.convert_to_error( "at most one binding per identifier is allowed", ); @@ -1172,7 +1215,7 @@ fn validate_destruct_pattern(p: &mut Parser, m: Marker) { continue; } - if within.text() != "_" && !used.insert(within.text().clone()) { + if !used.insert(within.text().clone()) { within.convert_to_error( "at most one binding per identifier is allowed", ); @@ -1189,7 +1232,9 @@ fn validate_destruct_pattern(p: &mut Parser, m: Marker) { } let Some(within) = child.children_mut().last_mut() else { return }; - if within.kind() != SyntaxKind::Ident { + if within.kind() != SyntaxKind::Ident + && within.kind() != SyntaxKind::Underscore + { within.convert_to_error(eco_format!( "expected identifier, found {}", within.kind().name(), @@ -1197,7 +1242,10 @@ fn validate_destruct_pattern(p: &mut Parser, m: Marker) { child.make_erroneous(); } } - SyntaxKind::LeftParen | SyntaxKind::RightParen | SyntaxKind::Comma => {} + SyntaxKind::LeftParen + | SyntaxKind::RightParen + | SyntaxKind::Comma + | SyntaxKind::Underscore => {} kind => { child.convert_to_error(eco_format!( "expected identifier or destructuring sink, found {}", @@ -1313,6 +1361,10 @@ impl<'s> Parser<'s> { self.nodes.get(m.0) } + fn node_mut(&mut self, m: Marker) -> Option<&mut SyntaxNode> { + self.nodes.get_mut(m.0) + } + fn post_process(&mut self, m: Marker) -> impl Iterator { self.nodes[m.0..] .iter_mut() diff --git a/tests/typ/compiler/closure.typ b/tests/typ/compiler/closure.typ index 915de8509..2d12a2bc1 100644 --- a/tests/typ/compiler/closure.typ +++ b/tests/typ/compiler/closure.typ @@ -170,3 +170,7 @@ --- // Error: 10-14 expected identifier, found `none` #let foo(none: b) = key + +--- +// Error: 11 expected comma +#let foo(_: 3) = none diff --git a/tests/typ/compiler/let.typ b/tests/typ/compiler/let.typ index 70657617b..160d21479 100644 --- a/tests/typ/compiler/let.typ +++ b/tests/typ/compiler/let.typ @@ -221,6 +221,26 @@ Three // Error: 9-13 expected identifier, found boolean #let (..true) = false +--- +#let _ = 4 + +#for _ in range(2) [] + +// Error: 2-3 unexpected underscore +#_ + +// Error: 8-9 unexpected underscore +#lorem(_) + +// Error: 3-4 expected expression, found underscore +#(_,) + +// Error: 3-4 expected expression, found underscore +#{_} + +// Error: 8-9 expected expression, found underscore +#{ 1 + _ } + --- // Error: 13 expected equals sign #let func(x)