1use std::{collections::BTreeMap, sync::Arc};
2
3use ron::extensions::Extensions;
4use serde::{Deserialize, Serialize};
5use trustfall::{FieldValue, TransparentValue};
6
7use crate::ReleaseType;
8
9#[non_exhaustive]
10#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
11pub enum RequiredSemverUpdate {
12 #[serde(alias = "minor")]
13 Minor,
14 #[serde(alias = "major")]
15 Major,
16}
17
18impl RequiredSemverUpdate {
19 pub fn as_str(&self) -> &'static str {
20 match self {
21 Self::Major => "major",
22 Self::Minor => "minor",
23 }
24 }
25}
26
27impl From<RequiredSemverUpdate> for ReleaseType {
28 fn from(value: RequiredSemverUpdate) -> Self {
29 match value {
30 RequiredSemverUpdate::Major => Self::Major,
31 RequiredSemverUpdate::Minor => Self::Minor,
32 }
33 }
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
38pub enum LintLevel {
39 #[serde(alias = "allow")]
41 Allow,
42 #[serde(alias = "warn")]
44 Warn,
45 #[serde(alias = "deny")]
47 Deny,
48}
49
50impl LintLevel {
51 pub fn as_str(self) -> &'static str {
52 match self {
53 LintLevel::Allow => "allow",
54 LintLevel::Warn => "warn",
55 LintLevel::Deny => "deny",
56 }
57 }
58}
59
60#[derive(Debug, Clone, Copy, PartialEq, Eq)]
62pub enum ActualSemverUpdate {
63 Major,
64 Minor,
65 Patch,
66 NotChanged,
67}
68
69impl ActualSemverUpdate {
70 pub(crate) fn supports_requirement(&self, required: RequiredSemverUpdate) -> bool {
71 match (*self, required) {
72 (ActualSemverUpdate::Major, _) => true,
73 (ActualSemverUpdate::Minor, RequiredSemverUpdate::Major) => false,
74 (ActualSemverUpdate::Minor, _) => true,
75 (_, _) => false,
76 }
77 }
78}
79
80impl From<ReleaseType> for ActualSemverUpdate {
81 fn from(value: ReleaseType) -> Self {
82 match value {
83 ReleaseType::Major => Self::Major,
84 ReleaseType::Minor => Self::Minor,
85 ReleaseType::Patch => Self::Patch,
86 }
87 }
88}
89
90#[non_exhaustive]
93#[derive(Debug, Clone, Serialize, Deserialize)]
94pub struct SemverQuery {
95 pub id: String,
96
97 pub(crate) human_readable_name: String,
98
99 pub description: String,
100
101 pub required_update: RequiredSemverUpdate,
102
103 pub lint_level: LintLevel,
105
106 #[serde(default)]
107 pub reference: Option<String>,
108
109 #[serde(default)]
110 pub reference_link: Option<String>,
111
112 pub(crate) query: String,
113
114 #[serde(default)]
115 pub(crate) arguments: BTreeMap<String, TransparentValue>,
116
117 pub(crate) error_message: String,
121
122 #[serde(default)]
125 pub(crate) per_result_error_template: Option<String>,
126
127 #[serde(default)]
130 pub witness: Option<Witness>,
131}
132
133impl SemverQuery {
134 pub fn from_ron_str(query_text: &str) -> ron::Result<Self> {
138 let mut deserializer = ron::Deserializer::from_str_with_options(
139 query_text,
140 &ron::Options::default().with_default_extension(Extensions::IMPLICIT_SOME),
141 )?;
142
143 Self::deserialize(&mut deserializer)
144 }
145
146 pub fn all_queries() -> BTreeMap<String, SemverQuery> {
147 let mut queries = BTreeMap::default();
148 for (id, query_text) in get_queries() {
149 let query = Self::from_ron_str(query_text).unwrap_or_else(|e| {
150 panic!(
151 "\
152 Failed to parse a query: {e}
153 ```ron
154 {query_text}
155 ```"
156 );
157 });
158 assert_eq!(id, query.id, "Query id must match file name");
159 let id_conflict = queries.insert(query.id.clone(), query);
160 assert!(id_conflict.is_none(), "{id_conflict:?}");
161 }
162
163 queries
164 }
165}
166
167#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize)]
169#[serde(rename_all = "kebab-case")]
170pub struct QueryOverride {
171 #[serde(default)]
176 pub required_update: Option<RequiredSemverUpdate>,
177
178 #[serde(default)]
183 pub lint_level: Option<LintLevel>,
184}
185
186pub type OverrideMap = BTreeMap<String, QueryOverride>;
188
189#[derive(Debug, Clone, Default, PartialEq, Eq)]
196pub struct OverrideStack(Vec<OverrideMap>);
197
198impl OverrideStack {
199 #[must_use]
201 pub fn new() -> Self {
202 Self(Vec::new())
203 }
204
205 pub fn push(&mut self, item: &OverrideMap) {
210 self.0.push(item.clone());
211 }
212
213 #[must_use]
217 pub fn effective_lint_level(&self, query: &SemverQuery) -> LintLevel {
218 self.0
219 .iter()
220 .rev()
221 .find_map(|x| x.get(&query.id).and_then(|y| y.lint_level))
222 .unwrap_or(query.lint_level)
223 }
224
225 #[must_use]
229 pub fn effective_required_update(&self, query: &SemverQuery) -> RequiredSemverUpdate {
230 self.0
231 .iter()
232 .rev()
233 .find_map(|x| x.get(&query.id).and_then(|y| y.required_update))
234 .unwrap_or(query.required_update)
235 }
236}
237
238#[derive(Debug, Clone, Serialize, Deserialize)]
249pub struct Witness {
250 pub hint_template: String,
268
269 #[serde(default)]
290 pub witness_template: Option<String>,
291
292 #[serde(default)]
297 pub witness_query: Option<WitnessQuery>,
298}
299
300#[derive(Debug, Clone, Serialize, Deserialize)]
304pub struct WitnessQuery {
305 pub query: String,
307
308 #[serde(default)]
313 pub arguments: BTreeMap<Arc<str>, InheritedValue>,
314}
315
316impl WitnessQuery {
317 pub fn inherit_arguments_from(
322 &self,
323 source_map: &BTreeMap<std::sync::Arc<str>, FieldValue>,
324 ) -> anyhow::Result<BTreeMap<Arc<str>, FieldValue>> {
325 let mut mapped = BTreeMap::new();
326
327 for (key, value) in self.arguments.iter() {
328 let mapped_value = match value {
329 InheritedValue::Inherited { inherit } => source_map
331 .get(inherit.as_str())
332 .cloned()
333 .ok_or(anyhow::anyhow!(
334 "inherited output key `{inherit}` does not exist in {source_map:?}"
335 ))?,
336 InheritedValue::Constant(value) => value.clone().into(),
338 };
339 mapped.insert(Arc::clone(key), mapped_value);
340 }
341
342 Ok(mapped)
343 }
344}
345
346#[derive(Debug, Clone, Serialize, Deserialize)]
349#[serde(untagged, deny_unknown_fields)]
350pub enum InheritedValue {
351 Inherited { inherit: String },
353 Constant(TransparentValue),
355}
356
357#[cfg(test)]
358mod tests {
359 use std::borrow::Cow;
360 use std::collections::{BTreeSet, HashMap};
361 use std::ffi::OsStr;
362 use std::path::PathBuf;
363 use std::sync::{Arc, OnceLock};
364 use std::time::SystemTime;
365 use std::{collections::BTreeMap, path::Path};
366
367 use anyhow::Context;
368 use fs_err::PathExt;
369 use rayon::prelude::*;
370 use serde::{Deserialize, Serialize};
371 use toml::Value;
372 use trustfall::{FieldValue, TransparentValue};
373 use trustfall_core::ir::IndexedQuery;
374 use trustfall_rustdoc::{
375 VersionedIndex, VersionedRustdocAdapter, VersionedStorage, load_rustdoc,
376 };
377
378 use crate::query::{
379 InheritedValue, LintLevel, OverrideMap, OverrideStack, QueryOverride, RequiredSemverUpdate,
380 SemverQuery,
381 };
382 use crate::templating::make_handlebars_registry;
383
384 static TEST_CRATE_NAMES: OnceLock<Vec<String>> = OnceLock::new();
385
386 static TEST_CRATE_RUSTDOCS: OnceLock<BTreeMap<String, (VersionedStorage, VersionedStorage)>> =
388 OnceLock::new();
389
390 static TEST_CRATE_INDEXES: OnceLock<
392 BTreeMap<String, (VersionedIndex<'static>, VersionedIndex<'static>)>,
393 > = OnceLock::new();
394
395 fn get_test_crate_names() -> &'static [String] {
396 TEST_CRATE_NAMES.get_or_init(initialize_test_crate_names)
397 }
398
399 fn get_all_test_crates() -> &'static BTreeMap<String, (VersionedStorage, VersionedStorage)> {
400 TEST_CRATE_RUSTDOCS.get_or_init(initialize_test_crate_rustdocs)
401 }
402
403 #[test]
404 fn lint_files_have_matching_ids() {
405 let lints_dir = Path::new("src/lints");
406 let ron_files = collect_ron_files(lints_dir);
407
408 assert!(
409 !ron_files.is_empty(),
410 "expected at least one lint definition in {lints_dir:?}"
411 );
412
413 for path in ron_files {
414 let stem = path
415 .file_stem()
416 .and_then(OsStr::to_str)
417 .expect("lint file must have a valid UTF-8 stem");
418 assert!(
419 is_lower_snake_case(stem),
420 "lint file stem `{stem}` must be lower snake case"
421 );
422
423 let contents = fs_err::read_to_string(&path).expect("failed to read lint file");
424 let query = SemverQuery::from_ron_str(&contents).expect("failed to parse lint");
425 assert_eq!(
426 stem, query.id,
427 "lint id must match file stem for {:?}",
428 path
429 );
430 }
431 }
432
433 fn collect_ron_files(dir: &Path) -> Vec<PathBuf> {
434 let mut result = Vec::new();
435 let mut stack = vec![dir.to_path_buf()];
436
437 while let Some(current) = stack.pop() {
438 for entry in fs_err::read_dir(¤t).expect("failed to read directory") {
439 let entry = entry.expect("failed to read directory entry");
440 let path = entry.path();
441 if entry
442 .file_type()
443 .expect("failed to determine file type")
444 .is_dir()
445 {
446 stack.push(path);
447 } else if path.extension() == Some(OsStr::new("ron")) {
448 result.push(path);
449 }
450 }
451 }
452
453 result.sort();
454 result
455 }
456
457 fn is_lower_snake_case(value: &str) -> bool {
458 !value.is_empty()
459 && value.chars().all(|ch| ch.is_ascii_lowercase() || ch == '_')
460 && !value.starts_with('_')
461 && !value.ends_with('_')
462 && !value.contains("__")
463 }
464
465 fn get_all_test_crate_indexes()
466 -> &'static BTreeMap<String, (VersionedIndex<'static>, VersionedIndex<'static>)> {
467 TEST_CRATE_INDEXES.get_or_init(initialize_test_crate_indexes)
468 }
469
470 fn get_test_crate_indexes(
471 test_crate: &str,
472 ) -> &'static (VersionedIndex<'static>, VersionedIndex<'static>) {
473 &get_all_test_crate_indexes()[test_crate]
474 }
475
476 fn initialize_test_crate_names() -> Vec<String> {
477 std::fs::read_dir("./test_crates/")
478 .expect("directory test_crates/ not found")
479 .map(|dir_entry| dir_entry.expect("failed to list test_crates/"))
480 .filter(|dir_entry| {
481 if !dir_entry
489 .metadata()
490 .expect("failed to retrieve test_crates/* metadata")
491 .is_dir()
492 {
493 return false;
494 }
495
496 let mut test_crate_cargo_toml = dir_entry.path();
497 test_crate_cargo_toml.extend(["old", "Cargo.toml"]);
498 test_crate_cargo_toml.as_path().is_file()
499 })
500 .map(|dir_entry| {
501 String::from(
502 String::from(
503 dir_entry
504 .path()
505 .to_str()
506 .expect("failed to convert dir_entry to String"),
507 )
508 .strip_prefix("./test_crates/")
509 .expect(
510 "the dir_entry doesn't start with './test_crates/', which is unexpected",
511 ),
512 )
513 })
514 .collect()
515 }
516
517 fn initialize_test_crate_rustdocs() -> BTreeMap<String, (VersionedStorage, VersionedStorage)> {
518 get_test_crate_names()
519 .par_iter()
520 .map(|crate_pair| {
521 let old_rustdoc = load_pregenerated_rustdoc(crate_pair.as_str(), "old");
522 let new_rustdoc = load_pregenerated_rustdoc(crate_pair, "new");
523
524 (crate_pair.clone(), (old_rustdoc, new_rustdoc))
525 })
526 .collect()
527 }
528
529 fn initialize_test_crate_indexes()
530 -> BTreeMap<String, (VersionedIndex<'static>, VersionedIndex<'static>)> {
531 get_all_test_crates()
532 .par_iter()
533 .map(|(key, (old_crate, new_crate))| {
534 let old_index = VersionedIndex::from_storage(old_crate);
535 let new_index = VersionedIndex::from_storage(new_crate);
536 (key.clone(), (old_index, new_index))
537 })
538 .collect()
539 }
540
541 fn load_pregenerated_rustdoc(crate_pair: &str, crate_version: &str) -> VersionedStorage {
542 let rustdoc_path =
543 format!("./localdata/test_data/{crate_pair}/{crate_version}/rustdoc.json");
544 let metadata_path =
545 format!("./localdata/test_data/{crate_pair}/{crate_version}/metadata.json");
546 let metadata_text = std::fs::read_to_string(&metadata_path).map_err(|e| anyhow::anyhow!(e).context(
547 format!("Could not load {metadata_path} file. These files are newly required as of PR#1007. Please re-run ./scripts/regenerate_test_rustdocs.sh"))).expect("failed to load metadata");
548 let metadata = serde_json::from_str(&metadata_text).expect("failed to parse metadata file");
549 load_rustdoc(Path::new(&rustdoc_path), Some(metadata))
550 .with_context(|| format!("Could not load {rustdoc_path} file, did you forget to run ./scripts/regenerate_test_rustdocs.sh ?"))
551 .expect("failed to load rustdoc")
552 }
553
554 #[derive(Debug, PartialEq, Eq)]
555 struct PackageManifest {
556 name: String,
557 version: String,
558 edition: String,
559 }
560
561 fn load_package_manifest(manifest_dir: &Path) -> PackageManifest {
562 let manifest_path = manifest_dir.join("Cargo.toml");
563 let manifest_text =
564 fs_err::read_to_string(&manifest_path).expect("failed to load manifest for test crate");
565 let manifest: Value = toml::from_str(&manifest_text)
566 .unwrap_or_else(|e| panic!("failed to parse {}: {e}", manifest_path.display()));
567
568 let package_table = manifest
569 .get("package")
570 .and_then(Value::as_table)
571 .unwrap_or_else(|| {
572 panic!(
573 "manifest at {} missing [package] table",
574 manifest_path.display()
575 )
576 });
577
578 let name = package_table
579 .get("name")
580 .and_then(Value::as_str)
581 .unwrap_or_else(|| {
582 panic!(
583 "manifest at {} missing package.name",
584 manifest_path.display()
585 )
586 })
587 .to_owned();
588 let version = package_table
589 .get("version")
590 .and_then(Value::as_str)
591 .unwrap_or_else(|| {
592 panic!(
593 "manifest at {} missing package.version",
594 manifest_path.display()
595 )
596 })
597 .to_owned();
598 let edition = package_table
599 .get("edition")
600 .and_then(Value::as_str)
601 .unwrap_or_else(|| {
602 panic!(
603 "manifest at {} missing package.edition",
604 manifest_path.display()
605 )
606 })
607 .to_owned();
608
609 let publish_value = package_table.get("publish").unwrap_or_else(|| {
610 panic!(
611 "manifest at {} missing package.publish",
612 manifest_path.display()
613 )
614 });
615 assert!(
616 matches!(publish_value, Value::Boolean(false)),
617 "manifest at {} must set package.publish = false",
618 manifest_path.display()
619 );
620
621 PackageManifest {
622 name,
623 version,
624 edition,
625 }
626 }
627
628 const VERSION_MISMATCH_ALLOWED: &[&str] = &[
629 "semver_trick_self_referential",
630 "trait_missing_with_major_bump",
631 ];
632
633 #[test]
634 fn test_crates_have_consistent_manifests() {
635 let base_path = Path::new("./test_crates");
636 let entries = fs_err::read_dir(base_path).expect("directory test_crates/ not found");
637 let mut checked_pairs = 0usize;
638
639 for entry in entries {
640 let entry = entry.expect("failed to read test_crates entry");
641 let path = entry.path();
642 if !entry
643 .metadata()
644 .expect("failed to read metadata for test_crates entry")
645 .is_dir()
646 {
647 continue;
648 }
649
650 let old_dir = path.join("old");
651 let new_dir = path.join("new");
652 let old_dir_manifest = old_dir.join("Cargo.toml");
653 let new_dir_manifest = new_dir.join("Cargo.toml");
654 if !(old_dir.is_dir()
655 && new_dir.is_dir()
656 && old_dir_manifest.is_file()
657 && new_dir_manifest.is_file())
658 {
659 continue;
660 }
661
662 let dir_name = path
663 .file_name()
664 .and_then(|name| name.to_str())
665 .expect("test_crate directory must be valid UTF-8");
666
667 let old_manifest = load_package_manifest(&old_dir);
668 let new_manifest = load_package_manifest(&new_dir);
669
670 let PackageManifest {
671 name: old_name,
672 version: old_version,
673 edition: old_edition,
674 } = old_manifest;
675 let PackageManifest {
676 name: new_name,
677 version: new_version,
678 edition: new_edition,
679 } = new_manifest;
680
681 assert_eq!(
682 old_name, dir_name,
683 "manifest name must match directory name for {dir_name}"
684 );
685 assert_eq!(
686 new_name, dir_name,
687 "manifest name must match directory name for {dir_name}"
688 );
689 assert_eq!(
690 old_edition, new_edition,
691 "old and new editions differ for {dir_name}"
692 );
693
694 if !VERSION_MISMATCH_ALLOWED.contains(&dir_name) {
695 assert_eq!(
696 old_version, new_version,
697 "old and new versions differ for {dir_name}"
698 );
699 }
700
701 checked_pairs += 1;
702 }
703
704 assert!(
705 checked_pairs > 0,
706 "expected to check at least one test crate pair"
707 );
708 }
709
710 #[test]
711 fn all_queries_are_valid() {
712 let (_baseline, current) = get_test_crate_indexes("template");
713
714 let adapter =
715 VersionedRustdocAdapter::new(current, Some(current)).expect("failed to create adapter");
716 for semver_query in SemverQuery::all_queries().into_values() {
717 let _ = adapter
718 .run_query(&semver_query.query, semver_query.arguments)
719 .expect("not a valid query");
720 }
721 }
722
723 #[test]
724 fn pub_use_handling() {
725 let (_baseline, current) = get_test_crate_indexes("pub_use_handling");
726
727 let query = r#"
728 {
729 Crate {
730 item {
731 ... on Struct {
732 name @filter(op: "=", value: ["$struct"])
733
734 canonical_path {
735 canonical_path: path @output
736 }
737
738 importable_path @fold {
739 path @output
740 }
741 }
742 }
743 }
744 }"#;
745 let mut arguments = BTreeMap::new();
746 arguments.insert("struct", "CheckPubUseHandling");
747
748 let adapter =
749 VersionedRustdocAdapter::new(current, None).expect("could not create adapter");
750
751 let results_iter = adapter
752 .run_query(query, arguments)
753 .expect("failed to run query");
754 let actual_results: Vec<BTreeMap<_, _>> = results_iter
755 .map(|res| res.into_iter().map(|(k, v)| (k.to_string(), v)).collect())
756 .collect();
757
758 let expected_result: FieldValue =
759 vec!["pub_use_handling", "inner", "CheckPubUseHandling"].into();
760 assert_eq!(1, actual_results.len(), "{actual_results:?}");
761 assert_eq!(
762 expected_result, actual_results[0]["canonical_path"],
763 "{actual_results:?}"
764 );
765
766 let mut actual_paths = actual_results[0]["path"]
767 .as_vec_with(|val| val.as_vec_with(FieldValue::as_str))
768 .expect("not a Vec<Vec<&str>>");
769 actual_paths.sort_unstable();
770
771 let expected_paths = vec![
772 vec!["pub_use_handling", "CheckPubUseHandling"],
773 vec!["pub_use_handling", "inner", "CheckPubUseHandling"],
774 ];
775 assert_eq!(expected_paths, actual_paths);
776 }
777
778 type TestOutput = BTreeMap<String, Vec<BTreeMap<String, FieldValue>>>;
779
780 #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
781 #[non_exhaustive]
782 struct WitnessOutput {
783 filename: String,
784 begin_line: usize,
785 hint: String,
786 }
787
788 impl PartialOrd for WitnessOutput {
789 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
790 Some(self.cmp(other))
791 }
792 }
793
794 impl Ord for WitnessOutput {
796 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
797 (&self.filename, self.begin_line).cmp(&(&other.filename, other.begin_line))
798 }
799 }
800
801 fn pretty_format_output_difference(
802 query_name: &str,
803 output_name1: &'static str,
804 output1: TestOutput,
805 output_name2: &'static str,
806 output2: TestOutput,
807 ) -> String {
808 let output_ron1 =
809 ron::ser::to_string_pretty(&output1, ron::ser::PrettyConfig::default()).unwrap();
810 let output_ron2 =
811 ron::ser::to_string_pretty(&output2, ron::ser::PrettyConfig::default()).unwrap();
812 let diff = similar_asserts::SimpleDiff::from_str(
813 &output_ron1,
814 &output_ron2,
815 output_name1,
816 output_name2,
817 );
818 [
819 format!("Query {query_name} produced incorrect output (./src/lints/{query_name}.ron)."),
820 diff.to_string(),
821 "Remember that result output order matters, and remember to re-run \
822 ./scripts/regenerate_test_rustdocs.sh when needed."
823 .to_string(),
824 ]
825 .join("\n\n")
826 }
827
828 fn run_query_on_crate_pair(
829 semver_query: &SemverQuery,
830 parsed_query: Arc<IndexedQuery>, crate_pair_name: &String,
832 indexed_crate_new: &VersionedIndex<'_>,
833 indexed_crate_old: &VersionedIndex<'_>,
834 ) -> (String, Vec<BTreeMap<String, FieldValue>>) {
835 let adapter = VersionedRustdocAdapter::new(indexed_crate_new, Some(indexed_crate_old))
836 .expect("could not create adapter");
837
838 let results_iter = adapter
839 .run_query_with_indexed_query(parsed_query.clone(), semver_query.arguments.clone())
840 .unwrap();
841
842 let fold_keys_and_targets: BTreeMap<&str, Vec<Arc<str>>> = parsed_query
849 .outputs
850 .iter()
851 .filter_map(|(name, output)| {
852 if name.as_ref().ends_with("_begin_line") && output.value_type.is_list() {
853 if let Some(fold) = parsed_query
854 .ir_query
855 .root_component
856 .folds
857 .values()
858 .find(|fold| fold.component.root == parsed_query.vids[&output.vid].root)
859 {
860 let targets = parsed_query
861 .outputs
862 .values()
863 .filter_map(|o| {
864 fold.component
865 .vertices
866 .contains_key(&o.vid)
867 .then_some(Arc::clone(&o.name))
868 })
869 .collect();
870 Some((name.as_ref(), targets))
871 } else {
872 None
873 }
874 } else {
875 None
876 }
877 })
878 .collect();
879
880 let results = results_iter
881 .map(move |mut res| {
882 for (fold_key, targets) in &fold_keys_and_targets {
884 let mut data: Vec<(u64, usize)> = res[*fold_key]
885 .as_vec_with(FieldValue::as_u64)
886 .expect("fold key was not a list of u64")
887 .into_iter()
888 .enumerate()
889 .map(|(idx, val)| (val, idx))
890 .collect();
891 data.sort_unstable();
892 for target in targets {
893 res.entry(Arc::clone(target)).and_modify(|value| {
894 if let Some(slice) = value.as_slice() {
897 let new_order = data
898 .iter()
899 .map(|(_, idx)| slice[*idx].clone())
900 .collect::<Vec<_>>()
901 .into();
902 *value = new_order;
903 }
904 });
905 }
906 }
907
908 res.into_iter().map(|(k, v)| (k.to_string(), v)).collect()
910 })
911 .collect::<Vec<BTreeMap<_, _>>>();
912 (format!("./test_crates/{crate_pair_name}/"), results)
913 }
914
915 fn assert_no_false_positives_in_nonchanged_crate(
916 query_name: &str,
917 semver_query: &SemverQuery,
918 indexed_query: Arc<IndexedQuery>, indexed_crate: &VersionedIndex<'_>,
920 crate_pair_name: &String,
921 crate_version: &str,
922 ) {
923 let (crate_pair_path, output) = run_query_on_crate_pair(
924 semver_query,
925 indexed_query,
926 crate_pair_name,
927 indexed_crate,
928 indexed_crate,
929 );
930 if !output.is_empty() {
931 let actual_output_name = Box::leak(Box::new(format!(
936 "actual ({crate_pair_name}/{crate_version})"
937 )));
938 let output_difference = pretty_format_output_difference(
939 query_name,
940 "expected (empty)",
941 BTreeMap::new(),
942 actual_output_name,
943 BTreeMap::from([(crate_pair_path, output)]),
944 );
945 panic!(
946 "The query produced a non-empty output when it compared two crates with the same rustdoc.\n{output_difference}\n"
947 );
948 }
949 }
950
951 pub(in crate::query) fn check_query_execution(query_name: &str) {
952 let query_text = std::fs::read_to_string(format!("./src/lints/{query_name}.ron")).unwrap();
953 let semver_query = SemverQuery::from_ron_str(&query_text).unwrap();
954
955 let mut parsed_query_cache: HashMap<u32, Arc<IndexedQuery>> = HashMap::new();
957
958 let mut query_execution_results: TestOutput = get_test_crate_names()
959 .iter()
960 .map(|crate_pair_name| {
961 let (baseline, current) = get_test_crate_indexes(crate_pair_name);
962
963 let adapter = VersionedRustdocAdapter::new(current, Some(baseline))
964 .expect("could not create adapter");
965
966 let indexed_query =
967 parsed_query_cache
968 .entry(adapter.version())
969 .or_insert_with(|| {
970 trustfall_core::frontend::parse(adapter.schema(), &semver_query.query)
971 .expect("Query failed to parse.")
972 });
973
974 assert_no_false_positives_in_nonchanged_crate(
975 query_name,
976 &semver_query,
977 indexed_query.clone(),
978 current,
979 crate_pair_name,
980 "new",
981 );
982 assert_no_false_positives_in_nonchanged_crate(
983 query_name,
984 &semver_query,
985 indexed_query.clone(),
986 baseline,
987 crate_pair_name,
988 "old",
989 );
990
991 run_query_on_crate_pair(
992 &semver_query,
993 indexed_query.clone(),
994 crate_pair_name,
995 current,
996 baseline,
997 )
998 })
999 .filter(|(_crate_pair_name, output)| !output.is_empty())
1000 .collect();
1001
1002 #[derive(Clone, Eq, PartialEq, Ord, PartialOrd)]
1005 enum SortKey {
1006 Span(Arc<str>, usize),
1007 Explicit(Vec<Arc<str>>),
1008 }
1009
1010 let key_func = |elem: &BTreeMap<String, FieldValue>| {
1011 if elem.contains_key("ordering_key") {
1020 let mut ordering_key_names: Vec<_> = elem
1021 .keys()
1022 .filter(|key| key.starts_with("ordering_key"))
1023 .collect();
1024 ordering_key_names.sort_unstable();
1025 let ordering_keys = ordering_key_names
1026 .into_iter()
1027 .map(|key| {
1028 let value = elem
1029 .get(key)
1030 .unwrap_or_else(|| panic!("{key} output missing from result"));
1031 Arc::clone(
1032 value
1033 .as_arc_str()
1034 .expect("ordering_key output was not a string"),
1035 )
1036 })
1037 .collect();
1038 SortKey::Explicit(ordering_keys)
1039 } else {
1040 let filename = elem.get("span_filename").map(|value| {
1041 value
1042 .as_arc_str()
1043 .expect("`span_filename` was not a string")
1044 });
1045 let line = elem
1046 .get("span_begin_line")
1047 .map(|value: &FieldValue| value.as_usize().expect("begin line was not an int"));
1048 match (filename, line) {
1049 (Some(filename), Some(line)) => SortKey::Span(Arc::clone(filename), line),
1050 (Some(_filename), None) => panic!(
1051 "No `span_begin_line` was returned by the query, even though `span_filename` was present. A valid query must either output an explicit `ordering_key`, or output both `span_filename` and `span_begin_line`. See https://github.com/obi1kenobi/cargo-semver-checks/blob/main/CONTRIBUTING.md for details."
1052 ),
1053 (None, Some(_line)) => panic!(
1054 "No `span_filename` was returned by the query, even though `span_begin_line` was present. A valid query must either output an explicit `ordering_key`, or output both `span_filename` and `span_begin_line`. See https://github.com/obi1kenobi/cargo-semver-checks/blob/main/CONTRIBUTING.md for details."
1055 ),
1056 (None, None) => panic!(
1057 "A valid query must either output an explicit `ordering_key`, or output both `span_filename` and `span_begin_line`. See https://github.com/obi1kenobi/cargo-semver-checks/blob/main/CONTRIBUTING.md for details."
1058 ),
1059 }
1060 }
1061 };
1062 for value in query_execution_results.values_mut() {
1063 value.sort_unstable_by_key(key_func);
1064 }
1065
1066 insta::with_settings!(
1067 {
1068 prepend_module_to_snapshot => false,
1069 snapshot_path => "../test_outputs/query_execution",
1070 omit_expression => true,
1071 },
1072 {
1073 insta::assert_ron_snapshot!(query_name, &query_execution_results);
1074 }
1075 );
1076
1077 let transparent_results: BTreeMap<_, Vec<BTreeMap<_, TransparentValue>>> =
1078 query_execution_results
1079 .into_iter()
1080 .map(|(k, v)| {
1081 (
1082 k,
1083 v.into_iter()
1084 .map(|x| x.into_iter().map(|(k, v)| (k, v.into())).collect())
1085 .collect(),
1086 )
1087 })
1088 .collect();
1089
1090 let registry = make_handlebars_registry();
1091 if let Some(template) = semver_query.per_result_error_template {
1092 assert!(!transparent_results.is_empty());
1093
1094 let flattened_actual_results: Vec<_> = transparent_results
1095 .iter()
1096 .flat_map(|(_key, value)| value)
1097 .collect();
1098 for semver_violation_result in flattened_actual_results {
1099 registry
1100 .render_template(&template, semver_violation_result)
1101 .with_context(|| "Error instantiating semver query template.")
1102 .expect("could not materialize template");
1103 }
1104 }
1105
1106 if let Some(witness) = semver_query.witness {
1107 let actual_witnesses: BTreeMap<_, BTreeSet<_>> = transparent_results
1108 .iter()
1109 .map(|(k, v)| {
1110 (
1111 Cow::Borrowed(k.as_str()),
1112 v.iter()
1113 .map(|values| {
1114 let Some(TransparentValue::String(filename)) = values.get("span_filename") else {
1115 unreachable!("Missing span_filename String, this should be validated above")
1116 };
1117 let begin_line = match values.get("span_begin_line") {
1118 Some(TransparentValue::Int64(i)) => *i as usize,
1119 Some(TransparentValue::Uint64(n)) => *n as usize,
1120 _ => unreachable!("Missing span_begin_line Int, this should be validated above"),
1121 };
1122
1123 WitnessOutput {
1125 filename: filename.to_string(),
1126 begin_line,
1127 hint: registry
1128 .render_template(&witness.hint_template, values)
1129 .expect("error rendering hint template"),
1130 }
1131 })
1132 .collect(),
1133 )
1134 })
1135 .collect();
1136
1137 insta::with_settings!(
1138 {
1139 prepend_module_to_snapshot => false,
1140 snapshot_path => "../test_outputs/witnesses",
1141 omit_expression => true,
1142 description => format!(
1143 "Lint `{query_name}` did not have the expected witness output.\n\
1144 See https://github.com/obi1kenobi/cargo-semver-checks/blob/main/CONTRIBUTING.md#testing-witnesses\n\
1145 for more information."
1146 ),
1147 },
1148 {
1149 let formatted_witnesses = toml::to_string_pretty(&actual_witnesses)
1150 .expect("failed to serialize witness snapshots as TOML");
1151 insta::assert_snapshot!(query_name, formatted_witnesses);
1152 }
1153 );
1154 }
1155 }
1156
1157 #[must_use]
1160 fn make_blank_query(
1161 id: String,
1162 lint_level: LintLevel,
1163 required_update: RequiredSemverUpdate,
1164 ) -> SemverQuery {
1165 SemverQuery {
1166 id,
1167 lint_level,
1168 required_update,
1169 human_readable_name: String::new(),
1170 description: String::new(),
1171 reference: None,
1172 reference_link: None,
1173 query: String::new(),
1174 arguments: BTreeMap::new(),
1175 error_message: String::new(),
1176 per_result_error_template: None,
1177 witness: None,
1178 }
1179 }
1180
1181 #[test]
1182 fn test_overrides() {
1183 let mut stack = OverrideStack::new();
1184 stack.push(&OverrideMap::from_iter([
1185 (
1186 "query1".into(),
1187 QueryOverride {
1188 lint_level: Some(LintLevel::Allow),
1189 required_update: Some(RequiredSemverUpdate::Minor),
1190 },
1191 ),
1192 (
1193 "query2".into(),
1194 QueryOverride {
1195 lint_level: None,
1196 required_update: Some(RequiredSemverUpdate::Minor),
1197 },
1198 ),
1199 ]));
1200
1201 let q1 = make_blank_query(
1202 "query1".into(),
1203 LintLevel::Deny,
1204 RequiredSemverUpdate::Major,
1205 );
1206 let q2 = make_blank_query(
1207 "query2".into(),
1208 LintLevel::Warn,
1209 RequiredSemverUpdate::Major,
1210 );
1211
1212 assert_eq!(stack.effective_lint_level(&q1), LintLevel::Allow);
1214 assert_eq!(
1215 stack.effective_required_update(&q1),
1216 RequiredSemverUpdate::Minor
1217 );
1218
1219 assert_eq!(stack.effective_lint_level(&q2), LintLevel::Warn);
1222 assert_eq!(
1223 stack.effective_required_update(&q2),
1224 RequiredSemverUpdate::Minor
1225 );
1226 }
1227
1228 #[test]
1229 fn test_override_precedence() {
1230 let mut stack = OverrideStack::new();
1231 stack.push(&OverrideMap::from_iter([
1232 (
1233 "query1".into(),
1234 QueryOverride {
1235 lint_level: Some(LintLevel::Allow),
1236 required_update: Some(RequiredSemverUpdate::Minor),
1237 },
1238 ),
1239 (
1240 ("query2".into()),
1241 QueryOverride {
1242 lint_level: None,
1243 required_update: Some(RequiredSemverUpdate::Minor),
1244 },
1245 ),
1246 ]));
1247
1248 stack.push(&OverrideMap::from_iter([(
1249 "query1".into(),
1250 QueryOverride {
1251 required_update: None,
1252 lint_level: Some(LintLevel::Warn),
1253 },
1254 )]));
1255
1256 let q1 = make_blank_query(
1257 "query1".into(),
1258 LintLevel::Deny,
1259 RequiredSemverUpdate::Major,
1260 );
1261 let q2 = make_blank_query(
1262 "query2".into(),
1263 LintLevel::Warn,
1264 RequiredSemverUpdate::Major,
1265 );
1266
1267 assert_eq!(stack.effective_lint_level(&q1), LintLevel::Warn);
1269 assert_eq!(
1272 stack.effective_required_update(&q1),
1273 RequiredSemverUpdate::Minor
1274 );
1275
1276 assert_eq!(stack.effective_lint_level(&q2), LintLevel::Warn);
1279 assert_eq!(
1280 stack.effective_required_update(&q2),
1281 RequiredSemverUpdate::Minor
1282 );
1283 }
1284
1285 #[test]
1288 fn test_inherited_value_deserialization() {
1289 let my_map: BTreeMap<String, InheritedValue> = ron::from_str(
1290 r#"{
1291 "abc": (inherit: "abc"),
1292 "string": "literal_string",
1293 "int": -30,
1294 "int_list": [-30, -2],
1295 "string_list": ["abc", "123"],
1296 }"#,
1297 )
1298 .expect("deserialization failed");
1299
1300 let Some(InheritedValue::Inherited { inherit: abc }) = my_map.get("abc") else {
1301 panic!("Expected Inherited, got {:?}", my_map.get("abc"));
1302 };
1303
1304 assert_eq!(abc, "abc");
1305
1306 let Some(InheritedValue::Constant(TransparentValue::String(string))) = my_map.get("string")
1307 else {
1308 panic!("Expected Constant(String), got {:?}", my_map.get("string"));
1309 };
1310
1311 assert_eq!(&**string, "literal_string");
1312
1313 let Some(InheritedValue::Constant(TransparentValue::Int64(int))) = my_map.get("int") else {
1314 panic!("Expected Constant(Int64), got {:?}", my_map.get("int"));
1315 };
1316
1317 assert_eq!(*int, -30);
1318
1319 let Some(InheritedValue::Constant(TransparentValue::List(ints))) = my_map.get("int_list")
1320 else {
1321 panic!("Expected Constant(List), got {:?}", my_map.get("lint_list"));
1322 };
1323
1324 let Some(TransparentValue::Int64(-30)) = ints.first() else {
1325 panic!("Expected Int64(-30), got {:?}", ints.first());
1326 };
1327
1328 let Some(TransparentValue::Int64(-2)) = ints.get(1) else {
1329 panic!("Expected Int64(-30), got {:?}", ints.get(1));
1330 };
1331
1332 let Some(InheritedValue::Constant(TransparentValue::List(strs))) =
1333 my_map.get("string_list")
1334 else {
1335 panic!(
1336 "Expected Constant(List), got {:?}",
1337 my_map.get("string_list")
1338 );
1339 };
1340
1341 let Some(TransparentValue::String(s)) = strs.first() else {
1342 panic!("Expected String, got {:?}", strs.first());
1343 };
1344
1345 assert_eq!(&**s, "abc");
1346
1347 let Some(TransparentValue::String(s)) = strs.get(1) else {
1348 panic!("Expected String, got {:?}", strs.get(1));
1349 };
1350
1351 assert_eq!(&**s, "123");
1352
1353 ron::from_str::<InheritedValue>(r#"[(inherit: "invalid")]"#)
1354 .expect_err("nested values should be TransparentValues, not InheritedValues");
1355 }
1356
1357 pub(super) fn check_all_lint_files_are_used_in_add_lints(added_lints: &[&str]) {
1358 let mut lints_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1359 lints_dir.push("src");
1360 lints_dir.push("lints");
1361
1362 let expected_lints: BTreeSet<_> = added_lints.iter().copied().collect();
1363 let mut missing_lints: BTreeSet<String> = Default::default();
1364
1365 let dir_contents =
1366 fs_err::read_dir(lints_dir).expect("failed to read 'src/lints' directory");
1367 for file in dir_contents {
1368 let file = file.expect("failed to examine file");
1369 let path = file.path();
1370
1371 if path.extension().map(|x| x.to_string_lossy()) == Some(Cow::Borrowed("ron")) {
1373 let stem = path
1374 .file_stem()
1375 .map(|x| x.to_string_lossy())
1376 .expect("failed to get file name as utf-8");
1377
1378 if !expected_lints.contains(stem.as_ref()) {
1381 missing_lints.insert(stem.to_string());
1382 }
1383 }
1384 }
1385
1386 assert!(
1387 missing_lints.is_empty(),
1388 "some lints in 'src/lints/' haven't been registered using the `add_lints!()` macro, \
1389 so they won't be part of cargo-semver-checks: {missing_lints:?}"
1390 )
1391 }
1392
1393 #[test]
1394 fn lint_file_names_and_ids_match() {
1395 let mut lints_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1396 lints_dir.push("src");
1397 lints_dir.push("lints");
1398
1399 for entry in fs_err::read_dir(&lints_dir).expect("failed to read 'src/lints' directory") {
1400 let entry = entry.expect("failed to examine file");
1401 let path = entry.path();
1402
1403 if path.extension().and_then(OsStr::to_str) != Some("ron") {
1404 continue;
1405 }
1406
1407 let stem = path
1408 .file_stem()
1409 .and_then(OsStr::to_str)
1410 .expect("failed to get file name as utf-8");
1411
1412 assert!(
1413 stem.chars().all(|ch| ch.is_ascii_lowercase() || ch == '_'),
1414 "lint file name '{stem}' is not snake_case"
1415 );
1416 assert!(
1417 !stem.starts_with('_'),
1418 "lint file name '{stem}' must not start with '_'"
1419 );
1420 assert!(
1421 !stem.ends_with('_'),
1422 "lint file name '{stem}' must not end with '_'"
1423 );
1424 assert!(
1425 !stem.contains("__"),
1426 "lint file name '{stem}' must not contain '__'"
1427 );
1428
1429 let query_text =
1430 fs_err::read_to_string(&path).expect("failed to read lint definition file");
1431 let semver_query =
1432 SemverQuery::from_ron_str(&query_text).expect("failed to parse lint definition");
1433
1434 assert_eq!(
1435 stem,
1436 semver_query.id,
1437 "lint id does not match file name for {}",
1438 path.display()
1439 );
1440 }
1441 }
1442
1443 #[test]
1444 fn test_data_is_fresh() -> anyhow::Result<()> {
1445 fn recursive_file_times<P: Into<PathBuf>>(
1448 dir: P,
1449 set: &mut BTreeSet<SystemTime>,
1450 ) -> std::io::Result<()> {
1451 for item in fs_err::read_dir(dir)? {
1452 let item = item?;
1453 let metadata = item.metadata()?;
1454 if metadata.is_dir() {
1455 if item.file_name() == "target" {
1457 continue;
1458 }
1459 recursive_file_times(item.path(), set)?;
1460 } else if let Some("rs" | "toml" | "json") =
1461 item.path().extension().and_then(OsStr::to_str)
1462 {
1463 set.insert(metadata.modified()?);
1464 }
1465 }
1466
1467 Ok(())
1468 }
1469
1470 let test_crate_dir = Path::new("test_crates");
1471 let localdata_dir = Path::new("localdata").join("test_data");
1472
1473 if !localdata_dir.fs_err_try_exists()? {
1474 panic!(
1475 "The localdata directory '{}' does not exist yet.\n\
1476 Please run `scripts/regenerate_test_rustdocs.sh`.",
1477 localdata_dir.display()
1478 );
1479 }
1480
1481 for test_crate in fs_err::read_dir(test_crate_dir)? {
1482 let test_crate = test_crate?;
1483
1484 if !test_crate.metadata()?.is_dir() {
1485 continue;
1486 }
1487
1488 if !test_crate
1489 .path()
1490 .join("new")
1491 .join("Cargo.toml")
1492 .fs_err_try_exists()?
1493 || !test_crate
1494 .path()
1495 .join("old")
1496 .join("Cargo.toml")
1497 .fs_err_try_exists()?
1498 {
1499 continue;
1500 }
1501
1502 for version in ["new", "old"] {
1503 let test_crate_path = test_crate.path().join(version);
1504
1505 let mut test_crate_times = BTreeSet::new();
1506 recursive_file_times(test_crate_path.clone(), &mut test_crate_times)?;
1507
1508 let localdata_path = localdata_dir.join(test_crate.file_name()).join(version);
1509 let mut localdata_times = BTreeSet::new();
1510
1511 recursive_file_times(localdata_path.clone(), &mut localdata_times).context(
1512 "If this directory doesn't exist, run `scripts/regenerate_test_rustdocs.sh`",
1513 )?;
1514
1515 if let (Some(test_max), Some(local_min)) =
1518 (test_crate_times.last(), localdata_times.first())
1519 && test_max > local_min
1520 {
1521 panic!(
1522 "Files in the '{}' directory are newer than the local data generated by \n\
1523 scripts/regenerate_test_rustdocs.sh in '{}'.\n\n\
1524 Run `scripts/regenerate_test_rustdocs.sh` to generate fresh local data.",
1525 test_crate_path.display(),
1526 localdata_path.display()
1527 )
1528 }
1529 }
1530 }
1531
1532 Ok(())
1533 }
1534}
1535
1536#[cfg(test)]
1537macro_rules! lint_test {
1538 ($name:ident) => {
1540 #[test]
1541 fn $name() {
1542 super::tests::check_query_execution(stringify!($name))
1543 }
1544 };
1545 (($name:ident, $conf_pred:meta)) => {
1548 #[test]
1549 #[cfg_attr(not($conf_pred), ignore)]
1550 fn $name() {
1551 super::tests::check_query_execution(stringify!($name))
1552 }
1553 };
1554}
1555
1556macro_rules! lint_name {
1557 ($name:ident) => {
1558 stringify!($name)
1559 };
1560 (($name:ident, $conf_pred:meta)) => {
1561 stringify!($name)
1562 };
1563}
1564
1565macro_rules! add_lints {
1566 ($($args:tt,)+) => {
1567 #[cfg(test)]
1568 mod tests_lints {
1569 $(
1570 lint_test!($args);
1571 )*
1572
1573 #[test]
1574 fn all_lint_files_are_used_in_add_lints() {
1575 let added_lints = [
1576 $(
1577 lint_name!($args),
1578 )*
1579 ];
1580
1581 super::tests::check_all_lint_files_are_used_in_add_lints(&added_lints);
1582 }
1583 }
1584
1585 fn get_queries() -> Vec<(&'static str, &'static str)> {
1586 vec![
1587 $(
1588 (
1589 lint_name!($args),
1590 include_str!(concat!("lints/", lint_name!($args), ".ron")),
1591 ),
1592 )*
1593 ]
1594 }
1595 };
1596 ($($args:tt),*) => {
1597 compile_error!("Please add a trailing comma after each lint identifier. This ensures our scripts like 'make_new_lint.sh' can safely edit invocations of this macro as needed.");
1598 }
1599}
1600
1601#[rustfmt::skip] add_lints!(
1605 (exported_function_requires_more_target_features, any(target_arch = "x86", target_arch = "x86_64")),
1606 (exported_function_target_feature_added, any(target_arch = "x86", target_arch = "x86_64")),
1607 (safe_function_requires_more_target_features, any(target_arch = "x86", target_arch = "x86_64")),
1608 (safe_function_target_feature_added, any(target_arch = "x86", target_arch = "x86_64")),
1609 (safe_inherent_method_requires_more_target_features, any(target_arch = "x86", target_arch = "x86_64")),
1610 (safe_inherent_method_target_feature_added, any(target_arch = "x86", target_arch = "x86_64")),
1611 (trait_method_target_feature_removed, any(target_arch = "x86", target_arch = "x86_64")),
1612 (unsafe_function_requires_more_target_features, any(target_arch = "x86", target_arch = "x86_64")),
1613 (unsafe_function_target_feature_added, any(target_arch = "x86", target_arch = "x86_64")),
1614 (unsafe_inherent_method_requires_more_target_features, any(target_arch = "x86", target_arch = "x86_64")),
1615 (unsafe_inherent_method_target_feature_added, any(target_arch = "x86", target_arch = "x86_64")),
1616 (unsafe_trait_method_requires_more_target_features, any(target_arch = "x86", target_arch = "x86_64")),
1617 (unsafe_trait_method_target_feature_added, any(target_arch = "x86", target_arch = "x86_64")),
1618 attribute_proc_macro_missing,
1619 auto_trait_impl_removed,
1620 constructible_struct_adds_field,
1621 constructible_struct_adds_private_field,
1622 constructible_struct_changed_type,
1623 copy_impl_added,
1624 declarative_macro_missing,
1625 derive_helper_attr_removed,
1626 derive_proc_macro_missing,
1627 derive_trait_impl_removed,
1628 enum_changed_kind,
1629 enum_discriminants_undefined_non_exhaustive_variant,
1630 enum_discriminants_undefined_non_unit_variant,
1631 enum_marked_non_exhaustive,
1632 enum_missing,
1633 enum_must_use_added,
1634 enum_must_use_removed,
1635 enum_no_longer_non_exhaustive,
1636 enum_no_repr_variant_discriminant_changed,
1637 enum_non_exhaustive_struct_variant_field_added,
1638 enum_non_exhaustive_tuple_variant_changed_kind,
1639 enum_non_exhaustive_tuple_variant_field_added,
1640 enum_now_doc_hidden,
1641 enum_repr_int_added,
1642 enum_repr_int_changed,
1643 enum_repr_int_removed,
1644 enum_repr_transparent_removed,
1645 enum_repr_variant_discriminant_changed,
1646 enum_struct_variant_changed_kind,
1647 enum_struct_variant_field_added,
1648 enum_struct_variant_field_marked_deprecated,
1649 enum_struct_variant_field_missing,
1650 enum_struct_variant_field_now_doc_hidden,
1651 enum_tuple_variant_changed_kind,
1652 enum_tuple_variant_field_added,
1653 enum_tuple_variant_field_marked_deprecated,
1654 enum_tuple_variant_field_missing,
1655 enum_tuple_variant_field_now_doc_hidden,
1656 enum_unit_variant_changed_kind,
1657 enum_variant_added,
1658 enum_variant_marked_deprecated,
1659 enum_variant_marked_non_exhaustive,
1660 enum_variant_missing,
1661 enum_variant_no_longer_non_exhaustive,
1662 exhaustive_enum_added,
1663 exhaustive_struct_added,
1664 exhaustive_struct_with_doc_hidden_fields_added,
1665 exhaustive_struct_with_private_fields_added,
1666 exported_function_abi_no_longer_unwind,
1667 exported_function_abi_now_unwind,
1668 exported_function_changed_abi,
1669 exported_function_now_returns_unit,
1670 exported_function_parameter_count_changed,
1671 exported_function_return_value_added,
1672 feature_missing,
1673 feature_newly_enables_feature,
1674 feature_no_longer_enables_feature,
1675 feature_not_enabled_by_default,
1676 function_abi_no_longer_unwind,
1677 function_abi_now_unwind,
1678 function_changed_abi,
1679 function_const_generic_reordered,
1680 function_const_removed,
1681 function_export_name_changed,
1682 function_generic_type_reordered,
1683 function_like_proc_macro_missing,
1684 function_marked_deprecated,
1685 function_missing,
1686 function_must_use_added,
1687 function_must_use_removed,
1688 function_no_longer_unsafe,
1689 function_now_const,
1690 function_now_doc_hidden,
1691 function_now_returns_unit,
1692 function_parameter_count_changed,
1693 function_requires_different_const_generic_params,
1694 function_requires_different_generic_type_params,
1695 function_unsafe_added,
1696 global_value_marked_deprecated,
1697 inherent_associated_const_now_doc_hidden,
1698 inherent_associated_pub_const_added,
1699 inherent_associated_pub_const_missing,
1700 inherent_method_added,
1701 inherent_method_changed_abi,
1702 inherent_method_const_generic_reordered,
1703 inherent_method_const_removed,
1704 inherent_method_generic_type_reordered,
1705 inherent_method_missing,
1706 inherent_method_must_use_added,
1707 inherent_method_must_use_removed,
1708 inherent_method_no_longer_unsafe,
1709 inherent_method_no_longer_unwind,
1710 inherent_method_now_const,
1711 inherent_method_now_doc_hidden,
1712 inherent_method_now_returns_unit,
1713 inherent_method_now_unwind,
1714 inherent_method_unsafe_added,
1715 macro_marked_deprecated,
1716 macro_no_longer_exported,
1717 macro_now_doc_hidden,
1718 method_export_name_changed,
1719 method_no_longer_has_receiver,
1720 method_parameter_count_changed,
1721 method_receiver_mut_ref_became_owned,
1722 method_receiver_ref_became_mut,
1723 method_receiver_ref_became_owned,
1724 method_receiver_type_changed,
1725 method_requires_different_const_generic_params,
1726 method_requires_different_generic_type_params,
1727 module_missing,
1728 non_exhaustive_enum_added,
1729 non_exhaustive_struct_added,
1730 non_exhaustive_struct_changed_type,
1731 partial_ord_enum_struct_variant_fields_reordered,
1732 partial_ord_enum_variants_reordered,
1733 partial_ord_struct_fields_reordered,
1734 proc_macro_marked_deprecated,
1735 proc_macro_now_doc_hidden,
1736 pub_api_sealed_trait_became_unconditionally_sealed,
1737 pub_api_sealed_trait_became_unsealed,
1738 pub_api_sealed_trait_method_receiver_added,
1739 pub_api_sealed_trait_method_receiver_mut_ref_became_ref,
1740 pub_api_sealed_trait_method_return_value_added,
1741 pub_api_sealed_trait_method_target_feature_removed,
1742 pub_const_added,
1743 pub_module_level_const_missing,
1744 pub_module_level_const_now_doc_hidden,
1745 pub_static_added,
1746 pub_static_missing,
1747 pub_static_mut_now_immutable,
1748 pub_static_now_doc_hidden,
1749 pub_static_now_mutable,
1750 repr_align_added,
1751 repr_align_changed,
1752 repr_align_removed,
1753 repr_c_added,
1754 repr_c_enum_struct_variant_fields_reordered,
1755 repr_c_plain_struct_fields_reordered,
1756 repr_c_removed,
1757 repr_packed_added,
1758 repr_packed_changed,
1759 repr_packed_removed,
1760 repr_transparent_added,
1761 sized_impl_removed,
1762 static_became_unsafe,
1763 struct_field_marked_deprecated,
1764 struct_marked_non_exhaustive,
1765 struct_missing,
1766 struct_must_use_added,
1767 struct_must_use_removed,
1768 struct_no_longer_has_non_pub_fields,
1769 struct_no_longer_non_exhaustive,
1770 struct_now_doc_hidden,
1771 struct_pub_field_missing,
1772 struct_pub_field_now_doc_hidden,
1773 struct_repr_transparent_removed,
1774 struct_with_no_pub_fields_changed_type,
1775 struct_with_pub_fields_changed_type,
1776 trait_added_supertrait,
1777 trait_allows_fewer_const_generic_params,
1778 trait_allows_fewer_generic_type_params,
1779 trait_associated_const_added,
1780 trait_associated_const_default_removed,
1781 trait_associated_const_marked_deprecated,
1782 trait_associated_const_now_doc_hidden,
1783 trait_associated_type_added,
1784 trait_associated_type_default_removed,
1785 trait_associated_type_marked_deprecated,
1786 trait_associated_type_now_doc_hidden,
1787 trait_changed_kind,
1788 trait_const_generic_reordered,
1789 trait_generic_type_reordered,
1790 trait_marked_deprecated,
1791 trait_method_added,
1792 trait_method_changed_abi,
1793 trait_method_const_generic_reordered,
1794 trait_method_default_impl_removed,
1795 trait_method_generic_type_reordered,
1796 trait_method_marked_deprecated,
1797 trait_method_missing,
1798 trait_method_no_longer_has_receiver,
1799 trait_method_no_longer_unwind,
1800 trait_method_now_doc_hidden,
1801 trait_method_now_returns_unit,
1802 trait_method_now_unwind,
1803 trait_method_parameter_count_changed,
1804 trait_method_receiver_added,
1805 trait_method_receiver_mut_ref_became_owned,
1806 trait_method_receiver_mut_ref_became_ref,
1807 trait_method_receiver_owned_became_mut_ref,
1808 trait_method_receiver_owned_became_ref,
1809 trait_method_receiver_ref_became_mut,
1810 trait_method_receiver_ref_became_owned,
1811 trait_method_receiver_type_changed,
1812 trait_method_requires_different_const_generic_params,
1813 trait_method_requires_different_generic_type_params,
1814 trait_method_return_value_added,
1815 trait_method_unsafe_added,
1816 trait_method_unsafe_removed,
1817 trait_mismatched_generic_lifetimes,
1818 trait_missing,
1819 trait_must_use_added,
1820 trait_must_use_removed,
1821 trait_newly_sealed,
1822 trait_no_longer_dyn_compatible,
1823 trait_now_doc_hidden,
1824 trait_removed_associated_constant,
1825 trait_removed_associated_type,
1826 trait_removed_supertrait,
1827 trait_requires_more_const_generic_params,
1828 trait_requires_more_generic_type_params,
1829 trait_unsafe_added,
1830 trait_unsafe_removed,
1831 tuple_struct_to_plain_struct,
1832 type_allows_fewer_const_generic_params,
1833 type_allows_fewer_generic_type_params,
1834 type_associated_const_marked_deprecated,
1835 type_const_generic_reordered,
1836 type_generic_type_reordered,
1837 type_marked_deprecated,
1838 type_method_marked_deprecated,
1839 type_mismatched_generic_lifetimes,
1840 type_requires_more_const_generic_params,
1841 type_requires_more_generic_type_params,
1842 unconditionally_sealed_trait_became_pub_api_sealed,
1843 unconditionally_sealed_trait_became_unsealed,
1844 union_added,
1845 union_changed_kind,
1846 union_changed_to_incompatible_struct,
1847 union_field_added_with_all_pub_fields,
1848 union_field_added_with_non_pub_fields,
1849 union_field_missing,
1850 union_missing,
1851 union_must_use_added,
1852 union_must_use_removed,
1853 union_now_doc_hidden,
1854 union_pub_field_now_doc_hidden,
1855 union_with_multiple_pub_fields_changed_to_struct,
1856 unit_struct_changed_kind,
1857);