diff --git a/e2e/playwright/snapshot-tests.spec.ts-snapshots/Grid-visibility-Grid-turned-off-to-on-via-command-bar-1-Google-Chrome-linux.png b/e2e/playwright/snapshot-tests.spec.ts-snapshots/Grid-visibility-Grid-turned-off-to-on-via-command-bar-1-Google-Chrome-linux.png index dc1bc65ee..f23905874 100644 Binary files a/e2e/playwright/snapshot-tests.spec.ts-snapshots/Grid-visibility-Grid-turned-off-to-on-via-command-bar-1-Google-Chrome-linux.png and b/e2e/playwright/snapshot-tests.spec.ts-snapshots/Grid-visibility-Grid-turned-off-to-on-via-command-bar-1-Google-Chrome-linux.png differ diff --git a/e2e/playwright/snapshot-tests.spec.ts-snapshots/code-color-goober-code-color-goober-1-Google-Chrome-linux.png b/e2e/playwright/snapshot-tests.spec.ts-snapshots/code-color-goober-code-color-goober-1-Google-Chrome-linux.png index da5b8e1c0..8e8580f92 100644 Binary files a/e2e/playwright/snapshot-tests.spec.ts-snapshots/code-color-goober-code-color-goober-1-Google-Chrome-linux.png and b/e2e/playwright/snapshot-tests.spec.ts-snapshots/code-color-goober-code-color-goober-1-Google-Chrome-linux.png differ diff --git a/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-default-planes-should-be-stable-XZ-1-Google-Chrome-linux.png b/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-default-planes-should-be-stable-XZ-1-Google-Chrome-linux.png index cb3c09653..6ee8231bf 100644 Binary files a/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-default-planes-should-be-stable-XZ-1-Google-Chrome-linux.png and b/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-default-planes-should-be-stable-XZ-1-Google-Chrome-linux.png differ diff --git a/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-default-planes-should-be-stable-YZ-1-Google-Chrome-linux.png b/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-default-planes-should-be-stable-YZ-1-Google-Chrome-linux.png index c10cfeff0..6a1ce492c 100644 Binary files a/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-default-planes-should-be-stable-YZ-1-Google-Chrome-linux.png and b/e2e/playwright/snapshot-tests.spec.ts-snapshots/extrude-on-default-planes-should-be-stable-YZ-1-Google-Chrome-linux.png differ diff --git a/rust/kcl-lib/src/parsing/parser.rs b/rust/kcl-lib/src/parsing/parser.rs index 0a2b210e8..9ee14d09b 100644 --- a/rust/kcl-lib/src/parsing/parser.rs +++ b/rust/kcl-lib/src/parsing/parser.rs @@ -2809,29 +2809,45 @@ fn fn_call_kw(i: &mut TokenSlice) -> PResult> { let _ = open_paren.parse_next(i)?; ignore_whitespace(i); + #[allow(clippy::large_enum_variant)] + pub enum ArgPlace { + NonCode(Node), + LabeledArg(LabeledArg), + UnlabeledArg(Expr), + } let initial_unlabeled_arg = opt((expression, comma, opt(whitespace)).map(|(arg, _, _)| arg)).parse_next(i)?; let args: Vec<_> = repeat( 0.., alt(( - terminated(non_code_node.map(NonCodeOr::NonCode), whitespace), - terminated(labeled_argument, labeled_arg_separator).map(NonCodeOr::Code), + terminated(non_code_node.map(ArgPlace::NonCode), whitespace), + terminated(labeled_argument, labeled_arg_separator).map(ArgPlace::LabeledArg), + expression.map(ArgPlace::UnlabeledArg), )), ) .parse_next(i)?; - let (args, non_code_nodes): (Vec<_>, BTreeMap) = args.into_iter().enumerate().fold( + let (args, non_code_nodes): (Vec<_>, BTreeMap) = args.into_iter().enumerate().try_fold( (Vec::new(), BTreeMap::new()), |(mut args, mut non_code_nodes), (i, e)| { match e { - NonCodeOr::NonCode(x) => { + ArgPlace::NonCode(x) => { non_code_nodes.insert(i, vec![x]); } - NonCodeOr::Code(x) => { + ArgPlace::LabeledArg(x) => { args.push(x); } + ArgPlace::UnlabeledArg(arg) => { + return Err(ErrMode::Cut( + CompilationError::fatal( + SourceRange::from(arg), + "This argument needs a label, but it doesn't have one", + ) + .into(), + )); + } } - (args, non_code_nodes) + Ok((args, non_code_nodes)) }, - ); + )?; if let Some(std_fn) = crate::std::get_stdlib_fn(&fn_name.name) { let just_args: Vec<_> = args.iter().collect(); typecheck_all_kw(std_fn, &just_args)?; @@ -4641,6 +4657,27 @@ baz = 2 assert_eq!(actual.operator, UnaryOperator::Not); crate::parsing::top_level_parse(some_program_string).unwrap(); // Updated import path } + + #[test] + fn test_sensible_error_when_missing_equals_in_kwarg() { + for (i, program) in ["f(x=1,y)", "f(x=1,y,z)", "f(x=1,y,z=1)", "f(x=1, y, z=1)"] + .into_iter() + .enumerate() + { + let tokens = crate::parsing::token::lex(program, ModuleId::default()).unwrap(); + let err = fn_call_kw.parse(tokens.as_slice()).unwrap_err(); + let cause = err.inner().cause.as_ref().unwrap(); + assert_eq!( + cause.message, "This argument needs a label, but it doesn't have one", + "failed test {i}: {program}" + ); + assert_eq!( + cause.source_range.start(), + program.find("y").unwrap(), + "failed test {i}: {program}" + ); + } + } } #[cfg(test)]