Repository: astral-sh/ruff
File: crates/ruff_python_parser/src/parser/expression.rs (Rust)

Code before:
            self.bump_value(TokenKind::IpyEscapeCommand)
        else {
            unreachable!()
        };

        let command = ast::ExprIpyEscapeCommand {
            range: self.node_range(start),
            node_index: AtomicNodeIndex::NONE,
            kind,
            value,
        };

        // test_err ipython_help_escape_command_as_expression
        // # parse_options: {"mode": "ipython"}
        // with a,?b: pass
        if !matches!(kind, IpyEscapeKind::Magic | IpyEscapeKind::Shell) {
            // IPython escape commands like `?` (Help) are not allowed in expression contexts.
            // Only `%` (Magic) and `!` (Shell) escape commands can be used as expressions.
            // The lexer should prevent this, but we check here as a defensive measure.
            self.add_error(
                ParseErrorType::OtherError(format!(
                    "IPython escape command expression is only allowed for % and !, found {kind:?}"
                )),
                &command,
            );
        }

        if self.options.mode != Mode::Ipython {
            self.add_error(ParseErrorType::UnexpectedIpythonEscapeCommand, &command);
        }

        command
    }

    /// Performs the following validations on the function call arguments:
    /// 1. There aren't any duplicate keyword argument
    /// 2. If there are more than one argument (positional or keyword) or a single argument with a
    ///    trailing comma, all generator expressions present should be parenthesized.
    fn validate_arguments(&mut self, arguments: &ast::Arguments, has_trailing_comma: bool) {
        let mut all_arg_names =
            FxHashSet::with_capacity_and_hasher(arguments.keywords.len(), FxBuildHasher);

        for (name, range) in arguments
            .keywords
            .iter()
            .filter_map(|argument| argument.arg.as_ref().map(|arg| (arg, argument.range)))
        {
            let arg_name = name.as_str();
            if !all_arg_names.insert(arg_name) {
                self.add_error(
                    ParseErrorType::DuplicateKeywordArgumentError(arg_name.to_string()),

Review comment (suggestion):
And this, you can move the test cases above the recovery branch in `parser/mod.rs`

Code after:
            unreachable!("IPython escape command expression is only allowed for % and !");
        }

        let command = ast::ExprIpyEscapeCommand {
            range: self.node_range(start),
            node_index: AtomicNodeIndex::NONE,
            kind,
            value,
        };

        if self.options.mode != Mode::Ipython {
            self.add_error(ParseErrorType::UnexpectedIpythonEscapeCommand, &command);
        }

        command
    }

    /// Performs the following validations on the function call arguments:
    /// 1. There aren't any duplicate keyword argument
    /// 2. If there are more than one argument (positional or keyword) or a single argument with a
    ///    trailing comma, all generator expressions present should be parenthesized.
    fn validate_arguments(&mut self, arguments: &ast::Arguments, has_trailing_comma: bool) {
        let mut all_arg_names =
            FxHashSet::with_capacity_and_hasher(arguments.keywords.len(), FxBuildHasher);

        for (name, range) in arguments
            .keywords
            .iter()
            .filter_map(|argument| argument.arg.as_ref().map(|arg| (arg, argument.range)))
        {
            let arg_name = name.as_str();
            if !all_arg_names.insert(arg_name) {
                self.add_error(
                    ParseErrorType::DuplicateKeywordArgumentError(arg_name.to_string()),
                    range,
                );
            }
        }

        if has_trailing_comma || arguments.len() > 1 {
            for arg in &*arguments.args {
                if let Some(ast::ExprGenerator {
                    range,
                    parenthesized: false,
                    ..
                }) = arg.as_generator_expr()
                {
                    // test_ok args_unparenthesized_generator
                    // zip((x for x in range(10)), (y for y in range(10)))
                    // sum(x for x in range(10))
                    // sum((x for x in range(10)),)