diff --git a/kani-compiler/src/kani_middle/resolve.rs b/kani-compiler/src/kani_middle/resolve.rs index 30d951e22e65..9b46eb2e7d09 100644 --- a/kani-compiler/src/kani_middle/resolve.rs +++ b/kani-compiler/src/kani_middle/resolve.rs @@ -708,6 +708,8 @@ fn is_item_name(tcx: TyCtxt, item: DefId, name: &str) -> bool { last == name } +/// Use this when we don't just care about the item name matching (c.f. is_item_name), +/// but also if the generic arguments are the same, e.g. ::unchecked_add. fn is_item_name_with_generic_args( tcx: TyCtxt, item: DefId, @@ -715,6 +717,54 @@ fn is_item_name_with_generic_args( name: &str, ) -> bool { let item_path = tcx.def_path_str(item); - let all_but_base_type = item_path.find("::").map_or("", |idx| &item_path[idx..]); - all_but_base_type == format!("{generic_args}::{name}") + last_two_items_of_path_match(&item_path, generic_args, name) +} + +// This is just a helper function for is_item_name_with_generic_args. +// It's in a separate function so we can unit-test it without a mock TyCtxt or DefIds. +fn last_two_items_of_path_match(item_path: &str, generic_args: &str, name: &str) -> bool { + let parts: Vec<&str> = item_path.split("::").collect(); + + if parts.len() < 2 { + return false; + } + + let actual_last_two = + format!("{}{}{}{}", "::", parts[parts.len() - 2], "::", parts[parts.len() - 1]); + + let last_two = format!("{}{}{}", generic_args, "::", name); + + // The last two components of the item_path should be the same as ::{generic_args}::{name} + last_two == actual_last_two +} + +#[cfg(test)] +mod tests { + mod simple_last_two_items_of_path_match { + use crate::kani_middle::resolve::last_two_items_of_path_match; + + #[test] + fn length_one_item_prefix() { + let generic_args = "::"; + let name = "unchecked_add"; + let item_path = format!("NonZero{}::{}", generic_args, name); + assert!(last_two_items_of_path_match(&item_path, generic_args, name)) + } + + #[test] + fn length_three_item_prefix() { + let generic_args = "::"; + let name = "unchecked_add"; + let item_path = format!("core::num::NonZero{}::{}", generic_args, name); + assert!(last_two_items_of_path_match(&item_path, generic_args, name)) + } + + #[test] + fn wrong_generic_arg() { + let generic_args = "::"; + let name = "unchecked_add"; + let item_path = format!("core::num::NonZero{}::{}", "::", name); + assert!(!last_two_items_of_path_match(&item_path, generic_args, name)) + } + } } diff --git a/tests/kani/FunctionContracts/multiple_inherent_impls.rs b/tests/kani/FunctionContracts/multiple_inherent_impls.rs index cdd25337cd36..76ee51d99590 100644 --- a/tests/kani/FunctionContracts/multiple_inherent_impls.rs +++ b/tests/kani/FunctionContracts/multiple_inherent_impls.rs @@ -26,3 +26,37 @@ fn verify_unchecked_mul_ambiguous_path() { let x: NonZero = NonZero(-1); x.unchecked_mul(-2); } + +// Test that the resolution still works if the function in question is nested inside multiple modules, +// i.e. the absolute path to the function can be arbitrarily long. +// As long as the generic arguments and function name match, resolution should succeed. +// This mimics the actual structure of NonZero relative to its harnesses in the standard library. +pub mod num { + pub mod negative { + pub struct NegativeNumber(pub T); + + impl NegativeNumber { + #[kani::requires(self.0.checked_mul(x).is_some())] + pub fn unchecked_mul(self, x: i32) -> i32 { + self.0 * x + } + } + + impl NegativeNumber { + #[kani::requires(self.0.checked_mul(x).is_some())] + pub fn unchecked_mul(self, x: i16) -> i16 { + self.0 * x + } + } + } +} + +mod verify { + use crate::num::negative::*; + + #[kani::proof_for_contract(NegativeNumber::::unchecked_mul)] + fn verify_unchecked_mul_ambiguous_path() { + let x: NegativeNumber = NegativeNumber(-1); + x.unchecked_mul(-2); + } +}