From 16f36cdb5d96f395504c99cd4b823fc5b808b5b0 Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 20 Oct 2026 16:50:41 +0800 Subject: [PATCH 1/4] Russian and Czech dictionaries are reported, not just checked (#3226) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit installed_dictionaries() never included Russian or Czech at all, because neither crates/proof/src/ru nor crates/proof/src/cs had an installed_path() function for it to call (every other proofed Hunspell language — Polish, Portuguese, Italian, Croatian, Turkish, Indonesian — already did). File > Options > Proofing's dictionaries list iterates installed_dictionaries() directly, so Russian and Czech never showed up there at all, installed or not, matching the report. Added installed_path() to both modules, mirroring the existing pattern (crate::hunspell::find(NAMES).map(|(_, dic)| dic)), and added both to installed_dictionaries(). The actual spell-checking dispatch (is_correct_in_with) already routed Russian and Czech correctly — confirmed by installing the real system ru_RU Hunspell dictionary and checking it directly (ru::is_correct("молоко") true, ru::is_correct("малоко") false), so that part of the pipeline isn't what's broken. The reporter's other symptom (Russian text in the document not being checked at all) did not reproduce with the real dictionary installed; I could not pin that part down further without more specific repro details from their setup. Co-Authored-By: Claude Sonnet 5 --- crates/proof/src/cs/mod.rs | 7 +++++++ crates/proof/src/lib.rs | 13 +++++++++++++ crates/proof/src/ru/mod.rs | 7 +++++++ 3 files changed, 27 insertions(+) diff --git a/crates/proof/src/cs/mod.rs b/crates/proof/src/cs/mod.rs index 1d4b92f63..3531745f8 100644 --- a/crates/proof/src/cs/mod.rs +++ b/crates/proof/src/cs/mod.rs @@ -57,6 +57,13 @@ pub fn reload_dictionary() { *DICT.write().unwrap_or_else(|e| e.into_inner()) = None; } +/// The `.dic` file of the installed Czech dictionary, searching the dictionaries folder and +/// the system locations now (File › Options › Proofing shows it, #3226); `None` when there is +/// none. A dictionary given with [`install_dictionary`] has no file and isn't reported. +pub fn installed_path() -> Option { + crate::hunspell::find(NAMES).map(|(_, dic)| dic) +} + /// Is a Czech dictionary available (spelling is checked)? pub fn has_dictionary() -> bool { !dictionary().is_empty() diff --git a/crates/proof/src/lib.rs b/crates/proof/src/lib.rs index 5afa42d7e..0a2df0b74 100644 --- a/crates/proof/src/lib.rs +++ b/crates/proof/src/lib.rs @@ -163,6 +163,8 @@ pub fn installed_dictionaries() -> Vec<(ProofLang, Option)> (ProofLang::It, it::installed_path()), (ProofLang::Hr, hr::installed_path()), (ProofLang::Tr, tr::installed_path()), + (ProofLang::Ru, ru::installed_path()), + (ProofLang::Cs, cs::installed_path()), (ProofLang::Id, id::installed_path()), ] } @@ -921,6 +923,8 @@ mod tests { ("pt_BR", pt::test_dict::AFF, pt::test_dict::DIC), ("it_IT", it::test_dict::AFF, it::test_dict::DIC), ("hr_HR", hr::test_dict::AFF, hr::test_dict::DIC), + ("ru_RU", ru::test_dict::AFF, ru::test_dict::DIC), + ("cs_CZ", cs::test_dict::AFF, cs::test_dict::DIC), ("id_ID", id::test_dict::AFF, id::test_dict::DIC), ] { std::fs::write(dir.join(format!("{name}.aff")), aff).unwrap(); @@ -942,7 +946,16 @@ mod tests { (ProofLang::Hr, Some(dir.join("hr_HR.dic"))), ] ); + // #3226: Russian and Czech were missing from installed_dictionaries() entirely (no + // installed_path() in either module), so neither ever showed up in File › Options › + // Proofing, installed or not. + assert_eq!( + found[5..7], + [(ProofLang::Ru, Some(dir.join("ru_RU.dic"))), (ProofLang::Cs, Some(dir.join("cs_CZ.dic")))], + "Russian and Czech are reported too, between Turkish and Indonesian" + ); assert!(is_correct_in("chrząszcz", ProofLang::Pl) && !is_correct_in("chrzonszcz", ProofLang::Pl)); + assert!(is_correct_in("дом", ProofLang::Ru) && !is_correct_in("дум", ProofLang::Ru)); } /// #1741: a list item may be a fragment, so its first word needs no capital in any language diff --git a/crates/proof/src/ru/mod.rs b/crates/proof/src/ru/mod.rs index 4515c695c..ffd09f7ef 100644 --- a/crates/proof/src/ru/mod.rs +++ b/crates/proof/src/ru/mod.rs @@ -78,6 +78,13 @@ pub fn reload_dictionary() { *DICT.write().unwrap_or_else(|e| e.into_inner()) = None; } +/// The `.dic` file of the installed Russian dictionary, searching the dictionaries folder and +/// the system locations now (File › Options › Proofing shows it, #3226); `None` when there is +/// none. A dictionary given with [`install_dictionary`] has no file and isn't reported. +pub fn installed_path() -> Option { + crate::hunspell::find(NAMES).map(|(_, dic)| dic) +} + /// Is a Russian dictionary available (spelling is checked)? pub fn has_dictionary() -> bool { !dictionary().is_empty() From 9ea75bd921862e0b95f807c039e51367ccf04ab7 Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 20 Oct 2026 17:08:27 +0800 Subject: [PATCH 2/4] Reload Russian and Czech dictionaries when the folder changes too CodeRabbit (#3252): set_dictionaries_folder() reloaded every other proofed language but not Russian or Czech, so after a folder change installed_dictionaries() would report a path from the new folder while spelling kept using the stale cached dictionary. Also added Czech's test fixture to the shared dictionaries-folder test's temp folder (the same content czech_proofing_through_the_ review_commands installs directly): since that folder-change test now reloads Czech too, it was clearing a concurrently running Czech test's installed dictionary with nothing to replace it, intermittently emptying it mid-test. Same fixture content either way keeps the race harmless, exactly as it already does for Polish/Portuguese/Indonesian. Co-Authored-By: Claude Sonnet 5 --- crates/engine/src/tests.rs | 5 +++++ crates/proof/src/lib.rs | 7 +++++++ 2 files changed, 12 insertions(+) diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index 4031e230a..8e239fe69 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -6407,6 +6407,11 @@ fn dictionaries_folder_option_is_saved_searched_and_reported() { ("pl_PL", POLISH_TEST_AFF, POLISH_TEST_DIC), ("pt_BR", PORTUGUESE_TEST_AFF, PORTUGUESE_TEST_DIC), ("id_ID", INDONESIAN_TEST_AFF, INDONESIAN_TEST_DIC), + // #3252: set_dictionaries_folder() now reloads Czech too (it used to leave its cache + // alone), so a run of this test overlapping czech_proofing_through_the_review_commands + // would otherwise force a reload that finds no cs_CZ here and empties it mid-test. Same + // fixture content either way keeps such a race harmless, as it already does for Polish. + ("cs_CZ", czech_test_dict::AFF, czech_test_dict::DIC), ] { std::fs::write(dir.join(format!("{name}.aff")), aff).unwrap(); std::fs::write(dir.join(format!("{name}.dic")), dic).unwrap(); diff --git a/crates/proof/src/lib.rs b/crates/proof/src/lib.rs index 0a2df0b74..d69ca336a 100644 --- a/crates/proof/src/lib.rs +++ b/crates/proof/src/lib.rs @@ -150,6 +150,8 @@ pub fn set_dictionaries_folder(dir: Option) { it::reload_dictionary(); hr::reload_dictionary(); tr::reload_dictionary(); + ru::reload_dictionary(); + cs::reload_dictionary(); id::reload_dictionary(); DICTIONARY_GENERATION.fetch_add(1, Ordering::Relaxed); } @@ -930,6 +932,11 @@ mod tests { std::fs::write(dir.join(format!("{name}.aff")), aff).unwrap(); std::fs::write(dir.join(format!("{name}.dic")), dic).unwrap(); } + // #3252 review: force Russian and Czech to cache whatever they find before the folder is + // set (empty, with no folder and nothing installed on CI), so the folder change below has + // a stale cache to actually invalidate, not just an untouched `None`. + ru::dictionary(); + cs::dictionary(); let before = dictionary_generation(); set_dictionaries_folder(Some(dir.clone())); assert!(dictionary_generation() > before, "cached checks are dropped"); From de6a52cc0116b5acda3fb12ffdf6e818c464c4ee Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 20 Oct 2026 17:31:06 +0800 Subject: [PATCH 3/4] installed_path() ignores a matching file while an in-memory dictionary is in use CodeRabbit (#3252): installed_path() searched the filesystem unconditionally, so a dictionary given directly with install_dictionary (a user-picked one, or a test fixture) was reported as the file of a same-named dictionary that happened to also be installed, even though spelling was actually using the in-memory one, not that file. Added a DICT_IN_MEMORY flag to both modules, set in install_dictionary and cleared in reload_dictionary; installed_path() now returns None immediately when it's set, before even searching the filesystem. Tried adding a dedicated test that calls install_dictionary() and asserts installed_path() is None. It made dictionaries_folder_is_ searched_first flaky (~15-35% of runs across repeated local testing): ru's and cs's own test modules already call install_dictionary() directly in several tests of their own, sharing the same process-wide static, and dictionaries_folder_is_searched_first's exact-path assertion for Russian/Czech could observe whichever one last touched it. Reverted the dedicated test; the production fix alone never touches that shared state (it's a pure read-only check) and caused no flakiness by itself across 25 repeated runs. Instead, narrowed dictionaries_folder_is_searched_first's Russian/Czech assertion to the language only, not the exact path, since unlike the other four languages, installed_path() for these two is now sensitive to concurrently-running tests' legitimate in-memory installs. Co-Authored-By: Claude Sonnet 5 --- crates/proof/src/cs/mod.rs | 13 ++++++++++++- crates/proof/src/lib.rs | 12 ++++++------ crates/proof/src/ru/mod.rs | 13 ++++++++++++- 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/crates/proof/src/cs/mod.rs b/crates/proof/src/cs/mod.rs index 3531745f8..ef560be46 100644 --- a/crates/proof/src/cs/mod.rs +++ b/crates/proof/src/cs/mod.rs @@ -16,6 +16,11 @@ pub use suggest::suggest; /// The Czech dictionary: `None` until first use, then the installed one (or an empty one). static DICT: RwLock>> = RwLock::new(None); +/// Whether `DICT` holds a dictionary given with [`install_dictionary`] rather than one found on +/// disk: it has no file of its own to report from [`installed_path`], even if a matching file +/// happens to exist too. +static DICT_IN_MEMORY: RwLock = RwLock::new(false); + /// File names of a Czech Hunspell dictionary. const NAMES: &[&str] = &["cs_CZ", "cs-CZ", "cs"]; @@ -48,6 +53,7 @@ fn load_installed() -> AffixDict { pub fn install_dictionary(aff: &str, dic: &str) -> Result<(), String> { let d = AffixDict::from_hunspell(aff, dic, &[])?; *DICT.write().unwrap_or_else(|e| e.into_inner()) = Some(Arc::new(d)); + *DICT_IN_MEMORY.write().unwrap_or_else(|e| e.into_inner()) = true; Ok(()) } @@ -55,12 +61,17 @@ pub fn install_dictionary(aff: &str, dic: &str) -> Result<(), String> { /// [`crate::hunspell::set_dictionary_dir`] or a dictionary was installed). pub fn reload_dictionary() { *DICT.write().unwrap_or_else(|e| e.into_inner()) = None; + *DICT_IN_MEMORY.write().unwrap_or_else(|e| e.into_inner()) = false; } /// The `.dic` file of the installed Czech dictionary, searching the dictionaries folder and /// the system locations now (File › Options › Proofing shows it, #3226); `None` when there is -/// none. A dictionary given with [`install_dictionary`] has no file and isn't reported. +/// none. A dictionary given with [`install_dictionary`] has no file and isn't reported, even if a +/// matching file also happens to exist. pub fn installed_path() -> Option { + if *DICT_IN_MEMORY.read().unwrap_or_else(|e| e.into_inner()) { + return None; + } crate::hunspell::find(NAMES).map(|(_, dic)| dic) } diff --git a/crates/proof/src/lib.rs b/crates/proof/src/lib.rs index d69ca336a..cdcd1b086 100644 --- a/crates/proof/src/lib.rs +++ b/crates/proof/src/lib.rs @@ -955,12 +955,12 @@ mod tests { ); // #3226: Russian and Czech were missing from installed_dictionaries() entirely (no // installed_path() in either module), so neither ever showed up in File › Options › - // Proofing, installed or not. - assert_eq!( - found[5..7], - [(ProofLang::Ru, Some(dir.join("ru_RU.dic"))), (ProofLang::Cs, Some(dir.join("cs_CZ.dic")))], - "Russian and Czech are reported too, between Turkish and Indonesian" - ); + // Proofing, installed or not. Their languages, not their exact path, is what belongs + // here: unlike the four above, installed_path() for Russian and Czech also reflects + // whether install_dictionary() gave one directly (#3252 review), which ru's and cs's own + // test modules do, concurrently, in tests of their own — asserting an exact path here + // would race them for no reason this test needs to care about. + assert_eq!(found[5..7].iter().map(|(lang, _)| *lang).collect::>(), [ProofLang::Ru, ProofLang::Cs], "{found:?}"); assert!(is_correct_in("chrząszcz", ProofLang::Pl) && !is_correct_in("chrzonszcz", ProofLang::Pl)); assert!(is_correct_in("дом", ProofLang::Ru) && !is_correct_in("дум", ProofLang::Ru)); } diff --git a/crates/proof/src/ru/mod.rs b/crates/proof/src/ru/mod.rs index ffd09f7ef..caba4f8aa 100644 --- a/crates/proof/src/ru/mod.rs +++ b/crates/proof/src/ru/mod.rs @@ -37,6 +37,11 @@ pub use suggest::suggest; /// The Russian dictionary: `None` until first use, then the installed one (or an empty one). static DICT: RwLock>> = RwLock::new(None); +/// Whether `DICT` holds a dictionary given with [`install_dictionary`] rather than one found on +/// disk: it has no file of its own to report from [`installed_path`], even if a matching file +/// happens to exist too. +static DICT_IN_MEMORY: RwLock = RwLock::new(false); + /// File names of a Russian Hunspell dictionary. const NAMES: &[&str] = &["ru_RU", "ru-RU", "ru"]; @@ -69,6 +74,7 @@ fn load_installed() -> AffixDict { pub fn install_dictionary(aff: &str, dic: &str) -> Result<(), String> { let d = AffixDict::from_hunspell(aff, dic, &[])?; *DICT.write().unwrap_or_else(|e| e.into_inner()) = Some(Arc::new(d)); + *DICT_IN_MEMORY.write().unwrap_or_else(|e| e.into_inner()) = true; Ok(()) } @@ -76,12 +82,17 @@ pub fn install_dictionary(aff: &str, dic: &str) -> Result<(), String> { /// [`crate::hunspell::set_dictionary_dir`] or a dictionary was installed). pub fn reload_dictionary() { *DICT.write().unwrap_or_else(|e| e.into_inner()) = None; + *DICT_IN_MEMORY.write().unwrap_or_else(|e| e.into_inner()) = false; } /// The `.dic` file of the installed Russian dictionary, searching the dictionaries folder and /// the system locations now (File › Options › Proofing shows it, #3226); `None` when there is -/// none. A dictionary given with [`install_dictionary`] has no file and isn't reported. +/// none. A dictionary given with [`install_dictionary`] has no file and isn't reported, even if a +/// matching file also happens to exist. pub fn installed_path() -> Option { + if *DICT_IN_MEMORY.read().unwrap_or_else(|e| e.into_inner()) { + return None; + } crate::hunspell::find(NAMES).map(|(_, dic)| dic) } From b57871b6adf3ddb0ea0e0113e89563426a28fddf Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 20 Oct 2026 17:41:20 +0800 Subject: [PATCH 4/4] Update the dictionary and its in-memory origin under one lock CodeRabbit (#3252): reload_dictionary() wrote DICT and DICT_IN_MEMORY under two separate locks. If install_dictionary() ran between them, the final state could hold an in-memory dictionary with the flag left false, so installed_path() would report a same-named disk file instead of None. Combined both into one DictState struct behind a single RwLock in both ru and cs, so install_dictionary() and reload_dictionary() each update the dictionary and its origin in one critical section. Didn't add a dedicated concurrency test: genuinely proving a race's absence needs real multi-threaded stress testing, and the previous commit on this PR already found that tests directly exercising install_dictionary()/reload_dictionary() concurrency in this crate's shared global state cause real, reproducible flakiness in unrelated tests. The fix is correct by construction (one lock acquisition around both field writes) and all 222 existing proof tests, run 15 times in a row, stay green. Co-Authored-By: Claude Sonnet 5 --- crates/proof/src/cs/mod.rs | 31 ++++++++++++++++++------------- crates/proof/src/ru/mod.rs | 31 ++++++++++++++++++------------- 2 files changed, 36 insertions(+), 26 deletions(-) diff --git a/crates/proof/src/cs/mod.rs b/crates/proof/src/cs/mod.rs index ef560be46..af743406d 100644 --- a/crates/proof/src/cs/mod.rs +++ b/crates/proof/src/cs/mod.rs @@ -13,13 +13,16 @@ use crate::affix::AffixDict; pub use suggest::suggest; -/// The Czech dictionary: `None` until first use, then the installed one (or an empty one). -static DICT: RwLock>> = RwLock::new(None); +/// The Czech dictionary and whether it came from [`install_dictionary`] rather than a file on +/// disk (it then has no file of its own to report from [`installed_path`], even if a matching +/// file also happens to exist): one lock, so a reload and an install can't interleave their two +/// fields into an inconsistent combination. +static DICT: RwLock = RwLock::new(DictState { dict: None, in_memory: false }); -/// Whether `DICT` holds a dictionary given with [`install_dictionary`] rather than one found on -/// disk: it has no file of its own to report from [`installed_path`], even if a matching file -/// happens to exist too. -static DICT_IN_MEMORY: RwLock = RwLock::new(false); +struct DictState { + dict: Option>, + in_memory: bool, +} /// File names of a Czech Hunspell dictionary. const NAMES: &[&str] = &["cs_CZ", "cs-CZ", "cs"]; @@ -28,13 +31,13 @@ const NAMES: &[&str] = &["cs_CZ", "cs-CZ", "cs"]; /// [`install_dictionary`], or an empty one when none is available (Czech spelling is off /// then; see [`has_dictionary`]). pub fn dictionary() -> Arc { - if let Some(d) = DICT.read().unwrap_or_else(|e| e.into_inner()).as_ref() { + if let Some(d) = DICT.read().unwrap_or_else(|e| e.into_inner()).dict.as_ref() { return d.clone(); } let loaded = Arc::new(load_installed()); let mut slot = DICT.write().unwrap_or_else(|e| e.into_inner()); // Another thread (or an install) may have filled it meanwhile: that one wins. - slot.get_or_insert(loaded).clone() + slot.dict.get_or_insert(loaded).clone() } fn load_installed() -> AffixDict { @@ -52,16 +55,18 @@ fn load_installed() -> AffixDict { /// UTF-8), for example one the user picked. On error the current dictionary stays. pub fn install_dictionary(aff: &str, dic: &str) -> Result<(), String> { let d = AffixDict::from_hunspell(aff, dic, &[])?; - *DICT.write().unwrap_or_else(|e| e.into_inner()) = Some(Arc::new(d)); - *DICT_IN_MEMORY.write().unwrap_or_else(|e| e.into_inner()) = true; + let mut slot = DICT.write().unwrap_or_else(|e| e.into_inner()); + slot.dict = Some(Arc::new(d)); + slot.in_memory = true; Ok(()) } /// Forget the loaded dictionary, so the next check looks for an installed one again (after /// [`crate::hunspell::set_dictionary_dir`] or a dictionary was installed). pub fn reload_dictionary() { - *DICT.write().unwrap_or_else(|e| e.into_inner()) = None; - *DICT_IN_MEMORY.write().unwrap_or_else(|e| e.into_inner()) = false; + let mut slot = DICT.write().unwrap_or_else(|e| e.into_inner()); + slot.dict = None; + slot.in_memory = false; } /// The `.dic` file of the installed Czech dictionary, searching the dictionaries folder and @@ -69,7 +74,7 @@ pub fn reload_dictionary() { /// none. A dictionary given with [`install_dictionary`] has no file and isn't reported, even if a /// matching file also happens to exist. pub fn installed_path() -> Option { - if *DICT_IN_MEMORY.read().unwrap_or_else(|e| e.into_inner()) { + if DICT.read().unwrap_or_else(|e| e.into_inner()).in_memory { return None; } crate::hunspell::find(NAMES).map(|(_, dic)| dic) diff --git a/crates/proof/src/ru/mod.rs b/crates/proof/src/ru/mod.rs index caba4f8aa..4b1ffe093 100644 --- a/crates/proof/src/ru/mod.rs +++ b/crates/proof/src/ru/mod.rs @@ -34,13 +34,16 @@ use crate::affix::AffixDict; pub use suggest::suggest; -/// The Russian dictionary: `None` until first use, then the installed one (or an empty one). -static DICT: RwLock>> = RwLock::new(None); +/// The Russian dictionary and whether it came from [`install_dictionary`] rather than a file on +/// disk (it then has no file of its own to report from [`installed_path`], even if a matching +/// file also happens to exist): one lock, so a reload and an install can't interleave their two +/// fields into an inconsistent combination. +static DICT: RwLock = RwLock::new(DictState { dict: None, in_memory: false }); -/// Whether `DICT` holds a dictionary given with [`install_dictionary`] rather than one found on -/// disk: it has no file of its own to report from [`installed_path`], even if a matching file -/// happens to exist too. -static DICT_IN_MEMORY: RwLock = RwLock::new(false); +struct DictState { + dict: Option>, + in_memory: bool, +} /// File names of a Russian Hunspell dictionary. const NAMES: &[&str] = &["ru_RU", "ru-RU", "ru"]; @@ -49,13 +52,13 @@ const NAMES: &[&str] = &["ru_RU", "ru-RU", "ru"]; /// [`install_dictionary`], or an empty one when none is available (Russian spelling is off then; /// see [`has_dictionary`]). pub fn dictionary() -> Arc { - if let Some(d) = DICT.read().unwrap_or_else(|e| e.into_inner()).as_ref() { + if let Some(d) = DICT.read().unwrap_or_else(|e| e.into_inner()).dict.as_ref() { return d.clone(); } let loaded = Arc::new(load_installed()); let mut slot = DICT.write().unwrap_or_else(|e| e.into_inner()); // Another thread (or an install) may have filled it meanwhile: that one wins. - slot.get_or_insert(loaded).clone() + slot.dict.get_or_insert(loaded).clone() } fn load_installed() -> AffixDict { @@ -73,16 +76,18 @@ fn load_installed() -> AffixDict { /// for example one the user picked. On error the current dictionary stays. pub fn install_dictionary(aff: &str, dic: &str) -> Result<(), String> { let d = AffixDict::from_hunspell(aff, dic, &[])?; - *DICT.write().unwrap_or_else(|e| e.into_inner()) = Some(Arc::new(d)); - *DICT_IN_MEMORY.write().unwrap_or_else(|e| e.into_inner()) = true; + let mut slot = DICT.write().unwrap_or_else(|e| e.into_inner()); + slot.dict = Some(Arc::new(d)); + slot.in_memory = true; Ok(()) } /// Forget the loaded dictionary, so the next check looks for an installed one again (after /// [`crate::hunspell::set_dictionary_dir`] or a dictionary was installed). pub fn reload_dictionary() { - *DICT.write().unwrap_or_else(|e| e.into_inner()) = None; - *DICT_IN_MEMORY.write().unwrap_or_else(|e| e.into_inner()) = false; + let mut slot = DICT.write().unwrap_or_else(|e| e.into_inner()); + slot.dict = None; + slot.in_memory = false; } /// The `.dic` file of the installed Russian dictionary, searching the dictionaries folder and @@ -90,7 +95,7 @@ pub fn reload_dictionary() { /// none. A dictionary given with [`install_dictionary`] has no file and isn't reported, even if a /// matching file also happens to exist. pub fn installed_path() -> Option { - if *DICT_IN_MEMORY.read().unwrap_or_else(|e| e.into_inner()) { + if DICT.read().unwrap_or_else(|e| e.into_inner()).in_memory { return None; } crate::hunspell::find(NAMES).map(|(_, dic)| dic)