diff --git a/src/parser.rs b/src/parser.rs index 61f2fb2..62e96ea 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -180,19 +180,27 @@ impl<'source> Parser<'source> { fn handle_import_future_keywords(&mut self, comps: &[Span]) -> Result { if comps.len() >= 2 && comps[0].text() == "future" && comps[1].text() == "keywords" { - match comps.len() - 2 { - 1 => self.set_future_keyword(comps[2].text(), &Some(comps[2].clone()))?, + match comps.len().saturating_sub(2) { + 1 if comps.len() >= 3 => { + self.set_future_keyword(comps[2].text(), &Some(comps[2].clone()))? + } 0 => { let span = &comps[1]; for kw in FUTURE_KEYWORDS.iter() { self.set_future_keyword(kw, &Some(span.clone()))?; } } - _ => { + _ if comps.len() >= 4 => { let s = &comps[3]; - return Err(self - .source - .error(s.line, s.col - 1, "invalid future keyword")); + return Err(self.source.error( + s.line, + s.col.saturating_sub(1), + "invalid future keyword", + )); + } + _ => { + let s = &comps[1]; + return Err(self.source.error(s.line, s.col, "invalid future keyword")); } } Ok(true) @@ -1067,16 +1075,14 @@ impl<'source> Parser<'source> { } span.end = self.end; - // Since exprs are discarded, adjust the expression index counter. - self.eidx -= vars.len() as u32; + // Since exprs are discarded, adjust the expression index counter (saturating to avoid underflow). + self.eidx = self.eidx.saturating_sub(vars.len() as u32); return Ok(Literal::SomeVars { span, vars }); } - let (key, value) = match refs.len() { - 2 => (Some(refs[0].clone()), refs[1].clone()), - 1 => (None, refs[0].clone()), - _ => { - let span = &vars[2]; + if refs.len() >= 3 { + // Too many identifiers before `in`. + if let Some(span) = vars.get(2).or_else(|| vars.last()) { return Err(anyhow!( "{}:{}:{} error: encountered `{}` while expecting `in`", span.source.file(), @@ -1085,6 +1091,21 @@ impl<'source> Parser<'source> { span.text() )); } + return Err(anyhow!( + "invalid some-decl: expected `in` after variable names" + )); + } + + let (key, value) = match refs.len() { + 2 => (Some(refs[0].clone()), refs[1].clone()), + 1 => (None, refs[0].clone()), + _ => { + // We always parse at least one identifier before `in`; guard defensively. + // parse_ident rejects `in` when no vars are present, so this is effectively unreachable. + return Err(anyhow!( + "invalid some-decl: expected variable names before `in`" + )); + } }; self.parse_future_keyword("in", false, "while parsing some-decl")?; diff --git a/tests/parser/cases/import/future.yaml b/tests/parser/cases/import/future.yaml index f538d99..4b7a281 100644 --- a/tests/parser/cases/import/future.yaml +++ b/tests/parser/cases/import/future.yaml @@ -163,6 +163,12 @@ cases: num_statements: 0 want_result: {} + - note: invalid-future-component + rego: | + package test + import future.keywords.foo.bar + error: "invalid future keyword" + - note: shadow/1 rego: | package test diff --git a/tests/parser/cases/some/some.vars.yaml b/tests/parser/cases/some/some.vars.yaml index e3a294d..4a9cf9d 100644 --- a/tests/parser/cases/some/some.vars.yaml +++ b/tests/parser/cases/some/some.vars.yaml @@ -78,3 +78,21 @@ cases: some a, 5 } error: encountered `5` while expecting identifier + + - note: no-vars-before-in + rego: | + package test + import future.keywords.in + x = y { + some in xs + } + error: "unexpected keyword `in`" + + - note: too-many-before-in + rego: | + package test + import future.keywords.in + x = y { + some a, b, c in xs + } + error: "encountered `c` while expecting `in`"