difftreelog
feat parse new object iteration syntax
in: master
10 files changed
cmds/jrsonnet/Cargo.tomldiffbeforeafterboth--- a/cmds/jrsonnet/Cargo.toml
+++ b/cmds/jrsonnet/Cargo.toml
@@ -31,7 +31,7 @@
]
# Destructuring of locals
exp-destruct = ["jrsonnet-evaluator/exp-destruct"]
-# Iteration over objects yields [key, value] elements
+# Iteration over objects using [key]: value syntax
exp-object-iteration = ["jrsonnet-evaluator/exp-object-iteration"]
# Bigint type
exp-bigint = ["jrsonnet-evaluator/exp-bigint", "jrsonnet-cli/exp-bigint"]
crates/jrsonnet-evaluator/Cargo.tomldiffbeforeafterboth--- a/crates/jrsonnet-evaluator/Cargo.toml
+++ b/crates/jrsonnet-evaluator/Cargo.toml
@@ -40,8 +40,12 @@
"jrsonnet-peg-parser?/exp-destruct",
"jrsonnet-ir-parser?/exp-destruct",
]
-# Iteration over objects yields [key, value] elements
-exp-object-iteration = []
+# Iteration over objects using [key]: value syntax
+exp-object-iteration = [
+ "jrsonnet-ir/exp-object-iteration",
+ "jrsonnet-peg-parser?/exp-object-iteration",
+ "jrsonnet-ir-parser?/exp-object-iteration",
+]
# Bigint type
exp-bigint = ["num-bigint", "jrsonnet-types/exp-bigint"]
# obj?.field, obj?.['field']
crates/jrsonnet-evaluator/src/analyze.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/analyze.rs
+++ b/crates/jrsonnet-evaluator/src/analyze.rs
@@ -1862,6 +1862,8 @@
);
(r, rest)
}
+ #[cfg(feature = "exp-object-iteration")]
+ CompSpec::ForObjSpec(_) => todo!(),
}
}
let outer_depth = stack.depth;
crates/jrsonnet-ir-parser/Cargo.tomldiffbeforeafterboth--- a/crates/jrsonnet-ir-parser/Cargo.toml
+++ b/crates/jrsonnet-ir-parser/Cargo.toml
@@ -11,9 +11,10 @@
[features]
default = []
-experimental = ["exp-null-coaelse", "exp-destruct"]
+experimental = ["exp-null-coaelse", "exp-destruct", "exp-object-iteration"]
exp-null-coaelse = ["jrsonnet-ir/exp-null-coaelse"]
exp-destruct = ["jrsonnet-ir/exp-destruct"]
+exp-object-iteration = ["jrsonnet-ir/exp-object-iteration"]
[dependencies]
insta.workspace = true
crates/jrsonnet-ir-parser/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-ir-parser/src/lib.rs
+++ b/crates/jrsonnet-ir-parser/src/lib.rs
@@ -66,6 +66,13 @@
!self.at_eof() && self.peek() == kind
}
+ #[allow(dead_code)]
+ fn nth(&self, n: usize) -> SyntaxKind {
+ self.lexemes
+ .get(self.offset + n)
+ .map_or(SyntaxKind::EOF, |l| l.kind)
+ }
+
fn eat_any(&mut self) {
self.offset += 1;
}
@@ -561,20 +568,36 @@
}
}
-fn for_spec(p: &mut Parser<'_>) -> Result<ForSpecData> {
+fn for_spec(p: &mut Parser<'_>) -> Result<CompSpec> {
p.eat(T![for])?;
+ #[cfg(feature = "exp-object-iteration")]
+ if p.at(T!['[']) && p.nth(1) == SyntaxKind::IDENT && p.nth(2) == T![']'] && p.nth(3) == T![:] {
+ p.eat(T!['['])?;
+ let key = ident(p)?;
+ p.eat(T![']'])?;
+ let visibility = visibility(p)?;
+ let value = destruct(p)?;
+ p.eat(T![in])?;
+ let over = expr(p)?;
+ return Ok(CompSpec::ForObjSpec(jrsonnet_ir::ForObjSpecData {
+ key,
+ visibility,
+ value,
+ over,
+ }));
+ }
let d = destruct(p)?;
p.eat(T![in])?;
let over = expr(p)?;
- Ok(ForSpecData { destruct: d, over })
+ Ok(CompSpec::ForSpec(ForSpecData { destruct: d, over }))
}
fn compspecs(p: &mut Parser<'_>) -> Result<Vec<CompSpec>> {
let mut specs = Vec::new();
- specs.push(CompSpec::ForSpec(for_spec(p)?));
+ specs.push(for_spec(p)?);
loop {
if p.at(T![for]) {
- specs.push(CompSpec::ForSpec(for_spec(p)?));
+ specs.push(for_spec(p)?);
} else if p.at(T![if]) {
let isd = if_spec_data(p)?;
specs.push(CompSpec::IfSpec(isd));
crates/jrsonnet-ir/Cargo.tomldiffbeforeafterboth--- a/crates/jrsonnet-ir/Cargo.toml
+++ b/crates/jrsonnet-ir/Cargo.toml
@@ -12,9 +12,10 @@
[features]
default = []
-experimental = ["exp-destruct", "exp-null-coaelse"]
+experimental = ["exp-destruct", "exp-null-coaelse", "exp-object-iteration"]
exp-destruct = []
exp-null-coaelse = []
+exp-object-iteration = []
[dependencies]
jrsonnet-interner.workspace = true
crates/jrsonnet-ir/src/expr.rsdiffbeforeafterboth--- a/crates/jrsonnet-ir/src/expr.rs
+++ b/crates/jrsonnet-ir/src/expr.rs
@@ -314,10 +314,21 @@
pub over: Expr,
}
+#[cfg(feature = "exp-object-iteration")]
#[derive(Debug, PartialEq, Acyclic)]
+pub struct ForObjSpecData {
+ pub key: IStr,
+ pub visibility: Visibility,
+ pub value: Destruct,
+ pub over: Expr,
+}
+
+#[derive(Debug, PartialEq, Acyclic)]
pub enum CompSpec {
IfSpec(IfSpecData),
ForSpec(ForSpecData),
+ #[cfg(feature = "exp-object-iteration")]
+ ForObjSpec(ForObjSpecData),
}
#[derive(Debug, PartialEq, Acyclic)]
crates/jrsonnet-ir/src/visit.rsdiffbeforeafterboth1use jrsonnet_interner::IStr;23use crate::{4 ArgsDesc, AssertExpr, AssertStmt, BinaryOp, BindSpec, CompSpec, Destruct, Expr, ExprParam,5 ExprParams, FieldMember, FieldName, ForSpecData, IfElse, IfSpecData, ImportKind, IndexPart,6 ObjBody, ObjComp, ObjMembers, Slice, SliceDesc,7};89pub trait Visitor: Sized {10 fn visit_expr(&mut self, e: &Expr) {11 visit_expr(self, e)12 }13 fn visit_import(&mut self, _as_expression: bool, _value: IStr) {}14}1516#[cfg(feature = "exp-destruct")]17pub fn visit_destruct_rest<V: Visitor>(_v: &mut V, destruct: &crate::DestructRest) {18 match destruct {19 crate::DestructRest::Keep(_name) => {}20 crate::DestructRest::Drop => {}21 }22}2324#[allow(unused_variables, reason = "used with exp-destruct")]25pub fn visit_destruct<V: Visitor>(v: &mut V, destruct: &Destruct) {26 match destruct {27 Destruct::Full(_istr) => {}28 #[cfg(feature = "exp-destruct")]29 Destruct::Skip => {}30 #[cfg(feature = "exp-destruct")]31 Destruct::Array { start, rest, end } => {32 for s in start {33 visit_destruct(v, s);34 }35 if let Some(rest) = rest {36 visit_destruct_rest(v, rest);37 }38 for s in end {39 visit_destruct(v, s);40 }41 }42 #[cfg(feature = "exp-destruct")]43 Destruct::Object { fields, rest } => {44 for (_name, into, default) in fields {45 if let Some(into) = into {46 visit_destruct(v, into);47 }48 if let Some(default) = default {49 v.visit_expr(default);50 }51 if let Some(rest) = rest {52 visit_destruct_rest(v, rest);53 }54 }55 }56 }57}5859pub fn visit_if_spec<V: Visitor>(v: &mut V, cond: &IfSpecData) {60 let IfSpecData { span: _, cond } = cond;61 v.visit_expr(cond);62}6364pub fn visit_comp_spec<V: Visitor>(v: &mut V, c: &CompSpec) {65 match c {66 CompSpec::IfSpec(cond) => visit_if_spec(v, cond),67 CompSpec::ForSpec(for_spec_data) => {68 let ForSpecData { destruct, over } = for_spec_data;69 visit_destruct(v, destruct);70 v.visit_expr(over);71 }72 }73}74pub fn visit_params<V: Visitor>(v: &mut V, par: &ExprParams) {75 let ExprParams {76 exprs,77 signature: _,78 binds_len: _,79 } = par;80 for par in &**exprs {81 let ExprParam { destruct, default } = ∥82 visit_destruct(v, destruct);83 if let Some(default) = default {84 v.visit_expr(default);85 }86 }87}8889pub fn visit_bind_spec<V: Visitor>(v: &mut V, bind: &BindSpec) {90 match bind {91 BindSpec::Field { into, value } => {92 visit_destruct(v, into);93 v.visit_expr(value);94 }95 BindSpec::Function {96 name: _,97 params,98 value,99 } => {100 visit_params(v, params);101 v.visit_expr(value);102 }103 }104}105106pub fn visit_field_member<V: Visitor>(v: &mut V, mem: &FieldMember) {107 let FieldMember {108 name,109 plus: _,110 params,111 visibility: _,112 value,113 } = mem;114 match &**name {115 FieldName::Fixed(_istr) => {}116 FieldName::Dyn(expr) => v.visit_expr(expr),117 }118 if let Some(params) = params {119 visit_params(v, params);120 }121 v.visit_expr(value);122}123124pub fn visit_obj_body<V: Visitor>(v: &mut V, obj_body: &ObjBody) {125 match obj_body {126 ObjBody::MemberList(obj_members) => {127 let ObjMembers {128 locals,129 asserts,130 fields,131 } = obj_members;132 for local in &**locals {133 visit_bind_spec(v, local);134 }135 for assert in &**asserts {136 visit_assert_stmt(v, assert);137 }138 for field in fields {139 visit_field_member(v, field);140 }141 }142 ObjBody::ObjComp(obj_comp) => {143 let ObjComp {144 locals,145 field,146 compspecs,147 } = obj_comp;148 for local in &**locals {149 visit_bind_spec(v, local);150 }151 visit_field_member(v, field);152 for compspec in compspecs {153 visit_comp_spec(v, compspec);154 }155 }156 }157}158159pub fn visit_assert_stmt<V: Visitor>(v: &mut V, ass: &AssertStmt) {160 let AssertStmt { assertion, message } = ass;161 v.visit_expr(assertion);162 if let Some(message) = message {163 v.visit_expr(message);164 }165}166pub fn visit_expr<V: Visitor>(v: &mut V, e: &Expr) {167 match e {168 Expr::Literal(_literal_type) => {}169 Expr::Str(_istr) => {}170 Expr::Num(_num) => {}171 Expr::Var(_spanned) => {}172 Expr::Arr(exprs) => {173 for e in &**exprs {174 v.visit_expr(e);175 }176 }177 Expr::ArrComp(expr, comp_specs) => {178 v.visit_expr(expr);179 for ele in comp_specs {180 visit_comp_spec(v, ele);181 }182 }183 Expr::Obj(obj_body) => visit_obj_body(v, obj_body),184 Expr::ObjExtend(expr, obj_body) => {185 v.visit_expr(expr);186 visit_obj_body(v, obj_body);187 }188 Expr::UnaryOp(_unary_op_type, expr) => {189 v.visit_expr(expr);190 }191 Expr::BinaryOp(binary_op) => {192 let BinaryOp { lhs, op: _, rhs } = &**binary_op;193 v.visit_expr(lhs);194 v.visit_expr(rhs);195 }196 Expr::AssertExpr(assert_expr) => {197 let AssertExpr { assert, rest } = &**assert_expr;198 visit_assert_stmt(v, assert);199 v.visit_expr(rest);200 }201 Expr::LocalExpr(bind_specs, expr) => {202 for local in bind_specs {203 visit_bind_spec(v, local);204 }205 v.visit_expr(expr);206 }207 Expr::Import(kind, expr) => {208 v.visit_expr(expr);209210 if let Expr::Str(expr) = &**expr {211 v.visit_import(matches!(**kind, ImportKind::Normal), expr.clone());212 }213 }214 Expr::ErrorStmt(_span, expr) => {215 v.visit_expr(expr);216 }217 Expr::Apply(expr, spanned, _) => {218 v.visit_expr(expr);219 let ArgsDesc {220 unnamed,221 names: _,222 values,223 } = &**spanned;224 for unnamed in unnamed {225 v.visit_expr(unnamed);226 }227 for named in values {228 v.visit_expr(named);229 }230 }231 Expr::Index { indexable, parts } => {232 v.visit_expr(indexable);233234 for part in parts {235 let IndexPart {236 span: _,237 value,238 #[cfg(feature = "exp-null-coaelse")]239 null_coaelse: _,240 } = part;241 v.visit_expr(value);242 }243 }244 Expr::Function(expr_params, expr) => {245 visit_params(v, expr_params);246 v.visit_expr(expr);247 }248 Expr::IfElse(if_else) => {249 let IfElse {250 cond,251 cond_then,252 cond_else,253 } = &**if_else;254 visit_if_spec(v, cond);255 v.visit_expr(cond_then);256 if let Some(cond_else) = cond_else {257 v.visit_expr(cond_else);258 }259 }260 Expr::Slice(slice) => {261 let Slice { value, slice } = &**slice;262 v.visit_expr(value);263 let SliceDesc { start, end, step } = slice;264265 if let Some(start) = start {266 v.visit_expr(start);267 }268 if let Some(end) = end {269 v.visit_expr(end);270 }271 if let Some(step) = step {272 v.visit_expr(step);273 }274 }275 }276}1use jrsonnet_interner::IStr;23#[cfg(feature = "exp-object-iteration")]4use crate::ForObjSpecData;5use crate::{6 ArgsDesc, AssertExpr, AssertStmt, BinaryOp, BindSpec, CompSpec, Destruct, Expr, ExprParam,7 ExprParams, FieldMember, FieldName, ForSpecData, IfElse, IfSpecData, ImportKind, IndexPart,8 ObjBody, ObjComp, ObjMembers, Slice, SliceDesc,9};1011pub trait Visitor: Sized {12 fn visit_expr(&mut self, e: &Expr) {13 visit_expr(self, e)14 }15 fn visit_import(&mut self, _as_expression: bool, _value: IStr) {}16}1718#[cfg(feature = "exp-destruct")]19pub fn visit_destruct_rest<V: Visitor>(_v: &mut V, destruct: &crate::DestructRest) {20 match destruct {21 crate::DestructRest::Keep(_name) => {}22 crate::DestructRest::Drop => {}23 }24}2526#[allow(unused_variables, reason = "used with exp-destruct")]27pub fn visit_destruct<V: Visitor>(v: &mut V, destruct: &Destruct) {28 match destruct {29 Destruct::Full(_istr) => {}30 #[cfg(feature = "exp-destruct")]31 Destruct::Skip => {}32 #[cfg(feature = "exp-destruct")]33 Destruct::Array { start, rest, end } => {34 for s in start {35 visit_destruct(v, s);36 }37 if let Some(rest) = rest {38 visit_destruct_rest(v, rest);39 }40 for s in end {41 visit_destruct(v, s);42 }43 }44 #[cfg(feature = "exp-destruct")]45 Destruct::Object { fields, rest } => {46 for (_name, into, default) in fields {47 if let Some(into) = into {48 visit_destruct(v, into);49 }50 if let Some(default) = default {51 v.visit_expr(default);52 }53 if let Some(rest) = rest {54 visit_destruct_rest(v, rest);55 }56 }57 }58 }59}6061pub fn visit_if_spec<V: Visitor>(v: &mut V, cond: &IfSpecData) {62 let IfSpecData { span: _, cond } = cond;63 v.visit_expr(cond);64}6566pub fn visit_comp_spec<V: Visitor>(v: &mut V, c: &CompSpec) {67 match c {68 CompSpec::IfSpec(cond) => visit_if_spec(v, cond),69 CompSpec::ForSpec(for_spec_data) => {70 let ForSpecData { destruct, over } = for_spec_data;71 visit_destruct(v, destruct);72 v.visit_expr(over);73 }74 #[cfg(feature = "exp-object-iteration")]75 CompSpec::ForObjSpec(for_obj_spec_data) => {76 let ForObjSpecData {77 key: _,78 visibility: _,79 value,80 over,81 } = for_obj_spec_data;82 visit_destruct(v, value);83 v.visit_expr(over);84 }85 }86}87pub fn visit_params<V: Visitor>(v: &mut V, par: &ExprParams) {88 let ExprParams {89 exprs,90 signature: _,91 binds_len: _,92 } = par;93 for par in &**exprs {94 let ExprParam { destruct, default } = ∥95 visit_destruct(v, destruct);96 if let Some(default) = default {97 v.visit_expr(default);98 }99 }100}101102pub fn visit_bind_spec<V: Visitor>(v: &mut V, bind: &BindSpec) {103 match bind {104 BindSpec::Field { into, value } => {105 visit_destruct(v, into);106 v.visit_expr(value);107 }108 BindSpec::Function {109 name: _,110 params,111 value,112 } => {113 visit_params(v, params);114 v.visit_expr(value);115 }116 }117}118119pub fn visit_field_member<V: Visitor>(v: &mut V, mem: &FieldMember) {120 let FieldMember {121 name,122 plus: _,123 params,124 visibility: _,125 value,126 } = mem;127 match &**name {128 FieldName::Fixed(_istr) => {}129 FieldName::Dyn(expr) => v.visit_expr(expr),130 }131 if let Some(params) = params {132 visit_params(v, params);133 }134 v.visit_expr(value);135}136137pub fn visit_obj_body<V: Visitor>(v: &mut V, obj_body: &ObjBody) {138 match obj_body {139 ObjBody::MemberList(obj_members) => {140 let ObjMembers {141 locals,142 asserts,143 fields,144 } = obj_members;145 for local in &**locals {146 visit_bind_spec(v, local);147 }148 for assert in &**asserts {149 visit_assert_stmt(v, assert);150 }151 for field in fields {152 visit_field_member(v, field);153 }154 }155 ObjBody::ObjComp(obj_comp) => {156 let ObjComp {157 locals,158 field,159 compspecs,160 } = obj_comp;161 for local in &**locals {162 visit_bind_spec(v, local);163 }164 visit_field_member(v, field);165 for compspec in compspecs {166 visit_comp_spec(v, compspec);167 }168 }169 }170}171172pub fn visit_assert_stmt<V: Visitor>(v: &mut V, ass: &AssertStmt) {173 let AssertStmt { assertion, message } = ass;174 v.visit_expr(assertion);175 if let Some(message) = message {176 v.visit_expr(message);177 }178}179pub fn visit_expr<V: Visitor>(v: &mut V, e: &Expr) {180 match e {181 Expr::Literal(_literal_type) => {}182 Expr::Str(_istr) => {}183 Expr::Num(_num) => {}184 Expr::Var(_spanned) => {}185 Expr::Arr(exprs) => {186 for e in &**exprs {187 v.visit_expr(e);188 }189 }190 Expr::ArrComp(expr, comp_specs) => {191 v.visit_expr(expr);192 for ele in comp_specs {193 visit_comp_spec(v, ele);194 }195 }196 Expr::Obj(obj_body) => visit_obj_body(v, obj_body),197 Expr::ObjExtend(expr, obj_body) => {198 v.visit_expr(expr);199 visit_obj_body(v, obj_body);200 }201 Expr::UnaryOp(_unary_op_type, expr) => {202 v.visit_expr(expr);203 }204 Expr::BinaryOp(binary_op) => {205 let BinaryOp { lhs, op: _, rhs } = &**binary_op;206 v.visit_expr(lhs);207 v.visit_expr(rhs);208 }209 Expr::AssertExpr(assert_expr) => {210 let AssertExpr { assert, rest } = &**assert_expr;211 visit_assert_stmt(v, assert);212 v.visit_expr(rest);213 }214 Expr::LocalExpr(bind_specs, expr) => {215 for local in bind_specs {216 visit_bind_spec(v, local);217 }218 v.visit_expr(expr);219 }220 Expr::Import(kind, expr) => {221 v.visit_expr(expr);222223 if let Expr::Str(expr) = &**expr {224 v.visit_import(matches!(**kind, ImportKind::Normal), expr.clone());225 }226 }227 Expr::ErrorStmt(_span, expr) => {228 v.visit_expr(expr);229 }230 Expr::Apply(expr, spanned, _) => {231 v.visit_expr(expr);232 let ArgsDesc {233 unnamed,234 names: _,235 values,236 } = &**spanned;237 for unnamed in unnamed {238 v.visit_expr(unnamed);239 }240 for named in values {241 v.visit_expr(named);242 }243 }244 Expr::Index { indexable, parts } => {245 v.visit_expr(indexable);246247 for part in parts {248 let IndexPart {249 span: _,250 value,251 #[cfg(feature = "exp-null-coaelse")]252 null_coaelse: _,253 } = part;254 v.visit_expr(value);255 }256 }257 Expr::Function(expr_params, expr) => {258 visit_params(v, expr_params);259 v.visit_expr(expr);260 }261 Expr::IfElse(if_else) => {262 let IfElse {263 cond,264 cond_then,265 cond_else,266 } = &**if_else;267 visit_if_spec(v, cond);268 v.visit_expr(cond_then);269 if let Some(cond_else) = cond_else {270 v.visit_expr(cond_else);271 }272 }273 Expr::Slice(slice) => {274 let Slice { value, slice } = &**slice;275 v.visit_expr(value);276 let SliceDesc { start, end, step } = slice;277278 if let Some(start) = start {279 v.visit_expr(start);280 }281 if let Some(end) = end {282 v.visit_expr(end);283 }284 if let Some(step) = step {285 v.visit_expr(step);286 }287 }288 }289}crates/jrsonnet-peg-parser/Cargo.tomldiffbeforeafterboth--- a/crates/jrsonnet-peg-parser/Cargo.toml
+++ b/crates/jrsonnet-peg-parser/Cargo.toml
@@ -22,6 +22,7 @@
[features]
default = []
-experimental = ["exp-destruct", "exp-null-coaelse"]
+experimental = ["exp-destruct", "exp-null-coaelse", "exp-object-iteration"]
exp-destruct = ["jrsonnet-ir/exp-destruct"]
exp-null-coaelse = ["jrsonnet-ir/exp-null-coaelse"]
+exp-object-iteration = ["jrsonnet-ir/exp-object-iteration"]
crates/jrsonnet-peg-parser/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-peg-parser/src/lib.rs
+++ b/crates/jrsonnet-peg-parser/src/lib.rs
@@ -241,11 +241,21 @@
= i:spanned(<keyword("if")>, s) _ cond:expr(s) {IfSpecData { span: i.span, cond }}
pub rule forspec(s: &ParserSettings) -> ForSpecData
= keyword("for") _ destruct:destruct(s) _ keyword("in") _ over:expr(s) { ForSpecData { destruct, over } }
+ rule ensure_object_iteration()
+ = "" {?
+ #[cfg(not(feature = "exp-object-iteration"))] return Err("!!!experimental object iteration was not enabled");
+ #[cfg(feature = "exp-object-iteration")] Ok(())
+ }
+ pub rule forobjspec(s: &ParserSettings) -> CompSpec
+ = ensure_object_iteration() keyword("for") _ "[" _ key:id() _ "]" _ vis:visibility() _ value:destruct(s) _ keyword("in") _ over:expr(s) {
+ #[cfg(feature = "exp-object-iteration")] return CompSpec::ForObjSpec(jrsonnet_ir::ForObjSpecData { key, visibility: vis, value, over });
+ #[cfg(not(feature = "exp-object-iteration"))] unreachable!("ensure_object_iteration will fail if feature is not enabled")
+ }
rule compspec(s: &ParserSettings) -> CompSpec
- = i:ifspec(s) { CompSpec::IfSpec(i) } / f:forspec(s) {CompSpec::ForSpec(f)}
+ = i:ifspec(s) { CompSpec::IfSpec(i) } / f:forobjspec(s) { f } / f:forspec(s) {CompSpec::ForSpec(f)}
pub rule compspecs(s: &ParserSettings) -> Vec<CompSpec>
= specs:compspec(s) ++ _ {?
- if !matches!(specs[0], CompSpec::ForSpec(_)) {
+ if matches!(specs[0], CompSpec::IfSpec(_)) {
return Err("<first compspec should be for>")
}
Ok(specs)