From 2fd17dd8d9aca554496995bc59e79d634e649cd7 Mon Sep 17 00:00:00 2001 From: Mykhailo Chalyi Date: Mon, 25 May 2026 15:07:31 +0000 Subject: [PATCH] fix(rg): skip indexed prefilter for --no-unicode non-literal queries Indexed prefilter could return incomplete candidate set under ASCII-mode searches when --no-unicode combined with non-literal indexed queries (e.g. -F -w), causing false-negative results. The fast path was only disabled for --no-unicode when fixed_strings was false, but -F -w still hit the indexed path with non-literal index query. Hoist index_can_use_literal computation up and skip indexed prefilter whenever ASCII semantics required but index query is not pure literal. Rebased on current main; original PR #1747 by chaliy. --- crates/bashkit/src/builtins/rg/mod.rs | 28 +++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/crates/bashkit/src/builtins/rg/mod.rs b/crates/bashkit/src/builtins/rg/mod.rs index 428a4f9c..65340d4a 100644 --- a/crates/bashkit/src/builtins/rg/mod.rs +++ b/crates/bashkit/src/builtins/rg/mod.rs @@ -3544,6 +3544,7 @@ async fn try_indexed_search( opts: &RgOptions, cwd: &Path, ) -> Option> { + let index_can_use_literal = opts.fixed_strings && !opts.word_boundary && !opts.line_regexp; if opts.invert_match || opts.files_without_matches || opts.crlf @@ -3555,7 +3556,7 @@ async fn try_indexed_search( || opts.follow_symlinks || opts.search_zip || opts.preprocessor.is_some() - || (!opts.unicode && !opts.fixed_strings) + || (!opts.unicode && !index_can_use_literal) || opts.sort != RgSort::Path { return None; @@ -3587,7 +3588,6 @@ async fn try_indexed_search( if !caps.content_search || (!opts.fixed_strings && !caps.regex) { return None; } - let index_can_use_literal = opts.fixed_strings && !opts.word_boundary && !opts.line_regexp; let pattern = if index_can_use_literal { opts.patterns[0].clone() } else { @@ -12876,6 +12876,30 @@ mod tests { assert_eq!(result.stdout, "needle\r\n"); } + #[tokio::test] + async fn test_rg_indexed_search_skipped_for_no_unicode_non_literal_queries() { + let inner = InMemoryFs::new(); + inner.mkdir(Path::new("/safe"), true).await.unwrap(); + inner + .write_file(Path::new("/safe/unicode.txt"), b"cafe\ncafe\ncaf\xc3\xa9\n") + .await + .unwrap(); + + let fs = Arc::new(IndexedTestFs { + inner, + matches: vec![], + }); + + let result = run_rg_with_fs( + &["--no-ignore", "--no-unicode", "-F", "-w", "caf", "/safe"], + None, + fs, + ) + .await; + assert_eq!(result.exit_code, 0); + assert_eq!(result.stdout, "/safe/unicode.txt:café\n"); + } + #[test] fn real_rg_binary_is_available_for_differential_tests() { require_real_rg();