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