From 63e00d8690501e07ca8860409eafa3b81640c445 Mon Sep 17 00:00:00 2001 From: Lewis Russell Date: Tue, 26 May 2026 12:24:25 +0100 Subject: [PATCH] perf: avoid unrelated multi-return correlation Only run return-overload correlation when the compared variable histories refer to results from the same multi-return call expression. Same-call cases like `local ok, value = pcall(fn)` still narrow through the shared overload row. Different assignments can both have multi-return refs without sharing a return tuple. Treating those unrelated call histories as correlation candidates sends the analyzer into expensive subqueries and does not improve narrowing accuracy. Add ntest as a dev dependency so the issue #1094 stress regression has a bounded timeout. Fixes #1094 Assisted-by: Codex --- Cargo.lock | 178 +++++++++++++----- Cargo.toml | 1 + crates/emmylua_code_analysis/Cargo.toml | 1 + .../src/compilation/test/flow.rs | 33 ++++ .../src/db_index/flow/flow_tree.rs | 24 ++- .../narrow/condition_flow/binary_flow.rs | 8 +- .../narrow/condition_flow/correlated_flow.rs | 4 +- .../infer/narrow/condition_flow/mod.rs | 3 +- 8 files changed, 197 insertions(+), 55 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d7e9be17c..333319f65 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -273,7 +273,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -414,7 +414,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn", + "syn 2.0.117", ] [[package]] @@ -425,7 +425,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -496,7 +496,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -509,7 +509,7 @@ checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005" name = "edit_version" version = "0.1.0" dependencies = [ - "toml_edit", + "toml_edit 0.22.27", ] [[package]] @@ -569,6 +569,7 @@ dependencies = [ "itertools 0.14.0", "log", "luars", + "ntest", "percent-encoding", "regex", "reqwest", @@ -592,7 +593,7 @@ version = "0.5.0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -630,7 +631,7 @@ dependencies = [ "serde_yaml_ng", "similar", "smol_str", - "toml_edit", + "toml_edit 0.22.27", "walkdir", ] @@ -922,7 +923,7 @@ checksum = "c31d9f07c9c19b855faebf71637be3b43f8e13a518aece5d61a3beee7710b4ef" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -937,7 +938,7 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap 2.10.0", + "indexmap 2.14.0", "slab", "tokio", "tokio-util", @@ -1302,13 +1303,14 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.10.0" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.15.4", + "hashbrown 0.17.0", "serde", + "serde_core", ] [[package]] @@ -1520,7 +1522,7 @@ checksum = "c72a649276cb0d9349bd29ae2695662aa6d4a1c6cc2ecf4f060e0e292eac291f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1625,6 +1627,39 @@ dependencies = [ "serde", ] +[[package]] +name = "ntest" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54d1aa56874c2152c24681ed0df95ee155cc06c5c61b78e2d1e8c0cae8bc5326" +dependencies = [ + "ntest_test_cases", + "ntest_timeout", +] + +[[package]] +name = "ntest_test_cases" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6913433c6319ef9b2df316bb8e3db864a41724c2bb8f12555e07dc4ec69d3db1" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ntest_timeout" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9224be3459a0c1d6e9b0f42ab0e76e98b29aef5aba33c0487dfcf47ea08b5150" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "num-conv" version = "0.1.0" @@ -1675,7 +1710,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1781,7 +1816,7 @@ dependencies = [ "pest_meta", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1883,6 +1918,15 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit 0.25.11+spec-1.1.0", +] + [[package]] name = "proc-macro2" version = "1.0.106" @@ -1974,7 +2018,7 @@ checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2097,7 +2141,7 @@ dependencies = [ "serde", "serde_json", "serde_yaml", - "syn", + "syn 2.0.117", ] [[package]] @@ -2257,7 +2301,7 @@ dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn", + "syn 2.0.117", ] [[package]] @@ -2316,7 +2360,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2327,7 +2371,7 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2351,7 +2395,7 @@ checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2373,7 +2417,7 @@ dependencies = [ "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.10.0", + "indexmap 2.14.0", "schemars 0.9.0", "schemars 1.0.4", "serde", @@ -2392,7 +2436,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2401,7 +2445,7 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.10.0", + "indexmap 2.14.0", "itoa", "ryu", "serde", @@ -2414,7 +2458,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b4db627b98b36d4203a7b458cf3573730f2bb591b28871d916dfa9efabfd41f" dependencies = [ - "indexmap 2.10.0", + "indexmap 2.14.0", "itoa", "ryu", "serde", @@ -2529,6 +2573,17 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.117" @@ -2557,7 +2612,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2658,7 +2713,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2669,7 +2724,7 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2738,7 +2793,7 @@ checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -2782,8 +2837,8 @@ checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" dependencies = [ "serde", "serde_spanned", - "toml_datetime", - "toml_edit", + "toml_datetime 0.6.11", + "toml_edit 0.22.27", ] [[package]] @@ -2795,18 +2850,48 @@ dependencies = [ "serde", ] +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + [[package]] name = "toml_edit" version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ - "indexmap 2.10.0", + "indexmap 2.14.0", "serde", "serde_spanned", - "toml_datetime", + "toml_datetime 0.6.11", "toml_write", - "winnow", + "winnow 0.7.12", +] + +[[package]] +name = "toml_edit" +version = "0.25.11+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" +dependencies = [ + "indexmap 2.14.0", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "winnow 1.0.3", +] + +[[package]] +name = "toml_parser" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +dependencies = [ + "winnow 1.0.3", ] [[package]] @@ -3036,7 +3121,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn", + "syn 2.0.117", "wasm-bindgen-shared", ] @@ -3071,7 +3156,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3162,7 +3247,7 @@ checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -3173,7 +3258,7 @@ checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -3408,6 +3493,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "winnow" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" +dependencies = [ + "memchr", +] + [[package]] name = "wit-bindgen-rt" version = "0.39.0" @@ -3443,7 +3537,7 @@ checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", "synstructure", ] @@ -3464,7 +3558,7 @@ checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -3484,7 +3578,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", "synstructure", ] @@ -3524,7 +3618,7 @@ checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 2a2be82d4..2b3c79140 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,6 +53,7 @@ ansi_term = "0.12.1" num-traits = { version = "0.2", features = ["std"] } mimalloc = { version = "0.1.50" } googletest = "0.14.2" +ntest = "0.9.5" unicode-general-category = "1.0.0" luars = { version = "0.23.0", features = ["serde", "sandbox"] } # request's default-tls feature pulls in a dependency on aws-lc-rs, diff --git a/crates/emmylua_code_analysis/Cargo.toml b/crates/emmylua_code_analysis/Cargo.toml index fc2b71e68..24f7a0696 100644 --- a/crates/emmylua_code_analysis/Cargo.toml +++ b/crates/emmylua_code_analysis/Cargo.toml @@ -16,6 +16,7 @@ include = [ ] [dev-dependencies] googletest.workspace = true +ntest.workspace = true # Inherit workspace lints configuration [lints] diff --git a/crates/emmylua_code_analysis/src/compilation/test/flow.rs b/crates/emmylua_code_analysis/src/compilation/test/flow.rs index 903a13d46..4bcd66dd8 100644 --- a/crates/emmylua_code_analysis/src/compilation/test/flow.rs +++ b/crates/emmylua_code_analysis/src/compilation/test/flow.rs @@ -2,6 +2,7 @@ mod test { use crate::{DiagnosticCode, LuaType, VirtualWorkspace}; use emmylua_parser::{LuaAstToken, LuaLocalName}; + use ntest::timeout; const STACKED_TYPE_GUARDS: usize = 180; const LARGE_LINEAR_ASSIGNMENT_STEPS: usize = 2048; @@ -467,6 +468,38 @@ mod test { assert_eq!(ws.humanize_type(after_assign), "integer"); } + #[test] + #[timeout(5000)] + fn test_issue_1094_self_call_fallback_stress() { + let mut ws = VirtualWorkspace::new(); + let repeated_calls = (2..=30) + .map(|i| format!("if count == 0 then count = self:api{i}():api(code) end\n")) + .collect::(); + let block = format!( + r#" + function class(className, super) + end + + local Test = class("Test") + + function Test:api1(code, isBind) + local count = self:api():api(code) + {repeated_calls} + return count + end + "# + ); + + let file_id = ws.def(&block); + assert!( + ws.analysis + .compilation + .get_semantic_model(file_id) + .is_some(), + "expected semantic model for repeated self-call fallback stress repro" + ); + } + #[test] fn test_issue_1028_maxwellhome_like_large_array_builds_semantic_model() { let mut ws = VirtualWorkspace::new(); diff --git a/crates/emmylua_code_analysis/src/db_index/flow/flow_tree.rs b/crates/emmylua_code_analysis/src/db_index/flow/flow_tree.rs index 677ec7768..6b17ff757 100644 --- a/crates/emmylua_code_analysis/src/db_index/flow/flow_tree.rs +++ b/crates/emmylua_code_analysis/src/db_index/flow/flow_tree.rs @@ -51,8 +51,28 @@ impl FlowTree { self.decl_bind_expr_ref.get(decl_id).cloned() } - pub fn has_decl_multi_return_refs(&self, decl_id: &LuaDeclId) -> bool { - self.decl_multi_return_ref.contains_key(decl_id) + pub fn has_shared_multi_return_refs( + &self, + left_decl_id: &LuaDeclId, + right_decl_id: &LuaDeclId, + ) -> bool { + let Some(left_refs) = self.decl_multi_return_ref.get(left_decl_id) else { + return false; + }; + let Some(right_refs) = self.decl_multi_return_ref.get(right_decl_id) else { + return false; + }; + + left_refs + .iter() + .filter_map(|entry| entry.reference.as_ref()) + .any(|left_ref| { + let left_call_id = left_ref.call_expr.get_syntax_id(); + right_refs + .iter() + .filter_map(|entry| entry.reference.as_ref()) + .any(|right_ref| right_ref.call_expr.get_syntax_id() == left_call_id) + }) } /// Chooses the search roots used to resolve correlated multi-return refs. diff --git a/crates/emmylua_code_analysis/src/semantic/infer/narrow/condition_flow/binary_flow.rs b/crates/emmylua_code_analysis/src/semantic/infer/narrow/condition_flow/binary_flow.rs index 0a0fc35ea..871ffc618 100644 --- a/crates/emmylua_code_analysis/src/semantic/infer/narrow/condition_flow/binary_flow.rs +++ b/crates/emmylua_code_analysis/src/semantic/infer/narrow/condition_flow/binary_flow.rs @@ -274,9 +274,7 @@ fn maybe_type_guard_binary_action( let Some(target_decl_id) = var_ref_id.get_decl_id_ref() else { return Ok(None); }; - if !tree.has_decl_multi_return_refs(&discriminant_decl_id) - || !tree.has_decl_multi_return_refs(&target_decl_id) - { + if !tree.has_shared_multi_return_refs(&discriminant_decl_id, &target_decl_id) { return Ok(None); } @@ -377,9 +375,7 @@ fn get_var_eq_condition_action( let Some(target_decl_id) = var_ref_id.get_decl_id_ref() else { return Ok(ConditionFlowAction::Continue); }; - if !tree.has_decl_multi_return_refs(&discriminant_decl_id) - || !tree.has_decl_multi_return_refs(&target_decl_id) - { + if !tree.has_shared_multi_return_refs(&discriminant_decl_id, &target_decl_id) { return Ok(ConditionFlowAction::Continue); } let antecedent_flow_id = get_single_antecedent(flow_node)?; diff --git a/crates/emmylua_code_analysis/src/semantic/infer/narrow/condition_flow/correlated_flow.rs b/crates/emmylua_code_analysis/src/semantic/infer/narrow/condition_flow/correlated_flow.rs index 78c464852..a2d06dd5f 100644 --- a/crates/emmylua_code_analysis/src/semantic/infer/narrow/condition_flow/correlated_flow.rs +++ b/crates/emmylua_code_analysis/src/semantic/infer/narrow/condition_flow/correlated_flow.rs @@ -146,9 +146,7 @@ pub(in crate::semantic::infer::narrow) fn prepare_var_from_return_overload_condi let Some(target_decl_id) = var_ref_id.get_decl_id_ref() else { return Ok(ConditionFlowAction::Continue); }; - if !tree.has_decl_multi_return_refs(&discriminant_decl_id) - || !tree.has_decl_multi_return_refs(&target_decl_id) - { + if !tree.has_shared_multi_return_refs(&discriminant_decl_id, &target_decl_id) { return Ok(ConditionFlowAction::Continue); } diff --git a/crates/emmylua_code_analysis/src/semantic/infer/narrow/condition_flow/mod.rs b/crates/emmylua_code_analysis/src/semantic/infer/narrow/condition_flow/mod.rs index 27f0b4753..16e5c20b4 100644 --- a/crates/emmylua_code_analysis/src/semantic/infer/narrow/condition_flow/mod.rs +++ b/crates/emmylua_code_analysis/src/semantic/infer/narrow/condition_flow/mod.rs @@ -554,8 +554,7 @@ pub(super) fn get_type_at_condition_flow( }; if let Some(target_decl_id) = var_ref_id.get_decl_id_ref() - && tree.has_decl_multi_return_refs(&decl_id) - && tree.has_decl_multi_return_refs(&target_decl_id) + && tree.has_shared_multi_return_refs(&decl_id, &target_decl_id) { let antecedent_flow_id = get_single_antecedent(flow_node)?; let fallback_expr = tree