diff --git a/Cargo.lock b/Cargo.lock index 21e0a824..f703a4b8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -130,7 +130,7 @@ checksum = "869119e97797867fd90f5e22af7d0bd274bd4635ebb9eb68c04f3f513ae6c412" [[package]] name = "cargo-semver-checks" -version = "0.4.4" +version = "0.5.0" dependencies = [ "anyhow", "atty", diff --git a/Cargo.toml b/Cargo.toml index ff305c1c..d1df6161 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cargo-semver-checks" -version = "0.4.4" +version = "0.5.0" edition = "2021" authors = ["Predrag Gruevski "] license = "Apache-2.0" diff --git a/semver_tests/src/test_cases/item_missing.rs b/semver_tests/src/test_cases/item_missing.rs index 2581f0de..1f7a5d63 100644 --- a/semver_tests/src/test_cases/item_missing.rs +++ b/semver_tests/src/test_cases/item_missing.rs @@ -21,4 +21,24 @@ impl Foo { #[cfg(not(feature = "inherent_method_missing"))] pub fn will_be_removed_method(&self) {} + + #[cfg(not(feature = "inherent_method_missing"))] + pub fn moved_trait_provided_method(&self) {} + + #[cfg(not(feature = "inherent_method_missing"))] + pub fn moved_method(&self) {} +} + +// Moving an inherent method to an implemented trait should not be a breaking change, +// both when the method is defined inside the trait and when it's implemented externally. +#[cfg(feature = "inherent_method_missing")] +pub trait Bar { + fn moved_trait_provided_method(&self) {} + + fn moved_method(&self); +} + +#[cfg(feature = "inherent_method_missing")] +impl Bar for Foo { + fn moved_method(&self) {} } diff --git a/src/adapter.rs b/src/adapter.rs index d3cfbf37..c818478c 100644 --- a/src/adapter.rs +++ b/src/adapter.rs @@ -1,6 +1,8 @@ -use std::sync::Arc; +use std::{collections::BTreeSet, sync::Arc}; -use rustdoc_types::{Crate, Enum, Function, Impl, Item, Method, Span, Struct, Type, Variant}; +use rustdoc_types::{ + Crate, Enum, Function, Id, Impl, Item, ItemEnum, Method, Span, Struct, Type, Variant, +}; use trustfall_core::{ interpreter::{Adapter, DataContext, InterpretedQuery}, ir::{EdgeParameters, Eid, FieldValue, Vid}, @@ -714,7 +716,33 @@ impl<'a> Adapter<'a> for RustdocAdapter<'a> { }; let impl_token = token.as_impl().expect("not an Impl token"); - Box::new(impl_token.items.iter().filter_map(move |item_id| { + let provided_methods: Box> = if impl_token.provided_trait_methods.is_empty() { + Box::new(std::iter::empty()) + } else { + let method_names: BTreeSet<&str> = impl_token.provided_trait_methods.iter().map(|x| x.as_str()).collect(); + + let trait_type = impl_token.trait_.as_ref().expect("no trait but provided_trait_methods was non-empty"); + let trait_item = match trait_type { + Type::ResolvedPath { name: _, id, args: _, param_names: _ } => { + &item_index[id] + } + _ => unimplemented!("found provided_trait_methods when the trait was not a ResolvedPath: {trait_type:?}"), + }; + + if let ItemEnum::Trait(trait_item) = &trait_item.inner { + Box::new(trait_item.items.iter().filter(move |item_id| { + let next_item = &item_index[*item_id]; + if let Some(name) = &next_item.name { + method_names.contains(name.as_str()) + } else { + false + } + })) + } else { + unreachable!("found a non-trait type {trait_item:?}"); + } + }; + Box::new(provided_methods.chain(impl_token.items.iter()).filter_map(move |item_id| { let next_item = &item_index[item_id]; match &next_item.inner { rustdoc_types::ItemEnum::Method(..) => { diff --git a/src/queries/inherent_method_missing.ron b/src/queries/inherent_method_missing.ron index 00597923..2584db86 100644 --- a/src/queries/inherent_method_missing.ron +++ b/src/queries/inherent_method_missing.ron @@ -41,9 +41,12 @@ SemverQuery( path @filter(op: "=", value: ["%path"]) } - inherent_impl @fold @transform(op: "count") @filter(op: "=", value: ["$zero"]) { + # We use "impl" instead of "inherent_impl" here because moving + # an inherently-implemented method to a trait is not necessarily + # a breaking change, so we don't want to report it. + impl @fold @transform(op: "count") @filter(op: "=", value: ["$zero"]) { method { - visibility_limit @filter(op: "=", value: ["$public"]) + visibility_limit @filter(op: "one_of", value: ["$public_or_default"]) name @filter(op: "=", value: ["%method_name"]) } } @@ -54,6 +57,7 @@ SemverQuery( }"#, arguments: { "public": "public", + "public_or_default": ["public", "default"], "zero": 0, }, error_message: "A publicly-visible method or associated fn is no longer available under its prior name. It may have been renamed or removed entirely.",